decred.org/dcrdex@v1.0.5/server/db/driver/pg/matches_online_test.go (about) 1 //go:build pgonline 2 3 package pg 4 5 import ( 6 "bytes" 7 "errors" 8 "fmt" 9 "testing" 10 "time" 11 12 "decred.org/dcrdex/dex/candles" 13 "decred.org/dcrdex/dex/encode" 14 "decred.org/dcrdex/dex/order" 15 "decred.org/dcrdex/server/account" 16 "decred.org/dcrdex/server/db" 17 ) 18 19 func TestInsertMatch(t *testing.T) { 20 if err := cleanTables(archie.db); err != nil { 21 t.Fatalf("cleanTables: %v", err) 22 } 23 24 // Make a perfect 1 lot match. 25 limitBuyStanding := newLimitOrder(false, 4500000, 1, order.StandingTiF, 0) 26 limitSellImmediate := newLimitOrder(true, 4490000, 1, order.ImmediateTiF, 10) 27 28 epochID := order.EpochID{132412341, 1000} 29 // Taker is selling. 30 matchA := newMatch(limitBuyStanding, limitSellImmediate, limitSellImmediate.Quantity, epochID) 31 32 base, quote := limitBuyStanding.Base(), limitBuyStanding.Quote() 33 34 matchAUpdated := matchA 35 matchAUpdated.Status = order.MakerSwapCast 36 // matchAUpdated.Sigs.MakerMatch = randomBytes(73) 37 38 cancelLOBuy := newCancelOrder(limitBuyStanding.ID(), base, quote, 0) 39 matchCancel := newMatch(limitBuyStanding, cancelLOBuy, 0, epochID) 40 matchCancel.Status = order.MatchComplete // will be forced to complete on store too 41 42 tests := []struct { 43 name string 44 match *order.Match 45 wantErr bool 46 isCancel bool 47 }{ 48 { 49 "store ok", 50 matchA, 51 false, 52 false, 53 }, 54 { 55 "update ok", 56 matchAUpdated, 57 false, 58 false, 59 }, 60 { 61 "update again ok", 62 matchAUpdated, 63 false, 64 false, 65 }, 66 { 67 "cancel", 68 matchCancel, 69 false, 70 true, 71 }, 72 } 73 74 for _, tt := range tests { 75 t.Run(tt.name, func(t *testing.T) { 76 err := archie.InsertMatch(tt.match) 77 if (err != nil) != tt.wantErr { 78 t.Errorf("InsertMatch() error = %v, wantErr %v", err, tt.wantErr) 79 } 80 81 if tt.wantErr { 82 return 83 } 84 85 matchID := tt.match.ID() 86 matchData, err := archie.MatchByID(matchID, base, quote) 87 if err != nil { 88 t.Fatal(err) 89 } 90 if matchData.ID != matchID { 91 t.Errorf("Retrieved match with ID %v, expected %v", matchData.ID, matchID) 92 } 93 if matchData.Status != tt.match.Status { 94 t.Errorf("Incorrect match status, got %d, expected %d", 95 matchData.Status, tt.match.Status) 96 } 97 if tt.isCancel { 98 if matchData.Active { 99 t.Errorf("Incorrect match active flag, got %v, expected false", 100 matchData.Active) 101 } 102 trade := tt.match.Taker.Trade() 103 if trade != nil { 104 if matchData.TakerSell != trade.Sell { 105 t.Errorf("expected takerSell = %v, got %v", trade.Sell, matchData.TakerSell) 106 } 107 if matchData.BaseRate != tt.match.FeeRateBase { 108 t.Errorf("expected base fee rate %d, got %d", tt.match.FeeRateBase, matchData.BaseRate) 109 } 110 } else { 111 if matchData.BaseRate != 0 { 112 t.Errorf("cancel order should have 0 base fee rate, got %d", matchData.BaseRate) 113 } 114 if matchData.QuoteRate != 0 { 115 t.Errorf("cancel order should have 0 quote fee rate, got %d", matchData.QuoteRate) 116 } 117 if matchData.TakerSell { 118 t.Errorf("cancel order should have false for takerSell") 119 } 120 } 121 if matchData.TakerAddr != "" { 122 t.Errorf("Expected empty taker address for cancel match, got %v", matchData.TakerAddr) 123 } 124 if matchData.MakerAddr != "" { 125 t.Errorf("Expected empty maker address for cancel match, got %v", matchData.MakerAddr) 126 } 127 } 128 }) 129 } 130 } 131 132 func TestSetSwapData(t *testing.T) { 133 if err := cleanTables(archie.db); err != nil { 134 t.Fatalf("cleanTables: %v", err) 135 } 136 137 // Make a perfect 1 lot match. 138 limitBuyStanding := newLimitOrder(false, 4500000, 1, order.StandingTiF, 0) 139 limitSellImmediate := newLimitOrder(true, 4490000, 1, order.ImmediateTiF, 10) 140 141 epochID := order.EpochID{132412341, 1000} 142 matchA := newMatch(limitBuyStanding, limitSellImmediate, limitSellImmediate.Quantity, epochID) 143 matchID := matchA.ID() 144 145 base, quote := limitBuyStanding.Base(), limitBuyStanding.Quote() 146 147 checkMatch := func(wantStatus order.MatchStatus, wantActive bool) error { 148 matchData, err := archie.MatchByID(matchID, base, quote) 149 if err != nil { 150 return err 151 } 152 if matchData.ID != matchID { 153 return fmt.Errorf("Retrieved match with ID %v, expected %v", matchData.ID, matchID) 154 } 155 if matchData.Status != wantStatus { 156 return fmt.Errorf("Incorrect match status, got %d, expected %d", 157 matchData.Status, wantStatus) 158 } 159 if matchData.Active != wantActive { 160 return fmt.Errorf("Incorrect match active flag, got %v, expected %v", 161 matchData.Active, wantActive) 162 } 163 return nil 164 } 165 166 err := archie.InsertMatch(matchA) 167 if err != nil { 168 t.Errorf("InsertMatch() failed: %v", err) 169 } 170 171 if err = checkMatch(order.NewlyMatched, true); err != nil { 172 t.Fatal(err) 173 } 174 175 mid := db.MarketMatchID{ 176 MatchID: matchA.ID(), 177 Base: base, 178 Quote: quote, 179 } 180 181 // Match Ack Sig A (maker's match ack sig) 182 sigMakerMatch := randomBytes(73) 183 err = archie.SaveMatchAckSigA(mid, sigMakerMatch) 184 if err != nil { 185 t.Fatal(err) 186 } 187 status, swapData, err := archie.SwapData(mid) 188 if err != nil { 189 t.Fatal(err) 190 } 191 if status != order.NewlyMatched { 192 t.Errorf("Got status %v, expected %v", status, order.NewlyMatched) 193 } 194 if !bytes.Equal(swapData.SigMatchAckMaker, sigMakerMatch) { 195 t.Fatalf("SigMatchAckMaker incorrect. got %v, expected %v", 196 swapData.SigMatchAckMaker, sigMakerMatch) 197 } 198 199 // Match Ack Sig B (taker's match ack sig) 200 sigTakerMatch := randomBytes(73) 201 err = archie.SaveMatchAckSigB(mid, sigTakerMatch) 202 if err != nil { 203 t.Fatal(err) 204 } 205 status, swapData, err = archie.SwapData(mid) 206 if err != nil { 207 t.Fatal(err) 208 } 209 if status != order.NewlyMatched { 210 t.Errorf("Got status %v, expected %v", status, order.NewlyMatched) 211 } 212 if !bytes.Equal(swapData.SigMatchAckTaker, sigTakerMatch) { 213 t.Fatalf("SigMatchAckTaker incorrect. got %v, expected %v", 214 swapData.SigMatchAckTaker, sigTakerMatch) 215 } 216 217 // Contract A 218 contractA := randomBytes(128) 219 coinIDA := randomBytes(36) 220 contractATime := int64(1234) 221 err = archie.SaveContractA(mid, contractA, coinIDA, contractATime) 222 if err != nil { 223 t.Fatal(err) 224 } 225 226 status, swapData, err = archie.SwapData(mid) 227 if err != nil { 228 t.Fatal(err) 229 } 230 if status != order.MakerSwapCast { 231 t.Errorf("Got status %v, expected %v", status, order.MakerSwapCast) 232 } 233 if !bytes.Equal(swapData.ContractA, contractA) { 234 t.Fatalf("ContractA incorrect. got %v, expected %v", 235 swapData.ContractA, contractA) 236 } 237 if !bytes.Equal(swapData.ContractACoinID, coinIDA) { 238 t.Fatalf("ContractACoinID incorrect. got %v, expected %v", 239 swapData.ContractACoinID, coinIDA) 240 } 241 if swapData.ContractATime != contractATime { 242 t.Fatalf("ContractATime incorrect. got %d, expected %d", 243 swapData.ContractATime, contractATime) 244 } 245 246 // Party B's signature for acknowledgement of contract A 247 auditSigB := randomBytes(73) 248 if err = archie.SaveAuditAckSigB(mid, auditSigB); err != nil { 249 t.Fatal(err) 250 } 251 252 status, swapData, err = archie.SwapData(mid) 253 if err != nil { 254 t.Fatal(err) 255 } 256 if status != order.MakerSwapCast { 257 t.Errorf("Got status %v, expected %v", status, order.MakerSwapCast) 258 } 259 if !bytes.Equal(swapData.ContractAAckSig, auditSigB) { 260 t.Fatalf("ContractAAckSig incorrect. got %v, expected %v", 261 swapData.ContractAAckSig, auditSigB) 262 } 263 264 // Contract B 265 contractB := randomBytes(128) 266 coinIDB := randomBytes(36) 267 contractBTime := int64(1235) 268 err = archie.SaveContractB(mid, contractB, coinIDB, contractBTime) 269 if err != nil { 270 t.Fatal(err) 271 } 272 273 status, swapData, err = archie.SwapData(mid) 274 if err != nil { 275 t.Fatal(err) 276 } 277 if status != order.TakerSwapCast { 278 t.Errorf("Got status %v, expected %v", status, order.TakerSwapCast) 279 } 280 if !bytes.Equal(swapData.ContractB, contractB) { 281 t.Fatalf("ContractB incorrect. got %v, expected %v", 282 swapData.ContractB, contractB) 283 } 284 if !bytes.Equal(swapData.ContractBCoinID, coinIDB) { 285 t.Fatalf("ContractBCoinID incorrect. got %v, expected %v", 286 swapData.ContractBCoinID, coinIDB) 287 } 288 if swapData.ContractBTime != contractBTime { 289 t.Fatalf("ContractBTime incorrect. got %d, expected %d", 290 swapData.ContractBTime, contractBTime) 291 } 292 293 // Party A's signature for acknowledgement of contract B 294 auditSigA := randomBytes(73) 295 if err = archie.SaveAuditAckSigA(mid, auditSigA); err != nil { 296 t.Fatal(err) 297 } 298 299 status, swapData, err = archie.SwapData(mid) 300 if err != nil { 301 t.Fatal(err) 302 } 303 if status != order.TakerSwapCast { 304 t.Errorf("Got status %v, expected %v", status, order.TakerSwapCast) 305 } 306 if !bytes.Equal(swapData.ContractBAckSig, auditSigA) { 307 t.Fatalf("ContractBAckSig incorrect. got %v, expected %v", 308 swapData.ContractBAckSig, auditSigB) 309 } 310 311 // Redeem A 312 redeemCoinIDA := randomBytes(36) 313 secret := randomBytes(72) 314 redeemATime := int64(1234) 315 err = archie.SaveRedeemA(mid, redeemCoinIDA, secret, redeemATime) 316 if err != nil { 317 t.Fatal(err) 318 } 319 status, swapData, err = archie.SwapData(mid) 320 if err != nil { 321 t.Fatal(err) 322 } 323 if status != order.MakerRedeemed { 324 t.Errorf("Got status %v, expected %v", status, order.MakerRedeemed) 325 } 326 if !bytes.Equal(swapData.RedeemACoinID, redeemCoinIDA) { 327 t.Fatalf("RedeemACoinID incorrect. got %v, expected %v", 328 swapData.RedeemACoinID, redeemCoinIDA) 329 } 330 if !bytes.Equal(swapData.RedeemASecret, secret) { 331 t.Fatalf("RedeemASecret incorrect. got %v, expected %v", 332 swapData.RedeemASecret, secret) 333 } 334 if swapData.RedeemATime != redeemATime { 335 t.Fatalf("RedeemATime incorrect. got %d, expected %d", 336 swapData.RedeemATime, redeemATime) 337 } 338 339 // Party B's signature for acknowledgement of A's redemption 340 redeemAckSigB := randomBytes(73) 341 if err = archie.SaveRedeemAckSigB(mid, redeemAckSigB); err != nil { 342 t.Fatal(err) 343 } 344 345 status, swapData, err = archie.SwapData(mid) 346 if err != nil { 347 t.Fatal(err) 348 } 349 if status != order.MakerRedeemed { 350 t.Errorf("Got status %v, expected %v", status, order.MakerRedeemed) 351 } 352 if !bytes.Equal(swapData.RedeemAAckSig, redeemAckSigB) { 353 t.Fatalf("RedeemAAckSig incorrect. got %v, expected %v", 354 swapData.RedeemAAckSig, redeemAckSigB) 355 } 356 357 // Redeem B 358 redeemCoinIDB := randomBytes(36) 359 redeemBTime := int64(1234) 360 err = archie.SaveRedeemB(mid, redeemCoinIDB, redeemBTime) 361 if err != nil { 362 t.Fatal(err) 363 } 364 365 status, swapData, err = archie.SwapData(mid) 366 if err != nil { 367 t.Fatal(err) 368 } 369 if status != order.MatchComplete { 370 t.Errorf("Got status %v, expected %v", status, order.MatchComplete) 371 } 372 if !bytes.Equal(swapData.RedeemBCoinID, redeemCoinIDB) { 373 t.Fatalf("RedeemBCoinID incorrect. got %v, expected %v", 374 swapData.RedeemBCoinID, redeemCoinIDB) 375 } 376 if swapData.RedeemBTime != redeemBTime { 377 t.Fatalf("RedeemBTime incorrect. got %d, expected %d", 378 swapData.RedeemBTime, redeemBTime) 379 } 380 381 // Check active flag via MatchByID. 382 if err = checkMatch(order.MatchComplete, false); err != nil { 383 t.Fatal(err) 384 } 385 } 386 387 func TestMatchByID(t *testing.T) { 388 if err := cleanTables(archie.db); err != nil { 389 t.Fatalf("cleanTables: %v", err) 390 } 391 392 // Make a perfect 1 lot match. 393 limitBuyStanding := newLimitOrder(false, 4500000, 1, order.StandingTiF, 0) 394 limitSellImmediate := newLimitOrder(true, 4490000, 1, order.ImmediateTiF, 10) 395 396 base, quote := limitBuyStanding.Base(), limitBuyStanding.Quote() 397 398 // Store it. 399 epochID := order.EpochID{132412341, 1000} 400 match := newMatch(limitBuyStanding, limitSellImmediate, limitSellImmediate.Quantity, epochID) 401 err := archie.InsertMatch(match) 402 if err != nil { 403 t.Fatalf("InsertMatch() failed: %v", err) 404 } 405 406 tests := []struct { 407 name string 408 matchID order.MatchID 409 base, quote uint32 410 wantedErr error 411 }{ 412 { 413 "ok", 414 match.ID(), 415 base, quote, 416 nil, 417 }, 418 { 419 "no order", 420 order.MatchID{}, 421 base, quote, 422 db.ArchiveError{Code: db.ErrUnknownMatch}, 423 }, 424 { 425 "bad market", 426 match.ID(), 427 base, base, 428 db.ArchiveError{Code: db.ErrUnsupportedMarket}, 429 }, 430 } 431 432 for _, tt := range tests { 433 t.Run(tt.name, func(t *testing.T) { 434 matchData, err := archie.MatchByID(tt.matchID, tt.base, tt.quote) 435 if !db.SameErrorTypes(err, tt.wantedErr) { 436 t.Fatal(err) 437 } 438 if err == nil && matchData.ID != tt.matchID { 439 t.Errorf("Retrieved match with ID %v, expected %v", matchData.ID, tt.matchID) 440 } 441 }) 442 } 443 } 444 445 func TestUserMatches(t *testing.T) { 446 if err := cleanTables(archie.db); err != nil { 447 t.Fatalf("cleanTables: %v", err) 448 } 449 450 // Make a perfect 1 lot match. 451 limitBuyStanding := newLimitOrder(false, 4500000, 1, order.StandingTiF, 0) 452 limitSellImmediate := newLimitOrder(true, 4490000, 1, order.ImmediateTiF, 10) 453 454 base, quote := limitBuyStanding.Base(), limitBuyStanding.Quote() 455 456 // Store it. 457 epochID := order.EpochID{132412341, 1000} 458 match := newMatch(limitBuyStanding, limitSellImmediate, limitSellImmediate.Quantity, epochID) 459 err := archie.InsertMatch(match) 460 if err != nil { 461 t.Fatalf("InsertMatch() failed: %v", err) 462 } 463 464 tests := []struct { 465 name string 466 acctID account.AccountID 467 numExpected int 468 wantedErr error 469 }{ 470 { 471 "ok maker", 472 limitBuyStanding.User(), 473 1, 474 nil, 475 }, 476 { 477 "ok taker", 478 limitSellImmediate.User(), 479 1, 480 nil, 481 }, 482 { 483 "nope", 484 randomAccountID(), 485 0, 486 nil, 487 }, 488 } 489 490 for _, tt := range tests { 491 t.Run(tt.name, func(t *testing.T) { 492 matchData, err := archie.UserMatches(tt.acctID, base, quote) 493 if err != tt.wantedErr { 494 t.Fatal(err) 495 } 496 if len(matchData) != tt.numExpected { 497 t.Errorf("Retrieved %d matches for user %v, expected %d.", len(matchData), tt.acctID, tt.numExpected) 498 } 499 }) 500 } 501 } 502 503 func TestMarketMatches(t *testing.T) { 504 if err := cleanTables(archie.db); err != nil { 505 t.Fatalf("cleanTables: %v", err) 506 } 507 508 // Make a perfect 1 lot match. 509 limitBuyStanding := newLimitOrder(false, 4500000, 1, order.StandingTiF, 0) 510 limitSellImmediate := newLimitOrder(true, 4490000, 1, order.ImmediateTiF, 10) 511 512 base, quote := limitBuyStanding.Base(), limitBuyStanding.Quote() 513 514 // Store it. 515 epochID := order.EpochID{132412341, 1000} 516 match := newMatch(limitBuyStanding, limitSellImmediate, limitSellImmediate.Quantity, epochID) 517 err := archie.InsertMatch(match) 518 if err != nil { 519 t.Fatalf("InsertMatch() failed: %v", err) 520 } 521 // Make another perfect 1 lot match. 522 limitBuyStanding = newLimitOrder(false, 4500000, 1, order.StandingTiF, 0) 523 limitSellImmediate = newLimitOrder(true, 4490000, 1, order.ImmediateTiF, 10) 524 525 // Store it. 526 match = newMatch(limitBuyStanding, limitSellImmediate, limitSellImmediate.Quantity, epochID) 527 err = archie.InsertMatch(match) 528 if err != nil { 529 t.Fatalf("InsertMatch() failed: %v", err) 530 } 531 archie.SetMatchInactive(db.MarketMatchID{ 532 MatchID: match.ID(), 533 Base: base, 534 Quote: quote, 535 }, false) 536 537 // This one has txns. 538 mktMatchID := db.MarketMatchID{ 539 MatchID: match.ID(), 540 Base: limitBuyStanding.Base(), 541 Quote: limitBuyStanding.Quote(), 542 } 543 midWithCoins := mktMatchID.MatchID 544 MakerSwap, MakerContract := encode.RandomBytes(36), encode.RandomBytes(50) 545 err = archie.SaveContractA(mktMatchID, MakerContract, MakerSwap, 0) 546 if err != nil { 547 t.Fatalf("SaveContractA error: %v", err) 548 } 549 550 TakerSwap, TakerContract := encode.RandomBytes(36), encode.RandomBytes(50) 551 err = archie.SaveContractB(mktMatchID, TakerContract, TakerSwap, 0) 552 if err != nil { 553 t.Fatalf("SaveContractB error: %v", err) 554 } 555 556 MakerRedeem, Secret := encode.RandomBytes(36), encode.RandomBytes(32) 557 err = archie.SaveRedeemA(mktMatchID, MakerRedeem, Secret, 0) 558 if err != nil { 559 t.Fatalf("SaveContractB error: %v", err) 560 } 561 // TakerRedeem not stored. 562 563 // Make another perfect 1 lot match on another market. 564 limitBuyStanding = newLimitOrderWithAssets(false, 4500000, 1, order.StandingTiF, 0, AssetBTC, AssetLTC) 565 limitSellImmediate = newLimitOrderWithAssets(true, 4490000, 1, order.ImmediateTiF, 10, AssetBTC, AssetLTC) 566 567 // Store it. 568 match = newMatch(limitBuyStanding, limitSellImmediate, limitSellImmediate.Quantity, epochID) 569 err = archie.InsertMatch(match) 570 if err != nil { 571 t.Fatalf("InsertMatch() failed: %v", err) 572 } 573 574 // Only active. 575 matchData, err := archie.MarketMatches(base, quote) 576 if err != nil { 577 t.Fatal(err) 578 } 579 if len(matchData) != 1 { 580 t.Errorf("Retrieved %d matches for market, expected 1.", len(matchData)) 581 } 582 // Include inactive (true), and no limit (-1). 583 matchData = []*db.MatchDataWithCoins{} 584 N, err := archie.MarketMatchesStreaming(base, quote, true, -1, func(md *db.MatchDataWithCoins) error { 585 matchData = append(matchData, md) 586 return nil 587 }) 588 if err != nil { 589 t.Fatal(err) 590 } 591 if N != len(matchData) { 592 t.Errorf("Retrieved %d matches for market, but method claimed %d.", len(matchData), N) 593 } 594 if len(matchData) != 2 { 595 t.Errorf("Retrieved %d matches for market, expected 2.", len(matchData)) 596 } 597 598 // Find the match with the stored coins and verify them. 599 var found bool 600 for _, md := range matchData { 601 if md.ID == midWithCoins { 602 found = true 603 if !bytes.Equal(md.MakerSwapCoin, MakerSwap) { 604 t.Errorf("Wrong maker swap coin %x, wanted %x", md.MakerSwapCoin, MakerSwap) 605 } 606 if !bytes.Equal(md.TakerSwapCoin, TakerSwap) { 607 t.Errorf("Wrong taker swap coin %x, wanted %x", md.TakerSwapCoin, TakerSwap) 608 } 609 if !bytes.Equal(md.MakerRedeemCoin, MakerRedeem) { 610 t.Errorf("Wrong maker redeem coin %x, wanted %x", md.MakerRedeemCoin, MakerRedeem) 611 } 612 if len(md.TakerRedeemCoin) > 0 { 613 t.Errorf("got taker redeem coin %x, but expected none", md.TakerRedeemCoin) 614 } 615 break 616 } 617 } 618 if !found { 619 t.Errorf("failed to find match with the coins") 620 } 621 622 // Bad Market. 623 matchData, err = archie.MarketMatches(base, base) 624 noMktErr := new(db.ArchiveError) 625 if !errors.As(err, noMktErr) || noMktErr.Code != db.ErrUnsupportedMarket { 626 t.Fatalf("incorrect error for unsupported market: %v", err) 627 } 628 } 629 630 type matchPair struct { 631 match *order.Match 632 status *db.MatchStatus 633 } 634 635 func generateMatch(t *testing.T, matchStatus order.MatchStatus, active bool, makerBuyer, takerSeller account.AccountID, epochIdx ...uint64) *matchPair { 636 t.Helper() 637 loBuy := newLimitOrder(false, 4500000, 1, order.StandingTiF, 0) 638 loBuy.P.AccountID = makerBuyer 639 loSell := newLimitOrder(true, 4490000, 1, order.ImmediateTiF, 10) 640 loSell.P.AccountID = takerSeller 641 642 epIdx := uint64(132412341) 643 if len(epochIdx) > 0 { 644 epIdx = epochIdx[0] 645 } 646 epochID := order.EpochID{epIdx, 1000} 647 648 err := archie.StoreOrder(loBuy, int64(epochID.Idx), int64(epochID.Dur), order.OrderStatusExecuted) 649 if err != nil { 650 t.Fatalf("failed to store order: %v", err) 651 } 652 err = archie.StoreOrder(loSell, int64(epochID.Idx), int64(epochID.Dur), order.OrderStatusExecuted) 653 if err != nil { 654 t.Fatalf("failed to store order: %v", err) 655 } 656 657 match := newMatch(loBuy, loSell, loSell.Quantity, epochID) 658 match.Status = matchStatus 659 err = archie.InsertMatch(match) 660 if err != nil { 661 t.Fatalf("InsertMatch() failed: %v", err) 662 } 663 matchID := match.ID() 664 mktMatchID := db.MarketMatchID{ 665 MatchID: matchID, 666 Base: loBuy.Base(), 667 Quote: loBuy.Quote(), 668 } 669 // Just alternate the active state. 670 status := &db.MatchStatus{ 671 Status: matchStatus, 672 Active: active, 673 } 674 if !active { 675 archie.SetMatchInactive(mktMatchID, false) 676 } 677 for iStatus := order.NewlyMatched; iStatus <= matchStatus; iStatus++ { 678 switch iStatus { 679 case order.MakerSwapCast: 680 status.MakerContract = encode.RandomBytes(50) 681 status.MakerSwap = encode.RandomBytes(36) 682 err := archie.SaveContractA(mktMatchID, status.MakerContract, status.MakerSwap, 0) 683 if err != nil { 684 t.Fatalf("SaveContractA error: %v", err) 685 } 686 case order.TakerSwapCast: 687 status.TakerContract = encode.RandomBytes(50) 688 status.TakerSwap = encode.RandomBytes(36) 689 err := archie.SaveContractB(mktMatchID, status.TakerContract, status.TakerSwap, 0) 690 if err != nil { 691 t.Fatalf("SaveContractB error: %v", err) 692 } 693 case order.MakerRedeemed: 694 status.MakerRedeem = encode.RandomBytes(36) 695 status.Secret = encode.RandomBytes(32) 696 err := archie.SaveRedeemA(mktMatchID, status.MakerRedeem, status.Secret, 0) 697 if err != nil { 698 t.Fatalf("SaveContractB error: %v", err) 699 } 700 case order.MatchComplete: 701 status.TakerRedeem = encode.RandomBytes(36) 702 err := archie.SaveRedeemB(mktMatchID, status.TakerRedeem, 0) 703 if err != nil { 704 t.Fatalf("SaveContractB error: %v", err) 705 } 706 } 707 } 708 return &matchPair{match: match, status: status} 709 } 710 711 func TestCompletedAndAtFaultMatchStats(t *testing.T) { 712 if err := cleanTables(archie.db); err != nil { 713 t.Fatalf("cleanTables: %v", err) 714 } 715 716 epIdx := uint64(132412341) 717 nextIdx := func() uint64 { 718 epIdx++ 719 return epIdx 720 } 721 722 maker, taker := randomAccountID(), randomAccountID() 723 matches := []*matchPair{ 724 generateMatch(t, order.TakerSwapCast, false, maker, taker, nextIdx()), // 0: failed, maker fault 725 generateMatch(t, order.MatchComplete, false, maker, taker, nextIdx()), // 1: success 726 generateMatch(t, order.MakerRedeemed, true, maker, taker, nextIdx()), // 2: still active, but maker success 727 generateMatch(t, order.MakerRedeemed, false, maker, taker, nextIdx()), // 3: failed, maker success, taker fault 728 generateMatch(t, order.MakerRedeemed, false, maker, maker, nextIdx()), // 4: failed, maker fault (no same-user maker success until MatchComplete) 729 generateMatch(t, order.MakerSwapCast, false, maker, taker, nextIdx()), // 5: failed, taker fault 730 generateMatch(t, order.NewlyMatched, false, maker, taker, nextIdx()), // 6: failed, maker fault 731 } 732 733 // Make a perfect 1 lot match in different market (BTC-LTC). 734 limitBuy := newLimitOrder(false, 4500000, 1, order.StandingTiF, 20) 735 limitBuy.BaseAsset, limitBuy.QuoteAsset = AssetBTC, AssetLTC 736 limitBuy.AccountID = maker 737 limitSell := newLimitOrder(true, 4490000, 1, order.ImmediateTiF, 30) 738 limitSell.BaseAsset, limitSell.QuoteAsset = AssetBTC, AssetLTC 739 taker2 := randomAccountID() 740 limitSell.AccountID = taker2 741 matchLTC := newMatch(limitBuy, limitSell, limitSell.Quantity, order.EpochID{nextIdx(), 1000}) 742 matchLTC.Status = order.MatchComplete 743 err := archie.InsertMatch(matchLTC) 744 if err != nil { 745 t.Fatalf("InsertMatch() failed: %v", err) 746 } 747 archie.SetMatchInactive(db.MarketMatchID{ 748 MatchID: matchLTC.ID(), 749 Base: limitBuy.Base(), 750 Quote: limitBuy.Quote(), 751 }, false) 752 // 7: success 753 matches = append(matches, &matchPair{ 754 match: matchLTC, 755 status: &db.MatchStatus{ 756 Active: false, 757 Status: matchLTC.Status, 758 }, 759 }) 760 // TODO: update with a forgiven one 761 762 epochTime := func(mp *matchPair) int64 { 763 return mp.match.Epoch.End().UnixMilli() 764 } 765 766 tests := []struct { 767 name string 768 acctID account.AccountID 769 wantOutcomes []*db.MatchOutcome 770 wantedErr error 771 }{ 772 { 773 "maker", 774 maker, 775 []*db.MatchOutcome{ // descending by time (MatchID field TODO) 776 { 777 Status: matches[7].match.Status, 778 Fail: false, 779 Time: epochTime(matches[7]), 780 }, { 781 Status: matches[6].match.Status, 782 Fail: true, 783 Time: epochTime(matches[6]), 784 }, { 785 Status: matches[4].match.Status, 786 Fail: true, 787 Time: epochTime(matches[4]), 788 }, { 789 Status: matches[3].match.Status, 790 Fail: false, 791 Time: epochTime(matches[3]), 792 }, { 793 Status: matches[2].match.Status, 794 Fail: false, 795 Time: epochTime(matches[2]), 796 }, { 797 Status: matches[1].match.Status, 798 Fail: false, 799 Time: epochTime(matches[1]), 800 }, { 801 Status: matches[0].match.Status, 802 Fail: true, 803 Time: epochTime(matches[0]), 804 }, 805 }, 806 nil, 807 }, 808 { 809 "taker", 810 taker, 811 []*db.MatchOutcome{ 812 { 813 Status: matches[5].match.Status, 814 Fail: true, 815 Time: epochTime(matches[5]), 816 }, { 817 Status: matches[3].match.Status, 818 Fail: true, 819 Time: epochTime(matches[3]), 820 }, { 821 Status: matches[1].match.Status, 822 Fail: false, 823 Time: epochTime(matches[1]), 824 }, 825 }, 826 nil, 827 }, 828 { 829 "nope", 830 randomAccountID(), 831 nil, 832 nil, 833 }, 834 } 835 836 for _, tt := range tests { 837 t.Run(tt.name, func(t *testing.T) { 838 outcomes, err := archie.CompletedAndAtFaultMatchStats(tt.acctID, 60) 839 if err != tt.wantedErr { 840 t.Fatal(err) 841 } 842 if len(outcomes) != len(tt.wantOutcomes) { 843 t.Errorf("Retrieved %d match outcomes for user %v, expected %d.", len(outcomes), tt.acctID, len(tt.wantOutcomes)) 844 } 845 for i, mo := range tt.wantOutcomes { 846 if outcomes[i].Time != mo.Time || outcomes[i].Status != mo.Status || outcomes[i].Fail != mo.Fail { 847 t.Log(outcomes[i]) 848 t.Log(mo) 849 t.Errorf("wrong %d", i) 850 } 851 } 852 }) 853 } 854 } 855 856 func TestUserMatchFails(t *testing.T) { 857 if err := cleanTables(archie.db); err != nil { 858 t.Fatalf("cleanTables: %v", err) 859 } 860 861 epIdx := uint64(132412341) 862 nextIdx := func() uint64 { 863 epIdx++ 864 return epIdx 865 } 866 867 user, otherUser := randomAccountID(), randomAccountID() 868 matches := []*matchPair{ 869 generateMatch(t, order.TakerSwapCast, false, user, otherUser, nextIdx()), // 0: failed, user fault 870 generateMatch(t, order.MatchComplete, false, user, otherUser, nextIdx()), // 1: success 871 generateMatch(t, order.MakerRedeemed, true, user, otherUser, nextIdx()), // 2: still active, but user success 872 generateMatch(t, order.MakerRedeemed, false, otherUser, user, nextIdx()), // 3: failed, user success, otherUser fault 873 generateMatch(t, order.MakerSwapCast, false, otherUser, user, nextIdx()), // 5: failed, user fault 874 generateMatch(t, order.NewlyMatched, false, otherUser, user, nextIdx()), // 6: failed, otherUser fault 875 } 876 // Put one of them on another market 877 m4 := matches[4] 878 m4.match.Maker.Prefix().BaseAsset = AssetBTC 879 m4.match.Maker.Prefix().QuoteAsset = AssetLTC 880 m4.match.Taker.Prefix().BaseAsset = AssetBTC 881 m4.match.Taker.Prefix().QuoteAsset = AssetLTC 882 for _, m := range matches { 883 err := archie.InsertMatch(m.match) 884 if err != nil { 885 t.Fatalf("InsertMatch() failed: %v", err) 886 } 887 } 888 fails, err := archie.UserMatchFails(user, 100) 889 if err != nil { 890 t.Fatalf("UserMatchFails() failed: %v", err) 891 } 892 check: 893 for _, i := range []int{0, 3, 4} { 894 matchID := matches[i].match.ID() 895 for _, fail := range fails { 896 if fail.ID == matchID { 897 continue check 898 } 899 } 900 t.Fatalf("expected to find fail for match at index %d, but did not", i) 901 } 902 } 903 904 func TestAllActiveUserMatches(t *testing.T) { 905 if err := cleanTables(archie.db); err != nil { 906 t.Fatalf("cleanTables: %v", err) 907 } 908 909 // Make a perfect 1 lot match. 910 limitBuyStanding := newLimitOrder(false, 4500000, 1, order.StandingTiF, 0) 911 limitSellImmediate := newLimitOrder(true, 4490000, 1, order.ImmediateTiF, 10) 912 913 // Make it complete and store it. 914 epochID := order.EpochID{132412341, 1000} 915 // maker buy (quote swap asset), taker sell (base swap asset) 916 match := newMatch(limitBuyStanding, limitSellImmediate, limitSellImmediate.Quantity, epochID) 917 match.Status = order.TakerSwapCast // failed here 918 err := archie.InsertMatch(match) // active by default 919 if err != nil { 920 t.Fatalf("InsertMatch() failed: %v", err) 921 } 922 err = archie.SetMatchInactive(db.MatchID(match), false) // set inactive, not forgiven 923 if err != nil { 924 t.Fatalf("SetMatchInactive() failed: %v", err) 925 } 926 927 // Make a perfect 1 lot match, same parties. 928 limitBuyStanding2 := newLimitOrder(false, 4500000, 1, order.StandingTiF, 20) 929 limitBuyStanding2.AccountID = limitBuyStanding.AccountID 930 limitSellImmediate2 := newLimitOrder(true, 4490000, 1, order.ImmediateTiF, 30) 931 limitSellImmediate2.AccountID = limitSellImmediate.AccountID 932 933 // Store it. 934 epochID2 := order.EpochID{132412342, 1000} 935 // maker buy (quote swap asset), taker sell (base swap asset) 936 match2 := newMatch(limitBuyStanding2, limitSellImmediate2, limitSellImmediate2.Quantity, epochID2) 937 err = archie.InsertMatch(match2) 938 if err != nil { 939 t.Fatalf("InsertMatch() failed: %v", err) 940 } 941 942 // Make a perfect 1 lot BTC-LTC match. 943 limitBuyStanding3 := newLimitOrder(false, 4500000, 1, order.StandingTiF, 20) 944 limitBuyStanding3.BaseAsset = AssetBTC 945 limitBuyStanding3.QuoteAsset = AssetLTC 946 limitBuyStanding3.AccountID = limitBuyStanding.AccountID 947 limitSellImmediate3 := newLimitOrder(true, 4490000, 1, order.ImmediateTiF, 30) 948 limitSellImmediate3.BaseAsset = AssetBTC 949 limitSellImmediate3.QuoteAsset = AssetLTC 950 limitSellImmediate3.AccountID = limitSellImmediate.AccountID 951 952 // Store it. 953 epochID3 := order.EpochID{132412342, 1000} 954 match3 := newMatch(limitBuyStanding3, limitSellImmediate3, limitSellImmediate3.Quantity, epochID3) 955 err = archie.InsertMatch(match3) 956 if err != nil { 957 t.Fatalf("InsertMatch() failed: %v", err) 958 } 959 960 tests := []struct { 961 name string 962 acctID account.AccountID 963 numExpected int 964 wantMatch []*order.Match 965 wantedErr error 966 }{ 967 { 968 "ok maker", 969 limitBuyStanding.User(), 970 2, 971 []*order.Match{match2, match3}, 972 nil, 973 }, 974 { 975 "ok taker", 976 limitSellImmediate.User(), 977 2, 978 []*order.Match{match2, match3}, 979 nil, 980 }, 981 { 982 "nope", 983 randomAccountID(), 984 0, 985 nil, 986 nil, 987 }, 988 } 989 990 idInMatchSlice := func(mid order.MatchID, ms []*order.Match) int { 991 for i := range ms { 992 if ms[i].ID() == mid { 993 return i 994 } 995 } 996 return -1 997 } 998 999 for _, tt := range tests { 1000 t.Run(tt.name, func(t *testing.T) { 1001 userMatch, err := archie.AllActiveUserMatches(tt.acctID) 1002 if err != tt.wantedErr { 1003 t.Fatal(err) 1004 } 1005 if len(userMatch) != tt.numExpected { 1006 t.Errorf("Retrieved %d matches for user %v, expected %d.", len(userMatch), tt.acctID, tt.numExpected) 1007 } 1008 for _, match := range userMatch { 1009 loc := idInMatchSlice(match.ID, tt.wantMatch) 1010 if loc == -1 { 1011 t.Errorf("Unknown match ID retrieved: %v.", match.ID) 1012 continue 1013 } 1014 if tt.wantMatch[loc].FeeRateBase != match.BaseRate { 1015 t.Errorf("incorrect base fee rate. got %d, want %d", 1016 match.BaseRate, tt.wantMatch[loc].FeeRateBase) 1017 } 1018 if tt.wantMatch[loc].FeeRateQuote != match.QuoteRate { 1019 t.Errorf("incorrect quote fee rate. got %d, want %d", 1020 match.QuoteRate, tt.wantMatch[loc].FeeRateQuote) 1021 } 1022 if tt.wantMatch[loc].Epoch.End() != match.Epoch.End() { 1023 t.Errorf("incorrect match time. got %v, want %v", 1024 match.Epoch.End(), tt.wantMatch[loc].Epoch.End()) 1025 } 1026 if tt.wantMatch[loc].Taker.Trade().Address != match.TakerAddr { 1027 t.Errorf("incorrect counterparty swap address. got %v, want %v", 1028 match.TakerAddr, tt.wantMatch[loc].Taker.Trade().Address) 1029 } 1030 if tt.wantMatch[loc].Maker.Address != match.MakerAddr { 1031 t.Errorf("incorrect counterparty swap address. got %v, want %v", 1032 match.MakerAddr, tt.wantMatch[loc].Maker.Address) 1033 } 1034 } 1035 }) 1036 } 1037 } 1038 1039 func TestActiveSwaps(t *testing.T) { 1040 if err := cleanTables(archie.db); err != nil { 1041 t.Fatalf("cleanTables: %v", err) 1042 } 1043 1044 swapsDetails, err := archie.ActiveSwaps() 1045 if err != nil { 1046 t.Fatal(err) 1047 } 1048 if len(swapsDetails) > 0 { 1049 t.Fatalf("got details for %d swaps, expected 0", len(swapsDetails)) 1050 } 1051 1052 user1 := randomAccountID() 1053 user2 := randomAccountID() 1054 match := generateMatch(t, order.MakerRedeemed, true, user1, user2) 1055 1056 swapsDetails, err = archie.ActiveSwaps() 1057 if err != nil { 1058 t.Fatal(err) 1059 } 1060 if len(swapsDetails) != 1 { 1061 t.Fatalf("got details for %d swaps, expected 1", len(swapsDetails)) 1062 } 1063 swapDetails := swapsDetails[0] 1064 1065 taker, _, err := archie.Order(swapDetails.MatchData.Taker, swapDetails.Base, swapDetails.Quote) 1066 if err != nil { 1067 t.Fatalf("Failed to load taker order: %v", err) 1068 } 1069 if taker.ID() != swapDetails.MatchData.Taker { 1070 t.Fatalf("Failed to load order %v, computed ID %v instead", swapDetails.MatchData.Taker, taker.ID()) 1071 } 1072 if match.match.Taker.ID() != swapDetails.MatchData.Taker { 1073 t.Fatalf("Failed to load order %v, computed ID %v instead", swapDetails.MatchData.Taker, taker.ID()) 1074 } 1075 1076 maker, _, err := archie.Order(swapDetails.MatchData.Maker, swapDetails.Base, swapDetails.Quote) 1077 if err != nil { 1078 t.Fatalf("Failed to load maker order: %v", err) 1079 } 1080 if maker.ID() != swapDetails.MatchData.Maker { 1081 t.Fatalf("Failed to load order %v, computed ID %v instead", swapDetails.MatchData.Maker, maker.ID()) 1082 } 1083 if match.match.Maker.ID() != swapDetails.MatchData.Maker { 1084 t.Fatalf("Failed to load order %v, computed ID %v instead", swapDetails.MatchData.Maker, maker.ID()) 1085 } 1086 1087 if match.match.Rate != swapDetails.Rate { 1088 t.Fatalf("wrong rate loaded, got %d want %d", swapDetails.Rate, match.match.Rate) 1089 } 1090 if match.match.Quantity != swapDetails.Quantity { 1091 t.Fatalf("wrong quantity loaded, got %d want %d", swapDetails.Quantity, match.match.Quantity) 1092 } 1093 makerLO, ok := maker.(*order.LimitOrder) 1094 if !ok { 1095 t.Fatalf("Maker order was not a limit order: %T", maker) 1096 } 1097 1098 matchBack := &order.Match{ 1099 Taker: taker, 1100 Maker: makerLO, 1101 Quantity: swapDetails.Quantity, 1102 Rate: swapDetails.Rate, 1103 FeeRateBase: swapDetails.BaseRate, 1104 FeeRateQuote: swapDetails.QuoteRate, 1105 Epoch: swapDetails.Epoch, 1106 Status: swapDetails.Status, 1107 Sigs: order.Signatures{ // not really needed 1108 MakerMatch: swapDetails.SwapData.SigMatchAckMaker, 1109 TakerMatch: swapDetails.SwapData.SigMatchAckTaker, 1110 MakerAudit: swapDetails.SwapData.ContractAAckSig, 1111 TakerAudit: swapDetails.SwapData.ContractBAckSig, 1112 TakerRedeem: swapDetails.SwapData.RedeemAAckSig, 1113 }, 1114 } 1115 1116 wantMid := match.match.ID() 1117 if wantMid != swapDetails.MatchData.ID { 1118 t.Fatalf("incorrect match ID %v, expected %v", swapDetails.MatchData.ID, wantMid) 1119 } 1120 // recompute the match ID from the loaded orders (their computed IDs), match rate, qty, etc. 1121 if wantMid != matchBack.ID() { 1122 t.Fatalf("Failed to reconstruct Match %v, computed ID %v instead", matchBack.ID(), wantMid) 1123 } 1124 } 1125 1126 func TestMatchStatuses(t *testing.T) { 1127 if err := cleanTables(archie.db); err != nil { 1128 t.Fatalf("cleanTables: %v", err) 1129 } 1130 1131 // Unknown market 1132 aid := randomAccountID() 1133 var mid order.MatchID 1134 copy(mid[:], encode.RandomBytes(32)) 1135 _, err := archie.MatchStatuses(aid, 100, 101, []order.MatchID{mid}) 1136 noMktErr := new(db.ArchiveError) 1137 if !errors.As(err, noMktErr) || noMktErr.Code != db.ErrUnsupportedMarket { 1138 t.Fatalf("incorrect error for unsupported market: %v", err) 1139 } 1140 1141 user1 := randomAccountID() 1142 user2 := randomAccountID() 1143 1144 matches := []*matchPair{ 1145 generateMatch(t, order.NewlyMatched, true, user1, user2), // 0 1146 generateMatch(t, order.MakerSwapCast, false, user1, user2), // 1 1147 generateMatch(t, order.TakerSwapCast, true, user1, user2), // 2 1148 generateMatch(t, order.MakerRedeemed, true, user1, user2), // 3 1149 generateMatch(t, order.MatchComplete, false, user1, user2), // 4 -- inactive via SaveRedeemB 1150 generateMatch(t, order.MakerRedeemed, false, randomAccountID(), randomAccountID()), // 5 1151 } 1152 1153 idList := func(idxs ...int) []order.MatchID { 1154 ids := make([]order.MatchID, 0, len(idxs)) 1155 for _, i := range idxs { 1156 ids = append(ids, matches[i].match.ID()) 1157 } 1158 return ids 1159 } 1160 1161 tests := []struct { 1162 name string 1163 user account.AccountID 1164 req []order.MatchID 1165 exp []int // matches index 1166 }{ 1167 // user 1: 1 hit 1168 { 1169 name: "find1", 1170 user: user1, 1171 req: idList(0), 1172 exp: []int{0}, 1173 }, 1174 // user 1: 1 hit + 1 miss. 1175 { 1176 name: "find1-miss1", 1177 user: user1, 1178 req: idList(1, 5), 1179 exp: []int{1}, 1180 }, 1181 // user 2 hit 4 1182 { 1183 name: "find4", 1184 user: user2, 1185 req: idList(0, 1, 2, 3), 1186 exp: []int{0, 1, 2, 3}, 1187 }, 1188 } 1189 1190 for _, tt := range tests { 1191 statuses, err := archie.MatchStatuses(tt.user, AssetDCR, AssetBTC, tt.req) 1192 if err != nil { 1193 t.Fatalf("%s: error getting order statuses: %v", tt.name, err) 1194 } 1195 if len(statuses) != len(tt.exp) { 1196 t.Fatalf("%s: wrongs number of statuses returned. expected %d, got %d", tt.name, len(tt.exp), len(statuses)) 1197 } 1198 top: 1199 for _, expIdx := range tt.exp { 1200 matchPair := matches[expIdx] 1201 expStatus := matchPair.status 1202 matchID := matchPair.match.ID() 1203 // Find the status 1204 for _, status := range statuses { 1205 if status.ID != matchID { 1206 continue 1207 } 1208 if status.Status != expStatus.Status { 1209 t.Fatalf("%s: expIdx = %d, wrong status. expected %s, got %s", tt.name, expIdx, expStatus.Status, status.Status) 1210 } 1211 if !bytes.Equal(status.MakerContract, expStatus.MakerContract) { 1212 t.Fatalf("%s: wrong MakerContract. expected %x, got %x", tt.name, expStatus.MakerContract, status.MakerContract) 1213 } 1214 if !bytes.Equal(status.TakerContract, expStatus.TakerContract) { 1215 t.Fatalf("%s: wrong TakerContract. expected %x, got %x", tt.name, expStatus.TakerContract, status.TakerContract) 1216 } 1217 if !bytes.Equal(status.MakerSwap, expStatus.MakerSwap) { 1218 t.Fatalf("%s: wrong MakerSwap. expected %x, got %x", tt.name, expStatus.MakerSwap, status.MakerSwap) 1219 } 1220 if !bytes.Equal(status.TakerSwap, expStatus.TakerSwap) { 1221 t.Fatalf("%s: wrong TakerSwap. expected %x, got %x", tt.name, expStatus.TakerSwap, status.TakerSwap) 1222 } 1223 if !bytes.Equal(status.MakerRedeem, expStatus.MakerRedeem) { 1224 t.Fatalf("%s: wrong MakerRedeem. expected %x, got %x", tt.name, expStatus.MakerRedeem, status.MakerRedeem) 1225 } 1226 if !bytes.Equal(status.TakerRedeem, expStatus.TakerRedeem) { 1227 t.Fatalf("%s: wrong TakerRedeem. expected %x, got %x", tt.name, expStatus.TakerRedeem, status.TakerRedeem) 1228 } 1229 if !bytes.Equal(status.Secret, expStatus.Secret) { 1230 t.Fatalf("%s: wrong Secret. expected %x, got %x", tt.name, expStatus.Secret, status.Secret) 1231 } 1232 if status.Active != expStatus.Active { 1233 t.Fatalf("%s: wrong Active. expected %t, got %t", tt.name, expStatus.Active, status.Active) 1234 } 1235 continue top 1236 } 1237 t.Fatalf("%s: expected match at index %d not found in results", tt.name, expIdx) 1238 } 1239 } 1240 1241 } 1242 1243 func TestEpochReport(t *testing.T) { 1244 if err := cleanTables(archie.db); err != nil { 1245 t.Fatalf("cleanTables: %v", err) 1246 } 1247 1248 lastRate, err := archie.LastEpochRate(42, 0) 1249 if err != nil { 1250 t.Fatalf("error getting last epoch rate from empty table (should be err = nil, rate = 0): %v", err) 1251 } 1252 if lastRate != 0 { 1253 t.Fatalf("wrong initial last rate. expected 0, got %d", lastRate) 1254 } 1255 1256 var epochIdx, epochDur int64 = 13245678, 6000 1257 err = archie.InsertEpoch(&db.EpochResults{ 1258 MktBase: 42, 1259 MktQuote: 0, 1260 Idx: epochIdx, 1261 Dur: epochDur, 1262 MatchVolume: 1, 1263 HighRate: 2, 1264 LowRate: 3, 1265 StartRate: 4, 1266 EndRate: 5, 1267 QuoteVolume: 6, 1268 }) 1269 1270 if err != nil { 1271 t.Fatalf("error inserting first epoch: %v", err) 1272 } 1273 1274 lastRate, err = archie.LastEpochRate(42, 0) 1275 if err != nil { 1276 t.Fatalf("error getting last epoch rate from after first epoch: %v", err) 1277 } 1278 if lastRate != 5 { 1279 t.Fatalf("wrong first epoch last rate. expected 5, got %d", lastRate) 1280 } 1281 1282 // Trying for the same epoch should violate a primary key constraint. 1283 err = archie.InsertEpoch(&db.EpochResults{ 1284 MktBase: 42, 1285 MktQuote: 0, 1286 Idx: epochIdx, 1287 Dur: epochDur, 1288 }) 1289 if err == nil { 1290 t.Fatalf("no error for duplicate epoch") 1291 } 1292 1293 err = archie.InsertEpoch(&db.EpochResults{ 1294 MktBase: 42, 1295 MktQuote: 0, 1296 Idx: epochIdx + 1, 1297 Dur: epochDur, 1298 MatchVolume: 11, 1299 HighRate: 12, 1300 LowRate: 13, 1301 StartRate: 14, 1302 EndRate: 15, 1303 QuoteVolume: 16, 1304 }) 1305 if err != nil { 1306 t.Fatalf("error inserting second epoch: %v", err) 1307 } 1308 1309 lastRate, err = archie.LastEpochRate(42, 0) 1310 if err != nil { 1311 t.Fatalf("error getting last epoch rate from after second-to-last epoch: %v", err) 1312 } 1313 if lastRate != 15 { 1314 t.Fatalf("wrong second-to-last epoch last rate. expected 15, got %d", lastRate) 1315 } 1316 1317 archie.InsertEpoch(&db.EpochResults{ 1318 MktBase: 42, 1319 MktQuote: 0, 1320 Idx: epochIdx + 2, 1321 Dur: epochDur, 1322 MatchVolume: 100, 1323 HighRate: 100, 1324 LowRate: 100, 1325 StartRate: 100, 1326 EndRate: 100, 1327 QuoteVolume: 100, 1328 }) 1329 1330 epochCache := candles.NewCache(3, uint64(epochDur)) 1331 dayCache := candles.NewCache(2, uint64(time.Hour*24/time.Millisecond)) 1332 1333 err = archie.LoadEpochStats(42, 0, []*candles.Cache{epochCache, dayCache}) 1334 if err != nil { 1335 t.Fatalf("error loading epoch stats: %v", err) 1336 } 1337 1338 epochCandles := epochCache.WireCandles(3).Candles() 1339 if len(epochCandles) != 3 { 1340 t.Fatalf("epoch cache has wrong number of entries. expected 3, got %d", len(epochCandles)) 1341 } 1342 lastCandle := epochCandles[len(epochCandles)-1] 1343 if lastCandle.MatchVolume != 100 { 1344 t.Fatalf("wrong last epoch candle match volume. expected 100, got %d", lastCandle.MatchVolume) 1345 } 1346 1347 dayCandles := dayCache.WireCandles(2).Candles() 1348 if len(dayCandles) != 1 { 1349 t.Fatalf("day cache has wrong number of entries. expected 1, got %d", len(dayCandles)) 1350 } 1351 lastCandle = dayCandles[len(dayCandles)-1] 1352 if lastCandle.MatchVolume != 112 { // 1 + 11 1353 t.Fatalf("wrong last day candle MatchVolume. expected 112, got %d", lastCandle.MatchVolume) 1354 } 1355 if lastCandle.QuoteVolume != 122 { // 6 + 16 1356 t.Fatalf("wrong last day candle QuoteVolume. expected 122, got %d", lastCandle.MatchVolume) 1357 } 1358 if lastCandle.HighRate != 100 { 1359 t.Fatalf("wrong last day candle HighRate. expected 100, got %d", lastCandle.HighRate) 1360 } 1361 if lastCandle.LowRate != 3 { 1362 t.Fatalf("wrong last day candle LowRate. expected 3, got %d", lastCandle.LowRate) 1363 } 1364 if lastCandle.StartRate != 4 { 1365 t.Fatalf("wrong last day candle StartRate. expected 4, got %d", lastCandle.StartRate) 1366 } 1367 if lastCandle.EndRate != 100 { 1368 t.Fatalf("wrong last day candle EndRate. expected 100, got %d", lastCandle.EndRate) 1369 } 1370 1371 }