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

lightningnetwork / lnd / 11216766535

07 Oct 2024 01:37PM UTC coverage: 57.817% (-1.0%) from 58.817%
11216766535

Pull #9148

github

ProofOfKeags
lnwire: remove kickoff feerate from propose/commit
Pull Request #9148: DynComms [2/n]: lnwire: add authenticated wire messages for Dyn*

571 of 879 new or added lines in 16 files covered. (64.96%)

23253 existing lines in 251 files now uncovered.

99022 of 171268 relevant lines covered (57.82%)

38420.67 hits per line

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

80.88
/sweep/walletsweep.go
1
package sweep
2

3
import (
4
        "errors"
5
        "fmt"
6
        "math"
7
        "time"
8

9
        "github.com/btcsuite/btcd/btcutil"
10
        "github.com/btcsuite/btcd/txscript"
11
        "github.com/btcsuite/btcd/wire"
12
        "github.com/btcsuite/btcwallet/wtxmgr"
13
        "github.com/lightningnetwork/lnd/fn"
14
        "github.com/lightningnetwork/lnd/input"
15
        "github.com/lightningnetwork/lnd/lnwallet"
16
        "github.com/lightningnetwork/lnd/lnwallet/chainfee"
17
        "github.com/lightningnetwork/lnd/lnwallet/chanfunding"
18
        "golang.org/x/exp/maps"
19
)
20

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

26
        // ErrFeePreferenceConflict is returned when both a fee rate and a conf
27
        // target is set for a fee preference.
28
        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")
33
)
34

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

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

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

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

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

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

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

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

17✔
83
        var (
17✔
84
                feeRate chainfee.SatPerKWeight
17✔
85
                err     error
17✔
86
        )
17✔
87

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

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

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

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

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

×
UNCOV
122
                        feeRate = chainfee.FeePerKwFloor
×
UNCOV
123
                }
×
124
        }
125

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

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

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

2✔
146
                return maxFeeRate, nil
2✔
147
        }
2✔
148

149
        return feeRate, nil
9✔
150
}
151

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

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

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

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

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

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

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

212
        // Amt is the amount to pay to the given address.
213
        Amt btcutil.Amount
214
}
215

216
// CraftSweepAllTx attempts to craft a WalletSweepPackage which will allow the
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.
222
func CraftSweepAllTx(feeRate, maxFeeRate chainfee.SatPerKWeight,
223
        blockHeight uint32, deliveryAddrs []DeliveryAddr,
224
        changeAddr btcutil.Address, coinSelectLocker CoinSelectionLocker,
225
        utxoSource UtxoSource, outputLeaser OutputLeaser,
226
        signer input.Signer, minConfs int32,
227
        selectUtxos fn.Set[wire.OutPoint]) (*WalletSweepPackage, error) {
5✔
228

5✔
229
        // TODO(roasbeef): turn off ATPL as well when available?
5✔
230

5✔
231
        var outputsForSweep []*lnwallet.Utxo
5✔
232

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

251
        // Next, we'll use the coinSelectLocker to ensure that no coin
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.
255
        err := coinSelectLocker.WithCoinSelectLock(func() error {
10✔
256
                log.Trace("[WithCoinSelectLock] entered the lock")
5✔
257

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

268
                log.Trace("[WithCoinSelectLock] finished fetching UTXOs")
5✔
269

5✔
270
                // Use select utxos, if provided.
5✔
271
                if len(selectUtxos) > 0 {
7✔
272
                        utxos, err = fetchUtxosFromOutpoints(
2✔
273
                                utxos, selectUtxos.ToSlice(),
2✔
274
                        )
2✔
275
                        if err != nil {
3✔
276
                                return err
1✔
277
                        }
1✔
278
                }
279

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

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

296
                log.Trace("[WithCoinSelectLock] exited the lock")
4✔
297

4✔
298
                outputsForSweep = append(outputsForSweep, utxos...)
4✔
299

4✔
300
                return nil
4✔
301
        })
302
        if err != nil {
7✔
303
                // If we failed at all, we'll unlock any outputs selected just
2✔
304
                // in case we had any lingering outputs.
2✔
305
                unlockOutputs()
2✔
306

2✔
307
                return nil, fmt.Errorf("unable to fetch+lock wallet utxos: %w",
2✔
308
                        err)
2✔
309
        }
2✔
310

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

6✔
328
                pkScript := output.PkScript
6✔
329

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

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

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

UNCOV
346
                case lnwallet.TaprootPubkey:
×
UNCOV
347
                        witnessType = input.TaprootPubKeySpend
×
UNCOV
348
                        signDesc.HashType = txscript.SigHashDefault
×
349

350
                // All other output types we count as unknown and will fail to
351
                // sweep.
352
                default:
1✔
353
                        unlockOutputs()
1✔
354

1✔
355
                        return nil, fmt.Errorf("unable to sweep coins, "+
1✔
356
                                "unknown script: %x", pkScript[:])
1✔
357
                }
358

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

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

×
375
                        return nil, err
×
376
                }
×
377

UNCOV
378
                txOuts = append(txOuts, &wire.TxOut{
×
UNCOV
379
                        PkScript: pkScript,
×
UNCOV
380
                        Value:    int64(d.Amt),
×
UNCOV
381
                })
×
382
        }
383

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

×
390
                return nil, err
×
391
        }
×
392

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

×
402
                return nil, err
×
403
        }
×
404

405
        return &WalletSweepPackage{
2✔
406
                SweepTx:            sweepTx,
2✔
407
                CancelSweepAttempt: unlockOutputs,
2✔
408
        }, nil
2✔
409
}
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) {
2✔
415

2✔
416
        lookup := fn.SliceToMap(utxos, func(utxo *lnwallet.Utxo) wire.OutPoint {
4✔
417
                return utxo.OutPoint
2✔
418
        }, func(utxo *lnwallet.Utxo) *lnwallet.Utxo {
4✔
419
                return utxo
2✔
420
        })
2✔
421

422
        subMap, err := fn.NewSubMap(lookup, outpoints)
2✔
423
        if err != nil {
3✔
424
                return nil, fmt.Errorf("%w: %v", ErrUnknownUTXO, err.Error())
1✔
425
        }
1✔
426

427
        fetchedUtxos := maps.Values(subMap)
1✔
428

1✔
429
        return fetchedUtxos, nil
1✔
430
}
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