2021-12-15 07:01:20 +00:00
|
|
|
package liquidity
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
|
|
|
"testing"
|
|
|
|
|
2022-03-14 12:36:02 +00:00
|
|
|
"github.com/btcsuite/btcd/btcutil"
|
2021-12-15 07:01:20 +00:00
|
|
|
"github.com/lightninglabs/loop"
|
|
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
|
|
|
"github.com/lightningnetwork/lnd/routing/route"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/stretchr/testify/mock"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"google.golang.org/grpc/codes"
|
|
|
|
"google.golang.org/grpc/status"
|
|
|
|
)
|
|
|
|
|
|
|
|
// TestLoopinInUse tests that the loop in swap builder prevents dispatching
|
|
|
|
// swaps for peers when there is already a swap running for that peer.
|
|
|
|
func TestLoopinInUse(t *testing.T) {
|
|
|
|
var (
|
|
|
|
peer1 = route.Vertex{1}
|
|
|
|
chan1 = lnwire.NewShortChanIDFromInt(1)
|
|
|
|
|
|
|
|
peer2 = route.Vertex{2}
|
|
|
|
chan2 = lnwire.NewShortChanIDFromInt(2)
|
|
|
|
)
|
|
|
|
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
ongoingLoopOut *lnwire.ShortChannelID
|
|
|
|
ongoingLoopIn *route.Vertex
|
|
|
|
failedLoopIn *route.Vertex
|
|
|
|
expectedErr error
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "swap allowed",
|
|
|
|
ongoingLoopIn: &peer2,
|
|
|
|
ongoingLoopOut: &chan2,
|
|
|
|
failedLoopIn: &peer2,
|
|
|
|
expectedErr: nil,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "conflicts with loop out",
|
|
|
|
ongoingLoopOut: &chan1,
|
|
|
|
expectedErr: newReasonError(ReasonLoopOut),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "conflicts with loop in",
|
|
|
|
ongoingLoopIn: &peer1,
|
|
|
|
expectedErr: newReasonError(ReasonLoopIn),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "previous failed loopin",
|
|
|
|
failedLoopIn: &peer1,
|
|
|
|
expectedErr: newReasonError(ReasonFailureBackoff),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, testCase := range tests {
|
|
|
|
traffic := newSwapTraffic()
|
|
|
|
|
|
|
|
if testCase.ongoingLoopOut != nil {
|
|
|
|
traffic.ongoingLoopOut[*testCase.ongoingLoopOut] = true
|
|
|
|
}
|
|
|
|
|
|
|
|
if testCase.ongoingLoopIn != nil {
|
|
|
|
traffic.ongoingLoopIn[*testCase.ongoingLoopIn] = true
|
|
|
|
}
|
|
|
|
|
|
|
|
if testCase.failedLoopIn != nil {
|
|
|
|
traffic.failedLoopIn[*testCase.failedLoopIn] = testTime
|
|
|
|
}
|
|
|
|
|
|
|
|
builder := newLoopInBuilder(nil)
|
|
|
|
err := builder.inUse(traffic, peer1, []lnwire.ShortChannelID{
|
|
|
|
chan1,
|
|
|
|
})
|
|
|
|
|
|
|
|
require.Equal(t, testCase.expectedErr, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TestLoopinBuildSwap tests construction of loop in swaps for autoloop,
|
|
|
|
// including the case where the client cannot get a quote because it is not
|
|
|
|
// reachable from the server.
|
|
|
|
func TestLoopinBuildSwap(t *testing.T) {
|
|
|
|
var (
|
|
|
|
peer1 = route.Vertex{1}
|
|
|
|
chan1 = lnwire.NewShortChanIDFromInt(1)
|
|
|
|
|
|
|
|
htlcConfTarget int32 = 6
|
|
|
|
swapAmt btcutil.Amount = 100000
|
|
|
|
|
|
|
|
quote = &loop.LoopInQuote{
|
|
|
|
SwapFee: 1,
|
|
|
|
MinerFee: 2,
|
|
|
|
}
|
|
|
|
|
|
|
|
expectedSwap = &loopInSwapSuggestion{
|
|
|
|
loop.LoopInRequest{
|
|
|
|
Amount: swapAmt,
|
|
|
|
MaxSwapFee: quote.SwapFee,
|
|
|
|
MaxMinerFee: quote.MinerFee,
|
|
|
|
HtlcConfTarget: htlcConfTarget,
|
|
|
|
LastHop: &peer1,
|
|
|
|
Initiator: autoloopSwapInitiator,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
quoteRequest = &loop.LoopInQuoteRequest{
|
|
|
|
Amount: swapAmt,
|
|
|
|
LastHop: &peer1,
|
|
|
|
HtlcConfTarget: htlcConfTarget,
|
|
|
|
}
|
|
|
|
|
|
|
|
errPrecondition = status.Error(codes.FailedPrecondition, "failed")
|
|
|
|
errOtherCode = status.Error(codes.DeadlineExceeded, "timeout")
|
|
|
|
errNoCode = errors.New("failure")
|
|
|
|
)
|
|
|
|
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
prepareMock func(*mockCfg)
|
|
|
|
expectedSwap swapSuggestion
|
|
|
|
expectedErr error
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "quote successful",
|
|
|
|
prepareMock: func(m *mockCfg) {
|
|
|
|
m.On(
|
|
|
|
"LoopInQuote", mock.Anything,
|
|
|
|
quoteRequest,
|
|
|
|
).Return(quote, nil)
|
|
|
|
},
|
|
|
|
expectedSwap: expectedSwap,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "client unreachable",
|
|
|
|
prepareMock: func(m *mockCfg) {
|
|
|
|
m.On(
|
|
|
|
"LoopInQuote", mock.Anything,
|
|
|
|
quoteRequest,
|
|
|
|
).Return(quote, errPrecondition)
|
|
|
|
},
|
|
|
|
expectedSwap: nil,
|
|
|
|
expectedErr: newReasonError(ReasonLoopInUnreachable),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "other error code",
|
|
|
|
prepareMock: func(m *mockCfg) {
|
|
|
|
m.On(
|
|
|
|
"LoopInQuote", mock.Anything,
|
|
|
|
quoteRequest,
|
|
|
|
).Return(quote, errOtherCode)
|
|
|
|
},
|
|
|
|
expectedSwap: nil,
|
|
|
|
expectedErr: errOtherCode,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "no error code",
|
|
|
|
prepareMock: func(m *mockCfg) {
|
|
|
|
m.On(
|
|
|
|
"LoopInQuote", mock.Anything,
|
|
|
|
quoteRequest,
|
|
|
|
).Return(quote, errNoCode)
|
|
|
|
},
|
|
|
|
expectedSwap: nil,
|
|
|
|
expectedErr: errNoCode,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, testCase := range tests {
|
|
|
|
mock, cfg := newMockConfig()
|
|
|
|
params := defaultParameters
|
|
|
|
params.HtlcConfTarget = htlcConfTarget
|
|
|
|
params.AutoFeeBudget = 100000
|
|
|
|
|
|
|
|
testCase.prepareMock(mock)
|
|
|
|
|
|
|
|
builder := newLoopInBuilder(cfg)
|
|
|
|
swap, err := builder.buildSwap(
|
|
|
|
context.Background(), peer1, []lnwire.ShortChannelID{
|
|
|
|
chan1,
|
2023-05-29 10:23:55 +00:00
|
|
|
}, swapAmt, params,
|
2021-12-15 07:01:20 +00:00
|
|
|
)
|
|
|
|
assert.Equal(t, testCase.expectedSwap, swap)
|
|
|
|
assert.Equal(t, testCase.expectedErr, err)
|
|
|
|
|
|
|
|
mock.AssertExpectations(t)
|
|
|
|
}
|
|
|
|
}
|