|
|
|
@ -966,6 +966,211 @@ func TestAutoloopBothTypes(t *testing.T) {
|
|
|
|
|
c.stop()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestAutoLoopRecurringBudget tests that the autolooper will perform swaps that
|
|
|
|
|
// respect the fee budget, and that it will refresh the budget based on the
|
|
|
|
|
// defined refresh period.
|
|
|
|
|
func TestAutoLoopRecurringBudget(t *testing.T) {
|
|
|
|
|
defer test.Guard(t)()
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
|
channels = []lndclient.ChannelInfo{
|
|
|
|
|
channel1, channel2,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
swapFeePPM uint64 = 1000
|
|
|
|
|
routeFeePPM uint64 = 1000
|
|
|
|
|
prepayFeePPM uint64 = 1000
|
|
|
|
|
prepayAmount = btcutil.Amount(20000)
|
|
|
|
|
maxMiner = btcutil.Amount(20000)
|
|
|
|
|
|
|
|
|
|
params = Parameters{
|
|
|
|
|
Autoloop: true,
|
|
|
|
|
AutoFeeBudget: 36000,
|
|
|
|
|
AutoFeeRefreshPeriod: time.Hour * 3,
|
|
|
|
|
MaxAutoInFlight: 2,
|
|
|
|
|
FailureBackOff: time.Hour,
|
|
|
|
|
SweepConfTarget: 10,
|
|
|
|
|
FeeLimit: NewFeeCategoryLimit(
|
|
|
|
|
swapFeePPM, routeFeePPM, prepayFeePPM, maxMiner,
|
|
|
|
|
prepayAmount, 20000,
|
|
|
|
|
),
|
|
|
|
|
ChannelRules: map[lnwire.ShortChannelID]*SwapRule{
|
|
|
|
|
chanID1: chanRule,
|
|
|
|
|
chanID2: chanRule,
|
|
|
|
|
},
|
|
|
|
|
HtlcConfTarget: defaultHtlcConfTarget,
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
c := newAutoloopTestCtx(t, params, channels, testRestrictions)
|
|
|
|
|
c.start()
|
|
|
|
|
|
|
|
|
|
// Calculate our maximum allowed fees and create quotes that fall within
|
|
|
|
|
// our budget.
|
|
|
|
|
var (
|
|
|
|
|
amt = chan1Rec.Amount
|
|
|
|
|
|
|
|
|
|
maxSwapFee = ppmToSat(amt, swapFeePPM)
|
|
|
|
|
|
|
|
|
|
// Create a quote that is within our limits. We do not set miner
|
|
|
|
|
// fee because this value is not actually set by the server.
|
|
|
|
|
quote1 = &loop.LoopOutQuote{
|
|
|
|
|
SwapFee: maxSwapFee,
|
|
|
|
|
PrepayAmount: prepayAmount - 10,
|
|
|
|
|
MinerFee: maxMiner - 10,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
quote2 = &loop.LoopOutQuote{
|
|
|
|
|
SwapFee: maxSwapFee,
|
|
|
|
|
PrepayAmount: prepayAmount - 20,
|
|
|
|
|
MinerFee: maxMiner - 10,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
quoteRequest = &loop.LoopOutQuoteRequest{
|
|
|
|
|
Amount: amt,
|
|
|
|
|
SweepConfTarget: params.SweepConfTarget,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
quotes1 = []quoteRequestResp{
|
|
|
|
|
{
|
|
|
|
|
request: quoteRequest,
|
|
|
|
|
quote: quote1,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
request: quoteRequest,
|
|
|
|
|
quote: quote2,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
quotes2 = []quoteRequestResp{
|
|
|
|
|
{
|
|
|
|
|
request: quoteRequest,
|
|
|
|
|
quote: quote2,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
maxRouteFee = ppmToSat(amt, routeFeePPM)
|
|
|
|
|
|
|
|
|
|
chan1Swap = &loop.OutRequest{
|
|
|
|
|
Amount: amt,
|
|
|
|
|
MaxSwapRoutingFee: maxRouteFee,
|
|
|
|
|
MaxPrepayRoutingFee: ppmToSat(
|
|
|
|
|
quote1.PrepayAmount, prepayFeePPM,
|
|
|
|
|
),
|
|
|
|
|
MaxSwapFee: quote1.SwapFee,
|
|
|
|
|
MaxPrepayAmount: quote1.PrepayAmount,
|
|
|
|
|
MaxMinerFee: maxMiner,
|
|
|
|
|
SweepConfTarget: params.SweepConfTarget,
|
|
|
|
|
OutgoingChanSet: loopdb.ChannelSet{chanID1.ToUint64()},
|
|
|
|
|
Label: labels.AutoloopLabel(swap.TypeOut),
|
|
|
|
|
Initiator: autoloopSwapInitiator,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
chan2Swap = &loop.OutRequest{
|
|
|
|
|
Amount: amt,
|
|
|
|
|
MaxSwapRoutingFee: maxRouteFee,
|
|
|
|
|
MaxPrepayRoutingFee: ppmToSat(
|
|
|
|
|
quote2.PrepayAmount, routeFeePPM,
|
|
|
|
|
),
|
|
|
|
|
MaxSwapFee: quote2.SwapFee,
|
|
|
|
|
MaxPrepayAmount: quote2.PrepayAmount,
|
|
|
|
|
MaxMinerFee: maxMiner,
|
|
|
|
|
SweepConfTarget: params.SweepConfTarget,
|
|
|
|
|
OutgoingChanSet: loopdb.ChannelSet{chanID2.ToUint64()},
|
|
|
|
|
Label: labels.AutoloopLabel(swap.TypeOut),
|
|
|
|
|
Initiator: autoloopSwapInitiator,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
loopOuts1 = []loopOutRequestResp{
|
|
|
|
|
{
|
|
|
|
|
request: chan1Swap,
|
|
|
|
|
response: &loop.LoopOutSwapInfo{
|
|
|
|
|
SwapHash: lntypes.Hash{1},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
loopOuts2 = []loopOutRequestResp{
|
|
|
|
|
{
|
|
|
|
|
request: chan2Swap,
|
|
|
|
|
response: &loop.LoopOutSwapInfo{
|
|
|
|
|
SwapHash: lntypes.Hash{1},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// 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,
|
|
|
|
|
}
|
|
|
|
|
c.autoloop(step)
|
|
|
|
|
|
|
|
|
|
existing := []*loopdb.LoopOut{
|
|
|
|
|
existingSwapFromRequest(
|
|
|
|
|
chan1Swap, testTime, []*loopdb.LoopEvent{
|
|
|
|
|
{
|
|
|
|
|
SwapStateData: loopdb.SwapStateData{
|
|
|
|
|
State: loopdb.StateInitiated,
|
|
|
|
|
},
|
|
|
|
|
Time: testTime,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
step = &autoloopStep{
|
|
|
|
|
minAmt: 1,
|
|
|
|
|
maxAmt: amt + 1,
|
|
|
|
|
quotesOut: quotes2,
|
|
|
|
|
existingOut: existing,
|
|
|
|
|
expectedOut: nil,
|
|
|
|
|
}
|
|
|
|
|
// Tick again, we should expect no loop outs because our budget would be
|
|
|
|
|
// exceeded.
|
|
|
|
|
c.autoloop(step)
|
|
|
|
|
|
|
|
|
|
// Create the existing entry for the first swap, marking its last update
|
|
|
|
|
// with success and a specific timestamp.
|
|
|
|
|
existing2 := []*loopdb.LoopOut{
|
|
|
|
|
existingSwapFromRequest(
|
|
|
|
|
chan1Swap, testTime, []*loopdb.LoopEvent{
|
|
|
|
|
{
|
|
|
|
|
SwapStateData: loopdb.SwapStateData{
|
|
|
|
|
State: loopdb.StateSuccess,
|
|
|
|
|
},
|
|
|
|
|
Time: testTime,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Apply the balance shifts on the channels in order to get the correct
|
|
|
|
|
// recommendations on next tick.
|
|
|
|
|
c.lnd.Channels[0].LocalBalance = 2500
|
|
|
|
|
c.lnd.Channels[0].RemoteBalance = 7500
|
|
|
|
|
|
|
|
|
|
// Advance time to the future, causing a budget refresh.
|
|
|
|
|
c.testClock.SetTime(testTime.Add(time.Hour * 25))
|
|
|
|
|
|
|
|
|
|
step = &autoloopStep{
|
|
|
|
|
minAmt: 1,
|
|
|
|
|
maxAmt: amt + 1,
|
|
|
|
|
quotesOut: quotes2,
|
|
|
|
|
existingOut: existing2,
|
|
|
|
|
expectedOut: loopOuts2,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Tick again, we should expect a loop out to occur on the 2nd channel.
|
|
|
|
|
c.autoloop(step)
|
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
|