decred.org/dcrdex@v1.0.5/server/market/routers_test.go (about)

     1  package market
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"crypto/sha256"
     7  	"encoding/hex"
     8  	"encoding/json"
     9  	"fmt"
    10  	"math/rand"
    11  	"os"
    12  	"sync"
    13  	"sync/atomic"
    14  	"testing"
    15  	"time"
    16  
    17  	"decred.org/dcrdex/dex"
    18  	"decred.org/dcrdex/dex/calc"
    19  	"decred.org/dcrdex/dex/msgjson"
    20  	"decred.org/dcrdex/dex/order"
    21  	ordertest "decred.org/dcrdex/dex/order/test"
    22  	"decred.org/dcrdex/server/account"
    23  	"decred.org/dcrdex/server/asset"
    24  	"decred.org/dcrdex/server/auth"
    25  	"decred.org/dcrdex/server/book"
    26  	"decred.org/dcrdex/server/comms"
    27  	"decred.org/dcrdex/server/db"
    28  	"decred.org/dcrdex/server/matcher"
    29  	"decred.org/dcrdex/server/swap"
    30  	"github.com/decred/dcrd/dcrec/secp256k1/v4"
    31  	"github.com/decred/dcrd/dcrec/secp256k1/v4/ecdsa"
    32  	"github.com/decred/slog"
    33  )
    34  
    35  const (
    36  	dummySize    = 50
    37  	tInitTxSize  = 150
    38  	tRedeemSize  = 50
    39  	btcLotSize   = 10_0000 // 0.001
    40  	btcRateStep  = 1_000
    41  	dcrLotSize   = 1000_0000 // 0.1
    42  	dcrRateStep  = 100_000
    43  	initLotLimit = 2
    44  	btcID        = 0
    45  	dcrID        = 42
    46  	ltcID        = 2
    47  	dogeID       = 3
    48  	btcAddr      = "18Zpft83eov56iESWuPpV8XFLJ1b8gMZy7"
    49  	dcrAddr      = "DsYXjAK3UiTVN9js8v9G21iRbr2wPty7f12"
    50  	mktName1     = "btc_ltc"
    51  	mkt1BaseRate = 5e7
    52  	mktName2     = "dcr_doge"
    53  	mkt2BaseRate = 8e9
    54  	mktName3     = "dcr_btc"
    55  	mkt3BaseRate = 3e9
    56  
    57  	clientPreimageDelay = 75 * time.Millisecond
    58  )
    59  
    60  var (
    61  	oRig       *tOrderRig
    62  	dummyError = fmt.Errorf("expected test error")
    63  	testCtx    context.Context
    64  	rig        *testRig
    65  	mkt1       = &ordertest.Market{
    66  		Base:    0, // BTC
    67  		Quote:   2, // LTC
    68  		LotSize: btcLotSize,
    69  	}
    70  	mkt2 = &ordertest.Market{
    71  		Base:    42, // DCR
    72  		Quote:   3,  // DOGE
    73  		LotSize: dcrLotSize,
    74  	}
    75  	mkt3 = &ordertest.Market{
    76  		Base:    42, // DCR
    77  		Quote:   0,  // BTC
    78  		LotSize: dcrLotSize,
    79  	}
    80  	buyer1 = &ordertest.Writer{
    81  		Addr:   "LSdTvMHRm8sScqwCi6x9wzYQae8JeZhx6y", // LTC receiving address
    82  		Acct:   ordertest.NextAccount(),
    83  		Sell:   false,
    84  		Market: mkt1,
    85  	}
    86  	seller1 = &ordertest.Writer{
    87  		Addr:   "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa", // BTC
    88  		Acct:   ordertest.NextAccount(),
    89  		Sell:   true,
    90  		Market: mkt1,
    91  	}
    92  	buyer2 = &ordertest.Writer{
    93  		Addr:   "DsaAKsMvZ6HrqhmbhLjV9qVbPkkzF5daowT", // DCR
    94  		Acct:   ordertest.NextAccount(),
    95  		Sell:   false,
    96  		Market: mkt2,
    97  	}
    98  	seller2 = &ordertest.Writer{
    99  		Addr:   "DE53BHmWWEi4G5a3REEJsjMpgNTnzKT98a", // DOGE
   100  		Acct:   ordertest.NextAccount(),
   101  		Sell:   true,
   102  		Market: mkt2,
   103  	}
   104  	buyer3 = &ordertest.Writer{
   105  		Addr:   "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa", // BTC
   106  		Acct:   ordertest.NextAccount(),
   107  		Sell:   false,
   108  		Market: mkt3,
   109  	}
   110  	seller3 = &ordertest.Writer{
   111  		Addr:   "DsaAKsMvZ6HrqhmbhLjV9qVbPkkzF5daowT", // DCR
   112  		Acct:   ordertest.NextAccount(),
   113  		Sell:   true,
   114  		Market: mkt3,
   115  	}
   116  )
   117  
   118  var rnd = rand.New(rand.NewSource(1))
   119  
   120  func nowMs() time.Time {
   121  	return time.Now().Truncate(time.Millisecond).UTC()
   122  }
   123  
   124  // The AuthManager handles client-related actions, including authorization and
   125  // communications.
   126  type TAuth struct {
   127  	authErr            error
   128  	sendsMtx           sync.Mutex
   129  	sends              []*msgjson.Message
   130  	sent               chan *msgjson.Error
   131  	piMtx              sync.Mutex
   132  	preimagesByMsgID   map[uint64]order.Preimage
   133  	preimagesByOrdID   map[string]order.Preimage
   134  	handlePreimageDone chan struct{}
   135  	handleMatchDone    chan *msgjson.Message
   136  	suspensions        map[account.AccountID]bool
   137  	canceledOrder      order.OrderID
   138  	cancelOrder        order.OrderID
   139  	rep                struct {
   140  		tier            int64
   141  		score, maxScore int32
   142  		err             error
   143  	}
   144  }
   145  
   146  func (a *TAuth) Route(route string, handler func(account.AccountID, *msgjson.Message) *msgjson.Error) {
   147  	log.Infof("Route for %s", route)
   148  }
   149  func (a *TAuth) Suspended(user account.AccountID) (found, suspended bool) {
   150  	suspended, found = a.suspensions[user]
   151  	return // TODO: test suspended account handling (no trades, just cancels)
   152  }
   153  func (a *TAuth) Auth(user account.AccountID, msg, sig []byte) error {
   154  	//log.Infof("Auth for user %v", user)
   155  	return a.authErr
   156  }
   157  func (a *TAuth) Sign(...msgjson.Signable) {}
   158  func (a *TAuth) Send(user account.AccountID, msg *msgjson.Message) error {
   159  	//log.Infof("Send for user %v. Message: %v", user, msg)
   160  	a.sendsMtx.Lock()
   161  	a.sends = append(a.sends, msg)
   162  	a.sendsMtx.Unlock()
   163  
   164  	var msgErr *msgjson.Error
   165  	var payload *msgjson.ResponsePayload
   166  	if msg.Type == msgjson.Response {
   167  		var err error
   168  		payload, err = msg.Response()
   169  		if err != nil {
   170  			return fmt.Errorf("Failed to unmarshal message ResponsePayload: %w", err)
   171  		}
   172  		msgErr = payload.Error
   173  	}
   174  
   175  	a.piMtx.Lock()
   176  	defer a.piMtx.Unlock()
   177  	preimage, ok := a.preimagesByMsgID[msg.ID]
   178  	if ok && payload != nil {
   179  		log.Infof("preimage found for msg id %v: %x", msg.ID, preimage)
   180  		if payload.Error != nil {
   181  			return fmt.Errorf("invalid response: %v", payload.Error.Message)
   182  		}
   183  		ordRes := new(msgjson.OrderResult)
   184  		err := json.Unmarshal(payload.Result, ordRes)
   185  		if err != nil {
   186  			return fmt.Errorf("Failed to unmarshal message Payload into OrderResult: %w", err)
   187  		}
   188  		log.Debugf("setting preimage for order %v", ordRes.OrderID)
   189  		a.preimagesByOrdID[ordRes.OrderID.String()] = preimage
   190  	}
   191  
   192  	if a.sent != nil {
   193  		a.sent <- msgErr
   194  	}
   195  
   196  	return nil
   197  }
   198  func (a *TAuth) getSend() *msgjson.Message {
   199  	a.sendsMtx.Lock()
   200  	defer a.sendsMtx.Unlock()
   201  	if len(a.sends) == 0 {
   202  		return nil
   203  	}
   204  	msg := a.sends[0]
   205  	a.sends = a.sends[1:]
   206  	return msg
   207  }
   208  func (a *TAuth) Request(user account.AccountID, msg *msgjson.Message, f func(comms.Link, *msgjson.Message)) error {
   209  	return a.RequestWithTimeout(user, msg, f, time.Hour, func() {})
   210  }
   211  func (a *TAuth) RequestWithTimeout(user account.AccountID, msg *msgjson.Message, f func(comms.Link, *msgjson.Message), expDur time.Duration, exp func()) error {
   212  	log.Infof("Request for user %v", user)
   213  	// Emulate the client.
   214  	if msg.Route == msgjson.PreimageRoute {
   215  		// Respond with the preimage for the referenced order id in the
   216  		// PreimageRequest.
   217  		var piReq msgjson.PreimageRequest
   218  		json.Unmarshal(msg.Payload, &piReq)
   219  		log.Info("order id:", piReq.OrderID.String())
   220  		a.piMtx.Lock()
   221  		pi, found := a.preimagesByOrdID[piReq.OrderID.String()]
   222  		a.piMtx.Unlock()
   223  		if !found {
   224  			// If we have no preimage for this order, then we've decided to
   225  			// expire the response after the expire duration.
   226  			time.AfterFunc(expDur, exp)
   227  		}
   228  		log.Infof("found preimage: %x", pi)
   229  		piMsg := &msgjson.PreimageResponse{
   230  			Preimage: pi[:],
   231  		}
   232  		resp, _ := msgjson.NewResponse(5, piMsg, nil)
   233  		go func() {
   234  			// Simulate network latency before handling the response.
   235  			time.Sleep(clientPreimageDelay)
   236  			f(nil, resp)
   237  			if a.handlePreimageDone != nil { // this tests wants to know when this is done
   238  				a.handlePreimageDone <- struct{}{}
   239  			}
   240  		}()
   241  	} else if msg.Route == msgjson.MatchRoute {
   242  		if a.handleMatchDone != nil {
   243  			a.handleMatchDone <- msg
   244  		}
   245  	}
   246  	return nil
   247  }
   248  
   249  func (a *TAuth) PreimageSuccess(user account.AccountID, refTime time.Time, oid order.OrderID) {}
   250  func (a *TAuth) MissedPreimage(user account.AccountID, refTime time.Time, oid order.OrderID)  {}
   251  func (a *TAuth) SwapSuccess(user account.AccountID, mmid db.MarketMatchID, value uint64, refTime time.Time) {
   252  }
   253  func (a *TAuth) Inaction(user account.AccountID, step auth.NoActionStep, mmid db.MarketMatchID, matchValue uint64, refTime time.Time, oid order.OrderID) {
   254  }
   255  func (a *TAuth) UserReputation(user account.AccountID) (tier int64, score, maxScore int32, err error) {
   256  	if a.rep.maxScore == 0 {
   257  		return 1, 30, 60, a.rep.err
   258  	}
   259  	return a.rep.tier, a.rep.score, a.rep.maxScore, a.rep.err
   260  }
   261  func (a *TAuth) AcctStatus(user account.AccountID) (connected bool, tier int64) {
   262  	return true, 1
   263  }
   264  func (a *TAuth) RecordCompletedOrder(account.AccountID, order.OrderID, time.Time) {}
   265  func (a *TAuth) RecordCancel(aid account.AccountID, coid, oid order.OrderID, epochGap int32, t time.Time) {
   266  	a.cancelOrder = coid
   267  	a.canceledOrder = oid
   268  }
   269  
   270  type TMarketTunnel struct {
   271  	adds        []*orderRecord
   272  	added       chan struct{}
   273  	auth        *TAuth
   274  	midGap      uint64
   275  	lotSize     uint64
   276  	rateStep    uint64
   277  	mbBuffer    float64
   278  	epochIdx    uint64
   279  	epochDur    uint64
   280  	locked      bool
   281  	cancelable  bool
   282  	acctQty     uint64
   283  	acctLots    uint64
   284  	acctRedeems int
   285  	base, quote uint32
   286  	parcels     float64
   287  }
   288  
   289  func tNewMarket(auth *TAuth) *TMarketTunnel {
   290  	return &TMarketTunnel{
   291  		adds:       make([]*orderRecord, 0),
   292  		auth:       auth,
   293  		midGap:     btcRateStep * 1000,
   294  		lotSize:    dcrLotSize,
   295  		rateStep:   btcRateStep,
   296  		mbBuffer:   1.5, // 150% of lot size
   297  		cancelable: true,
   298  		epochIdx:   1573773894,
   299  		epochDur:   60_000,
   300  	}
   301  }
   302  
   303  func (m *TMarketTunnel) SubmitOrder(o *orderRecord) error {
   304  	// set the server time
   305  	now := nowMs()
   306  	o.order.SetTime(now)
   307  
   308  	m.adds = append(m.adds, o)
   309  
   310  	// Send the order, but skip the signature
   311  	oid := o.order.ID()
   312  	resp, _ := msgjson.NewResponse(1, &msgjson.OrderResult{
   313  		Sig:        msgjson.Bytes{},
   314  		OrderID:    oid[:],
   315  		ServerTime: uint64(now.UnixMilli()),
   316  	}, nil)
   317  	err := m.auth.Send(account.AccountID{}, resp)
   318  	if err != nil {
   319  		log.Debug("Send:", err)
   320  	}
   321  
   322  	if m.added != nil {
   323  		m.added <- struct{}{}
   324  	}
   325  
   326  	return nil
   327  }
   328  
   329  func (m *TMarketTunnel) MidGap() uint64 {
   330  	return m.midGap
   331  }
   332  
   333  func (m *TMarketTunnel) LotSize() uint64 {
   334  	return m.lotSize
   335  }
   336  
   337  func (m *TMarketTunnel) RateStep() uint64 {
   338  	return m.rateStep
   339  }
   340  
   341  func (m *TMarketTunnel) CoinLocked(assetID uint32, coinid order.CoinID) bool {
   342  	return m.locked
   343  }
   344  
   345  func (m *TMarketTunnel) MarketBuyBuffer() float64 {
   346  	return m.mbBuffer
   347  }
   348  
   349  func (m *TMarketTunnel) pop() *orderRecord {
   350  	if len(m.adds) == 0 {
   351  		return nil
   352  	}
   353  	o := m.adds[0]
   354  	m.adds = m.adds[1:]
   355  	return o
   356  }
   357  
   358  func (m *TMarketTunnel) Cancelable(order.OrderID) bool {
   359  	return m.cancelable
   360  }
   361  
   362  func (m *TMarketTunnel) Suspend(asSoonAs time.Time, persistBook bool) (finalEpochIdx int64, finalEpochEnd time.Time) {
   363  	// no suspension
   364  	return -1, time.Time{}
   365  }
   366  
   367  func (m *TMarketTunnel) Running() bool {
   368  	return true
   369  }
   370  
   371  func (m *TMarketTunnel) CheckUnfilled(assetID uint32, user account.AccountID) (unbooked []*order.LimitOrder) {
   372  	return
   373  }
   374  
   375  func (m *TMarketTunnel) AccountPending(acctAddr string, assetID uint32) (qty, lots uint64, redeems int) {
   376  	return m.acctQty, m.acctLots, m.acctRedeems
   377  }
   378  
   379  func (m *TMarketTunnel) Base() uint32 {
   380  	return m.base
   381  }
   382  
   383  func (m *TMarketTunnel) Quote() uint32 {
   384  	return m.quote
   385  }
   386  
   387  func (m *TMarketTunnel) Parcels(account.AccountID, uint64) float64 {
   388  	return m.parcels
   389  }
   390  
   391  type TBackend struct {
   392  	utxoErr        error
   393  	utxos          map[string]uint64
   394  	addrChecks     bool
   395  	synced         uint32
   396  	syncedErr      error
   397  	confsMinus2    int64
   398  	invalidFeeRate bool
   399  	unfunded       bool
   400  }
   401  
   402  func tNewUTXOBackend() *tUTXOBackend {
   403  	return &tUTXOBackend{
   404  		TBackend: &TBackend{
   405  			utxos:      make(map[string]uint64),
   406  			addrChecks: true,
   407  			synced:     1,
   408  		},
   409  	}
   410  }
   411  
   412  func tNewAccountBackend() *tAccountBackend {
   413  	return &tAccountBackend{
   414  		TBackend: &TBackend{
   415  			utxos:      make(map[string]uint64),
   416  			addrChecks: true,
   417  			synced:     1,
   418  		},
   419  	}
   420  }
   421  
   422  func (b *TBackend) utxo(coinID []byte) (*tUTXO, error) {
   423  	str := hex.EncodeToString(coinID)
   424  	v := b.utxos[str]
   425  	if v == 0 && b.utxoErr == nil {
   426  		return nil, asset.CoinNotFoundError // try again for waiters
   427  	}
   428  	return &tUTXO{
   429  		val:     v,
   430  		decoded: str,
   431  		confs:   b.confsMinus2 + 2,
   432  		feeRate: 20,
   433  	}, b.utxoErr
   434  }
   435  func (b *TBackend) Contract(coinID, redeemScript []byte) (*asset.Contract, error) {
   436  	c, err := b.utxo(coinID)
   437  	if err != nil {
   438  		return nil, err
   439  	}
   440  	return &asset.Contract{Coin: c}, nil
   441  }
   442  func (b *TBackend) Redemption(redemptionID, contractID, contractData []byte) (asset.Coin, error) {
   443  	return b.utxo(redemptionID)
   444  }
   445  func (b *TBackend) BlockChannel(size int) <-chan *asset.BlockUpdate { return nil }
   446  func (b *TBackend) CheckSwapAddress(string) bool                    { return b.addrChecks }
   447  func (b *TBackend) addUTXO(coin *msgjson.Coin, val uint64) {
   448  	b.utxos[hex.EncodeToString(coin.ID)] = val
   449  }
   450  func (b *TBackend) Connect(context.Context) (*sync.WaitGroup, error) { return nil, nil }
   451  func (b *TBackend) ValidateCoinID(coinID []byte) (string, error) {
   452  	return "", nil
   453  }
   454  func (b *TBackend) ValidateContract(contract []byte) error {
   455  	return nil
   456  }
   457  
   458  func (b *TBackend) ValidateSecret(secret, contract []byte) bool { return true }
   459  func (b *TBackend) FeeRate(context.Context) (uint64, error) {
   460  	return 9, nil
   461  }
   462  
   463  func (b *TBackend) Synced() (bool, error) {
   464  	if b.syncedErr != nil {
   465  		return false, b.syncedErr
   466  	}
   467  	return atomic.LoadUint32(&oRig.dcr.synced) == 1, nil
   468  }
   469  
   470  func (b *TBackend) TxData([]byte) ([]byte, error) { return nil, nil }
   471  func (*TBackend) Info() *asset.BackendInfo {
   472  	return &asset.BackendInfo{}
   473  }
   474  func (b *TBackend) ValidateFeeRate(asset.Coin, uint64) bool {
   475  	return !b.invalidFeeRate
   476  }
   477  
   478  type tUTXOBackend struct {
   479  	*TBackend
   480  }
   481  
   482  func (b *tUTXOBackend) FundingCoin(ctx context.Context, coinID, redeemScript []byte) (asset.FundingCoin, error) {
   483  	return b.utxo(coinID)
   484  }
   485  
   486  func (b *tUTXOBackend) VerifyUnspentCoin(_ context.Context, coinID []byte) error {
   487  	_, err := b.utxo(coinID)
   488  	return err
   489  }
   490  
   491  func (b *tUTXOBackend) ValidateOrderFunding(swapVal, valSum, inputCount, inputsSize, maxSwaps uint64, nfo *dex.Asset) bool {
   492  	return !b.unfunded
   493  }
   494  
   495  type tAccountBackend struct {
   496  	*TBackend
   497  	bal    uint64
   498  	balErr error
   499  	sigErr error
   500  }
   501  
   502  func (b *tAccountBackend) AccountBalance(addr string) (uint64, error) {
   503  	return b.bal, b.balErr
   504  }
   505  
   506  func (b *tAccountBackend) ValidateSignature(addr string, pubkey, msg, sig []byte) error {
   507  	return b.sigErr
   508  }
   509  
   510  func (b *tAccountBackend) InitTxSize() uint64 {
   511  	return tInitTxSize
   512  }
   513  
   514  func (b *tAccountBackend) RedeemSize() uint64 {
   515  	return tRedeemSize
   516  }
   517  
   518  type tUTXO struct {
   519  	val     uint64
   520  	decoded string
   521  	feeRate uint64
   522  	confs   int64
   523  }
   524  
   525  var utxoAuthErr error
   526  
   527  func (u *tUTXO) Coin() asset.Coin                             { return u }
   528  func (u *tUTXO) Confirmations(context.Context) (int64, error) { return u.confs, nil }
   529  func (u *tUTXO) Auth(pubkeys, sigs [][]byte, msg []byte) error {
   530  	return utxoAuthErr
   531  }
   532  func (u *tUTXO) SpendSize() uint32               { return dummySize }
   533  func (u *tUTXO) ID() []byte                      { return nil }
   534  func (u *tUTXO) TxID() string                    { return "" }
   535  func (u *tUTXO) String() string                  { return u.decoded }
   536  func (u *tUTXO) SpendsCoin([]byte) (bool, error) { return true, nil }
   537  func (u *tUTXO) Value() uint64                   { return u.val }
   538  func (u *tUTXO) FeeRate() uint64                 { return 0 }
   539  
   540  type tUser struct {
   541  	acct    account.AccountID
   542  	privKey *secp256k1.PrivateKey
   543  }
   544  
   545  type tMatchNegotiator struct {
   546  	qty, swaps map[uint32]uint64
   547  	redeems    map[uint32]int
   548  }
   549  
   550  func tNewMatchNegotiator() *tMatchNegotiator {
   551  	return &tMatchNegotiator{
   552  		qty:     make(map[uint32]uint64),
   553  		swaps:   make(map[uint32]uint64),
   554  		redeems: make(map[uint32]int),
   555  	}
   556  }
   557  
   558  func (m *tMatchNegotiator) AccountStats(acctAddr string, assetID uint32) (qty, swaps uint64, redeems int) {
   559  	return m.qty[assetID], m.swaps[assetID], m.redeems[assetID]
   560  }
   561  
   562  type tOrderRig struct {
   563  	btc             *tUTXOBackend
   564  	dcr             *tUTXOBackend
   565  	eth             *tAccountBackend
   566  	polygon         *tAccountBackend
   567  	user            *tUser
   568  	auth            *TAuth
   569  	market          *TMarketTunnel
   570  	router          *OrderRouter
   571  	matchNegotiator *tMatchNegotiator
   572  	swapper         *tMatchSwapper
   573  }
   574  
   575  func (rig *tOrderRig) signedUTXO(id int, val uint64, numSigs int) *msgjson.Coin {
   576  	u := rig.user
   577  	coin := &msgjson.Coin{
   578  		ID: randomBytes(36),
   579  	}
   580  	pk := u.privKey.PubKey().SerializeCompressed()
   581  	for i := 0; i < numSigs; i++ {
   582  		msgHash := sha256.Sum256(coin.ID)
   583  		sig := ecdsa.Sign(u.privKey, msgHash[:])
   584  		coin.Sigs = append(coin.Sigs, sig.Serialize())
   585  		coin.PubKeys = append(coin.PubKeys, pk)
   586  	}
   587  	switch id {
   588  	case btcID:
   589  		rig.btc.addUTXO(coin, val)
   590  	case dcrID:
   591  		rig.dcr.addUTXO(coin, val)
   592  	}
   593  	return coin
   594  }
   595  
   596  var assetBTC = &asset.BackedAsset{
   597  	Asset: dex.Asset{
   598  		ID:         0,
   599  		Symbol:     "btc",
   600  		MaxFeeRate: 14,
   601  		SwapConf:   2,
   602  	},
   603  }
   604  
   605  var assetDCR = &asset.BackedAsset{
   606  	Asset: dex.Asset{
   607  		ID:         42,
   608  		Symbol:     "dcr",
   609  		MaxFeeRate: 10,
   610  		SwapConf:   2,
   611  	},
   612  }
   613  
   614  var assetETH = &asset.BackedAsset{
   615  	Asset: dex.Asset{
   616  		ID:         60,
   617  		Symbol:     "eth",
   618  		MaxFeeRate: 10,
   619  		SwapConf:   2,
   620  	},
   621  }
   622  
   623  var assetToken = &asset.BackedAsset{
   624  	Asset: dex.Asset{
   625  		ID:         60001,
   626  		Symbol:     "usdc.eth",
   627  		MaxFeeRate: 12,
   628  		SwapConf:   2,
   629  	},
   630  }
   631  
   632  var assetMATIC = &asset.BackedAsset{
   633  	Asset: dex.Asset{
   634  		ID:         966,
   635  		Symbol:     "polygon",
   636  		MaxFeeRate: 10,
   637  		SwapConf:   2,
   638  	},
   639  }
   640  
   641  var assetUnknown = &asset.BackedAsset{
   642  	Asset: dex.Asset{
   643  		ID:         54321,
   644  		Symbol:     "buk",
   645  		MaxFeeRate: 10,
   646  		SwapConf:   0,
   647  	},
   648  }
   649  
   650  func randomBytes(len int) []byte {
   651  	bytes := make([]byte, len)
   652  	rnd.Read(bytes)
   653  	return bytes
   654  }
   655  
   656  func makeEnsureErr(t *testing.T) func(tag string, rpcErr *msgjson.Error, code int) {
   657  	return func(tag string, rpcErr *msgjson.Error, code int) {
   658  		t.Helper()
   659  		if rpcErr == nil {
   660  			if code == -1 {
   661  				return
   662  			}
   663  			t.Fatalf("%s: no rpc error for code %d", tag, code)
   664  		}
   665  		if rpcErr.Code != code {
   666  			t.Fatalf("%s: wrong error code. expected %d, got %d: %s", tag, code, rpcErr.Code, rpcErr.Message)
   667  		}
   668  	}
   669  }
   670  
   671  type tFeeSource struct {
   672  	feeMinus10 int64
   673  }
   674  
   675  func (s *tFeeSource) LastRate(assetID uint32) (feeRate uint64) {
   676  	return uint64(s.feeMinus10 + 10)
   677  }
   678  
   679  type tMatchSwapper struct {
   680  	qtys map[[2]uint32]uint64
   681  }
   682  
   683  func (s *tMatchSwapper) UnsettledQuantity(user account.AccountID) map[[2]uint32]uint64 {
   684  	if s.qtys != nil {
   685  		return s.qtys
   686  	}
   687  	return make(map[[2]uint32]uint64)
   688  }
   689  
   690  func TestMain(m *testing.M) {
   691  	logger := slog.NewBackend(os.Stdout).Logger("MARKETTEST")
   692  	logger.SetLevel(slog.LevelDebug)
   693  	UseLogger(logger)
   694  	book.UseLogger(logger)
   695  	matcher.UseLogger(logger)
   696  	swap.UseLogger(logger)
   697  
   698  	ordertest.UseRand(rnd) // yuk, but we have old tests with deterministic sequences from math/rand that I'm not rewriting now
   699  
   700  	privKey, _ := secp256k1.GeneratePrivateKey()
   701  	auth := &TAuth{
   702  		sends:            make([]*msgjson.Message, 0),
   703  		preimagesByMsgID: make(map[uint64]order.Preimage),
   704  		preimagesByOrdID: make(map[string]order.Preimage),
   705  	}
   706  	matchNegotiator := tNewMatchNegotiator()
   707  	swapper := &tMatchSwapper{}
   708  
   709  	oRig = &tOrderRig{
   710  		btc:     tNewUTXOBackend(),
   711  		dcr:     tNewUTXOBackend(),
   712  		eth:     tNewAccountBackend(),
   713  		polygon: tNewAccountBackend(),
   714  		user: &tUser{
   715  			acct:    ordertest.NextAccount(),
   716  			privKey: privKey,
   717  		},
   718  		auth:            auth,
   719  		market:          tNewMarket(auth),
   720  		matchNegotiator: matchNegotiator,
   721  		swapper:         swapper,
   722  	}
   723  	assetDCR.Backend = oRig.dcr
   724  	assetBTC.Backend = oRig.btc
   725  	assetETH.Backend = oRig.eth
   726  	assetMATIC.Backend = oRig.polygon
   727  	tunnels := map[string]MarketTunnel{
   728  		"dcr_btc":     oRig.market,
   729  		"eth_btc":     oRig.market,
   730  		"dcr_eth":     oRig.market,
   731  		"eth_polygon": oRig.market,
   732  	}
   733  	pendingAccounters := make(map[string]PendingAccounter)
   734  	for name := range tunnels {
   735  		pendingAccounters[name] = oRig.market
   736  	}
   737  	assets := map[uint32]*asset.BackedAsset{
   738  		0:   assetBTC,
   739  		42:  assetDCR,
   740  		60:  assetETH,
   741  		966: assetMATIC,
   742  	}
   743  	balancer, err := NewDEXBalancer(pendingAccounters, assets, matchNegotiator)
   744  	if err != nil {
   745  		panic("NewDEXBalancer error:" + err.Error())
   746  	}
   747  	oRig.router = NewOrderRouter(&OrderRouterConfig{
   748  		AuthManager:  oRig.auth,
   749  		Assets:       assets,
   750  		Markets:      tunnels,
   751  		FeeSource:    &tFeeSource{},
   752  		DEXBalancer:  balancer,
   753  		MatchSwapper: swapper,
   754  	})
   755  	rig = newTestRig()
   756  	src1 := rig.source1
   757  	src2 := rig.source2
   758  	src3 := rig.source3
   759  	// Load up the order books up with 16 orders each.
   760  	for i := 0; i < 8; i++ {
   761  		src1.sells = append(src1.sells,
   762  			makeLO(seller1, mkRate1(1.0, 1.2), randLots(10), order.StandingTiF))
   763  		src1.buys = append(src1.buys,
   764  			makeLO(buyer1, mkRate1(0.8, 1.0), randLots(10), order.StandingTiF))
   765  		src2.sells = append(src2.sells,
   766  			makeLO(seller2, mkRate2(1.0, 1.2), randLots(10), order.StandingTiF))
   767  		src2.buys = append(src2.buys,
   768  			makeLO(buyer2, mkRate2(0.8, 1.0), randLots(10), order.StandingTiF))
   769  		src3.sells = append(src3.sells,
   770  			makeLO(seller3, mkRate3(1.0, 1.2), randLots(10), order.StandingTiF))
   771  		src3.buys = append(src3.buys,
   772  			makeLO(buyer3, mkRate3(0.8, 1.0), randLots(10), order.StandingTiF))
   773  	}
   774  	tick(100)
   775  	doIt := func() int {
   776  		// Not counted as coverage, must test Archiver constructor explicitly.
   777  		var shutdown context.CancelFunc
   778  		testCtx, shutdown = context.WithCancel(context.Background())
   779  		rig.router = NewBookRouter(rig.sources(), &tFeeSource{}, func(route string, handler comms.MsgHandler) {})
   780  		var wg sync.WaitGroup
   781  		wg.Add(1)
   782  		go func() {
   783  			rig.router.Run(testCtx)
   784  			wg.Done()
   785  		}()
   786  		wg.Add(1)
   787  		go func() {
   788  			oRig.router.Run(testCtx)
   789  			wg.Done()
   790  		}()
   791  		time.Sleep(100 * time.Millisecond) // let the router actually start in runBook
   792  		defer func() {
   793  			shutdown()
   794  			wg.Wait()
   795  		}()
   796  		return m.Run()
   797  	}
   798  	os.Exit(doIt())
   799  }
   800  
   801  // Order router tests
   802  
   803  func TestLimit(t *testing.T) {
   804  	const lots = 10
   805  	qty := uint64(dcrLotSize) * lots
   806  	rate := uint64(1000) * dcrRateStep
   807  	user := oRig.user
   808  	clientTime := nowMs()
   809  	pi := ordertest.RandomPreimage()
   810  	commit := pi.Commit()
   811  	limit := msgjson.LimitOrder{
   812  		Prefix: msgjson.Prefix{
   813  			AccountID:  user.acct[:],
   814  			Base:       dcrID,
   815  			Quote:      btcID,
   816  			OrderType:  msgjson.LimitOrderNum,
   817  			ClientTime: uint64(clientTime.UnixMilli()),
   818  			Commit:     commit[:],
   819  		},
   820  		Trade: msgjson.Trade{
   821  			Side:     msgjson.SellOrderNum,
   822  			Quantity: qty,
   823  			Coins: []*msgjson.Coin{
   824  				oRig.signedUTXO(dcrID, qty-dcrLotSize, 1),
   825  				oRig.signedUTXO(dcrID, 2*dcrLotSize, 2),
   826  			},
   827  			Address: btcAddr,
   828  		},
   829  		Rate: rate,
   830  		TiF:  msgjson.StandingOrderNum,
   831  	}
   832  	reqID := uint64(5)
   833  
   834  	ensureErr := makeEnsureErr(t)
   835  
   836  	oRig.auth.sent = make(chan *msgjson.Error, 1)
   837  	defer func() { oRig.auth.sent = nil }()
   838  
   839  	sendLimit := func() *msgjson.Error {
   840  		msg, _ := msgjson.NewRequest(reqID, msgjson.LimitRoute, limit)
   841  		err := oRig.router.handleLimit(user.acct, msg)
   842  		if err != nil {
   843  			return err
   844  		}
   845  		// wait for the async success (nil) / err (non-nil)
   846  		return <-oRig.auth.sent
   847  	}
   848  
   849  	var oRecord *orderRecord
   850  	ensureSuccess := func(tag string) {
   851  		t.Helper()
   852  		ensureErr(tag, sendLimit(), -1)
   853  		select {
   854  		case <-oRig.market.added:
   855  		case <-time.After(time.Second):
   856  			t.Fatalf("no order submitted to epoch")
   857  		}
   858  		oRecord = oRig.market.pop()
   859  		if oRecord == nil {
   860  			t.Fatalf("no order submitted to epoch")
   861  		}
   862  	}
   863  
   864  	// First just send it through and ensure there are no errors.
   865  	oRig.market.added = make(chan struct{}, 1)
   866  	defer func() { oRig.market.added = nil }()
   867  	ensureSuccess("valid order")
   868  
   869  	// Check TiF
   870  	epochOrder := oRecord.order.(*order.LimitOrder)
   871  	if epochOrder.Force != order.StandingTiF {
   872  		t.Errorf("Got force %v, expected %v (standing)", epochOrder.Force, order.StandingTiF)
   873  	}
   874  
   875  	// Now check with immediate TiF.
   876  	limit.TiF = msgjson.ImmediateOrderNum
   877  	ensureSuccess("valid immediate order")
   878  	epochOrder = oRecord.order.(*order.LimitOrder)
   879  	if epochOrder.Force != order.ImmediateTiF {
   880  		t.Errorf("Got force %v, expected %v (immediate)", epochOrder.Force, order.ImmediateTiF)
   881  	}
   882  
   883  	// Test an invalid payload.
   884  	msg := new(msgjson.Message)
   885  	msg.Payload = []byte(`?`)
   886  	rpcErr := oRig.router.handleLimit(user.acct, msg)
   887  	ensureErr("bad payload", rpcErr, msgjson.RPCParseError)
   888  
   889  	// Wrong order type marked for limit order
   890  	limit.OrderType = msgjson.MarketOrderNum
   891  	ensureErr("wrong order type", sendLimit(), msgjson.OrderParameterError)
   892  	limit.OrderType = msgjson.LimitOrderNum
   893  
   894  	testPrefixTrade(&limit.Prefix, &limit.Trade, oRig.dcr.TBackend, oRig.btc.TBackend,
   895  		func(tag string, code int) { t.Helper(); ensureErr(tag, sendLimit(), code) },
   896  	)
   897  
   898  	// Zero-conf fails fee rate validation.
   899  	oRig.dcr.confsMinus2 = -2
   900  	oRig.dcr.invalidFeeRate = true
   901  	ensureErr("low-fee zero-conf order", sendLimit(), msgjson.FundingError)
   902  	// reset
   903  	oRig.dcr.confsMinus2 = 0
   904  	oRig.dcr.invalidFeeRate = false
   905  
   906  	// Rate = 0
   907  	limit.Rate = 0
   908  	ensureErr("zero rate", sendLimit(), msgjson.OrderParameterError)
   909  	limit.Rate = rate
   910  
   911  	// non-step-multiple rate
   912  	limit.Rate = rate + (btcRateStep / 2)
   913  	ensureErr("non-step-multiple", sendLimit(), msgjson.OrderParameterError)
   914  	limit.Rate = rate
   915  
   916  	// Time-in-force incorrectly marked
   917  	limit.TiF = 0 // not msgjson.StandingOrderNum (1) or msgjson.ImmediateOrderNum (2)
   918  	ensureErr("bad tif", sendLimit(), msgjson.OrderParameterError)
   919  	limit.TiF = msgjson.StandingOrderNum
   920  
   921  	// Now switch it to a buy order, and ensure it passes
   922  	// Clear the sends cache first.
   923  	oRig.auth.sends = nil
   924  	limit.Side = msgjson.BuyOrderNum
   925  	buyUTXO := oRig.signedUTXO(btcID, matcher.BaseToQuote(rate, qty*2), 1)
   926  	limit.Coins = []*msgjson.Coin{
   927  		buyUTXO,
   928  	}
   929  	limit.Address = dcrAddr
   930  	ensureSuccess("buy order")
   931  
   932  	// Create the order manually, so that we can compare the IDs as another check
   933  	// of equivalence.
   934  	lo := &order.LimitOrder{
   935  		P: order.Prefix{
   936  			AccountID:  user.acct,
   937  			BaseAsset:  limit.Base,
   938  			QuoteAsset: limit.Quote,
   939  			OrderType:  order.LimitOrderType,
   940  			ClientTime: clientTime,
   941  			Commit:     commit,
   942  		},
   943  		T: order.Trade{
   944  			Sell:     false,
   945  			Quantity: qty,
   946  			Address:  dcrAddr,
   947  		},
   948  		Rate:  rate,
   949  		Force: order.StandingTiF,
   950  	}
   951  
   952  	// Check the utxo
   953  	epochOrder = oRecord.order.(*order.LimitOrder)
   954  	if len(epochOrder.Coins) != 1 {
   955  		t.Fatalf("expected 1 order UTXO, got %d", len(epochOrder.Coins))
   956  	}
   957  	epochUTXO := epochOrder.Coins[0]
   958  	if !bytes.Equal(epochUTXO, buyUTXO.ID) {
   959  		t.Fatalf("utxo reporting wrong txid")
   960  	}
   961  
   962  	// Now steal the Coins
   963  	lo.Coins = epochOrder.Coins
   964  
   965  	// Get the server time from the response.
   966  	respMsg := oRig.auth.getSend()
   967  	if respMsg == nil {
   968  		t.Fatalf("no response from limit order")
   969  	}
   970  	resp, _ := respMsg.Response()
   971  	result := new(msgjson.OrderResult)
   972  	err := json.Unmarshal(resp.Result, result)
   973  	if err != nil {
   974  		t.Fatalf("unmarshal error: %v", err)
   975  	}
   976  	lo.ServerTime = time.UnixMilli(int64(result.ServerTime))
   977  
   978  	// Check equivalence of IDs.
   979  	if epochOrder.ID() != lo.ID() {
   980  		t.Fatalf("failed to duplicate ID, got %v, wanted %v", lo.UID(), epochOrder.UID())
   981  	}
   982  
   983  	// Not enough funds for account-based asset.
   984  	limit.Side = msgjson.SellOrderNum
   985  	limit.Base = assetETH.ID
   986  	limit.RedeemSig = &msgjson.RedeemSig{}
   987  	reqFunds := calc.RequiredOrderFunds(qty, 0, lots, tInitTxSize, tInitTxSize, assetETH.Asset.MaxFeeRate)
   988  	oRig.eth.bal = reqFunds - 1 // 1 gwei too few
   989  	ensureErr("not enough for order", sendLimit(), msgjson.FundingError)
   990  
   991  	// Now with enough funds
   992  	oRig.eth.bal = reqFunds
   993  	ensureSuccess("well-funded account-based backend")
   994  
   995  	// Just enough for the order, but not enough because there are pending
   996  	// redeems in Swapper.
   997  	redeemCost := tRedeemSize * assetETH.MaxFeeRate
   998  	oRig.matchNegotiator.redeems[assetETH.ID] = 1
   999  	oRig.eth.bal = reqFunds + redeemCost - 1
  1000  	ensureErr("not enough for active redeems", sendLimit(), msgjson.FundingError)
  1001  
  1002  	// Enough for redeem too.
  1003  	oRig.eth.bal = reqFunds + redeemCost
  1004  	ensureSuccess("well-funded account-based backend with redeems")
  1005  
  1006  	// If we're buying, and the base asset is account-based, then it's the
  1007  	// number of redeems we're concerned with.
  1008  	limit.Side = msgjson.BuyOrderNum
  1009  	// Start with no active redeems. 10 lots should be 10 redeems.
  1010  	oRig.matchNegotiator.redeems[assetETH.ID] = 0
  1011  	oRig.eth.bal = lots*redeemCost - 1
  1012  	ensureErr("not enough to redeem", sendLimit(), msgjson.FundingError)
  1013  
  1014  	// Now with enough
  1015  	oRig.eth.bal = lots * redeemCost
  1016  	ensureSuccess("redeem to account-based")
  1017  
  1018  	// With funding from account based quote asset. Fail first.
  1019  	limit.Quote = assetMATIC.ID
  1020  	reqFunds = calc.RequiredOrderFunds(qty, 0, lots, tInitTxSize, tInitTxSize, assetMATIC.Asset.MaxFeeRate)
  1021  	oRig.polygon.bal = reqFunds - 1
  1022  	ensureErr("not enough to order account-based quote", sendLimit(), msgjson.FundingError)
  1023  
  1024  	// Now with enough.
  1025  	oRig.polygon.bal = reqFunds
  1026  	ensureSuccess("spend to account-based quote")
  1027  
  1028  	// Switch directions.
  1029  	limit.Side = msgjson.SellOrderNum
  1030  	oRig.eth.bal = calc.RequiredOrderFunds(qty, 0, lots, tInitTxSize, tInitTxSize, assetETH.Asset.MaxFeeRate)
  1031  
  1032  	// Not enough to redeem.
  1033  	redeemCost = tRedeemSize * assetETH.MaxFeeRate
  1034  	oRig.polygon.bal = lots*redeemCost - 1
  1035  	ensureErr("not enough to redeem account-based quote", sendLimit(), msgjson.FundingError)
  1036  
  1037  	oRig.polygon.bal = lots * redeemCost
  1038  	ensureSuccess("enough to redeem account-based quote")
  1039  }
  1040  
  1041  func TestMarketStartProcessStop(t *testing.T) {
  1042  	const sellLots = 10
  1043  	qty := uint64(dcrLotSize) * sellLots
  1044  	user := oRig.user
  1045  	clientTime := nowMs()
  1046  	pi := ordertest.RandomPreimage()
  1047  	commit := pi.Commit()
  1048  	mkt := msgjson.MarketOrder{
  1049  		Prefix: msgjson.Prefix{
  1050  			AccountID:  user.acct[:],
  1051  			Base:       dcrID,
  1052  			Quote:      btcID,
  1053  			OrderType:  msgjson.MarketOrderNum,
  1054  			ClientTime: uint64(clientTime.UnixMilli()),
  1055  			Commit:     commit[:],
  1056  		},
  1057  		Trade: msgjson.Trade{
  1058  			Side:     msgjson.SellOrderNum,
  1059  			Quantity: qty,
  1060  			Coins: []*msgjson.Coin{
  1061  				oRig.signedUTXO(dcrID, qty-dcrLotSize, 1),
  1062  				oRig.signedUTXO(dcrID, 2*dcrLotSize, 2),
  1063  			},
  1064  			Address: btcAddr,
  1065  		},
  1066  	}
  1067  
  1068  	reqID := uint64(5)
  1069  
  1070  	sendMarket := func() *msgjson.Error {
  1071  		msg, _ := msgjson.NewRequest(reqID, msgjson.MarketRoute, mkt)
  1072  		err := oRig.router.handleMarket(user.acct, msg)
  1073  		if err != nil {
  1074  			return err
  1075  		}
  1076  		// wait for the async success (nil) / err (non-nil)
  1077  		return <-oRig.auth.sent
  1078  	}
  1079  
  1080  	ensureErr := makeEnsureErr(t)
  1081  
  1082  	var oRecord *orderRecord
  1083  	ensureSuccess := func(tag string) {
  1084  		t.Helper()
  1085  		ensureErr(tag, sendMarket(), -1)
  1086  		select {
  1087  		case <-oRig.market.added:
  1088  		case <-time.After(time.Second):
  1089  			t.Fatalf("valid zero-conf order not submitted to epoch")
  1090  		}
  1091  		oRecord = oRig.market.pop()
  1092  		if oRecord == nil {
  1093  			t.Fatalf("valid zero-conf order not submitted to epoch")
  1094  		}
  1095  	}
  1096  
  1097  	oRig.auth.sent = make(chan *msgjson.Error, 1)
  1098  	defer func() { oRig.auth.sent = nil }()
  1099  
  1100  	// First just send it through and ensure there are no errors.
  1101  	oRig.market.added = make(chan struct{}, 1)
  1102  	defer func() { oRig.market.added = nil }()
  1103  	ensureSuccess("valid order")
  1104  
  1105  	// Test an invalid payload.
  1106  	msg := new(msgjson.Message)
  1107  	msg.Payload = []byte(`?`)
  1108  	rpcErr := oRig.router.handleMarket(user.acct, msg)
  1109  	ensureErr("bad payload", rpcErr, msgjson.RPCParseError)
  1110  
  1111  	// Wrong order type marked for market order
  1112  	mkt.OrderType = msgjson.LimitOrderNum
  1113  	ensureErr("wrong order type", sendMarket(), msgjson.OrderParameterError)
  1114  	mkt.OrderType = msgjson.MarketOrderNum
  1115  
  1116  	testPrefixTrade(&mkt.Prefix, &mkt.Trade, oRig.dcr.TBackend, oRig.btc.TBackend,
  1117  		func(tag string, code int) { t.Helper(); ensureErr(tag, sendMarket(), code) },
  1118  	)
  1119  
  1120  	// Zero-conf fails fee rate validation.
  1121  	oRig.dcr.confsMinus2 = -2
  1122  	oRig.dcr.invalidFeeRate = true
  1123  	ensureErr("low-fee zero-conf order", sendMarket(), msgjson.FundingError)
  1124  	oRig.dcr.confsMinus2 = 0
  1125  	oRig.dcr.invalidFeeRate = false
  1126  
  1127  	// Redeem to a quote asset.
  1128  	mkt.Quote = assetETH.ID
  1129  	mkt.RedeemSig = &msgjson.RedeemSig{}
  1130  	redeemCost := tRedeemSize * assetETH.MaxFeeRate
  1131  	oRig.eth.bal = sellLots*redeemCost - 1
  1132  	ensureErr("can't redeem to acct-based quote", sendMarket(), msgjson.FundingError)
  1133  
  1134  	// No RedeemSig is an error
  1135  	mkt.RedeemSig = nil
  1136  	ensureErr("no redeem sig", sendMarket(), msgjson.OrderParameterError)
  1137  	mkt.RedeemSig = &msgjson.RedeemSig{}
  1138  
  1139  	// Now with enough
  1140  	oRig.eth.bal = sellLots * redeemCost
  1141  	ensureSuccess("redeem to acct-based quote")
  1142  
  1143  	// Now switch it to a buy order, and ensure it passes
  1144  	// Clear the sends cache first.
  1145  	oRig.auth.sends = nil
  1146  	mkt.Quote = assetBTC.ID
  1147  	mkt.Side = msgjson.BuyOrderNum
  1148  
  1149  	midGap := oRig.market.MidGap()
  1150  	buyUTXO := oRig.signedUTXO(btcID, matcher.BaseToQuote(midGap, qty), 1)
  1151  	mkt.Coins = []*msgjson.Coin{
  1152  		buyUTXO,
  1153  	}
  1154  	mkt.Address = dcrAddr
  1155  
  1156  	// First check an order that doesn't satisfy the market buy buffer. For
  1157  	// testing, the market buy buffer is set to 1.5.
  1158  	mktBuyQty := matcher.BaseToQuote(midGap, uint64(dcrLotSize*1.4))
  1159  	mkt.Quantity = mktBuyQty
  1160  	ensureErr("insufficient market buy funding", sendMarket(), msgjson.FundingError)
  1161  
  1162  	mktBuyQty = matcher.BaseToQuote(midGap, uint64(dcrLotSize*1.6))
  1163  	mkt.Quantity = mktBuyQty
  1164  	mkt.ServerTime = 0
  1165  	oRig.auth.sends = nil
  1166  	ensureSuccess("market buy")
  1167  
  1168  	// Create the order manually, so that we can compare the IDs as another check
  1169  	// of equivalence.
  1170  	mo := &order.MarketOrder{
  1171  		P: order.Prefix{
  1172  			AccountID:  user.acct,
  1173  			BaseAsset:  mkt.Base,
  1174  			QuoteAsset: mkt.Quote,
  1175  			OrderType:  order.MarketOrderType,
  1176  			ClientTime: clientTime,
  1177  			Commit:     commit,
  1178  		},
  1179  		T: order.Trade{
  1180  			Sell:     false,
  1181  			Quantity: mktBuyQty,
  1182  			Address:  dcrAddr,
  1183  		},
  1184  	}
  1185  
  1186  	// Check the utxo
  1187  	epochOrder := oRecord.order.(*order.MarketOrder)
  1188  	if len(epochOrder.Coins) != 1 {
  1189  		t.Fatalf("expected 1 order UTXO, got %d", len(epochOrder.Coins))
  1190  	}
  1191  	epochUTXO := epochOrder.Coins[0]
  1192  	if !bytes.Equal(epochUTXO, buyUTXO.ID) {
  1193  		t.Fatalf("utxo reporting wrong txid")
  1194  	}
  1195  
  1196  	// Now steal the Coins
  1197  	mo.Coins = epochOrder.Coins
  1198  
  1199  	// Get the server time from the response.
  1200  	respMsg := oRig.auth.getSend()
  1201  	if respMsg == nil {
  1202  		t.Fatalf("no response from market order")
  1203  	}
  1204  	resp, _ := respMsg.Response()
  1205  	result := new(msgjson.OrderResult)
  1206  	err := json.Unmarshal(resp.Result, result)
  1207  	if err != nil {
  1208  		t.Fatalf("unmarshal error: %v", err)
  1209  	}
  1210  	mo.ServerTime = time.UnixMilli(int64(result.ServerTime))
  1211  
  1212  	// Check equivalence of IDs.
  1213  	if epochOrder.ID() != mo.ID() {
  1214  		t.Fatalf("failed to duplicate ID")
  1215  	}
  1216  
  1217  	// Fund with an account-based asset.
  1218  	mkt.Quote = assetETH.ID
  1219  
  1220  	// Not enough.
  1221  	oRig.eth.bal = mktBuyQty / 2
  1222  	ensureErr("insufficient account-based funding", sendMarket(), msgjson.FundingError)
  1223  
  1224  	// With enough.
  1225  	oRig.eth.bal = calc.RequiredOrderFunds(mktBuyQty, 0, 1, tInitTxSize, tInitTxSize, assetETH.Asset.MaxFeeRate)
  1226  	ensureSuccess("account-based funding")
  1227  
  1228  }
  1229  
  1230  func TestCancel(t *testing.T) {
  1231  	user := oRig.user
  1232  	targetID := order.OrderID{244}
  1233  	clientTime := nowMs()
  1234  	pi := ordertest.RandomPreimage()
  1235  	commit := pi.Commit()
  1236  	cancel := msgjson.CancelOrder{
  1237  		Prefix: msgjson.Prefix{
  1238  			AccountID:  user.acct[:],
  1239  			Base:       dcrID,
  1240  			Quote:      btcID,
  1241  			OrderType:  msgjson.CancelOrderNum,
  1242  			ClientTime: uint64(clientTime.UnixMilli()),
  1243  			Commit:     commit[:],
  1244  		},
  1245  		TargetID: targetID[:],
  1246  	}
  1247  	reqID := uint64(5)
  1248  
  1249  	ensureErr := makeEnsureErr(t)
  1250  
  1251  	sendCancel := func() *msgjson.Error {
  1252  		msg, _ := msgjson.NewRequest(reqID, msgjson.CancelRoute, cancel)
  1253  		return oRig.router.handleCancel(user.acct, msg)
  1254  	}
  1255  
  1256  	// First just send it through and ensure there are no errors.
  1257  	ensureErr("valid order", sendCancel(), -1)
  1258  	// Make sure the order was submitted to the market
  1259  	oRecord := oRig.market.pop()
  1260  	if oRecord == nil {
  1261  		t.Fatalf("no order submitted to epoch")
  1262  	}
  1263  
  1264  	// Test an invalid payload.
  1265  	msg := new(msgjson.Message)
  1266  	msg.Payload = []byte(`?`)
  1267  	rpcErr := oRig.router.handleCancel(user.acct, msg)
  1268  	ensureErr("bad payload", rpcErr, msgjson.RPCParseError)
  1269  
  1270  	// Unknown order.
  1271  	oRig.market.cancelable = false
  1272  	ensureErr("non cancelable", sendCancel(), msgjson.OrderParameterError)
  1273  	oRig.market.cancelable = true
  1274  
  1275  	// Wrong order type marked for cancel order
  1276  	cancel.OrderType = msgjson.LimitOrderNum
  1277  	ensureErr("wrong order type", sendCancel(), msgjson.OrderParameterError)
  1278  	cancel.OrderType = msgjson.CancelOrderNum
  1279  
  1280  	testPrefix(&cancel.Prefix, func(tag string, code int) {
  1281  		ensureErr(tag, sendCancel(), code)
  1282  	})
  1283  
  1284  	// Test a short order ID.
  1285  	badID := []byte{0x01, 0x02}
  1286  	cancel.TargetID = badID
  1287  	ensureErr("bad target ID", sendCancel(), msgjson.OrderParameterError)
  1288  	cancel.TargetID = targetID[:]
  1289  
  1290  	// Clear the sends cache.
  1291  	oRig.auth.sends = nil
  1292  
  1293  	// Create the order manually, so that we can compare the IDs as another check
  1294  	// of equivalence.
  1295  	co := &order.CancelOrder{
  1296  		P: order.Prefix{
  1297  			AccountID:  user.acct,
  1298  			BaseAsset:  cancel.Base,
  1299  			QuoteAsset: cancel.Quote,
  1300  			OrderType:  order.CancelOrderType,
  1301  			ClientTime: clientTime,
  1302  			Commit:     commit,
  1303  		},
  1304  		TargetOrderID: targetID,
  1305  	}
  1306  	// Send the order through again, so we can grab it from the epoch.
  1307  	rpcErr = sendCancel()
  1308  	if rpcErr != nil {
  1309  		t.Fatalf("error for valid order (after prefix testing): %s", rpcErr.Message)
  1310  	}
  1311  	oRecord = oRig.market.pop()
  1312  	if oRecord == nil {
  1313  		t.Fatalf("no cancel order submitted to epoch")
  1314  	}
  1315  
  1316  	// Check the utxo
  1317  	epochOrder := oRecord.order.(*order.CancelOrder)
  1318  
  1319  	// Get the server time from the response.
  1320  	respMsg := oRig.auth.getSend()
  1321  	if respMsg == nil {
  1322  		t.Fatalf("no response from market order")
  1323  	}
  1324  	resp, _ := respMsg.Response()
  1325  	result := new(msgjson.OrderResult)
  1326  	err := json.Unmarshal(resp.Result, result)
  1327  	if err != nil {
  1328  		t.Fatalf("unmarshal error: %v", err)
  1329  	}
  1330  	co.ServerTime = time.UnixMilli(int64(result.ServerTime))
  1331  
  1332  	// Check equivalence of IDs.
  1333  	if epochOrder.ID() != co.ID() {
  1334  		t.Fatalf("failed to duplicate ID: %v != %v", epochOrder.ID(), co.ID())
  1335  	}
  1336  }
  1337  
  1338  func testPrefix(prefix *msgjson.Prefix, checkCode func(string, int)) {
  1339  	ogAcct := prefix.AccountID
  1340  	oid := ordertest.NextAccount()
  1341  	prefix.AccountID = oid[:]
  1342  	checkCode("bad account", msgjson.OrderParameterError)
  1343  	prefix.AccountID = ogAcct
  1344  
  1345  	// Signature error
  1346  	oRig.auth.authErr = dummyError
  1347  	checkCode("bad order sig", msgjson.SignatureError)
  1348  	oRig.auth.authErr = nil
  1349  
  1350  	// Unknown asset
  1351  	prefix.Base = assetUnknown.ID
  1352  	checkCode("unknown asset", msgjson.UnknownMarketError)
  1353  
  1354  	// Unknown market. 1 is BIP0044 testnet designator, which a "known" asset,
  1355  	// but with no markets.
  1356  	prefix.Base = 1
  1357  	checkCode("unknown market", msgjson.UnknownMarketError)
  1358  	prefix.Base = assetDCR.ID
  1359  
  1360  	// Too old
  1361  	ct := prefix.ClientTime
  1362  	prefix.ClientTime = ct - maxClockOffset - 1 // offset >= maxClockOffset
  1363  	checkCode("too old", msgjson.ClockRangeError)
  1364  	prefix.ClientTime = ct
  1365  
  1366  	// Set server time = bad
  1367  	prefix.ServerTime = 1
  1368  	checkCode("server time set", msgjson.OrderParameterError)
  1369  	prefix.ServerTime = 0
  1370  }
  1371  
  1372  func testPrefixTrade(prefix *msgjson.Prefix, trade *msgjson.Trade, fundingAsset, receivingAsset *TBackend, checkCode func(string, int)) {
  1373  	// Wrong account ID
  1374  	testPrefix(prefix, checkCode)
  1375  
  1376  	// Invalid side number
  1377  	trade.Side = 100
  1378  	checkCode("bad side num", msgjson.OrderParameterError)
  1379  	trade.Side = msgjson.SellOrderNum
  1380  
  1381  	// Zero quantity
  1382  	qty := trade.Quantity
  1383  	trade.Quantity = 0
  1384  	checkCode("zero quantity", msgjson.OrderParameterError)
  1385  
  1386  	// non-lot-multiple
  1387  	trade.Quantity = qty + (dcrLotSize / 2)
  1388  	checkCode("non-lot-multiple", msgjson.OrderParameterError)
  1389  	trade.Quantity = qty
  1390  
  1391  	// No utxos
  1392  	ogUTXOs := trade.Coins
  1393  	trade.Coins = nil
  1394  	checkCode("no utxos", msgjson.FundingError)
  1395  	trade.Coins = ogUTXOs
  1396  
  1397  	// No signatures
  1398  	utxo1 := trade.Coins[1]
  1399  	ogSigs := utxo1.Sigs
  1400  	utxo1.Sigs = nil
  1401  	checkCode("no utxo sigs", msgjson.SignatureError)
  1402  
  1403  	// Different number of signatures than pubkeys
  1404  	utxo1.Sigs = ogSigs[:1]
  1405  	checkCode("not enough sigs", msgjson.OrderParameterError)
  1406  	utxo1.Sigs = ogSigs
  1407  
  1408  	// output is locked
  1409  	oRig.market.locked = true
  1410  	checkCode("output locked", msgjson.FundingError)
  1411  	oRig.market.locked = false
  1412  
  1413  	// utxo err
  1414  	fundingAsset.utxoErr = dummyError // anything but asset.CoinNotFoundError
  1415  	checkCode("utxo err", msgjson.FundingError)
  1416  	fundingAsset.utxoErr = nil
  1417  
  1418  	// UTXO Auth error
  1419  	utxoAuthErr = dummyError
  1420  	checkCode("utxo auth error", msgjson.CoinAuthError)
  1421  	utxoAuthErr = nil
  1422  
  1423  	// Clear the order from the epoch.
  1424  	oRig.market.pop()
  1425  
  1426  	// Not enough funding
  1427  	trade.Coins = ogUTXOs[:1]
  1428  	fundingAsset.unfunded = true
  1429  	checkCode("unfunded", msgjson.FundingError)
  1430  	fundingAsset.unfunded = false
  1431  	trade.Coins = ogUTXOs
  1432  
  1433  	// Invalid address
  1434  	receivingAsset.addrChecks = false
  1435  	checkCode("bad address", msgjson.OrderParameterError)
  1436  	receivingAsset.addrChecks = true
  1437  }
  1438  
  1439  // Book Router Tests
  1440  
  1441  // nolint:unparam
  1442  func randLots(max int) uint64 {
  1443  	return uint64(rnd.Intn(max) + 1)
  1444  }
  1445  
  1446  func randRate(baseRate, lotSize uint64, min, max float64) uint64 {
  1447  	multiplier := rnd.Float64()*(max-min) + min
  1448  	rate := uint64(multiplier * float64(baseRate))
  1449  	return rate - rate%lotSize
  1450  }
  1451  
  1452  func makeLO(writer *ordertest.Writer, rate, lots uint64, force order.TimeInForce) *order.LimitOrder {
  1453  	lo, _ := ordertest.WriteLimitOrder(writer, rate, lots, force, 0)
  1454  	return lo
  1455  }
  1456  
  1457  func makeLORevealed(writer *ordertest.Writer, rate, lots uint64, force order.TimeInForce) (*order.LimitOrder, order.Preimage) {
  1458  	return ordertest.WriteLimitOrder(writer, rate, lots, force, 0)
  1459  }
  1460  
  1461  func makeMO(writer *ordertest.Writer, lots uint64) *order.MarketOrder {
  1462  	mo, _ := ordertest.WriteMarketOrder(writer, lots, 0)
  1463  	return mo
  1464  }
  1465  
  1466  func makeMORevealed(writer *ordertest.Writer, lots uint64) (*order.MarketOrder, order.Preimage) {
  1467  	return ordertest.WriteMarketOrder(writer, lots, 0)
  1468  }
  1469  
  1470  func makeCO(writer *ordertest.Writer, targetID order.OrderID) *order.CancelOrder {
  1471  	co, _ := ordertest.WriteCancelOrder(writer, targetID, 0)
  1472  	return co
  1473  }
  1474  
  1475  func makeCORevealed(writer *ordertest.Writer, targetID order.OrderID) (*order.CancelOrder, order.Preimage) {
  1476  	return ordertest.WriteCancelOrder(writer, targetID, 0)
  1477  }
  1478  
  1479  type TBookSource struct {
  1480  	buys  []*order.LimitOrder
  1481  	sells []*order.LimitOrder
  1482  	feed  chan *updateSignal
  1483  	base  uint32
  1484  	quote uint32
  1485  }
  1486  
  1487  func (s *TBookSource) Base() uint32 {
  1488  	return s.base
  1489  }
  1490  
  1491  func (s *TBookSource) Quote() uint32 {
  1492  	return s.quote
  1493  }
  1494  
  1495  func tNewBookSource(base, quote uint32) *TBookSource {
  1496  	return &TBookSource{
  1497  		feed:  make(chan *updateSignal, 16),
  1498  		base:  base,
  1499  		quote: quote,
  1500  	}
  1501  }
  1502  
  1503  func (s *TBookSource) Book() (eidx int64, buys []*order.LimitOrder, sells []*order.LimitOrder) {
  1504  	return 13241324, s.buys, s.sells
  1505  }
  1506  func (s *TBookSource) OrderFeed() <-chan *updateSignal {
  1507  	return s.feed
  1508  }
  1509  
  1510  type TLink struct {
  1511  	mtx         sync.Mutex
  1512  	id          uint64
  1513  	ip          dex.IPKey
  1514  	addr        string
  1515  	sends       []*msgjson.Message
  1516  	sendErr     error
  1517  	sendTrigger chan struct{}
  1518  	banished    bool
  1519  	on          uint32
  1520  	closed      chan struct{}
  1521  	sendRawErr  error
  1522  }
  1523  
  1524  var linkCounter uint64
  1525  
  1526  func tNewLink() *TLink {
  1527  	linkCounter++
  1528  	return &TLink{
  1529  		id:          linkCounter,
  1530  		ip:          dex.NewIPKey("[1:800:dead:cafe::]"),
  1531  		addr:        "testaddr",
  1532  		sends:       make([]*msgjson.Message, 0),
  1533  		sendTrigger: make(chan struct{}, 1),
  1534  	}
  1535  }
  1536  
  1537  func (conn *TLink) Authorized()   {}
  1538  func (conn *TLink) ID() uint64    { return conn.id }
  1539  func (conn *TLink) IP() dex.IPKey { return conn.ip }
  1540  func (conn *TLink) Addr() string  { return conn.addr }
  1541  func (conn *TLink) Send(msg *msgjson.Message) error {
  1542  	conn.mtx.Lock()
  1543  	defer conn.mtx.Unlock()
  1544  	if conn.sendErr != nil {
  1545  		return conn.sendErr
  1546  	}
  1547  	conn.sends = append(conn.sends, msg)
  1548  	conn.sendTrigger <- struct{}{}
  1549  	return nil
  1550  }
  1551  func (conn *TLink) SendRaw(b []byte) error {
  1552  	conn.mtx.Lock()
  1553  	defer conn.mtx.Unlock()
  1554  	if conn.sendRawErr != nil {
  1555  		return conn.sendRawErr
  1556  	}
  1557  	msg, err := msgjson.DecodeMessage(b)
  1558  	if err != nil {
  1559  		return err
  1560  	}
  1561  	conn.sends = append(conn.sends, msg)
  1562  	conn.sendTrigger <- struct{}{}
  1563  	return nil
  1564  }
  1565  func (conn *TLink) SendError(id uint64, msgErr *msgjson.Error) {
  1566  	msg, err := msgjson.NewResponse(id, nil, msgErr)
  1567  	if err != nil {
  1568  		log.Errorf("SendError: failed to create message: %v", err)
  1569  	}
  1570  	conn.mtx.Lock()
  1571  	defer conn.mtx.Unlock()
  1572  	conn.sends = append(conn.sends, msg)
  1573  	conn.sendTrigger <- struct{}{}
  1574  }
  1575  
  1576  func (conn *TLink) getSend() *msgjson.Message {
  1577  	select {
  1578  	case <-conn.sendTrigger:
  1579  	case <-time.NewTimer(2 * time.Second).C:
  1580  		panic("no send")
  1581  	}
  1582  
  1583  	conn.mtx.Lock()
  1584  	defer conn.mtx.Unlock()
  1585  	if len(conn.sends) == 0 {
  1586  		return nil
  1587  	}
  1588  	s := conn.sends[0]
  1589  	conn.sends = conn.sends[1:]
  1590  	return s
  1591  }
  1592  
  1593  // There are no requests in the routers.
  1594  func (conn *TLink) Request(msg *msgjson.Message, f func(comms.Link, *msgjson.Message), expDur time.Duration, exp func()) error {
  1595  	return nil
  1596  }
  1597  func (conn *TLink) RequestRaw(msgID uint64, rawMsg []byte, f func(comms.Link, *msgjson.Message), expireTime time.Duration, expire func()) error {
  1598  	return nil
  1599  }
  1600  func (conn *TLink) Banish() {
  1601  	conn.banished = true
  1602  }
  1603  func (conn *TLink) Done() <-chan struct{} {
  1604  	return conn.closed
  1605  }
  1606  func (conn *TLink) Disconnect() {
  1607  	if atomic.CompareAndSwapUint32(&conn.on, 0, 1) {
  1608  		close(conn.closed)
  1609  	}
  1610  }
  1611  
  1612  func (conn *TLink) SetCustomID(string) {}
  1613  func (conn *TLink) CustomID() string   { return "" }
  1614  
  1615  type testRig struct {
  1616  	router  *BookRouter
  1617  	source1 *TBookSource // btc_ltc
  1618  	source2 *TBookSource // dcr_doge
  1619  	source3 *TBookSource // dcr_btc
  1620  }
  1621  
  1622  func newTestRig() *testRig {
  1623  	src1 := tNewBookSource(btcID, ltcID)
  1624  	src2 := tNewBookSource(dcrID, dogeID)
  1625  	src3 := tNewBookSource(dcrID, btcID)
  1626  	return &testRig{
  1627  		source1: src1,
  1628  		source2: src2,
  1629  		source3: src3,
  1630  	}
  1631  }
  1632  
  1633  func (rig *testRig) sources() map[string]BookSource {
  1634  	return map[string]BookSource{
  1635  		mktName1: rig.source1,
  1636  		mktName2: rig.source2,
  1637  		mktName3: rig.source3,
  1638  	}
  1639  }
  1640  
  1641  func tick(d int) { time.Sleep(time.Duration(d) * time.Millisecond) }
  1642  
  1643  func newSubscription(mkt *ordertest.Market) *msgjson.Message {
  1644  	msg, _ := msgjson.NewRequest(1, msgjson.OrderBookRoute, &msgjson.OrderBookSubscription{
  1645  		Base:  mkt.Base,
  1646  		Quote: mkt.Quote,
  1647  	})
  1648  	return msg
  1649  }
  1650  
  1651  func newSubscriber(mkt *ordertest.Market) (*TLink, *msgjson.Message) {
  1652  	return tNewLink(), newSubscription(mkt)
  1653  }
  1654  
  1655  func findOrder(id msgjson.Bytes, books ...[]*order.LimitOrder) *order.LimitOrder {
  1656  	for _, book := range books {
  1657  		for _, o := range book {
  1658  			if o.ID().String() == id.String() {
  1659  				return o
  1660  			}
  1661  		}
  1662  	}
  1663  	return nil
  1664  }
  1665  
  1666  func getEpochNoteFromLink(t *testing.T, link *TLink) *msgjson.EpochOrderNote {
  1667  	t.Helper()
  1668  	noteMsg := link.getSend()
  1669  	if noteMsg == nil {
  1670  		t.Fatalf("no epoch notification sent")
  1671  	}
  1672  	epochNote := new(msgjson.EpochOrderNote)
  1673  	err := json.Unmarshal(noteMsg.Payload, epochNote)
  1674  	if err != nil {
  1675  		t.Fatalf("error unmarshaling epoch notification: %v", err)
  1676  	}
  1677  	return epochNote
  1678  }
  1679  
  1680  func getBookNoteFromLink(t *testing.T, link *TLink) *msgjson.BookOrderNote {
  1681  	t.Helper()
  1682  	noteMsg := link.getSend()
  1683  	if noteMsg == nil {
  1684  		t.Fatalf("no epoch notification sent")
  1685  	}
  1686  	bookNote := new(msgjson.BookOrderNote)
  1687  	err := json.Unmarshal(noteMsg.Payload, bookNote)
  1688  	if err != nil {
  1689  		t.Fatalf("error unmarshaling epoch notification: %v", err)
  1690  	}
  1691  	return bookNote
  1692  }
  1693  
  1694  func getUpdateRemainingNoteFromLink(t *testing.T, link *TLink) *msgjson.UpdateRemainingNote {
  1695  	t.Helper()
  1696  	noteMsg := link.getSend()
  1697  	if noteMsg == nil {
  1698  		t.Fatalf("no epoch notification sent")
  1699  	}
  1700  	urNote := new(msgjson.UpdateRemainingNote)
  1701  	err := json.Unmarshal(noteMsg.Payload, urNote)
  1702  	if err != nil {
  1703  		t.Fatalf("error unmarshaling epoch notification: %v", err)
  1704  	}
  1705  	return urNote
  1706  }
  1707  
  1708  func getUnbookNoteFromLink(t *testing.T, link *TLink) *msgjson.UnbookOrderNote {
  1709  	t.Helper()
  1710  	noteMsg := link.getSend()
  1711  	if noteMsg == nil {
  1712  		t.Fatalf("no epoch notification sent")
  1713  	}
  1714  	unbookNote := new(msgjson.UnbookOrderNote)
  1715  	err := json.Unmarshal(noteMsg.Payload, unbookNote)
  1716  	if err != nil {
  1717  		t.Fatalf("error unmarshaling epoch notification: %v", err)
  1718  	}
  1719  	return unbookNote
  1720  }
  1721  
  1722  func mkRate1(min, max float64) uint64 {
  1723  	return randRate(mkt1BaseRate, mkt1.LotSize, min, max)
  1724  }
  1725  
  1726  func mkRate2(min, max float64) uint64 {
  1727  	return randRate(mkt2BaseRate, mkt2.LotSize, min, max)
  1728  }
  1729  
  1730  func mkRate3(min, max float64) uint64 {
  1731  	return randRate(mkt3BaseRate, mkt3.LotSize, min, max)
  1732  }
  1733  
  1734  func TestRouter(t *testing.T) {
  1735  	src1 := rig.source1
  1736  	src2 := rig.source2
  1737  	router := rig.router
  1738  
  1739  	checkResponse := func(tag, mktName string, msgID uint64, conn *TLink) []*msgjson.BookOrderNote {
  1740  		t.Helper()
  1741  		respMsg := conn.getSend()
  1742  		if respMsg == nil {
  1743  			t.Fatalf("(%s): no response sent for subscription", tag)
  1744  		}
  1745  		if respMsg.ID != msgID {
  1746  			t.Fatalf("(%s): wrong ID for response. wanted %d, got %d", tag, msgID, respMsg.ID)
  1747  		}
  1748  		resp, err := respMsg.Response()
  1749  		if err != nil {
  1750  			t.Fatalf("(%s): error parsing response: %v", tag, err)
  1751  		}
  1752  		if resp.Error != nil {
  1753  			t.Fatalf("response is error: %v", resp.Error)
  1754  		}
  1755  		book := new(msgjson.OrderBook)
  1756  		err = json.Unmarshal(resp.Result, book)
  1757  		if err != nil {
  1758  			t.Fatalf("(%s): unmarshal error: %v", tag, err)
  1759  		}
  1760  		if len(book.Orders) != 16 {
  1761  			t.Fatalf("(%s): expected 16 orders, received %d", tag, len(book.Orders))
  1762  		}
  1763  		if book.MarketID != mktName {
  1764  			t.Fatalf("(%s): wrong market ID. expected %s, got %s", tag, mktName1, book.MarketID)
  1765  		}
  1766  		return book.Orders
  1767  	}
  1768  
  1769  	findBookOrder := func(id msgjson.Bytes, src *TBookSource) *order.LimitOrder {
  1770  		t.Helper()
  1771  		return findOrder(id, src.buys, src.sells)
  1772  	}
  1773  
  1774  	compareTrade := func(msgOrder *msgjson.BookOrderNote, ord order.Order, tag string) {
  1775  		t.Helper()
  1776  		prefix, trade := ord.Prefix(), ord.Trade()
  1777  		if trade.Sell != (msgOrder.Side == msgjson.SellOrderNum) {
  1778  			t.Fatalf("%s: message order has wrong side marked. sell = %t, side = '%d'", tag, trade.Sell, msgOrder.Side)
  1779  		}
  1780  		if msgOrder.Quantity != trade.Remaining() {
  1781  			t.Fatalf("%s: message order quantity incorrect. expected %d, got %d", tag, trade.Quantity, msgOrder.Quantity)
  1782  		}
  1783  		if msgOrder.Time != uint64(prefix.Time()) {
  1784  			t.Fatalf("%s: wrong time. expected %d, got %d", tag, prefix.Time(), msgOrder.Time)
  1785  		}
  1786  	}
  1787  
  1788  	compareLO := func(msgOrder *msgjson.BookOrderNote, lo *order.LimitOrder, tifFlag uint8, tag string) {
  1789  		t.Helper()
  1790  		if msgOrder.Rate != lo.Rate {
  1791  			t.Fatalf("%s: message order rate incorrect. expected %d, got %d", tag, lo.Rate, msgOrder.Rate)
  1792  		}
  1793  		if msgOrder.TiF != tifFlag {
  1794  			t.Fatalf("%s: message order has wrong time-in-force flag. wanted %d, got %d", tag, tifFlag, msgOrder.TiF)
  1795  		}
  1796  		compareTrade(msgOrder, lo, tag)
  1797  	}
  1798  
  1799  	// A helper function to scan through the received msgjson.OrderBook.Orders and
  1800  	// compare the orders to the order book.
  1801  	checkBook := func(source *TBookSource, tifFlag uint8, tag string, msgOrders ...*msgjson.BookOrderNote) {
  1802  		t.Helper()
  1803  		for i, msgOrder := range msgOrders {
  1804  			lo := findBookOrder(msgOrder.OrderID, source)
  1805  			if lo == nil {
  1806  				t.Fatalf("%s(%d): order not found", tag, i)
  1807  			}
  1808  			compareLO(msgOrder, lo, tifFlag, tag)
  1809  		}
  1810  	}
  1811  
  1812  	// Have a subscriber connect and pull the orders from market 1.
  1813  	// The format used here is link[market]_[count]
  1814  	link1, sub := newSubscriber(mkt1)
  1815  	if err := router.handleOrderBook(link1, sub); err != nil {
  1816  		t.Fatalf("handleOrderBook: %v", err)
  1817  	}
  1818  	orders := checkResponse("first link, market 1", mktName1, sub.ID, link1)
  1819  	checkBook(src1, msgjson.StandingOrderNum, "first link, market 1", orders...)
  1820  
  1821  	// Another subscriber to the same market should behave identically.
  1822  	link2, sub := newSubscriber(mkt1)
  1823  	if err := router.handleOrderBook(link2, sub); err != nil {
  1824  		t.Fatalf("handleOrderBook: %v", err)
  1825  	}
  1826  	orders = checkResponse("second link, market 1", mktName1, sub.ID, link2)
  1827  	checkBook(src1, msgjson.StandingOrderNum, "second link, market 1", orders...)
  1828  
  1829  	// An epoch notification sent on market 1's channel should arrive at both
  1830  	// clients.
  1831  	lo := makeLO(buyer1, mkRate1(0.8, 1.0), randLots(10), order.ImmediateTiF)
  1832  	sig := &updateSignal{
  1833  		action: epochAction,
  1834  		data: sigDataEpochOrder{
  1835  			order:    lo,
  1836  			epochIdx: 12345678,
  1837  		},
  1838  	}
  1839  	src1.feed <- sig
  1840  
  1841  	epochNote := getEpochNoteFromLink(t, link1)
  1842  	compareLO(&epochNote.BookOrderNote, lo, msgjson.ImmediateOrderNum, "epoch notification, link1")
  1843  	if epochNote.MarketID != mktName1 {
  1844  		t.Fatalf("wrong market id. got %s, wanted %s", mktName1, epochNote.MarketID)
  1845  	}
  1846  
  1847  	epochNote = getEpochNoteFromLink(t, link2)
  1848  	compareLO(&epochNote.BookOrderNote, lo, msgjson.ImmediateOrderNum, "epoch notification, link2")
  1849  
  1850  	// just for kicks, checks the epoch is as expected.
  1851  	wantIdx := sig.data.(sigDataEpochOrder).epochIdx
  1852  	if epochNote.Epoch != uint64(wantIdx) {
  1853  		t.Fatalf("wrong epoch. wanted %d, got %d", wantIdx, epochNote.Epoch)
  1854  	}
  1855  
  1856  	// Have both subscribers subscribe to market 2.
  1857  	sub = newSubscription(mkt2)
  1858  	if err := router.handleOrderBook(link1, sub); err != nil {
  1859  		t.Fatalf("handleOrderBook: %v", err)
  1860  	}
  1861  	if err := router.handleOrderBook(link2, sub); err != nil {
  1862  		t.Fatalf("handleOrderBook: %v", err)
  1863  	}
  1864  	orders = checkResponse("first link, market 2", mktName2, sub.ID, link1)
  1865  	checkBook(src2, msgjson.StandingOrderNum, "first link, market 2", orders...)
  1866  	orders = checkResponse("second link, market 2", mktName2, sub.ID, link2)
  1867  	checkBook(src2, msgjson.StandingOrderNum, "second link, market 2", orders...)
  1868  
  1869  	// Send an epoch update for a market order.
  1870  	mo := makeMO(buyer2, randLots(10))
  1871  	sig = &updateSignal{
  1872  		action: epochAction,
  1873  		data: sigDataEpochOrder{
  1874  			order:    mo,
  1875  			epochIdx: 12345678,
  1876  		},
  1877  	}
  1878  	src2.feed <- sig
  1879  
  1880  	epochNote = getEpochNoteFromLink(t, link1)
  1881  	compareTrade(&epochNote.BookOrderNote, mo, "link 1 market 2 epoch update (market order)")
  1882  
  1883  	epochNote = getEpochNoteFromLink(t, link2)
  1884  	compareTrade(&epochNote.BookOrderNote, mo, "link 2 market 2 epoch update (market order)")
  1885  
  1886  	// Make a new standing limit order with a quantity of at least 3 lots for
  1887  	// the market 2 sell book. Book it with a bookAction, fill 1 lot, and send
  1888  	// an updateRemainingAction update.
  1889  	lo = makeLO(seller2, mkRate2(1.0, 1.2), randLots(10)+1, order.StandingTiF)
  1890  	lo.FillAmt = mkt2.LotSize
  1891  
  1892  	sig = &updateSignal{
  1893  		action: bookAction,
  1894  		data: sigDataBookedOrder{
  1895  			order:    lo,
  1896  			epochIdx: 12344365,
  1897  		},
  1898  	}
  1899  	src2.feed <- sig
  1900  
  1901  	bookNote := getBookNoteFromLink(t, link1)
  1902  	compareLO(bookNote, lo, msgjson.StandingOrderNum, "book notification, link1, market 2")
  1903  	if bookNote.MarketID != mktName2 {
  1904  		t.Fatalf("wrong market id. wanted %s, got %s", mktName2, bookNote.MarketID)
  1905  	}
  1906  
  1907  	bookNote = getBookNoteFromLink(t, link2)
  1908  	compareLO(bookNote, lo, msgjson.StandingOrderNum, "book notification, link2, market 2")
  1909  
  1910  	if bookNote.Quantity != lo.Remaining() {
  1911  		t.Fatalf("wrong quantity in book update. expected %d, got %d", lo.Remaining(), bookNote.Quantity)
  1912  	}
  1913  
  1914  	// Update the order's remaining quantity. Leave one lot remaining.
  1915  	lo.FillAmt = lo.Quantity - mkt2.LotSize
  1916  
  1917  	sig = &updateSignal{
  1918  		action: updateRemainingAction,
  1919  		data: sigDataUpdateRemaining{
  1920  			order:    lo,
  1921  			epochIdx: 12344365,
  1922  		},
  1923  	}
  1924  
  1925  	src2.feed <- sig
  1926  
  1927  	urNote := getUpdateRemainingNoteFromLink(t, link2)
  1928  	if urNote.Remaining != lo.Remaining() {
  1929  		t.Fatalf("wrong remaining quantity for link2. expected %d, got %d", lo.Remaining(), urNote.Remaining)
  1930  	}
  1931  	// clear the send from client 1
  1932  	link1.getSend()
  1933  
  1934  	// Now unbook the order.
  1935  	sig = &updateSignal{
  1936  		action: unbookAction,
  1937  		data: sigDataUnbookedOrder{
  1938  			order:    lo,
  1939  			epochIdx: 12345678,
  1940  		},
  1941  	}
  1942  	src2.feed <- sig
  1943  
  1944  	unbookNote := getUnbookNoteFromLink(t, link1)
  1945  	if lo.ID().String() != unbookNote.OrderID.String() {
  1946  		t.Fatalf("wrong cancel ID. expected %s, got %s", lo.ID(), unbookNote.OrderID)
  1947  	}
  1948  	if unbookNote.MarketID != mktName2 {
  1949  		t.Fatalf("wrong market id. wanted %s, got %s", mktName2, unbookNote.MarketID)
  1950  	}
  1951  	// clear the send from client 2
  1952  	link2.getSend()
  1953  
  1954  	// Make sure the order is no longer stored in the router's books
  1955  	if router.books[mktName2].orders[lo.ID()] != nil {
  1956  		t.Fatalf("order still in book after unbookAction")
  1957  	}
  1958  
  1959  	// Now unsubscribe link 1 from market 1.
  1960  	unsub, _ := msgjson.NewRequest(10, msgjson.UnsubOrderBookRoute, &msgjson.UnsubOrderBook{
  1961  		MarketID: mktName1,
  1962  	})
  1963  	if err := router.handleUnsubOrderBook(link1, unsub); err != nil {
  1964  		t.Fatalf("handleUnsubOrderBook: %v", err)
  1965  	}
  1966  
  1967  	// Client 1 should have an unsub response from the server.
  1968  	respMsg := link1.getSend()
  1969  	if respMsg == nil {
  1970  		t.Fatalf("no response for unsub")
  1971  	}
  1972  	resp, _ := respMsg.Response()
  1973  	var success bool
  1974  	err := json.Unmarshal(resp.Result, &success)
  1975  	if err != nil {
  1976  		t.Fatalf("err unmarshaling unsub response")
  1977  	}
  1978  	if !success {
  1979  		t.Fatalf("expected true for unsub result, got false")
  1980  	}
  1981  
  1982  	mo = makeMO(seller1, randLots(10))
  1983  	sig = &updateSignal{
  1984  		action: epochAction,
  1985  		data: sigDataEpochOrder{
  1986  			order:    mo,
  1987  			epochIdx: 12345678,
  1988  		},
  1989  	}
  1990  	src1.feed <- sig
  1991  
  1992  	if link2.getSend() == nil {
  1993  		t.Fatalf("client 2 didn't receive an update after client 1 unsubbed")
  1994  	}
  1995  	select {
  1996  	case <-link1.sendTrigger:
  1997  		t.Fatalf("client 1 should not have received an update after unsubscribing")
  1998  	case <-time.NewTimer(20 * time.Millisecond).C:
  1999  		// Client 2 already got a send, so we should not need to wait much if at
  2000  		// all since BookRouter.sendNote is already running or done.
  2001  	}
  2002  
  2003  	// Now epoch a cancel order to client 2.
  2004  	targetID := src1.buys[0].ID()
  2005  	co := makeCO(buyer1, targetID)
  2006  	sig = &updateSignal{
  2007  		action: epochAction,
  2008  		data: sigDataEpochOrder{
  2009  			order:    co,
  2010  			epochIdx: 12345678,
  2011  		},
  2012  	}
  2013  	src1.feed <- sig
  2014  
  2015  	epochNote = getEpochNoteFromLink(t, link2)
  2016  	if epochNote.OrderType != msgjson.CancelOrderNum {
  2017  		t.Fatalf("epoch cancel notification not of cancel type. expected %d, got %d",
  2018  			msgjson.CancelOrderNum, epochNote.OrderType)
  2019  	}
  2020  
  2021  	if epochNote.TargetID.String() != targetID.String() {
  2022  		t.Fatalf("epoch cancel notification has wrong order ID. expected %s, got %s",
  2023  			targetID, epochNote.TargetID)
  2024  	}
  2025  
  2026  	// Send another, but err on the send. Check for unsubscribed
  2027  	link2.sendRawErr = dummyError
  2028  	src1.feed <- sig
  2029  
  2030  	// Wait for (*BookRouter).sendNote to remove the erroring link from the
  2031  	// subscription conns map.
  2032  	time.Sleep(50 * time.Millisecond)
  2033  
  2034  	subs := router.books[mktName1].subs
  2035  	subs.mtx.RLock()
  2036  	l := subs.conns[link2.ID()]
  2037  	subs.mtx.RUnlock()
  2038  	if l != nil {
  2039  		t.Fatalf("client not removed from subscription list")
  2040  	}
  2041  }
  2042  
  2043  // func TestFeeRateRequest(t *testing.T) {
  2044  // 	router := rig.router
  2045  // 	cl := tNewLink()
  2046  // 	// Request for an unknown asset.
  2047  // 	msg, _ := msgjson.NewRequest(1, msgjson.FeeRateRoute, 555)
  2048  // 	msgErr := router.handleFeeRate(cl, msg)
  2049  // 	if msgErr == nil {
  2050  // 		t.Fatalf("no error for unknown asset")
  2051  // 	}
  2052  
  2053  // 	checkFeeRate := func(expRate uint64) {
  2054  // 		t.Helper()
  2055  // 		msg, _ = msgjson.NewRequest(1, msgjson.FeeRateRoute, dcrID)
  2056  // 		msgErr = router.handleFeeRate(cl, msg)
  2057  // 		if msgErr != nil {
  2058  // 			t.Fatalf("no error for unknown asset")
  2059  // 		}
  2060  // 		resp := cl.getSend()
  2061  // 		if resp == nil {
  2062  // 			t.Fatalf("no response")
  2063  // 		}
  2064  // 		var feeRate uint64
  2065  // 		err := resp.UnmarshalResult(&feeRate)
  2066  // 		if err != nil {
  2067  // 			t.Fatalf("error unmarshaling result: %v", err)
  2068  // 		}
  2069  // 		if feeRate != expRate {
  2070  // 			t.Fatalf("unexpected fee rate. wanted %d, got %d", expRate, feeRate)
  2071  // 		}
  2072  // 	}
  2073  
  2074  // 	// Request for known asset that hasn't been primed should be no error but
  2075  // 	// value zero.
  2076  // 	checkFeeRate(0)
  2077  
  2078  // 	// Prime the cache and ask again.
  2079  // 	atomic.StoreUint64(router.feeRateCache[dcrID], 8)
  2080  // 	checkFeeRate(8)
  2081  // }
  2082  
  2083  func TestBadMessages(t *testing.T) {
  2084  	router := rig.router
  2085  	link, sub := newSubscriber(mkt1)
  2086  
  2087  	checkErr := func(tag string, rpcErr *msgjson.Error, code int) {
  2088  		t.Helper()
  2089  		if rpcErr == nil {
  2090  			t.Fatalf("%s: no error", tag)
  2091  		}
  2092  		if rpcErr.Code != code {
  2093  			t.Fatalf("%s: wrong code. wanted %d, got %d", tag, code, rpcErr.Code)
  2094  		}
  2095  	}
  2096  
  2097  	// Bad encoding
  2098  	ogPayload := sub.Payload
  2099  	sub.Payload = []byte(`?`)
  2100  	rpcErr := router.handleOrderBook(link, sub)
  2101  	checkErr("bad payload", rpcErr, msgjson.RPCParseError)
  2102  	sub.Payload = ogPayload
  2103  
  2104  	// Use an unknown market
  2105  	badMkt := &ordertest.Market{
  2106  		Base:  400000,
  2107  		Quote: 400001,
  2108  	}
  2109  	sub = newSubscription(badMkt)
  2110  	rpcErr = router.handleOrderBook(link, sub)
  2111  	checkErr("bad payload", rpcErr, msgjson.UnknownMarket)
  2112  
  2113  	// Valid asset IDs, but not an actual market on the DEX.
  2114  	badMkt = &ordertest.Market{
  2115  		Base:  15845,   // SDGO
  2116  		Quote: 5264462, // PTN
  2117  	}
  2118  	sub = newSubscription(badMkt)
  2119  	rpcErr = router.handleOrderBook(link, sub)
  2120  	checkErr("bad payload", rpcErr, msgjson.UnknownMarket)
  2121  
  2122  	// Unsub with invalid payload
  2123  	unsub, _ := msgjson.NewRequest(10, msgjson.UnsubOrderBookRoute, &msgjson.UnsubOrderBook{
  2124  		MarketID: mktName1,
  2125  	})
  2126  	ogPayload = unsub.Payload
  2127  	unsub.Payload = []byte(`?`)
  2128  	rpcErr = router.handleUnsubOrderBook(link, unsub)
  2129  	checkErr("bad payload", rpcErr, msgjson.RPCParseError)
  2130  	unsub.Payload = ogPayload
  2131  
  2132  	// Try unsubscribing from an unknown market
  2133  	unsub, _ = msgjson.NewRequest(10, msgjson.UnsubOrderBookRoute, &msgjson.UnsubOrderBook{
  2134  		MarketID: "sdgo_ptn",
  2135  	})
  2136  	rpcErr = router.handleUnsubOrderBook(link, unsub)
  2137  	checkErr("bad payload", rpcErr, msgjson.UnknownMarket)
  2138  
  2139  	// Unsub a user that's not subscribed.
  2140  	unsub, _ = msgjson.NewRequest(10, msgjson.UnsubOrderBookRoute, &msgjson.UnsubOrderBook{
  2141  		MarketID: mktName1,
  2142  	})
  2143  	rpcErr = router.handleUnsubOrderBook(link, unsub)
  2144  	checkErr("bad payload", rpcErr, msgjson.NotSubscribedError)
  2145  }
  2146  
  2147  func TestPriceFeed(t *testing.T) {
  2148  	mktID := "abc_123"
  2149  	rig.router.spots[mktID] = &msgjson.Spot{Vol24: 54321}
  2150  
  2151  	link := tNewLink()
  2152  	sub, _ := msgjson.NewRequest(1, msgjson.PriceFeedRoute, nil)
  2153  	if err := rig.router.handlePriceFeeder(link, sub); err != nil {
  2154  		t.Fatalf("handlePriceFeeder: %v", err)
  2155  	}
  2156  
  2157  	primerMsg := link.getSend()
  2158  	var spots map[string]*msgjson.Spot
  2159  	err := primerMsg.UnmarshalResult(&spots)
  2160  	if err != nil {
  2161  		t.Fatalf("error unmarshaling initial price_feed response: %v", err)
  2162  	}
  2163  
  2164  	if len(spots) != 1 {
  2165  		t.Fatalf("expected 1 spot, got %d", len(spots))
  2166  	}
  2167  
  2168  	spot, found := spots[mktID]
  2169  	if !found {
  2170  		t.Fatal("spot not communicated")
  2171  	}
  2172  
  2173  	if spot.Vol24 != 54321 {
  2174  		t.Fatal("spot volume not communicated")
  2175  	}
  2176  
  2177  	rig.source1.feed <- &updateSignal{
  2178  		action: epochReportAction,
  2179  		data: sigDataEpochReport{
  2180  			spot:  &msgjson.Spot{Vol24: 12345},
  2181  			stats: &matcher.MatchCycleStats{},
  2182  		},
  2183  	}
  2184  
  2185  	update := link.getSend()
  2186  	spot = new(msgjson.Spot)
  2187  	if err := update.Unmarshal(spot); err != nil {
  2188  		t.Fatalf("error unmarhsaling spot: %v", err)
  2189  	}
  2190  	if spot.Vol24 != 12345 {
  2191  		t.Fatal("update volume not communicated")
  2192  	}
  2193  }
  2194  
  2195  func TestParcelLimits(t *testing.T) {
  2196  	mkt0 := tNewMarket(oRig.auth)
  2197  	mkt1 := tNewMarket(oRig.auth)
  2198  	mkt2 := tNewMarket(oRig.auth)
  2199  
  2200  	oRig.router.tunnels = map[string]MarketTunnel{
  2201  		"dcr_btc":      mkt0,
  2202  		"dcr_eth":      mkt1,
  2203  		"firo_polygon": mkt2,
  2204  	}
  2205  
  2206  	dcrID, btcID := uint32(42), uint32(0)
  2207  
  2208  	lo := &order.LimitOrder{
  2209  		P: order.Prefix{
  2210  			BaseAsset:  dcrID,
  2211  			QuoteAsset: btcID,
  2212  			OrderType:  order.LimitOrderType,
  2213  		},
  2214  		T: order.Trade{
  2215  			Sell:     true,
  2216  			Quantity: dcrLotSize,
  2217  		},
  2218  		Rate:  dcrRateStep * 100,
  2219  		Force: order.StandingTiF,
  2220  	}
  2221  
  2222  	oRecord := &orderRecord{order: lo}
  2223  
  2224  	lotSize := mkt0.LotSize()
  2225  	calcParcels := func(settlingWeight uint64) float64 {
  2226  		return calc.Parcels(settlingWeight+lo.Quantity, 0, lotSize, 1)
  2227  	}
  2228  
  2229  	ensureSuccess := func() {
  2230  		t.Helper()
  2231  		// Single lot should definitely be ok.
  2232  		if ok := oRig.router.CheckParcelLimit(oRecord.order.User(), "dcr_btc", calcParcels); !ok {
  2233  			t.Fatalf("not ok")
  2234  		}
  2235  	}
  2236  
  2237  	ensureErr := func() {
  2238  		t.Helper()
  2239  		if ok := oRig.router.CheckParcelLimit(oRecord.order.User(), "dcr_btc", calcParcels); ok {
  2240  			t.Fatalf("not error")
  2241  		}
  2242  	}
  2243  
  2244  	// Single lot should definitely be ok.
  2245  	ensureSuccess()
  2246  
  2247  	// User at tier 0 cannot place orders
  2248  	rep := &oRig.auth.rep
  2249  	rep.maxScore = 60
  2250  	rep.tier = 0
  2251  	ensureErr()
  2252  	rep.tier = 1
  2253  
  2254  	var maxParcels uint64 = dex.PerTierBaseParcelLimit // based on score of 0
  2255  	maxMakerQty := lotSize * maxParcels
  2256  
  2257  	lo.Quantity = maxMakerQty
  2258  	ensureSuccess()
  2259  
  2260  	lo.Quantity = maxMakerQty + lotSize
  2261  	ensureErr()
  2262  
  2263  	// Max out score
  2264  	rep.score = rep.maxScore
  2265  	maxParcels *= dex.ParcelLimitScoreMultiplier
  2266  	parcelQty := lotSize
  2267  	maxMakerQty = parcelQty * maxParcels
  2268  
  2269  	// too much
  2270  	lo.Quantity = maxMakerQty + lotSize
  2271  	ensureErr()
  2272  
  2273  	// max ok
  2274  	lo.Quantity = maxMakerQty
  2275  	ensureSuccess()
  2276  
  2277  	// Adding some settling quantity.
  2278  	oRig.swapper.qtys = map[[2]uint32]uint64{
  2279  		{dcrID, btcID}: lotSize,
  2280  	}
  2281  	ensureErr()
  2282  
  2283  	// Parcels from another market lowers our limit for this order.
  2284  	mkt1.parcels = 1
  2285  	maxMakerQty = (maxParcels-1)*parcelQty - lotSize /* settling */
  2286  	lo.Quantity = maxMakerQty
  2287  	ensureSuccess()
  2288  
  2289  	lo.Quantity += lotSize
  2290  	ensureErr()
  2291  }