decred.org/dcrdex@v1.0.5/server/swap/swap.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 swap
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"errors"
    10  	"fmt"
    11  	"sync"
    12  	"sync/atomic"
    13  	"time"
    14  
    15  	"decred.org/dcrdex/dex"
    16  	"decred.org/dcrdex/dex/calc"
    17  	"decred.org/dcrdex/dex/encode"
    18  	"decred.org/dcrdex/dex/meter"
    19  	"decred.org/dcrdex/dex/msgjson"
    20  	"decred.org/dcrdex/dex/order"
    21  	"decred.org/dcrdex/dex/wait"
    22  	"decred.org/dcrdex/server/account"
    23  	"decred.org/dcrdex/server/asset"
    24  	"decred.org/dcrdex/server/auth"
    25  	"decred.org/dcrdex/server/coinlock"
    26  	"decred.org/dcrdex/server/comms"
    27  	"decred.org/dcrdex/server/db"
    28  	"decred.org/dcrdex/server/matcher"
    29  )
    30  
    31  var (
    32  	// The coin waiter will initially query for transaction data every
    33  	// fastRecheckInterval, but will eventually taper to taperedRecheckInterval.
    34  	fastRecheckInterval    = time.Second * 3
    35  	taperedRecheckInterval = time.Second * 30
    36  	// minBlockPeriod is the minimum delay between block-triggered
    37  	// confirmation/inaction checks. This helps with limiting notification
    38  	// bursts when blocks are generated closely together (e.g. in Ethereum
    39  	// occasionally several blocks are generated in a single second).
    40  	minBlockPeriod = time.Second * 10
    41  )
    42  
    43  func unixMsNow() time.Time {
    44  	return time.Now().Truncate(time.Millisecond).UTC()
    45  }
    46  
    47  func makerTaker(isMaker bool) string {
    48  	if isMaker {
    49  		return "maker"
    50  	}
    51  	return "taker"
    52  }
    53  
    54  // AuthManager handles client-related actions, including authorization and
    55  // communications.
    56  type AuthManager interface {
    57  	Route(string, func(account.AccountID, *msgjson.Message) *msgjson.Error)
    58  	Auth(user account.AccountID, msg, sig []byte) error
    59  	Sign(...msgjson.Signable)
    60  	Send(account.AccountID, *msgjson.Message) error
    61  	Request(account.AccountID, *msgjson.Message, func(comms.Link, *msgjson.Message)) error
    62  	RequestWithTimeout(user account.AccountID, req *msgjson.Message, handlerFunc func(comms.Link, *msgjson.Message),
    63  		expireTimeout time.Duration, expireFunc func()) error
    64  	SwapSuccess(user account.AccountID, mmid db.MarketMatchID, value uint64, refTime time.Time)
    65  	Inaction(user account.AccountID, misstep auth.NoActionStep, mmid db.MarketMatchID, matchValue uint64, refTime time.Time, oid order.OrderID)
    66  }
    67  
    68  // Storage updates match data in what is presumably a database.
    69  type Storage interface {
    70  	db.SwapArchiver
    71  	LastErr() error
    72  	Fatal() <-chan struct{}
    73  	Order(oid order.OrderID, base, quote uint32) (order.Order, order.OrderStatus, error)
    74  	CancelOrder(*order.LimitOrder) error
    75  	InsertMatch(match *order.Match) error
    76  }
    77  
    78  // swapStatus is information related to the completion or incompletion of each
    79  // sequential step of the atomic swap negotiation process. Each user has their
    80  // own swapStatus.
    81  type swapStatus struct {
    82  	// The asset to which the user broadcasts their swap transaction.
    83  	swapAsset   uint32
    84  	redeemAsset uint32
    85  
    86  	swapSearching   uint32 // atomic
    87  	redeemSearching uint32 // atomic
    88  
    89  	mtx sync.RWMutex
    90  	// The time that the swap coordinator sees the transaction.
    91  	swapTime time.Time
    92  	swap     *asset.Contract
    93  	// The time that the transaction receives its SwapConf'th confirmation.
    94  	swapConfirmed time.Time
    95  	// The time that the swap coordinator sees the user's redemption
    96  	// transaction.
    97  	redeemTime time.Time
    98  	redemption asset.Coin
    99  }
   100  
   101  // String satisfies the Stringer interface for pretty printing. The swapStatus
   102  // RWMutex should be held for reads when using.
   103  func (ss *swapStatus) String() string {
   104  	return fmt.Sprintf("swapAsset: %d, redeemAsset: %d, swapTime: %v, swap: %v, swapConfirmed: %v, redeemTime: %v, redemption: %v",
   105  		ss.swapAsset, ss.redeemAsset, ss.swapTime, ss.swap, ss.swapConfirmed, ss.redeemTime, ss.redemption)
   106  }
   107  
   108  func (ss *swapStatus) startSwapSearch() bool {
   109  	return atomic.CompareAndSwapUint32(&ss.swapSearching, 0, 1)
   110  }
   111  
   112  func (ss *swapStatus) endSwapSearch() {
   113  	atomic.StoreUint32(&ss.swapSearching, 0)
   114  }
   115  
   116  func (ss *swapStatus) startRedeemSearch() bool {
   117  	return atomic.CompareAndSwapUint32(&ss.redeemSearching, 0, 1)
   118  }
   119  
   120  func (ss *swapStatus) endRedeemSearch() {
   121  	atomic.StoreUint32(&ss.redeemSearching, 0)
   122  }
   123  
   124  func (ss *swapStatus) swapConfTime() time.Time {
   125  	ss.mtx.RLock()
   126  	defer ss.mtx.RUnlock()
   127  	return ss.swapConfirmed
   128  }
   129  
   130  func (ss *swapStatus) contractState() (known, confirmed bool) {
   131  	ss.mtx.RLock()
   132  	defer ss.mtx.RUnlock()
   133  	return ss.swap != nil, !ss.swapConfirmed.IsZero()
   134  }
   135  
   136  func (ss *swapStatus) redeemSeenTime() time.Time {
   137  	ss.mtx.RLock()
   138  	defer ss.mtx.RUnlock()
   139  	return ss.redeemTime
   140  }
   141  
   142  // matchTracker embeds an order.Match and adds some data necessary for tracking
   143  // the match negotiation.
   144  type matchTracker struct {
   145  	mtx sync.RWMutex // Match.Sigs and Match.Status
   146  	*order.Match
   147  	time        time.Time // the match request time, not epoch close
   148  	matchTime   time.Time // epoch close time
   149  	makerStatus *swapStatus
   150  	takerStatus *swapStatus
   151  }
   152  
   153  // expiredBy returns true if the lock time of either party's *known* swap is
   154  // before the reference time e.g. time.Now().
   155  func (mt *matchTracker) expiredBy(ref time.Time) bool {
   156  	mSwap, tSwap := mt.makerStatus.swap, mt.takerStatus.swap
   157  	return (tSwap != nil && tSwap.LockTime.Before(ref)) ||
   158  		(mSwap != nil && mSwap.LockTime.Before(ref))
   159  }
   160  
   161  // A blockNotification is used internally when an asset.Backend reports a new
   162  // block.
   163  type blockNotification struct {
   164  	time    time.Time
   165  	assetID uint32
   166  	err     error
   167  }
   168  
   169  // A stepActor is a structure holding information about one party of a match.
   170  // stepActor is used with the stepInformation structure, which is used for
   171  // sequencing swap negotiation.
   172  type stepActor struct {
   173  	user account.AccountID
   174  	// swapAsset is the asset to which this actor broadcasts their swap tx.
   175  	swapAsset uint32
   176  	isMaker   bool
   177  	order     order.Order
   178  	// The swapStatus from the Match. Could be either the
   179  	// (matchTracker).makerStatus or (matchTracker).takerStatus, depending on who
   180  	// this actor is.
   181  	status *swapStatus
   182  }
   183  
   184  // String satisfies the Stringer interface for pretty printing. The swapStatus
   185  // RWMutex should be held for reads when using for a.status reads.
   186  func (a stepActor) String() string {
   187  	return fmt.Sprintf("user: %v, swapAsset: %v, isMaker: %v, order: %v, status: {%v}",
   188  		a.user, a.swapAsset, a.isMaker, a.order, a.status)
   189  }
   190  
   191  // stepInformation holds information about the current state of the swap
   192  // negotiation. A new stepInformation should be generated with (Swapper).step at
   193  // every step of the negotiation process.
   194  type stepInformation struct {
   195  	match *matchTracker
   196  	// The actor is the user info for the user who is expected to be broadcasting
   197  	// a swap or redemption transaction next.
   198  	actor stepActor
   199  	// counterParty is the user that is not expected to be acting next.
   200  	counterParty stepActor
   201  	// asset is the asset backend for swapAsset.
   202  	asset *asset.BackedAsset
   203  	// isBaseAsset will be true if the current step involves a transaction on the
   204  	// match market's base asset blockchain, false if on quote asset's blockchain.
   205  	isBaseAsset bool
   206  	step        order.MatchStatus
   207  	nextStep    order.MatchStatus
   208  	// checkVal holds the trade amount in units of the currently acting asset,
   209  	// and is used to validate the swap transaction details.
   210  	checkVal uint64
   211  }
   212  
   213  // SwapperAsset is a BackedAsset with an optional CoinLocker.
   214  type SwapperAsset struct {
   215  	*asset.BackedAsset
   216  	Locker coinlock.CoinLocker // should be *coinlock.AssetCoinLocker
   217  }
   218  
   219  // Swapper handles order matches by handling authentication and inter-party
   220  // communications between clients, or 'users'. The Swapper authenticates users
   221  // (vua AuthManager) and validates transactions as they are reported.
   222  type Swapper struct {
   223  	// coins is a map to all the Asset information, including the asset backends,
   224  	// used by this Swapper.
   225  	coins map[uint32]*SwapperAsset
   226  	// storage is a Database backend.
   227  	storage Storage
   228  	// authMgr is an AuthManager for client messaging and authentication.
   229  	authMgr AuthManager
   230  	// swapDone is callback for reporting a swap outcome.
   231  	swapDone func(oid order.Order, match *order.Match, fail bool)
   232  
   233  	// The matches maps and the contained matches are protected by the matchMtx.
   234  	matchMtx    sync.RWMutex
   235  	matches     map[order.MatchID]*matchTracker
   236  	userMatches map[account.AccountID]map[order.MatchID]*matchTracker
   237  	acctMatches map[uint32]map[string]map[order.MatchID]*matchTracker
   238  
   239  	// The broadcast timeout.
   240  	bTimeout time.Duration
   241  	// txWaitExpiration is the longest the Swapper will wait for a coin waiter.
   242  	txWaitExpiration time.Duration
   243  	// Expected locktimes for maker and taker swaps.
   244  	lockTimeTaker time.Duration
   245  	lockTimeMaker time.Duration
   246  	// latencyQ is a queue for coin waiters to deal with network latency.
   247  	latencyQ *wait.TaperingTickerQueue
   248  
   249  	// handlerMtx should be read-locked for the duration of the comms route
   250  	// handlers (handleInit and handleRedeem) and Negotiate. This blocks
   251  	// shutdown until any coin waiters are registered with latencyQ. It should
   252  	// be write-locked before setting the stop flag.
   253  	handlerMtx sync.RWMutex
   254  	// stop is used to prevent new handlers from starting coin waiters. It is
   255  	// set to true during shutdown of Run.
   256  	stop bool
   257  }
   258  
   259  // Config is the swapper configuration settings. A Config instance is the only
   260  // argument to the Swapper constructor.
   261  type Config struct {
   262  	// Assets is a map to all the asset information, including the asset backends,
   263  	// used by this Swapper.
   264  	Assets map[uint32]*SwapperAsset
   265  	// AuthManager is the auth manager for client messaging and authentication.
   266  	AuthManager AuthManager
   267  	// A database backend.
   268  	Storage Storage
   269  	// BroadcastTimeout is how long the Swapper will wait for expected swap
   270  	// transactions following new blocks.
   271  	BroadcastTimeout time.Duration
   272  	// TxWaitExpiration is the longest the Swapper will wait for a coin waiter.
   273  	// This could be thought of as the maximum allowable backend latency.
   274  	TxWaitExpiration time.Duration
   275  	// LockTimeTaker is the locktime Swapper will use for auditing taker swaps.
   276  	LockTimeTaker time.Duration
   277  	// LockTimeMaker is the locktime Swapper will use for auditing maker swaps.
   278  	LockTimeMaker time.Duration
   279  	// NoResume indicates that the swapper should not resume active swaps.
   280  	NoResume bool
   281  	// AllowPartialRestore indicates if it is acceptable to load only some of
   282  	// the active swaps if the Swapper's asset configuration lacks assets
   283  	// required to load them all.
   284  	AllowPartialRestore bool
   285  	// SwapDone registers a match with the DEX manager (or other consumer) for a
   286  	// given order as being finished.
   287  	SwapDone func(oid order.Order, match *order.Match, fail bool)
   288  }
   289  
   290  // NewSwapper is a constructor for a Swapper.
   291  func NewSwapper(cfg *Config) (*Swapper, error) {
   292  	for _, asset := range cfg.Assets {
   293  		if asset.MaxFeeRate == 0 {
   294  			return nil, fmt.Errorf("max fee rate of 0 is invalid for asset %q", asset.Symbol)
   295  		}
   296  	}
   297  
   298  	acctMatches := make(map[uint32]map[string]map[order.MatchID]*matchTracker)
   299  	for _, a := range cfg.Assets {
   300  		if _, ok := a.Backend.(asset.AccountBalancer); ok {
   301  			acctMatches[a.ID] = make(map[string]map[order.MatchID]*matchTracker)
   302  		}
   303  	}
   304  
   305  	authMgr := cfg.AuthManager
   306  	swapper := &Swapper{
   307  		coins:            cfg.Assets,
   308  		storage:          cfg.Storage,
   309  		authMgr:          authMgr,
   310  		swapDone:         cfg.SwapDone,
   311  		latencyQ:         wait.NewTaperingTickerQueue(fastRecheckInterval, taperedRecheckInterval),
   312  		matches:          make(map[order.MatchID]*matchTracker),
   313  		userMatches:      make(map[account.AccountID]map[order.MatchID]*matchTracker),
   314  		acctMatches:      acctMatches,
   315  		bTimeout:         cfg.BroadcastTimeout,
   316  		txWaitExpiration: cfg.TxWaitExpiration,
   317  		lockTimeTaker:    cfg.LockTimeTaker,
   318  		lockTimeMaker:    cfg.LockTimeMaker,
   319  	}
   320  
   321  	// Ensure txWaitExpiration is not greater than broadcast timeout setting.
   322  	if swapper.txWaitExpiration > swapper.bTimeout {
   323  		swapper.txWaitExpiration = swapper.bTimeout
   324  	}
   325  
   326  	if !cfg.NoResume {
   327  		err := swapper.restoreActiveSwaps(cfg.AllowPartialRestore)
   328  		if err != nil {
   329  			return nil, err
   330  		}
   331  	}
   332  
   333  	// The swapper is only concerned with two types of client-originating
   334  	// method requests.
   335  	authMgr.Route(msgjson.InitRoute, swapper.handleInit)
   336  	authMgr.Route(msgjson.RedeemRoute, swapper.handleRedeem)
   337  
   338  	return swapper, nil
   339  }
   340  
   341  // addMatch registers a match. The matchMtx must be locked.
   342  func (s *Swapper) addMatch(mt *matchTracker) {
   343  	mid := mt.ID()
   344  	s.matches[mid] = mt
   345  
   346  	// Add the match to both maker's and taker's match maps.
   347  	maker, taker := mt.Maker.User(), mt.Taker.User()
   348  	for _, user := range []account.AccountID{maker, taker} {
   349  		userMatches, found := s.userMatches[user]
   350  		if !found {
   351  			s.userMatches[user] = map[order.MatchID]*matchTracker{
   352  				mid: mt,
   353  			}
   354  		} else {
   355  			userMatches[mid] = mt // may overwrite for self-match (ok)
   356  		}
   357  		if maker == taker {
   358  			break
   359  		}
   360  	}
   361  
   362  	addAcctMatch := func(matches map[string]map[order.MatchID]*matchTracker, acctAddr string, mt *matchTracker) {
   363  		acctMatches := matches[acctAddr]
   364  		if acctMatches == nil {
   365  			acctMatches = make(map[order.MatchID]*matchTracker, 1)
   366  			matches[acctAddr] = acctMatches
   367  		}
   368  		acctMatches[mt.ID()] = mt
   369  	}
   370  
   371  	if s.acctMatches[mt.Maker.Base()] != nil {
   372  		acctMatches := s.acctMatches[mt.Maker.Base()]
   373  		addAcctMatch(acctMatches, mt.Maker.BaseAccount(), mt)
   374  		addAcctMatch(acctMatches, mt.Taker.Trade().BaseAccount(), mt)
   375  	}
   376  	if s.acctMatches[mt.Maker.Quote()] != nil {
   377  		acctMatches := s.acctMatches[mt.Maker.Quote()]
   378  		addAcctMatch(acctMatches, mt.Maker.QuoteAccount(), mt)
   379  		addAcctMatch(acctMatches, mt.Taker.Trade().QuoteAccount(), mt)
   380  	}
   381  }
   382  
   383  // deleteMatch unregisters a match. The matchMtx must be locked.
   384  func (s *Swapper) deleteMatch(mt *matchTracker) {
   385  	mid := mt.ID()
   386  	delete(s.matches, mid)
   387  
   388  	// Unlock the maker and taker order coins. May be redundant if processBlock
   389  	// confirmed both swaps, but premature/quick counterparty actions that
   390  	// advance match status first prevent that.
   391  	s.unlockOrderCoins(mt.Maker)
   392  	s.unlockOrderCoins(mt.Taker)
   393  
   394  	// Remove the match from both maker's and taker's match maps.
   395  	maker, taker := mt.Maker.User(), mt.Taker.User()
   396  	for _, user := range []account.AccountID{maker, taker} {
   397  		userMatches, found := s.userMatches[user]
   398  		if !found {
   399  			// Should not happen if consistently using addMatch.
   400  			log.Errorf("deleteMatch: No matches for user %v found!", user)
   401  			continue
   402  		}
   403  		delete(userMatches, mid)
   404  		if len(userMatches) == 0 {
   405  			delete(s.userMatches, user)
   406  		}
   407  		if maker == taker {
   408  			break
   409  		}
   410  	}
   411  
   412  	deleteAcctMatch := func(matches map[string]map[order.MatchID]*matchTracker, acctAddr string, mt *matchTracker) {
   413  		acctMatches := matches[acctAddr]
   414  		if acctMatches == nil {
   415  			return
   416  		}
   417  		delete(acctMatches, mt.ID())
   418  		if len(acctMatches) == 0 {
   419  			delete(matches, acctAddr)
   420  		}
   421  	}
   422  
   423  	if s.acctMatches[mt.Maker.Base()] != nil {
   424  		acctMatches := s.acctMatches[mt.Maker.Base()]
   425  		deleteAcctMatch(acctMatches, mt.Maker.BaseAccount(), mt)
   426  		deleteAcctMatch(acctMatches, mt.Taker.Trade().BaseAccount(), mt)
   427  	}
   428  	if s.acctMatches[mt.Maker.Quote()] != nil {
   429  		acctMatches := s.acctMatches[mt.Maker.Quote()]
   430  		deleteAcctMatch(acctMatches, mt.Maker.QuoteAccount(), mt)
   431  		deleteAcctMatch(acctMatches, mt.Taker.Trade().QuoteAccount(), mt)
   432  	}
   433  }
   434  
   435  // UnsettledQuantity sums up the settling quantity per market for a user. Part
   436  // of the market.MatchSwapper interface.
   437  func (s *Swapper) UnsettledQuantity(user account.AccountID) map[[2]uint32]uint64 {
   438  	s.matchMtx.RLock()
   439  	defer s.matchMtx.RUnlock()
   440  	marketQuantities := make(map[[2]uint32]uint64)
   441  	userMatches, found := s.userMatches[user]
   442  	if !found {
   443  		return marketQuantities
   444  	}
   445  	for _, mt := range userMatches {
   446  		mt.mtx.RLock()
   447  		matchStatus := mt.Status
   448  		mt.mtx.RUnlock()
   449  		if mt.Maker.AccountID == user {
   450  			if matchStatus >= order.MakerRedeemed {
   451  				continue
   452  			}
   453  		} else if matchStatus >= order.TakerSwapCast {
   454  			continue
   455  		}
   456  		mktID := [2]uint32{mt.Maker.BaseAsset, mt.Maker.QuoteAsset}
   457  		marketQuantities[mktID] += mt.Quantity
   458  	}
   459  	return marketQuantities
   460  }
   461  
   462  // pendingAccountStats is used to sum in-process match stats for the
   463  // AccountStats method.
   464  type pendingAccountStats struct {
   465  	acctAddr string
   466  	assetID  uint32
   467  	swaps    uint64
   468  	qty      uint64
   469  	redeems  int
   470  }
   471  
   472  func newPendingAccountStats(acctAddr string, assetID uint32) *pendingAccountStats {
   473  	return &pendingAccountStats{
   474  		acctAddr: acctAddr,
   475  		assetID:  assetID,
   476  	}
   477  }
   478  
   479  func (p *pendingAccountStats) addMatch(mt *matchTracker) {
   480  	p.addOrder(mt, mt.Maker, order.MakerSwapCast, order.MakerRedeemed)
   481  	p.addOrder(mt, mt.Taker, order.TakerSwapCast, order.MatchComplete)
   482  }
   483  
   484  func (p *pendingAccountStats) addOrder(mt *matchTracker, ord order.Order, swappedStatus, redeemedStatus order.MatchStatus) {
   485  	trade := ord.Trade()
   486  	if ord.Base() == p.assetID && trade.BaseAccount() == p.acctAddr {
   487  		if trade.Sell {
   488  			if mt.Status < swappedStatus {
   489  				p.qty += mt.Quantity
   490  				p.swaps++
   491  			}
   492  		} else if mt.Status < redeemedStatus {
   493  			p.redeems++
   494  		}
   495  	}
   496  	if ord.Quote() == p.assetID && trade.QuoteAccount() == p.acctAddr {
   497  		if !trade.Sell {
   498  			if mt.Status < swappedStatus {
   499  				p.qty += calc.BaseToQuote(mt.Rate, mt.Quantity)
   500  				p.swaps++ // The swap is expected to occur in 1 transaction.
   501  			}
   502  		} else if mt.Status < redeemedStatus {
   503  			p.redeems++
   504  		}
   505  	}
   506  }
   507  
   508  // AccountStats is part of the MatchNegotiator interface to report in-process
   509  // match information for a asset account address.
   510  func (s *Swapper) AccountStats(acctAddr string, assetID uint32) (qty, swaps uint64, redeems int) {
   511  	stats := newPendingAccountStats(acctAddr, assetID)
   512  	s.matchMtx.RLock()
   513  	defer s.matchMtx.RUnlock()
   514  	acctMatches := s.acctMatches[assetID]
   515  	if acctMatches == nil {
   516  		return // How?
   517  	}
   518  	for _, mt := range acctMatches[acctAddr] {
   519  		stats.addMatch(mt)
   520  	}
   521  	return stats.qty, stats.swaps, stats.redeems
   522  }
   523  
   524  // ChainsSynced will return true if both specified asset's backends are synced.
   525  func (s *Swapper) ChainsSynced(base, quote uint32) (bool, error) {
   526  	b, found := s.coins[base]
   527  	if !found {
   528  		return false, fmt.Errorf("no backend found for %d", base)
   529  	}
   530  	baseSynced, err := b.Backend.Synced()
   531  	if err != nil {
   532  		return false, fmt.Errorf("error checking sync status for %d: %w", base, err)
   533  	}
   534  	if !baseSynced {
   535  		return false, nil
   536  	}
   537  	q, found := s.coins[quote]
   538  	if !found {
   539  		return false, fmt.Errorf("no backend found for %d", base)
   540  	}
   541  	quoteSynced, err := q.Backend.Synced()
   542  	if err != nil {
   543  		return false, fmt.Errorf("error checking sync status for %d: %w", quote, err)
   544  	}
   545  	return quoteSynced, nil
   546  }
   547  
   548  func (s *Swapper) restoreActiveSwaps(allowPartial bool) error {
   549  	// Load active swap data from DB.
   550  	swapData, err := s.storage.ActiveSwaps()
   551  	if err != nil {
   552  		return err
   553  	}
   554  	log.Infof("Loaded swap data for %d active swaps.", len(swapData))
   555  	if len(swapData) == 0 {
   556  		return nil
   557  	}
   558  
   559  	// Check that the required assets backends are available.
   560  	missingAssets := make(map[uint32]bool)
   561  	checkAsset := func(id uint32) {
   562  		if s.coins[id] == nil && !missingAssets[id] {
   563  			log.Warnf("Unable to find backend for asset %d with active swaps.", id)
   564  			missingAssets[id] = true
   565  		}
   566  	}
   567  	for _, sd := range swapData {
   568  		checkAsset(sd.Base)
   569  		checkAsset(sd.Quote)
   570  	}
   571  
   572  	if len(missingAssets) > 0 && !allowPartial {
   573  		return fmt.Errorf("missing backend for asset with active swaps")
   574  	}
   575  
   576  	// Load the matchTrackers, calling the Contract and Redemption asset.Backend
   577  	// methods as needed.
   578  
   579  	type swapStatusData struct {
   580  		SwapAsset       uint32 // from market schema and takerSell bool
   581  		RedeemAsset     uint32
   582  		SwapTime        int64  // {a,b}ContractTime
   583  		ContractCoinOut []byte // {a,b}ContractCoinID
   584  		ContractScript  []byte // {a,b}Contract
   585  		RedeemTime      int64  // {a,b}RedeemTime
   586  		RedeemCoinIn    []byte // {a,b}aRedeemCoinID
   587  		// SwapConfirmTime is not stored in the DB, so use time.Now() if the
   588  		// contract has reached SwapConf.
   589  	}
   590  
   591  	translateSwapStatus := func(ss *swapStatus, ssd *swapStatusData, cpSwapCoin []byte) error {
   592  		ss.swapAsset, ss.redeemAsset = ssd.SwapAsset, ssd.RedeemAsset
   593  
   594  		swapCoin := ssd.ContractCoinOut
   595  		if len(swapCoin) > 0 {
   596  			assetID := ssd.SwapAsset
   597  			swapAsset := s.coins[assetID]
   598  			swap, err := swapAsset.Backend.Contract(swapCoin, ssd.ContractScript)
   599  			if err != nil {
   600  				return fmt.Errorf("unable to find swap out coin %x for asset %d: %w", swapCoin, assetID, err)
   601  			}
   602  			ss.swap = swap
   603  			ss.swapTime = time.UnixMilli(ssd.SwapTime)
   604  
   605  			swapConfs, err := swap.Confirmations(context.Background())
   606  			if err != nil {
   607  				log.Warnf("No swap confirmed time for %v: %v", swap, err)
   608  			} else if swapConfs >= int64(swapAsset.SwapConf) {
   609  				// We don't record the time at which we saw the block that got
   610  				// the swap to SwapConf, so give the user extra time.
   611  				ss.swapConfirmed = time.Now().UTC()
   612  			}
   613  		}
   614  
   615  		if redeemCoin := ssd.RedeemCoinIn; len(redeemCoin) > 0 {
   616  			assetID := ssd.RedeemAsset
   617  			redeem, err := s.coins[assetID].Backend.Redemption(redeemCoin, cpSwapCoin, ssd.ContractScript)
   618  			if err != nil {
   619  				return fmt.Errorf("unable to find redeem in coin %x for asset %d: %w", redeemCoin, assetID, err)
   620  			}
   621  			ss.redemption = redeem
   622  			ss.redeemTime = time.UnixMilli(ssd.RedeemTime)
   623  		}
   624  
   625  		return nil
   626  	}
   627  
   628  	s.matches = make(map[order.MatchID]*matchTracker, len(swapData))
   629  	s.userMatches = make(map[account.AccountID]map[order.MatchID]*matchTracker)
   630  	for _, sd := range swapData {
   631  		if missingAssets[sd.Base] {
   632  			log.Warnf("Dropping match %v with no backend available for base asset %d", sd.ID, sd.Base)
   633  			continue
   634  		}
   635  		if missingAssets[sd.Quote] {
   636  			log.Warnf("Dropping match %v with no backend available for quote asset %d", sd.ID, sd.Quote)
   637  			continue
   638  		}
   639  		// Load the maker's order.LimitOrder and taker's order.Order. WARNING:
   640  		// This is a different Order instance from whatever Market or other
   641  		// subsystems might have. As such, the mutable fields or accessors of
   642  		// mutable data should not be used.
   643  		taker, _, err := s.storage.Order(sd.MatchData.Taker, sd.Base, sd.Quote)
   644  		if err != nil {
   645  			log.Errorf("Failed to load taker order: %v", err)
   646  			continue
   647  		}
   648  		if taker.ID() != sd.MatchData.Taker {
   649  			log.Errorf("Failed to load order %v, computed ID %v instead", sd.MatchData.Taker, taker.ID())
   650  			continue
   651  		}
   652  		maker, _, err := s.storage.Order(sd.MatchData.Maker, sd.Base, sd.Quote)
   653  		if err != nil {
   654  			log.Errorf("Failed to load taker order: %v", err)
   655  			continue
   656  		}
   657  		if maker.ID() != sd.MatchData.Maker {
   658  			log.Errorf("Failed to load order %v, computed ID %v instead", sd.MatchData.Maker, maker.ID())
   659  			continue
   660  		}
   661  		makerLO, ok := maker.(*order.LimitOrder)
   662  		if !ok {
   663  			log.Errorf("Maker order was not a limit order: %T", maker)
   664  			continue
   665  		}
   666  
   667  		match := &order.Match{
   668  			Taker:        taker,
   669  			Maker:        makerLO,
   670  			Quantity:     sd.Quantity,
   671  			Rate:         sd.Rate,
   672  			FeeRateBase:  sd.BaseRate,
   673  			FeeRateQuote: sd.QuoteRate,
   674  			Epoch:        sd.Epoch,
   675  			Status:       sd.Status,
   676  			Sigs: order.Signatures{ // not really needed
   677  				MakerMatch:  sd.SwapData.SigMatchAckMaker,
   678  				TakerMatch:  sd.SwapData.SigMatchAckTaker,
   679  				MakerAudit:  sd.SwapData.ContractAAckSig,
   680  				TakerAudit:  sd.SwapData.ContractBAckSig,
   681  				TakerRedeem: sd.SwapData.RedeemAAckSig,
   682  			},
   683  		}
   684  
   685  		mid := sd.MatchData.ID
   686  		if mid != match.ID() { // serialization is order IDs, qty, and rate
   687  			log.Errorf("Failed to load Match %v, computed ID %v instead", mid, match.ID())
   688  			continue
   689  		}
   690  
   691  		// Check and skip matches for missing assets.
   692  		makerSwapAsset, makerRedeemAsset := sd.Base, sd.Quote // maker selling -> their swap asset is base
   693  		if sd.TakerSell {                                     // maker buying -> their swap asset is quote
   694  			makerSwapAsset, makerRedeemAsset = sd.Quote, sd.Base
   695  		}
   696  		if missingAssets[makerSwapAsset] {
   697  			log.Infof("Skipping match %v with missing asset %d backend", mid, makerSwapAsset)
   698  			continue
   699  		}
   700  		if missingAssets[makerRedeemAsset] {
   701  			log.Infof("Skipping match %v with missing asset %d backend", mid, makerRedeemAsset)
   702  			continue
   703  		}
   704  
   705  		epochCloseTime := match.Epoch.End()
   706  		mt := &matchTracker{
   707  			Match:       match,
   708  			time:        epochCloseTime.Add(time.Minute), // not quite, just be generous
   709  			matchTime:   epochCloseTime,
   710  			makerStatus: &swapStatus{}, // populated by translateSwapStatus
   711  			takerStatus: &swapStatus{},
   712  		}
   713  
   714  		makerStatus := &swapStatusData{
   715  			SwapAsset:       makerSwapAsset,
   716  			RedeemAsset:     makerRedeemAsset,
   717  			SwapTime:        sd.SwapData.ContractATime,
   718  			ContractCoinOut: sd.SwapData.ContractACoinID,
   719  			ContractScript:  sd.SwapData.ContractA,
   720  			RedeemTime:      sd.SwapData.RedeemATime,
   721  			RedeemCoinIn:    sd.SwapData.RedeemACoinID,
   722  		}
   723  		takerStatus := &swapStatusData{
   724  			SwapAsset:       makerRedeemAsset,
   725  			RedeemAsset:     makerSwapAsset,
   726  			SwapTime:        sd.SwapData.ContractBTime,
   727  			ContractCoinOut: sd.SwapData.ContractBCoinID,
   728  			ContractScript:  sd.SwapData.ContractB,
   729  			RedeemTime:      sd.SwapData.RedeemBTime,
   730  			RedeemCoinIn:    sd.SwapData.RedeemBCoinID,
   731  		}
   732  
   733  		if err := translateSwapStatus(mt.makerStatus, makerStatus, takerStatus.ContractCoinOut); err != nil {
   734  			log.Errorf("Loading match %v failed: %v", mid, err)
   735  			continue
   736  		}
   737  		if err := translateSwapStatus(mt.takerStatus, takerStatus, makerStatus.ContractCoinOut); err != nil {
   738  			log.Errorf("Loading match %v failed: %v", mid, err)
   739  			continue
   740  		}
   741  
   742  		log.Infof("Resuming swap %v in status %v", mid, mt.Status)
   743  		s.addMatch(mt)
   744  	}
   745  
   746  	// Live coin waiters are abandoned on Swapper shutdown. When a client
   747  	// reconnects or their init request times out, they will resend it.
   748  
   749  	return nil
   750  }
   751  
   752  // Run is the main Swapper loop. It's primary purpose is to update transaction
   753  // confirmations when new blocks are mined, and to trigger inaction checks.
   754  func (s *Swapper) Run(ctx context.Context) {
   755  	// Permit internal cancel on anomaly such as storage failure.
   756  	ctxMaster, cancel := context.WithCancel(ctx)
   757  
   758  	// Graceful shutdown first allows active incoming messages to be handled,
   759  	// blocks more incoming messages in the handler functions, stops the helper
   760  	// goroutines (latency queue used by the handlers, and the block ntfn
   761  	// receiver), and finally the main loop via the mainLoop channel.
   762  	var wgHelpers, wgMain sync.WaitGroup
   763  	ctxHelpers, cancelHelpers := context.WithCancel(context.Background())
   764  	mainLoop := make(chan struct{}) // close after helpers stop for graceful shutdown
   765  	defer func() {
   766  		// Stop handlers receiving messages and queueing latency Waiters.
   767  		s.handlerMtx.Lock() // block until active handlers return
   768  		s.stop = true       // prevent new handlers from starting waiters
   769  		// NOTE: could also do authMgr.Route(msgjson.{InitRoute,RedeemRoute}, shuttingDownHandler)
   770  		s.handlerMtx.Unlock()
   771  
   772  		// Stop the latencyQ of Waiters and the block update goroutines that
   773  		// send to the main loop.
   774  		cancelHelpers()
   775  		wgHelpers.Wait()
   776  
   777  		// Now that handlers AND the coin waiter queue are stopped, the
   778  		// liveWaiters can be accessed without locking.
   779  
   780  		// Stop the main loop if there was no internal error.
   781  		close(mainLoop)
   782  		wgMain.Wait()
   783  	}()
   784  
   785  	// Start a listen loop for each asset's block channel. Normal shutdown stops
   786  	// this before the main loop since this sends to the main loop.
   787  	blockNotes := make(chan *blockNotification, 32*len(s.coins))
   788  	addAsset := func(assetID uint32, blockSource <-chan *asset.BlockUpdate) {
   789  		errOut, errIn := meter.DelayedRelay(ctxHelpers, minBlockPeriod, 32)
   790  		wgHelpers.Add(1)
   791  		go func() {
   792  			defer wgHelpers.Done()
   793  			for {
   794  				select {
   795  				case blk, ok := <-blockSource:
   796  					if !ok {
   797  						log.Errorf("Asset %d has closed the block channel.", assetID)
   798  						return
   799  					}
   800  
   801  					select {
   802  					case errIn <- blk.Err:
   803  					default: // if blocking, the relay is either metering anyway or spewing errors
   804  					}
   805  
   806  				case blkErr, ok := <-errOut: // nils are metered and aggregated
   807  					if !ok { // relay stopped
   808  						return
   809  					}
   810  					select {
   811  					case <-ctxHelpers.Done():
   812  						return
   813  					case blockNotes <- &blockNotification{
   814  						time:    time.Now().UTC(),
   815  						assetID: assetID,
   816  						err:     blkErr,
   817  					}:
   818  					}
   819  
   820  				case <-ctxHelpers.Done():
   821  					return
   822  				}
   823  			}
   824  		}()
   825  	}
   826  	for assetID, lockable := range s.coins {
   827  		addAsset(assetID, lockable.Backend.BlockChannel(32))
   828  	}
   829  
   830  	// Start the queue of coinwaiters for the init and redeem handlers. The
   831  	// handlers must be stopped/blocked before stopping this.
   832  	wgHelpers.Add(1)
   833  	go func() {
   834  		s.latencyQ.Run(ctxHelpers)
   835  		wgHelpers.Done()
   836  	}()
   837  
   838  	log.Debugf("Swapper started with %v broadcast timeout and %v tx wait expiration.", s.bTimeout, s.txWaitExpiration)
   839  
   840  	// Block-based inaction checks are started with Timers, and run in the main
   841  	// loop to avoid locks and WaitGroups.
   842  	bcastBlockTrigger := make(chan uint32, 32*len(s.coins))
   843  	scheduleInactionCheck := func(assetID uint32) {
   844  		time.AfterFunc(s.bTimeout, func() {
   845  			// TODO: This pattern would still send the block trigger half of the
   846  			// time if the ctxMaster is canceled.
   847  			if ctxMaster.Err() != nil {
   848  				return
   849  			}
   850  			select {
   851  			case bcastBlockTrigger <- assetID: // all checks run in main loop
   852  			case <-ctxMaster.Done():
   853  			}
   854  		})
   855  	}
   856  
   857  	// On startup, schedule an inaction check for each asset. Ideally these
   858  	// would start bTimeout after the best block times.
   859  	for assetID := range s.coins {
   860  		scheduleInactionCheck(assetID)
   861  	}
   862  
   863  	// Event-based action checks are started with a single ticker. Each of the
   864  	// events, e.g. match request, could start a timer, but this is simpler and
   865  	// allows batching the match checks.
   866  	bcastEventTrigger := bufferedTicker(ctxMaster, s.bTimeout/4)
   867  
   868  	processBlockWithTimeout := func(block *blockNotification) {
   869  		ctxTime, cancelTimeCtx := context.WithTimeout(ctxMaster, 5*time.Second)
   870  		defer cancelTimeCtx()
   871  		s.processBlock(ctxTime, block)
   872  	}
   873  
   874  	// Main loop can stop on internal error via cancel(), or when the caller
   875  	// cancels the parent context triggering graceful shutdown.
   876  	wgMain.Add(1)
   877  	go func() {
   878  		defer wgMain.Done()
   879  		defer cancel() // ctxMaster for anomalous return
   880  		for {
   881  			select {
   882  			case <-s.storage.Fatal():
   883  				return
   884  			case block := <-blockNotes:
   885  				if block.err != nil {
   886  					var connectionErr asset.ConnectionError
   887  					if errors.As(block.err, &connectionErr) {
   888  						// Connection issues handling can be triggered here.
   889  						log.Errorf("connection error detected for %d: %v", block.assetID, block.err)
   890  					} else {
   891  						log.Errorf("asset %d is reporting a block notification error: %v", block.assetID, block.err)
   892  					}
   893  					continue
   894  				}
   895  
   896  				// processBlock will update confirmation times in the swapStatus
   897  				// structs.
   898  				processBlockWithTimeout(block)
   899  
   900  				// Schedule an inaction check for matches that involve this
   901  				// asset, as they could be expecting user action within bTimeout
   902  				// of this event.
   903  				scheduleInactionCheck(block.assetID)
   904  
   905  			case assetID := <-bcastBlockTrigger:
   906  				// There was a new block for this asset bTimeout ago.
   907  				s.checkInactionBlockBased(assetID)
   908  
   909  			case <-bcastEventTrigger:
   910  				// Inaction checks that are not relative to blocks.
   911  				s.checkInactionEventBased()
   912  
   913  			case <-mainLoop:
   914  				return
   915  			}
   916  		}
   917  	}()
   918  
   919  	// Wait for caller cancel or anomalous return from main loop.
   920  	<-ctxMaster.Done()
   921  }
   922  
   923  // bufferedTicker creates a "ticker" that periodically sends on the returned
   924  // channel, which has a buffer of length 1 and thus suitable for use in a select
   925  // with other events that might cause a regular Ticker send to be dropped.
   926  func bufferedTicker(ctx context.Context, dur time.Duration) chan struct{} {
   927  	buffered := make(chan struct{}, 1) // only need 1 since back-to-back is pointless
   928  	go func() {
   929  		ticker := time.NewTicker(dur)
   930  		defer ticker.Stop()
   931  		for {
   932  			select {
   933  			case <-ticker.C:
   934  				buffered <- struct{}{}
   935  			case <-ctx.Done():
   936  				return
   937  			}
   938  		}
   939  	}()
   940  	return buffered
   941  }
   942  
   943  func (s *Swapper) tryConfirmSwap(ctx context.Context, status *swapStatus, confTime time.Time) (final bool) {
   944  	if known, confirmed := status.contractState(); !known {
   945  		return // no swap yet to confirm
   946  	} else if confirmed {
   947  		return true // already confirmed
   948  	}
   949  
   950  	// Swap known means status.swap is set, and that it will not be replaced
   951  	// because we are gating processInit with the swapSearching semaphore.
   952  	confs, err := status.swap.Confirmations(ctx)
   953  	if err != nil {
   954  		log.Warnf("Unable to get confirmations for swap tx %v: %v", status.swap.TxID(), err)
   955  		return
   956  	}
   957  
   958  	status.mtx.Lock()
   959  	defer status.mtx.Unlock()
   960  	if !status.swapConfirmed.IsZero() { // in case a concurrent check already marked it
   961  		return true
   962  	}
   963  
   964  	swapConf := s.coins[status.swapAsset].SwapConf // swapStatus exists, therefore swapAsset is in the map
   965  	if confs >= int64(swapConf) {
   966  		log.Debugf("Swap %v (%s) has reached %d confirmations (%d required)",
   967  			status.swap, dex.BipIDSymbol(status.swapAsset), confs, swapConf)
   968  		status.swapConfirmed = confTime.UTC()
   969  		final = true
   970  	}
   971  	return
   972  }
   973  
   974  func (s *Swapper) matchSlice() []*matchTracker {
   975  	s.matchMtx.RLock()
   976  	defer s.matchMtx.RUnlock()
   977  	matches := make([]*matchTracker, 0, len(s.matches))
   978  	for _, match := range s.matches {
   979  		matches = append(matches, match)
   980  	}
   981  	return matches
   982  }
   983  
   984  // processBlock scans the matches and updates a swapConfirmed time if the
   985  // required confirmations are reached. Once a relevant transaction has the
   986  // requisite number of confirmations, the next-to-act has only duration
   987  // (Swapper).bTimeout to broadcast the next transaction in the settlement
   988  // sequence. The timeout is not evaluated here, but in (Swapper).checkInaction.
   989  // This method simply sets swapConfirmed in the last actor's swapStatus.
   990  func (s *Swapper) processBlock(ctx context.Context, block *blockNotification) {
   991  	for _, match := range s.matchSlice() {
   992  		// If it's neither of the match assets, nothing to do.
   993  		if match.makerStatus.swapAsset != block.assetID &&
   994  			match.takerStatus.swapAsset != block.assetID {
   995  			return
   996  		}
   997  
   998  		// Lock the matchTracker so the following checks and updates are atomic
   999  		// with respect to Status.
  1000  		match.mtx.RLock()
  1001  		defer match.mtx.RUnlock()
  1002  
  1003  		switch match.Status {
  1004  		case order.MakerSwapCast:
  1005  			if match.makerStatus.swapAsset != block.assetID {
  1006  				break
  1007  			}
  1008  			// If the maker has broadcast their transaction, the taker's
  1009  			// broadcast timeout starts once the maker's swap has SwapConf
  1010  			// confs.
  1011  			if s.tryConfirmSwap(ctx, match.makerStatus, block.time) {
  1012  				s.unlockOrderCoins(match.Maker)
  1013  			}
  1014  		case order.TakerSwapCast:
  1015  			if match.takerStatus.swapAsset != block.assetID {
  1016  				break
  1017  			}
  1018  			// If the taker has broadcast their transaction, the maker's
  1019  			// broadcast timeout (for redemption) starts once the taker's swap
  1020  			// has SwapConf confs.
  1021  			if s.tryConfirmSwap(ctx, match.takerStatus, block.time) {
  1022  				s.unlockOrderCoins(match.Taker)
  1023  			}
  1024  		}
  1025  	}
  1026  }
  1027  
  1028  // failMatch revokes the match and marks the swap as done for accounting
  1029  // purposes. If userFault is false, there will be no penalty, such as if the
  1030  // failure is because a swap tx lock time expired before required confirmations
  1031  // were reached.
  1032  func (s *Swapper) failMatch(match *matchTracker, userFault bool) {
  1033  	// From the match status, determine maker/taker fault and the corresponding
  1034  	// auth.NoActionStep.
  1035  	var makerFault bool
  1036  	var misstep auth.NoActionStep
  1037  	var refTime time.Time // a reference time found in the DB for reproducibly sorting outcomes
  1038  	switch match.Status {
  1039  	case order.NewlyMatched:
  1040  		misstep = auth.NoSwapAsMaker
  1041  		refTime = match.Epoch.End()
  1042  		makerFault = true
  1043  	case order.MakerSwapCast:
  1044  		misstep = auth.NoSwapAsTaker
  1045  		refTime = match.makerStatus.swapTime // swapConfirmed time is not in the DB
  1046  	case order.TakerSwapCast:
  1047  		misstep = auth.NoRedeemAsMaker
  1048  		refTime = match.takerStatus.swapTime // swapConfirmed time is not in the DB
  1049  		makerFault = true
  1050  	case order.MakerRedeemed:
  1051  		misstep = auth.NoRedeemAsTaker
  1052  		refTime = match.makerStatus.redeemTime
  1053  	default:
  1054  		log.Errorf("Invalid failMatch status %v for match %v", match.Status, match.ID())
  1055  		return
  1056  	}
  1057  
  1058  	orderAtFault, otherOrder := match.Taker, order.Order(match.Maker) // an order.Order
  1059  	if makerFault {
  1060  		orderAtFault, otherOrder = match.Maker, match.Taker
  1061  	}
  1062  	log.Debugf("failMatch: swap %v failing at %v (%v), user fault = %v",
  1063  		match.ID(), match.Status, misstep, userFault)
  1064  
  1065  	// Record the end of this match's processing.
  1066  	s.storage.SetMatchInactive(db.MatchID(match.Match), !userFault)
  1067  
  1068  	// Cancellation rate accounting
  1069  	s.swapDone(orderAtFault, match.Match, userFault) // will also unbook/revoke order if needed
  1070  
  1071  	// Accounting for the maker has already taken place if they have redeemed.
  1072  	if match.Status != order.MakerRedeemed {
  1073  		s.swapDone(otherOrder, match.Match, false)
  1074  	}
  1075  
  1076  	// Register the failure to act violation, adjusting the user's score.
  1077  	if userFault {
  1078  		s.authMgr.Inaction(orderAtFault.User(), misstep, db.MatchID(match.Match),
  1079  			match.Quantity, refTime, orderAtFault.ID())
  1080  	}
  1081  
  1082  	// Send the revoke_match messages, and solicit acks.
  1083  	s.revoke(match)
  1084  }
  1085  
  1086  type fail struct {
  1087  	match *matchTracker
  1088  	fault bool
  1089  }
  1090  
  1091  // checkInactionEventBased scans the swapStatus structures, checking for actions
  1092  // that are expected in a time frame relative to another event that is not a
  1093  // confirmation time. If a client is found to have not acted when required, a
  1094  // match may be revoked and a penalty assigned to the user. This includes
  1095  // matches in NewlyMatched that have not received a maker swap following the
  1096  // match request, and in MakerRedeemed that have not received a taker redeem
  1097  // following the redemption request triggered by the makers redeem.
  1098  func (s *Swapper) checkInactionEventBased() {
  1099  	// If the DB is failing, do not penalize or attempt to start revocations.
  1100  	if err := s.storage.LastErr(); err != nil {
  1101  		log.Errorf("DB in failing state.")
  1102  		return
  1103  	}
  1104  
  1105  	var failures []fail
  1106  
  1107  	// Do time.Since(event) with the same now time for each match.
  1108  	now := time.Now()
  1109  	tooOld := func(evt time.Time) bool {
  1110  		return now.Sub(evt) >= s.bTimeout
  1111  	}
  1112  
  1113  	checkMatch := func(match *matchTracker) {
  1114  		// Lock entire matchTracker so the following is atomic with respect to
  1115  		// Status.
  1116  		match.mtx.RLock()
  1117  		defer match.mtx.RUnlock()
  1118  
  1119  		log.Tracef("checkInactionEventBased: match %v (%v)", match.ID(), match.Status)
  1120  
  1121  		deleteMatch := func(fault bool) {
  1122  			s.deleteMatch(match)
  1123  			failures = append(failures, fail{match, fault}) // to process after map delete
  1124  		}
  1125  
  1126  		switch match.Status {
  1127  		case order.NewlyMatched:
  1128  			// Maker has not broadcast their swap. They have until match time
  1129  			// plus bTimeout.
  1130  			if tooOld(match.time) {
  1131  				deleteMatch(true)
  1132  			}
  1133  		case order.MakerSwapCast:
  1134  			// If the taker contract's expected lock time would be in the past,
  1135  			// revoke this match with no penalty.
  1136  			expectedTakerLockTime := match.matchTime.Add(s.lockTimeTaker)
  1137  			if expectedTakerLockTime.Before(now) {
  1138  				log.Infof("Revoking match %v at %v because the expected taker swap locktime would be in the past (%v).",
  1139  					match.ID(), match.Status, expectedTakerLockTime)
  1140  				deleteMatch(false)
  1141  			} else if match.expiredBy(now) {
  1142  				// The taker's contract should expire first, but also check the
  1143  				// lock time of the maker's known swap.
  1144  				log.Warnf("Revoking match %v at %v because maker's published contract has expired.",
  1145  					match.ID(), match.Status) // WRN because taker's should expire first
  1146  				deleteMatch(false)
  1147  			}
  1148  		case order.TakerSwapCast:
  1149  			// If either published contract's lock time is already passed,
  1150  			// revoke with no penalty because the swap cannot complete safely.
  1151  			if match.expiredBy(now) {
  1152  				log.Infof("Revoking match %v at %v because at least one published contract has expired.",
  1153  					match.ID(), match.Status)
  1154  				deleteMatch(false)
  1155  			}
  1156  		case order.MakerRedeemed:
  1157  			// If the maker has redeemed, the taker can redeem immediately, so
  1158  			// check the timeout against the time the Swapper received the
  1159  			// maker's `redeem` request (and sent the taker's 'redemption').
  1160  			if tooOld(match.makerStatus.redeemSeenTime()) { // rlocks swapStatus.mtx
  1161  				deleteMatch(true)
  1162  			}
  1163  		case order.MatchComplete:
  1164  			// If we got an ack from the redemption request sent to maker
  1165  			// (detailing the taker's redeem), or it has been a while since
  1166  			// taker redeemed, delete the match. Former should have deleted it.
  1167  			if len(match.Sigs.MakerRedeem) > 0 || tooOld(match.takerStatus.redeemSeenTime()) {
  1168  				log.Debugf("Deleting completed match %v", match.ID())
  1169  				s.deleteMatch(match) // no fail or revoke, just remove from map
  1170  			}
  1171  		}
  1172  	}
  1173  
  1174  	// Check and delete atomically
  1175  	s.matchMtx.Lock()
  1176  	for _, match := range s.matches {
  1177  		checkMatch(match)
  1178  	}
  1179  	s.matchMtx.Unlock()
  1180  
  1181  	// Record failed matches in the DB and auth mgr, unlock coins, and send
  1182  	// revoke_match messages.
  1183  	for _, fail := range failures {
  1184  		s.failMatch(fail.match, fail.fault)
  1185  	}
  1186  }
  1187  
  1188  // checkInactionBlockBased scans the swapStatus structures relevant to the
  1189  // specified asset. If a client is found to have not acted when required, a
  1190  // match may be revoked and a penalty assigned to the user. This includes
  1191  // matches in MakerSwapCast that have not received a taker swap after the
  1192  // maker's swap reaches the required confirmation count, and in TakerSwapCast
  1193  // that have not received a maker redeem after the taker's swap reaches the
  1194  // required confirmation count.
  1195  func (s *Swapper) checkInactionBlockBased(assetID uint32) {
  1196  	// If the DB is failing, do not penalize or attempt to start revocations.
  1197  	if err := s.storage.LastErr(); err != nil {
  1198  		log.Errorf("DB in failing state.")
  1199  		return
  1200  	}
  1201  
  1202  	var failures []fail
  1203  	// Do time.Since(event) with the same now time for each match.
  1204  	now := time.Now()
  1205  	tooOld := func(evt time.Time) bool {
  1206  		// If the time is not set (zero), it has not happened yet (not too old).
  1207  		return !evt.IsZero() && now.Sub(evt) >= s.bTimeout
  1208  	}
  1209  
  1210  	checkMatch := func(match *matchTracker) {
  1211  		if match.makerStatus.swapAsset != assetID && match.takerStatus.swapAsset != assetID {
  1212  			return
  1213  		}
  1214  
  1215  		// Lock entire matchTracker so the following is atomic with respect to
  1216  		// Status.
  1217  		match.mtx.RLock()
  1218  		defer match.mtx.RUnlock()
  1219  
  1220  		log.Tracef("checkInactionBlockBased: asset %d, match %v (%v)",
  1221  			assetID, match.ID(), match.Status)
  1222  
  1223  		deleteMatch := func() {
  1224  			// Fail the match, and assign fault if lock times are not passed.
  1225  			s.deleteMatch(match)
  1226  			failures = append(failures, fail{match, !match.expiredBy(now)})
  1227  		}
  1228  
  1229  		switch match.Status {
  1230  		case order.MakerSwapCast:
  1231  			if tooOld(match.makerStatus.swapConfTime()) { // rlocks swapStatus.mtx
  1232  				deleteMatch()
  1233  			}
  1234  		case order.TakerSwapCast:
  1235  			if tooOld(match.takerStatus.swapConfTime()) {
  1236  				deleteMatch()
  1237  			}
  1238  		}
  1239  	}
  1240  
  1241  	// Check and delete atomically.
  1242  	s.matchMtx.Lock()
  1243  	for _, match := range s.matches {
  1244  		checkMatch(match)
  1245  	}
  1246  	s.matchMtx.Unlock()
  1247  
  1248  	// Record failed matches in the DB and auth mgr, unlock coins, and send
  1249  	// revoke_match messages.
  1250  	for _, fail := range failures {
  1251  		s.failMatch(fail.match, fail.fault)
  1252  	}
  1253  }
  1254  
  1255  // respondError sends an rpcError to a user.
  1256  func (s *Swapper) respondError(id uint64, user account.AccountID, code int, errMsg string) {
  1257  	log.Debugf("Error going to user %v, code: %d, msg: %s", user, code, errMsg)
  1258  	msg, err := msgjson.NewResponse(id, nil, &msgjson.Error{
  1259  		Code:    code,
  1260  		Message: errMsg,
  1261  	})
  1262  	if err != nil {
  1263  		log.Errorf("Failed to create error response with message '%s': %v", msg, err)
  1264  		return // this should not be possible, but don't pass nil msg to Send
  1265  	}
  1266  	if err := s.authMgr.Send(user, msg); err != nil {
  1267  		log.Infof("Unable to send error response (code = %d, msg = %s) to disconnected user %v: %q",
  1268  			code, errMsg, user, err)
  1269  	}
  1270  }
  1271  
  1272  // respondSuccess sends a successful response to a user.
  1273  func (s *Swapper) respondSuccess(id uint64, user account.AccountID, result any) {
  1274  	msg, err := msgjson.NewResponse(id, result, nil)
  1275  	if err != nil {
  1276  		log.Errorf("failed to send success: %v", err)
  1277  		return // this should not be possible, but don't pass nil msg to Send
  1278  	}
  1279  	if err := s.authMgr.Send(user, msg); err != nil {
  1280  		log.Infof("Unable to send success response to disconnected user %v: %v", user, err)
  1281  	}
  1282  }
  1283  
  1284  // step creates a stepInformation structure for the specified match. A new
  1285  // stepInformation should be created for every client communication. The user
  1286  // is also validated as the actor. An error is returned if the user has not
  1287  // acknowledged their previous DEX requests.
  1288  func (s *Swapper) step(user account.AccountID, matchID order.MatchID) (*stepInformation, *msgjson.Error) {
  1289  	s.matchMtx.RLock()
  1290  	match, found := s.matches[matchID]
  1291  	s.matchMtx.RUnlock()
  1292  	if !found {
  1293  		return nil, &msgjson.Error{
  1294  			Code:    msgjson.RPCUnknownMatch,
  1295  			Message: "unknown match ID",
  1296  		}
  1297  	}
  1298  
  1299  	// Get the step-related information for both parties.
  1300  	var isBaseAsset bool
  1301  	var actor, counterParty stepActor
  1302  	var nextStep order.MatchStatus
  1303  	maker, taker := match.Maker, match.Taker
  1304  
  1305  	// Lock for Status and Sigs.
  1306  	match.mtx.RLock()
  1307  	defer match.mtx.RUnlock()
  1308  
  1309  	// maker sell: base swap, quote redeem
  1310  	// taker buy: quote swap, base redeem
  1311  
  1312  	// maker buy: quote swap, base redeem
  1313  	// taker sell: base swap, quote redeem
  1314  
  1315  	// Maker broadcasts the swap contract. Sequence: NewlyMatched ->
  1316  	// MakerSwapCast -> TakerSwapCast -> MakerRedeemed -> MatchComplete
  1317  	switch match.Status {
  1318  	case order.NewlyMatched, order.TakerSwapCast:
  1319  		counterParty.order, actor.order = taker, maker
  1320  		actor.status = match.makerStatus
  1321  		counterParty.status = match.takerStatus
  1322  		actor.user = maker.User()
  1323  		counterParty.user = taker.User()
  1324  		actor.isMaker = true
  1325  		if match.Status == order.NewlyMatched {
  1326  			nextStep = order.MakerSwapCast
  1327  			isBaseAsset = maker.Sell // maker swap: base asset if sell
  1328  			if len(match.Sigs.MakerMatch) == 0 {
  1329  				log.Debugf("swap %v at status %v missing MakerMatch signature(s) expected before NewlyMatched->MakerSwapCast",
  1330  					match.ID(), match.Status)
  1331  			}
  1332  		} else /* TakerSwapCast */ {
  1333  			nextStep = order.MakerRedeemed
  1334  			isBaseAsset = !maker.Sell // maker redeem: base asset if buy
  1335  			if len(match.Sigs.MakerAudit) == 0 {
  1336  				log.Debugf("Swap %v at status %v missing MakerAudit signature(s) expected before TakerSwapCast->MakerRedeemed",
  1337  					match.ID(), match.Status)
  1338  			}
  1339  		}
  1340  	case order.MakerSwapCast, order.MakerRedeemed:
  1341  		counterParty.order, actor.order = maker, taker
  1342  		actor.status = match.takerStatus
  1343  		counterParty.status = match.makerStatus
  1344  		actor.user = taker.User()
  1345  		counterParty.user = maker.User()
  1346  		counterParty.isMaker = true
  1347  		if match.Status == order.MakerSwapCast {
  1348  			nextStep = order.TakerSwapCast
  1349  			isBaseAsset = !maker.Sell // taker swap: base asset if sell (maker buy)
  1350  			if len(match.Sigs.TakerMatch) == 0 {
  1351  				log.Debugf("Swap %v at status %v missing TakerMatch signature(s) expected before MakerSwapCast->TakerSwapCast",
  1352  					match.ID(), match.Status)
  1353  			}
  1354  			if len(match.Sigs.TakerAudit) == 0 {
  1355  				log.Debugf("Swap %v at status %v missing TakerAudit signature(s) expected before MakerSwapCast->TakerSwapCast",
  1356  					match.ID(), match.Status)
  1357  			}
  1358  		} else /* MakerRedeemed */ {
  1359  			nextStep = order.MatchComplete
  1360  			// Note that the swap is still considered "active" until both
  1361  			// counterparties acknowledge the redemptions.
  1362  			isBaseAsset = maker.Sell // taker redeem: base asset if buy (maker sell)
  1363  			if len(match.Sigs.TakerRedeem) == 0 {
  1364  				log.Debugf("Swap %v at status %v missing TakerRedeem signature(s) expected before MakerRedeemed->MatchComplete",
  1365  					match.ID(), match.Status)
  1366  			}
  1367  		}
  1368  	default:
  1369  		return nil, &msgjson.Error{
  1370  			Code:    msgjson.SettlementSequenceError,
  1371  			Message: "unknown settlement sequence identifier",
  1372  		}
  1373  	}
  1374  
  1375  	// Verify that the user specified is the actor for this step.
  1376  	if actor.user != user { // NOTE: self-trade slips past this
  1377  		return nil, &msgjson.Error{
  1378  			Code:    msgjson.SettlementSequenceError,
  1379  			Message: "expected other party to act",
  1380  		}
  1381  	}
  1382  
  1383  	// Set the actors' swapAsset and the swap contract checkVal.
  1384  	var checkVal uint64
  1385  	if isBaseAsset {
  1386  		actor.swapAsset = maker.BaseAsset
  1387  		counterParty.swapAsset = maker.QuoteAsset
  1388  		checkVal = match.Quantity
  1389  	} else {
  1390  		actor.swapAsset = maker.QuoteAsset
  1391  		counterParty.swapAsset = maker.BaseAsset
  1392  		checkVal = matcher.BaseToQuote(maker.Rate, match.Quantity)
  1393  	}
  1394  
  1395  	return &stepInformation{
  1396  		match:        match,
  1397  		actor:        actor,
  1398  		counterParty: counterParty,
  1399  		// By the time a match is created, the presence of the asset in the map
  1400  		// has already been verified.
  1401  		asset:       s.coins[actor.swapAsset].BackedAsset,
  1402  		isBaseAsset: isBaseAsset,
  1403  		step:        match.Status,
  1404  		nextStep:    nextStep,
  1405  		checkVal:    checkVal,
  1406  	}, nil
  1407  }
  1408  
  1409  // authUser verifies that the msgjson.Signable is signed by the user. This
  1410  // method relies on the AuthManager to validate the signature of the serialized
  1411  // data. nil is returned for successful signature verification.
  1412  func (s *Swapper) authUser(user account.AccountID, params msgjson.Signable) *msgjson.Error {
  1413  	// Authorize the user.
  1414  	msg := params.Serialize()
  1415  	err := s.authMgr.Auth(user, msg, params.SigBytes())
  1416  	if err != nil {
  1417  		return &msgjson.Error{
  1418  			Code:    msgjson.SignatureError,
  1419  			Message: "error authenticating init params",
  1420  		}
  1421  	}
  1422  	return nil
  1423  }
  1424  
  1425  // messageAcker is information needed to process the user's
  1426  // msgjson.Acknowledgement.
  1427  type messageAcker struct {
  1428  	user    account.AccountID
  1429  	match   *matchTracker
  1430  	params  msgjson.Signable
  1431  	isMaker bool
  1432  	isAudit bool
  1433  }
  1434  
  1435  // processAck processes a msgjson.Acknowledgement to the audit, redemption, and
  1436  // revoke_match requests, validating the signature and updating the
  1437  // (order.Match).Sigs record. This is required by processInit, processRedeem,
  1438  // and revoke. Match Acknowledgements are handled by processMatchAck.
  1439  func (s *Swapper) processAck(msg *msgjson.Message, acker *messageAcker) {
  1440  	ack := new(msgjson.Acknowledgement)
  1441  	err := msg.UnmarshalResult(ack)
  1442  	if err != nil {
  1443  		s.respondError(msg.ID, acker.user, msgjson.RPCParseError, fmt.Sprintf("error parsing acknowledgment: %v", err))
  1444  		return
  1445  	}
  1446  	// Note: ack.MatchID unused, but could be checked against acker.match.ID().
  1447  
  1448  	// Check the signature.
  1449  	sigMsg := acker.params.Serialize()
  1450  	err = s.authMgr.Auth(acker.user, sigMsg, ack.Sig)
  1451  	if err != nil {
  1452  		s.respondError(msg.ID, acker.user, msgjson.SignatureError,
  1453  			fmt.Sprintf("signature validation error: %v", err))
  1454  		return
  1455  	}
  1456  
  1457  	switch acker.params.(type) {
  1458  	case *msgjson.Audit, *msgjson.Redemption:
  1459  	default:
  1460  		log.Warnf("unrecognized ack type %T", acker.params)
  1461  		return
  1462  	}
  1463  
  1464  	// Set and store the appropriate signature, based on the current step and
  1465  	// actor.
  1466  	mktMatch := db.MatchID(acker.match.Match)
  1467  
  1468  	// If this is the maker's (optional) redeem ack sig, we can stop tracking
  1469  	// the match. Do it here to avoid lock order violation (a deadlock trap).
  1470  	if acker.isMaker && !acker.isAudit { // getting Sigs.MakerRedeem
  1471  		log.Debugf("Deleting completed match %v", mktMatch)
  1472  		s.matchMtx.Lock() // before locking matchTracker.mtx
  1473  		s.deleteMatch(acker.match)
  1474  		s.matchMtx.Unlock()
  1475  	}
  1476  
  1477  	acker.match.mtx.Lock()
  1478  	defer acker.match.mtx.Unlock()
  1479  
  1480  	// This is an ack of either contract audit or redemption receipt.
  1481  	if acker.isAudit {
  1482  		log.Debugf("Received contract 'audit' acknowledgement from user %v (%s) for match %v (%v)",
  1483  			acker.user, makerTaker(acker.isMaker), acker.match.Match.ID(), acker.match.Status)
  1484  		// It's a contract audit ack.
  1485  		if acker.isMaker {
  1486  			acker.match.Sigs.MakerAudit = ack.Sig         // i.e. audited taker's contract
  1487  			s.storage.SaveAuditAckSigA(mktMatch, ack.Sig) // sql error makes backend go fatal
  1488  		} else {
  1489  			acker.match.Sigs.TakerAudit = ack.Sig
  1490  			s.storage.SaveAuditAckSigB(mktMatch, ack.Sig)
  1491  		}
  1492  		return
  1493  	}
  1494  
  1495  	// It's a redemption ack.
  1496  	log.Debugf("Received 'redemption' acknowledgement from user %v (%s) for match %v (%s)",
  1497  		acker.user, makerTaker(acker.isMaker), acker.match.Match.ID(), acker.match.Status)
  1498  
  1499  	// This is a redemption acknowledgement. Store the ack signature, and
  1500  	// potentially record the order as complete with the auth manager and in
  1501  	// persistent storage.
  1502  
  1503  	// Record the taker's redeem ack sig. One from the maker isn't required.
  1504  	if acker.isMaker { // maker acknowledging the redeem req we sent regarding the taker redeem
  1505  		acker.match.Sigs.MakerRedeem = ack.Sig
  1506  		// We don't save that pointless sig anymore; use it as a flag.
  1507  	} else { // taker acknowledging the redeem req we sent regarding the maker redeem
  1508  		acker.match.Sigs.TakerRedeem = ack.Sig
  1509  		if err = s.storage.SaveRedeemAckSigB(mktMatch, ack.Sig); err != nil {
  1510  			s.respondError(msg.ID, acker.user, msgjson.RPCInternalError,
  1511  				"internal server error")
  1512  			log.Errorf("SaveRedeemAckSigB failed for match %v: %v", mktMatch.String(), err)
  1513  			return
  1514  		}
  1515  	}
  1516  }
  1517  
  1518  // processInit processes the `init` RPC request, which is used to inform the DEX
  1519  // of a newly broadcast swap transaction. Once the transaction is seen and
  1520  // audited by the Swapper, the counter-party is informed with an 'audit'
  1521  // request. This method is run as a coin waiter, hence the return value
  1522  // indicates if future attempts should be made to check coin status.
  1523  func (s *Swapper) processInit(msg *msgjson.Message, params *msgjson.Init, stepInfo *stepInformation) wait.TryDirective {
  1524  	actor, counterParty := stepInfo.actor, stepInfo.counterParty
  1525  
  1526  	// Validate the swap contract
  1527  	chain := stepInfo.asset.Backend
  1528  	contract, err := chain.Contract(params.CoinID, params.Contract)
  1529  	if err != nil {
  1530  		if errors.Is(err, asset.CoinNotFoundError) {
  1531  			return wait.TryAgain
  1532  		}
  1533  		actor.status.mtx.RLock()
  1534  		log.Warnf("Contract error encountered for match %s, actor %s using coin ID %v and contract %v: %v",
  1535  			stepInfo.match.ID(), actor, params.CoinID, params.Contract, err)
  1536  		actor.status.mtx.RUnlock()
  1537  		actor.status.endSwapSearch() // allow client retry even before notifying him
  1538  		s.respondError(msg.ID, actor.user, msgjson.ContractError,
  1539  			fmt.Sprintf("contract error encountered: %v", err))
  1540  		return wait.DontTryAgain
  1541  	}
  1542  
  1543  	// Enforce the prescribed swap fee rate, but only if the swap is not already
  1544  	// confirmed.
  1545  	swapConfs := func() int64 { // not executed if adequate fee rate
  1546  		ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
  1547  		defer cancel()
  1548  		confs, err := contract.Confirmations(ctx)
  1549  		if err != nil {
  1550  			log.Warnf("Failed to get confirmations on swap tx %v: %v", contract.TxID(), err)
  1551  			confs = 0 // should be already
  1552  		}
  1553  		return confs
  1554  	}
  1555  	reqFeeRate := stepInfo.match.FeeRateQuote
  1556  	if stepInfo.isBaseAsset {
  1557  		reqFeeRate = stepInfo.match.FeeRateBase
  1558  	}
  1559  
  1560  	if !chain.ValidateFeeRate(contract.Coin, reqFeeRate) {
  1561  		confs := swapConfs()
  1562  		if confs < 1 {
  1563  			actor.status.endSwapSearch() // allow client retry even before notifying him
  1564  			s.respondError(msg.ID, actor.user, msgjson.ContractError, "low tx fee")
  1565  			return wait.DontTryAgain
  1566  		}
  1567  		log.Infof("Swap txn %v (%s) with low fee rate (%v required), accepted with %d confirmations.",
  1568  			contract, stepInfo.asset.Symbol, reqFeeRate, confs)
  1569  	}
  1570  	if contract.SwapAddress != counterParty.order.Trade().SwapAddress() {
  1571  		actor.status.endSwapSearch() // allow client retry even before notifying him
  1572  		s.respondError(msg.ID, actor.user, msgjson.ContractError,
  1573  			fmt.Sprintf("incorrect recipient. expected %s. got %s",
  1574  				contract.SwapAddress, counterParty.order.Trade().SwapAddress()))
  1575  		return wait.DontTryAgain
  1576  	}
  1577  	if contract.Value() != stepInfo.checkVal {
  1578  		actor.status.endSwapSearch() // allow client retry even before notifying him
  1579  		s.respondError(msg.ID, actor.user, msgjson.ContractError,
  1580  			fmt.Sprintf("contract error. expected contract value to be %d, got %d", stepInfo.checkVal, contract.Value()))
  1581  		return wait.DontTryAgain
  1582  	}
  1583  	if !actor.isMaker && !bytes.Equal(contract.SecretHash, counterParty.status.swap.SecretHash) {
  1584  		actor.status.endSwapSearch() // allow client retry even before notifying him
  1585  		s.respondError(msg.ID, actor.user, msgjson.ContractError,
  1586  			fmt.Sprintf("incorrect secret hash. expected %x. got %x",
  1587  				contract.SecretHash, counterParty.status.swap.SecretHash))
  1588  		return wait.DontTryAgain
  1589  	}
  1590  
  1591  	reqLockTime := encode.DropMilliseconds(stepInfo.match.matchTime.Add(s.lockTimeTaker))
  1592  	if actor.isMaker {
  1593  		reqLockTime = encode.DropMilliseconds(stepInfo.match.matchTime.Add(s.lockTimeMaker))
  1594  	}
  1595  	if contract.LockTime.Before(reqLockTime) {
  1596  		actor.status.endSwapSearch() // allow client retry even before notifying him
  1597  		s.respondError(msg.ID, actor.user, msgjson.ContractError,
  1598  			fmt.Sprintf("contract error. expected lock time >= %s, got %s", reqLockTime, contract.LockTime))
  1599  		return wait.DontTryAgain
  1600  	} else if remain := time.Until(contract.LockTime); remain < 0 {
  1601  		actor.status.endSwapSearch() // allow client retry even before notifying him
  1602  		s.respondError(msg.ID, actor.user, msgjson.ContractError,
  1603  			fmt.Sprintf("contract is correct, but lock time passed %s ago", remain))
  1604  		// Revoke the match proactively before checkInaction gets to it.
  1605  		s.matchMtx.Lock()
  1606  		defer s.matchMtx.Unlock()
  1607  		if _, found := s.matches[stepInfo.match.ID()]; found {
  1608  			s.failMatch(stepInfo.match, false) // no fault
  1609  			s.deleteMatch(stepInfo.match)
  1610  		} // else it's already revoked
  1611  		return wait.DontTryAgain // and don't tell counterparty of expired contract they should not redeem
  1612  	}
  1613  
  1614  	// Update the match.
  1615  	swapTime := unixMsNow()
  1616  	matchID := stepInfo.match.Match.ID()
  1617  
  1618  	// Store the swap contract and the coinID (e.g. txid:vout) containing the
  1619  	// contract script hash. Maker is party A, the initiator. Taker is party B,
  1620  	// the participant.
  1621  	//
  1622  	// Failure to update match status is a fatal DB error. If we continue, the
  1623  	// swap may succeed, but there will be no way to recover or retrospectively
  1624  	// determine the swap outcome. Abort.
  1625  	storFn := s.storage.SaveContractB
  1626  	if stepInfo.actor.isMaker {
  1627  		storFn = s.storage.SaveContractA
  1628  	}
  1629  	mktMatch := db.MatchID(stepInfo.match.Match)
  1630  	swapTimeMs := swapTime.UnixMilli()
  1631  	err = storFn(mktMatch, params.Contract, params.CoinID, swapTimeMs)
  1632  	if err != nil {
  1633  		log.Errorf("saving swap contract (match id=%v, maker=%v) failed: %v",
  1634  			matchID, actor.isMaker, err)
  1635  		s.respondError(msg.ID, actor.user, msgjson.RPCInternalError,
  1636  			"internal server error")
  1637  		// TODO: revoke the match without penalties instead of retrying forever?
  1638  		return wait.TryAgain
  1639  	}
  1640  
  1641  	// Modify the match's swapStatuses, but only if the match wasn't revoked
  1642  	// while waiting for the txn.
  1643  	s.matchMtx.RLock()
  1644  	if _, found := s.matches[matchID]; !found {
  1645  		s.matchMtx.RUnlock()
  1646  		log.Errorf("Contract txn located after match was revoked (match id=%v, maker=%v)",
  1647  			matchID, actor.isMaker)
  1648  		actor.status.endSwapSearch() // allow client retry even before notifying him
  1649  		s.respondError(msg.ID, actor.user, msgjson.ContractError, "match already revoked due to inaction")
  1650  		return wait.DontTryAgain
  1651  	}
  1652  
  1653  	actor.status.mtx.Lock()
  1654  	actor.status.swap = contract // swap should not be set already (handleInit should gate)
  1655  	actor.status.swapTime = swapTime
  1656  	actor.status.mtx.Unlock()
  1657  
  1658  	stepInfo.match.mtx.Lock()
  1659  	stepInfo.match.Status = stepInfo.nextStep // handleInit (gate mechanism) won't allow backward progress
  1660  	stepInfo.match.mtx.Unlock()
  1661  
  1662  	// Only unlock match map after the statuses and txn times are stored,
  1663  	// ensuring that checkInaction will not revoke the match as we respond and
  1664  	// request counterparty audit.
  1665  	s.matchMtx.RUnlock()
  1666  
  1667  	// Contract now recorded and will be used to reject backward progress (duplicate
  1668  	// or malicious requests client might still send after this point).
  1669  	actor.status.endSwapSearch()
  1670  
  1671  	log.Debugf("processInit: valid contract %v (%s) received at %v from user %v (%s) for match %v, "+
  1672  		"swapStatus %v => %v", contract, stepInfo.asset.Symbol, swapTime, actor.user,
  1673  		makerTaker(actor.isMaker), matchID, stepInfo.step, stepInfo.nextStep)
  1674  
  1675  	// Issue a positive response to the actor.
  1676  	s.authMgr.Sign(params)
  1677  	s.respondSuccess(msg.ID, actor.user, &msgjson.Acknowledgement{
  1678  		MatchID: matchID[:],
  1679  		Sig:     params.Sig,
  1680  	})
  1681  
  1682  	// Prepare an 'audit' request for the counter-party.
  1683  	auditParams := &msgjson.Audit{
  1684  		OrderID:  idToBytes(counterParty.order.ID()),
  1685  		MatchID:  matchID[:],
  1686  		Time:     uint64(swapTimeMs),
  1687  		CoinID:   params.CoinID,
  1688  		Contract: params.Contract,
  1689  		TxData:   contract.TxData,
  1690  	}
  1691  	s.authMgr.Sign(auditParams)
  1692  	notification, err := msgjson.NewRequest(comms.NextID(), msgjson.AuditRoute, auditParams)
  1693  	if err != nil {
  1694  		// This is likely an impossible condition.
  1695  		log.Errorf("error creating audit request: %v", err)
  1696  		return wait.DontTryAgain
  1697  	}
  1698  
  1699  	// Set up the acknowledgement for the callback.
  1700  	ack := &messageAcker{
  1701  		user:    counterParty.user,
  1702  		match:   stepInfo.match,
  1703  		params:  auditParams,
  1704  		isMaker: counterParty.isMaker,
  1705  		isAudit: true,
  1706  	}
  1707  	// Send the 'audit' request to the counter-party.
  1708  	log.Debugf("processInit: sending contract 'audit' request to counterparty %v (%s) "+
  1709  		"for match %v", ack.user, makerTaker(ack.isMaker), matchID)
  1710  	// The counterparty will audit the contract by retrieving it, which may
  1711  	// involve them waiting for up to the broadcast timeout before responding,
  1712  	// so the user gets at least s.bTimeout to the request.
  1713  	err = s.authMgr.RequestWithTimeout(ack.user, notification, func(_ comms.Link, resp *msgjson.Message) {
  1714  		s.processAck(resp, ack) // resp.ID == notification.ID
  1715  	}, s.bTimeout, func() {
  1716  		log.Infof("Timeout waiting for contract 'audit' request acknowledgement from user %v (%s) for match %v",
  1717  			ack.user, makerTaker(ack.isMaker), matchID)
  1718  	})
  1719  	if err != nil {
  1720  		log.Debug("Couldn't send 'audit' request to user %v (%s) for match %v", ack.user, makerTaker(ack.isMaker), matchID)
  1721  	}
  1722  
  1723  	return wait.DontTryAgain
  1724  }
  1725  
  1726  // processRedeem processes a 'redeem' request from a client. processRedeem does
  1727  // not perform user authentication, which is handled in handleRedeem before
  1728  // processRedeem is invoked. This method is run as a coin waiter.
  1729  func (s *Swapper) processRedeem(msg *msgjson.Message, params *msgjson.Redeem, stepInfo *stepInformation) wait.TryDirective {
  1730  	// TODO(consider): Extract secret from initiator's (maker's) redemption
  1731  	// transaction. The Backend would need a method identify the component of
  1732  	// the redemption transaction that contains the secret and extract it. In a
  1733  	// UTXO-based asset, this means finding the input that spends the output of
  1734  	// the counterparty's contract, and process that input's signature script
  1735  	// with FindKeyPush. Presently this is up to the clients and not stored with
  1736  	// the server.
  1737  
  1738  	// Make sure that the expected output is being spent.
  1739  	actor, counterParty := stepInfo.actor, stepInfo.counterParty
  1740  	counterParty.status.mtx.RLock()
  1741  	cpContract := counterParty.status.swap.ContractData
  1742  	cpSwapCoin := counterParty.status.swap.ID()
  1743  	cpSwapStr := counterParty.status.swap.String()
  1744  	counterParty.status.mtx.RUnlock()
  1745  
  1746  	// Get the transaction.
  1747  	match := stepInfo.match
  1748  	matchID := match.ID()
  1749  	chain := stepInfo.asset.Backend
  1750  	if !chain.ValidateSecret(params.Secret, cpContract) {
  1751  		log.Infof("Secret validation failed (match id=%v, maker=%v, secret=%v)",
  1752  			matchID, actor.isMaker, params.Secret)
  1753  		actor.status.endRedeemSearch() // allow client retry even before notifying him
  1754  		s.respondError(msg.ID, actor.user, msgjson.InvalidRequestError, "secret validation failed")
  1755  		return wait.DontTryAgain
  1756  	}
  1757  	redemption, err := chain.Redemption(params.CoinID, cpSwapCoin, cpContract)
  1758  	// If there is an error, don't return an error yet, since it could be due to
  1759  	// network latency. Instead, queue it up for another check.
  1760  	if err != nil {
  1761  		if errors.Is(err, asset.CoinNotFoundError) {
  1762  			return wait.TryAgain
  1763  		}
  1764  		actor.status.mtx.RLock()
  1765  		log.Warnf("Redemption error encountered for match %s, actor %s, using coin ID %v to satisfy contract at %x: %v",
  1766  			stepInfo.match.ID(), actor, params.CoinID, cpSwapCoin, err)
  1767  		actor.status.mtx.RUnlock()
  1768  		actor.status.endRedeemSearch() // allow client retry even before notifying him
  1769  		s.respondError(msg.ID, actor.user, msgjson.RedemptionError,
  1770  			fmt.Sprintf("redemption error encountered: %v", err))
  1771  		return wait.DontTryAgain
  1772  	}
  1773  
  1774  	newStatus := stepInfo.nextStep
  1775  
  1776  	// NOTE: redemption.FeeRate is not checked since the counterparty is not
  1777  	// inconvenienced by slow confirmation of the redemption.
  1778  
  1779  	// Modify the match's swapStatuses, but only if the match wasn't revoked
  1780  	// while waiting for the txn.
  1781  	s.matchMtx.RLock()
  1782  	if _, found := s.matches[matchID]; !found {
  1783  		s.matchMtx.RUnlock()
  1784  		log.Errorf("Redeem txn found after match was revoked (match id=%v, maker=%v)",
  1785  			matchID, actor.isMaker)
  1786  		actor.status.endRedeemSearch() // allow client retry even before notifying him
  1787  		s.respondError(msg.ID, actor.user, msgjson.RedemptionError, "match already revoked due to inaction")
  1788  		return wait.DontTryAgain
  1789  	}
  1790  
  1791  	actor.status.mtx.Lock()
  1792  	redeemTime := unixMsNow()
  1793  	actor.status.redemption = redemption // redemption should not be set already (handleRedeem should gate)
  1794  	actor.status.redeemTime = redeemTime
  1795  	actor.status.mtx.Unlock()
  1796  
  1797  	match.mtx.Lock()
  1798  	match.Status = newStatus // handleRedeem (gate mechanism) won't allow backward progress
  1799  	match.mtx.Unlock()
  1800  
  1801  	// Only unlock match map after the statuses and txn times are stored,
  1802  	// ensuring that checkInaction will not revoke the match as we respond.
  1803  	s.matchMtx.RUnlock()
  1804  
  1805  	// Redemption now recorded and will be used to reject backward progress (duplicate
  1806  	// or malicious requests client might still send after this point).
  1807  	actor.status.endRedeemSearch()
  1808  
  1809  	log.Debugf("processRedeem: valid redemption %v (%s) spending contract %s received at %v from %v (%s) for match %v, "+
  1810  		"swapStatus %v => %v", redemption, stepInfo.asset.Symbol, cpSwapStr, redeemTime, actor.user,
  1811  		makerTaker(actor.isMaker), matchID, stepInfo.step, newStatus)
  1812  	// If MatchComplete, we'll delete the match in processAck if the maker
  1813  	// responds to the courtesy redemption request, or checkInactionEventBased.
  1814  
  1815  	// Store the swap contract and the coinID (e.g. txid:vout) containing the
  1816  	// contract script hash. Maker is party A, the initiator, who first reveals
  1817  	// the secret. Taker is party B, the participant.
  1818  	storFn := s.storage.SaveRedeemB // taker's redeem also sets match status to MatchComplete, active to FALSE
  1819  	if actor.isMaker {
  1820  		// Maker redeem stores the secret too.
  1821  		storFn = func(mid db.MarketMatchID, coinID []byte, timestamp int64) error {
  1822  			return s.storage.SaveRedeemA(mid, coinID, params.Secret, timestamp) // also sets match status to MakerRedeemed
  1823  		}
  1824  	}
  1825  
  1826  	redeemTimeMs := redeemTime.UnixMilli()
  1827  	err = storFn(db.MatchID(match.Match), params.CoinID, redeemTimeMs)
  1828  	if err != nil {
  1829  		log.Errorf("saving redeem transaction (match id=%v, maker=%v) failed: %v",
  1830  			matchID, actor.isMaker, err)
  1831  		// Neither party's fault. Continue.
  1832  	}
  1833  
  1834  	// Credit the user for completing the swap, adjusting the user's score.
  1835  	if actor.user != counterParty.user {
  1836  		s.authMgr.SwapSuccess(actor.user, db.MatchID(match.Match), match.Quantity, redeemTime) // maybe call this in swapDone callback
  1837  	}
  1838  
  1839  	// Issue a positive response to the actor.
  1840  	s.authMgr.Sign(params)
  1841  	s.respondSuccess(msg.ID, actor.user, &msgjson.Acknowledgement{
  1842  		MatchID: matchID[:],
  1843  		Sig:     params.Sig,
  1844  	})
  1845  
  1846  	// Cancellation rate accounting
  1847  	ord := match.Taker
  1848  	if actor.isMaker {
  1849  		ord = match.Maker
  1850  	}
  1851  
  1852  	s.swapDone(ord, match.Match, false)
  1853  
  1854  	// Inform the counterparty, even though the maker doesn't really care about
  1855  	// the taker's redeem details.
  1856  	rParams := &msgjson.Redemption{
  1857  		Redeem: msgjson.Redeem{
  1858  			OrderID: idToBytes(counterParty.order.ID()),
  1859  			MatchID: matchID[:],
  1860  			CoinID:  params.CoinID,
  1861  			Secret:  params.Secret,
  1862  		},
  1863  		Time: uint64(redeemTimeMs),
  1864  	}
  1865  	s.authMgr.Sign(rParams)
  1866  	redemptionReq, err := msgjson.NewRequest(comms.NextID(), msgjson.RedemptionRoute, rParams)
  1867  	if err != nil {
  1868  		log.Errorf("error creating redemption request: %v", err)
  1869  		return wait.DontTryAgain
  1870  	}
  1871  
  1872  	// Send the redemption request.
  1873  	log.Debugf("processRedeem: sending 'redemption' request to counterparty %v (%s) "+
  1874  		"for match %v", counterParty.user, makerTaker(counterParty.isMaker), matchID)
  1875  
  1876  	// Set up the redemption acknowledgement callback.
  1877  	ack := &messageAcker{
  1878  		user:    counterParty.user,
  1879  		match:   match,
  1880  		params:  rParams,
  1881  		isMaker: counterParty.isMaker,
  1882  		// isAudit: false,
  1883  	}
  1884  
  1885  	// The counterparty does not need to actually locate the redemption txn,
  1886  	// so use the default request timeout.
  1887  	err = s.authMgr.RequestWithTimeout(ack.user, redemptionReq, func(_ comms.Link, resp *msgjson.Message) {
  1888  		s.processAck(resp, ack) // resp.ID == notification.ID
  1889  	}, time.Until(redeemTime.Add(s.bTimeout)), func() {
  1890  		log.Infof("Timeout waiting for 'redemption' request from user %v (%s) for match %v",
  1891  			ack.user, makerTaker(ack.isMaker), matchID)
  1892  	})
  1893  	if err != nil {
  1894  		log.Debugf("Couldn't send 'redemption' request to user %v (%s) for match %v", ack.user, makerTaker(ack.isMaker), matchID)
  1895  	}
  1896  
  1897  	return wait.DontTryAgain
  1898  }
  1899  
  1900  // handleInit handles the 'init' request from a user, which is used to inform
  1901  // the DEX of a newly broadcast swap transaction. The Init message includes the
  1902  // swap contract script and the CoinID of contract. Most of the work is
  1903  // performed by processInit, but the request is parsed and user is authenticated
  1904  // first.
  1905  func (s *Swapper) handleInit(user account.AccountID, msg *msgjson.Message) *msgjson.Error {
  1906  	s.handlerMtx.RLock()
  1907  	defer s.handlerMtx.RUnlock() // block shutdown until registered with latencyQ
  1908  	if s.stop {
  1909  		return &msgjson.Error{
  1910  			Code:    msgjson.TryAgainLaterError,
  1911  			Message: "The swapper is stopping. Try again later.",
  1912  		}
  1913  	}
  1914  
  1915  	params := new(msgjson.Init)
  1916  	err := msg.Unmarshal(&params)
  1917  	if err != nil || params == nil {
  1918  		return &msgjson.Error{
  1919  			Code:    msgjson.RPCParseError,
  1920  			Message: "Error decoding 'init' method params",
  1921  		}
  1922  	}
  1923  
  1924  	// Verify the user's signature of params.
  1925  	rpcErr := s.authUser(user, params)
  1926  	if rpcErr != nil {
  1927  		return rpcErr
  1928  	}
  1929  
  1930  	log.Debugf("handleInit: 'init' received from user %v for match %v, order %v",
  1931  		user, params.MatchID, params.OrderID)
  1932  
  1933  	if len(params.MatchID) != order.MatchIDSize {
  1934  		return &msgjson.Error{
  1935  			Code:    msgjson.RPCParseError,
  1936  			Message: "Invalid 'matchid' in 'init' message",
  1937  		}
  1938  	}
  1939  
  1940  	var matchID order.MatchID
  1941  	copy(matchID[:], params.MatchID)
  1942  	stepInfo, rpcErr := s.step(user, matchID)
  1943  	if rpcErr != nil {
  1944  		return rpcErr
  1945  	}
  1946  
  1947  	// init requests should only be sent when contracts are still required, in
  1948  	// the correct sequence, and by the correct party.
  1949  	switch stepInfo.step {
  1950  	case order.NewlyMatched, order.MakerSwapCast:
  1951  		// Ensure we only start one coin waiter for this swap. This is an atomic
  1952  		// CAS, so it must ultimately be followed by endSwapSearch().
  1953  		if !stepInfo.actor.status.startSwapSearch() {
  1954  			return &msgjson.Error{
  1955  				Code:    msgjson.DuplicateRequestError, // not really a sequence error since they are still the "actor"
  1956  				Message: "already received a swap contract, search in progress",
  1957  			}
  1958  		}
  1959  	default:
  1960  		return &msgjson.Error{
  1961  			Code:    msgjson.SettlementSequenceError,
  1962  			Message: "swap contract already provided",
  1963  		}
  1964  	}
  1965  
  1966  	// Validate the coinID and contract script before starting a coin waiter.
  1967  	coinStr, err := stepInfo.asset.Backend.ValidateCoinID(params.CoinID)
  1968  	if err != nil {
  1969  		stepInfo.actor.status.endSwapSearch() // not gonna start the search
  1970  		// TODO: ensure Backends provide sanitized errors or type information to
  1971  		// provide more details to the client.
  1972  		return &msgjson.Error{
  1973  			Code:    msgjson.ContractError,
  1974  			Message: "invalid contract coinID or script",
  1975  		}
  1976  	}
  1977  	err = stepInfo.asset.Backend.ValidateContract(params.Contract)
  1978  	if err != nil {
  1979  		stepInfo.actor.status.endSwapSearch() // not gonna start the search
  1980  		log.Debugf("ValidateContract (asset %v, coin %v) failure: %v", stepInfo.asset.Symbol, coinStr, err)
  1981  		// TODO: ensure Backends provide sanitized errors or type information to
  1982  		// provide more details to the client.
  1983  		return &msgjson.Error{
  1984  			Code:    msgjson.ContractError,
  1985  			Message: "invalid swap contract",
  1986  		}
  1987  	}
  1988  
  1989  	// TODO: consider also checking recipient of contract here, but it is also
  1990  	// checked in processInit. Note that value cannot be checked as transaction
  1991  	// details, which includes the coin/output value, are not yet retrieved.
  1992  
  1993  	// Search for the transaction for the full txWaitExpiration, even if it goes
  1994  	// past the inaction deadline. processInit recognizes when it is revoked.
  1995  	expireTime := time.Now().Add(s.txWaitExpiration).UTC()
  1996  	log.Debugf("Allowing until %v (%v) to locate contract from %v (%v), match %v, tx %s (%s)",
  1997  		expireTime, time.Until(expireTime), makerTaker(stepInfo.actor.isMaker),
  1998  		stepInfo.step, matchID, coinStr, stepInfo.asset.Symbol)
  1999  
  2000  	// Since we have to consider broadcast latency of the asset's network, run
  2001  	// this as a coin waiter.
  2002  	s.latencyQ.Wait(&wait.Waiter{
  2003  		Expiration: expireTime,
  2004  		TryFunc: func() wait.TryDirective {
  2005  			return s.processInit(msg, params, stepInfo)
  2006  		},
  2007  		ExpireFunc: func() {
  2008  			stepInfo.actor.status.endSwapSearch() // allow init retries
  2009  			// NOTE: We may consider a shorter expire time so the client can
  2010  			// receive warning that there may be node or wallet connectivity
  2011  			// trouble while they still have a chance to fix it.
  2012  			s.respondError(msg.ID, user, msgjson.TransactionUndiscovered,
  2013  				fmt.Sprintf("failed to find contract coin %v", coinStr))
  2014  		},
  2015  	})
  2016  	return nil
  2017  }
  2018  
  2019  // handleRedeem handles the 'redeem' request from a user. Most of the work is
  2020  // performed by processRedeem, but the request is parsed and user is
  2021  // authenticated first.
  2022  func (s *Swapper) handleRedeem(user account.AccountID, msg *msgjson.Message) *msgjson.Error {
  2023  	s.handlerMtx.RLock()
  2024  	defer s.handlerMtx.RUnlock() // block shutdown until registered with latencyQ
  2025  	if s.stop {
  2026  		return &msgjson.Error{
  2027  			Code:    msgjson.TryAgainLaterError,
  2028  			Message: "The swapper is stopping. Try again later.",
  2029  		}
  2030  	}
  2031  
  2032  	params := new(msgjson.Redeem)
  2033  	err := msg.Unmarshal(&params)
  2034  	if err != nil || params == nil {
  2035  		return &msgjson.Error{
  2036  			Code:    msgjson.RPCParseError,
  2037  			Message: "Error decoding 'redeem' request payload",
  2038  		}
  2039  	}
  2040  
  2041  	rpcErr := s.authUser(user, params)
  2042  	if rpcErr != nil {
  2043  		return rpcErr
  2044  	}
  2045  
  2046  	log.Debugf("handleRedeem: 'redeem' received from %v for match %v, order %v",
  2047  		user, params.MatchID, params.OrderID)
  2048  
  2049  	if len(params.MatchID) != order.MatchIDSize {
  2050  		return &msgjson.Error{
  2051  			Code:    msgjson.RPCParseError,
  2052  			Message: "Invalid 'matchid' in 'redeem' message",
  2053  		}
  2054  	}
  2055  
  2056  	var matchID order.MatchID
  2057  	copy(matchID[:], params.MatchID)
  2058  	stepInfo, rpcErr := s.step(user, matchID)
  2059  	if rpcErr != nil {
  2060  		return rpcErr
  2061  	}
  2062  
  2063  	// redeem requests should only be sent when all contracts have been
  2064  	// received, in the correct sequence, and by the correct party.
  2065  	switch stepInfo.step {
  2066  	case order.TakerSwapCast, order.MakerRedeemed:
  2067  		// Ensure we only start one coin waiter for this redeem. This is an
  2068  		// atomic CAS, so it must ultimately be followed by endRedeemSearch().
  2069  		if !stepInfo.actor.status.startRedeemSearch() {
  2070  			return &msgjson.Error{
  2071  				Code:    msgjson.DuplicateRequestError, // not really a sequence error since they are still the "actor"
  2072  				Message: "already received a redeem transaction, search in progress",
  2073  			}
  2074  		}
  2075  	default: // also includes MatchComplete
  2076  		return &msgjson.Error{
  2077  			Code:    msgjson.SettlementSequenceError,
  2078  			Message: "swap contracts not yet received",
  2079  		}
  2080  	}
  2081  
  2082  	// Validate the redeem coin ID before starting a wait. This does not
  2083  	// check the blockchain, but does ensure the CoinID can be decoded for the
  2084  	// asset before starting up a coin waiter.
  2085  	coinStr, err := stepInfo.asset.Backend.ValidateCoinID(params.CoinID)
  2086  	if err != nil {
  2087  		stepInfo.actor.status.endRedeemSearch()
  2088  		// TODO: ensure Backends provide sanitized errors or type information to
  2089  		// provide more details to the client.
  2090  		return &msgjson.Error{
  2091  			Code:    msgjson.ContractError,
  2092  			Message: "invalid 'redeem' parameters",
  2093  		}
  2094  	}
  2095  
  2096  	// Search for the transaction for the full txWaitExpiration, even if it goes
  2097  	// past the inaction deadline. processRedeem recognizes when it is revoked.
  2098  	expireTime := time.Now().Add(s.txWaitExpiration).UTC()
  2099  	log.Debugf("Allowing until %v (%v) to locate redeem from %v (%v), match %v, tx %s (%s)",
  2100  		expireTime, time.Until(expireTime), makerTaker(stepInfo.actor.isMaker),
  2101  		stepInfo.step, matchID, coinStr, stepInfo.asset.Symbol)
  2102  
  2103  	// Since we have to consider latency, run this as a coin waiter.
  2104  	s.latencyQ.Wait(&wait.Waiter{
  2105  		Expiration: expireTime,
  2106  		TryFunc: func() wait.TryDirective {
  2107  			return s.processRedeem(msg, params, stepInfo)
  2108  		},
  2109  		ExpireFunc: func() {
  2110  			stepInfo.actor.status.endRedeemSearch()
  2111  			// NOTE: We may consider a shorter expire time so the client can
  2112  			// receive warning that there may be node or wallet connectivity
  2113  			// trouble while they still have a chance to fix it.
  2114  			s.respondError(msg.ID, user, msgjson.TransactionUndiscovered,
  2115  				fmt.Sprintf("failed to find redeemed coin %v", coinStr))
  2116  		},
  2117  	})
  2118  	return nil
  2119  }
  2120  
  2121  // revoke revokes the match, sending the 'revoke_match' request to each client
  2122  // and processing the acknowledgement. Match Sigs and Status are not accessed.
  2123  func (s *Swapper) revoke(match *matchTracker) {
  2124  	route := msgjson.RevokeMatchRoute
  2125  	log.Infof("Sending a '%s' notification to each client for match %v",
  2126  		route, match.ID())
  2127  
  2128  	sendRev := func(mid order.MatchID, ord order.Order) {
  2129  		msg := &msgjson.RevokeMatch{
  2130  			OrderID: ord.ID().Bytes(),
  2131  			MatchID: mid[:],
  2132  		}
  2133  		s.authMgr.Sign(msg)
  2134  		ntfn, err := msgjson.NewNotification(route, msg)
  2135  		if err != nil {
  2136  			log.Errorf("Failed to create '%s' notification for user %v, match %v: %v",
  2137  				route, ord.User(), mid, err)
  2138  			return
  2139  		}
  2140  		if err = s.authMgr.Send(ord.User(), ntfn); err != nil {
  2141  			log.Debugf("Failed to send '%s' notification to user %v, match %v: %v",
  2142  				route, ord.User(), mid, err)
  2143  		}
  2144  	}
  2145  
  2146  	mid := match.ID()
  2147  	sendRev(mid, match.Taker)
  2148  	sendRev(mid, match.Maker)
  2149  }
  2150  
  2151  // For the 'match' request, the user returns a msgjson.Acknowledgement array
  2152  // with signatures for each match ID. The match acknowledgements were requested
  2153  // from each matched user in Negotiate.
  2154  func (s *Swapper) processMatchAcks(user account.AccountID, msg *msgjson.Message, matches []*messageAcker) {
  2155  	// NOTE: acks must be in same order as matches []*messageAcker.
  2156  	var acks []msgjson.Acknowledgement
  2157  	err := msg.UnmarshalResult(&acks)
  2158  	if err != nil {
  2159  		s.respondError(msg.ID, user, msgjson.RPCParseError,
  2160  			fmt.Sprintf("error parsing match request acknowledgment: %v", err))
  2161  		return
  2162  	}
  2163  	if len(matches) != len(acks) {
  2164  		s.respondError(msg.ID, user, msgjson.AckCountError,
  2165  			fmt.Sprintf("expected %d acknowledgements, got %d", len(matches), len(acks)))
  2166  		return
  2167  	}
  2168  
  2169  	log.Debugf("processMatchAcks: 'match' ack received from %v for %d matches",
  2170  		user, len(matches))
  2171  
  2172  	// Verify the signature of each Acknowledgement, and store the signatures in
  2173  	// the matchTracker of each match (messageAcker). The signature will be
  2174  	// either a MakerMatch or TakerMatch signature depending on whether the
  2175  	// responding user is the maker or taker.
  2176  	for i, matchInfo := range matches {
  2177  		ack := &acks[i]
  2178  		match := matchInfo.match
  2179  
  2180  		matchID := match.ID()
  2181  		if !bytes.Equal(ack.MatchID, matchID[:]) {
  2182  			s.respondError(msg.ID, user, msgjson.IDMismatchError,
  2183  				fmt.Sprintf("unexpected match ID at acknowledgment index %d", i))
  2184  			return
  2185  		}
  2186  		sigMsg := matchInfo.params.Serialize()
  2187  		err = s.authMgr.Auth(user, sigMsg, ack.Sig)
  2188  		if err != nil {
  2189  			log.Warnf("processMatchAcks: 'match' ack for match %v from user %v, "+
  2190  				" failed sig verification: %v", matchID, user, err)
  2191  			s.respondError(msg.ID, user, msgjson.SignatureError,
  2192  				fmt.Sprintf("signature validation error: %v", err))
  2193  			return
  2194  		}
  2195  
  2196  		// Store the signature in the matchTracker. These must be collected
  2197  		// before the init steps begin and swap contracts are broadcasted.
  2198  		match.mtx.Lock()
  2199  		status := match.Status
  2200  		if matchInfo.isMaker {
  2201  			match.Sigs.MakerMatch = ack.Sig
  2202  		} else {
  2203  			match.Sigs.TakerMatch = ack.Sig
  2204  		}
  2205  		match.mtx.Unlock()
  2206  		log.Debugf("processMatchAcks: storing valid 'match' ack signature from %v (maker=%v) "+
  2207  			"for match %v (status %v)", user, matchInfo.isMaker, matchID, status)
  2208  	}
  2209  
  2210  	// Store the signatures in the DB.
  2211  	for i, matchInfo := range matches {
  2212  		ackSig := acks[i].Sig
  2213  		match := matchInfo.match
  2214  
  2215  		storFn := s.storage.SaveMatchAckSigB
  2216  		if matchInfo.isMaker {
  2217  			storFn = s.storage.SaveMatchAckSigA
  2218  		}
  2219  		matchID := match.ID()
  2220  		mid := db.MarketMatchID{
  2221  			MatchID: matchID,
  2222  			Base:    match.Maker.BaseAsset, // same for taker's redeem as BaseAsset refers to the market
  2223  			Quote:   match.Maker.QuoteAsset,
  2224  		}
  2225  		err = storFn(mid, ackSig)
  2226  		if err != nil {
  2227  			log.Errorf("saving match ack signature (match id=%v, maker=%v) failed: %v",
  2228  				matchID, matchInfo.isMaker, err)
  2229  			s.respondError(msg.ID, matchInfo.user, msgjson.UnknownMarketError,
  2230  				"internal server error")
  2231  			// TODO: revoke the match without penalties?
  2232  			return
  2233  		}
  2234  	}
  2235  }
  2236  
  2237  // CheckUnspent attempts to verify a coin ID for a given asset by retrieving the
  2238  // corresponding asset.Coin. If the coin is not found or spent, an
  2239  // asset.CoinNotFoundError is returned. CheckUnspent returns immediately with
  2240  // no error if the requested asset is not a utxo-based asset.
  2241  func (s *Swapper) CheckUnspent(ctx context.Context, assetID uint32, coinID []byte) error {
  2242  	backend := s.coins[assetID]
  2243  	if backend == nil {
  2244  		return fmt.Errorf("unknown asset %d", assetID)
  2245  	}
  2246  	outputTracker, is := backend.Backend.(asset.OutputTracker)
  2247  	if !is {
  2248  		return nil
  2249  	}
  2250  	return outputTracker.VerifyUnspentCoin(ctx, coinID)
  2251  }
  2252  
  2253  // LockOrdersCoins locks the backing coins for the provided orders.
  2254  func (s *Swapper) LockOrdersCoins(orders []order.Order) {
  2255  	// Separate orders according to the asset of their locked coins.
  2256  	assetCoinOrders := make(map[uint32][]order.Order, len(orders))
  2257  	for _, ord := range orders {
  2258  		// Identify the asset of the locked coins.
  2259  		asset := ord.Quote()
  2260  		if ord.Trade().Sell {
  2261  			asset = ord.Base()
  2262  		}
  2263  		assetCoinOrders[asset] = append(assetCoinOrders[asset], ord)
  2264  	}
  2265  
  2266  	for asset, orders := range assetCoinOrders {
  2267  		s.lockOrdersCoins(asset, orders)
  2268  	}
  2269  }
  2270  
  2271  func (s *Swapper) lockOrdersCoins(assetID uint32, orders []order.Order) {
  2272  	swapperAsset := s.coins[assetID]
  2273  	if swapperAsset == nil {
  2274  		log.Errorf(fmt.Sprintf("lockOrderCoins called for unknown asset %d", assetID))
  2275  		return
  2276  	}
  2277  	if swapperAsset.Locker == nil {
  2278  		return
  2279  	}
  2280  
  2281  	swapperAsset.Locker.LockOrdersCoins(orders)
  2282  }
  2283  
  2284  // LockCoins locks coins of a given asset. The OrderID is used for tracking.
  2285  func (s *Swapper) LockCoins(asset uint32, coins map[order.OrderID][]order.CoinID) {
  2286  	swapperAsset := s.coins[asset]
  2287  	if swapperAsset == nil {
  2288  		panic(fmt.Sprintf("Unable to lock coins for asset %d", asset))
  2289  	}
  2290  	if swapperAsset.Locker == nil {
  2291  		return
  2292  	}
  2293  
  2294  	swapperAsset.Locker.LockCoins(coins)
  2295  }
  2296  
  2297  // unlockOrderCoins is not exported since only the Swapper knows when to unlock
  2298  // coins (when funding coins are spent in a fully-confirmed contract).
  2299  func (s *Swapper) unlockOrderCoins(ord order.Order) {
  2300  	assetID := ord.Quote()
  2301  	if ord.Trade().Sell {
  2302  		assetID = ord.Base()
  2303  	}
  2304  
  2305  	s.unlockOrderIDCoins(assetID, ord.ID())
  2306  }
  2307  
  2308  func (s *Swapper) unlockOrderIDCoins(assetID uint32, oid order.OrderID) {
  2309  	swapperAsset := s.coins[assetID]
  2310  	if swapperAsset == nil {
  2311  		log.Errorf(fmt.Sprintf("unlockOrderIDCoins called for unknown asset %d", assetID))
  2312  		return
  2313  	}
  2314  	if swapperAsset.Locker == nil {
  2315  		return
  2316  	}
  2317  
  2318  	swapperAsset.Locker.UnlockOrderCoins(oid)
  2319  }
  2320  
  2321  // matchNotifications creates a pair of msgjson.Match from a matchTracker.
  2322  func matchNotifications(match *matchTracker) (makerMsg *msgjson.Match, takerMsg *msgjson.Match) {
  2323  	// NOTE: If we decide that msgjson.Match should just have a
  2324  	// "FeeRateBaseSwap" field, this could be set according to the
  2325  	// swapStatus.swapAsset field:
  2326  	//
  2327  	// base, quote := match.Maker.BaseAsset, match.Maker.QuoteAsset
  2328  	// feeRate := func(assetID uint32) uint64 {
  2329  	// 	if assetID == match.Maker.BaseAsset {
  2330  	// 		return match.FeeRateBase
  2331  	// 	}
  2332  	// 	return match.FeeRateQuote
  2333  	// }
  2334  	// FeeRateMakerSwap := feeRate(match.makerStatus.swapAsset)
  2335  
  2336  	// If the taker order is a cancel, omit the maker (trade) order's address
  2337  	// since it is dead weight. Consider omitting the numeric fields too.
  2338  	var makerAddr string
  2339  	if match.Taker.Type() != order.CancelOrderType {
  2340  		makerAddr = order.ExtractAddress(match.Maker)
  2341  	}
  2342  
  2343  	stamp := uint64(match.matchTime.UnixMilli())
  2344  	return &msgjson.Match{
  2345  			OrderID:      idToBytes(match.Maker.ID()),
  2346  			MatchID:      idToBytes(match.ID()),
  2347  			Quantity:     match.Quantity,
  2348  			Rate:         match.Rate,
  2349  			Address:      order.ExtractAddress(match.Taker),
  2350  			ServerTime:   stamp,
  2351  			FeeRateBase:  match.FeeRateBase,
  2352  			FeeRateQuote: match.FeeRateQuote,
  2353  			Side:         uint8(order.Maker),
  2354  		}, &msgjson.Match{
  2355  			OrderID:      idToBytes(match.Taker.ID()),
  2356  			MatchID:      idToBytes(match.ID()),
  2357  			Quantity:     match.Quantity,
  2358  			Rate:         match.Rate,
  2359  			Address:      makerAddr,
  2360  			ServerTime:   stamp,
  2361  			FeeRateBase:  match.FeeRateBase,
  2362  			FeeRateQuote: match.FeeRateQuote,
  2363  			Side:         uint8(order.Taker),
  2364  		}
  2365  }
  2366  
  2367  // readMatches translates a slice of raw matches from the market manager into
  2368  // a slice of matchTrackers.
  2369  func readMatches(matchSets []*order.MatchSet) []*matchTracker {
  2370  	// The initial capacity guess here is a minimum, but will avoid a few
  2371  	// reallocs.
  2372  	nowMs := unixMsNow()
  2373  	matches := make([]*matchTracker, 0, len(matchSets))
  2374  	for _, matchSet := range matchSets {
  2375  		for _, match := range matchSet.Matches() {
  2376  			maker := match.Maker
  2377  			base, quote := maker.BaseAsset, maker.QuoteAsset
  2378  			var makerSwapAsset, takerSwapAsset uint32
  2379  			if maker.Sell {
  2380  				makerSwapAsset = base
  2381  				takerSwapAsset = quote
  2382  			} else {
  2383  				makerSwapAsset = quote
  2384  				takerSwapAsset = base
  2385  			}
  2386  
  2387  			matches = append(matches, &matchTracker{
  2388  				Match:     match,
  2389  				time:      nowMs,
  2390  				matchTime: match.Epoch.End(),
  2391  				makerStatus: &swapStatus{
  2392  					swapAsset:   makerSwapAsset,
  2393  					redeemAsset: takerSwapAsset,
  2394  				},
  2395  				takerStatus: &swapStatus{
  2396  					swapAsset:   takerSwapAsset,
  2397  					redeemAsset: makerSwapAsset,
  2398  				},
  2399  			})
  2400  		}
  2401  	}
  2402  	return matches
  2403  }
  2404  
  2405  // Negotiate takes ownership of the matches and begins swap negotiation. For
  2406  // reliable identification of completed orders when redeem acks are received and
  2407  // processed by processAck, BeginMatchAndNegotiate should be called prior to
  2408  // matching and order status/amount updates, and EndMatchAndNegotiate should be
  2409  // called after Negotiate. This locking sequence allows for orders that may
  2410  // already be involved in active swaps to remain unmodified by the
  2411  // Matcher/Market until new matches are recorded by the Swapper in Negotiate. If
  2412  // this is not done, it is possible that an order may be flagged as completed if
  2413  // a swap A completes after Matching and creation of swap B but before Negotiate
  2414  // has a chance to record the new swap.
  2415  func (s *Swapper) Negotiate(matchSets []*order.MatchSet) {
  2416  	// If the Swapper is stopping, the Markets should also be stopping, but
  2417  	// block this just in case.
  2418  	s.handlerMtx.RLock()
  2419  	defer s.handlerMtx.RUnlock()
  2420  	if s.stop {
  2421  		log.Errorf("Negotiate called on stopped swapper. Matches lost!")
  2422  		return
  2423  	}
  2424  
  2425  	// Lock trade order coins, and get current optimal fee rates. Also filter
  2426  	// out matches with unsupported assets, which should not happen if the
  2427  	// Market is behaving, but be defensive.
  2428  	supportedMatchSets := matchSets[:0]                    // same buffer, start empty
  2429  	swapOrders := make([]order.Order, 0, 2*len(matchSets)) // size guess, with the single maker case
  2430  	for _, match := range matchSets {
  2431  		supportedMatchSets = append(supportedMatchSets, match)
  2432  
  2433  		if match.Taker.Type() == order.CancelOrderType {
  2434  			continue
  2435  		}
  2436  
  2437  		swapOrders = append(swapOrders, match.Taker)
  2438  		for _, maker := range match.Makers {
  2439  			swapOrders = append(swapOrders, maker)
  2440  		}
  2441  	}
  2442  	matchSets = supportedMatchSets
  2443  
  2444  	s.LockOrdersCoins(swapOrders)
  2445  
  2446  	// Set up the matchTrackers, which includes a slice of Matches.
  2447  	matches := readMatches(matchSets)
  2448  
  2449  	// Record the matches. If any DB updates fail, no swaps proceed. We could
  2450  	// let the others proceed, but that could seem selective trickery to the
  2451  	// clients.
  2452  	for _, match := range matches {
  2453  		// Note that matches where the taker order is a cancel will be stored
  2454  		// with status MatchComplete, and without the maker or taker swap
  2455  		// addresses. The match will also be flagged as inactive since there is
  2456  		// no associated swap negotiation.
  2457  
  2458  		// TODO: Initially store cancel matches lacking ack sigs as active, only
  2459  		// flagging as inactive when both maker and taker match ack sigs have
  2460  		// been received. The client will need a mechanism to provide the ack,
  2461  		// perhaps having the server resend missing match ack requests on client
  2462  		// connect.
  2463  		if err := s.storage.InsertMatch(match.Match); err != nil {
  2464  			log.Errorf("InsertMatch (match id=%v) failed: %v", match.ID(), err)
  2465  			// TODO: notify clients (notification or response to what?)
  2466  			// abortAll()
  2467  			return
  2468  		}
  2469  	}
  2470  
  2471  	userMatches := make(map[account.AccountID][]*messageAcker)
  2472  	// addUserMatch signs a match notification message, and add the data
  2473  	// required to process the acknowledgment to the userMatches map.
  2474  	addUserMatch := func(acker *messageAcker) {
  2475  		s.authMgr.Sign(acker.params)
  2476  		userMatches[acker.user] = append(userMatches[acker.user], acker)
  2477  	}
  2478  
  2479  	// Setting length to max possible, which is over-allocating by the number of
  2480  	// cancels.
  2481  	toMonitor := make([]*matchTracker, 0, len(matches))
  2482  	for _, match := range matches {
  2483  		if match.Taker.Type() == order.CancelOrderType {
  2484  			// If this is a cancellation, there is nothing to track. Just cancel
  2485  			// the target order by removing it from the DB. It is already
  2486  			// removed from book by the Market.
  2487  			err := s.storage.CancelOrder(match.Maker) // TODO: do this in Market?
  2488  			if err != nil {
  2489  				log.Errorf("Failed to cancel order %v", match.Maker)
  2490  				// If the DB update failed, the target order status was not
  2491  				// updated, but removed from the in-memory book. This is
  2492  				// potentially a critical failure since the dex will restore the
  2493  				// book from the DB. TODO: Notify clients.
  2494  				return
  2495  			}
  2496  		} else {
  2497  			toMonitor = append(toMonitor, match)
  2498  		}
  2499  
  2500  		// Create an acker for maker and taker, sharing the same matchTracker.
  2501  		makerMsg, takerMsg := matchNotifications(match) // msgjson.Match for each party
  2502  		addUserMatch(&messageAcker{
  2503  			user:    match.Maker.User(),
  2504  			match:   match,
  2505  			params:  makerMsg,
  2506  			isMaker: true,
  2507  			// isAudit: false,
  2508  		})
  2509  		addUserMatch(&messageAcker{
  2510  			user:    match.Taker.User(),
  2511  			match:   match,
  2512  			params:  takerMsg,
  2513  			isMaker: false,
  2514  			// isAudit: false,
  2515  		})
  2516  	}
  2517  
  2518  	// Add the matches to the matches/userMatches maps.
  2519  	s.matchMtx.Lock()
  2520  	for _, match := range toMonitor {
  2521  		s.addMatch(match)
  2522  	}
  2523  	s.matchMtx.Unlock()
  2524  
  2525  	// Send the user match notifications.
  2526  	for user, matches := range userMatches {
  2527  		// msgs is a slice of msgjson.Match created by newMatchAckers
  2528  		// (matchNotifications) for all makers and takers.
  2529  		msgs := make([]msgjson.Signable, 0, len(matches))
  2530  		for _, m := range matches {
  2531  			msgs = append(msgs, m.params)
  2532  		}
  2533  
  2534  		// Solicit match acknowledgments. Each Match is signed in addUserMatch.
  2535  		req, err := msgjson.NewRequest(comms.NextID(), msgjson.MatchRoute, msgs)
  2536  		if err != nil {
  2537  			log.Errorf("error creating match notification request: %v", err)
  2538  			// Should never happen, but the client can still use match_status.
  2539  			continue
  2540  		}
  2541  
  2542  		// Copy the loop variables for capture by the match acknowledgement
  2543  		// response handler.
  2544  		u, m := user, matches
  2545  		log.Debugf("Negotiate: sending 'match' ack request to user %v for %d matches",
  2546  			u, len(m))
  2547  
  2548  		// Send the request.
  2549  		err = s.authMgr.Request(u, req, func(_ comms.Link, resp *msgjson.Message) {
  2550  			s.processMatchAcks(u, resp, m)
  2551  		})
  2552  		if err != nil {
  2553  			log.Infof("Failed to send %v request to %v. The match will be returned in the connect response.",
  2554  				req.Route, u)
  2555  		}
  2556  	}
  2557  }
  2558  
  2559  func idToBytes(id [order.OrderIDSize]byte) []byte {
  2560  	return id[:]
  2561  }