diff --git a/liquidity/autoloop_testcontext_test.go b/liquidity/autoloop_testcontext_test.go index ef48bdf..e9b2f02 100644 --- a/liquidity/autoloop_testcontext_test.go +++ b/liquidity/autoloop_testcontext_test.go @@ -179,7 +179,7 @@ func newAutoloopTestCtx(t *testing.T, parameters Parameters, // Create a manager with our test config and set our starting set of // parameters. testCtx.manager = NewManager(cfg) - err := testCtx.manager.SetParameters(context.Background(), parameters) + err := testCtx.manager.setParameters(context.Background(), parameters) assert.NoError(t, err) <-done return testCtx diff --git a/liquidity/liquidity.go b/liquidity/liquidity.go index 3a9850b..1b20c03 100644 --- a/liquidity/liquidity.go +++ b/liquidity/liquidity.go @@ -52,6 +52,8 @@ import ( "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/ticker" + + clientrpc "github.com/lightninglabs/loop/looprpc" ) const ( @@ -239,9 +241,24 @@ func (m *Manager) GetParameters() Parameters { return cloneParameters(m.params) } +// SetParameters takes an RPC request and calls the internal method to set +// parameters for the manager. +func (m *Manager) SetParameters(ctx context.Context, + req *clientrpc.LiquidityParameters) error { + + params, err := rpcToParameters(req) + if err != nil { + return err + } + + return m.setParameters(ctx, *params) +} + // SetParameters updates our current set of parameters if the new parameters // provided are valid. -func (m *Manager) SetParameters(ctx context.Context, params Parameters) error { +func (m *Manager) setParameters(ctx context.Context, + params Parameters) error { + restrictions, err := m.cfg.Restrictions(ctx, swap.TypeOut) if err != nil { return err @@ -252,7 +269,9 @@ func (m *Manager) SetParameters(ctx context.Context, params Parameters) error { return err } - err = params.validate(m.cfg.MinimumConfirmations, channels, restrictions) + err = params.validate( + m.cfg.MinimumConfirmations, channels, restrictions, + ) if err != nil { return err } diff --git a/liquidity/liquidity_test.go b/liquidity/liquidity_test.go index f673289..5b5bba1 100644 --- a/liquidity/liquidity_test.go +++ b/liquidity/liquidity_test.go @@ -221,7 +221,7 @@ func TestParameters(t *testing.T) { chanID: originalRule, } - err := manager.SetParameters(context.Background(), expected) + err := manager.setParameters(context.Background(), expected) require.NoError(t, err) // Check that changing the parameters we just set does not mutate @@ -242,7 +242,7 @@ func TestParameters(t *testing.T) { Type: swap.TypeOut, }, } - err = manager.SetParameters(context.Background(), expected) + err = manager.setParameters(context.Background(), expected) require.Equal(t, ErrZeroChannelID, err) } @@ -1778,7 +1778,7 @@ func testSuggestSwaps(t *testing.T, setup *testSuggestSwapsSetup, // them to use the rules set by the test. manager := NewManager(setup.cfg) - err := manager.SetParameters(context.Background(), setup.params) + err := manager.setParameters(context.Background(), setup.params) require.NoError(t, err) actual, err := manager.SuggestSwaps(context.Background(), false) @@ -1960,7 +1960,7 @@ func TestCurrentTraffic(t *testing.T) { params := m.GetParameters() params.FailureBackOff = backoff - require.NoError(t, m.SetParameters(context.Background(), params)) + require.NoError(t, m.setParameters(context.Background(), params)) actual := m.currentSwapTraffic(testCase.loopOut, testCase.loopIn) require.Equal(t, testCase.expected, actual) diff --git a/liquidity/parameters.go b/liquidity/parameters.go index ff6355b..35cfb67 100644 --- a/liquidity/parameters.go +++ b/liquidity/parameters.go @@ -9,8 +9,11 @@ import ( "github.com/btcsuite/btcd/btcutil" "github.com/lightninglabs/lndclient" "github.com/lightninglabs/loop/swap" + "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing/route" + + clientrpc "github.com/lightninglabs/loop/looprpc" ) var ( @@ -271,3 +274,150 @@ func cloneParameters(params Parameters) Parameters { return paramCopy } + +// rpcToFee converts the values provided over rpc to a fee limit interface, +// failing if an inconsistent set of fields are set. +func rpcToFee(req *clientrpc.LiquidityParameters) (FeeLimit, error) { + // Check which fee limit type we have values set for. If any fields + // relevant to our individual categories are set, we count that type + // as set. + isFeePPM := req.FeePpm != 0 + isCategories := req.MaxSwapFeePpm != 0 || req.MaxRoutingFeePpm != 0 || + req.MaxPrepayRoutingFeePpm != 0 || req.MaxMinerFeeSat != 0 || + req.MaxPrepaySat != 0 || req.SweepFeeRateSatPerVbyte != 0 + + switch { + case isFeePPM && isCategories: + return nil, errors.New("set either fee ppm, or individual " + + "fee categories") + case isFeePPM: + return NewFeePortion(req.FeePpm), nil + + case isCategories: + satPerKVbyte := chainfee.SatPerKVByte( + req.SweepFeeRateSatPerVbyte * 1000, + ) + + return NewFeeCategoryLimit( + req.MaxSwapFeePpm, + req.MaxRoutingFeePpm, + req.MaxPrepayRoutingFeePpm, + btcutil.Amount(req.MaxMinerFeeSat), + btcutil.Amount(req.MaxPrepaySat), + satPerKVbyte.FeePerKWeight(), + ), nil + + default: + return nil, errors.New("no fee categories set") + } +} + +// rpcToRule switches on rpc rule type to convert to our rule interface. +func rpcToRule(rule *clientrpc.LiquidityRule) (*SwapRule, error) { + swapType := swap.TypeOut + if rule.SwapType == clientrpc.SwapType_LOOP_IN { + swapType = swap.TypeIn + } + + switch rule.Type { + case clientrpc.LiquidityRuleType_UNKNOWN: + return nil, fmt.Errorf("rule type field must be set") + + case clientrpc.LiquidityRuleType_THRESHOLD: + return &SwapRule{ + ThresholdRule: NewThresholdRule( + int(rule.IncomingThreshold), + int(rule.OutgoingThreshold), + ), + Type: swapType, + }, nil + + default: + return nil, fmt.Errorf("unknown rule: %T", rule) + } +} + +// rpcToParameters takes a `LiquidityParameters` and creates a `Parameters` +// from it. +func rpcToParameters(req *clientrpc.LiquidityParameters) (*Parameters, + error) { + + feeLimit, err := rpcToFee(req) + if err != nil { + return nil, err + } + + params := &Parameters{ + FeeLimit: feeLimit, + SweepConfTarget: req.SweepConfTarget, + FailureBackOff: time.Duration(req.FailureBackoffSec) * + time.Second, + Autoloop: req.Autoloop, + AutoFeeBudget: btcutil.Amount(req.AutoloopBudgetSat), + MaxAutoInFlight: int(req.AutoMaxInFlight), + ChannelRules: make( + map[lnwire.ShortChannelID]*SwapRule, + ), + PeerRules: make( + map[route.Vertex]*SwapRule, + ), + ClientRestrictions: Restrictions{ + Minimum: btcutil.Amount(req.MinSwapAmount), + Maximum: btcutil.Amount(req.MaxSwapAmount), + }, + HtlcConfTarget: req.HtlcConfTarget, + } + + // Zero unix time is different to zero golang time. + if req.AutoloopBudgetStartSec != 0 { + params.AutoFeeStartDate = time.Unix( + int64(req.AutoloopBudgetStartSec), 0, + ) + } + + for _, rule := range req.Rules { + peerRule := rule.Pubkey != nil + chanRule := rule.ChannelId != 0 + + liquidityRule, err := rpcToRule(rule) + if err != nil { + return nil, err + } + + switch { + case peerRule && chanRule: + return nil, fmt.Errorf("cannot set channel: %v and "+ + "peer: %v fields in rule", rule.ChannelId, + rule.Pubkey) + + case peerRule: + pubkey, err := route.NewVertexFromBytes(rule.Pubkey) + if err != nil { + return nil, err + } + + if _, ok := params.PeerRules[pubkey]; ok { + return nil, fmt.Errorf("multiple rules set "+ + "for peer: %v", pubkey) + } + + params.PeerRules[pubkey] = liquidityRule + + case chanRule: + shortID := lnwire.NewShortChanIDFromInt(rule.ChannelId) + + if _, ok := params.ChannelRules[shortID]; ok { + return nil, fmt.Errorf("multiple rules set "+ + "for channel: %v", shortID) + } + + params.ChannelRules[shortID] = liquidityRule + + default: + return nil, errors.New("please set channel id or " + + "pubkey for rule") + } + } + + return params, nil +} diff --git a/loopd/swapclient_server.go b/loopd/swapclient_server.go index 3e643ad..564d84a 100644 --- a/loopd/swapclient_server.go +++ b/loopd/swapclient_server.go @@ -21,8 +21,6 @@ import ( "github.com/lightninglabs/loop/swap" looprpc "github.com/lightninglabs/loop/swapserverrpc" "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" "github.com/lightningnetwork/lnd/zpay32" @@ -806,154 +804,14 @@ func (s *swapClientServer) SetLiquidityParams(ctx context.Context, in *clientrpc.SetLiquidityParamsRequest) (*clientrpc.SetLiquidityParamsResponse, error) { - feeLimit, err := rpcToFee(in.Parameters) + err := s.liquidityMgr.SetParameters(ctx, in.Parameters) if err != nil { return nil, err } - params := liquidity.Parameters{ - FeeLimit: feeLimit, - SweepConfTarget: in.Parameters.SweepConfTarget, - FailureBackOff: time.Duration(in.Parameters.FailureBackoffSec) * - time.Second, - Autoloop: in.Parameters.Autoloop, - AutoFeeBudget: btcutil.Amount(in.Parameters.AutoloopBudgetSat), - MaxAutoInFlight: int(in.Parameters.AutoMaxInFlight), - ChannelRules: make( - map[lnwire.ShortChannelID]*liquidity.SwapRule, - ), - PeerRules: make( - map[route.Vertex]*liquidity.SwapRule, - ), - ClientRestrictions: liquidity.Restrictions{ - Minimum: btcutil.Amount(in.Parameters.MinSwapAmount), - Maximum: btcutil.Amount(in.Parameters.MaxSwapAmount), - }, - HtlcConfTarget: in.Parameters.HtlcConfTarget, - } - - // Zero unix time is different to zero golang time. - if in.Parameters.AutoloopBudgetStartSec != 0 { - params.AutoFeeStartDate = time.Unix( - int64(in.Parameters.AutoloopBudgetStartSec), 0, - ) - } - - for _, rule := range in.Parameters.Rules { - peerRule := rule.Pubkey != nil - chanRule := rule.ChannelId != 0 - - liquidityRule, err := rpcToRule(rule) - if err != nil { - return nil, err - } - - switch { - case peerRule && chanRule: - return nil, fmt.Errorf("cannot set channel: %v and "+ - "peer: %v fields in rule", rule.ChannelId, - rule.Pubkey) - - case peerRule: - pubkey, err := route.NewVertexFromBytes(rule.Pubkey) - if err != nil { - return nil, err - } - - if _, ok := params.PeerRules[pubkey]; ok { - return nil, fmt.Errorf("multiple rules set "+ - "for peer: %v", pubkey) - } - - params.PeerRules[pubkey] = liquidityRule - - case chanRule: - shortID := lnwire.NewShortChanIDFromInt(rule.ChannelId) - - if _, ok := params.ChannelRules[shortID]; ok { - return nil, fmt.Errorf("multiple rules set "+ - "for channel: %v", shortID) - } - - params.ChannelRules[shortID] = liquidityRule - - default: - return nil, errors.New("please set channel id or " + - "pubkey for rule") - } - } - - if err := s.liquidityMgr.SetParameters(ctx, params); err != nil { - return nil, err - } - return &clientrpc.SetLiquidityParamsResponse{}, nil } -// rpcToFee converts the values provided over rpc to a fee limit interface, -// failing if an inconsistent set of fields are set. -func rpcToFee(req *clientrpc.LiquidityParameters) (liquidity.FeeLimit, - error) { - - // Check which fee limit type we have values set for. If any fields - // relevant to our individual categories are set, we count that type - // as set. - isFeePPM := req.FeePpm != 0 - isCategories := req.MaxSwapFeePpm != 0 || req.MaxRoutingFeePpm != 0 || - req.MaxPrepayRoutingFeePpm != 0 || req.MaxMinerFeeSat != 0 || - req.MaxPrepaySat != 0 || req.SweepFeeRateSatPerVbyte != 0 - - switch { - case isFeePPM && isCategories: - return nil, errors.New("set either fee ppm, or individual " + - "fee categories") - case isFeePPM: - return liquidity.NewFeePortion(req.FeePpm), nil - - case isCategories: - satPerVbyte := chainfee.SatPerKVByte( - req.SweepFeeRateSatPerVbyte * 1000, - ) - - return liquidity.NewFeeCategoryLimit( - req.MaxSwapFeePpm, - req.MaxRoutingFeePpm, - req.MaxPrepayRoutingFeePpm, - btcutil.Amount(req.MaxMinerFeeSat), - btcutil.Amount(req.MaxPrepaySat), - satPerVbyte.FeePerKWeight(), - ), nil - - default: - return nil, errors.New("no fee categories set") - } -} - -// rpcToRule switches on rpc rule type to convert to our rule interface. -func rpcToRule(rule *clientrpc.LiquidityRule) (*liquidity.SwapRule, error) { - swapType := swap.TypeOut - if rule.SwapType == clientrpc.SwapType_LOOP_IN { - swapType = swap.TypeIn - } - - switch rule.Type { - case clientrpc.LiquidityRuleType_UNKNOWN: - return nil, fmt.Errorf("rule type field must be set") - - case clientrpc.LiquidityRuleType_THRESHOLD: - return &liquidity.SwapRule{ - ThresholdRule: liquidity.NewThresholdRule( - int(rule.IncomingThreshold), - int(rule.OutgoingThreshold), - ), - Type: swapType, - }, nil - - default: - return nil, fmt.Errorf("unknown rule: %T", rule) - } -} - // SuggestSwaps provides a list of suggested swaps based on lnd's current // channel balances and rules set by the liquidity manager. func (s *swapClientServer) SuggestSwaps(ctx context.Context,