mirror of
https://github.com/lightninglabs/loop.git
synced 2026-08-13 12:33:03 +02:00
multi: factor out sweep fee clamping function
This commit is contained in:
parent
9ee7a36f37
commit
0b53296aa8
6 changed files with 153 additions and 40 deletions
|
|
@ -317,9 +317,16 @@ func (f *FSM) BuildHTLCAction(ctx context.Context,
|
|||
return f.handleErrorAndUnlockReservations(ctx, err)
|
||||
}
|
||||
|
||||
minRelayFee, err := f.cfg.Wallet.MinRelayFee(ctx)
|
||||
if err != nil {
|
||||
return f.handleErrorAndUnlockReservations(ctx, err)
|
||||
}
|
||||
|
||||
// Now that our nonces are set, we can create and sign the htlc
|
||||
// transaction.
|
||||
htlcTx, err := f.InstantOut.createHtlcTransaction(f.cfg.Network)
|
||||
htlcTx, err := f.InstantOut.createHtlcTransaction(
|
||||
f.cfg.Network, minRelayFee,
|
||||
)
|
||||
if err != nil {
|
||||
return f.handleErrorAndUnlockReservations(ctx, err)
|
||||
}
|
||||
|
|
@ -382,6 +389,11 @@ func (f *FSM) PushPreimageAction(ctx context.Context,
|
|||
return f.handleErrorAndUnlockReservations(ctx, err)
|
||||
}
|
||||
|
||||
minRelayFee, err := f.cfg.Wallet.MinRelayFee(ctx)
|
||||
if err != nil {
|
||||
return f.handleErrorAndUnlockReservations(ctx, err)
|
||||
}
|
||||
|
||||
pushPreImageRes, err := f.cfg.InstantOutClient.PushPreimage(
|
||||
ctx,
|
||||
&swapserverrpc.PushPreimageRequest{
|
||||
|
|
@ -400,7 +412,9 @@ func (f *FSM) PushPreimageAction(ctx context.Context,
|
|||
|
||||
// Now that we have the sweepless sweep signatures we can build and
|
||||
// publish the sweepless sweep transaction.
|
||||
sweepTx, err := f.InstantOut.createSweeplessSweepTx(feeRate)
|
||||
sweepTx, err := f.InstantOut.createSweeplessSweepTx(
|
||||
feeRate, minRelayFee,
|
||||
)
|
||||
if err != nil {
|
||||
f.LastActionError = err
|
||||
return OnErrorPublishHtlc
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import (
|
|||
"github.com/lightninglabs/loop/instantout/reservation"
|
||||
"github.com/lightninglabs/loop/loopdb"
|
||||
"github.com/lightninglabs/loop/swap"
|
||||
"github.com/lightninglabs/loop/utils"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
|
|
@ -145,8 +146,8 @@ func (i *InstantOut) getInputReservations() (InputReservations, error) {
|
|||
}
|
||||
|
||||
// createHtlcTransaction creates the htlc transaction for the instant out.
|
||||
func (i *InstantOut) createHtlcTransaction(network *chaincfg.Params) (
|
||||
*wire.MsgTx, error) {
|
||||
func (i *InstantOut) createHtlcTransaction(network *chaincfg.Params,
|
||||
minRelayFeeRate chainfee.SatPerKWeight) (*wire.MsgTx, error) {
|
||||
|
||||
if network == nil {
|
||||
return nil, errors.New("no network provided")
|
||||
|
|
@ -170,7 +171,16 @@ func (i *InstantOut) createHtlcTransaction(network *chaincfg.Params) (
|
|||
// Estimate the fee
|
||||
weight := htlcWeight(len(inputReservations))
|
||||
fee := i.htlcFeeRate.FeeForWeight(weight)
|
||||
if fee > i.Value/5 {
|
||||
|
||||
// We cap the fee at 20% of the deposit value.
|
||||
_, clamped, err := utils.ClampSweepFee(
|
||||
fee, i.Value, utils.MaxFeeToAmountRatio, minRelayFeeRate,
|
||||
weight,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if clamped {
|
||||
return nil, errors.New("fee is higher than 20% of " +
|
||||
"sweep value")
|
||||
}
|
||||
|
|
@ -193,8 +203,8 @@ func (i *InstantOut) createHtlcTransaction(network *chaincfg.Params) (
|
|||
|
||||
// createSweeplessSweepTx creates the sweepless sweep transaction for the
|
||||
// instant out.
|
||||
func (i *InstantOut) createSweeplessSweepTx(feerate chainfee.SatPerKWeight) (
|
||||
*wire.MsgTx, error) {
|
||||
func (i *InstantOut) createSweeplessSweepTx(feerate,
|
||||
minRelayFeeRate chainfee.SatPerKWeight) (*wire.MsgTx, error) {
|
||||
|
||||
inputReservations, err := i.getInputReservations()
|
||||
if err != nil {
|
||||
|
|
@ -214,7 +224,14 @@ func (i *InstantOut) createSweeplessSweepTx(feerate chainfee.SatPerKWeight) (
|
|||
// Estimate the fee
|
||||
weight := sweeplessSweepWeight(len(inputReservations))
|
||||
fee := feerate.FeeForWeight(weight)
|
||||
if fee > i.Value/5 {
|
||||
_, clamped, err := utils.ClampSweepFee(
|
||||
fee, i.Value, utils.MaxFeeToAmountRatio, minRelayFeeRate,
|
||||
weight,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if clamped {
|
||||
return nil, errors.New("fee is higher than 20% of " +
|
||||
"sweep value")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/lightninglabs/lndclient"
|
||||
"github.com/lightninglabs/loop/fsm"
|
||||
"github.com/lightninglabs/loop/staticaddr/script"
|
||||
"github.com/lightninglabs/loop/utils"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
)
|
||||
|
||||
|
|
@ -47,12 +48,25 @@ func (f *FSM) PublishDepositExpirySweepAction(ctx context.Context,
|
|||
"estimation failed: %w", err))
|
||||
}
|
||||
|
||||
minRelayFeeRate, err := f.cfg.WalletKit.MinRelayFee(ctx)
|
||||
if err != nil {
|
||||
return f.HandleError(fmt.Errorf("timeout sweep min relay "+
|
||||
"query failed: %w", err))
|
||||
}
|
||||
|
||||
weight := script.ExpirySpendWeight()
|
||||
|
||||
fee := feeRateEstimator.FeeForWeight(lntypes.WeightUnit(weight))
|
||||
|
||||
// We cap the fee at 20% of the deposit value.
|
||||
if fee > f.deposit.Value/5 {
|
||||
_, clamped, err := utils.ClampSweepFee(
|
||||
fee, f.deposit.Value, utils.MaxFeeToAmountRatio,
|
||||
minRelayFeeRate, lntypes.WeightUnit(weight),
|
||||
)
|
||||
if err != nil {
|
||||
return f.HandleError(err)
|
||||
}
|
||||
if clamped {
|
||||
return f.HandleError(errors.New("fee is greater than 20% of " +
|
||||
"the deposit value"))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,10 +46,6 @@ const (
|
|||
// transaction.
|
||||
batchConfHeight = 3
|
||||
|
||||
// maxFeeToSwapAmtRatio is the maximum fee to swap amount ratio that
|
||||
// we allow for a batch transaction.
|
||||
maxFeeToSwapAmtRatio = 0.2
|
||||
|
||||
// MaxSweepsPerBatch is the maximum number of sweeps in a single batch.
|
||||
// It is needed to prevent sweep tx from becoming non-standard. Max
|
||||
// standard transaction is 400k wu, a non-cooperative input is 393 wu.
|
||||
|
|
@ -1414,9 +1410,9 @@ func constructUnsignedTx(sweeps []sweep, address btcutil.Address,
|
|||
}
|
||||
|
||||
// Clamp the calculated fee to the max allowed fee amount for the batch.
|
||||
fee, err := clampBatchFee(
|
||||
fee, _, err := utils.ClampSweepFee(
|
||||
feeForWeight, batchAmt-btcutil.Amount(sumChange),
|
||||
minRelayFeeRate, weight,
|
||||
utils.MaxFeeToAmountRatio, minRelayFeeRate, weight,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, 0, 0, 0, fmt.Errorf("failed to clamp batch "+
|
||||
|
|
@ -2644,28 +2640,3 @@ func (b *batch) persistConfirmedBatch(ctx context.Context,
|
|||
|
||||
return b.store.ConfirmBatchWithSweeps(ctx, b.dbBatch(), sweeps)
|
||||
}
|
||||
|
||||
// clampBatchFee takes the fee amount and total amount of the sweeps in the
|
||||
// batch and makes sure the fee is not too high. If the fee is too high, it is
|
||||
// clamped to the maximum allowed fee. If the clamped fee results in a fee rate
|
||||
// below the minimum relay fee, an error is returned.
|
||||
func clampBatchFee(fee btcutil.Amount, totalAmount btcutil.Amount,
|
||||
minRelayFeeRate chainfee.SatPerKWeight,
|
||||
weight lntypes.WeightUnit) (btcutil.Amount, error) {
|
||||
|
||||
maxFeeAmount := btcutil.Amount(float64(totalAmount) *
|
||||
maxFeeToSwapAmtRatio)
|
||||
|
||||
clampedFee := fee
|
||||
if fee > maxFeeAmount {
|
||||
clampedFee = maxFeeAmount
|
||||
}
|
||||
|
||||
clampedFeeRate := chainfee.NewSatPerKWeight(clampedFee, weight)
|
||||
if clampedFeeRate < minRelayFeeRate {
|
||||
return 0, fmt.Errorf("clamped fee rate %v is less than "+
|
||||
"minimum relay fee %v", clampedFeeRate, minRelayFeeRate)
|
||||
}
|
||||
|
||||
return clampedFee, nil
|
||||
}
|
||||
|
|
|
|||
44
utils/fees.go
Normal file
44
utils/fees.go
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||
)
|
||||
|
||||
const (
|
||||
// MaxFeeToAmountRatio is the maximum fee to total amount ratio allowed
|
||||
// for a sweep transaction.
|
||||
MaxFeeToAmountRatio = 0.2
|
||||
)
|
||||
|
||||
// ClampSweepFee caps a fee to a percentage of the provided total amount and
|
||||
// verifies the resulting fee rate is not below the minimum relay fee. It
|
||||
// returns the clamped fee, whether it was clamped, or an error if the clamped
|
||||
// fee would fall below the minimum relay fee.
|
||||
func ClampSweepFee(fee btcutil.Amount, totalAmount btcutil.Amount,
|
||||
ratio float64, minRelayFeeRate chainfee.SatPerKWeight,
|
||||
weight lntypes.WeightUnit) (btcutil.Amount, bool, error) {
|
||||
|
||||
maxFeeAmount := btcutil.Amount(float64(totalAmount) * ratio)
|
||||
|
||||
clampedFee := fee
|
||||
clamped := false
|
||||
if fee > maxFeeAmount {
|
||||
clampedFee = maxFeeAmount
|
||||
clamped = true
|
||||
}
|
||||
|
||||
if minRelayFeeRate > 0 {
|
||||
clampedFeeRate := chainfee.NewSatPerKWeight(clampedFee, weight)
|
||||
if clampedFeeRate < minRelayFeeRate {
|
||||
return 0, clamped, fmt.Errorf("clamped fee rate %v is "+
|
||||
"less than minimum relay fee %v",
|
||||
clampedFeeRate, minRelayFeeRate)
|
||||
}
|
||||
}
|
||||
|
||||
return clampedFee, clamped, nil
|
||||
}
|
||||
53
utils/fees_test.go
Normal file
53
utils/fees_test.go
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestClampSweepFee verifies clamping still respects min relay fee
|
||||
// requirements and reports clamping.
|
||||
func TestClampSweepFee(t *testing.T) {
|
||||
weight := lntypes.WeightUnit(400)
|
||||
minRelay := chainfee.SatPerKWeight(253)
|
||||
total := btcutil.Amount(100_000)
|
||||
|
||||
// Fee below the clamp threshold and above min relay should pass
|
||||
// through.
|
||||
fee := chainfee.SatPerKWeight(1_000).FeeForWeight(weight)
|
||||
clamped, clampedFlag, err := ClampSweepFee(
|
||||
fee, total, MaxFeeToAmountRatio, minRelay, weight,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, fee, clamped)
|
||||
require.False(t, clampedFlag)
|
||||
|
||||
// A clamped fee that would fall below min relay should error.
|
||||
// The fee will be clamped to 20 sats.
|
||||
fee = btcutil.Amount(10_000)
|
||||
total = btcutil.Amount(100)
|
||||
_, clampedFlag, err = ClampSweepFee(
|
||||
fee, total, MaxFeeToAmountRatio, minRelay, weight,
|
||||
)
|
||||
require.True(t, clampedFlag)
|
||||
require.Error(t, err)
|
||||
|
||||
// A fee above the clamp threshold should be clamped without error when
|
||||
// still above min relay. The fee is 30% of total, will clamp to 20%.
|
||||
fee = btcutil.Amount(30_000)
|
||||
total = btcutil.Amount(100_000)
|
||||
clamped, clampedFlag, err = ClampSweepFee(
|
||||
fee, total, MaxFeeToAmountRatio, minRelay, weight,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.True(t, clampedFlag)
|
||||
|
||||
expectedClampedFee := btcutil.Amount(
|
||||
float64(total) * MaxFeeToAmountRatio,
|
||||
)
|
||||
require.Equal(t, expectedClampedFee, clamped)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue