mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-03-04 09:48:19 +01:00
lnwallet+sweep: calculate max allowed feerate on BumpResult
This commit adds the method `MaxFeeRateAllowed` to calculate the max fee rate. The caller may specify a large MaxFeeRate value, which cannot be cover by the budget. In that case, we default to use the max feerate calculated using `budget/weight`.
This commit is contained in:
parent
f85661d94a
commit
ecd471ac75
3 changed files with 183 additions and 0 deletions
|
@ -58,6 +58,11 @@ func (s SatPerKVByte) String() string {
|
|||
// SatPerKWeight represents a fee rate in sat/kw.
|
||||
type SatPerKWeight btcutil.Amount
|
||||
|
||||
// NewSatPerKWeight creates a new fee rate in sat/kw.
|
||||
func NewSatPerKWeight(fee btcutil.Amount, weight uint64) SatPerKWeight {
|
||||
return SatPerKWeight(fee.MulF64(1000 / float64(weight)))
|
||||
}
|
||||
|
||||
// FeeForWeight calculates the fee resulting from this fee rate and the given
|
||||
// weight in weight units (wu).
|
||||
func (s SatPerKWeight) FeeForWeight(wu int64) btcutil.Amount {
|
||||
|
|
|
@ -88,6 +88,63 @@ type BumpRequest struct {
|
|||
MaxFeeRate chainfee.SatPerKWeight
|
||||
}
|
||||
|
||||
// MaxFeeRateAllowed returns the maximum fee rate allowed for the given
|
||||
// request. It calculates the feerate using the supplied budget and the weight,
|
||||
// compares it with the specified MaxFeeRate, and returns the smaller of the
|
||||
// two.
|
||||
func (r *BumpRequest) MaxFeeRateAllowed() (chainfee.SatPerKWeight, error) {
|
||||
// Get the size of the sweep tx, which will be used to calculate the
|
||||
// budget fee rate.
|
||||
size, err := calcSweepTxWeight(r.Inputs, r.DeliveryAddress)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Use the budget and MaxFeeRate to decide the max allowed fee rate.
|
||||
// This is needed as, when the input has a large value and the user
|
||||
// sets the budget to be proportional to the input value, the fee rate
|
||||
// can be very high and we need to make sure it doesn't exceed the max
|
||||
// fee rate.
|
||||
maxFeeRateAllowed := chainfee.NewSatPerKWeight(r.Budget, size)
|
||||
if maxFeeRateAllowed > r.MaxFeeRate {
|
||||
log.Debugf("Budget feerate %v exceeds MaxFeeRate %v, use "+
|
||||
"MaxFeeRate instead", maxFeeRateAllowed, r.MaxFeeRate)
|
||||
|
||||
return r.MaxFeeRate, nil
|
||||
}
|
||||
|
||||
log.Debugf("Budget feerate %v below MaxFeeRate %v, use budget feerate "+
|
||||
"instead", maxFeeRateAllowed, r.MaxFeeRate)
|
||||
|
||||
return maxFeeRateAllowed, nil
|
||||
}
|
||||
|
||||
// calcSweepTxWeight calculates the weight of the sweep tx. It assumes a
|
||||
// sweeping tx always has a single output(change).
|
||||
func calcSweepTxWeight(inputs []input.Input,
|
||||
outputPkScript []byte) (uint64, error) {
|
||||
|
||||
// Use a const fee rate as we only use the weight estimator to
|
||||
// calculate the size.
|
||||
const feeRate = 1
|
||||
|
||||
// Initialize the tx weight estimator with,
|
||||
// - nil outputs as we only have one single change output.
|
||||
// - const fee rate as we don't care about the fees here.
|
||||
// - 0 maxfeerate as we don't care about fees here.
|
||||
//
|
||||
// TODO(yy): we should refactor the weight estimator to not require a
|
||||
// fee rate and max fee rate and make it a pure tx weight calculator.
|
||||
_, estimator, err := getWeightEstimate(
|
||||
inputs, nil, feeRate, 0, outputPkScript,
|
||||
)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return uint64(estimator.weight()), nil
|
||||
}
|
||||
|
||||
// BumpResult is used by the Bumper to send updates about the tx being
|
||||
// broadcast.
|
||||
type BumpResult struct {
|
||||
|
|
|
@ -3,10 +3,24 @@ package sweep
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var (
|
||||
// Create a taproot change script.
|
||||
changePkScript = []byte{
|
||||
0x51, 0x20,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
}
|
||||
)
|
||||
|
||||
// TestBumpResultValidate tests the validate method of the BumpResult struct.
|
||||
func TestBumpResultValidate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
@ -50,3 +64,110 @@ func TestBumpResultValidate(t *testing.T) {
|
|||
}
|
||||
require.NoError(t, b.Validate())
|
||||
}
|
||||
|
||||
// TestCalcSweepTxWeight checks that the weight of the sweep tx is calculated
|
||||
// correctly.
|
||||
func TestCalcSweepTxWeight(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create an input.
|
||||
inp := createTestInput(100, input.WitnessKeyHash)
|
||||
|
||||
// Use a wrong change script to test the error case.
|
||||
weight, err := calcSweepTxWeight([]input.Input{&inp}, []byte{0})
|
||||
require.Error(t, err)
|
||||
require.Zero(t, weight)
|
||||
|
||||
// Use a correct change script to test the success case.
|
||||
weight, err = calcSweepTxWeight([]input.Input{&inp}, changePkScript)
|
||||
require.NoError(t, err)
|
||||
|
||||
// BaseTxSize 8 bytes
|
||||
// InputSize 1+41 bytes
|
||||
// One P2TROutputSize 1+43 bytes
|
||||
// One P2WKHWitnessSize 2+109 bytes
|
||||
// Total weight = (8+42+44) * 4 + 111 = 487
|
||||
require.EqualValuesf(t, 487, weight, "unexpected weight %v", weight)
|
||||
}
|
||||
|
||||
// TestBumpRequestMaxFeeRateAllowed tests the max fee rate allowed for a bump
|
||||
// request.
|
||||
func TestBumpRequestMaxFeeRateAllowed(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create a test input.
|
||||
inp := createTestInput(100, input.WitnessKeyHash)
|
||||
|
||||
// The weight is 487.
|
||||
weight, err := calcSweepTxWeight([]input.Input{&inp}, changePkScript)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Define a test budget and calculates its fee rate.
|
||||
budget := btcutil.Amount(1000)
|
||||
budgetFeeRate := chainfee.NewSatPerKWeight(budget, weight)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
req *BumpRequest
|
||||
expectedMaxFeeRate chainfee.SatPerKWeight
|
||||
expectedErr bool
|
||||
}{
|
||||
{
|
||||
// Use a wrong change script to test the error case.
|
||||
name: "error calc weight",
|
||||
req: &BumpRequest{
|
||||
DeliveryAddress: []byte{1},
|
||||
},
|
||||
expectedMaxFeeRate: 0,
|
||||
expectedErr: true,
|
||||
},
|
||||
{
|
||||
// When the budget cannot give a fee rate that matches
|
||||
// the supplied MaxFeeRate, the max allowed feerate is
|
||||
// capped by the budget.
|
||||
name: "use budget as max fee rate",
|
||||
req: &BumpRequest{
|
||||
DeliveryAddress: changePkScript,
|
||||
Inputs: []input.Input{&inp},
|
||||
Budget: budget,
|
||||
MaxFeeRate: budgetFeeRate + 1,
|
||||
},
|
||||
expectedMaxFeeRate: budgetFeeRate,
|
||||
},
|
||||
{
|
||||
// When the budget can give a fee rate that matches the
|
||||
// supplied MaxFeeRate, the max allowed feerate is
|
||||
// capped by the MaxFeeRate.
|
||||
name: "use config as max fee rate",
|
||||
req: &BumpRequest{
|
||||
DeliveryAddress: changePkScript,
|
||||
Inputs: []input.Input{&inp},
|
||||
Budget: budget,
|
||||
MaxFeeRate: budgetFeeRate - 1,
|
||||
},
|
||||
expectedMaxFeeRate: budgetFeeRate - 1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// Check the method under test.
|
||||
maxFeeRate, err := tc.req.MaxFeeRateAllowed()
|
||||
|
||||
// If we expect an error, check the error is returned
|
||||
// and the feerate is empty.
|
||||
if tc.expectedErr {
|
||||
require.Error(t, err)
|
||||
require.Zero(t, maxFeeRate)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, check the max fee rate is as expected.
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.expectedMaxFeeRate, maxFeeRate)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue