loop/loopout_feerate_test.go
Boris Nagaev d554b42ef6
multi: migrate to btcd v2 modules
Update LND, Aperture, and Taproot Assets to revisions using the
btcd v2 modules, and update lndclient to v0.21.0-3. Migrate Loop
chain, transaction, and address types to their corresponding v2
packages.

The lndclient release includes the migration from:
https://github.com/lightninglabs/lndclient/pull/280

Taproot Assets is temporarily replaced with its btcd v2 revision
because the v0.8 release branch has not adopted the new modules.

This raises the minimum Go version to 1.26 and changes exported
address types.
2026-08-12 23:39:26 +00:00

278 lines
7.2 KiB
Go

package loop
import (
"context"
"fmt"
"testing"
btcaddr "github.com/btcsuite/btcd/address/v2"
"github.com/btcsuite/btcd/btcutil/v2"
"github.com/btcsuite/btcd/chaincfg/v2"
"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 btcaddr.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 *btcaddr.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)
})
}
}