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