• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lightningnetwork / lnd / 15561477203

10 Jun 2025 01:54PM UTC coverage: 58.351% (-10.1%) from 68.487%
15561477203

Pull #9356

github

web-flow
Merge 6440b25db into c6d6d4c0b
Pull Request #9356: lnrpc: add incoming/outgoing channel ids filter to forwarding history request

33 of 36 new or added lines in 2 files covered. (91.67%)

28366 existing lines in 455 files now uncovered.

97715 of 167461 relevant lines covered (58.35%)

1.81 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

75.49
/sweep/walletsweep.go
1
package sweep
2

3
import (
4
        "errors"
5
        "fmt"
6
        "maps"
7
        "math"
8
        "slices"
9
        "time"
10

11
        "github.com/btcsuite/btcd/btcutil"
12
        "github.com/btcsuite/btcd/txscript"
13
        "github.com/btcsuite/btcd/wire"
14
        "github.com/btcsuite/btcwallet/wtxmgr"
15
        "github.com/lightningnetwork/lnd/fn/v2"
16
        "github.com/lightningnetwork/lnd/input"
17
        "github.com/lightningnetwork/lnd/lnwallet"
18
        "github.com/lightningnetwork/lnd/lnwallet/chainfee"
19
        "github.com/lightningnetwork/lnd/lnwallet/chanfunding"
20
)
21

22
var (
23
        // ErrNoFeePreference is returned when we attempt to satisfy a sweep
24
        // request from a client whom did not specify a fee preference.
25
        ErrNoFeePreference = errors.New("no fee preference specified")
26

27
        // ErrFeePreferenceConflict is returned when both a fee rate and a conf
28
        // target is set for a fee preference.
29
        ErrFeePreferenceConflict = errors.New("fee preference conflict")
30

31
        // ErrUnknownUTXO is returned when creating a sweeping tx using an UTXO
32
        // that's unknown to the wallet.
33
        ErrUnknownUTXO = errors.New("unknown utxo")
34
)
35

36
// FeePreference defines an interface that allows the caller to specify how the
37
// fee rate should be handled. Depending on the implementation, the fee rate
38
// can either be specified directly, or via a conf target which relies on the
39
// chain backend(`bitcoind`) to give a fee estimation, or a customized fee
40
// function which handles fee calculation based on the specified
41
// urgency(deadline).
42
type FeePreference interface {
43
        // String returns a human-readable string of the fee preference.
44
        String() string
45

46
        // Estimate takes a fee estimator and a max allowed fee rate and
47
        // returns a fee rate for the given fee preference. It ensures that the
48
        // fee rate respects the bounds of the relay fee and the specified max
49
        // fee rates.
50
        Estimate(chainfee.Estimator,
51
                chainfee.SatPerKWeight) (chainfee.SatPerKWeight, error)
52
}
53

54
// FeeEstimateInfo allows callers to express their time value for inclusion of
55
// a transaction into a block via either a confirmation target, or a fee rate.
56
type FeeEstimateInfo struct {
57
        // ConfTarget if non-zero, signals a fee preference expressed in the
58
        // number of desired blocks between first broadcast, and confirmation.
59
        ConfTarget uint32
60

61
        // FeeRate if non-zero, signals a fee pre fence expressed in the fee
62
        // rate expressed in sat/kw for a particular transaction.
63
        FeeRate chainfee.SatPerKWeight
64
}
65

66
// Compile-time constraint to ensure FeeEstimateInfo implements FeePreference.
67
var _ FeePreference = (*FeeEstimateInfo)(nil)
68

69
// String returns a human-readable string of the fee preference.
70
func (f FeeEstimateInfo) String() string {
×
71
        if f.ConfTarget != 0 {
×
72
                return fmt.Sprintf("%v blocks", f.ConfTarget)
×
73
        }
×
74

75
        return f.FeeRate.String()
×
76
}
77

78
// Estimate returns a fee rate for the given fee preference. It ensures that
79
// the fee rate respects the bounds of the relay fee and the max fee rates, if
80
// specified.
81
func (f FeeEstimateInfo) Estimate(estimator chainfee.Estimator,
82
        maxFeeRate chainfee.SatPerKWeight) (chainfee.SatPerKWeight, error) {
3✔
83

3✔
84
        var (
3✔
85
                feeRate chainfee.SatPerKWeight
3✔
86
                err     error
3✔
87
        )
3✔
88

3✔
89
        switch {
3✔
90
        // Ensure a type of fee preference is specified to prevent using a
91
        // default below.
UNCOV
92
        case f.FeeRate == 0 && f.ConfTarget == 0:
×
UNCOV
93
                return 0, ErrNoFeePreference
×
94

95
        // If both values are set, then we'll return an error as we require a
96
        // strict directive.
UNCOV
97
        case f.FeeRate != 0 && f.ConfTarget != 0:
×
UNCOV
98
                return 0, ErrFeePreferenceConflict
×
99

100
        // If the target number of confirmations is set, then we'll use that to
101
        // consult our fee estimator for an adequate fee.
102
        case f.ConfTarget != 0:
3✔
103
                feeRate, err = estimator.EstimateFeePerKW((f.ConfTarget))
3✔
104
                if err != nil {
3✔
UNCOV
105
                        return 0, fmt.Errorf("unable to query fee "+
×
UNCOV
106
                                "estimator: %w", err)
×
UNCOV
107
                }
×
108

109
        // If a manual sat/kw fee rate is set, then we'll use that directly.
110
        // We'll need to convert it to sat/kw as this is what we use
111
        // internally.
112
        case f.FeeRate != 0:
3✔
113
                feeRate = f.FeeRate
3✔
114

3✔
115
                // Because the user can specify 1 sat/vByte on the RPC
3✔
116
                // interface, which corresponds to 250 sat/kw, we need to bump
3✔
117
                // that to the minimum "safe" fee rate which is 253 sat/kw.
3✔
118
                if feeRate == chainfee.AbsoluteFeePerKwFloor {
6✔
119
                        log.Infof("Manual fee rate input of %d sat/kw is "+
3✔
120
                                "too low, using %d sat/kw instead", feeRate,
3✔
121
                                chainfee.FeePerKwFloor)
3✔
122

3✔
123
                        feeRate = chainfee.FeePerKwFloor
3✔
124
                }
3✔
125
        }
126

127
        // Get the relay fee as the min fee rate.
128
        minFeeRate := estimator.RelayFeePerKW()
3✔
129

3✔
130
        // If that bumped fee rate of at least 253 sat/kw is still lower than
3✔
131
        // the relay fee rate, we return an error to let the user know. Note
3✔
132
        // that "Relay fee rate" may mean slightly different things depending
3✔
133
        // on the backend. For bitcoind, it is effectively max(relay fee, min
3✔
134
        // mempool fee).
3✔
135
        if feeRate < minFeeRate {
3✔
UNCOV
136
                return 0, fmt.Errorf("%w: got %v, minimum is %v",
×
UNCOV
137
                        ErrFeePreferenceTooLow, feeRate, minFeeRate)
×
UNCOV
138
        }
×
139

140
        // If a maxFeeRate is specified and the estimated fee rate is above the
141
        // maximum allowed fee rate, default to the max fee rate.
142
        if maxFeeRate != 0 && feeRate > maxFeeRate {
6✔
143
                log.Warnf("Estimated fee rate %v exceeds max allowed fee "+
3✔
144
                        "rate %v, using max fee rate instead", feeRate,
3✔
145
                        maxFeeRate)
3✔
146

3✔
147
                return maxFeeRate, nil
3✔
148
        }
3✔
149

150
        return feeRate, nil
3✔
151
}
152

153
// UtxoSource is an interface that allows a caller to access a source of UTXOs
154
// to use when crafting sweep transactions.
155
type UtxoSource interface {
156
        // ListUnspentWitnessFromDefaultAccount returns all UTXOs from the
157
        // default wallet account that have between minConfs and maxConfs
158
        // number of confirmations.
159
        ListUnspentWitnessFromDefaultAccount(minConfs, maxConfs int32) (
160
                []*lnwallet.Utxo, error)
161
}
162

