mirror of
https://github.com/lightninglabs/loop.git
synced 2026-08-13 12:33:03 +02:00
The fee estimator fails if conf_target=1. Use conf_target=2 instead. Fix https://github.com/lightninglabs/loop/issues/898
277 lines
7.2 KiB
Go
277 lines
7.2 KiB
Go
package loop
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/btcsuite/btcd/btcutil"
|
|
"github.com/btcsuite/btcd/chaincfg"
|
|
"github.com/lightninglabs/loop/loopdb"
|
|
"github.com/lightninglabs/loop/sweep"
|
|
"github.com/lightninglabs/loop/test"
|
|
"github.com/lightningnetwork/lnd/input"
|
|
"github.com/lightningnetwork/lnd/lntypes"
|
|
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// testSweeper is implementation of sweeper.Sweeper for test.
|
|
type testSweeper struct {
|
|
}
|
|
|
|
// GetSweepFeeDetails calculates the required tx fee to spend to destAddr. It
|
|
// takes a function that is expected to add the weight of the input to the
|
|
// weight estimator. It returns also the fee rate and transaction weight.
|
|
func (s testSweeper) GetSweepFeeDetails(ctx context.Context,
|
|
addInputEstimate func(*input.TxWeightEstimator) error,
|
|
destAddr btcutil.Address, sweepConfTarget int32,
|
|
label string) (btcutil.Amount, chainfee.SatPerKWeight,
|
|
lntypes.WeightUnit, error) {
|
|
|
|
var feeRate chainfee.SatPerKWeight
|
|
switch {
|
|
case sweepConfTarget == 0:
|
|
return 0, 0, 0, fmt.Errorf("zero sweepConfTarget")
|
|
|
|
case sweepConfTarget == 1:
|
|
return 0, 0, 0, fmt.Errorf("sweepConfTarget is 1")
|
|
|
|
case sweepConfTarget == 2:
|
|
feeRate = 25000
|
|
|
|
case sweepConfTarget == 3:
|
|
feeRate = 20000
|
|
|
|
case sweepConfTarget < 10:
|
|
feeRate = 8000
|
|
|
|
case sweepConfTarget < 100:
|
|
feeRate = 5000
|
|
|
|
case sweepConfTarget < 1000:
|
|
feeRate = 2000
|
|
|
|
default:
|
|
feeRate = 250
|
|
}
|
|
|
|
// Calculate weight for this tx.
|
|
var weightEstimate input.TxWeightEstimator
|
|
|
|
// Add output.
|
|
err := sweep.AddOutputEstimate(&weightEstimate, destAddr)
|
|
if err != nil {
|
|
return 0, 0, 0, fmt.Errorf("failed to add output weight "+
|
|
"estimate: %w", err)
|
|
}
|
|
|
|
// Add input.
|
|
err = addInputEstimate(&weightEstimate)
|
|
if err != nil {
|
|
return 0, 0, 0, fmt.Errorf("failed to add input weight "+
|
|
"estimate: %w", err)
|
|
}
|
|
|
|
// Find weight.
|
|
weight := weightEstimate.Weight()
|
|
|
|
return feeRate.FeeForWeight(weight), feeRate, weight, nil
|
|
}
|
|
|
|
// TestLoopOutSweepFeerateProvider tests that loopOutSweepFeerateProvider
|
|
// provides correct fee rate for loop-out swaps.
|
|
func TestLoopOutSweepFeerateProvider(t *testing.T) {
|
|
htlcKeys := func() loopdb.HtlcKeys {
|
|
var senderKey, receiverKey [33]byte
|
|
|
|
// Generate keys.
|
|
_, senderPubKey := test.CreateKey(1)
|
|
copy(senderKey[:], senderPubKey.SerializeCompressed())
|
|
_, receiverPubKey := test.CreateKey(2)
|
|
copy(receiverKey[:], receiverPubKey.SerializeCompressed())
|
|
|
|
return loopdb.HtlcKeys{
|
|
SenderScriptKey: senderKey,
|
|
ReceiverScriptKey: receiverKey,
|
|
SenderInternalPubKey: senderKey,
|
|
ReceiverInternalPubKey: receiverKey,
|
|
}
|
|
}()
|
|
|
|
var destAddr *btcutil.AddressTaproot
|
|
|
|
swapInvoice := "lntb1230n1pjjszzgpp5j76f03wrkya4sm4gxv6az5nmz5aqsvmn4" +
|
|
"tpguu2sdvdyygedqjgqdq9xyerxcqzzsxqr23ssp5rwzmwtfjmsgranfk8sr" +
|
|
"4p4gcgmvyd42uug8pxteg2mkk23ndvkqs9qyyssq44ruk3ex59cmv4dm6k4v" +
|
|
"0kc6c0gcqjs0gkljfyd6c6uatqa2f67xlx3pcg5tnvcae5p3jju8ra77e87d" +
|
|
"vhhs0jrx53wnc0fq9rkrhmqqelyx7l"
|
|
|
|
cases := []struct {
|
|
name string
|
|
cltvExpiry int32
|
|
height int32
|
|
amount btcutil.Amount
|
|
protocolVersion loopdb.ProtocolVersion
|
|
wantConfTarget int32
|
|
wantFeeRate chainfee.SatPerKWeight
|
|
wantError string
|
|
}{
|
|
{
|
|
name: "simple case",
|
|
cltvExpiry: 801_000,
|
|
height: 800_900,
|
|
amount: 1_000_000,
|
|
protocolVersion: loopdb.ProtocolVersionMuSig2,
|
|
wantConfTarget: 100,
|
|
wantFeeRate: 2000,
|
|
},
|
|
{
|
|
name: "zero height",
|
|
cltvExpiry: 801_000,
|
|
height: 0,
|
|
amount: 1_000_000,
|
|
protocolVersion: loopdb.ProtocolVersionMuSig2,
|
|
wantError: "got zero best block height",
|
|
},
|
|
{
|
|
name: "huge amount, no proportional fee",
|
|
cltvExpiry: 801_000,
|
|
height: 800_900,
|
|
amount: 100_000_000,
|
|
protocolVersion: loopdb.ProtocolVersionMuSig2,
|
|
wantConfTarget: 100,
|
|
wantFeeRate: 2000,
|
|
},
|
|
{
|
|
name: "huge amount, no proportional fee, v2",
|
|
cltvExpiry: 801_000,
|
|
height: 800_900,
|
|
amount: 100_000_000,
|
|
protocolVersion: loopdb.ProtocolVersionLoopOutCancel,
|
|
wantConfTarget: 100,
|
|
wantFeeRate: 2000,
|
|
},
|
|
{
|
|
name: "huge amount, no proportional fee, " +
|
|
"capped by urgent fee",
|
|
cltvExpiry: 801_000,
|
|
height: 800_900,
|
|
amount: 200_000_000,
|
|
protocolVersion: loopdb.ProtocolVersionMuSig2,
|
|
wantConfTarget: 100,
|
|
wantFeeRate: 2000,
|
|
},
|
|
{
|
|
name: "11 blocks until expiry",
|
|
cltvExpiry: 801_000,
|
|
height: 800_989,
|
|
amount: 1_000_000,
|
|
protocolVersion: loopdb.ProtocolVersionMuSig2,
|
|
wantConfTarget: 11,
|
|
wantFeeRate: 5000,
|
|
},
|
|
{
|
|
name: "10 blocks until expiry",
|
|
cltvExpiry: 801_000,
|
|
height: 800_990,
|
|
amount: 1_000_000,
|
|
protocolVersion: loopdb.ProtocolVersionMuSig2,
|
|
wantConfTarget: 3,
|
|
wantFeeRate: 22000,
|
|
},
|
|
{
|
|
name: "9 blocks until expiry",
|
|
cltvExpiry: 801_000,
|
|
height: 800_991,
|
|
amount: 1_000_000,
|
|
protocolVersion: loopdb.ProtocolVersionMuSig2,
|
|
wantConfTarget: 3,
|
|
wantFeeRate: 22000,
|
|
},
|
|
{
|
|
name: "3 blocks until expiry",
|
|
cltvExpiry: 801_000,
|
|
height: 800_997,
|
|
amount: 1_000_000,
|
|
protocolVersion: loopdb.ProtocolVersionMuSig2,
|
|
wantConfTarget: 3,
|
|
wantFeeRate: 22000,
|
|
},
|
|
{
|
|
name: "2 blocks until expiry",
|
|
cltvExpiry: 801_000,
|
|
height: 800_998,
|
|
amount: 1_000_000,
|
|
protocolVersion: loopdb.ProtocolVersionMuSig2,
|
|
wantConfTarget: 2,
|
|
wantFeeRate: 27500,
|
|
},
|
|
{
|
|
name: "1 blocks until expiry",
|
|
cltvExpiry: 801_000,
|
|
height: 800_999,
|
|
amount: 1_000_000,
|
|
protocolVersion: loopdb.ProtocolVersionMuSig2,
|
|
wantConfTarget: 2,
|
|
wantFeeRate: 27500,
|
|
},
|
|
{
|
|
name: "expired",
|
|
cltvExpiry: 801_000,
|
|
height: 801_000,
|
|
amount: 1_000_000,
|
|
protocolVersion: loopdb.ProtocolVersionMuSig2,
|
|
wantConfTarget: 2,
|
|
wantFeeRate: 27500,
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
store := loopdb.NewStoreMock(t)
|
|
|
|
ctx := context.Background()
|
|
|
|
swapHash := lntypes.Hash{1, 1, 1}
|
|
swap := &loopdb.LoopOutContract{
|
|
SwapContract: loopdb.SwapContract{
|
|
CltvExpiry: tc.cltvExpiry,
|
|
AmountRequested: tc.amount,
|
|
ProtocolVersion: tc.protocolVersion,
|
|
HtlcKeys: htlcKeys,
|
|
},
|
|
|
|
DestAddr: destAddr,
|
|
SwapInvoice: swapInvoice,
|
|
SweepConfTarget: 100,
|
|
}
|
|
|
|
err := store.CreateLoopOut(ctx, swapHash, swap)
|
|
require.NoError(t, err)
|
|
store.AssertLoopOutStored()
|
|
|
|
getHeight := func() int32 {
|
|
return tc.height
|
|
}
|
|
|
|
p := newLoopOutSweepFeerateProvider(
|
|
testSweeper{}, store,
|
|
&chaincfg.RegressionNetParams, getHeight,
|
|
)
|
|
|
|
confTarget, feeRate, err := p.GetConfTargetAndFeeRate(
|
|
ctx, swapHash,
|
|
)
|
|
if tc.wantError != "" {
|
|
require.ErrorContains(t, err, tc.wantError)
|
|
|
|
return
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, tc.wantConfTarget, confTarget)
|
|
require.Equal(t, tc.wantFeeRate, feeRate)
|
|
})
|
|
}
|
|
}
|