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