From c067169e6fa4557ce2639dfb4b502305e7a06352 Mon Sep 17 00:00:00 2001 From: carla Date: Tue, 30 Nov 2021 13:18:30 +0200 Subject: [PATCH] liquidity: add loop fee estimation and swap interface impl Add an implementation of our swap interface which can be used for loop in, and fee estimation. For fee estimation, we always want to calculate worst case loop in fees, so that autoloop never goes over its budget. However, for loop in we can't estimate how much a timeout would cost, because we must sweep the output (can't set a limit like loop out), and fee estimation in a few hundred blocks (when we'd sweep the timeout) is totally unreliable. Instead, we use a high fee rate as our best-effort fee rate for the future. We can also be confident that that loop in swaps will succeed, since once the htlc is locked in, all that is required is for the server to sweep. --- liquidity/liquidity.go | 6 ++++ liquidity/loopin.go | 82 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 liquidity/loopin.go diff --git a/liquidity/liquidity.go b/liquidity/liquidity.go index dd2cf3c..7e776ca 100644 --- a/liquidity/liquidity.go +++ b/liquidity/liquidity.go @@ -81,6 +81,12 @@ const ( // autoloopSwapInitiator is the value we send in the initiator field of // a swap request when issuing an automatic swap. autoloopSwapInitiator = "autoloop" + + // We use a static fee rate to estimate our sweep fee, because we + // can't realistically estimate what our fee estimate will be by the + // time we reach timeout. We set this to a high estimate so that we can + // account for worst-case fees, (1250 * 4 / 1000) = 50 sat/byte. + defaultLoopInSweepFee = chainfee.SatPerKWeight(1250) ) var ( diff --git a/liquidity/loopin.go b/liquidity/loopin.go new file mode 100644 index 0000000..5591e09 --- /dev/null +++ b/liquidity/loopin.go @@ -0,0 +1,82 @@ +package liquidity + +import ( + "github.com/btcsuite/btcutil" + "github.com/lightninglabs/loop" + "github.com/lightninglabs/loop/swap" + "github.com/lightningnetwork/lnd/input" + "github.com/lightningnetwork/lnd/lnwallet/chainfee" + "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/routing/route" +) + +// Compile time assertion that loop in suggestions satisfy our interface. +var _ swapSuggestion = (*loopInSwapSuggestion)(nil) + +type loopInSwapSuggestion struct { + loop.LoopInRequest +} + +// amount returns the amount of the swap suggestion. +func (l *loopInSwapSuggestion) amount() btcutil.Amount { + return l.Amount +} + +// fees returns the highest fees that we could pay for the swap suggestion. +func (l *loopInSwapSuggestion) fees() btcutil.Amount { + return worstCaseInFees( + l.MaxMinerFee, l.MaxSwapFee, defaultLoopInSweepFee, + ) +} + +// channels returns no channels for loop in swap suggestions because we do not +// restrict loop in swaps by channel id. +func (l *loopInSwapSuggestion) channels() []lnwire.ShortChannelID { + return nil +} + +// peers returns the peer that a loop in swap is restricted to, if it is set. +func (l *loopInSwapSuggestion) peers(_ map[uint64]route.Vertex) []route.Vertex { + if l.LastHop == nil { + return nil + } + + return []route.Vertex{ + *l.LastHop, + } +} + +// worstCaseInFees returns the largest possible fees for a loop in swap. +func worstCaseInFees(maxMinerFee, swapFee btcutil.Amount, + sweepEst chainfee.SatPerKWeight) btcutil.Amount { + + failureFee := maxMinerFee + loopInSweepFee(sweepEst) + successFee := maxMinerFee + swapFee + + if failureFee > successFee { + return failureFee + } + + return successFee +} + +// loopInSweepFee provides an estimated fee for our sweep transaction, based +// on the fee rate provided. We can calculate our fees for htlcv2 and p2wkh +// timeout addresses because automated loop ins will be handled entirely by the +// client, so we know what types will be used. +func loopInSweepFee(fee chainfee.SatPerKWeight) btcutil.Amount { + var estimator input.TxWeightEstimator + + // We sweep loop in swaps to wpkh addresses provided by lnd. + estimator.AddP2WKHOutput() + + // Create a htlcv2, which is what all autoloops will use, so that we + // can get our maximum timeout witness size. + htlc := swap.HtlcScriptV2{} + maxSize := htlc.MaxTimeoutWitnessSize() + + estimator.AddWitnessInput(maxSize) + weight := int64(estimator.Weight()) + + return fee.FeeForWeight(weight) +}