@@ -10,10 +10,12 @@ import (
1010 "github.com/btcsuite/btcd/txscript"
1111 "github.com/btcsuite/btcd/wire"
1212 "github.com/btcsuite/btcwallet/wtxmgr"
13+ "github.com/lightningnetwork/lnd/fn"
1314 "github.com/lightningnetwork/lnd/input"
1415 "github.com/lightningnetwork/lnd/lnwallet"
1516 "github.com/lightningnetwork/lnd/lnwallet/chainfee"
1617 "github.com/lightningnetwork/lnd/lnwallet/chanfunding"
18+ "golang.org/x/exp/maps"
1719)
1820
1921var (
2426 // ErrFeePreferenceConflict is returned when both a fee rate and a conf
2527 // target is set for a fee preference.
2628 ErrFeePreferenceConflict = errors .New ("fee preference conflict" )
29+
30+ // ErrUnknownUTXO is returned when creating a sweeping tx using an UTXO
31+ // that's unknown to the wallet.
32+ ErrUnknownUTXO = errors .New ("unknown utxo" )
2733)
2834
2935// FeePreference defines an interface that allows the caller to specify how the
@@ -181,11 +187,11 @@ type OutputLeaser interface {
181187}
182188
183189// WalletSweepPackage is a package that gives the caller the ability to sweep
184- // ALL funds from a wallet in a single transaction. We also package a function
185- // closure that allows one to abort the operation.
190+ // relevant funds from a wallet in a single transaction. We also package a
191+ // function closure that allows one to abort the operation.
186192type WalletSweepPackage struct {
187193 // SweepTx is a fully signed, and valid transaction that is broadcast,
188- // will sweep ALL confirmed coins in the wallet with a single
194+ // will sweep ALL relevant confirmed coins in the wallet with a single
189195 // transaction.
190196 SweepTx * wire.MsgTx
191197
@@ -208,27 +214,28 @@ type DeliveryAddr struct {
208214}
209215
210216// CraftSweepAllTx attempts to craft a WalletSweepPackage which will allow the
211- // caller to sweep ALL outputs within the wallet to a list of outputs. Any
212- // leftover amount after these outputs and transaction fee, is sent to a single
213- // output, as specified by the change address. The sweep transaction will be
214- // crafted with the target fee rate, and will use the utxoSource and
215- // outputLeaser as sources for wallet funds.
217+ // caller to sweep ALL funds in ALL or SELECT outputs within the wallet to a
218+ // list of outputs. Any leftover amount after these outputs and transaction fee,
219+ // is sent to a single output, as specified by the change address. The sweep
220+ // transaction will be crafted with the target fee rate, and will use the
221+ // utxoSource and outputLeaser as sources for wallet funds.
216222func CraftSweepAllTx (feeRate , maxFeeRate chainfee.SatPerKWeight ,
217223 blockHeight uint32 , deliveryAddrs []DeliveryAddr ,
218224 changeAddr btcutil.Address , coinSelectLocker CoinSelectionLocker ,
219225 utxoSource UtxoSource , outputLeaser OutputLeaser ,
220- signer input.Signer , minConfs int32 ) (* WalletSweepPackage , error ) {
226+ signer input.Signer , minConfs int32 ,
227+ selectUtxos fn.Set [wire.OutPoint ]) (* WalletSweepPackage , error ) {
221228
222229 // TODO(roasbeef): turn off ATPL as well when available?
223230
224- var allOutputs []* lnwallet.Utxo
231+ var outputsForSweep []* lnwallet.Utxo
225232
226233 // We'll make a function closure up front that allows us to unlock all
227234 // selected outputs to ensure that they become available again in the
228235 // case of an error after the outputs have been locked, but before we
229236 // can actually craft a sweeping transaction.
230237 unlockOutputs := func () {
231- for _ , utxo := range allOutputs {
238+ for _ , utxo := range outputsForSweep {
232239 // Log the error but continue since we're already
233240 // handling an error.
234241 err := outputLeaser .ReleaseOutput (
@@ -242,9 +249,9 @@ func CraftSweepAllTx(feeRate, maxFeeRate chainfee.SatPerKWeight,
242249 }
243250
244251 // Next, we'll use the coinSelectLocker to ensure that no coin
245- // selection takes place while we fetch and lock all outputs the wallet
246- // knows of. Otherwise, it may be possible for a new funding flow to
247- // lock an output while we fetch the set of unspent witnesses.
252+ // selection takes place while we fetch and lock outputs in the
253+ // wallet. Otherwise, it may be possible for a new funding flow to lock
254+ // an output while we fetch the set of unspent witnesses.
248255 err := coinSelectLocker .WithCoinSelectLock (func () error {
249256 log .Trace ("[WithCoinSelectLock] entered the lock" )
250257
@@ -260,6 +267,16 @@ func CraftSweepAllTx(feeRate, maxFeeRate chainfee.SatPerKWeight,
260267
261268 log .Trace ("[WithCoinSelectLock] finished fetching UTXOs" )
262269
270+ // Use select utxos, if provided.
271+ if len (selectUtxos ) > 0 {
272+ utxos , err = fetchUtxosFromOutpoints (
273+ utxos , selectUtxos .ToSlice (),
274+ )
275+ if err != nil {
276+ return err
277+ }
278+ }
279+
263280 // We'll now lock each UTXO to ensure that other callers don't
264281 // attempt to use these UTXOs in transactions while we're
265282 // crafting out sweep all transaction.
@@ -278,7 +295,7 @@ func CraftSweepAllTx(feeRate, maxFeeRate chainfee.SatPerKWeight,
278295
279296 log .Trace ("[WithCoinSelectLock] exited the lock" )
280297
281- allOutputs = append (allOutputs , utxos ... )
298+ outputsForSweep = append (outputsForSweep , utxos ... )
282299
283300 return nil
284301 })
@@ -287,15 +304,15 @@ func CraftSweepAllTx(feeRate, maxFeeRate chainfee.SatPerKWeight,
287304 // in case we had any lingering outputs.
288305 unlockOutputs ()
289306
290- return nil , fmt .Errorf ("unable to fetch+lock wallet " +
291- "utxos: %v" , err )
307+ return nil , fmt .Errorf ("unable to fetch+lock wallet utxos: %w" ,
308+ err )
292309 }
293310
294311 // Now that we've locked all the potential outputs to sweep, we'll
295312 // assemble an input for each of them, so we can hand it off to the
296313 // sweeper to generate and sign a transaction for us.
297314 var inputsToSweep []input.Input
298- for _ , output := range allOutputs {
315+ for _ , output := range outputsForSweep {
299316 // As we'll be signing for outputs under control of the wallet,
300317 // we only need to populate the output value and output script.
301318 // The rest of the items will be populated internally within
@@ -390,3 +407,24 @@ func CraftSweepAllTx(feeRate, maxFeeRate chainfee.SatPerKWeight,
390407 CancelSweepAttempt : unlockOutputs ,
391408 }, nil
392409}
410+
411+ // fetchUtxosFromOutpoints returns UTXOs for given outpoints. Errors if any
412+ // outpoint is not in the passed slice of utxos.
413+ func fetchUtxosFromOutpoints (utxos []* lnwallet.Utxo ,
414+ outpoints []wire.OutPoint ) ([]* lnwallet.Utxo , error ) {
415+
416+ lookup := fn .SliceToMap (utxos , func (utxo * lnwallet.Utxo ) wire.OutPoint {
417+ return utxo .OutPoint
418+ }, func (utxo * lnwallet.Utxo ) * lnwallet.Utxo {
419+ return utxo
420+ })
421+
422+ subMap , err := fn .NewSubMap (lookup , outpoints )
423+ if err != nil {
424+ return nil , fmt .Errorf ("%w: %v" , ErrUnknownUTXO , err .Error ())
425+ }
426+
427+ fetchedUtxos := maps .Values (subMap )
428+
429+ return fetchedUtxos , nil
430+ }
0 commit comments