decred.org/dcrdex@v1.0.5/server/db/driver/pg/orders.go (about) 1 // This code is available on the terms of the project LICENSE.md file, 2 // also available online at https://blueoakcouncil.org/license/1.0.0. 3 4 package pg 5 6 import ( 7 "context" 8 "database/sql" 9 "database/sql/driver" 10 "errors" 11 "fmt" 12 "sort" 13 "time" 14 15 "decred.org/dcrdex/dex" 16 "decred.org/dcrdex/dex/order" 17 "decred.org/dcrdex/server/account" 18 "decred.org/dcrdex/server/db" 19 "decred.org/dcrdex/server/db/driver/pg/internal" 20 "github.com/lib/pq" 21 ) 22 23 // Wrap the CoinID slice to implement custom Scanner and Valuer. 24 type dbCoins []order.CoinID 25 26 // Value implements the sql/driver.Valuer interface. The coin IDs are encoded as 27 // L0|ID0|L1|ID1|... where | is simple concatenation, Ln is the length of the 28 // nth coin ID, and IDn is the bytes of the nth coinID. 29 func (coins dbCoins) Value() (driver.Value, error) { 30 if len(coins) == 0 { 31 return []byte{}, nil 32 } 33 // As an initial guess that's likely accurate for most coins, allocate as if 34 // each coin ID is the same length. 35 lenGuess := len(coins[0]) 36 b := make([]byte, 0, len(coins)*(lenGuess+1)) 37 for _, coin := range coins { 38 b = append(b, byte(len(coin))) 39 b = append(b, coin...) 40 } 41 return b, nil 42 } 43 44 // Scan implements the sql.Scanner interface. 45 func (coins *dbCoins) Scan(src any) error { 46 b := src.([]byte) 47 if len(b) == 0 { 48 *coins = dbCoins{} 49 return nil 50 } 51 lenGuess := int(b[0]) 52 if lenGuess == 0 { 53 return fmt.Errorf("zero-length coin ID indicated") 54 } 55 c := make(dbCoins, 0, len(b)/(lenGuess+1)) 56 for len(b) > 0 { 57 cLen := int(b[0]) 58 if cLen == 0 { 59 return fmt.Errorf("zero-length coin ID indicated") 60 } 61 if len(b) < cLen+1 { 62 return fmt.Errorf("too many bytes indicated") 63 } 64 65 // Deep copy the coin ID (a slice) since the backing buffer may be 66 // reused. 67 bc := make([]byte, cLen) 68 copy(bc, b[1:cLen+1]) 69 c = append(c, bc) 70 71 b = b[cLen+1:] 72 } 73 74 *coins = c 75 return nil 76 } 77 78 var _ db.OrderArchiver = (*Archiver)(nil) 79 80 // Order retrieves an order with the given OrderID, stored for the market 81 // specified by the given base and quote assets. A non-nil error will be 82 // returned if the market is not recognized. If the order is not found, the 83 // error value is ErrUnknownOrder, and the type is order.OrderStatusUnknown. The 84 // only recognized order types are market, limit, and cancel. 85 func (a *Archiver) Order(oid order.OrderID, base, quote uint32) (order.Order, order.OrderStatus, error) { 86 marketSchema, err := a.marketSchema(base, quote) 87 if err != nil { 88 return nil, order.OrderStatusUnknown, err 89 } 90 91 // Since order type is unknown: 92 // - try to load from orders table, which includes market and limit orders 93 // - if found, coerce into the correct order type and return 94 // - if not found, try loading a cancel order with this oid 95 var errA db.ArchiveError 96 ord, status, err := loadTrade(a.db, a.dbName, marketSchema, oid) 97 if errors.As(err, &errA) { 98 if errA.Code != db.ErrUnknownOrder { 99 return nil, order.OrderStatusUnknown, err 100 } 101 // Try the cancel orders. 102 var co *order.CancelOrder 103 co, status, err = loadCancelOrder(a.db, a.dbName, marketSchema, oid) 104 if err != nil { 105 return nil, order.OrderStatusUnknown, err // includes ErrUnknownOrder 106 } 107 co.BaseAsset, co.QuoteAsset = base, quote 108 return co, pgToMarketStatus(status), err 109 // no other order types to try presently 110 } 111 if err != nil { 112 return nil, order.OrderStatusUnknown, err 113 } 114 prefix := ord.Prefix() 115 prefix.BaseAsset, prefix.QuoteAsset = base, quote 116 return ord, pgToMarketStatus(status), nil 117 } 118 119 type pgOrderStatus int16 120 121 const ( 122 orderStatusUnknown pgOrderStatus = iota 123 orderStatusEpoch 124 orderStatusBooked 125 orderStatusExecuted 126 orderStatusFailed // failed helps distinguish matched from unmatched executed cancel orders 127 orderStatusCanceled 128 orderStatusRevoked // indicates a trade order was revoked, or in the cancels table that the cancel is server-generated 129 ) 130 131 func marketToPgStatus(status order.OrderStatus) pgOrderStatus { 132 switch status { 133 case order.OrderStatusEpoch: 134 return orderStatusEpoch 135 case order.OrderStatusBooked: 136 return orderStatusBooked 137 case order.OrderStatusExecuted: 138 return orderStatusExecuted 139 case order.OrderStatusCanceled: 140 return orderStatusCanceled 141 case order.OrderStatusRevoked: 142 return orderStatusRevoked 143 } 144 return orderStatusUnknown 145 } 146 147 func pgToMarketStatus(status pgOrderStatus) order.OrderStatus { 148 switch status { 149 case orderStatusEpoch: 150 return order.OrderStatusEpoch 151 case orderStatusBooked: 152 return order.OrderStatusBooked 153 case orderStatusExecuted, orderStatusFailed: // failed is executed as far as the market is concerned 154 return order.OrderStatusExecuted 155 case orderStatusCanceled: 156 return order.OrderStatusCanceled 157 case orderStatusRevoked, -orderStatusRevoked: // negative revoke status means forgiven preimage miss 158 return order.OrderStatusRevoked 159 } 160 return order.OrderStatusUnknown 161 } 162 163 func (status pgOrderStatus) String() string { 164 switch status { 165 case orderStatusFailed: 166 return "failed" 167 default: 168 return pgToMarketStatus(status).String() 169 } 170 } 171 172 func (status pgOrderStatus) active() bool { 173 switch status { 174 case orderStatusEpoch, orderStatusBooked: 175 return true 176 case orderStatusCanceled, orderStatusRevoked, -orderStatusRevoked, 177 orderStatusExecuted, orderStatusFailed, orderStatusUnknown: 178 return false 179 default: 180 panic("unknown order status!") // programmer error 181 } 182 } 183 184 // NewEpochOrder stores the given order with epoch status. This is equivalent to 185 // StoreOrder with OrderStatusEpoch. 186 func (a *Archiver) NewEpochOrder(ord order.Order, epochIdx, epochDur int64, epochGap int32) error { 187 return a.storeOrder(ord, epochIdx, epochDur, epochGap, orderStatusEpoch) 188 } 189 190 // NewArchivedCancel stores a cancel order directly in the executed state. This 191 // is used for orders that are canceled when the market is suspended, and therefore 192 // do not need to be matched. 193 func (a *Archiver) NewArchivedCancel(ord *order.CancelOrder, epochID, epochDur int64) error { 194 marketSchema, err := a.marketSchema(ord.Base(), ord.Quote()) 195 if err != nil { 196 return err 197 } 198 status := orderStatusExecuted 199 tableName := fullCancelOrderTableName(a.dbName, marketSchema, status.active()) 200 N, err := storeCancelOrder(a.db, tableName, ord, status, epochID, epochDur, db.EpochGapNA) 201 if err != nil { 202 a.fatalBackendErr(err) 203 return fmt.Errorf("storeCancelOrder failed: %w", err) 204 } 205 if N != 1 { 206 err = fmt.Errorf("failed to store order %v: %d rows affected, expected 1", 207 ord.UID(), N) 208 return err 209 } 210 211 return nil 212 } 213 214 func makePseudoCancel(target order.OrderID, user account.AccountID, base, quote uint32, timeStamp time.Time) *order.CancelOrder { 215 // Create a server-generated cancel order to record the server's revoke 216 // order action. 217 return &order.CancelOrder{ 218 P: order.Prefix{ 219 AccountID: user, 220 BaseAsset: base, 221 QuoteAsset: quote, 222 OrderType: order.CancelOrderType, 223 ClientTime: timeStamp, 224 ServerTime: timeStamp, 225 // The zero-value for Commitment is stored as NULL. See 226 // (Commitment).Value. 227 }, 228 TargetOrderID: target, 229 } 230 } 231 232 // FlushBook revokes all booked orders for a market. 233 func (a *Archiver) FlushBook(base, quote uint32) (sellsRemoved, buysRemoved []order.OrderID, err error) { 234 var marketSchema string 235 marketSchema, err = a.marketSchema(base, quote) 236 if err != nil { 237 return 238 } 239 240 // Booked orders (active) are made revoked (archived). 241 srcTableName := fullOrderTableName(a.dbName, marketSchema, orderStatusBooked.active()) 242 dstTableName := fullOrderTableName(a.dbName, marketSchema, orderStatusRevoked.active()) 243 244 timeStamp := time.Now().Truncate(time.Millisecond).UTC() 245 246 var dbTx *sql.Tx 247 dbTx, err = a.db.Begin() 248 if err != nil { 249 err = fmt.Errorf("failed to begin database transaction: %w", err) 250 return 251 } 252 253 fail := func() { 254 sellsRemoved, buysRemoved = nil, nil 255 a.fatalBackendErr(err) 256 _ = dbTx.Rollback() 257 } 258 259 // Changed all booked orders to revoked. 260 stmt := fmt.Sprintf(internal.PurgeBook, srcTableName, orderStatusRevoked, dstTableName) 261 var rows *sql.Rows 262 rows, err = dbTx.Query(stmt, orderStatusBooked) 263 if err != nil { 264 fail() 265 return 266 } 267 defer rows.Close() 268 269 var cos []*order.CancelOrder 270 for rows.Next() { 271 var oid order.OrderID 272 var sell bool 273 var aid account.AccountID 274 if err = rows.Scan(&oid, &sell, &aid); err != nil { 275 fail() 276 return 277 } 278 cos = append(cos, makePseudoCancel(oid, aid, base, quote, timeStamp)) 279 if sell { 280 sellsRemoved = append(sellsRemoved, oid) 281 } else { 282 buysRemoved = append(buysRemoved, oid) 283 } 284 } 285 286 if err = rows.Err(); err != nil { 287 fail() 288 return 289 } 290 291 // Insert the pseudo-cancel orders. 292 cancelTable := fullCancelOrderTableName(a.dbName, marketSchema, orderStatusRevoked.active()) 293 stmt = fmt.Sprintf(internal.InsertCancelOrder, cancelTable) 294 for _, co := range cos { 295 // Special values for this server-generate cancel order: 296 // - Pass nil instead of the zero value Commitment to save a comparison 297 // in (Commitment).Value with the zero value. 298 // - Set epoch idx to exemptEpochIdx (-1) and dur to dummyEpochDur (1), 299 // consistent with revokeOrder(..., exempt=true). 300 _, err = dbTx.Exec(stmt, co.ID(), co.AccountID, co.ClientTime, 301 co.ServerTime, nil, co.TargetOrderID, orderStatusRevoked, exemptEpochIdx, dummyEpochDur, db.EpochGapNA) 302 if err != nil { 303 fail() 304 err = fmt.Errorf("failed to store pseudo-cancel order: %w", err) 305 return 306 } 307 } 308 309 if err = dbTx.Commit(); err != nil { 310 fail() 311 err = fmt.Errorf("failed to commit transaction: %w", err) 312 return 313 } 314 315 return 316 } 317 318 // BookOrders retrieves all booked orders (with order status booked) for the 319 // specified market. This will be used to repopulate a market's book on 320 // construction of the market. 321 func (a *Archiver) BookOrders(base, quote uint32) ([]*order.LimitOrder, error) { 322 marketSchema, err := a.marketSchema(base, quote) 323 if err != nil { 324 return nil, err 325 } 326 327 // All booked orders are active. 328 tableName := fullOrderTableName(a.dbName, marketSchema, true) // active (true) 329 330 // no query timeout here, only explicit cancellation 331 ords, err := ordersByStatusFromTable(a.ctx, a.db, tableName, base, quote, orderStatusBooked) 332 if err != nil { 333 return nil, err 334 } 335 336 // Verify loaded orders are limits, and cast to *LimitOrder. 337 limits := make([]*order.LimitOrder, 0, len(ords)) 338 for _, ord := range ords { 339 lo, ok := ord.(*order.LimitOrder) 340 if !ok { 341 log.Errorf("loaded book order %v that was not a limit order", ord.ID()) 342 continue 343 } 344 345 limits = append(limits, lo) 346 } 347 348 return limits, nil 349 } 350 351 // EpochOrders retrieves all epoch orders for the specified market returns them 352 // as a slice of order.Order. 353 func (a *Archiver) EpochOrders(base, quote uint32) ([]order.Order, error) { 354 los, mos, cos, err := a.epochOrders(base, quote) 355 if err != nil { 356 return nil, err 357 } 358 orders := make([]order.Order, 0, len(los)+len(mos)+len(cos)) 359 for _, o := range los { 360 orders = append(orders, o) 361 } 362 for _, o := range mos { 363 orders = append(orders, o) 364 } 365 for _, o := range cos { 366 orders = append(orders, o) 367 } 368 return orders, nil 369 } 370 371 // epochOrders retrieves all epoch orders for the specified market. 372 func (a *Archiver) epochOrders(base, quote uint32) ([]*order.LimitOrder, []*order.MarketOrder, []*order.CancelOrder, error) { 373 marketSchema, err := a.marketSchema(base, quote) 374 if err != nil { 375 return nil, nil, nil, err 376 } 377 378 tableName := fullOrderTableName(a.dbName, marketSchema, true) // active (true) 379 380 // no query timeout here, only explicit cancellation 381 ords, err := ordersByStatusFromTable(a.ctx, a.db, tableName, base, quote, orderStatusEpoch) 382 if err != nil { 383 return nil, nil, nil, err 384 } 385 386 // Verify loaded order type and add to correct slice. 387 var limits []*order.LimitOrder 388 var markets []*order.MarketOrder 389 for _, ord := range ords { 390 switch o := ord.(type) { 391 case *order.LimitOrder: 392 limits = append(limits, o) 393 case *order.MarketOrder: 394 markets = append(markets, o) 395 default: 396 log.Errorf("loaded epoch order %v that was not a limit or market order: %T", ord.ID(), ord) 397 } 398 } 399 400 tableName = fullCancelOrderTableName(a.dbName, marketSchema, true) // active(true) 401 cancels, err := cancelOrdersByStatusFromTable(a.ctx, a.db, tableName, base, quote, orderStatusEpoch) 402 if err != nil { 403 return nil, nil, nil, err 404 } 405 406 return limits, markets, cancels, nil 407 } 408 409 // ActiveOrderCoins retrieves a CoinID slice for each active order. 410 func (a *Archiver) ActiveOrderCoins(base, quote uint32) (baseCoins, quoteCoins map[order.OrderID][]order.CoinID, err error) { 411 var marketSchema string 412 marketSchema, err = a.marketSchema(base, quote) 413 if err != nil { 414 return 415 } 416 417 tableName := fullOrderTableName(a.dbName, marketSchema, true) // active (true) 418 stmt := fmt.Sprintf(internal.SelectOrderCoinIDs, tableName) 419 420 var rows *sql.Rows 421 rows, err = a.db.Query(stmt) 422 switch { 423 case errors.Is(err, sql.ErrNoRows): 424 err = nil 425 fallthrough 426 case err == nil: 427 baseCoins = make(map[order.OrderID][]order.CoinID) 428 quoteCoins = make(map[order.OrderID][]order.CoinID) 429 default: 430 return 431 } 432 defer rows.Close() 433 434 for rows.Next() { 435 var oid order.OrderID 436 var coins dbCoins 437 var sell bool 438 err = rows.Scan(&oid, &sell, &coins) 439 if err != nil { 440 return nil, nil, err 441 } 442 443 // Sell orders lock base asset coins. 444 if sell { 445 baseCoins[oid] = coins 446 } else { 447 // Buy orders lock quote asset coins. 448 quoteCoins[oid] = coins 449 } 450 } 451 452 if err = rows.Err(); err != nil { 453 return nil, nil, err 454 } 455 456 return 457 } 458 459 // BookOrder updates the given LimitOrder with booked status. 460 func (a *Archiver) BookOrder(lo *order.LimitOrder) error { 461 return a.updateOrderStatus(lo, orderStatusBooked) 462 } 463 464 // ExecuteOrder updates the given Order with executed status. 465 func (a *Archiver) ExecuteOrder(ord order.Order) error { 466 return a.updateOrderStatus(ord, orderStatusExecuted) 467 } 468 469 // CancelOrder updates a LimitOrder with canceled status. If the order does not 470 // exist in the Archiver, CancelOrder returns ErrUnknownOrder. To store a new 471 // limit order with canceled status, use StoreOrder. 472 func (a *Archiver) CancelOrder(lo *order.LimitOrder) error { 473 return a.updateOrderStatus(lo, orderStatusCanceled) 474 } 475 476 // RevokeOrder updates an Order with revoked status, which is used for 477 // DEX-revoked orders rather than orders matched with a user's CancelOrder. If 478 // the order does not exist in the Archiver, RevokeOrder returns 479 // ErrUnknownOrder. This may change orders with status executed to revoked, 480 // which may be unexpected. 481 func (a *Archiver) RevokeOrder(ord order.Order) (cancelID order.OrderID, timeStamp time.Time, err error) { 482 return a.revokeOrder(ord, false) 483 } 484 485 // RevokeOrderUncounted is like RevokeOrder except that the generated cancel 486 // order will not be counted against the user. i.e. ExecutedCancelsForUser 487 // should not return the cancel orders created this way. 488 func (a *Archiver) RevokeOrderUncounted(ord order.Order) (cancelID order.OrderID, timeStamp time.Time, err error) { 489 return a.revokeOrder(ord, true) 490 } 491 492 const ( 493 exemptEpochIdx int64 = -1 494 countedEpochIdx int64 = 0 495 dummyEpochDur int64 = 1 // for idx*duration math 496 ) 497 498 func (a *Archiver) revokeOrder(ord order.Order, exempt bool) (cancelID order.OrderID, timeStamp time.Time, err error) { 499 // Revoke the targeted order. 500 err = a.updateOrderStatus(ord, orderStatusRevoked) 501 if err != nil { 502 return 503 } 504 505 // Store the pseudo-cancel order with 0 epoch idx and duration and status 506 // orderStatusRevoked as indicators that this is a revocation. 507 timeStamp = time.Now().Truncate(time.Millisecond).UTC() 508 co := makePseudoCancel(ord.ID(), ord.User(), ord.Base(), ord.Quote(), timeStamp) 509 cancelID = co.ID() 510 epochIdx := countedEpochIdx 511 if exempt { 512 epochIdx = exemptEpochIdx 513 } 514 err = a.storeOrder(co, epochIdx, dummyEpochDur, db.EpochGapNA, orderStatusRevoked) 515 return 516 } 517 518 // FailCancelOrder updates or inserts the given CancelOrder with failed status. 519 // To update a CancelOrder with executed status, use ExecuteOrder. 520 func (a *Archiver) FailCancelOrder(co *order.CancelOrder) error { 521 return a.updateOrderStatus(co, orderStatusFailed) 522 } 523 524 func validateOrder(ord order.Order, status pgOrderStatus, mkt *dex.MarketInfo) bool { 525 if status == orderStatusFailed && ord.Type() != order.CancelOrderType { 526 return false 527 } 528 return db.ValidateOrder(ord, pgToMarketStatus(status), mkt) 529 } 530 531 // StoreOrder stores an order for the specified epoch ID (idx:dur) with the 532 // provided status. The market is determined from the Order. A non-nil error 533 // will be returned if the market is not recognized. All orders are validated 534 // via server/db.ValidateOrder to ensure only sensible orders reach persistent 535 // storage. Updating orders should be done via one of the update functions such 536 // as UpdateOrderStatus. 537 func (a *Archiver) StoreOrder(ord order.Order, epochIdx, epochDur int64, status order.OrderStatus) error { 538 return a.storeOrder(ord, epochIdx, epochDur, db.EpochGapNA, marketToPgStatus(status)) 539 } 540 541 func (a *Archiver) storeOrder(ord order.Order, epochIdx, epochDur int64, epochGap int32, status pgOrderStatus) error { 542 marketSchema, err := a.marketSchema(ord.Base(), ord.Quote()) 543 if err != nil { 544 return err 545 } 546 547 if !validateOrder(ord, status, a.markets[marketSchema]) { 548 return db.ArchiveError{ 549 Code: db.ErrInvalidOrder, 550 Detail: fmt.Sprintf("invalid order %v for status %v and market %v", 551 ord.UID(), status, a.markets[marketSchema]), 552 } 553 } 554 555 // Check for order commitment duplicates. This also covers order ID since 556 // commitment is part of order serialization. Note that it checks ALL 557 // markets, so this may be excessive. This check may be more appropriate in 558 // the caller, or may be removed in favor of a different check depending on 559 // where preimages are stored. If we allow reused commitments if the 560 // preimages are only revealed once, then the unique constraint on the 561 // commit column in the orders tables would need to be removed. 562 563 // IDEA: Do not apply this constraint to server-generated cancel orders, 564 // which we may wish to have a zero value commitment and status revoked. 565 // if _, isCancel := ord.(*order.CancelOrder); !isCancel || status != orderStatusRevoked { 566 commit := ord.Commitment() 567 found, prevOid, err := a.OrderWithCommit(a.ctx, commit) // no query timeouts in storeOrder, only explicit cancellation 568 if err != nil { 569 return err 570 } 571 if found { 572 return db.ArchiveError{ 573 Code: db.ErrReusedCommit, 574 Detail: fmt.Sprintf("order %v reuses commit %v from previous order %v", 575 ord.UID(), commit, prevOid), 576 } 577 } 578 579 var N int64 580 switch ot := ord.(type) { 581 case *order.CancelOrder: 582 tableName := fullCancelOrderTableName(a.dbName, marketSchema, status.active()) 583 N, err = storeCancelOrder(a.db, tableName, ot, status, epochIdx, epochDur, epochGap) 584 if err != nil { 585 a.fatalBackendErr(err) 586 return fmt.Errorf("storeCancelOrder failed: %w", err) 587 } 588 case *order.MarketOrder: 589 tableName := fullOrderTableName(a.dbName, marketSchema, status.active()) 590 N, err = storeMarketOrder(a.db, tableName, ot, status, epochIdx, epochDur) 591 if err != nil { 592 a.fatalBackendErr(err) 593 return fmt.Errorf("storeMarketOrder failed: %w", err) 594 } 595 case *order.LimitOrder: 596 tableName := fullOrderTableName(a.dbName, marketSchema, status.active()) 597 N, err = storeLimitOrder(a.db, tableName, ot, status, epochIdx, epochDur) 598 if err != nil { 599 a.fatalBackendErr(err) 600 return fmt.Errorf("storeLimitOrder failed: %w", err) 601 } 602 default: 603 panic("ValidateOrder should have caught this") 604 } 605 606 if N != 1 { 607 err = fmt.Errorf("failed to store order %v: %d rows affected, expected 1", 608 ord.UID(), N) 609 a.fatalBackendErr(err) 610 return err 611 } 612 613 return nil 614 } 615 616 func (a *Archiver) orderTableName(ord order.Order) (string, pgOrderStatus, error) { 617 status, orderType, _, err := a.orderStatus(ord) 618 if err != nil { 619 return "", status, err 620 } 621 622 marketSchema, err := a.marketSchema(ord.Base(), ord.Quote()) 623 if err != nil { 624 return "", status, err 625 } 626 627 var tableName string 628 switch orderType { 629 case order.MarketOrderType, order.LimitOrderType: 630 tableName = fullOrderTableName(a.dbName, marketSchema, status.active()) 631 case order.CancelOrderType: 632 tableName = fullCancelOrderTableName(a.dbName, marketSchema, status.active()) 633 default: 634 return "", status, fmt.Errorf("unrecognized order type %v", orderType) 635 } 636 return tableName, status, nil 637 } 638 639 func (a *Archiver) OrderPreimage(ord order.Order) (order.Preimage, error) { 640 var pi order.Preimage 641 642 tableName, _, err := a.orderTableName(ord) 643 if err != nil { 644 return pi, err 645 } 646 647 stmt := fmt.Sprintf(internal.SelectOrderPreimage, tableName) 648 err = a.db.QueryRow(stmt, ord.ID()).Scan(&pi) 649 return pi, err 650 } 651 652 // StorePreimage stores the preimage associated with an existing order. 653 func (a *Archiver) StorePreimage(ord order.Order, pi order.Preimage) error { 654 tableName, status, err := a.orderTableName(ord) 655 if err != nil { 656 return err 657 } 658 659 // Preimages are stored during epoch processing, specifically after users 660 // have responded with their preimages but before swap negotiation begins. 661 // Thus, this order should be "active" i.e. not in an archived orders table. 662 if !status.active() { 663 log.Warnf("Attempting to set preimage for archived order %v", ord.UID()) 664 } 665 666 stmt := fmt.Sprintf(internal.SetOrderPreimage, tableName) 667 N, err := sqlExec(a.db, stmt, pi, ord.ID()) 668 if err != nil { 669 a.fatalBackendErr(err) 670 return err 671 } 672 if N != 1 { 673 return fmt.Errorf("failed to update 1 order's preimage, updated %d", N) 674 } 675 return nil 676 } 677 678 // SetOrderCompleteTime sets the successful swap completion time for an existing 679 // order. It is an error if the order is not in executed status. 680 func (a *Archiver) SetOrderCompleteTime(ord order.Order, compTimeMs int64) error { 681 status, orderType, _, err := a.orderStatus(ord) 682 if err != nil { 683 return err 684 } 685 686 if status != orderStatusExecuted { // complete_time is only set for executed orders, not canceled or revoked 687 log.Warnf("Attempting to set swap completion time for order %v in status %v, not executed", 688 ord.UID(), status) 689 return db.ArchiveError{ 690 Code: db.ErrOrderNotExecuted, 691 Detail: fmt.Sprintf("unable to set completed time for order %v in status %v, not executed", 692 ord.UID(), status), 693 } 694 } 695 696 marketSchema, err := a.marketSchema(ord.Base(), ord.Quote()) 697 if err != nil { 698 return db.ArchiveError{ 699 Code: db.ErrInvalidOrder, 700 Detail: fmt.Sprintf("unknown market (%d, %d) for order %v", 701 ord.Base(), ord.Quote(), ord.UID()), 702 } 703 } 704 705 var tableName string 706 switch orderType { 707 case order.MarketOrderType, order.LimitOrderType: 708 tableName = fullOrderTableName(a.dbName, marketSchema, status.active()) 709 case order.CancelOrderType: 710 tableName = fullCancelOrderTableName(a.dbName, marketSchema, status.active()) 711 default: 712 return db.ArchiveError{ 713 Code: db.ErrInvalidOrder, 714 Detail: fmt.Sprintf("unknown type for order %v: %v", ord.UID(), orderType), 715 } 716 } 717 718 stmt := fmt.Sprintf(internal.SetOrderCompleteTime, tableName) 719 N, err := sqlExec(a.db, stmt, compTimeMs, ord.ID()) 720 if err != nil { 721 a.fatalBackendErr(err) 722 return db.ArchiveError{ 723 Code: db.ErrGeneralFailure, 724 Detail: "SetOrderCompleteTime failed:" + err.Error(), 725 } 726 } 727 if N != 1 { 728 return db.ArchiveError{ 729 Code: db.ErrUpdateCount, 730 Detail: fmt.Sprintf("failed to update 1 order's completion time, updated %d", N), 731 } 732 } 733 return nil 734 } 735 736 type orderCompStamped struct { 737 oid order.OrderID 738 t int64 739 } 740 741 // CompletedUserOrders retrieves the N most recently completed orders for a user 742 // across all markets. 743 func (a *Archiver) CompletedUserOrders(aid account.AccountID, N int) (oids []order.OrderID, compTimes []int64, err error) { 744 var ords []orderCompStamped 745 746 for schema := range a.markets { 747 tableName := fullOrderTableName(a.dbName, schema, false) // NOT active table 748 ctx, cancel := context.WithTimeout(a.ctx, a.queryTimeout) 749 mktOids, err := completedUserOrders(ctx, a.db, tableName, aid, N) 750 cancel() 751 if err != nil { 752 return nil, nil, err 753 } 754 ords = append(ords, mktOids...) 755 } 756 757 sort.Slice(ords, func(i, j int) bool { 758 return ords[i].t > ords[j].t // descending, latest completed order first 759 }) 760 761 if N > len(ords) { 762 N = len(ords) 763 } 764 765 for i := range ords[:N] { 766 oids = append(oids, ords[i].oid) 767 compTimes = append(compTimes, ords[i].t) 768 } 769 770 return 771 } 772 773 func completedUserOrders(ctx context.Context, dbe *sql.DB, tableName string, aid account.AccountID, N int) (oids []orderCompStamped, err error) { 774 stmt := fmt.Sprintf(internal.RetrieveCompletedOrdersForAccount, tableName) 775 var rows *sql.Rows 776 rows, err = dbe.QueryContext(ctx, stmt, aid, N) 777 if err != nil { 778 return 779 } 780 defer rows.Close() 781 782 for rows.Next() { 783 var oid order.OrderID 784 var acct account.AccountID 785 var completeTime sql.NullInt64 786 err = rows.Scan(&oid, &acct, &completeTime) 787 if err != nil { 788 return nil, err 789 } 790 791 oids = append(oids, orderCompStamped{oid, completeTime.Int64}) 792 } 793 794 if err = rows.Err(); err != nil { 795 return nil, err 796 } 797 798 return 799 } 800 801 // PreimageStats retrieves results of the N most recent preimage requests for 802 // the user across all markets. 803 func (a *Archiver) PreimageStats(user account.AccountID, lastN int) ([]*db.PreimageResult, error) { 804 var outcomes []*db.PreimageResult 805 806 queryOutcomes := func(stmt string) error { 807 ctx, cancel := context.WithTimeout(a.ctx, a.queryTimeout) 808 defer cancel() 809 810 rows, err := a.db.QueryContext(ctx, stmt, user, lastN, orderStatusRevoked) 811 if err != nil { 812 return err 813 } 814 defer rows.Close() 815 816 for rows.Next() { 817 var miss bool 818 var time int64 819 var oid order.OrderID 820 err = rows.Scan(&oid, &miss, &time) 821 if err != nil { 822 return err 823 } 824 outcomes = append(outcomes, &db.PreimageResult{ 825 Miss: miss, 826 Time: time, 827 ID: oid, 828 }) 829 } 830 831 return rows.Err() 832 } 833 834 for schema := range a.markets { 835 // archived trade orders 836 stmt := fmt.Sprintf(internal.PreimageResultsLastN, fullOrderTableName(a.dbName, schema, false)) 837 if err := queryOutcomes(stmt); err != nil { 838 return nil, err 839 } 840 841 // archived cancel orders 842 stmt = fmt.Sprintf(internal.CancelPreimageResultsLastN, fullCancelOrderTableName(a.dbName, schema, false)) 843 if err := queryOutcomes(stmt); err != nil { 844 return nil, err 845 } 846 } 847 848 sort.Slice(outcomes, func(i, j int) bool { 849 return outcomes[j].Time < outcomes[i].Time // descending 850 }) 851 if len(outcomes) > lastN { 852 outcomes = outcomes[:lastN] 853 } 854 855 return outcomes, nil 856 } 857 858 // OrderStatusByID gets the status, type, and filled amount of the order with 859 // the given OrderID in the market specified by a base and quote asset. See also 860 // OrderStatus. If the order is not found, the error value is ErrUnknownOrder, 861 // and the type is order.OrderStatusUnknown. 862 func (a *Archiver) OrderStatusByID(oid order.OrderID, base, quote uint32) (order.OrderStatus, order.OrderType, int64, error) { 863 pgStatus, orderType, filled, err := a.orderStatusByID(oid, base, quote) 864 return pgToMarketStatus(pgStatus), orderType, filled, err 865 } 866 867 func (a *Archiver) orderStatusByID(oid order.OrderID, base, quote uint32) (pgOrderStatus, order.OrderType, int64, error) { 868 marketSchema, err := a.marketSchema(base, quote) 869 if err != nil { 870 return orderStatusUnknown, order.UnknownOrderType, -1, err 871 } 872 status, orderType, filled, err := orderStatus(a.db, oid, a.dbName, marketSchema) 873 if db.IsErrOrderUnknown(err) { 874 status, err = cancelOrderStatus(a.db, oid, a.dbName, marketSchema) 875 if err != nil { 876 // The severity of an unknown order is up to the caller. 877 if !db.IsErrOrderUnknown(err) { 878 a.fatalBackendErr(err) 879 } 880 return orderStatusUnknown, order.UnknownOrderType, -1, err // includes ErrUnknownOrder 881 } 882 filled = -1 883 orderType = order.CancelOrderType 884 } 885 return status, orderType, filled, err 886 } 887 888 // OrderStatus gets the status, ID, and filled amount of the given order. See 889 // also OrderStatusByID. 890 func (a *Archiver) OrderStatus(ord order.Order) (order.OrderStatus, order.OrderType, int64, error) { 891 return a.OrderStatusByID(ord.ID(), ord.Base(), ord.Quote()) 892 } 893 894 func (a *Archiver) orderStatus(ord order.Order) (pgOrderStatus, order.OrderType, int64, error) { 895 return a.orderStatusByID(ord.ID(), ord.Base(), ord.Quote()) 896 } 897 898 // UpdateOrderStatusByID updates the status and filled amount of the order with 899 // the given OrderID in the market specified by a base and quote asset. If 900 // filled is -1, the filled amount is unchanged. For cancel orders, the filled 901 // amount is ignored. OrderStatusByID is used to locate the existing order. If 902 // the order is not found, the error value is ErrUnknownOrder, and the type is 903 // market/order.OrderStatusUnknown. See also UpdateOrderStatus. 904 func (a *Archiver) UpdateOrderStatusByID(oid order.OrderID, base, quote uint32, status order.OrderStatus, filled int64) error { 905 return a.updateOrderStatusByID(oid, base, quote, marketToPgStatus(status), filled) 906 } 907 908 func (a *Archiver) updateOrderStatusByID(oid order.OrderID, base, quote uint32, status pgOrderStatus, filled int64) error { 909 marketSchema, err := a.marketSchema(base, quote) 910 if err != nil { 911 return err 912 } 913 914 initStatus, orderType, initFilled, err := a.orderStatusByID(oid, base, quote) 915 if err != nil { 916 return err 917 } 918 919 if initStatus == status && filled == initFilled { 920 log.Tracef("Not updating order with no status or filled amount change: %v.", oid) 921 return nil 922 } 923 if filled == -1 { 924 filled = initFilled 925 } 926 927 tableChange := status.active() != initStatus.active() 928 929 if !initStatus.active() { 930 if tableChange { 931 return fmt.Errorf("Moving an order from an archived to active status: "+ 932 "Order %s (%s -> %s)", oid, initStatus, status) 933 } 934 log.Infof("Archived order is changing status: Order %s (%s -> %s)", 935 oid, initStatus, status) 936 } 937 938 switch orderType { 939 case order.LimitOrderType, order.MarketOrderType: 940 srcTableName := fullOrderTableName(a.dbName, marketSchema, initStatus.active()) 941 if tableChange { 942 dstTableName := fullOrderTableName(a.dbName, marketSchema, status.active()) 943 return a.moveOrder(oid, srcTableName, dstTableName, status, filled) 944 } 945 946 // No table move, just update the order. 947 return updateOrderStatusAndFilledAmt(a.db, srcTableName, oid, status, uint64(filled)) 948 949 case order.CancelOrderType: 950 srcTableName := fullCancelOrderTableName(a.dbName, marketSchema, initStatus.active()) 951 if tableChange { 952 dstTableName := fullCancelOrderTableName(a.dbName, marketSchema, status.active()) 953 return a.moveCancelOrder(oid, srcTableName, dstTableName, status) 954 } 955 956 // No table move, just update the order. 957 return updateCancelOrderStatus(a.db, srcTableName, oid, status) 958 default: 959 return fmt.Errorf("unsupported order type: %v", orderType) 960 } 961 } 962 963 // UpdateOrderStatus updates the status and filled amount of the given order. 964 // Both the market and new filled amount are determined from the Order. 965 // OrderStatusByID is used to locate the existing order. See also 966 // UpdateOrderStatusByID. 967 func (a *Archiver) UpdateOrderStatus(ord order.Order, status order.OrderStatus) error { 968 return a.updateOrderStatus(ord, marketToPgStatus(status)) 969 } 970 971 func (a *Archiver) updateOrderStatus(ord order.Order, status pgOrderStatus) error { 972 var filled int64 973 if ord.Type() != order.CancelOrderType { 974 filled = int64(ord.Trade().Filled()) 975 } 976 return a.updateOrderStatusByID(ord.ID(), ord.Base(), ord.Quote(), status, filled) 977 } 978 979 func (a *Archiver) moveOrder(oid order.OrderID, srcTableName, dstTableName string, status pgOrderStatus, filled int64) error { 980 // Move the order, updating status and filled amount. 981 moved, err := moveOrder(a.db, srcTableName, dstTableName, oid, 982 status, uint64(filled)) 983 if err != nil { 984 a.fatalBackendErr(err) 985 return err 986 } 987 if !moved { 988 return fmt.Errorf("order %s not moved from %s to %s", oid, srcTableName, dstTableName) 989 } 990 return nil 991 } 992 993 func (a *Archiver) moveCancelOrder(oid order.OrderID, srcTableName, dstTableName string, status pgOrderStatus) error { 994 // Move the order, updating status and filled amount. 995 moved, err := moveCancelOrder(a.db, srcTableName, dstTableName, oid, 996 status) 997 if err != nil { 998 a.fatalBackendErr(err) 999 return err 1000 } 1001 if !moved { 1002 return fmt.Errorf("cancel order %s not moved from %s to %s", oid, srcTableName, dstTableName) 1003 } 1004 return nil 1005 } 1006 1007 // UpdateOrderFilledByID updates the filled amount of the order with the given 1008 // OrderID in the market specified by a base and quote asset. This function 1009 // applies only to market and limit orders, not cancel orders. OrderStatusByID 1010 // is used to locate the existing order. If the order is not found, the error 1011 // value is ErrUnknownOrder, and the type is order.OrderStatusUnknown. See also 1012 // UpdateOrderFilled. To also update the order status, use UpdateOrderStatusByID 1013 // or UpdateOrderStatus. 1014 func (a *Archiver) UpdateOrderFilledByID(oid order.OrderID, base, quote uint32, filled int64) error { 1015 // Locate the order. 1016 status, orderType, initFilled, err := a.orderStatusByID(oid, base, quote) 1017 if err != nil { 1018 return err 1019 } 1020 1021 switch orderType { 1022 case order.MarketOrderType, order.LimitOrderType: 1023 default: 1024 return fmt.Errorf("cannot set filled amount for order type %v", orderType) 1025 } 1026 1027 if filled == initFilled { 1028 return nil // nothing to do 1029 } 1030 1031 marketSchema, err := a.marketSchema(base, quote) 1032 if err != nil { 1033 return err // should be caught already by a.OrderStatusByID 1034 } 1035 tableName := fullOrderTableName(a.dbName, marketSchema, status.active()) 1036 err = updateOrderFilledAmt(a.db, tableName, oid, uint64(filled)) 1037 if err != nil { 1038 a.fatalBackendErr(err) // TODO: it could have changed tables since this function is not atomic 1039 } 1040 return err 1041 } 1042 1043 // UpdateOrderFilled updates the filled amount of the given order. Both the 1044 // market and new filled amount are determined from the Order. OrderStatusByID 1045 // is used to locate the existing order. This function applies only to limit 1046 // orders, not market or cancel orders. Market orders may only be updated by 1047 // ExecuteOrder since their filled amount only changes when their status 1048 // changes. See also UpdateOrderFilledByID. 1049 func (a *Archiver) UpdateOrderFilled(ord *order.LimitOrder) error { 1050 switch orderType := ord.Type(); orderType { 1051 case order.MarketOrderType, order.LimitOrderType: 1052 default: 1053 return fmt.Errorf("cannot set filled amount for order type %v", orderType) 1054 } 1055 return a.UpdateOrderFilledByID(ord.ID(), ord.Base(), ord.Quote(), int64(ord.Trade().Filled())) 1056 } 1057 1058 // UserOrders retrieves all orders for the given account in the market specified 1059 // by a base and quote asset. 1060 func (a *Archiver) UserOrders(ctx context.Context, aid account.AccountID, base, quote uint32) ([]order.Order, []order.OrderStatus, error) { 1061 marketSchema, err := a.marketSchema(base, quote) 1062 if err != nil { 1063 return nil, nil, err 1064 } 1065 1066 orders, pgStatuses, err := a.userOrders(ctx, base, quote, aid) 1067 if err != nil { 1068 a.fatalBackendErr(err) 1069 log.Errorf("Failed to query for orders by user for market %v and account %v", 1070 marketSchema, aid) 1071 return nil, nil, err 1072 } 1073 statuses := make([]order.OrderStatus, len(pgStatuses)) 1074 for i := range pgStatuses { 1075 statuses[i] = pgToMarketStatus(pgStatuses[i]) 1076 } 1077 return orders, statuses, err 1078 } 1079 1080 // UserOrderStatuses retrieves the statuses and filled amounts of the orders 1081 // with the provided order IDs for the given account in the market specified 1082 // by a base and quote asset. 1083 // The number and ordering of the returned statuses is not necessarily the same 1084 // as the number and ordering of the provided order IDs. It is not an error if 1085 // any or all of the provided order IDs cannot be found for the given account 1086 // in the specified market. 1087 func (a *Archiver) UserOrderStatuses(aid account.AccountID, base, quote uint32, oids []order.OrderID) ([]*db.OrderStatus, error) { 1088 marketSchema, err := a.marketSchema(base, quote) 1089 if err != nil { 1090 return nil, err 1091 } 1092 1093 // Active orders. 1094 fullTable := fullOrderTableName(a.dbName, marketSchema, true) 1095 activeOrderStatuses, err := a.userOrderStatusesFromTable(fullTable, aid, oids) 1096 if err != nil && !errors.Is(err, sql.ErrNoRows) { 1097 a.fatalBackendErr(err) 1098 log.Errorf("Failed to query for active order statuses by user for market %v and account %v", 1099 marketSchema, aid) 1100 return nil, err 1101 } 1102 1103 if len(oids) == len(activeOrderStatuses) { 1104 return activeOrderStatuses, nil 1105 } 1106 1107 foundOrders := make(map[order.OrderID]bool, len(activeOrderStatuses)) 1108 for _, status := range activeOrderStatuses { 1109 foundOrders[status.ID] = true 1110 } 1111 var remainingOids []order.OrderID 1112 for _, oid := range oids { 1113 if !foundOrders[oid] { 1114 remainingOids = append(remainingOids, oid) 1115 } 1116 } 1117 1118 // Archived Orders. 1119 fullTable = fullOrderTableName(a.dbName, marketSchema, false) 1120 archivedOrderStatuses, err := a.userOrderStatusesFromTable(fullTable, aid, remainingOids) 1121 if err != nil && !errors.Is(err, sql.ErrNoRows) { 1122 a.fatalBackendErr(err) 1123 log.Errorf("Failed to query for archived order statuses by user for market %v and account %v", 1124 marketSchema, aid) 1125 return nil, err 1126 } 1127 1128 return append(activeOrderStatuses, archivedOrderStatuses...), nil 1129 } 1130 1131 // ActiveUserOrderStatuses retrieves the statuses and filled amounts of all 1132 // active orders for a user across all markets. 1133 func (a *Archiver) ActiveUserOrderStatuses(aid account.AccountID) ([]*db.OrderStatus, error) { 1134 var orders []*db.OrderStatus 1135 for schema := range a.markets { 1136 tableName := fullOrderTableName(a.dbName, schema, true) // active table 1137 mktOrders, err := a.userOrderStatusesFromTable(tableName, aid, nil) 1138 if err != nil { 1139 return nil, err 1140 } 1141 orders = append(orders, mktOrders...) 1142 } 1143 return orders, nil 1144 } 1145 1146 // Pass nil or empty oids to return statuses for all user orders in the 1147 // specified table. 1148 func (a *Archiver) userOrderStatusesFromTable(fullTable string, aid account.AccountID, oids []order.OrderID) ([]*db.OrderStatus, error) { 1149 execQuery := func(ctx context.Context) (*sql.Rows, error) { 1150 if len(oids) == 0 { 1151 stmt := fmt.Sprintf(internal.SelectUserOrderStatuses, fullTable) 1152 return a.db.QueryContext(ctx, stmt, aid) 1153 } 1154 oidArr := make(pq.ByteaArray, 0, len(oids)) 1155 for i := range oids { 1156 oidArr = append(oidArr, oids[i][:]) 1157 } 1158 stmt := fmt.Sprintf(internal.SelectUserOrderStatusesByID, fullTable) 1159 return a.db.QueryContext(ctx, stmt, aid, oidArr) 1160 } 1161 1162 ctx, cancel := context.WithTimeout(a.ctx, a.queryTimeout) 1163 rows, err := execQuery(ctx) 1164 defer cancel() 1165 if err != nil { 1166 return nil, err 1167 } 1168 defer rows.Close() 1169 1170 statuses := make([]*db.OrderStatus, 0, len(oids)) 1171 for rows.Next() { 1172 var oid order.OrderID 1173 var status pgOrderStatus 1174 err = rows.Scan(&oid, &status) 1175 if err != nil { 1176 return nil, err 1177 } 1178 statuses = append(statuses, &db.OrderStatus{ 1179 ID: oid, 1180 Status: pgToMarketStatus(status), 1181 }) 1182 } 1183 1184 if err = rows.Err(); err != nil { 1185 return nil, err 1186 } 1187 1188 return statuses, nil 1189 } 1190 1191 // OrderWithCommit searches all markets' trade and cancel orders, both active 1192 // and archived, for an order with the given Commitment. 1193 func (a *Archiver) OrderWithCommit(ctx context.Context, commit order.Commitment) (found bool, oid order.OrderID, err error) { 1194 // Check all markets. 1195 for marketSchema := range a.markets { 1196 found, oid, err = orderForCommit(ctx, a.db, a.dbName, marketSchema, commit) 1197 if err != nil { 1198 a.fatalBackendErr(err) 1199 log.Errorf("Failed to query for orders by commit for market %v and commit %v", 1200 marketSchema, commit) 1201 return 1202 } 1203 if found { 1204 return 1205 } 1206 } 1207 return // false, zero, nil 1208 } 1209 1210 // ExecutedCancelsForUser retrieves up to N executed cancel orders for a given 1211 // user. These may be user-initiated cancels, or cancels created by the server 1212 // (revokes). Executed cancel orders from all markets are returned. 1213 func (a *Archiver) ExecutedCancelsForUser(aid account.AccountID, N int) (ords []*db.CancelRecord, err error) { 1214 1215 // Check all markets. 1216 for marketSchema := range a.markets { 1217 // Query for executed cancels (user-initiated). 1218 cancelTableName := fullCancelOrderTableName(a.dbName, marketSchema, false) // executed cancel orders are inactive 1219 epochsTableName := fullEpochsTableName(a.dbName, marketSchema) 1220 stmt := fmt.Sprintf(internal.RetrieveCancelTimesForUserByStatus, cancelTableName, epochsTableName) 1221 ctx, cancel := context.WithTimeout(a.ctx, a.queryTimeout) 1222 mktOrds, err := a.executedCancelsForUser(ctx, a.db, stmt, aid, N) 1223 cancel() 1224 if err != nil { 1225 return nil, err 1226 } 1227 ords = append(ords, mktOrds...) 1228 1229 // Query for revoked orders (server-initiated cancels). 1230 stmt = fmt.Sprintf(internal.SelectRevokeCancels, cancelTableName) 1231 ctx, cancel = context.WithTimeout(a.ctx, a.queryTimeout) 1232 mktOrds, err = a.revokeGeneratedCancelsForUser(ctx, a.db, stmt, aid, N) 1233 cancel() 1234 if err != nil { 1235 return nil, err 1236 } 1237 ords = append(ords, mktOrds...) 1238 } 1239 1240 sort.Slice(ords, func(i, j int) bool { 1241 return ords[i].MatchTime > ords[j].MatchTime // descending, latest completed order first 1242 }) 1243 1244 return 1245 } 1246 1247 func (a *Archiver) executedCancelsForUser(ctx context.Context, dbe *sql.DB, stmt string, 1248 aid account.AccountID, N int) (ords []*db.CancelRecord, err error) { 1249 1250 var rows *sql.Rows 1251 rows, err = dbe.QueryContext(ctx, stmt, aid, orderStatusExecuted, N) // excludes orderStatusFailed 1252 if err != nil { 1253 return 1254 } 1255 defer rows.Close() 1256 1257 for rows.Next() { 1258 var oid, target order.OrderID 1259 var execTime int64 1260 var epochGap int32 1261 err = rows.Scan(&oid, &target, &epochGap, &execTime) 1262 if err != nil { 1263 return 1264 } 1265 1266 ords = append(ords, &db.CancelRecord{ 1267 ID: oid, 1268 TargetID: target, 1269 MatchTime: execTime, 1270 EpochGap: epochGap, 1271 }) 1272 } 1273 1274 if err = rows.Err(); err != nil { 1275 return nil, err 1276 } 1277 return 1278 } 1279 1280 // revokeGeneratedCancelsForUser excludes exempt/uncounted cancels created with 1281 // RevokeOrderUncounted or revokeOrder(..., exempt=true). 1282 func (a *Archiver) revokeGeneratedCancelsForUser(ctx context.Context, dbe *sql.DB, stmt string, 1283 aid account.AccountID, N int) (ords []*db.CancelRecord, err error) { 1284 1285 var rows *sql.Rows 1286 rows, err = dbe.QueryContext(ctx, stmt, aid, orderStatusRevoked, N) 1287 if err != nil { 1288 return 1289 } 1290 defer rows.Close() 1291 1292 for rows.Next() { 1293 var oid, target order.OrderID 1294 var revokeTime time.Time 1295 var epochIdx int64 1296 err = rows.Scan(&oid, &target, &revokeTime, &epochIdx) 1297 if err != nil { 1298 return 1299 } 1300 1301 // only include non-exempt/counted cancels 1302 if epochIdx == exemptEpochIdx { 1303 continue 1304 } 1305 1306 ords = append(ords, &db.CancelRecord{ 1307 ID: oid, 1308 TargetID: target, 1309 MatchTime: revokeTime.UnixMilli(), 1310 EpochGap: db.EpochGapNA, 1311 }) 1312 } 1313 1314 if err = rows.Err(); err != nil { 1315 return nil, err 1316 } 1317 return 1318 } 1319 1320 // BEGIN regular order functions 1321 1322 func orderStatus(dbe *sql.DB, oid order.OrderID, dbName, marketSchema string) (pgOrderStatus, order.OrderType, int64, error) { 1323 // Search active orders first. 1324 fullTable := fullOrderTableName(dbName, marketSchema, true) 1325 found, status, orderType, filled, err := findOrder(dbe, oid, fullTable) 1326 if err != nil { 1327 return orderStatusUnknown, order.UnknownOrderType, -1, err 1328 } 1329 if found { 1330 return status, orderType, filled, nil 1331 } 1332 1333 // Search archived orders. 1334 fullTable = fullOrderTableName(dbName, marketSchema, false) 1335 found, status, orderType, filled, err = findOrder(dbe, oid, fullTable) 1336 if err != nil { 1337 return orderStatusUnknown, order.UnknownOrderType, -1, err 1338 } 1339 if found { 1340 return status, orderType, filled, nil 1341 } 1342 1343 // Order not found in either orders table. 1344 return orderStatusUnknown, order.UnknownOrderType, -1, db.ArchiveError{Code: db.ErrUnknownOrder} 1345 } 1346 1347 func findOrder(dbe *sql.DB, oid order.OrderID, fullTable string) (bool, pgOrderStatus, order.OrderType, int64, error) { 1348 stmt := fmt.Sprintf(internal.OrderStatus, fullTable) 1349 var status pgOrderStatus 1350 var filled int64 1351 var orderType order.OrderType 1352 err := dbe.QueryRow(stmt, oid).Scan(&orderType, &status, &filled) 1353 switch { 1354 case errors.Is(err, sql.ErrNoRows): 1355 return false, orderStatusUnknown, order.UnknownOrderType, -1, nil 1356 case err == nil: 1357 return true, status, orderType, filled, nil 1358 default: 1359 return false, orderStatusUnknown, order.UnknownOrderType, -1, err 1360 } 1361 } 1362 1363 // loadTrade does NOT set BaseAsset and QuoteAsset! 1364 func loadTrade(dbe *sql.DB, dbName, marketSchema string, oid order.OrderID) (order.Order, pgOrderStatus, error) { 1365 // Search active orders first. 1366 fullTable := fullOrderTableName(dbName, marketSchema, true) 1367 ord, status, err := loadTradeFromTable(dbe, fullTable, oid) 1368 switch { 1369 case errors.Is(err, sql.ErrNoRows): 1370 // try archived orders next 1371 case err == nil: 1372 // found 1373 return ord, status, nil 1374 default: 1375 // query error 1376 return ord, orderStatusUnknown, err 1377 } 1378 1379 // Search archived orders. 1380 fullTable = fullOrderTableName(dbName, marketSchema, false) 1381 ord, status, err = loadTradeFromTable(dbe, fullTable, oid) 1382 switch { 1383 case errors.Is(err, sql.ErrNoRows): 1384 return nil, orderStatusUnknown, db.ArchiveError{Code: db.ErrUnknownOrder} 1385 case err == nil: 1386 // found 1387 return ord, status, nil 1388 default: 1389 // query error 1390 return nil, orderStatusUnknown, err 1391 } 1392 } 1393 1394 // loadTradeFromTable does NOT set BaseAsset and QuoteAsset! 1395 func loadTradeFromTable(dbe *sql.DB, fullTable string, oid order.OrderID) (order.Order, pgOrderStatus, error) { 1396 stmt := fmt.Sprintf(internal.SelectOrder, fullTable) 1397 1398 var prefix order.Prefix 1399 var trade order.Trade 1400 var id order.OrderID 1401 var tif order.TimeInForce 1402 var rate uint64 1403 var status pgOrderStatus 1404 err := dbe.QueryRow(stmt, oid).Scan(&id, &prefix.OrderType, &trade.Sell, 1405 &prefix.AccountID, &trade.Address, &prefix.ClientTime, &prefix.ServerTime, 1406 &prefix.Commit, (*dbCoins)(&trade.Coins), 1407 &trade.Quantity, &rate, &tif, &status, &trade.FillAmt) 1408 if err != nil { 1409 return nil, orderStatusUnknown, err 1410 } 1411 switch prefix.OrderType { 1412 case order.LimitOrderType: 1413 return &order.LimitOrder{ 1414 T: *trade.Copy(), // govet would complain because Trade has a Mutex 1415 P: prefix, 1416 Rate: rate, 1417 Force: tif, 1418 }, status, nil 1419 case order.MarketOrderType: 1420 return &order.MarketOrder{ 1421 T: *trade.Copy(), 1422 P: prefix, 1423 }, status, nil 1424 1425 } 1426 return nil, 0, fmt.Errorf("unknown order type %d retrieved", prefix.OrderType) 1427 } 1428 1429 func (a *Archiver) userOrders(ctx context.Context, base, quote uint32, aid account.AccountID) ([]order.Order, []pgOrderStatus, error) { 1430 marketSchema, err := a.marketSchema(base, quote) 1431 if err != nil { 1432 return nil, nil, err 1433 } 1434 1435 // Active orders. 1436 fullTable := fullOrderTableName(a.dbName, marketSchema, true) 1437 orders, statuses, err := userOrdersFromTable(ctx, a.db, fullTable, base, quote, aid) 1438 if err != nil && !errors.Is(err, sql.ErrNoRows) { 1439 return nil, nil, err 1440 } 1441 1442 // Archived Orders. 1443 fullTable = fullOrderTableName(a.dbName, marketSchema, false) 1444 ordersArchived, statusesArchived, err := userOrdersFromTable(ctx, a.db, fullTable, base, quote, aid) 1445 if err != nil && !errors.Is(err, sql.ErrNoRows) { 1446 return nil, nil, err 1447 } 1448 1449 orders = append(orders, ordersArchived...) 1450 statuses = append(statuses, statusesArchived...) 1451 1452 return orders, statuses, nil 1453 } 1454 1455 func cancelOrdersByStatusFromTable(ctx context.Context, dbe *sql.DB, fullTable string, base, quote uint32, status pgOrderStatus) ([]*order.CancelOrder, error) { 1456 stmt := fmt.Sprintf(internal.SelectCancelOrdersByStatus, fullTable) 1457 rows, err := dbe.QueryContext(ctx, stmt, status) 1458 if err != nil { 1459 return nil, err 1460 } 1461 defer rows.Close() 1462 1463 var cos []*order.CancelOrder 1464 1465 for rows.Next() { 1466 var co order.CancelOrder 1467 co.OrderType = order.CancelOrderType 1468 err := rows.Scan(&co.AccountID, &co.ClientTime, 1469 &co.ServerTime, &co.Commit, &co.TargetOrderID) 1470 if err != nil { 1471 return nil, err 1472 } 1473 co.BaseAsset, co.QuoteAsset = base, quote 1474 cos = append(cos, &co) 1475 } 1476 1477 if err = rows.Err(); err != nil { 1478 return nil, err 1479 } 1480 1481 return cos, nil 1482 } 1483 1484 // base and quote are used to set the prefix, not specify which table to search. 1485 // NOTE: There is considerable overlap with userOrdersFromTable, but a 1486 // generalized function is likely to hurt readability and simplicity. 1487 func ordersByStatusFromTable(ctx context.Context, dbe *sql.DB, fullTable string, base, quote uint32, status pgOrderStatus) ([]order.Order, error) { 1488 stmt := fmt.Sprintf(internal.SelectOrdersByStatus, fullTable) 1489 rows, err := dbe.QueryContext(ctx, stmt, status) 1490 if err != nil { 1491 return nil, err 1492 } 1493 defer rows.Close() 1494 1495 var orders []order.Order 1496 1497 for rows.Next() { 1498 var prefix order.Prefix 1499 var trade order.Trade 1500 var id order.OrderID 1501 var tif order.TimeInForce 1502 var rate uint64 1503 err = rows.Scan(&id, &prefix.OrderType, &trade.Sell, 1504 &prefix.AccountID, &trade.Address, &prefix.ClientTime, &prefix.ServerTime, 1505 &prefix.Commit, (*dbCoins)(&trade.Coins), 1506 &trade.Quantity, &rate, &tif, &trade.FillAmt) 1507 if err != nil { 1508 return nil, err 1509 } 1510 prefix.BaseAsset, prefix.QuoteAsset = base, quote 1511 1512 var ord order.Order 1513 switch prefix.OrderType { 1514 case order.LimitOrderType: 1515 ord = &order.LimitOrder{ 1516 P: prefix, 1517 T: *trade.Copy(), 1518 Rate: rate, 1519 Force: tif, 1520 } 1521 case order.MarketOrderType: 1522 ord = &order.MarketOrder{ 1523 P: prefix, 1524 T: *trade.Copy(), 1525 } 1526 default: 1527 log.Errorf("ordersByStatusFromTable: encountered unexpected order type %v", 1528 prefix.OrderType) 1529 continue 1530 } 1531 1532 orders = append(orders, ord) 1533 } 1534 1535 if err = rows.Err(); err != nil { 1536 return nil, err 1537 } 1538 1539 return orders, nil 1540 } 1541 1542 // base and quote are used to set the prefix, not specify which table to search. 1543 func userOrdersFromTable(ctx context.Context, dbe *sql.DB, fullTable string, base, quote uint32, aid account.AccountID) ([]order.Order, []pgOrderStatus, error) { 1544 stmt := fmt.Sprintf(internal.SelectUserOrders, fullTable) 1545 rows, err := dbe.QueryContext(ctx, stmt, aid) 1546 if err != nil { 1547 return nil, nil, err 1548 } 1549 defer rows.Close() 1550 1551 var orders []order.Order 1552 var statuses []pgOrderStatus 1553 1554 for rows.Next() { 1555 var prefix order.Prefix 1556 var trade order.Trade 1557 var id order.OrderID 1558 var tif order.TimeInForce 1559 var rate uint64 1560 var status pgOrderStatus 1561 err = rows.Scan(&id, &prefix.OrderType, &trade.Sell, 1562 &prefix.AccountID, &trade.Address, &prefix.ClientTime, &prefix.ServerTime, 1563 &prefix.Commit, (*dbCoins)(&trade.Coins), 1564 &trade.Quantity, &rate, &tif, &status, &trade.FillAmt) 1565 if err != nil { 1566 return nil, nil, err 1567 } 1568 prefix.BaseAsset, prefix.QuoteAsset = base, quote 1569 1570 var ord order.Order 1571 switch prefix.OrderType { 1572 case order.LimitOrderType: 1573 ord = &order.LimitOrder{ 1574 P: prefix, 1575 T: *trade.Copy(), 1576 Rate: rate, 1577 Force: tif, 1578 } 1579 case order.MarketOrderType: 1580 ord = &order.MarketOrder{ 1581 P: prefix, 1582 T: *trade.Copy(), 1583 } 1584 default: 1585 log.Errorf("userOrdersFromTable: encountered unexpected order type %v", 1586 prefix.OrderType) 1587 continue 1588 } 1589 1590 orders = append(orders, ord) 1591 statuses = append(statuses, status) 1592 } 1593 1594 if err = rows.Err(); err != nil { 1595 return nil, nil, err 1596 } 1597 1598 return orders, statuses, nil 1599 } 1600 1601 func orderForCommit(ctx context.Context, dbe *sql.DB, dbName, marketSchema string, commit order.Commitment) (bool, order.OrderID, error) { 1602 var zeroOrderID order.OrderID 1603 1604 execCheckOrderStmt := func(stmt string) (bool, order.OrderID, error) { 1605 var oid order.OrderID 1606 err := dbe.QueryRowContext(ctx, stmt, commit).Scan(&oid) 1607 if err == nil { 1608 return true, oid, nil 1609 } else if !errors.Is(err, sql.ErrNoRows) { 1610 return false, zeroOrderID, err 1611 } 1612 // sql.ErrNoRows 1613 return false, zeroOrderID, nil 1614 } 1615 1616 checkTradeOrders := func(active bool) (bool, order.OrderID, error) { 1617 fullTable := fullOrderTableName(dbName, marketSchema, active) 1618 stmt := fmt.Sprintf(internal.SelectOrderByCommit, fullTable) 1619 return execCheckOrderStmt(stmt) 1620 } 1621 1622 checkCancelOrders := func(active bool) (bool, order.OrderID, error) { 1623 fullTable := fullCancelOrderTableName(dbName, marketSchema, active) 1624 stmt := fmt.Sprintf(internal.SelectOrderByCommit, fullTable) 1625 return execCheckOrderStmt(stmt) 1626 } 1627 1628 // Check active then archived cancel and trade orders. 1629 for _, active := range []bool{true, false} { 1630 // Trade orders. 1631 found, oid, err := checkTradeOrders(active) 1632 if found || err != nil { 1633 return found, oid, err 1634 } 1635 1636 // Cancel orders. 1637 found, oid, err = checkCancelOrders(active) 1638 if found || err != nil { 1639 return found, oid, err 1640 } 1641 } 1642 return false, zeroOrderID, nil 1643 } 1644 1645 func storeLimitOrder(dbe sqlExecutor, tableName string, lo *order.LimitOrder, status pgOrderStatus, epochIdx, epochDur int64) (int64, error) { 1646 stmt := fmt.Sprintf(internal.InsertOrder, tableName) 1647 return sqlExec(dbe, stmt, lo.ID(), lo.Type(), lo.Sell, lo.AccountID, 1648 lo.Address, lo.ClientTime, lo.ServerTime, lo.Commit, dbCoins(lo.Coins), 1649 lo.Quantity, lo.Rate, lo.Force, status, lo.Filled(), epochIdx, epochDur) 1650 } 1651 1652 func storeMarketOrder(dbe sqlExecutor, tableName string, mo *order.MarketOrder, status pgOrderStatus, epochIdx, epochDur int64) (int64, error) { 1653 stmt := fmt.Sprintf(internal.InsertOrder, tableName) 1654 return sqlExec(dbe, stmt, mo.ID(), mo.Type(), mo.Sell, mo.AccountID, 1655 mo.Address, mo.ClientTime, mo.ServerTime, mo.Commit, dbCoins(mo.Coins), 1656 mo.Quantity, 0, order.ImmediateTiF, status, mo.Filled(), epochIdx, epochDur) 1657 } 1658 1659 func updateOrderStatus(dbe sqlExecutor, tableName string, oid order.OrderID, status pgOrderStatus) error { 1660 stmt := fmt.Sprintf(internal.UpdateOrderStatus, tableName) 1661 _, err := dbe.Exec(stmt, status, oid) 1662 return err 1663 } 1664 1665 func updateOrderFilledAmt(dbe sqlExecutor, tableName string, oid order.OrderID, filled uint64) error { 1666 stmt := fmt.Sprintf(internal.UpdateOrderFilledAmt, tableName) 1667 _, err := dbe.Exec(stmt, filled, oid) 1668 return err 1669 } 1670 1671 func updateOrderStatusAndFilledAmt(dbe sqlExecutor, tableName string, oid order.OrderID, status pgOrderStatus, filled uint64) error { 1672 stmt := fmt.Sprintf(internal.UpdateOrderStatusAndFilledAmt, tableName) 1673 _, err := dbe.Exec(stmt, status, filled, oid) 1674 return err 1675 } 1676 1677 func moveOrder(dbe sqlExecutor, oldTableName, newTableName string, oid order.OrderID, newStatus pgOrderStatus, newFilled uint64) (bool, error) { 1678 stmt := fmt.Sprintf(internal.MoveOrder, oldTableName, newStatus, newFilled, newTableName) 1679 moved, err := sqlExec(dbe, stmt, oid) 1680 if err != nil { 1681 return false, err 1682 } 1683 if moved != 1 { 1684 panic(fmt.Sprintf("moved %d orders instead of 1", moved)) 1685 } 1686 return true, nil 1687 } 1688 1689 // END regular order functions 1690 1691 // BEGIN cancel order functions 1692 1693 func storeCancelOrder(dbe sqlExecutor, tableName string, co *order.CancelOrder, status pgOrderStatus, epochIdx, epochDur int64, epochGap int32) (int64, error) { 1694 stmt := fmt.Sprintf(internal.InsertCancelOrder, tableName) 1695 return sqlExec(dbe, stmt, co.ID(), co.AccountID, co.ClientTime, 1696 co.ServerTime, co.Commit, co.TargetOrderID, status, epochIdx, epochDur, epochGap) 1697 } 1698 1699 // loadCancelOrderFromTable does NOT set BaseAsset and QuoteAsset! 1700 func loadCancelOrderFromTable(dbe *sql.DB, fullTable string, oid order.OrderID) (*order.CancelOrder, pgOrderStatus, error) { 1701 stmt := fmt.Sprintf(internal.SelectCancelOrder, fullTable) 1702 1703 var co order.CancelOrder 1704 var id order.OrderID 1705 var status pgOrderStatus 1706 err := dbe.QueryRow(stmt, oid).Scan(&id, &co.AccountID, &co.ClientTime, 1707 &co.ServerTime, &co.Commit, &co.TargetOrderID, &status) 1708 if err != nil { 1709 return nil, orderStatusUnknown, err 1710 } 1711 1712 co.OrderType = order.CancelOrderType 1713 1714 return &co, status, nil 1715 } 1716 1717 // loadCancelOrder does NOT set BaseAsset and QuoteAsset! 1718 func loadCancelOrder(dbe *sql.DB, dbName, marketSchema string, oid order.OrderID) (*order.CancelOrder, pgOrderStatus, error) { 1719 // Search active orders first. 1720 fullTable := fullCancelOrderTableName(dbName, marketSchema, true) 1721 co, status, err := loadCancelOrderFromTable(dbe, fullTable, oid) 1722 switch { 1723 case errors.Is(err, sql.ErrNoRows): 1724 // try archived orders next 1725 case err == nil: 1726 // found 1727 return co, status, nil 1728 default: 1729 // query error 1730 return co, orderStatusUnknown, err 1731 } 1732 1733 // Search archived orders. 1734 fullTable = fullCancelOrderTableName(dbName, marketSchema, false) 1735 co, status, err = loadCancelOrderFromTable(dbe, fullTable, oid) 1736 switch { 1737 case errors.Is(err, sql.ErrNoRows): 1738 return nil, orderStatusUnknown, db.ArchiveError{Code: db.ErrUnknownOrder} 1739 case err == nil: 1740 // found 1741 return co, status, nil 1742 default: 1743 // query error 1744 return nil, orderStatusUnknown, err 1745 } 1746 } 1747 1748 func cancelOrderStatus(dbe *sql.DB, oid order.OrderID, dbName, marketSchema string) (pgOrderStatus, error) { 1749 // Search active orders first. 1750 found, status, err := findCancelOrder(dbe, oid, dbName, marketSchema, true) 1751 if err != nil { 1752 return orderStatusUnknown, err 1753 } 1754 if found { 1755 return status, nil 1756 } 1757 1758 // Search archived orders. 1759 found, status, err = findCancelOrder(dbe, oid, dbName, marketSchema, false) 1760 if err != nil { 1761 return orderStatusUnknown, err 1762 } 1763 if found { 1764 return status, nil 1765 } 1766 1767 // Order not found in either orders table. 1768 return orderStatusUnknown, db.ArchiveError{Code: db.ErrUnknownOrder} 1769 } 1770 1771 func findCancelOrder(dbe *sql.DB, oid order.OrderID, dbName, marketSchema string, active bool) (bool, pgOrderStatus, error) { 1772 fullTable := fullCancelOrderTableName(dbName, marketSchema, active) 1773 stmt := fmt.Sprintf(internal.CancelOrderStatus, fullTable) 1774 var status pgOrderStatus 1775 err := dbe.QueryRow(stmt, oid).Scan(&status) 1776 switch { 1777 case errors.Is(err, sql.ErrNoRows): 1778 return false, orderStatusUnknown, nil 1779 case err == nil: 1780 return true, status, nil 1781 default: 1782 return false, orderStatusUnknown, err 1783 } 1784 } 1785 1786 func updateCancelOrderStatus(dbe sqlExecutor, tableName string, oid order.OrderID, status pgOrderStatus) error { 1787 return updateOrderStatus(dbe, tableName, oid, status) 1788 } 1789 1790 func moveCancelOrder(dbe sqlExecutor, oldTableName, newTableName string, oid order.OrderID, newStatus pgOrderStatus) (bool, error) { 1791 stmt := fmt.Sprintf(internal.MoveCancelOrder, oldTableName, newStatus, newTableName) 1792 moved, err := sqlExec(dbe, stmt, oid) 1793 if err != nil { 1794 return false, err 1795 } 1796 if moved != 1 { 1797 panic(fmt.Sprintf("moved %d cancel orders instead of 1", moved)) 1798 } 1799 return true, nil 1800 } 1801 1802 // END cancel order functions