Skip to content

Commit 0a19485

Browse files
authored
Merge pull request #581 from GeorgeTsagk/fee-percentage-miner
liquidity: differentiate autoloop expected vs max miner fees
2 parents 6a4aa8c + 36f014a commit 0a19485

File tree

2 files changed

+64
-28
lines changed

2 files changed

+64
-28
lines changed

liquidity/fees.go

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,14 @@ const (
3838
// sweep fees, (750 * 4 /1000 = 3 sat/vByte).
3939
defaultSweepFeeRateLimit = chainfee.SatPerKWeight(750)
4040

41-
// minerMultiplier is a multiplier we use to scale our miner fee to
42-
// ensure that we will still be able to complete our swap in the case
43-
// of a severe fee spike.
44-
minerMultiplier = 100
41+
// minerMultiplier is a multiplier we use to predict the average chain
42+
// costs towards miner fees.
43+
minerMultiplier = 2
44+
45+
// maxMinerMultiplier is the maximum multiplier we use to scale our
46+
// miner fee to ensure that we will still be able to complete our swap
47+
// in the case of a severe fee spike.
48+
maxMinerMultiplier = 50
4549

4650
// defaultFeePPM is the default percentage of swap amount that we
4751
// allocate to fees, 2%.
@@ -341,6 +345,12 @@ func (f *FeePortion) loopOutLimits(swapAmt btcutil.Amount,
341345

342346
prepay, route, miner := f.loopOutFees(swapAmt, quote)
343347

348+
// Before checking our fees against our budget we remove the large
349+
// multiplier from the miner fees. We do this because we want to
350+
// consider the average case for our budget calculations and not the
351+
// severe edge-case miner fees.
352+
miner = miner / maxMinerMultiplier
353+
344354
// Calculate the worst case fees that we could pay for this swap,
345355
// ensuring that we are within our fee limit even if the swap fails.
346356
fees := worstCaseOutFees(
@@ -370,6 +380,8 @@ func (f *FeePortion) loopOutFees(amount btcutil.Amount,
370380
// amounts provided by the quote to get the total available for
371381
// off-chain fees.
372382
feeLimit := ppmToSat(amount, f.PartsPerMillion)
383+
384+
// Apply the small miner multiplier for the fee budget calculations.
373385
minerFee := scaleMinerFee(quote.MinerFee)
374386

375387
available := feeLimit - minerFee - quote.SwapFee
@@ -378,6 +390,9 @@ func (f *FeePortion) loopOutFees(amount btcutil.Amount,
378390
available, quote.PrepayAmount, amount,
379391
)
380392

393+
// Apply the big miner multiplier to get the worst case miner fees.
394+
minerFee = scaleMaxMinerFee(minerFee)
395+
381396
return prepayMaxFee, routeMaxFee, minerFee
382397
}
383398

@@ -394,11 +409,20 @@ func splitOffChain(available, prepayAmt,
394409
return prepayMaxFee, routeMaxFee
395410
}
396411

397-
// scaleMinerFee scales our miner fee by our constant multiplier.
412+
// scaleMinerFee scales our miner fee by a smaller multiplier. This scale does
413+
// not represent the worst-case maximum miner fees, but the average expected
414+
// fees.
398415
func scaleMinerFee(estimate btcutil.Amount) btcutil.Amount {
399416
return estimate * btcutil.Amount(minerMultiplier)
400417
}
401418

419+
// scaleMaxMinerFee scales our miner fee by a big multiplier. The returned value
420+
// represents the maximum amount that we consider spending for miner fees in
421+
// worst-case scenarios (fee-spikes).
422+
func scaleMaxMinerFee(estimate btcutil.Amount) btcutil.Amount {
423+
return estimate * btcutil.Amount(maxMinerMultiplier)
424+
}
425+
402426
func (f *FeePortion) loopInLimits(amount btcutil.Amount,
403427
quote *loop.LoopInQuote) error {
404428

liquidity/liquidity_test.go

Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,13 @@ var (
7373
OutgoingChanSet: loopdb.ChannelSet{chanID1.ToUint64()},
7474
MaxPrepayRoutingFee: prepayFee,
7575
MaxSwapRoutingFee: routingFee,
76-
MaxMinerFee: scaleMinerFee(testQuote.MinerFee),
77-
MaxSwapFee: testQuote.SwapFee,
78-
MaxPrepayAmount: testQuote.PrepayAmount,
79-
SweepConfTarget: defaultConfTarget,
80-
Initiator: autoloopSwapInitiator,
76+
MaxMinerFee: scaleMaxMinerFee(
77+
scaleMinerFee(testQuote.MinerFee),
78+
),
79+
MaxSwapFee: testQuote.SwapFee,
80+
MaxPrepayAmount: testQuote.PrepayAmount,
81+
SweepConfTarget: defaultConfTarget,
82+
Initiator: autoloopSwapInitiator,
8183
}
8284

8385
// chan2Rec is the suggested swap for channel 2 when we use chanRule.
@@ -86,11 +88,13 @@ var (
8688
OutgoingChanSet: loopdb.ChannelSet{chanID2.ToUint64()},
8789
MaxPrepayRoutingFee: prepayFee,
8890
MaxSwapRoutingFee: routingFee,
89-
MaxMinerFee: scaleMinerFee(testQuote.MinerFee),
90-
MaxPrepayAmount: testQuote.PrepayAmount,
91-
MaxSwapFee: testQuote.SwapFee,
92-
SweepConfTarget: defaultConfTarget,
93-
Initiator: autoloopSwapInitiator,
91+
MaxMinerFee: scaleMaxMinerFee(
92+
scaleMinerFee(testQuote.MinerFee),
93+
),
94+
MaxPrepayAmount: testQuote.PrepayAmount,
95+
MaxSwapFee: testQuote.SwapFee,
96+
SweepConfTarget: defaultConfTarget,
97+
Initiator: autoloopSwapInitiator,
9498
}
9599

96100
// chan1Out is a contract that uses channel 1, used to represent on
@@ -771,11 +775,13 @@ func TestSuggestSwaps(t *testing.T) {
771775
},
772776
MaxPrepayRoutingFee: prepay,
773777
MaxSwapRoutingFee: routing,
774-
MaxMinerFee: scaleMinerFee(testQuote.MinerFee),
775-
MaxSwapFee: testQuote.SwapFee,
776-
MaxPrepayAmount: testQuote.PrepayAmount,
777-
SweepConfTarget: defaultConfTarget,
778-
Initiator: autoloopSwapInitiator,
778+
MaxMinerFee: scaleMaxMinerFee(
779+
scaleMinerFee(testQuote.MinerFee),
780+
),
781+
MaxSwapFee: testQuote.SwapFee,
782+
MaxPrepayAmount: testQuote.PrepayAmount,
783+
SweepConfTarget: defaultConfTarget,
784+
Initiator: autoloopSwapInitiator,
779785
},
780786
},
781787
DisqualifiedChans: noneDisqualified,
@@ -1330,11 +1336,13 @@ func TestSizeRestrictions(t *testing.T) {
13301336
OutgoingChanSet: loopdb.ChannelSet{chanID1.ToUint64()},
13311337
MaxPrepayRoutingFee: prepay,
13321338
MaxSwapRoutingFee: routing,
1333-
MaxMinerFee: scaleMinerFee(testQuote.MinerFee),
1334-
MaxSwapFee: testQuote.SwapFee,
1335-
MaxPrepayAmount: testQuote.PrepayAmount,
1336-
SweepConfTarget: defaultConfTarget,
1337-
Initiator: autoloopSwapInitiator,
1339+
MaxMinerFee: scaleMaxMinerFee(
1340+
scaleMinerFee(testQuote.MinerFee),
1341+
),
1342+
MaxSwapFee: testQuote.SwapFee,
1343+
MaxPrepayAmount: testQuote.PrepayAmount,
1344+
SweepConfTarget: defaultConfTarget,
1345+
Initiator: autoloopSwapInitiator,
13381346
}
13391347
)
13401348

@@ -1487,7 +1495,9 @@ func TestFeePercentage(t *testing.T) {
14871495
rec = loop.OutRequest{
14881496
Amount: 7500,
14891497
OutgoingChanSet: loopdb.ChannelSet{chanID1.ToUint64()},
1490-
MaxMinerFee: scaleMinerFee(okQuote.MinerFee),
1498+
MaxMinerFee: scaleMaxMinerFee(
1499+
scaleMinerFee(testQuote.MinerFee),
1500+
),
14911501
MaxSwapFee: okQuote.SwapFee,
14921502
MaxPrepayAmount: okQuote.PrepayAmount,
14931503
SweepConfTarget: defaultConfTarget,
@@ -1555,7 +1565,7 @@ func TestFeePercentage(t *testing.T) {
15551565
quote: &loop.LoopOutQuote{
15561566
SwapFee: 60,
15571567
PrepayAmount: 30,
1558-
MinerFee: 1,
1568+
MinerFee: 50,
15591569
},
15601570
suggestions: &Suggestions{
15611571
DisqualifiedChans: map[lnwire.ShortChannelID]Reason{
@@ -1650,7 +1660,9 @@ func TestBudgetWithLoopin(t *testing.T) {
16501660
rec = loop.OutRequest{
16511661
Amount: 7500,
16521662
OutgoingChanSet: loopdb.ChannelSet{chanID1.ToUint64()},
1653-
MaxMinerFee: scaleMinerFee(okQuote.MinerFee),
1663+
MaxMinerFee: scaleMaxMinerFee(
1664+
scaleMinerFee(testQuote.MinerFee),
1665+
),
16541666
MaxSwapFee: okQuote.SwapFee,
16551667
MaxPrepayAmount: okQuote.PrepayAmount,
16561668
SweepConfTarget: defaultConfTarget,

0 commit comments

Comments
 (0)