From 7e9034b2ff26f5b1a5c8e42917359e6d492d617b Mon Sep 17 00:00:00 2001 From: carla Date: Wed, 30 Sep 2020 12:34:02 +0200 Subject: [PATCH 01/11] liquidity: return OutRequest from swap suggestions and set fees This commit updates swap suggestions to return loop out requests with sufficient fields populated so that a loop out can directly be dispatched from a suggestion. This requires setting of fees an min sweep conf targets (our htlc conf target and addresss will be set by the daemon's client rpc server if we do not provide them). We also do some test refactoring so that we can more easily test the suggest swaps endpoint. --- liquidity/liquidity.go | 73 +++++++++++++++-- liquidity/liquidity_test.go | 153 +++++++++++++++++++++--------------- loopd/swapclient_server.go | 12 ++- loopd/utils.go | 2 +- 4 files changed, 166 insertions(+), 74 deletions(-) diff --git a/liquidity/liquidity.go b/liquidity/liquidity.go index f90fa7c..47af6b1 100644 --- a/liquidity/liquidity.go +++ b/liquidity/liquidity.go @@ -9,10 +9,40 @@ import ( "strings" "sync" + "github.com/btcsuite/btcutil" "github.com/lightninglabs/lndclient" + "github.com/lightninglabs/loop" + "github.com/lightninglabs/loop/loopdb" "github.com/lightningnetwork/lnd/lnwire" ) +const ( + // FeeBase is the base that we use to express fees. + FeeBase = 1e6 + + // defaultSwapFeePPM is the default limit we place on swap fees, + // expressed as parts per million of swap volume, 0.5%. + defaultSwapFeePPM = 5000 + + // defaultRoutingFeePPM is the default limit we place on routing fees + // for the swap invoice, expressed as parts per million of swap volume, + // 1%. + defaultRoutingFeePPM = 10000 + + // defaultRoutingFeePPM is the default limit we place on routing fees + // for the prepay invoice, expressed as parts per million of prepay + // volume, 0.5%. + defaultPrepayRoutingFeePPM = 5000 + + // defaultMaximumMinerFee is the default limit we place on miner fees + // per swap. + defaultMaximumMinerFee = 15000 + + // defaultMaximumPrepay is the default limit we place on prepay + // invoices. + defaultMaximumPrepay = 30000 +) + var ( // ErrZeroChannelID is returned if we get a rule for a 0 channel ID. ErrZeroChannelID = fmt.Errorf("zero channel ID not allowed") @@ -25,8 +55,8 @@ type Config struct { // to loop out swaps. LoopOutRestrictions func(ctx context.Context) (*Restrictions, error) - // Lnd provides us with access to lnd's main rpc. - Lnd lndclient.LightningClient + // Lnd provides us with access to lnd's rpc servers. + Lnd *lndclient.LndServices } // Parameters is a set of parameters provided by the user which guide @@ -140,7 +170,7 @@ func cloneParameters(params Parameters) Parameters { // balance for the set of rules configured for the manager, failing if there are // no rules set. func (m *Manager) SuggestSwaps(ctx context.Context) ( - []*LoopOutRecommendation, error) { + []loop.OutRequest, error) { m.paramsLock.Lock() defer m.paramsLock.Unlock() @@ -151,7 +181,7 @@ func (m *Manager) SuggestSwaps(ctx context.Context) ( return nil, nil } - channels, err := m.cfg.Lnd.ListChannels(ctx) + channels, err := m.cfg.Lnd.Client.ListChannels(ctx) if err != nil { return nil, err } @@ -162,7 +192,7 @@ func (m *Manager) SuggestSwaps(ctx context.Context) ( return nil, err } - var suggestions []*LoopOutRecommendation + var suggestions []loop.OutRequest for _, channel := range channels { channelID := lnwire.NewShortChanIDFromInt(channel.ChannelID) rule, ok := m.params.ChannelRules[channelID] @@ -177,9 +207,40 @@ func (m *Manager) SuggestSwaps(ctx context.Context) ( // We can have nil suggestions in the case where no action is // required, so only add non-nil suggestions. if suggestion != nil { - suggestions = append(suggestions, suggestion) + outRequest := makeLoopOutRequest(suggestion) + suggestions = append(suggestions, outRequest) } } return suggestions, nil } + +// makeLoopOutRequest creates a loop out request from a suggestion, setting fee +// limits defined by our default fee values. +func makeLoopOutRequest(suggestion *LoopOutRecommendation) loop.OutRequest { + prepayMaxFee := ppmToSat( + defaultMaximumPrepay, defaultPrepayRoutingFeePPM, + ) + + routeMaxFee := ppmToSat(suggestion.Amount, defaultRoutingFeePPM) + maxSwapFee := ppmToSat(suggestion.Amount, defaultSwapFeePPM) + + return loop.OutRequest{ + Amount: suggestion.Amount, + OutgoingChanSet: loopdb.ChannelSet{ + suggestion.Channel.ToUint64(), + }, + MaxPrepayRoutingFee: prepayMaxFee, + MaxSwapRoutingFee: routeMaxFee, + MaxMinerFee: defaultMaximumMinerFee, + MaxSwapFee: maxSwapFee, + MaxPrepayAmount: defaultMaximumPrepay, + SweepConfTarget: loop.DefaultSweepConfTarget, + } +} + +// ppmToSat takes an amount and a measure of parts per million for the amount +// and returns the amount that the ppm represents. +func ppmToSat(amount btcutil.Amount, ppm int) btcutil.Amount { + return btcutil.Amount(uint64(amount) * uint64(ppm) / FeeBase) +} diff --git a/liquidity/liquidity_test.go b/liquidity/liquidity_test.go index 67fe1f8..386fc43 100644 --- a/liquidity/liquidity_test.go +++ b/liquidity/liquidity_test.go @@ -5,26 +5,64 @@ import ( "testing" "github.com/lightninglabs/lndclient" + "github.com/lightninglabs/loop" + "github.com/lightninglabs/loop/loopdb" "github.com/lightninglabs/loop/test" "github.com/lightningnetwork/lnd/lnwire" "github.com/stretchr/testify/require" ) +var ( + chanID1 = lnwire.NewShortChanIDFromInt(1) + chanID2 = lnwire.NewShortChanIDFromInt(2) + + channel1 = lndclient.ChannelInfo{ + ChannelID: chanID1.ToUint64(), + LocalBalance: 10000, + RemoteBalance: 0, + Capacity: 10000, + } + + // chanRule is a rule that produces chan1Rec. + chanRule = NewThresholdRule(50, 0) + + prepayFee = ppmToSat( + defaultMaximumPrepay, defaultPrepayRoutingFeePPM, + ) + routingFee = ppmToSat(7500, defaultRoutingFeePPM) + swapFee = ppmToSat(7500, defaultSwapFeePPM) + + // chan1Rec is the suggested swap for channel 1 when we use chanRule. + chan1Rec = loop.OutRequest{ + Amount: 7500, + OutgoingChanSet: loopdb.ChannelSet{chanID1.ToUint64()}, + MaxPrepayRoutingFee: prepayFee, + MaxSwapRoutingFee: routingFee, + MaxMinerFee: defaultMaximumMinerFee, + MaxSwapFee: swapFee, + MaxPrepayAmount: defaultMaximumPrepay, + SweepConfTarget: loop.DefaultSweepConfTarget, + } +) + // newTestConfig creates a default test config. -func newTestConfig() *Config { +func newTestConfig() (*Config, *test.LndMockServices) { + lnd := test.NewMockLnd() + return &Config{ LoopOutRestrictions: func(_ context.Context) (*Restrictions, error) { return NewRestrictions(1, 10000), nil }, - Lnd: test.NewMockLnd().Client, - } + Lnd: &lnd.LndServices, + }, lnd } // TestParameters tests getting and setting of parameters for our manager. func TestParameters(t *testing.T) { - manager := NewManager(newTestConfig()) + cfg, _ := newTestConfig() + manager := NewManager(cfg) chanID := lnwire.NewShortChanIDFromInt(1) @@ -68,62 +106,31 @@ func TestParameters(t *testing.T) { require.Equal(t, ErrZeroChannelID, err) } -// TestSuggestSwaps tests getting of swap suggestions. +// TestSuggestSwaps tests getting of swap suggestions based on the rules set for +// the liquidity manager and the current set of channel balances. func TestSuggestSwaps(t *testing.T) { - var ( - chanID1 = lnwire.NewShortChanIDFromInt(1) - chanID2 = lnwire.NewShortChanIDFromInt(2) - ) - tests := []struct { - name string - channels []lndclient.ChannelInfo - parameters Parameters - swaps []*LoopOutRecommendation + name string + rules map[lnwire.ShortChannelID]*ThresholdRule + swaps []loop.OutRequest }{ { - name: "no rules", - channels: nil, - parameters: newParameters(), + name: "no rules", + rules: map[lnwire.ShortChannelID]*ThresholdRule{}, }, { name: "loop out", - channels: []lndclient.ChannelInfo{ - { - ChannelID: 1, - Capacity: 1000, - LocalBalance: 1000, - RemoteBalance: 0, - }, - }, - parameters: Parameters{ - ChannelRules: map[lnwire.ShortChannelID]*ThresholdRule{ - chanID1: NewThresholdRule( - 10, 10, - ), - }, + rules: map[lnwire.ShortChannelID]*ThresholdRule{ + chanID1: chanRule, }, - swaps: []*LoopOutRecommendation{ - { - Channel: chanID1, - Amount: 500, - }, + swaps: []loop.OutRequest{ + chan1Rec, }, }, { name: "no rule for channel", - channels: []lndclient.ChannelInfo{ - { - ChannelID: 1, - Capacity: 1000, - LocalBalance: 0, - RemoteBalance: 1000, - }, - }, - parameters: Parameters{ - ChannelRules: map[lnwire.ShortChannelID]*ThresholdRule{ - chanID2: NewThresholdRule(10, 10), - }, + rules: map[lnwire.ShortChannelID]*ThresholdRule{ + chanID2: NewThresholdRule(10, 10), }, swaps: nil, }, @@ -133,23 +140,43 @@ func TestSuggestSwaps(t *testing.T) { testCase := testCase t.Run(testCase.name, func(t *testing.T) { - cfg := newTestConfig() - - // Create a mock lnd with the set of channels set in our - // test case. - mock := test.NewMockLnd() - mock.Channels = testCase.channels - cfg.Lnd = mock.Client + cfg, lnd := newTestConfig() - manager := NewManager(cfg) + channels := []lndclient.ChannelInfo{ + channel1, + } - // Set our test case parameters. - err := manager.SetParameters(testCase.parameters) - require.NoError(t, err) - - swaps, err := manager.SuggestSwaps(context.Background()) - require.NoError(t, err) - require.Equal(t, testCase.swaps, swaps) + testSuggestSwaps( + t, cfg, lnd, channels, testCase.rules, + testCase.swaps, + ) }) } } + +// testSuggestSwaps tests getting swap suggestions. +func testSuggestSwaps(t *testing.T, cfg *Config, lnd *test.LndMockServices, + channels []lndclient.ChannelInfo, + rules map[lnwire.ShortChannelID]*ThresholdRule, + expected []loop.OutRequest) { + + t.Parallel() + + // Create a mock lnd with the set of channels set in our test case and + // update our test case lnd to use these channels. + lnd.Channels = channels + + // Create a new manager, get our current set of parameters and update + // them to use the rules set by the test. + manager := NewManager(cfg) + + currentParams := manager.GetParameters() + currentParams.ChannelRules = rules + + err := manager.SetParameters(currentParams) + require.NoError(t, err) + + actual, err := manager.SuggestSwaps(context.Background()) + require.NoError(t, err) + require.Equal(t, expected, actual) +} diff --git a/loopd/swapclient_server.go b/loopd/swapclient_server.go index b7a28c2..64578e5 100644 --- a/loopd/swapclient_server.go +++ b/loopd/swapclient_server.go @@ -646,10 +646,14 @@ func (s *swapClientServer) SuggestSwaps(ctx context.Context, for _, swap := range swaps { loopOut = append(loopOut, &looprpc.LoopOutRequest{ - Amt: int64(swap.Amount), - OutgoingChanSet: []uint64{ - swap.Channel.ToUint64(), - }, + Amt: int64(swap.Amount), + OutgoingChanSet: swap.OutgoingChanSet, + MaxSwapFee: int64(swap.MaxSwapFee), + MaxMinerFee: int64(swap.MaxMinerFee), + MaxPrepayAmt: int64(swap.MaxPrepayAmount), + MaxSwapRoutingFee: int64(swap.MaxSwapRoutingFee), + MaxPrepayRoutingFee: int64(swap.MaxPrepayRoutingFee), + SweepConfTarget: swap.SweepConfTarget, }) } diff --git a/loopd/utils.go b/loopd/utils.go index 42278bc..c283e91 100644 --- a/loopd/utils.go +++ b/loopd/utils.go @@ -46,7 +46,7 @@ func getLiquidityManager(client *loop.Client) *liquidity.Manager { outTerms.MinSwapAmount, outTerms.MaxSwapAmount, ), nil }, - Lnd: client.LndServices.Client, + Lnd: client.LndServices, } return liquidity.NewManager(mngrCfg) From ad8b5d05520a967361fbfb5cd9fd8a5fecb900dc Mon Sep 17 00:00:00 2001 From: carla Date: Wed, 30 Sep 2020 12:34:05 +0200 Subject: [PATCH 02/11] liquidity: add clock for mocking time in tests --- go.mod | 1 + liquidity/liquidity.go | 4 ++++ liquidity/liquidity_test.go | 7 ++++++- loopd/utils.go | 4 +++- 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 61dc928..6dee319 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/lightninglabs/protobuf-hex-display v1.3.3-0.20191212020323-b444784ce75d github.com/lightningnetwork/lnd v0.11.1-beta.rc3 github.com/lightningnetwork/lnd/cert v1.0.3 + github.com/lightningnetwork/lnd/clock v1.0.1 github.com/lightningnetwork/lnd/queue v1.0.4 github.com/stretchr/testify v1.5.1 github.com/urfave/cli v1.20.0 diff --git a/liquidity/liquidity.go b/liquidity/liquidity.go index 47af6b1..bc5be2a 100644 --- a/liquidity/liquidity.go +++ b/liquidity/liquidity.go @@ -13,6 +13,7 @@ import ( "github.com/lightninglabs/lndclient" "github.com/lightninglabs/loop" "github.com/lightninglabs/loop/loopdb" + "github.com/lightningnetwork/lnd/clock" "github.com/lightningnetwork/lnd/lnwire" ) @@ -57,6 +58,9 @@ type Config struct { // Lnd provides us with access to lnd's rpc servers. Lnd *lndclient.LndServices + + // Clock allows easy mocking of time in unit tests. + Clock clock.Clock } // Parameters is a set of parameters provided by the user which guide diff --git a/liquidity/liquidity_test.go b/liquidity/liquidity_test.go index 386fc43..36492b8 100644 --- a/liquidity/liquidity_test.go +++ b/liquidity/liquidity_test.go @@ -3,16 +3,20 @@ package liquidity import ( "context" "testing" + "time" "github.com/lightninglabs/lndclient" "github.com/lightninglabs/loop" "github.com/lightninglabs/loop/loopdb" "github.com/lightninglabs/loop/test" + "github.com/lightningnetwork/lnd/clock" "github.com/lightningnetwork/lnd/lnwire" "github.com/stretchr/testify/require" ) var ( + testTime = time.Date(2020, 02, 13, 0, 0, 0, 0, time.UTC) + chanID1 = lnwire.NewShortChanIDFromInt(1) chanID2 = lnwire.NewShortChanIDFromInt(2) @@ -55,7 +59,8 @@ func newTestConfig() (*Config, *test.LndMockServices) { return NewRestrictions(1, 10000), nil }, - Lnd: &lnd.LndServices, + Lnd: &lnd.LndServices, + Clock: clock.NewTestClock(testTime), }, lnd } diff --git a/loopd/utils.go b/loopd/utils.go index c283e91..8b74a66 100644 --- a/loopd/utils.go +++ b/loopd/utils.go @@ -7,6 +7,7 @@ import ( "github.com/lightninglabs/lndclient" "github.com/lightninglabs/loop" "github.com/lightninglabs/loop/liquidity" + "github.com/lightningnetwork/lnd/clock" ) // getClient returns an instance of the swap client. @@ -46,7 +47,8 @@ func getLiquidityManager(client *loop.Client) *liquidity.Manager { outTerms.MinSwapAmount, outTerms.MaxSwapAmount, ), nil }, - Lnd: client.LndServices, + Lnd: client.LndServices, + Clock: clock.NewDefaultClock(), } return liquidity.NewManager(mngrCfg) From 559abd1eea90e6fb27fabb5a08971966aca12f79 Mon Sep 17 00:00:00 2001 From: carla Date: Wed, 30 Sep 2020 12:34:06 +0200 Subject: [PATCH 03/11] liquidity: add default parameters struct --- liquidity/liquidity.go | 15 +++++++-------- liquidity/liquidity_test.go | 11 +++++------ 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/liquidity/liquidity.go b/liquidity/liquidity.go index bc5be2a..0829363 100644 --- a/liquidity/liquidity.go +++ b/liquidity/liquidity.go @@ -45,6 +45,12 @@ const ( ) var ( + // defaultParameters contains the default parameters that we start our + // liquidity manger with. + defaultParameters = Parameters{ + ChannelRules: make(map[lnwire.ShortChannelID]*ThresholdRule), + } + // ErrZeroChannelID is returned if we get a rule for a 0 channel ID. ErrZeroChannelID = fmt.Errorf("zero channel ID not allowed") ) @@ -71,13 +77,6 @@ type Parameters struct { ChannelRules map[lnwire.ShortChannelID]*ThresholdRule } -// newParameters creates an empty set of parameters. -func newParameters() Parameters { - return Parameters{ - ChannelRules: make(map[lnwire.ShortChannelID]*ThresholdRule), - } -} - // String returns the string representation of our parameters. func (p Parameters) String() string { channelRules := make([]string, 0, len(p.ChannelRules)) @@ -127,7 +126,7 @@ type Manager struct { func NewManager(cfg *Config) *Manager { return &Manager{ cfg: cfg, - params: newParameters(), + params: defaultParameters, } } diff --git a/liquidity/liquidity_test.go b/liquidity/liquidity_test.go index 36492b8..cba9ca3 100644 --- a/liquidity/liquidity_test.go +++ b/liquidity/liquidity_test.go @@ -73,7 +73,7 @@ func TestParameters(t *testing.T) { // Start with the case where we have no rules set. startParams := manager.GetParameters() - require.Equal(t, newParameters(), startParams) + require.Equal(t, defaultParameters, startParams) // Mutate the parameters returned by our get function. startParams.ChannelRules[chanID] = NewThresholdRule(1, 1) @@ -81,15 +81,14 @@ func TestParameters(t *testing.T) { // Make sure that we have not mutated the liquidity manager's params // by making this change. params := manager.GetParameters() - require.Equal(t, newParameters(), params) + require.Equal(t, defaultParameters, params) // Provide a valid set of parameters and validate assert that they are // set. originalRule := NewThresholdRule(10, 10) - expected := Parameters{ - ChannelRules: map[lnwire.ShortChannelID]*ThresholdRule{ - chanID: originalRule, - }, + expected := defaultParameters + expected.ChannelRules = map[lnwire.ShortChannelID]*ThresholdRule{ + chanID: originalRule, } err := manager.SetParameters(expected) From 7740231bac0796bde6507f9bc6c3e08cbea2c569 Mon Sep 17 00:00:00 2001 From: carla Date: Wed, 30 Sep 2020 12:34:07 +0200 Subject: [PATCH 04/11] liquidity: make swap suggestions aware of ongoing swaps --- liquidity/liquidity.go | 122 ++++++++++++++++++++++++++++- liquidity/liquidity_test.go | 151 ++++++++++++++++++++++++++++++++++++ liquidity/log.go | 26 +++++++ loopd/log.go | 2 + loopd/utils.go | 6 +- 5 files changed, 301 insertions(+), 6 deletions(-) create mode 100644 liquidity/log.go diff --git a/liquidity/liquidity.go b/liquidity/liquidity.go index 0829363..1fd8f68 100644 --- a/liquidity/liquidity.go +++ b/liquidity/liquidity.go @@ -1,6 +1,12 @@ // Package liquidity is responsible for monitoring our node's liquidity. It // allows setting of a liquidity rule which describes the desired liquidity // balance on a per-channel basis. +// +// Swap suggestions are limited to channels that are not currently being used +// for a pending swap. If we are currently processing an unrestricted swap (ie, +// a loop out with no outgoing channel targets set or a loop in with no last +// hop set), we will not suggest any swaps because these swaps will shift the +// balances of our channels in ways we can't predict. package liquidity import ( @@ -15,6 +21,7 @@ import ( "github.com/lightninglabs/loop/loopdb" "github.com/lightningnetwork/lnd/clock" "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/routing/route" ) const ( @@ -65,6 +72,12 @@ type Config struct { // Lnd provides us with access to lnd's rpc servers. Lnd *lndclient.LndServices + // ListLoopOut returns all of the loop our swaps stored on disk. + ListLoopOut func() ([]*loopdb.LoopOut, error) + + // ListLoopIn returns all of the loop in swaps stored on disk. + ListLoopIn func() ([]*loopdb.LoopIn, error) + // Clock allows easy mocking of time in unit tests. Clock clock.Clock } @@ -184,19 +197,32 @@ func (m *Manager) SuggestSwaps(ctx context.Context) ( return nil, nil } - channels, err := m.cfg.Lnd.Client.ListChannels(ctx) + // Get the current server side restrictions. + outRestrictions, err := m.cfg.LoopOutRestrictions(ctx) if err != nil { return nil, err } - // Get the current server side restrictions. - outRestrictions, err := m.cfg.LoopOutRestrictions(ctx) + // List our current set of swaps so that we can determine which channels + // are already being utilized by swaps. Note that these calls may race + // with manual initiation of swaps. + loopOut, err := m.cfg.ListLoopOut() + if err != nil { + return nil, err + } + + loopIn, err := m.cfg.ListLoopIn() + if err != nil { + return nil, err + } + + eligible, err := m.getEligibleChannels(ctx, loopOut, loopIn) if err != nil { return nil, err } var suggestions []loop.OutRequest - for _, channel := range channels { + for _, channel := range eligible { channelID := lnwire.NewShortChanIDFromInt(channel.ChannelID) rule, ok := m.params.ChannelRules[channelID] if !ok { @@ -242,6 +268,94 @@ func makeLoopOutRequest(suggestion *LoopOutRecommendation) loop.OutRequest { } } +// getEligibleChannels takes lists of our existing loop out and in swaps, and +// gets a list of channels that are not currently being utilized for a swap. +// If an unrestricted swap is ongoing, we return an empty set of channels +// because we don't know which channels balances it will affect. +func (m *Manager) getEligibleChannels(ctx context.Context, + loopOut []*loopdb.LoopOut, loopIn []*loopdb.LoopIn) ( + []lndclient.ChannelInfo, error) { + + var ( + existingOut = make(map[lnwire.ShortChannelID]bool) + existingIn = make(map[route.Vertex]bool) + ) + + for _, out := range loopOut { + var ( + state = out.State().State + chanSet = out.Contract.OutgoingChanSet + ) + + // Skip completed swaps, they can't affect our channel balances. + if state.Type() != loopdb.StateTypePending { + continue + } + + if len(chanSet) == 0 { + log.Debugf("Ongoing unrestricted loop out: "+ + "%v, no suggestions at present", out.Hash) + + return nil, nil + } + + for _, id := range chanSet { + chanID := lnwire.NewShortChanIDFromInt(id) + existingOut[chanID] = true + } + } + + for _, in := range loopIn { + // Skip completed swaps, they can't affect our channel balances. + if in.State().State.Type() != loopdb.StateTypePending { + continue + } + + if in.Contract.LastHop == nil { + log.Debugf("Ongoing unrestricted loop in: "+ + "%v, no suggestions at present", in.Hash) + + return nil, nil + } + + existingIn[*in.Contract.LastHop] = true + } + + channels, err := m.cfg.Lnd.Client.ListChannels(ctx) + if err != nil { + return nil, err + } + + // Run through our set of channels and skip over any channels that + // are currently being utilized by a restricted swap (where restricted + // means that a loop out limited channels, or a loop in limited last + // hop). + var eligible []lndclient.ChannelInfo + for _, channel := range channels { + shortID := lnwire.NewShortChanIDFromInt(channel.ChannelID) + + if existingOut[shortID] { + log.Debugf("Channel: %v not eligible for "+ + "suggestions, ongoing loop out utilizing "+ + "channel", channel.ChannelID) + + continue + } + + if existingIn[channel.PubKeyBytes] { + log.Debugf("Channel: %v not eligible for "+ + "suggestions, ongoing loop in utilizing "+ + "peer", channel.ChannelID) + + continue + } + + eligible = append(eligible, channel) + } + + return eligible, nil +} + // ppmToSat takes an amount and a measure of parts per million for the amount // and returns the amount that the ppm represents. func ppmToSat(amount btcutil.Amount, ppm int) btcutil.Amount { diff --git a/liquidity/liquidity_test.go b/liquidity/liquidity_test.go index cba9ca3..b8413ec 100644 --- a/liquidity/liquidity_test.go +++ b/liquidity/liquidity_test.go @@ -11,6 +11,7 @@ import ( "github.com/lightninglabs/loop/test" "github.com/lightningnetwork/lnd/clock" "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/routing/route" "github.com/stretchr/testify/require" ) @@ -20,8 +21,20 @@ var ( chanID1 = lnwire.NewShortChanIDFromInt(1) chanID2 = lnwire.NewShortChanIDFromInt(2) + peer1 = route.Vertex{1} + peer2 = route.Vertex{2} + channel1 = lndclient.ChannelInfo{ ChannelID: chanID1.ToUint64(), + PubKeyBytes: peer1, + LocalBalance: 10000, + RemoteBalance: 0, + Capacity: 10000, + } + + channel2 = lndclient.ChannelInfo{ + ChannelID: chanID2.ToUint64(), + PubKeyBytes: peer2, LocalBalance: 10000, RemoteBalance: 0, Capacity: 10000, @@ -47,6 +60,28 @@ var ( MaxPrepayAmount: defaultMaximumPrepay, SweepConfTarget: loop.DefaultSweepConfTarget, } + + // chan2Rec is the suggested swap for channel 2 when we use chanRule. + chan2Rec = loop.OutRequest{ + Amount: 7500, + OutgoingChanSet: loopdb.ChannelSet{chanID2.ToUint64()}, + MaxPrepayRoutingFee: prepayFee, + MaxSwapRoutingFee: routingFee, + MaxMinerFee: defaultMaximumMinerFee, + MaxSwapFee: swapFee, + MaxPrepayAmount: defaultMaximumPrepay, + SweepConfTarget: loop.DefaultSweepConfTarget, + } + + // chan1Out is a contract that uses channel 1, used to represent on + // disk swap using chan 1. + chan1Out = &loopdb.LoopOutContract{ + OutgoingChanSet: loopdb.ChannelSet( + []uint64{ + chanID1.ToUint64(), + }, + ), + } ) // newTestConfig creates a default test config. @@ -61,6 +96,12 @@ func newTestConfig() (*Config, *test.LndMockServices) { }, Lnd: &lnd.LndServices, Clock: clock.NewTestClock(testTime), + ListLoopOut: func() ([]*loopdb.LoopOut, error) { + return nil, nil + }, + ListLoopIn: func() ([]*loopdb.LoopIn, error) { + return nil, nil + }, }, lnd } @@ -110,6 +151,116 @@ func TestParameters(t *testing.T) { require.Equal(t, ErrZeroChannelID, err) } +// TestRestrictedSuggestions tests getting of swap suggestions when we have +// other in-flight swaps. We setup our manager with a set of channels and rules +// that require a loop out swap, focusing on the filtering our of channels that +// are in use for in-flight swaps. +func TestRestrictedSuggestions(t *testing.T) { + tests := []struct { + name string + channels []lndclient.ChannelInfo + loopOut []*loopdb.LoopOut + loopIn []*loopdb.LoopIn + expected []loop.OutRequest + }{ + { + name: "no existing swaps", + channels: []lndclient.ChannelInfo{ + channel1, + }, + loopOut: nil, + loopIn: nil, + expected: []loop.OutRequest{ + chan1Rec, + }, + }, + { + name: "unrestricted loop out", + channels: []lndclient.ChannelInfo{ + channel1, channel2, + }, + loopOut: []*loopdb.LoopOut{ + { + Contract: &loopdb.LoopOutContract{ + OutgoingChanSet: nil, + }, + }, + }, + expected: nil, + }, + { + name: "unrestricted loop in", + channels: []lndclient.ChannelInfo{ + channel1, channel2, + }, + loopIn: []*loopdb.LoopIn{ + { + Contract: &loopdb.LoopInContract{ + LastHop: nil, + }, + }, + }, + expected: nil, + }, + { + name: "restricted loop out", + channels: []lndclient.ChannelInfo{ + channel1, channel2, + }, + loopOut: []*loopdb.LoopOut{ + { + Contract: chan1Out, + }, + }, + expected: []loop.OutRequest{ + chan2Rec, + }, + }, + { + name: "restricted loop in", + channels: []lndclient.ChannelInfo{ + channel1, channel2, + }, + loopIn: []*loopdb.LoopIn{ + { + Contract: &loopdb.LoopInContract{ + LastHop: &peer2, + }, + }, + }, + expected: []loop.OutRequest{ + chan1Rec, + }, + }, + } + + for _, testCase := range tests { + testCase := testCase + + t.Run(testCase.name, func(t *testing.T) { + // Create a manager config which will return the test + // case's set of existing swaps. + cfg, lnd := newTestConfig() + cfg.ListLoopOut = func() ([]*loopdb.LoopOut, error) { + return testCase.loopOut, nil + } + cfg.ListLoopIn = func() ([]*loopdb.LoopIn, error) { + return testCase.loopIn, nil + } + + rules := map[lnwire.ShortChannelID]*ThresholdRule{ + chanID1: chanRule, + chanID2: chanRule, + } + + testSuggestSwaps( + t, cfg, lnd, testCase.channels, rules, + testCase.expected, + ) + }) + } +} + // TestSuggestSwaps tests getting of swap suggestions based on the rules set for // the liquidity manager and the current set of channel balances. func TestSuggestSwaps(t *testing.T) { diff --git a/liquidity/log.go b/liquidity/log.go new file mode 100644 index 0000000..39a3ebe --- /dev/null +++ b/liquidity/log.go @@ -0,0 +1,26 @@ +package liquidity + +import ( + "github.com/btcsuite/btclog" + "github.com/lightningnetwork/lnd/build" +) + +// Subsystem defines the sub system name of this package. +const Subsystem = "LQDY" + +// log is a logger that is initialized with no output filters. This +// means the package will not perform any logging by default until the caller +// requests it. +var log btclog.Logger + +// The default amount of logging is none. +func init() { + UseLogger(build.NewSubLogger(Subsystem, nil)) +} + +// UseLogger uses a specified Logger to output package logging info. +// This should be used in preference to SetLogWriter if the caller is also +// using btclog. +func UseLogger(logger btclog.Logger) { + log = logger +} diff --git a/loopd/log.go b/loopd/log.go index 2abc58e..5dcf70e 100644 --- a/loopd/log.go +++ b/loopd/log.go @@ -4,6 +4,7 @@ import ( "github.com/btcsuite/btclog" "github.com/lightninglabs/lndclient" "github.com/lightninglabs/loop" + "github.com/lightninglabs/loop/liquidity" "github.com/lightninglabs/loop/loopdb" "github.com/lightninglabs/loop/lsat" "github.com/lightningnetwork/lnd/build" @@ -21,6 +22,7 @@ func init() { addSubLogger("LNDC", lndclient.UseLogger) addSubLogger("STORE", loopdb.UseLogger) addSubLogger(lsat.Subsystem, lsat.UseLogger) + addSubLogger(liquidity.Subsystem, liquidity.UseLogger) } // addSubLogger is a helper method to conveniently create and register the diff --git a/loopd/utils.go b/loopd/utils.go index 8b74a66..f8512cb 100644 --- a/loopd/utils.go +++ b/loopd/utils.go @@ -47,8 +47,10 @@ func getLiquidityManager(client *loop.Client) *liquidity.Manager { outTerms.MinSwapAmount, outTerms.MaxSwapAmount, ), nil }, - Lnd: client.LndServices, - Clock: clock.NewDefaultClock(), + Lnd: client.LndServices, + Clock: clock.NewDefaultClock(), + ListLoopOut: client.Store.FetchLoopOutSwaps, + ListLoopIn: client.Store.FetchLoopInSwaps, } return liquidity.NewManager(mngrCfg) From 64422ce26a49cc468536df87b321123658f169ed Mon Sep 17 00:00:00 2001 From: carla Date: Wed, 30 Sep 2020 12:34:08 +0200 Subject: [PATCH 05/11] liquidity: exclude recently failed swaps from suggestions --- liquidity/liquidity.go | 72 ++++++++++++++++++++++++++++--- liquidity/liquidity_test.go | 85 ++++++++++++++++++++++++++++++++++++- 2 files changed, 149 insertions(+), 8 deletions(-) diff --git a/liquidity/liquidity.go b/liquidity/liquidity.go index 1fd8f68..d95576a 100644 --- a/liquidity/liquidity.go +++ b/liquidity/liquidity.go @@ -14,6 +14,7 @@ import ( "fmt" "strings" "sync" + "time" "github.com/btcsuite/btcutil" "github.com/lightninglabs/lndclient" @@ -25,6 +26,10 @@ import ( ) const ( + // defaultFailureBackoff is the default amount of time we backoff if + // a channel is part of a temporarily failed swap. + defaultFailureBackoff = time.Hour * 24 + // FeeBase is the base that we use to express fees. FeeBase = 1e6 @@ -55,7 +60,8 @@ var ( // defaultParameters contains the default parameters that we start our // liquidity manger with. defaultParameters = Parameters{ - ChannelRules: make(map[lnwire.ShortChannelID]*ThresholdRule), + ChannelRules: make(map[lnwire.ShortChannelID]*ThresholdRule), + FailureBackOff: defaultFailureBackoff, } // ErrZeroChannelID is returned if we get a rule for a 0 channel ID. @@ -85,6 +91,12 @@ type Config struct { // Parameters is a set of parameters provided by the user which guide // how we assess liquidity. type Parameters struct { + // FailureBackOff is the amount of time that we require passes after a + // channel has been part of a failed loop out swap before we suggest + // using it again. + // TODO(carla): add exponential backoff + FailureBackOff time.Duration + // ChannelRules maps a short channel ID to a rule that describes how we // would like liquidity to be managed. ChannelRules map[lnwire.ShortChannelID]*ThresholdRule @@ -100,8 +112,8 @@ func (p Parameters) String() string { ) } - return fmt.Sprintf("channel rules: %v", - strings.Join(channelRules, ",")) + return fmt.Sprintf("channel rules: %v, failure backoff: %v", + strings.Join(channelRules, ","), p.FailureBackOff) } // validate checks whether a set of parameters is valid. @@ -169,10 +181,11 @@ func (m *Manager) SetParameters(params Parameters) error { // cannot mutate our parameters. Although our parameters struct itself is not // a reference, we still need to clone the contents of maps. func cloneParameters(params Parameters) Parameters { - paramCopy := Parameters{ - ChannelRules: make(map[lnwire.ShortChannelID]*ThresholdRule, - len(params.ChannelRules)), - } + paramCopy := params + paramCopy.ChannelRules = make( + map[lnwire.ShortChannelID]*ThresholdRule, + len(params.ChannelRules), + ) for channel, rule := range params.ChannelRules { ruleCopy := *rule @@ -279,15 +292,51 @@ func (m *Manager) getEligibleChannels(ctx context.Context, var ( existingOut = make(map[lnwire.ShortChannelID]bool) existingIn = make(map[route.Vertex]bool) + failedOut = make(map[lnwire.ShortChannelID]time.Time) ) + // Failure cutoff is the most recent failure timestamp we will still + // consider a channel eligible. Any channels involved in swaps that have + // failed since this point will not be considered. + failureCutoff := m.cfg.Clock.Now().Add(m.params.FailureBackOff * -1) + for _, out := range loopOut { var ( state = out.State().State chanSet = out.Contract.OutgoingChanSet ) + // If a loop out swap failed due to off chain payment after our + // failure cutoff, we add all of its channels to a set of + // recently failed channels. It is possible that not all of + // these channels were used for the swap, but we play it safe + // and back off for all of them. + // + // We only backoff for off temporary failures. In the case of + // chain payment failures, our swap failed to route and we do + // not want to repeatedly try to route through bad channels + // which remain unbalanced because they cannot route a swap, so + // we backoff. + if state == loopdb.StateFailOffchainPayments { + failedAt := out.LastUpdate().Time + + if failedAt.After(failureCutoff) { + for _, id := range chanSet { + chanID := lnwire.NewShortChanIDFromInt( + id, + ) + + failedOut[chanID] = failedAt + } + } + } + // Skip completed swaps, they can't affect our channel balances. + // Swaps that fail temporarily are considered to be in a pending + // state, so we will also check that channels being used by + // these swaps. This is important, because a temporarily failed + // swap could be re-dispatched on restart, affecting our + // balances. if state.Type() != loopdb.StateTypePending { continue } @@ -334,6 +383,15 @@ func (m *Manager) getEligibleChannels(ctx context.Context, for _, channel := range channels { shortID := lnwire.NewShortChanIDFromInt(channel.ChannelID) + lastFail, recentFail := failedOut[shortID] + if recentFail { + log.Debugf("Channel: %v not eligible for "+ + "suggestions, was part of a failed swap at: %v", + channel.ChannelID, lastFail) + + continue + } + if existingOut[shortID] { log.Debugf("Channel: %v not eligible for "+ "suggestions, ongoing loop out utilizing "+ diff --git a/liquidity/liquidity_test.go b/liquidity/liquidity_test.go index b8413ec..a347272 100644 --- a/liquidity/liquidity_test.go +++ b/liquidity/liquidity_test.go @@ -154,8 +154,38 @@ func TestParameters(t *testing.T) { // TestRestrictedSuggestions tests getting of swap suggestions when we have // other in-flight swaps. We setup our manager with a set of channels and rules // that require a loop out swap, focusing on the filtering our of channels that -// are in use for in-flight swaps. +// are in use for in-flight swaps, or those which have recently failed. func TestRestrictedSuggestions(t *testing.T) { + var ( + failedWithinTimeout = &loopdb.LoopEvent{ + SwapStateData: loopdb.SwapStateData{ + State: loopdb.StateFailOffchainPayments, + }, + Time: testTime, + } + + failedBeforeBackoff = &loopdb.LoopEvent{ + SwapStateData: loopdb.SwapStateData{ + State: loopdb.StateFailOffchainPayments, + }, + Time: testTime.Add( + defaultFailureBackoff * -1, + ), + } + + // failedTemporary is a swap that failed outside of our backoff + // period, but we still want to back off because the swap is + // considered pending. + failedTemporary = &loopdb.LoopEvent{ + SwapStateData: loopdb.SwapStateData{ + State: loopdb.StateFailTemporary, + }, + Time: testTime.Add( + defaultFailureBackoff * -3, + ), + } + ) + tests := []struct { name string channels []lndclient.ChannelInfo @@ -232,6 +262,59 @@ func TestRestrictedSuggestions(t *testing.T) { chan1Rec, }, }, + { + name: "swap failed recently", + channels: []lndclient.ChannelInfo{ + channel1, + }, + loopOut: []*loopdb.LoopOut{ + { + Contract: chan1Out, + Loop: loopdb.Loop{ + Events: []*loopdb.LoopEvent{ + failedWithinTimeout, + }, + }, + }, + }, + expected: nil, + }, + { + name: "swap failed before cutoff", + channels: []lndclient.ChannelInfo{ + channel1, + }, + loopOut: []*loopdb.LoopOut{ + { + Contract: chan1Out, + Loop: loopdb.Loop{ + Events: []*loopdb.LoopEvent{ + failedBeforeBackoff, + }, + }, + }, + }, + expected: []loop.OutRequest{ + chan1Rec, + }, + }, + { + name: "temporary failure", + channels: []lndclient.ChannelInfo{ + channel1, + }, + loopOut: []*loopdb.LoopOut{ + { + Contract: chan1Out, + Loop: loopdb.Loop{ + Events: []*loopdb.LoopEvent{ + failedTemporary, + }, + }, + }, + }, + expected: nil, + }, } for _, testCase := range tests { From 1d8609bae3600f231206dbf47f2e10cd2386976a Mon Sep 17 00:00:00 2001 From: carla Date: Wed, 30 Sep 2020 12:34:08 +0200 Subject: [PATCH 06/11] liquidity: add sweep fee limit and confirmations to suggestions To decide whether we event want to attempt a swap, we add a fee limit that we check against our estimate for the current number of confirmations we want our sweep to confirm in. If fees are higher than this limit, we do not suggest swaps. --- liquidity/liquidity.go | 95 +++++++++++++++++++++++++++++++++---- liquidity/liquidity_test.go | 56 ++++++++++++++++++++++ loopd/utils.go | 9 ++-- 3 files changed, 146 insertions(+), 14 deletions(-) diff --git a/liquidity/liquidity.go b/liquidity/liquidity.go index d95576a..4c35df7 100644 --- a/liquidity/liquidity.go +++ b/liquidity/liquidity.go @@ -7,6 +7,12 @@ // a loop out with no outgoing channel targets set or a loop in with no last // hop set), we will not suggest any swaps because these swaps will shift the // balances of our channels in ways we can't predict. +// +// Fee restrictions are placed on swap suggestions to ensure that we only +// suggest swaps that fit the configured fee preferences. +// - Sweep Fee Rate Limit: the maximum sat/vByte fee estimate for our sweep +// transaction to confirm within our configured number of confirmations +// that we will suggest swaps for. package liquidity import ( @@ -21,6 +27,7 @@ import ( "github.com/lightninglabs/loop" "github.com/lightninglabs/loop/loopdb" "github.com/lightningnetwork/lnd/clock" + "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing/route" ) @@ -54,18 +61,30 @@ const ( // defaultMaximumPrepay is the default limit we place on prepay // invoices. defaultMaximumPrepay = 30000 + + // defaultSweepFeeRateLimit is the default limit we place on estimated + // sweep fees, (750 * 4 /1000 = 3 sat/vByte). + defaultSweepFeeRateLimit = chainfee.SatPerKWeight(750) ) var ( // defaultParameters contains the default parameters that we start our // liquidity manger with. defaultParameters = Parameters{ - ChannelRules: make(map[lnwire.ShortChannelID]*ThresholdRule), - FailureBackOff: defaultFailureBackoff, + ChannelRules: make(map[lnwire.ShortChannelID]*ThresholdRule), + FailureBackOff: defaultFailureBackoff, + SweepFeeRateLimit: defaultSweepFeeRateLimit, + SweepConfTarget: loop.DefaultSweepConfTarget, } // ErrZeroChannelID is returned if we get a rule for a 0 channel ID. ErrZeroChannelID = fmt.Errorf("zero channel ID not allowed") + + // ErrInvalidSweepFeeRateLimit is returned if an invalid sweep fee limit + // is set. + ErrInvalidSweepFeeRateLimit = fmt.Errorf("sweep fee rate limit must "+ + "be > %v sat/vByte", + satPerKwToSatPerVByte(chainfee.AbsoluteFeePerKwFloor)) ) // Config contains the external functionality required to run the @@ -86,6 +105,10 @@ type Config struct { // Clock allows easy mocking of time in unit tests. Clock clock.Clock + + // MinimumConfirmations is the minimum number of confirmations we allow + // setting for sweep target. + MinimumConfirmations int32 } // Parameters is a set of parameters provided by the user which guide @@ -97,6 +120,15 @@ type Parameters struct { // TODO(carla): add exponential backoff FailureBackOff time.Duration + // SweepFeeRateLimit is the limit that we place on our estimated sweep + // fee. A swap will not be suggested if estimated fee rate is above this + // value. + SweepFeeRateLimit chainfee.SatPerKWeight + + // SweepConfTarget is the number of blocks we aim to confirm our sweep + // transaction in. This value affects the on chain fees we will pay. + SweepConfTarget int32 + // ChannelRules maps a short channel ID to a rule that describes how we // would like liquidity to be managed. ChannelRules map[lnwire.ShortChannelID]*ThresholdRule @@ -112,12 +144,16 @@ func (p Parameters) String() string { ) } - return fmt.Sprintf("channel rules: %v, failure backoff: %v", - strings.Join(channelRules, ","), p.FailureBackOff) + return fmt.Sprintf("channel rules: %v, failure backoff: %v, sweep "+ + "fee rate limit: %v, sweep conf target: %v", + strings.Join(channelRules, ","), p.FailureBackOff, + p.SweepFeeRateLimit, p.SweepConfTarget, + ) } -// validate checks whether a set of parameters is valid. -func (p Parameters) validate() error { +// validate checks whether a set of parameters is valid. It takes the minimum +// confirmations we allow for sweep confirmation target as a parameter. +func (p Parameters) validate(minConfs int32) error { for channel, rule := range p.ChannelRules { if channel.ToUint64() == 0 { return ErrZeroChannelID @@ -129,6 +165,19 @@ func (p Parameters) validate() error { } } + // Check that our sweep limit is above our minimum fee rate. We use + // absolute fee floor rather than kw floor because we will allow users + // to specify fee rate is sat/vByte and want to allow 1 sat/vByte. + if p.SweepFeeRateLimit < chainfee.AbsoluteFeePerKwFloor { + return ErrInvalidSweepFeeRateLimit + } + + // Check that our confirmation target is above our required minimum. + if p.SweepConfTarget < minConfs { + return fmt.Errorf("confirmation target must be at least: %v", + minConfs) + } + return nil } @@ -166,7 +215,7 @@ func (m *Manager) GetParameters() Parameters { // SetParameters updates our current set of parameters if the new parameters // provided are valid. func (m *Manager) SetParameters(params Parameters) error { - if err := params.validate(); err != nil { + if err := params.validate(m.cfg.MinimumConfirmations); err != nil { return err } @@ -210,6 +259,27 @@ func (m *Manager) SuggestSwaps(ctx context.Context) ( return nil, nil } + // Before we get any swap suggestions, we check what the current fee + // estimate is to sweep within our target number of confirmations. If + // This fee exceeds the fee limit we have set, we will not suggest any + // swaps at present. + estimate, err := m.cfg.Lnd.WalletKit.EstimateFee( + ctx, m.params.SweepConfTarget, + ) + if err != nil { + return nil, err + } + + if estimate > m.params.SweepFeeRateLimit { + log.Debugf("Current fee estimate to sweep within: %v blocks "+ + "%v sat/vByte exceeds limit of: %v sat/vByte", + m.params.SweepConfTarget, + satPerKwToSatPerVByte(estimate), + satPerKwToSatPerVByte(m.params.SweepFeeRateLimit)) + + return nil, nil + } + // Get the current server side restrictions. outRestrictions, err := m.cfg.LoopOutRestrictions(ctx) if err != nil { @@ -249,7 +319,7 @@ func (m *Manager) SuggestSwaps(ctx context.Context) ( // We can have nil suggestions in the case where no action is // required, so only add non-nil suggestions. if suggestion != nil { - outRequest := makeLoopOutRequest(suggestion) + outRequest := m.makeLoopOutRequest(suggestion) suggestions = append(suggestions, outRequest) } } @@ -259,7 +329,7 @@ func (m *Manager) SuggestSwaps(ctx context.Context) ( // makeLoopOutRequest creates a loop out request from a suggestion, setting fee // limits defined by our default fee values. -func makeLoopOutRequest(suggestion *LoopOutRecommendation) loop.OutRequest { +func (m *Manager) makeLoopOutRequest(suggestion *LoopOutRecommendation) loop.OutRequest { prepayMaxFee := ppmToSat( defaultMaximumPrepay, defaultPrepayRoutingFeePPM, ) @@ -277,7 +347,7 @@ func makeLoopOutRequest(suggestion *LoopOutRecommendation) loop.OutRequest { MaxMinerFee: defaultMaximumMinerFee, MaxSwapFee: maxSwapFee, MaxPrepayAmount: defaultMaximumPrepay, - SweepConfTarget: loop.DefaultSweepConfTarget, + SweepConfTarget: m.params.SweepConfTarget, } } @@ -414,6 +484,11 @@ func (m *Manager) getEligibleChannels(ctx context.Context, return eligible, nil } +// satPerKwToSatPerVByte converts sat per kWeight to sat per vByte. +func satPerKwToSatPerVByte(satPerKw chainfee.SatPerKWeight) int64 { + return int64(satPerKw.FeePerKVByte() / 1000) +} + // ppmToSat takes an amount and a measure of parts per million for the amount // and returns the amount that the ppm represents. func ppmToSat(amount btcutil.Amount, ppm int) btcutil.Amount { diff --git a/liquidity/liquidity_test.go b/liquidity/liquidity_test.go index a347272..fe8e074 100644 --- a/liquidity/liquidity_test.go +++ b/liquidity/liquidity_test.go @@ -10,6 +10,7 @@ import ( "github.com/lightninglabs/loop/loopdb" "github.com/lightninglabs/loop/test" "github.com/lightningnetwork/lnd/clock" + "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing/route" "github.com/stretchr/testify/require" @@ -88,6 +89,13 @@ var ( func newTestConfig() (*Config, *test.LndMockServices) { lnd := test.NewMockLnd() + // Set our fee estimate for the default number of confirmations to our + // limit so that our fees will be ok by default. + lnd.SetFeeEstimate( + defaultParameters.SweepConfTarget, + defaultParameters.SweepFeeRateLimit, + ) + return &Config{ LoopOutRestrictions: func(_ context.Context) (*Restrictions, error) { @@ -344,6 +352,54 @@ 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) { + tests := []struct { + name string + feeRate chainfee.SatPerKWeight + swaps []loop.OutRequest + }{ + { + name: "fee estimate ok", + feeRate: defaultSweepFeeRateLimit, + swaps: []loop.OutRequest{ + chan1Rec, + }, + }, + { + name: "fee estimate above limit", + feeRate: defaultSweepFeeRateLimit + 1, + swaps: nil, + }, + } + + for _, testCase := range tests { + testCase := testCase + + t.Run(testCase.name, func(t *testing.T) { + cfg, lnd := newTestConfig() + + // Set our test case's fee rate for our mock lnd. + lnd.SetFeeEstimate( + loop.DefaultSweepConfTarget, testCase.feeRate, + ) + + channels := []lndclient.ChannelInfo{ + channel1, + } + + rules := map[lnwire.ShortChannelID]*ThresholdRule{ + chanID1: chanRule, + } + + testSuggestSwaps( + t, cfg, lnd, channels, rules, testCase.swaps, + ) + }) + } +} + // TestSuggestSwaps tests getting of swap suggestions based on the rules set for // the liquidity manager and the current set of channel balances. func TestSuggestSwaps(t *testing.T) { diff --git a/loopd/utils.go b/loopd/utils.go index f8512cb..509bd3d 100644 --- a/loopd/utils.go +++ b/loopd/utils.go @@ -47,10 +47,11 @@ func getLiquidityManager(client *loop.Client) *liquidity.Manager { outTerms.MinSwapAmount, outTerms.MaxSwapAmount, ), nil }, - Lnd: client.LndServices, - Clock: clock.NewDefaultClock(), - ListLoopOut: client.Store.FetchLoopOutSwaps, - ListLoopIn: client.Store.FetchLoopInSwaps, + Lnd: client.LndServices, + Clock: clock.NewDefaultClock(), + ListLoopOut: client.Store.FetchLoopOutSwaps, + ListLoopIn: client.Store.FetchLoopInSwaps, + MinimumConfirmations: minConfTarget, } return liquidity.NewManager(mngrCfg) From 0212a41ed0de291a2361ceb176d4007ee98981a1 Mon Sep 17 00:00:00 2001 From: carla Date: Wed, 30 Sep 2020 12:34:09 +0200 Subject: [PATCH 07/11] liquidity: make swap suggestions fee-aware --- liquidity/liquidity.go | 201 ++++++++++++++++++++++++++++++++---- liquidity/liquidity_test.go | 91 ++++++++++++++-- liquidity/suggestions.go | 8 ++ loopd/utils.go | 1 + 4 files changed, 274 insertions(+), 27 deletions(-) diff --git a/liquidity/liquidity.go b/liquidity/liquidity.go index 4c35df7..af4475c 100644 --- a/liquidity/liquidity.go +++ b/liquidity/liquidity.go @@ -13,10 +13,28 @@ // - Sweep Fee Rate Limit: the maximum sat/vByte fee estimate for our sweep // transaction to confirm within our configured number of confirmations // that we will suggest swaps for. +// - Maximum Swap Fee PPM: the maximum server fee, expressed as parts per +// million of the full swap amount +// - Maximum Routing Fee PPM: the maximum off-chain routing fees for the swap +// invoice, expressed as parts per million of the swap amount. +// - Maximum Prepay Routing Fee PPM: the maximum off-chain routing fees for the +// swap prepayment, expressed as parts per million of the prepay amount. +// - Maximum Prepay: the maximum now-show fee, expressed in satoshis. This +// amount is only payable in the case where the swap server broadcasts a htlc +// and the client fails to sweep the preimage. +// - Maximum miner fee: the maximum miner fee we are willing to pay to sweep the +// on chain htlc. Note that the client will use current fee estimates to +// sweep, so this value acts more as a sanity check in the case of a large fee +// spike. +// +// The maximum fee per-swap is calculated as follows: +// (swap amount * serverPPM/1e6) + miner fee + (swap amount * routingPPM/1e6) +// + (prepay amount * prepayPPM/1e6). package liquidity import ( "context" + "errors" "fmt" "strings" "sync" @@ -55,8 +73,10 @@ const ( defaultPrepayRoutingFeePPM = 5000 // defaultMaximumMinerFee is the default limit we place on miner fees - // per swap. - defaultMaximumMinerFee = 15000 + // per swap. We apply a multiplier to this default fee to guard against + // the case where we have broadcast the preimage, then fees spike and + // we need to sweep the preimage. + defaultMaximumMinerFee = 15000 * 100 // defaultMaximumPrepay is the default limit we place on prepay // invoices. @@ -71,10 +91,15 @@ var ( // defaultParameters contains the default parameters that we start our // liquidity manger with. defaultParameters = Parameters{ - ChannelRules: make(map[lnwire.ShortChannelID]*ThresholdRule), - FailureBackOff: defaultFailureBackoff, - SweepFeeRateLimit: defaultSweepFeeRateLimit, - SweepConfTarget: loop.DefaultSweepConfTarget, + ChannelRules: make(map[lnwire.ShortChannelID]*ThresholdRule), + FailureBackOff: defaultFailureBackoff, + SweepFeeRateLimit: defaultSweepFeeRateLimit, + SweepConfTarget: loop.DefaultSweepConfTarget, + MaximumSwapFeePPM: defaultSwapFeePPM, + MaximumRoutingFeePPM: defaultRoutingFeePPM, + MaximumPrepayRoutingFeePPM: defaultPrepayRoutingFeePPM, + MaximumMinerFee: defaultMaximumMinerFee, + MaximumPrepay: defaultMaximumPrepay, } // ErrZeroChannelID is returned if we get a rule for a 0 channel ID. @@ -85,6 +110,21 @@ var ( ErrInvalidSweepFeeRateLimit = fmt.Errorf("sweep fee rate limit must "+ "be > %v sat/vByte", satPerKwToSatPerVByte(chainfee.AbsoluteFeePerKwFloor)) + + // ErrZeroMinerFee is returned if a zero maximum miner fee is set. + ErrZeroMinerFee = errors.New("maximum miner fee must be non-zero") + + // ErrZeroSwapFeePPM is returned if a zero server fee ppm is set. + ErrZeroSwapFeePPM = errors.New("swap fee PPM must be non-zero") + + // ErrZeroRoutingPPM is returned if a zero routing fee ppm is set. + ErrZeroRoutingPPM = errors.New("routing fee PPM must be non-zero") + + // ErrZeroPrepayPPM is returned if a zero prepay routing fee ppm is set. + ErrZeroPrepayPPM = errors.New("prepay routing fee PPM must be non-zero") + + // ErrZeroPrepay is returned if a zero maximum prepay is set. + ErrZeroPrepay = errors.New("maximum prepay must be non-zero") ) // Config contains the external functionality required to run the @@ -103,6 +143,11 @@ type Config struct { // ListLoopIn returns all of the loop in swaps stored on disk. ListLoopIn func() ([]*loopdb.LoopIn, error) + // LoopOutQuote gets swap fee, estimated miner fee and prepay amount for + // a loop out swap. + LoopOutQuote func(ctx context.Context, + request *loop.LoopOutQuoteRequest) (*loop.LoopOutQuote, error) + // Clock allows easy mocking of time in unit tests. Clock clock.Clock @@ -129,6 +174,33 @@ type Parameters struct { // transaction in. This value affects the on chain fees we will pay. SweepConfTarget int32 + // MaximumPrepay is the maximum prepay amount we are willing to pay per + // swap. + MaximumPrepay btcutil.Amount + + // MaximumSwapFeePPM is the maximum server fee we are willing to pay per + // swap expressed as parts per million of the swap volume. + MaximumSwapFeePPM int + + // MaximumRoutingFeePPM is the maximum off-chain routing fee we + // are willing to pay for off chain invoice routing fees per swap, + // expressed as parts per million of the swap amount. + MaximumRoutingFeePPM int + + // MaximumPrepayRoutingFeePPM is the maximum off-chain routing fee we + // are willing to pay for off chain prepay routing fees per swap, + // expressed as parts per million of the prepay amount. + MaximumPrepayRoutingFeePPM int + + // MaximumMinerFee is the maximum on chain fee that we cap our miner + // fee at in case where we need to claim on chain because we have + // revealed the preimage, but fees have spiked. We will not initiate a + // swap if we estimate that the sweep cost will be above our sweep + // fee limit, and we use fee estimates at time of sweep to set our fees, + // so this is just a sane cap covering the special case where we need to + // sweep during a fee spike. + MaximumMinerFee btcutil.Amount + // ChannelRules maps a short channel ID to a rule that describes how we // would like liquidity to be managed. ChannelRules map[lnwire.ShortChannelID]*ThresholdRule @@ -145,9 +217,13 @@ func (p Parameters) String() string { } return fmt.Sprintf("channel rules: %v, failure backoff: %v, sweep "+ - "fee rate limit: %v, sweep conf target: %v", + "fee rate limit: %v, sweep conf target: %v, maximum prepay: "+ + "%v, maximum miner fee: %v, maximum swap fee ppm: %v, maximum "+ + "routing fee ppm: %v, maximum prepay routing fee ppm: %v", strings.Join(channelRules, ","), p.FailureBackOff, - p.SweepFeeRateLimit, p.SweepConfTarget, + p.SweepFeeRateLimit, p.SweepConfTarget, p.MaximumPrepay, + p.MaximumMinerFee, p.MaximumSwapFeePPM, + p.MaximumRoutingFeePPM, p.MaximumPrepayRoutingFeePPM, ) } @@ -178,6 +254,27 @@ func (p Parameters) validate(minConfs int32) error { minConfs) } + // Check that we have non-zero fee limits. + if p.MaximumSwapFeePPM == 0 { + return ErrZeroSwapFeePPM + } + + if p.MaximumRoutingFeePPM == 0 { + return ErrZeroRoutingPPM + } + + if p.MaximumPrepayRoutingFeePPM == 0 { + return ErrZeroPrepayPPM + } + + if p.MaximumPrepay == 0 { + return ErrZeroPrepay + } + + if p.MaximumMinerFee == 0 { + return ErrZeroMinerFee + } + return nil } @@ -317,25 +414,62 @@ func (m *Manager) SuggestSwaps(ctx context.Context) ( suggestion := rule.suggestSwap(balance, outRestrictions) // We can have nil suggestions in the case where no action is - // required, so only add non-nil suggestions. - if suggestion != nil { - outRequest := m.makeLoopOutRequest(suggestion) - suggestions = append(suggestions, outRequest) + // required, so we skip over them. + if suggestion == nil { + continue } + + // Get a quote for a swap of this amount. + quote, err := m.cfg.LoopOutQuote( + ctx, &loop.LoopOutQuoteRequest{ + Amount: suggestion.Amount, + SweepConfTarget: m.params.SweepConfTarget, + SwapPublicationDeadline: m.cfg.Clock.Now(), + }, + ) + if err != nil { + return nil, err + } + + log.Debugf("quote for suggestion: %v, swap fee: %v, "+ + "miner fee: %v, prepay: %v", suggestion, quote.SwapFee, + quote.MinerFee, quote.PrepayAmount) + + // Check that the estimated fees for the suggested swap are + // below the fee limits configured by the manager. + err = m.checkFeeLimits(quote, suggestion.Amount) + if err != nil { + log.Infof("suggestion: %v expected fees too high: %v", + suggestion, err) + + continue + } + + outRequest := m.makeLoopOutRequest(suggestion, quote) + suggestions = append(suggestions, outRequest) } return suggestions, nil } -// makeLoopOutRequest creates a loop out request from a suggestion, setting fee -// limits defined by our default fee values. -func (m *Manager) makeLoopOutRequest(suggestion *LoopOutRecommendation) loop.OutRequest { +// makeLoopOutRequest creates a loop out request from a suggestion. Since we +// do not get any information about our off-chain routing fees when we request +// a quote, we just set our prepay and route maximum fees directly from the +// amounts we expect to route. The estimation we use elsewhere is the repo is +// route-independent, which is a very poor estimation so we don't bother with +// checking against this inaccurate constant. We use the exact prepay amount +// and swap fee given to us by the server, but use our maximum miner fee anyway +// to give us some leeway when performing the swap. +func (m *Manager) makeLoopOutRequest(suggestion *LoopOutRecommendation, + quote *loop.LoopOutQuote) loop.OutRequest { + prepayMaxFee := ppmToSat( - defaultMaximumPrepay, defaultPrepayRoutingFeePPM, + quote.PrepayAmount, m.params.MaximumPrepayRoutingFeePPM, ) - routeMaxFee := ppmToSat(suggestion.Amount, defaultRoutingFeePPM) - maxSwapFee := ppmToSat(suggestion.Amount, defaultSwapFeePPM) + routeMaxFee := ppmToSat( + suggestion.Amount, m.params.MaximumRoutingFeePPM, + ) return loop.OutRequest{ Amount: suggestion.Amount, @@ -344,9 +478,9 @@ func (m *Manager) makeLoopOutRequest(suggestion *LoopOutRecommendation) loop.Out }, MaxPrepayRoutingFee: prepayMaxFee, MaxSwapRoutingFee: routeMaxFee, - MaxMinerFee: defaultMaximumMinerFee, - MaxSwapFee: maxSwapFee, - MaxPrepayAmount: defaultMaximumPrepay, + MaxMinerFee: m.params.MaximumMinerFee, + MaxSwapFee: quote.SwapFee, + MaxPrepayAmount: quote.PrepayAmount, SweepConfTarget: m.params.SweepConfTarget, } } @@ -484,6 +618,31 @@ func (m *Manager) getEligibleChannels(ctx context.Context, return eligible, nil } +// checkFeeLimits takes a set of fees for a swap and checks whether they exceed +// our swap limits. +func (m *Manager) checkFeeLimits(quote *loop.LoopOutQuote, + swapAmt btcutil.Amount) error { + + maxFee := ppmToSat(swapAmt, m.params.MaximumSwapFeePPM) + + if quote.SwapFee > maxFee { + return fmt.Errorf("quoted swap fee: %v > maximum swap fee: %v", + quote.SwapFee, maxFee) + } + + if quote.MinerFee > m.params.MaximumMinerFee { + return fmt.Errorf("quoted miner fee: %v > maximum miner "+ + "fee: %v", quote.MinerFee, m.params.MaximumMinerFee) + } + + if quote.PrepayAmount > m.params.MaximumPrepay { + return fmt.Errorf("quoted prepay: %v > maximum prepay: %v", + quote.PrepayAmount, m.params.MaximumPrepay) + } + + return nil +} + // satPerKwToSatPerVByte converts sat per kWeight to sat per vByte. func satPerKwToSatPerVByte(satPerKw chainfee.SatPerKWeight) int64 { return int64(satPerKw.FeePerKVByte() / 1000) diff --git a/liquidity/liquidity_test.go b/liquidity/liquidity_test.go index fe8e074..a38dd1b 100644 --- a/liquidity/liquidity_test.go +++ b/liquidity/liquidity_test.go @@ -5,6 +5,7 @@ import ( "testing" "time" + "github.com/btcsuite/btcutil" "github.com/lightninglabs/lndclient" "github.com/lightninglabs/loop" "github.com/lightninglabs/loop/loopdb" @@ -44,11 +45,16 @@ var ( // chanRule is a rule that produces chan1Rec. chanRule = NewThresholdRule(50, 0) + testQuote = &loop.LoopOutQuote{ + SwapFee: btcutil.Amount(1), + PrepayAmount: btcutil.Amount(500), + MinerFee: btcutil.Amount(50), + } + prepayFee = ppmToSat( - defaultMaximumPrepay, defaultPrepayRoutingFeePPM, + testQuote.PrepayAmount, defaultPrepayRoutingFeePPM, ) routingFee = ppmToSat(7500, defaultRoutingFeePPM) - swapFee = ppmToSat(7500, defaultSwapFeePPM) // chan1Rec is the suggested swap for channel 1 when we use chanRule. chan1Rec = loop.OutRequest{ @@ -57,8 +63,8 @@ var ( MaxPrepayRoutingFee: prepayFee, MaxSwapRoutingFee: routingFee, MaxMinerFee: defaultMaximumMinerFee, - MaxSwapFee: swapFee, - MaxPrepayAmount: defaultMaximumPrepay, + MaxSwapFee: testQuote.SwapFee, + MaxPrepayAmount: testQuote.PrepayAmount, SweepConfTarget: loop.DefaultSweepConfTarget, } @@ -69,8 +75,8 @@ var ( MaxPrepayRoutingFee: prepayFee, MaxSwapRoutingFee: routingFee, MaxMinerFee: defaultMaximumMinerFee, - MaxSwapFee: swapFee, - MaxPrepayAmount: defaultMaximumPrepay, + MaxPrepayAmount: testQuote.PrepayAmount, + MaxSwapFee: testQuote.SwapFee, SweepConfTarget: loop.DefaultSweepConfTarget, } @@ -110,6 +116,12 @@ func newTestConfig() (*Config, *test.LndMockServices) { ListLoopIn: func() ([]*loopdb.LoopIn, error) { return nil, nil }, + LoopOutQuote: func(_ context.Context, + _ *loop.LoopOutQuoteRequest) (*loop.LoopOutQuote, + error) { + + return testQuote, nil + }, }, lnd } @@ -448,6 +460,73 @@ func TestSuggestSwaps(t *testing.T) { } } +// TestFeeLimits tests limiting of swap suggestions by fees. +func TestFeeLimits(t *testing.T) { + tests := []struct { + name string + quote *loop.LoopOutQuote + expected []loop.OutRequest + }{ + { + name: "fees ok", + quote: testQuote, + expected: []loop.OutRequest{ + chan1Rec, + }, + }, + { + name: "insufficient prepay", + quote: &loop.LoopOutQuote{ + SwapFee: 1, + PrepayAmount: defaultMaximumPrepay + 1, + MinerFee: 50, + }, + }, + { + name: "insufficient miner fee", + quote: &loop.LoopOutQuote{ + SwapFee: 1, + PrepayAmount: 100, + MinerFee: defaultMaximumMinerFee + 1, + }, + }, + { + // Swap fee limited to 0.5% of 7500 = 37,5. + name: "insufficient swap fee", + quote: &loop.LoopOutQuote{ + SwapFee: 38, + PrepayAmount: 100, + MinerFee: 500, + }, + }, + } + + for _, testCase := range tests { + testCase := testCase + + t.Run(testCase.name, func(t *testing.T) { + cfg, lnd := newTestConfig() + cfg.LoopOutQuote = func(context.Context, + *loop.LoopOutQuoteRequest) (*loop.LoopOutQuote, + error) { + + return testCase.quote, nil + } + + channels := []lndclient.ChannelInfo{ + channel1, + } + rules := map[lnwire.ShortChannelID]*ThresholdRule{ + chanID1: chanRule, + } + + testSuggestSwaps( + t, cfg, lnd, channels, rules, testCase.expected, + ) + }) + } +} + // testSuggestSwaps tests getting swap suggestions. func testSuggestSwaps(t *testing.T, cfg *Config, lnd *test.LndMockServices, channels []lndclient.ChannelInfo, diff --git a/liquidity/suggestions.go b/liquidity/suggestions.go index 681d105..3f2bfc0 100644 --- a/liquidity/suggestions.go +++ b/liquidity/suggestions.go @@ -1,6 +1,8 @@ package liquidity import ( + "fmt" + "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/lnwire" ) @@ -15,6 +17,12 @@ type LoopOutRecommendation struct { Channel lnwire.ShortChannelID } +// String returns a string representation of a loop out recommendation. +func (l *LoopOutRecommendation) String() string { + return fmt.Sprintf("loop out: %v over %v", l.Amount, + l.Channel.ToUint64()) +} + // newLoopOutRecommendation creates a new loop out swap. func newLoopOutRecommendation(amount btcutil.Amount, channelID lnwire.ShortChannelID) *LoopOutRecommendation { diff --git a/loopd/utils.go b/loopd/utils.go index 509bd3d..b70cab2 100644 --- a/loopd/utils.go +++ b/loopd/utils.go @@ -49,6 +49,7 @@ func getLiquidityManager(client *loop.Client) *liquidity.Manager { }, Lnd: client.LndServices, Clock: clock.NewDefaultClock(), + LoopOutQuote: client.LoopOutQuote, ListLoopOut: client.Store.FetchLoopOutSwaps, ListLoopIn: client.Store.FetchLoopInSwaps, MinimumConfirmations: minConfTarget, From f23a5279270f8216600ee9313fe592c4b76043dc Mon Sep 17 00:00:00 2001 From: carla Date: Wed, 30 Sep 2020 12:34:10 +0200 Subject: [PATCH 08/11] looprpc: add fee and backoff parameters to rpc --- loopd/swapclient_server.go | 24 +++ looprpc/client.pb.go | 379 +++++++++++++++++++++++------------- looprpc/client.proto | 52 +++++ looprpc/client.swagger.json | 40 ++++ 4 files changed, 356 insertions(+), 139 deletions(-) diff --git a/loopd/swapclient_server.go b/loopd/swapclient_server.go index 64578e5..7dc1008 100644 --- a/loopd/swapclient_server.go +++ b/loopd/swapclient_server.go @@ -16,6 +16,7 @@ import ( "github.com/lightninglabs/loop/looprpc" "github.com/lightninglabs/loop/swap" "github.com/lightningnetwork/lnd/lntypes" + "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/queue" "github.com/lightningnetwork/lnd/routing/route" @@ -555,7 +556,17 @@ func (s *swapClientServer) GetLiquidityParams(_ context.Context, cfg := s.liquidityMgr.GetParameters() + satPerByte := cfg.SweepFeeRateLimit.FeePerKVByte() / 1000 + rpcCfg := &looprpc.LiquidityParameters{ + MaxMinerFeeSat: uint64(cfg.MaximumMinerFee), + MaxSwapFeePpm: uint64(cfg.MaximumSwapFeePPM), + MaxRoutingFeePpm: uint64(cfg.MaximumRoutingFeePPM), + MaxPrepayRoutingFeePpm: uint64(cfg.MaximumPrepayRoutingFeePPM), + MaxPrepaySat: uint64(cfg.MaximumPrepay), + SweepFeeRateSatPerVbyte: uint64(satPerByte), + SweepConfTarget: cfg.SweepConfTarget, + FailureBackoffSec: uint64(cfg.FailureBackOff.Seconds()), Rules: make( []*looprpc.LiquidityRule, 0, len(cfg.ChannelRules), ), @@ -581,7 +592,20 @@ func (s *swapClientServer) SetLiquidityParams(_ context.Context, in *looprpc.SetLiquidityParamsRequest) (*looprpc.SetLiquidityParamsResponse, error) { + satPerVbyte := chainfee.SatPerKVByte( + in.Parameters.SweepFeeRateSatPerVbyte * 1000, + ) + params := liquidity.Parameters{ + MaximumMinerFee: btcutil.Amount(in.Parameters.MaxMinerFeeSat), + MaximumSwapFeePPM: int(in.Parameters.MaxSwapFeePpm), + MaximumRoutingFeePPM: int(in.Parameters.MaxRoutingFeePpm), + MaximumPrepayRoutingFeePPM: int(in.Parameters.MaxPrepayRoutingFeePpm), + MaximumPrepay: btcutil.Amount(in.Parameters.MaxPrepaySat), + SweepFeeRateLimit: satPerVbyte.FeePerKWeight(), + SweepConfTarget: in.Parameters.SweepConfTarget, + FailureBackOff: time.Duration(in.Parameters.FailureBackoffSec) * + time.Second, ChannelRules: make( map[lnwire.ShortChannelID]*liquidity.ThresholdRule, len(in.Parameters.Rules), diff --git a/looprpc/client.pb.go b/looprpc/client.pb.go index f1c2bd5..a5c043f 100644 --- a/looprpc/client.pb.go +++ b/looprpc/client.pb.go @@ -1543,10 +1543,46 @@ var xxx_messageInfo_GetLiquidityParamsRequest proto.InternalMessageInfo type LiquidityParameters struct { // //A set of liquidity rules that describe the desired liquidity balance. - Rules []*LiquidityRule `protobuf:"bytes,1,rep,name=rules,proto3" json:"rules,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + Rules []*LiquidityRule `protobuf:"bytes,1,rep,name=rules,proto3" json:"rules,omitempty"` + // + //The limit we place on our estimated sweep cost for a swap in sat/vByte. If + //the estimated fee for our sweep transaction within the specified + //confirmation target is above this value, we will not suggest any swaps. + SweepFeeRateSatPerVbyte uint64 `protobuf:"varint,2,opt,name=sweep_fee_rate_sat_per_vbyte,json=sweepFeeRateSatPerVbyte,proto3" json:"sweep_fee_rate_sat_per_vbyte,omitempty"` + // + //The maximum fee paid to the server for facilitating the swap, expressed + //as parts per million of the swap volume. + MaxSwapFeePpm uint64 `protobuf:"varint,3,opt,name=max_swap_fee_ppm,json=maxSwapFeePpm,proto3" json:"max_swap_fee_ppm,omitempty"` + // + //The maximum fee paid to route the swap invoice off chain, expressed as + //parts per million of the volume being routed. + MaxRoutingFeePpm uint64 `protobuf:"varint,4,opt,name=max_routing_fee_ppm,json=maxRoutingFeePpm,proto3" json:"max_routing_fee_ppm,omitempty"` + // + //The maximum fee paid to route the prepay invoice off chain, expressed as + //parts per million of the volume being routed. + MaxPrepayRoutingFeePpm uint64 `protobuf:"varint,5,opt,name=max_prepay_routing_fee_ppm,json=maxPrepayRoutingFeePpm,proto3" json:"max_prepay_routing_fee_ppm,omitempty"` + // + //The maximum no-show penalty in satoshis paid for a swap. + MaxPrepaySat uint64 `protobuf:"varint,6,opt,name=max_prepay_sat,json=maxPrepaySat,proto3" json:"max_prepay_sat,omitempty"` + // + //The maximum miner fee we will pay to sweep the swap on chain. Note that we + //will not suggest a swap if the estimate is above the sweep limit set by + //these parameters, and we use the current fee estimate to sweep on chain so + //this value is only a cap placed on the amount we spend on fees in the case + //where the swap needs to be claimed on chain, but fees have suddenly spiked. + MaxMinerFeeSat uint64 `protobuf:"varint,7,opt,name=max_miner_fee_sat,json=maxMinerFeeSat,proto3" json:"max_miner_fee_sat,omitempty"` + // + //The number of blocks from the on-chain HTLC's confirmation height that it + //should be swept within. + SweepConfTarget int32 `protobuf:"varint,8,opt,name=sweep_conf_target,json=sweepConfTarget,proto3" json:"sweep_conf_target,omitempty"` + // + //The amount of time we require pass since a channel was part of a failed + //swap due to off chain payment failure until it will be considered for swap + //suggestions again, expressed in seconds. + FailureBackoffSec uint64 `protobuf:"varint,9,opt,name=failure_backoff_sec,json=failureBackoffSec,proto3" json:"failure_backoff_sec,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *LiquidityParameters) Reset() { *m = LiquidityParameters{} } @@ -1581,6 +1617,62 @@ func (m *LiquidityParameters) GetRules() []*LiquidityRule { return nil } +func (m *LiquidityParameters) GetSweepFeeRateSatPerVbyte() uint64 { + if m != nil { + return m.SweepFeeRateSatPerVbyte + } + return 0 +} + +func (m *LiquidityParameters) GetMaxSwapFeePpm() uint64 { + if m != nil { + return m.MaxSwapFeePpm + } + return 0 +} + +func (m *LiquidityParameters) GetMaxRoutingFeePpm() uint64 { + if m != nil { + return m.MaxRoutingFeePpm + } + return 0 +} + +func (m *LiquidityParameters) GetMaxPrepayRoutingFeePpm() uint64 { + if m != nil { + return m.MaxPrepayRoutingFeePpm + } + return 0 +} + +func (m *LiquidityParameters) GetMaxPrepaySat() uint64 { + if m != nil { + return m.MaxPrepaySat + } + return 0 +} + +func (m *LiquidityParameters) GetMaxMinerFeeSat() uint64 { + if m != nil { + return m.MaxMinerFeeSat + } + return 0 +} + +func (m *LiquidityParameters) GetSweepConfTarget() int32 { + if m != nil { + return m.SweepConfTarget + } + return 0 +} + +func (m *LiquidityParameters) GetFailureBackoffSec() uint64 { + if m != nil { + return m.FailureBackoffSec + } + return 0 +} + type LiquidityRule struct { // //The short channel ID of the channel that this rule should be applied to. @@ -1838,141 +1930,150 @@ func init() { func init() { proto.RegisterFile("client.proto", fileDescriptor_014de31d7ac8c57c) } var fileDescriptor_014de31d7ac8c57c = []byte{ - // 2133 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x58, 0xcd, 0x6e, 0x23, 0xc7, - 0xf1, 0x5f, 0x7e, 0x89, 0x64, 0x71, 0x48, 0x8e, 0x5a, 0xbb, 0x12, 0x45, 0xcb, 0x58, 0xed, 0xac, - 0xf7, 0xff, 0x97, 0x15, 0x5b, 0x8c, 0xe5, 0x53, 0x0c, 0x27, 0x00, 0x97, 0xa2, 0x2c, 0x2a, 0x12, - 0xc9, 0x0c, 0xa9, 0x35, 0x36, 0x08, 0x30, 0x68, 0x91, 0x2d, 0x71, 0x10, 0xce, 0xc7, 0xce, 0x34, - 0x77, 0x25, 0x18, 0x49, 0x80, 0xbc, 0x80, 0x0f, 0x79, 0x83, 0x3c, 0x43, 0x6e, 0xc9, 0x23, 0xe4, - 0x94, 0x1c, 0x73, 0x0d, 0x10, 0xe4, 0x90, 0x77, 0x08, 0xba, 0xba, 0x67, 0x38, 0xa4, 0x48, 0x05, - 0x39, 0xe4, 0x26, 0xfe, 0xea, 0xd7, 0x55, 0x5d, 0x55, 0x5d, 0x1f, 0x23, 0xd0, 0x46, 0x53, 0x9b, - 0xb9, 0xfc, 0xc8, 0x0f, 0x3c, 0xee, 0x91, 0xfc, 0xd4, 0xf3, 0xfc, 0xc0, 0x1f, 0xd5, 0xf7, 0x6e, - 0x3d, 0xef, 0x76, 0xca, 0x1a, 0xd4, 0xb7, 0x1b, 0xd4, 0x75, 0x3d, 0x4e, 0xb9, 0xed, 0xb9, 0xa1, - 0xa4, 0x19, 0xdf, 0x67, 0xa1, 0x72, 0xe1, 0x79, 0x7e, 0x6f, 0xc6, 0x4d, 0xf6, 0x6e, 0xc6, 0x42, - 0x4e, 0x74, 0xc8, 0x50, 0x87, 0xd7, 0x52, 0xfb, 0xa9, 0x83, 0x8c, 0x29, 0xfe, 0x24, 0x04, 0xb2, - 0x63, 0x16, 0xf2, 0x5a, 0x7a, 0x3f, 0x75, 0x50, 0x34, 0xf1, 0x6f, 0xd2, 0x80, 0xa7, 0x0e, 0xbd, - 0xb3, 0xc2, 0x0f, 0xd4, 0xb7, 0x02, 0x6f, 0xc6, 0x6d, 0xf7, 0xd6, 0xba, 0x61, 0xac, 0x96, 0xc1, - 0x63, 0x9b, 0x0e, 0xbd, 0x1b, 0x7c, 0xa0, 0xbe, 0x29, 0x25, 0xa7, 0x8c, 0x91, 0x2f, 0x61, 0x5b, - 0x1c, 0xf0, 0x03, 0xe6, 0xd3, 0xfb, 0x85, 0x23, 0x59, 0x3c, 0xb2, 0xe5, 0xd0, 0xbb, 0x3e, 0x0a, - 0x13, 0x87, 0xf6, 0x41, 0x8b, 0xad, 0x08, 0x6a, 0x0e, 0xa9, 0xa0, 0xb4, 0x0b, 0xc6, 0x27, 0x50, - 0x49, 0xa8, 0x15, 0x17, 0xdf, 0x40, 0x8e, 0x16, 0xab, 0x6b, 0x3a, 0x9c, 0x18, 0x50, 0x16, 0x2c, - 0xc7, 0x76, 0x59, 0x80, 0x8a, 0xf2, 0x48, 0x2a, 0x39, 0xf4, 0xee, 0x52, 0x60, 0x42, 0xd3, 0x67, - 0xa0, 0x8b, 0x98, 0x59, 0xde, 0x8c, 0x5b, 0xa3, 0x09, 0x75, 0x5d, 0x36, 0xad, 0x15, 0xf6, 0x53, - 0x07, 0xd9, 0xd7, 0xe9, 0x5a, 0xca, 0xac, 0x4c, 0x65, 0x94, 0x5a, 0x52, 0x42, 0x0e, 0x61, 0xd3, - 0x9b, 0xf1, 0x5b, 0x4f, 0x38, 0x21, 0xd8, 0x56, 0xc8, 0x78, 0xad, 0xb4, 0x9f, 0x39, 0xc8, 0x9a, - 0xd5, 0x48, 0x20, 0xb8, 0x03, 0xc6, 0x05, 0x37, 0xfc, 0xc0, 0x98, 0x6f, 0x8d, 0x3c, 0xf7, 0xc6, - 0xe2, 0x34, 0xb8, 0x65, 0xbc, 0x56, 0xdc, 0x4f, 0x1d, 0xe4, 0xcc, 0x2a, 0x0a, 0x5a, 0x9e, 0x7b, - 0x33, 0x44, 0x98, 0x7c, 0x0e, 0x64, 0xc2, 0xa7, 0x23, 0xa4, 0xda, 0x81, 0x23, 0x93, 0x55, 0x2b, - 0x23, 0x79, 0x53, 0x48, 0x5a, 0x49, 0x01, 0xf9, 0x0a, 0x76, 0x31, 0x38, 0xfe, 0xec, 0x7a, 0x6a, - 0x8f, 0x10, 0xb4, 0xc6, 0x8c, 0x8e, 0xa7, 0xb6, 0xcb, 0x6a, 0x20, 0x6e, 0x6f, 0xee, 0x08, 0x42, - 0x7f, 0x2e, 0x3f, 0x51, 0x62, 0xf2, 0x14, 0x72, 0x53, 0x7a, 0xcd, 0xa6, 0x35, 0x0d, 0xf3, 0x2a, - 0x7f, 0x18, 0xff, 0x48, 0x41, 0x59, 0xbc, 0x88, 0x8e, 0xbb, 0xfe, 0x41, 0x2c, 0xa7, 0x25, 0xfd, - 0x20, 0x2d, 0x0f, 0x02, 0x9e, 0x79, 0x18, 0xf0, 0x5d, 0x28, 0x4c, 0x69, 0xc8, 0xad, 0x89, 0xe7, - 0xe3, 0x1b, 0xd0, 0xcc, 0xbc, 0xf8, 0x7d, 0xe6, 0xf9, 0xe4, 0x25, 0x94, 0xd9, 0x1d, 0x67, 0x81, - 0x4b, 0xa7, 0x96, 0x70, 0x1a, 0x13, 0x5f, 0x30, 0xb5, 0x08, 0x3c, 0xe3, 0xd3, 0x11, 0x39, 0x00, - 0x3d, 0x0e, 0x55, 0x14, 0xd5, 0x0d, 0x0c, 0x54, 0x25, 0x0a, 0x94, 0x0a, 0x6a, 0xec, 0x69, 0x3e, - 0xe9, 0xe9, 0x3f, 0x53, 0xa0, 0xe1, 0x23, 0x65, 0xa1, 0xef, 0xb9, 0x21, 0x23, 0x04, 0xd2, 0xf6, - 0x18, 0xfd, 0x2c, 0x62, 0xce, 0xd3, 0xf6, 0x58, 0x5c, 0xd2, 0x1e, 0x5b, 0xd7, 0xf7, 0x9c, 0x85, - 0xe8, 0x83, 0x66, 0xe6, 0xed, 0xf1, 0x6b, 0xf1, 0x93, 0xbc, 0x02, 0x0d, 0xed, 0xd3, 0xf1, 0x38, - 0x60, 0x61, 0x28, 0xcb, 0x03, 0x0f, 0x96, 0x04, 0xde, 0x94, 0x30, 0x39, 0x82, 0xad, 0x24, 0xcd, - 0x72, 0xfd, 0xe3, 0x0f, 0xe1, 0x04, 0x3d, 0x2e, 0xca, 0x94, 0x2a, 0x66, 0x17, 0x05, 0xe4, 0x33, - 0xf5, 0x02, 0x22, 0xbe, 0xa4, 0xe7, 0x90, 0xae, 0x27, 0xe8, 0x7d, 0x64, 0xbf, 0x82, 0x4a, 0xc8, - 0x82, 0xf7, 0x2c, 0xb0, 0x1c, 0x16, 0x86, 0xf4, 0x96, 0x61, 0x08, 0x8a, 0x66, 0x59, 0xa2, 0x97, - 0x12, 0x34, 0x74, 0xa8, 0x5c, 0x7a, 0xae, 0xcd, 0xbd, 0x40, 0x65, 0xd5, 0xf8, 0x43, 0x16, 0x40, - 0x78, 0x3f, 0xe0, 0x94, 0xcf, 0xc2, 0x95, 0x55, 0x2f, 0xa2, 0x91, 0x5e, 0x1b, 0x8d, 0xd2, 0x72, - 0x34, 0xb2, 0xfc, 0xde, 0x97, 0x89, 0xae, 0x1c, 0x6f, 0x1e, 0xa9, 0xfe, 0x73, 0x24, 0x6c, 0x0c, - 0xef, 0x7d, 0x66, 0xa2, 0x98, 0x1c, 0x40, 0x2e, 0xe4, 0x94, 0xcb, 0xaa, 0xaf, 0x1c, 0x93, 0x05, - 0x9e, 0xb8, 0x0b, 0x33, 0x25, 0x81, 0xfc, 0x18, 0x2a, 0x37, 0xd4, 0x9e, 0xce, 0x02, 0x66, 0x05, - 0x8c, 0x86, 0x9e, 0x5b, 0xab, 0xe0, 0x91, 0xed, 0xf8, 0xc8, 0xa9, 0x14, 0x9b, 0x28, 0x35, 0xcb, - 0x37, 0xc9, 0x9f, 0xe4, 0xff, 0xa1, 0x6a, 0xbb, 0x36, 0xb7, 0x65, 0x4d, 0x70, 0xdb, 0x89, 0xba, - 0x47, 0x65, 0x0e, 0x0f, 0x6d, 0x47, 0xdc, 0x48, 0xc7, 0x67, 0x38, 0xf3, 0xc7, 0x94, 0x33, 0xc9, - 0x94, 0x3d, 0xa4, 0x22, 0xf0, 0x2b, 0x84, 0x91, 0xb9, 0x9c, 0xf0, 0xfc, 0xea, 0x84, 0xaf, 0x4e, - 0xa0, 0xb6, 0x26, 0x81, 0x6b, 0x9e, 0x47, 0x79, 0xdd, 0xf3, 0x78, 0x0e, 0xa5, 0x91, 0x17, 0x72, - 0x4b, 0xe6, 0x17, 0x3b, 0x54, 0xc6, 0x04, 0x01, 0x0d, 0x10, 0x21, 0x2f, 0x40, 0x43, 0x82, 0xe7, - 0x8e, 0x26, 0xd4, 0x76, 0xb1, 0xd1, 0x64, 0x4c, 0x3c, 0xd4, 0x93, 0x90, 0x28, 0x2f, 0x49, 0xb9, - 0xb9, 0x91, 0x1c, 0x90, 0x3d, 0x13, 0x39, 0x0a, 0x9b, 0x17, 0x4d, 0x35, 0x59, 0x34, 0x04, 0xf4, - 0x0b, 0x3b, 0xe4, 0x22, 0x5b, 0x61, 0xf4, 0x94, 0x7e, 0x02, 0x9b, 0x09, 0x4c, 0x15, 0xd3, 0xa7, - 0x90, 0x13, 0xfd, 0x21, 0xac, 0xa5, 0xf6, 0x33, 0x07, 0xa5, 0xe3, 0xad, 0x07, 0x89, 0x9e, 0x85, - 0xa6, 0x64, 0x18, 0x2f, 0xa0, 0x2a, 0xc0, 0x8e, 0x7b, 0xe3, 0x45, 0x3d, 0xa7, 0x12, 0x97, 0xa2, - 0x26, 0x1e, 0x9e, 0x51, 0x01, 0x6d, 0xc8, 0x02, 0x27, 0x36, 0xf9, 0x1b, 0xa8, 0x76, 0x5c, 0x85, - 0x28, 0x83, 0xff, 0x07, 0x55, 0xc7, 0x76, 0x65, 0x53, 0xa2, 0x8e, 0x37, 0x73, 0xb9, 0x4a, 0x78, - 0xd9, 0xb1, 0x5d, 0xa1, 0xbf, 0x89, 0x20, 0xf2, 0xa2, 0xe6, 0xa5, 0x78, 0x1b, 0x8a, 0x27, 0xfb, - 0x97, 0xe4, 0x9d, 0x67, 0x0b, 0x29, 0x3d, 0x7d, 0x9e, 0x2d, 0xa4, 0xf5, 0xcc, 0x79, 0xb6, 0x90, - 0xd1, 0xb3, 0xe7, 0xd9, 0x42, 0x56, 0xcf, 0x9d, 0x67, 0x0b, 0x79, 0xbd, 0x60, 0xfc, 0x39, 0x05, - 0x7a, 0x6f, 0xc6, 0xff, 0xa7, 0x57, 0xc0, 0xe1, 0x66, 0xbb, 0xd6, 0x68, 0xca, 0xdf, 0x5b, 0x63, - 0x36, 0xe5, 0x14, 0xd3, 0x9d, 0x33, 0x35, 0xc7, 0x76, 0x5b, 0x53, 0xfe, 0xfe, 0x44, 0x60, 0xd1, - 0x08, 0x4c, 0xb0, 0x8a, 0x8a, 0x45, 0xef, 0x62, 0xd6, 0x7f, 0x70, 0xe7, 0xf7, 0x29, 0xd0, 0x7e, - 0x36, 0xf3, 0x38, 0x5b, 0xdf, 0xf4, 0xf1, 0xe1, 0xcd, 0x3b, 0x6d, 0x1a, 0x6d, 0xc0, 0x68, 0xde, - 0x65, 0x1f, 0x34, 0xed, 0xcc, 0x8a, 0xa6, 0xfd, 0xe8, 0xc0, 0xca, 0x3e, 0x3a, 0xb0, 0x8c, 0xef, - 0x53, 0x22, 0xeb, 0xea, 0x9a, 0x2a, 0xe4, 0xfb, 0xa0, 0x45, 0x63, 0xc8, 0x0a, 0x69, 0x74, 0x61, - 0x08, 0xe5, 0x1c, 0x1a, 0x50, 0xdc, 0x54, 0xb0, 0xc0, 0xd0, 0x62, 0x38, 0x89, 0x99, 0x6a, 0x53, - 0x11, 0xb2, 0xbe, 0x14, 0xa9, 0x03, 0x1f, 0x03, 0x24, 0x62, 0x99, 0x43, 0x3f, 0x8b, 0xa3, 0x44, - 0x20, 0x65, 0x08, 0xb3, 0x7a, 0xce, 0xf8, 0x8b, 0x7c, 0x05, 0xff, 0xed, 0x95, 0x3e, 0x81, 0xca, - 0x7c, 0x61, 0x41, 0x8e, 0x9c, 0xa0, 0x9a, 0x1f, 0x6d, 0x2c, 0x82, 0xf5, 0x03, 0xd5, 0x47, 0xe4, - 0xee, 0xb0, 0x78, 0xed, 0xaa, 0x90, 0x0c, 0x84, 0x40, 0xa9, 0xc4, 0x1d, 0x43, 0xc4, 0x95, 0xde, - 0x3b, 0xcc, 0xe5, 0x16, 0x2e, 0x6c, 0x72, 0xaa, 0x56, 0x31, 0x9e, 0x12, 0x3f, 0x11, 0xb9, 0x7d, - 0xdc, 0x41, 0xa3, 0x0a, 0xe5, 0xa1, 0xf7, 0x4b, 0xe6, 0xc6, 0xc5, 0xf6, 0x35, 0x54, 0x22, 0x40, - 0xb9, 0x78, 0x08, 0x1b, 0x1c, 0x11, 0x55, 0xdd, 0xf3, 0x36, 0x7e, 0x11, 0x52, 0x8e, 0x64, 0x53, - 0x31, 0x8c, 0x3f, 0xa6, 0xa1, 0x18, 0xa3, 0xe2, 0x91, 0x5c, 0xd3, 0x90, 0x59, 0x0e, 0x1d, 0xd1, - 0xc0, 0xf3, 0x5c, 0x55, 0xe3, 0x9a, 0x00, 0x2f, 0x15, 0x26, 0x5a, 0x58, 0xe4, 0xc7, 0x84, 0x86, - 0x13, 0x8c, 0x8e, 0x66, 0x96, 0x14, 0x76, 0x46, 0xc3, 0x09, 0xf9, 0x14, 0xf4, 0x88, 0xe2, 0x07, - 0xcc, 0x76, 0xc4, 0xe4, 0x93, 0xf3, 0xb9, 0xaa, 0xf0, 0xbe, 0x82, 0x45, 0x83, 0x97, 0x45, 0x66, - 0xf9, 0xd4, 0x1e, 0x5b, 0x8e, 0x88, 0xa2, 0xdc, 0x39, 0x2b, 0x12, 0xef, 0x53, 0x7b, 0x7c, 0x19, - 0x52, 0x4e, 0xbe, 0x80, 0x67, 0x89, 0xc5, 0x34, 0x41, 0x97, 0x55, 0x4c, 0x82, 0x78, 0x33, 0x8d, - 0x8f, 0xbc, 0x00, 0x4d, 0x4c, 0x0c, 0x6b, 0x14, 0x30, 0xca, 0xd9, 0x58, 0xd5, 0x71, 0x49, 0x60, - 0x2d, 0x09, 0x91, 0x1a, 0xe4, 0xd9, 0x9d, 0x6f, 0x07, 0x6c, 0x8c, 0x13, 0xa3, 0x60, 0x46, 0x3f, - 0xc5, 0xe1, 0x90, 0x7b, 0x01, 0xbd, 0x65, 0x96, 0x4b, 0x1d, 0x86, 0xd5, 0x5d, 0x34, 0x4b, 0x0a, - 0xeb, 0x52, 0x87, 0x19, 0x1f, 0xc1, 0xee, 0x37, 0x8c, 0x5f, 0xd8, 0xef, 0x66, 0xf6, 0xd8, 0xe6, - 0xf7, 0x7d, 0x1a, 0xd0, 0x79, 0x17, 0x6c, 0xc1, 0xd6, 0xa2, 0x84, 0x71, 0x16, 0x88, 0x01, 0x94, - 0x0b, 0x66, 0x53, 0x16, 0x25, 0x67, 0x3e, 0x30, 0x63, 0xb2, 0x39, 0x9b, 0x32, 0x53, 0x92, 0x8c, - 0x3f, 0x89, 0x85, 0x2f, 0x29, 0xc0, 0xf7, 0x21, 0xd7, 0x5c, 0x4b, 0x35, 0xe1, 0xac, 0x59, 0x54, - 0x48, 0x67, 0x4c, 0x8e, 0xd4, 0xa4, 0x4f, 0xe3, 0x38, 0xae, 0xaf, 0xd6, 0x9e, 0x18, 0xf9, 0x9f, - 0x03, 0xb1, 0xdd, 0x91, 0xe7, 0x88, 0xb0, 0xf2, 0x49, 0xc0, 0xc2, 0x89, 0x37, 0x1d, 0x63, 0xb2, - 0xca, 0xe6, 0x66, 0x24, 0x19, 0x46, 0x02, 0x41, 0x8f, 0x37, 0xeb, 0x39, 0x3d, 0x2b, 0xe9, 0x91, - 0x24, 0xa6, 0x1b, 0x6f, 0x61, 0x77, 0xb0, 0x2e, 0x40, 0xe4, 0x6b, 0x00, 0x3f, 0x8e, 0x0b, 0x7a, - 0x52, 0x3a, 0xde, 0x7b, 0x78, 0xe1, 0x79, 0xec, 0xcc, 0x04, 0xdf, 0xd8, 0x83, 0xfa, 0x2a, 0xd5, - 0xb2, 0x06, 0x8c, 0x67, 0xb0, 0x35, 0x98, 0xdd, 0xde, 0xb2, 0xa5, 0x61, 0x78, 0x0e, 0x4f, 0x17, - 0x61, 0x55, 0x32, 0xc7, 0x50, 0x88, 0x3e, 0x2f, 0x54, 0x5e, 0x76, 0xe6, 0x17, 0x59, 0xf8, 0x02, - 0x33, 0xf3, 0xea, 0x5b, 0xe3, 0xf0, 0x15, 0x14, 0xa2, 0xf5, 0x89, 0x68, 0x50, 0xb8, 0xe8, 0xf5, - 0xfa, 0x56, 0xef, 0x6a, 0xa8, 0x3f, 0x21, 0x25, 0xc8, 0xe3, 0xaf, 0x4e, 0x57, 0x4f, 0x1d, 0x86, - 0x50, 0x8c, 0xb7, 0x27, 0x52, 0x86, 0x62, 0xa7, 0xdb, 0x19, 0x76, 0x9a, 0xc3, 0xf6, 0x89, 0xfe, - 0x84, 0x3c, 0x83, 0xcd, 0xbe, 0xd9, 0xee, 0x5c, 0x36, 0xbf, 0x69, 0x5b, 0x66, 0xfb, 0x4d, 0xbb, - 0x79, 0xd1, 0x3e, 0xd1, 0x53, 0x84, 0x40, 0xe5, 0x6c, 0x78, 0xd1, 0xb2, 0xfa, 0x57, 0xaf, 0x2f, - 0x3a, 0x83, 0xb3, 0xf6, 0x89, 0x9e, 0x16, 0x3a, 0x07, 0x57, 0xad, 0x56, 0x7b, 0x30, 0xd0, 0x33, - 0x04, 0x60, 0xe3, 0xb4, 0xd9, 0x11, 0xe4, 0x2c, 0xd9, 0x82, 0x6a, 0xa7, 0xfb, 0xa6, 0xd7, 0x69, - 0xb5, 0xad, 0x41, 0x7b, 0x38, 0x14, 0x60, 0xee, 0xf0, 0x5f, 0x29, 0x28, 0x2f, 0x2c, 0x60, 0x64, - 0x07, 0xb6, 0xc4, 0x91, 0x2b, 0x53, 0x58, 0x6a, 0x0e, 0x7a, 0x5d, 0xab, 0xdb, 0xeb, 0xb6, 0xf5, - 0x27, 0xe4, 0x23, 0xd8, 0x59, 0x12, 0xf4, 0x4e, 0x4f, 0x5b, 0x67, 0x4d, 0x71, 0x79, 0x52, 0x87, - 0xed, 0x25, 0xe1, 0xb0, 0x73, 0xd9, 0x16, 0x5e, 0xa6, 0xc9, 0x3e, 0xec, 0x2d, 0xc9, 0x06, 0xdf, - 0xb6, 0xdb, 0xfd, 0x98, 0x91, 0x21, 0xaf, 0xe0, 0xc5, 0x12, 0xa3, 0xd3, 0x1d, 0x5c, 0x9d, 0x9e, - 0x76, 0x5a, 0x9d, 0x76, 0x77, 0x68, 0xbd, 0x69, 0x5e, 0x5c, 0xb5, 0xf5, 0x2c, 0xd9, 0x83, 0xda, - 0xb2, 0x91, 0xf6, 0x65, 0xbf, 0x67, 0x36, 0xcd, 0xb7, 0x7a, 0x8e, 0xbc, 0x84, 0xe7, 0x0f, 0x94, - 0xb4, 0x7a, 0xa6, 0xd9, 0x6e, 0x0d, 0xad, 0xe6, 0x65, 0xef, 0xaa, 0x3b, 0xd4, 0x37, 0x0e, 0x1b, - 0x62, 0xc9, 0x59, 0x7a, 0xe0, 0x22, 0x64, 0x57, 0xdd, 0x9f, 0x76, 0x7b, 0xdf, 0x76, 0xf5, 0x27, - 0x22, 0xf2, 0xc3, 0x33, 0xb3, 0x3d, 0x38, 0xeb, 0x5d, 0x9c, 0xe8, 0xa9, 0xe3, 0xbf, 0x15, 0xe5, - 0x82, 0xdd, 0xc2, 0xcf, 0x72, 0x62, 0x42, 0x5e, 0xa5, 0x99, 0xac, 0x4b, 0x7c, 0xfd, 0xd9, 0xc2, - 0x92, 0x14, 0xbf, 0xb4, 0x9d, 0xdf, 0xfe, 0xf5, 0xef, 0xbf, 0x4b, 0x6f, 0x1a, 0x5a, 0xe3, 0xfd, - 0x17, 0x0d, 0xc1, 0x68, 0x78, 0x33, 0xfe, 0x55, 0xea, 0x90, 0xf4, 0x60, 0x43, 0x7e, 0xaa, 0x91, - 0xed, 0x05, 0x95, 0xf1, 0xb7, 0xdb, 0x3a, 0x8d, 0xdb, 0xa8, 0x51, 0x37, 0x4a, 0xb1, 0x46, 0xdb, - 0x15, 0x0a, 0x7f, 0x04, 0x79, 0xf5, 0x99, 0x90, 0xb8, 0xe4, 0xe2, 0x87, 0x43, 0x7d, 0xd5, 0x26, - 0xf7, 0xc3, 0x14, 0xf9, 0x39, 0x14, 0xe3, 0x25, 0x90, 0xec, 0x26, 0x6a, 0x6c, 0xb1, 0x3e, 0xea, - 0xf5, 0x55, 0xa2, 0xc5, 0x6b, 0x91, 0x4a, 0x7c, 0x2d, 0x5c, 0x10, 0xc9, 0x95, 0xac, 0x03, 0xb1, - 0x20, 0x92, 0xda, 0x82, 0xf9, 0xc4, 0xce, 0xb8, 0xf2, 0x62, 0x46, 0x1d, 0x55, 0x3e, 0x25, 0x64, - 0x41, 0x65, 0xe3, 0x3b, 0x7b, 0xfc, 0x2b, 0xf2, 0x0b, 0xd0, 0x54, 0x02, 0x70, 0x8d, 0x23, 0xf3, - 0x60, 0x25, 0x77, 0xcd, 0xfa, 0xdc, 0x99, 0xe5, 0x85, 0x6f, 0x85, 0x76, 0x6f, 0xc6, 0x1b, 0x1c, - 0xb5, 0x5d, 0xc7, 0xda, 0x71, 0x3d, 0x48, 0x68, 0x4f, 0x2e, 0x5a, 0x8b, 0xda, 0x17, 0x16, 0x09, - 0x63, 0x1f, 0xb5, 0xd7, 0x49, 0x6d, 0x41, 0xfb, 0x3b, 0xc1, 0x69, 0x7c, 0x47, 0x1d, 0x2e, 0x3c, - 0xa8, 0x88, 0xe9, 0x80, 0x29, 0x7f, 0xd4, 0x87, 0x79, 0xd4, 0x96, 0xd6, 0x66, 0x63, 0x17, 0x8d, - 0x6c, 0x91, 0xcd, 0xc4, 0x53, 0x88, 0x3d, 0x98, 0x6b, 0x7f, 0xd4, 0x87, 0xa4, 0xf6, 0x45, 0x17, - 0x9e, 0xa3, 0xf6, 0x5d, 0xb2, 0x93, 0xd4, 0x9e, 0xf4, 0xe0, 0x2d, 0x94, 0x85, 0x8d, 0x68, 0x3f, - 0x08, 0x13, 0x2f, 0x79, 0x61, 0x09, 0xa9, 0xef, 0x3c, 0xc0, 0x17, 0xab, 0x83, 0x54, 0xd1, 0x44, - 0x48, 0x79, 0x43, 0x2e, 0x1e, 0x84, 0x03, 0x79, 0x38, 0x3a, 0x89, 0x11, 0xeb, 0x59, 0x3b, 0x57, - 0xeb, 0x8f, 0x8e, 0x08, 0x63, 0x0f, 0x0d, 0x6e, 0x93, 0xa7, 0x68, 0x30, 0x22, 0x34, 0x7c, 0xa9, - 0xff, 0xd7, 0x40, 0x06, 0x8f, 0x59, 0x5d, 0x3b, 0xac, 0xea, 0x2f, 0x1f, 0xe5, 0x2c, 0x06, 0xd4, - 0x58, 0x69, 0x5c, 0x94, 0x30, 0x03, 0x2d, 0x39, 0x7f, 0xc8, 0xdc, 0x97, 0x15, 0xd3, 0xaa, 0xfe, - 0xf1, 0x1a, 0xa9, 0xb2, 0x56, 0x43, 0x6b, 0x84, 0xe8, 0xc2, 0x1a, 0x9d, 0x71, 0xaf, 0x11, 0x4a, - 0xda, 0xf5, 0x06, 0xfe, 0xff, 0xf0, 0xcb, 0x7f, 0x07, 0x00, 0x00, 0xff, 0xff, 0x65, 0x6d, 0xf3, - 0xf6, 0x76, 0x14, 0x00, 0x00, + // 2280 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x58, 0xcf, 0x6f, 0x22, 0xc9, + 0xf5, 0x1f, 0xa0, 0x31, 0xf0, 0x68, 0xa0, 0x5d, 0x9e, 0xb1, 0x31, 0xeb, 0xd5, 0x7a, 0x7a, 0x76, + 0xbe, 0xeb, 0xf1, 0x77, 0xc7, 0x64, 0xbd, 0xa7, 0x8c, 0x76, 0x23, 0x31, 0x18, 0xaf, 0x71, 0x6c, + 0x20, 0x0d, 0x9e, 0xd5, 0x44, 0x91, 0x5a, 0x65, 0x28, 0xdb, 0xad, 0xa5, 0x7f, 0x4c, 0x77, 0x31, + 0x63, 0x6b, 0x95, 0x44, 0xca, 0x3f, 0xb0, 0x87, 0xfc, 0x07, 0xf9, 0x1b, 0x72, 0x4b, 0x6e, 0xb9, + 0xe6, 0x94, 0x1c, 0x73, 0x8d, 0x14, 0xe5, 0x90, 0xff, 0x21, 0xaa, 0x57, 0xdd, 0x4d, 0x37, 0x06, + 0x47, 0x39, 0xe4, 0x46, 0xbf, 0xf7, 0xa9, 0x57, 0xf5, 0x7e, 0xd6, 0xa7, 0x00, 0x75, 0x3c, 0xb5, + 0x98, 0xc3, 0x0f, 0x3c, 0xdf, 0xe5, 0x2e, 0x29, 0x4c, 0x5d, 0xd7, 0xf3, 0xbd, 0x71, 0x63, 0xe7, + 0xda, 0x75, 0xaf, 0xa7, 0xac, 0x49, 0x3d, 0xab, 0x49, 0x1d, 0xc7, 0xe5, 0x94, 0x5b, 0xae, 0x13, + 0x48, 0x98, 0xfe, 0x83, 0x02, 0xd5, 0x33, 0xd7, 0xf5, 0xfa, 0x33, 0x6e, 0xb0, 0x77, 0x33, 0x16, + 0x70, 0xa2, 0x41, 0x8e, 0xda, 0xbc, 0x9e, 0xd9, 0xcd, 0xec, 0xe5, 0x0c, 0xf1, 0x93, 0x10, 0x50, + 0x26, 0x2c, 0xe0, 0xf5, 0xec, 0x6e, 0x66, 0xaf, 0x64, 0xe0, 0x6f, 0xd2, 0x84, 0xc7, 0x36, 0xbd, + 0x35, 0x83, 0x0f, 0xd4, 0x33, 0x7d, 0x77, 0xc6, 0x2d, 0xe7, 0xda, 0xbc, 0x62, 0xac, 0x9e, 0xc3, + 0x65, 0xeb, 0x36, 0xbd, 0x1d, 0x7e, 0xa0, 0x9e, 0x21, 0x35, 0xc7, 0x8c, 0x91, 0x2f, 0x61, 0x53, + 0x2c, 0xf0, 0x7c, 0xe6, 0xd1, 0xbb, 0xd4, 0x12, 0x05, 0x97, 0x6c, 0xd8, 0xf4, 0x76, 0x80, 0xca, + 0xc4, 0xa2, 0x5d, 0x50, 0xe3, 0x5d, 0x04, 0x34, 0x8f, 0x50, 0x08, 0xad, 0x0b, 0xc4, 0xa7, 0x50, + 0x4d, 0x98, 0x15, 0x07, 0x5f, 0x43, 0x8c, 0x1a, 0x9b, 0x6b, 0xd9, 0x9c, 0xe8, 0x50, 0x11, 0x28, + 0xdb, 0x72, 0x98, 0x8f, 0x86, 0x0a, 0x08, 0x2a, 0xdb, 0xf4, 0xf6, 0x5c, 0xc8, 0x84, 0xa5, 0xcf, + 0x41, 0x13, 0x31, 0x33, 0xdd, 0x19, 0x37, 0xc7, 0x37, 0xd4, 0x71, 0xd8, 0xb4, 0x5e, 0xdc, 0xcd, + 0xec, 0x29, 0xaf, 0xb3, 0xf5, 0x8c, 0x51, 0x9d, 0xca, 0x28, 0xb5, 0xa5, 0x86, 0xec, 0xc3, 0xba, + 0x3b, 0xe3, 0xd7, 0xae, 0x70, 0x42, 0xa0, 0xcd, 0x80, 0xf1, 0x7a, 0x79, 0x37, 0xb7, 0xa7, 0x18, + 0xb5, 0x48, 0x21, 0xb0, 0x43, 0xc6, 0x05, 0x36, 0xf8, 0xc0, 0x98, 0x67, 0x8e, 0x5d, 0xe7, 0xca, + 0xe4, 0xd4, 0xbf, 0x66, 0xbc, 0x5e, 0xda, 0xcd, 0xec, 0xe5, 0x8d, 0x1a, 0x2a, 0xda, 0xae, 0x73, + 0x35, 0x42, 0x31, 0x79, 0x09, 0xe4, 0x86, 0x4f, 0xc7, 0x08, 0xb5, 0x7c, 0x5b, 0x26, 0xab, 0x5e, + 0x41, 0xf0, 0xba, 0xd0, 0xb4, 0x93, 0x0a, 0xf2, 0x0a, 0xb6, 0x31, 0x38, 0xde, 0xec, 0x72, 0x6a, + 0x8d, 0x51, 0x68, 0x4e, 0x18, 0x9d, 0x4c, 0x2d, 0x87, 0xd5, 0x41, 0x9c, 0xde, 0xd8, 0x12, 0x80, + 0xc1, 0x5c, 0x7f, 0x14, 0xaa, 0xc9, 0x63, 0xc8, 0x4f, 0xe9, 0x25, 0x9b, 0xd6, 0x55, 0xcc, 0xab, + 0xfc, 0xd0, 0xff, 0x91, 0x81, 0x8a, 0xa8, 0x88, 0xae, 0xb3, 0xba, 0x20, 0x16, 0xd3, 0x92, 0xbd, + 0x97, 0x96, 0x7b, 0x01, 0xcf, 0xdd, 0x0f, 0xf8, 0x36, 0x14, 0xa7, 0x34, 0xe0, 0xe6, 0x8d, 0xeb, + 0x61, 0x0d, 0xa8, 0x46, 0x41, 0x7c, 0x9f, 0xb8, 0x1e, 0x79, 0x06, 0x15, 0x76, 0xcb, 0x99, 0xef, + 0xd0, 0xa9, 0x29, 0x9c, 0xc6, 0xc4, 0x17, 0x0d, 0x35, 0x12, 0x9e, 0xf0, 0xe9, 0x98, 0xec, 0x81, + 0x16, 0x87, 0x2a, 0x8a, 0xea, 0x1a, 0x06, 0xaa, 0x1a, 0x05, 0x2a, 0x0c, 0x6a, 0xec, 0x69, 0x21, + 0xe9, 0xe9, 0x3f, 0x33, 0xa0, 0x62, 0x91, 0xb2, 0xc0, 0x73, 0x9d, 0x80, 0x11, 0x02, 0x59, 0x6b, + 0x82, 0x7e, 0x96, 0x30, 0xe7, 0x59, 0x6b, 0x22, 0x0e, 0x69, 0x4d, 0xcc, 0xcb, 0x3b, 0xce, 0x02, + 0xf4, 0x41, 0x35, 0x0a, 0xd6, 0xe4, 0xb5, 0xf8, 0x24, 0xcf, 0x41, 0xc5, 0xfd, 0xe9, 0x64, 0xe2, + 0xb3, 0x20, 0x90, 0xed, 0x81, 0x0b, 0xcb, 0x42, 0xde, 0x92, 0x62, 0x72, 0x00, 0x1b, 0x49, 0x98, + 0xe9, 0x78, 0x87, 0x1f, 0x82, 0x1b, 0xf4, 0xb8, 0x24, 0x53, 0x1a, 0x22, 0x7b, 0xa8, 0x20, 0x9f, + 0x87, 0x15, 0x10, 0xe1, 0x25, 0x3c, 0x8f, 0x70, 0x2d, 0x01, 0x1f, 0x20, 0xfa, 0x39, 0x54, 0x03, + 0xe6, 0xbf, 0x67, 0xbe, 0x69, 0xb3, 0x20, 0xa0, 0xd7, 0x0c, 0x43, 0x50, 0x32, 0x2a, 0x52, 0x7a, + 0x2e, 0x85, 0xba, 0x06, 0xd5, 0x73, 0xd7, 0xb1, 0xb8, 0xeb, 0x87, 0x59, 0xd5, 0x7f, 0xaf, 0x00, + 0x08, 0xef, 0x87, 0x9c, 0xf2, 0x59, 0xb0, 0xb4, 0xeb, 0x45, 0x34, 0xb2, 0x2b, 0xa3, 0x51, 0x5e, + 0x8c, 0x86, 0xc2, 0xef, 0x3c, 0x99, 0xe8, 0xea, 0xe1, 0xfa, 0x41, 0x38, 0x7f, 0x0e, 0xc4, 0x1e, + 0xa3, 0x3b, 0x8f, 0x19, 0xa8, 0x26, 0x7b, 0x90, 0x0f, 0x38, 0xe5, 0xb2, 0xeb, 0xab, 0x87, 0x24, + 0x85, 0x13, 0x67, 0x61, 0x86, 0x04, 0x90, 0xaf, 0xa1, 0x7a, 0x45, 0xad, 0xe9, 0xcc, 0x67, 0xa6, + 0xcf, 0x68, 0xe0, 0x3a, 0xf5, 0x2a, 0x2e, 0xd9, 0x8c, 0x97, 0x1c, 0x4b, 0xb5, 0x81, 0x5a, 0xa3, + 0x72, 0x95, 0xfc, 0x24, 0x9f, 0x41, 0xcd, 0x72, 0x2c, 0x6e, 0xc9, 0x9e, 0xe0, 0x96, 0x1d, 0x4d, + 0x8f, 0xea, 0x5c, 0x3c, 0xb2, 0x6c, 0x71, 0x22, 0x0d, 0xcb, 0x70, 0xe6, 0x4d, 0x28, 0x67, 0x12, + 0x29, 0x67, 0x48, 0x55, 0xc8, 0x2f, 0x50, 0x8c, 0xc8, 0xc5, 0x84, 0x17, 0x96, 0x27, 0x7c, 0x79, + 0x02, 0xd5, 0x15, 0x09, 0x5c, 0x51, 0x1e, 0x95, 0x55, 0xe5, 0xf1, 0x09, 0x94, 0xc7, 0x6e, 0xc0, + 0x4d, 0x99, 0x5f, 0x9c, 0x50, 0x39, 0x03, 0x84, 0x68, 0x88, 0x12, 0xf2, 0x14, 0x54, 0x04, 0xb8, + 0xce, 0xf8, 0x86, 0x5a, 0x0e, 0x0e, 0x9a, 0x9c, 0x81, 0x8b, 0xfa, 0x52, 0x24, 0xda, 0x4b, 0x42, + 0xae, 0xae, 0x24, 0x06, 0xe4, 0xcc, 0x44, 0x4c, 0x28, 0x9b, 0x37, 0x4d, 0x2d, 0xd9, 0x34, 0x04, + 0xb4, 0x33, 0x2b, 0xe0, 0x22, 0x5b, 0x41, 0x54, 0x4a, 0x3f, 0x81, 0xf5, 0x84, 0x2c, 0x6c, 0xa6, + 0x17, 0x90, 0x17, 0xf3, 0x21, 0xa8, 0x67, 0x76, 0x73, 0x7b, 0xe5, 0xc3, 0x8d, 0x7b, 0x89, 0x9e, + 0x05, 0x86, 0x44, 0xe8, 0x4f, 0xa1, 0x26, 0x84, 0x5d, 0xe7, 0xca, 0x8d, 0x66, 0x4e, 0x35, 0x6e, + 0x45, 0x55, 0x14, 0x9e, 0x5e, 0x05, 0x75, 0xc4, 0x7c, 0x3b, 0xde, 0xf2, 0xd7, 0x50, 0xeb, 0x3a, + 0xa1, 0x24, 0xdc, 0xf0, 0xff, 0xa0, 0x66, 0x5b, 0x8e, 0x1c, 0x4a, 0xd4, 0x76, 0x67, 0x0e, 0x0f, + 0x13, 0x5e, 0xb1, 0x2d, 0x47, 0xd8, 0x6f, 0xa1, 0x10, 0x71, 0xd1, 0xf0, 0x0a, 0x71, 0x6b, 0x21, + 0x4e, 0xce, 0x2f, 0x89, 0x3b, 0x55, 0x8a, 0x19, 0x2d, 0x7b, 0xaa, 0x14, 0xb3, 0x5a, 0xee, 0x54, + 0x29, 0xe6, 0x34, 0xe5, 0x54, 0x29, 0x2a, 0x5a, 0xfe, 0x54, 0x29, 0x16, 0xb4, 0xa2, 0xfe, 0xe7, + 0x0c, 0x68, 0xfd, 0x19, 0xff, 0x9f, 0x1e, 0x01, 0x2f, 0x37, 0xcb, 0x31, 0xc7, 0x53, 0xfe, 0xde, + 0x9c, 0xb0, 0x29, 0xa7, 0x98, 0xee, 0xbc, 0xa1, 0xda, 0x96, 0xd3, 0x9e, 0xf2, 0xf7, 0x47, 0x42, + 0x16, 0x5d, 0x81, 0x09, 0x54, 0x29, 0x44, 0xd1, 0xdb, 0x18, 0xf5, 0x1f, 0xdc, 0xf9, 0x5d, 0x06, + 0xd4, 0x9f, 0xcd, 0x5c, 0xce, 0x56, 0x0f, 0x7d, 0x2c, 0xbc, 0xf9, 0xa4, 0xcd, 0xe2, 0x1e, 0x30, + 0x9e, 0x4f, 0xd9, 0x7b, 0x43, 0x3b, 0xb7, 0x64, 0x68, 0x3f, 0x78, 0x61, 0x29, 0x0f, 0x5e, 0x58, + 0xfa, 0x0f, 0x19, 0x91, 0xf5, 0xf0, 0x98, 0x61, 0xc8, 0x77, 0x41, 0x8d, 0xae, 0x21, 0x33, 0xa0, + 0xd1, 0x81, 0x21, 0x90, 0xf7, 0xd0, 0x90, 0x22, 0x53, 0xc1, 0x06, 0xc3, 0x1d, 0x83, 0x9b, 0x18, + 0x19, 0x32, 0x15, 0xa1, 0x1b, 0x48, 0x55, 0xb8, 0xe0, 0x63, 0x80, 0x44, 0x2c, 0xf3, 0xe8, 0x67, + 0x69, 0x9c, 0x08, 0xa4, 0x0c, 0xa1, 0xa2, 0xe5, 0xf5, 0xbf, 0xc8, 0x2a, 0xf8, 0x6f, 0x8f, 0xf4, + 0x29, 0x54, 0xe7, 0x84, 0x05, 0x31, 0xf2, 0x06, 0x55, 0xbd, 0x88, 0xb1, 0x08, 0xd4, 0xff, 0x87, + 0x73, 0x44, 0x72, 0x87, 0xf4, 0xb1, 0x6b, 0x42, 0x33, 0x14, 0x8a, 0xd0, 0x24, 0x72, 0x0c, 0x11, + 0x57, 0x7a, 0x67, 0x33, 0x87, 0x9b, 0x48, 0xd8, 0xe4, 0xad, 0x5a, 0xc3, 0x78, 0x4a, 0xf9, 0x91, + 0xc8, 0xed, 0xc3, 0x0e, 0xea, 0x35, 0xa8, 0x8c, 0xdc, 0xef, 0x98, 0x13, 0x37, 0xdb, 0x57, 0x50, + 0x8d, 0x04, 0xa1, 0x8b, 0xfb, 0xb0, 0xc6, 0x51, 0x12, 0x76, 0xf7, 0x7c, 0x8c, 0x9f, 0x05, 0x94, + 0x23, 0xd8, 0x08, 0x11, 0xfa, 0x1f, 0xb2, 0x50, 0x8a, 0xa5, 0xa2, 0x48, 0x2e, 0x69, 0xc0, 0x4c, + 0x9b, 0x8e, 0xa9, 0xef, 0xba, 0x4e, 0xd8, 0xe3, 0xaa, 0x10, 0x9e, 0x87, 0x32, 0x31, 0xc2, 0x22, + 0x3f, 0x6e, 0x68, 0x70, 0x83, 0xd1, 0x51, 0x8d, 0x72, 0x28, 0x3b, 0xa1, 0xc1, 0x0d, 0x79, 0x01, + 0x5a, 0x04, 0xf1, 0x7c, 0x66, 0xd9, 0xe2, 0xe6, 0x93, 0xf7, 0x73, 0x2d, 0x94, 0x0f, 0x42, 0xb1, + 0x18, 0xf0, 0xb2, 0xc9, 0x4c, 0x8f, 0x5a, 0x13, 0xd3, 0x16, 0x51, 0x94, 0x9c, 0xb3, 0x2a, 0xe5, + 0x03, 0x6a, 0x4d, 0xce, 0x03, 0xca, 0xc9, 0x17, 0xf0, 0x24, 0x41, 0x4c, 0x13, 0x70, 0xd9, 0xc5, + 0xc4, 0x8f, 0x99, 0x69, 0xbc, 0xe4, 0x29, 0xa8, 0xe2, 0xc6, 0x30, 0xc7, 0x3e, 0xa3, 0x9c, 0x4d, + 0xc2, 0x3e, 0x2e, 0x0b, 0x59, 0x5b, 0x8a, 0x48, 0x1d, 0x0a, 0xec, 0xd6, 0xb3, 0x7c, 0x36, 0xc1, + 0x1b, 0xa3, 0x68, 0x44, 0x9f, 0x62, 0x71, 0xc0, 0x5d, 0x9f, 0x5e, 0x33, 0xd3, 0xa1, 0x36, 0xc3, + 0xee, 0x2e, 0x19, 0xe5, 0x50, 0xd6, 0xa3, 0x36, 0xd3, 0x3f, 0x82, 0xed, 0x6f, 0x18, 0x3f, 0xb3, + 0xde, 0xcd, 0xac, 0x89, 0xc5, 0xef, 0x06, 0xd4, 0xa7, 0xf3, 0x29, 0xf8, 0xa7, 0x1c, 0x6c, 0xa4, + 0x55, 0x8c, 0x33, 0x5f, 0xdc, 0x40, 0x79, 0x7f, 0x36, 0x65, 0x51, 0x76, 0xe6, 0x37, 0x66, 0x0c, + 0x36, 0x66, 0x53, 0x66, 0x48, 0x10, 0xf9, 0x1a, 0x76, 0xe6, 0x25, 0xe6, 0x8b, 0x3b, 0x30, 0xa0, + 0xdc, 0xf4, 0x98, 0x6f, 0xbe, 0x17, 0x37, 0x3d, 0x46, 0x1f, 0xbb, 0x52, 0x56, 0x9b, 0x41, 0xb9, + 0xa8, 0xb8, 0x01, 0xf3, 0xdf, 0x08, 0x35, 0xf9, 0x0c, 0xb4, 0x24, 0x19, 0x34, 0x3d, 0xcf, 0xc6, + 0x4c, 0x28, 0xf1, 0x34, 0x13, 0xf1, 0xf2, 0x6c, 0xf2, 0x12, 0x04, 0xc7, 0x37, 0x53, 0x11, 0xf6, + 0xec, 0xb0, 0xe9, 0x85, 0x8d, 0x39, 0xf1, 0x17, 0xf0, 0x57, 0xd0, 0x58, 0xfe, 0x60, 0xc0, 0x55, + 0x79, 0x5c, 0xb5, 0xb9, 0xe4, 0xd1, 0x20, 0xd6, 0xa6, 0x5f, 0x05, 0x22, 0x83, 0x6b, 0x88, 0x9f, + 0xbf, 0x0a, 0x44, 0xcf, 0xbc, 0x80, 0xf5, 0x14, 0x49, 0x45, 0x60, 0x01, 0x81, 0xd5, 0x04, 0x51, + 0x8d, 0xdb, 0x6b, 0x91, 0xc2, 0x17, 0x97, 0x53, 0xf8, 0x03, 0xd8, 0x88, 0x88, 0xcb, 0x25, 0x1d, + 0x7f, 0xe7, 0x5e, 0x5d, 0x99, 0x01, 0x1b, 0xe3, 0x50, 0x56, 0x8c, 0xf5, 0x50, 0xf5, 0x5a, 0x6a, + 0x86, 0x6c, 0xac, 0xff, 0x51, 0x30, 0xee, 0x64, 0x62, 0xb0, 0x41, 0xe5, 0x3b, 0xc3, 0x0c, 0x6f, + 0x41, 0xc5, 0x28, 0x85, 0x92, 0xee, 0x84, 0x1c, 0x84, 0x54, 0x2b, 0x8b, 0x7c, 0xa8, 0xb1, 0x3c, + 0xbb, 0x09, 0xce, 0xf5, 0x12, 0x88, 0xe5, 0x8c, 0x5d, 0x5b, 0xc4, 0x8f, 0xdf, 0xf8, 0x2c, 0xb8, + 0x71, 0xa7, 0x13, 0xcc, 0x51, 0xc5, 0x58, 0x8f, 0x34, 0xa3, 0x48, 0x21, 0xe0, 0xf1, 0xd3, 0x66, + 0x0e, 0x57, 0x24, 0x3c, 0xd2, 0xc4, 0x70, 0xfd, 0x2d, 0x6c, 0x0f, 0x57, 0x55, 0x28, 0xf9, 0x0a, + 0xc0, 0x8b, 0xeb, 0x12, 0x3d, 0x29, 0x1f, 0xee, 0xdc, 0x3f, 0xf0, 0xbc, 0x76, 0x8d, 0x04, 0x5e, + 0xdf, 0x81, 0xc6, 0x32, 0xd3, 0x72, 0x08, 0xe9, 0x4f, 0x60, 0x63, 0x38, 0xbb, 0xbe, 0x66, 0x0b, + 0x6c, 0xe4, 0x14, 0x1e, 0xa7, 0xc5, 0xe1, 0xcc, 0x3a, 0x84, 0x62, 0xf4, 0xbe, 0x0b, 0xfb, 0x62, + 0x6b, 0x7e, 0x90, 0xd4, 0x13, 0xd8, 0x28, 0x84, 0x8f, 0xbd, 0xfd, 0xe7, 0x50, 0x8c, 0xf8, 0x2b, + 0x51, 0xa1, 0x78, 0xd6, 0xef, 0x0f, 0xcc, 0xfe, 0xc5, 0x48, 0x7b, 0x44, 0xca, 0x50, 0xc0, 0xaf, + 0x6e, 0x4f, 0xcb, 0xec, 0x07, 0x50, 0x8a, 0xe9, 0x2b, 0xa9, 0x40, 0xa9, 0xdb, 0xeb, 0x8e, 0xba, + 0xad, 0x51, 0xe7, 0x48, 0x7b, 0x44, 0x9e, 0xc0, 0xfa, 0xc0, 0xe8, 0x74, 0xcf, 0x5b, 0xdf, 0x74, + 0x4c, 0xa3, 0xf3, 0xa6, 0xd3, 0x3a, 0xeb, 0x1c, 0x69, 0x19, 0x42, 0xa0, 0x7a, 0x32, 0x3a, 0x6b, + 0x9b, 0x83, 0x8b, 0xd7, 0x67, 0xdd, 0xe1, 0x49, 0xe7, 0x48, 0xcb, 0x0a, 0x9b, 0xc3, 0x8b, 0x76, + 0xbb, 0x33, 0x1c, 0x6a, 0x39, 0x02, 0xb0, 0x76, 0xdc, 0xea, 0x0a, 0xb0, 0x42, 0x36, 0xa0, 0xd6, + 0xed, 0xbd, 0xe9, 0x77, 0xdb, 0x1d, 0x73, 0xd8, 0x19, 0x8d, 0x84, 0x30, 0xbf, 0xff, 0xaf, 0x0c, + 0x54, 0x52, 0x0c, 0x98, 0x6c, 0xc1, 0x86, 0x58, 0x72, 0x61, 0x88, 0x9d, 0x5a, 0xc3, 0x7e, 0xcf, + 0xec, 0xf5, 0x7b, 0x1d, 0xed, 0x11, 0xf9, 0x08, 0xb6, 0x16, 0x14, 0xfd, 0xe3, 0xe3, 0xf6, 0x49, + 0x4b, 0x1c, 0x9e, 0x34, 0x60, 0x73, 0x41, 0x39, 0xea, 0x9e, 0x77, 0x84, 0x97, 0x59, 0xb2, 0x0b, + 0x3b, 0x0b, 0xba, 0xe1, 0xb7, 0x9d, 0xce, 0x20, 0x46, 0xe4, 0xc8, 0x73, 0x78, 0xba, 0x80, 0xe8, + 0xf6, 0x86, 0x17, 0xc7, 0xc7, 0xdd, 0x76, 0xb7, 0xd3, 0x1b, 0x99, 0x6f, 0x5a, 0x67, 0x17, 0x1d, + 0x4d, 0x21, 0x3b, 0x50, 0x5f, 0xdc, 0xa4, 0x73, 0x3e, 0xe8, 0x1b, 0x2d, 0xe3, 0xad, 0x96, 0x27, + 0xcf, 0xe0, 0x93, 0x7b, 0x46, 0xda, 0x7d, 0xc3, 0xe8, 0xb4, 0x47, 0x66, 0xeb, 0xbc, 0x7f, 0xd1, + 0x1b, 0x69, 0x6b, 0xfb, 0x4d, 0xc1, 0x32, 0x17, 0x0a, 0x5c, 0x84, 0xec, 0xa2, 0xf7, 0xd3, 0x5e, + 0xff, 0xdb, 0x9e, 0xf6, 0x48, 0x44, 0x7e, 0x74, 0x62, 0x74, 0x86, 0x27, 0xfd, 0xb3, 0x23, 0x2d, + 0x73, 0xf8, 0xb7, 0x92, 0x7c, 0xe1, 0xb4, 0xf1, 0x7f, 0x11, 0x62, 0x40, 0x21, 0x4c, 0x33, 0x59, + 0x95, 0xf8, 0xc6, 0x93, 0x14, 0x4b, 0x8d, 0x2b, 0x6d, 0xeb, 0x37, 0x7f, 0xfd, 0xfb, 0x6f, 0xb3, + 0xeb, 0xba, 0xda, 0x7c, 0xff, 0x45, 0x53, 0x20, 0x9a, 0xee, 0x8c, 0xbf, 0xca, 0xec, 0x93, 0x3e, + 0xac, 0xc9, 0xb7, 0x32, 0xd9, 0x4c, 0x99, 0x8c, 0x1f, 0xcf, 0xab, 0x2c, 0x6e, 0xa2, 0x45, 0x4d, + 0x2f, 0xc7, 0x16, 0x2d, 0x47, 0x18, 0xfc, 0x31, 0x14, 0xc2, 0x77, 0x5a, 0xe2, 0x90, 0xe9, 0x97, + 0x5b, 0x63, 0x19, 0x95, 0xfe, 0x51, 0x86, 0xfc, 0x1c, 0x4a, 0x31, 0x0b, 0x27, 0xdb, 0x89, 0x1e, + 0x4b, 0xf7, 0x47, 0xa3, 0xb1, 0x4c, 0x95, 0x3e, 0x16, 0xa9, 0xc6, 0xc7, 0x42, 0x86, 0x4e, 0x2e, + 0x64, 0x1f, 0x08, 0x86, 0x4e, 0xea, 0xa9, 0xed, 0x13, 0xa4, 0x7d, 0xe9, 0xc1, 0xf4, 0x06, 0x9a, + 0x7c, 0x4c, 0x48, 0xca, 0x64, 0xf3, 0x7b, 0x6b, 0xf2, 0x4b, 0xf2, 0x0b, 0x50, 0xc3, 0x04, 0x20, + 0x8f, 0x26, 0xf3, 0x60, 0x25, 0xc9, 0x7e, 0x63, 0xee, 0xcc, 0x22, 0xe3, 0x5e, 0x62, 0xdd, 0x9d, + 0xf1, 0x26, 0x47, 0x6b, 0x97, 0xb1, 0x75, 0xe4, 0x67, 0x09, 0xeb, 0x49, 0xa6, 0x9b, 0xb6, 0x9e, + 0x62, 0x72, 0xfa, 0x2e, 0x5a, 0x6f, 0x90, 0x7a, 0xca, 0xfa, 0x3b, 0x81, 0x69, 0x7e, 0x4f, 0x6d, + 0x2e, 0x3c, 0xa8, 0x8a, 0xeb, 0x19, 0x53, 0xfe, 0xa0, 0x0f, 0xf3, 0xa8, 0x2d, 0xbc, 0x5b, 0xf4, + 0x6d, 0xdc, 0x64, 0x83, 0xac, 0x27, 0x4a, 0x21, 0xf6, 0x60, 0x6e, 0xfd, 0x41, 0x1f, 0x92, 0xd6, + 0xd3, 0x2e, 0x7c, 0x82, 0xd6, 0xb7, 0xc9, 0x56, 0xd2, 0x7a, 0xd2, 0x83, 0xb7, 0x50, 0x11, 0x7b, + 0x44, 0x04, 0x2d, 0x48, 0x54, 0x72, 0x8a, 0x05, 0x36, 0xb6, 0xee, 0xc9, 0xd3, 0xdd, 0x41, 0x6a, + 0xb8, 0x45, 0x40, 0x79, 0x53, 0x32, 0x3f, 0xc2, 0x81, 0xdc, 0xe7, 0x2e, 0x44, 0x8f, 0xed, 0xac, + 0x24, 0x36, 0x8d, 0x07, 0xaf, 0x08, 0x7d, 0x07, 0x37, 0xdc, 0x24, 0x8f, 0x71, 0xc3, 0x08, 0xd0, + 0xf4, 0xa4, 0xfd, 0x5f, 0x01, 0x19, 0x3e, 0xb4, 0xeb, 0xca, 0xcb, 0xaa, 0xf1, 0xec, 0x41, 0x4c, + 0x3a, 0xa0, 0xfa, 0xd2, 0xcd, 0x45, 0x0b, 0x33, 0x50, 0x93, 0xf7, 0x0f, 0x99, 0xfb, 0xb2, 0xe4, + 0xb6, 0x6a, 0x7c, 0xbc, 0x42, 0x1b, 0xee, 0x56, 0xc7, 0xdd, 0x08, 0xd1, 0xc4, 0x6e, 0x74, 0xc6, + 0xdd, 0x66, 0x20, 0x61, 0x97, 0x6b, 0xf8, 0x07, 0xee, 0x97, 0xff, 0x0e, 0x00, 0x00, 0xff, 0xff, + 0xff, 0xa6, 0xdb, 0xca, 0xf7, 0x15, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. diff --git a/looprpc/client.proto b/looprpc/client.proto index 78453fb..ed81681 100644 --- a/looprpc/client.proto +++ b/looprpc/client.proto @@ -710,6 +710,58 @@ message LiquidityParameters{ A set of liquidity rules that describe the desired liquidity balance. */ repeated LiquidityRule rules = 1; + + /* + The limit we place on our estimated sweep cost for a swap in sat/vByte. If + the estimated fee for our sweep transaction within the specified + confirmation target is above this value, we will not suggest any swaps. + */ + uint64 sweep_fee_rate_sat_per_vbyte = 2; + + /* + The maximum fee paid to the server for facilitating the swap, expressed + as parts per million of the swap volume. + */ + uint64 max_swap_fee_ppm = 3; + + /* + The maximum fee paid to route the swap invoice off chain, expressed as + parts per million of the volume being routed. + */ + uint64 max_routing_fee_ppm = 4; + + /* + The maximum fee paid to route the prepay invoice off chain, expressed as + parts per million of the volume being routed. + */ + uint64 max_prepay_routing_fee_ppm = 5; + + /* + The maximum no-show penalty in satoshis paid for a swap. + */ + uint64 max_prepay_sat = 6; + + /* + The maximum miner fee we will pay to sweep the swap on chain. Note that we + will not suggest a swap if the estimate is above the sweep limit set by + these parameters, and we use the current fee estimate to sweep on chain so + this value is only a cap placed on the amount we spend on fees in the case + where the swap needs to be claimed on chain, but fees have suddenly spiked. + */ + uint64 max_miner_fee_sat = 7; + + /* + The number of blocks from the on-chain HTLC's confirmation height that it + should be swept within. + */ + int32 sweep_conf_target = 8; + + /* + The amount of time we require pass since a channel was part of a failed + swap due to off chain payment failure until it will be considered for swap + suggestions again, expressed in seconds. + */ + uint64 failure_backoff_sec = 9; } enum LiquidityRuleType{ diff --git a/looprpc/client.swagger.json b/looprpc/client.swagger.json index 2161365..2af10ce 100644 --- a/looprpc/client.swagger.json +++ b/looprpc/client.swagger.json @@ -453,6 +453,46 @@ "$ref": "#/definitions/looprpcLiquidityRule" }, "description": "A set of liquidity rules that describe the desired liquidity balance." + }, + "sweep_fee_rate_sat_per_vbyte": { + "type": "string", + "format": "uint64", + "description": "The limit we place on our estimated sweep cost for a swap in sat/vByte. If\nthe estimated fee for our sweep transaction within the specified\nconfirmation target is above this value, we will not suggest any swaps." + }, + "max_swap_fee_ppm": { + "type": "string", + "format": "uint64", + "description": "The maximum fee paid to the server for facilitating the swap, expressed\nas parts per million of the swap volume." + }, + "max_routing_fee_ppm": { + "type": "string", + "format": "uint64", + "description": "The maximum fee paid to route the swap invoice off chain, expressed as\nparts per million of the volume being routed." + }, + "max_prepay_routing_fee_ppm": { + "type": "string", + "format": "uint64", + "description": "The maximum fee paid to route the prepay invoice off chain, expressed as\nparts per million of the volume being routed." + }, + "max_prepay_sat": { + "type": "string", + "format": "uint64", + "description": "The maximum no-show penalty in satoshis paid for a swap." + }, + "max_miner_fee_sat": { + "type": "string", + "format": "uint64", + "description": "The maximum miner fee we will pay to sweep the swap on chain. Note that we\nwill not suggest a swap if the estimate is above the sweep limit set by\nthese parameters, and we use the current fee estimate to sweep on chain so\nthis value is only a cap placed on the amount we spend on fees in the case\nwhere the swap needs to be claimed on chain, but fees have suddenly spiked." + }, + "sweep_conf_target": { + "type": "integer", + "format": "int32", + "description": "The number of blocks from the on-chain HTLC's confirmation height that it\nshould be swept within." + }, + "failure_backoff_sec": { + "type": "string", + "format": "uint64", + "description": "The amount of time we require pass since a channel was part of a failed\nswap due to off chain payment failure until it will be considered for swap\nsuggestions again, expressed in seconds." } } }, From 8931fd370c4953fb94b901c48cf82b37e1075b5b Mon Sep 17 00:00:00 2001 From: carla Date: Wed, 30 Sep 2020 12:34:11 +0200 Subject: [PATCH 09/11] loop: rename setparam command to setrule As we add more paramters to the liqudity manager, it will become more difficult to include them in a single cli endpoint with rules. This commit renames the existing setparam command (which is only used for rules at present), so that we can have a dedicated paramters cli command for all the new values we are adding. --- cmd/loop/liquidity.go | 22 ++++++++++++---------- cmd/loop/main.go | 2 +- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/cmd/loop/liquidity.go b/cmd/loop/liquidity.go index c382d7e..30716f1 100644 --- a/cmd/loop/liquidity.go +++ b/cmd/loop/liquidity.go @@ -36,8 +36,8 @@ func getParams(ctx *cli.Context) error { return nil } -var setLiquidityParamCommand = cli.Command{ - Name: "setparam", +var setLiquidityRuleCommand = cli.Command{ + Name: "setrule", Usage: "set liquidity manager rule for a channel", Description: "Update or remove the liquidity rule for a channel.", ArgsUsage: "shortchanid", @@ -58,10 +58,10 @@ var setLiquidityParamCommand = cli.Command{ Usage: "remove the rule currently set for the channel.", }, }, - Action: setParam, + Action: setRule, } -func setParam(ctx *cli.Context) error { +func setRule(ctx *cli.Context) error { // We require that a channel ID is set for this rule update. if ctx.NArg() != 1 { return fmt.Errorf("please set a channel id for the rule " + @@ -122,12 +122,11 @@ func setParam(ctx *cli.Context) error { "flag") } + params.Rules = otherRules _, err = client.SetLiquidityParams( context.Background(), &looprpc.SetLiquidityParamsRequest{ - Parameters: &looprpc.LiquidityParameters{ - Rules: otherRules, - }, + Parameters: params, }, ) return err @@ -158,13 +157,16 @@ func setParam(ctx *cli.Context) error { ) } + // Just set the rules on our current set of parameters and leave the + // other values untouched. + otherRules = append(otherRules, newRule) + params.Rules = otherRules + // Update our parameters to the existing set, plus our new rule. _, err = client.SetLiquidityParams( context.Background(), &looprpc.SetLiquidityParamsRequest{ - Parameters: &looprpc.LiquidityParameters{ - Rules: append(otherRules, newRule), - }, + Parameters: params, }, ) diff --git a/cmd/loop/main.go b/cmd/loop/main.go index 97d33cd..9749306 100644 --- a/cmd/loop/main.go +++ b/cmd/loop/main.go @@ -129,7 +129,7 @@ func main() { loopOutCommand, loopInCommand, termsCommand, monitorCommand, quoteCommand, listAuthCommand, listSwapsCommand, swapInfoCommand, getLiquidityParamsCommand, - setLiquidityParamCommand, suggestSwapCommand, + setLiquidityRuleCommand, suggestSwapCommand, } err := app.Run(os.Args) From ed95c16ae47e84f93ffa7402c977441d2e2f2849 Mon Sep 17 00:00:00 2001 From: carla Date: Wed, 30 Sep 2020 12:34:12 +0200 Subject: [PATCH 10/11] loop: add set params command --- cmd/loop/liquidity.go | 155 ++++++++++++++++++++++++++++++++++++++++++ cmd/loop/main.go | 2 +- 2 files changed, 156 insertions(+), 1 deletion(-) diff --git a/cmd/loop/liquidity.go b/cmd/loop/liquidity.go index 30716f1..35e4eb4 100644 --- a/cmd/loop/liquidity.go +++ b/cmd/loop/liquidity.go @@ -5,6 +5,7 @@ import ( "fmt" "strconv" + "github.com/lightninglabs/loop/liquidity" "github.com/lightninglabs/loop/looprpc" "github.com/urfave/cli" ) @@ -173,6 +174,160 @@ func setRule(ctx *cli.Context) error { return err } +var setParamsCommand = cli.Command{ + Name: "setparams", + Usage: "update the parameters set for the liquidity manager", + Description: "Updates the parameters set for the liquidity manager.", + Flags: []cli.Flag{ + cli.IntFlag{ + Name: "sweeplimit", + Usage: "the limit placed on our estimated sweep fee " + + "in sat/vByte.", + }, + cli.Float64Flag{ + Name: "maxswapfee", + Usage: "the maximum percentage of swap volume we are " + + "willing to pay in server fees.", + }, + cli.Float64Flag{ + Name: "maxroutingfee", + Usage: "the maximum percentage of off-chain payment " + + "volume that are are willing to pay in " + + "routing fees.", + }, + cli.Float64Flag{ + Name: "maxprepayfee", + Usage: "the maximum percentage of off-chain prepay " + + "volume that are are willing to pay in " + + "routing fees.", + }, + cli.Uint64Flag{ + Name: "maxprepay", + Usage: "the maximum no-show (prepay) in satoshis that " + + "swap suggestions should be limited to.", + }, + cli.Uint64Flag{ + Name: "maxminer", + Usage: "the maximum miner fee in satoshis that swap " + + "suggestions should be limited to.", + }, + cli.IntFlag{ + Name: "sweepconf", + Usage: "the number of blocks from htlc height that " + + "swap suggestion sweeps should target, used " + + "to estimate max miner fee.", + }, + cli.Uint64Flag{ + Name: "failurebackoff", + Usage: "the amount of time, in seconds, that " + + "should pass before a channel that " + + "previously had a failed swap will be " + + "included in suggestions.", + }, + }, + Action: setParams, +} + +func setParams(ctx *cli.Context) error { + client, cleanup, err := getClient(ctx) + if err != nil { + return err + } + defer cleanup() + + // We need to set the full set of current parameters every time we call + // SetParameters. To allow users to set only individual fields on the + // cli, we lookup our current params, then update individual values. + params, err := client.GetLiquidityParams( + context.Background(), &looprpc.GetLiquidityParamsRequest{}, + ) + if err != nil { + return err + } + + var flagSet bool + + if ctx.IsSet("maxswapfee") { + feeRate := ctx.Float64("maxswapfee") + params.MaxSwapFeePpm, err = ppmFromPercentage(feeRate) + if err != nil { + return err + } + + flagSet = true + } + + if ctx.IsSet("sweeplimit") { + satPerVByte := ctx.Int("sweeplimit") + params.SweepFeeRateSatPerVbyte = uint64(satPerVByte) + + flagSet = true + } + + if ctx.IsSet("maxroutingfee") { + feeRate := ctx.Float64("maxroutingfee") + params.MaxRoutingFeePpm, err = ppmFromPercentage(feeRate) + if err != nil { + return err + } + + flagSet = true + } + + if ctx.IsSet("maxprepayfee") { + feeRate := ctx.Float64("maxprepayfee") + params.MaxPrepayRoutingFeePpm, err = ppmFromPercentage(feeRate) + if err != nil { + return err + } + + flagSet = true + } + + if ctx.IsSet("maxprepay") { + params.MaxPrepaySat = ctx.Uint64("maxprepay") + flagSet = true + } + + if ctx.IsSet("maxminer") { + params.MaxMinerFeeSat = ctx.Uint64("maxminer") + flagSet = true + } + + if ctx.IsSet("sweepconf") { + params.SweepConfTarget = int32(ctx.Int("sweepconf")) + flagSet = true + } + + if ctx.IsSet("failurebackoff") { + params.FailureBackoffSec = ctx.Uint64("failurebackoff") + flagSet = true + } + + if !flagSet { + return fmt.Errorf("at least one flag required to set params") + } + + // Update our parameters to our mutated values. + _, err = client.SetLiquidityParams( + context.Background(), &looprpc.SetLiquidityParamsRequest{ + Parameters: params, + }, + ) + + return err +} + +// ppmFromPercentage converts a percentage, expressed as a float, to parts +// per million. +func ppmFromPercentage(percentage float64) (uint64, error) { + if percentage <= 0 || percentage >= 100 { + return 0, fmt.Errorf("fee percentage must be in (0;100)") + } + + return uint64(percentage / 100 * liquidity.FeeBase), nil +} + var suggestSwapCommand = cli.Command{ Name: "suggestswaps", Usage: "show a list of suggested swaps", diff --git a/cmd/loop/main.go b/cmd/loop/main.go index 9749306..0dcd620 100644 --- a/cmd/loop/main.go +++ b/cmd/loop/main.go @@ -129,7 +129,7 @@ func main() { loopOutCommand, loopInCommand, termsCommand, monitorCommand, quoteCommand, listAuthCommand, listSwapsCommand, swapInfoCommand, getLiquidityParamsCommand, - setLiquidityRuleCommand, suggestSwapCommand, + setLiquidityRuleCommand, suggestSwapCommand, setParamsCommand, } err := app.Run(os.Args) From 14ccdc9b61070e21cdae02c9c99b3f1ab0a85d30 Mon Sep 17 00:00:00 2001 From: carla Date: Wed, 30 Sep 2020 12:34:13 +0200 Subject: [PATCH 11/11] release_notes: add swap suggestions updates to release notes --- release_notes.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/release_notes.md b/release_notes.md index 6ecd2d4..dd26ac6 100644 --- a/release_notes.md +++ b/release_notes.md @@ -18,6 +18,19 @@ This file tracks release notes for the loop client. This has to potential to greatly reduce chain fee costs. Note that it is not yet possible to select specific peers to loop in through. +##### Updated Swap Suggestions +* The swap suggestions endpoint has been updated to be fee-aware. Swaps that + exceed the fee limits set by the liquidity manager will no longer be + suggested (see `getParams` for the current limits, and use `setParams` to + update these values). +* Swap suggestions are now aware of ongoing and previously failed swaps. They + will not suggest swaps for channels that are currently being utilized for + swaps, and will not suggest any swaps if a swap that is not limited to a + specific peer or channel is ongoing. If a channel was part of a failed swap + within the last 24H, it will be excluded from our swap suggestions (this + value is configurable). +* The `debug` logging level is recommended if using this feature. + #### Breaking Changes * Macaroon authentication has been enabled for the `loopd` gRPC and REST @@ -29,4 +42,8 @@ This file tracks release notes for the loop client. testnet you need to specify the `--network=testnet` flag. [More information about TLS and macaroons.](README.md#authentication-and-transport-security) +* The `setparm` loopcli endpoint is renamed to `setrule` because this endpoint + is only used for setting liqudity rules (parameters can be set using the new + `setparams` endpoint). + #### Bug Fixes \ No newline at end of file