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 }