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{