decred.org/dcrdex@v1.0.5/server/swap/swap_test.go (about) 1 // This code is available on the terms of the project LICENSE.md file, 2 // also available online at https://blueoakcouncil.org/license/1.0.0. 3 4 package swap 5 6 import ( 7 "bytes" 8 "context" 9 "crypto/sha256" 10 "encoding/binary" 11 "encoding/hex" 12 "encoding/json" 13 "errors" 14 "fmt" 15 "math/rand" 16 "os" 17 "strconv" 18 "strings" 19 "sync" 20 "sync/atomic" 21 "testing" 22 "time" 23 24 "decred.org/dcrdex/dex" 25 "decred.org/dcrdex/dex/calc" 26 "decred.org/dcrdex/dex/encode" 27 "decred.org/dcrdex/dex/msgjson" 28 "decred.org/dcrdex/dex/order" 29 "decred.org/dcrdex/server/account" 30 "decred.org/dcrdex/server/asset" 31 "decred.org/dcrdex/server/auth" 32 "decred.org/dcrdex/server/coinlock" 33 "decred.org/dcrdex/server/comms" 34 "decred.org/dcrdex/server/db" 35 "decred.org/dcrdex/server/matcher" 36 "github.com/decred/dcrd/dcrec/secp256k1/v4" 37 "github.com/decred/dcrd/dcrec/secp256k1/v4/ecdsa" 38 ) 39 40 const ( 41 ABCID = 123 42 XYZID = 789 43 ACCTID = 456 44 ) 45 46 var ( 47 testCtx context.Context 48 acctTemplate = account.AccountID{ 49 0x22, 0x4c, 0xba, 0xaa, 0xfa, 0x80, 0xbf, 0x3b, 0xd1, 0xff, 0x73, 0x15, 50 0x90, 0xbc, 0xbd, 0xda, 0x5a, 0x76, 0xf9, 0x1e, 0x60, 0xa1, 0x56, 0x99, 51 0x46, 0x34, 0xe9, 0x1c, 0xaa, 0xaa, 0xaa, 0xaa, 52 } 53 acctCounter uint32 54 dexPrivKey *secp256k1.PrivateKey 55 tBcastTimeout time.Duration 56 txWaitExpiration time.Duration 57 ) 58 59 type tUser struct { 60 sig []byte 61 sigHex string 62 acct account.AccountID 63 addr string 64 lbl string 65 matchIDs []order.MatchID 66 } 67 68 func tickMempool() { 69 time.Sleep(fastRecheckInterval * 3 / 2) 70 } 71 72 func timeOutMempool() { 73 time.Sleep(txWaitExpiration * 3 / 2) 74 } 75 76 func dirtyEncode(s string) []byte { 77 b, err := hex.DecodeString(s) 78 if err != nil { 79 fmt.Printf("dirtyEncode error for input '%s': %v", s, err) 80 } 81 return b 82 } 83 84 // A new tUser with a unique account ID, signature, and address. 85 func tNewUser(lbl string) *tUser { 86 intBytes := make([]byte, 4) 87 binary.BigEndian.PutUint32(intBytes, acctCounter) 88 acctID := account.AccountID{} 89 copy(acctID[:], acctTemplate[:]) 90 copy(acctID[account.HashSize-4:], intBytes) 91 addr := strconv.Itoa(int(acctCounter)) 92 sig := []byte{0xab} // Just to differentiate from the addr. 93 sig = append(sig, intBytes...) 94 sigHex := hex.EncodeToString(sig) 95 acctCounter++ 96 return &tUser{ 97 sig: sig, 98 sigHex: sigHex, 99 acct: acctID, 100 addr: addr, 101 lbl: lbl, 102 } 103 } 104 105 type TRequest struct { 106 req *msgjson.Message 107 respFunc func(comms.Link, *msgjson.Message) 108 } 109 110 // This stub satisfies AuthManager. 111 type TAuthManager struct { 112 mtx sync.Mutex 113 authErr error 114 privkey *secp256k1.PrivateKey 115 reqs map[account.AccountID][]*TRequest 116 resps map[account.AccountID][]*msgjson.Message 117 ntfns map[account.AccountID][]*msgjson.Message 118 newNtfn chan struct{} 119 suspensions map[account.AccountID]account.Rule 120 newSuspend chan struct{} 121 swapID uint64 122 // Use swapReceived if you need to synchronize error responses to init 123 // requests. 124 swapReceived chan struct{} 125 auditReq chan struct{} 126 redeemID uint64 127 // Use redeemReceived if you need to synchronize error responses to redeem 128 // requests. 129 redeemReceived chan struct{} 130 redemptionReq chan struct{} 131 } 132 133 func newTAuthManager() *TAuthManager { 134 // Reuse any previously generated dex server private key. 135 if dexPrivKey == nil { 136 dexPrivKey, _ = secp256k1.GeneratePrivateKey() 137 } 138 return &TAuthManager{ 139 privkey: dexPrivKey, 140 reqs: make(map[account.AccountID][]*TRequest), 141 ntfns: make(map[account.AccountID][]*msgjson.Message), 142 resps: make(map[account.AccountID][]*msgjson.Message), 143 suspensions: make(map[account.AccountID]account.Rule), 144 } 145 } 146 147 func (m *TAuthManager) Send(user account.AccountID, msg *msgjson.Message) error { 148 m.mtx.Lock() 149 defer m.mtx.Unlock() 150 l := m.resps[user] 151 if l == nil { 152 l = make([]*msgjson.Message, 0, 1) 153 } 154 if msg.Route == "" { 155 // response 156 m.resps[user] = append(l, msg) 157 if m.redeemReceived != nil && msg.ID == m.redeemID { 158 m.redeemReceived <- struct{}{} 159 } 160 if m.swapReceived != nil && msg.ID == m.swapID { 161 m.swapReceived <- struct{}{} 162 } 163 } else { 164 // notification 165 m.ntfns[user] = append(l, msg) 166 if m.newNtfn != nil { 167 m.newNtfn <- struct{}{} 168 } 169 } 170 return nil 171 } 172 173 func (m *TAuthManager) Request(user account.AccountID, msg *msgjson.Message, 174 f func(comms.Link, *msgjson.Message)) error { 175 return m.RequestWithTimeout(user, msg, f, time.Hour, func() {}) 176 } 177 178 func (m *TAuthManager) RequestWithTimeout(user account.AccountID, msg *msgjson.Message, 179 f func(comms.Link, *msgjson.Message), _ time.Duration, _ func()) error { 180 m.mtx.Lock() 181 defer m.mtx.Unlock() 182 tReq := &TRequest{ 183 req: msg, 184 respFunc: f, 185 } 186 l := m.reqs[user] 187 if l == nil { 188 l = make([]*TRequest, 0, 1) 189 } 190 m.reqs[user] = append(l, tReq) 191 switch { 192 case m.auditReq != nil && msg.Route == msgjson.AuditRoute: 193 m.auditReq <- struct{}{} 194 case m.redemptionReq != nil && msg.Route == msgjson.RedemptionRoute: 195 m.redemptionReq <- struct{}{} 196 } 197 return nil 198 } 199 func (m *TAuthManager) Sign(signables ...msgjson.Signable) { 200 for _, signable := range signables { 201 hash := sha256.Sum256(signable.Serialize()) 202 sig := ecdsa.Sign(m.privkey, hash[:]) 203 signable.SetSig(sig.Serialize()) 204 } 205 } 206 func (m *TAuthManager) Suspended(user account.AccountID) (found, suspended bool) { 207 var rule account.Rule 208 rule, found = m.suspensions[user] 209 suspended = rule != account.NoRule 210 return // TODO: test suspended account handling (no trades, just cancels) 211 } 212 func (m *TAuthManager) Auth(user account.AccountID, msg, sig []byte) error { 213 return m.authErr 214 } 215 func (m *TAuthManager) Route(string, 216 func(account.AccountID, *msgjson.Message) *msgjson.Error) { 217 } 218 219 func (m *TAuthManager) SwapSuccess(id account.AccountID, mmid db.MarketMatchID, value uint64, refTime time.Time) { 220 } 221 func (m *TAuthManager) Inaction(id account.AccountID, step auth.NoActionStep, mmid db.MarketMatchID, matchValue uint64, refTime time.Time, oid order.OrderID) { 222 m.penalize(id, account.FailureToAct) 223 } 224 func (m *TAuthManager) penalize(id account.AccountID, rule account.Rule) { 225 m.mtx.Lock() 226 defer m.mtx.Unlock() 227 m.suspensions[id] = rule 228 if m.newSuspend != nil { 229 m.newSuspend <- struct{}{} 230 } 231 } 232 233 func (m *TAuthManager) flushPenalty(user account.AccountID) (found bool, rule account.Rule) { 234 m.mtx.Lock() 235 defer m.mtx.Unlock() 236 rule, found = m.suspensions[user] 237 if found { 238 delete(m.suspensions, user) 239 } 240 return 241 } 242 243 // pop front 244 func (m *TAuthManager) popReq(id account.AccountID) *TRequest { 245 m.mtx.Lock() 246 defer m.mtx.Unlock() 247 reqs := m.reqs[id] 248 if len(reqs) == 0 { 249 return nil 250 } 251 req := reqs[0] 252 m.reqs[id] = reqs[1:] 253 return req 254 } 255 256 // push front 257 func (m *TAuthManager) pushReq(id account.AccountID, req *TRequest) { 258 m.mtx.Lock() 259 defer m.mtx.Unlock() 260 m.reqs[id] = append([]*TRequest{req}, m.reqs[id]...) 261 } 262 263 func (m *TAuthManager) getNtfn(id account.AccountID, route string, payload any) error { 264 m.mtx.Lock() 265 defer m.mtx.Unlock() 266 msgs := m.ntfns[id] 267 if len(msgs) == 0 { 268 return errors.New("no message") 269 } 270 msg := msgs[0] 271 if msg.Route != route { 272 return fmt.Errorf("wrong route: %v", route) 273 } 274 return msg.Unmarshal(payload) 275 } 276 277 // push front 278 func (m *TAuthManager) pushResp(id account.AccountID, msg *msgjson.Message) { 279 m.mtx.Lock() 280 defer m.mtx.Unlock() 281 m.resps[id] = append([]*msgjson.Message{msg}, m.resps[id]...) 282 } 283 284 // pop front 285 func (m *TAuthManager) popResp(id account.AccountID) (msg *msgjson.Message, resp *msgjson.ResponsePayload) { 286 m.mtx.Lock() 287 defer m.mtx.Unlock() 288 msgs := m.resps[id] 289 if len(msgs) == 0 { 290 return 291 } 292 msg = msgs[0] 293 m.resps[id] = msgs[1:] 294 resp, _ = msg.Response() 295 return 296 } 297 298 type TStorage struct { 299 fatalMtx sync.RWMutex 300 fatal chan struct{} 301 fatalErr error 302 } 303 304 func (ts *TStorage) LastErr() error { 305 ts.fatalMtx.RLock() 306 defer ts.fatalMtx.RUnlock() 307 return ts.fatalErr 308 } 309 310 func (ts *TStorage) Fatal() <-chan struct{} { 311 ts.fatalMtx.RLock() 312 defer ts.fatalMtx.RUnlock() 313 return ts.fatal 314 } 315 316 func (ts *TStorage) fatalBackendErr(err error) { 317 ts.fatalMtx.Lock() 318 if ts.fatal == nil { 319 ts.fatal = make(chan struct{}) 320 close(ts.fatal) 321 } 322 ts.fatalErr = err // consider slice and append 323 ts.fatalMtx.Unlock() 324 } 325 326 func (ts *TStorage) Order(oid order.OrderID, base, quote uint32) (order.Order, order.OrderStatus, error) { 327 return nil, order.OrderStatusUnknown, nil // not loading swaps 328 } 329 func (ts *TStorage) CancelOrder(*order.LimitOrder) error { return nil } 330 func (ts *TStorage) ActiveSwaps() ([]*db.SwapDataFull, error) { return nil, nil } 331 func (ts *TStorage) InsertMatch(match *order.Match) error { return nil } 332 func (ts *TStorage) SwapData(mid db.MarketMatchID) (order.MatchStatus, *db.SwapData, error) { 333 return 0, nil, nil 334 } 335 func (ts *TStorage) SaveMatchAckSigA(mid db.MarketMatchID, sig []byte) error { return nil } 336 func (ts *TStorage) SaveMatchAckSigB(mid db.MarketMatchID, sig []byte) error { return nil } 337 338 // Contract data. 339 func (ts *TStorage) SaveContractA(mid db.MarketMatchID, contract []byte, coinID []byte, timestamp int64) error { 340 return nil 341 } 342 func (ts *TStorage) SaveAuditAckSigB(mid db.MarketMatchID, sig []byte) error { return nil } 343 func (ts *TStorage) SaveContractB(mid db.MarketMatchID, contract []byte, coinID []byte, timestamp int64) error { 344 return nil 345 } 346 func (ts *TStorage) SaveAuditAckSigA(mid db.MarketMatchID, sig []byte) error { return nil } 347 348 // Redeem data. 349 func (ts *TStorage) SaveRedeemA(mid db.MarketMatchID, coinID, secret []byte, timestamp int64) error { 350 return nil 351 } 352 func (ts *TStorage) SaveRedeemAckSigB(mid db.MarketMatchID, sig []byte) error { 353 return nil 354 } 355 func (ts *TStorage) SaveRedeemB(mid db.MarketMatchID, coinID []byte, timestamp int64) error { 356 return nil 357 } 358 func (ts *TStorage) SetMatchInactive(mid db.MarketMatchID, forgive bool) error { return nil } 359 360 type redeemKey struct { 361 redemptionCoin string 362 counterpartySwapCoin string 363 } 364 365 // This stub satisfies asset.Backend. 366 type TBackend struct { 367 mtx sync.RWMutex 368 contracts map[string]*asset.Contract 369 contractErr error 370 fundsErr error 371 redemptions map[redeemKey]asset.Coin 372 redemptionErr error 373 bChan chan *asset.BlockUpdate // to trigger processBlock and eventually (after up to BroadcastTimeout) checkInaction depending on block time 374 lbl string 375 invalidFeeRate bool 376 } 377 378 func newTBackend(lbl string) TBackend { 379 return TBackend{ 380 bChan: make(chan *asset.BlockUpdate, 5), 381 lbl: lbl, 382 contracts: make(map[string]*asset.Contract), 383 redemptions: make(map[redeemKey]asset.Coin), 384 fundsErr: asset.CoinNotFoundError, 385 } 386 } 387 388 func newUTXOBackend(lbl string) *TUTXOBackend { 389 return &TUTXOBackend{TBackend: newTBackend(lbl)} 390 } 391 392 func newAccountBackend(lbl string) *TAccountBackend { 393 return &TAccountBackend{newTBackend(lbl)} 394 } 395 396 func (a *TBackend) Contract(coinID, redeemScript []byte) (*asset.Contract, error) { 397 a.mtx.RLock() 398 defer a.mtx.RUnlock() 399 if a.contractErr != nil { 400 return nil, a.contractErr 401 } 402 contract, found := a.contracts[string(coinID)] 403 if !found || contract == nil { 404 return nil, asset.CoinNotFoundError 405 } 406 407 return contract, nil 408 } 409 func (a *TBackend) Redemption(redemptionID, cpSwapCoinID, contractData []byte) (asset.Coin, error) { 410 a.mtx.RLock() 411 defer a.mtx.RUnlock() 412 if a.redemptionErr != nil { 413 return nil, a.redemptionErr 414 } 415 redeem, found := a.redemptions[redeemKey{string(redemptionID), string(cpSwapCoinID)}] 416 if !found || redeem == nil { 417 return nil, asset.CoinNotFoundError 418 } 419 return redeem, nil 420 } 421 func (a *TBackend) ValidateCoinID(coinID []byte) (string, error) { 422 return "", nil 423 } 424 func (a *TBackend) ValidateContract(contract []byte) error { 425 return nil 426 } 427 func (a *TBackend) BlockChannel(size int) <-chan *asset.BlockUpdate { return a.bChan } 428 func (a *TBackend) FeeRate(context.Context) (uint64, error) { return 10, nil } 429 func (a *TBackend) CheckSwapAddress(string) bool { return true } 430 func (a *TBackend) Connect(context.Context) (*sync.WaitGroup, error) { return nil, nil } 431 func (a *TBackend) ValidateSecret(secret, contract []byte) bool { return true } 432 func (a *TBackend) Synced() (bool, error) { return true, nil } 433 func (a *TBackend) TxData([]byte) ([]byte, error) { 434 return nil, nil 435 } 436 437 func (a *TBackend) setContractErr(err error) { 438 a.mtx.Lock() 439 defer a.mtx.Unlock() 440 a.contractErr = err 441 } 442 func (a *TBackend) setContract(contract *asset.Contract, resetErr bool) { 443 a.mtx.Lock() 444 a.contracts[string(contract.ID())] = contract 445 if resetErr { 446 a.contractErr = nil 447 } 448 a.mtx.Unlock() 449 } 450 451 func (a *TBackend) setRedemptionErr(err error) { 452 a.mtx.Lock() 453 defer a.mtx.Unlock() 454 a.redemptionErr = err 455 } 456 func (a *TBackend) setRedemption(redeem asset.Coin, cpSwap asset.Coin, resetErr bool) { 457 a.mtx.Lock() 458 a.redemptions[redeemKey{string(redeem.ID()), string(cpSwap.ID())}] = redeem 459 if resetErr { 460 a.redemptionErr = nil 461 } 462 a.mtx.Unlock() 463 } 464 func (*TBackend) Info() *asset.BackendInfo { 465 return &asset.BackendInfo{} 466 } 467 func (a *TBackend) ValidateFeeRate(asset.Coin, uint64) bool { 468 return !a.invalidFeeRate 469 } 470 471 type TUTXOBackend struct { 472 TBackend 473 funds asset.FundingCoin 474 } 475 476 func (a *TUTXOBackend) FundingCoin(_ context.Context, coinID, redeemScript []byte) (asset.FundingCoin, error) { 477 a.mtx.RLock() 478 defer a.mtx.RUnlock() 479 return a.funds, a.fundsErr 480 } 481 482 func (a *TUTXOBackend) VerifyUnspentCoin(_ context.Context, coinID []byte) error { return nil } 483 484 type TAccountBackend struct { 485 TBackend 486 } 487 488 var _ asset.AccountBalancer = (*TAccountBackend)(nil) 489 490 func (b *TAccountBackend) AccountBalance(addr string) (uint64, error) { 491 return 0, nil 492 } 493 494 func (b *TAccountBackend) ValidateSignature(addr string, pubkey, msg, sig []byte) error { 495 return nil 496 } 497 498 func (a *TAccountBackend) InitTxSize() uint64 { return 100 } 499 500 func (b *TAccountBackend) RedeemSize() uint64 { 501 return 21_000 502 } 503 504 // This stub satisfies asset.Transaction, used by asset.Backend. 505 type TCoin struct { 506 mtx sync.RWMutex 507 id []byte 508 confs int64 509 confsErr error 510 auditAddr string 511 auditVal uint64 512 feeRate uint64 513 } 514 515 func (coin *TCoin) Confirmations(context.Context) (int64, error) { 516 coin.mtx.RLock() 517 defer coin.mtx.RUnlock() 518 return coin.confs, coin.confsErr 519 } 520 521 func (coin *TCoin) Addresses() []string { 522 return []string{coin.auditAddr} 523 } 524 525 func (coin *TCoin) setConfs(confs int64) { 526 coin.mtx.Lock() 527 defer coin.mtx.Unlock() 528 coin.confs = confs 529 } 530 531 func (coin *TCoin) Auth(pubkeys, sigs [][]byte, msg []byte) error { return nil } 532 func (coin *TCoin) ID() []byte { return coin.id } 533 func (coin *TCoin) TxID() string { return hex.EncodeToString(coin.id) } 534 func (coin *TCoin) Value() uint64 { return coin.auditVal } 535 func (coin *TCoin) SpendSize() uint32 { return 0 } 536 func (coin *TCoin) String() string { return hex.EncodeToString(coin.id) /* not txid:vout */ } 537 538 func (coin *TCoin) FeeRate() uint64 { 539 return coin.feeRate 540 } 541 542 func TNewAsset(backend asset.Backend, assetID uint32) *asset.BackedAsset { 543 return &asset.BackedAsset{ 544 Backend: backend, 545 Asset: dex.Asset{ 546 ID: assetID, 547 Symbol: "qwe", 548 MaxFeeRate: 120, // not used by Swapper other than prohibiting zero 549 SwapConf: 2, 550 }, 551 } 552 } 553 554 var testMsgID uint64 555 556 func nextID() uint64 { 557 return atomic.AddUint64(&testMsgID, 1) 558 } 559 560 func tNewResponse(id uint64, resp []byte) *msgjson.Message { 561 msg, _ := msgjson.NewResponse(id, json.RawMessage(resp), nil) 562 return msg 563 } 564 565 // testRig is the primary test data structure. 566 type testRig struct { 567 abc *asset.BackedAsset 568 abcNode *TUTXOBackend 569 xyz *asset.BackedAsset 570 xyzNode *TUTXOBackend 571 acctAsset *asset.BackedAsset 572 acctNode *TAccountBackend 573 auth *TAuthManager 574 swapper *Swapper 575 swapperWaiter *dex.StartStopWaiter 576 storage *TStorage 577 matches *tMatchSet 578 matchInfo *tMatch 579 noResume bool 580 } 581 582 func tNewTestRig(matchInfo *tMatch) (*testRig, func()) { 583 storage := &TStorage{} 584 authMgr := newTAuthManager() 585 var noResume bool 586 587 abcBackend := newUTXOBackend("abc") 588 xyzBackend := newUTXOBackend("xyz") 589 acctBackend := newAccountBackend("acct") 590 591 abcAsset := TNewAsset(abcBackend, ABCID) 592 abcCoinLocker := coinlock.NewAssetCoinLocker() 593 594 xyzAsset := TNewAsset(xyzBackend, XYZID) 595 xyzCoinLocker := coinlock.NewAssetCoinLocker() 596 597 acctAsset := TNewAsset(acctBackend, ACCTID) 598 599 swapper, err := NewSwapper(&Config{ 600 Assets: map[uint32]*SwapperAsset{ 601 ABCID: {abcAsset, abcCoinLocker}, 602 XYZID: {xyzAsset, xyzCoinLocker}, 603 ACCTID: {BackedAsset: acctAsset}, // no coin locker for account based asset. 604 }, 605 Storage: storage, 606 AuthManager: authMgr, 607 BroadcastTimeout: tBcastTimeout, 608 TxWaitExpiration: txWaitExpiration, 609 LockTimeTaker: dex.LockTimeTaker(dex.Testnet), 610 LockTimeMaker: dex.LockTimeMaker(dex.Testnet), 611 SwapDone: func(ord order.Order, match *order.Match, fail bool) {}, 612 }) 613 if err != nil { 614 panic(err.Error()) 615 } 616 617 ssw := dex.NewStartStopWaiter(swapper) 618 ssw.Start(testCtx) 619 cleanup := func() { 620 ssw.Stop() 621 ssw.WaitForShutdown() 622 } 623 624 return &testRig{ 625 abc: abcAsset, 626 abcNode: abcBackend, 627 xyz: xyzAsset, 628 xyzNode: xyzBackend, 629 acctAsset: acctAsset, 630 acctNode: acctBackend, 631 auth: authMgr, 632 swapper: swapper, 633 swapperWaiter: ssw, 634 storage: storage, 635 matchInfo: matchInfo, 636 noResume: noResume, 637 }, cleanup 638 } 639 640 func (rig *testRig) getTracker() *matchTracker { 641 rig.swapper.matchMtx.Lock() 642 defer rig.swapper.matchMtx.Unlock() 643 return rig.swapper.matches[rig.matchInfo.matchID] 644 } 645 646 // waitChans waits on the specified channels sequentially in the order given. 647 // nil channels are ignored. 648 func (rig *testRig) waitChans(tag string, chans ...chan struct{}) error { 649 for _, c := range chans { 650 if c == nil { 651 continue 652 } 653 select { 654 case <-c: 655 case <-time.After(time.Second): 656 return fmt.Errorf("waiting on %q timed out", tag) 657 } 658 } 659 return nil 660 } 661 662 // Taker: Acknowledge the servers match notification. 663 func (rig *testRig) ackMatch_maker(checkSig bool) (err error) { 664 matchInfo := rig.matchInfo 665 err = rig.ackMatch(matchInfo.maker, matchInfo.makerOID, matchInfo.taker.addr) 666 if err != nil { 667 return err 668 } 669 if checkSig { 670 tracker := rig.getTracker() 671 if !bytes.Equal(tracker.Sigs.MakerMatch, matchInfo.maker.sig) { 672 return fmt.Errorf("expected maker audit signature '%x', got '%x'", matchInfo.maker.sig, tracker.Sigs.MakerMatch) 673 } 674 } 675 return nil 676 } 677 678 // Maker: Acknowledge the servers match notification. 679 func (rig *testRig) ackMatch_taker(checkSig bool) error { 680 matchInfo := rig.matchInfo 681 err := rig.ackMatch(matchInfo.taker, matchInfo.takerOID, matchInfo.maker.addr) 682 if err != nil { 683 return err 684 } 685 if checkSig { 686 tracker := rig.getTracker() 687 if !bytes.Equal(tracker.Sigs.TakerMatch, matchInfo.taker.sig) { 688 return fmt.Errorf("expected taker audit signature '%x', got '%x'", matchInfo.taker.sig, tracker.Sigs.TakerMatch) 689 } 690 } 691 return nil 692 } 693 694 func (rig *testRig) ackMatch(user *tUser, oid order.OrderID, counterAddr string) error { 695 req := rig.auth.popReq(user.acct) 696 if req == nil { 697 return fmt.Errorf("failed to find match notification for %s", user.lbl) 698 } 699 if req.req.Route != msgjson.MatchRoute { 700 return fmt.Errorf("expected method '%s', got '%s'", msgjson.MatchRoute, req.req.Route) 701 } 702 err := rig.checkMatchNotification(req.req, oid, counterAddr) 703 if err != nil { 704 return err 705 } 706 // The maker and taker would sign the notifications and return a list of 707 // authorizations. 708 resp := tNewResponse(req.req.ID, tAckArr(user, user.matchIDs)) 709 req.respFunc(nil, resp) // e.g. processMatchAcks, may Send resp on error 710 return nil 711 } 712 713 // Helper to check the match notifications. 714 func (rig *testRig) checkMatchNotification(msg *msgjson.Message, oid order.OrderID, counterAddr string) error { 715 matchInfo := rig.matchInfo 716 var notes []*msgjson.Match 717 err := json.Unmarshal(msg.Payload, ¬es) 718 if err != nil { 719 fmt.Printf("checkMatchNotification unmarshal error: %v\n", err) 720 } 721 var notification *msgjson.Match 722 for _, n := range notes { 723 if bytes.Equal(n.MatchID, matchInfo.matchID[:]) { 724 notification = n 725 break 726 } 727 if err = checkSigS256(n, rig.auth.privkey.PubKey()); err != nil { 728 return fmt.Errorf("incorrect server signature: %w", err) 729 } 730 } 731 if notification == nil { 732 return fmt.Errorf("did not find match ID %s in match notifications", matchInfo.matchID) 733 } 734 if notification.OrderID.String() != oid.String() { 735 return fmt.Errorf("expected order ID %s, got %s", oid, notification.OrderID) 736 } 737 if notification.Quantity != matchInfo.qty { 738 return fmt.Errorf("expected order quantity %d, got %d", matchInfo.qty, notification.Quantity) 739 } 740 if notification.Rate != matchInfo.rate { 741 return fmt.Errorf("expected match rate %d, got %d", matchInfo.rate, notification.Rate) 742 } 743 if notification.Address != counterAddr { 744 return fmt.Errorf("expected match address %s, got %s", counterAddr, notification.Address) 745 } 746 return nil 747 } 748 749 // Helper to check the swap status for specified user. 750 // Swap counterparty usually gets a request from server notifying that it's time 751 // for his turn. Synchronize with that before checking status change. 752 func (rig *testRig) ensureSwapStatus(tag string, wantStatus order.MatchStatus, waitOn ...chan struct{}) error { 753 if err := rig.waitChans(tag, waitOn...); err != nil { 754 return err 755 } 756 tracker := rig.getTracker() 757 status := tracker.Status 758 if status != wantStatus { 759 return fmt.Errorf("unexpected swap status %d after maker swap notification, wanted: %s", status, wantStatus) 760 } 761 return nil 762 } 763 764 // Can be used to ensure that a non-error response is returned from the swapper. 765 func (rig *testRig) checkServerResponseSuccess(user *tUser) error { 766 msg, resp := rig.auth.popResp(user.acct) 767 if msg == nil { 768 return fmt.Errorf("unexpected nil response to %s's request", user.lbl) 769 } 770 if resp.Error != nil { 771 return fmt.Errorf("%s swap rpc error. code: %d, msg: %s", user.lbl, resp.Error.Code, resp.Error.Message) 772 } 773 return nil 774 } 775 776 // Can be used to ensure that an error response is returned from the swapper. 777 func (rig *testRig) checkServerResponseFail(user *tUser, code int, grep ...string) error { 778 msg, resp := rig.auth.popResp(user.acct) 779 if msg == nil { 780 return fmt.Errorf("no response for %s", user.lbl) 781 } 782 if resp.Error == nil { 783 return fmt.Errorf("no error for %s", user.lbl) 784 } 785 if resp.Error.Code != code { 786 return fmt.Errorf("wrong error code for %s. expected %d, got %d", user.lbl, code, resp.Error.Code) 787 } 788 if len(grep) > 0 && !strings.Contains(resp.Error.Message, grep[0]) { 789 return fmt.Errorf("error missing the message %q", grep[0]) 790 } 791 return nil 792 } 793 794 // Maker: Send swap transaction (swap init request). 795 func (rig *testRig) sendSwap_maker(expectSuccess bool) (err error) { 796 matchInfo := rig.matchInfo 797 swap, err := rig.sendSwap(matchInfo.maker, matchInfo.makerOID, matchInfo.taker.addr) 798 if err != nil { 799 return fmt.Errorf("error sending maker swap request: %w", err) 800 } 801 matchInfo.db.makerSwap = swap 802 if expectSuccess { 803 err = rig.ensureSwapStatus("server received our swap -> counterparty got audit request", 804 order.MakerSwapCast, rig.auth.swapReceived, rig.auth.auditReq) 805 if err != nil { 806 return fmt.Errorf("ensure swap status: %w", err) 807 } 808 err = rig.checkServerResponseSuccess(matchInfo.maker) 809 if err != nil { 810 return fmt.Errorf("check server response success: %w", err) 811 } 812 } 813 return nil 814 } 815 816 // Taker: Send swap transaction (swap init request).. 817 func (rig *testRig) sendSwap_taker(expectSuccess bool) (err error) { 818 matchInfo := rig.matchInfo 819 swap, err := rig.sendSwap(matchInfo.taker, matchInfo.takerOID, matchInfo.maker.addr) 820 if err != nil { 821 return fmt.Errorf("error sending taker swap request: %w", err) 822 } 823 matchInfo.db.takerSwap = swap 824 if err != nil { 825 return err 826 } 827 if expectSuccess { 828 err = rig.ensureSwapStatus("server received our swap -> counterparty got audit request", 829 order.TakerSwapCast, rig.auth.swapReceived, rig.auth.auditReq) 830 if err != nil { 831 return fmt.Errorf("ensure swap status: %w", err) 832 } 833 err = rig.checkServerResponseSuccess(matchInfo.taker) 834 if err != nil { 835 return fmt.Errorf("check server response success: %w", err) 836 } 837 } 838 return nil 839 } 840 841 func (rig *testRig) sendSwap(user *tUser, oid order.OrderID, recipient string) (*tSwap, error) { 842 matchInfo := rig.matchInfo 843 swap := tNewSwap(matchInfo, oid, recipient, user) 844 if isQuoteSwap(user, matchInfo.match) { 845 rig.xyzNode.setContract(swap.coin, false) 846 } else { 847 rig.abcNode.setContract(swap.coin, false) 848 } 849 rig.auth.swapID = swap.req.ID 850 rpcErr := rig.swapper.handleInit(user.acct, swap.req) 851 if rpcErr != nil { 852 resp, _ := msgjson.NewResponse(swap.req.ID, nil, rpcErr) 853 _ = rig.auth.Send(user.acct, resp) 854 return nil, fmt.Errorf("%s swap rpc error. code: %d, msg: %s", user.lbl, rpcErr.Code, rpcErr.Message) 855 } 856 return swap, nil 857 } 858 859 // Taker: Process the 'audit' request from the swapper. The request should be 860 // acknowledged separately with ackAudit_taker. 861 func (rig *testRig) auditSwap_taker() error { 862 matchInfo := rig.matchInfo 863 req := rig.auth.popReq(matchInfo.taker.acct) 864 matchInfo.db.takerAudit = req 865 if req == nil { 866 return fmt.Errorf("failed to find audit request for taker after maker's init") 867 } 868 return rig.auditSwap(req.req, matchInfo.takerOID, matchInfo.db.makerSwap, "taker", matchInfo.taker) 869 } 870 871 // Maker: Process the 'audit' request from the swapper. The request should be 872 // acknowledged separately with ackAudit_maker. 873 func (rig *testRig) auditSwap_maker() error { 874 matchInfo := rig.matchInfo 875 req := rig.auth.popReq(matchInfo.maker.acct) 876 matchInfo.db.makerAudit = req 877 if req == nil { 878 return fmt.Errorf("failed to find audit request for maker after taker's init") 879 } 880 return rig.auditSwap(req.req, matchInfo.makerOID, matchInfo.db.takerSwap, "maker", matchInfo.maker) 881 } 882 883 // checkSigS256 checks that the message's signature was created with the 884 // private key for the provided secp256k1 public key. 885 func checkSigS256(msg msgjson.Signable, pubKey *secp256k1.PublicKey) error { 886 signature, err := ecdsa.ParseDERSignature(msg.SigBytes()) 887 if err != nil { 888 return fmt.Errorf("error decoding secp256k1 Signature from bytes: %w", err) 889 } 890 msgB := msg.Serialize() 891 hash := sha256.Sum256(msgB) 892 if !signature.Verify(hash[:], pubKey) { 893 return fmt.Errorf("secp256k1 signature verification failed") 894 } 895 return nil 896 } 897 898 func (rig *testRig) auditSwap(msg *msgjson.Message, oid order.OrderID, swap *tSwap, tag string, user *tUser) error { 899 if msg == nil { 900 return fmt.Errorf("no %s 'audit' request from DEX", user.lbl) 901 } 902 903 if msg.Route != msgjson.AuditRoute { 904 return fmt.Errorf("expected method '%s', got '%s'", msgjson.AuditRoute, msg.Route) 905 } 906 var params *msgjson.Audit 907 err := json.Unmarshal(msg.Payload, ¶ms) 908 if err != nil { 909 return fmt.Errorf("error unmarshaling audit params: %w", err) 910 } 911 if err = checkSigS256(params, rig.auth.privkey.PubKey()); err != nil { 912 return fmt.Errorf("incorrect server signature: %w", err) 913 } 914 if params.OrderID.String() != oid.String() { 915 return fmt.Errorf("%s : incorrect order ID in auditSwap, expected '%s', got '%s'", tag, oid, params.OrderID) 916 } 917 matchID := rig.matchInfo.matchID 918 if params.MatchID.String() != matchID.String() { 919 return fmt.Errorf("%s : incorrect match ID in auditSwap, expected '%s', got '%s'", tag, matchID, params.MatchID) 920 } 921 if params.Contract.String() != swap.contract { 922 return fmt.Errorf("%s : incorrect contract. expected '%s', got '%s'", tag, swap.contract, params.Contract) 923 } 924 if !bytes.Equal(params.TxData, swap.coin.TxData) { 925 return fmt.Errorf("%s : incorrect tx data. expected '%s', got '%s'", tag, swap.coin.TxData, params.TxData) 926 } 927 return nil 928 } 929 930 // Maker: Acknowledge the DEX 'audit' request. 931 func (rig *testRig) ackAudit_maker(checkSig bool) error { 932 maker := rig.matchInfo.maker 933 err := rig.ackAudit(maker, rig.matchInfo.db.makerAudit) 934 if err != nil { 935 return err 936 } 937 if checkSig { 938 tracker := rig.getTracker() 939 if !bytes.Equal(tracker.Sigs.MakerAudit, maker.sig) { 940 return fmt.Errorf("expected taker audit signature '%x', got '%x'", maker.sig, tracker.Sigs.MakerAudit) 941 } 942 } 943 return nil 944 } 945 946 // Taker: Acknowledge the DEX 'audit' request. 947 func (rig *testRig) ackAudit_taker(checkSig bool) error { 948 taker := rig.matchInfo.taker 949 err := rig.ackAudit(taker, rig.matchInfo.db.takerAudit) 950 if err != nil { 951 return err 952 } 953 if checkSig { 954 tracker := rig.getTracker() 955 if !bytes.Equal(tracker.Sigs.TakerAudit, taker.sig) { 956 return fmt.Errorf("expected taker audit signature '%x', got '%x'", taker.sig, tracker.Sigs.TakerAudit) 957 } 958 } 959 return nil 960 } 961 962 func (rig *testRig) ackAudit(user *tUser, req *TRequest) error { 963 if req == nil { 964 return fmt.Errorf("no %s 'audit' request from DEX", user.lbl) 965 } 966 req.respFunc(nil, tNewResponse(req.req.ID, tAck(user, rig.matchInfo.matchID))) 967 return nil 968 } 969 970 // Maker: Redeem taker's swap transaction. 971 func (rig *testRig) redeem_maker(expectSuccess bool) error { 972 matchInfo := rig.matchInfo 973 redeem, err := rig.redeem(matchInfo.maker, matchInfo.makerOID) 974 if err != nil { 975 return fmt.Errorf("error sending maker redeem request: %w", err) 976 } 977 matchInfo.db.makerRedeem = redeem 978 if expectSuccess { 979 err = rig.ensureSwapStatus("server received our redeem -> counterparty got redemption request", 980 order.MakerRedeemed, rig.auth.redeemReceived, rig.auth.redemptionReq) 981 if err != nil { 982 return fmt.Errorf("ensure swap status: %w", err) 983 } 984 err = rig.checkServerResponseSuccess(matchInfo.maker) 985 if err != nil { 986 return fmt.Errorf("check server response success: %w", err) 987 } 988 } 989 return nil 990 } 991 992 // Taker: Redeem maker's swap transaction. 993 func (rig *testRig) redeem_taker(expectSuccess bool) error { 994 matchInfo := rig.matchInfo 995 redeem, err := rig.redeem(matchInfo.taker, matchInfo.takerOID) 996 if err != nil { 997 return fmt.Errorf("error sending taker redeem request: %w", err) 998 } 999 matchInfo.db.takerRedeem = redeem 1000 if expectSuccess { 1001 if err := rig.waitChans("server received our redeem and we got a redemption note", rig.auth.redeemReceived, rig.auth.redemptionReq); err != nil { 1002 return err 1003 } 1004 tracker := rig.getTracker() 1005 if tracker.Status != order.MatchComplete { 1006 return fmt.Errorf("unexpected swap status %d after taker redeem notification", tracker.Status) 1007 } 1008 err = rig.checkServerResponseSuccess(matchInfo.taker) 1009 if err != nil { 1010 return fmt.Errorf("check server response success: %w", err) 1011 } 1012 } 1013 return nil 1014 } 1015 1016 func (rig *testRig) redeem(user *tUser, oid order.OrderID) (*tRedeem, error) { 1017 matchInfo := rig.matchInfo 1018 redeem := tNewRedeem(matchInfo, oid, user) 1019 if isQuoteSwap(user, matchInfo.match) { 1020 // do not clear redemptionErr yet 1021 rig.abcNode.setRedemption(redeem.coin, redeem.cpSwapCoin, false) 1022 } else { 1023 rig.xyzNode.setRedemption(redeem.coin, redeem.cpSwapCoin, false) 1024 } 1025 rig.auth.redeemID = redeem.req.ID 1026 rpcErr := rig.swapper.handleRedeem(user.acct, redeem.req) 1027 if rpcErr != nil { 1028 resp, _ := msgjson.NewResponse(redeem.req.ID, nil, rpcErr) 1029 _ = rig.auth.Send(user.acct, resp) 1030 return nil, fmt.Errorf("%s swap rpc error. code: %d, msg: %s", user.lbl, rpcErr.Code, rpcErr.Message) 1031 } 1032 return redeem, nil 1033 } 1034 1035 // Taker: Acknowledge the DEX 'redemption' request. 1036 func (rig *testRig) ackRedemption_taker(checkSig bool) error { 1037 matchInfo := rig.matchInfo 1038 err := rig.ackRedemption(matchInfo.taker, matchInfo.takerOID, matchInfo.db.makerRedeem) 1039 if err != nil { 1040 return err 1041 } 1042 if checkSig { 1043 tracker := rig.getTracker() 1044 if !bytes.Equal(tracker.Sigs.TakerRedeem, matchInfo.taker.sig) { 1045 return fmt.Errorf("expected taker redemption signature '%x', got '%x'", matchInfo.taker.sig, tracker.Sigs.TakerRedeem) 1046 } 1047 } 1048 return nil 1049 } 1050 1051 // Maker: Acknowledge the DEX 'redemption' request. 1052 func (rig *testRig) ackRedemption_maker(checkSig bool) error { 1053 matchInfo := rig.matchInfo 1054 err := rig.ackRedemption(matchInfo.maker, matchInfo.makerOID, matchInfo.db.takerRedeem) 1055 if err != nil { 1056 return err 1057 } 1058 if checkSig { 1059 tracker := rig.getTracker() 1060 if tracker != nil { 1061 return fmt.Errorf("expected match to be removed, found it, in status %v", tracker.Status) 1062 } 1063 } 1064 return nil 1065 } 1066 1067 func (rig *testRig) ackRedemption(user *tUser, oid order.OrderID, redeem *tRedeem) error { 1068 if redeem == nil { 1069 return fmt.Errorf("nil redeem info") 1070 } 1071 req := rig.auth.popReq(user.acct) 1072 if req == nil { 1073 return fmt.Errorf("failed to find redemption request for %s", user.lbl) 1074 } 1075 err := rig.checkRedeem(req.req, oid, redeem.coin.ID(), user.lbl) 1076 if err != nil { 1077 return err 1078 } 1079 req.respFunc(nil, tNewResponse(req.req.ID, tAck(user, rig.matchInfo.matchID))) 1080 return nil 1081 } 1082 1083 func (rig *testRig) checkRedeem(msg *msgjson.Message, oid order.OrderID, coinID []byte, tag string) error { 1084 var params *msgjson.Redemption 1085 err := json.Unmarshal(msg.Payload, ¶ms) 1086 if err != nil { 1087 return fmt.Errorf("error unmarshaling redeem params: %w", err) 1088 } 1089 if err = checkSigS256(params, rig.auth.privkey.PubKey()); err != nil { 1090 return fmt.Errorf("incorrect server signature: %w", err) 1091 } 1092 if params.OrderID.String() != oid.String() { 1093 return fmt.Errorf("%s : incorrect order ID in checkRedeem, expected '%s', got '%s'", tag, oid, params.OrderID) 1094 } 1095 matchID := rig.matchInfo.matchID 1096 if params.MatchID.String() != matchID.String() { 1097 return fmt.Errorf("%s : incorrect match ID in checkRedeem, expected '%s', got '%s'", tag, matchID, params.MatchID) 1098 } 1099 if !bytes.Equal(params.CoinID, coinID) { 1100 return fmt.Errorf("%s : incorrect coinID in checkRedeem. expected '%x', got '%x'", tag, coinID, params.CoinID) 1101 } 1102 return nil 1103 } 1104 1105 func makeCancelOrder(limitOrder *order.LimitOrder, user *tUser) *order.CancelOrder { 1106 return &order.CancelOrder{ 1107 P: order.Prefix{ 1108 AccountID: user.acct, 1109 BaseAsset: limitOrder.BaseAsset, 1110 QuoteAsset: limitOrder.QuoteAsset, 1111 OrderType: order.CancelOrderType, 1112 ClientTime: unixMsNow(), 1113 ServerTime: unixMsNow(), 1114 }, 1115 TargetOrderID: limitOrder.ID(), 1116 } 1117 } 1118 1119 func makeLimitOrder(qty, rate uint64, user *tUser, makerSell bool) *order.LimitOrder { 1120 return &order.LimitOrder{ 1121 P: order.Prefix{ 1122 AccountID: user.acct, 1123 BaseAsset: ABCID, 1124 QuoteAsset: XYZID, 1125 OrderType: order.LimitOrderType, 1126 ClientTime: time.UnixMilli(1566497654000), 1127 ServerTime: time.UnixMilli(1566497655000), 1128 }, 1129 T: order.Trade{ 1130 Sell: makerSell, 1131 Quantity: qty, 1132 Address: user.addr, 1133 }, 1134 Rate: rate, 1135 } 1136 } 1137 1138 func makeMarketOrder(qty uint64, user *tUser, makerSell bool) *order.MarketOrder { 1139 return &order.MarketOrder{ 1140 P: order.Prefix{ 1141 AccountID: user.acct, 1142 BaseAsset: ABCID, 1143 QuoteAsset: XYZID, 1144 OrderType: order.LimitOrderType, 1145 ClientTime: time.UnixMilli(1566497654000), 1146 ServerTime: time.UnixMilli(1566497655000), 1147 }, 1148 T: order.Trade{ 1149 Sell: makerSell, 1150 Quantity: qty, 1151 Address: user.addr, 1152 }, 1153 } 1154 } 1155 1156 func limitLimitPair(makerQty, takerQty, makerRate, takerRate uint64, maker, taker *tUser, makerSell bool) (*order.LimitOrder, *order.LimitOrder) { 1157 return makeLimitOrder(makerQty, makerRate, maker, makerSell), makeLimitOrder(takerQty, takerRate, taker, !makerSell) 1158 } 1159 1160 func marketLimitPair(makerQty, takerQty, rate uint64, maker, taker *tUser, makerSell bool) (*order.LimitOrder, *order.MarketOrder) { 1161 return makeLimitOrder(makerQty, rate, maker, makerSell), makeMarketOrder(takerQty, taker, !makerSell) 1162 } 1163 1164 func tLimitPair(makerQty, takerQty, matchQty, makerRate, takerRate uint64, makerSell bool) *tMatchSet { 1165 set := new(tMatchSet) 1166 maker, taker := tNewUser("maker"), tNewUser("taker") 1167 makerOrder, takerOrder := limitLimitPair(makerQty, takerQty, makerRate, takerRate, maker, taker, makerSell) 1168 return set.add(tMatchInfo(maker, taker, matchQty, makerRate, makerOrder, takerOrder)) 1169 } 1170 1171 func tPerfectLimitLimit(qty, rate uint64, makerSell bool) *tMatchSet { 1172 return tLimitPair(qty, qty, qty, rate, rate, makerSell) 1173 } 1174 1175 func tMarketPair(makerQty, takerQty, rate uint64, makerSell bool) *tMatchSet { 1176 set := new(tMatchSet) 1177 maker, taker := tNewUser("maker"), tNewUser("taker") 1178 makerOrder, takerOrder := marketLimitPair(makerQty, takerQty, rate, maker, taker, makerSell) 1179 return set.add(tMatchInfo(maker, taker, makerQty, rate, makerOrder, takerOrder)) 1180 } 1181 1182 func tPerfectLimitMarket(qty, rate uint64, makerSell bool) *tMatchSet { 1183 return tMarketPair(qty, qty, rate, makerSell) 1184 } 1185 1186 func tCancelPair() *tMatchSet { 1187 set := new(tMatchSet) 1188 user := tNewUser("user") 1189 qty := uint64(1e8) 1190 rate := uint64(1e8) 1191 makerOrder := makeLimitOrder(qty, rate, user, true) 1192 cancelOrder := makeCancelOrder(makerOrder, user) 1193 return set.add(tMatchInfo(user, user, qty, rate, makerOrder, cancelOrder)) 1194 } 1195 1196 // tMatch is the match info for a single match. A tMatch is typically created 1197 // with tMatchInfo. 1198 type tMatch struct { 1199 match *order.Match 1200 matchID order.MatchID 1201 makerOID order.OrderID 1202 takerOID order.OrderID 1203 qty uint64 1204 rate uint64 1205 maker *tUser 1206 taker *tUser 1207 db struct { 1208 makerRedeem *tRedeem 1209 takerRedeem *tRedeem 1210 makerSwap *tSwap 1211 takerSwap *tSwap 1212 makerAudit *TRequest 1213 takerAudit *TRequest 1214 } 1215 } 1216 1217 func makeAck(mid order.MatchID, sig []byte) msgjson.Acknowledgement { 1218 return msgjson.Acknowledgement{ 1219 MatchID: mid[:], 1220 Sig: sig, 1221 } 1222 } 1223 1224 func tAck(user *tUser, matchID order.MatchID) []byte { 1225 b, _ := json.Marshal(makeAck(matchID, user.sig)) 1226 return b 1227 } 1228 1229 func tAckArr(user *tUser, matchIDs []order.MatchID) []byte { 1230 ackArr := make([]msgjson.Acknowledgement, 0, len(matchIDs)) 1231 for _, matchID := range matchIDs { 1232 ackArr = append(ackArr, makeAck(matchID, user.sig)) 1233 } 1234 b, _ := json.Marshal(ackArr) 1235 return b 1236 } 1237 1238 func tMatchInfo(maker, taker *tUser, matchQty, matchRate uint64, makerOrder *order.LimitOrder, takerOrder order.Order) *tMatch { 1239 match := &order.Match{ 1240 Taker: takerOrder, 1241 Maker: makerOrder, 1242 Quantity: matchQty, 1243 Rate: matchRate, 1244 FeeRateBase: 42, 1245 FeeRateQuote: 62, 1246 Epoch: order.EpochID{ // make Epoch.End() be now, Idx and Dur not important per se 1247 Idx: uint64(time.Now().UnixMilli()), 1248 Dur: 1, 1249 }, // Need Epoch set for lock time 1250 } 1251 mid := match.ID() 1252 maker.matchIDs = append(maker.matchIDs, mid) 1253 taker.matchIDs = append(taker.matchIDs, mid) 1254 return &tMatch{ 1255 match: match, 1256 qty: matchQty, 1257 rate: matchRate, 1258 matchID: mid, 1259 makerOID: makerOrder.ID(), 1260 takerOID: takerOrder.ID(), 1261 maker: maker, 1262 taker: taker, 1263 } 1264 } 1265 1266 // Matches are submitted to the swapper in small batches, one for each taker. 1267 type tMatchSet struct { 1268 matchSet *order.MatchSet 1269 matchInfos []*tMatch 1270 } 1271 1272 // Add a new match to the tMatchSet. 1273 func (set *tMatchSet) add(matchInfo *tMatch) *tMatchSet { 1274 match := matchInfo.match 1275 if set.matchSet == nil { 1276 set.matchSet = &order.MatchSet{Taker: match.Taker} 1277 } 1278 if set.matchSet.Taker.User() != match.Taker.User() { 1279 fmt.Println("!!!tMatchSet taker mismatch!!!") 1280 } 1281 ms := set.matchSet 1282 ms.Epoch = matchInfo.match.Epoch 1283 ms.Makers = append(ms.Makers, match.Maker) 1284 ms.Amounts = append(ms.Amounts, matchInfo.qty) 1285 ms.Rates = append(ms.Rates, matchInfo.rate) 1286 ms.Total += matchInfo.qty 1287 // In practice, a MatchSet's fee rate is used to set the individual match 1288 // fee rates via (*MatchSet).Matches in Negotiate > readMatches. 1289 ms.FeeRateBase = matchInfo.match.FeeRateBase 1290 ms.FeeRateQuote = matchInfo.match.FeeRateQuote 1291 set.matchInfos = append(set.matchInfos, matchInfo) 1292 return set 1293 } 1294 1295 // Either a market or limit order taker, and any number of makers. 1296 func tMultiMatchSet(matchQtys, rates []uint64, makerSell bool, isMarket bool) *tMatchSet { 1297 var sum uint64 1298 for _, v := range matchQtys { 1299 sum += v 1300 } 1301 // Taker order can be > sum of match amounts 1302 taker := tNewUser("taker") 1303 var takerOrder order.Order 1304 if isMarket { 1305 takerOrder = makeMarketOrder(sum*5/4, taker, !makerSell) 1306 } else { 1307 takerOrder = makeLimitOrder(sum*5/4, rates[0], taker, !makerSell) 1308 } 1309 set := new(tMatchSet) 1310 now := uint64(time.Now().UnixMilli()) 1311 for i, v := range matchQtys { 1312 maker := tNewUser("maker" + strconv.Itoa(i)) 1313 // Alternate market and limit orders 1314 makerOrder := makeLimitOrder(v, rates[i], maker, makerSell) 1315 matchInfo := tMatchInfo(maker, taker, v, rates[i], makerOrder, takerOrder) 1316 matchInfo.match.Epoch.Idx = now 1317 matchInfo.match.Epoch.Dur = 1 1318 set.add(matchInfo) 1319 } 1320 return set 1321 } 1322 1323 // tSwap is the information needed for spoofing a swap transaction. 1324 type tSwap struct { 1325 coin *asset.Contract 1326 req *msgjson.Message 1327 contract string 1328 } 1329 1330 var ( 1331 tConfsSpoofer int64 1332 tValSpoofer uint64 = 1 1333 tRecipientSpoofer = "" 1334 tLockTimeSpoofer time.Time 1335 ) 1336 1337 func tNewSwap(matchInfo *tMatch, oid order.OrderID, recipient string, user *tUser) *tSwap { 1338 auditVal := matchInfo.qty 1339 if isQuoteSwap(user, matchInfo.match) { 1340 auditVal = matcher.BaseToQuote(matchInfo.rate, matchInfo.qty) 1341 } 1342 coinID := randBytes(36) 1343 coin := &TCoin{ 1344 feeRate: 1, 1345 confs: tConfsSpoofer, 1346 auditAddr: recipient + tRecipientSpoofer, 1347 auditVal: auditVal * tValSpoofer, 1348 id: coinID, 1349 } 1350 1351 contract := &asset.Contract{ 1352 Coin: coin, 1353 SwapAddress: recipient + tRecipientSpoofer, 1354 TxData: encode.RandomBytes(100), 1355 } 1356 1357 contract.LockTime = encode.DropMilliseconds(matchInfo.match.Epoch.End().Add(dex.LockTimeTaker(dex.Testnet))) 1358 if user == matchInfo.maker { 1359 contract.LockTime = encode.DropMilliseconds(matchInfo.match.Epoch.End().Add(dex.LockTimeMaker(dex.Testnet))) 1360 } 1361 1362 if !tLockTimeSpoofer.IsZero() { 1363 contract.LockTime = tLockTimeSpoofer 1364 } 1365 1366 script := "01234567" + user.sigHex 1367 req, _ := msgjson.NewRequest(nextID(), msgjson.InitRoute, &msgjson.Init{ 1368 OrderID: oid[:], 1369 MatchID: matchInfo.matchID[:], 1370 // We control what the backend returns, so the txid doesn't matter right now. 1371 CoinID: coinID, 1372 //Time: uint64(time.Now().UnixMilli()), 1373 Contract: dirtyEncode(script), 1374 }) 1375 1376 return &tSwap{ 1377 coin: contract, 1378 req: req, 1379 contract: script, 1380 } 1381 } 1382 1383 func isQuoteSwap(user *tUser, match *order.Match) bool { 1384 makerSell := match.Maker.Sell 1385 isMaker := user.acct == match.Maker.User() 1386 return (isMaker && !makerSell) || (!isMaker && makerSell) 1387 } 1388 1389 func randBytes(len int) []byte { 1390 bytes := make([]byte, len) 1391 rand.Read(bytes) 1392 return bytes 1393 } 1394 1395 // tRedeem is the information needed to spoof a redemption transaction. 1396 type tRedeem struct { 1397 req *msgjson.Message 1398 coin *TCoin 1399 cpSwapCoin *asset.Contract 1400 } 1401 1402 func tNewRedeem(matchInfo *tMatch, oid order.OrderID, user *tUser) *tRedeem { 1403 coinID := randBytes(36) 1404 req, _ := msgjson.NewRequest(nextID(), msgjson.RedeemRoute, &msgjson.Redeem{ 1405 OrderID: oid[:], 1406 MatchID: matchInfo.matchID[:], 1407 CoinID: coinID, 1408 //Time: uint64(time.Now().UnixMilli()), 1409 }) 1410 var cpSwapCoin *asset.Contract 1411 switch user.acct { 1412 case matchInfo.maker.acct: 1413 cpSwapCoin = matchInfo.db.takerSwap.coin 1414 case matchInfo.taker.acct: 1415 cpSwapCoin = matchInfo.db.makerSwap.coin 1416 default: 1417 panic("wrong user") 1418 } 1419 return &tRedeem{ 1420 req: req, 1421 coin: &TCoin{id: coinID}, 1422 cpSwapCoin: cpSwapCoin, 1423 } 1424 } 1425 1426 // Create a closure that will call t.Fatal if an error is non-nil. 1427 func makeEnsureNilErr(t *testing.T) func(error) { 1428 return func(err error) { 1429 t.Helper() 1430 if err != nil { 1431 t.Fatal(err) 1432 } 1433 } 1434 } 1435 1436 func TestMain(m *testing.M) { 1437 fastRecheckInterval = time.Millisecond * 40 1438 taperedRecheckInterval = time.Millisecond * 40 1439 txWaitExpiration = time.Millisecond * 200 1440 tBcastTimeout = txWaitExpiration * 5 1441 minBlockPeriod = tBcastTimeout / 3 1442 logger := dex.StdOutLogger("TEST", dex.LevelTrace) 1443 UseLogger(logger) 1444 db.UseLogger(logger) 1445 matcher.UseLogger(logger) 1446 comms.UseLogger(logger) 1447 var shutdown func() 1448 testCtx, shutdown = context.WithCancel(context.Background()) 1449 code := m.Run() 1450 shutdown() 1451 os.Exit(code) 1452 } 1453 1454 func TestFatalStorageErr(t *testing.T) { 1455 rig, cleanup := tNewTestRig(nil) 1456 defer cleanup() 1457 1458 // Test a fatal storage backend error that causes the main loop to return 1459 // first, the anomalous shutdown route. 1460 rig.storage.fatalBackendErr(errors.New("backend error")) 1461 1462 rig.swapperWaiter.WaitForShutdown() 1463 } 1464 1465 func testSwap(t *testing.T, rig *testRig) { 1466 t.Helper() 1467 ensureNilErr := makeEnsureNilErr(t) 1468 1469 sendBlock := func(node *TBackend) { 1470 node.bChan <- &asset.BlockUpdate{Err: nil} 1471 } 1472 1473 // Step through the negotiation process. No errors should be generated. 1474 var takerAcked bool 1475 for _, matchInfo := range rig.matches.matchInfos { 1476 rig.matchInfo = matchInfo 1477 ensureNilErr(rig.ackMatch_maker(true)) 1478 if !takerAcked { 1479 ensureNilErr(rig.ackMatch_taker(true)) 1480 takerAcked = true 1481 } 1482 1483 // Assuming market's base asset is abc, quote is xyz 1484 makerSwapAsset, takerSwapAsset := rig.xyz, rig.abc 1485 if matchInfo.match.Maker.Sell { 1486 makerSwapAsset, takerSwapAsset = rig.abc, rig.xyz 1487 } 1488 1489 ensureNilErr(rig.sendSwap_maker(true)) 1490 ensureNilErr(rig.auditSwap_taker()) 1491 ensureNilErr(rig.ackAudit_taker(true)) 1492 matchInfo.db.makerSwap.coin.Coin.(*TCoin).setConfs(int64(makerSwapAsset.SwapConf)) 1493 sendBlock(&makerSwapAsset.Backend.(*TUTXOBackend).TBackend) 1494 ensureNilErr(rig.sendSwap_taker(true)) 1495 ensureNilErr(rig.auditSwap_maker()) 1496 ensureNilErr(rig.ackAudit_maker(true)) 1497 matchInfo.db.takerSwap.coin.Coin.(*TCoin).setConfs(int64(takerSwapAsset.SwapConf)) 1498 sendBlock(&takerSwapAsset.Backend.(*TUTXOBackend).TBackend) 1499 ensureNilErr(rig.redeem_maker(true)) 1500 ensureNilErr(rig.ackRedemption_taker(true)) 1501 ensureNilErr(rig.redeem_taker(true)) 1502 ensureNilErr(rig.ackRedemption_maker(true)) 1503 } 1504 } 1505 1506 func TestSwaps(t *testing.T) { 1507 rig, cleanup := tNewTestRig(nil) 1508 defer cleanup() 1509 1510 rig.auth.auditReq = make(chan struct{}, 1) 1511 rig.auth.redeemReceived = make(chan struct{}, 2) 1512 rig.auth.redemptionReq = make(chan struct{}, 2) 1513 1514 for _, makerSell := range []bool{true, false} { 1515 sellStr := " buy" 1516 if makerSell { 1517 sellStr = " sell" 1518 } 1519 t.Run("perfect limit-limit match"+sellStr, func(t *testing.T) { 1520 rig.matches = tPerfectLimitLimit(uint64(1e8), uint64(1e8), makerSell) 1521 rig.swapper.Negotiate([]*order.MatchSet{rig.matches.matchSet}) 1522 testSwap(t, rig) 1523 }) 1524 t.Run("perfect limit-market match"+sellStr, func(t *testing.T) { 1525 rig.matches = tPerfectLimitMarket(uint64(1e8), uint64(1e8), makerSell) 1526 rig.swapper.Negotiate([]*order.MatchSet{rig.matches.matchSet}) 1527 testSwap(t, rig) 1528 }) 1529 t.Run("imperfect limit-market match"+sellStr, func(t *testing.T) { 1530 // only requirement is that maker val > taker val. 1531 rig.matches = tMarketPair(uint64(10e8), uint64(2e8), uint64(5e8), makerSell) 1532 rig.swapper.Negotiate([]*order.MatchSet{rig.matches.matchSet}) 1533 testSwap(t, rig) 1534 }) 1535 t.Run("imperfect limit-limit match"+sellStr, func(t *testing.T) { 1536 rig.matches = tLimitPair(uint64(10e8), uint64(2e8), uint64(2e8), uint64(5e8), uint64(5e8), makerSell) 1537 rig.swapper.Negotiate([]*order.MatchSet{rig.matches.matchSet}) 1538 testSwap(t, rig) 1539 }) 1540 for _, isMarket := range []bool{true, false} { 1541 marketStr := " limit" 1542 if isMarket { 1543 marketStr = " market" 1544 } 1545 t.Run("three match set"+sellStr+marketStr, func(t *testing.T) { 1546 matchQtys := []uint64{uint64(1e8), uint64(9e8), uint64(3e8)} 1547 rates := []uint64{uint64(10e8), uint64(11e8), uint64(12e8)} 1548 // one taker, 3 makers => 4 'match' requests 1549 rig.matches = tMultiMatchSet(matchQtys, rates, makerSell, isMarket) 1550 1551 rig.swapper.Negotiate([]*order.MatchSet{rig.matches.matchSet}) 1552 testSwap(t, rig) 1553 }) 1554 } 1555 } 1556 } 1557 1558 func TestInvalidFeeRate(t *testing.T) { 1559 set := tPerfectLimitLimit(uint64(1e8), uint64(1e8), true) 1560 matchInfo := set.matchInfos[0] 1561 rig, cleanup := tNewTestRig(matchInfo) 1562 defer cleanup() 1563 1564 rig.auth.swapReceived = make(chan struct{}, 1) 1565 1566 rig.swapper.Negotiate([]*order.MatchSet{set.matchSet}) 1567 1568 rig.abcNode.invalidFeeRate = true 1569 1570 // The error will be generated by the chainWaiter thread, so will need to 1571 // check the response. 1572 if err := rig.sendSwap_maker(false); err != nil { 1573 t.Fatal(err) 1574 } 1575 timeOutMempool() 1576 if err := rig.waitChans("invalid fee rate", rig.auth.swapReceived); err != nil { 1577 t.Fatalf("error waiting for response: %v", err) 1578 } 1579 // Should have an rpc error. 1580 msg, resp := rig.auth.popResp(matchInfo.maker.acct) 1581 if msg == nil { 1582 t.Fatalf("no response for missing tx after timeout") 1583 } 1584 if resp.Error == nil { 1585 t.Fatalf("no rpc error for erroneous maker swap %v", resp.Error) 1586 } 1587 } 1588 1589 func TestTxWaiters(t *testing.T) { 1590 set := tPerfectLimitLimit(uint64(1e8), uint64(1e8), true) 1591 matchInfo := set.matchInfos[0] 1592 rig, cleanup := tNewTestRig(matchInfo) 1593 defer cleanup() 1594 1595 rig.auth.auditReq = make(chan struct{}, 1) 1596 rig.auth.redeemReceived = make(chan struct{}, 1) 1597 rig.auth.redemptionReq = make(chan struct{}, 1) 1598 rig.auth.swapReceived = make(chan struct{}, 1) 1599 1600 ensureNilErr := makeEnsureNilErr(t) 1601 dummyError := fmt.Errorf("test error") 1602 1603 sendBlock := func(node *TBackend) { 1604 node.bChan <- &asset.BlockUpdate{Err: nil} 1605 } 1606 1607 rig.swapper.Negotiate([]*order.MatchSet{set.matchSet}) 1608 1609 // Get the MatchNotifications that the swapper sent to the clients and check 1610 // the match notification length, content, IDs, etc. 1611 if err := rig.ackMatch_maker(true); err != nil { 1612 t.Fatal(err) 1613 } 1614 if err := rig.ackMatch_taker(true); err != nil { 1615 t.Fatal(err) 1616 } 1617 1618 // Set a non-latency error. 1619 rig.abcNode.setContractErr(dummyError) 1620 rig.sendSwap_maker(false) 1621 if err := rig.waitChans("maker contract error", rig.auth.swapReceived); err != nil { 1622 t.Fatalf("error waiting for maker swap error response: %v", err) 1623 } 1624 msg, _ := rig.auth.popResp(matchInfo.maker.acct) 1625 if msg == nil { 1626 t.Fatalf("no response for erroneous maker swap") 1627 } 1628 1629 // Set an error for the maker's swap asset 1630 rig.abcNode.setContractErr(asset.CoinNotFoundError) 1631 // The error will be generated by the chainWaiter thread, so will need to 1632 // check the response. 1633 if err := rig.sendSwap_maker(false); err != nil { 1634 t.Fatal(err) 1635 } 1636 1637 // Duplicate init request should get rejected. 1638 dupSwapReq := *matchInfo.db.makerSwap.req 1639 dupSwapReq.ID = nextID() // same content but new request ID 1640 rpcErr := rig.swapper.handleInit(matchInfo.maker.acct, &dupSwapReq) 1641 if rpcErr == nil { 1642 t.Fatal("should have rejected the duplicate init request") 1643 } 1644 if rpcErr.Code != msgjson.DuplicateRequestError { 1645 t.Errorf("duplicate init request expected code %d, got %d", 1646 msgjson.DuplicateRequestError, rpcErr.Code) 1647 } 1648 1649 // Now timeout the initial search. 1650 timeOutMempool() 1651 if err := rig.waitChans("maker mempool timeout error", rig.auth.swapReceived); err != nil { 1652 t.Fatalf("error waiting for maker swap error response: %v", err) 1653 } 1654 // Should have an rpc error. 1655 msg, resp := rig.auth.popResp(matchInfo.maker.acct) 1656 if msg == nil { 1657 t.Fatalf("no response for missing tx after timeout") 1658 } 1659 if resp.Error == nil { 1660 t.Fatalf("no rpc error for erroneous maker swap") 1661 } 1662 1663 rig.abcNode.setContractErr(nil) 1664 // Everything should work now. 1665 if err := rig.sendSwap_maker(true); err != nil { 1666 t.Fatal(err) 1667 } 1668 matchInfo.db.makerSwap.coin.Coin.(*TCoin).setConfs(int64(rig.abc.SwapConf)) 1669 sendBlock(&rig.abc.Backend.(*TUTXOBackend).TBackend) 1670 if err := rig.auditSwap_taker(); err != nil { 1671 t.Fatal(err) 1672 } 1673 if err := rig.ackAudit_taker(true); err != nil { 1674 t.Fatal(err) 1675 } 1676 // Non-latency error. 1677 rig.xyzNode.setContractErr(dummyError) 1678 rig.sendSwap_taker(false) 1679 if err := rig.waitChans("taker contract error", rig.auth.swapReceived); err != nil { 1680 t.Fatalf("error waiting for taker swap error response: %v", err) 1681 } 1682 msg, _ = rig.auth.popResp(matchInfo.taker.acct) 1683 if msg == nil { 1684 t.Fatalf("no response for erroneous taker swap") 1685 } 1686 // For the taker swap, simulate latency. 1687 rig.xyzNode.setContractErr(asset.CoinNotFoundError) 1688 ensureNilErr(rig.sendSwap_taker(false)) 1689 // Wait a tick 1690 tickMempool() 1691 // There should not be a response yet. 1692 msg, _ = rig.auth.popResp(matchInfo.taker.acct) 1693 if msg != nil { 1694 t.Fatalf("unexpected response for latent taker swap") 1695 } 1696 // Clear the error. 1697 rig.xyzNode.setContractErr(nil) 1698 tickMempool() 1699 if err := rig.waitChans("taker mempool timeout error", rig.auth.swapReceived); err != nil { 1700 t.Fatalf("error waiting for taker timeout error response: %v", err) 1701 } 1702 1703 msg, resp = rig.auth.popResp(matchInfo.taker.acct) 1704 if msg == nil { 1705 t.Fatalf("no response for ok taker swap") 1706 } 1707 if resp.Error != nil { 1708 t.Fatalf("unexpected rpc error for ok taker swap. code: %d, msg: %s", 1709 resp.Error.Code, resp.Error.Message) 1710 } 1711 matchInfo.db.takerSwap.coin.Coin.(*TCoin).setConfs(int64(rig.xyz.SwapConf)) 1712 sendBlock(&rig.xyz.Backend.(*TUTXOBackend).TBackend) 1713 1714 ensureNilErr(rig.auditSwap_maker()) 1715 ensureNilErr(rig.ackAudit_maker(true)) 1716 1717 // Set a transaction error for the maker's redemption. 1718 rig.xyzNode.setRedemptionErr(asset.CoinNotFoundError) 1719 1720 ensureNilErr(rig.redeem_maker(false)) 1721 tickMempool() 1722 tickMempool() 1723 msg, _ = rig.auth.popResp(matchInfo.maker.acct) 1724 if msg != nil { 1725 t.Fatalf("unexpected response for latent maker redeem") 1726 } 1727 // Clear the error. 1728 rig.xyzNode.setRedemptionErr(nil) 1729 tickMempool() 1730 if err := rig.waitChans("maker redemption error", rig.auth.redeemReceived); err != nil { 1731 t.Fatalf("error waiting for taker timeout error response: %v", err) 1732 } 1733 msg, resp = rig.auth.popResp(matchInfo.maker.acct) 1734 if msg == nil { 1735 t.Fatalf("no response for erroneous maker redeem") 1736 } 1737 if resp.Error != nil { 1738 t.Fatalf("unexpected rpc error for erroneous maker redeem. code: %d, msg: %s", 1739 resp.Error.Code, resp.Error.Message) 1740 } 1741 // Back to the taker, but let it timeout first, and then rebroadcast. 1742 // Get the tracker now, since it will be removed from the match dict if 1743 // everything goes right 1744 tracker := rig.getTracker() 1745 ensureNilErr(rig.ackRedemption_taker(true)) 1746 rig.abcNode.setRedemptionErr(asset.CoinNotFoundError) 1747 ensureNilErr(rig.redeem_taker(false)) 1748 timeOutMempool() 1749 if err := rig.waitChans("taker redemption timeout", rig.auth.redeemReceived); err != nil { 1750 t.Fatalf("error waiting for taker timeout error response: %v", err) 1751 } 1752 msg, _ = rig.auth.popResp(matchInfo.taker.acct) 1753 if msg == nil { 1754 t.Fatalf("no response for erroneous taker redeem") 1755 } 1756 rig.abcNode.setRedemptionErr(nil) 1757 ensureNilErr(rig.redeem_taker(true)) 1758 tickMempool() 1759 tickMempool() 1760 ensureNilErr(rig.ackRedemption_maker(true)) 1761 // Set the number of confirmations on the redemptions. 1762 matchInfo.db.makerRedeem.coin.setConfs(int64(rig.xyz.SwapConf)) 1763 matchInfo.db.takerRedeem.coin.setConfs(int64(rig.abc.SwapConf)) 1764 // send a block through for either chain to trigger a completion check. 1765 rig.xyzNode.bChan <- &asset.BlockUpdate{Err: nil} 1766 tickMempool() 1767 if tracker.Status != order.MatchComplete { 1768 t.Fatalf("match not marked as complete: %d", tracker.Status) 1769 } 1770 // Make sure that the tracker is removed from swappers match map. 1771 if rig.getTracker() != nil { 1772 t.Fatalf("matchTracker not removed from swapper's match map") 1773 } 1774 } 1775 1776 func TestBroadcastTimeouts(t *testing.T) { 1777 rig, cleanup := tNewTestRig(nil) 1778 defer cleanup() 1779 1780 rig.auth.newSuspend = make(chan struct{}, 1) 1781 rig.auth.auditReq = make(chan struct{}, 1) 1782 rig.auth.redemptionReq = make(chan struct{}, 1) 1783 rig.auth.newNtfn = make(chan struct{}, 2) // 2 revoke_match requests 1784 1785 ensureNilErr := makeEnsureNilErr(t) 1786 sendBlock := func(node *TBackend) { 1787 node.bChan <- &asset.BlockUpdate{Err: nil} 1788 } 1789 1790 ntfnWait := func(timeout time.Duration) { 1791 t.Helper() 1792 select { 1793 case <-rig.auth.newNtfn: 1794 case <-time.After(timeout): 1795 t.Fatalf("no notification received") 1796 } 1797 } 1798 1799 checkRevokeMatch := func(user *tUser, i int) { 1800 t.Helper() 1801 rev := new(msgjson.RevokeMatch) 1802 err := rig.auth.getNtfn(user.acct, msgjson.RevokeMatchRoute, rev) 1803 if err != nil { 1804 t.Fatalf("failed to get revoke_match ntfn: %v", err) 1805 } 1806 if err = checkSigS256(rev, rig.auth.privkey.PubKey()); err != nil { 1807 t.Fatalf("incorrect server signature: %v", err) 1808 } 1809 if !bytes.Equal(rev.MatchID, rig.matchInfo.matchID[:]) { 1810 t.Fatalf("unexpected revocation match ID for %s at step %d. expected %s, got %s", 1811 user.lbl, i, rig.matchInfo.matchID, rev.MatchID) 1812 } 1813 1814 // TODO: expect revoke order for at-fault user 1815 } 1816 1817 // tryExpire will sleep for the duration of a BroadcastTimeout, and then 1818 // check that a penalty was assigned to the appropriate user, and that a 1819 // revoke_match message is sent to both users. 1820 tryExpire := func(i, j int, step order.MatchStatus, jerk, victim *tUser, node *TBackend) bool { 1821 t.Helper() 1822 if i != j { 1823 return false 1824 } 1825 // Sending a block through should schedule an inaction check after duration 1826 // BroadcastTimeout. 1827 sendBlock(node) 1828 select { 1829 case <-rig.auth.newSuspend: 1830 case <-time.After(rig.swapper.bTimeout * 2): 1831 t.Fatalf("no penalization happened") 1832 } 1833 found, rule := rig.auth.flushPenalty(jerk.acct) 1834 if !found { 1835 t.Fatalf("failed to penalize user at step %d", i) 1836 } 1837 if rule == account.NoRule { 1838 t.Fatalf("no penalty at step %d (status %v)", i, step) 1839 } 1840 // Make sure the specified user has a cancellation for this order 1841 ntfnWait(rig.swapper.bTimeout * 3) // wait for both revoke requests, no particular order 1842 ntfnWait(rig.swapper.bTimeout * 3) 1843 checkRevokeMatch(jerk, i) 1844 checkRevokeMatch(victim, i) 1845 return true 1846 } 1847 // Run a timeout test after every important step. 1848 for i := 0; i <= 3; i++ { 1849 set := tPerfectLimitLimit(uint64(1e8), uint64(1e8), true) // same orders, different users 1850 matchInfo := set.matchInfos[0] 1851 rig.matchInfo = matchInfo 1852 rig.swapper.Negotiate([]*order.MatchSet{set.matchSet}) 1853 // Step through the negotiation process. No errors should be generated. 1854 // TODO: timeout each match ack, not block based inaction. 1855 1856 ensureNilErr(rig.ackMatch_maker(true)) 1857 ensureNilErr(rig.ackMatch_taker(true)) 1858 1859 // Timeout waiting for maker swap. 1860 if tryExpire(i, 0, order.NewlyMatched, matchInfo.maker, matchInfo.taker, &rig.abcNode.TBackend) { 1861 continue 1862 } 1863 1864 ensureNilErr(rig.sendSwap_maker(true)) 1865 1866 // Pull the server's 'audit' request from the comms queue. 1867 ensureNilErr(rig.auditSwap_taker()) 1868 1869 // NOTE: timeout on the taker's audit ack response itself does not cause 1870 // a revocation. The taker not broadcasting their swap when maker's swap 1871 // reaches swapconf plus bTimeout is the trigger. 1872 ensureNilErr(rig.ackAudit_taker(true)) 1873 1874 // Maker's swap reaches swapConf. 1875 matchInfo.db.makerSwap.coin.Coin.(*TCoin).setConfs(int64(rig.abc.SwapConf)) 1876 sendBlock(&rig.abcNode.TBackend) // tryConfirmSwap 1877 // With maker swap confirmed, inaction happens bTimeout after 1878 // swapConfirmed time. 1879 if tryExpire(i, 1, order.MakerSwapCast, matchInfo.taker, matchInfo.maker, &rig.xyzNode.TBackend) { 1880 continue 1881 } 1882 1883 ensureNilErr(rig.sendSwap_taker(true)) 1884 1885 // Pull the server's 'audit' request from the comms queue 1886 ensureNilErr(rig.auditSwap_maker()) 1887 1888 // NOTE: timeout on the maker's audit ack response itself does not cause 1889 // a revocation. The maker not broadcasting their redeem when taker's 1890 // swap reaches swapconf plus bTimeout is the trigger. 1891 ensureNilErr(rig.ackAudit_maker(true)) 1892 1893 // Taker's swap reaches swapConf. 1894 matchInfo.db.takerSwap.coin.Coin.(*TCoin).setConfs(int64(rig.xyz.SwapConf)) 1895 sendBlock(&rig.xyzNode.TBackend) 1896 // With taker swap confirmed, inaction happens bTimeout after 1897 // swapConfirmed time. 1898 if tryExpire(i, 2, order.TakerSwapCast, matchInfo.maker, matchInfo.taker, &rig.xyzNode.TBackend) { 1899 continue 1900 } 1901 1902 ensureNilErr(rig.redeem_maker(true)) 1903 1904 // Pull the server's 'redemption' request from the comms queue 1905 ensureNilErr(rig.ackRedemption_taker(true)) 1906 1907 // Maker's redeem reaches swapConf. Not necessary for taker redeem. 1908 // matchInfo.db.makerRedeem.coin.setConfs(int64(rig.xyz.SwapConf)) 1909 // sendBlock(rig.xyzNode) 1910 if tryExpire(i, 3, order.MakerRedeemed, matchInfo.taker, matchInfo.maker, &rig.abcNode.TBackend) { 1911 continue 1912 } 1913 1914 // Next is redeem_taker... not a block-based inaction. 1915 1916 return 1917 } 1918 } 1919 1920 func TestSigErrors(t *testing.T) { 1921 dummyError := fmt.Errorf("test error") 1922 set := tPerfectLimitLimit(uint64(1e8), uint64(1e8), true) 1923 matchInfo := set.matchInfos[0] 1924 rig, cleanup := tNewTestRig(matchInfo) 1925 defer cleanup() 1926 1927 rig.auth.auditReq = make(chan struct{}, 1) 1928 rig.auth.redemptionReq = make(chan struct{}, 1) 1929 rig.auth.redeemReceived = make(chan struct{}, 1) 1930 rig.auth.swapReceived = make(chan struct{}, 1) 1931 1932 rig.swapper.Negotiate([]*order.MatchSet{set.matchSet}) // pushes a new req to m.reqs 1933 ensureNilErr := makeEnsureNilErr(t) 1934 1935 // We need a way to restore the state of the queue after testing an auth error. 1936 var tReq *TRequest 1937 var msg *msgjson.Message 1938 apply := func(user *tUser) { 1939 if msg != nil { 1940 rig.auth.pushResp(user.acct, msg) 1941 } 1942 if tReq != nil { 1943 rig.auth.pushReq(user.acct, tReq) 1944 } 1945 } 1946 stash := func(user *tUser) { 1947 msg, _ = rig.auth.popResp(user.acct) 1948 tReq = rig.auth.popReq(user.acct) 1949 apply(user) // put them back now that we have a copy 1950 } 1951 testAction := func(stepFunc func(bool) error, user *tUser, failChans ...chan struct{}) { 1952 t.Helper() 1953 // First do it with an auth error. 1954 rig.auth.authErr = dummyError 1955 stash(user) // make a copy of this user's next req/resp pair 1956 // The error will be pulled from the auth manager. 1957 _ = stepFunc(false) // popReq => req.respFunc(..., tNewResponse()) => send error to client (m.resps) 1958 if err := rig.waitChans("testAction", failChans...); err != nil { 1959 t.Fatalf("testAction failChan wait error: %v", err) 1960 } 1961 // ensureSigErr makes sure that the specified user has a signature error 1962 // response from the swapper. 1963 ensureNilErr(rig.checkServerResponseFail(user, msgjson.SignatureError)) 1964 // Again with no auth error to go to the next step. 1965 rig.auth.authErr = nil 1966 apply(user) // restore the initial live request 1967 ensureNilErr(stepFunc(true)) 1968 } 1969 maker, taker := matchInfo.maker, matchInfo.taker 1970 // 1 live match, 2 pending client acks 1971 testAction(rig.ackMatch_maker, maker) 1972 testAction(rig.ackMatch_taker, taker) 1973 testAction(rig.sendSwap_maker, maker, rig.auth.swapReceived) 1974 ensureNilErr(rig.auditSwap_taker()) 1975 testAction(rig.ackAudit_taker, taker) 1976 testAction(rig.sendSwap_taker, taker, rig.auth.swapReceived) 1977 ensureNilErr(rig.auditSwap_maker()) 1978 testAction(rig.ackAudit_maker, maker) 1979 testAction(rig.redeem_maker, maker, rig.auth.redeemReceived) 1980 testAction(rig.ackRedemption_taker, taker) 1981 testAction(rig.redeem_taker, taker, rig.auth.redeemReceived) 1982 testAction(rig.ackRedemption_maker, maker) 1983 } 1984 1985 func TestMalformedSwap(t *testing.T) { 1986 set := tPerfectLimitLimit(uint64(1e8), uint64(1e8), true) 1987 matchInfo := set.matchInfos[0] 1988 rig, cleanup := tNewTestRig(matchInfo) 1989 defer cleanup() 1990 1991 rig.auth.swapReceived = make(chan struct{}, 1) 1992 1993 rig.swapper.Negotiate([]*order.MatchSet{set.matchSet}) 1994 ensureNilErr := makeEnsureNilErr(t) 1995 1996 ensureNilErr(rig.ackMatch_maker(true)) 1997 ensureNilErr(rig.ackMatch_taker(true)) 1998 1999 ensureErr := func(tag string) { 2000 t.Helper() 2001 ensureNilErr(rig.sendSwap_maker(false)) 2002 ensureNilErr(rig.waitChans(tag, rig.auth.swapReceived)) 2003 } 2004 2005 // Bad contract value 2006 tValSpoofer = 2 2007 ensureErr("bad maker contract") 2008 ensureNilErr(rig.checkServerResponseFail(matchInfo.maker, msgjson.ContractError)) 2009 tValSpoofer = 1 2010 // Bad contract recipient 2011 tRecipientSpoofer = "2" 2012 ensureErr("bad recipient") 2013 ensureNilErr(rig.checkServerResponseFail(matchInfo.maker, msgjson.ContractError)) 2014 tRecipientSpoofer = "" 2015 // Bad locktime 2016 tLockTimeSpoofer = time.Unix(1, 0) 2017 ensureErr("bad locktime") 2018 ensureNilErr(rig.checkServerResponseFail(matchInfo.maker, msgjson.ContractError)) 2019 tLockTimeSpoofer = time.Time{} 2020 2021 // Low fee, unconfirmed 2022 rig.abcNode.invalidFeeRate = true 2023 ensureErr("low fee") 2024 ensureNilErr(rig.checkServerResponseFail(matchInfo.maker, msgjson.ContractError)) 2025 2026 // Works with low fee, but now a confirmation. 2027 tConfsSpoofer = 1 2028 ensureNilErr(rig.sendSwap_maker(true)) 2029 } 2030 2031 func TestRetriesDuringSwap(t *testing.T) { 2032 rig, cleanup := tNewTestRig(nil) 2033 defer cleanup() 2034 2035 ensureNilErr := makeEnsureNilErr(t) 2036 // retryUntilSuccess will retry action until success or timeout. 2037 retryUntilSuccess := func(action func() error, onSuccess, onFail func()) { 2038 startWaitingTime := time.Now() 2039 for { 2040 err := action() 2041 if err == nil { 2042 // We are done, finally got a retry attempt that worked. 2043 onSuccess() 2044 break 2045 } 2046 onFail() 2047 2048 if time.Since(startWaitingTime) > 10*time.Second { 2049 t.Fatalf("timed out retrying, err: %v", err) 2050 } 2051 time.Sleep(100 * time.Millisecond) 2052 } 2053 } 2054 2055 match := tPerfectLimitLimit(uint64(1e8), uint64(1e8), false) 2056 rig.matches = match 2057 rig.matchInfo = match.matchInfos[0] 2058 rig.auth.swapReceived = make(chan struct{}, 1) 2059 rig.auth.auditReq = make(chan struct{}, 1) 2060 rig.auth.redeemReceived = make(chan struct{}, 1) 2061 rig.auth.redemptionReq = make(chan struct{}, 1) 2062 rig.swapper.Negotiate([]*order.MatchSet{rig.matches.matchSet}) 2063 2064 ensureNilErr(rig.ackMatch_maker(true)) 2065 ensureNilErr(rig.ackMatch_taker(true)) 2066 2067 ensureNilErr(rig.sendSwap_maker(true)) 2068 ensureNilErr(rig.auditSwap_taker()) 2069 tracker := rig.getTracker() 2070 // We're "rewinding time" back NewlyMatched status to be able to retry sending 2071 // the same init request again. 2072 tracker.Status = order.NewlyMatched 2073 retryUntilSuccess(func() error { 2074 // We might get a couple of duplicate init request errors here, in that case 2075 // we simply retry request later. 2076 // We need to wait for swapSearching semaphore (it coordinates init request 2077 // handling) to clear, so our retry request should eventually work (it has 2078 // adequate fee and 0 confs). 2079 return rig.sendSwap_maker(false) 2080 }, func() { 2081 // On success, executes once. 2082 ensureNilErr(rig.ensureSwapStatus("server received our swap -> counterparty got audit request", 2083 order.MakerSwapCast, rig.auth.swapReceived, rig.auth.auditReq)) 2084 ensureNilErr(rig.checkServerResponseSuccess(rig.matchInfo.maker)) 2085 ensureNilErr(rig.auditSwap_taker()) 2086 }, func() { 2087 // On every failure. 2088 ensureNilErr(rig.ensureSwapStatus("server received our swap", 2089 order.NewlyMatched, rig.auth.swapReceived)) 2090 ensureNilErr(rig.checkServerResponseFail(rig.matchInfo.maker, msgjson.DuplicateRequestError)) 2091 }) 2092 2093 ensureNilErr(rig.ackAudit_taker(true)) 2094 ensureNilErr(rig.sendSwap_taker(true)) 2095 ensureNilErr(rig.auditSwap_maker()) 2096 tracker = rig.getTracker() 2097 // We're "rewinding time" back MakerSwapCast status to be able to retry sending 2098 // the same init request again. 2099 tracker.Status = order.MakerSwapCast 2100 retryUntilSuccess(func() error { 2101 // We might get a couple of duplicate init request errors here, in that case 2102 // we simply retry request later. 2103 // We need to wait for swapSearching semaphore (it coordinates init request 2104 // handling) to clear, so our retry request should eventually work (it has 2105 // adequate fee and 0 confs). 2106 return rig.sendSwap_taker(false) 2107 }, func() { 2108 // On success, executes once. 2109 ensureNilErr(rig.ensureSwapStatus("server received our swap -> counterparty got audit request", 2110 order.TakerSwapCast, rig.auth.swapReceived, rig.auth.auditReq)) 2111 ensureNilErr(rig.checkServerResponseSuccess(rig.matchInfo.taker)) 2112 ensureNilErr(rig.auditSwap_maker()) 2113 }, func() { 2114 // On every failure. 2115 ensureNilErr(rig.ensureSwapStatus("server received our swap", 2116 order.MakerSwapCast, rig.auth.swapReceived)) 2117 ensureNilErr(rig.checkServerResponseFail(rig.matchInfo.taker, msgjson.DuplicateRequestError)) 2118 }) 2119 2120 ensureNilErr(rig.ackAudit_maker(true)) 2121 2122 ensureNilErr(rig.redeem_maker(true)) 2123 ensureNilErr(rig.ackRedemption_taker(true)) 2124 tracker = rig.getTracker() 2125 // We're "rewinding time" back TakerSwapCast status to be able to retry sending 2126 // the same redeem request again. 2127 tracker.Status = order.TakerSwapCast 2128 retryUntilSuccess(func() error { 2129 // We might get a couple of duplicate redeem request errors here, in that case 2130 // we simply retry request later. 2131 // We need to wait for redeemSearching semaphore (it coordinates redeem request 2132 // handling) to clear, so our retry request should eventually work. 2133 return rig.redeem_maker(false) 2134 }, func() { 2135 // On success, executes once. 2136 ensureNilErr(rig.ensureSwapStatus("server received our redeem -> counterparty got redemption request", 2137 order.MakerRedeemed, rig.auth.redeemReceived, rig.auth.redemptionReq)) 2138 ensureNilErr(rig.checkServerResponseSuccess(rig.matchInfo.maker)) 2139 ensureNilErr(rig.ackRedemption_taker(true)) 2140 }, func() { 2141 // On every failure. 2142 ensureNilErr(rig.ensureSwapStatus("server received our redeem", 2143 order.TakerSwapCast, rig.auth.redeemReceived)) 2144 ensureNilErr(rig.checkServerResponseFail(rig.matchInfo.maker, msgjson.DuplicateRequestError)) 2145 }) 2146 2147 ensureNilErr(rig.redeem_taker(true)) 2148 // Check that any subsequent redeem request retry will just return a 2149 // settlement sequence error, and after the redemption ack, an "unknown 2150 // match" error. 2151 err := rig.redeem_taker(false) 2152 if err == nil { 2153 t.Fatalf("expected 2nd redeem request to fail after 1st one succeeded") 2154 } 2155 ensureNilErr(rig.waitChans("server received our redeem", rig.auth.redeemReceived)) 2156 tracker = rig.getTracker() 2157 if tracker == nil { 2158 t.Fatal("missing match tracker after taker redeem") 2159 } 2160 if tracker.Status != order.MatchComplete { 2161 t.Fatalf("unexpected swap status %d after taker redeem notification", tracker.Status) 2162 } 2163 ensureNilErr(rig.checkServerResponseFail(rig.matchInfo.taker, msgjson.SettlementSequenceError)) 2164 2165 tickMempool() 2166 tickMempool() 2167 ensureNilErr(rig.ackRedemption_maker(true)) // will cause match to be deleted 2168 2169 tracker = rig.getTracker() 2170 if tracker != nil { 2171 t.Fatalf("expected match to be removed, found it, in status %v", tracker.Status) 2172 } 2173 2174 // Now it will fail with "unknown match". 2175 err = rig.redeem_taker(false) 2176 if err == nil { 2177 t.Fatalf("expected 2nd redeem request to fail after 1st one succeeded") 2178 } 2179 2180 ensureNilErr(rig.checkServerResponseFail(rig.matchInfo.taker, msgjson.RPCUnknownMatch)) 2181 } 2182 2183 func TestBadParams(t *testing.T) { 2184 set := tPerfectLimitLimit(uint64(1e8), uint64(1e8), true) 2185 matchInfo := set.matchInfos[0] 2186 rig, cleanup := tNewTestRig(matchInfo) 2187 defer cleanup() 2188 rig.swapper.Negotiate([]*order.MatchSet{set.matchSet}) 2189 swapper := rig.swapper 2190 match := rig.getTracker() 2191 user := matchInfo.maker 2192 acker := &messageAcker{ 2193 user: user.acct, 2194 match: match, 2195 isMaker: true, 2196 isAudit: true, 2197 } 2198 ensureNilErr := makeEnsureNilErr(t) 2199 2200 ackArr := make([]*msgjson.Acknowledgement, 0) 2201 matches := []*messageAcker{ 2202 {match: rig.getTracker()}, 2203 } 2204 2205 encodedAckArray := func() json.RawMessage { 2206 b, _ := json.Marshal(ackArr) 2207 return json.RawMessage(b) 2208 } 2209 2210 // Invalid result. 2211 msg, _ := msgjson.NewResponse(1, nil, nil) 2212 msg.Payload = json.RawMessage(`{"result":?}`) 2213 swapper.processAck(msg, acker) 2214 ensureNilErr(rig.checkServerResponseFail(user, msgjson.RPCParseError)) 2215 swapper.processMatchAcks(user.acct, msg, []*messageAcker{}) 2216 ensureNilErr(rig.checkServerResponseFail(user, msgjson.RPCParseError)) 2217 2218 msg, _ = msgjson.NewResponse(1, encodedAckArray(), nil) 2219 swapper.processMatchAcks(user.acct, msg, matches) 2220 ensureNilErr(rig.checkServerResponseFail(user, msgjson.AckCountError)) 2221 } 2222 2223 func TestCancel(t *testing.T) { 2224 set := tCancelPair() 2225 matchInfo := set.matchInfos[0] 2226 rig, cleanup := tNewTestRig(matchInfo) 2227 defer cleanup() 2228 rig.swapper.Negotiate([]*order.MatchSet{set.matchSet}) 2229 // There should be no matchTracker 2230 if rig.getTracker() != nil { 2231 t.Fatalf("found matchTracker for a cancellation") 2232 } 2233 // The user should have two match requests. 2234 user := matchInfo.maker 2235 req := rig.auth.popReq(user.acct) 2236 if req == nil { 2237 t.Fatalf("no request sent from Negotiate") 2238 } 2239 matchNotes := make([]*msgjson.Match, 0) 2240 err := json.Unmarshal(req.req.Payload, &matchNotes) 2241 if err != nil { 2242 t.Fatalf("unmarshal error: %v", err) 2243 } 2244 if len(matchNotes) != 2 { 2245 t.Fatalf("expected 2 match notification, got %d", len(matchNotes)) 2246 } 2247 for _, match := range matchNotes { 2248 if err = checkSigS256(match, rig.auth.privkey.PubKey()); err != nil { 2249 t.Fatalf("incorrect server signature: %v", err) 2250 } 2251 } 2252 makerNote, takerNote := matchNotes[0], matchNotes[1] 2253 if makerNote.OrderID.String() != matchInfo.makerOID.String() { 2254 t.Fatalf("expected maker ID %s, got %s", matchInfo.makerOID, makerNote.OrderID) 2255 } 2256 if takerNote.OrderID.String() != matchInfo.takerOID.String() { 2257 t.Fatalf("expected taker ID %s, got %s", matchInfo.takerOID, takerNote.OrderID) 2258 } 2259 if makerNote.MatchID.String() != takerNote.MatchID.String() { 2260 t.Fatalf("match ID mismatch. %s != %s", makerNote.MatchID, takerNote.MatchID) 2261 } 2262 } 2263 2264 func TestAccountTracking(t *testing.T) { 2265 const lotSize uint64 = 1e8 2266 const rate = 2e10 2267 const lots = 5 2268 const qty = lotSize * lots 2269 makerAddr := "maker" 2270 takerAddr := "taker" 2271 2272 trackedSet := tPerfectLimitLimit(qty, rate, true) 2273 matchInfo := trackedSet.matchInfos[0] 2274 2275 rig, cleanup := tNewTestRig(matchInfo) 2276 defer cleanup() 2277 2278 var baseAsset, quoteAsset uint32 = ACCTID, XYZID 2279 2280 addOrder := func(matchSet *order.MatchSet, makerSell bool) { 2281 maker, taker := matchSet.Makers[0], matchSet.Taker 2282 taker.Prefix().BaseAsset = baseAsset 2283 maker.BaseAsset = baseAsset 2284 taker.Prefix().QuoteAsset = quoteAsset 2285 maker.QuoteAsset = quoteAsset 2286 maker.Coins = []order.CoinID{[]byte(makerAddr)} 2287 taker.Trade().Coins = []order.CoinID{[]byte(takerAddr)} 2288 taker.Trade().Address = takerAddr 2289 maker.Address = makerAddr 2290 if makerSell { 2291 taker.Trade().Sell = false 2292 maker.Sell = true 2293 } else { 2294 taker.Trade().Sell = true 2295 maker.Sell = false 2296 } 2297 rig.swapper.Negotiate([]*order.MatchSet{matchSet}) 2298 } 2299 2300 checkStats := func(addr string, expQty, expSwaps uint64, expRedeems int) { 2301 t.Helper() 2302 q, s, r := rig.swapper.AccountStats(addr, ACCTID) 2303 if q != expQty { 2304 t.Fatalf("wrong quantity. wanted %d, got %d", expQty, q) 2305 } 2306 if s != expSwaps { 2307 t.Fatalf("wrong swaps. wanted %d, got %d", expSwaps, s) 2308 } 2309 if r != expRedeems { 2310 t.Fatalf("wrong redeems. wanted %d, got %d", expRedeems, r) 2311 } 2312 } 2313 2314 addOrder(trackedSet.matchSet, true) 2315 checkStats(makerAddr, qty, 1, 0) 2316 checkStats(takerAddr, 0, 0, 1) 2317 2318 tracker := rig.getTracker() 2319 tracker.Status = order.MakerSwapCast 2320 checkStats(makerAddr, 0, 0, 0) 2321 checkStats(takerAddr, 0, 0, 1) 2322 2323 tracker.Status = order.MatchComplete 2324 checkStats(takerAddr, 0, 0, 0) 2325 2326 rig.swapper.matchMtx.Lock() 2327 rig.swapper.deleteMatch(tracker) 2328 rig.swapper.matchMtx.Unlock() 2329 if len(rig.swapper.acctMatches[ACCTID]) > 0 { 2330 t.Fatalf("account tracking not deleted for removed match") 2331 } 2332 2333 // Do 3 maker buys, 2 maker sells, and check the numbers. 2334 for i := 0; i < 5; i++ { 2335 set := tPerfectLimitLimit(qty, rate, i > 2) 2336 addOrder(set.matchSet, i > 2) 2337 } 2338 2339 checkStats(makerAddr, qty*2, 2, 3) 2340 checkStats(takerAddr, qty*3, 3, 2) 2341 2342 quoteAsset, baseAsset = baseAsset, quoteAsset 2343 set := tPerfectLimitLimit(qty, rate, false) 2344 2345 addOrder(set.matchSet, false) 2346 checkStats(makerAddr, qty*2+calc.BaseToQuote(rate, qty), 3, 3) 2347 checkStats(takerAddr, qty*3, 3, 3) 2348 } 2349 2350 // TODO: TestSwapper_restoreActiveSwaps? It would be almost entirely driven by 2351 // stubbed out asset backend and storage.