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

lightningnetwork / lnd / 15283631962

27 May 2025 07:07PM UTC coverage: 68.776% (+10.8%) from 57.977%
15283631962

push

github

web-flow
Merge pull request #9854 from yyforyongyu/fix-fetchinput

gomod: update `btcwallet` to fix `FetchOutpointInfo`

0 of 572 new or added lines in 2 files covered. (0.0%)

24 existing lines in 5 files now uncovered.

133993 of 194825 relevant lines covered (68.78%)

21982.24 hits per line

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

0.0
/itest/lnd_bump_fee.go
1
package itest
2

3
import (
4
        "fmt"
5

6
        "github.com/btcsuite/btcd/btcutil"
7
        "github.com/btcsuite/btcd/wire"
8
        "github.com/lightningnetwork/lnd/lnrpc"
9
        "github.com/lightningnetwork/lnd/lnrpc/walletrpc"
10
        "github.com/lightningnetwork/lnd/lntest"
11
        "github.com/lightningnetwork/lnd/lntest/node"
12
        "github.com/lightningnetwork/lnd/lntest/wait"
13
        "github.com/lightningnetwork/lnd/lnwallet/chainfee"
14
        "github.com/lightningnetwork/lnd/sweep"
15
        "github.com/stretchr/testify/require"
16
)
17

