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  }