github.com/decred/dcrlnd@v0.7.6/sweep/tx_input_set.go (about) 1 package sweep 2 3 import ( 4 "fmt" 5 "math" 6 7 "github.com/decred/dcrd/dcrutil/v4" 8 "github.com/decred/dcrd/txscript/v4" 9 "github.com/decred/dcrd/wire" 10 "github.com/decred/dcrlnd/input" 11 "github.com/decred/dcrlnd/lnwallet" 12 "github.com/decred/dcrlnd/lnwallet/chainfee" 13 ) 14 15 // addConstraints defines the constraints to apply when adding an input. 16 type addConstraints uint8 17 18 const ( 19 // constraintsRegular is for regular input sweeps that should have a positive 20 // yield. 21 constraintsRegular addConstraints = iota 22 23 // constraintsWallet is for wallet inputs that are only added to bring up the tx 24 // output value. 25 constraintsWallet 26 27 // constraintsForce is for inputs that should be swept even with a negative 28 // yield at the set fee rate. 29 constraintsForce 30 ) 31 32 type txInputSetState struct { 33 // feeRate is the fee rate to use for the sweep transaction. 34 feeRate chainfee.AtomPerKByte 35 36 // inputTotal is the total value of all inputs. 37 inputTotal dcrutil.Amount 38 39 // requiredOutput is the sum of the outputs committed to by the inputs. 40 requiredOutput dcrutil.Amount 41 42 // changeOutput is the value of the change output. This will be what is 43 // left over after subtracting the requiredOutput and the tx fee from 44 // the inputTotal. 45 // 46 // NOTE: This might be below the dust limit, or even negative since it 47 // is the change remaining in csse we pay the fee for a change output. 48 changeOutput dcrutil.Amount 49 50 // inputs is the set of tx inputs. 51 inputs []input.Input 52 53 // walletInputTotal is the total value of inputs coming from the wallet. 54 walletInputTotal dcrutil.Amount 55 56 // force indicates that this set must be swept even if the total yield 57 // is negative. 58 force bool 59 } 60 61 // sizeEstimate is the (worst case) tx size with the current set of 62 // inputs. It takes a parameter whether to add a change output or not. 63 func (t *txInputSetState) sizeEstimate(change bool) *sizeEstimator { 64 sizeEstimate := newSizeEstimator(t.feeRate) 65 for _, i := range t.inputs { 66 // Can ignore error, because it has already been checked when 67 // calculating the yields. 68 _ = sizeEstimate.add(i) 69 70 r := i.RequiredTxOut() 71 if r != nil { 72 sizeEstimate.addOutput(r) 73 } 74 } 75 76 // Add a change output to the weight estimate if requested. 77 if change { 78 sizeEstimate.addP2PKHOutput() 79 } 80 81 return sizeEstimate 82 } 83 84 // totalOutput is the total amount left for us after paying fees. 85 // 86 // NOTE: This might be dust. 87 func (t *txInputSetState) totalOutput() dcrutil.Amount { 88 return t.requiredOutput + t.changeOutput 89 } 90 91 func (t *txInputSetState) clone() txInputSetState { 92 s := txInputSetState{ 93 feeRate: t.feeRate, 94 inputTotal: t.inputTotal, 95 changeOutput: t.changeOutput, 96 requiredOutput: t.requiredOutput, 97 walletInputTotal: t.walletInputTotal, 98 force: t.force, 99 inputs: make([]input.Input, len(t.inputs)), 100 } 101 copy(s.inputs, t.inputs) 102 103 return s 104 } 105 106 // txInputSet is an object that accumulates tx inputs and keeps running counters 107 // on various properties of the tx. 108 type txInputSet struct { 109 txInputSetState 110 111 // maxInputs is the maximum number of inputs that will be accepted in 112 // the set. 113 maxInputs int 114 115 // wallet contains wallet functionality required by the input set to 116 // retrieve utxos. 117 wallet Wallet 118 } 119 120 // newTxInputSet constructs a new, empty input set. 121 func newTxInputSet(wallet Wallet, feePerKB chainfee.AtomPerKByte, 122 maxInputs int) *txInputSet { 123 124 state := txInputSetState{ 125 feeRate: feePerKB, 126 } 127 128 b := txInputSet{ 129 maxInputs: maxInputs, 130 wallet: wallet, 131 txInputSetState: state, 132 } 133 134 return &b 135 } 136 137 // enoughInput returns true if we've accumulated enough inputs to pay the fees 138 // and have at least one output that meets the dust limit. 139 func (t *txInputSet) enoughInput() bool { 140 // If we have a change output above dust, then we certainly have enough 141 // inputs to the transaction. 142 if t.changeOutput >= lnwallet.DustLimitForSize(input.P2PKHPkScriptSize) { 143 return true 144 } 145 146 // We did not have enough input for a change output. Check if we have 147 // enough input to pay the fees for a transaction with no change 148 // output. 149 fee := t.sizeEstimate(false).fee() 150 if t.inputTotal < t.requiredOutput+fee { 151 return false 152 } 153 154 // We could pay the fees, but we still need at least one output to be 155 // above the dust limit for the tx to be valid (we assume that these 156 // required outputs only get added if they are above dust) 157 for _, inp := range t.inputs { 158 if inp.RequiredTxOut() != nil { 159 return true 160 } 161 } 162 163 return false 164 } 165 166 // add adds a new input to the set. It returns a bool indicating whether the 167 // input was added to the set. An input is rejected if it decreases the tx 168 // output value after paying fees. 169 func (t *txInputSet) addToState(inp input.Input, constraints addConstraints) *txInputSetState { 170 // Stop if max inputs is reached. Do not count additional wallet inputs, 171 // because we don't know in advance how many we may need. 172 if constraints != constraintsWallet && 173 len(t.inputs) >= t.maxInputs { 174 175 return nil 176 } 177 178 // If the input comes with a required tx out that is below dust, we 179 // won't add it. 180 reqOut := inp.RequiredTxOut() 181 if reqOut != nil { 182 // Fetch the dust limit for this output. 183 dustLimit := lnwallet.DustLimitForSize(int64(len(reqOut.PkScript))) 184 if dcrutil.Amount(reqOut.Value) < dustLimit { 185 return nil 186 } 187 } 188 189 // Clone the current set state. 190 s := t.clone() 191 192 // Add the new input. 193 s.inputs = append(s.inputs, inp) 194 195 // Add the value of the new input. 196 value := dcrutil.Amount(inp.SignDesc().Output.Value) 197 s.inputTotal += value 198 199 // Recalculate the tx fee. 200 fee := s.sizeEstimate(true).fee() 201 202 // Calculate the new output value. 203 if reqOut != nil { 204 s.requiredOutput += dcrutil.Amount(reqOut.Value) 205 } 206 s.changeOutput = s.inputTotal - s.requiredOutput - fee 207 208 // Calculate the yield of this input from the change in total tx output 209 // value. 210 inputYield := s.totalOutput() - t.totalOutput() 211 212 switch constraints { 213 214 // Don't sweep inputs that cost us more to sweep than they give us. 215 case constraintsRegular: 216 if inputYield <= 0 { 217 return nil 218 } 219 220 // For force adds, no further constraints apply. 221 case constraintsForce: 222 s.force = true 223 224 // We are attaching a wallet input to raise the tx output value above 225 // the dust limit. 226 case constraintsWallet: 227 // Skip this wallet input if adding it would lower the output 228 // value. 229 if inputYield <= 0 { 230 return nil 231 } 232 233 // Calculate the total value that we spend in this tx from the 234 // wallet if we'd add this wallet input. 235 s.walletInputTotal += value 236 237 // In any case, we don't want to lose money by sweeping. If we 238 // don't get more out of the tx then we put in ourselves, do not 239 // add this wallet input. If there is at least one force sweep 240 // in the set, this does no longer apply. 241 // 242 // We should only add wallet inputs to get the tx output value 243 // above the dust limit, otherwise we'd only burn into fees. 244 // This is guarded by tryAddWalletInputsIfNeeded. 245 // 246 // TODO(joostjager): Possibly require a max ratio between the 247 // value of the wallet input and what we get out of this 248 // transaction. To prevent attaching and locking a big utxo for 249 // very little benefit. 250 if !s.force && s.walletInputTotal >= s.totalOutput() { 251 log.Debugf("Rejecting wallet input of %v, because it "+ 252 "would make a negative yielding transaction "+ 253 "(%v)", 254 value, s.totalOutput()-s.walletInputTotal) 255 256 return nil 257 } 258 } 259 260 return &s 261 } 262 263 // add adds a new input to the set. It returns a bool indicating whether the 264 // input was added to the set. An input is rejected if it decreases the tx 265 // output value after paying fees. 266 func (t *txInputSet) add(input input.Input, constraints addConstraints) bool { 267 newState := t.addToState(input, constraints) 268 if newState == nil { 269 return false 270 } 271 272 t.txInputSetState = *newState 273 return true 274 } 275 276 // addPositiveYieldInputs adds sweepableInputs that have a positive yield to the 277 // input set. This function assumes that the list of inputs is sorted descending 278 // by yield. 279 // 280 // TODO(roasbeef): Consider including some negative yield inputs too to clean 281 // up the utxo set even if it costs us some fees up front. In the spirit of 282 // minimizing any negative externalities we cause for the Bitcoin system as a 283 // whole. 284 func (t *txInputSet) addPositiveYieldInputs(sweepableInputs []txInput) { 285 for i, inp := range sweepableInputs { 286 // Apply relaxed constraints for force sweeps. 287 constraints := constraintsRegular 288 if inp.parameters().Force { 289 constraints = constraintsForce 290 } 291 292 // Try to add the input to the transaction. If that doesn't 293 // succeed because it wouldn't increase the output value, 294 // return. Assuming inputs are sorted by yield, any further 295 // inputs wouldn't increase the output value either. 296 if !t.add(inp, constraints) { 297 var rem []input.Input 298 for j := i; j < len(sweepableInputs); j++ { 299 rem = append(rem, sweepableInputs[j]) 300 } 301 log.Debugf("%d negative yield inputs not added to "+ 302 "input set: %v", len(rem), 303 inputTypeSummary(rem)) 304 return 305 } 306 307 log.Debugf("Added positive yield input %v to input set", 308 inputTypeSummary([]input.Input{inp})) 309 } 310 311 // We managed to add all inputs to the set. 312 } 313 314 // tryAddWalletInputsIfNeeded retrieves utxos from the wallet and tries adding 315 // as many as required to bring the tx output value above the given minimum. 316 func (t *txInputSet) tryAddWalletInputsIfNeeded() error { 317 // If we've already have enough to pay the transaction fees and have at 318 // least one output materialize, no action is needed. 319 if t.enoughInput() { 320 return nil 321 } 322 323 // Retrieve wallet utxos. Only consider confirmed utxos to prevent 324 // problems around RBF rules for unconfirmed inputs. This currently 325 // ignores the configured coin selection strategy. 326 utxos, err := t.wallet.ListUnspentWitnessFromDefaultAccount( 327 1, math.MaxInt32, 328 ) 329 if err != nil { 330 return err 331 } 332 333 for _, utxo := range utxos { 334 input, err := createWalletTxInput(utxo) 335 if err != nil { 336 return err 337 } 338 339 // If the wallet input isn't positively-yielding at this fee 340 // rate, skip it. 341 if !t.add(input, constraintsWallet) { 342 continue 343 } 344 345 // Return if we've reached the minimum output amount. 346 if t.enoughInput() { 347 return nil 348 } 349 } 350 351 // We were not able to reach the minimum output amount. 352 return nil 353 } 354 355 // createWalletTxInput converts a wallet utxo into an object that can be added 356 // to the other inputs to sweep. 357 func createWalletTxInput(utxo *lnwallet.Utxo) (input.Input, error) { 358 var witnessType input.WitnessType 359 switch utxo.AddressType { 360 case lnwallet.PubKeyHash: 361 witnessType = input.PublicKeyHash 362 default: 363 return nil, fmt.Errorf("unknown address type %v", 364 utxo.AddressType) 365 } 366 367 signDesc := &input.SignDescriptor{ 368 Output: &wire.TxOut{ 369 PkScript: utxo.PkScript, 370 Value: int64(utxo.Value), 371 }, 372 HashType: txscript.SigHashAll, 373 } 374 375 // A height hint doesn't need to be set, because we don't monitor these 376 // inputs for spend. 377 heightHint := uint32(0) 378 379 return input.NewBaseInput( 380 &utxo.OutPoint, witnessType, signDesc, heightHint, 381 ), nil 382 }