mirror of
https://github.com/lightninglabs/loop.git
synced 2026-08-19 13:17:50 +02:00
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.
This commit is contained in:
parent
48744e8a2f
commit
7e9034b2ff
4 changed files with 166 additions and 74 deletions
|
|
@ -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,
|
||||
},
|
||||
rules: map[lnwire.ShortChannelID]*ThresholdRule{
|
||||
chanID1: chanRule,
|
||||
},
|
||||
parameters: Parameters{
|
||||
ChannelRules: map[lnwire.ShortChannelID]*ThresholdRule{
|
||||
chanID1: NewThresholdRule(
|
||||
10, 10,
|
||||
),
|
||||
},
|
||||
},
|
||||
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()
|
||||
cfg, lnd := 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
|
||||
channels := []lndclient.ChannelInfo{
|
||||
channel1,
|
||||
}
|
||||
|
||||
manager := NewManager(cfg)
|
||||
|
||||
// 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…
Add table
Add a link
Reference in a new issue