sweep: make sure the full input is accounted

Fix the case where previously only the witness data is taken into
account when calculating the fees.
This commit is contained in:
yyforyongyu 2024-05-24 23:47:04 +08:00
parent 17a089c899
commit 5e8452cc5d
No known key found for this signature in database
GPG Key ID: 9BCD95C4FF296868
2 changed files with 42 additions and 17 deletions

View File

@ -5,6 +5,8 @@ import (
"github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwallet/chainfee"
) )
@ -201,8 +203,8 @@ func (b *BudgetAggregator) filterInputs(inputs InputsMap) InputsMap {
for _, pi := range inputs { for _, pi := range inputs {
op := pi.OutPoint() op := pi.OutPoint()
// Get the size and skip if there's an error. // Get the size of the witness and skip if there's an error.
size, _, err := pi.WitnessType().SizeUpperBound() witnessSize, _, err := pi.WitnessType().SizeUpperBound()
if err != nil { if err != nil {
log.Warnf("Skipped input=%v: cannot get its size: %v", log.Warnf("Skipped input=%v: cannot get its size: %v",
op, err) op, err)
@ -210,12 +212,27 @@ func (b *BudgetAggregator) filterInputs(inputs InputsMap) InputsMap {
continue continue
} }
//nolint:lll
// Calculate the size if the input is included in the tx.
//
// NOTE: When including this input, we need to account the
// non-witness data which is expressed in vb.
//
// TODO(yy): This is not accurate for tapscript input. We need
// to unify calculations used in the `TxWeightEstimator` inside
// `input/size.go` and `weightEstimator` in
// `weight_estimator.go`. And calculate the expected weights
// similar to BOLT-3:
// https://github.com/lightning/bolts/blob/master/03-transactions.md#appendix-a-expected-weights
wu := lntypes.VByte(input.InputSize).ToWU() + witnessSize
// Skip inputs that has too little budget. // Skip inputs that has too little budget.
minFee := minFeeRate.FeeForWeight(size) minFee := minFeeRate.FeeForWeight(wu)
if pi.params.Budget < minFee { if pi.params.Budget < minFee {
log.Warnf("Skipped input=%v: has budget=%v, but the "+ log.Warnf("Skipped input=%v: has budget=%v, but the "+
"min fee requires %v", op, pi.params.Budget, "min fee requires %v (feerate=%v), size=%v", op,
minFee) pi.params.Budget, minFee,
minFeeRate.FeePerVByte(), wu.ToVB())
continue continue
} }
@ -224,11 +241,12 @@ func (b *BudgetAggregator) filterInputs(inputs InputsMap) InputsMap {
startingFeeRate := pi.params.StartingFeeRate.UnwrapOr( startingFeeRate := pi.params.StartingFeeRate.UnwrapOr(
chainfee.SatPerKWeight(0), chainfee.SatPerKWeight(0),
) )
startingFee := startingFeeRate.FeeForWeight(size) startingFee := startingFeeRate.FeeForWeight(wu)
if pi.params.Budget < startingFee { if pi.params.Budget < startingFee {
log.Errorf("Skipped input=%v: has budget=%v, but the "+ log.Errorf("Skipped input=%v: has budget=%v, but the "+
"starting fee requires %v", op, "starting fee requires %v (feerate=%v), "+
pi.params.Budget, minFee) "size=%v", op, pi.params.Budget, startingFee,
startingFeeRate.FeePerVByte(), wu.ToVB())
continue continue
} }

View File

@ -43,8 +43,11 @@ func TestBudgetAggregatorFilterInputs(t *testing.T) {
defer wt.AssertExpectations(t) defer wt.AssertExpectations(t)
// Mock the `SizeUpperBound` method to return the size four times. // Mock the `SizeUpperBound` method to return the size four times.
const wtSize lntypes.WeightUnit = 100 const wu lntypes.WeightUnit = 100
wt.On("SizeUpperBound").Return(wtSize, true, nil).Times(4) wt.On("SizeUpperBound").Return(wu, true, nil).Times(4)
// Calculate the input size.
inpSize := lntypes.VByte(input.InputSize).ToWU() + wu
// Create a mock input that will be filtered out due to error. // Create a mock input that will be filtered out due to error.
inpErr := &input.MockInput{} inpErr := &input.MockInput{}
@ -64,9 +67,9 @@ func TestBudgetAggregatorFilterInputs(t *testing.T) {
var ( var (
// Define three budget values, one below the min fee rate, one // Define three budget values, one below the min fee rate, one
// above and one equal to it. // above and one equal to it.
budgetLow = minFeeRate.FeeForWeight(wtSize) - 1 budgetLow = minFeeRate.FeeForWeight(inpSize) - 1
budgetEqual = minFeeRate.FeeForWeight(wtSize) budgetEqual = minFeeRate.FeeForWeight(inpSize)
budgetHigh = minFeeRate.FeeForWeight(wtSize) + 1 budgetHigh = minFeeRate.FeeForWeight(inpSize) + 1
// Define three outpoints with different budget values. // Define three outpoints with different budget values.
opLow = wire.OutPoint{Hash: chainhash.Hash{2}} opLow = wire.OutPoint{Hash: chainhash.Hash{2}}
@ -398,8 +401,12 @@ func TestBudgetInputSetClusterInputs(t *testing.T) {
// Mock the `SizeUpperBound` method to return the size 10 times since // Mock the `SizeUpperBound` method to return the size 10 times since
// we are using ten inputs. // we are using ten inputs.
const wtSize lntypes.WeightUnit = 100 const wu lntypes.WeightUnit = 100
wt.On("SizeUpperBound").Return(wtSize, true, nil).Times(10) wt.On("SizeUpperBound").Return(wu, true, nil).Times(10)
// Calculate the input size.
inpSize := lntypes.VByte(input.InputSize).ToWU() + wu
wt.On("String").Return("mock witness type") wt.On("String").Return("mock witness type")
// Mock the estimator to return a constant fee rate. // Mock the estimator to return a constant fee rate.
@ -409,8 +416,8 @@ func TestBudgetInputSetClusterInputs(t *testing.T) {
var ( var (
// Define two budget values, one below the min fee rate and one // Define two budget values, one below the min fee rate and one
// above it. // above it.
budgetLow = minFeeRate.FeeForWeight(wtSize) - 1 budgetLow = minFeeRate.FeeForWeight(inpSize) - 1
budgetHigh = minFeeRate.FeeForWeight(wtSize) + 1 budgetHigh = minFeeRate.FeeForWeight(inpSize) + 1
// Create three deadline heights, which means there are three // Create three deadline heights, which means there are three
// groups of inputs to be expected. // groups of inputs to be expected.