18
// testBumpFeeLowBudget checks that when the requested ideal budget cannot be
19
// met, the sweeper still sweeps the input with the actual budget.
NEW
20
func testBumpFeeLowBudget(ht *lntest.HarnessTest) {
×
NEW
21
        // Create a new node with a large `maxfeerate` so it's easier to run the
×
NEW
22
        // test.
×
NEW
23
        alice := ht.NewNode("Alice", []string{
×
NEW
24
                "--sweeper.maxfeerate=10000",
×
NEW
25
        })
×
NEW
26

×
NEW
27
        // Fund Alice 2 UTXOs, each has 100k sats. One of the UTXOs will be used
×
NEW
28
        // to create a tx which she sends some coins to herself. The other will
×
NEW
29
        // be used as the budget when CPFPing the above tx.
×
NEW
30
        coin := btcutil.Amount(100_000)
×
NEW
31
        ht.FundCoins(coin, alice)
×
NEW
32
        ht.FundCoins(coin, alice)
×
NEW
33

×
NEW
34
        // Alice sends 50k sats to herself.
×
NEW
35
        tx := ht.SendCoins(alice, alice, coin/2)
×
NEW
36
        txid := tx.TxHash()
×
NEW
37

×
NEW
38
        // Get Alice's wallet balance to calculate the fees used in the above
×
NEW
39
        // tx.
×
NEW
40
        resp := alice.RPC.WalletBalance()
×
NEW
41

×
NEW
42
        // balance is the expected final balance. Alice's initial balance is
×
NEW
43
        // 200k sats, with 100k sats as the budget for the sweeping tx, which
×
NEW
44
        // means her final balance should be 100k sats minus the mining fees
×
NEW
45
        // used in the above `SendCoins`.
×
NEW
46
        balance := btcutil.Amount(
×
NEW
47
                resp.UnconfirmedBalance + resp.ConfirmedBalance,
×
NEW
48
        )
×
NEW
49
        fee := coin*2 - balance
×
NEW
50
        ht.Logf("Alice's expected final balance=%v, fee=%v", balance, fee)
×
NEW
51

×
NEW
52
        // Alice now tries to bump the first output on this tx.
×
NEW
53
        op := &lnrpc.OutPoint{
×
NEW
54
                TxidBytes:   txid[:],
×
NEW
55
                OutputIndex: uint32(0),
×
NEW
56
        }
×
NEW
57
        value := btcutil.Amount(tx.TxOut[0].Value)
×
NEW
58

×
NEW
59
        // assertPendingSweepResp is a helper closure that asserts the response
×
NEW
60
        // from `PendingSweep` RPC is returned with expected values. It also
×
NEW
61
        // returns the sweeping tx for further checks.
×
NEW
62
        assertPendingSweepResp := func(budget uint64,
×
NEW
63
                deadline uint32) *wire.MsgTx {
×
NEW
64

×
NEW
65
                // Alice should still have one pending sweep.
×
NEW
66
                pendingSweep := ht.AssertNumPendingSweeps(alice, 1)[0]
×
NEW
67

×
NEW
68
                // Validate all fields returned from `PendingSweeps` are as
×
NEW
69
                // expected.
×
NEW
70
                require.Equal(ht, op.TxidBytes, pendingSweep.Outpoint.TxidBytes)
×
NEW
71
                require.Equal(ht, op.OutputIndex,
×
NEW
72
                        pendingSweep.Outpoint.OutputIndex)
×
NEW
73
                require.Equal(ht, walletrpc.WitnessType_TAPROOT_PUB_KEY_SPEND,
×
NEW
74
                        pendingSweep.WitnessType)
×
NEW
75
                require.EqualValuesf(ht, value, pendingSweep.AmountSat,
×
NEW
76
                        "amount not matched: want=%d, got=%d", value,
×
NEW
77
                        pendingSweep.AmountSat)
×
NEW
78
                require.True(ht, pendingSweep.Immediate)
×
NEW
79

×
NEW
80
                require.EqualValuesf(ht, budget, pendingSweep.Budget,
×
NEW
81
                        "budget not matched: want=%d, got=%d", budget,
×
NEW
82
                        pendingSweep.Budget)
×
NEW
83

×
NEW
84
                // Since the request doesn't specify a deadline, we expect the
×
NEW
85
                // existing deadline to be used.
×
NEW
86
                require.Equalf(ht, deadline, pendingSweep.DeadlineHeight,
×
NEW
87
                        "deadline height not matched: want=%d, got=%d",
×
NEW
88
                        deadline, pendingSweep.DeadlineHeight)
×
NEW
89

×
NEW
90
                // We expect to see Alice's original tx and her CPFP tx in the
×
NEW
91
                // mempool.
×
NEW
92
                txns := ht.GetNumTxsFromMempool(2)
×
NEW
93

×
NEW
94
                // Find the sweeping tx - assume it's the first item, if it has
×
NEW
95
                // the same txid as the parent tx, use the second item.
×
NEW
96
                sweepTx := txns[0]
×
NEW
97
                if sweepTx.TxHash() == tx.TxHash() {
×
NEW
98
                        sweepTx = txns[1]
×
NEW
99
                }
×
100

NEW
101
                return sweepTx
×
102
        }
103

104
        // Use a budget that Alice cannot cover using her wallet UTXOs.
NEW
105
        budget := coin * 2
×
NEW
106

×
NEW
107
        // Use a deadlineDelta of 3 such that the fee func is initialized as,
×
NEW
108
        // - starting fee rate: 1 sat/vbyte
×
NEW
109
        // - deadline: 3
×
NEW
110
        // - budget: 200% of Alice's available funds.
×
NEW
111
        deadlineDelta := 3
×
NEW
112

×
NEW
113
        // First bump request - we expect it to succeed as Alice's current funds
×
NEW
114
        // can cover the fees used here given the position of the fee func is at
×
NEW
115
        // 0.
×
NEW
116
        bumpFeeReq := &walletrpc.BumpFeeRequest{
×
NEW
117
                Outpoint:      op,
×
NEW
118
                Budget:        uint64(budget),
×
NEW
119
                Immediate:     true,
×
NEW
120
                DeadlineDelta: uint32(deadlineDelta),
×
NEW
121
        }
×
NEW
122
        alice.RPC.BumpFee(bumpFeeReq)
×
NEW
123

×
NEW
124
        // Calculate the deadline height.
×
NEW
125
        deadline := ht.CurrentHeight() + uint32(deadlineDelta)
×
NEW
126

×
NEW
127
        // Assert the pending sweep is created with the expected values:
×
NEW
128
        // - deadline: 3+current height.
×
NEW
129
        // - budget: 2x the wallet balance.
×
NEW
130
        sweepTx1 := assertPendingSweepResp(uint64(budget), deadline)
×
NEW
131

×
NEW
132
        // Mine a block to trigger Alice's sweeper to fee bump the tx.
×
NEW
133
        //
×
NEW
134
        // Second bump request - we expect it to succeed as Alice's current
×
NEW
135
        // funds can cover the fees used here, which is 66.7% of her available
×
NEW
136
        // funds given the position of the fee func is at 1.
×
NEW
137
        ht.MineEmptyBlocks(1)
×
NEW
138

×
NEW
139
        // Assert the old sweeping tx has been replaced.
×
NEW
140
        ht.AssertTxNotInMempool(sweepTx1.TxHash())
×
NEW
141

×
NEW
142
        // Assert a new sweeping tx is made.
×
NEW
143
        sweepTx2 := assertPendingSweepResp(uint64(budget), deadline)
×
NEW
144

×
NEW
145
        // Mine a block to trigger Alice's sweeper to fee bump the tx.
×
NEW
146
        //
×
NEW
147
        // Third bump request - we expect it to fail as Alice's current funds
×
NEW
148
        // cannot cover the fees now, which is 133.3% of her available funds
×
NEW
149
        // given the position of the fee func is at 2.
×
NEW
150
        ht.MineEmptyBlocks(1)
×
NEW
151

×
NEW
152
        // Assert the above sweeping tx is still in the mempool.
×
NEW
153
        ht.AssertTxInMempool(sweepTx2.TxHash())
×
NEW
154

×
NEW
155
        // Fund Alice 200k sats, which will be used to cover the budget.
×
NEW
156
        //
×
NEW
157
        // TODO(yy): We are funding Alice more than enough - at this stage Alice
×
NEW
158
        // has a confirmed UTXO of `coin` amount in her wallet, so ideally we
×
NEW
159
        // should only fund another UTXO of `coin` amount. However, since the
×
NEW
160
        // confirmed wallet UTXO has already been used in sweepTx2, there's no
×
NEW
161
        // easy way to tell her wallet to reuse that UTXO in the upcoming
×
NEW
162
        // sweeping tx.
×
NEW
163
        // To properly fix it, we should provide more granular UTXO management
×
NEW
164
        // here by leveraing `LeaseOutput` - whenever we use a wallet UTXO, we
×
NEW
165
        // should lock it first. And when the sweeping attempt fails, we should
×
NEW
166
        // release it so the UTXO can be used again in another batch.
×
NEW
167
        walletTx := ht.FundCoinsUnconfirmed(coin*2, alice)
×
NEW
168

×
NEW
169
        // Mine a block to confirm the above funding coin.
×
NEW
170
        //
×
NEW
171
        // Fourth bump request - we expect it to succeed as Alice's current
×
NEW
172
        // funds can cover the full budget.
×
NEW
173
        ht.MineBlockWithTx(walletTx)
×
NEW
174

×
NEW
175
        flakeRaceInBitcoinClientNotifications(ht)
×
NEW
176

×
NEW
177
        // Assert Alice's previous sweeping tx has been replaced.
×
NEW
178
        ht.AssertTxNotInMempool(sweepTx2.TxHash())
×
NEW
179

×
NEW
180
        // Assert the pending sweep is created with the expected values:
×
NEW
181
        // - deadline: 3+current height.
×
NEW
182
        // - budget: 2x the wallet balance.
×
NEW
183
        sweepTx3 := assertPendingSweepResp(uint64(budget), deadline)
×
NEW
184
        require.NotEqual(ht, sweepTx2.TxHash(), sweepTx3.TxHash())
×
NEW
185

×
NEW
186
        // Mine the sweeping tx.
×
NEW
187
        ht.MineBlocksAndAssertNumTxes(1, 2)
×
NEW
188

×
NEW
189
        // Assert Alice's wallet balance.       a
×
NEW
190
        ht.WaitForBalanceConfirmed(alice, balance)
×
191
}
192

193
// testBumpFee checks that when a new input is requested, it's first bumped via
194
// CPFP, then RBF. Along the way, we check the `BumpFee` can properly update
195
// the fee function used by supplying new params.
NEW
196
func testBumpFee(ht *lntest.HarnessTest) {
×
NEW
197
        alice := ht.NewNodeWithCoins("Alice", nil)
×
NEW
198

×
NEW
199
        runBumpFee(ht, alice)
×
NEW
200
}
×
201

202
// runBumpFee checks the `BumpFee` RPC can properly bump the fee of a given
203
// input.
NEW
204
func runBumpFee(ht *lntest.HarnessTest, alice *node.HarnessNode) {
×
NEW
205
        // Skip this test for neutrino, as it's not aware of mempool
×
NEW
206
        // transactions.
×
NEW
207
        if ht.IsNeutrinoBackend() {
×
NEW
208
                ht.Skipf("skipping BumpFee test for neutrino backend")
×
NEW
209
        }
×
210

211
        // startFeeRate is the min fee rate in sats/vbyte. This value should be
212
        // used as the starting fee rate when the default no deadline is used.
NEW
213
        startFeeRate := uint64(1)
×
NEW
214

×
NEW
215
        // We'll start the test by sending Alice some coins, which she'll use
×
NEW
216
        // to send to herself.
×
NEW
217
        ht.FundCoins(btcutil.SatoshiPerBitcoin, alice)
×
NEW
218

×
NEW
219
        // Alice sends a coin to herself.
×
NEW
220
        tx := ht.SendCoins(alice, alice, btcutil.SatoshiPerBitcoin)
×
NEW
221
        txid := tx.TxHash()
×
NEW
222

×
NEW
223
        // Alice now tries to bump the first output on this tx.
×
NEW
224
        op := &lnrpc.OutPoint{
×
NEW
225
                TxidBytes:   txid[:],
×
NEW
226
                OutputIndex: uint32(0),
×
NEW
227
        }
×
NEW
228
        value := btcutil.Amount(tx.TxOut[0].Value)
×
NEW
229

×
NEW
230
        // assertPendingSweepResp is a helper closure that asserts the response
×
NEW
231
        // from `PendingSweep` RPC is returned with expected values. It also
×
NEW
232
        // returns the sweeping tx for further checks.
×
NEW
233
        assertPendingSweepResp := func(broadcastAttempts uint32, budget uint64,
×
NEW
234
                deadline uint32, startingFeeRate uint64) *wire.MsgTx {
×
NEW
235

×
NEW
236
                err := wait.NoError(func() error {
×
NEW
237
                        // Alice should still have one pending sweep.
×
NEW
238
                        ps := ht.AssertNumPendingSweeps(alice, 1)[0]
×
NEW
239

×
NEW
240
                        // Validate all fields returned from `PendingSweeps` are
×
NEW
241
                        // as expected.
×
NEW
242
                        //
×
NEW
243
                        // These fields should stay the same during the test so
×
NEW
244
                        // we assert the values without wait.
×
NEW
245
                        require.Equal(ht, op.TxidBytes, ps.Outpoint.TxidBytes)
×
NEW
246
                        require.Equal(ht, op.OutputIndex,
×
NEW
247
                                ps.Outpoint.OutputIndex)
×
NEW
248
                        require.Equal(ht,
×
NEW
249
                                walletrpc.WitnessType_TAPROOT_PUB_KEY_SPEND,
×
NEW
250
                                ps.WitnessType)
×
NEW
251
                        require.EqualValuesf(ht, value, ps.AmountSat,
×
NEW
252
                                "amount not matched: want=%d, got=%d", value,
×
NEW
253
                                ps.AmountSat)
×
NEW
254

×
NEW
255
                        // The following fields can change during the test so we
×
NEW
256
                        // return an error if they don't match, which will be
×
NEW
257
                        // checked again in this wait call.
×
NEW
258
                        if !ps.Immediate {
×
NEW
259
                                return fmt.Errorf("immediate should be true")
×
NEW
260
                        }
×
261

NEW
262
                        if broadcastAttempts != ps.BroadcastAttempts {
×
NEW
263
                                return fmt.Errorf("broadcastAttempts not "+
×
NEW
264
                                        "matched: want=%d, got=%d",
×
NEW
265
                                        broadcastAttempts, ps.BroadcastAttempts)
×
NEW
266
                        }
×
NEW
267
                        if budget != ps.Budget {
×
NEW
268
                                return fmt.Errorf("budget not matched: "+
×
NEW
269
                                        "want=%d, got=%d", budget, ps.Budget)
×
NEW
270
                        }
×
271

272
                        // Since the request doesn't specify a deadline, we
273
                        // expect the existing deadline to be used.
NEW
274
                        if deadline != ps.DeadlineHeight {
×
NEW
275
                                return fmt.Errorf("deadline height not "+
×
NEW
276
                                        "matched: want=%d, got=%d", deadline,
×
NEW
277
                                        ps.DeadlineHeight)
×
NEW
278
                        }
×
279

280
                        // Since the request specifies a starting fee rate, we
281
                        // expect that to be used as the starting fee rate.
NEW
282
                        if startingFeeRate != ps.RequestedSatPerVbyte {
×
NEW
283
                                return fmt.Errorf("requested starting fee "+
×
NEW
284
                                        "rate not matched: want=%d, got=%d",
×
NEW
285
                                        startingFeeRate,
×
NEW
286
                                        ps.RequestedSatPerVbyte)
×
NEW
287
                        }
×
288

NEW
289
                        return nil
×
290
                }, wait.DefaultTimeout)
NEW
291
                require.NoError(ht, err, "timeout checking pending sweep")
×
NEW
292

×
NEW
293
                // We expect to see Alice's original tx and her CPFP tx in the
×
NEW
294
                // mempool.
×
NEW
295
                txns := ht.GetNumTxsFromMempool(2)
×
NEW
296

×
NEW
297
                // Find the sweeping tx - assume it's the first item, if it has
×
NEW
298
                // the same txid as the parent tx, use the second item.
×
NEW
299
                sweepTx := txns[0]
×
NEW
300
                if sweepTx.TxHash() == tx.TxHash() {
×
NEW
301
                        sweepTx = txns[1]
×
NEW
302
                }
×
303

NEW
304
                return sweepTx
×
305
        }
306

307
        // assertFeeRateEqual is a helper closure that asserts the fee rate of
308
        // the pending sweep tx is equal to the expected fee rate.
NEW
309
        assertFeeRateEqual := func(expected uint64) {
×
NEW
310
                err := wait.NoError(func() error {
×
NEW
311
                        // Alice should still have one pending sweep.
×
NEW
312
                        pendingSweep := ht.AssertNumPendingSweeps(alice, 1)[0]
×
NEW
313

×
NEW
314
                        if pendingSweep.SatPerVbyte == expected {
×
NEW
315
                                return nil
×
NEW
316
                        }
×
317

NEW
318
                        return fmt.Errorf("expected current fee rate %d, got "+
×
NEW
319
                                "%d", expected, pendingSweep.SatPerVbyte)
×
320
                }, wait.DefaultTimeout)
NEW
321
                require.NoError(ht, err, "fee rate not updated")
×
322
        }
323

324
        // assertFeeRateGreater is a helper closure that asserts the fee rate
325
        // of the pending sweep tx is greater than the expected fee rate.
NEW
326
        assertFeeRateGreater := func(expected uint64) {
×
NEW
327
                err := wait.NoError(func() error {
×
NEW
328
                        // Alice should still have one pending sweep.
×
NEW
329
                        pendingSweep := ht.AssertNumPendingSweeps(alice, 1)[0]
×
NEW
330

×
NEW
331
                        if pendingSweep.SatPerVbyte > expected {
×
NEW
332
                                return nil
×
NEW
333
                        }
×
334

NEW
335
                        return fmt.Errorf("expected current fee rate greater "+
×
NEW
336
                                "than %d, got %d", expected,
×
NEW
337
                                pendingSweep.SatPerVbyte)
×
338
                }, wait.DefaultTimeout)
NEW
339
                require.NoError(ht, err, "fee rate not updated")
×
340
        }
341

342
        // First bump request - we'll specify nothing except `Immediate` to let
343
        // the sweeper handle the fee, and we expect a fee func that has,
344
        // - starting fee rate: 1 sat/vbyte (min relay fee rate).
345
        // - deadline: 1008 (default deadline).
346
        // - budget: 50% of the input value.
NEW
347
        bumpFeeReq := &walletrpc.BumpFeeRequest{
×
NEW
348
                Outpoint: op,
×
NEW
349
                // We use a force param to create the sweeping tx immediately.
×
NEW
350
                Immediate: true,
×
NEW
351
        }
×
NEW
352
        alice.RPC.BumpFee(bumpFeeReq)
×
NEW
353

×
NEW
354
        // Since the request doesn't specify a deadline, we expect the default
×
NEW
355
        // deadline to be used.
×
NEW
356
        currentHeight := int32(ht.CurrentHeight())
×
NEW
357
        deadline := uint32(currentHeight + sweep.DefaultDeadlineDelta)
×
NEW
358

×
NEW
359
        // Assert the pending sweep is created with the expected values:
×
NEW
360
        // - broadcast attempts: 1.
×
NEW
361
        // - starting fee rate: 1 sat/vbyte (min relay fee rate).
×
NEW
362
        // - deadline: 1008 (default deadline).
×
NEW
363
        // - budget: 50% of the input value.
×
NEW
364
        sweepTx1 := assertPendingSweepResp(1, uint64(value/2), deadline, 0)
×
NEW
365

×
NEW
366
        // Since the request doesn't specify a starting fee rate, we expect the
×
NEW
367
        // min relay fee rate is used as the current fee rate.
×
NEW
368
        assertFeeRateEqual(startFeeRate)
×
NEW
369

×
NEW
370
        // First we test the case where we specify the conf target to increase
×
NEW
371
        // the starting fee rate of the fee function.
×
NEW
372
        confTargetFeeRate := chainfee.SatPerVByte(50)
×
NEW
373
        ht.SetFeeEstimateWithConf(confTargetFeeRate.FeePerKWeight(), 3)
×
NEW
374

×
NEW
375
        // Second bump request - we will specify the conf target and expect a
×
NEW
376
        // starting fee rate that is estimated using the provided estimator.
×
NEW
377
        // - starting fee rate: 50 sat/vbyte (conf target 3).
×
NEW
378
        // - deadline: 1008 (default deadline).
×
NEW
379
        // - budget: 50% of the input value.
×
NEW
380
        bumpFeeReq = &walletrpc.BumpFeeRequest{
×
NEW
381
                Outpoint: op,
×
NEW
382
                // We use a force param to create the sweeping tx immediately.
×
NEW
383
                Immediate:  true,
×
NEW
384
                TargetConf: 3,
×
NEW
385
        }
×
NEW
386

×
NEW
387
        alice.RPC.BumpFee(bumpFeeReq)
×
NEW
388

×
NEW
389
        // Alice's old sweeping tx should be replaced.
×
NEW
390
        ht.AssertTxNotInMempool(sweepTx1.TxHash())
×
NEW
391

×
NEW
392
        // Assert the pending sweep is created with the expected values:
×
NEW
393
        // - broadcast attempts: 2.
×
NEW
394
        // - starting fee rate: 50 sat/vbyte (conf target 3).
×
NEW
395
        // - deadline: 1008 (default deadline).
×
NEW
396
        // - budget: 50% of the input value.
×
NEW
397
        sweepTx2 := assertPendingSweepResp(
×
NEW
398
                2, uint64(value/2), deadline, uint64(confTargetFeeRate),
×
NEW
399
        )
×
NEW
400

×
NEW
401
        // testFeeRate sepcifies a starting fee rate in sat/vbyte.
×
NEW
402
        const testFeeRate = uint64(100)
×
NEW
403

×
NEW
404
        // Third bump request - we will specify the fee rate and expect a fee
×
NEW
405
        // func to change the starting fee rate of the fee function,
×
NEW
406
        // - starting fee rate: 100 sat/vbyte.
×
NEW
407
        // - deadline: 1008 (default deadline).
×
NEW
408
        // - budget: 50% of the input value.
×
NEW
409
        bumpFeeReq = &walletrpc.BumpFeeRequest{
×
NEW
410
                Outpoint: op,
×
NEW
411
                // We use a force param to create the sweeping tx immediately.
×
NEW
412
                Immediate:   true,
×
NEW
413
                SatPerVbyte: testFeeRate,
×
NEW
414
        }
×
NEW
415
        alice.RPC.BumpFee(bumpFeeReq)
×
NEW
416

×
NEW
417
        // Alice's old sweeping tx should be replaced.
×
NEW
418
        ht.AssertTxNotInMempool(sweepTx2.TxHash())
×
NEW
419

×
NEW
420
        // Assert the pending sweep is created with the expected values:
×
NEW
421
        // - broadcast attempts: 3.
×
NEW
422
        // - starting fee rate: 100 sat/vbyte.
×
NEW
423
        // - deadline: 1008 (default deadline).
×
NEW
424
        // - budget: 50% of the input value.
×
NEW
425
        sweepTx3 := assertPendingSweepResp(
×
NEW
426
                3, uint64(value/2), deadline, testFeeRate,
×
NEW
427
        )
×
NEW
428

×
NEW
429
        // We expect the requested starting fee rate to be the current fee
×
NEW
430
        // rate.
×
NEW
431
        assertFeeRateEqual(testFeeRate)
×
NEW
432

×
NEW
433
        // testBudget specifies a budget in sats.
×
NEW
434
        testBudget := uint64(float64(value) * 0.1)
×
NEW
435

×
NEW
436
        // Fourth bump request - we will specify the budget and expect a fee
×
NEW
437
        // func that has,
×
NEW
438
        // - starting fee rate: 100 sat/vbyte, stays unchanged.
×
NEW
439
        // - deadline: 1008 (default deadline).
×
NEW
440
        // - budget: 10% of the input value.
×
NEW
441
        bumpFeeReq = &walletrpc.BumpFeeRequest{
×
NEW
442
                Outpoint: op,
×
NEW
443
                // We use a force param to create the sweeping tx immediately.
×
NEW
444
                Immediate: true,
×
NEW
445
                Budget:    testBudget,
×
NEW
446
        }
×
NEW
447
        alice.RPC.BumpFee(bumpFeeReq)
×
NEW
448

×
NEW
449
        // Alice's old sweeping tx should be replaced.
×
NEW
450
        ht.AssertTxNotInMempool(sweepTx3.TxHash())
×
NEW
451

×
NEW
452
        // Assert the pending sweep is created with the expected values:
×
NEW
453
        // - broadcast attempts: 4.
×
NEW
454
        // - starting fee rate: 100 sat/vbyte, stays unchanged.
×
NEW
455
        // - deadline: 1008 (default deadline).
×
NEW
456
        // - budget: 10% of the input value.
×
NEW
457
        sweepTx4 := assertPendingSweepResp(4, testBudget, deadline, testFeeRate)
×
NEW
458

×
NEW
459
        // We expect the current fee rate to be increased because we ensure the
×
NEW
460
        // initial broadcast always succeeds.
×
NEW
461
        assertFeeRateGreater(testFeeRate)
×
NEW
462

×
NEW
463
        // Create a test deadline delta to use in the next test.
×
NEW
464
        testDeadlineDelta := uint32(100)
×
NEW
465
        deadlineHeight := uint32(currentHeight) + testDeadlineDelta
×
NEW
466

×
NEW
467
        // Fifth bump request - we will specify the deadline and expect a fee
×
NEW
468
        // func that has,
×
NEW
469
        // - starting fee rate: 100 sat/vbyte, stays unchanged.
×
NEW
470
        // - deadline: 100.
×
NEW
471
        // - budget: 10% of the input value, stays unchanged.
×
NEW
472
        bumpFeeReq = &walletrpc.BumpFeeRequest{
×
NEW
473
                Outpoint: op,
×
NEW
474
                // We use a force param to create the sweeping tx immediately.
×
NEW
475
                Immediate:     true,
×
NEW
476
                DeadlineDelta: testDeadlineDelta,
×
NEW
477
                Budget:        testBudget,
×
NEW
478
        }
×
NEW
479
        alice.RPC.BumpFee(bumpFeeReq)
×
NEW
480

×
NEW
481
        // Alice's old sweeping tx should be replaced.
×
NEW
482
        ht.AssertTxNotInMempool(sweepTx4.TxHash())
×
NEW
483

×
NEW
484
        // Assert the pending sweep is created with the expected values:
×
NEW
485
        // - broadcast attempts: 5.
×
NEW
486
        // - starting fee rate: 100 sat/vbyte, stays unchanged.
×
NEW
487
        // - deadline: 100.
×
NEW
488
        // - budget: 10% of the input value, stays unchanged.
×
NEW
489
        sweepTx5 := assertPendingSweepResp(
×
NEW
490
                5, testBudget, deadlineHeight, testFeeRate,
×
NEW
491
        )
×
NEW
492

×
NEW
493
        // We expect the current fee rate to be increased because we ensure the
×
NEW
494
        // initial broadcast always succeeds.
×
NEW
495
        assertFeeRateGreater(testFeeRate)
×
NEW
496

×
NEW
497
        // Sixth bump request - we test the behavior of `Immediate` - every
×
NEW
498
        // time it's called, the fee function will keep increasing the fee rate
×
NEW
499
        // until the broadcast can succeed. The fee func that has,
×
NEW
500
        // - starting fee rate: 100 sat/vbyte, stays unchanged.
×
NEW
501
        // - deadline: 100, stays unchanged.
×
NEW
502
        // - budget: 10% of the input value, stays unchanged.
×
NEW
503
        bumpFeeReq = &walletrpc.BumpFeeRequest{
×
NEW
504
                Outpoint: op,
×
NEW
505
                // We use a force param to create the sweeping tx immediately.
×
NEW
506
                Immediate: true,
×
NEW
507
        }
×
NEW
508
        alice.RPC.BumpFee(bumpFeeReq)
×
NEW
509

×
NEW
510
        // Alice's old sweeping tx should be replaced.
×
NEW
511
        ht.AssertTxNotInMempool(sweepTx5.TxHash())
×
NEW
512

×
NEW
513
        // Assert the pending sweep is created with the expected values:
×
NEW
514
        // - broadcast attempts: 6.
×
NEW
515
        // - starting fee rate: 100 sat/vbyte, stays unchanged.
×
NEW
516
        // - deadline: 100, stays unchanged.
×
NEW
517
        // - budget: 10% of the input value, stays unchanged.
×
NEW
518
        sweepTx6 := assertPendingSweepResp(
×
NEW
519
                6, testBudget, deadlineHeight, testFeeRate,
×
NEW
520
        )
×
NEW
521

×
NEW
522
        // We expect the current fee rate to be increased because we ensure the
×
NEW
523
        // initial broadcast always succeeds.
×
NEW
524
        assertFeeRateGreater(testFeeRate)
×
NEW
525

×
NEW
526
        smallBudget := uint64(1000)
×
NEW
527

×
NEW
528
        // Finally, we test the behavior of lowering the fee rate. The fee func
×
NEW
529
        // that has,
×
NEW
530
        // - starting fee rate: 1 sat/vbyte.
×
NEW
531
        // - deadline: 1.
×
NEW
532
        // - budget: 1000 sats.
×
NEW
533
        bumpFeeReq = &walletrpc.BumpFeeRequest{
×
NEW
534
                Outpoint: op,
×
NEW
535
                // We use a force param to create the sweeping tx immediately.
×
NEW
536
                Immediate:   true,
×
NEW
537
                SatPerVbyte: startFeeRate,
×
NEW
538
                // The budget and the deadline delta must be set together.
×
NEW
539
                Budget:        smallBudget,
×
NEW
540
                DeadlineDelta: 1,
×
NEW
541
        }
×
NEW
542
        alice.RPC.BumpFee(bumpFeeReq)
×
NEW
543

×
NEW
544
        // Calculate the ending fee rate, which is used in the above fee bump
×
NEW
545
        // when fee function's max posistion is reached.
×
NEW
546
        txWeight := ht.CalculateTxWeight(sweepTx6)
×
NEW
547
        endingFeeRate := chainfee.NewSatPerKWeight(
×
NEW
548
                btcutil.Amount(smallBudget), txWeight,
×
NEW
549
        )
×
NEW
550

×
NEW
551
        // Since the fee function has been maxed out, the starting fee rate for
×
NEW
552
        // the next sweep attempt should be the ending fee rate.
×
NEW
553
        //
×
NEW
554
        // TODO(yy): The weight estimator used in the sweeper gives a different
×
NEW
555
        // result than the weight calculated here, which is the result from
×
NEW
556
        // `blockchain.GetTransactionWeight`. For this particular tx:
×
NEW
557
        // - result from the `weightEstimator`: 445 wu
×
NEW
558
        // - result from `GetTransactionWeight`: 444 wu
×
NEW
559
        //
×
NEW
560
        // This means the fee rates are different,
×
NEW
561
        // - `weightEstimator`: 2247 sat/kw, or 8 sat/vb (8.988 round down)
×
NEW
562
        // - here we have 2252 sat/kw, or 9 sat/vb (9.008 round down)
×
NEW
563
        //
×
NEW
564
        // We should investigate and check whether if it's possible to make the
×
NEW
565
        // `weightEstimator` more accurate.
×
NEW
566
        expectedStartFeeRate := uint64(endingFeeRate.FeePerVByte()) - 1
×
NEW
567

×
NEW
568
        // Assert the pending sweep is created with the expected values:
×
NEW
569
        // - broadcast attempts: 7.
×
NEW
570
        // - starting fee rate: 8 sat/vbyte.
×
NEW
571
        // - deadline: 1.
×
NEW
572
        // - budget: 1000 sats.
×
NEW
573
        sweepTx7 := assertPendingSweepResp(
×
NEW
574
                7, smallBudget, uint32(currentHeight+1), expectedStartFeeRate,
×
NEW
575
        )
×
NEW
576

×
NEW
577
        // Since this budget is too small to cover the RBF, we expect the
×
NEW
578
        // sweeping attempt to fail.
×
NEW
579
        require.Equal(ht, sweepTx6.TxHash(), sweepTx7.TxHash(), "tx6 should "+
×
NEW
580
                "not be replaced: tx6=%v, tx7=%v", sweepTx6.TxHash(),
×
NEW
581
                sweepTx7.TxHash())
×
NEW
582

×
NEW
583
        // We expect the current fee rate to be increased because we ensure the
×
NEW
584
        // initial broadcast always succeeds.
×
NEW
585
        assertFeeRateGreater(testFeeRate)
×
NEW
586

×
NEW
587
        // Clean up the mempool.
×
NEW
588
        ht.MineBlocksAndAssertNumTxes(1, 2)
×
589
}
590

591
// testBumpFeeExternalInput assert that when the bump fee RPC is called with an
592
// outpoint unknown to the node's wallet, an error is returned.
NEW
593
func testBumpFeeExternalInput(ht *lntest.HarnessTest) {
×
NEW
594
        alice := ht.NewNode("Alice", nil)
×
NEW
595
        bob := ht.NewNode("Bob", nil)
×
NEW
596

×
NEW
597
        // We'll start the test by sending Alice some coins, which she'll use
×
NEW
598
        // to send to Bob.
×
NEW
599
        ht.FundCoins(btcutil.SatoshiPerBitcoin, alice)
×
NEW
600

×
NEW
601
        // Alice sends 0.5 BTC to Bob. This tx should have two outputs - one
×
NEW
602
        // that belongs to Bob, the other is Alice's change output.
×
NEW
603
        tx := ht.SendCoins(alice, bob, btcutil.SatoshiPerBitcoin/2)
×
NEW
604
        txid := tx.TxHash()
×
NEW
605

×
NEW
606
        // Find the wrong index to perform the fee bump. We assume the first
×
NEW
607
        // output belongs to Bob, and switch to the second if the second output
×
NEW
608
        // has a larger output value. Given we've funded Alice 1 btc, she then
×
NEW
609
        // sends 0.5 btc to Bob, her change output will be below 0.5 btc after
×
NEW
610
        // paying the mining fees.
×
NEW
611
        wrongIndex := 0
×
NEW
612
        if tx.TxOut[0].Value < tx.TxOut[1].Value {
×
NEW
613
                wrongIndex = 1
×
NEW
614
        }
×
615

616
        // Alice now tries to bump the wrong output on this tx.
NEW
617
        op := &lnrpc.OutPoint{
×
NEW
618
                TxidBytes:   txid[:],
×
NEW
619
                OutputIndex: uint32(wrongIndex),
×
NEW
620
        }
×
NEW
621

×
NEW
622
        // Create a request with the wrong outpoint.
×
NEW
623
        bumpFeeReq := &walletrpc.BumpFeeRequest{
×
NEW
624
                Outpoint: op,
×
NEW
625
                // We use a force param to create the sweeping tx immediately.
×
NEW
626
                Immediate: true,
×
NEW
627
        }
×
NEW
628
        err := alice.RPC.BumpFeeAssertErr(bumpFeeReq)
×
NEW
629
        require.ErrorContains(ht, err, "does not belong to the wallet")
×
NEW
630

×
NEW
631
        // Clean up the mempool.
×
NEW
632
        ht.MineBlocksAndAssertNumTxes(1, 1)
×
633
}
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