liquidity+loopd: move last budget refresh to parameters

pull/556/head
George Tsagkarelis 1 year ago
parent 74f6cc8d4b
commit aca6428b0e
No known key found for this signature in database
GPG Key ID: E08DEA9B12B66AF6

@ -28,6 +28,7 @@ func TestAutoLoopDisabled(t *testing.T) {
}
params := defaultParameters
params.AutoloopBudgetLastRefresh = testBudgetStart
params.ChannelRules = map[lnwire.ShortChannelID]*SwapRule{
chanID1: chanRule,
}
@ -95,12 +96,13 @@ func TestAutoLoopEnabled(t *testing.T) {
// autoloop budget is set to allow exactly 2 swaps at the prices
// that we set in our test quotes.
params = Parameters{
Autoloop: true,
AutoFeeBudget: 40066,
AutoFeeRefreshPeriod: testBudgetRefresh,
MaxAutoInFlight: 2,
FailureBackOff: time.Hour,
SweepConfTarget: 10,
Autoloop: true,
AutoFeeBudget: 40066,
AutoFeeRefreshPeriod: testBudgetRefresh,
AutoloopBudgetLastRefresh: testBudgetStart,
MaxAutoInFlight: 2,
FailureBackOff: time.Hour,
SweepConfTarget: 10,
FeeLimit: NewFeeCategoryLimit(
swapFeePPM, routeFeePPM, prepayFeePPM, maxMiner,
prepayAmount, 20000,
@ -353,13 +355,14 @@ func TestAutoloopAddress(t *testing.T) {
// Create some dummy parameters for autoloop and also specify an
// destination address.
params = Parameters{
Autoloop: true,
AutoFeeBudget: 40066,
DestAddr: addr,
AutoFeeRefreshPeriod: testBudgetRefresh,
MaxAutoInFlight: 2,
FailureBackOff: time.Hour,
SweepConfTarget: 10,
Autoloop: true,
AutoFeeBudget: 40066,
DestAddr: addr,
AutoFeeRefreshPeriod: testBudgetRefresh,
AutoloopBudgetLastRefresh: testBudgetStart,
MaxAutoInFlight: 2,
FailureBackOff: time.Hour,
SweepConfTarget: 10,
FeeLimit: NewFeeCategoryLimit(
swapFeePPM, routeFeePPM, prepayFeePPM, maxMiner,
prepayAmount, 20000,
@ -523,12 +526,13 @@ func TestCompositeRules(t *testing.T) {
swapFeePPM, routeFeePPM, prepayFeePPM, maxMiner,
prepayAmount, 20000,
),
Autoloop: true,
AutoFeeBudget: 100000,
AutoFeeRefreshPeriod: testBudgetRefresh,
MaxAutoInFlight: 2,
FailureBackOff: time.Hour,
SweepConfTarget: 10,
Autoloop: true,
AutoFeeBudget: 100000,
AutoFeeRefreshPeriod: testBudgetRefresh,
AutoloopBudgetLastRefresh: testBudgetStart,
MaxAutoInFlight: 2,
FailureBackOff: time.Hour,
SweepConfTarget: 10,
ChannelRules: map[lnwire.ShortChannelID]*SwapRule{
chanID1: chanRule,
},
@ -715,13 +719,14 @@ func TestAutoLoopInEnabled(t *testing.T) {
peer2MaxFee = ppmToSat(peer2ExpectedAmt, swapFeePPM)
params = Parameters{
Autoloop: true,
AutoFeeBudget: peer1MaxFee + peer2MaxFee + 1,
AutoFeeRefreshPeriod: testBudgetRefresh,
MaxAutoInFlight: 2,
FailureBackOff: time.Hour,
FeeLimit: NewFeePortion(swapFeePPM),
ChannelRules: make(map[lnwire.ShortChannelID]*SwapRule),
Autoloop: true,
AutoFeeBudget: peer1MaxFee + peer2MaxFee + 1,
AutoFeeRefreshPeriod: testBudgetRefresh,
AutoloopBudgetLastRefresh: testBudgetStart,
MaxAutoInFlight: 2,
FailureBackOff: time.Hour,
FeeLimit: NewFeePortion(swapFeePPM),
ChannelRules: make(map[lnwire.ShortChannelID]*SwapRule),
PeerRules: map[route.Vertex]*SwapRule{
peer1: rule,
peer2: rule,
@ -898,12 +903,13 @@ func TestAutoloopBothTypes(t *testing.T) {
loopInMaxFee = ppmToSat(loopInAmount, swapFeePPM)
params = Parameters{
Autoloop: true,
AutoFeeBudget: loopOutMaxFee + loopInMaxFee + 1,
AutoFeeRefreshPeriod: testBudgetRefresh,
MaxAutoInFlight: 2,
FailureBackOff: time.Hour,
FeeLimit: NewFeePortion(swapFeePPM),
Autoloop: true,
AutoFeeBudget: loopOutMaxFee + loopInMaxFee + 1,
AutoFeeRefreshPeriod: testBudgetRefresh,
AutoloopBudgetLastRefresh: testBudgetStart,
MaxAutoInFlight: 2,
FailureBackOff: time.Hour,
FeeLimit: NewFeePortion(swapFeePPM),
ChannelRules: map[lnwire.ShortChannelID]*SwapRule{
chanID1: outRule,
},
@ -1041,12 +1047,13 @@ func TestAutoLoopRecurringBudget(t *testing.T) {
maxMiner = btcutil.Amount(20000)
params = Parameters{
Autoloop: true,
AutoFeeBudget: 36000,
AutoFeeRefreshPeriod: time.Hour * 3,
MaxAutoInFlight: 2,
FailureBackOff: time.Hour,
SweepConfTarget: 10,
Autoloop: true,
AutoFeeBudget: 36000,
AutoFeeRefreshPeriod: time.Hour * 3,
AutoloopBudgetLastRefresh: testBudgetStart,
MaxAutoInFlight: 2,
FailureBackOff: time.Hour,
SweepConfTarget: 10,
FeeLimit: NewFeeCategoryLimit(
swapFeePPM, routeFeePPM, prepayFeePPM, maxMiner,
prepayAmount, 20000,

@ -129,8 +129,7 @@ func newAutoloopTestCtx(t *testing.T, parameters Parameters,
testCtx.lnd.Channels = channels
cfg := &Config{
AutoloopTicker: ticker.NewForce(DefaultAutoloopTicker),
AutoloopBudgetLastRefresh: testBudgetStart,
AutoloopTicker: ticker.NewForce(DefaultAutoloopTicker),
Restrictions: func(_ context.Context, swapType swap.Type) (*Restrictions,
error) {

@ -166,10 +166,6 @@ type Config struct {
// trigger autoloop in itests.
AutoloopTicker *ticker.Force
// AutoloopBudgetLastRefresh is the last time at which we refreshed
// our budget.
AutoloopBudgetLastRefresh time.Time
// Restrictions returns the restrictions that the server applies to
// swaps.
Restrictions func(ctx context.Context, swapType swap.Type) (
@ -301,7 +297,7 @@ func (m *Manager) GetParameters() Parameters {
func (m *Manager) SetParameters(ctx context.Context,
req *clientrpc.LiquidityParameters) error {
params, err := rpcToParameters(req)
params, err := RpcToParameters(req)
if err != nil {
return err
}
@ -743,7 +739,7 @@ func (m *Manager) SuggestSwaps(ctx context.Context, autoloop bool) (
}
} else {
refreshTime := m.params.AutoFeeRefreshPeriod -
time.Since(m.cfg.AutoloopBudgetLastRefresh)
time.Since(m.params.AutoloopBudgetLastRefresh)
log.Infof("Swap fee exceeds budget, remaining budget: "+
"%v, swap fee %v, next budget refresh: %v",
@ -929,7 +925,7 @@ func (m *Manager) checkExistingAutoLoops(ctx context.Context,
mSatToSatoshis(prepay.Value),
)
} else if out.LastUpdateTime().After(
m.cfg.AutoloopBudgetLastRefresh,
m.params.AutoloopBudgetLastRefresh,
) {
summary.spentFees += out.State().Cost.Total()
@ -943,7 +939,7 @@ func (m *Manager) checkExistingAutoLoops(ctx context.Context,
pending := in.State().State.Type() == loopdb.StateTypePending
inBudget := !in.LastUpdateTime().
Before(m.cfg.AutoloopBudgetLastRefresh)
Before(m.params.AutoloopBudgetLastRefresh)
// If an autoloop is in a pending state, we always count it in
// our current budget, and record the worst-case fees for it,
@ -1054,11 +1050,23 @@ func (m *Manager) currentSwapTraffic(loopOut []*loopdb.LoopOut,
// budget refresh is greater than our configured refresh period. If so, the last
// refresh timestamp.
func (m *Manager) refreshAutoloopBudget(ctx context.Context) {
if time.Since(m.cfg.AutoloopBudgetLastRefresh) >
if time.Since(m.params.AutoloopBudgetLastRefresh) >
m.params.AutoFeeRefreshPeriod {
log.Debug("Refreshing autoloop budget")
m.cfg.AutoloopBudgetLastRefresh = m.cfg.Clock.Now()
m.params.AutoloopBudgetLastRefresh = m.cfg.Clock.Now()
paramsRpc, err := ParametersToRpc(m.params)
if err != nil {
log.Errorf("Error converting parameters to rpc: %v",
err)
return
}
err = m.saveParams(paramsRpc)
if err != nil {
log.Errorf("Error saving parameters: %v", err)
}
}
}

@ -148,9 +148,8 @@ func newTestConfig() (*Config, *test.LndMockServices) {
return testRestrictions, nil
},
Lnd: &lnd.LndServices,
Clock: clock.NewTestClock(testTime),
AutoloopBudgetLastRefresh: testBudgetStart,
Lnd: &lnd.LndServices,
Clock: clock.NewTestClock(testTime),
ListLoopOut: func() ([]*loopdb.LoopOut, error) {
return nil, nil
},
@ -572,6 +571,7 @@ func TestRestrictedSuggestions(t *testing.T) {
lnd.Channels = testCase.channels
params := defaultParameters
params.AutoloopBudgetLastRefresh = testBudgetStart
if testCase.chanRules != nil {
params.ChannelRules = testCase.chanRules
}
@ -652,6 +652,7 @@ func TestSweepFeeLimit(t *testing.T) {
}
params := defaultParameters
params.AutoloopBudgetLastRefresh = testBudgetStart
params.FeeLimit = defaultFeeCategoryLimit()
// Set our budget to cover a single swap with these
@ -794,6 +795,7 @@ func TestSuggestSwaps(t *testing.T) {
lnd.Channels = testCase.channels
params := defaultParameters
params.AutoloopBudgetLastRefresh = testBudgetStart
if testCase.rules != nil {
params.ChannelRules = testCase.rules
}
@ -901,6 +903,7 @@ func TestFeeLimits(t *testing.T) {
// Set our params to use individual fee limits.
params := defaultParameters
params.AutoloopBudgetLastRefresh = testBudgetStart
params.FeeLimit = defaultFeeCategoryLimit()
// Set our budget to cover a single swap with these
@ -1108,6 +1111,7 @@ func TestFeeBudget(t *testing.T) {
}
params.AutoFeeBudget = testCase.budget
params.AutoFeeRefreshPeriod = testBudgetRefresh
params.AutoloopBudgetLastRefresh = testBudgetStart
params.MaxAutoInFlight = 2
params.FeeLimit = NewFeeCategoryLimit(
defaultSwapFeePPM, defaultRoutingFeePPM,
@ -1272,6 +1276,7 @@ func TestInFlightLimit(t *testing.T) {
}
params := defaultParameters
params.AutoloopBudgetLastRefresh = testBudgetStart
if testCase.peerRules != nil {
params.PeerRules = testCase.peerRules
@ -1431,6 +1436,7 @@ func TestSizeRestrictions(t *testing.T) {
}
params := defaultParameters
params.AutoloopBudgetLastRefresh = testBudgetStart
params.ClientRestrictions = testCase.clientRestrictions
params.ChannelRules = map[lnwire.ShortChannelID]*SwapRule{
chanID1: chanRule,
@ -1593,6 +1599,7 @@ func TestFeePercentage(t *testing.T) {
}
params := defaultParameters
params.AutoloopBudgetLastRefresh = testBudgetStart
params.FeeLimit = NewFeePortion(testCase.feePPM)
params.ChannelRules = map[lnwire.ShortChannelID]*SwapRule{
chanID1: chanRule,
@ -1765,6 +1772,7 @@ func TestBudgetWithLoopin(t *testing.T) {
params := defaultParameters
params.AutoFeeBudget = budget
params.AutoFeeRefreshPeriod = testBudgetRefresh
params.AutoloopBudgetLastRefresh = testBudgetStart
params.FeeLimit = NewFeePortion(testPPM)
params.ChannelRules = map[lnwire.ShortChannelID]*SwapRule{
@ -1821,6 +1829,7 @@ func testSuggestSwaps(t *testing.T, setup *testSuggestSwapsSetup,
}
params := defaultParameters
params.AutoloopBudgetLastRefresh = testBudgetStart
params.ChannelRules = map[lnwire.ShortChannelID]*SwapRule{
chanID1: chanRule,
chanID2: chanRule,

@ -20,16 +20,17 @@ var (
// defaultParameters contains the default parameters that we start our
// liquidity manager with.
defaultParameters = Parameters{
AutoFeeBudget: defaultBudget,
AutoFeeRefreshPeriod: defaultBudgetRefreshPeriod,
DestAddr: nil,
MaxAutoInFlight: defaultMaxInFlight,
ChannelRules: make(map[lnwire.ShortChannelID]*SwapRule),
PeerRules: make(map[route.Vertex]*SwapRule),
FailureBackOff: defaultFailureBackoff,
SweepConfTarget: defaultConfTarget,
HtlcConfTarget: defaultHtlcConfTarget,
FeeLimit: defaultFeePortion(),
AutoFeeBudget: defaultBudget,
AutoFeeRefreshPeriod: defaultBudgetRefreshPeriod,
AutoloopBudgetLastRefresh: time.Now(),
DestAddr: nil,
MaxAutoInFlight: defaultMaxInFlight,
ChannelRules: make(map[lnwire.ShortChannelID]*SwapRule),
PeerRules: make(map[route.Vertex]*SwapRule),
FailureBackOff: defaultFailureBackoff,
SweepConfTarget: defaultConfTarget,
HtlcConfTarget: defaultHtlcConfTarget,
FeeLimit: defaultFeePortion(),
}
)
@ -52,6 +53,10 @@ type Parameters struct {
// auto fee budget is refreshed.
AutoFeeRefreshPeriod time.Duration
// AutoloopBudgetLastRefresh is the last time at which we refreshed
// our budget.
AutoloopBudgetLastRefresh time.Time
// MaxAutoInFlight is the maximum number of in-flight automatically
// dispatched swaps we allow.
MaxAutoInFlight int
@ -348,7 +353,7 @@ func rpcToRule(rule *clientrpc.LiquidityRule) (*SwapRule, error) {
// rpcToParameters takes a `LiquidityParameters` and creates a `Parameters`
// from it.
func rpcToParameters(req *clientrpc.LiquidityParameters) (*Parameters,
func RpcToParameters(req *clientrpc.LiquidityParameters) (*Parameters,
error) {
feeLimit, err := rpcToFee(req)
@ -373,7 +378,10 @@ func rpcToParameters(req *clientrpc.LiquidityParameters) (*Parameters,
SweepConfTarget: req.SweepConfTarget,
FailureBackOff: time.Duration(req.FailureBackoffSec) *
time.Second,
Autoloop: req.Autoloop,
Autoloop: req.Autoloop,
AutoloopBudgetLastRefresh: time.Unix(
int64(req.AutoloopBudgetLastRefresh), 0,
),
DestAddr: destaddr,
AutoFeeBudget: btcutil.Amount(req.AutoloopBudgetSat),
MaxAutoInFlight: int(req.AutoMaxInFlight),
@ -442,3 +450,90 @@ func rpcToParameters(req *clientrpc.LiquidityParameters) (*Parameters,
return params, nil
}
// ParametersToRpc takes a `Parameters` and creates a `LiquidityParameters`
// from it.
func ParametersToRpc(cfg Parameters) (*clientrpc.LiquidityParameters,
error) {
totalRules := len(cfg.ChannelRules) + len(cfg.PeerRules)
var destaddr string
if cfg.DestAddr != nil {
destaddr = cfg.DestAddr.String()
}
rpcCfg := &clientrpc.LiquidityParameters{
SweepConfTarget: cfg.SweepConfTarget,
FailureBackoffSec: uint64(cfg.FailureBackOff.Seconds()),
Autoloop: cfg.Autoloop,
AutoloopBudgetSat: uint64(cfg.AutoFeeBudget),
AutoloopBudgetRefreshPeriodSec: uint64(
cfg.AutoFeeRefreshPeriod.Seconds(),
),
AutoloopBudgetLastRefresh: uint64(
cfg.AutoloopBudgetLastRefresh.Unix(),
),
AutoMaxInFlight: uint64(cfg.MaxAutoInFlight),
AutoloopDestAddress: destaddr,
Rules: make(
[]*clientrpc.LiquidityRule, 0, totalRules,
),
MinSwapAmount: uint64(cfg.ClientRestrictions.Minimum),
MaxSwapAmount: uint64(cfg.ClientRestrictions.Maximum),
HtlcConfTarget: cfg.HtlcConfTarget,
}
switch f := cfg.FeeLimit.(type) {
case *FeeCategoryLimit:
satPerByte := f.SweepFeeRateLimit.FeePerKVByte() / 1000
rpcCfg.SweepFeeRateSatPerVbyte = uint64(satPerByte)
rpcCfg.MaxMinerFeeSat = uint64(f.MaximumMinerFee)
rpcCfg.MaxSwapFeePpm = f.MaximumSwapFeePPM
rpcCfg.MaxRoutingFeePpm = f.MaximumRoutingFeePPM
rpcCfg.MaxPrepayRoutingFeePpm = f.MaximumPrepayRoutingFeePPM
rpcCfg.MaxPrepaySat = uint64(f.MaximumPrepay)
case *FeePortion:
rpcCfg.FeePpm = f.PartsPerMillion
default:
return nil, fmt.Errorf("unknown fee limit: %T", cfg.FeeLimit)
}
for channel, rule := range cfg.ChannelRules {
rpcRule := newRPCRule(channel.ToUint64(), nil, rule)
rpcCfg.Rules = append(rpcCfg.Rules, rpcRule)
}
for peer, rule := range cfg.PeerRules {
peer := peer
rpcRule := newRPCRule(0, peer[:], rule)
rpcCfg.Rules = append(rpcCfg.Rules, rpcRule)
}
return rpcCfg, nil
}
// newRPCRule is a helper function that creates a `LiquidityRule` based on the
// provided `SwapRule` for the given channelID or peer.
func newRPCRule(channelID uint64, peer []byte,
rule *SwapRule) *clientrpc.LiquidityRule {
rpcRule := &clientrpc.LiquidityRule{
ChannelId: channelID,
Pubkey: peer,
Type: clientrpc.LiquidityRuleType_THRESHOLD,
IncomingThreshold: uint32(rule.MinimumIncoming),
OutgoingThreshold: uint32(rule.MinimumOutgoing),
SwapType: clientrpc.SwapType_LOOP_OUT,
}
if rule.Type == swap.TypeIn {
rpcRule.SwapType = clientrpc.SwapType_LOOP_IN
}
return rpcRule
}

@ -745,83 +745,14 @@ func (s *swapClientServer) GetLiquidityParams(_ context.Context,
cfg := s.liquidityMgr.GetParameters()
totalRules := len(cfg.ChannelRules) + len(cfg.PeerRules)
var destaddr string
if cfg.DestAddr != nil {
destaddr = cfg.DestAddr.String()
}
rpcCfg := &clientrpc.LiquidityParameters{
SweepConfTarget: cfg.SweepConfTarget,
FailureBackoffSec: uint64(cfg.FailureBackOff.Seconds()),
Autoloop: cfg.Autoloop,
AutoloopBudgetSat: uint64(cfg.AutoFeeBudget),
AutoloopBudgetRefreshPeriodSec: uint64(
cfg.AutoFeeRefreshPeriod.Seconds(),
),
AutoMaxInFlight: uint64(cfg.MaxAutoInFlight),
AutoloopDestAddress: destaddr,
Rules: make(
[]*clientrpc.LiquidityRule, 0, totalRules,
),
MinSwapAmount: uint64(cfg.ClientRestrictions.Minimum),
MaxSwapAmount: uint64(cfg.ClientRestrictions.Maximum),
HtlcConfTarget: cfg.HtlcConfTarget,
}
switch f := cfg.FeeLimit.(type) {
case *liquidity.FeeCategoryLimit:
satPerByte := f.SweepFeeRateLimit.FeePerKVByte() / 1000
rpcCfg.SweepFeeRateSatPerVbyte = uint64(satPerByte)
rpcCfg.MaxMinerFeeSat = uint64(f.MaximumMinerFee)
rpcCfg.MaxSwapFeePpm = f.MaximumSwapFeePPM
rpcCfg.MaxRoutingFeePpm = f.MaximumRoutingFeePPM
rpcCfg.MaxPrepayRoutingFeePpm = f.MaximumPrepayRoutingFeePPM
rpcCfg.MaxPrepaySat = uint64(f.MaximumPrepay)
case *liquidity.FeePortion:
rpcCfg.FeePpm = f.PartsPerMillion
default:
return nil, fmt.Errorf("unknown fee limit: %T", cfg.FeeLimit)
}
for channel, rule := range cfg.ChannelRules {
rpcRule := newRPCRule(channel.ToUint64(), nil, rule)
rpcCfg.Rules = append(rpcCfg.Rules, rpcRule)
}
for peer, rule := range cfg.PeerRules {
peer := peer
rpcRule := newRPCRule(0, peer[:], rule)
rpcCfg.Rules = append(rpcCfg.Rules, rpcRule)
rpcCfg, err := liquidity.ParametersToRpc(cfg)
if err != nil {
return nil, err
}
return rpcCfg, nil
}
func newRPCRule(channelID uint64, peer []byte,
rule *liquidity.SwapRule) *clientrpc.LiquidityRule {
rpcRule := &clientrpc.LiquidityRule{
ChannelId: channelID,
Pubkey: peer,
Type: clientrpc.LiquidityRuleType_THRESHOLD,
IncomingThreshold: uint32(rule.MinimumIncoming),
OutgoingThreshold: uint32(rule.MinimumOutgoing),
SwapType: clientrpc.SwapType_LOOP_OUT,
}
if rule.Type == swap.TypeIn {
rpcRule.SwapType = clientrpc.SwapType_LOOP_IN
}
return rpcRule
}
// SetLiquidityParams attempts to set our current liquidity manager's
// parameters.
func (s *swapClientServer) SetLiquidityParams(ctx context.Context,

@ -2,7 +2,6 @@ package loopd
import (
"context"
"time"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightninglabs/lndclient"
@ -40,10 +39,9 @@ func getClient(config *Config, lnd *lndclient.LndServices) (*loop.Client,
func getLiquidityManager(client *loop.Client) *liquidity.Manager {
mngrCfg := &liquidity.Config{
AutoloopTicker: ticker.NewForce(liquidity.DefaultAutoloopTicker),
AutoloopBudgetLastRefresh: time.Now(),
LoopOut: client.LoopOut,
LoopIn: client.LoopIn,
AutoloopTicker: ticker.NewForce(liquidity.DefaultAutoloopTicker),
LoopOut: client.LoopOut,
LoopIn: client.LoopIn,
Restrictions: func(ctx context.Context,
swapType swap.Type) (*liquidity.Restrictions, error) {

Loading…
Cancel
Save