163
// CoinSelectionLocker is an interface that allows the caller to perform an
164
// operation, which is synchronized with all coin selection attempts. This can
165
// be used when an operation requires that all coin selection operations cease
166
// forward progress. Think of this as an exclusive lock on coin selection
167
// operations.
168
type CoinSelectionLocker interface {
169
        // WithCoinSelectLock will execute the passed function closure in a
170
        // synchronized manner preventing any coin selection operations from
171
        // proceeding while the closure is executing. This can be seen as the
172
        // ability to execute a function closure under an exclusive coin
173
        // selection lock.
174
        WithCoinSelectLock(func() error) error
175
}
176

177
// OutputLeaser allows a caller to lease/release an output. When leased, the
178
// outputs shouldn't be used for any sort of channel funding or coin selection.
179
// Leased outputs are expected to be persisted between restarts.
180
type OutputLeaser interface {
181
        // LeaseOutput leases a target output, rendering it unusable for coin
182
        // selection.
183
        LeaseOutput(i wtxmgr.LockID, o wire.OutPoint, d time.Duration) (
184
                time.Time, error)
185

186
        // ReleaseOutput releases a target output, allowing it to be used for
187
        // coin selection once again.
188
        ReleaseOutput(i wtxmgr.LockID, o wire.OutPoint) error
189
}
190

191
// WalletSweepPackage is a package that gives the caller the ability to sweep
192
// relevant funds from a wallet in a single transaction. We also package a
193
// function closure that allows one to abort the operation.
194
type WalletSweepPackage struct {
195
        // SweepTx is a fully signed, and valid transaction that is broadcast,
196
        // will sweep ALL relevant confirmed coins in the wallet with a single
197
        // transaction.
198
        SweepTx *wire.MsgTx
199

200
        // CancelSweepAttempt allows the caller to cancel the sweep attempt.
201
        //
202
        // NOTE: If the sweeping transaction isn't or cannot be broadcast, then
203
        // this closure MUST be called, otherwise all selected utxos will be
204
        // unable to be used.
205
        CancelSweepAttempt func()
206
}
207

208
// DeliveryAddr is a pair of (address, amount) used to craft a transaction
209
// paying to more than one specified address.
210
type DeliveryAddr struct {
211
        // Addr is the address to pay to.
212
        Addr btcutil.Address
213

214
        // Amt is the amount to pay to the given address.
215
        Amt btcutil.Amount
216
}
217

