decred.org/dcrdex@v1.0.5/server/coinlock/coinlocker.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 coinlock
     5  
     6  import (
     7  	"fmt"
     8  	"sync"
     9  
    10  	"decred.org/dcrdex/dex/order"
    11  )
    12  
    13  type CoinID = order.CoinID
    14  
    15  // CoinLockChecker provides the ability to check if a coin or an order's backing
    16  // coins are locked.
    17  type CoinLockChecker interface {
    18  	// CoinLocked indicates if a coin is locked.
    19  	CoinLocked(coin CoinID) bool
    20  	// OrderCoinsLocked returns all coins locked by an order.
    21  	OrderCoinsLocked(oid order.OrderID) []CoinID
    22  }
    23  
    24  // CoinLocker provides the ability to lock, unlock and check lock status of
    25  // coins.
    26  type CoinLocker interface {
    27  	CoinLockChecker
    28  	// UnlockAll releases all locked coins.
    29  	UnlockAll()
    30  	// UnlockOrderCoins unlocks all locked coins associated with an order.
    31  	UnlockOrderCoins(oid order.OrderID)
    32  	// UnlockOrdersCoins is like UnlockOrderCoins for multiple orders.
    33  	UnlockOrdersCoins(oids []order.OrderID)
    34  	// LockOrdersCoins locks all of the coins associated with multiple orders.
    35  	LockOrdersCoins(orders []order.Order) (failed []order.Order)
    36  	// LockCoins locks coins associated with certain orders. The input is
    37  	// defined as a map of OrderIDs to a CoinID slice since it is likely easiest
    38  	// for the caller to construct the input in this way.
    39  	LockCoins(orderCoins map[order.OrderID][]CoinID) (failed map[order.OrderID][]CoinID)
    40  }
    41  
    42  // MasterCoinLocker coordinates a book and swap coin locker. The lock status of
    43  // a coin may be checked, and the locker for the book and swapper may be
    44  // obtained via the Book and Swap methods.
    45  type MasterCoinLocker struct {
    46  	bookLock *AssetCoinLocker
    47  	swapLock *AssetCoinLocker
    48  }
    49  
    50  // NewMasterCoinLocker creates a new NewMasterCoinLocker.
    51  func NewMasterCoinLocker() *MasterCoinLocker {
    52  	return &MasterCoinLocker{
    53  		bookLock: NewAssetCoinLocker(),
    54  		swapLock: NewAssetCoinLocker(),
    55  	}
    56  }
    57  
    58  // CoinLocked indicates if a coin is locked by either the swap or book lock.
    59  func (cl *MasterCoinLocker) CoinLocked(coin CoinID) bool {
    60  	lockedBySwap := cl.swapLock.CoinLocked(coin)
    61  	if lockedBySwap {
    62  		return true
    63  	}
    64  
    65  	return cl.bookLock.CoinLocked(coin)
    66  }
    67  
    68  // OrderCoinsLocked lists all coins locked by a given order are locked by either
    69  // the swap or book lock.
    70  func (cl *MasterCoinLocker) OrderCoinsLocked(oid order.OrderID) []CoinID {
    71  	coins := cl.swapLock.OrderCoinsLocked(oid)
    72  	if len(coins) > 0 {
    73  		return coins
    74  	}
    75  
    76  	// TODO: Figure out how we will handle the evolving coinIDs for an order
    77  	// with partial fills and decide how to merge the results of both swap and
    78  	// book locks.
    79  	return cl.bookLock.OrderCoinsLocked(oid)
    80  }
    81  
    82  // Book provides the market-level CoinLocker.
    83  func (cl *MasterCoinLocker) Book() CoinLocker {
    84  	return &bookLocker{cl}
    85  }
    86  
    87  // Swap provides the swap-level CoinLocker.
    88  func (cl *MasterCoinLocker) Swap() CoinLocker {
    89  	return &swapLocker{cl}
    90  }
    91  
    92  type bookLocker struct {
    93  	*MasterCoinLocker
    94  }
    95  
    96  // LockOrdersCoins locks all coins for the given orders.
    97  func (bl *bookLocker) LockOrdersCoins(orders []order.Order) []order.Order {
    98  	return bl.bookLock.LockOrdersCoins(orders)
    99  }
   100  
   101  // LockOrdersCoins locks coins associated with certain orders.
   102  func (bl *bookLocker) LockCoins(orderCoins map[order.OrderID][]CoinID) map[order.OrderID][]CoinID {
   103  	return bl.bookLock.LockCoins(orderCoins)
   104  }
   105  
   106  // UnlockAll releases all locked coins.
   107  func (bl *bookLocker) UnlockAll() {
   108  	bl.bookLock.UnlockAll()
   109  }
   110  
   111  // UnlockOrdersCoins unlocks all locked coins associated with an order.
   112  func (bl *bookLocker) UnlockOrdersCoins(oids []order.OrderID) {
   113  	bl.bookLock.UnlockOrdersCoins(oids)
   114  }
   115  
   116  // UnlockOrderCoins unlocks all locked coins associated with an order.
   117  func (bl *bookLocker) UnlockOrderCoins(oid order.OrderID) {
   118  	bl.bookLock.UnlockOrderCoins(oid)
   119  }
   120  
   121  var _ (CoinLocker) = (*bookLocker)(nil)
   122  
   123  type swapLocker struct {
   124  	*MasterCoinLocker
   125  }
   126  
   127  // LockOrdersCoins locks all coins for the given orders.
   128  func (sl *swapLocker) LockOrdersCoins(orders []order.Order) []order.Order {
   129  	return sl.swapLock.LockOrdersCoins(orders)
   130  }
   131  
   132  // LockOrdersCoins locks coins associated with certain orders.
   133  func (sl *swapLocker) LockCoins(orderCoins map[order.OrderID][]CoinID) map[order.OrderID][]CoinID {
   134  	return sl.swapLock.LockCoins(orderCoins)
   135  }
   136  
   137  // UnlockAll releases all locked coins.
   138  func (sl *swapLocker) UnlockAll() {
   139  	sl.swapLock.UnlockAll()
   140  }
   141  
   142  // UnlockOrderCoins unlocks all locked coins associated with an order.
   143  func (sl *swapLocker) UnlockOrderCoins(oid order.OrderID) {
   144  	sl.swapLock.UnlockOrderCoins(oid)
   145  }
   146  
   147  // UnlockOrdersCoins unlocks all locked coins associated with an order.
   148  func (sl *swapLocker) UnlockOrdersCoins(oids []order.OrderID) {
   149  	sl.swapLock.UnlockOrdersCoins(oids)
   150  }
   151  
   152  var _ (CoinLocker) = (*swapLocker)(nil)
   153  
   154  type coinIDKey string
   155  
   156  // AssetCoinLocker is a coin locker for a single asset. Do not use this for more
   157  // than one asset.
   158  type AssetCoinLocker struct {
   159  	coinMtx            sync.RWMutex
   160  	lockedCoins        map[coinIDKey]struct{}
   161  	lockedCoinsByOrder map[order.OrderID][]CoinID
   162  }
   163  
   164  // NewAssetCoinLocker constructs a new AssetCoinLocker.
   165  func NewAssetCoinLocker() *AssetCoinLocker {
   166  	return &AssetCoinLocker{
   167  		lockedCoins:        make(map[coinIDKey]struct{}),
   168  		lockedCoinsByOrder: make(map[order.OrderID][]CoinID),
   169  	}
   170  }
   171  
   172  // UnlockAll releases all locked coins.
   173  func (ac *AssetCoinLocker) UnlockAll() {
   174  	ac.coinMtx.Lock()
   175  	ac.lockedCoins = make(map[coinIDKey]struct{})
   176  	ac.lockedCoinsByOrder = make(map[order.OrderID][]CoinID)
   177  	ac.coinMtx.Unlock()
   178  }
   179  
   180  // CoinLocked indicates if a coin identifier (e.g. UTXO) is locked.
   181  func (ac *AssetCoinLocker) CoinLocked(coin CoinID) bool {
   182  	ac.coinMtx.RLock()
   183  	_, locked := ac.lockedCoins[coinIDKey(coin)]
   184  	ac.coinMtx.RUnlock()
   185  	return locked
   186  }
   187  
   188  // OrderCoinsLocked lists the coin IDs (e.g. UTXOs) locked by an order.
   189  func (ac *AssetCoinLocker) OrderCoinsLocked(oid order.OrderID) []CoinID {
   190  	ac.coinMtx.RLock()
   191  	defer ac.coinMtx.RUnlock()
   192  	return ac.lockedCoinsByOrder[oid]
   193  }
   194  
   195  // unlockOrderCoins should be called with the coinMtx locked.
   196  func (ac *AssetCoinLocker) unlockOrderCoins(oid order.OrderID) {
   197  	coins := ac.lockedCoinsByOrder[oid]
   198  	for i := range coins {
   199  		delete(ac.lockedCoins, coinIDKey(coins[i]))
   200  	}
   201  }
   202  
   203  // UnlockOrderCoins unlocks any coins backing the order.
   204  func (ac *AssetCoinLocker) UnlockOrderCoins(oid order.OrderID) {
   205  	ac.coinMtx.Lock()
   206  	ac.unlockOrderCoins(oid)
   207  	ac.coinMtx.Unlock()
   208  }
   209  
   210  // UnlockOrdersCoins unlocks any coins backing the orders.
   211  func (ac *AssetCoinLocker) UnlockOrdersCoins(oids []order.OrderID) {
   212  	ac.coinMtx.Lock()
   213  	for i := range oids {
   214  		ac.unlockOrderCoins(oids[i])
   215  	}
   216  	ac.coinMtx.Unlock()
   217  }
   218  
   219  // LockCoins locks all coins (e.g. UTXOS) connected with certain orders.
   220  func (ac *AssetCoinLocker) LockCoins(orderCoins map[order.OrderID][]CoinID) (failed map[order.OrderID][]CoinID) {
   221  	failed = make(map[order.OrderID][]CoinID)
   222  	ac.coinMtx.Lock()
   223  	for oid, coins := range orderCoins {
   224  		var fail bool
   225  		for i := range coins {
   226  			_, locked := ac.lockedCoins[coinIDKey(coins[i])]
   227  			if locked {
   228  				failed[oid] = append(failed[oid], coins[i])
   229  				fail = true
   230  			}
   231  		}
   232  		if fail {
   233  			// Do not lock any of this order's coins.
   234  			continue
   235  		}
   236  
   237  		ac.lockedCoinsByOrder[oid] = coins
   238  		for i := range coins {
   239  			ac.lockedCoins[coinIDKey(coins[i])] = struct{}{}
   240  		}
   241  	}
   242  	ac.coinMtx.Unlock()
   243  	return
   244  }
   245  
   246  // LockOrdersCoins locks all coins associated with certain orders.
   247  func (ac *AssetCoinLocker) LockOrdersCoins(orders []order.Order) (failed []order.Order) {
   248  	ac.coinMtx.Lock()
   249  ordersLoop:
   250  	for _, ord := range orders {
   251  		coinIDs := ord.Trade().Coins
   252  		if len(coinIDs) == 0 {
   253  			continue // e.g. CancelOrder
   254  		}
   255  
   256  		for i := range coinIDs {
   257  			_, locked := ac.lockedCoins[coinIDKey(coinIDs[i])]
   258  			if locked {
   259  				failed = append(failed, ord)
   260  				continue ordersLoop
   261  			}
   262  		}
   263  
   264  		ac.lockedCoinsByOrder[ord.ID()] = coinIDs
   265  		for i := range coinIDs {
   266  			ac.lockedCoins[coinIDKey(coinIDs[i])] = struct{}{}
   267  		}
   268  	}
   269  	ac.coinMtx.Unlock()
   270  	return
   271  }
   272  
   273  // DEXCoinLocker manages multiple MasterCoinLocker, one for each asset used by
   274  // the DEX.
   275  type DEXCoinLocker struct {
   276  	masterLocks map[uint32]*MasterCoinLocker
   277  }
   278  
   279  // NewDEXCoinLocker creates a new DEXCoinLocker for the given assets.
   280  func NewDEXCoinLocker(assets []uint32) *DEXCoinLocker {
   281  	masterLocks := make(map[uint32]*MasterCoinLocker, len(assets))
   282  	for _, asset := range assets {
   283  		masterLocks[asset] = NewMasterCoinLocker()
   284  	}
   285  
   286  	return &DEXCoinLocker{masterLocks}
   287  }
   288  
   289  // AssetLocker retrieves the MasterCoinLocker for an asset.
   290  func (c *DEXCoinLocker) AssetLocker(asset uint32) *MasterCoinLocker {
   291  	return c.masterLocks[asset]
   292  }
   293  
   294  // CoinLocked checks if a coin belonging to an asset is locked.
   295  func (c *DEXCoinLocker) CoinLocked(asset uint32, coin string) bool {
   296  	locker := c.masterLocks[asset]
   297  	if locker == nil {
   298  		panic(fmt.Sprintf("unknown asset %d", asset))
   299  	}
   300  
   301  	return locker.CoinLocked(CoinID(coin))
   302  }
   303  
   304  // OrderCoinsLocked retrieves all locked coins for a given asset and user.
   305  func (c *DEXCoinLocker) OrderCoinsLocked(asset uint32, oid order.OrderID) []CoinID {
   306  	locker := c.masterLocks[asset]
   307  	if locker == nil {
   308  		panic(fmt.Sprintf("unknown asset %d", asset))
   309  	}
   310  
   311  	return locker.OrderCoinsLocked(oid)
   312  }