decred.org/dcrdex@v1.0.3/client/orderbook/orderbook_test.go (about) 1 package orderbook 2 3 import ( 4 "bytes" 5 "encoding/hex" 6 "testing" 7 8 "decred.org/dcrdex/dex/msgjson" 9 "decred.org/dcrdex/dex/order" 10 ) 11 12 func makeOrderBookMsg(seq uint64, mid string, orders []*msgjson.BookOrderNote) *msgjson.OrderBook { 13 return &msgjson.OrderBook{ 14 Seq: seq, 15 MarketID: mid, 16 Orders: orders, 17 } 18 } 19 20 func makeBookOrderNote(seq uint64, mid string, oid order.OrderID, side uint8, 21 qty uint64, rate uint64, time uint64) *msgjson.BookOrderNote { 22 return &msgjson.BookOrderNote{ 23 TradeNote: msgjson.TradeNote{ 24 Side: side, 25 Quantity: qty, 26 Rate: rate, 27 TiF: 0, 28 Time: time, 29 }, 30 OrderNote: msgjson.OrderNote{ 31 Seq: seq, 32 MarketID: mid, 33 OrderID: oid[:], 34 }, 35 } 36 } 37 38 func makeUnbookOrderNote(seq uint64, mid string, oid order.OrderID) *msgjson.UnbookOrderNote { 39 return &msgjson.UnbookOrderNote{ 40 Seq: seq, 41 MarketID: mid, 42 OrderID: oid[:], 43 } 44 } 45 46 func makeCachedBookOrderNote(orderNote *msgjson.BookOrderNote) *cachedOrderNote { 47 return &cachedOrderNote{ 48 Route: msgjson.BookOrderRoute, 49 OrderNote: orderNote, 50 } 51 } 52 53 func makeCachedUnbookOrderNote(orderNote *msgjson.UnbookOrderNote) *cachedOrderNote { 54 return &cachedOrderNote{ 55 Route: msgjson.UnbookOrderRoute, 56 OrderNote: orderNote, 57 } 58 } 59 60 func makeOrderBook(seq uint64, marketID string, orders []*Order, cachedOrders []*cachedOrderNote, synced bool) *OrderBook { 61 ob := NewOrderBook(tLogger) 62 ob.noteQueue = cachedOrders 63 ob.marketID = marketID 64 ob.seq = seq 65 66 for _, order := range orders { 67 ob.orders[order.OrderID] = rateSell{order.Rate, order.sell()} 68 69 switch order.Side { 70 case msgjson.BuyOrderNum: 71 ob.buys.Add(order) 72 73 case msgjson.SellOrderNum: 74 ob.sells.Add(order) 75 } 76 } 77 78 ob.setSynced(synced) 79 80 return ob 81 } 82 83 func TestOrderBookSync(t *testing.T) { 84 tests := []struct { 85 label string 86 snapshot *msgjson.OrderBook 87 orderBook *OrderBook 88 expected *OrderBook 89 initialQueueState []*cachedOrderNote 90 initialSyncState bool 91 wantErr bool 92 }{ 93 { 94 label: "Sync blank unsynced order book", 95 snapshot: makeOrderBookMsg( 96 2, 97 "ob", 98 []*msgjson.BookOrderNote{ 99 makeBookOrderNote(1, "ob", [32]byte{'b'}, msgjson.BuyOrderNum, 10, 1, 2), 100 makeBookOrderNote(2, "ob", [32]byte{'c'}, msgjson.BuyOrderNum, 10, 2, 5), 101 }, 102 ), 103 orderBook: NewOrderBook(tLogger), 104 expected: makeOrderBook( 105 2, 106 "ob", 107 []*Order{ 108 makeOrder([32]byte{'b'}, msgjson.BuyOrderNum, 10, 1, 2), 109 makeOrder([32]byte{'c'}, msgjson.BuyOrderNum, 10, 2, 5), 110 }, 111 make([]*cachedOrderNote, 0), 112 true, 113 ), 114 initialQueueState: make([]*cachedOrderNote, 0), 115 initialSyncState: false, 116 wantErr: false, 117 }, 118 { 119 label: "Sync blank order book with no order notes", 120 snapshot: makeOrderBookMsg( 121 2, 122 "ob", 123 []*msgjson.BookOrderNote{}, 124 ), 125 orderBook: NewOrderBook(tLogger), 126 expected: makeOrderBook( 127 2, 128 "ob", 129 []*Order{}, 130 make([]*cachedOrderNote, 0), 131 true, 132 ), 133 initialQueueState: make([]*cachedOrderNote, 0), 134 initialSyncState: false, 135 wantErr: false, 136 }, 137 { 138 label: "Sync already synced order book", 139 snapshot: makeOrderBookMsg( 140 2, 141 "ob", 142 []*msgjson.BookOrderNote{ 143 makeBookOrderNote(1, "ob", [32]byte{'b'}, msgjson.BuyOrderNum, 10, 1, 2), 144 makeBookOrderNote(2, "ob", [32]byte{'c'}, msgjson.BuyOrderNum, 10, 2, 5), 145 }, 146 ), 147 orderBook: NewOrderBook(tLogger), 148 expected: nil, 149 initialQueueState: make([]*cachedOrderNote, 0), 150 initialSyncState: true, 151 wantErr: true, 152 }, 153 { 154 label: "Sync order book with cached unbook order note", 155 snapshot: makeOrderBookMsg( 156 2, 157 "ob", 158 []*msgjson.BookOrderNote{ 159 makeBookOrderNote(1, "ob", [32]byte{'b'}, msgjson.BuyOrderNum, 10, 1, 2), 160 makeBookOrderNote(2, "ob", [32]byte{'c'}, msgjson.BuyOrderNum, 10, 2, 5), 161 }, 162 ), 163 orderBook: NewOrderBook(tLogger), 164 expected: makeOrderBook( 165 3, 166 "ob", 167 []*Order{ 168 makeOrder([32]byte{'c'}, msgjson.BuyOrderNum, 10, 1, 5), 169 }, 170 make([]*cachedOrderNote, 0), 171 true, 172 ), 173 initialQueueState: []*cachedOrderNote{ 174 makeCachedUnbookOrderNote(makeUnbookOrderNote(3, "ob", [32]byte{'b'})), 175 }, 176 initialSyncState: false, 177 wantErr: false, 178 }, 179 { 180 label: "Sync order book with cached book order note", 181 snapshot: makeOrderBookMsg( 182 3, 183 "ob", 184 []*msgjson.BookOrderNote{ 185 makeBookOrderNote(1, "ob", [32]byte{'b'}, msgjson.BuyOrderNum, 10, 1, 2), 186 makeBookOrderNote(2, "ob", [32]byte{'c'}, msgjson.BuyOrderNum, 5, 2, 5), 187 makeBookOrderNote(3, "ob", [32]byte{'d'}, msgjson.SellOrderNum, 6, 3, 10), 188 }, 189 ), 190 orderBook: NewOrderBook(tLogger), 191 expected: makeOrderBook( 192 4, 193 "ob", 194 []*Order{ 195 makeOrder([32]byte{'b'}, msgjson.BuyOrderNum, 10, 1, 2), 196 makeOrder([32]byte{'c'}, msgjson.BuyOrderNum, 5, 2, 5), 197 makeOrder([32]byte{'d'}, msgjson.SellOrderNum, 6, 3, 10), 198 makeOrder([32]byte{'e'}, msgjson.SellOrderNum, 4, 2, 12), 199 }, 200 make([]*cachedOrderNote, 0), 201 true, 202 ), 203 initialQueueState: []*cachedOrderNote{ 204 makeCachedBookOrderNote( 205 makeBookOrderNote(4, "ob", [32]byte{'e'}, msgjson.SellOrderNum, 4, 2, 12)), 206 }, 207 initialSyncState: false, 208 wantErr: false, 209 }, 210 } 211 212 for idx, tc := range tests { 213 tc.orderBook.noteQueue = tc.initialQueueState 214 tc.orderBook.setSynced(tc.initialSyncState) 215 err := tc.orderBook.Sync(tc.snapshot) 216 if (err != nil) != tc.wantErr { 217 t.Fatalf("[OrderBook.Sync] #%d: error: %v, wantErr: %v", 218 idx+1, err, tc.wantErr) 219 } 220 221 if !tc.wantErr { 222 if tc.orderBook.seq != tc.expected.seq { 223 t.Fatalf("[OrderBook.Sync] #%d: expected sequence of %d,"+ 224 " got %d", idx+1, tc.expected.seq, tc.orderBook.seq) 225 } 226 227 if tc.orderBook.marketID != tc.expected.marketID { 228 t.Fatalf("[OrderBook.Sync] #%d: expected market id of %s,"+ 229 " got %s", idx+1, tc.expected.marketID, tc.orderBook.marketID) 230 } 231 232 if len(tc.orderBook.orders) != len(tc.expected.orders) { 233 t.Fatalf("[OrderBook.Sync] #%d: expected orders size of %d,"+ 234 " got %d", idx+1, len(tc.expected.orders), len(tc.orderBook.orders)) 235 } 236 237 if len(tc.orderBook.buys.bins) != len(tc.expected.buys.bins) { 238 t.Fatalf("[OrderBook.Sync] #%d: expected buys book side "+ 239 "size of %d, got %d", idx+1, len(tc.expected.buys.bins), 240 len(tc.orderBook.buys.bins)) 241 } 242 243 if len(tc.orderBook.sells.bins) != len(tc.expected.sells.bins) { 244 t.Fatalf("[OrderBook.Sync] #%d: expected buys book side "+ 245 "size of %d, got %d", idx+1, len(tc.expected.sells.bins), 246 len(tc.orderBook.sells.bins)) 247 } 248 249 if len(tc.orderBook.noteQueue) != len(tc.expected.noteQueue) { 250 t.Fatalf("[OrderBook.Sync] #%d: expected note queue "+ 251 "size of %d, got %d", idx+1, len(tc.expected.noteQueue), 252 len(tc.orderBook.noteQueue)) 253 } 254 } 255 } 256 } 257 258 func TestOrderBookBook(t *testing.T) { 259 tests := []struct { 260 label string 261 orderBook *OrderBook 262 note *msgjson.BookOrderNote 263 expected *OrderBook 264 wantErr bool 265 }{ 266 { 267 label: "Book a buy order", 268 orderBook: makeOrderBook( 269 2, 270 "ob", 271 []*Order{ 272 makeOrder([32]byte{'b'}, msgjson.BuyOrderNum, 10, 1, 2), 273 makeOrder([32]byte{'c'}, msgjson.BuyOrderNum, 10, 2, 5), 274 }, 275 make([]*cachedOrderNote, 0), 276 true, 277 ), 278 note: makeBookOrderNote(3, "ob", [32]byte{'d'}, msgjson.BuyOrderNum, 5, 3, 10), 279 expected: makeOrderBook( 280 3, 281 "ob", 282 []*Order{ 283 makeOrder([32]byte{'b'}, msgjson.BuyOrderNum, 10, 1, 2), 284 makeOrder([32]byte{'c'}, msgjson.BuyOrderNum, 10, 2, 5), 285 makeOrder([32]byte{'d'}, msgjson.BuyOrderNum, 5, 3, 10), 286 }, 287 make([]*cachedOrderNote, 0), 288 true, 289 ), 290 wantErr: false, 291 }, 292 // May want to re-implement strict sequence checking. Might use these tests 293 // again. 294 // { 295 // label: "Book buy order with outdated sequence value", 296 // orderBook: makeOrderBook( 297 // 2, 298 // "ob", 299 // []*Order{ 300 // makeOrder([32]byte{'b'}, msgjson.BuyOrderNum, 10, 1, 2), 301 // makeOrder([32]byte{'c'}, msgjson.BuyOrderNum, 10, 2, 5), 302 // }, 303 // make([]*cachedOrderNote, 0), 304 // true, 305 // ), 306 // note: makeBookOrderNote(0, "ob", [32]byte{'a'}, msgjson.BuyOrderNum, 2, 3, 1), 307 // expected: makeOrderBook( 308 // 2, 309 // "ob", 310 // []*Order{ 311 // makeOrder([32]byte{'b'}, msgjson.BuyOrderNum, 10, 1, 2), 312 // makeOrder([32]byte{'c'}, msgjson.BuyOrderNum, 10, 2, 5), 313 // }, 314 // make([]*cachedOrderNote, 0), 315 // true, 316 // ), 317 // wantErr: false, 318 // }, 319 { 320 label: "Book buy order to unsynced order book", 321 orderBook: makeOrderBook( 322 2, 323 "ob", 324 []*Order{ 325 makeOrder([32]byte{'b'}, msgjson.BuyOrderNum, 10, 1, 2), 326 makeOrder([32]byte{'c'}, msgjson.BuyOrderNum, 10, 2, 5), 327 }, 328 make([]*cachedOrderNote, 0), 329 false, 330 ), 331 note: makeBookOrderNote(3, "ob", [32]byte{'a'}, msgjson.BuyOrderNum, 2, 3, 10), 332 expected: makeOrderBook( 333 2, 334 "ob", 335 []*Order{ 336 makeOrder([32]byte{'b'}, msgjson.BuyOrderNum, 10, 1, 2), 337 makeOrder([32]byte{'c'}, msgjson.BuyOrderNum, 10, 2, 5), 338 }, 339 []*cachedOrderNote{ 340 makeCachedBookOrderNote( 341 makeBookOrderNote(3, "ob", [32]byte{'a'}, msgjson.BuyOrderNum, 2, 3, 10)), 342 }, 343 false, 344 ), 345 wantErr: false, 346 }, 347 { 348 label: "Book sell order to order book with different market id", 349 orderBook: makeOrderBook( 350 2, 351 "ob", 352 []*Order{ 353 makeOrder([32]byte{'b'}, msgjson.SellOrderNum, 10, 1, 2), 354 makeOrder([32]byte{'c'}, msgjson.SellOrderNum, 10, 2, 5), 355 }, 356 make([]*cachedOrderNote, 0), 357 true, 358 ), 359 note: makeBookOrderNote(3, "oc", [32]byte{'a'}, msgjson.SellOrderNum, 2, 3, 10), 360 expected: nil, 361 wantErr: true, 362 }, 363 // May want to re-implement strict sequence checking. Might use these tests 364 // again. 365 // { 366 // label: "Book sell order to synced order book with future sequence value", 367 // orderBook: makeOrderBook( 368 // 2, 369 // "ob", 370 // []*Order{ 371 // makeOrder([32]byte{'b'}, msgjson.SellOrderNum, 10, 1, 2), 372 // makeOrder([32]byte{'c'}, msgjson.SellOrderNum, 10, 2, 5), 373 // }, 374 // make([]*cachedOrderNote, 0), 375 // true, 376 // ), 377 // note: makeBookOrderNote(5, "ob", [32]byte{'d'}, msgjson.SellOrderNum, 5, 3, 10), 378 // expected: nil, 379 // wantErr: true, 380 // }, 381 } 382 383 for idx, tc := range tests { 384 err := tc.orderBook.Book(tc.note) 385 if (err != nil) != tc.wantErr { 386 t.Fatalf("[OrderBook.Book:%s]: error: %v, wantErr: %v", 387 tc.label, err, tc.wantErr) 388 } 389 390 if !tc.wantErr { 391 if tc.orderBook.seq != tc.expected.seq { 392 t.Fatalf("[OrderBook.Book] #%d: expected sequence of %d,"+ 393 " got %d", idx+1, tc.expected.seq, tc.orderBook.seq) 394 } 395 396 if tc.orderBook.marketID != tc.expected.marketID { 397 t.Fatalf("[OrderBook.Book] #%d: expected market id of %s,"+ 398 " got %s", idx+1, tc.expected.marketID, tc.orderBook.marketID) 399 } 400 401 if len(tc.orderBook.orders) != len(tc.expected.orders) { 402 t.Fatalf("[OrderBook.Book] #%d: expected orders size of %d,"+ 403 " got %d", idx+1, len(tc.expected.orders), len(tc.orderBook.orders)) 404 } 405 406 if len(tc.orderBook.buys.bins) != len(tc.expected.buys.bins) { 407 t.Fatalf("[OrderBook.Book] #%d: expected buys book side "+ 408 "size of %d, got %d", idx+1, len(tc.expected.buys.bins), 409 len(tc.orderBook.buys.bins)) 410 } 411 412 if len(tc.orderBook.sells.bins) != len(tc.expected.sells.bins) { 413 t.Fatalf("[OrderBook.Book] #%d: expected buys book side "+ 414 "size of %d, got %d", idx+1, len(tc.expected.sells.bins), 415 len(tc.orderBook.sells.bins)) 416 } 417 418 if len(tc.orderBook.noteQueue) != len(tc.expected.noteQueue) { 419 t.Fatalf("[OrderBook.Book] #%d: expected note queue "+ 420 "size of %d, got %d", idx+1, len(tc.expected.noteQueue), 421 len(tc.orderBook.noteQueue)) 422 } 423 } 424 } 425 } 426 427 func TestOrderBookUpdateRemaining(t *testing.T) { 428 var qty uint64 = 10 429 var remaining uint64 = 5 430 mid := "abc_xyz" 431 oid := order.OrderID{0x01} 432 433 book := makeOrderBook( 434 1, 435 mid, 436 []*Order{ 437 makeOrder(oid, msgjson.SellOrderNum, qty, 1, 2), 438 }, 439 make([]*cachedOrderNote, 0), 440 true, 441 ) 442 443 urNote := &msgjson.UpdateRemainingNote{ 444 OrderNote: msgjson.OrderNote{ 445 OrderID: oid[:], 446 MarketID: mid, 447 }, 448 Remaining: remaining, 449 } 450 451 err := book.UpdateRemaining(urNote) 452 if err != nil { 453 t.Fatalf("error updating remaining qty: %v", err) 454 } 455 _, sells, _ := book.Orders() 456 if sells[0].Quantity != remaining { 457 t.Fatalf("failed to update remaining,. Wanted %d, got %d", remaining, sells[0].Quantity) 458 } 459 460 // Unknown order 461 wrongID := order.OrderID{0x02} 462 urNote.OrderID = wrongID[:] 463 err = book.UpdateRemaining(urNote) 464 if err == nil { 465 t.Fatalf("no error updating remaining qty for unknown order") 466 } 467 468 // Bad order ID 469 urNote.OrderID = []byte{0x03} 470 err = book.UpdateRemaining(urNote) 471 if err == nil { 472 t.Fatalf("no error updating remaining qty for invalid order ID") 473 } 474 } 475 476 func TestOrderBookUnbook(t *testing.T) { 477 tests := []struct { 478 label string 479 orderBook *OrderBook 480 note *msgjson.UnbookOrderNote 481 expected *OrderBook 482 wantErr bool 483 }{ 484 { 485 label: "Unbook sell order with synced order book", 486 orderBook: makeOrderBook( 487 2, 488 "ob", 489 []*Order{ 490 makeOrder([32]byte{'b'}, msgjson.SellOrderNum, 10, 1, 2), 491 makeOrder([32]byte{'c'}, msgjson.SellOrderNum, 10, 2, 5), 492 }, 493 make([]*cachedOrderNote, 0), 494 true, 495 ), 496 note: makeUnbookOrderNote(3, "ob", [32]byte{'c'}), 497 expected: makeOrderBook( 498 3, 499 "ob", 500 []*Order{ 501 makeOrder([32]byte{'b'}, msgjson.SellOrderNum, 10, 1, 2), 502 }, 503 make([]*cachedOrderNote, 0), 504 true, 505 ), 506 wantErr: false, 507 }, 508 // May want to re-implement strict sequence checking. Might use these tests 509 // again. 510 // { 511 // label: "Unbook sell order with outdated sequence value", 512 // orderBook: makeOrderBook( 513 // 2, 514 // "ob", 515 // []*Order{ 516 // makeOrder([32]byte{'b'}, msgjson.SellOrderNum, 10, 1, 2), 517 // makeOrder([32]byte{'c'}, msgjson.SellOrderNum, 10, 2, 5), 518 // }, 519 // make([]*cachedOrderNote, 0), 520 // true, 521 // ), 522 // note: makeUnbookOrderNote(0, "ob", [32]byte{'a'}), 523 // expected: makeOrderBook( 524 // 2, 525 // "ob", 526 // []*Order{ 527 // makeOrder([32]byte{'b'}, msgjson.SellOrderNum, 10, 1, 2), 528 // makeOrder([32]byte{'c'}, msgjson.SellOrderNum, 10, 2, 5), 529 // }, 530 // make([]*cachedOrderNote, 0), 531 // true, 532 // ), 533 // wantErr: false, 534 // }, 535 { 536 label: "Unbook sell order with unsynced order book", 537 orderBook: makeOrderBook( 538 2, 539 "ob", 540 []*Order{ 541 makeOrder([32]byte{'b'}, msgjson.SellOrderNum, 10, 1, 2), 542 makeOrder([32]byte{'c'}, msgjson.SellOrderNum, 10, 2, 5), 543 }, 544 make([]*cachedOrderNote, 0), 545 false, 546 ), 547 note: makeUnbookOrderNote(3, "ob", [32]byte{'a'}), 548 expected: makeOrderBook( 549 2, 550 "ob", 551 []*Order{ 552 makeOrder([32]byte{'b'}, msgjson.SellOrderNum, 10, 1, 2), 553 makeOrder([32]byte{'c'}, msgjson.SellOrderNum, 10, 2, 5), 554 }, 555 []*cachedOrderNote{ 556 makeCachedUnbookOrderNote( 557 makeUnbookOrderNote(3, "ob", [32]byte{'a'})), 558 }, 559 false, 560 ), 561 wantErr: false, 562 }, 563 { 564 label: "Unbook sell order with different market id", 565 orderBook: makeOrderBook( 566 2, 567 "ob", 568 []*Order{ 569 makeOrder([32]byte{'b'}, msgjson.SellOrderNum, 10, 1, 2), 570 makeOrder([32]byte{'c'}, msgjson.SellOrderNum, 10, 2, 5), 571 }, 572 make([]*cachedOrderNote, 0), 573 true, 574 ), 575 note: makeUnbookOrderNote(3, "oc", [32]byte{'a'}), 576 expected: nil, 577 wantErr: true, 578 }, 579 { 580 label: "Unbook sell order with future sequence value", 581 orderBook: makeOrderBook( 582 2, 583 "ob", 584 []*Order{ 585 makeOrder([32]byte{'b'}, msgjson.SellOrderNum, 10, 1, 2), 586 makeOrder([32]byte{'c'}, msgjson.SellOrderNum, 10, 2, 5), 587 }, 588 make([]*cachedOrderNote, 0), 589 true, 590 ), 591 note: makeUnbookOrderNote(5, "ob", [32]byte{'d'}), 592 expected: nil, 593 wantErr: true, 594 }, 595 } 596 597 for idx, tc := range tests { 598 err := tc.orderBook.Unbook(tc.note) 599 if (err != nil) != tc.wantErr { 600 t.Fatalf("[OrderBook.Book:%s]: error: %v, wantErr: %v", 601 tc.label, err, tc.wantErr) 602 } 603 604 if !tc.wantErr { 605 if tc.orderBook.seq != tc.expected.seq { 606 t.Fatalf("[OrderBook.Book] #%d: expected sequence of %d,"+ 607 " got %d", idx+1, tc.expected.seq, tc.orderBook.seq) 608 } 609 610 if tc.orderBook.marketID != tc.expected.marketID { 611 t.Fatalf("[OrderBook.Book] #%d: expected market id of %s,"+ 612 " got %s", idx+1, tc.expected.marketID, tc.orderBook.marketID) 613 } 614 615 if len(tc.orderBook.orders) != len(tc.expected.orders) { 616 t.Fatalf("[OrderBook.Book] #%d: expected orders size of %d,"+ 617 " got %d", idx+1, len(tc.expected.orders), len(tc.orderBook.orders)) 618 } 619 620 if len(tc.orderBook.buys.bins) != len(tc.expected.buys.bins) { 621 t.Fatalf("[OrderBook.Book] #%d: expected buys book side "+ 622 "size of %d, got %d", idx+1, len(tc.expected.buys.bins), 623 len(tc.orderBook.buys.bins)) 624 } 625 626 if len(tc.orderBook.sells.bins) != len(tc.expected.sells.bins) { 627 t.Fatalf("[OrderBook.Book] #%d: expected buys book side "+ 628 "size of %d, got %d", idx+1, len(tc.expected.sells.bins), 629 len(tc.orderBook.sells.bins)) 630 } 631 632 if len(tc.orderBook.noteQueue) != len(tc.expected.noteQueue) { 633 t.Fatalf("[OrderBook.Book] #%d: expected note queue "+ 634 "size of %d, got %d", idx+1, len(tc.expected.noteQueue), 635 len(tc.orderBook.noteQueue)) 636 } 637 } 638 } 639 } 640 641 func TestOrderBookBestNOrders(t *testing.T) { 642 tests := []struct { 643 label string 644 orderBook *OrderBook 645 n int 646 sell bool 647 expected []*Order 648 wantErr bool 649 }{ 650 { 651 label: "Fetch best N orders from buy book side", 652 orderBook: makeOrderBook( 653 2, 654 "ob", 655 []*Order{ 656 makeOrder([32]byte{'b'}, msgjson.BuyOrderNum, 10, 1, 2), 657 makeOrder([32]byte{'c'}, msgjson.BuyOrderNum, 10, 2, 5), 658 makeOrder([32]byte{'d'}, msgjson.BuyOrderNum, 5, 3, 10), 659 makeOrder([32]byte{'e'}, msgjson.BuyOrderNum, 8, 4, 12), 660 }, 661 make([]*cachedOrderNote, 0), 662 true, 663 ), 664 n: 3, 665 sell: false, 666 expected: []*Order{ 667 makeOrder([32]byte{'e'}, msgjson.BuyOrderNum, 8, 4, 12), 668 makeOrder([32]byte{'d'}, msgjson.BuyOrderNum, 5, 3, 10), 669 makeOrder([32]byte{'c'}, msgjson.BuyOrderNum, 10, 2, 5), 670 }, 671 wantErr: false, 672 }, 673 { 674 label: "Fetch best N orders from empty sell book side", 675 orderBook: makeOrderBook( 676 2, 677 "ob", 678 []*Order{ 679 makeOrder([32]byte{'b'}, msgjson.BuyOrderNum, 10, 1, 2), 680 makeOrder([32]byte{'c'}, msgjson.BuyOrderNum, 10, 2, 5), 681 makeOrder([32]byte{'d'}, msgjson.BuyOrderNum, 5, 3, 10), 682 makeOrder([32]byte{'e'}, msgjson.BuyOrderNum, 8, 4, 12), 683 }, 684 make([]*cachedOrderNote, 0), 685 true, 686 ), 687 n: 3, 688 sell: true, 689 expected: []*Order{}, 690 wantErr: false, 691 }, 692 { 693 label: "Fetch best N orders (all orders) from sell book side", 694 orderBook: makeOrderBook( 695 2, 696 "ob", 697 []*Order{ 698 makeOrder([32]byte{'b'}, msgjson.SellOrderNum, 10, 1, 2), 699 makeOrder([32]byte{'c'}, msgjson.SellOrderNum, 10, 2, 5), 700 makeOrder([32]byte{'d'}, msgjson.SellOrderNum, 5, 3, 10), 701 makeOrder([32]byte{'e'}, msgjson.SellOrderNum, 8, 4, 12), 702 }, 703 make([]*cachedOrderNote, 0), 704 true, 705 ), 706 n: 5, 707 sell: true, 708 expected: []*Order{ 709 makeOrder([32]byte{'b'}, msgjson.SellOrderNum, 10, 1, 2), 710 makeOrder([32]byte{'c'}, msgjson.SellOrderNum, 10, 2, 5), 711 makeOrder([32]byte{'d'}, msgjson.SellOrderNum, 5, 3, 10), 712 makeOrder([32]byte{'e'}, msgjson.SellOrderNum, 8, 4, 12), 713 }, 714 wantErr: false, 715 }, 716 { 717 label: "Fetch best N orders from unsynced order book", 718 orderBook: makeOrderBook( 719 2, 720 "ob", 721 []*Order{ 722 makeOrder([32]byte{'b'}, msgjson.SellOrderNum, 10, 1, 2), 723 makeOrder([32]byte{'c'}, msgjson.SellOrderNum, 10, 2, 5), 724 makeOrder([32]byte{'d'}, msgjson.SellOrderNum, 5, 3, 10), 725 makeOrder([32]byte{'e'}, msgjson.SellOrderNum, 8, 4, 12), 726 }, 727 make([]*cachedOrderNote, 0), 728 false, 729 ), 730 n: 5, 731 sell: true, 732 expected: nil, 733 wantErr: true, 734 }, 735 { 736 label: "Fetch best N orders (some orders) from sell book side", 737 orderBook: makeOrderBook( 738 2, 739 "ob", 740 []*Order{ 741 makeOrder([32]byte{'b'}, msgjson.SellOrderNum, 10, 1, 2), 742 makeOrder([32]byte{'c'}, msgjson.SellOrderNum, 10, 2, 5), 743 makeOrder([32]byte{'d'}, msgjson.SellOrderNum, 5, 3, 10), 744 makeOrder([32]byte{'e'}, msgjson.SellOrderNum, 8, 4, 12), 745 }, 746 make([]*cachedOrderNote, 0), 747 true, 748 ), 749 n: 3, 750 sell: true, 751 expected: []*Order{ 752 makeOrder([32]byte{'b'}, msgjson.SellOrderNum, 10, 1, 2), 753 makeOrder([32]byte{'c'}, msgjson.SellOrderNum, 10, 2, 5), 754 makeOrder([32]byte{'d'}, msgjson.SellOrderNum, 5, 3, 10), 755 }, 756 wantErr: false, 757 }, 758 } 759 760 for idx, tc := range tests { 761 best, _, err := tc.orderBook.BestNOrders(tc.n, tc.sell) 762 if (err != nil) != tc.wantErr { 763 t.Fatalf("[OrderBook.BestNOrders] #%d: error: %v, wantErr: %v", 764 idx+1, err, tc.wantErr) 765 } 766 767 if !tc.wantErr { 768 if len(best) != len(tc.expected) { 769 t.Fatalf("[OrderBook.BestNOrders] #%d: expected best N orders "+ 770 "size of %d, got %d", idx+1, len(tc.expected), 771 len(best)) 772 } 773 774 for i := 0; i < len(best); i++ { 775 if !bytes.Equal(best[i].OrderID[:], tc.expected[i].OrderID[:]) { 776 t.Fatalf("[OrderBook.BestNOrders] #%d: expected order at "+ 777 "index %d to be %x, got %x", idx+1, i, 778 tc.expected[i].OrderID[:], best[i].OrderID[:]) 779 } 780 781 if best[i].Quantity != tc.expected[i].Quantity { 782 t.Fatalf("[OrderBook.BestNOrders] #%d: expected order "+ 783 "quantity at index %d to be %x, got %x", idx+1, i, 784 tc.expected[i].Quantity, best[i].Quantity) 785 } 786 787 if best[i].Time != tc.expected[i].Time { 788 t.Fatalf("[OrderBook.BestNOrders] #%d: expected order "+ 789 "timestamp at index %d to be %x, got %x", idx+1, i, 790 tc.expected[i].Time, best[i].Time) 791 } 792 } 793 } 794 } 795 } 796 797 func TestOrderBookBestFill(t *testing.T) { 798 tests := []struct { 799 label string 800 orderBook *OrderBook 801 qty uint64 802 side uint8 803 expected []*Fill 804 }{ 805 { 806 label: "Fetch best fill from buy side", 807 orderBook: makeOrderBook( 808 2, 809 "ob", 810 []*Order{ 811 makeOrder([32]byte{'b'}, msgjson.BuyOrderNum, 10, 1, 2), 812 makeOrder([32]byte{'c'}, msgjson.BuyOrderNum, 10, 2, 5), 813 makeOrder([32]byte{'d'}, msgjson.BuyOrderNum, 5, 3, 10), 814 makeOrder([32]byte{'e'}, msgjson.BuyOrderNum, 8, 4, 12), 815 }, 816 make([]*cachedOrderNote, 0), 817 true, 818 ), 819 qty: 24, 820 side: msgjson.BuyOrderNum, 821 expected: []*Fill{ 822 { 823 Rate: 4, 824 Quantity: 8, 825 }, 826 { 827 Rate: 3, 828 Quantity: 5, 829 }, 830 { 831 Rate: 2, 832 Quantity: 10, 833 }, 834 { 835 Rate: 1, 836 Quantity: 1, 837 }, 838 }, 839 }, 840 { 841 label: "Fetch best fill from empty buy side", 842 orderBook: makeOrderBook( 843 2, 844 "ob", 845 []*Order{ 846 makeOrder([32]byte{'b'}, msgjson.SellOrderNum, 10, 1, 2), 847 makeOrder([32]byte{'c'}, msgjson.SellOrderNum, 10, 2, 5), 848 makeOrder([32]byte{'d'}, msgjson.SellOrderNum, 5, 3, 10), 849 makeOrder([32]byte{'e'}, msgjson.SellOrderNum, 8, 4, 12), 850 }, 851 make([]*cachedOrderNote, 0), 852 true, 853 ), 854 qty: 24, 855 side: msgjson.BuyOrderNum, 856 expected: []*Fill{}, 857 }, 858 { 859 label: "Fetch best fill (order book total less than fill quantity) from buy side", 860 orderBook: makeOrderBook( 861 2, 862 "ob", 863 []*Order{ 864 makeOrder([32]byte{'b'}, msgjson.BuyOrderNum, 10, 1, 2), 865 makeOrder([32]byte{'c'}, msgjson.BuyOrderNum, 10, 2, 5), 866 }, 867 make([]*cachedOrderNote, 0), 868 true, 869 ), 870 qty: 40, 871 side: msgjson.BuyOrderNum, 872 expected: []*Fill{ 873 { 874 Rate: 2, 875 Quantity: 10, 876 }, 877 { 878 Rate: 1, 879 Quantity: 10, 880 }, 881 }, 882 }, 883 } 884 885 // bestFill returns the best fill for a quantity from the provided side. 886 bestFill := func(ob *OrderBook, qty uint64, side uint8) ([]*Fill, bool) { 887 switch side { 888 case msgjson.BuyOrderNum: 889 return ob.buys.BestFill(qty) 890 case msgjson.SellOrderNum: 891 return ob.sells.BestFill(qty) 892 } 893 return nil, false 894 } 895 896 for idx, tc := range tests { 897 best, _ := bestFill(tc.orderBook, tc.qty, tc.side) 898 899 if len(best) != len(tc.expected) { 900 t.Fatalf("[OrderBook.BestFill] #%d: expected best fill "+ 901 "size of %d, got %d", idx+1, len(tc.expected), len(best)) 902 } 903 904 for i := 0; i < len(best); i++ { 905 if best[i].Rate != tc.expected[i].Rate { 906 t.Fatalf("[OrderBook.BestFill] #%d: expected fill at "+ 907 "index %d to have rate %d, got %d", idx+1, i, 908 tc.expected[i].Rate, best[i].Rate) 909 } 910 911 if best[i].Quantity != tc.expected[i].Quantity { 912 t.Fatalf("[OrderBook.BestFill] #%d: expected fill at "+ 913 "index %d to have quantity %d, got %d", idx+1, i, 914 tc.expected[i].Quantity, best[i].Quantity) 915 } 916 } 917 } 918 } 919 920 func TestVWAP(t *testing.T) { 921 orders := []*Order{ 922 // buys 923 makeOrder([32]byte{'b'}, msgjson.BuyOrderNum, 10, 200, 2), 924 makeOrder([32]byte{'b'}, msgjson.BuyOrderNum, 20, 180, 2), 925 makeOrder([32]byte{'b'}, msgjson.BuyOrderNum, 10, 160, 2), 926 927 // sells 928 makeOrder([32]byte{'b'}, msgjson.SellOrderNum, 10, 220, 2), 929 makeOrder([32]byte{'b'}, msgjson.SellOrderNum, 20, 240, 2), 930 makeOrder([32]byte{'b'}, msgjson.SellOrderNum, 10, 260, 2), 931 } 932 933 ob := makeOrderBook(1, "ob", orders, make([]*cachedOrderNote, 0), true) 934 935 type test struct { 936 sell bool 937 lots uint64 938 lotSize uint64 939 940 expectedFilled bool 941 expectedAvg uint64 942 expectedExtrema uint64 943 } 944 945 tests := []test{ 946 { 947 sell: true, 948 lots: 1, 949 lotSize: 10, 950 951 expectedFilled: true, 952 expectedAvg: 220, 953 expectedExtrema: 220, 954 }, 955 { 956 sell: true, 957 lots: 2, 958 lotSize: 10, 959 960 expectedFilled: true, 961 expectedAvg: 230, 962 expectedExtrema: 240, 963 }, 964 { 965 sell: true, 966 lots: 3, 967 lotSize: 10, 968 969 expectedFilled: true, 970 expectedAvg: (220 + 240 + 240) / 3, 971 expectedExtrema: 240, 972 }, 973 { 974 sell: true, 975 lots: 4, 976 lotSize: 10, 977 978 expectedFilled: true, 979 expectedAvg: (220 + 240 + 240 + 260) / 4, 980 expectedExtrema: 260, 981 }, 982 { 983 sell: true, 984 lots: 5, 985 lotSize: 10, 986 987 expectedFilled: false, 988 }, 989 { 990 sell: false, 991 lots: 1, 992 lotSize: 10, 993 994 expectedFilled: true, 995 expectedAvg: 200, 996 expectedExtrema: 200, 997 }, 998 { 999 sell: false, 1000 lots: 2, 1001 lotSize: 10, 1002 1003 expectedFilled: true, 1004 expectedAvg: 190, 1005 expectedExtrema: 180, 1006 }, 1007 { 1008 sell: false, 1009 lots: 3, 1010 lotSize: 10, 1011 1012 expectedFilled: true, 1013 expectedAvg: (200 + 180 + 180) / 3, 1014 expectedExtrema: 180, 1015 }, 1016 { 1017 sell: false, 1018 lots: 4, 1019 lotSize: 10, 1020 1021 expectedFilled: true, 1022 expectedAvg: (200 + 180 + 180 + 160) / 4, 1023 expectedExtrema: 160, 1024 }, 1025 { 1026 sell: false, 1027 lots: 5, 1028 lotSize: 10, 1029 1030 expectedFilled: false, 1031 }, 1032 } 1033 1034 for idx, test := range tests { 1035 avg, extrema, filled, err := ob.VWAP(test.lots, test.lotSize, test.sell) 1036 if err != nil { 1037 t.Fatalf("[VWAP] #%d: unexpected error: %v", idx+1, err) 1038 } 1039 1040 if filled != test.expectedFilled { 1041 t.Fatalf("[VWAP] #%d: expected filled to be %t, got %t", idx+1, test.expectedFilled, filled) 1042 } 1043 1044 if avg != test.expectedAvg { 1045 t.Fatalf("[VWAP] #%d: expected average to be %d, got %d", idx+1, test.expectedAvg, avg) 1046 } 1047 1048 if extrema != test.expectedExtrema { 1049 t.Fatalf("[VWAP] #%d: expected extrema to be %d, got %d", idx+1, test.expectedExtrema, extrema) 1050 } 1051 } 1052 } 1053 1054 func TestValidateMatchProof(t *testing.T) { 1055 mid := "mkt" 1056 ob := NewOrderBook(tLogger) 1057 epoch := uint64(10) 1058 n1Pimg := [32]byte{'1'} 1059 n1Commitment := makeCommitment(n1Pimg) 1060 n1OrderID := [32]byte{'a'} 1061 n1 := makeEpochOrderNote(mid, n1OrderID, msgjson.BuyOrderNum, 1, 2, n1Commitment, epoch) 1062 1063 n2Pimg := [32]byte{'2'} 1064 n2Commitment := makeCommitment(n2Pimg) 1065 n2OrderID := [32]byte{'b'} 1066 n2 := makeEpochOrderNote(mid, n2OrderID, msgjson.BuyOrderNum, 1, 2, n2Commitment, epoch) 1067 1068 n3Pimg := [32]byte{'3'} 1069 n3Commitment := makeCommitment(n3Pimg) 1070 n3OrderID := [32]byte{'c'} 1071 n3 := makeEpochOrderNote(mid, n3OrderID, msgjson.BuyOrderNum, 1, 2, n3Commitment, epoch) 1072 1073 err := ob.Enqueue(n1) 1074 if err != nil { 1075 t.Fatalf("[Enqueue]: unexpected error: %v", err) 1076 } 1077 1078 err = ob.Enqueue(n2) 1079 if err != nil { 1080 t.Fatalf("[Enqueue]: unexpected error: %v", err) 1081 } 1082 1083 err = ob.Enqueue(n3) 1084 if err != nil { 1085 t.Fatalf("[Enqueue]: unexpected error: %v", err) 1086 } 1087 1088 expectedCSum, _ := hex.DecodeString("9db8c0547f3b80574df730c3b7005ccef4310e93f766442110fc2c9353230985") 1089 expectedSeed, _ := hex.DecodeString("e2b770f60baab7ac877edfa55bd1443b591c1cdd461667c6eb737ae0c65daf2d") 1090 1091 matchProofNote := msgjson.MatchProofNote{ 1092 MarketID: mid, 1093 Epoch: epoch, 1094 Preimages: []msgjson.Bytes{n1Pimg[:], n2Pimg[:], n3Pimg[:]}, 1095 Misses: []msgjson.Bytes{}, 1096 CSum: expectedCSum, 1097 Seed: expectedSeed, 1098 } 1099 1100 // Ensure a valid match proof message gets validated as expected. 1101 if err := ob.ValidateMatchProof(matchProofNote); err != nil { 1102 t.Fatalf("[ValidateMatchProof]: unexpected error: %v", err) 1103 } 1104 1105 ob = NewOrderBook(tLogger) 1106 1107 err = ob.Enqueue(n1) 1108 if err != nil { 1109 t.Fatalf("[Enqueue]: unexpected error: %v", err) 1110 } 1111 1112 err = ob.Enqueue(n2) 1113 if err != nil { 1114 t.Fatalf("[Enqueue]: unexpected error: %v", err) 1115 } 1116 1117 err = ob.Enqueue(n3) 1118 if err != nil { 1119 t.Fatalf("[Enqueue]: unexpected error: %v", err) 1120 } 1121 1122 expectedCSumWithMisses := expectedCSum // csum not affected by misses 1123 expectedSeedWithMisses, _ := hex.DecodeString("01a161289f06be16ea9b5a5a5492f5664f3e92750dc5ce3fa5775eb9be225730") 1124 1125 matchProofNoteWithMisses := msgjson.MatchProofNote{ 1126 MarketID: mid, 1127 Epoch: epoch, 1128 Preimages: []msgjson.Bytes{n1Pimg[:], n3Pimg[:]}, 1129 Misses: []msgjson.Bytes{n2.OrderID}, 1130 CSum: expectedCSumWithMisses, 1131 Seed: expectedSeedWithMisses, 1132 } 1133 1134 // Ensure a valid match proof message gets validated as expected. 1135 if err := ob.ValidateMatchProof(matchProofNoteWithMisses); err != nil { 1136 t.Fatalf("[ValidateMatchProof (with misses)]: unexpected error: %v", err) 1137 } 1138 1139 ob = NewOrderBook(tLogger) 1140 1141 // firstProof for idx-1, length mismatch ignored 1142 emptyProofNote := msgjson.MatchProofNote{ 1143 MarketID: mid, 1144 Epoch: epoch - 1, // previous 1145 Preimages: []msgjson.Bytes{n1Pimg[:]}, // ob will be missing this, but it's the firstProof, so no error 1146 } 1147 if err := ob.ValidateMatchProof(emptyProofNote); err != nil { 1148 t.Fatalf("[ValidateMatchProof (empty)]: unexpected error: %v", err) 1149 } 1150 1151 // next proof will not permit length mismatches 1152 err = ob.Enqueue(n1) 1153 if err != nil { 1154 t.Fatalf("[Enqueue]: unexpected error: %v", err) 1155 } 1156 1157 err = ob.Enqueue(n2) 1158 if err != nil { 1159 t.Fatalf("[Enqueue]: unexpected error: %v", err) 1160 } 1161 1162 err = ob.Enqueue(n3) 1163 if err != nil { 1164 t.Fatalf("[Enqueue]: unexpected error: %v", err) 1165 } 1166 1167 matchProofNote = msgjson.MatchProofNote{ 1168 MarketID: mid, 1169 Epoch: epoch, 1170 Preimages: []msgjson.Bytes{n1Pimg[:], n2Pimg[:]}, 1171 Misses: []msgjson.Bytes{}, 1172 CSum: expectedCSum, 1173 Seed: expectedSeed, 1174 } 1175 1176 // Ensure a invalid match proof message (missing a preimage) gets 1177 // detected as expected. 1178 if err := ob.ValidateMatchProof(matchProofNote); err == nil { 1179 t.Fatalf("[ValidateMatchProof (missing a preimage)]: unexpected an error") 1180 } 1181 1182 ob = NewOrderBook(tLogger) 1183 1184 err = ob.Enqueue(n1) 1185 if err != nil { 1186 t.Fatalf("[Enqueue]: unexpected error: %v", err) 1187 } 1188 1189 err = ob.Enqueue(n2) 1190 if err != nil { 1191 t.Fatalf("[Enqueue]: unexpected error: %v", err) 1192 } 1193 1194 err = ob.Enqueue(n3) 1195 if err != nil { 1196 t.Fatalf("[Enqueue]: unexpected error: %v", err) 1197 } 1198 1199 junkSeed, _ := hex.DecodeString("e2b770f60baab7ac877edfa55bd1443b591c1cdd461667c6eb737ae0c65daf2d") 1200 1201 matchProofNote = msgjson.MatchProofNote{ 1202 MarketID: mid, 1203 Epoch: epoch, 1204 Preimages: []msgjson.Bytes{n1Pimg[:], n3Pimg[:]}, 1205 Misses: []msgjson.Bytes{n2.OrderID}, 1206 CSum: expectedCSum, 1207 Seed: junkSeed, 1208 } 1209 1210 // Ensure a invalid match proof message (invalid seed) gets detected 1211 // as expected. 1212 if err := ob.ValidateMatchProof(matchProofNote); err == nil { 1213 t.Fatalf("[ValidateMatchProof (invalid seed)]: unexpected error: %v", err) 1214 } 1215 1216 ob = NewOrderBook(tLogger) 1217 1218 err = ob.Enqueue(n1) 1219 if err != nil { 1220 t.Fatalf("[Enqueue]: unexpected error: %v", err) 1221 } 1222 1223 err = ob.Enqueue(n2) 1224 if err != nil { 1225 t.Fatalf("[Enqueue]: unexpected error: %v", err) 1226 } 1227 1228 err = ob.Enqueue(n3) 1229 if err != nil { 1230 t.Fatalf("[Enqueue]: unexpected error: %v", err) 1231 } 1232 1233 junkCSum, _ := hex.DecodeString("000000000f3b80574df730c3b7005ccef4310e93f766442110fc2c9353230985") 1234 1235 matchProofNote = msgjson.MatchProofNote{ 1236 MarketID: mid, 1237 Epoch: epoch, 1238 Preimages: []msgjson.Bytes{n1Pimg[:], n3Pimg[:]}, 1239 Misses: []msgjson.Bytes{n2.OrderID}, 1240 CSum: junkCSum, 1241 Seed: expectedSeedWithMisses, 1242 } 1243 1244 // Ensure a invalid match proof message (invalid csum) gets detected 1245 // as expected. 1246 if err := ob.ValidateMatchProof(matchProofNote); err == nil { 1247 t.Fatalf("[ValidateMatchProof (invalid csum)]: unexpected error: %v", err) 1248 } 1249 }