218
// CraftSweepAllTx attempts to craft a WalletSweepPackage which will allow the
219
// caller to sweep ALL funds in ALL or SELECT outputs within the wallet to a
220
// list of outputs. Any leftover amount after these outputs and transaction fee,
221
// is sent to a single output, as specified by the change address. The sweep
222
// transaction will be crafted with the target fee rate, and will use the
223
// utxoSource and outputLeaser as sources for wallet funds.
224
func CraftSweepAllTx(feeRate, maxFeeRate chainfee.SatPerKWeight,
225
        blockHeight uint32, deliveryAddrs []DeliveryAddr,
226
        changeAddr btcutil.Address, coinSelectLocker CoinSelectionLocker,
227
        utxoSource UtxoSource, outputLeaser OutputLeaser,
228
        signer input.Signer, minConfs int32,
229
        selectUtxos fn.Set[wire.OutPoint]) (*WalletSweepPackage, error) {
3✔
230

3✔
231
        // TODO(roasbeef): turn off ATPL as well when available?
3✔
232

3✔
233
        var outputsForSweep []*lnwallet.Utxo
3✔
234

3✔
235
        // We'll make a function closure up front that allows us to unlock all
3✔
236
        // selected outputs to ensure that they become available again in the
3✔
237
        // case of an error after the outputs have been locked, but before we
3✔
238
        // can actually craft a sweeping transaction.
3✔
239
        unlockOutputs := func() {
6✔
240
                for _, utxo := range outputsForSweep {
6✔
241
                        // Log the error but continue since we're already
3✔
242
                        // handling an error.
3✔
243
                        err := outputLeaser.ReleaseOutput(
3✔
244
                                chanfunding.LndInternalLockID, utxo.OutPoint,
3✔
245
                        )
3✔
246
                        if err != nil {
3✔
247
                                log.Warnf("Failed to release UTXO %s (%v))",
×
248
                                        utxo.OutPoint, err)
×
249
                        }
×
250
                }
251
        }
252

253
        // Next, we'll use the coinSelectLocker to ensure that no coin
254
        // selection takes place while we fetch and lock outputs in the
255
        // wallet. Otherwise, it may be possible for a new funding flow to lock
256
        // an output while we fetch the set of unspent witnesses.
257
        err := coinSelectLocker.WithCoinSelectLock(func() error {
6✔
258
                log.Trace("[WithCoinSelectLock] entered the lock")
3✔
259

3✔
260
                // Now that we can be sure that no other coin selection
3✔
261
                // operations are going on, we can grab a clean snapshot of the
3✔
262
                // current UTXO state of the wallet.
3✔
263
                utxos, err := utxoSource.ListUnspentWitnessFromDefaultAccount(
3✔
264
                        minConfs, math.MaxInt32,
3✔
265
                )
3✔
266
                if err != nil {
3✔
267
                        return err
×
268
                }
×
269

270
                log.Trace("[WithCoinSelectLock] finished fetching UTXOs")
3✔
271

3✔
272
                // Use select utxos, if provided.
3✔
273
                if len(selectUtxos) > 0 {
6✔
274
                        utxos, err = fetchUtxosFromOutpoints(
3✔
275
                                utxos, selectUtxos.ToSlice(),
3✔
276
                        )
3✔
277
                        if err != nil {
3✔
UNCOV
278
                                return err
×
UNCOV
279
                        }
×
280
                }
281

282
                // We'll now lock each UTXO to ensure that other callers don't
283
                // attempt to use these UTXOs in transactions while we're
284
                // crafting out sweep all transaction.
285
                for _, utxo := range utxos {
6✔
286
                        log.Tracef("[WithCoinSelectLock] leasing utxo: %v",
3✔
287
                                utxo.OutPoint)
3✔
288

3✔
289
                        _, err = outputLeaser.LeaseOutput(
3✔
290
                                chanfunding.LndInternalLockID, utxo.OutPoint,
3✔
291
                                chanfunding.DefaultLockDuration,
3✔
292
                        )
3✔
293
                        if err != nil {
3✔
294
                                return err
×
295
                        }
×
296
                }
297

298
                log.Trace("[WithCoinSelectLock] exited the lock")
3✔
299

3✔
300
                outputsForSweep = append(outputsForSweep, utxos...)
3✔
301

3✔
302
                return nil
3✔
303
        })
304
        if err != nil {
3✔
UNCOV
305
                // If we failed at all, we'll unlock any outputs selected just
×
UNCOV
306
                // in case we had any lingering outputs.
×
UNCOV
307
                unlockOutputs()
×
UNCOV
308

×
UNCOV
309
                return nil, fmt.Errorf("unable to fetch+lock wallet utxos: %w",
×
UNCOV
310
                        err)
×
UNCOV
311
        }
×
312

313
        // Now that we've locked all the potential outputs to sweep, we'll
314
        // assemble an input for each of them, so we can hand it off to the
315
        // sweeper to generate and sign a transaction for us.
316
        var inputsToSweep []input.Input
3✔
317
        for _, output := range outputsForSweep {
6✔
318
                // As we'll be signing for outputs under control of the wallet,
3✔
319
                // we only need to populate the output value and output script.
3✔
320
                // The rest of the items will be populated internally within
3✔
321
                // the sweeper via the witness generation function.
3✔
322
                signDesc := &input.SignDescriptor{
3✔
323
                        Output: &wire.TxOut{
3✔
324
                                PkScript: output.PkScript,
3✔
325
                                Value:    int64(output.Value),
3✔
326
                        },
3✔
327
                        HashType: txscript.SigHashAll,
3✔
328
                }
3✔
329

3✔
330
                pkScript := output.PkScript
3✔
331

3✔
332
                // Based on the output type, we'll map it to the proper witness
3✔
333
                // type so we can generate the set of input scripts needed to
3✔
334
                // sweep the output.
3✔
335
                var witnessType input.WitnessType
3✔
336
                switch output.AddressType {
3✔
337

338
                // If this is a p2wkh output, then we'll assume it's a witness
339
                // key hash witness type.
340
                case lnwallet.WitnessPubKey:
3✔
341
                        witnessType = input.WitnessKeyHash
3✔
342

343
                // If this is a p2sh output, then as since it's under control
344
                // of the wallet, we'll assume it's a nested p2sh output.
345
                case lnwallet.NestedWitnessPubKey:
3✔
346
                        witnessType = input.NestedWitnessKeyHash
3✔
347

348
                case lnwallet.TaprootPubkey:
3✔
349
                        witnessType = input.TaprootPubKeySpend
3✔
350
                        signDesc.HashType = txscript.SigHashDefault
3✔
351

352
                // All other output types we count as unknown and will fail to
353
                // sweep.
UNCOV
354
                default:
×
UNCOV
355
                        unlockOutputs()
×
UNCOV
356

×
UNCOV
357
                        return nil, fmt.Errorf("unable to sweep coins, "+
×
UNCOV
358
                                "unknown script: %x", pkScript[:])
×
359
                }
360

361
                // Now that we've constructed the items required, we'll make an
362
                // input which can be passed to the sweeper for ultimate
363
                // sweeping.
364
                input := input.MakeBaseInput(
3✔
365
                        &output.OutPoint, witnessType, signDesc, 0, nil,
3✔
366
                )
3✔
367
                inputsToSweep = append(inputsToSweep, &input)
3✔
368
        }
369

370
        // Create a list of TxOuts from the given delivery addresses.
371
        var txOuts []*wire.TxOut
3✔
372
        for _, d := range deliveryAddrs {
6✔
373
                pkScript, err := txscript.PayToAddrScript(d.Addr)
3✔
374
                if err != nil {
3✔
375
                        unlockOutputs()
×
376

×
377
                        return nil, err
×
378
                }
×
379

380
                txOuts = append(txOuts, &wire.TxOut{
3✔
381
                        PkScript: pkScript,
3✔
382
                        Value:    int64(d.Amt),
3✔
383
                })
3✔
384
        }
385

386
        // Next, we'll convert the change addr to a pkScript that we can use
387
        // to create the sweep transaction.
388
        changePkScript, err := txscript.PayToAddrScript(changeAddr)
3✔
389
        if err != nil {
3✔
390
                unlockOutputs()
×
391

×
392
                return nil, err
×
393
        }
×
394

395
        // Finally, we'll ask the sweeper to craft a sweep transaction which
396
        // respects our fee preference and targets all the UTXOs of the wallet.
397
        sweepTx, _, err := createSweepTx(
3✔
398
                inputsToSweep, txOuts, changePkScript, blockHeight,
3✔
399
                feeRate, maxFeeRate, signer,
3✔
400
        )
3✔
401
        if err != nil {
3✔
402
                unlockOutputs()
×
403

×
404
                return nil, err
×
405
        }
×
406

407
        return &WalletSweepPackage{
3✔
408
                SweepTx:            sweepTx,
3✔
409
                CancelSweepAttempt: unlockOutputs,
3✔
410
        }, nil
3✔
411
}
412

413
// fetchUtxosFromOutpoints returns UTXOs for given outpoints. Errors if any
414
// outpoint is not in the passed slice of utxos.
415
func fetchUtxosFromOutpoints(utxos []*lnwallet.Utxo,
416
        outpoints []wire.OutPoint) ([]*lnwallet.Utxo, error) {
3✔
417

3✔
418
        lookup := fn.SliceToMap(utxos, func(utxo *lnwallet.Utxo) wire.OutPoint {
6✔
419
                return utxo.OutPoint
3✔
420
        }, func(utxo *lnwallet.Utxo) *lnwallet.Utxo {
6✔
421
                return utxo
3✔
422
        })
3✔
423

424
        subMap, err := fn.NewSubMap(lookup, outpoints)
3✔
425
        if err != nil {
3✔
UNCOV
426
                return nil, fmt.Errorf("%w: %v", ErrUnknownUTXO, err.Error())
×
UNCOV
427
        }
×
428

429
        fetchedUtxos := slices.Collect(maps.Values(subMap))
3✔
430

3✔
431
        return fetchedUtxos, nil
3✔
432
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc