From 102d3cdd1a5ec3207e1892583fb6a780ae97917a Mon Sep 17 00:00:00 2001 From: George Tsagkarelis Date: Thu, 4 May 2023 18:15:33 +0300 Subject: [PATCH 1/2] liquidity: differentiate autoloop expected vs max miner fees --- liquidity/fees.go | 34 +++++++++++++++++++++++++++++----- liquidity/liquidity_test.go | 2 +- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/liquidity/fees.go b/liquidity/fees.go index 36a437a..6645cc0 100644 --- a/liquidity/fees.go +++ b/liquidity/fees.go @@ -38,10 +38,14 @@ const ( // sweep fees, (750 * 4 /1000 = 3 sat/vByte). defaultSweepFeeRateLimit = chainfee.SatPerKWeight(750) - // minerMultiplier is a multiplier we use to scale our miner fee to - // ensure that we will still be able to complete our swap in the case - // of a severe fee spike. - minerMultiplier = 100 + // minerMultiplier is a multiplier we use to predict the average chain + // costs towards miner fees. + minerMultiplier = 2 + + // maxMinerMultiplier is the maximum multiplier we use to scale our + // miner fee to ensure that we will still be able to complete our swap + // in the case of a severe fee spike. + maxMinerMultiplier = 50 // defaultFeePPM is the default percentage of swap amount that we // allocate to fees, 2%. @@ -341,6 +345,12 @@ func (f *FeePortion) loopOutLimits(swapAmt btcutil.Amount, prepay, route, miner := f.loopOutFees(swapAmt, quote) + // Before checking our fees against our budget we remove the large + // multiplier from the miner fees. We do this because we want to + // consider the average case for our budget calculations and not the + // severe edge-case miner fees. + miner = miner / maxMinerMultiplier + // Calculate the worst case fees that we could pay for this swap, // ensuring that we are within our fee limit even if the swap fails. fees := worstCaseOutFees( @@ -370,6 +380,8 @@ func (f *FeePortion) loopOutFees(amount btcutil.Amount, // amounts provided by the quote to get the total available for // off-chain fees. feeLimit := ppmToSat(amount, f.PartsPerMillion) + + // Apply the small miner multiplier for the fee budget calculations. minerFee := scaleMinerFee(quote.MinerFee) available := feeLimit - minerFee - quote.SwapFee @@ -378,6 +390,9 @@ func (f *FeePortion) loopOutFees(amount btcutil.Amount, available, quote.PrepayAmount, amount, ) + // Apply the big miner multiplier to get the worst case miner fees. + minerFee = scaleMaxMinerFee(minerFee) + return prepayMaxFee, routeMaxFee, minerFee } @@ -394,11 +409,20 @@ func splitOffChain(available, prepayAmt, return prepayMaxFee, routeMaxFee } -// scaleMinerFee scales our miner fee by our constant multiplier. +// scaleMinerFee scales our miner fee by a smaller multiplier. This scale does +// not represent the worst-case maximum miner fees, but the average expected +// fees. func scaleMinerFee(estimate btcutil.Amount) btcutil.Amount { return estimate * btcutil.Amount(minerMultiplier) } +// scaleMaxMinerFee scales our miner fee by a big multiplier. The returned value +// represents the maximum amount that we consider spending for miner fees in +// worst-case scenarios (fee-spikes). +func scaleMaxMinerFee(estimate btcutil.Amount) btcutil.Amount { + return estimate * btcutil.Amount(maxMinerMultiplier) +} + func (f *FeePortion) loopInLimits(amount btcutil.Amount, quote *loop.LoopInQuote) error { diff --git a/liquidity/liquidity_test.go b/liquidity/liquidity_test.go index f3d3a9d..5b6e3e8 100644 --- a/liquidity/liquidity_test.go +++ b/liquidity/liquidity_test.go @@ -1555,7 +1555,7 @@ func TestFeePercentage(t *testing.T) { quote: &loop.LoopOutQuote{ SwapFee: 60, PrepayAmount: 30, - MinerFee: 1, + MinerFee: 50, }, suggestions: &Suggestions{ DisqualifiedChans: map[lnwire.ShortChannelID]Reason{ From 36f014ac0a73950dcfff3bfc28a1a78e54338f83 Mon Sep 17 00:00:00 2001 From: George Tsagkarelis Date: Wed, 10 May 2023 15:44:09 +0300 Subject: [PATCH 2/2] liquidity: fix tests to reflect new miner fee scaling --- liquidity/liquidity_test.go | 56 ++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/liquidity/liquidity_test.go b/liquidity/liquidity_test.go index 5b6e3e8..0dcad20 100644 --- a/liquidity/liquidity_test.go +++ b/liquidity/liquidity_test.go @@ -73,11 +73,13 @@ var ( OutgoingChanSet: loopdb.ChannelSet{chanID1.ToUint64()}, MaxPrepayRoutingFee: prepayFee, MaxSwapRoutingFee: routingFee, - MaxMinerFee: scaleMinerFee(testQuote.MinerFee), - MaxSwapFee: testQuote.SwapFee, - MaxPrepayAmount: testQuote.PrepayAmount, - SweepConfTarget: defaultConfTarget, - Initiator: autoloopSwapInitiator, + MaxMinerFee: scaleMaxMinerFee( + scaleMinerFee(testQuote.MinerFee), + ), + MaxSwapFee: testQuote.SwapFee, + MaxPrepayAmount: testQuote.PrepayAmount, + SweepConfTarget: defaultConfTarget, + Initiator: autoloopSwapInitiator, } // chan2Rec is the suggested swap for channel 2 when we use chanRule. @@ -86,11 +88,13 @@ var ( OutgoingChanSet: loopdb.ChannelSet{chanID2.ToUint64()}, MaxPrepayRoutingFee: prepayFee, MaxSwapRoutingFee: routingFee, - MaxMinerFee: scaleMinerFee(testQuote.MinerFee), - MaxPrepayAmount: testQuote.PrepayAmount, - MaxSwapFee: testQuote.SwapFee, - SweepConfTarget: defaultConfTarget, - Initiator: autoloopSwapInitiator, + MaxMinerFee: scaleMaxMinerFee( + scaleMinerFee(testQuote.MinerFee), + ), + MaxPrepayAmount: testQuote.PrepayAmount, + MaxSwapFee: testQuote.SwapFee, + SweepConfTarget: defaultConfTarget, + Initiator: autoloopSwapInitiator, } // chan1Out is a contract that uses channel 1, used to represent on @@ -771,11 +775,13 @@ func TestSuggestSwaps(t *testing.T) { }, MaxPrepayRoutingFee: prepay, MaxSwapRoutingFee: routing, - MaxMinerFee: scaleMinerFee(testQuote.MinerFee), - MaxSwapFee: testQuote.SwapFee, - MaxPrepayAmount: testQuote.PrepayAmount, - SweepConfTarget: defaultConfTarget, - Initiator: autoloopSwapInitiator, + MaxMinerFee: scaleMaxMinerFee( + scaleMinerFee(testQuote.MinerFee), + ), + MaxSwapFee: testQuote.SwapFee, + MaxPrepayAmount: testQuote.PrepayAmount, + SweepConfTarget: defaultConfTarget, + Initiator: autoloopSwapInitiator, }, }, DisqualifiedChans: noneDisqualified, @@ -1330,11 +1336,13 @@ func TestSizeRestrictions(t *testing.T) { OutgoingChanSet: loopdb.ChannelSet{chanID1.ToUint64()}, MaxPrepayRoutingFee: prepay, MaxSwapRoutingFee: routing, - MaxMinerFee: scaleMinerFee(testQuote.MinerFee), - MaxSwapFee: testQuote.SwapFee, - MaxPrepayAmount: testQuote.PrepayAmount, - SweepConfTarget: defaultConfTarget, - Initiator: autoloopSwapInitiator, + MaxMinerFee: scaleMaxMinerFee( + scaleMinerFee(testQuote.MinerFee), + ), + MaxSwapFee: testQuote.SwapFee, + MaxPrepayAmount: testQuote.PrepayAmount, + SweepConfTarget: defaultConfTarget, + Initiator: autoloopSwapInitiator, } ) @@ -1487,7 +1495,9 @@ func TestFeePercentage(t *testing.T) { rec = loop.OutRequest{ Amount: 7500, OutgoingChanSet: loopdb.ChannelSet{chanID1.ToUint64()}, - MaxMinerFee: scaleMinerFee(okQuote.MinerFee), + MaxMinerFee: scaleMaxMinerFee( + scaleMinerFee(testQuote.MinerFee), + ), MaxSwapFee: okQuote.SwapFee, MaxPrepayAmount: okQuote.PrepayAmount, SweepConfTarget: defaultConfTarget, @@ -1650,7 +1660,9 @@ func TestBudgetWithLoopin(t *testing.T) { rec = loop.OutRequest{ Amount: 7500, OutgoingChanSet: loopdb.ChannelSet{chanID1.ToUint64()}, - MaxMinerFee: scaleMinerFee(okQuote.MinerFee), + MaxMinerFee: scaleMaxMinerFee( + scaleMinerFee(testQuote.MinerFee), + ), MaxSwapFee: okQuote.SwapFee, MaxPrepayAmount: okQuote.PrepayAmount, SweepConfTarget: defaultConfTarget,