mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-09-06 17:47:01 +02:00
Merge branch '0-19-1-rc1-branch-9854' into 0-19-1-rc1-branch
This commit is contained in:
@@ -27,6 +27,9 @@
|
|||||||
that would occur when an attempt was made to write a backup file for a channel
|
that would occur when an attempt was made to write a backup file for a channel
|
||||||
peer that has advertised an address that we do not yet know how to parse.
|
peer that has advertised an address that we do not yet know how to parse.
|
||||||
|
|
||||||
|
- Fixed [a case](https://github.com/lightningnetwork/lnd/pull/9854) where the
|
||||||
|
`BumpFee` doesn't give an error response.
|
||||||
|
|
||||||
# New Features
|
# New Features
|
||||||
|
|
||||||
## Functional Enhancements
|
## Functional Enhancements
|
||||||
@@ -82,4 +85,5 @@
|
|||||||
# Contributors (Alphabetical Order)
|
# Contributors (Alphabetical Order)
|
||||||
|
|
||||||
* Elle Mouton
|
* Elle Mouton
|
||||||
|
* Yong Yu
|
||||||
* Ziggie
|
* Ziggie
|
||||||
|
59
docs/release-notes/release-notes-template.md
Normal file
59
docs/release-notes/release-notes-template.md
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
# Release Notes
|
||||||
|
- [Bug Fixes](#bug-fixes)
|
||||||
|
- [New Features](#new-features)
|
||||||
|
- [Functional Enhancements](#functional-enhancements)
|
||||||
|
- [RPC Additions](#rpc-additions)
|
||||||
|
- [lncli Additions](#lncli-additions)
|
||||||
|
- [Improvements](#improvements)
|
||||||
|
- [Functional Updates](#functional-updates)
|
||||||
|
- [RPC Updates](#rpc-updates)
|
||||||
|
- [lncli Updates](#lncli-updates)
|
||||||
|
- [Breaking Changes](#breaking-changes)
|
||||||
|
- [Performance Improvements](#performance-improvements)
|
||||||
|
- [Deprecations](#deprecations)
|
||||||
|
- [Technical and Architectural Updates](#technical-and-architectural-updates)
|
||||||
|
- [BOLT Spec Updates](#bolt-spec-updates)
|
||||||
|
- [Testing](#testing)
|
||||||
|
- [Database](#database)
|
||||||
|
- [Code Health](#code-health)
|
||||||
|
- [Tooling and Documentation](#tooling-and-documentation)
|
||||||
|
|
||||||
|
# Bug Fixes
|
||||||
|
|
||||||
|
# New Features
|
||||||
|
|
||||||
|
## Functional Enhancements
|
||||||
|
|
||||||
|
## RPC Additions
|
||||||
|
|
||||||
|
## lncli Additions
|
||||||
|
|
||||||
|
# Improvements
|
||||||
|
|
||||||
|
## Functional Updates
|
||||||
|
|
||||||
|
## RPC Updates
|
||||||
|
|
||||||
|
## lncli Updates
|
||||||
|
|
||||||
|
## Code Health
|
||||||
|
|
||||||
|
## Breaking Changes
|
||||||
|
|
||||||
|
## Performance Improvements
|
||||||
|
|
||||||
|
## Deprecations
|
||||||
|
|
||||||
|
# Technical and Architectural Updates
|
||||||
|
|
||||||
|
## BOLT Spec Updates
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
## Database
|
||||||
|
|
||||||
|
## Code Health
|
||||||
|
|
||||||
|
## Tooling and Documentation
|
||||||
|
|
||||||
|
# Contributors (Alphabetical Order)
|
2
go.mod
2
go.mod
@@ -11,7 +11,7 @@ require (
|
|||||||
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0
|
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0
|
||||||
github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c
|
github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c
|
||||||
github.com/btcsuite/btclog/v2 v2.0.1-0.20250110154127-3ae4bf1cb318
|
github.com/btcsuite/btclog/v2 v2.0.1-0.20250110154127-3ae4bf1cb318
|
||||||
github.com/btcsuite/btcwallet v0.16.13
|
github.com/btcsuite/btcwallet v0.16.14
|
||||||
github.com/btcsuite/btcwallet/wallet/txauthor v1.3.5
|
github.com/btcsuite/btcwallet/wallet/txauthor v1.3.5
|
||||||
github.com/btcsuite/btcwallet/wallet/txrules v1.2.2
|
github.com/btcsuite/btcwallet/wallet/txrules v1.2.2
|
||||||
github.com/btcsuite/btcwallet/walletdb v1.5.1
|
github.com/btcsuite/btcwallet/walletdb v1.5.1
|
||||||
|
4
go.sum
4
go.sum
@@ -62,8 +62,8 @@ github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c/go.mod h1:w7xnGOhw
|
|||||||
github.com/btcsuite/btclog/v2 v2.0.1-0.20250110154127-3ae4bf1cb318 h1:oCjIcinPt7XQ644MP/22JcjYEC84qRc3bRBH0d7Hhd4=
|
github.com/btcsuite/btclog/v2 v2.0.1-0.20250110154127-3ae4bf1cb318 h1:oCjIcinPt7XQ644MP/22JcjYEC84qRc3bRBH0d7Hhd4=
|
||||||
github.com/btcsuite/btclog/v2 v2.0.1-0.20250110154127-3ae4bf1cb318/go.mod h1:XItGUfVOxotJL8kkuk2Hj3EVow5KCugXl3wWfQ6K0AE=
|
github.com/btcsuite/btclog/v2 v2.0.1-0.20250110154127-3ae4bf1cb318/go.mod h1:XItGUfVOxotJL8kkuk2Hj3EVow5KCugXl3wWfQ6K0AE=
|
||||||
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
|
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
|
||||||
github.com/btcsuite/btcwallet v0.16.13 h1:JGu+wrihQ0I00ODb3w92JtBPbrHxZhbcvU01O+e+lKw=
|
github.com/btcsuite/btcwallet v0.16.14 h1:CofysgmI1ednkLsXontAdBoXJkbiim7unXnFKhLLjnE=
|
||||||
github.com/btcsuite/btcwallet v0.16.13/go.mod h1:H6dfoZcWPonM2wbVsR2ZBY0PKNZKdQyLAmnX8vL9JFA=
|
github.com/btcsuite/btcwallet v0.16.14/go.mod h1:H6dfoZcWPonM2wbVsR2ZBY0PKNZKdQyLAmnX8vL9JFA=
|
||||||
github.com/btcsuite/btcwallet/wallet/txauthor v1.3.5 h1:Rr0njWI3r341nhSPesKQ2JF+ugDSzdPoeckS75SeDZk=
|
github.com/btcsuite/btcwallet/wallet/txauthor v1.3.5 h1:Rr0njWI3r341nhSPesKQ2JF+ugDSzdPoeckS75SeDZk=
|
||||||
github.com/btcsuite/btcwallet/wallet/txauthor v1.3.5/go.mod h1:+tXJ3Ym0nlQc/iHSwW1qzjmPs3ev+UVWMbGgfV1OZqU=
|
github.com/btcsuite/btcwallet/wallet/txauthor v1.3.5/go.mod h1:+tXJ3Ym0nlQc/iHSwW1qzjmPs3ev+UVWMbGgfV1OZqU=
|
||||||
github.com/btcsuite/btcwallet/wallet/txrules v1.2.2 h1:YEO+Lx1ZJJAtdRrjuhXjWrYsmAk26wLTlNzxt2q0lhk=
|
github.com/btcsuite/btcwallet/wallet/txrules v1.2.2 h1:YEO+Lx1ZJJAtdRrjuhXjWrYsmAk26wLTlNzxt2q0lhk=
|
||||||
|
@@ -463,6 +463,10 @@ var allTestCases = []*lntest.TestCase{
|
|||||||
Name: "bumpfee",
|
Name: "bumpfee",
|
||||||
TestFunc: testBumpFee,
|
TestFunc: testBumpFee,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "bumpfee external input",
|
||||||
|
TestFunc: testBumpFeeExternalInput,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "bumpforceclosefee",
|
Name: "bumpforceclosefee",
|
||||||
TestFunc: testBumpForceCloseFee,
|
TestFunc: testBumpForceCloseFee,
|
||||||
|
633
itest/lnd_bump_fee.go
Normal file
633
itest/lnd_bump_fee.go
Normal file
@@ -0,0 +1,633 @@
|
|||||||
|
package itest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/btcutil"
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/lightningnetwork/lnd/lnrpc"
|
||||||
|
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
|
||||||
|
"github.com/lightningnetwork/lnd/lntest"
|
||||||
|
"github.com/lightningnetwork/lnd/lntest/node"
|
||||||
|
"github.com/lightningnetwork/lnd/lntest/wait"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||||
|
"github.com/lightningnetwork/lnd/sweep"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
// testBumpFeeLowBudget checks that when the requested ideal budget cannot be
|
||||||
|
// met, the sweeper still sweeps the input with the actual budget.
|
||||||
|
func testBumpFeeLowBudget(ht *lntest.HarnessTest) {
|
||||||
|
// Create a new node with a large `maxfeerate` so it's easier to run the
|
||||||
|
// test.
|
||||||
|
alice := ht.NewNode("Alice", []string{
|
||||||
|
"--sweeper.maxfeerate=10000",
|
||||||
|
})
|
||||||
|
|
||||||
|
// Fund Alice 2 UTXOs, each has 100k sats. One of the UTXOs will be used
|
||||||
|
// to create a tx which she sends some coins to herself. The other will
|
||||||
|
// be used as the budget when CPFPing the above tx.
|
||||||
|
coin := btcutil.Amount(100_000)
|
||||||
|
ht.FundCoins(coin, alice)
|
||||||
|
ht.FundCoins(coin, alice)
|
||||||
|
|
||||||
|
// Alice sends 50k sats to herself.
|
||||||
|
tx := ht.SendCoins(alice, alice, coin/2)
|
||||||
|
txid := tx.TxHash()
|
||||||
|
|
||||||
|
// Get Alice's wallet balance to calculate the fees used in the above
|
||||||
|
// tx.
|
||||||
|
resp := alice.RPC.WalletBalance()
|
||||||
|
|
||||||
|
// balance is the expected final balance. Alice's initial balance is
|
||||||
|
// 200k sats, with 100k sats as the budget for the sweeping tx, which
|
||||||
|
// means her final balance should be 100k sats minus the mining fees
|
||||||
|
// used in the above `SendCoins`.
|
||||||
|
balance := btcutil.Amount(
|
||||||
|
resp.UnconfirmedBalance + resp.ConfirmedBalance,
|
||||||
|
)
|
||||||
|
fee := coin*2 - balance
|
||||||
|
ht.Logf("Alice's expected final balance=%v, fee=%v", balance, fee)
|
||||||
|
|
||||||
|
// Alice now tries to bump the first output on this tx.
|
||||||
|
op := &lnrpc.OutPoint{
|
||||||
|
TxidBytes: txid[:],
|
||||||
|
OutputIndex: uint32(0),
|
||||||
|
}
|
||||||
|
value := btcutil.Amount(tx.TxOut[0].Value)
|
||||||
|
|
||||||
|
// assertPendingSweepResp is a helper closure that asserts the response
|
||||||
|
// from `PendingSweep` RPC is returned with expected values. It also
|
||||||
|
// returns the sweeping tx for further checks.
|
||||||
|
assertPendingSweepResp := func(budget uint64,
|
||||||
|
deadline uint32) *wire.MsgTx {
|
||||||
|
|
||||||
|
// Alice should still have one pending sweep.
|
||||||
|
pendingSweep := ht.AssertNumPendingSweeps(alice, 1)[0]
|
||||||
|
|
||||||
|
// Validate all fields returned from `PendingSweeps` are as
|
||||||
|
// expected.
|
||||||
|
require.Equal(ht, op.TxidBytes, pendingSweep.Outpoint.TxidBytes)
|
||||||
|
require.Equal(ht, op.OutputIndex,
|
||||||
|
pendingSweep.Outpoint.OutputIndex)
|
||||||
|
require.Equal(ht, walletrpc.WitnessType_TAPROOT_PUB_KEY_SPEND,
|
||||||
|
pendingSweep.WitnessType)
|
||||||
|
require.EqualValuesf(ht, value, pendingSweep.AmountSat,
|
||||||
|
"amount not matched: want=%d, got=%d", value,
|
||||||
|
pendingSweep.AmountSat)
|
||||||
|
require.True(ht, pendingSweep.Immediate)
|
||||||
|
|
||||||
|
require.EqualValuesf(ht, budget, pendingSweep.Budget,
|
||||||
|
"budget not matched: want=%d, got=%d", budget,
|
||||||
|
pendingSweep.Budget)
|
||||||
|
|
||||||
|
// Since the request doesn't specify a deadline, we expect the
|
||||||
|
// existing deadline to be used.
|
||||||
|
require.Equalf(ht, deadline, pendingSweep.DeadlineHeight,
|
||||||
|
"deadline height not matched: want=%d, got=%d",
|
||||||
|
deadline, pendingSweep.DeadlineHeight)
|
||||||
|
|
||||||
|
// We expect to see Alice's original tx and her CPFP tx in the
|
||||||
|
// mempool.
|
||||||
|
txns := ht.GetNumTxsFromMempool(2)
|
||||||
|
|
||||||
|
// Find the sweeping tx - assume it's the first item, if it has
|
||||||
|
// the same txid as the parent tx, use the second item.
|
||||||
|
sweepTx := txns[0]
|
||||||
|
if sweepTx.TxHash() == tx.TxHash() {
|
||||||
|
sweepTx = txns[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return sweepTx
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use a budget that Alice cannot cover using her wallet UTXOs.
|
||||||
|
budget := coin * 2
|
||||||
|
|
||||||
|
// Use a deadlineDelta of 3 such that the fee func is initialized as,
|
||||||
|
// - starting fee rate: 1 sat/vbyte
|
||||||
|
// - deadline: 3
|
||||||
|
// - budget: 200% of Alice's available funds.
|
||||||
|
deadlineDelta := 3
|
||||||
|
|
||||||
|
// First bump request - we expect it to succeed as Alice's current funds
|
||||||
|
// can cover the fees used here given the position of the fee func is at
|
||||||
|
// 0.
|
||||||
|
bumpFeeReq := &walletrpc.BumpFeeRequest{
|
||||||
|
Outpoint: op,
|
||||||
|
Budget: uint64(budget),
|
||||||
|
Immediate: true,
|
||||||
|
DeadlineDelta: uint32(deadlineDelta),
|
||||||
|
}
|
||||||
|
alice.RPC.BumpFee(bumpFeeReq)
|
||||||
|
|
||||||
|
// Calculate the deadline height.
|
||||||
|
deadline := ht.CurrentHeight() + uint32(deadlineDelta)
|
||||||
|
|
||||||
|
// Assert the pending sweep is created with the expected values:
|
||||||
|
// - deadline: 3+current height.
|
||||||
|
// - budget: 2x the wallet balance.
|
||||||
|
sweepTx1 := assertPendingSweepResp(uint64(budget), deadline)
|
||||||
|
|
||||||
|
// Mine a block to trigger Alice's sweeper to fee bump the tx.
|
||||||
|
//
|
||||||
|
// Second bump request - we expect it to succeed as Alice's current
|
||||||
|
// funds can cover the fees used here, which is 66.7% of her available
|
||||||
|
// funds given the position of the fee func is at 1.
|
||||||
|
ht.MineEmptyBlocks(1)
|
||||||
|
|
||||||
|
// Assert the old sweeping tx has been replaced.
|
||||||
|
ht.AssertTxNotInMempool(sweepTx1.TxHash())
|
||||||
|
|
||||||
|
// Assert a new sweeping tx is made.
|
||||||
|
sweepTx2 := assertPendingSweepResp(uint64(budget), deadline)
|
||||||
|
|
||||||
|
// Mine a block to trigger Alice's sweeper to fee bump the tx.
|
||||||
|
//
|
||||||
|
// Third bump request - we expect it to fail as Alice's current funds
|
||||||
|
// cannot cover the fees now, which is 133.3% of her available funds
|
||||||
|
// given the position of the fee func is at 2.
|
||||||
|
ht.MineEmptyBlocks(1)
|
||||||
|
|
||||||
|
// Assert the above sweeping tx is still in the mempool.
|
||||||
|
ht.AssertTxInMempool(sweepTx2.TxHash())
|
||||||
|
|
||||||
|
// Fund Alice 200k sats, which will be used to cover the budget.
|
||||||
|
//
|
||||||
|
// TODO(yy): We are funding Alice more than enough - at this stage Alice
|
||||||
|
// has a confirmed UTXO of `coin` amount in her wallet, so ideally we
|
||||||
|
// should only fund another UTXO of `coin` amount. However, since the
|
||||||
|
// confirmed wallet UTXO has already been used in sweepTx2, there's no
|
||||||
|
// easy way to tell her wallet to reuse that UTXO in the upcoming
|
||||||
|
// sweeping tx.
|
||||||
|
// To properly fix it, we should provide more granular UTXO management
|
||||||
|
// here by leveraing `LeaseOutput` - whenever we use a wallet UTXO, we
|
||||||
|
// should lock it first. And when the sweeping attempt fails, we should
|
||||||
|
// release it so the UTXO can be used again in another batch.
|
||||||
|
walletTx := ht.FundCoinsUnconfirmed(coin*2, alice)
|
||||||
|
|
||||||
|
// Mine a block to confirm the above funding coin.
|
||||||
|
//
|
||||||
|
// Fourth bump request - we expect it to succeed as Alice's current
|
||||||
|
// funds can cover the full budget.
|
||||||
|
ht.MineBlockWithTx(walletTx)
|
||||||
|
|
||||||
|
flakeRaceInBitcoinClientNotifications(ht)
|
||||||
|
|
||||||
|
// Assert Alice's previous sweeping tx has been replaced.
|
||||||
|
ht.AssertTxNotInMempool(sweepTx2.TxHash())
|
||||||
|
|
||||||
|
// Assert the pending sweep is created with the expected values:
|
||||||
|
// - deadline: 3+current height.
|
||||||
|
// - budget: 2x the wallet balance.
|
||||||
|
sweepTx3 := assertPendingSweepResp(uint64(budget), deadline)
|
||||||
|
require.NotEqual(ht, sweepTx2.TxHash(), sweepTx3.TxHash())
|
||||||
|
|
||||||
|
// Mine the sweeping tx.
|
||||||
|
ht.MineBlocksAndAssertNumTxes(1, 2)
|
||||||
|
|
||||||
|
// Assert Alice's wallet balance. a
|
||||||
|
ht.WaitForBalanceConfirmed(alice, balance)
|
||||||
|
}
|
||||||
|
|
||||||
|
// testBumpFee checks that when a new input is requested, it's first bumped via
|
||||||
|
// CPFP, then RBF. Along the way, we check the `BumpFee` can properly update
|
||||||
|
// the fee function used by supplying new params.
|
||||||
|
func testBumpFee(ht *lntest.HarnessTest) {
|
||||||
|
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||||
|
|
||||||
|
runBumpFee(ht, alice)
|
||||||
|
}
|
||||||
|
|
||||||
|
// runBumpFee checks the `BumpFee` RPC can properly bump the fee of a given
|
||||||
|
// input.
|
||||||
|
func runBumpFee(ht *lntest.HarnessTest, alice *node.HarnessNode) {
|
||||||
|
// Skip this test for neutrino, as it's not aware of mempool
|
||||||
|
// transactions.
|
||||||
|
if ht.IsNeutrinoBackend() {
|
||||||
|
ht.Skipf("skipping BumpFee test for neutrino backend")
|
||||||
|
}
|
||||||
|
|
||||||
|
// startFeeRate is the min fee rate in sats/vbyte. This value should be
|
||||||
|
// used as the starting fee rate when the default no deadline is used.
|
||||||
|
startFeeRate := uint64(1)
|
||||||
|
|
||||||
|
// We'll start the test by sending Alice some coins, which she'll use
|
||||||
|
// to send to herself.
|
||||||
|
ht.FundCoins(btcutil.SatoshiPerBitcoin, alice)
|
||||||
|
|
||||||
|
// Alice sends a coin to herself.
|
||||||
|
tx := ht.SendCoins(alice, alice, btcutil.SatoshiPerBitcoin)
|
||||||
|
txid := tx.TxHash()
|
||||||
|
|
||||||
|
// Alice now tries to bump the first output on this tx.
|
||||||
|
op := &lnrpc.OutPoint{
|
||||||
|
TxidBytes: txid[:],
|
||||||
|
OutputIndex: uint32(0),
|
||||||
|
}
|
||||||
|
value := btcutil.Amount(tx.TxOut[0].Value)
|
||||||
|
|
||||||
|
// assertPendingSweepResp is a helper closure that asserts the response
|
||||||
|
// from `PendingSweep` RPC is returned with expected values. It also
|
||||||
|
// returns the sweeping tx for further checks.
|
||||||
|
assertPendingSweepResp := func(broadcastAttempts uint32, budget uint64,
|
||||||
|
deadline uint32, startingFeeRate uint64) *wire.MsgTx {
|
||||||
|
|
||||||
|
err := wait.NoError(func() error {
|
||||||
|
// Alice should still have one pending sweep.
|
||||||
|
ps := ht.AssertNumPendingSweeps(alice, 1)[0]
|
||||||
|
|
||||||
|
// Validate all fields returned from `PendingSweeps` are
|
||||||
|
// as expected.
|
||||||
|
//
|
||||||
|
// These fields should stay the same during the test so
|
||||||
|
// we assert the values without wait.
|
||||||
|
require.Equal(ht, op.TxidBytes, ps.Outpoint.TxidBytes)
|
||||||
|
require.Equal(ht, op.OutputIndex,
|
||||||
|
ps.Outpoint.OutputIndex)
|
||||||
|
require.Equal(ht,
|
||||||
|
walletrpc.WitnessType_TAPROOT_PUB_KEY_SPEND,
|
||||||
|
ps.WitnessType)
|
||||||
|
require.EqualValuesf(ht, value, ps.AmountSat,
|
||||||
|
"amount not matched: want=%d, got=%d", value,
|
||||||
|
ps.AmountSat)
|
||||||
|
|
||||||
|
// The following fields can change during the test so we
|
||||||
|
// return an error if they don't match, which will be
|
||||||
|
// checked again in this wait call.
|
||||||
|
if !ps.Immediate {
|
||||||
|
return fmt.Errorf("immediate should be true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if broadcastAttempts != ps.BroadcastAttempts {
|
||||||
|
return fmt.Errorf("broadcastAttempts not "+
|
||||||
|
"matched: want=%d, got=%d",
|
||||||
|
broadcastAttempts, ps.BroadcastAttempts)
|
||||||
|
}
|
||||||
|
if budget != ps.Budget {
|
||||||
|
return fmt.Errorf("budget not matched: "+
|
||||||
|
"want=%d, got=%d", budget, ps.Budget)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since the request doesn't specify a deadline, we
|
||||||
|
// expect the existing deadline to be used.
|
||||||
|
if deadline != ps.DeadlineHeight {
|
||||||
|
return fmt.Errorf("deadline height not "+
|
||||||
|
"matched: want=%d, got=%d", deadline,
|
||||||
|
ps.DeadlineHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since the request specifies a starting fee rate, we
|
||||||
|
// expect that to be used as the starting fee rate.
|
||||||
|
if startingFeeRate != ps.RequestedSatPerVbyte {
|
||||||
|
return fmt.Errorf("requested starting fee "+
|
||||||
|
"rate not matched: want=%d, got=%d",
|
||||||
|
startingFeeRate,
|
||||||
|
ps.RequestedSatPerVbyte)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}, wait.DefaultTimeout)
|
||||||
|
require.NoError(ht, err, "timeout checking pending sweep")
|
||||||
|
|
||||||
|
// We expect to see Alice's original tx and her CPFP tx in the
|
||||||
|
// mempool.
|
||||||
|
txns := ht.GetNumTxsFromMempool(2)
|
||||||
|
|
||||||
|
// Find the sweeping tx - assume it's the first item, if it has
|
||||||
|
// the same txid as the parent tx, use the second item.
|
||||||
|
sweepTx := txns[0]
|
||||||
|
if sweepTx.TxHash() == tx.TxHash() {
|
||||||
|
sweepTx = txns[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return sweepTx
|
||||||
|
}
|
||||||
|
|
||||||
|
// assertFeeRateEqual is a helper closure that asserts the fee rate of
|
||||||
|
// the pending sweep tx is equal to the expected fee rate.
|
||||||
|
assertFeeRateEqual := func(expected uint64) {
|
||||||
|
err := wait.NoError(func() error {
|
||||||
|
// Alice should still have one pending sweep.
|
||||||
|
pendingSweep := ht.AssertNumPendingSweeps(alice, 1)[0]
|
||||||
|
|
||||||
|
if pendingSweep.SatPerVbyte == expected {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("expected current fee rate %d, got "+
|
||||||
|
"%d", expected, pendingSweep.SatPerVbyte)
|
||||||
|
}, wait.DefaultTimeout)
|
||||||
|
require.NoError(ht, err, "fee rate not updated")
|
||||||
|
}
|
||||||
|
|
||||||
|
// assertFeeRateGreater is a helper closure that asserts the fee rate
|
||||||
|
// of the pending sweep tx is greater than the expected fee rate.
|
||||||
|
assertFeeRateGreater := func(expected uint64) {
|
||||||
|
err := wait.NoError(func() error {
|
||||||
|
// Alice should still have one pending sweep.
|
||||||
|
pendingSweep := ht.AssertNumPendingSweeps(alice, 1)[0]
|
||||||
|
|
||||||
|
if pendingSweep.SatPerVbyte > expected {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("expected current fee rate greater "+
|
||||||
|
"than %d, got %d", expected,
|
||||||
|
pendingSweep.SatPerVbyte)
|
||||||
|
}, wait.DefaultTimeout)
|
||||||
|
require.NoError(ht, err, "fee rate not updated")
|
||||||
|
}
|
||||||
|
|
||||||
|
// First bump request - we'll specify nothing except `Immediate` to let
|
||||||
|
// the sweeper handle the fee, and we expect a fee func that has,
|
||||||
|
// - starting fee rate: 1 sat/vbyte (min relay fee rate).
|
||||||
|
// - deadline: 1008 (default deadline).
|
||||||
|
// - budget: 50% of the input value.
|
||||||
|
bumpFeeReq := &walletrpc.BumpFeeRequest{
|
||||||
|
Outpoint: op,
|
||||||
|
// We use a force param to create the sweeping tx immediately.
|
||||||
|
Immediate: true,
|
||||||
|
}
|
||||||
|
alice.RPC.BumpFee(bumpFeeReq)
|
||||||
|
|
||||||
|
// Since the request doesn't specify a deadline, we expect the default
|
||||||
|
// deadline to be used.
|
||||||
|
currentHeight := int32(ht.CurrentHeight())
|
||||||
|
deadline := uint32(currentHeight + sweep.DefaultDeadlineDelta)
|
||||||
|
|
||||||
|
// Assert the pending sweep is created with the expected values:
|
||||||
|
// - broadcast attempts: 1.
|
||||||
|
// - starting fee rate: 1 sat/vbyte (min relay fee rate).
|
||||||
|
// - deadline: 1008 (default deadline).
|
||||||
|
// - budget: 50% of the input value.
|
||||||
|
sweepTx1 := assertPendingSweepResp(1, uint64(value/2), deadline, 0)
|
||||||
|
|
||||||
|
// Since the request doesn't specify a starting fee rate, we expect the
|
||||||
|
// min relay fee rate is used as the current fee rate.
|
||||||
|
assertFeeRateEqual(startFeeRate)
|
||||||
|
|
||||||
|
// First we test the case where we specify the conf target to increase
|
||||||
|
// the starting fee rate of the fee function.
|
||||||
|
confTargetFeeRate := chainfee.SatPerVByte(50)
|
||||||
|
ht.SetFeeEstimateWithConf(confTargetFeeRate.FeePerKWeight(), 3)
|
||||||
|
|
||||||
|
// Second bump request - we will specify the conf target and expect a
|
||||||
|
// starting fee rate that is estimated using the provided estimator.
|
||||||
|
// - starting fee rate: 50 sat/vbyte (conf target 3).
|
||||||
|
// - deadline: 1008 (default deadline).
|
||||||
|
// - budget: 50% of the input value.
|
||||||
|
bumpFeeReq = &walletrpc.BumpFeeRequest{
|
||||||
|
Outpoint: op,
|
||||||
|
// We use a force param to create the sweeping tx immediately.
|
||||||
|
Immediate: true,
|
||||||
|
TargetConf: 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
alice.RPC.BumpFee(bumpFeeReq)
|
||||||
|
|
||||||
|
// Alice's old sweeping tx should be replaced.
|
||||||
|
ht.AssertTxNotInMempool(sweepTx1.TxHash())
|
||||||
|
|
||||||
|
// Assert the pending sweep is created with the expected values:
|
||||||
|
// - broadcast attempts: 2.
|
||||||
|
// - starting fee rate: 50 sat/vbyte (conf target 3).
|
||||||
|
// - deadline: 1008 (default deadline).
|
||||||
|
// - budget: 50% of the input value.
|
||||||
|
sweepTx2 := assertPendingSweepResp(
|
||||||
|
2, uint64(value/2), deadline, uint64(confTargetFeeRate),
|
||||||
|
)
|
||||||
|
|
||||||
|
// testFeeRate sepcifies a starting fee rate in sat/vbyte.
|
||||||
|
const testFeeRate = uint64(100)
|
||||||
|
|
||||||
|
// Third bump request - we will specify the fee rate and expect a fee
|
||||||
|
// func to change the starting fee rate of the fee function,
|
||||||
|
// - starting fee rate: 100 sat/vbyte.
|
||||||
|
// - deadline: 1008 (default deadline).
|
||||||
|
// - budget: 50% of the input value.
|
||||||
|
bumpFeeReq = &walletrpc.BumpFeeRequest{
|
||||||
|
Outpoint: op,
|
||||||
|
// We use a force param to create the sweeping tx immediately.
|
||||||
|
Immediate: true,
|
||||||
|
SatPerVbyte: testFeeRate,
|
||||||
|
}
|
||||||
|
alice.RPC.BumpFee(bumpFeeReq)
|
||||||
|
|
||||||
|
// Alice's old sweeping tx should be replaced.
|
||||||
|
ht.AssertTxNotInMempool(sweepTx2.TxHash())
|
||||||
|
|
||||||
|
// Assert the pending sweep is created with the expected values:
|
||||||
|
// - broadcast attempts: 3.
|
||||||
|
// - starting fee rate: 100 sat/vbyte.
|
||||||
|
// - deadline: 1008 (default deadline).
|
||||||
|
// - budget: 50% of the input value.
|
||||||
|
sweepTx3 := assertPendingSweepResp(
|
||||||
|
3, uint64(value/2), deadline, testFeeRate,
|
||||||
|
)
|
||||||
|
|
||||||
|
// We expect the requested starting fee rate to be the current fee
|
||||||
|
// rate.
|
||||||
|
assertFeeRateEqual(testFeeRate)
|
||||||
|
|
||||||
|
// testBudget specifies a budget in sats.
|
||||||
|
testBudget := uint64(float64(value) * 0.1)
|
||||||
|
|
||||||
|
// Fourth bump request - we will specify the budget and expect a fee
|
||||||
|
// func that has,
|
||||||
|
// - starting fee rate: 100 sat/vbyte, stays unchanged.
|
||||||
|
// - deadline: 1008 (default deadline).
|
||||||
|
// - budget: 10% of the input value.
|
||||||
|
bumpFeeReq = &walletrpc.BumpFeeRequest{
|
||||||
|
Outpoint: op,
|
||||||
|
// We use a force param to create the sweeping tx immediately.
|
||||||
|
Immediate: true,
|
||||||
|
Budget: testBudget,
|
||||||
|
}
|
||||||
|
alice.RPC.BumpFee(bumpFeeReq)
|
||||||
|
|
||||||
|
// Alice's old sweeping tx should be replaced.
|
||||||
|
ht.AssertTxNotInMempool(sweepTx3.TxHash())
|
||||||
|
|
||||||
|
// Assert the pending sweep is created with the expected values:
|
||||||
|
// - broadcast attempts: 4.
|
||||||
|
// - starting fee rate: 100 sat/vbyte, stays unchanged.
|
||||||
|
// - deadline: 1008 (default deadline).
|
||||||
|
// - budget: 10% of the input value.
|
||||||
|
sweepTx4 := assertPendingSweepResp(4, testBudget, deadline, testFeeRate)
|
||||||
|
|
||||||
|
// We expect the current fee rate to be increased because we ensure the
|
||||||
|
// initial broadcast always succeeds.
|
||||||
|
assertFeeRateGreater(testFeeRate)
|
||||||
|
|
||||||
|
// Create a test deadline delta to use in the next test.
|
||||||
|
testDeadlineDelta := uint32(100)
|
||||||
|
deadlineHeight := uint32(currentHeight) + testDeadlineDelta
|
||||||
|
|
||||||
|
// Fifth bump request - we will specify the deadline and expect a fee
|
||||||
|
// func that has,
|
||||||
|
// - starting fee rate: 100 sat/vbyte, stays unchanged.
|
||||||
|
// - deadline: 100.
|
||||||
|
// - budget: 10% of the input value, stays unchanged.
|
||||||
|
bumpFeeReq = &walletrpc.BumpFeeRequest{
|
||||||
|
Outpoint: op,
|
||||||
|
// We use a force param to create the sweeping tx immediately.
|
||||||
|
Immediate: true,
|
||||||
|
DeadlineDelta: testDeadlineDelta,
|
||||||
|
Budget: testBudget,
|
||||||
|
}
|
||||||
|
alice.RPC.BumpFee(bumpFeeReq)
|
||||||
|
|
||||||
|
// Alice's old sweeping tx should be replaced.
|
||||||
|
ht.AssertTxNotInMempool(sweepTx4.TxHash())
|
||||||
|
|
||||||
|
// Assert the pending sweep is created with the expected values:
|
||||||
|
// - broadcast attempts: 5.
|
||||||
|
// - starting fee rate: 100 sat/vbyte, stays unchanged.
|
||||||
|
// - deadline: 100.
|
||||||
|
// - budget: 10% of the input value, stays unchanged.
|
||||||
|
sweepTx5 := assertPendingSweepResp(
|
||||||
|
5, testBudget, deadlineHeight, testFeeRate,
|
||||||
|
)
|
||||||
|
|
||||||
|
// We expect the current fee rate to be increased because we ensure the
|
||||||
|
// initial broadcast always succeeds.
|
||||||
|
assertFeeRateGreater(testFeeRate)
|
||||||
|
|
||||||
|
// Sixth bump request - we test the behavior of `Immediate` - every
|
||||||
|
// time it's called, the fee function will keep increasing the fee rate
|
||||||
|
// until the broadcast can succeed. The fee func that has,
|
||||||
|
// - starting fee rate: 100 sat/vbyte, stays unchanged.
|
||||||
|
// - deadline: 100, stays unchanged.
|
||||||
|
// - budget: 10% of the input value, stays unchanged.
|
||||||
|
bumpFeeReq = &walletrpc.BumpFeeRequest{
|
||||||
|
Outpoint: op,
|
||||||
|
// We use a force param to create the sweeping tx immediately.
|
||||||
|
Immediate: true,
|
||||||
|
}
|
||||||
|
alice.RPC.BumpFee(bumpFeeReq)
|
||||||
|
|
||||||
|
// Alice's old sweeping tx should be replaced.
|
||||||
|
ht.AssertTxNotInMempool(sweepTx5.TxHash())
|
||||||
|
|
||||||
|
// Assert the pending sweep is created with the expected values:
|
||||||
|
// - broadcast attempts: 6.
|
||||||
|
// - starting fee rate: 100 sat/vbyte, stays unchanged.
|
||||||
|
// - deadline: 100, stays unchanged.
|
||||||
|
// - budget: 10% of the input value, stays unchanged.
|
||||||
|
sweepTx6 := assertPendingSweepResp(
|
||||||
|
6, testBudget, deadlineHeight, testFeeRate,
|
||||||
|
)
|
||||||
|
|
||||||
|
// We expect the current fee rate to be increased because we ensure the
|
||||||
|
// initial broadcast always succeeds.
|
||||||
|
assertFeeRateGreater(testFeeRate)
|
||||||
|
|
||||||
|
smallBudget := uint64(1000)
|
||||||
|
|
||||||
|
// Finally, we test the behavior of lowering the fee rate. The fee func
|
||||||
|
// that has,
|
||||||
|
// - starting fee rate: 1 sat/vbyte.
|
||||||
|
// - deadline: 1.
|
||||||
|
// - budget: 1000 sats.
|
||||||
|
bumpFeeReq = &walletrpc.BumpFeeRequest{
|
||||||
|
Outpoint: op,
|
||||||
|
// We use a force param to create the sweeping tx immediately.
|
||||||
|
Immediate: true,
|
||||||
|
SatPerVbyte: startFeeRate,
|
||||||
|
// The budget and the deadline delta must be set together.
|
||||||
|
Budget: smallBudget,
|
||||||
|
DeadlineDelta: 1,
|
||||||
|
}
|
||||||
|
alice.RPC.BumpFee(bumpFeeReq)
|
||||||
|
|
||||||
|
// Calculate the ending fee rate, which is used in the above fee bump
|
||||||
|
// when fee function's max posistion is reached.
|
||||||
|
txWeight := ht.CalculateTxWeight(sweepTx6)
|
||||||
|
endingFeeRate := chainfee.NewSatPerKWeight(
|
||||||
|
btcutil.Amount(smallBudget), txWeight,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Since the fee function has been maxed out, the starting fee rate for
|
||||||
|
// the next sweep attempt should be the ending fee rate.
|
||||||
|
//
|
||||||
|
// TODO(yy): The weight estimator used in the sweeper gives a different
|
||||||
|
// result than the weight calculated here, which is the result from
|
||||||
|
// `blockchain.GetTransactionWeight`. For this particular tx:
|
||||||
|
// - result from the `weightEstimator`: 445 wu
|
||||||
|
// - result from `GetTransactionWeight`: 444 wu
|
||||||
|
//
|
||||||
|
// This means the fee rates are different,
|
||||||
|
// - `weightEstimator`: 2247 sat/kw, or 8 sat/vb (8.988 round down)
|
||||||
|
// - here we have 2252 sat/kw, or 9 sat/vb (9.008 round down)
|
||||||
|
//
|
||||||
|
// We should investigate and check whether if it's possible to make the
|
||||||
|
// `weightEstimator` more accurate.
|
||||||
|
expectedStartFeeRate := uint64(endingFeeRate.FeePerVByte()) - 1
|
||||||
|
|
||||||
|
// Assert the pending sweep is created with the expected values:
|
||||||
|
// - broadcast attempts: 7.
|
||||||
|
// - starting fee rate: 8 sat/vbyte.
|
||||||
|
// - deadline: 1.
|
||||||
|
// - budget: 1000 sats.
|
||||||
|
sweepTx7 := assertPendingSweepResp(
|
||||||
|
7, smallBudget, uint32(currentHeight+1), expectedStartFeeRate,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Since this budget is too small to cover the RBF, we expect the
|
||||||
|
// sweeping attempt to fail.
|
||||||
|
require.Equal(ht, sweepTx6.TxHash(), sweepTx7.TxHash(), "tx6 should "+
|
||||||
|
"not be replaced: tx6=%v, tx7=%v", sweepTx6.TxHash(),
|
||||||
|
sweepTx7.TxHash())
|
||||||
|
|
||||||
|
// We expect the current fee rate to be increased because we ensure the
|
||||||
|
// initial broadcast always succeeds.
|
||||||
|
assertFeeRateGreater(testFeeRate)
|
||||||
|
|
||||||
|
// Clean up the mempool.
|
||||||
|
ht.MineBlocksAndAssertNumTxes(1, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// testBumpFeeExternalInput assert that when the bump fee RPC is called with an
|
||||||
|
// outpoint unknown to the node's wallet, an error is returned.
|
||||||
|
func testBumpFeeExternalInput(ht *lntest.HarnessTest) {
|
||||||
|
alice := ht.NewNode("Alice", nil)
|
||||||
|
bob := ht.NewNode("Bob", nil)
|
||||||
|
|
||||||
|
// We'll start the test by sending Alice some coins, which she'll use
|
||||||
|
// to send to Bob.
|
||||||
|
ht.FundCoins(btcutil.SatoshiPerBitcoin, alice)
|
||||||
|
|
||||||
|
// Alice sends 0.5 BTC to Bob. This tx should have two outputs - one
|
||||||
|
// that belongs to Bob, the other is Alice's change output.
|
||||||
|
tx := ht.SendCoins(alice, bob, btcutil.SatoshiPerBitcoin/2)
|
||||||
|
txid := tx.TxHash()
|
||||||
|
|
||||||
|
// Find the wrong index to perform the fee bump. We assume the first
|
||||||
|
// output belongs to Bob, and switch to the second if the second output
|
||||||
|
// has a larger output value. Given we've funded Alice 1 btc, she then
|
||||||
|
// sends 0.5 btc to Bob, her change output will be below 0.5 btc after
|
||||||
|
// paying the mining fees.
|
||||||
|
wrongIndex := 0
|
||||||
|
if tx.TxOut[0].Value < tx.TxOut[1].Value {
|
||||||
|
wrongIndex = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alice now tries to bump the wrong output on this tx.
|
||||||
|
op := &lnrpc.OutPoint{
|
||||||
|
TxidBytes: txid[:],
|
||||||
|
OutputIndex: uint32(wrongIndex),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a request with the wrong outpoint.
|
||||||
|
bumpFeeReq := &walletrpc.BumpFeeRequest{
|
||||||
|
Outpoint: op,
|
||||||
|
// We use a force param to create the sweeping tx immediately.
|
||||||
|
Immediate: true,
|
||||||
|
}
|
||||||
|
err := alice.RPC.BumpFeeAssertErr(bumpFeeReq)
|
||||||
|
require.ErrorContains(ht, err, "does not belong to the wallet")
|
||||||
|
|
||||||
|
// Clean up the mempool.
|
||||||
|
ht.MineBlocksAndAssertNumTxes(1, 1)
|
||||||
|
}
|
@@ -15,9 +15,7 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
||||||
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
|
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
|
||||||
"github.com/lightningnetwork/lnd/lntest"
|
"github.com/lightningnetwork/lnd/lntest"
|
||||||
"github.com/lightningnetwork/lnd/lntest/node"
|
|
||||||
"github.com/lightningnetwork/lnd/lntest/rpc"
|
"github.com/lightningnetwork/lnd/lntest/rpc"
|
||||||
"github.com/lightningnetwork/lnd/lntest/wait"
|
|
||||||
"github.com/lightningnetwork/lnd/lntypes"
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||||
"github.com/lightningnetwork/lnd/routing"
|
"github.com/lightningnetwork/lnd/routing"
|
||||||
@@ -1569,404 +1567,6 @@ func testSweepCommitOutputAndAnchor(ht *lntest.HarnessTest) {
|
|||||||
ht.MineBlocksAndAssertNumTxes(1, 2)
|
ht.MineBlocksAndAssertNumTxes(1, 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// testBumpFee checks that when a new input is requested, it's first bumped via
|
|
||||||
// CPFP, then RBF. Along the way, we check the `BumpFee` can properly update
|
|
||||||
// the fee function used by supplying new params.
|
|
||||||
func testBumpFee(ht *lntest.HarnessTest) {
|
|
||||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
|
||||||
|
|
||||||
runBumpFee(ht, alice)
|
|
||||||
}
|
|
||||||
|
|
||||||
// runBumpFee checks the `BumpFee` RPC can properly bump the fee of a given
|
|
||||||
// input.
|
|
||||||
func runBumpFee(ht *lntest.HarnessTest, alice *node.HarnessNode) {
|
|
||||||
// Skip this test for neutrino, as it's not aware of mempool
|
|
||||||
// transactions.
|
|
||||||
if ht.IsNeutrinoBackend() {
|
|
||||||
ht.Skipf("skipping BumpFee test for neutrino backend")
|
|
||||||
}
|
|
||||||
|
|
||||||
// startFeeRate is the min fee rate in sats/vbyte. This value should be
|
|
||||||
// used as the starting fee rate when the default no deadline is used.
|
|
||||||
startFeeRate := uint64(1)
|
|
||||||
|
|
||||||
// We'll start the test by sending Alice some coins, which she'll use
|
|
||||||
// to send to Bob.
|
|
||||||
ht.FundCoins(btcutil.SatoshiPerBitcoin, alice)
|
|
||||||
|
|
||||||
// Alice sends a coin to herself.
|
|
||||||
tx := ht.SendCoins(alice, alice, btcutil.SatoshiPerBitcoin)
|
|
||||||
txid := tx.TxHash()
|
|
||||||
|
|
||||||
// Alice now tries to bump the first output on this tx.
|
|
||||||
op := &lnrpc.OutPoint{
|
|
||||||
TxidBytes: txid[:],
|
|
||||||
OutputIndex: uint32(0),
|
|
||||||
}
|
|
||||||
value := btcutil.Amount(tx.TxOut[0].Value)
|
|
||||||
|
|
||||||
// assertPendingSweepResp is a helper closure that asserts the response
|
|
||||||
// from `PendingSweep` RPC is returned with expected values. It also
|
|
||||||
// returns the sweeping tx for further checks.
|
|
||||||
assertPendingSweepResp := func(broadcastAttempts uint32, budget uint64,
|
|
||||||
deadline uint32, startingFeeRate uint64) *wire.MsgTx {
|
|
||||||
|
|
||||||
err := wait.NoError(func() error {
|
|
||||||
// Alice should still have one pending sweep.
|
|
||||||
ps := ht.AssertNumPendingSweeps(alice, 1)[0]
|
|
||||||
|
|
||||||
// Validate all fields returned from `PendingSweeps` are
|
|
||||||
// as expected.
|
|
||||||
//
|
|
||||||
// These fields should stay the same during the test so
|
|
||||||
// we assert the values without wait.
|
|
||||||
require.Equal(ht, op.TxidBytes, ps.Outpoint.TxidBytes)
|
|
||||||
require.Equal(ht, op.OutputIndex,
|
|
||||||
ps.Outpoint.OutputIndex)
|
|
||||||
require.Equal(ht,
|
|
||||||
walletrpc.WitnessType_TAPROOT_PUB_KEY_SPEND,
|
|
||||||
ps.WitnessType)
|
|
||||||
require.EqualValuesf(ht, value, ps.AmountSat,
|
|
||||||
"amount not matched: want=%d, got=%d", value,
|
|
||||||
ps.AmountSat)
|
|
||||||
|
|
||||||
// The following fields can change during the test so we
|
|
||||||
// return an error if they don't match, which will be
|
|
||||||
// checked again in this wait call.
|
|
||||||
if ps.Immediate != true {
|
|
||||||
return fmt.Errorf("Immediate should be true")
|
|
||||||
}
|
|
||||||
|
|
||||||
if broadcastAttempts != ps.BroadcastAttempts {
|
|
||||||
return fmt.Errorf("broadcastAttempts not "+
|
|
||||||
"matched: want=%d, got=%d",
|
|
||||||
broadcastAttempts, ps.BroadcastAttempts)
|
|
||||||
}
|
|
||||||
if budget != ps.Budget {
|
|
||||||
return fmt.Errorf("budget not matched: "+
|
|
||||||
"want=%d, got=%d", budget, ps.Budget)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Since the request doesn't specify a deadline, we
|
|
||||||
// expect the existing deadline to be used.
|
|
||||||
if deadline != ps.DeadlineHeight {
|
|
||||||
return fmt.Errorf("deadline height not "+
|
|
||||||
"matched: want=%d, got=%d", deadline,
|
|
||||||
ps.DeadlineHeight)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Since the request specifies a starting fee rate, we
|
|
||||||
// expect that to be used as the starting fee rate.
|
|
||||||
if startingFeeRate != ps.RequestedSatPerVbyte {
|
|
||||||
return fmt.Errorf("requested starting fee "+
|
|
||||||
"rate not matched: want=%d, got=%d",
|
|
||||||
startingFeeRate,
|
|
||||||
ps.RequestedSatPerVbyte)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}, wait.DefaultTimeout)
|
|
||||||
require.NoError(ht, err, "timeout checking pending sweep")
|
|
||||||
|
|
||||||
// We expect to see Alice's original tx and her CPFP tx in the
|
|
||||||
// mempool.
|
|
||||||
txns := ht.GetNumTxsFromMempool(2)
|
|
||||||
|
|
||||||
// Find the sweeping tx - assume it's the first item, if it has
|
|
||||||
// the same txid as the parent tx, use the second item.
|
|
||||||
sweepTx := txns[0]
|
|
||||||
if sweepTx.TxHash() == tx.TxHash() {
|
|
||||||
sweepTx = txns[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
return sweepTx
|
|
||||||
}
|
|
||||||
|
|
||||||
// assertFeeRateEqual is a helper closure that asserts the fee rate of
|
|
||||||
// the pending sweep tx is equal to the expected fee rate.
|
|
||||||
assertFeeRateEqual := func(expected uint64) {
|
|
||||||
err := wait.NoError(func() error {
|
|
||||||
// Alice should still have one pending sweep.
|
|
||||||
pendingSweep := ht.AssertNumPendingSweeps(alice, 1)[0]
|
|
||||||
|
|
||||||
if pendingSweep.SatPerVbyte == expected {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("expected current fee rate %d, got "+
|
|
||||||
"%d", expected, pendingSweep.SatPerVbyte)
|
|
||||||
}, wait.DefaultTimeout)
|
|
||||||
require.NoError(ht, err, "fee rate not updated")
|
|
||||||
}
|
|
||||||
|
|
||||||
// assertFeeRateGreater is a helper closure that asserts the fee rate
|
|
||||||
// of the pending sweep tx is greater than the expected fee rate.
|
|
||||||
assertFeeRateGreater := func(expected uint64) {
|
|
||||||
err := wait.NoError(func() error {
|
|
||||||
// Alice should still have one pending sweep.
|
|
||||||
pendingSweep := ht.AssertNumPendingSweeps(alice, 1)[0]
|
|
||||||
|
|
||||||
if pendingSweep.SatPerVbyte > expected {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("expected current fee rate greater "+
|
|
||||||
"than %d, got %d", expected,
|
|
||||||
pendingSweep.SatPerVbyte)
|
|
||||||
}, wait.DefaultTimeout)
|
|
||||||
require.NoError(ht, err, "fee rate not updated")
|
|
||||||
}
|
|
||||||
|
|
||||||
// First bump request - we'll specify nothing except `Immediate` to let
|
|
||||||
// the sweeper handle the fee, and we expect a fee func that has,
|
|
||||||
// - starting fee rate: 1 sat/vbyte (min relay fee rate).
|
|
||||||
// - deadline: 1008 (default deadline).
|
|
||||||
// - budget: 50% of the input value.
|
|
||||||
bumpFeeReq := &walletrpc.BumpFeeRequest{
|
|
||||||
Outpoint: op,
|
|
||||||
// We use a force param to create the sweeping tx immediately.
|
|
||||||
Immediate: true,
|
|
||||||
}
|
|
||||||
alice.RPC.BumpFee(bumpFeeReq)
|
|
||||||
|
|
||||||
// Since the request doesn't specify a deadline, we expect the default
|
|
||||||
// deadline to be used.
|
|
||||||
currentHeight := int32(ht.CurrentHeight())
|
|
||||||
deadline := uint32(currentHeight + sweep.DefaultDeadlineDelta)
|
|
||||||
|
|
||||||
// Assert the pending sweep is created with the expected values:
|
|
||||||
// - broadcast attempts: 1.
|
|
||||||
// - starting fee rate: 1 sat/vbyte (min relay fee rate).
|
|
||||||
// - deadline: 1008 (default deadline).
|
|
||||||
// - budget: 50% of the input value.
|
|
||||||
sweepTx1 := assertPendingSweepResp(1, uint64(value/2), deadline, 0)
|
|
||||||
|
|
||||||
// Since the request doesn't specify a starting fee rate, we expect the
|
|
||||||
// min relay fee rate is used as the current fee rate.
|
|
||||||
assertFeeRateEqual(startFeeRate)
|
|
||||||
|
|
||||||
// First we test the case where we specify the conf target to increase
|
|
||||||
// the starting fee rate of the fee function.
|
|
||||||
confTargetFeeRate := chainfee.SatPerVByte(50)
|
|
||||||
ht.SetFeeEstimateWithConf(confTargetFeeRate.FeePerKWeight(), 3)
|
|
||||||
|
|
||||||
// Second bump request - we will specify the conf target and expect a
|
|
||||||
// starting fee rate that is estimated using the provided estimator.
|
|
||||||
// - starting fee rate: 50 sat/vbyte (conf target 3).
|
|
||||||
// - deadline: 1008 (default deadline).
|
|
||||||
// - budget: 50% of the input value.
|
|
||||||
bumpFeeReq = &walletrpc.BumpFeeRequest{
|
|
||||||
Outpoint: op,
|
|
||||||
// We use a force param to create the sweeping tx immediately.
|
|
||||||
Immediate: true,
|
|
||||||
TargetConf: 3,
|
|
||||||
}
|
|
||||||
|
|
||||||
alice.RPC.BumpFee(bumpFeeReq)
|
|
||||||
|
|
||||||
// Alice's old sweeping tx should be replaced.
|
|
||||||
ht.AssertTxNotInMempool(sweepTx1.TxHash())
|
|
||||||
|
|
||||||
// Assert the pending sweep is created with the expected values:
|
|
||||||
// - broadcast attempts: 2.
|
|
||||||
// - starting fee rate: 50 sat/vbyte (conf target 3).
|
|
||||||
// - deadline: 1008 (default deadline).
|
|
||||||
// - budget: 50% of the input value.
|
|
||||||
sweepTx2 := assertPendingSweepResp(
|
|
||||||
2, uint64(value/2), deadline, uint64(confTargetFeeRate),
|
|
||||||
)
|
|
||||||
|
|
||||||
// testFeeRate sepcifies a starting fee rate in sat/vbyte.
|
|
||||||
const testFeeRate = uint64(100)
|
|
||||||
|
|
||||||
// Third bump request - we will specify the fee rate and expect a fee
|
|
||||||
// func to change the starting fee rate of the fee function,
|
|
||||||
// - starting fee rate: 100 sat/vbyte.
|
|
||||||
// - deadline: 1008 (default deadline).
|
|
||||||
// - budget: 50% of the input value.
|
|
||||||
bumpFeeReq = &walletrpc.BumpFeeRequest{
|
|
||||||
Outpoint: op,
|
|
||||||
// We use a force param to create the sweeping tx immediately.
|
|
||||||
Immediate: true,
|
|
||||||
SatPerVbyte: testFeeRate,
|
|
||||||
}
|
|
||||||
alice.RPC.BumpFee(bumpFeeReq)
|
|
||||||
|
|
||||||
// Alice's old sweeping tx should be replaced.
|
|
||||||
ht.AssertTxNotInMempool(sweepTx2.TxHash())
|
|
||||||
|
|
||||||
// Assert the pending sweep is created with the expected values:
|
|
||||||
// - broadcast attempts: 3.
|
|
||||||
// - starting fee rate: 100 sat/vbyte.
|
|
||||||
// - deadline: 1008 (default deadline).
|
|
||||||
// - budget: 50% of the input value.
|
|
||||||
sweepTx3 := assertPendingSweepResp(
|
|
||||||
3, uint64(value/2), deadline, testFeeRate,
|
|
||||||
)
|
|
||||||
|
|
||||||
// We expect the requested starting fee rate to be the current fee
|
|
||||||
// rate.
|
|
||||||
assertFeeRateEqual(testFeeRate)
|
|
||||||
|
|
||||||
// testBudget specifies a budget in sats.
|
|
||||||
testBudget := uint64(float64(value) * 0.1)
|
|
||||||
|
|
||||||
// Fourth bump request - we will specify the budget and expect a fee
|
|
||||||
// func that has,
|
|
||||||
// - starting fee rate: 100 sat/vbyte, stays unchanged.
|
|
||||||
// - deadline: 1008 (default deadline).
|
|
||||||
// - budget: 10% of the input value.
|
|
||||||
bumpFeeReq = &walletrpc.BumpFeeRequest{
|
|
||||||
Outpoint: op,
|
|
||||||
// We use a force param to create the sweeping tx immediately.
|
|
||||||
Immediate: true,
|
|
||||||
Budget: testBudget,
|
|
||||||
}
|
|
||||||
alice.RPC.BumpFee(bumpFeeReq)
|
|
||||||
|
|
||||||
// Alice's old sweeping tx should be replaced.
|
|
||||||
ht.AssertTxNotInMempool(sweepTx3.TxHash())
|
|
||||||
|
|
||||||
// Assert the pending sweep is created with the expected values:
|
|
||||||
// - broadcast attempts: 4.
|
|
||||||
// - starting fee rate: 100 sat/vbyte, stays unchanged.
|
|
||||||
// - deadline: 1008 (default deadline).
|
|
||||||
// - budget: 10% of the input value.
|
|
||||||
sweepTx4 := assertPendingSweepResp(4, testBudget, deadline, testFeeRate)
|
|
||||||
|
|
||||||
// We expect the current fee rate to be increased because we ensure the
|
|
||||||
// initial broadcast always succeeds.
|
|
||||||
assertFeeRateGreater(testFeeRate)
|
|
||||||
|
|
||||||
// Create a test deadline delta to use in the next test.
|
|
||||||
testDeadlineDelta := uint32(100)
|
|
||||||
deadlineHeight := uint32(currentHeight) + testDeadlineDelta
|
|
||||||
|
|
||||||
// Fifth bump request - we will specify the deadline and expect a fee
|
|
||||||
// func that has,
|
|
||||||
// - starting fee rate: 100 sat/vbyte, stays unchanged.
|
|
||||||
// - deadline: 100.
|
|
||||||
// - budget: 10% of the input value, stays unchanged.
|
|
||||||
bumpFeeReq = &walletrpc.BumpFeeRequest{
|
|
||||||
Outpoint: op,
|
|
||||||
// We use a force param to create the sweeping tx immediately.
|
|
||||||
Immediate: true,
|
|
||||||
DeadlineDelta: testDeadlineDelta,
|
|
||||||
Budget: testBudget,
|
|
||||||
}
|
|
||||||
alice.RPC.BumpFee(bumpFeeReq)
|
|
||||||
|
|
||||||
// Alice's old sweeping tx should be replaced.
|
|
||||||
ht.AssertTxNotInMempool(sweepTx4.TxHash())
|
|
||||||
|
|
||||||
// Assert the pending sweep is created with the expected values:
|
|
||||||
// - broadcast attempts: 5.
|
|
||||||
// - starting fee rate: 100 sat/vbyte, stays unchanged.
|
|
||||||
// - deadline: 100.
|
|
||||||
// - budget: 10% of the input value, stays unchanged.
|
|
||||||
sweepTx5 := assertPendingSweepResp(
|
|
||||||
5, testBudget, deadlineHeight, testFeeRate,
|
|
||||||
)
|
|
||||||
|
|
||||||
// We expect the current fee rate to be increased because we ensure the
|
|
||||||
// initial broadcast always succeeds.
|
|
||||||
assertFeeRateGreater(testFeeRate)
|
|
||||||
|
|
||||||
// Sixth bump request - we test the behavior of `Immediate` - every
|
|
||||||
// time it's called, the fee function will keep increasing the fee rate
|
|
||||||
// until the broadcast can succeed. The fee func that has,
|
|
||||||
// - starting fee rate: 100 sat/vbyte, stays unchanged.
|
|
||||||
// - deadline: 100, stays unchanged.
|
|
||||||
// - budget: 10% of the input value, stays unchanged.
|
|
||||||
bumpFeeReq = &walletrpc.BumpFeeRequest{
|
|
||||||
Outpoint: op,
|
|
||||||
// We use a force param to create the sweeping tx immediately.
|
|
||||||
Immediate: true,
|
|
||||||
}
|
|
||||||
alice.RPC.BumpFee(bumpFeeReq)
|
|
||||||
|
|
||||||
// Alice's old sweeping tx should be replaced.
|
|
||||||
ht.AssertTxNotInMempool(sweepTx5.TxHash())
|
|
||||||
|
|
||||||
// Assert the pending sweep is created with the expected values:
|
|
||||||
// - broadcast attempts: 6.
|
|
||||||
// - starting fee rate: 100 sat/vbyte, stays unchanged.
|
|
||||||
// - deadline: 100, stays unchanged.
|
|
||||||
// - budget: 10% of the input value, stays unchanged.
|
|
||||||
sweepTx6 := assertPendingSweepResp(
|
|
||||||
6, testBudget, deadlineHeight, testFeeRate,
|
|
||||||
)
|
|
||||||
|
|
||||||
// We expect the current fee rate to be increased because we ensure the
|
|
||||||
// initial broadcast always succeeds.
|
|
||||||
assertFeeRateGreater(testFeeRate)
|
|
||||||
|
|
||||||
smallBudget := uint64(1000)
|
|
||||||
|
|
||||||
// Finally, we test the behavior of lowering the fee rate. The fee func
|
|
||||||
// that has,
|
|
||||||
// - starting fee rate: 1 sat/vbyte.
|
|
||||||
// - deadline: 1.
|
|
||||||
// - budget: 1000 sats.
|
|
||||||
bumpFeeReq = &walletrpc.BumpFeeRequest{
|
|
||||||
Outpoint: op,
|
|
||||||
// We use a force param to create the sweeping tx immediately.
|
|
||||||
Immediate: true,
|
|
||||||
SatPerVbyte: startFeeRate,
|
|
||||||
// The budget and the deadline delta must be set together.
|
|
||||||
Budget: smallBudget,
|
|
||||||
DeadlineDelta: 1,
|
|
||||||
}
|
|
||||||
alice.RPC.BumpFee(bumpFeeReq)
|
|
||||||
|
|
||||||
// Calculate the ending fee rate, which is used in the above fee bump
|
|
||||||
// when fee function's max posistion is reached.
|
|
||||||
txWeight := ht.CalculateTxWeight(sweepTx6)
|
|
||||||
endingFeeRate := chainfee.NewSatPerKWeight(
|
|
||||||
btcutil.Amount(smallBudget), txWeight,
|
|
||||||
)
|
|
||||||
|
|
||||||
// Since the fee function has been maxed out, the starting fee rate for
|
|
||||||
// the next sweep attempt should be the ending fee rate.
|
|
||||||
//
|
|
||||||
// TODO(yy): The weight estimator used in the sweeper gives a different
|
|
||||||
// result than the weight calculated here, which is the result from
|
|
||||||
// `blockchain.GetTransactionWeight`. For this particular tx:
|
|
||||||
// - result from the `weightEstimator`: 445 wu
|
|
||||||
// - result from `GetTransactionWeight`: 444 wu
|
|
||||||
//
|
|
||||||
// This means the fee rates are different,
|
|
||||||
// - `weightEstimator`: 2247 sat/kw, or 8 sat/vb (8.988 round down)
|
|
||||||
// - here we have 2252 sat/kw, or 9 sat/vb (9.008 round down)
|
|
||||||
//
|
|
||||||
// We should investigate and check whether if it's possible to make the
|
|
||||||
// `weightEstimator` more accurate.
|
|
||||||
expectedStartFeeRate := uint64(endingFeeRate.FeePerVByte()) - 1
|
|
||||||
|
|
||||||
// Assert the pending sweep is created with the expected values:
|
|
||||||
// - broadcast attempts: 7.
|
|
||||||
// - starting fee rate: 8 sat/vbyte.
|
|
||||||
// - deadline: 1.
|
|
||||||
// - budget: 1000 sats.
|
|
||||||
sweepTx7 := assertPendingSweepResp(
|
|
||||||
7, smallBudget, uint32(currentHeight+1), expectedStartFeeRate,
|
|
||||||
)
|
|
||||||
|
|
||||||
// Since this budget is too small to cover the RBF, we expect the
|
|
||||||
// sweeping attempt to fail.
|
|
||||||
require.Equal(ht, sweepTx6.TxHash(), sweepTx7.TxHash(), "tx6 should "+
|
|
||||||
"not be replaced: tx6=%v, tx7=%v", sweepTx6.TxHash(),
|
|
||||||
sweepTx7.TxHash())
|
|
||||||
|
|
||||||
// We expect the current fee rate to be increased because we ensure the
|
|
||||||
// initial broadcast always succeeds.
|
|
||||||
assertFeeRateGreater(testFeeRate)
|
|
||||||
|
|
||||||
// Clean up the mempool.
|
|
||||||
ht.MineBlocksAndAssertNumTxes(1, 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// testBumpForceCloseFee tests that when a force close transaction, in
|
// testBumpForceCloseFee tests that when a force close transaction, in
|
||||||
// particular a commitment which has no HTLCs at stake, can be bumped via the
|
// particular a commitment which has no HTLCs at stake, can be bumped via the
|
||||||
// rpc endpoint `BumpForceCloseFee`.
|
// rpc endpoint `BumpForceCloseFee`.
|
||||||
@@ -2385,178 +1985,3 @@ func testFeeReplacement(ht *lntest.HarnessTest) {
|
|||||||
// Finally, clean the mempool.
|
// Finally, clean the mempool.
|
||||||
ht.MineBlocksAndAssertNumTxes(1, 1)
|
ht.MineBlocksAndAssertNumTxes(1, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// testBumpFeeLowBudget checks that when the requested ideal budget cannot be
|
|
||||||
// met, the sweeper still sweeps the input with the actual budget.
|
|
||||||
func testBumpFeeLowBudget(ht *lntest.HarnessTest) {
|
|
||||||
// Create a new node with a large `maxfeerate` so it's easier to run the
|
|
||||||
// test.
|
|
||||||
alice := ht.NewNode("Alice", []string{
|
|
||||||
"--sweeper.maxfeerate=10000",
|
|
||||||
})
|
|
||||||
|
|
||||||
// Fund Alice 2 UTXOs, each has 100k sats. One of the UTXOs will be used
|
|
||||||
// to create a tx which she sends some coins to herself. The other will
|
|
||||||
// be used as the budget when CPFPing the above tx.
|
|
||||||
coin := btcutil.Amount(100_000)
|
|
||||||
ht.FundCoins(coin, alice)
|
|
||||||
ht.FundCoins(coin, alice)
|
|
||||||
|
|
||||||
// Alice sends 50k sats to herself.
|
|
||||||
tx := ht.SendCoins(alice, alice, coin/2)
|
|
||||||
txid := tx.TxHash()
|
|
||||||
|
|
||||||
// Get Alice's wallet balance to calculate the fees used in the above
|
|
||||||
// tx.
|
|
||||||
resp := alice.RPC.WalletBalance()
|
|
||||||
|
|
||||||
// balance is the expected final balance. Alice's initial balance is
|
|
||||||
// 200k sats, with 100k sats as the budget for the sweeping tx, which
|
|
||||||
// means her final balance should be 100k sats minus the mining fees
|
|
||||||
// used in the above `SendCoins`.
|
|
||||||
balance := btcutil.Amount(
|
|
||||||
resp.UnconfirmedBalance + resp.ConfirmedBalance,
|
|
||||||
)
|
|
||||||
fee := coin*2 - balance
|
|
||||||
ht.Logf("Alice's expected final balance=%v, fee=%v", balance, fee)
|
|
||||||
|
|
||||||
// Alice now tries to bump the first output on this tx.
|
|
||||||
op := &lnrpc.OutPoint{
|
|
||||||
TxidBytes: txid[:],
|
|
||||||
OutputIndex: uint32(0),
|
|
||||||
}
|
|
||||||
value := btcutil.Amount(tx.TxOut[0].Value)
|
|
||||||
|
|
||||||
// assertPendingSweepResp is a helper closure that asserts the response
|
|
||||||
// from `PendingSweep` RPC is returned with expected values. It also
|
|
||||||
// returns the sweeping tx for further checks.
|
|
||||||
assertPendingSweepResp := func(budget uint64,
|
|
||||||
deadline uint32) *wire.MsgTx {
|
|
||||||
|
|
||||||
// Alice should still have one pending sweep.
|
|
||||||
pendingSweep := ht.AssertNumPendingSweeps(alice, 1)[0]
|
|
||||||
|
|
||||||
// Validate all fields returned from `PendingSweeps` are as
|
|
||||||
// expected.
|
|
||||||
require.Equal(ht, op.TxidBytes, pendingSweep.Outpoint.TxidBytes)
|
|
||||||
require.Equal(ht, op.OutputIndex,
|
|
||||||
pendingSweep.Outpoint.OutputIndex)
|
|
||||||
require.Equal(ht, walletrpc.WitnessType_TAPROOT_PUB_KEY_SPEND,
|
|
||||||
pendingSweep.WitnessType)
|
|
||||||
require.EqualValuesf(ht, value, pendingSweep.AmountSat,
|
|
||||||
"amount not matched: want=%d, got=%d", value,
|
|
||||||
pendingSweep.AmountSat)
|
|
||||||
require.True(ht, pendingSweep.Immediate)
|
|
||||||
|
|
||||||
require.EqualValuesf(ht, budget, pendingSweep.Budget,
|
|
||||||
"budget not matched: want=%d, got=%d", budget,
|
|
||||||
pendingSweep.Budget)
|
|
||||||
|
|
||||||
// Since the request doesn't specify a deadline, we expect the
|
|
||||||
// existing deadline to be used.
|
|
||||||
require.Equalf(ht, deadline, pendingSweep.DeadlineHeight,
|
|
||||||
"deadline height not matched: want=%d, got=%d",
|
|
||||||
deadline, pendingSweep.DeadlineHeight)
|
|
||||||
|
|
||||||
// We expect to see Alice's original tx and her CPFP tx in the
|
|
||||||
// mempool.
|
|
||||||
txns := ht.GetNumTxsFromMempool(2)
|
|
||||||
|
|
||||||
// Find the sweeping tx - assume it's the first item, if it has
|
|
||||||
// the same txid as the parent tx, use the second item.
|
|
||||||
sweepTx := txns[0]
|
|
||||||
if sweepTx.TxHash() == tx.TxHash() {
|
|
||||||
sweepTx = txns[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
return sweepTx
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use a budget that Alice cannot cover using her wallet UTXOs.
|
|
||||||
budget := coin * 2
|
|
||||||
|
|
||||||
// Use a deadlineDelta of 3 such that the fee func is initialized as,
|
|
||||||
// - starting fee rate: 1 sat/vbyte
|
|
||||||
// - deadline: 3
|
|
||||||
// - budget: 200% of Alice's available funds.
|
|
||||||
deadlineDelta := 3
|
|
||||||
|
|
||||||
// First bump request - we expect it to succeed as Alice's current funds
|
|
||||||
// can cover the fees used here given the position of the fee func is at
|
|
||||||
// 0.
|
|
||||||
bumpFeeReq := &walletrpc.BumpFeeRequest{
|
|
||||||
Outpoint: op,
|
|
||||||
Budget: uint64(budget),
|
|
||||||
Immediate: true,
|
|
||||||
DeadlineDelta: uint32(deadlineDelta),
|
|
||||||
}
|
|
||||||
alice.RPC.BumpFee(bumpFeeReq)
|
|
||||||
|
|
||||||
// Calculate the deadline height.
|
|
||||||
deadline := ht.CurrentHeight() + uint32(deadlineDelta)
|
|
||||||
|
|
||||||
// Assert the pending sweep is created with the expected values:
|
|
||||||
// - deadline: 3+current height.
|
|
||||||
// - budget: 2x the wallet balance.
|
|
||||||
sweepTx1 := assertPendingSweepResp(uint64(budget), deadline)
|
|
||||||
|
|
||||||
// Mine a block to trigger Alice's sweeper to fee bump the tx.
|
|
||||||
//
|
|
||||||
// Second bump request - we expect it to succeed as Alice's current
|
|
||||||
// funds can cover the fees used here, which is 66.7% of her available
|
|
||||||
// funds given the position of the fee func is at 1.
|
|
||||||
ht.MineEmptyBlocks(1)
|
|
||||||
|
|
||||||
// Assert the old sweeping tx has been replaced.
|
|
||||||
ht.AssertTxNotInMempool(sweepTx1.TxHash())
|
|
||||||
|
|
||||||
// Assert a new sweeping tx is made.
|
|
||||||
sweepTx2 := assertPendingSweepResp(uint64(budget), deadline)
|
|
||||||
|
|
||||||
// Mine a block to trigger Alice's sweeper to fee bump the tx.
|
|
||||||
//
|
|
||||||
// Third bump request - we expect it to fail as Alice's current funds
|
|
||||||
// cannot cover the fees now, which is 133.3% of her available funds
|
|
||||||
// given the position of the fee func is at 2.
|
|
||||||
ht.MineEmptyBlocks(1)
|
|
||||||
|
|
||||||
// Assert the above sweeping tx is still in the mempool.
|
|
||||||
ht.AssertTxInMempool(sweepTx2.TxHash())
|
|
||||||
|
|
||||||
// Fund Alice 200k sats, which will be used to cover the budget.
|
|
||||||
//
|
|
||||||
// TODO(yy): We are funding Alice more than enough - at this stage Alice
|
|
||||||
// has a confirmed UTXO of `coin` amount in her wallet, so ideally we
|
|
||||||
// should only fund another UTXO of `coin` amount. However, since the
|
|
||||||
// confirmed wallet UTXO has already been used in sweepTx2, there's no
|
|
||||||
// easy way to tell her wallet to reuse that UTXO in the upcoming
|
|
||||||
// sweeping tx.
|
|
||||||
// To properly fix it, we should provide more granular UTXO management
|
|
||||||
// here by leveraing `LeaseOutput` - whenever we use a wallet UTXO, we
|
|
||||||
// should lock it first. And when the sweeping attempt fails, we should
|
|
||||||
// release it so the UTXO can be used again in another batch.
|
|
||||||
walletTx := ht.FundCoinsUnconfirmed(coin*2, alice)
|
|
||||||
|
|
||||||
// Mine a block to confirm the above funding coin.
|
|
||||||
//
|
|
||||||
// Fourth bump request - we expect it to succeed as Alice's current
|
|
||||||
// funds can cover the full budget.
|
|
||||||
ht.MineBlockWithTx(walletTx)
|
|
||||||
|
|
||||||
flakeRaceInBitcoinClientNotifications(ht)
|
|
||||||
|
|
||||||
// Assert Alice's previous sweeping tx has been replaced.
|
|
||||||
ht.AssertTxNotInMempool(sweepTx2.TxHash())
|
|
||||||
|
|
||||||
// Assert the pending sweep is created with the expected values:
|
|
||||||
// - deadline: 3+current height.
|
|
||||||
// - budget: 2x the wallet balance.
|
|
||||||
sweepTx3 := assertPendingSweepResp(uint64(budget), deadline)
|
|
||||||
require.NotEqual(ht, sweepTx2.TxHash(), sweepTx3.TxHash())
|
|
||||||
|
|
||||||
// Mine the sweeping tx.
|
|
||||||
ht.MineBlocksAndAssertNumTxes(1, 2)
|
|
||||||
|
|
||||||
// Assert Alice's wallet balance. a
|
|
||||||
ht.WaitForBalanceConfirmed(alice, balance)
|
|
||||||
}
|
|
||||||
|
@@ -254,6 +254,18 @@ func (h *HarnessRPC) BumpFee(
|
|||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BumpFeeAssertErr makes a RPC call to the node's WalletKitClient and asserts
|
||||||
|
// that an error is returned.
|
||||||
|
func (h *HarnessRPC) BumpFeeAssertErr(req *walletrpc.BumpFeeRequest) error {
|
||||||
|
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
_, err := h.WalletKit.BumpFee(ctxt, req)
|
||||||
|
require.Errorf(h, err, "%s: expect BumpFee to return an error", h.Name)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// BumpForceCloseFee makes a RPC call to the node's WalletKitClient and asserts.
|
// BumpForceCloseFee makes a RPC call to the node's WalletKitClient and asserts.
|
||||||
//
|
//
|
||||||
//nolint:ll
|
//nolint:ll
|
||||||
|
Reference in New Issue
Block a user