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.
pull/289/head
carla 4 years ago
parent 48744e8a2f
commit 7e9034b2ff
No known key found for this signature in database
GPG Key ID: 4CA7FE54A6213C91

@ -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)
}

@ -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)
}

@ -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,
})
}

@ -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)

Loading…
Cancel
Save