decred.org/dcrdex@v1.0.3/client/asset/btc/coinmanager.go (about) 1 package btc 2 3 import ( 4 "errors" 5 "fmt" 6 "math" 7 "sort" 8 "sync" 9 10 "decred.org/dcrdex/client/asset" 11 "decred.org/dcrdex/dex" 12 dexbtc "decred.org/dcrdex/dex/networks/btc" 13 "github.com/btcsuite/btcd/btcutil" 14 "github.com/btcsuite/btcd/chaincfg" 15 "github.com/btcsuite/btcd/chaincfg/chainhash" 16 "github.com/btcsuite/btcd/txscript" 17 "github.com/btcsuite/btcd/wire" 18 ) 19 20 // CompositeUTXO combines utxo info with the spending input information. 21 type CompositeUTXO struct { 22 *UTxO 23 Confs uint32 24 RedeemScript []byte 25 Input *dexbtc.SpendInfo 26 } 27 28 // EnoughFunc considers information about funding inputs and indicates whether 29 // it is enough to fund an order. EnoughFunc is bound to an order by the 30 // OrderFundingThresholder. 31 type EnoughFunc func(inputCount, inputsSize, sum uint64) (bool, uint64) 32 33 // OrderEstimator is a function that accepts information about an order and 34 // estimates the total required funds needed for the order. 35 type OrderEstimator func(swapVal, inputCount, inputsSize, maxSwaps, feeRate uint64) uint64 36 37 // OrderFundingThresholder accepts information about an order and generates an 38 // EnoughFunc that can be used to test funding input combinations. 39 type OrderFundingThresholder func(val, lots, maxFeeRate uint64, reportChange bool) EnoughFunc 40 41 // CoinManager provides utilities for working with unspent transaction outputs. 42 // In addition to translation to and from custom wallet types, there are 43 // CoinManager methods to help pick UTXOs for funding in various contexts. 44 type CoinManager struct { 45 // Coins returned by Fund are cached for quick reference. 46 mtx sync.RWMutex 47 log dex.Logger 48 49 orderEnough OrderFundingThresholder 50 chainParams *chaincfg.Params 51 listUnspent func() ([]*ListUnspentResult, error) 52 lockUnspent func(unlock bool, ops []*Output) error 53 listLocked func() ([]*RPCOutpoint, error) 54 getTxOut func(txHash *chainhash.Hash, vout uint32) (*wire.TxOut, error) 55 stringAddr func(btcutil.Address) (string, error) 56 57 lockedOutputs map[OutPoint]*UTxO 58 } 59 60 func NewCoinManager( 61 log dex.Logger, 62 chainParams *chaincfg.Params, 63 orderEnough OrderFundingThresholder, 64 listUnspent func() ([]*ListUnspentResult, error), 65 lockUnspent func(unlock bool, ops []*Output) error, 66 listLocked func() ([]*RPCOutpoint, error), 67 getTxOut func(txHash *chainhash.Hash, vout uint32) (*wire.TxOut, error), 68 stringAddr func(btcutil.Address) (string, error), 69 ) *CoinManager { 70 71 return &CoinManager{ 72 log: log, 73 orderEnough: orderEnough, 74 chainParams: chainParams, 75 listUnspent: listUnspent, 76 lockUnspent: lockUnspent, 77 listLocked: listLocked, 78 getTxOut: getTxOut, 79 lockedOutputs: make(map[OutPoint]*UTxO), 80 stringAddr: stringAddr, 81 } 82 } 83 84 // FundWithUTXOs attempts to find the best combination of UTXOs to satisfy the 85 // given EnoughFunc while respecting the specified keep reserves (if non-zero). 86 func (c *CoinManager) FundWithUTXOs( 87 utxos []*CompositeUTXO, 88 keep uint64, 89 lockUnspents bool, 90 enough EnoughFunc, 91 ) (coins asset.Coins, fundingCoins map[OutPoint]*UTxO, spents []*Output, redeemScripts []dex.Bytes, size, sum uint64, err error) { 92 var avail uint64 93 for _, utxo := range utxos { 94 avail += utxo.Amount 95 } 96 97 c.mtx.Lock() 98 defer c.mtx.Unlock() 99 return c.fundWithUTXOs(utxos, avail, keep, lockUnspents, enough) 100 } 101 102 func (c *CoinManager) fundWithUTXOs( 103 utxos []*CompositeUTXO, 104 avail, keep uint64, 105 lockUnspents bool, 106 enough EnoughFunc, 107 ) (coins asset.Coins, fundingCoins map[OutPoint]*UTxO, spents []*Output, redeemScripts []dex.Bytes, size, sum uint64, err error) { 108 109 if keep > 0 { 110 kept := leastOverFund(reserveEnough(keep), utxos) 111 c.log.Debugf("Setting aside %v BTC in %d UTXOs to respect the %v BTC reserved amount", 112 toBTC(SumUTXOs(kept)), len(kept), toBTC(keep)) 113 utxosPruned := UTxOSetDiff(utxos, kept) 114 sum, _, size, coins, fundingCoins, redeemScripts, spents, err = TryFund(utxosPruned, enough) 115 if err != nil { 116 c.log.Debugf("Unable to fund order with UTXOs set aside (%v), trying again with full UTXO set.", err) 117 } 118 } 119 if len(spents) == 0 { // either keep is zero or it failed with utxosPruned 120 // Without utxos set aside for keep, we have to consider any spendable 121 // change (extra) that the enough func grants us. 122 123 var extra uint64 124 sum, extra, size, coins, fundingCoins, redeemScripts, spents, err = TryFund(utxos, enough) 125 if err != nil { 126 return nil, nil, nil, nil, 0, 0, err 127 } 128 if avail-sum+extra < keep { 129 return nil, nil, nil, nil, 0, 0, asset.ErrInsufficientBalance 130 } 131 // else we got lucky with the legacy funding approach and there was 132 // either available unspent or the enough func granted spendable change. 133 if keep > 0 && extra > 0 { 134 c.log.Debugf("Funding succeeded with %v BTC in spendable change.", toBTC(extra)) 135 } 136 } 137 138 if lockUnspents { 139 err = c.lockUnspent(false, spents) 140 if err != nil { 141 return nil, nil, nil, nil, 0, 0, fmt.Errorf("LockUnspent error: %w", err) 142 } 143 for pt, utxo := range fundingCoins { 144 c.lockedOutputs[pt] = utxo 145 } 146 } 147 148 return coins, fundingCoins, spents, redeemScripts, size, sum, err 149 } 150 151 func (c *CoinManager) fund(keep uint64, minConfs uint32, lockUnspents bool, 152 enough func(_, size, sum uint64) (bool, uint64)) ( 153 coins asset.Coins, fundingCoins map[OutPoint]*UTxO, spents []*Output, redeemScripts []dex.Bytes, size, sum uint64, err error) { 154 utxos, _, avail, err := c.spendableUTXOs(minConfs) 155 if err != nil { 156 return nil, nil, nil, nil, 0, 0, fmt.Errorf("error getting spendable utxos: %w", err) 157 } 158 return c.fundWithUTXOs(utxos, avail, keep, lockUnspents, enough) 159 } 160 161 // Fund attempts to satisfy the given EnoughFunc with all available UTXOs. For 162 // situations where Fund might be called repeatedly, the caller should instead 163 // do SpendableUTXOs and use the results in FundWithUTXOs. 164 func (c *CoinManager) Fund( 165 keep uint64, 166 minConfs uint32, 167 lockUnspents bool, 168 enough EnoughFunc, 169 ) (coins asset.Coins, fundingCoins map[OutPoint]*UTxO, spents []*Output, redeemScripts []dex.Bytes, size, sum uint64, err error) { 170 171 c.mtx.Lock() 172 defer c.mtx.Unlock() 173 174 return c.fund(keep, minConfs, lockUnspents, enough) 175 } 176 177 // OrderWithLeastOverFund returns the index of the order from a slice of orders 178 // that requires the least over-funding without using more than maxLock. It 179 // also returns the UTXOs that were used to fund the order. If none can be 180 // funded without using more than maxLock, -1 is returned. 181 func (c *CoinManager) OrderWithLeastOverFund(maxLock, feeRate uint64, orders []*asset.MultiOrderValue, utxos []*CompositeUTXO) (orderIndex int, leastOverFundingUTXOs []*CompositeUTXO) { 182 minOverFund := uint64(math.MaxUint64) 183 orderIndex = -1 184 for i, value := range orders { 185 enough := c.orderEnough(value.Value, value.MaxSwapCount, feeRate, false) 186 var fundingUTXOs []*CompositeUTXO 187 if maxLock > 0 { 188 fundingUTXOs = leastOverFundWithLimit(enough, maxLock, utxos) 189 } else { 190 fundingUTXOs = leastOverFund(enough, utxos) 191 } 192 if len(fundingUTXOs) == 0 { 193 continue 194 } 195 sum := SumUTXOs(fundingUTXOs) 196 overFund := sum - value.Value 197 if overFund < minOverFund { 198 minOverFund = overFund 199 orderIndex = i 200 leastOverFundingUTXOs = fundingUTXOs 201 } 202 } 203 return 204 } 205 206 // fundMultiBestEffort makes a best effort to fund every order. If it is not 207 // possible, it returns coins for the orders that could be funded. The coins 208 // that fund each order are returned in the same order as the values that were 209 // passed in. If a split is allowed and all orders cannot be funded, nil slices 210 // are returned. 211 func (c *CoinManager) FundMultiBestEffort(keep, maxLock uint64, values []*asset.MultiOrderValue, 212 maxFeeRate uint64, splitAllowed bool) ([]asset.Coins, [][]dex.Bytes, map[OutPoint]*UTxO, []*Output, error) { 213 utxos, _, avail, err := c.SpendableUTXOs(0) 214 if err != nil { 215 return nil, nil, nil, nil, fmt.Errorf("error getting spendable utxos: %w", err) 216 } 217 218 fundAllOrders := func() [][]*CompositeUTXO { 219 indexToFundingCoins := make(map[int][]*CompositeUTXO, len(values)) 220 remainingUTXOs := utxos 221 remainingOrders := values 222 remainingIndexes := make([]int, len(values)) 223 for i := range remainingIndexes { 224 remainingIndexes[i] = i 225 } 226 var totalFunded uint64 227 for range values { 228 orderIndex, fundingUTXOs := c.OrderWithLeastOverFund(maxLock-totalFunded, maxFeeRate, remainingOrders, remainingUTXOs) 229 if orderIndex == -1 { 230 return nil 231 } 232 totalFunded += SumUTXOs(fundingUTXOs) 233 if totalFunded > avail-keep { 234 return nil 235 } 236 newRemainingOrders := make([]*asset.MultiOrderValue, 0, len(remainingOrders)-1) 237 newRemainingIndexes := make([]int, 0, len(remainingOrders)-1) 238 for j := range remainingOrders { 239 if j != orderIndex { 240 newRemainingOrders = append(newRemainingOrders, remainingOrders[j]) 241 newRemainingIndexes = append(newRemainingIndexes, remainingIndexes[j]) 242 } 243 } 244 indexToFundingCoins[remainingIndexes[orderIndex]] = fundingUTXOs 245 remainingOrders = newRemainingOrders 246 remainingIndexes = newRemainingIndexes 247 remainingUTXOs = UTxOSetDiff(remainingUTXOs, fundingUTXOs) 248 } 249 allFundingUTXOs := make([][]*CompositeUTXO, len(values)) 250 for i := range values { 251 allFundingUTXOs[i] = indexToFundingCoins[i] 252 } 253 return allFundingUTXOs 254 } 255 256 fundInOrder := func(orderedValues []*asset.MultiOrderValue) [][]*CompositeUTXO { 257 allFundingUTXOs := make([][]*CompositeUTXO, 0, len(orderedValues)) 258 remainingUTXOs := utxos 259 var totalFunded uint64 260 for _, value := range orderedValues { 261 enough := c.orderEnough(value.Value, value.MaxSwapCount, maxFeeRate, false) 262 263 var fundingUTXOs []*CompositeUTXO 264 if maxLock > 0 { 265 if maxLock < totalFunded { 266 // Should never happen unless there is a bug in leastOverFundWithLimit 267 c.log.Errorf("maxLock < totalFunded. %d < %d", maxLock, totalFunded) 268 return allFundingUTXOs 269 } 270 fundingUTXOs = leastOverFundWithLimit(enough, maxLock-totalFunded, remainingUTXOs) 271 } else { 272 fundingUTXOs = leastOverFund(enough, remainingUTXOs) 273 } 274 if len(fundingUTXOs) == 0 { 275 return allFundingUTXOs 276 } 277 totalFunded += SumUTXOs(fundingUTXOs) 278 if totalFunded > avail-keep { 279 return allFundingUTXOs 280 } 281 allFundingUTXOs = append(allFundingUTXOs, fundingUTXOs) 282 remainingUTXOs = UTxOSetDiff(remainingUTXOs, fundingUTXOs) 283 } 284 return allFundingUTXOs 285 } 286 287 returnValues := func(allFundingUTXOs [][]*CompositeUTXO) (coins []asset.Coins, redeemScripts [][]dex.Bytes, fundingCoins map[OutPoint]*UTxO, spents []*Output, err error) { 288 coins = make([]asset.Coins, len(allFundingUTXOs)) 289 fundingCoins = make(map[OutPoint]*UTxO) 290 spents = make([]*Output, 0, len(allFundingUTXOs)) 291 redeemScripts = make([][]dex.Bytes, len(allFundingUTXOs)) 292 for i, fundingUTXOs := range allFundingUTXOs { 293 coins[i] = make(asset.Coins, len(fundingUTXOs)) 294 redeemScripts[i] = make([]dex.Bytes, len(fundingUTXOs)) 295 for j, output := range fundingUTXOs { 296 coins[i][j] = NewOutput(output.TxHash, output.Vout, output.Amount) 297 fundingCoins[OutPoint{TxHash: *output.TxHash, Vout: output.Vout}] = &UTxO{ 298 TxHash: output.TxHash, 299 Vout: output.Vout, 300 Amount: output.Amount, 301 Address: output.Address, 302 } 303 spents = append(spents, NewOutput(output.TxHash, output.Vout, output.Amount)) 304 redeemScripts[i][j] = output.RedeemScript 305 } 306 } 307 return 308 } 309 310 // Attempt to fund all orders by selecting the order that requires the least 311 // over funding, removing the funding utxos from the set of available utxos, 312 // and continuing until all orders are funded. 313 allFundingUTXOs := fundAllOrders() 314 if allFundingUTXOs != nil { 315 return returnValues(allFundingUTXOs) 316 } 317 318 // Return nil if a split is allowed. There is no need to fund in priority 319 // order if a split will be done regardless. 320 if splitAllowed { 321 return returnValues([][]*CompositeUTXO{}) 322 } 323 324 // If could not fully fund, fund as much as possible in the priority 325 // order. 326 allFundingUTXOs = fundInOrder(values) 327 return returnValues(allFundingUTXOs) 328 } 329 330 // SpendableUTXOs filters the RPC utxos for those that are spendable with 331 // regards to the DEX's configuration, and considered safe to spend according to 332 // confirmations and coin source. The UTXOs will be sorted by ascending value. 333 // spendableUTXOs should only be called with the fundingMtx RLock'ed. 334 func (c *CoinManager) SpendableUTXOs(confs uint32) ([]*CompositeUTXO, map[OutPoint]*CompositeUTXO, uint64, error) { 335 c.mtx.RLock() 336 defer c.mtx.RUnlock() 337 return c.spendableUTXOs(confs) 338 } 339 340 func (c *CoinManager) spendableUTXOs(confs uint32) ([]*CompositeUTXO, map[OutPoint]*CompositeUTXO, uint64, error) { 341 unspents, err := c.listUnspent() 342 if err != nil { 343 return nil, nil, 0, err 344 } 345 346 utxos, utxoMap, sum, err := convertUnspent(confs, unspents, c.chainParams) 347 if err != nil { 348 return nil, nil, 0, err 349 } 350 351 var relock []*Output 352 var i int 353 for _, utxo := range utxos { 354 // Guard against inconsistencies between the wallet's view of 355 // spendable unlocked UTXOs and ExchangeWallet's. e.g. User manually 356 // unlocked something or even restarted the wallet software. 357 pt := NewOutPoint(utxo.TxHash, utxo.Vout) 358 if c.lockedOutputs[pt] != nil { 359 c.log.Warnf("Known order-funding coin %s returned by listunspent!", pt) 360 delete(utxoMap, pt) 361 relock = append(relock, &Output{pt, utxo.Amount}) 362 } else { // in-place filter maintaining order 363 utxos[i] = utxo 364 i++ 365 } 366 } 367 if len(relock) > 0 { 368 if err = c.lockUnspent(false, relock); err != nil { 369 c.log.Errorf("Failed to re-lock funding coins with wallet: %v", err) 370 } 371 } 372 utxos = utxos[:i] 373 return utxos, utxoMap, sum, nil 374 } 375 376 // ReturnCoins makes the locked utxos available for use again. 377 func (c *CoinManager) ReturnCoins(unspents asset.Coins) error { 378 if unspents == nil { // not just empty to make this harder to do accidentally 379 c.log.Debugf("Returning all coins.") 380 c.mtx.Lock() 381 defer c.mtx.Unlock() 382 if err := c.lockUnspent(true, nil); err != nil { 383 return err 384 } 385 c.lockedOutputs = make(map[OutPoint]*UTxO) 386 return nil 387 } 388 if len(unspents) == 0 { 389 return fmt.Errorf("cannot return zero coins") 390 } 391 392 ops := make([]*Output, 0, len(unspents)) 393 c.log.Debugf("returning coins %s", unspents) 394 c.mtx.Lock() 395 defer c.mtx.Unlock() 396 for _, unspent := range unspents { 397 op, err := ConvertCoin(unspent) 398 if err != nil { 399 return fmt.Errorf("error converting coin: %w", err) 400 } 401 ops = append(ops, op) 402 } 403 if err := c.lockUnspent(true, ops); err != nil { 404 return err // could it have unlocked some of them? we may want to loop instead if that's the case 405 } 406 for _, op := range ops { 407 delete(c.lockedOutputs, op.Pt) 408 } 409 return nil 410 } 411 412 // ReturnOutPoint makes the UTXO represented by the OutPoint available for use 413 // again. 414 func (c *CoinManager) ReturnOutPoint(pt OutPoint) error { 415 c.mtx.Lock() 416 defer c.mtx.Unlock() 417 if err := c.lockUnspent(true, []*Output{NewOutput(&pt.TxHash, pt.Vout, 0)}); err != nil { 418 return err // could it have unlocked some of them? we may want to loop instead if that's the case 419 } 420 delete(c.lockedOutputs, pt) 421 return nil 422 } 423 424 // FundingCoins attempts to find the specified utxos and locks them. 425 func (c *CoinManager) FundingCoins(ids []dex.Bytes) (asset.Coins, error) { 426 // First check if we have the coins in cache. 427 coins := make(asset.Coins, 0, len(ids)) 428 notFound := make(map[OutPoint]bool) 429 c.mtx.Lock() 430 defer c.mtx.Unlock() // stay locked until we update the map at the end 431 for _, id := range ids { 432 txHash, vout, err := decodeCoinID(id) 433 if err != nil { 434 return nil, err 435 } 436 pt := NewOutPoint(txHash, vout) 437 fundingCoin, found := c.lockedOutputs[pt] 438 if found { 439 coins = append(coins, NewOutput(txHash, vout, fundingCoin.Amount)) 440 continue 441 } 442 notFound[pt] = true 443 } 444 if len(notFound) == 0 { 445 return coins, nil 446 } 447 448 // Check locked outputs for not found coins. 449 lockedOutpoints, err := c.listLocked() 450 if err != nil { 451 return nil, err 452 } 453 454 for _, rpcOP := range lockedOutpoints { 455 txHash, err := chainhash.NewHashFromStr(rpcOP.TxID) 456 if err != nil { 457 return nil, fmt.Errorf("error decoding txid from rpc server %s: %w", rpcOP.TxID, err) 458 } 459 pt := NewOutPoint(txHash, rpcOP.Vout) 460 if !notFound[pt] { 461 continue // unrelated to the order 462 } 463 464 txOut, err := c.getTxOut(txHash, rpcOP.Vout) 465 if err != nil { 466 return nil, err 467 } 468 if txOut == nil { 469 continue 470 } 471 if txOut.Value <= 0 { 472 c.log.Warnf("Invalid value %v for %v", txOut.Value, pt) 473 continue // try the listunspent output 474 } 475 _, addrs, _, err := txscript.ExtractPkScriptAddrs(txOut.PkScript, c.chainParams) 476 if err != nil { 477 c.log.Warnf("Invalid pkScript for %v: %v", pt, err) 478 continue 479 } 480 if len(addrs) != 1 { 481 c.log.Warnf("pkScript for %v contains %d addresses instead of one", pt, len(addrs)) 482 continue 483 } 484 addrStr, err := c.stringAddr(addrs[0]) 485 if err != nil { 486 c.log.Errorf("Failed to stringify address %v (default encoding): %v", addrs[0], err) 487 addrStr = addrs[0].String() // may or may not be able to retrieve the private keys by address! 488 } 489 utxo := &UTxO{ 490 TxHash: txHash, 491 Vout: rpcOP.Vout, 492 Address: addrStr, // for retrieving private key by address string 493 Amount: uint64(txOut.Value), 494 } 495 coin := NewOutput(txHash, rpcOP.Vout, uint64(txOut.Value)) 496 coins = append(coins, coin) 497 c.lockedOutputs[pt] = utxo 498 delete(notFound, pt) 499 if len(notFound) == 0 { 500 return coins, nil 501 } 502 } 503 504 // Some funding coins still not found after checking locked outputs. 505 // Check wallet unspent outputs as last resort. Lock the coins if found. 506 _, utxoMap, _, err := c.spendableUTXOs(0) 507 if err != nil { 508 return nil, err 509 } 510 coinsToLock := make([]*Output, 0, len(notFound)) 511 for pt := range notFound { 512 utxo, found := utxoMap[pt] 513 if !found { 514 return nil, fmt.Errorf("funding coin not found: %s", pt.String()) 515 } 516 c.lockedOutputs[pt] = utxo.UTxO 517 coin := NewOutput(utxo.TxHash, utxo.Vout, utxo.Amount) 518 coins = append(coins, coin) 519 coinsToLock = append(coinsToLock, coin) 520 delete(notFound, pt) 521 } 522 c.log.Debugf("Locking funding coins that were unlocked %v", coinsToLock) 523 err = c.lockUnspent(false, coinsToLock) 524 if err != nil { 525 return nil, err 526 } 527 return coins, nil 528 } 529 530 // LockUTXOs locks the specified utxos. 531 // TODO: Move lockUnspent calls into this method instead of the caller doing it 532 // at every callsite, and because that's what we do with unlocking. 533 func (c *CoinManager) LockUTXOs(utxos []*UTxO) { 534 c.mtx.Lock() 535 for _, utxo := range utxos { 536 c.lockedOutputs[NewOutPoint(utxo.TxHash, utxo.Vout)] = utxo 537 } 538 c.mtx.Unlock() 539 } 540 541 // LockOutputs locks the utxos in the provided mapping. 542 func (c *CoinManager) LockOutputsMap(utxos map[OutPoint]*UTxO) { 543 c.mtx.Lock() 544 for pt, utxo := range utxos { 545 c.lockedOutputs[pt] = utxo 546 } 547 c.mtx.Unlock() 548 } 549 550 // UnlockOutPoints unlocks the utxos represented by the provided outpoints. 551 func (c *CoinManager) UnlockOutPoints(pts []OutPoint) { 552 c.mtx.Lock() 553 for _, pt := range pts { 554 delete(c.lockedOutputs, pt) 555 } 556 c.mtx.Unlock() 557 } 558 559 // LockedOutput returns the currently locked utxo represented by the provided 560 // outpoint, or nil if there is no record of the utxo in the local map. 561 func (c *CoinManager) LockedOutput(pt OutPoint) *UTxO { 562 c.mtx.Lock() 563 defer c.mtx.Unlock() 564 return c.lockedOutputs[pt] 565 } 566 567 func convertUnspent(confs uint32, unspents []*ListUnspentResult, chainParams *chaincfg.Params) ([]*CompositeUTXO, map[OutPoint]*CompositeUTXO, uint64, error) { 568 sort.Slice(unspents, func(i, j int) bool { return unspents[i].Amount < unspents[j].Amount }) 569 var sum uint64 570 utxos := make([]*CompositeUTXO, 0, len(unspents)) 571 utxoMap := make(map[OutPoint]*CompositeUTXO, len(unspents)) 572 for _, txout := range unspents { 573 if txout.Confirmations >= confs && txout.Safe() && txout.Spendable { 574 txHash, err := chainhash.NewHashFromStr(txout.TxID) 575 if err != nil { 576 return nil, nil, 0, fmt.Errorf("error decoding txid in ListUnspentResult: %w", err) 577 } 578 579 nfo, err := dexbtc.InputInfo(txout.ScriptPubKey, txout.RedeemScript, chainParams) 580 if err != nil { 581 if errors.Is(err, dex.UnsupportedScriptError) { 582 continue 583 } 584 return nil, nil, 0, fmt.Errorf("error reading asset info: %w", err) 585 } 586 if nfo.ScriptType == dexbtc.ScriptUnsupported || nfo.NonStandardScript { 587 // InputInfo sets NonStandardScript for P2SH with non-standard 588 // redeem scripts. Don't return these since they cannot fund 589 // arbitrary txns. 590 continue 591 } 592 utxo := &CompositeUTXO{ 593 UTxO: &UTxO{ 594 TxHash: txHash, 595 Vout: txout.Vout, 596 Address: txout.Address, 597 Amount: toSatoshi(txout.Amount), 598 }, 599 Confs: txout.Confirmations, 600 RedeemScript: txout.RedeemScript, 601 Input: nfo, 602 } 603 utxos = append(utxos, utxo) 604 utxoMap[NewOutPoint(txHash, txout.Vout)] = utxo 605 sum += toSatoshi(txout.Amount) 606 } 607 } 608 return utxos, utxoMap, sum, nil 609 } 610 611 func TryFund( 612 utxos []*CompositeUTXO, 613 enough EnoughFunc, 614 ) ( 615 sum, extra, size uint64, 616 coins asset.Coins, 617 fundingCoins map[OutPoint]*UTxO, 618 redeemScripts []dex.Bytes, 619 spents []*Output, 620 err error, 621 ) { 622 623 fundingCoins = make(map[OutPoint]*UTxO) 624 625 isEnoughWith := func(count int, unspent *CompositeUTXO) bool { 626 ok, _ := enough(uint64(count), size+uint64(unspent.Input.VBytes()), sum+unspent.Amount) 627 return ok 628 } 629 630 addUTXO := func(unspent *CompositeUTXO) { 631 v := unspent.Amount 632 op := NewOutput(unspent.TxHash, unspent.Vout, v) 633 coins = append(coins, op) 634 redeemScripts = append(redeemScripts, unspent.RedeemScript) 635 spents = append(spents, op) 636 size += uint64(unspent.Input.VBytes()) 637 fundingCoins[op.Pt] = unspent.UTxO 638 sum += v 639 } 640 641 tryUTXOs := func(minconf uint32) bool { 642 sum, size = 0, 0 643 coins, spents, redeemScripts = nil, nil, nil 644 fundingCoins = make(map[OutPoint]*UTxO) 645 646 okUTXOs := make([]*CompositeUTXO, 0, len(utxos)) // over-allocate 647 for _, cu := range utxos { 648 if cu.Confs >= minconf { 649 okUTXOs = append(okUTXOs, cu) 650 } 651 } 652 653 for { 654 // If there are none left, we don't have enough. 655 if len(okUTXOs) == 0 { 656 return false 657 } 658 659 // Check if the largest output is too small. 660 lastUTXO := okUTXOs[len(okUTXOs)-1] 661 if !isEnoughWith(1, lastUTXO) { 662 addUTXO(lastUTXO) 663 okUTXOs = okUTXOs[0 : len(okUTXOs)-1] 664 continue 665 } 666 667 // We only need one then. Find it. 668 idx := sort.Search(len(okUTXOs), func(i int) bool { 669 return isEnoughWith(1, okUTXOs[i]) 670 }) 671 // No need to check idx == len(okUTXOs). We already verified that the last 672 // utxo passes above. 673 addUTXO(okUTXOs[idx]) 674 _, extra = enough(uint64(len(coins)), size, sum) 675 return true 676 } 677 } 678 679 // First try with confs>0, falling back to allowing 0-conf outputs. 680 if !tryUTXOs(1) { 681 if !tryUTXOs(0) { 682 return 0, 0, 0, nil, nil, nil, nil, fmt.Errorf("not enough to cover requested funds. "+ 683 "%d available in %d UTXOs", amount(sum), len(coins)) 684 } 685 } 686 687 return 688 }