decred.org/dcrdex@v1.0.3/client/asset/btc/coin_selection.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 btc 5 6 import ( 7 "math" 8 "math/rand" 9 "sort" 10 "time" 11 12 "decred.org/dcrdex/dex/calc" 13 dexbtc "decred.org/dcrdex/dex/networks/btc" 14 ) 15 16 // sendEnough generates a function that can be used as the enough argument to 17 // the fund method when creating transactions to send funds. If fees are to be 18 // subtracted from the inputs, set subtract so that the required amount excludes 19 // the transaction fee. If change from the transaction should be considered 20 // immediately available, set reportChange to indicate this and the returned 21 // enough func will return a non-zero excess value. Otherwise, the enough func 22 // will always return 0, leaving only unselected UTXOs to cover any required 23 // reserves. 24 func sendEnough(amt, feeRate uint64, subtract bool, baseTxSize uint64, segwit, reportChange bool) EnoughFunc { 25 return func(_, inputSize, sum uint64) (bool, uint64) { 26 txFee := (baseTxSize + inputSize) * feeRate 27 req := amt 28 if !subtract { // add the fee to required 29 req += txFee 30 } 31 if sum < req { 32 return false, 0 33 } 34 excess := sum - req 35 if !reportChange || dexbtc.IsDustVal(dexbtc.P2PKHOutputSize, excess, feeRate, segwit) { 36 excess = 0 37 } 38 return true, excess 39 } 40 } 41 42 // orderEnough generates a function that can be used as the enough argument to 43 // the fund method. If change from a split transaction will be created AND 44 // immediately available, set reportChange to indicate this and the returned 45 // enough func will return a non-zero excess value reflecting this potential 46 // spit tx change. Otherwise, the enough func will always return 0, leaving 47 // only unselected UTXOs to cover any required reserves. 48 func orderEnough(val, lots, feeRate, initTxSizeBase, initTxSize uint64, segwit, reportChange bool) EnoughFunc { 49 return func(_, inputsSize, sum uint64) (bool, uint64) { 50 reqFunds := calc.RequiredOrderFunds(val, inputsSize, lots, initTxSizeBase, initTxSize, feeRate) 51 if sum >= reqFunds { 52 excess := sum - reqFunds 53 if !reportChange || dexbtc.IsDustVal(dexbtc.P2PKHOutputSize, excess, feeRate, segwit) { 54 excess = 0 55 } 56 return true, excess 57 } 58 return false, 0 59 } 60 } 61 62 // reserveEnough generates a function that can be used as the enough argument 63 // to the fund method. The function returns true if sum is greater than equal 64 // to amt. 65 func reserveEnough(amt uint64) EnoughFunc { 66 return func(_, _, sum uint64) (bool, uint64) { 67 return sum >= amt, 0 68 } 69 } 70 71 func sumUTXOSize(set []*CompositeUTXO) (tot uint64) { 72 for _, utxo := range set { 73 tot += uint64(utxo.Input.VBytes()) 74 } 75 return tot 76 } 77 78 func SumUTXOs(set []*CompositeUTXO) (tot uint64) { 79 for _, utxo := range set { 80 tot += utxo.Amount 81 } 82 return tot 83 } 84 85 // subsetWithLeastOverFund attempts to select the subset of UTXOs with 86 // the smallest total value that is enough. It does this by making 87 // 1000 random selections and returning the best one. Each selection 88 // involves two passes over the UTXOs. The first pass randomly selects 89 // each UTXO with 50% probability. Then, the second pass selects any 90 // unused UTXOs until the total value is enough. 91 func subsetWithLeastOverFund(enough EnoughFunc, maxFund uint64, utxos []*CompositeUTXO) []*CompositeUTXO { 92 best := uint64(1 << 62) 93 var bestIncluded []bool 94 bestNumIncluded := 0 95 96 rnd := rand.New(rand.NewSource(time.Now().Unix())) 97 98 shuffledUTXOs := make([]*CompositeUTXO, len(utxos)) 99 copy(shuffledUTXOs, utxos) 100 rnd.Shuffle(len(shuffledUTXOs), func(i, j int) { 101 shuffledUTXOs[i], shuffledUTXOs[j] = shuffledUTXOs[j], shuffledUTXOs[i] 102 }) 103 104 included := make([]bool, len(utxos)) 105 const iterations = 1000 106 107 for nRep := 0; nRep < iterations; nRep++ { 108 var nTotal uint64 109 var totalSize uint64 110 var numIncluded int 111 112 for nPass := 0; nPass < 2; nPass++ { 113 for i := 0; i < len(shuffledUTXOs); i++ { 114 var use bool 115 if nPass == 0 { 116 use = rnd.Int63()&1 == 1 117 } else { 118 use = !included[i] 119 } 120 if use { 121 included[i] = true 122 numIncluded++ 123 nTotal += shuffledUTXOs[i].Amount 124 totalSize += uint64(shuffledUTXOs[i].Input.VBytes()) 125 if e, _ := enough(uint64(numIncluded), totalSize, nTotal); e { 126 if (nTotal < best || (nTotal == best && numIncluded < bestNumIncluded)) && nTotal <= maxFund { 127 best = nTotal 128 if bestIncluded == nil { 129 bestIncluded = make([]bool, len(shuffledUTXOs)) 130 } 131 copy(bestIncluded, included) 132 bestNumIncluded = numIncluded 133 } 134 included[i] = false 135 nTotal -= shuffledUTXOs[i].Amount 136 totalSize -= uint64(shuffledUTXOs[i].Input.VBytes()) 137 numIncluded-- 138 } 139 } 140 } 141 } 142 for i := 0; i < len(included); i++ { 143 included[i] = false 144 } 145 } 146 147 if bestIncluded == nil { 148 return nil 149 } 150 151 set := make([]*CompositeUTXO, 0, len(shuffledUTXOs)) 152 for i, inc := range bestIncluded { 153 if inc { 154 set = append(set, shuffledUTXOs[i]) 155 } 156 } 157 158 return set 159 } 160 161 // leastOverFund attempts to pick a subset of the provided UTXOs to reach the 162 // required amount with the objective of minimizing the total amount of the 163 // selected UTXOs. This is different from the objective used when funding 164 // orders, which is to minimize the number of UTXOs (to minimize fees). 165 // 166 // The UTXOs MUST be sorted in ascending order (smallest first, largest last)! 167 // 168 // This begins by partitioning the slice before the smallest single UTXO that is 169 // large enough to fully fund the requested amount, if it exists. If the smaller 170 // set is insufficient, the single largest UTXO is returned. If instead the set 171 // of smaller UTXOs has enough total value, it will search for a subset that 172 // reaches the amount with least over-funding (see subsetWithLeastSumGreaterThan). 173 // If that subset has less combined value than the single 174 // sufficiently-large UTXO (if it exists), the subset will be returned, 175 // otherwise the single UTXO will be returned. 176 // 177 // If the provided UTXO set has less combined value than the requested amount a 178 // nil slice is returned. 179 func leastOverFund(enough EnoughFunc, utxos []*CompositeUTXO) []*CompositeUTXO { 180 return leastOverFundWithLimit(enough, math.MaxUint64, utxos) 181 } 182 183 // leastOverFundWithLimit is the same as leastOverFund, but with an additional 184 // maxFund parameter. The total value of the returned UTXOs will not exceed 185 // maxFund. 186 func leastOverFundWithLimit(enough EnoughFunc, maxFund uint64, utxos []*CompositeUTXO) []*CompositeUTXO { 187 // Remove the UTXOs that are larger than maxFund 188 var smallEnoughUTXOs []*CompositeUTXO 189 idx := sort.Search(len(utxos), func(i int) bool { 190 utxo := utxos[i] 191 return utxo.Amount > maxFund 192 }) 193 if idx == len(utxos) { 194 smallEnoughUTXOs = utxos 195 } else { 196 smallEnoughUTXOs = utxos[:idx] 197 } 198 199 // Partition - smallest UTXO that is large enough to fully fund, and the set 200 // of smaller ones. 201 idx = sort.Search(len(smallEnoughUTXOs), func(i int) bool { 202 utxo := smallEnoughUTXOs[i] 203 e, _ := enough(1, uint64(utxo.Input.VBytes()), utxo.Amount) 204 return e 205 }) 206 var small []*CompositeUTXO 207 var single *CompositeUTXO // only return this if smaller ones would use more 208 if idx == len(smallEnoughUTXOs) { // no one is enough 209 small = smallEnoughUTXOs 210 } else { 211 small = smallEnoughUTXOs[:idx] 212 single = smallEnoughUTXOs[idx] 213 } 214 215 var set []*CompositeUTXO 216 smallSetTotalValue := SumUTXOs(small) 217 smallSetTotalSize := sumUTXOSize(small) 218 if e, _ := enough(uint64(len(small)), smallSetTotalSize, smallSetTotalValue); !e { 219 if single != nil { 220 return []*CompositeUTXO{single} 221 } else { 222 return nil 223 } 224 } else { 225 set = subsetWithLeastOverFund(enough, maxFund, small) 226 } 227 228 // Return the small UTXO subset if it is less than the single big UTXO. 229 if single != nil && single.Amount < SumUTXOs(set) { 230 return []*CompositeUTXO{single} 231 } 232 233 return set 234 } 235 236 // UTxOSetDiff performs the setdiff(set,sub) of two UTXO sets. That is, any 237 // UTXOs that are both sets are removed from the first. The comparison is done 238 // *by pointer*, with no regard to the values of the CompositeUTXO elements. 239 func UTxOSetDiff(set, sub []*CompositeUTXO) []*CompositeUTXO { 240 var availUTXOs []*CompositeUTXO 241 avail: 242 for _, utxo := range set { 243 for _, kept := range sub { 244 if utxo == kept { // by pointer 245 continue avail 246 } 247 } 248 availUTXOs = append(availUTXOs, utxo) 249 } 250 return availUTXOs 251 }