github.com/decred/dcrlnd@v0.7.6/sweep/walletsweep.go (about) 1 package sweep 2 3 import ( 4 "fmt" 5 "math" 6 7 "github.com/decred/dcrd/chaincfg/v3" 8 "github.com/decred/dcrd/dcrutil/v4" 9 "github.com/decred/dcrd/txscript/v4" 10 "github.com/decred/dcrd/txscript/v4/stdaddr" 11 "github.com/decred/dcrd/txscript/v4/stdscript" 12 "github.com/decred/dcrd/wire" 13 "github.com/decred/dcrlnd/input" 14 "github.com/decred/dcrlnd/lnwallet" 15 "github.com/decred/dcrlnd/lnwallet/chainfee" 16 ) 17 18 const ( 19 // defaultNumBlocksEstimate is the number of blocks that we fall back 20 // to issuing an estimate for if a fee pre fence doesn't specify an 21 // explicit conf target or fee rate. 22 defaultNumBlocksEstimate = 6 23 24 scriptVersion uint16 = 0 25 ) 26 27 // FeePreference allows callers to express their time value for inclusion of a 28 // transaction into a block via either a confirmation target, or a fee rate. 29 type FeePreference struct { 30 // ConfTarget if non-zero, signals a fee preference expressed in the 31 // number of desired blocks between first broadcast, and confirmation. 32 ConfTarget uint32 33 34 // FeeRate if non-zero, signals a fee pre fence expressed in the fee 35 // rate expressed in atom/KB for a particular transaction. 36 FeeRate chainfee.AtomPerKByte 37 } 38 39 // String returns a human-readable string of the fee preference. 40 func (p FeePreference) String() string { 41 if p.ConfTarget != 0 { 42 return fmt.Sprintf("%v blocks", p.ConfTarget) 43 } 44 return p.FeeRate.String() 45 } 46 47 // DetermineFeePerKB will determine the fee in atom/KB that should be paid 48 // given an estimator, a confirmation target, and a manual value for sat/byte. 49 // A value is chosen based on the two free parameters as one, or both of them 50 // can be zero. 51 func DetermineFeePerKB(feeEstimator chainfee.Estimator, 52 feePref FeePreference) (chainfee.AtomPerKByte, error) { 53 54 switch { 55 // If both values are set, then we'll return an error as we require a 56 // strict directive. 57 case feePref.FeeRate != 0 && feePref.ConfTarget != 0: 58 return 0, fmt.Errorf("only FeeRate or ConfTarget should " + 59 "be set for FeePreferences") 60 61 // If the target number of confirmations is set, then we'll use that to 62 // consult our fee estimator for an adequate fee. 63 case feePref.ConfTarget != 0: 64 feePerKB, err := feeEstimator.EstimateFeePerKB( 65 feePref.ConfTarget, 66 ) 67 if err != nil { 68 return 0, fmt.Errorf("unable to query fee "+ 69 "estimator: %v", err) 70 } 71 72 return feePerKB, nil 73 74 // If a manual sat/byte fee rate is set, then we'll use that directly. 75 // We'll need to convert it to atom/KB as this is what we use 76 // internally. 77 case feePref.FeeRate != 0: 78 feePerKB := feePref.FeeRate 79 if feePerKB < chainfee.FeePerKBFloor { 80 log.Infof("Manual fee rate input of %d atom/KB is "+ 81 "too low, using %d atom/KB instead", feePerKB, 82 chainfee.FeePerKBFloor) 83 84 feePerKB = chainfee.FeePerKBFloor 85 } 86 87 return feePerKB, nil 88 89 // Otherwise, we'll attempt a relaxed confirmation target for the 90 // transaction 91 default: 92 feePerKB, err := feeEstimator.EstimateFeePerKB( 93 defaultNumBlocksEstimate, 94 ) 95 if err != nil { 96 return 0, fmt.Errorf("unable to query fee estimator: "+ 97 "%v", err) 98 } 99 100 return feePerKB, nil 101 } 102 } 103 104 // UtxoSource is an interface that allows a caller to access a source of UTXOs 105 // to use when crafting sweep transactions. 106 type UtxoSource interface { 107 // ListUnspentWitness returns all UTXOs from the default wallet account 108 // that have between minConfs and maxConfs number of confirmations. 109 ListUnspentWitnessFromDefaultAccount(minConfs, maxConfs int32) ( 110 []*lnwallet.Utxo, error) 111 } 112 113 // CoinSelectionLocker is an interface that allows the caller to perform an 114 // operation, which is synchronized with all coin selection attempts. This can 115 // be used when an operation requires that all coin selection operations cease 116 // forward progress. Think of this as an exclusive lock on coin selection 117 // operations. 118 type CoinSelectionLocker interface { 119 // WithCoinSelectLock will execute the passed function closure in a 120 // synchronized manner preventing any coin selection operations from 121 // proceeding while the closure is executing. This can be seen as the 122 // ability to execute a function closure under an exclusive coin 123 // selection lock. 124 WithCoinSelectLock(func() error) error 125 } 126 127 // OutpointLocker allows a caller to lock/unlock an outpoint. When locked, the 128 // outpoints shouldn't be used for any sort of channel funding of coin 129 // selection. Locked outpoints are not expected to be persisted between restarts. 130 type OutpointLocker interface { 131 // LockOutpoint locks a target outpoint, rendering it unusable for coin 132 // selection. 133 LockOutpoint(o wire.OutPoint) 134 135 // UnlockOutpoint unlocks a target outpoint, allowing it to be used for 136 // coin selection once again. 137 UnlockOutpoint(o wire.OutPoint) 138 } 139 140 // WalletSweepPackage is a package that gives the caller the ability to sweep 141 // ALL funds from a wallet in a single transaction. We also package a function 142 // closure that allows one to abort the operation. 143 type WalletSweepPackage struct { 144 // SweepTx is a fully signed, and valid transaction that is broadcast, 145 // will sweep ALL confirmed coins in the wallet with a single 146 // transaction. 147 SweepTx *wire.MsgTx 148 149 // CancelSweepAttempt allows the caller to cancel the sweep attempt. 150 // 151 // NOTE: If the sweeping transaction isn't or cannot be broadcast, then 152 // this closure MUST be called, otherwise all selected utxos will be 153 // unable to be used. 154 CancelSweepAttempt func() 155 } 156 157 // DeliveryAddr is a pair of (address, amount) used to craft a transaction 158 // paying to more than one specified address. 159 type DeliveryAddr struct { 160 // Addr is the address to pay to. 161 Addr stdaddr.Address 162 163 // Amt is the amount to pay to the given address. 164 Amt dcrutil.Amount 165 } 166 167 // CraftSweepAllTx attempts to craft a WalletSweepPackage which will allow the 168 // caller to sweep ALL outputs within the wallet to a list of outputs. Any 169 // leftover amount after these outputs and transaction fee, is sent to a single 170 // output, as specified by the change address. The sweep transaction will be 171 // crafted with the target fee rate, and will use the utxoSource and 172 // outpointLocker as sources for wallet funds. 173 func CraftSweepAllTx(feeRate chainfee.AtomPerKByte, blockHeight uint32, 174 deliveryAddrs []DeliveryAddr, changeAddr stdaddr.Address, 175 coinSelectLocker CoinSelectionLocker, utxoSource UtxoSource, 176 outpointLocker OutpointLocker, feeEstimator chainfee.Estimator, 177 signer input.Signer, netParams *chaincfg.Params, minConfs int32) (*WalletSweepPackage, error) { 178 179 // TODO(roasbeef): turn off ATPL as well when available? 180 181 var allOutputs []*lnwallet.Utxo 182 183 // We'll make a function closure up front that allows us to unlock all 184 // selected outputs to ensure that they become available again in the 185 // case of an error after the outputs have been locked, but before we 186 // can actually craft a sweeping transaction. 187 unlockOutputs := func() { 188 for _, utxo := range allOutputs { 189 outpointLocker.UnlockOutpoint(utxo.OutPoint) 190 } 191 } 192 193 // Next, we'll use the coinSelectLocker to ensure that no coin 194 // selection takes place while we fetch and lock all outputs the wallet 195 // knows of. Otherwise, it may be possible for a new funding flow to 196 // lock an output while we fetch the set of unspent witnesses. 197 err := coinSelectLocker.WithCoinSelectLock(func() error { 198 // Now that we can be sure that no other coin selection 199 // operations are going on, we can grab a clean snapshot of the 200 // current UTXO state of the wallet. 201 utxos, err := utxoSource.ListUnspentWitnessFromDefaultAccount( 202 minConfs, math.MaxInt32, 203 ) 204 if err != nil { 205 return err 206 } 207 208 // We'll now lock each UTXO to ensure that other callers don't 209 // attempt to use these UTXOs in transactions while we're 210 // crafting out sweep all transaction. 211 for _, utxo := range utxos { 212 outpointLocker.LockOutpoint(utxo.OutPoint) 213 } 214 215 allOutputs = append(allOutputs, utxos...) 216 217 return nil 218 }) 219 if err != nil { 220 // If we failed at all, we'll unlock any outputs selected just 221 // in case we had any lingering outputs. 222 unlockOutputs() 223 224 return nil, fmt.Errorf("unable to fetch+lock wallet "+ 225 "utxos: %v", err) 226 } 227 228 // Now that we've locked all the potential outputs to sweep, we'll 229 // assemble an input for each of them, so we can hand it off to the 230 // sweeper to generate and sign a transaction for us. 231 var inputsToSweep []input.Input 232 for _, output := range allOutputs { 233 // As we'll be signing for outputs under control of the wallet, 234 // we only need to populate the output value and output script. 235 // The rest of the items will be populated internally within 236 // the sweeper via the witness generation function. 237 signDesc := &input.SignDescriptor{ 238 Output: &wire.TxOut{ 239 PkScript: output.PkScript, 240 Value: int64(output.Value), 241 }, 242 HashType: txscript.SigHashAll, 243 } 244 245 pkScript := output.PkScript 246 scriptClass := stdscript.DetermineScriptType( 247 scriptVersion, pkScript, 248 ) 249 250 // Based on the output type, we'll map it to the proper witness 251 // type so we can generate the set of input scripts needed to 252 // sweep the output. 253 var witnessType input.WitnessType 254 switch { 255 256 // We only support redeeming standard p2pkh outputs for the 257 // moment. 258 case scriptClass == stdscript.STPubKeyHashEcdsaSecp256k1: 259 witnessType = input.PublicKeyHash 260 261 // All other output types we count as unknown and will fail to 262 // sweep. 263 default: 264 unlockOutputs() 265 266 return nil, fmt.Errorf("unable to sweep coins, "+ 267 "unknown script: %x", pkScript[:]) 268 } 269 270 // Now that we've constructed the items required, we'll make an 271 // input which can be passed to the sweeper for ultimate 272 // sweeping. 273 input := input.MakeBaseInput( 274 &output.OutPoint, witnessType, signDesc, 0, nil, 275 ) 276 inputsToSweep = append(inputsToSweep, &input) 277 } 278 279 // Create a list of TxOuts from the given delivery addresses. 280 var txOuts []*wire.TxOut 281 for _, d := range deliveryAddrs { 282 version, pkScript := d.Addr.PaymentScript() 283 284 txOuts = append(txOuts, &wire.TxOut{ 285 Version: version, 286 PkScript: pkScript, 287 Value: int64(d.Amt), 288 }) 289 } 290 291 // Next, we'll convert the change addr to a pkScript that we can use 292 // to create the sweep transaction. 293 changePkScript, err := input.PayToAddrScript(changeAddr) 294 if err != nil { 295 unlockOutputs() 296 297 return nil, err 298 } 299 300 // Finally, we'll ask the sweeper to craft a sweep transaction which 301 // respects our fee preference and targets all the UTXOs of the wallet. 302 sweepTx, err := createSweepTx( 303 inputsToSweep, txOuts, changePkScript, blockHeight, feeRate, 304 signer, netParams, 305 ) 306 if err != nil { 307 unlockOutputs() 308 309 return nil, err 310 } 311 312 return &WalletSweepPackage{ 313 SweepTx: sweepTx, 314 CancelSweepAttempt: unlockOutputs, 315 }, nil 316 }