decred.org/dcrdex@v1.0.5/server/market/market.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 market
     5  
     6  import (
     7  	"context"
     8  	"encoding/json"
     9  	"errors"
    10  	"fmt"
    11  	"math"
    12  	"strings"
    13  	"sync"
    14  	"sync/atomic"
    15  	"time"
    16  
    17  	"decred.org/dcrdex/dex"
    18  	"decred.org/dcrdex/dex/calc"
    19  	"decred.org/dcrdex/dex/msgjson"
    20  	"decred.org/dcrdex/dex/order"
    21  	"decred.org/dcrdex/dex/ws"
    22  	"decred.org/dcrdex/server/account"
    23  	"decred.org/dcrdex/server/asset"
    24  	"decred.org/dcrdex/server/auth"
    25  	"decred.org/dcrdex/server/book"
    26  	"decred.org/dcrdex/server/coinlock"
    27  	"decred.org/dcrdex/server/comms"
    28  	"decred.org/dcrdex/server/db"
    29  	"decred.org/dcrdex/server/matcher"
    30  )
    31  
    32  // Error is just a basic error.
    33  type Error string
    34  
    35  // Error satisfies the error interface.
    36  func (e Error) Error() string {
    37  	return string(e)
    38  }
    39  
    40  const (
    41  	ErrMarketNotRunning       = Error("market not running")
    42  	ErrInvalidOrder           = Error("order failed validation")
    43  	ErrInvalidRate            = Error("limit order rate too low")
    44  	ErrInvalidCommitment      = Error("order commitment invalid")
    45  	ErrEpochMissed            = Error("order unexpectedly missed its intended epoch")
    46  	ErrDuplicateOrder         = Error("order already in epoch") // maybe remove since this is ill defined
    47  	ErrQuantityTooHigh        = Error("order quantity exceeds user limit")
    48  	ErrDuplicateCancelOrder   = Error("equivalent cancel order already in epoch")
    49  	ErrTooManyCancelOrders    = Error("too many cancel orders in current epoch")
    50  	ErrCancelNotPermitted     = Error("cancel order account does not match targeted order account")
    51  	ErrTargetNotActive        = Error("target order not active on this market")
    52  	ErrTargetNotCancelable    = Error("targeted order is not a limit order with standing time-in-force")
    53  	ErrSuspendedAccount       = Error("suspended account")
    54  	ErrMalformedOrderResponse = Error("malformed order response")
    55  	ErrInternalServer         = Error("internal server error")
    56  )
    57  
    58  // Swapper coordinates atomic swaps for one or more matchsets.
    59  type Swapper interface {
    60  	Negotiate(matchSets []*order.MatchSet)
    61  	CheckUnspent(ctx context.Context, asset uint32, coinID []byte) error
    62  	ChainsSynced(base, quote uint32) (bool, error)
    63  }
    64  
    65  type DataCollector interface {
    66  	ReportEpoch(base, quote uint32, epochIdx uint64, stats *matcher.MatchCycleStats) (*msgjson.Spot, error)
    67  }
    68  
    69  // FeeFetcher is a fee fetcher for fetching fees. Fees are fickle, so fetch fees
    70  // with FeeFetcher fairly frequently.
    71  type FeeFetcher interface {
    72  	FeeRate(context.Context) uint64
    73  	SwapFeeRate(context.Context) uint64
    74  	LastRate() uint64
    75  	MaxFeeRate() uint64
    76  }
    77  
    78  // Balancer provides a method to check that an account on an account-based
    79  // asset has sufficient balance.
    80  type Balancer interface {
    81  	// CheckBalance checks that the address's account has sufficient balance to
    82  	// trade the outgoing number of lots (totaling qty) and incoming number of
    83  	// redeems.
    84  	CheckBalance(acctAddr string, assetID, redeemAssetID uint32, qty, lots uint64, redeems int) bool
    85  }
    86  
    87  // Config is the Market configuration.
    88  type Config struct {
    89  	MarketInfo       *dex.MarketInfo
    90  	Storage          Storage
    91  	Swapper          Swapper
    92  	AuthManager      AuthManager
    93  	FeeFetcherBase   FeeFetcher
    94  	CoinLockerBase   coinlock.CoinLocker
    95  	FeeFetcherQuote  FeeFetcher
    96  	CoinLockerQuote  coinlock.CoinLocker
    97  	DataCollector    DataCollector
    98  	Balancer         Balancer
    99  	CheckParcelLimit func(user account.AccountID, calcParcels MarketParcelCalculator) bool
   100  	MinimumRate      uint64
   101  }
   102  
   103  // Market is the market manager. It should not be overly involved with details
   104  // of accounts and authentication. Via the account package it should request
   105  // account status with new orders, verification of order signatures. The Market
   106  // should also perform various account package callbacks such as order status
   107  // updates so that the account package code can keep various data up-to-date,
   108  // including order status, history, cancellation statistics, etc.
   109  //
   110  // The Market performs the following:
   111  //  1. Receive and validate new order data (amounts vs. lot size, check fees,
   112  //     utxos, sufficient market buy buffer, etc.).
   113  //  2. Put incoming orders into the current epoch queue.
   114  //  3. Maintain an order book, which must also implement matcher.Booker.
   115  //  4. Initiate order matching with matcher.Match(book, currentQueue)
   116  //  5. During and/or after matching:
   117  //     * update the book (remove orders, add new standing orders, etc.)
   118  //     * retire/archive the epoch queue
   119  //     * publish the matches (and order book changes?)
   120  //     * initiate swaps for each match (possibly groups of related matches)
   121  //  6. Cycle the epochs.
   122  //  7. Record all events with the archivist.
   123  type Market struct {
   124  	marketInfo *dex.MarketInfo
   125  
   126  	tasks sync.WaitGroup // for lazy asynchronous tasks e.g. revoke ntfns
   127  
   128  	// Communications.
   129  	orderRouter chan *orderUpdateSignal // incoming orders, via SubmitOrderAsync
   130  
   131  	orderFeedMtx sync.RWMutex         // guards orderFeeds and running
   132  	orderFeeds   []chan *updateSignal // all outgoing notification consumers
   133  
   134  	runMtx  sync.RWMutex
   135  	running chan struct{} // closed when running (accepting new orders)
   136  	up      uint32        // Run is called, either waiting for first epoch or running
   137  
   138  	bookMtx      sync.Mutex // guards book and bookEpochIdx
   139  	book         *book.Book
   140  	bookEpochIdx int64 // next epoch from the point of view of the book
   141  	settling     map[order.OrderID]uint64
   142  
   143  	epochMtx         sync.RWMutex
   144  	startEpochIdx    int64
   145  	activeEpochIdx   int64
   146  	suspendEpochIdx  int64
   147  	persistBook      bool
   148  	epochCommitments map[order.Commitment]order.OrderID
   149  	epochOrders      map[order.OrderID]order.Order
   150  
   151  	matcher *matcher.Matcher
   152  	swapper Swapper
   153  	auth    AuthManager
   154  
   155  	feeScalesMtx sync.RWMutex
   156  	feeScales    struct {
   157  		base  float64
   158  		quote float64
   159  	}
   160  
   161  	coinLockerBase  coinlock.CoinLocker
   162  	coinLockerQuote coinlock.CoinLocker
   163  
   164  	baseFeeFetcher  FeeFetcher
   165  	quoteFeeFetcher FeeFetcher
   166  
   167  	// Persistent data storage
   168  	storage Storage
   169  
   170  	// Data API
   171  	dataCollector DataCollector
   172  	lastRate      uint64
   173  
   174  	checkParcelLimit func(user account.AccountID, calcParcels MarketParcelCalculator) bool
   175  
   176  	minimumRate uint64
   177  }
   178  
   179  // Storage is the DB interface required by Market.
   180  type Storage interface {
   181  	db.OrderArchiver
   182  	LastErr() error
   183  	Fatal() <-chan struct{}
   184  	Close() error
   185  	InsertEpoch(ed *db.EpochResults) error
   186  	LastEpochRate(base, quote uint32) (uint64, error)
   187  	MarketMatches(base, quote uint32) ([]*db.MatchDataWithCoins, error)
   188  	InsertMatch(match *order.Match) error
   189  }
   190  
   191  // NewMarket creates a new Market for the provided base and quote assets, with
   192  // an epoch cycling at given duration in milliseconds.
   193  func NewMarket(cfg *Config) (*Market, error) {
   194  	// Make sure the DEXArchivist is healthy before taking orders.
   195  	storage, mktInfo, swapper := cfg.Storage, cfg.MarketInfo, cfg.Swapper
   196  	if err := storage.LastErr(); err != nil {
   197  		return nil, err
   198  	}
   199  
   200  	// Load existing book orders from the DB.
   201  	base, quote := mktInfo.Base, mktInfo.Quote
   202  
   203  	bookOrders, err := storage.BookOrders(base, quote)
   204  	if err != nil {
   205  		return nil, err
   206  	}
   207  	log.Infof("Loaded %d stored book orders.", len(bookOrders))
   208  
   209  	baseIsAcctBased := cfg.CoinLockerBase == nil
   210  	quoteIsAcctBased := cfg.CoinLockerQuote == nil
   211  
   212  	// Put the book orders in a map so orders that no longer have funding coins
   213  	// can be removed easily.
   214  	bookOrdersByID := make(map[order.OrderID]*order.LimitOrder, len(bookOrders))
   215  	for _, lo := range bookOrders {
   216  		// Limit order amount requirements are simple unlike market buys.
   217  		if lo.Quantity%mktInfo.LotSize != 0 || lo.FillAmt%mktInfo.LotSize != 0 {
   218  			// To change market configuration, the operator should suspended the
   219  			// market with persist=false, but that may not have happened, or
   220  			// maybe a revoke failed.
   221  			log.Errorf("Not rebooking order %v with amount (%v/%v) incompatible with current lot size (%v)",
   222  				lo.ID(), lo.FillAmt, lo.Quantity, mktInfo.LotSize)
   223  			// Revoke the order, but do not count this against the user.
   224  			if _, _, err = storage.RevokeOrderUncounted(lo); err != nil {
   225  				log.Errorf("Failed to revoke order %v: %v", lo, err)
   226  				// But still not added back on the book.
   227  			}
   228  			continue
   229  		}
   230  		bookOrdersByID[lo.ID()] = lo
   231  	}
   232  
   233  	// "execute" any epoch orders in DB that may be left over from unclean
   234  	// shutdown. Whatever epoch they were in will not be seen again.
   235  	epochOrders, err := storage.EpochOrders(base, quote)
   236  	if err != nil {
   237  		return nil, err
   238  	}
   239  	for _, ord := range epochOrders {
   240  		oid := ord.ID()
   241  		log.Infof("Dropping old epoch order %v", oid)
   242  		if co, ok := ord.(*order.CancelOrder); ok {
   243  			if err := storage.FailCancelOrder(co); err != nil {
   244  				log.Errorf("Failed to set orphaned epoch cancel order %v as executed: %v", oid, err)
   245  			}
   246  			continue
   247  		}
   248  		if err := storage.ExecuteOrder(ord); err != nil {
   249  			log.Errorf("Failed to set orphaned epoch trade order %v as executed: %v", oid, err)
   250  		}
   251  	}
   252  
   253  	// Set up tracking. Which of these are actually used depend on whether the
   254  	// assets are account- or utxo-based.
   255  	// utxo-based
   256  	var baseCoins, quoteCoins map[order.OrderID][]order.CoinID
   257  	var missingCoinFails map[order.OrderID]struct{}
   258  	// account-based
   259  	var quoteAcctStats, baseAcctStats accountCounter
   260  	var failedBaseAccts, failedQuoteAccts map[string]bool
   261  	var failedAcctOrders map[order.OrderID]struct{}
   262  	var acctTracking book.AccountTracking
   263  
   264  	if baseIsAcctBased {
   265  		acctTracking |= book.AccountTrackingBase
   266  		baseAcctStats = make(accountCounter)
   267  		failedBaseAccts = make(map[string]bool)
   268  		failedAcctOrders = make(map[order.OrderID]struct{})
   269  	} else {
   270  		baseCoins = make(map[order.OrderID][]order.CoinID)
   271  		missingCoinFails = make(map[order.OrderID]struct{})
   272  	}
   273  
   274  	if quoteIsAcctBased {
   275  		acctTracking |= book.AccountTrackingQuote
   276  		quoteAcctStats = make(accountCounter)
   277  		failedQuoteAccts = make(map[string]bool)
   278  		if failedAcctOrders == nil {
   279  			failedAcctOrders = make(map[order.OrderID]struct{})
   280  		}
   281  	} else {
   282  		quoteCoins = make(map[order.OrderID][]order.CoinID)
   283  		if missingCoinFails == nil {
   284  			missingCoinFails = make(map[order.OrderID]struct{})
   285  		}
   286  	}
   287  
   288  ordersLoop:
   289  	for id, lo := range bookOrdersByID {
   290  		if lo.FillAmt > 0 {
   291  			// Order already matched with another trade, so it is expected that
   292  			// the funding coins are spent in a swap.
   293  			//
   294  			// In general, our position is that the server is not ultimately
   295  			// responsible for verifying that all orders have locked coins since
   296  			// the client will be penalized if they cannot complete the swap.
   297  			// The least the server can do is ensure funding coins for NEW
   298  			// orders are unspent and owned by the user.
   299  
   300  			// On to the next order. Do not lock coins that are spent or should
   301  			// be spent in a swap contract.
   302  			continue
   303  		}
   304  
   305  		// Verify all funding coins for this order.
   306  		assetID := quote
   307  		if lo.Sell {
   308  			assetID = base
   309  		}
   310  		for i := range lo.Coins {
   311  			err = swapper.CheckUnspent(context.Background(), assetID, lo.Coins[i]) // no timeout
   312  			if err == nil {
   313  				continue
   314  			}
   315  
   316  			if errors.Is(err, asset.CoinNotFoundError) {
   317  				// spent, exclude this order
   318  				log.Warnf("Coin %s not unspent for unfilled order %v. "+
   319  					"Revoking the order.", fmtCoinID(assetID, lo.Coins[i]), lo)
   320  			} else {
   321  				// other failure (coinID decode, RPC, etc.)
   322  				return nil, fmt.Errorf("unexpected error checking coinID %v for order %v: %w",
   323  					lo.Coins[i], lo, err)
   324  				// NOTE: This does not revoke orders from storage since this is
   325  				// likely to be a configuration or node issue.
   326  			}
   327  
   328  			delete(bookOrdersByID, id)
   329  			// Revoke the order, but do not count this against the user.
   330  			if _, _, err = storage.RevokeOrderUncounted(lo); err != nil {
   331  				log.Errorf("Failed to revoke order %v: %v", lo, err)
   332  			}
   333  			// No penalization here presently since the market was down, but if
   334  			// a suspend message with persist=true was sent, the users should
   335  			// have kept their coins locked. (TODO)
   336  			continue ordersLoop
   337  		}
   338  
   339  		if baseIsAcctBased {
   340  			var addr string
   341  			var qty, lots uint64
   342  			var redeems int
   343  			if lo.Sell {
   344  				// address is zeroth coin
   345  				if len(lo.Coins) != 1 {
   346  					log.Errorf("rejecting account-based-base-asset order %s that has no coins ¯\\_(ツ)_/¯", lo.ID())
   347  					continue ordersLoop
   348  				}
   349  				addr = string(lo.Coins[0])
   350  				qty = lo.Quantity
   351  				lots = qty / mktInfo.LotSize
   352  			} else {
   353  				addr = lo.Address
   354  				redeems = int((lo.Quantity - lo.FillAmt) / mktInfo.LotSize)
   355  			}
   356  			baseAcctStats.add(addr, qty, lots, redeems)
   357  		} else if lo.Sell {
   358  			baseCoins[id] = lo.Coins
   359  		}
   360  
   361  		if quoteIsAcctBased {
   362  			var addr string
   363  			var qty, lots uint64
   364  			var redeems int
   365  			if lo.Sell { // sell base => redeem acct-based quote
   366  				addr = lo.Address
   367  				redeems = int((lo.Quantity - lo.FillAmt) / mktInfo.LotSize)
   368  			} else { // buy base => offer acct-based quote
   369  				// address is zeroth coin
   370  				if len(lo.Coins) != 1 {
   371  					log.Errorf("rejecting account-based-base-asset order %s that has no coins ¯\\_(ツ)_/¯", lo.ID())
   372  					continue ordersLoop
   373  				}
   374  				addr = string(lo.Coins[0])
   375  				lots = lo.Quantity / mktInfo.LotSize
   376  				qty = calc.BaseToQuote(lo.Rate, lo.Quantity)
   377  			}
   378  			quoteAcctStats.add(addr, qty, lots, redeems)
   379  		} else if !lo.Sell {
   380  			quoteCoins[id] = lo.Coins
   381  		}
   382  	}
   383  
   384  	if baseIsAcctBased {
   385  		log.Debugf("Checking %d base asset (%d) balances.", len(baseAcctStats), base)
   386  		for acctAddr, stats := range baseAcctStats {
   387  			if !cfg.Balancer.CheckBalance(acctAddr, mktInfo.Base, mktInfo.Quote, stats.qty, stats.lots, stats.redeems) {
   388  				log.Info("%s base asset account failed the startup balance check on the %s market", acctAddr, mktInfo.Name)
   389  				failedBaseAccts[acctAddr] = true
   390  			}
   391  		}
   392  	} else {
   393  		log.Debugf("Locking %d base asset (%d) coins.", len(baseCoins), base)
   394  		if log.Level() <= dex.LevelTrace {
   395  			for oid, coins := range baseCoins {
   396  				log.Tracef(" - order %v: %v", oid, coins)
   397  			}
   398  		}
   399  		for oid := range cfg.CoinLockerBase.LockCoins(baseCoins) {
   400  			missingCoinFails[oid] = struct{}{}
   401  		}
   402  	}
   403  
   404  	if quoteIsAcctBased {
   405  		log.Debugf("Checking %d quote asset (%d) balances.", len(quoteAcctStats), quote)
   406  		for acctAddr, stats := range quoteAcctStats { // quoteAcctStats is nil for utxo-based quote assets
   407  			if !cfg.Balancer.CheckBalance(acctAddr, mktInfo.Quote, mktInfo.Base, stats.qty, stats.lots, stats.redeems) {
   408  				log.Errorf("%s quote asset account failed the startup balance check on the %s market", acctAddr, mktInfo.Name)
   409  				failedQuoteAccts[acctAddr] = true
   410  			}
   411  		}
   412  	} else {
   413  		log.Debugf("Locking %d quote asset (%d) coins.", len(quoteCoins), quote)
   414  		if log.Level() <= dex.LevelTrace {
   415  			for oid, coins := range quoteCoins {
   416  				log.Tracef(" - order %v: %v", oid, coins)
   417  			}
   418  		}
   419  		for oid := range cfg.CoinLockerQuote.LockCoins(quoteCoins) {
   420  			missingCoinFails[oid] = struct{}{}
   421  		}
   422  	}
   423  
   424  	for oid := range missingCoinFails {
   425  		log.Warnf("Revoking book order %v with already locked coins.", oid)
   426  		bad := bookOrdersByID[oid]
   427  		delete(bookOrdersByID, oid)
   428  		// Revoke the order, but do not count this against the user.
   429  		if _, _, err = storage.RevokeOrderUncounted(bad); err != nil {
   430  			log.Errorf("Failed to revoke order %v: %v", bad, err)
   431  			// But still not added back on the book.
   432  		}
   433  	}
   434  
   435  	Book := book.New(mktInfo.LotSize, acctTracking)
   436  	for _, lo := range bookOrdersByID {
   437  		// Catch account-based asset low-balance rejections here.
   438  		if baseIsAcctBased && failedBaseAccts[lo.BaseAccount()] {
   439  			failedAcctOrders[lo.ID()] = struct{}{}
   440  			log.Warnf("Skipping insert of order %s into %s book because base asset "+
   441  				"account failed the balance check", lo.ID(), mktInfo.Name)
   442  			continue
   443  		}
   444  		if quoteIsAcctBased && failedQuoteAccts[lo.QuoteAccount()] {
   445  			failedAcctOrders[lo.ID()] = struct{}{}
   446  			log.Warnf("Skipping insert of order %s into %s book because quote asset "+
   447  				"account failed the balance check", lo.ID(), mktInfo.Name)
   448  			continue
   449  		}
   450  		if ok := Book.Insert(lo); !ok {
   451  			// This can only happen if one of the loaded orders has an
   452  			// incompatible lot size for the current market config, which was
   453  			// already checked above.
   454  			log.Errorf("Failed to insert order %v into %v book.", mktInfo.Name, lo)
   455  		}
   456  	}
   457  
   458  	// Revoke the low-balance rejections in the database.
   459  	for oid := range failedAcctOrders {
   460  		// Already logged in the Book.Insert loop.
   461  		if _, _, err = storage.RevokeOrderUncounted(bookOrdersByID[oid]); err != nil {
   462  			log.Errorf("Failed to revoke order with insufficient account balance %v: %v", bookOrdersByID[oid], err)
   463  		}
   464  	}
   465  
   466  	// Populate the order settling amount map from the active matches in DB.
   467  	activeMatches, err := storage.MarketMatches(base, quote)
   468  	if err != nil {
   469  		return nil, fmt.Errorf("failed to load active matches for market %v: %w", mktInfo.Name, err)
   470  	}
   471  	settling := make(map[order.OrderID]uint64)
   472  	for _, match := range activeMatches {
   473  		settling[match.Taker] += match.Quantity
   474  		settling[match.Maker] += match.Quantity
   475  		// Note: we actually don't want to bother with matches for orders that
   476  		// were canceled or had at-fault match failures, since including them
   477  		// give that user another shot to get a successfully "completed" order
   478  		// if they complete these remaining matches, but it's OK. We'd have to
   479  		// query these order statuses, and look for at-fault match failures
   480  		// involving them, so just give the user the benefit of the doubt.
   481  	}
   482  	log.Infof("Tracking %d orders with %d active matches.", len(settling), len(activeMatches))
   483  
   484  	lastEpochEndRate, err := storage.LastEpochRate(base, quote)
   485  	if err != nil {
   486  		return nil, fmt.Errorf("failed to load last epoch end rate: %w", err)
   487  	}
   488  
   489  	return &Market{
   490  		running:          make(chan struct{}), // closed on market start
   491  		marketInfo:       mktInfo,
   492  		book:             Book,
   493  		settling:         settling,
   494  		matcher:          matcher.New(),
   495  		persistBook:      true,
   496  		epochCommitments: make(map[order.Commitment]order.OrderID),
   497  		epochOrders:      make(map[order.OrderID]order.Order),
   498  		swapper:          swapper,
   499  		auth:             cfg.AuthManager,
   500  		storage:          storage,
   501  		coinLockerBase:   cfg.CoinLockerBase,
   502  		coinLockerQuote:  cfg.CoinLockerQuote,
   503  		baseFeeFetcher:   cfg.FeeFetcherBase,
   504  		quoteFeeFetcher:  cfg.FeeFetcherQuote,
   505  		dataCollector:    cfg.DataCollector,
   506  		lastRate:         lastEpochEndRate,
   507  		checkParcelLimit: cfg.CheckParcelLimit,
   508  		minimumRate:      cfg.MinimumRate,
   509  	}, nil
   510  }
   511  
   512  // SuspendASAP suspends requests the market to gracefully suspend epoch cycling
   513  // as soon as possible, always allowing an active epoch to close. See also
   514  // Suspend.
   515  func (m *Market) SuspendASAP(persistBook bool) (finalEpochIdx int64, finalEpochEnd time.Time) {
   516  	return m.Suspend(time.Now(), persistBook)
   517  }
   518  
   519  // Suspend requests the market to gracefully suspend epoch cycling as soon as
   520  // the given time, always allowing the epoch including that time to complete. If
   521  // the time is before the current epoch, the current epoch will be the last.
   522  func (m *Market) Suspend(asSoonAs time.Time, persistBook bool) (finalEpochIdx int64, finalEpochEnd time.Time) {
   523  	// epochMtx guards activeEpochIdx, startEpochIdx, suspendEpochIdx, and
   524  	// persistBook.
   525  	m.epochMtx.Lock()
   526  	defer m.epochMtx.Unlock()
   527  
   528  	dur := int64(m.EpochDuration())
   529  
   530  	epochEnd := func(idx int64) time.Time {
   531  		start := time.UnixMilli(idx * dur)
   532  		return start.Add(time.Duration(dur) * time.Millisecond)
   533  	}
   534  
   535  	// Determine which epoch includes asSoonAs, and compute its end time. If
   536  	// asSoonAs is in a past epoch, suspend at the end of the active epoch.
   537  
   538  	soonestFinalIdx := m.activeEpochIdx
   539  	if soonestFinalIdx == 0 {
   540  		// Cannot schedule a suspend if Run isn't running.
   541  		if m.startEpochIdx == 0 {
   542  			return -1, time.Time{}
   543  		}
   544  		// Not yet started. Soonest suspend idx is the start epoch idx - 1.
   545  		soonestFinalIdx = m.startEpochIdx - 1
   546  	}
   547  
   548  	if soonestEnd := epochEnd(soonestFinalIdx); asSoonAs.Before(soonestEnd) {
   549  		// Suspend at the end of the active epoch or the one prior to start.
   550  		finalEpochIdx = soonestFinalIdx
   551  		finalEpochEnd = soonestEnd
   552  	} else {
   553  		// Suspend at the end of the epoch that includes the target time.
   554  		ms := asSoonAs.UnixMilli()
   555  		finalEpochIdx = ms / dur
   556  		// Allow stopping at boundary, prior to the epoch starting at this time.
   557  		if ms%dur == 0 {
   558  			finalEpochIdx--
   559  		}
   560  		finalEpochEnd = epochEnd(finalEpochIdx)
   561  	}
   562  
   563  	m.suspendEpochIdx = finalEpochIdx
   564  	m.persistBook = persistBook
   565  
   566  	return
   567  }
   568  
   569  // ResumeEpoch gets the next available resume epoch index for the currently
   570  // configured epoch duration for the market and the provided earliest allowable
   571  // start time. The market must be running, otherwise the zero index is returned.
   572  func (m *Market) ResumeEpoch(asSoonAs time.Time) (startEpochIdx int64) {
   573  	// Only allow scheduling a resume if the market is not running.
   574  	if m.Running() {
   575  		return
   576  	}
   577  
   578  	dur := int64(m.EpochDuration())
   579  
   580  	now := time.Now().UnixMilli()
   581  	nextEpochIdx := 1 + now/dur
   582  
   583  	ms := asSoonAs.UnixMilli()
   584  	startEpochIdx = 1 + ms/dur
   585  
   586  	if startEpochIdx < nextEpochIdx {
   587  		startEpochIdx = nextEpochIdx
   588  	}
   589  	return
   590  }
   591  
   592  // SetStartEpochIdx sets the starting epoch index. This should generally be
   593  // called before Run, or Start used to specify the index at the same time.
   594  func (m *Market) SetStartEpochIdx(startEpochIdx int64) {
   595  	m.epochMtx.Lock()
   596  	m.startEpochIdx = startEpochIdx
   597  	m.epochMtx.Unlock()
   598  }
   599  
   600  // Start begins order processing with a starting epoch index. See also
   601  // SetStartEpochIdx and Run. Stop the Market by cancelling the context.
   602  func (m *Market) Start(ctx context.Context, startEpochIdx int64) {
   603  	m.SetStartEpochIdx(startEpochIdx)
   604  	m.Run(ctx)
   605  }
   606  
   607  // waitForEpochOpen waits until the start of epoch processing.
   608  func (m *Market) waitForEpochOpen() {
   609  	m.runMtx.RLock()
   610  	c := m.running // the field may be rewritten, but only after close
   611  	m.runMtx.RUnlock()
   612  	<-c
   613  }
   614  
   615  // Status describes the operation state of the Market.
   616  type Status struct {
   617  	Running       bool
   618  	EpochDuration uint64 // to compute times from epoch inds
   619  	ActiveEpoch   int64
   620  	StartEpoch    int64
   621  	SuspendEpoch  int64
   622  	PersistBook   bool
   623  	Base, Quote   uint32
   624  }
   625  
   626  // Status returns the current operating state of the Market.
   627  func (m *Market) Status() *Status {
   628  	m.epochMtx.Lock()
   629  	defer m.epochMtx.Unlock()
   630  	return &Status{
   631  		Running:       m.Running(),
   632  		EpochDuration: m.marketInfo.EpochDuration,
   633  		ActiveEpoch:   m.activeEpochIdx,
   634  		StartEpoch:    m.startEpochIdx,
   635  		SuspendEpoch:  m.suspendEpochIdx,
   636  		PersistBook:   m.persistBook,
   637  		Base:          m.marketInfo.Base,
   638  		Quote:         m.marketInfo.Quote,
   639  	}
   640  }
   641  
   642  // Running indicates is the market is accepting new orders. This will return
   643  // false when suspended, but false does not necessarily mean Run has stopped
   644  // since a start epoch may be set. Note that this method is of limited use and
   645  // communicating subsystems shouldn't rely on the result for correct operation
   646  // since a market could start or stop. Rather, they should infer or be informed
   647  // of market status rather than rely on this.
   648  //
   649  // TODO: Instead of using Running in OrderRouter and DEX, these types should
   650  // track statuses (known suspend times).
   651  func (m *Market) Running() bool {
   652  	m.runMtx.RLock()
   653  	defer m.runMtx.RUnlock()
   654  	select {
   655  	case <-m.running:
   656  		return true
   657  	default:
   658  		return false
   659  	}
   660  }
   661  
   662  // EpochDuration returns the Market's epoch duration in milliseconds.
   663  func (m *Market) EpochDuration() uint64 {
   664  	return m.marketInfo.EpochDuration
   665  }
   666  
   667  // MarketBuyBuffer returns the Market's market-buy buffer.
   668  func (m *Market) MarketBuyBuffer() float64 {
   669  	return m.marketInfo.MarketBuyBuffer
   670  }
   671  
   672  // LotSize returns the market's lot size in units of the base asset.
   673  func (m *Market) LotSize() uint64 {
   674  	return m.marketInfo.LotSize
   675  }
   676  
   677  // RateStep returns the market's rate step in units of the quote asset.
   678  func (m *Market) RateStep() uint64 {
   679  	return m.marketInfo.RateStep
   680  }
   681  
   682  // Base is the base asset ID.
   683  func (m *Market) Base() uint32 {
   684  	return m.marketInfo.Base
   685  }
   686  
   687  // Quote is the quote asset ID.
   688  func (m *Market) Quote() uint32 {
   689  	return m.marketInfo.Quote
   690  }
   691  
   692  // OrderFeed provides a new order book update channel. Channels provided before
   693  // the market starts and while a market is running are both valid. When the
   694  // market stops, channels are closed (invalidated), and new channels should be
   695  // requested if the market starts again.
   696  func (m *Market) OrderFeed() <-chan *updateSignal {
   697  	bookUpdates := make(chan *updateSignal, 1)
   698  	m.orderFeedMtx.Lock()
   699  	m.orderFeeds = append(m.orderFeeds, bookUpdates)
   700  	m.orderFeedMtx.Unlock()
   701  	return bookUpdates
   702  }
   703  
   704  // FeedDone informs the market that the caller is finished receiving from the
   705  // given channel, which should have been obtained from OrderFeed. If the channel
   706  // was a registered order feed channel from OrderFeed, it is closed and removed
   707  // so that no further signals will be send on the channel.
   708  func (m *Market) FeedDone(feed <-chan *updateSignal) bool {
   709  	m.orderFeedMtx.Lock()
   710  	defer m.orderFeedMtx.Unlock()
   711  	for i := range m.orderFeeds {
   712  		if m.orderFeeds[i] == feed {
   713  			close(m.orderFeeds[i])
   714  			// Order is not important to delete the channel without allocation.
   715  			m.orderFeeds[i] = m.orderFeeds[len(m.orderFeeds)-1]
   716  			m.orderFeeds[len(m.orderFeeds)-1] = nil // chan is a pointer
   717  			m.orderFeeds = m.orderFeeds[:len(m.orderFeeds)-1]
   718  			return true
   719  		}
   720  	}
   721  	return false
   722  }
   723  
   724  // sendToFeeds sends an *updateSignal to all order feed channels created with
   725  // OrderFeed().
   726  func (m *Market) sendToFeeds(sig *updateSignal) {
   727  	m.orderFeedMtx.RLock()
   728  	for _, s := range m.orderFeeds {
   729  		s <- sig
   730  	}
   731  	m.orderFeedMtx.RUnlock()
   732  }
   733  
   734  type orderUpdateSignal struct {
   735  	rec     *orderRecord
   736  	errChan chan error // should be buffered
   737  }
   738  
   739  func newOrderUpdateSignal(ord *orderRecord) *orderUpdateSignal {
   740  	return &orderUpdateSignal{ord, make(chan error, 1)}
   741  }
   742  
   743  // SubmitOrder submits a new order for inclusion into the current epoch. This is
   744  // the synchronous version of SubmitOrderAsync.
   745  func (m *Market) SubmitOrder(rec *orderRecord) error {
   746  	return <-m.SubmitOrderAsync(rec)
   747  }
   748  
   749  // processCancelOrderWhileSuspended is called when cancelling an order while
   750  // the market is suspended and Run is not running. The error sent on errChan
   751  // is returned to the client.
   752  //
   753  // This function:
   754  // 1. Removes the target order from the book.
   755  // 2. Unlocks the order coins.
   756  // 3. Updates the storage with the new cancel order and cancels the existing limit order.
   757  // 4. Responds to the client that the order was received.
   758  // 5. Sends the unbooked order to the order feeds.
   759  // 6. Creates a match object, stores it, and notifies the client of the match.
   760  func (m *Market) processCancelOrderWhileSuspended(rec *orderRecord, errChan chan<- error) {
   761  	co, ok := rec.order.(*order.CancelOrder)
   762  	if !ok {
   763  		errChan <- ErrInvalidOrder
   764  		return
   765  	}
   766  
   767  	if cancelable, _, err := m.CancelableBy(co.TargetOrderID, co.AccountID); !cancelable {
   768  		errChan <- err
   769  		return
   770  	}
   771  
   772  	m.bookMtx.Lock()
   773  	delete(m.settling, co.TargetOrderID)
   774  	lo, ok := m.book.Remove(co.TargetOrderID)
   775  	m.bookMtx.Unlock()
   776  	if !ok {
   777  		errChan <- ErrTargetNotCancelable
   778  		return
   779  	}
   780  
   781  	m.unlockOrderCoins(lo)
   782  
   783  	sTime := time.Now().Truncate(time.Millisecond).UTC()
   784  	co.SetTime(sTime)
   785  
   786  	// Create the client response here, but don't send it until the order has been
   787  	// committed to the storage.
   788  	respMsg, err := m.orderResponse(rec)
   789  	if err != nil {
   790  		errChan <- fmt.Errorf("failed to create order response: %w", err)
   791  		return
   792  	}
   793  
   794  	dur := int64(m.EpochDuration())
   795  	now := time.Now().UnixMilli()
   796  	epochIdx := now / dur
   797  	if err := m.storage.NewArchivedCancel(co, epochIdx, dur); err != nil {
   798  		errChan <- err
   799  		return
   800  	}
   801  	if err := m.storage.CancelOrder(lo); err != nil {
   802  		errChan <- err
   803  		return
   804  	}
   805  
   806  	err = m.auth.Send(rec.order.User(), respMsg)
   807  	if err != nil {
   808  		log.Errorf("Failed to send cancel order response: %v", err)
   809  	}
   810  
   811  	sig := &updateSignal{
   812  		action: unbookAction,
   813  		data: sigDataUnbookedOrder{
   814  			order:    lo,
   815  			epochIdx: 0,
   816  		},
   817  	}
   818  	m.sendToFeeds(sig)
   819  
   820  	match := order.Match{
   821  		Taker:    co,
   822  		Maker:    lo,
   823  		Quantity: lo.Remaining(),
   824  		Rate:     lo.Rate,
   825  		Epoch: order.EpochID{
   826  			Idx: uint64(epochIdx),
   827  			Dur: m.EpochDuration(),
   828  		},
   829  		FeeRateBase:  m.getFeeRate(m.Base(), m.baseFeeFetcher),
   830  		FeeRateQuote: m.getFeeRate(m.Quote(), m.quoteFeeFetcher),
   831  	}
   832  	// insertMatchErr is sent on errChan at the end of the function. We
   833  	// want to send the match request to the client even if this insertion
   834  	// fails.
   835  	insertMatchErr := m.storage.InsertMatch(&match)
   836  
   837  	makerMsg, takerMsg := matchNotifications(&match)
   838  	m.auth.Sign(makerMsg)
   839  	m.auth.Sign(takerMsg)
   840  	msgs := []msgjson.Signable{makerMsg, takerMsg}
   841  	req, err := msgjson.NewRequest(comms.NextID(), msgjson.MatchRoute, msgs)
   842  	if err != nil {
   843  		log.Errorf("Failed to create match request: %v", err)
   844  	} else {
   845  		err = m.auth.Request(rec.order.User(), req, func(_ comms.Link, resp *msgjson.Message) {
   846  			m.processMatchAcksForCancel(rec.order.User(), resp)
   847  		})
   848  		if err != nil {
   849  			log.Errorf("Failed to send match request: %v", err)
   850  		}
   851  	}
   852  
   853  	errChan <- insertMatchErr
   854  }
   855  
   856  // matchNotifications creates a pair of msgjson.Match from a match.
   857  func matchNotifications(match *order.Match) (makerMsg *msgjson.Match, takerMsg *msgjson.Match) {
   858  	stamp := uint64(time.Now().UnixMilli())
   859  	return &msgjson.Match{
   860  			OrderID:      idToBytes(match.Maker.ID()),
   861  			MatchID:      idToBytes(match.ID()),
   862  			Quantity:     match.Quantity,
   863  			Rate:         match.Rate,
   864  			Address:      order.ExtractAddress(match.Taker),
   865  			ServerTime:   stamp,
   866  			FeeRateBase:  match.FeeRateBase,
   867  			FeeRateQuote: match.FeeRateQuote,
   868  			Side:         uint8(order.Maker),
   869  		}, &msgjson.Match{
   870  			OrderID:      idToBytes(match.Taker.ID()),
   871  			MatchID:      idToBytes(match.ID()),
   872  			Quantity:     match.Quantity,
   873  			Rate:         match.Rate,
   874  			Address:      order.ExtractAddress(match.Maker),
   875  			ServerTime:   stamp,
   876  			FeeRateBase:  match.FeeRateBase,
   877  			FeeRateQuote: match.FeeRateQuote,
   878  			Side:         uint8(order.Taker),
   879  		}
   880  }
   881  
   882  // processMatchAcksForCancel is called when receiving a response to a match
   883  // request for a cancel order. Nothing is done other than logging and verifying
   884  // that the response is in the correct format.
   885  //
   886  // This is currently only used for cancel orders that happen while the market is
   887  // suspended, but may be later used for all cancel orders.
   888  func (m *Market) processMatchAcksForCancel(user account.AccountID, msg *msgjson.Message) {
   889  	var acks []msgjson.Acknowledgement
   890  	err := msg.UnmarshalResult(&acks)
   891  	if err != nil {
   892  		m.respondError(msg.ID, user, msgjson.RPCParseError,
   893  			fmt.Sprintf("error parsing match request acknowledgment: %v", err))
   894  		return
   895  	}
   896  	// The acknowledgment for both the taker and maker should come from the same user
   897  	expectedNumAcks := 2
   898  	if len(acks) != expectedNumAcks {
   899  		m.respondError(msg.ID, user, msgjson.AckCountError,
   900  			fmt.Sprintf("expected %d acknowledgements, got %d", expectedNumAcks, len(acks)))
   901  		return
   902  	}
   903  	log.Debugf("processMatchAcksForCancel: 'match' ack received from %v", user)
   904  }
   905  
   906  // SubmitOrderAsync submits a new order for inclusion into the current epoch.
   907  // When submission is completed, an error value will be sent on the channel.
   908  // This is the asynchronous version of SubmitOrder.
   909  func (m *Market) SubmitOrderAsync(rec *orderRecord) <-chan error {
   910  	sendErr := func(err error) <-chan error {
   911  		errChan := make(chan error, 1)
   912  		errChan <- err // i.e. ErrInvalidOrder, ErrInvalidCommitment
   913  		return errChan
   914  	}
   915  
   916  	// Validate the order. The order router must do it's own validation, but do
   917  	// a second validation for (1) this Market and (2) epoch status, before
   918  	// putting it on the queue.
   919  	if err := m.validateOrder(rec.order); err != nil {
   920  		// Order ID cannot be computed since ServerTime has not been set.
   921  		log.Debugf("SubmitOrderAsync: Invalid order received from user %v with commitment %v: %v",
   922  			rec.order.User(), rec.order.Commitment(), err)
   923  		return sendErr(err)
   924  	}
   925  
   926  	// Only submit orders while market is running.
   927  	m.runMtx.RLock()
   928  	defer m.runMtx.RUnlock()
   929  
   930  	select {
   931  	case <-m.running:
   932  	default:
   933  		if rec.order.Type() == order.CancelOrderType {
   934  			errChan := make(chan error, 1)
   935  			go m.processCancelOrderWhileSuspended(rec, errChan)
   936  			return errChan
   937  		}
   938  		// m.orderRouter is closed
   939  		log.Infof("SubmitOrderAsync: Market stopped with an order in submission (commitment %v).",
   940  			rec.order.Commitment()) // The order is not time stamped, so no OrderID.
   941  		return sendErr(ErrMarketNotRunning)
   942  	}
   943  
   944  	sig := newOrderUpdateSignal(rec)
   945  	// The lock is still held, so there is a receiver: either Run's main loop or
   946  	// the drain in Run's defer that runs until m.running starts blocking.
   947  	m.orderRouter <- sig
   948  	return sig.errChan
   949  }
   950  
   951  // MidGap returns the mid-gap market rate, which is ths rate halfway between the
   952  // best buy order and the best sell order in the order book. If one side has no
   953  // orders, the best order rate on other side is returned. If both sides have no
   954  // orders, 0 is returned.
   955  func (m *Market) MidGap() uint64 {
   956  	_, mid, _ := m.rates()
   957  	return mid
   958  }
   959  
   960  func (m *Market) rates() (bestBuyRate, mid, bestSellRate uint64) {
   961  	bestBuy, bestSell := m.book.Best()
   962  	if bestBuy == nil {
   963  		if bestSell == nil {
   964  			return
   965  		}
   966  		return 0, bestSell.Rate, bestSell.Rate
   967  	} else if bestSell == nil {
   968  		return bestBuy.Rate, bestBuy.Rate, math.MaxUint64
   969  	}
   970  	mid = (bestBuy.Rate + bestSell.Rate) / 2 // note downward bias on truncate
   971  	return bestBuy.Rate, mid, bestSell.Rate
   972  }
   973  
   974  // CoinLocked checks if a coin is locked. The asset is specified since we should
   975  // not assume that a CoinID for one asset cannot be made to match another
   976  // asset's CoinID.
   977  func (m *Market) CoinLocked(asset uint32, coin coinlock.CoinID) bool {
   978  	switch {
   979  	case asset == m.marketInfo.Base && m.coinLockerBase != nil:
   980  		return m.coinLockerBase.CoinLocked(coin)
   981  	case asset == m.marketInfo.Quote && m.coinLockerQuote != nil:
   982  		return m.coinLockerQuote.CoinLocked(coin)
   983  	default:
   984  		panic(fmt.Sprintf("invalid utxo-based asset %d for market %s", asset, m.marketInfo.Name))
   985  	}
   986  }
   987  
   988  // Cancelable determines if an order is a limit order with time-in-force
   989  // standing that is in either the epoch queue or in the order book.
   990  func (m *Market) Cancelable(oid order.OrderID) bool {
   991  	// All book orders are standing limit orders.
   992  	if m.book.HaveOrder(oid) {
   993  		return true
   994  	}
   995  
   996  	// Check the active epochs (includes current and next).
   997  	m.epochMtx.RLock()
   998  	ord := m.epochOrders[oid]
   999  	m.epochMtx.RUnlock()
  1000  
  1001  	if lo, ok := ord.(*order.LimitOrder); ok {
  1002  		return lo.Force == order.StandingTiF
  1003  	}
  1004  	return false
  1005  }
  1006  
  1007  // CancelableBy determines if an order is cancelable by a certain account. This
  1008  // means: (1) an order in the book or epoch queue, (2) type limit with
  1009  // time-in-force standing (implied for book orders), and (3) AccountID field
  1010  // matching the provided account ID.
  1011  func (m *Market) CancelableBy(oid order.OrderID, aid account.AccountID) (bool, time.Time, error) {
  1012  	// All book orders are standing limit orders.
  1013  	if lo := m.book.Order(oid); lo != nil {
  1014  		if lo.AccountID == aid {
  1015  			return true, lo.ServerTime, nil
  1016  		}
  1017  		return false, time.Time{}, ErrCancelNotPermitted
  1018  	}
  1019  
  1020  	// Check the active epochs (includes current and next).
  1021  	m.epochMtx.RLock()
  1022  	ord := m.epochOrders[oid]
  1023  	m.epochMtx.RUnlock()
  1024  
  1025  	if ord == nil {
  1026  		return false, time.Time{}, ErrTargetNotActive
  1027  	}
  1028  
  1029  	lo, ok := ord.(*order.LimitOrder)
  1030  	if !ok {
  1031  		return false, time.Time{}, ErrTargetNotCancelable
  1032  	}
  1033  	if lo.Force != order.StandingTiF {
  1034  		return false, time.Time{}, ErrTargetNotCancelable
  1035  	}
  1036  	if lo.AccountID != aid {
  1037  		return false, time.Time{}, ErrCancelNotPermitted
  1038  	}
  1039  	return true, lo.ServerTime, nil
  1040  }
  1041  
  1042  func (m *Market) checkUnfilledOrders(assetID uint32, unfilled []*order.LimitOrder) (unbooked []*order.LimitOrder) {
  1043  	checkUnspent := func(assetID uint32, coinID []byte) error {
  1044  		ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
  1045  		defer cancel()
  1046  		return m.swapper.CheckUnspent(ctx, assetID, coinID)
  1047  	}
  1048  
  1049  orders:
  1050  	for _, lo := range unfilled {
  1051  		log.Tracef("Checking %d funding coins for order %v", len(lo.Coins), lo.ID())
  1052  		for i := range lo.Coins {
  1053  			err := checkUnspent(assetID, lo.Coins[i])
  1054  			if err == nil {
  1055  				continue // unspent, check next coin
  1056  			}
  1057  
  1058  			if !errors.Is(err, asset.CoinNotFoundError) {
  1059  				// other failure (timeout, coinID decode, RPC, etc.)
  1060  				log.Errorf("Unexpected error checking coinID %v for order %v: %v",
  1061  					lo.Coins[i], lo, err)
  1062  				continue orders
  1063  				// NOTE: This does not revoke orders from storage since this is
  1064  				// likely to be a configuration or node issue.
  1065  			}
  1066  
  1067  			// Final fill amount check in case it was matched after we pulled
  1068  			// the list of unfilled orders from the book.
  1069  			if lo.Filled() == 0 {
  1070  				log.Warnf("Coin %s not unspent for unfilled order %v. "+
  1071  					"Revoking the order.", fmtCoinID(assetID, lo.Coins[i]), lo)
  1072  				m.Unbook(lo)
  1073  				unbooked = append(unbooked, lo)
  1074  			}
  1075  			continue orders
  1076  		}
  1077  	}
  1078  	return
  1079  }
  1080  
  1081  // SwapDone registers a match for a given order as being finished. Whether the
  1082  // match was a successful or failed swap is indicated by fail. This is used to
  1083  // (1) register completed orders for cancellation rate purposes, and (2) to
  1084  // unbook at-fault limit orders.
  1085  //
  1086  // Implementation note: Orders that have failed a swap or were canceled (see
  1087  // processReadyEpoch) are removed from the settling map regardless of any amount
  1088  // still setting for such orders.
  1089  func (m *Market) SwapDone(ord order.Order, match *order.Match, fail bool) {
  1090  	oid := ord.ID()
  1091  	m.bookMtx.Lock()
  1092  	defer m.bookMtx.Unlock()
  1093  	settling, found := m.settling[oid]
  1094  	if !found {
  1095  		// Order was canceled, revoked, or already had failed swap, and was
  1096  		// removed from the map. No more settling amount tracking needed.
  1097  		return
  1098  	}
  1099  	if settling < match.Quantity {
  1100  		log.Errorf("Finished swap %v (qty %d) for order %v larger than current settling (%d) amount.",
  1101  			match.ID(), match.Quantity, oid, settling)
  1102  		settling = 0
  1103  	} else {
  1104  		settling -= match.Quantity
  1105  	}
  1106  
  1107  	// Limit orders may need to be unbooked, or considered for further matches.
  1108  	lo, limit := ord.(*order.LimitOrder)
  1109  
  1110  	// For a failed swap, remove the map entry, and unbook/revoke the order.
  1111  	if fail {
  1112  		delete(m.settling, oid)
  1113  		if limit {
  1114  			// Try to unbook and revoke failed limit orders.
  1115  			_, removed := m.book.Remove(oid)
  1116  			m.unlockOrderCoins(lo)
  1117  			if removed {
  1118  				// Lazily update DB and auth, and notify orderbook subscribers.
  1119  				m.lazy(func() { m.unbookedOrder(lo) })
  1120  			}
  1121  		}
  1122  		return
  1123  	}
  1124  
  1125  	// Continue tracking if there are swaps settling or it is booked (more
  1126  	// matches can be made). We check Book.HaveOrder instead of Remaining since
  1127  	// the provided Order instance may not belong to Market and may thus be out
  1128  	// of sync with respect to filled amount.
  1129  	if settling > 0 || (limit && lo.Force == order.StandingTiF && m.book.HaveOrder(oid)) {
  1130  		m.settling[oid] = settling
  1131  		return
  1132  	}
  1133  
  1134  	// The order can no longer be matched and nothing is settling.
  1135  	delete(m.settling, oid)
  1136  
  1137  	// Register the order as successfully completed in the auth manager.
  1138  	compTime := time.Now().UTC()
  1139  	m.auth.RecordCompletedOrder(ord.User(), oid, compTime)
  1140  	// Record the successful completion time.
  1141  	if err := m.storage.SetOrderCompleteTime(ord, compTime.UnixMilli()); err != nil {
  1142  		if db.IsErrGeneralFailure(err) {
  1143  			log.Errorf("fatal error with SetOrderCompleteTime for order %v: %v", ord, err)
  1144  			return
  1145  		}
  1146  		log.Errorf("SetOrderCompleteTime for %v: %v", ord, err)
  1147  	}
  1148  }
  1149  
  1150  // CheckUnfilled checks unfilled book orders belonging to a user and funded by
  1151  // coins for a given asset to ensure that their funding coins are not spent. If
  1152  // any of an order's funding coins are spent, the order is unbooked (removed
  1153  // from the in-memory book, revoked in the DB, a cancellation marked against the
  1154  // user, coins unlocked, and orderbook subscribers notified). See Unbook for
  1155  // details.
  1156  func (m *Market) CheckUnfilled(assetID uint32, user account.AccountID) (unbooked []*order.LimitOrder) {
  1157  	base, quote := m.marketInfo.Base, m.marketInfo.Quote
  1158  	if assetID != base && assetID != quote {
  1159  		return
  1160  	}
  1161  	var unfilled []*order.LimitOrder
  1162  	switch assetID {
  1163  	case base:
  1164  		// Sell orders are funded by the base asset.
  1165  		unfilled = m.book.UnfilledUserSells(user)
  1166  	case quote:
  1167  		// Buy orders are funded by the quote asset.
  1168  		unfilled = m.book.UnfilledUserBuys(user)
  1169  	default:
  1170  		return
  1171  	}
  1172  
  1173  	return m.checkUnfilledOrders(assetID, unfilled)
  1174  }
  1175  
  1176  // AccountPending sums the orders quantities that pay to or from the specified
  1177  // account address.
  1178  func (m *Market) AccountPending(acctAddr string, assetID uint32) (qty, lots uint64, redeems int) {
  1179  	base, quote := m.marketInfo.Base, m.marketInfo.Quote
  1180  	if (assetID != base && assetID != quote) ||
  1181  		(assetID == m.marketInfo.Base && m.coinLockerBase != nil) ||
  1182  		(assetID == m.marketInfo.Quote && m.coinLockerQuote != nil) {
  1183  
  1184  		return
  1185  	}
  1186  
  1187  	midGap := m.MidGap()
  1188  	if midGap == 0 {
  1189  		midGap = m.RateStep()
  1190  	}
  1191  
  1192  	lotSize := m.marketInfo.LotSize
  1193  	switch assetID {
  1194  	case base:
  1195  		m.iterateBaseAccount(acctAddr, func(trade *order.Trade, rate uint64) {
  1196  			r := trade.Remaining()
  1197  			if trade.Sell {
  1198  				qty += r
  1199  				lots += r / lotSize
  1200  			} else {
  1201  				if rate == 0 { // market buy
  1202  					redeems += int(calc.QuoteToBase(midGap, r) / lotSize)
  1203  				} else {
  1204  					redeems += int(r / lotSize)
  1205  				}
  1206  			}
  1207  		})
  1208  	case quote:
  1209  		m.iterateQuoteAccount(acctAddr, func(trade *order.Trade, rate uint64) {
  1210  			r := trade.Remaining()
  1211  			if trade.Sell {
  1212  				redeems += int(r / lotSize)
  1213  			} else {
  1214  				if rate == 0 { // market buy
  1215  					qty += r
  1216  					lots += calc.QuoteToBase(midGap, r) / lotSize
  1217  				} else {
  1218  					qty += calc.BaseToQuote(midGap, r)
  1219  					lots += r / lotSize
  1220  				}
  1221  			}
  1222  		})
  1223  	}
  1224  	return
  1225  }
  1226  
  1227  func (m *Market) iterateBaseAccount(acctAddr string, f func(*order.Trade, uint64)) {
  1228  	m.epochMtx.RLock()
  1229  	for _, epOrd := range m.epochOrders {
  1230  		if epOrd.Type() == order.CancelOrderType || epOrd.Trade().BaseAccount() != acctAddr {
  1231  			continue
  1232  		}
  1233  		var rate uint64
  1234  		if lo, is := epOrd.(*order.LimitOrder); is {
  1235  			rate = lo.Rate
  1236  		}
  1237  		f(epOrd.Trade(), rate)
  1238  
  1239  	}
  1240  	m.epochMtx.RUnlock()
  1241  	m.book.IterateBaseAccount(acctAddr, func(lo *order.LimitOrder) {
  1242  		f(lo.Trade(), lo.Rate)
  1243  	})
  1244  }
  1245  
  1246  func (m *Market) iterateQuoteAccount(acctAddr string, f func(*order.Trade, uint64)) {
  1247  	m.epochMtx.RLock()
  1248  	for _, epOrd := range m.epochOrders {
  1249  		if epOrd.Type() == order.CancelOrderType || epOrd.Trade().QuoteAccount() != acctAddr {
  1250  			continue
  1251  		}
  1252  		var rate uint64
  1253  		if lo, is := epOrd.(*order.LimitOrder); is {
  1254  			rate = lo.Rate
  1255  		}
  1256  		f(epOrd.Trade(), rate)
  1257  	}
  1258  	m.epochMtx.RUnlock()
  1259  	m.book.IterateQuoteAccount(acctAddr, func(lo *order.LimitOrder) {
  1260  		f(lo.Trade(), lo.Rate)
  1261  	})
  1262  }
  1263  
  1264  // Book retrieves the market's current order book and the current epoch index.
  1265  // If the Market is not yet running or the start epoch has not yet begun, the
  1266  // epoch index will be zero.
  1267  func (m *Market) Book() (epoch int64, buys, sells []*order.LimitOrder) {
  1268  	// NOTE: it may be desirable to cache the response.
  1269  	m.bookMtx.Lock()
  1270  	buys = m.book.BuyOrders()
  1271  	sells = m.book.SellOrders()
  1272  	epoch = m.bookEpochIdx
  1273  	m.bookMtx.Unlock()
  1274  	return
  1275  }
  1276  
  1277  // PurgeBook flushes all booked orders from the in-memory book and persistent
  1278  // storage. In terms of storage, this means changing orders with status booked
  1279  // to status revoked.
  1280  func (m *Market) PurgeBook() {
  1281  	// Clear booked orders from the DB and the in-memory book.
  1282  	removed := m.purgeBook()
  1283  
  1284  	// Send individual revoke order notifications. These are not part of the
  1285  	// orderbook subscription, so the users will receive them whether or not
  1286  	// they are subscribed for book updates.
  1287  	for oid, aid := range removed {
  1288  		m.sendRevokeOrderNote(oid, aid)
  1289  	}
  1290  }
  1291  
  1292  func (m *Market) purgeBook() (removed map[order.OrderID]account.AccountID) {
  1293  	m.bookMtx.Lock()
  1294  	defer m.bookMtx.Unlock()
  1295  
  1296  	// Revoke all booked orders in the DB.
  1297  	sellsCleared, buysCleared, err := m.storage.FlushBook(m.marketInfo.Base, m.marketInfo.Quote)
  1298  	if err != nil {
  1299  		log.Errorf("Failed to flush book for market %s: %v", m.marketInfo.Name, err)
  1300  		return
  1301  	}
  1302  
  1303  	// Clear the in-memory order book to match the DB.
  1304  	buysRemoved, sellsRemoved := m.book.Clear()
  1305  
  1306  	log.Infof("Flushed %d sell orders and %d buy orders from market %q book",
  1307  		len(sellsRemoved), len(buysRemoved), m.marketInfo.Name)
  1308  	// Maybe the DB cleaned up orphaned orders. Log any discrepancies.
  1309  	if len(sellsRemoved) != len(sellsCleared) {
  1310  		log.Warnf("Removed %d sell orders from the book, but %d were updated in the DB.",
  1311  			len(sellsRemoved), len(sellsCleared))
  1312  	}
  1313  	if len(buysRemoved) != len(buysCleared) {
  1314  		log.Warnf("Removed %d buy orders from the book, but %d were updated in the DB.",
  1315  			len(buysRemoved), len(buysCleared))
  1316  	}
  1317  
  1318  	// Unlock coins for removed orders.
  1319  
  1320  	// TODO: only unlock previously booked order coins, do not include coins
  1321  	// that might belong to orders still in epoch status. This won't matter if
  1322  	// the market is suspended, but it does if PurgeBook is used while the
  1323  	// market is still accepting new orders and processing epochs.
  1324  
  1325  	// Unlock base asset coins locked by sell orders.
  1326  	if m.coinLockerBase != nil {
  1327  		for i := range sellsRemoved {
  1328  			m.coinLockerBase.UnlockOrderCoins(sellsRemoved[i].ID())
  1329  		}
  1330  	}
  1331  
  1332  	// Unlock quote asset coins locked by buy orders.
  1333  	if m.coinLockerQuote != nil {
  1334  		for i := range buysRemoved {
  1335  			m.coinLockerQuote.UnlockOrderCoins(buysRemoved[i].ID())
  1336  		}
  1337  	}
  1338  
  1339  	removed = make(map[order.OrderID]account.AccountID, len(buysRemoved)+len(sellsRemoved))
  1340  	for _, lo := range append(sellsRemoved, buysRemoved...) {
  1341  		removed[lo.ID()] = lo.AccountID
  1342  	}
  1343  
  1344  	return
  1345  }
  1346  
  1347  func (m *Market) lazy(do func()) {
  1348  	m.tasks.Add(1)
  1349  	go func() {
  1350  		defer m.tasks.Done()
  1351  		do()
  1352  	}()
  1353  }
  1354  
  1355  // Run is the main order processing loop, which takes new orders, notifies book
  1356  // subscribers, and cycles the epochs. The caller should cancel the provided
  1357  // Context to stop the market. The outgoing order feed channels persist after
  1358  // Run returns for possible Market resume, and for Swapper's unbook callback to
  1359  // function using sendToFeeds.
  1360  func (m *Market) Run(ctx context.Context) {
  1361  	// Prevent multiple incantations of Run.
  1362  	if !atomic.CompareAndSwapUint32(&m.up, 0, 1) {
  1363  		log.Errorf("Run: Market not stopped!")
  1364  		return
  1365  	}
  1366  	defer atomic.StoreUint32(&m.up, 0)
  1367  
  1368  	var running bool
  1369  	ctxRun, cancel := context.WithCancel(ctx)
  1370  	var wgFeeds, wgEpochs sync.WaitGroup
  1371  	notifyChan := make(chan *updateSignal, 32)
  1372  
  1373  	// For clarity, define the shutdown sequence in a single closure rather than
  1374  	// the defer stack.
  1375  	defer func() {
  1376  		// Drain the order router of incoming orders that made it in after the
  1377  		// main loop broke and before flagging the market stopped. Do this in a
  1378  		// goroutine because the market is flagged as stopped under runMtx lock
  1379  		// in this defer and there is a risk of deadlock in SubmitOrderAsync
  1380  		// that sends under runMtx lock as well.
  1381  		wgFeeds.Add(1)
  1382  		go func() {
  1383  			defer wgFeeds.Done()
  1384  			for sig := range m.orderRouter {
  1385  				sig.errChan <- ErrMarketNotRunning
  1386  			}
  1387  		}()
  1388  
  1389  		// Under lock, flag as not running.
  1390  		m.runMtx.Lock() // block while SubmitOrderAsync is sending to the drain
  1391  		if !running {
  1392  			// In case the market is stopped before the first epoch, close the
  1393  			// running channel so that waitForEpochOpen does not hang.
  1394  			close(m.running)
  1395  		}
  1396  		m.running = make(chan struct{})
  1397  		running = false
  1398  		close(m.orderRouter) // stop the order router drain
  1399  		m.runMtx.Unlock()
  1400  
  1401  		// Stop and wait for epoch pump and processing pipeline goroutines.
  1402  		cancel() // may already be done by suspend
  1403  		wgEpochs.Wait()
  1404  		// Book mod goroutines done, may purge if requested.
  1405  
  1406  		// persistBook is set under epochMtx lock.
  1407  		m.epochMtx.Lock()
  1408  
  1409  		// Signal to the book router of the suspend now that the closed epoch
  1410  		// processing pipeline is finished (wgEpochs).
  1411  		notifyChan <- &updateSignal{
  1412  			action: suspendAction,
  1413  			data: sigDataSuspend{
  1414  				finalEpoch:  m.activeEpochIdx,
  1415  				persistBook: m.persistBook,
  1416  			},
  1417  		}
  1418  
  1419  		if !m.persistBook {
  1420  			m.PurgeBook()
  1421  		}
  1422  
  1423  		m.persistBook = true // future resume default
  1424  		m.activeEpochIdx = 0
  1425  
  1426  		// Revoke any unmatched epoch orders (if context was canceled, not a
  1427  		// clean suspend stopped the market).
  1428  		for oid, ord := range m.epochOrders {
  1429  			log.Infof("Dropping epoch order %v", oid)
  1430  			if co, ok := ord.(*order.CancelOrder); ok {
  1431  				if err := m.storage.FailCancelOrder(co); err != nil {
  1432  					log.Errorf("Failed to set orphaned epoch cancel order %v as executed: %v", oid, err)
  1433  				}
  1434  				continue
  1435  			}
  1436  			if err := m.storage.ExecuteOrder(ord); err != nil {
  1437  				log.Errorf("Failed to set orphaned epoch trade order %v as executed: %v", oid, err)
  1438  			}
  1439  		}
  1440  		m.epochMtx.Unlock()
  1441  
  1442  		// Stop and wait for the order feed goroutine.
  1443  		close(notifyChan)
  1444  		wgFeeds.Wait()
  1445  
  1446  		m.tasks.Wait()
  1447  
  1448  		log.Infof("Market %q stopped.", m.marketInfo.Name)
  1449  	}()
  1450  
  1451  	// Start outgoing order feed notification goroutine.
  1452  	wgFeeds.Add(1)
  1453  	go func() {
  1454  		defer wgFeeds.Done()
  1455  		for sig := range notifyChan {
  1456  			m.sendToFeeds(sig)
  1457  		}
  1458  	}()
  1459  
  1460  	// Start the closed epoch pump, which drives preimage collection and orderly
  1461  	// epoch processing.
  1462  	eq := newEpochPump()
  1463  	wgEpochs.Add(1)
  1464  	go func() {
  1465  		defer wgEpochs.Done()
  1466  		eq.Run(ctxRun)
  1467  	}()
  1468  
  1469  	// Start the closed epoch processing pipeline.
  1470  	wgEpochs.Add(1)
  1471  	go func() {
  1472  		defer wgEpochs.Done()
  1473  		for ep := range eq.ready {
  1474  			// prepEpoch has completed preimage collection.
  1475  			m.processReadyEpoch(ep, notifyChan)
  1476  		}
  1477  		log.Debugf("epoch pump drained for market %s", m.marketInfo.Name)
  1478  		// There must be no more notify calls.
  1479  	}()
  1480  
  1481  	m.epochMtx.Lock()
  1482  	nextEpochIdx := m.startEpochIdx
  1483  	if nextEpochIdx == 0 {
  1484  		log.Warnf("Run: startEpochIdx not set. Starting at the next epoch.")
  1485  		now := time.Now().UnixMilli()
  1486  		nextEpochIdx = 1 + now/int64(m.EpochDuration())
  1487  		m.startEpochIdx = nextEpochIdx
  1488  	}
  1489  	m.epochMtx.Unlock()
  1490  
  1491  	epochDuration := int64(m.marketInfo.EpochDuration)
  1492  	nextEpoch := NewEpoch(nextEpochIdx, epochDuration)
  1493  	epochCycle := time.After(time.Until(nextEpoch.Start))
  1494  
  1495  	var currentEpoch *EpochQueue
  1496  	cycleEpoch := func() {
  1497  		if currentEpoch != nil {
  1498  			// Process the epoch asynchronously since there is a delay while the
  1499  			// preimages are requested and clients respond with their preimages.
  1500  			if !m.enqueueEpoch(eq, currentEpoch) {
  1501  				return
  1502  			}
  1503  
  1504  			// The epoch is closed, long live the epoch.
  1505  			sig := &updateSignal{
  1506  				action: newEpochAction,
  1507  				data:   sigDataNewEpoch{idx: nextEpoch.Epoch},
  1508  			}
  1509  			notifyChan <- sig
  1510  		}
  1511  
  1512  		// Guard activeEpochIdx and suspendEpochIdx.
  1513  		m.epochMtx.Lock()
  1514  		defer m.epochMtx.Unlock()
  1515  
  1516  		// Check suspendEpochIdx and suspend if the just-closed epoch idx is the
  1517  		// suspend epoch.
  1518  		if m.suspendEpochIdx == nextEpoch.Epoch-1 {
  1519  			// Reject incoming orders.
  1520  			currentEpoch = nil
  1521  			cancel() // graceful market shutdown
  1522  			return
  1523  		}
  1524  
  1525  		currentEpoch = nextEpoch
  1526  		nextEpochIdx = currentEpoch.Epoch + 1
  1527  		m.activeEpochIdx = currentEpoch.Epoch
  1528  
  1529  		if !running {
  1530  			// Check that both blockchains are synced before actually starting.
  1531  			synced, err := m.swapper.ChainsSynced(m.marketInfo.Base, m.marketInfo.Quote)
  1532  			if err != nil {
  1533  				log.Errorf("Not starting %s market because of ChainsSynced error: %v", m.marketInfo.Name, err)
  1534  			} else if !synced {
  1535  				log.Debugf("Delaying start of %s market because chains aren't synced", m.marketInfo.Name)
  1536  			} else {
  1537  				// Open up SubmitOrderAsync.
  1538  				close(m.running)
  1539  				running = true
  1540  				log.Infof("Market %s now accepting orders, epoch %d:%d", m.marketInfo.Name,
  1541  					currentEpoch.Epoch, epochDuration)
  1542  				// Signal to the book router if this is a resume.
  1543  				if m.suspendEpochIdx != 0 {
  1544  					notifyChan <- &updateSignal{
  1545  						action: resumeAction,
  1546  						data: sigDataResume{
  1547  							epochIdx: currentEpoch.Epoch,
  1548  							// TODO: signal config or new config
  1549  						},
  1550  					}
  1551  				}
  1552  			}
  1553  		}
  1554  
  1555  		// Replace the next epoch and set the cycle Timer.
  1556  		nextEpoch = NewEpoch(nextEpochIdx, epochDuration)
  1557  		epochCycle = time.After(time.Until(nextEpoch.Start))
  1558  	}
  1559  
  1560  	// Set the orderRouter field now since the main loop below receives on it,
  1561  	// even though SubmitOrderAsync disallows sends on orderRouter when the
  1562  	// market is not running.
  1563  	m.orderRouter = make(chan *orderUpdateSignal, 32) // implicitly guarded by m.runMtx since Market is not running yet
  1564  
  1565  	for {
  1566  		if ctxRun.Err() != nil {
  1567  			return
  1568  		}
  1569  
  1570  		if err := m.storage.LastErr(); err != nil {
  1571  			log.Criticalf("Archivist failing. Last unexpected error: %v", err)
  1572  			return
  1573  		}
  1574  
  1575  		// Prioritize the epoch cycle.
  1576  		select {
  1577  		case <-epochCycle:
  1578  			cycleEpoch()
  1579  		default:
  1580  		}
  1581  
  1582  		// cycleEpoch can cancel ctxRun if suspend initiated.
  1583  		if ctxRun.Err() != nil {
  1584  			return
  1585  		}
  1586  
  1587  		// Wait for the next signal (cancel, new order, or epoch cycle).
  1588  		select {
  1589  		case <-ctxRun.Done():
  1590  			return
  1591  
  1592  		case s := <-m.orderRouter:
  1593  			if currentEpoch == nil {
  1594  				// The order is not time-stamped yet, so the ID cannot be computed.
  1595  				log.Debugf("Order type %v received prior to market start.", s.rec.order.Type())
  1596  				s.errChan <- ErrMarketNotRunning
  1597  				continue
  1598  			}
  1599  
  1600  			// Set the order's server time stamp, giving the order a valid ID.
  1601  			sTime := time.Now().Truncate(time.Millisecond).UTC()
  1602  			s.rec.order.SetTime(sTime) // Order.ID()/UID()/String() is OK now.
  1603  			log.Tracef("Received order %v at %v", s.rec.order, sTime)
  1604  
  1605  			// Push the order into the next epoch if receiving and stamping it
  1606  			// took just a little too long.
  1607  			var orderEpoch *EpochQueue
  1608  			switch {
  1609  			case currentEpoch.IncludesTime(sTime):
  1610  				orderEpoch = currentEpoch
  1611  			case nextEpoch.IncludesTime(sTime):
  1612  				log.Infof("Order %v (sTime=%d) fell into the next epoch [%d,%d)",
  1613  					s.rec.order, sTime.UnixNano(), nextEpoch.Start.Unix(), nextEpoch.End.Unix())
  1614  				orderEpoch = nextEpoch
  1615  			default:
  1616  				// This should not happen.
  1617  				log.Errorf("Time %d does not fit into current or next epoch!",
  1618  					sTime.UnixNano())
  1619  				s.errChan <- ErrEpochMissed
  1620  				continue
  1621  			}
  1622  
  1623  			// Process the order in the target epoch queue.
  1624  			err := m.processOrder(s.rec, orderEpoch, notifyChan, s.errChan)
  1625  			if err != nil {
  1626  				log.Errorf("Failed to process order %v: %v", s.rec.order, err)
  1627  				// Signal to the other Run goroutines to return.
  1628  				return
  1629  			}
  1630  
  1631  		case <-epochCycle:
  1632  			cycleEpoch()
  1633  		}
  1634  	}
  1635  
  1636  }
  1637  
  1638  func (m *Market) coinsLocked(o order.Order) ([]order.CoinID, uint32) {
  1639  	if o.Type() == order.CancelOrderType {
  1640  		return nil, 0
  1641  	}
  1642  
  1643  	locker := m.coinLockerQuote
  1644  	assetID := m.marketInfo.Quote
  1645  	if o.Trade().Trade().Sell {
  1646  		locker = m.coinLockerBase
  1647  		assetID = m.marketInfo.Base
  1648  	}
  1649  
  1650  	if locker == nil { // Not utxo-based
  1651  		return nil, 0
  1652  	}
  1653  
  1654  	// Check if this order is known by the locker.
  1655  	lockedCoins := locker.OrderCoinsLocked(o.ID())
  1656  	if len(lockedCoins) > 0 {
  1657  		return lockedCoins, assetID
  1658  	}
  1659  
  1660  	// Check the individual coins.
  1661  	for _, coin := range o.Trade().Coins {
  1662  		if locker.CoinLocked(coin) {
  1663  			lockedCoins = append(lockedCoins, coin)
  1664  		}
  1665  	}
  1666  	return lockedCoins, assetID
  1667  }
  1668  
  1669  func (m *Market) lockOrderCoins(o order.Order) {
  1670  	if o.Type() == order.CancelOrderType {
  1671  		return
  1672  	}
  1673  
  1674  	if o.Trade().Sell {
  1675  		if m.coinLockerBase != nil {
  1676  			m.coinLockerBase.LockOrdersCoins([]order.Order{o})
  1677  		}
  1678  
  1679  	} else if m.coinLockerQuote != nil {
  1680  		m.coinLockerQuote.LockOrdersCoins([]order.Order{o})
  1681  	}
  1682  }
  1683  
  1684  func (m *Market) unlockOrderCoins(o order.Order) {
  1685  	if o.Type() == order.CancelOrderType {
  1686  		return
  1687  	}
  1688  
  1689  	if o.Trade().Sell {
  1690  		if m.coinLockerBase != nil {
  1691  			m.coinLockerBase.UnlockOrderCoins(o.ID())
  1692  		}
  1693  	} else if m.coinLockerQuote != nil {
  1694  		m.coinLockerQuote.UnlockOrderCoins(o.ID())
  1695  	}
  1696  }
  1697  
  1698  func (m *Market) analysisHelpers() (
  1699  	likelyTaker func(ord order.Order) bool,
  1700  	baseQty func(ord order.Order) uint64,
  1701  ) {
  1702  	bestBuy, midGap, bestSell := m.rates()
  1703  	likelyTaker = func(ord order.Order) bool {
  1704  		lo, ok := ord.(*order.LimitOrder)
  1705  		if !ok || lo.Force == order.ImmediateTiF {
  1706  			return true
  1707  		}
  1708  		// Must cross the spread to be a taker (not so conservative).
  1709  		switch {
  1710  		case midGap == 0:
  1711  			return false // empty market: could be taker, but assume not
  1712  		case lo.Sell:
  1713  			return lo.Rate <= bestBuy
  1714  		default:
  1715  			return lo.Rate >= bestSell
  1716  		}
  1717  	}
  1718  	baseQty = func(ord order.Order) uint64 {
  1719  		if ord.Type() == order.CancelOrderType {
  1720  			return 0
  1721  		}
  1722  		qty := ord.Trade().Quantity
  1723  		if ord.Type() == order.MarketOrderType && !ord.Trade().Sell {
  1724  			// Market buy qty is in quote asset. Convert to base.
  1725  			if midGap == 0 {
  1726  				qty = m.marketInfo.LotSize // no orders on the book; call it 1 lot
  1727  			} else {
  1728  				qty = calc.QuoteToBase(midGap, qty)
  1729  			}
  1730  		}
  1731  		return qty
  1732  	}
  1733  	return
  1734  }
  1735  
  1736  // ParcelSize returns market's configured parcel size.
  1737  func (m *Market) ParcelSize() uint32 {
  1738  	return m.marketInfo.ParcelSize
  1739  }
  1740  
  1741  // Parcels calculates the total parcels for the market with the specified
  1742  // settling quantity. Parcels is used as part of order validation for global
  1743  // parcel limits. Parcels is not called for the market for which the order is
  1744  // for, which will use m.checkParcelLimit to validate in processOrder.
  1745  func (m *Market) Parcels(user account.AccountID, settlingQty uint64) float64 {
  1746  	return m.parcels(user, settlingQty)
  1747  }
  1748  
  1749  func (m *Market) parcels(user account.AccountID, addParcelWeight uint64) float64 {
  1750  	likelyTaker, baseQty := m.analysisHelpers()
  1751  	var takerQty, makerQty uint64
  1752  	m.epochMtx.RLock()
  1753  	for _, epOrd := range m.epochOrders {
  1754  		if epOrd.User() != user || epOrd.Type() == order.CancelOrderType {
  1755  			continue
  1756  		}
  1757  
  1758  		// Even if standing, may count as taker for purposes of taker qty limit.
  1759  		if likelyTaker(epOrd) {
  1760  			takerQty += baseQty(epOrd)
  1761  		} else {
  1762  			makerQty += baseQty(epOrd)
  1763  		}
  1764  	}
  1765  	m.epochMtx.RUnlock()
  1766  
  1767  	bookedBuyAmt, bookedSellAmt, _, _ := m.book.UserOrderTotals(user)
  1768  	makerQty += bookedBuyAmt + bookedSellAmt
  1769  	return calc.Parcels(makerQty+addParcelWeight, takerQty, m.marketInfo.LotSize, m.marketInfo.ParcelSize)
  1770  }
  1771  
  1772  // processOrder performs the following actions:
  1773  // 1. Verify the order is new and that none of the backing coins are locked.
  1774  // 2. Lock the order's coins.
  1775  // 3. Store the order in the DB.
  1776  // 4. Insert the order into the EpochQueue.
  1777  // 5. Respond to the client that placed the order.
  1778  // 6. Notify epoch queue event subscribers.
  1779  func (m *Market) processOrder(rec *orderRecord, epoch *EpochQueue, notifyChan chan<- *updateSignal, errChan chan<- error) error {
  1780  	// Disallow trade orders from suspended accounts. Cancel orders are allowed.
  1781  	if rec.order.Type() != order.CancelOrderType {
  1782  		// Do not bother the auth manager for cancel orders.
  1783  		if _, tier := m.auth.AcctStatus(rec.order.User()); tier < 1 {
  1784  			log.Debugf("Account %v with tier %d not allowed to submit order %v", rec.order.User(), tier, rec.order.ID())
  1785  			errChan <- ErrSuspendedAccount
  1786  			return nil
  1787  		}
  1788  	}
  1789  
  1790  	// Verify that an order with the same commitment is not already in the epoch
  1791  	// queue. Since commitment is part of the order serialization and thus order
  1792  	// ID, this also prevents orders with the same ID.
  1793  	// TODO: Prevent commitment reuse in general, without expensive DB queries.
  1794  	ord := rec.order
  1795  	oid := ord.ID()
  1796  	user := ord.User()
  1797  
  1798  	commit := ord.Commitment()
  1799  	m.epochMtx.RLock()
  1800  	otherOid, found := m.epochCommitments[commit]
  1801  	m.epochMtx.RUnlock()
  1802  	if found {
  1803  		log.Debugf("Received order %v with commitment %x also used in previous order %v!",
  1804  			oid, commit, otherOid)
  1805  		errChan <- ErrInvalidCommitment
  1806  		return nil
  1807  	}
  1808  
  1809  	// Verify that another cancel order targeting the same order is not already
  1810  	// in the epoch queue. Market and limit orders using the same coin IDs as
  1811  	// other orders is prevented by the coinlocker.
  1812  	epochGap := db.EpochGapNA
  1813  	if co, ok := ord.(*order.CancelOrder); ok {
  1814  		if eco := epoch.CancelTargets[co.TargetOrderID]; eco != nil {
  1815  			log.Debugf("Received cancel order %v targeting %v, but already have %v.",
  1816  				co, co.TargetOrderID, eco)
  1817  			errChan <- ErrDuplicateCancelOrder
  1818  			return nil
  1819  		}
  1820  
  1821  		if nc := epoch.UserCancels[co.AccountID]; nc >= m.marketInfo.MaxUserCancelsPerEpoch {
  1822  			log.Debugf("Received cancel order %v targeting %v, but user already has %d cancel orders in this epoch.",
  1823  				co, co.TargetOrderID, nc)
  1824  			errChan <- ErrTooManyCancelOrders
  1825  			return nil
  1826  		}
  1827  
  1828  		// Verify that the target order is on the books or in the epoch queue,
  1829  		// and that the account of the CancelOrder is the same as the account of
  1830  		// the target order.
  1831  		cancelable, loTime, err := m.CancelableBy(co.TargetOrderID, co.AccountID)
  1832  		if !cancelable {
  1833  			log.Debugf("Cancel order %v (account=%v) target order %v: %v",
  1834  				co, co.AccountID, co.TargetOrderID, err)
  1835  			errChan <- err
  1836  			return nil
  1837  		}
  1838  
  1839  		epochGap = int32(epoch.Epoch - loTime.UnixMilli()/epoch.Duration)
  1840  
  1841  	} else { // Not a cancel order, check user limits.
  1842  		likelyTaker, baseQty := m.analysisHelpers()
  1843  		orderWeight := baseQty(ord)
  1844  		if likelyTaker(ord) {
  1845  			orderWeight *= 2
  1846  		}
  1847  		calcParcels := func(settlingWeight uint64) float64 {
  1848  			return m.parcels(user, settlingWeight+orderWeight)
  1849  		}
  1850  		if !m.checkParcelLimit(user, calcParcels) {
  1851  			log.Debugf("Received order %s that pushed user over the parcel limit", oid)
  1852  			errChan <- ErrQuantityTooHigh
  1853  			return nil
  1854  		}
  1855  	}
  1856  
  1857  	// Sign the order and prepare the client response. Only after the archiver
  1858  	// has successfully stored the new epoch order should the order be committed
  1859  	// for processing.
  1860  	respMsg, err := m.orderResponse(rec)
  1861  	if err != nil {
  1862  		log.Errorf("failed to create msgjson.Message for order %v, msgID %v response: %v",
  1863  			ord, rec.msgID, err)
  1864  		errChan <- ErrMalformedOrderResponse
  1865  		return nil
  1866  	}
  1867  
  1868  	// Ensure that the received order does not use locked coins.
  1869  	if lockedCoins, assetID := m.coinsLocked(ord); len(lockedCoins) > 0 {
  1870  		log.Debugf("processOrder: Order %v submitted with already-locked %s coins: %v",
  1871  			ord, strings.ToUpper(dex.BipIDSymbol(assetID)), fmtCoinIDs(assetID, lockedCoins))
  1872  		errChan <- ErrInvalidOrder
  1873  		return nil
  1874  	}
  1875  
  1876  	// For market and limit orders, lock the backing coins NOW so orders using
  1877  	// locked coins cannot get into the epoch queue. Later, in processReadyEpoch
  1878  	// or the Swapper, release these coins when the swap is completed.
  1879  	m.lockOrderCoins(ord)
  1880  
  1881  	// Check for known orders in the DB with the same Commitment.
  1882  	//
  1883  	// NOTE: This is disabled since (1) it may not scale as order history grows,
  1884  	// and (2) it is hard to see how this can be done by new servers in a mesh.
  1885  	// NOTE 2: Perhaps a better check would be commits with revealed preimages,
  1886  	// since a dedicated commit->preimage map or DB is conceivable.
  1887  	//
  1888  	// commitFound, prevOrderID, err := m.storage.OrderWithCommit(ctx, commit)
  1889  	// if err != nil {
  1890  	// 	errChan <- ErrInternalServer
  1891  	// 	return fmt.Errorf("processOrder: Failed to query for orders by commitment: %v", err)
  1892  	// }
  1893  	// if commitFound {
  1894  	// 	log.Debugf("processOrder: Order %v submitted with reused commitment %v "+
  1895  	// 		"from previous order %v", ord, commit, prevOrderID)
  1896  	// 	errChan <- ErrInvalidCommitment
  1897  	// 	return nil
  1898  	// }
  1899  
  1900  	// Store the new epoch order BEFORE inserting it into the epoch queue,
  1901  	// initiating the swap, and notifying book subscribers.
  1902  	if err := m.storage.NewEpochOrder(ord, epoch.Epoch, epoch.Duration, epochGap); err != nil {
  1903  		errChan <- ErrInternalServer
  1904  		return fmt.Errorf("processOrder: Failed to store new epoch order %v: %w",
  1905  			ord, err)
  1906  	}
  1907  
  1908  	// Insert the order into the epoch queue.
  1909  	epoch.Insert(ord)
  1910  
  1911  	m.epochMtx.Lock()
  1912  	m.epochOrders[oid] = ord
  1913  	m.epochCommitments[commit] = oid
  1914  	m.epochMtx.Unlock()
  1915  
  1916  	// Respond to the order router only after updating epochOrders so that
  1917  	// Cancelable will reflect that the order is now in the epoch queue.
  1918  	errChan <- nil
  1919  
  1920  	// Inform the client that the order has been received, stamped, signed, and
  1921  	// inserted into the current epoch queue.
  1922  	m.lazy(func() {
  1923  		if err := m.auth.Send(user, respMsg); err != nil {
  1924  			log.Infof("Failed to send signed new order response to user %v, order %v: %v",
  1925  				user, oid, err)
  1926  		}
  1927  	})
  1928  
  1929  	// Send epoch update to epoch queue subscribers.
  1930  	notifyChan <- &updateSignal{
  1931  		action: epochAction,
  1932  		data: sigDataEpochOrder{
  1933  			order:    ord,
  1934  			epochIdx: epoch.Epoch,
  1935  		},
  1936  	}
  1937  	// With the notification sent to subscribers, this order must be included in
  1938  	// the processing of this epoch.
  1939  	return nil
  1940  }
  1941  
  1942  func idToBytes(id [order.OrderIDSize]byte) []byte {
  1943  	return id[:]
  1944  }
  1945  
  1946  // respondError sends an rpcError to a user.
  1947  func (m *Market) respondError(id uint64, user account.AccountID, code int, errMsg string) {
  1948  	log.Debugf("sending error to user %v, code: %d, msg: %s", user, code, errMsg)
  1949  	msg, err := msgjson.NewResponse(id, nil, &msgjson.Error{
  1950  		Code:    code,
  1951  		Message: errMsg,
  1952  	})
  1953  	if err != nil {
  1954  		log.Errorf("error creating error response with message '%s': %v", msg, err)
  1955  	}
  1956  	if err := m.auth.Send(user, msg); err != nil {
  1957  		log.Infof("Failed to send %s error response (code = %d, msg = %s) to user %v: %v",
  1958  			msg.Route, code, errMsg, user, err)
  1959  	}
  1960  }
  1961  
  1962  // preimage request-response handling data
  1963  type piData struct {
  1964  	ord      order.Order
  1965  	preimage chan *order.Preimage
  1966  }
  1967  
  1968  // handlePreimageResp is to be used in the response callback function provided
  1969  // to AuthManager.Request for the preimage route.
  1970  func (m *Market) handlePreimageResp(msg *msgjson.Message, reqData *piData) {
  1971  	sendPI := func(pi *order.Preimage) {
  1972  		reqData.preimage <- pi
  1973  	}
  1974  
  1975  	var piResp msgjson.PreimageResponse
  1976  	resp, err := msg.Response()
  1977  	if err != nil {
  1978  		sendPI(nil)
  1979  		m.respondError(msg.ID, reqData.ord.User(), msgjson.RPCParseError,
  1980  			fmt.Sprintf("error parsing preimage notification response: %v", err))
  1981  		return
  1982  	}
  1983  	if resp.Error != nil {
  1984  		log.Warnf("Client failed to handle preimage request: %v", resp.Error)
  1985  		sendPI(nil)
  1986  		return
  1987  	}
  1988  	err = json.Unmarshal(resp.Result, &piResp)
  1989  	if err != nil {
  1990  		sendPI(nil)
  1991  		m.respondError(msg.ID, reqData.ord.User(), msgjson.RPCParseError,
  1992  			fmt.Sprintf("error parsing preimage response payload result: %v", err))
  1993  		return
  1994  	}
  1995  
  1996  	// Validate preimage length.
  1997  	if len(piResp.Preimage) != order.PreimageSize {
  1998  		sendPI(nil)
  1999  		m.respondError(msg.ID, reqData.ord.User(), msgjson.InvalidPreimage,
  2000  			fmt.Sprintf("invalid preimage length (%d byes)", len(piResp.Preimage)))
  2001  		return
  2002  	}
  2003  
  2004  	// Check that the preimage is the hash of the order commitment.
  2005  	var pi order.Preimage
  2006  	copy(pi[:], piResp.Preimage)
  2007  	piCommit := pi.Commit()
  2008  	if reqData.ord.Commitment() != piCommit {
  2009  		sendPI(nil)
  2010  		oc := reqData.ord.Commitment()
  2011  		m.respondError(msg.ID, reqData.ord.User(), msgjson.PreimageCommitmentMismatch,
  2012  			fmt.Sprintf("preimage hash %x does not match order commitment %x",
  2013  				piCommit[:], oc[:]))
  2014  		return
  2015  	}
  2016  
  2017  	// The preimage is good.
  2018  	log.Tracef("Good preimage received for order %v: %x", reqData.ord, pi)
  2019  	err = m.storage.StorePreimage(reqData.ord, pi)
  2020  	if err != nil {
  2021  		log.Errorf("StorePreimage: %v", err)
  2022  		// Fatal backend error. New swaps will not begin, but pass the preimage
  2023  		// along so that it does not appear as a miss to collectPreimages.
  2024  		m.respondError(msg.ID, reqData.ord.User(), msgjson.RPCInternalError,
  2025  			"internal server error")
  2026  	}
  2027  
  2028  	sendPI(&pi)
  2029  }
  2030  
  2031  // collectPreimages solicits preimages from the owners of each of the orders in
  2032  // the provided queue with a 'preimage' ntfn/request via AuthManager.Request,
  2033  // and returns the preimages contained in the client responses. This function
  2034  // can block for up to 20 seconds (piTimeout) to allow clients time to respond.
  2035  // Clients that fail to respond, or respond with invalid data (see
  2036  // handlePreimageResp), are counted as misses.
  2037  func (m *Market) collectPreimages(orders []order.Order) (cSum []byte, ordersRevealed []*matcher.OrderRevealed, misses []order.Order) {
  2038  	// Compute the commitment checksum for the order queue.
  2039  	cSum = matcher.CSum(orders)
  2040  
  2041  	// Request preimages from the clients.
  2042  	piTimeout := 20 * time.Second
  2043  	preimages := make(map[order.Order]chan *order.Preimage, len(orders))
  2044  	for _, ord := range orders {
  2045  		// Make the 'preimage' request.
  2046  		commit := ord.Commitment()
  2047  		piReqParams := &msgjson.PreimageRequest{
  2048  			OrderID:        idToBytes(ord.ID()),
  2049  			Commitment:     commit[:],
  2050  			CommitChecksum: cSum,
  2051  		}
  2052  		req, err := msgjson.NewRequest(comms.NextID(), msgjson.PreimageRoute, piReqParams)
  2053  		if err != nil {
  2054  			// This is likely an impossible condition, but it's not the client's
  2055  			// fault.
  2056  			log.Errorf("error creating preimage request: %v", err)
  2057  			// TODO: respond to client with server error.
  2058  			continue
  2059  		}
  2060  
  2061  		// The clients preimage response comes back via a channel, where nil
  2062  		// indicates client failure to respond, either due to disconnection or
  2063  		// no action.
  2064  		piChan := make(chan *order.Preimage, 1) // buffer so the link's in handler does not block
  2065  
  2066  		reqData := &piData{
  2067  			ord:      ord,
  2068  			preimage: piChan,
  2069  		}
  2070  
  2071  		// Failure to respond in time or an async link write error is a miss,
  2072  		// signalled by a nil pointer. Request errors returned by
  2073  		// RequestWithTimeout instead register a miss immediately.
  2074  		miss := func() { piChan <- nil }
  2075  
  2076  		// Send the preimage request to the order's owner.
  2077  		err = m.auth.RequestWithTimeout(ord.User(), req, func(_ comms.Link, msg *msgjson.Message) {
  2078  			m.handlePreimageResp(msg, reqData) // sends on piChan
  2079  		}, piTimeout, miss)
  2080  		if err != nil {
  2081  			if errors.Is(err, ws.ErrPeerDisconnected) || errors.Is(err, auth.ErrUserNotConnected) {
  2082  				log.Debugf("Preimage request failed, client gone: %v", err)
  2083  			} else {
  2084  				// We may need a way to identify server connectivity problems so
  2085  				// clients are not penalized when it is not their fault. For
  2086  				// now, log this at warning level since the error is not novel.
  2087  				log.Warnf("Preimage request failed: %v", err)
  2088  			}
  2089  
  2090  			// Register the miss now, no channel receive for this order.
  2091  			misses = append(misses, ord)
  2092  			continue
  2093  		}
  2094  
  2095  		log.Tracef("Preimage request sent for order %v", ord)
  2096  		preimages[ord] = piChan
  2097  	}
  2098  
  2099  	// Receive preimages from response channels.
  2100  	for ord, pic := range preimages {
  2101  		pi := <-pic
  2102  		if pi == nil {
  2103  			misses = append(misses, ord)
  2104  		} else {
  2105  			ordersRevealed = append(ordersRevealed, &matcher.OrderRevealed{
  2106  				Order:    ord,
  2107  				Preimage: *pi,
  2108  			})
  2109  		}
  2110  	}
  2111  
  2112  	return
  2113  }
  2114  
  2115  func (m *Market) enqueueEpoch(eq *epochPump, epoch *EpochQueue) bool {
  2116  	// Enqueue the epoch for matching when preimage collection is completed and
  2117  	// it is this epoch's turn.
  2118  	rq := eq.Insert(epoch)
  2119  	if rq == nil {
  2120  		// should not happen if cycleEpoch considers when the halt began.
  2121  		log.Errorf("failed to enqueue an epoch into a halted epoch pump")
  2122  		return false
  2123  	}
  2124  
  2125  	// With this epoch closed, these orders are no longer cancelable, if and
  2126  	// until they are booked in processReadyEpoch (after preimage collection).
  2127  	orders := epoch.OrderSlice()
  2128  	m.epochMtx.Lock()
  2129  	for _, ord := range orders {
  2130  		delete(m.epochOrders, ord.ID())
  2131  		delete(m.epochCommitments, ord.Commitment())
  2132  		// Would be nice to remove orders from users that got suspended, but the
  2133  		// epoch order notifications were sent to subscribers when the order was
  2134  		// received, thus setting expectations for auditing the queue.
  2135  		//
  2136  		// Preimage collection for suspended users could be skipped, forcing
  2137  		// them into the misses slice perhaps by passing user IDs to skip into
  2138  		// prepEpoch, with a SPEC UPDATE noting that preimage requests are not
  2139  		// sent to suspended accounts.
  2140  	}
  2141  	m.epochMtx.Unlock()
  2142  
  2143  	// Start preimage collection.
  2144  	go func() {
  2145  		rq.cSum, rq.ordersRevealed, rq.misses = m.prepEpoch(orders, epoch.End)
  2146  		close(rq.ready)
  2147  	}()
  2148  
  2149  	return true
  2150  }
  2151  
  2152  func (m *Market) sendRevokeOrderNote(oid order.OrderID, user account.AccountID) {
  2153  	// Send revoke_order notification to order owner.
  2154  	route := msgjson.RevokeOrderRoute
  2155  	log.Infof("Sending a '%s' notification to %v for order %v", route, user, oid)
  2156  	revMsg := &msgjson.RevokeOrder{
  2157  		OrderID: oid.Bytes(),
  2158  	}
  2159  	m.auth.Sign(revMsg)
  2160  	revNtfn, err := msgjson.NewNotification(route, revMsg)
  2161  	if err != nil {
  2162  		log.Errorf("Failed to create %s notification for order %v: %v", route, oid, err)
  2163  	} else {
  2164  		err = m.auth.Send(user, revNtfn)
  2165  		if err != nil {
  2166  			log.Debugf("Failed to send %s notification to user %v: %v", route, user, err)
  2167  		}
  2168  	}
  2169  }
  2170  
  2171  // prepEpoch collects order preimages, and penalizes users who fail to respond.
  2172  func (m *Market) prepEpoch(orders []order.Order, epochEnd time.Time) (cSum []byte, ordersRevealed []*matcher.OrderRevealed, misses []order.Order) {
  2173  	// Solicit the preimages for each order.
  2174  	cSum, ordersRevealed, misses = m.collectPreimages(orders)
  2175  	if len(orders) > 0 {
  2176  		log.Infof("Collected %d valid order preimages, missed %d. Commit checksum: %x",
  2177  			len(ordersRevealed), len(misses), cSum)
  2178  	}
  2179  
  2180  	for _, ord := range misses {
  2181  		oid, user := ord.ID(), ord.User()
  2182  		log.Infof("No preimage received for order %v from user %v. Recording violation and revoking order.",
  2183  			oid, user)
  2184  		// Unlock the order's coins locked in processOrder.
  2185  		m.unlockOrderCoins(ord) // could also be done in processReadyEpoch
  2186  		// Change the order status from orderStatusEpoch to orderStatusRevoked.
  2187  		coid, revTime, err := m.storage.RevokeOrder(ord)
  2188  		if err == nil {
  2189  			m.auth.RecordCancel(user, coid, oid, db.EpochGapNA, revTime)
  2190  		} else {
  2191  			log.Errorf("Failed to revoke order %v with a new cancel order: %v",
  2192  				ord.UID(), err)
  2193  		}
  2194  		// Register the preimage miss violation, adjusting the user's score.
  2195  		m.auth.MissedPreimage(user, epochEnd, oid)
  2196  		// The user is most likely offline, but it is possible they have
  2197  		// reconnected too late for the preimage request but after
  2198  		// storage.RevokeOrder updated the order status. Try to notify.
  2199  		go m.sendRevokeOrderNote(oid, user)
  2200  	}
  2201  
  2202  	// Register the preimage collection successes, potentially evicting preimage
  2203  	// miss violations for purposes of user scoring.
  2204  	for _, ord := range ordersRevealed {
  2205  		m.auth.PreimageSuccess(ord.Order.User(), epochEnd, ord.Order.ID())
  2206  	}
  2207  
  2208  	return
  2209  }
  2210  
  2211  // UnbookUserOrders unbooks all orders belonging to a user, unlocks the coins
  2212  // that were used to fund the unbooked orders, changes the orders' statuses to
  2213  // revoked in the DB, and notifies orderbook subscribers.
  2214  func (m *Market) UnbookUserOrders(user account.AccountID) {
  2215  	m.bookMtx.Lock()
  2216  	removedBuys, removedSells := m.book.RemoveUserOrders(user)
  2217  	// No order completion credit in SwapDone for revoked orders:
  2218  	for _, lo := range removedSells {
  2219  		delete(m.settling, lo.ID())
  2220  	}
  2221  	for _, lo := range removedBuys {
  2222  		delete(m.settling, lo.ID())
  2223  	}
  2224  	m.bookMtx.Unlock()
  2225  
  2226  	total := len(removedBuys) + len(removedSells)
  2227  	if total == 0 {
  2228  		return
  2229  	}
  2230  
  2231  	log.Infof("Unbooked %d orders (%d buys, %d sells) from market %v from user %v.",
  2232  		total, len(removedBuys), len(removedSells), m.marketInfo.Name, user)
  2233  
  2234  	// Unlock the order funding coins, update order statuses in DB, and notify
  2235  	// orderbook subscribers.
  2236  	sellIDs := make([]order.OrderID, 0, len(removedSells))
  2237  	for _, lo := range removedSells {
  2238  		sellIDs = append(sellIDs, lo.ID())
  2239  		m.unbookedOrder(lo)
  2240  	}
  2241  	if m.coinLockerBase != nil {
  2242  		m.coinLockerBase.UnlockOrdersCoins(sellIDs)
  2243  	}
  2244  
  2245  	buyIDs := make([]order.OrderID, 0, len(removedBuys))
  2246  	for _, lo := range removedBuys {
  2247  		buyIDs = append(buyIDs, lo.ID())
  2248  		m.unbookedOrder(lo)
  2249  	}
  2250  	if m.coinLockerQuote != nil {
  2251  		m.coinLockerQuote.UnlockOrdersCoins(buyIDs)
  2252  	}
  2253  }
  2254  
  2255  // Unbook allows the DEX manager to remove a booked order. This does: (1) remove
  2256  // the order from the in-memory book, (2) unlock funding order coins, (3) set
  2257  // the order's status in the DB to "revoked", (4) inform the auth manager of the
  2258  // action for cancellation ratio accounting, and (5) send an 'unbook'
  2259  // notification to subscribers of this market's order book. Note that this
  2260  // presently treats the user as at-fault by counting the revocation in the
  2261  // user's cancellation statistics.
  2262  func (m *Market) Unbook(lo *order.LimitOrder) bool {
  2263  	// Ensure we do not unbook during matching.
  2264  	m.bookMtx.Lock()
  2265  	_, removed := m.book.Remove(lo.ID())
  2266  	delete(m.settling, lo.ID()) // no order completion credit in SwapDone for revoked orders
  2267  	m.bookMtx.Unlock()
  2268  
  2269  	m.unlockOrderCoins(lo)
  2270  
  2271  	if removed {
  2272  		// Update the order status in DB, and notify orderbook subscribers.
  2273  		m.unbookedOrder(lo)
  2274  	}
  2275  	return removed
  2276  }
  2277  
  2278  func (m *Market) unbookedOrder(lo *order.LimitOrder) {
  2279  	// Create the server-generated cancel order, and register it with the
  2280  	// AuthManager for cancellation rate computation if still connected.
  2281  	oid, user := lo.ID(), lo.User()
  2282  	coid, revTime, err := m.storage.RevokeOrder(lo)
  2283  	if err == nil {
  2284  		m.auth.RecordCancel(user, coid, oid, db.EpochGapNA, revTime)
  2285  	} else {
  2286  		log.Errorf("Failed to revoke order %v with a new cancel order: %v",
  2287  			lo.UID(), err)
  2288  	}
  2289  
  2290  	// Send revoke_order notification to order owner.
  2291  	m.sendRevokeOrderNote(oid, user)
  2292  
  2293  	// Send "unbook" notification to order book subscribers.
  2294  	m.sendToFeeds(&updateSignal{
  2295  		action: unbookAction,
  2296  		data: sigDataUnbookedOrder{
  2297  			order:    lo,
  2298  			epochIdx: -1, // NOTE: no epoch
  2299  		},
  2300  	})
  2301  }
  2302  
  2303  // getFeeRate gets the fee rate for an asset.
  2304  func (m *Market) getFeeRate(assetID uint32, f FeeFetcher) uint64 {
  2305  	// Do not block indefinitely waiting for fetcher.
  2306  	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
  2307  	defer cancel()
  2308  	rate := f.SwapFeeRate(ctx)
  2309  	if ctx.Err() != nil { // timeout, try last known rate
  2310  		rate = f.LastRate()
  2311  		log.Warnf("Failed to get latest fee rate for %v. Using last known rate %d.",
  2312  			dex.BipIDSymbol(assetID), rate)
  2313  	}
  2314  	rate = m.ScaleFeeRate(assetID, rate)
  2315  	if rate > f.MaxFeeRate() || rate == 0 {
  2316  		rate = f.MaxFeeRate()
  2317  	}
  2318  	return rate
  2319  }
  2320  
  2321  // processReadyEpoch performs the following operations for a closed epoch that
  2322  // has finished preimage collection via collectPreimages:
  2323  //  1. Perform matching with the order book.
  2324  //  2. Send book and unbook notifications to the book subscribers.
  2325  //  3. Unlock coins with the book lock for unbooked and failed orders.
  2326  //  4. Lock coins with the swap lock.
  2327  //  5. Initiate the swap negotiation via the Market's Swapper.
  2328  //
  2329  // The EpochQueue's Orders map must not be modified by another goroutine.
  2330  func (m *Market) processReadyEpoch(epoch *readyEpoch, notifyChan chan<- *updateSignal) {
  2331  	// Ensure the epoch has actually completed preimage collection. This can
  2332  	// only fail if the epochPump malfunctioned. Remove this check eventually.
  2333  	select {
  2334  	case <-epoch.ready:
  2335  	default:
  2336  		log.Criticalf("preimages not yet collected for epoch %d!", epoch.Epoch)
  2337  		return // maybe panic
  2338  	}
  2339  
  2340  	// Abort epoch processing if there was a fatal DB backend error during
  2341  	// preimage collection.
  2342  	if err := m.storage.LastErr(); err != nil {
  2343  		log.Criticalf("aborting epoch processing on account of failing DB: %v", err)
  2344  		return
  2345  	}
  2346  
  2347  	// Get the base and quote fee rates.
  2348  	// NOTE: We might consider moving this before the match cycle and abandoning
  2349  	// the match cycle when no fee rate can be found (on mainnet). The only
  2350  	// hesitation there is that it makes certain maintenance tasks longer but
  2351  	// that's not unexpected in the world of cryptocurrency exchanges. It also
  2352  	// makes it harder to fire up a private DEX server to conduct a private
  2353  	// trade, but even in that case, I wouldn't want to be matching at the
  2354  	// fallback MaxFeeRate when the justifiable network rate is much lower. I do
  2355  	// remember some minor discussion of this at some point in the past, but I'd
  2356  	// like to bring it back.
  2357  	feeRateBase := m.getFeeRate(m.Base(), m.baseFeeFetcher)
  2358  	feeRateQuote := m.getFeeRate(m.Quote(), m.quoteFeeFetcher)
  2359  
  2360  	// Data from preimage collection
  2361  	ordersRevealed := epoch.ordersRevealed
  2362  	cSum := epoch.cSum
  2363  	misses := epoch.misses
  2364  
  2365  	// We can't call RecordCancel under the bookMtx since it can potentially
  2366  	// trigger a user suspension and unbooking via UnbookUserOrders, which locks
  2367  	// the bookMtx. So we'll track the info necessary to call RecordCancel and
  2368  	// call them after the matches loop.
  2369  	type cancelMatch struct {
  2370  		co      *order.CancelOrder
  2371  		loEpoch int64
  2372  	}
  2373  	cancelMatches := make([]cancelMatch, 0)
  2374  
  2375  	// Perform order matching using the preimages to shuffle the queue.
  2376  	m.bookMtx.Lock()        // allow a coherent view of book orders with (*Market).Book
  2377  	matchTime := time.Now() // considered as the time at which matched cancel orders are executed
  2378  	seed, matches, _, failed, doneOK, partial, booked, nomatched, unbooked, updates, stats := m.matcher.Match(m.book, ordersRevealed)
  2379  	m.bookEpochIdx = epoch.Epoch + 1
  2380  	epochDur := int64(m.EpochDuration())
  2381  	var canceled []order.OrderID
  2382  	for _, ms := range matches {
  2383  		// Set the epoch ID.
  2384  		ms.Epoch.Idx = uint64(epoch.Epoch)
  2385  		ms.Epoch.Dur = uint64(epoch.Duration)
  2386  		ms.FeeRateBase = feeRateBase
  2387  		ms.FeeRateQuote = feeRateQuote
  2388  
  2389  		// Update order settling amounts.
  2390  		for _, match := range ms.Matches() {
  2391  			if co, ok := match.Taker.(*order.CancelOrder); ok {
  2392  				canceled = append(canceled, co.TargetOrderID)
  2393  				cancelMatches = append(cancelMatches, cancelMatch{
  2394  					co:      co,
  2395  					loEpoch: match.Maker.ServerTime.UnixMilli() / epochDur,
  2396  				})
  2397  				continue
  2398  			}
  2399  			m.settling[match.Taker.ID()] += match.Quantity
  2400  			m.settling[match.Maker.ID()] += match.Quantity
  2401  		}
  2402  	}
  2403  	for _, oid := range canceled {
  2404  		// There may still be swaps settling, but we don't care anymore because
  2405  		// there is no completion credit on a canceled order.
  2406  		delete(m.settling, oid)
  2407  	}
  2408  	m.bookMtx.Unlock()
  2409  
  2410  	if len(ordersRevealed) > 0 {
  2411  		log.Infof("Matching complete for market %v epoch %d:"+
  2412  			" %d matches (%d partial fills), %d completed OK (not booked),"+
  2413  			" %d booked, %d unbooked, %d failed",
  2414  			m.marketInfo.Name, epoch.Epoch,
  2415  			len(matches), len(partial), len(doneOK),
  2416  			len(booked), len(unbooked), len(failed),
  2417  		)
  2418  	}
  2419  
  2420  	// Store data in epochs table, including matchTime so that cancel execution
  2421  	// times can be obtained from the DB for cancellation rate computation.
  2422  	oidsRevealed := make([]order.OrderID, 0, len(ordersRevealed))
  2423  	for _, or := range ordersRevealed {
  2424  		oidsRevealed = append(oidsRevealed, or.Order.ID())
  2425  	}
  2426  	oidsMissed := make([]order.OrderID, 0, len(misses))
  2427  	for _, om := range misses {
  2428  		oidsMissed = append(oidsMissed, om.ID())
  2429  	}
  2430  
  2431  	// If there were no matches, we need to persist that last rate from the last
  2432  	// match recorded.
  2433  	if stats.EndRate == 0 {
  2434  		stats.EndRate = m.lastRate
  2435  		stats.StartRate = m.lastRate
  2436  		stats.HighRate = m.lastRate
  2437  		stats.LowRate = m.lastRate
  2438  	} else {
  2439  		m.lastRate = stats.EndRate
  2440  	}
  2441  
  2442  	err := m.storage.InsertEpoch(&db.EpochResults{
  2443  		MktBase:        m.marketInfo.Base,
  2444  		MktQuote:       m.marketInfo.Quote,
  2445  		Idx:            epoch.Epoch,
  2446  		Dur:            epoch.Duration,
  2447  		MatchTime:      matchTime.UnixMilli(),
  2448  		CSum:           cSum,
  2449  		Seed:           seed,
  2450  		OrdersRevealed: oidsRevealed,
  2451  		OrdersMissed:   oidsMissed,
  2452  		MatchVolume:    stats.MatchVolume,
  2453  		QuoteVolume:    stats.QuoteVolume,
  2454  		BookBuys:       stats.BookBuys,
  2455  		BookBuys5:      stats.BookBuys5,
  2456  		BookBuys25:     stats.BookBuys25,
  2457  		BookSells:      stats.BookSells,
  2458  		BookSells5:     stats.BookSells5,
  2459  		BookSells25:    stats.BookSells25,
  2460  		HighRate:       stats.HighRate,
  2461  		LowRate:        stats.LowRate,
  2462  		StartRate:      stats.StartRate,
  2463  		EndRate:        stats.EndRate,
  2464  	})
  2465  	if err != nil {
  2466  		// fatal backend error, do not begin new swaps.
  2467  		return // TODO: notify clients
  2468  	}
  2469  
  2470  	// Note: validated preimages are stored in the orders/cancels tables on
  2471  	// receipt from the user by handlePreimageResp.
  2472  
  2473  	// Update orders in persistent storage. Trade orders may appear in multiple
  2474  	// trade order slices, so update in the sequence: booked, partial, completed
  2475  	// or canceled. However, an order in the failed slice will not be in another
  2476  	// slice since failed indicates unmatched&unbooked or bad lot size.
  2477  	//
  2478  	// TODO: Only execute the net effect. Each status update also updates the
  2479  	// filled amount of the trade order.
  2480  	//
  2481  	// Cancel order status updates are from epoch to executed or failed status.
  2482  
  2483  	// Newly-booked orders.
  2484  	for _, lo := range updates.TradesBooked {
  2485  		if err = m.storage.BookOrder(lo); err != nil {
  2486  			return
  2487  		}
  2488  	}
  2489  
  2490  	// Book orders that were partially filled and remain on the books.
  2491  	for _, lo := range updates.TradesPartial {
  2492  		if err = m.storage.UpdateOrderFilled(lo); err != nil {
  2493  			return
  2494  		}
  2495  	}
  2496  
  2497  	// Completed orders (includes epoch and formerly booked orders).
  2498  	for _, ord := range updates.TradesCompleted {
  2499  		if err = m.storage.ExecuteOrder(ord); err != nil {
  2500  			return
  2501  		}
  2502  	}
  2503  	// Canceled orders.
  2504  	for _, lo := range updates.TradesCanceled {
  2505  		if err = m.storage.CancelOrder(lo); err != nil {
  2506  			return
  2507  		}
  2508  	}
  2509  	// Failed orders refer to epoch queue orders that are unmatched&unbooked, or
  2510  	// had a bad lot size.
  2511  	for _, ord := range updates.TradesFailed {
  2512  		if err = m.storage.ExecuteOrder(ord); err != nil {
  2513  			return
  2514  		}
  2515  	}
  2516  
  2517  	// Change cancel orders from epoch status to executed or failed status.
  2518  	for _, co := range updates.CancelsFailed {
  2519  		if err = m.storage.FailCancelOrder(co); err != nil {
  2520  			return
  2521  		}
  2522  	}
  2523  	for _, co := range updates.CancelsExecuted {
  2524  		if err = m.storage.ExecuteOrder(co); err != nil {
  2525  			return
  2526  		}
  2527  	}
  2528  
  2529  	// Signal the match_proof to the orderbook subscribers.
  2530  	preimages := make([]order.Preimage, len(ordersRevealed))
  2531  	for i := range ordersRevealed {
  2532  		preimages[i] = ordersRevealed[i].Preimage
  2533  	}
  2534  	sig := &updateSignal{
  2535  		action: matchProofAction,
  2536  		data: sigDataMatchProof{
  2537  			matchProof: &order.MatchProof{
  2538  				Epoch: order.EpochID{
  2539  					Idx: uint64(epoch.Epoch),
  2540  					Dur: m.EpochDuration(),
  2541  				},
  2542  				Preimages: preimages,
  2543  				Misses:    misses,
  2544  				CSum:      cSum,
  2545  				Seed:      seed,
  2546  			},
  2547  		},
  2548  	}
  2549  	notifyChan <- sig
  2550  
  2551  	// Unlock passed but not booked order (e.g. matched market and immediate
  2552  	// orders) coins were locked upon order receipt in processOrder and must be
  2553  	// unlocked now since they do not go on the book.
  2554  	for _, k := range doneOK {
  2555  		m.unlockOrderCoins(k.Order)
  2556  	}
  2557  
  2558  	// Unlock unmatched (failed) order coins.
  2559  	for _, fo := range failed {
  2560  		m.unlockOrderCoins(fo.Order)
  2561  	}
  2562  
  2563  	// Booked order coins were locked upon receipt by processOrder, and remain
  2564  	// locked until they are either: unbooked by a future match that completely
  2565  	// fills the order, unbooked by a matched cancel order, or (unimplemented)
  2566  	// unbooked by another Market mechanism such as client disconnect or ban.
  2567  
  2568  	// Unlock unbooked order coins.
  2569  	for _, ubo := range unbooked {
  2570  		m.unlockOrderCoins(ubo)
  2571  	}
  2572  
  2573  	// Send "book" notifications to order book subscribers.
  2574  	for _, ord := range booked {
  2575  		sig := &updateSignal{
  2576  			action: bookAction,
  2577  			data: sigDataBookedOrder{
  2578  				order:    ord.Order,
  2579  				epochIdx: epoch.Epoch,
  2580  			},
  2581  		}
  2582  		notifyChan <- sig
  2583  	}
  2584  
  2585  	// Send "update_remaining" notifications to order book subscribers.
  2586  	for _, lo := range updates.TradesPartial {
  2587  		notifyChan <- &updateSignal{
  2588  			action: updateRemainingAction,
  2589  			data: sigDataUpdateRemaining{
  2590  				order:    lo,
  2591  				epochIdx: epoch.Epoch,
  2592  			},
  2593  		}
  2594  	}
  2595  
  2596  	// Send "unbook" notifications to order book subscribers. This must be after
  2597  	// update_remaining.
  2598  	for _, ord := range unbooked {
  2599  		sig := &updateSignal{
  2600  			action: unbookAction,
  2601  			data: sigDataUnbookedOrder{
  2602  				order:    ord,
  2603  				epochIdx: epoch.Epoch,
  2604  			},
  2605  		}
  2606  		notifyChan <- sig
  2607  	}
  2608  
  2609  	for _, c := range cancelMatches {
  2610  		co, loEpoch := c.co, c.loEpoch
  2611  		epochGap := int32((co.ServerTime.UnixMilli() / epochDur) - loEpoch)
  2612  		m.auth.RecordCancel(co.User(), co.ID(), co.TargetOrderID, epochGap, matchTime)
  2613  	}
  2614  
  2615  	// Send "nomatch" notifications.
  2616  	for _, ord := range nomatched {
  2617  		oid := ord.Order.ID()
  2618  		msg, err := msgjson.NewNotification(msgjson.NoMatchRoute, &msgjson.NoMatch{
  2619  			OrderID: oid[:],
  2620  		})
  2621  		if err != nil {
  2622  			// This is probably impossible in practice, but we'll log it anyway.
  2623  			log.Errorf("Failed to encode 'nomatch' notification.")
  2624  			continue
  2625  		}
  2626  		if err := m.auth.Send(ord.Order.User(), msg); err != nil {
  2627  			log.Infof("Failed to send nomatch to user %s: %v", ord.Order.User(), err)
  2628  		}
  2629  	}
  2630  
  2631  	// Update the API data collector.
  2632  	spot, err := m.dataCollector.ReportEpoch(m.Base(), m.Quote(), uint64(epoch.Epoch), stats)
  2633  	if err != nil {
  2634  		log.Errorf("Error updating API data collector: %v", err)
  2635  	}
  2636  
  2637  	matchReport := make([][2]int64, 0, len(matches))
  2638  	var lastRate uint64
  2639  	var lastSide bool
  2640  	for _, matchSet := range matches {
  2641  		for _, match := range matchSet.Matches() {
  2642  			t := match.Taker.Trade()
  2643  			if t == nil {
  2644  				continue
  2645  			}
  2646  			if match.Rate != lastRate || t.Sell != lastSide {
  2647  				matchReport = append(matchReport, [2]int64{int64(match.Rate), 0})
  2648  				lastRate, lastSide = match.Rate, t.Sell
  2649  			}
  2650  			if t.Sell {
  2651  				matchReport[len(matchReport)-1][1] += int64(match.Quantity)
  2652  			} else {
  2653  				matchReport[len(matchReport)-1][1] -= int64(match.Quantity)
  2654  			}
  2655  		}
  2656  	}
  2657  	// Send "epoch_report" notifications.
  2658  	notifyChan <- &updateSignal{
  2659  		action: epochReportAction,
  2660  		data: sigDataEpochReport{
  2661  			epochIdx:     epoch.Epoch,
  2662  			epochDur:     epoch.Duration,
  2663  			spot:         spot,
  2664  			stats:        stats,
  2665  			baseFeeRate:  feeRateBase,
  2666  			quoteFeeRate: feeRateQuote,
  2667  			matches:      matchReport,
  2668  		},
  2669  	}
  2670  
  2671  	// Initiate the swaps.
  2672  	if len(matches) > 0 {
  2673  		log.Debugf("Negotiating %d matches for epoch %d:%d", len(matches),
  2674  			epoch.Epoch, epoch.Duration)
  2675  		m.swapper.Negotiate(matches)
  2676  	}
  2677  }
  2678  
  2679  // validateOrder uses db.ValidateOrder to ensure that the provided order is
  2680  // valid for the current market with epoch order status.
  2681  func (m *Market) validateOrder(ord order.Order) error {
  2682  	// First check the order commitment before bothering the Market's run loop.
  2683  	c0 := order.Commitment{}
  2684  	if ord.Commitment() == c0 {
  2685  		// Note that OrderID may not be valid if ServerTime has not been set.
  2686  		return ErrInvalidCommitment
  2687  	}
  2688  
  2689  	if !db.ValidateOrder(ord, order.OrderStatusEpoch, m.marketInfo) {
  2690  		return ErrInvalidOrder // non-specific
  2691  	}
  2692  
  2693  	if lo, is := ord.(*order.LimitOrder); is && lo.Rate < m.minimumRate {
  2694  		return ErrInvalidRate
  2695  	}
  2696  
  2697  	return nil
  2698  }
  2699  
  2700  // orderResponse signs the order data and prepares the OrderResult to be sent to
  2701  // the client.
  2702  func (m *Market) orderResponse(oRecord *orderRecord) (*msgjson.Message, error) {
  2703  	// Add the server timestamp.
  2704  	stamp := uint64(oRecord.order.Time())
  2705  	oRecord.req.Stamp(stamp)
  2706  
  2707  	// Sign the serialized order request.
  2708  	m.auth.Sign(oRecord.req)
  2709  
  2710  	// Prepare the OrderResult, including the server signature and time stamp.
  2711  	oid := oRecord.order.ID()
  2712  	res := &msgjson.OrderResult{
  2713  		Sig:        oRecord.req.SigBytes(),
  2714  		OrderID:    oid[:],
  2715  		ServerTime: stamp,
  2716  	}
  2717  
  2718  	// Encode the order response as a message for the client.
  2719  	return msgjson.NewResponse(oRecord.msgID, res, nil)
  2720  }
  2721  
  2722  // SetFeeRateScale sets a swap fee scale factor for the given asset.
  2723  // SetFeeRateScale should be called regardless of whether the Market is
  2724  // suspended.
  2725  func (m *Market) SetFeeRateScale(assetID uint32, scale float64) {
  2726  	m.feeScalesMtx.Lock()
  2727  	switch assetID {
  2728  	case m.marketInfo.Base:
  2729  		m.feeScales.base = scale
  2730  	case m.marketInfo.Quote:
  2731  		m.feeScales.quote = scale
  2732  	default:
  2733  		log.Errorf("Unknown asset ID %d for market %d-%d",
  2734  			assetID, m.marketInfo.Base, m.marketInfo.Quote)
  2735  	}
  2736  	m.feeScalesMtx.Unlock()
  2737  }
  2738  
  2739  // ScaleFeeRate scales the provided fee rate with the given asset's swap fee
  2740  // rate scale factor, which is 1.0 by default.
  2741  func (m *Market) ScaleFeeRate(assetID uint32, feeRate uint64) uint64 {
  2742  	if feeRate == 0 {
  2743  		return feeRate // no idea if this is sensible for any asset, but ok
  2744  	}
  2745  	var feeScale float64
  2746  	m.feeScalesMtx.RLock()
  2747  	switch assetID {
  2748  	case m.marketInfo.Base:
  2749  		feeScale = m.feeScales.base
  2750  	default:
  2751  		feeScale = m.feeScales.quote
  2752  	}
  2753  	m.feeScalesMtx.RUnlock()
  2754  	if feeScale == 0 {
  2755  		return feeRate
  2756  	}
  2757  	if feeScale < 1 {
  2758  		log.Warnf("Using fee rate scale of %f < 1.0 for asset %d", feeScale, assetID)
  2759  	}
  2760  	// It started non-zero, so don't allow it to go to zero.
  2761  	return uint64(math.Max(1.0, math.Round(float64(feeRate)*feeScale)))
  2762  }
  2763  
  2764  type accountStats struct {
  2765  	qty, lots uint64
  2766  	redeems   int
  2767  }
  2768  
  2769  type accountCounter map[string]*accountStats
  2770  
  2771  func (a accountCounter) add(addr string, qty, lots uint64, redeems int) {
  2772  	stats, found := a[addr]
  2773  	if !found {
  2774  		stats = new(accountStats)
  2775  		a[addr] = stats
  2776  	}
  2777  	stats.qty += qty
  2778  	stats.lots += lots
  2779  	stats.redeems += redeems
  2780  }