decred.org/dcrdex@v1.0.5/server/auth/auth_test.go (about)

     1  // This code is available on the terms of the project LICENSE.md file,
     2  // also available online at https://blueoakcouncil.org/license/1.0.0.
     3  
     4  package auth
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"crypto/sha256"
    10  	"encoding/binary"
    11  	"encoding/json"
    12  	"fmt"
    13  	"math/rand"
    14  	"os"
    15  	"sync/atomic"
    16  	"testing"
    17  	"time"
    18  
    19  	"decred.org/dcrdex/dex"
    20  	"decred.org/dcrdex/dex/encode"
    21  	"decred.org/dcrdex/dex/msgjson"
    22  	"decred.org/dcrdex/dex/order"
    23  	ordertest "decred.org/dcrdex/dex/order/test"
    24  	"decred.org/dcrdex/server/account"
    25  	"decred.org/dcrdex/server/comms"
    26  	"decred.org/dcrdex/server/db"
    27  	"github.com/decred/dcrd/dcrec/secp256k1/v4"
    28  	"github.com/decred/dcrd/dcrec/secp256k1/v4/ecdsa"
    29  )
    30  
    31  func noop() {}
    32  
    33  func randBytes(l int) []byte {
    34  	b := make([]byte, l)
    35  	rand.Read(b)
    36  	return b
    37  }
    38  
    39  var randomMatchID = ordertest.RandomMatchID
    40  
    41  type ratioData struct {
    42  	oidsCompleted  []order.OrderID
    43  	timesCompleted []int64
    44  	oidsCancels    []order.OrderID
    45  	oidsCanceled   []order.OrderID
    46  	timesCanceled  []int64
    47  	epochGaps      []int32
    48  }
    49  
    50  // TStorage satisfies the Storage interface
    51  type TStorage struct {
    52  	acctInfo            *db.Account
    53  	acctInfoErr         error
    54  	acct                *account.Account
    55  	matches             []*db.MatchData
    56  	matchStatuses       []*db.MatchStatus
    57  	userPreimageResults []*db.PreimageResult
    58  	userMatchOutcomes   []*db.MatchOutcome
    59  	orderStatuses       []*db.OrderStatus
    60  	acctErr             error
    61  	regAddr             string
    62  	regAsset            uint32
    63  	regErr              error
    64  	payErr              error
    65  	bonds               []*db.Bond
    66  	ratio               ratioData
    67  }
    68  
    69  func (s *TStorage) AccountInfo(account.AccountID) (*db.Account, error) {
    70  	return s.acctInfo, s.acctInfoErr
    71  }
    72  func (s *TStorage) Account(acct account.AccountID, lockTimeThresh time.Time) (*account.Account, []*db.Bond) {
    73  	return s.acct, s.bonds
    74  }
    75  func (s *TStorage) setBondTier(tier uint32) {
    76  	s.bonds = []*db.Bond{{Strength: tier, LockTime: time.Now().Unix() * 2}}
    77  }
    78  func (s *TStorage) CreateAccountWithBond(acct *account.Account, bond *db.Bond) error { return nil }
    79  func (s *TStorage) AddBond(acct account.AccountID, bond *db.Bond) error              { return nil }
    80  func (s *TStorage) DeleteBond(assetID uint32, coinID []byte) error                   { return nil }
    81  func (s *TStorage) FetchPrepaidBond([]byte) (uint32, int64, error) {
    82  	return 1, time.Now().Add(time.Hour * 48).Unix(), nil
    83  }
    84  func (s *TStorage) DeletePrepaidBond(coinID []byte) (err error) { return nil }
    85  func (s *TStorage) StorePrepaidBonds(coinIDs [][]byte, strength uint32, lockTime int64) error {
    86  	return nil
    87  }
    88  func (s *TStorage) CompletedAndAtFaultMatchStats(aid account.AccountID, lastN int) ([]*db.MatchOutcome, error) {
    89  	return s.userMatchOutcomes, nil
    90  }
    91  func (s *TStorage) UserMatchFails(aid account.AccountID, lastN int) ([]*db.MatchFail, error) {
    92  	return nil, nil
    93  }
    94  func (s *TStorage) PreimageStats(user account.AccountID, lastN int) ([]*db.PreimageResult, error) {
    95  	return s.userPreimageResults, nil
    96  }
    97  func (s *TStorage) ForgiveMatchFail(mid order.MatchID) (bool, error) {
    98  	return false, nil
    99  }
   100  func (s *TStorage) UserOrderStatuses(aid account.AccountID, base, quote uint32, oids []order.OrderID) ([]*db.OrderStatus, error) {
   101  	return s.orderStatuses, nil
   102  }
   103  func (s *TStorage) ActiveUserOrderStatuses(aid account.AccountID) ([]*db.OrderStatus, error) {
   104  	var activeOrderStatuses []*db.OrderStatus
   105  	for _, orderStatus := range s.orderStatuses {
   106  		if orderStatus.Status == order.OrderStatusEpoch || orderStatus.Status == order.OrderStatusBooked {
   107  			activeOrderStatuses = append(activeOrderStatuses, orderStatus)
   108  		}
   109  	}
   110  	return activeOrderStatuses, nil
   111  }
   112  func (s *TStorage) AllActiveUserMatches(account.AccountID) ([]*db.MatchData, error) {
   113  	return s.matches, nil
   114  }
   115  func (s *TStorage) MatchStatuses(aid account.AccountID, base, quote uint32, matchIDs []order.MatchID) ([]*db.MatchStatus, error) {
   116  	return s.matchStatuses, nil
   117  }
   118  func (s *TStorage) CreateAccount(acct *account.Account, assetID uint32, addr string) error {
   119  	s.regAddr = addr
   120  	s.regAsset = assetID
   121  	return s.acctErr
   122  }
   123  func (s *TStorage) setRatioData(dat *ratioData) {
   124  	s.ratio = *dat
   125  }
   126  func (s *TStorage) CompletedUserOrders(aid account.AccountID, _ int) (oids []order.OrderID, compTimes []int64, err error) {
   127  	return s.ratio.oidsCompleted, s.ratio.timesCompleted, nil
   128  }
   129  func (s *TStorage) ExecutedCancelsForUser(aid account.AccountID, _ int) (cancels []*db.CancelRecord, err error) {
   130  	for i := range s.ratio.oidsCanceled {
   131  		cancels = append(cancels, &db.CancelRecord{
   132  			ID:        s.ratio.oidsCancels[i],
   133  			TargetID:  s.ratio.oidsCanceled[i],
   134  			MatchTime: s.ratio.timesCanceled[i],
   135  			EpochGap:  s.ratio.epochGaps[i],
   136  		})
   137  	}
   138  	return cancels, nil
   139  }
   140  
   141  // TSigner satisfies the Signer interface
   142  type TSigner struct {
   143  	sig *ecdsa.Signature
   144  	//privKey *secp256k1.PrivateKey
   145  	pubkey *secp256k1.PublicKey
   146  }
   147  
   148  // Maybe actually change this to an ecdsa.Sign with a private key instead?
   149  func (s *TSigner) Sign(hash []byte) *ecdsa.Signature { return s.sig }
   150  func (s *TSigner) PubKey() *secp256k1.PublicKey      { return s.pubkey }
   151  
   152  type tReq struct {
   153  	msg      *msgjson.Message
   154  	respFunc func(comms.Link, *msgjson.Message)
   155  }
   156  
   157  // tRPCClient satisfies the comms.Link interface.
   158  type TRPCClient struct {
   159  	id         uint64
   160  	ip         dex.IPKey
   161  	addr       string
   162  	sendErr    error
   163  	sendRawErr error
   164  	requestErr error
   165  	banished   bool
   166  	sends      []*msgjson.Message
   167  	reqs       []*tReq
   168  	on         uint32
   169  	closed     chan struct{}
   170  }
   171  
   172  func (c *TRPCClient) ID() uint64    { return c.id }
   173  func (c *TRPCClient) IP() dex.IPKey { return c.ip }
   174  func (c *TRPCClient) Addr() string  { return c.addr }
   175  func (c *TRPCClient) Authorized()   {}
   176  func (c *TRPCClient) Send(msg *msgjson.Message) error {
   177  	c.sends = append(c.sends, msg)
   178  	return c.sendErr
   179  }
   180  func (c *TRPCClient) SendRaw(b []byte) error {
   181  	if c.sendRawErr != nil {
   182  		return c.sendRawErr
   183  	}
   184  	msg, err := msgjson.DecodeMessage(b)
   185  	if err != nil {
   186  		return err
   187  	}
   188  	c.sends = append(c.sends, msg)
   189  	return nil
   190  }
   191  func (c *TRPCClient) SendError(id uint64, msg *msgjson.Error) {
   192  }
   193  func (c *TRPCClient) Request(msg *msgjson.Message, f func(comms.Link, *msgjson.Message), _ time.Duration, _ func()) error {
   194  	c.reqs = append(c.reqs, &tReq{
   195  		msg:      msg,
   196  		respFunc: f,
   197  	})
   198  	return c.requestErr
   199  }
   200  func (c *TRPCClient) RequestRaw(msgID uint64, rawMsg []byte, f func(comms.Link, *msgjson.Message), expireTime time.Duration, expire func()) error {
   201  	return nil
   202  }
   203  
   204  func (c *TRPCClient) Done() <-chan struct{} {
   205  	return c.closed
   206  }
   207  func (c *TRPCClient) Disconnect() {
   208  	if atomic.CompareAndSwapUint32(&c.on, 0, 1) {
   209  		close(c.closed)
   210  	}
   211  }
   212  func (c *TRPCClient) Banish() { c.banished = true }
   213  func (c *TRPCClient) getReq() *tReq {
   214  	if len(c.reqs) == 0 {
   215  		return nil
   216  	}
   217  	req := c.reqs[0]
   218  	c.reqs = c.reqs[1:]
   219  	return req
   220  }
   221  func (c *TRPCClient) getSend() *msgjson.Message {
   222  	if len(c.sends) == 0 {
   223  		return nil
   224  	}
   225  	msg := c.sends[0]
   226  	c.sends = c.sends[1:]
   227  	return msg
   228  }
   229  
   230  func (c *TRPCClient) CustomID() string {
   231  	return ""
   232  }
   233  
   234  func (c *TRPCClient) SetCustomID(string) {}
   235  
   236  var tClientID uint64
   237  
   238  func tNewRPCClient() *TRPCClient {
   239  	tClientID++
   240  	return &TRPCClient{
   241  		id:     tClientID,
   242  		ip:     dex.NewIPKey("123.123.123.123"),
   243  		addr:   "addr",
   244  		closed: make(chan struct{}),
   245  	}
   246  }
   247  
   248  var tAcctID uint64
   249  
   250  func newAccountID() account.AccountID {
   251  	tAcctID++
   252  	ib := make([]byte, 8)
   253  	binary.BigEndian.PutUint64(ib, tAcctID)
   254  	var acctID account.AccountID
   255  	copy(acctID[len(acctID)-8:], ib)
   256  	return acctID
   257  }
   258  
   259  type tUser struct {
   260  	conn    *TRPCClient
   261  	acctID  account.AccountID
   262  	privKey *secp256k1.PrivateKey
   263  }
   264  
   265  // makes a new user with its own account ID, tRPCClient
   266  func tNewUser(t *testing.T) *tUser {
   267  	t.Helper()
   268  	conn := tNewRPCClient()
   269  	privKey, err := secp256k1.GeneratePrivateKey()
   270  	if err != nil {
   271  		t.Fatalf("error generating private key: %v", err)
   272  	}
   273  	acctID := account.NewID(privKey.PubKey().SerializeCompressed())
   274  	return &tUser{
   275  		conn:    conn,
   276  		acctID:  acctID,
   277  		privKey: privKey,
   278  	}
   279  }
   280  
   281  func (u *tUser) randomSignature() *ecdsa.Signature {
   282  	return ecdsa.Sign(u.privKey, randBytes(32))
   283  }
   284  
   285  type testRig struct {
   286  	mgr     *AuthManager
   287  	storage *TStorage
   288  	signer  *TSigner
   289  }
   290  
   291  var rig *testRig
   292  
   293  type tSignable struct {
   294  	b   []byte
   295  	sig []byte
   296  }
   297  
   298  func (s *tSignable) SetSig(b []byte)  { s.sig = b }
   299  func (s *tSignable) SigBytes() []byte { return s.sig }
   300  func (s *tSignable) Serialize() []byte {
   301  	return s.b
   302  }
   303  
   304  func signMsg(priv *secp256k1.PrivateKey, msg []byte) []byte {
   305  	hash := sha256.Sum256(msg)
   306  	sig := ecdsa.Sign(priv, hash[:])
   307  	return sig.Serialize()
   308  }
   309  
   310  func tNewConnect(user *tUser) *msgjson.Connect {
   311  	return &msgjson.Connect{
   312  		AccountID:  user.acctID[:],
   313  		APIVersion: 0,
   314  		Time:       uint64(time.Now().UnixMilli()),
   315  	}
   316  }
   317  
   318  func extractConnectResult(t *testing.T, msg *msgjson.Message) *msgjson.ConnectResult {
   319  	t.Helper()
   320  	if msg == nil {
   321  		t.Fatalf("no response from 'connect' request")
   322  	}
   323  	resp, _ := msg.Response()
   324  	result := new(msgjson.ConnectResult)
   325  	err := json.Unmarshal(resp.Result, result)
   326  	if err != nil {
   327  		t.Fatalf("unmarshal error: %v", err)
   328  	}
   329  	return result
   330  }
   331  
   332  func queueUser(t *testing.T, user *tUser) *msgjson.Message {
   333  	t.Helper()
   334  	rig.storage.acct = &account.Account{ID: user.acctID, PubKey: user.privKey.PubKey()}
   335  	connect := tNewConnect(user)
   336  	sigMsg := connect.Serialize()
   337  	sig := signMsg(user.privKey, sigMsg)
   338  	connect.SetSig(sig)
   339  	msg, _ := msgjson.NewRequest(comms.NextID(), msgjson.ConnectRoute, connect)
   340  	return msg
   341  }
   342  
   343  func connectUser(t *testing.T, user *tUser) *msgjson.Message {
   344  	t.Helper()
   345  	return tryConnectUser(t, user, false)
   346  }
   347  
   348  func tryConnectUser(t *testing.T, user *tUser, wantErr bool) *msgjson.Message {
   349  	t.Helper()
   350  	connect := queueUser(t, user)
   351  	err := rig.mgr.handleConnect(user.conn, connect)
   352  	if (err != nil) != wantErr {
   353  		t.Fatalf("handleConnect: wantErr=%v, got err=%v", wantErr, err)
   354  	}
   355  
   356  	// Check the response.
   357  	respMsg := user.conn.getSend()
   358  	if respMsg == nil {
   359  		t.Fatalf("no response from 'connect' request")
   360  	}
   361  	if respMsg.ID != connect.ID {
   362  		t.Fatalf("'connect' response has wrong ID. expected %d, got %d", connect.ID, respMsg.ID)
   363  	}
   364  	return respMsg
   365  }
   366  
   367  func makeEnsureErr(t *testing.T) func(rpcErr *msgjson.Error, tag string, code int) {
   368  	return func(rpcErr *msgjson.Error, tag string, code int) {
   369  		t.Helper()
   370  		if rpcErr == nil {
   371  			t.Fatalf("no error for %s ID", tag)
   372  		}
   373  		if rpcErr.Code != code {
   374  			t.Fatalf("wrong error code for %s. expected %d, got %d: %s",
   375  				tag, code, rpcErr.Code, rpcErr.Message)
   376  		}
   377  	}
   378  }
   379  
   380  func waitFor(pred func() bool, timeout time.Duration) (fail bool) {
   381  	tStart := time.Now()
   382  	for {
   383  		if pred() {
   384  			return false
   385  		}
   386  		if time.Since(tStart) > timeout {
   387  			return true
   388  		}
   389  		time.Sleep(10 * time.Millisecond)
   390  	}
   391  }
   392  
   393  var (
   394  	tBondConfs       int64 = 5
   395  	tParseBondTxAcct account.AccountID
   396  	tParseBondTxErr  error
   397  )
   398  
   399  func tParseBondTx(assetID uint32, ver uint16, rawTx []byte) (bondCoinID []byte, amt int64,
   400  	lockTime int64, acct account.AccountID, err error) {
   401  	return nil, 0, time.Now().Add(time.Minute).Unix(), tParseBondTxAcct, tParseBondTxErr
   402  }
   403  
   404  const (
   405  	tRegFee       uint64 = 500_000_000
   406  	tDexPubKeyHex string = "032e3678f9889206dcea4fc281556c9e543c5d5ffa7efe8d11118b52e29c773f27"
   407  )
   408  
   409  var tDexPubKeyBytes = []byte{
   410  	0x03, 0x2e, 0x36, 0x78, 0xf9, 0x88, 0x92, 0x06, 0xdc, 0xea, 0x4f, 0xc2,
   411  	0x81, 0x55, 0x6c, 0x9e, 0x54, 0x3c, 0x5d, 0x5f, 0xfa, 0x7e, 0xfe, 0x8d,
   412  	0x11, 0x11, 0x8b, 0x52, 0xe2, 0x9c, 0x77, 0x3f, 0x27,
   413  }
   414  
   415  var tRoutes = make(map[string]comms.MsgHandler)
   416  
   417  func TestMain(m *testing.M) {
   418  	doIt := func() int {
   419  		UseLogger(dex.StdOutLogger("AUTH_TEST", dex.LevelTrace))
   420  		ctx, shutdown := context.WithCancel(context.Background())
   421  		defer shutdown()
   422  		storage := &TStorage{}
   423  		// secp256k1.PrivKeyFromBytes
   424  		dexKey, _ := secp256k1.ParsePubKey(tDexPubKeyBytes)
   425  		signer := &TSigner{pubkey: dexKey}
   426  		authMgr := NewAuthManager(&Config{
   427  			Storage:    storage,
   428  			Signer:     signer,
   429  			BondExpiry: 86400,
   430  			BondAssets: map[string]*msgjson.BondAsset{
   431  				"dcr": {
   432  					Version: 0,
   433  					ID:      42,
   434  					Confs:   uint32(tBondConfs),
   435  					Amt:     tRegFee * 10,
   436  				},
   437  			},
   438  			BondTxParser:    tParseBondTx,
   439  			UserUnbooker:    func(account.AccountID) {},
   440  			MiaUserTimeout:  90 * time.Second, // TODO: test
   441  			CancelThreshold: 0.9,
   442  			TxDataSources:   make(map[uint32]TxDataSource),
   443  			Route: func(route string, handler comms.MsgHandler) {
   444  				tRoutes[route] = handler
   445  			},
   446  		})
   447  		go authMgr.Run(ctx)
   448  		rig = &testRig{
   449  			storage: storage,
   450  			signer:  signer,
   451  			mgr:     authMgr,
   452  		}
   453  		return m.Run()
   454  	}
   455  
   456  	os.Exit(doIt())
   457  }
   458  
   459  func userMatchData(takerUser account.AccountID) (*db.MatchData, *order.UserMatch) {
   460  	var baseRate, quoteRate uint64 = 123, 73
   461  	side := order.Taker
   462  	takerSell := true
   463  	feeRateSwap := baseRate // user is selling
   464  
   465  	anyID := newAccountID()
   466  	var mid order.MatchID
   467  	copy(mid[:], anyID[:])
   468  	anyID = newAccountID()
   469  	var oid order.OrderID
   470  	copy(oid[:], anyID[:])
   471  	takerUserMatch := &order.UserMatch{
   472  		OrderID:     oid,
   473  		MatchID:     mid,
   474  		Quantity:    1,
   475  		Rate:        2,
   476  		Address:     "makerSwapAddress", // counterparty
   477  		Status:      order.MakerRedeemed,
   478  		Side:        side,
   479  		FeeRateSwap: feeRateSwap,
   480  	}
   481  
   482  	var oid2 order.OrderID
   483  	anyID = newAccountID()
   484  	copy(oid2[:], anyID[:])
   485  	matchData := &db.MatchData{
   486  		ID:        mid,
   487  		Taker:     oid,
   488  		TakerAcct: takerUser,
   489  		TakerAddr: "takerSwapAddress",
   490  		TakerSell: takerSell,
   491  		Maker:     oid2,
   492  		MakerAcct: newAccountID(),
   493  		MakerAddr: takerUserMatch.Address,
   494  		Epoch: order.EpochID{
   495  			Dur: 10000,
   496  			Idx: 132412342,
   497  		},
   498  		Quantity:  takerUserMatch.Quantity,
   499  		Rate:      takerUserMatch.Rate,
   500  		BaseRate:  baseRate,
   501  		QuoteRate: quoteRate,
   502  		Active:    true,
   503  		Status:    takerUserMatch.Status,
   504  	}
   505  
   506  	//matchTime := matchData.Epoch.End()
   507  	return matchData, takerUserMatch
   508  }
   509  
   510  func TestGraceLimit(t *testing.T) {
   511  	tests := []struct {
   512  		name      string
   513  		thresh    float64
   514  		wantLimit int
   515  	}{
   516  		{"0.99 => 99", 0.99, 99}, // 98.99999999999991
   517  		{"0.98 => 49", 0.98, 49}, // 48.99999999999996
   518  		{"0.96 => 24", 0.96, 24}, // 23.99999999999998
   519  		{"0.95 => 19", 0.95, 19}, // 18.999999999999982
   520  		{"0.9 => 9", 0.9, 9},     // 9.000000000000002
   521  		{"0.875 => 7", 0.875, 7}, // exact
   522  		{"0.8 => 4", 0.8, 4},     // 4.000000000000001
   523  		{"0.75 => 3", 0.75, 3},   // exact
   524  		{"0.5 => 1", 0.5, 1},     // exact
   525  	}
   526  	for _, tt := range tests {
   527  		t.Run(tt.name, func(t *testing.T) {
   528  			auth := &AuthManager{
   529  				cancelThresh: tt.thresh,
   530  			}
   531  			got := auth.GraceLimit()
   532  			if got != tt.wantLimit {
   533  				t.Errorf("incorrect grace limit. got %d, want %d", got, tt.wantLimit)
   534  			}
   535  		})
   536  	}
   537  }
   538  
   539  var t0 = int64(1601418963000)
   540  
   541  func nextTime() int64 {
   542  	t0 += 10
   543  	return t0
   544  }
   545  
   546  func newMatchOutcome(status order.MatchStatus, mid order.MatchID, fail bool, val uint64, t int64) *db.MatchOutcome {
   547  	switch status {
   548  	case order.NewlyMatched, order.MakerSwapCast, order.TakerSwapCast:
   549  		if !fail {
   550  			panic("wrong")
   551  		}
   552  	case order.MatchComplete:
   553  		if fail {
   554  			panic("wrong")
   555  		}
   556  	}
   557  	return &db.MatchOutcome{
   558  		Status: status,
   559  		ID:     mid,
   560  		Fail:   fail,
   561  		Time:   t,
   562  		Value:  val,
   563  	}
   564  }
   565  
   566  func newPreimageResult(miss bool, t int64) *db.PreimageResult {
   567  	return &db.PreimageResult{
   568  		Miss: miss,
   569  		Time: t,
   570  		ID:   randomOrderID(),
   571  	}
   572  }
   573  
   574  func setViolations() (wantScore int32) {
   575  	rig.storage.userMatchOutcomes = []*db.MatchOutcome{
   576  		newMatchOutcome(order.NewlyMatched, randomMatchID(), true, 7, nextTime()),
   577  		newMatchOutcome(order.MatchComplete, randomMatchID(), false, 7, nextTime()), // success
   578  		newMatchOutcome(order.NewlyMatched, randomMatchID(), true, 7, nextTime()),
   579  		newMatchOutcome(order.MakerSwapCast, randomMatchID(), true, 7, nextTime()), // noSwapAsTaker at index 3
   580  		newMatchOutcome(order.TakerSwapCast, randomMatchID(), true, 7, nextTime()),
   581  		newMatchOutcome(order.MakerRedeemed, randomMatchID(), false, 7, nextTime()), // success (for maker)
   582  		newMatchOutcome(order.MakerRedeemed, randomMatchID(), true, 7, nextTime()),
   583  		newMatchOutcome(order.MatchComplete, randomMatchID(), false, 7, nextTime()), // success
   584  		newMatchOutcome(order.MatchComplete, randomMatchID(), false, 7, nextTime()), // success
   585  	}
   586  	t0 -= 4000
   587  	rig.storage.userPreimageResults = []*db.PreimageResult{newPreimageResult(true, nextTime())}
   588  	for range rig.storage.userMatchOutcomes {
   589  		rig.storage.userPreimageResults = append(rig.storage.userPreimageResults, newPreimageResult(false, nextTime()))
   590  	}
   591  	return 4*successScore + 1*preimageMissScore +
   592  		2*noSwapAsMakerScore + noSwapAsTakerScore + noRedeemAsMakerScore + 1*noRedeemAsTakerScore
   593  }
   594  
   595  func clearViolations() {
   596  	rig.storage.userMatchOutcomes = []*db.MatchOutcome{}
   597  }
   598  
   599  func TestAuthManager_loadUserScore(t *testing.T) {
   600  	// Spot test with all violations set
   601  	wantScore := setViolations()
   602  	defer clearViolations()
   603  	user := tNewUser(t)
   604  	score, err := rig.mgr.loadUserScore(user.acctID)
   605  	if err != nil {
   606  		t.Fatal(err)
   607  	}
   608  	if score != wantScore {
   609  		t.Errorf("wrong score. got %d, want %d", score, wantScore)
   610  	}
   611  
   612  	// add one NoSwapAsTaker (match inactive at MakerSwapCast)
   613  	rig.storage.userMatchOutcomes = append(rig.storage.userMatchOutcomes,
   614  		newMatchOutcome(order.MakerSwapCast, randomMatchID(), true, 7, nextTime()))
   615  	wantScore += noSwapAsTakerScore
   616  
   617  	score, err = rig.mgr.loadUserScore(user.acctID)
   618  	if err != nil {
   619  		t.Fatal(err)
   620  	}
   621  	if score != wantScore {
   622  		t.Errorf("wrong score. got %d, want %d", score, wantScore)
   623  	}
   624  
   625  	tests := []struct {
   626  		name           string
   627  		user           account.AccountID
   628  		matchOutcomes  []*db.MatchOutcome
   629  		preimageMisses []*db.PreimageResult
   630  		wantScore      int32
   631  	}{
   632  		{
   633  			name: "negative",
   634  			user: user.acctID,
   635  			matchOutcomes: []*db.MatchOutcome{
   636  				newMatchOutcome(order.MatchComplete, randomMatchID(), false, 7, nextTime()),
   637  				newMatchOutcome(order.MatchComplete, randomMatchID(), false, 7, nextTime()),
   638  				newMatchOutcome(order.MatchComplete, randomMatchID(), false, 7, nextTime()),
   639  				newMatchOutcome(order.MatchComplete, randomMatchID(), false, 7, nextTime()),
   640  			},
   641  			wantScore: 4,
   642  		},
   643  		{
   644  			name:          "nuthin",
   645  			user:          user.acctID,
   646  			matchOutcomes: []*db.MatchOutcome{},
   647  			wantScore:     0,
   648  		},
   649  		{
   650  			name: "balance",
   651  			user: user.acctID,
   652  			matchOutcomes: []*db.MatchOutcome{
   653  				newMatchOutcome(order.MatchComplete, randomMatchID(), false, 7, nextTime()),
   654  				newMatchOutcome(order.MatchComplete, randomMatchID(), false, 7, nextTime()),
   655  				newMatchOutcome(order.MatchComplete, randomMatchID(), false, 7, nextTime()),
   656  				newMatchOutcome(order.MatchComplete, randomMatchID(), false, 7, nextTime()),
   657  			},
   658  			preimageMisses: []*db.PreimageResult{
   659  				newPreimageResult(true, nextTime()),
   660  				newPreimageResult(true, nextTime()),
   661  			},
   662  			wantScore: 0,
   663  		},
   664  		{
   665  			name: "tipping red",
   666  			user: user.acctID,
   667  			matchOutcomes: []*db.MatchOutcome{
   668  				newMatchOutcome(order.NewlyMatched, randomMatchID(), true, 7, nextTime()),
   669  				newMatchOutcome(order.MakerSwapCast, randomMatchID(), true, 7, nextTime()),
   670  				newMatchOutcome(order.MatchComplete, randomMatchID(), false, 7, nextTime()),
   671  				newMatchOutcome(order.MatchComplete, randomMatchID(), false, 7, nextTime()),
   672  				newMatchOutcome(order.MatchComplete, randomMatchID(), false, 7, nextTime()),
   673  				newMatchOutcome(order.NewlyMatched, randomMatchID(), true, 7, nextTime()),
   674  				newMatchOutcome(order.MakerRedeemed, randomMatchID(), true, 7, nextTime()),
   675  				newMatchOutcome(order.MatchComplete, randomMatchID(), false, 7, nextTime()),
   676  				newMatchOutcome(order.MatchComplete, randomMatchID(), false, 7, nextTime()),
   677  			},
   678  			preimageMisses: []*db.PreimageResult{
   679  				newPreimageResult(true, nextTime()),
   680  				newPreimageResult(false, nextTime()),
   681  			},
   682  			wantScore: 2*noSwapAsMakerScore + 1*noSwapAsTakerScore + 0*noRedeemAsMakerScore +
   683  				1*noRedeemAsTakerScore + 1*preimageMissScore + 5*successScore,
   684  		},
   685  	}
   686  	for _, tt := range tests {
   687  		t.Run(tt.name, func(t *testing.T) {
   688  			rig.storage.userMatchOutcomes = tt.matchOutcomes
   689  			rig.storage.userPreimageResults = tt.preimageMisses
   690  			score, err := rig.mgr.loadUserScore(tt.user)
   691  			if err != nil {
   692  				t.Fatalf("got err: %v", err)
   693  			}
   694  			if score != tt.wantScore {
   695  				t.Errorf("incorrect user score. got %d, want %d", score, tt.wantScore)
   696  			}
   697  		})
   698  	}
   699  }
   700  
   701  func TestConnect(t *testing.T) {
   702  	user := tNewUser(t)
   703  	rig.signer.sig = user.randomSignature()
   704  
   705  	// Before connecting, put an activeOrder and activeMatch in storage.
   706  	matchData, userMatch := userMatchData(user.acctID)
   707  	matchTime := matchData.Epoch.End()
   708  
   709  	rig.storage.orderStatuses = []*db.OrderStatus{
   710  		{
   711  			ID:     userMatch.OrderID,
   712  			Status: order.OrderStatusBooked,
   713  		},
   714  	}
   715  	defer func() { rig.storage.orderStatuses = nil }()
   716  
   717  	rig.storage.matches = []*db.MatchData{matchData}
   718  	defer func() { rig.storage.matches = nil }()
   719  
   720  	epochGaps := []int32{1} // penalized
   721  
   722  	rig.storage.setRatioData(&ratioData{
   723  		oidsCompleted:  []order.OrderID{{0x1}},
   724  		timesCompleted: []int64{1234},
   725  		oidsCancels:    []order.OrderID{{0x2}},
   726  		oidsCanceled:   []order.OrderID{{0x1}},
   727  		timesCanceled:  []int64{1235},
   728  		epochGaps:      epochGaps,
   729  	}) // 1:1 = 50%
   730  	defer rig.storage.setRatioData(&ratioData{}) // clean slate
   731  
   732  	// TODO: update tests now that there is are no close/ban and unban
   733  	// operations, instead an integral tier.
   734  
   735  	// TODO: update tests now that cancel ratio is part of the score equation
   736  	// rather than a hard close operation.
   737  
   738  	/* cancel ratio stuff
   739  
   740  	// Close account on connect with failing cancel ratio, and no grace period.
   741  	rig.mgr.cancelThresh = 0.2 // thresh below actual ratio, and no grace period with total/(1+total) = 2/3 = 0.66... > 0.2
   742  	tryConnectUser(t, user, false)
   743  	if rig.storage.closedID != user.acctID {
   744  		t.Fatalf("Expected account %v to be closed on connect, got %v", user.acctID, rig.storage.closedID)
   745  	}
   746  
   747  	// Make it a free cancel.
   748  	rig.storage.closedID = account.AccountID{} // unclose the account in db
   749  	epochGaps[0] = 2
   750  	connectUser(t, user)
   751  	if rig.storage.closedID == user.acctID {
   752  		t.Fatalf("Expected account %v to NOT be closed with free cancels, but it was.", user)
   753  	}
   754  	epochGaps[0] = 1
   755  
   756  	// Try again just meeting cancel ratio.
   757  	rig.storage.closedID = account.AccountID{} // unclose the account in db
   758  	rig.mgr.cancelThresh = 0.6                 // passable threshold for 1 cancel : 1 completion (0.5)
   759  
   760  	connectUser(t, user)
   761  	if rig.storage.closedID == user.acctID {
   762  		t.Fatalf("Expected account %v to NOT be closed on connect, but it was.", user)
   763  	}
   764  
   765  	// Add another cancel, bringing cancels to 2, completions 1 for a ratio of
   766  	// 2:1 (2/3 = 0.666...), and total/(1+total) = 3/4 = 0.75 > thresh (0.6), so
   767  	// no grace period.
   768  	rig.storage.ratio.oidsCanceled = append(rig.storage.ratio.oidsCanceled, order.OrderID{0x3})
   769  	rig.storage.ratio.oidsCancels = append(rig.storage.ratio.oidsCancels, order.OrderID{0x4})
   770  	rig.storage.ratio.timesCanceled = append(rig.storage.ratio.timesCanceled, 12341234)
   771  	rig.storage.ratio.epochGaps = append(rig.storage.ratio.epochGaps, 1)
   772  
   773  	tryConnectUser(t, user, false)
   774  	if rig.storage.closedID != user.acctID {
   775  		t.Fatalf("Expected account %v to be closed on connect, got %v", user.acctID, rig.storage.closedID)
   776  	}
   777  
   778  	// Make one a free cancel.
   779  	rig.storage.closedID = account.AccountID{} // unclose the account in db
   780  	rig.storage.ratio.epochGaps[1] = 2
   781  	connectUser(t, user)
   782  	if rig.storage.closedID == user.acctID {
   783  		t.Fatalf("Expected account %v to NOT be closed with free cancels, but it was.", user)
   784  	}
   785  	rig.storage.ratio.epochGaps[1] = 0
   786  
   787  	// Try again just meeting cancel ratio.
   788  	rig.storage.closedID = account.AccountID{} // unclose the account in db
   789  	rig.mgr.cancelThresh = 0.7                 // passable threshold for 2 cancel : 1 completion (0.6666..)
   790  
   791  	tryConnectUser(t, user, false)
   792  	if rig.storage.closedID == user.acctID {
   793  		t.Fatalf("Expected account %v to NOT be closed on connect, but it was.", user)
   794  	}
   795  
   796  	// Test the grace period (threshold <= total/(1+total) and no completions)
   797  	// 2 cancels, 0 success, 2 total
   798  	rig.mgr.cancelThresh = 0.7             // 2/(1+2) = 0.66.. < threshold
   799  	rig.storage.ratio.timesCompleted = nil // no completions
   800  	rig.storage.ratio.oidsCompleted = nil
   801  	tryConnectUser(t, user, false)
   802  	if rig.storage.closedID == user.acctID {
   803  		t.Fatalf("Expected account %v to NOT be closed on connect, but it was.", user)
   804  	}
   805  
   806  	// 3 cancels, 0 success, 3 total => rate = 1.0, exceeds threshold
   807  	rig.mgr.cancelThresh = 0.75 // 3/(1+3) == threshold, still in grace period
   808  	rig.storage.ratio.oidsCanceled = append(rig.storage.ratio.oidsCanceled, order.OrderID{0x4})
   809  	rig.storage.ratio.oidsCancels = append(rig.storage.ratio.oidsCancels, order.OrderID{0x5})
   810  	rig.storage.ratio.timesCanceled = append(rig.storage.ratio.timesCanceled, 12341239)
   811  	rig.storage.ratio.epochGaps = append(rig.storage.ratio.epochGaps, 1)
   812  
   813  	tryConnectUser(t, user, false)
   814  	if rig.storage.closedID == user.acctID {
   815  		t.Fatalf("Expected account %v to NOT be closed on connect, but it was.", user)
   816  	}
   817  
   818  	*/
   819  
   820  	// Connect with a violation score above revocation threshold.
   821  	wantScore := setViolations()
   822  	defer clearViolations()
   823  
   824  	if wantScore > rig.mgr.penaltyThreshold {
   825  		t.Fatalf("test score of %v is not at least the revocation threshold of %v, revise the test", wantScore, rig.mgr.penaltyThreshold)
   826  	}
   827  
   828  	// Test loadUserScore while here.
   829  	score, err := rig.mgr.loadUserScore(user.acctID)
   830  	if err != nil {
   831  		t.Fatal(err)
   832  	}
   833  
   834  	if score != wantScore {
   835  		t.Errorf("wrong score. got %d, want %d", score, wantScore)
   836  	}
   837  
   838  	// No error, but Penalize account that was not previously closed.
   839  	tryConnectUser(t, user, false)
   840  
   841  	makerSwapCastIdx := 3
   842  	rig.storage.userMatchOutcomes = append(rig.storage.userMatchOutcomes[:makerSwapCastIdx], rig.storage.userMatchOutcomes[makerSwapCastIdx+1:]...)
   843  	wantScore -= noSwapAsTakerScore
   844  	if wantScore <= rig.mgr.penaltyThreshold {
   845  		t.Fatalf("test score of %v is not more than the penalty threshold of %v, revise the test", wantScore, rig.mgr.penaltyThreshold)
   846  	}
   847  	score, err = rig.mgr.loadUserScore(user.acctID)
   848  	if err != nil {
   849  		t.Fatal(err)
   850  	}
   851  	if score != wantScore {
   852  		t.Errorf("wrong score. got %d, want %d", score, wantScore)
   853  	}
   854  
   855  	// Connect the user.
   856  	respMsg := connectUser(t, user)
   857  	cResp := extractConnectResult(t, respMsg)
   858  	if len(cResp.ActiveOrderStatuses) != 1 {
   859  		t.Fatalf("no active orders")
   860  	}
   861  	msgOrder := cResp.ActiveOrderStatuses[0]
   862  	if msgOrder.ID.String() != userMatch.OrderID.String() {
   863  		t.Fatal("active order ID mismatch: ", msgOrder.ID.String(), " != ", userMatch.OrderID.String())
   864  	}
   865  	if msgOrder.Status != uint16(order.OrderStatusBooked) {
   866  		t.Fatal("active order Status mismatch: ", msgOrder.Status, " != ", order.OrderStatusBooked)
   867  	}
   868  	if len(cResp.ActiveMatches) != 1 {
   869  		t.Fatalf("no active matches")
   870  	}
   871  	msgMatch := cResp.ActiveMatches[0]
   872  	if msgMatch.OrderID.String() != userMatch.OrderID.String() {
   873  		t.Fatal("active match OrderID mismatch: ", msgMatch.OrderID.String(), " != ", userMatch.OrderID.String())
   874  	}
   875  	if msgMatch.MatchID.String() != userMatch.MatchID.String() {
   876  		t.Fatal("active match MatchID mismatch: ", msgMatch.MatchID.String(), " != ", userMatch.MatchID.String())
   877  	}
   878  	if msgMatch.Quantity != userMatch.Quantity {
   879  		t.Fatal("active match Quantity mismatch: ", msgMatch.Quantity, " != ", userMatch.Quantity)
   880  	}
   881  	if msgMatch.Rate != userMatch.Rate {
   882  		t.Fatal("active match Rate mismatch: ", msgMatch.Rate, " != ", userMatch.Rate)
   883  	}
   884  	if msgMatch.Address != userMatch.Address {
   885  		t.Fatal("active match Address mismatch: ", msgMatch.Address, " != ", userMatch.Address)
   886  	}
   887  	if msgMatch.Status != uint8(userMatch.Status) {
   888  		t.Fatal("active match Status mismatch: ", msgMatch.Status, " != ", userMatch.Status)
   889  	}
   890  	if msgMatch.Side != uint8(userMatch.Side) {
   891  		t.Fatal("active match Side mismatch: ", msgMatch.Side, " != ", userMatch.Side)
   892  	}
   893  	if msgMatch.FeeRateQuote != matchData.QuoteRate {
   894  		t.Fatal("active match quote fee rate mismatch: ", msgMatch.FeeRateQuote, " != ", matchData.QuoteRate)
   895  	}
   896  	if msgMatch.FeeRateBase != matchData.BaseRate {
   897  		t.Fatal("active match base fee rate mismatch: ", msgMatch.FeeRateBase, " != ", matchData.BaseRate)
   898  	}
   899  	if msgMatch.ServerTime != uint64(matchTime.UnixMilli()) {
   900  		t.Fatal("active match time mismatch: ", msgMatch.ServerTime, " != ", uint64(matchTime.UnixMilli()))
   901  	}
   902  
   903  	// Send a request to the client.
   904  	type tPayload struct {
   905  		A int
   906  	}
   907  	a5 := &tPayload{A: 5}
   908  	reqID := comms.NextID()
   909  	msg, err := msgjson.NewRequest(reqID, "request", a5)
   910  	if err != nil {
   911  		t.Fatalf("NewRequest error: %v", err)
   912  	}
   913  	var responded bool
   914  	rig.mgr.Request(user.acctID, msg, func(comms.Link, *msgjson.Message) {
   915  		responded = true
   916  	})
   917  	req := user.conn.getReq()
   918  	if req == nil {
   919  		t.Fatalf("no request")
   920  	}
   921  	var a tPayload
   922  	err = json.Unmarshal(req.msg.Payload, &a)
   923  	if err != nil {
   924  		t.Fatalf("unmarshal error: %v", err)
   925  	}
   926  	if a.A != 5 {
   927  		t.Fatalf("wrong value for A. expected 5, got %d", a.A)
   928  	}
   929  	// Respond to the DEX's request.
   930  	msg = &msgjson.Message{ID: reqID}
   931  	req.respFunc(user.conn, msg)
   932  	if !responded {
   933  		t.Fatalf("responded flag not set")
   934  	}
   935  
   936  	reuser := &tUser{
   937  		acctID:  user.acctID,
   938  		privKey: user.privKey,
   939  		conn:    tNewRPCClient(),
   940  	}
   941  	connectUser(t, reuser)
   942  	a10 := &tPayload{A: 10}
   943  	msg, _ = msgjson.NewRequest(comms.NextID(), "request", a10)
   944  	err = rig.mgr.RequestWithTimeout(reuser.acctID, msg, func(comms.Link, *msgjson.Message) {}, time.Minute, func() {})
   945  	if err != nil {
   946  		t.Fatalf("request failed: %v", err)
   947  	}
   948  	// The a10 message should be in the new connection
   949  	if user.conn.getReq() != nil {
   950  		t.Fatalf("old connection received a request after reconnection")
   951  	}
   952  	if reuser.conn.getReq() == nil {
   953  		t.Fatalf("new connection did not receive the request")
   954  	}
   955  }
   956  
   957  func TestAccountErrors(t *testing.T) {
   958  	user := tNewUser(t)
   959  	rig.signer.sig = user.randomSignature()
   960  	connect := queueUser(t, user)
   961  
   962  	// Put a match in storage
   963  	matchData, userMatch := userMatchData(user.acctID)
   964  	matchTime := matchData.Epoch.End()
   965  	rig.storage.matches = []*db.MatchData{matchData}
   966  
   967  	rig.mgr.handleConnect(user.conn, connect)
   968  	rig.storage.matches = nil
   969  
   970  	// Check the response.
   971  	respMsg := user.conn.getSend()
   972  	result := extractConnectResult(t, respMsg)
   973  	if len(result.ActiveMatches) != 1 {
   974  		t.Fatalf("expected 1 match, received %d", len(result.ActiveMatches))
   975  	}
   976  	match := result.ActiveMatches[0]
   977  	if match.OrderID.String() != userMatch.OrderID.String() {
   978  		t.Fatal("wrong OrderID: ", match.OrderID, " != ", userMatch.OrderID)
   979  	}
   980  	if match.MatchID.String() != userMatch.MatchID.String() {
   981  		t.Fatal("wrong MatchID: ", match.MatchID, " != ", userMatch.OrderID)
   982  	}
   983  	if match.Quantity != userMatch.Quantity {
   984  		t.Fatal("wrong Quantity: ", match.Quantity, " != ", userMatch.OrderID)
   985  	}
   986  	if match.Rate != userMatch.Rate {
   987  		t.Fatal("wrong Rate: ", match.Rate, " != ", userMatch.OrderID)
   988  	}
   989  	if match.Address != userMatch.Address {
   990  		t.Fatal("wrong Address: ", match.Address, " != ", userMatch.OrderID)
   991  	}
   992  	if match.Status != uint8(userMatch.Status) {
   993  		t.Fatal("wrong Status: ", match.Status, " != ", userMatch.OrderID)
   994  	}
   995  	if match.Side != uint8(userMatch.Side) {
   996  		t.Fatal("wrong Side: ", match.Side, " != ", userMatch.OrderID)
   997  	}
   998  	if match.FeeRateQuote != matchData.QuoteRate {
   999  		t.Fatal("wrong quote fee rate: ", match.FeeRateQuote, " != ", matchData.QuoteRate)
  1000  	}
  1001  	if match.FeeRateBase != matchData.BaseRate {
  1002  		t.Fatal("wrong base fee rate: ", match.FeeRateBase, " != ", matchData.BaseRate)
  1003  	}
  1004  	if match.ServerTime != uint64(matchTime.UnixMilli()) {
  1005  		t.Fatal("wrong match time: ", match.ServerTime, " != ", uint64(matchTime.UnixMilli()))
  1006  	}
  1007  	// Make a violation score above penalty threshold reflected by the DB.
  1008  	score := setViolations()
  1009  	defer clearViolations()
  1010  
  1011  	rig.mgr.removeClient(rig.mgr.user(user.acctID)) // disconnect first, NOTE that link.Disconnect is async
  1012  	user.conn = tNewRPCClient()                     // disconnect necessitates new conn ID
  1013  	rpcErr := rig.mgr.handleConnect(user.conn, connect)
  1014  	if rpcErr != nil {
  1015  		t.Fatalf("should be no error for closed account")
  1016  	}
  1017  	client := rig.mgr.user(user.acctID)
  1018  	rig.storage.setBondTier(1)
  1019  	if client == nil {
  1020  		t.Fatalf("client not found")
  1021  	}
  1022  	initPenaltyThresh := rig.mgr.penaltyThreshold
  1023  	defer func() { rig.mgr.penaltyThreshold = initPenaltyThresh }()
  1024  	rig.mgr.penaltyThreshold = score
  1025  	if client.tier > 0 {
  1026  		t.Errorf("client should have been tier 0")
  1027  	}
  1028  
  1029  	// Raise the penalty threshold to ensure automatic reinstatement.
  1030  	rig.mgr.penaltyThreshold = score - 1
  1031  
  1032  	rig.mgr.removeClient(rig.mgr.user(user.acctID)) // disconnect first, NOTE that link.Disconnect is async
  1033  	user.conn = tNewRPCClient()                     // disconnect necessitates new conn ID
  1034  	rpcErr = rig.mgr.handleConnect(user.conn, connect)
  1035  	if rpcErr != nil {
  1036  		t.Fatalf("should be no error for closed account")
  1037  	}
  1038  	client = rig.mgr.user(user.acctID)
  1039  	if client == nil {
  1040  		t.Fatalf("client not found")
  1041  	}
  1042  	if client.tier < 1 {
  1043  		t.Errorf("client should have unbanned automatically")
  1044  	}
  1045  
  1046  }
  1047  
  1048  func TestRoute(t *testing.T) {
  1049  	user := tNewUser(t)
  1050  	rig.signer.sig = user.randomSignature()
  1051  	connectUser(t, user)
  1052  
  1053  	var translated account.AccountID
  1054  	rig.mgr.Route("testroute", func(id account.AccountID, msg *msgjson.Message) *msgjson.Error {
  1055  		translated = id
  1056  		return nil
  1057  	})
  1058  	f := tRoutes["testroute"]
  1059  	if f == nil {
  1060  		t.Fatalf("'testroute' not registered")
  1061  	}
  1062  	rpcErr := f(user.conn, nil)
  1063  	if rpcErr != nil {
  1064  		t.Fatalf("rpc error: %s", rpcErr.Message)
  1065  	}
  1066  	if translated != user.acctID {
  1067  		t.Fatalf("account ID not set")
  1068  	}
  1069  
  1070  	// Run the route with an unknown client. Should be an UnauthorizedConnection
  1071  	// error.
  1072  	foreigner := tNewUser(t)
  1073  	rpcErr = f(foreigner.conn, nil)
  1074  	if rpcErr == nil {
  1075  		t.Fatalf("no error for unauthed user")
  1076  	}
  1077  	if rpcErr.Code != msgjson.UnauthorizedConnection {
  1078  		t.Fatalf("wrong error for unauthed user. expected %d, got %d",
  1079  			msgjson.UnauthorizedConnection, rpcErr.Code)
  1080  	}
  1081  }
  1082  
  1083  func TestAuth(t *testing.T) {
  1084  	user := tNewUser(t)
  1085  	rig.signer.sig = user.randomSignature()
  1086  	connectUser(t, user)
  1087  
  1088  	msgBytes := randBytes(50)
  1089  	sigBytes := signMsg(user.privKey, msgBytes)
  1090  	err := rig.mgr.Auth(user.acctID, msgBytes, sigBytes)
  1091  	if err != nil {
  1092  		t.Fatalf("unexpected auth error: %v", err)
  1093  	}
  1094  
  1095  	foreigner := tNewUser(t)
  1096  	sigBytes = signMsg(user.privKey, msgBytes)
  1097  	err = rig.mgr.Auth(foreigner.acctID, msgBytes, sigBytes)
  1098  	if err == nil {
  1099  		t.Fatalf("no auth error for foreigner")
  1100  	}
  1101  
  1102  	msgBytes = randBytes(50)
  1103  	err = rig.mgr.Auth(user.acctID, msgBytes, sigBytes)
  1104  	if err == nil {
  1105  		t.Fatalf("no error for wrong message")
  1106  	}
  1107  }
  1108  
  1109  func TestSign(t *testing.T) {
  1110  	sig1 := tNewUser(t).randomSignature()
  1111  	sig1Bytes := sig1.Serialize()
  1112  	rig.signer.sig = sig1
  1113  	s := &tSignable{b: randBytes(25)}
  1114  	rig.mgr.Sign(s)
  1115  	if !bytes.Equal(sig1Bytes, s.SigBytes()) {
  1116  		t.Fatalf("incorrect signature. expected %x, got %x", sig1.Serialize(), s.SigBytes())
  1117  	}
  1118  
  1119  	// Try two at a time
  1120  	s2 := &tSignable{b: randBytes(25)}
  1121  	rig.mgr.Sign(s, s2)
  1122  }
  1123  
  1124  func TestSend(t *testing.T) {
  1125  	user := tNewUser(t)
  1126  	rig.signer.sig = user.randomSignature()
  1127  	connectUser(t, user)
  1128  	foreigner := tNewUser(t)
  1129  
  1130  	type tA struct {
  1131  		A int
  1132  	}
  1133  	payload := &tA{A: 5}
  1134  	resp, _ := msgjson.NewResponse(comms.NextID(), payload, nil)
  1135  	payload = &tA{A: 10}
  1136  	req, _ := msgjson.NewRequest(comms.NextID(), "testroute", payload)
  1137  
  1138  	// Send a message to a foreigner
  1139  	rig.mgr.Send(foreigner.acctID, resp)
  1140  	if foreigner.conn.getSend() != nil {
  1141  		t.Fatalf("message magically got through to foreigner")
  1142  	}
  1143  	if user.conn.getSend() != nil {
  1144  		t.Fatalf("foreigner message sent to authed user")
  1145  	}
  1146  
  1147  	// Now send to the user
  1148  	rig.mgr.Send(user.acctID, resp)
  1149  	msg := user.conn.getSend()
  1150  	if msg == nil {
  1151  		t.Fatalf("no message for authed user")
  1152  	}
  1153  	tr := new(tA)
  1154  	r, _ := msg.Response()
  1155  	err := json.Unmarshal(r.Result, tr)
  1156  	if err != nil {
  1157  		t.Fatalf("unmarshal error: %v", err)
  1158  	}
  1159  	if tr.A != 5 {
  1160  		t.Fatalf("expected A = 5, got A = %d", tr.A)
  1161  	}
  1162  
  1163  	// Send a request to a foreigner
  1164  	rig.mgr.Request(foreigner.acctID, req, func(comms.Link, *msgjson.Message) {})
  1165  	if foreigner.conn.getReq() != nil {
  1166  		t.Fatalf("request magically got through to foreigner")
  1167  	}
  1168  	if user.conn.getReq() != nil {
  1169  		t.Fatalf("foreigner request sent to authed user")
  1170  	}
  1171  
  1172  	// Send a request to an authed user.
  1173  	rig.mgr.Request(user.acctID, req, func(comms.Link, *msgjson.Message) {})
  1174  	treq := user.conn.getReq()
  1175  	if treq == nil {
  1176  		t.Fatalf("no request for user")
  1177  	}
  1178  
  1179  	tr = new(tA)
  1180  	err = json.Unmarshal(treq.msg.Payload, tr)
  1181  	if err != nil {
  1182  		t.Fatalf("request unmarshal error: %v", err)
  1183  	}
  1184  	if tr.A != 10 {
  1185  		t.Fatalf("expected A = 10, got A = %d", tr.A)
  1186  	}
  1187  }
  1188  
  1189  func TestConnectErrors(t *testing.T) {
  1190  	user := tNewUser(t)
  1191  	rig.storage.acct = nil
  1192  	rig.signer.sig = user.randomSignature()
  1193  
  1194  	ensureErr := makeEnsureErr(t)
  1195  
  1196  	// Test an invalid json payload
  1197  	msg, err := msgjson.NewRequest(comms.NextID(), "testreq", nil)
  1198  	if err != nil {
  1199  		t.Fatalf("NewRequest error for invalid payload: %v", err)
  1200  	}
  1201  	msg.Payload = []byte(`?`)
  1202  	rpcErr := rig.mgr.handleConnect(user.conn, msg)
  1203  	ensureErr(rpcErr, "invalid payload", msgjson.RPCParseError)
  1204  
  1205  	connect := tNewConnect(user)
  1206  	encodeMsg := func() {
  1207  		msg, err = msgjson.NewRequest(comms.NextID(), "testreq", connect)
  1208  		if err != nil {
  1209  			t.Fatalf("NewRequest error for bad account ID: %v", err)
  1210  		}
  1211  	}
  1212  	// connect with an invalid ID
  1213  	connect.AccountID = []byte{0x01, 0x02, 0x03, 0x04}
  1214  	encodeMsg()
  1215  	rpcErr = rig.mgr.handleConnect(user.conn, msg)
  1216  	ensureErr(rpcErr, "invalid account ID", msgjson.AuthenticationError)
  1217  	connect.AccountID = user.acctID[:]
  1218  
  1219  	// user unknown to storage
  1220  	encodeMsg()
  1221  	rpcErr = rig.mgr.handleConnect(user.conn, msg)
  1222  	ensureErr(rpcErr, "account unknown to storage", msgjson.AccountNotFoundError)
  1223  	rig.storage.acct = &account.Account{ID: user.acctID, PubKey: user.privKey.PubKey()}
  1224  
  1225  	// bad signature
  1226  	connect.SetSig([]byte{0x09, 0x08})
  1227  	encodeMsg()
  1228  	rpcErr = rig.mgr.handleConnect(user.conn, msg)
  1229  	ensureErr(rpcErr, "bad signature", msgjson.SignatureError)
  1230  
  1231  	// A send error should not return an error, but the client should not be
  1232  	// saved to the map.
  1233  	// need to "register" the user first
  1234  	msgBytes := connect.Serialize()
  1235  	connect.SetSig(signMsg(user.privKey, msgBytes))
  1236  	encodeMsg()
  1237  	user.conn.sendErr = fmt.Errorf("test error")
  1238  	rpcErr = rig.mgr.handleConnect(user.conn, msg)
  1239  	if rpcErr != nil {
  1240  		t.Fatalf("non-nil msgjson.Error after send error: %s", rpcErr.Message)
  1241  	}
  1242  	user.conn.sendErr = nil
  1243  	if rig.mgr.user(user.acctID) != nil {
  1244  		t.Fatalf("user registered with send error")
  1245  	}
  1246  	// clear the response
  1247  	if user.conn.getSend() == nil {
  1248  		t.Fatalf("no response to clear")
  1249  	}
  1250  
  1251  	// success
  1252  	rpcErr = rig.mgr.handleConnect(user.conn, msg)
  1253  	if rpcErr != nil {
  1254  		t.Fatalf("error for good connect: %s", rpcErr.Message)
  1255  	}
  1256  	// clear the response
  1257  	if user.conn.getSend() == nil {
  1258  		t.Fatalf("no response to clear")
  1259  	}
  1260  }
  1261  
  1262  func TestHandleResponse(t *testing.T) {
  1263  	user := tNewUser(t)
  1264  	rig.signer.sig = user.randomSignature()
  1265  	connectUser(t, user)
  1266  	foreigner := tNewUser(t)
  1267  	unknownResponse, err := msgjson.NewResponse(comms.NextID(), 10, nil)
  1268  	if err != nil {
  1269  		t.Fatalf("error encoding unknown response: %v", err)
  1270  	}
  1271  
  1272  	// test foreigner. Really just want to make sure that this returns before
  1273  	// trying to run a nil handler function, which would panic.
  1274  	rig.mgr.handleResponse(foreigner.conn, unknownResponse)
  1275  
  1276  	// test for a missing handler
  1277  	rig.mgr.handleResponse(user.conn, unknownResponse)
  1278  	m := user.conn.getSend()
  1279  	if m == nil {
  1280  		t.Fatalf("no error sent for unknown response")
  1281  	}
  1282  	resp, _ := m.Response()
  1283  	if resp.Error == nil {
  1284  		t.Fatalf("error not set in response for unknown response")
  1285  	}
  1286  	if resp.Error.Code != msgjson.UnknownResponseID {
  1287  		t.Fatalf("wrong error code for unknown response. expected %d, got %d",
  1288  			msgjson.UnknownResponseID, resp.Error.Code)
  1289  	}
  1290  
  1291  	// Check that expired response handlers are removed from the map.
  1292  	client := rig.mgr.user(user.acctID)
  1293  	if client == nil {
  1294  		t.Fatalf("client not found")
  1295  	}
  1296  
  1297  	newID := comms.NextID()
  1298  	client.logReq(newID, func(comms.Link, *msgjson.Message) {},
  1299  		0, func() { t.Log("expired (ok)") })
  1300  	// Wait until response handler expires.
  1301  	if waitFor(func() bool {
  1302  		client.mtx.Lock()
  1303  		defer client.mtx.Unlock()
  1304  		return len(client.respHandlers) == 0
  1305  	}, 10*time.Second) {
  1306  		t.Fatalf("expected 0 response handlers, found %d", len(client.respHandlers))
  1307  	}
  1308  	client.mtx.Lock()
  1309  	if client.respHandlers[newID] != nil {
  1310  		t.Fatalf("response handler should have been expired")
  1311  	}
  1312  	client.mtx.Unlock()
  1313  
  1314  	// After logging a new request, there should still be exactly one response handler
  1315  	// present. A short sleep is added to give a chance for clean-up running in a
  1316  	// separate go-routine to finish before we continue asserting on the result.
  1317  	newID = comms.NextID()
  1318  	client.logReq(newID, func(comms.Link, *msgjson.Message) {}, time.Hour, noop)
  1319  	time.Sleep(time.Millisecond)
  1320  	client.mtx.Lock()
  1321  	if len(client.respHandlers) != 1 {
  1322  		t.Fatalf("expected 1 response handler, found %d", len(client.respHandlers))
  1323  	}
  1324  	if client.respHandlers[newID] == nil {
  1325  		t.Fatalf("wrong response handler left after cleanup cycle")
  1326  	}
  1327  	client.mtx.Unlock()
  1328  }
  1329  
  1330  func TestAuthManager_RecordCancel_RecordCompletedOrder(t *testing.T) {
  1331  	user := tNewUser(t)
  1332  	rig.signer.sig = user.randomSignature()
  1333  	connectUser(t, user)
  1334  
  1335  	client := rig.mgr.user(user.acctID)
  1336  	if client == nil {
  1337  		t.Fatalf("client not found")
  1338  	}
  1339  
  1340  	newOrderID := func() (oid order.OrderID) {
  1341  		rand.Read(oid[:])
  1342  		return
  1343  	}
  1344  
  1345  	orderOutcomes := rig.mgr.orderOutcomes[user.acctID]
  1346  
  1347  	oid := newOrderID()
  1348  	tCompleted := unixMsNow()
  1349  	rig.mgr.RecordCompletedOrder(user.acctID, oid, tCompleted)
  1350  
  1351  	total, cancels := orderOutcomes.counts()
  1352  	if total != 1 {
  1353  		t.Errorf("got %d total orders, expected %d", total, 1)
  1354  	}
  1355  	if cancels != 0 {
  1356  		t.Errorf("got %d cancels, expected %d", cancels, 0)
  1357  	}
  1358  
  1359  	checkOrd := func(ord *oidStamped, oid order.OrderID, cancel bool, timestamp int64) {
  1360  		if ord.OrderID != oid {
  1361  			t.Errorf("completed order id mismatch. got %v, expected %v",
  1362  				ord.OrderID, oid)
  1363  		}
  1364  		isCancel := ord.target != nil
  1365  		if isCancel != cancel {
  1366  			t.Errorf("order marked as cancel=%v, expected %v", isCancel, cancel)
  1367  		}
  1368  		if ord.time != timestamp {
  1369  			t.Errorf("completed order time mismatch. got %v, expected %v",
  1370  				ord.time, timestamp)
  1371  		}
  1372  	}
  1373  
  1374  	ord := orderOutcomes.orders[0]
  1375  	checkOrd(ord, oid, false, tCompleted.UnixMilli())
  1376  
  1377  	// another
  1378  	oid = newOrderID()
  1379  	tCompleted = tCompleted.Add(time.Millisecond) // newer
  1380  	rig.mgr.RecordCompletedOrder(user.acctID, oid, tCompleted)
  1381  
  1382  	total, cancels = orderOutcomes.counts()
  1383  	if total != 2 {
  1384  		t.Errorf("got %d total orders, expected %d", total, 2)
  1385  	}
  1386  	if cancels != 0 {
  1387  		t.Errorf("got %d cancels, expected %d", cancels, 0)
  1388  	}
  1389  
  1390  	ord = orderOutcomes.orders[1]
  1391  	checkOrd(ord, oid, false, tCompleted.UnixMilli())
  1392  
  1393  	// now a cancel
  1394  	coid := newOrderID()
  1395  	tCompleted = tCompleted.Add(time.Millisecond) // newer
  1396  	rig.mgr.RecordCancel(user.acctID, coid, oid, 1, tCompleted)
  1397  
  1398  	total, cancels = orderOutcomes.counts()
  1399  	if total != 3 {
  1400  		t.Errorf("got %d total orders, expected %d", total, 3)
  1401  	}
  1402  	if cancels != 1 {
  1403  		t.Errorf("got %d cancels, expected %d", cancels, 1)
  1404  	}
  1405  
  1406  	ord = orderOutcomes.orders[2]
  1407  	checkOrd(ord, coid, true, tCompleted.UnixMilli())
  1408  }
  1409  
  1410  func TestMatchStatus(t *testing.T) {
  1411  	user := tNewUser(t)
  1412  	rig.signer.sig = user.randomSignature()
  1413  	connectUser(t, user)
  1414  
  1415  	rig.storage.matchStatuses = []*db.MatchStatus{{
  1416  		Status:    order.MakerSwapCast,
  1417  		IsTaker:   true,
  1418  		MakerSwap: []byte{0x01},
  1419  	}}
  1420  
  1421  	tTxData := encode.RandomBytes(5)
  1422  	rig.mgr.txDataSources[0] = func([]byte) ([]byte, error) {
  1423  		return tTxData, nil
  1424  	}
  1425  
  1426  	reqPayload := []msgjson.MatchRequest{{MatchID: encode.RandomBytes(32)}}
  1427  
  1428  	req, _ := msgjson.NewRequest(1, msgjson.MatchStatusRoute, reqPayload)
  1429  
  1430  	getStatus := func() *msgjson.MatchStatusResult {
  1431  		msgErr := rig.mgr.handleMatchStatus(user.conn, req)
  1432  		if msgErr != nil {
  1433  			t.Fatalf("handleMatchStatus error: %v", msgErr)
  1434  		}
  1435  
  1436  		resp := user.conn.getSend()
  1437  		if resp == nil {
  1438  			t.Fatalf("no matches sent")
  1439  		}
  1440  
  1441  		statuses := []msgjson.MatchStatusResult{}
  1442  		err := resp.UnmarshalResult(&statuses)
  1443  		if err != nil {
  1444  			t.Fatalf("UnmarshalResult error: %v", err)
  1445  		}
  1446  		if len(statuses) != 1 {
  1447  			t.Fatalf("expected 1 match, got %d", len(statuses))
  1448  		}
  1449  		return &statuses[0]
  1450  	}
  1451  
  1452  	// As taker in MakerSwapCast, we expect tx data.
  1453  	status := getStatus()
  1454  	if !bytes.Equal(status.MakerTxData, tTxData) {
  1455  		t.Fatalf("wrong maker tx data. exected %x, got %s", tTxData, status.MakerTxData)
  1456  	}
  1457  
  1458  	// As maker, we don't expect any tx data.
  1459  	rig.storage.matchStatuses[0].IsTaker = false
  1460  	rig.storage.matchStatuses[0].IsMaker = true
  1461  	if len(getStatus().TakerTxData) != 0 {
  1462  		t.Fatalf("got tx data as maker in MakerSwapCast")
  1463  	}
  1464  
  1465  	// As maker in TakerSwapCast, we do expect tx data.
  1466  	rig.storage.matchStatuses[0].Status = order.TakerSwapCast
  1467  	rig.storage.matchStatuses[0].TakerSwap = []byte{0x01}
  1468  	txData := getStatus().TakerTxData
  1469  	if !bytes.Equal(txData, tTxData) {
  1470  		t.Fatalf("wrong taker tx data. exected %x, got %s", tTxData, txData)
  1471  	}
  1472  
  1473  	reqPayload[0].MatchID = []byte{}
  1474  	req, _ = msgjson.NewRequest(1, msgjson.MatchStatusRoute, reqPayload)
  1475  	msgErr := rig.mgr.handleMatchStatus(user.conn, req)
  1476  	if msgErr == nil {
  1477  		t.Fatalf("no error for bad match ID")
  1478  	}
  1479  }
  1480  
  1481  func TestOrderStatus(t *testing.T) {
  1482  	user := tNewUser(t)
  1483  	rig.signer.sig = user.randomSignature()
  1484  	connectUser(t, user)
  1485  
  1486  	rig.storage.orderStatuses = []*db.OrderStatus{{}}
  1487  
  1488  	reqPayload := []msgjson.OrderStatusRequest{
  1489  		{
  1490  			OrderID: encode.RandomBytes(order.OrderIDSize),
  1491  		},
  1492  	}
  1493  
  1494  	req, _ := msgjson.NewRequest(1, msgjson.OrderStatusRoute, reqPayload)
  1495  
  1496  	msgErr := rig.mgr.handleOrderStatus(user.conn, req)
  1497  	if msgErr != nil {
  1498  		t.Fatalf("handleOrderStatus error: %v", msgErr)
  1499  	}
  1500  
  1501  	resp := user.conn.getSend()
  1502  	if resp == nil {
  1503  		t.Fatalf("no orders sent")
  1504  	}
  1505  
  1506  	var statuses []*msgjson.OrderStatus
  1507  	err := resp.UnmarshalResult(&statuses)
  1508  	if err != nil {
  1509  		t.Fatalf("UnmarshalResult error: %v", err)
  1510  	}
  1511  	if len(statuses) != 1 {
  1512  		t.Fatalf("expected 1 order, got %d", len(statuses))
  1513  	}
  1514  
  1515  	reqPayload[0].OrderID = []byte{}
  1516  	req, _ = msgjson.NewRequest(1, msgjson.OrderStatusRoute, reqPayload)
  1517  	msgErr = rig.mgr.handleOrderStatus(user.conn, req)
  1518  	if msgErr == nil {
  1519  		t.Fatalf("no error for bad order ID")
  1520  	}
  1521  }
  1522  
  1523  func Test_checkSigS256(t *testing.T) {
  1524  	sig := []byte{0x30, 0, 0x02, 0x01, 9, 0x2, 0x01, 10}
  1525  	ecdsa.ParseDERSignature(sig) // panic on line 132: sigStr[2] != 0x02 after trimming to sigStr[:(1+2)]
  1526  
  1527  	sig = []byte{0x30, 1, 0x02, 0x01, 9, 0x2, 0x01, 10}
  1528  	ecdsa.ParseDERSignature(sig) // panic on line 139: rLen := int(sigStr[index]) with index=3 and len = 3
  1529  }