From d4b7f9a378f82f342b7e72594ae964ee2518fb5e Mon Sep 17 00:00:00 2001 From: carla Date: Tue, 2 Mar 2021 14:42:06 +0200 Subject: [PATCH] liquidity: update default fee setting to flat percentage --- liquidity/fees.go | 10 +++ liquidity/liquidity.go | 2 +- liquidity/liquidity_test.go | 142 ++++++++++++++++++++++++++---------- 3 files changed, 113 insertions(+), 41 deletions(-) diff --git a/liquidity/fees.go b/liquidity/fees.go index 443c34d..b3e3419 100644 --- a/liquidity/fees.go +++ b/liquidity/fees.go @@ -42,6 +42,10 @@ const ( // ensure that we will still be able to complete our swap in the case // of a severe fee spike. minerMultiplier = 100 + + // defaultFeePPM is the default percentage of swap amount that we + // allocate to fees, 2%. + defaultFeePPM = 20000 ) var ( @@ -245,6 +249,12 @@ type FeePortion struct { PartsPerMillion uint64 } +func defaultFeePortion() *FeePortion { + return &FeePortion{ + PartsPerMillion: defaultFeePPM, + } +} + // NewFeePortion creates a fee limit based on a flat percentage of swap amount. func NewFeePortion(ppm uint64) *FeePortion { return &FeePortion{ diff --git a/liquidity/liquidity.go b/liquidity/liquidity.go index 3046e6d..407aa5d 100644 --- a/liquidity/liquidity.go +++ b/liquidity/liquidity.go @@ -99,7 +99,7 @@ var ( PeerRules: make(map[route.Vertex]*ThresholdRule), FailureBackOff: defaultFailureBackoff, SweepConfTarget: loop.DefaultSweepConfTarget, - FeeLimit: defaultFeeCategoryLimit(), + FeeLimit: defaultFeePortion(), } // ErrZeroChannelID is returned if we get a rule for a 0 channel ID. diff --git a/liquidity/liquidity_test.go b/liquidity/liquidity_test.go index 5972acf..fad4390 100644 --- a/liquidity/liquidity_test.go +++ b/liquidity/liquidity_test.go @@ -50,15 +50,12 @@ var ( chanRule = NewThresholdRule(50, 0) testQuote = &loop.LoopOutQuote{ - SwapFee: btcutil.Amount(1), - PrepayAmount: btcutil.Amount(500), - MinerFee: btcutil.Amount(50), + SwapFee: btcutil.Amount(5), + PrepayAmount: btcutil.Amount(50), + MinerFee: btcutil.Amount(1), } - prepayFee = ppmToSat( - testQuote.PrepayAmount, defaultPrepayRoutingFeePPM, - ) - routingFee = ppmToSat(7500, defaultRoutingFeePPM) + prepayFee, routingFee = testPPMFees(defaultFeePPM, testQuote, 7500) // chan1Rec is the suggested swap for channel 1 when we use chanRule. chan1Rec = loop.OutRequest{ @@ -66,7 +63,7 @@ var ( OutgoingChanSet: loopdb.ChannelSet{chanID1.ToUint64()}, MaxPrepayRoutingFee: prepayFee, MaxSwapRoutingFee: routingFee, - MaxMinerFee: defaultMaximumMinerFee, + MaxMinerFee: scaleMinerFee(testQuote.MinerFee), MaxSwapFee: testQuote.SwapFee, MaxPrepayAmount: testQuote.PrepayAmount, SweepConfTarget: loop.DefaultSweepConfTarget, @@ -79,7 +76,7 @@ var ( OutgoingChanSet: loopdb.ChannelSet{chanID2.ToUint64()}, MaxPrepayRoutingFee: prepayFee, MaxSwapRoutingFee: routingFee, - MaxMinerFee: defaultMaximumMinerFee, + MaxMinerFee: scaleMinerFee(testQuote.MinerFee), MaxPrepayAmount: testQuote.PrepayAmount, MaxSwapFee: testQuote.SwapFee, SweepConfTarget: loop.DefaultSweepConfTarget, @@ -164,6 +161,21 @@ func testPPMFees(ppm uint64, quote *loop.LoopOutQuote, ) } +// applyFeeCategoryQuote returns a copy of the loop out request provided with +// fee categories updated to the quote and routing settings provided. +// nolint:unparam +func applyFeeCategoryQuote(req loop.OutRequest, minerFee btcutil.Amount, + prepayPPM, routingPPM uint64, quote loop.LoopOutQuote) loop.OutRequest { + + req.MaxPrepayRoutingFee = ppmToSat(quote.PrepayAmount, prepayPPM) + req.MaxSwapRoutingFee = ppmToSat(req.Amount, routingPPM) + req.MaxSwapFee = quote.SwapFee + req.MaxPrepayAmount = quote.PrepayAmount + req.MaxMinerFee = minerFee + + return req +} + // TestParameters tests getting and setting of parameters for our manager. func TestParameters(t *testing.T) { cfg, _ := newTestConfig() @@ -547,6 +559,12 @@ func TestRestrictedSuggestions(t *testing.T) { // TestSweepFeeLimit tests getting of swap suggestions when our estimated sweep // fee is above and below the configured limit. func TestSweepFeeLimit(t *testing.T) { + quote := &loop.LoopOutQuote{ + SwapFee: btcutil.Amount(1), + PrepayAmount: btcutil.Amount(500), + MinerFee: btcutil.Amount(50), + } + tests := []struct { name string feeRate chainfee.SatPerKWeight @@ -557,7 +575,11 @@ func TestSweepFeeLimit(t *testing.T) { feeRate: defaultSweepFeeRateLimit, suggestions: &Suggestions{ OutSwaps: []loop.OutRequest{ - chan1Rec, + applyFeeCategoryQuote( + chan1Rec, defaultMaximumMinerFee, + defaultPrepayRoutingFeePPM, + defaultRoutingFeePPM, *quote, + ), }, DisqualifiedChans: noneDisqualified, DisqualifiedPeers: noPeersDisqualified, @@ -581,6 +603,13 @@ func TestSweepFeeLimit(t *testing.T) { t.Run(testCase.name, func(t *testing.T) { cfg, lnd := newTestConfig() + cfg.LoopOutQuote = func(_ context.Context, + _ *loop.LoopOutQuoteRequest) (*loop.LoopOutQuote, + error) { + + return quote, nil + } + // Set our test case's fee rate for our mock lnd. lnd.SetFeeEstimate( loop.DefaultSweepConfTarget, testCase.feeRate, @@ -591,6 +620,8 @@ func TestSweepFeeLimit(t *testing.T) { } params := defaultParameters + params.FeeLimit = defaultFeeCategoryLimit() + params.ChannelRules = map[lnwire.ShortChannelID]*ThresholdRule{ chanID1: chanRule, } @@ -610,6 +641,9 @@ func TestSuggestSwaps(t *testing.T) { channel1, } + expectedAmt := btcutil.Amount(10000) + prepay, routing := testPPMFees(defaultFeePPM, testQuote, expectedAmt) + tests := []struct { name string channels []lndclient.ChannelInfo @@ -681,24 +715,18 @@ func TestSuggestSwaps(t *testing.T) { suggestions: &Suggestions{ OutSwaps: []loop.OutRequest{ { - Amount: 10000, + Amount: expectedAmt, OutgoingChanSet: loopdb.ChannelSet{ chanID1.ToUint64(), chanID2.ToUint64(), }, - MaxPrepayRoutingFee: ppmToSat( - testQuote.PrepayAmount, - defaultPrepayRoutingFeePPM, - ), - MaxSwapRoutingFee: ppmToSat( - 10000, - defaultRoutingFeePPM, - ), - MaxMinerFee: defaultMaximumMinerFee, - MaxSwapFee: testQuote.SwapFee, - MaxPrepayAmount: testQuote.PrepayAmount, - SweepConfTarget: loop.DefaultSweepConfTarget, - Initiator: autoloopSwapInitiator, + MaxPrepayRoutingFee: prepay, + MaxSwapRoutingFee: routing, + MaxMinerFee: scaleMinerFee(testQuote.MinerFee), + MaxSwapFee: testQuote.SwapFee, + MaxPrepayAmount: testQuote.PrepayAmount, + SweepConfTarget: loop.DefaultSweepConfTarget, + Initiator: autoloopSwapInitiator, }, }, DisqualifiedChans: noneDisqualified, @@ -736,6 +764,12 @@ func TestSuggestSwaps(t *testing.T) { // TestFeeLimits tests limiting of swap suggestions by fees. func TestFeeLimits(t *testing.T) { + quote := &loop.LoopOutQuote{ + SwapFee: btcutil.Amount(1), + PrepayAmount: btcutil.Amount(500), + MinerFee: btcutil.Amount(50), + } + tests := []struct { name string quote *loop.LoopOutQuote @@ -743,10 +777,14 @@ func TestFeeLimits(t *testing.T) { }{ { name: "fees ok", - quote: testQuote, + quote: quote, suggestions: &Suggestions{ OutSwaps: []loop.OutRequest{ - chan1Rec, + applyFeeCategoryQuote( + chan1Rec, defaultMaximumMinerFee, + defaultPrepayRoutingFeePPM, + defaultRoutingFeePPM, *quote, + ), }, DisqualifiedChans: noneDisqualified, DisqualifiedPeers: noPeersDisqualified, @@ -813,7 +851,10 @@ func TestFeeLimits(t *testing.T) { channel1, } + // Set our params to use individual fee limits. params := defaultParameters + params.FeeLimit = defaultFeeCategoryLimit() + params.ChannelRules = map[lnwire.ShortChannelID]*ThresholdRule{ chanID1: chanRule, } @@ -838,6 +879,21 @@ func TestFeeLimits(t *testing.T) { // amounts, we use our max miner fee to shift swap cost to values above/below // our budget, fixing our other fees at 114 sat for simplicity. func TestFeeBudget(t *testing.T) { + quote := &loop.LoopOutQuote{ + SwapFee: btcutil.Amount(1), + PrepayAmount: btcutil.Amount(500), + MinerFee: btcutil.Amount(50), + } + + chan1 := applyFeeCategoryQuote( + chan1Rec, 5000, defaultPrepayRoutingFeePPM, + defaultRoutingFeePPM, *quote, + ) + chan2 := applyFeeCategoryQuote( + chan2Rec, 5000, defaultPrepayRoutingFeePPM, + defaultRoutingFeePPM, *quote, + ) + tests := []struct { name string @@ -862,7 +918,7 @@ func TestFeeBudget(t *testing.T) { maxMinerFee: 5000, suggestions: &Suggestions{ OutSwaps: []loop.OutRequest{ - chan1Rec, chan2Rec, + chan1, chan2, }, DisqualifiedChans: noneDisqualified, DisqualifiedPeers: noPeersDisqualified, @@ -876,7 +932,7 @@ func TestFeeBudget(t *testing.T) { maxMinerFee: 5000, suggestions: &Suggestions{ OutSwaps: []loop.OutRequest{ - chan1Rec, + chan1, }, DisqualifiedChans: map[lnwire.ShortChannelID]Reason{ chanID2: ReasonBudgetInsufficient, @@ -895,7 +951,7 @@ func TestFeeBudget(t *testing.T) { }, suggestions: &Suggestions{ OutSwaps: []loop.OutRequest{ - chan1Rec, chan2Rec, + chan1, chan2, }, DisqualifiedChans: noneDisqualified, DisqualifiedPeers: noPeersDisqualified, @@ -912,7 +968,7 @@ func TestFeeBudget(t *testing.T) { }, suggestions: &Suggestions{ OutSwaps: []loop.OutRequest{ - chan1Rec, + chan1, }, DisqualifiedChans: map[lnwire.ShortChannelID]Reason{ chanID2: ReasonBudgetInsufficient, @@ -977,6 +1033,13 @@ func TestFeeBudget(t *testing.T) { return swaps, nil } + cfg.LoopOutQuote = func(_ context.Context, + _ *loop.LoopOutQuoteRequest) (*loop.LoopOutQuote, + error) { + + return quote, nil + } + // Set two channels that need swaps. lnd.Channels = []lndclient.ChannelInfo{ channel1, @@ -1141,18 +1204,17 @@ func TestSizeRestrictions(t *testing.T) { Maximum: 10000, } - outSwap = loop.OutRequest{ + prepay, routing = testPPMFees(defaultFeePPM, testQuote, 7000) + outSwap = loop.OutRequest{ Amount: 7000, OutgoingChanSet: loopdb.ChannelSet{chanID1.ToUint64()}, - MaxPrepayRoutingFee: prepayFee, - MaxSwapRoutingFee: ppmToSat( - 7000, defaultRoutingFeePPM, - ), - MaxMinerFee: defaultMaximumMinerFee, - MaxSwapFee: testQuote.SwapFee, - MaxPrepayAmount: testQuote.PrepayAmount, - SweepConfTarget: loop.DefaultSweepConfTarget, - Initiator: autoloopSwapInitiator, + MaxPrepayRoutingFee: prepay, + MaxSwapRoutingFee: routing, + MaxMinerFee: scaleMinerFee(testQuote.MinerFee), + MaxSwapFee: testQuote.SwapFee, + MaxPrepayAmount: testQuote.PrepayAmount, + SweepConfTarget: loop.DefaultSweepConfTarget, + Initiator: autoloopSwapInitiator, } )