mirror of
https://github.com/lightninglabs/loop.git
synced 2026-08-13 12:33:03 +02:00
liquidity: add easy autoloop test
This commit is contained in:
parent
fad9f40ae3
commit
9a9766775a
2 changed files with 262 additions and 18 deletions
|
|
@ -207,7 +207,7 @@ func TestAutoLoopEnabled(t *testing.T) {
|
|||
Events: []*loopdb.LoopEvent{
|
||||
{
|
||||
SwapStateData: loopdb.SwapStateData{
|
||||
State: loopdb.StateInitiated,
|
||||
State: loopdb.StateSuccess,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -472,7 +472,7 @@ func TestAutoloopAddress(t *testing.T) {
|
|||
Events: []*loopdb.LoopEvent{
|
||||
{
|
||||
SwapStateData: loopdb.SwapStateData{
|
||||
State: loopdb.StateHtlcPublished,
|
||||
State: loopdb.StateSuccess,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -647,7 +647,7 @@ func TestCompositeRules(t *testing.T) {
|
|||
Events: []*loopdb.LoopEvent{
|
||||
{
|
||||
SwapStateData: loopdb.SwapStateData{
|
||||
State: loopdb.StateHtlcPublished,
|
||||
State: loopdb.StateSuccess,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -984,7 +984,7 @@ func TestAutoloopBothTypes(t *testing.T) {
|
|||
Events: []*loopdb.LoopEvent{
|
||||
{
|
||||
SwapStateData: loopdb.SwapStateData{
|
||||
State: loopdb.StateHtlcPublished,
|
||||
State: loopdb.SwapState(loopdb.StateSuccess),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -1162,15 +1162,28 @@ func TestAutoLoopRecurringBudget(t *testing.T) {
|
|||
},
|
||||
},
|
||||
}
|
||||
|
||||
singleLoopOut = &loopdb.LoopOut{
|
||||
Loop: loopdb.Loop{
|
||||
Events: []*loopdb.LoopEvent{
|
||||
{
|
||||
SwapStateData: loopdb.SwapStateData{
|
||||
State: loopdb.SwapState(loopdb.StateSuccess),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// Tick our autolooper with no existing swaps, we expect a loop out
|
||||
// swap to be dispatched on first channel.
|
||||
step := &autoloopStep{
|
||||
minAmt: 1,
|
||||
maxAmt: amt + 1,
|
||||
quotesOut: quotes1,
|
||||
expectedOut: loopOuts1,
|
||||
minAmt: 1,
|
||||
maxAmt: amt + 1,
|
||||
quotesOut: quotes1,
|
||||
expectedOut: loopOuts1,
|
||||
existingOutSingle: singleLoopOut,
|
||||
}
|
||||
c.autoloop(step)
|
||||
|
||||
|
|
@ -1188,11 +1201,12 @@ func TestAutoLoopRecurringBudget(t *testing.T) {
|
|||
}
|
||||
|
||||
step = &autoloopStep{
|
||||
minAmt: 1,
|
||||
maxAmt: amt + 1,
|
||||
quotesOut: quotes2,
|
||||
existingOut: existing,
|
||||
expectedOut: nil,
|
||||
minAmt: 1,
|
||||
maxAmt: amt + 1,
|
||||
quotesOut: quotes2,
|
||||
existingOut: existing,
|
||||
expectedOut: nil,
|
||||
existingOutSingle: singleLoopOut,
|
||||
}
|
||||
// Tick again, we should expect no loop outs because our budget would be
|
||||
// exceeded.
|
||||
|
|
@ -1222,11 +1236,12 @@ func TestAutoLoopRecurringBudget(t *testing.T) {
|
|||
c.testClock.SetTime(testTime.Add(time.Hour * 25))
|
||||
|
||||
step = &autoloopStep{
|
||||
minAmt: 1,
|
||||
maxAmt: amt + 1,
|
||||
quotesOut: quotes2,
|
||||
existingOut: existing2,
|
||||
expectedOut: loopOuts2,
|
||||
minAmt: 1,
|
||||
maxAmt: amt + 1,
|
||||
quotesOut: quotes2,
|
||||
existingOut: existing2,
|
||||
expectedOut: loopOuts2,
|
||||
existingOutSingle: singleLoopOut,
|
||||
}
|
||||
|
||||
// Tick again, we should expect a loop out to occur on the 2nd channel.
|
||||
|
|
@ -1235,6 +1250,177 @@ func TestAutoLoopRecurringBudget(t *testing.T) {
|
|||
c.stop()
|
||||
}
|
||||
|
||||
// TestEasyAutoloop tests that the easy autoloop logic works as expected. This
|
||||
// involves testing that channels are correctly selected and that the balance
|
||||
// target is successfully met.
|
||||
func TestEasyAutoloop(t *testing.T) {
|
||||
defer test.Guard(t)
|
||||
|
||||
// We need to change the default channels we use for tests so that they
|
||||
// have different local balances in order to know which one is going to
|
||||
// be selected by easy autoloop.
|
||||
easyChannel1 := lndclient.ChannelInfo{
|
||||
Active: true,
|
||||
ChannelID: chanID1.ToUint64(),
|
||||
PubKeyBytes: peer1,
|
||||
LocalBalance: 95000,
|
||||
RemoteBalance: 0,
|
||||
Capacity: 100000,
|
||||
}
|
||||
|
||||
easyChannel2 := lndclient.ChannelInfo{
|
||||
Active: true,
|
||||
ChannelID: chanID1.ToUint64(),
|
||||
PubKeyBytes: peer1,
|
||||
LocalBalance: 75000,
|
||||
RemoteBalance: 0,
|
||||
Capacity: 100000,
|
||||
}
|
||||
|
||||
var (
|
||||
channels = []lndclient.ChannelInfo{
|
||||
easyChannel1, easyChannel2,
|
||||
}
|
||||
|
||||
params = Parameters{
|
||||
Autoloop: true,
|
||||
AutoFeeBudget: 36000,
|
||||
AutoFeeRefreshPeriod: time.Hour * 3,
|
||||
AutoloopBudgetLastRefresh: testBudgetStart,
|
||||
MaxAutoInFlight: 2,
|
||||
FailureBackOff: time.Hour,
|
||||
SweepConfTarget: 10,
|
||||
HtlcConfTarget: defaultHtlcConfTarget,
|
||||
EasyAutoloop: true,
|
||||
EasyAutoloopTarget: 75000,
|
||||
FeeLimit: defaultFeePortion(),
|
||||
}
|
||||
)
|
||||
|
||||
c := newAutoloopTestCtx(t, params, channels, testRestrictions)
|
||||
c.start()
|
||||
|
||||
var (
|
||||
maxAmt = 50000
|
||||
|
||||
chan1Swap = &loop.OutRequest{
|
||||
Amount: btcutil.Amount(maxAmt),
|
||||
OutgoingChanSet: loopdb.ChannelSet{easyChannel1.ChannelID},
|
||||
Label: labels.AutoloopLabel(swap.TypeOut),
|
||||
Initiator: autoloopSwapInitiator,
|
||||
}
|
||||
|
||||
quotesOut1 = []quoteRequestResp{
|
||||
{
|
||||
request: &loop.LoopOutQuoteRequest{
|
||||
Amount: btcutil.Amount(maxAmt),
|
||||
},
|
||||
quote: &loop.LoopOutQuote{
|
||||
SwapFee: 1,
|
||||
PrepayAmount: 1,
|
||||
MinerFee: 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
loopOut1 = []loopOutRequestResp{
|
||||
{
|
||||
request: chan1Swap,
|
||||
response: &loop.LoopOutSwapInfo{
|
||||
SwapHash: lntypes.Hash{1},
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// We expected one max size swap to be dispatched on our channel with
|
||||
// the biggest local balance.
|
||||
step := &easyAutoloopStep{
|
||||
minAmt: 1,
|
||||
maxAmt: 50000,
|
||||
quotesOut: quotesOut1,
|
||||
expectedOut: loopOut1,
|
||||
}
|
||||
|
||||
c.easyautoloop(step, false)
|
||||
c.stop()
|
||||
|
||||
// In order to reflect the change on the channel balances we create a
|
||||
// new context and restart the autolooper.
|
||||
easyChannel1.LocalBalance -= chan1Swap.Amount
|
||||
channels = []lndclient.ChannelInfo{
|
||||
easyChannel1, easyChannel2,
|
||||
}
|
||||
|
||||
c = newAutoloopTestCtx(t, params, channels, testRestrictions)
|
||||
c.start()
|
||||
|
||||
var (
|
||||
amt2 = 45_000
|
||||
|
||||
chan2Swap = &loop.OutRequest{
|
||||
Amount: btcutil.Amount(amt2),
|
||||
OutgoingChanSet: loopdb.ChannelSet{easyChannel2.ChannelID},
|
||||
Label: labels.AutoloopLabel(swap.TypeOut),
|
||||
Initiator: autoloopSwapInitiator,
|
||||
}
|
||||
|
||||
quotesOut2 = []quoteRequestResp{
|
||||
{
|
||||
request: &loop.LoopOutQuoteRequest{
|
||||
Amount: btcutil.Amount(amt2),
|
||||
},
|
||||
quote: &loop.LoopOutQuote{
|
||||
SwapFee: 1,
|
||||
PrepayAmount: 1,
|
||||
MinerFee: 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
loopOut2 = []loopOutRequestResp{
|
||||
{
|
||||
request: chan2Swap,
|
||||
response: &loop.LoopOutSwapInfo{
|
||||
SwapHash: lntypes.Hash{1},
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// We expect a swap of size 45_000 to be dispatched in order to meet the
|
||||
// defined target of 75_000.
|
||||
step = &easyAutoloopStep{
|
||||
minAmt: 1,
|
||||
maxAmt: 50000,
|
||||
quotesOut: quotesOut2,
|
||||
expectedOut: loopOut2,
|
||||
}
|
||||
|
||||
c.easyautoloop(step, false)
|
||||
c.stop()
|
||||
|
||||
// In order to reflect the change on the channel balances we create a
|
||||
// new context and restart the autolooper.
|
||||
easyChannel2.LocalBalance -= btcutil.Amount(amt2)
|
||||
channels = []lndclient.ChannelInfo{
|
||||
easyChannel1, easyChannel2,
|
||||
}
|
||||
|
||||
c = newAutoloopTestCtx(t, params, channels, testRestrictions)
|
||||
c.start()
|
||||
|
||||
// We have met the target of 75_000 so we don't expect any action from
|
||||
// easy autoloop. That's why we set noop to true in the call below.
|
||||
step = &easyAutoloopStep{
|
||||
minAmt: 1,
|
||||
maxAmt: 50000,
|
||||
}
|
||||
|
||||
c.easyautoloop(step, true)
|
||||
c.stop()
|
||||
}
|
||||
|
||||
// existingSwapFromRequest is a helper function which returns the db
|
||||
// representation of a loop out request with the event set provided.
|
||||
func existingSwapFromRequest(request *loop.OutRequest, initTime time.Time,
|
||||
|
|
|
|||
|
|
@ -19,6 +19,11 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultEventuallyTimeout = time.Second * 45
|
||||
defaultEventuallyInterval = time.Millisecond * 100
|
||||
)
|
||||
|
||||
type autoloopTestCtx struct {
|
||||
t *testing.T
|
||||
manager *Manager
|
||||
|
|
@ -272,6 +277,15 @@ type autoloopStep struct {
|
|||
keepDestAddr bool
|
||||
}
|
||||
|
||||
type easyAutoloopStep struct {
|
||||
minAmt btcutil.Amount
|
||||
maxAmt btcutil.Amount
|
||||
existingOut []*loopdb.LoopOut
|
||||
existingIn []*loopdb.LoopIn
|
||||
quotesOut []quoteRequestResp
|
||||
expectedOut []loopOutRequestResp
|
||||
}
|
||||
|
||||
// autoloop walks our test context through the process of triggering our
|
||||
// autoloop functionality, providing mocked values as required. The set of
|
||||
// quotes provided indicates that we expect swap suggestions to be made (since
|
||||
|
|
@ -325,6 +339,50 @@ func (c *autoloopTestCtx) autoloop(step *autoloopStep) {
|
|||
|
||||
require.True(c.t, c.matchLoopOuts(step.expectedOut, step.keepDestAddr))
|
||||
require.True(c.t, c.matchLoopIns(step.expectedIn))
|
||||
|
||||
require.Eventuallyf(c.t, func() bool {
|
||||
return c.manager.numActiveStickyLoops() == 0
|
||||
}, defaultEventuallyTimeout, defaultEventuallyInterval, "failed to"+
|
||||
" wait for sticky loop counter")
|
||||
}
|
||||
|
||||
// easyautoloop walks our test context through the process of triggering our
|
||||
// easy autoloop functionality, providing mocked values as required. The number
|
||||
// of values needed to mock easy autoloop are less than standard autoloop as the
|
||||
// goal of easy autoloop is to simplify its usage.
|
||||
func (c *autoloopTestCtx) easyautoloop(step *easyAutoloopStep, noop bool) {
|
||||
// Tick our autoloop ticker to force assessing whether we want to loop.
|
||||
c.manager.cfg.AutoloopTicker.Force <- testTime
|
||||
|
||||
// Provide the liquidity manager with our desired existing set of swaps.
|
||||
c.loopOuts <- step.existingOut
|
||||
c.loopIns <- step.existingIn
|
||||
|
||||
// If easy autoloop is not meant to be triggered we skip sending the
|
||||
// mock response for restrictions, as this is never called.
|
||||
if !noop {
|
||||
// Send a mocked response from the server with the swap size limits.
|
||||
c.loopOutRestrictions <- NewRestrictions(step.minAmt, step.maxAmt)
|
||||
}
|
||||
|
||||
for _, expected := range step.quotesOut {
|
||||
request := <-c.quoteRequest
|
||||
require.Equal(
|
||||
c.t, expected.request.Amount, request.Amount,
|
||||
)
|
||||
|
||||
c.quotes <- expected.quote
|
||||
}
|
||||
|
||||
for _, expected := range step.expectedOut {
|
||||
actual := <-c.outRequest
|
||||
|
||||
require.Equal(c.t, expected.request.Amount, actual.Amount)
|
||||
require.Equal(
|
||||
c.t, expected.request.OutgoingChanSet,
|
||||
actual.OutgoingChanSet,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// matchLoopOuts checks that the actual loop out requests we got match the
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue