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 }