decred.org/dcrdex@v1.0.5/server/book/orderpq_test.go (about) 1 package book 2 3 import ( 4 "flag" 5 "math/rand" 6 crand "math/rand" 7 "os" 8 "sort" 9 "testing" 10 11 "decred.org/dcrdex/dex/order" 12 "decred.org/dcrdex/server/account" 13 ) 14 15 type Order = order.LimitOrder 16 17 var ( 18 bigList []*Order 19 orders []*Order 20 rnd = rand.New(rand.NewSource(1)) 21 ) 22 23 func randomAccount() (user account.AccountID) { 24 crand.Read(user[:]) 25 return 26 } 27 28 func newFakeAddr() string { 29 const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" 30 b := make([]byte, 35) 31 for i := range b { 32 b[i] = letters[rnd.Int63()%int64(len(letters))] 33 } 34 b[0], b[1] = 'D', 's' // at least have it resemble an address 35 return string(b) 36 } 37 38 func genBigList(listSize int) { 39 if bigList != nil { 40 return 41 } 42 seed := int64(-3405439173988651889) 43 rnd.Seed(seed) 44 45 dupRate := 800 46 if listSize < dupRate { 47 dupRate = listSize / 10 48 } 49 if dupRate == 0 { 50 dupRate = 2 51 } 52 53 bigList = make([]*Order, 0, listSize) 54 for i := 0; i < listSize; i++ { 55 lo := newLimitOrder(false, uint64(rnd.Int63n(90000000)), uint64(rnd.Int63n(6))+1, order.StandingTiF, rnd.Int63n(240)-120) 56 lo.Address = newFakeAddr() 57 // duplicate some prices 58 if (i+1)%(listSize/dupRate) == 0 { 59 lo.Rate = bigList[i/2].Rate 60 lo.Quantity = bigList[i/2].Quantity + 1 61 } 62 _ = lo.ID() // compute and cache the OrderID 63 bigList = append(bigList, lo) 64 } 65 } 66 67 const ( 68 shortListLen = 12_000 69 longListLen = 400_000 70 ) 71 72 func TestMain(m *testing.M) { 73 flag.Parse() // for -short 74 rnd.Seed(4) // some predetermined and reproducible order IDs 75 orders = []*Order{ 76 newLimitOrder(false, 42000000, 2, order.StandingTiF, 0), 77 newLimitOrder(false, 10000, 2, order.StandingTiF, 0), 78 newLimitOrder(false, 42000000, 2, order.StandingTiF, -1000), // rate dup, different time 79 newLimitOrder(false, 123000000, 2, order.StandingTiF, 0), 80 newLimitOrder(false, 42000000, 1, order.StandingTiF, 0), // rate and time dup, different OrderID 81 } 82 if testing.Short() { 83 genBigList(shortListLen) 84 } else { 85 genBigList(longListLen) 86 } 87 // The first and last of the sells are the same user. 88 firstSell, lastSell := bookSellOrders[0], bookSellOrders[len(bookSellOrders)-1] 89 firstSell.Coins[0] = lastSell.Coins[0] 90 firstSell.Address = lastSell.Address 91 os.Exit(m.Run()) 92 } 93 94 func TestLargeOrderMaxPriorityQueue(t *testing.T) { 95 // Max oriented queue 96 pq := NewMaxOrderPQ(uint32(len(bigList) - len(bigList)/16)) // a little smaller to force a realloc 97 for i, o := range bigList { 98 ok := pq.Insert(o) 99 if !ok { 100 t.Fatalf("Failed to insert order %d: %v", i, o) 101 } 102 } 103 104 if pq.Len() != len(bigList) { 105 t.Errorf("pq length incorrect. expected %d, got %d", len(bigList), pq.Len()) 106 } 107 108 initLen := pq.Len() 109 best := pq.ExtractBest() 110 allOrders := make([]*Order, 0, initLen) 111 allOrders = append(allOrders, best) 112 113 lastTime := best.Time() 114 lastRate := best.Price() 115 rates := make([]uint64, initLen) 116 rates[0] = lastRate 117 118 lastLen := pq.Len() 119 i := int(1) // already popped 0 120 for pq.Len() > 0 { 121 best = pq.ExtractBest() 122 allOrders = append(allOrders, best) 123 rate := best.Price() 124 if rate > lastRate { 125 t.Fatalf("Current rate %d > last rate %d. Should be less.", 126 rate, lastRate) 127 } 128 thisTime := best.Time() 129 if rate == lastRate && thisTime < lastTime { 130 t.Fatalf("Orders with the same rate; current time %d < last time %d. Should be greater.", 131 thisTime, lastTime) 132 } 133 lastRate = rate 134 lastTime = thisTime 135 136 rates[i] = rate 137 i++ 138 139 if pq.Len() != lastLen-1 { 140 t.Fatalf("Queue length failed to shrink by 1.") 141 } 142 lastLen = pq.Len() 143 } 144 145 // Ensure sorted in a different way. 146 sorted := sort.SliceIsSorted(rates, func(i, j int) bool { 147 return rates[j] < rates[i] 148 }) 149 if !sorted { 150 t.Errorf("Rates should have been sorted.") 151 } 152 153 pq.Reset(allOrders) 154 if pq.Len() != len(bigList) { 155 t.Errorf("pq length incorrect. expected %d, got %d", len(bigList), pq.Len()) 156 } 157 if pq.PeekBest().Price() != rates[0] { 158 t.Errorf("Heap Reset failed.") 159 } 160 161 pq.Reheap() 162 if pq.Len() != len(bigList) { 163 t.Errorf("pq length incorrect. expected %d, got %d", len(bigList), pq.Len()) 164 } 165 if pq.PeekBest().Price() != rates[0] { 166 t.Errorf("Heap Reset failed.") 167 } 168 } 169 170 func TestLargeOrderMinPriorityQueue(t *testing.T) { 171 // Min oriented queue 172 pq := NewMinOrderPQ(uint32(len(bigList) - len(bigList)/16)) // a little smaller to force a realloc 173 for _, o := range bigList { 174 ok := pq.Insert(o) 175 if !ok { 176 t.Fatalf("Failed to insert order %v", o) 177 } 178 } 179 180 if pq.Len() != len(bigList) { 181 t.Errorf("pq length incorrect. expected %d, got %d", len(bigList), pq.Len()) 182 } 183 184 initLen := pq.Len() 185 best := pq.ExtractBest() 186 allOrders := make([]*Order, 0, initLen) 187 allOrders = append(allOrders, best) 188 189 lastTime := best.Time() 190 lastRate := best.Price() 191 rates := make([]uint64, initLen) 192 rates[0] = lastRate 193 194 lastLen := pq.Len() 195 i := int(1) // already popped 0 196 for pq.Len() > 0 { 197 best = pq.ExtractBest() 198 allOrders = append(allOrders, best) 199 rate := best.Price() 200 if rate < lastRate { 201 t.Fatalf("Current (%d) rate %d < last rate %d. Should be greater.", 202 i, rate, lastRate) 203 } 204 thisTime := best.Time() 205 if rate == lastRate && thisTime < lastTime { 206 t.Fatalf("Orders with the same rate; current time %d < last time %d. Should be greater.", 207 thisTime, lastTime) 208 } 209 lastRate = rate 210 lastTime = thisTime 211 212 rates[i] = rate 213 i++ 214 215 if pq.Len() != lastLen-1 { 216 t.Fatalf("Queue length failed to shrink by 1.") 217 } 218 lastLen = pq.Len() 219 } 220 221 // Ensure sorted in a different way. 222 sorted := sort.SliceIsSorted(rates, func(i, j int) bool { 223 return rates[i] < rates[j] 224 }) 225 if !sorted { 226 t.Errorf("Rates should have been sorted.") 227 } 228 229 pq.Reset(allOrders) 230 if pq.Len() != len(bigList) { 231 t.Errorf("pq length incorrect. expected %d, got %d", len(bigList), pq.Len()) 232 } 233 if pq.PeekBest().Price() != rates[0] { 234 t.Errorf("Heap Reset failed.") 235 } 236 237 pq.Reheap() 238 if pq.Len() != len(bigList) { 239 t.Errorf("pq length incorrect. expected %d, got %d", len(bigList), pq.Len()) 240 } 241 if pq.PeekBest().Price() != rates[0] { 242 t.Errorf("Heap Reset failed.") 243 } 244 } 245 246 func TestLargeOrderMaxPriorityQueue_Orders(t *testing.T) { 247 // Max oriented queue (sell book) 248 pq := NewMaxOrderPQ(uint32(len(bigList))) 249 for _, o := range bigList { 250 ok := pq.Insert(o) 251 if !ok { 252 t.Fatalf("Failed to insert order %v", o) 253 } 254 } 255 256 if pq.Len() != len(bigList) { 257 t.Errorf("pq length incorrect. expected %d, got %d", len(bigList), pq.Len()) 258 } 259 260 // Copy out all orders, sorted. 261 ordersSorted := pq.Orders() 262 263 // Ensure sorted in a different way. 264 sorted := sort.SliceIsSorted(ordersSorted, func(i, j int) bool { 265 if ordersSorted[i].Price() == ordersSorted[j].Price() { 266 return ordersSorted[i].Time() < ordersSorted[j].Time() 267 } 268 return ordersSorted[i].Price() > ordersSorted[j].Price() // max pq 269 }) 270 if !sorted { 271 t.Errorf("Rates should have been sorted") 272 // for _, op := range ordersSorted { 273 // t.Log(op.Price(), op.Time()) 274 // } 275 } 276 277 ordersSorted2 := pq.OrdersN(pq.Count()) 278 if len(ordersSorted2) != len(ordersSorted) { 279 t.Fatalf("Orders() and OrdersN(Count()) returned different slices.") 280 } 281 for i, o := range ordersSorted { 282 if o.ID() != ordersSorted2[i].ID() { 283 t.Errorf("Mismatched orders: %v != %v", o, ordersSorted2[i]) 284 } 285 } 286 287 // Copy out just the six best orders. 288 sixOrders := pq.OrdersN(6) 289 for i := range sixOrders { 290 if sixOrders[i].UID() != ordersSorted[i].UID() { 291 t.Errorf("Order %d incorrect. Got %s, expected %s", 292 i, sixOrders[i].UID(), ordersSorted[i].UID()) 293 } 294 } 295 296 // Do it again to ensure the queue is not changed by copying. 297 sixOrders2 := pq.OrdersN(6) 298 for i := range sixOrders2 { 299 if sixOrders[i].UID() != sixOrders2[i].UID() { 300 t.Errorf("Order %d incorrect. Got %s, expected %s", 301 i, sixOrders2[i].UID(), sixOrders[i].UID()) 302 } 303 } 304 305 pq.Reset(ordersSorted) 306 if pq.Len() != len(bigList) { 307 t.Errorf("pq length incorrect. expected %d, got %d", len(bigList), pq.Len()) 308 } 309 if pq.PeekBest().Price() != ordersSorted[0].Price() { 310 t.Errorf("Heap Reset failed.") 311 } 312 } 313 314 func TestLargeOrderMaxPriorityQueue_realloc(t *testing.T) { 315 // Max oriented queue (sell book) 316 pq := NewMaxOrderPQ(uint32(len(bigList))) // no realloc for initial inserts 317 for _, o := range bigList { 318 ok := pq.Insert(o) 319 if !ok { 320 t.Fatalf("Failed to insert order %v", o) 321 } 322 } 323 324 if pq.Len() != len(bigList) { 325 t.Errorf("pq length incorrect. expected %d, got %d", len(bigList), pq.Len()) 326 } 327 328 newCap := pq.capacity * 3 / 2 329 pq.realloc(newCap) 330 331 if pq.capacity != newCap { 332 t.Errorf("Reallocated capacity incorrect. Expected %d, got %d", 333 newCap, pq.capacity) 334 } 335 336 if pq.Len() != len(bigList) { 337 t.Errorf("pq length incorrect. expected %d, got %d", len(bigList), pq.Len()) 338 } 339 340 // Extract all orders, sorted. This should realloc and decrease cap on the way. 341 ordersSorted := pq.ExtractN(pq.Count()) 342 343 if !testing.Short() && pq.Cap() >= newCap { // only long list is big enough to trigger dealloc 344 t.Errorf("expected a capacity decrease") 345 } 346 t.Logf("OrderPQ capacity decreased from %d to %d after extracting all entries", newCap, pq.Cap()) 347 348 // Ensure sorted in a different way. 349 sorted := sort.SliceIsSorted(ordersSorted, func(i, j int) bool { 350 if ordersSorted[i].Price() == ordersSorted[j].Price() { 351 return ordersSorted[i].Time() < ordersSorted[j].Time() 352 } 353 return ordersSorted[i].Price() > ordersSorted[j].Price() // max pq 354 }) 355 if !sorted { 356 t.Errorf("Rates should have been sorted") 357 // for _, op := range ordersSorted { 358 // t.Log(op.Price(), op.Time()) 359 // } 360 } 361 362 // Remake the queue with the extracted orders. 363 pq.Reset(ordersSorted) 364 if pq.Len() != len(bigList) { 365 t.Errorf("pq length incorrect. expected %d, got %d", len(bigList), pq.Len()) 366 } 367 if pq.PeekBest().Price() != ordersSorted[0].Price() { 368 t.Errorf("Heap Reset failed.") 369 } 370 } 371 372 func TestMinOrderPQ(t *testing.T) { 373 pq := NewMinOrderPQ(0) // zero cap to force a realloc right away 374 375 for _, o := range orders { 376 ok := pq.Insert(o) 377 if !ok { 378 t.Errorf("Failed to insert order %v", o) 379 } 380 } 381 382 best := pq.ExtractBest() 383 if best.UID() != orders[1].UID() { 384 t.Errorf("Incorrect lowest rate order returned: rate = %d, UID = %s", 385 best.Price(), best.UID()) 386 } 387 } 388 389 func TestMaxOrderPQ(t *testing.T) { 390 pq := NewMaxOrderPQ(0) 391 392 for _, o := range orders { 393 ok := pq.Insert(o) 394 if !ok { 395 t.Errorf("Failed to insert order %v", o) 396 } 397 } 398 399 best := pq.ExtractBest() 400 if best.UID() != orders[3].UID() { 401 t.Errorf("Incorrect highest rate order returned: rate = %d, UID = %s", 402 best.Price(), best.UID()) 403 } 404 } 405 406 func TestMaxOrderPQ_TieRate(t *testing.T) { 407 pq := NewMaxOrderPQ(4) 408 409 for _, o := range orders[:3] { 410 ok := pq.Insert(o) 411 if !ok { 412 t.Errorf("Failed to insert order %v", o) 413 } 414 } 415 416 best := pq.ExtractBest() 417 if best.UID() != orders[2].UID() { 418 t.Errorf("Incorrect highest rate order returned: rate = %d, UID = %s", 419 best.Price(), best.UID()) 420 } 421 } 422 423 func TestMaxOrderPQ_TieRateAndTime(t *testing.T) { 424 pq := NewMaxOrderPQ(4) 425 426 // 3671433862ea385f967377d836040cfa74bd66cf35ee1795cc48cab2a9576bc1 427 ok := pq.Insert(orders[0]) 428 if !ok { 429 t.Errorf("Failed to insert order %v", orders[0]) 430 } 431 432 // 1cc5fe0804be6b8112b71dd2d8d4a48177ee78a7f2c663e620c369d3211c86a1 ** higher priority 433 ok = pq.Insert(orders[4]) 434 if !ok { 435 t.Errorf("Failed to insert order %v", orders[4]) 436 } 437 438 best := pq.ExtractBest() 439 if best.UID() != orders[4].UID() { 440 t.Errorf("Incorrect highest rate order returned: rate = %d, UID = %s", 441 best.Price(), best.UID()) 442 } 443 } 444 445 func TestOrderPQCapacity(t *testing.T) { 446 pq := NewMaxOrderPQ(2) 447 448 ok := pq.Insert(orders[0]) 449 if !ok { 450 t.Errorf("Failed to insert order %v", orders[0]) 451 } 452 453 ok = pq.Insert(orders[1]) 454 if !ok { 455 t.Errorf("Failed to insert order %v", orders[1]) 456 } 457 458 ok = pq.Insert(orders[2]) 459 if !ok { 460 t.Errorf("Failed to insert order %v", orders[2]) 461 } 462 463 if pq.Cap() <= 2 { 464 t.Errorf("Failed to grow capacity") 465 } 466 467 if pq.Len() != 3 { 468 t.Errorf("Failed to insert order with capacity growth") 469 } 470 471 best := pq.PeekBest() 472 if best.UID() != orders[2].UID() { 473 t.Errorf("Incorrect highest rate order returned: rate = %d, UID = %s", 474 best.Price(), best.UID()) 475 } 476 } 477 478 func TestOrderPQ_Insert_negative(t *testing.T) { 479 pq := NewMinOrderPQ(2) 480 481 ok := pq.Insert(orders[0]) 482 if !ok { 483 t.Errorf("Failed to insert order %v", orders[0]) 484 } 485 486 ok = pq.Insert(orders[0]) 487 if ok { 488 t.Errorf("Inserted duplicate order %v", orders[1]) 489 } 490 491 ok = pq.Insert(nil) 492 if ok { 493 t.Errorf("Inserted nil order %v", orders[1]) 494 } 495 } 496 497 func TestOrderPQ_Remove(t *testing.T) { 498 pq := NewMaxOrderPQ(2) 499 500 ok := pq.Insert(orders[0]) 501 if !ok { 502 t.Errorf("Failed to insert order %v", orders[0]) 503 } 504 505 ok = pq.Insert(orders[1]) 506 if !ok { 507 t.Errorf("Failed to insert order %v", orders[1]) 508 } 509 510 pq.RemoveOrder(orders[1]) 511 if pq.Len() != 1 { 512 t.Errorf("Queue length expected %d, got %d", 1, pq.Len()) 513 } 514 remainingID := pq.PeekBest().ID() 515 if remainingID != orders[0].ID() { 516 t.Errorf("Remaining element expected %s, got %s", orders[0].ID(), 517 remainingID) 518 } 519 pq.RemoveOrderID(remainingID) 520 if pq.Len() != 0 { 521 t.Errorf("Expected empty queue, got %d", pq.Len()) 522 } 523 } 524 525 func TestOrderPQ_RemoveUserOrders(t *testing.T) { 526 pq := NewMaxOrderPQ(6) 527 528 ok := pq.Insert(orders[0]) 529 if !ok { 530 t.Errorf("Failed to insert order %v", orders[0]) 531 } 532 533 ok = pq.Insert(orders[1]) 534 if !ok { 535 t.Errorf("Failed to insert order %v", orders[1]) 536 } 537 538 user0 := orders[0].AccountID 539 540 user1 := randomAccount() 541 other := newLimitOrder(false, 42000000, 2, order.StandingTiF, 0) 542 other.AccountID = user1 543 ok = pq.Insert(other) 544 if !ok { 545 t.Errorf("Failed to insert order %v", other) 546 } 547 548 amt, count := pq.UserOrderTotals(user0) 549 wantAmt := orders[0].Remaining() + orders[1].Remaining() 550 if amt != wantAmt { 551 t.Errorf("wanted %d remaining, got %d", wantAmt, amt) 552 } 553 if count != 2 { 554 t.Errorf("wanted %d orders, got %d", 2, count) 555 } 556 557 amt, count = pq.UserOrderTotals(user1) 558 wantAmt = other.Remaining() 559 if amt != wantAmt { 560 t.Errorf("wanted %d remaining, got %d", wantAmt, amt) 561 } 562 if count != 1 { 563 t.Errorf("wanted %d orders, got %d", 1, count) 564 } 565 566 removed := pq.RemoveUserOrders(user0) 567 if pq.Len() != 1 { 568 t.Errorf("Queue length expected %d, got %d", 1, pq.Len()) 569 } 570 if len(removed) != 2 { 571 t.Fatalf("removed %d orders, expected %d", len(removed), 2) 572 } 573 for _, oid := range []order.OrderID{orders[0].ID(), orders[1].ID()} { 574 var found bool 575 for i := range removed { 576 if oid == removed[i].ID() { 577 found = true 578 break 579 } 580 } 581 if !found { 582 t.Errorf("didn't remove order %v", oid) 583 } 584 } 585 586 remainingID := pq.PeekBest().ID() 587 if remainingID != other.ID() { 588 t.Errorf("Remaining element expected %s, got %s", other.ID(), 589 remainingID) 590 } 591 removed = pq.RemoveUserOrders(user1) 592 if remain := pq.Len(); remain != 0 { 593 t.Errorf("didn't remove all orders, still have %d", remain) 594 } 595 if len(removed) != 1 { 596 t.Fatalf("removed %d orders, expected %d", len(removed), 1) 597 } 598 if removed[0].ID() != other.ID() { 599 t.Errorf("removed order %v, expected %v", removed[0], other.ID()) 600 } 601 } 602 603 func TestOrderPQMin_Worst(t *testing.T) { 604 pq0 := NewMinOrderPQ(4) 605 worst := pq0.Worst() 606 if worst != nil { 607 t.Errorf("Worst for an empty queue should be nil, got %v", worst) 608 } 609 610 pq1 := NewMinOrderPQ(4) 611 if !pq1.Insert(bigList[0]) { 612 t.Fatalf("Failed to insert order %v", bigList[0]) 613 } 614 worst = pq1.Worst() 615 if worst.UID() != bigList[0].UID() { 616 t.Errorf("Worst failed to return the only order in the queue, got %v", worst) 617 } 618 619 // Min oriented queue 620 pq := NewMinOrderPQ(uint32(len(bigList) - len(bigList)/16)) 621 for _, o := range bigList { 622 ok := pq.Insert(o) 623 if !ok { 624 t.Fatalf("Failed to insert order %v", o) 625 } 626 } 627 628 if pq.Len() != len(bigList) { 629 t.Errorf("pq length incorrect. expected %d, got %d", len(bigList), pq.Len()) 630 } 631 632 sort.Slice(bigList, func(i, j int) bool { 633 if bigList[i].Price() == bigList[j].Price() { 634 return bigList[i].Time() < bigList[j].Time() 635 } 636 return bigList[i].Price() < bigList[j].Price() 637 }) 638 639 // Worst for a min queue is highest rate. 640 worst = pq.Worst() 641 if worst.UID() != bigList[len(bigList)-1].UID() { 642 t.Errorf("Incorrect worst order. Got %s, expected %s", worst.UID(), bigList[len(bigList)-1].UID()) 643 } 644 } 645 646 func TestOrderPQMax_Worst(t *testing.T) { 647 // Max oriented queue 648 pq := NewMaxOrderPQ(uint32(len(bigList))) 649 for _, o := range bigList { 650 ok := pq.Insert(o) 651 if !ok { 652 t.Fatalf("Failed to insert order %v", o) 653 } 654 } 655 656 if pq.Len() != len(bigList) { 657 t.Errorf("pq length incorrect. expected %d, got %d", len(bigList), pq.Len()) 658 } 659 660 sort.Slice(bigList, func(j, i int) bool { 661 if bigList[i].Price() == bigList[j].Price() { 662 return bigList[i].Time() < bigList[j].Time() 663 } 664 return bigList[i].Price() < bigList[j].Price() 665 }) 666 667 // Worst for a min queue is highest rate. 668 worst := pq.Worst() 669 if worst.UID() != bigList[len(bigList)-1].UID() { 670 t.Errorf("Incorrect worst order. Got %s, expected %s", worst.UID(), bigList[len(bigList)-1].UID()) 671 } 672 } 673 674 func TestOrderPQMax_leafNodes(t *testing.T) { 675 // Max oriented queue 676 newQ := func(list []*Order) *OrderPQ { 677 pq := NewMaxOrderPQ(uint32(len(bigList))) 678 for _, o := range list { 679 ok := pq.Insert(o) 680 if !ok { 681 t.Fatalf("Failed to insert order %v", o) 682 } 683 } 684 return pq 685 } 686 687 for sz := 0; sz < 131; sz++ { 688 list := bigList[:sz] 689 pq := newQ(list) 690 leaves := pq.leafNodes() 691 total := pq.Count() 692 expectedNum := total / 2 693 if total%2 != 0 { 694 expectedNum++ 695 } 696 if len(leaves) != expectedNum { 697 t.Errorf("Incorrect number of leaf nodes. Got %d, expected %d", 698 len(leaves), expectedNum) 699 } 700 } 701 }