mirror of
https://github.com/lightninglabs/loop.git
synced 2026-08-13 12:33:03 +02:00
Include https://github.com/lightninglabs/lndclient/pull/280 multi: migrate to btcd v2 modules + add WalletKit.SubmitPackage Migrate Loop imports and update Aperture and Taproot Assets to compatible revisions so this commit remains green on its own.
742 lines
18 KiB
Go
742 lines
18 KiB
Go
package loop
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/btcsuite/btcd/btcec/v2"
|
|
"github.com/btcsuite/btcd/btcutil/v2"
|
|
"github.com/lightninglabs/lndclient"
|
|
"github.com/lightninglabs/loop/test"
|
|
"github.com/lightningnetwork/lnd/clock"
|
|
"github.com/lightningnetwork/lnd/routing/route"
|
|
"github.com/lightningnetwork/lnd/zpay32"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
var (
|
|
alice = route.Vertex{1}
|
|
bob = route.Vertex{2}
|
|
charlie = route.Vertex{3}
|
|
dave = route.Vertex{4}
|
|
eugene = route.Vertex{5}
|
|
loopNode = route.Vertex{99}
|
|
|
|
privFrank, _ = btcec.NewPrivateKey()
|
|
frankPubKey = privFrank.PubKey()
|
|
frank = route.NewVertex(frankPubKey)
|
|
|
|
privGeorge, _ = btcec.NewPrivateKey()
|
|
georgePubKey = privGeorge.PubKey()
|
|
george = route.NewVertex(georgePubKey)
|
|
)
|
|
|
|
// testChan holds simplified test data for channels.
|
|
type testChan struct {
|
|
nodeID1 route.Vertex
|
|
nodeID2 route.Vertex
|
|
chanID uint64
|
|
capacity int64
|
|
feeBase1 int64
|
|
feeRate1 int64
|
|
feeBase2 int64
|
|
feeRate2 int64
|
|
}
|
|
|
|
// makeTestNetwork is a helper creating mocked network data from test inputs.
|
|
func makeTestNetwork(channels []testChan) ([]lndclient.ChannelInfo,
|
|
map[uint64]*lndclient.ChannelEdge) {
|
|
|
|
chanInfos := make([]lndclient.ChannelInfo, len(channels))
|
|
edges := make(map[uint64]*lndclient.ChannelEdge, len(channels))
|
|
for i, ch := range channels {
|
|
chanInfos[i] = lndclient.ChannelInfo{
|
|
ChannelID: ch.chanID,
|
|
}
|
|
|
|
edges[ch.chanID] = &lndclient.ChannelEdge{
|
|
ChannelID: ch.chanID,
|
|
Capacity: btcutil.Amount(ch.capacity),
|
|
Node1: ch.nodeID1,
|
|
Node2: ch.nodeID2,
|
|
Node1Policy: &lndclient.RoutingPolicy{
|
|
FeeBaseMsat: ch.feeBase1,
|
|
FeeRateMilliMsat: ch.feeRate1,
|
|
},
|
|
Node2Policy: &lndclient.RoutingPolicy{
|
|
FeeBaseMsat: ch.feeBase2,
|
|
FeeRateMilliMsat: ch.feeRate2,
|
|
},
|
|
}
|
|
}
|
|
|
|
return chanInfos, edges
|
|
}
|
|
|
|
// TestLowHighRoutingPlugin tests that the low-high routing plugin does indeed
|
|
// gradually change MC settings in favour of more expensive inbound channels
|
|
// towards the Loop server.
|
|
func TestLowHighRoutingPlugin(t *testing.T) {
|
|
target := loopNode
|
|
amt := btcutil.Amount(50)
|
|
testTime := time.Now().UTC()
|
|
|
|
tests := []struct {
|
|
name string
|
|
channels []testChan
|
|
routeHints [][]zpay32.HopHint
|
|
initError error
|
|
missionControlState [][]lndclient.MissionControlEntry
|
|
restoredMissionControlState []lndclient.MissionControlEntry
|
|
}{
|
|
{
|
|
name: "degenerate network 1",
|
|
//
|
|
// Alice --- Loop
|
|
//
|
|
channels: []testChan{
|
|
{alice, loopNode, 1, 1000, 1000, 1, 1000, 1},
|
|
},
|
|
initError: ErrRoutingPluginNotApplicable,
|
|
missionControlState: [][]lndclient.MissionControlEntry{
|
|
// The original MC state we start with.
|
|
{
|
|
{
|
|
NodeFrom: alice,
|
|
NodeTo: loopNode,
|
|
SuccessTime: testTime,
|
|
SuccessAmt: 10000,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "degenerate network 2",
|
|
//
|
|
// Alice --- Bob --- Loop
|
|
//
|
|
channels: []testChan{
|
|
// Alice - Bob
|
|
{alice, bob, 1, 1000, 1000, 1, 1000, 1},
|
|
// Bob - Loop
|
|
{bob, loopNode, 2, 1000, 1000, 1, 1000, 1},
|
|
},
|
|
initError: ErrRoutingPluginNotApplicable,
|
|
missionControlState: [][]lndclient.MissionControlEntry{
|
|
// The original MC state we start with.
|
|
{
|
|
{},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "degenerate network 3",
|
|
//
|
|
// _____Bob_____
|
|
// / \
|
|
// Alice Dave---Loop
|
|
// \___
|
|
// Charlie
|
|
//
|
|
channels: []testChan{
|
|
{alice, bob, 1, 1000, 1000, 1, 1000, 1},
|
|
{alice, charlie, 2, 1000, 1000, 1, 1000, 1},
|
|
// Bob - Dave (cheap)
|
|
{bob, dave, 3, 1000, 1000, 1, 1000, 1},
|
|
{dave, loopNode, 5, 1000, 1000, 1, 1000, 1},
|
|
},
|
|
initError: ErrRoutingPluginNotApplicable,
|
|
missionControlState: [][]lndclient.MissionControlEntry{
|
|
// The original MC state we start with.
|
|
{
|
|
{
|
|
NodeFrom: bob,
|
|
NodeTo: dave,
|
|
FailTime: time.Time{},
|
|
FailAmt: 0,
|
|
SuccessTime: testTime,
|
|
SuccessAmt: 10000,
|
|
},
|
|
},
|
|
},
|
|
restoredMissionControlState: []lndclient.MissionControlEntry{
|
|
{
|
|
NodeFrom: bob,
|
|
NodeTo: dave,
|
|
FailTime: time.Time{},
|
|
FailAmt: 0,
|
|
SuccessTime: testTime,
|
|
SuccessAmt: 10000,
|
|
},
|
|
},
|
|
},
|
|
{ // nolint: dupl
|
|
name: "fork before loop node 1",
|
|
//
|
|
// _____Bob_____
|
|
// / \
|
|
// Alice Dave---Loop
|
|
// \___ ___/
|
|
// Charlie
|
|
//
|
|
channels: []testChan{
|
|
{alice, bob, 1, 1000, 1000, 1, 1000, 1},
|
|
{alice, charlie, 2, 1000, 1000, 1, 1000, 1},
|
|
// Bob - Dave (cheap)
|
|
{bob, dave, 3, 1000, 1000, 1, 1000, 1},
|
|
// Charlie - Dave (expensive)
|
|
{charlie, dave, 4, 1000, 1000, 100, 1000, 1},
|
|
{dave, loopNode, 5, 1000, 1000, 1, 1000, 1},
|
|
},
|
|
initError: nil,
|
|
missionControlState: [][]lndclient.MissionControlEntry{
|
|
// The original MC state we start with.
|
|
{
|
|
{
|
|
NodeFrom: bob,
|
|
NodeTo: dave,
|
|
FailTime: time.Time{},
|
|
FailAmt: 0,
|
|
SuccessTime: testTime,
|
|
SuccessAmt: 10000,
|
|
},
|
|
},
|
|
// MC state set on the second attempt.
|
|
{
|
|
// Discourage Bob - Dave
|
|
{
|
|
NodeFrom: bob,
|
|
NodeTo: dave,
|
|
FailTime: testTime,
|
|
FailAmt: 1,
|
|
},
|
|
// Encourage Charlie - Dave
|
|
{
|
|
NodeFrom: charlie,
|
|
NodeTo: dave,
|
|
SuccessTime: testTime,
|
|
SuccessAmt: 1000000,
|
|
},
|
|
},
|
|
},
|
|
restoredMissionControlState: []lndclient.MissionControlEntry{
|
|
{
|
|
NodeFrom: bob,
|
|
NodeTo: dave,
|
|
FailTime: time.Time{},
|
|
FailAmt: 0,
|
|
SuccessTime: testTime,
|
|
SuccessAmt: 10000,
|
|
},
|
|
{
|
|
NodeFrom: charlie,
|
|
NodeTo: dave,
|
|
FailTime: testTime,
|
|
FailAmt: 1000001,
|
|
SuccessTime: testTime,
|
|
SuccessAmt: 1000000,
|
|
},
|
|
},
|
|
},
|
|
{ // nolint: dupl
|
|
name: "fork before loop node 1 with equal inbound fees",
|
|
//
|
|
// _____Bob_____
|
|
// / \
|
|
// Alice Dave---Loop
|
|
// \___ ___/
|
|
// Charlie
|
|
//
|
|
channels: []testChan{
|
|
{alice, bob, 1, 999, 1000, 1, 1000, 1},
|
|
{alice, charlie, 2, 9999, 1000, 1, 1000, 1},
|
|
// Bob - Dave (expensive)
|
|
{bob, dave, 3, 999, 1000, 100, 1000, 1},
|
|
// Charlie - Dave (expensive)
|
|
{charlie, dave, 4, 999, 1000, 100, 1000, 1},
|
|
{dave, loopNode, 5, 999, 1000, 1, 1000, 1},
|
|
},
|
|
initError: nil,
|
|
missionControlState: [][]lndclient.MissionControlEntry{
|
|
// The original MC state we start with.
|
|
{
|
|
{
|
|
NodeFrom: dave,
|
|
NodeTo: loopNode,
|
|
SuccessTime: testTime,
|
|
SuccessAmt: 10000,
|
|
},
|
|
},
|
|
// MC state on the second attempt encourages
|
|
// both inbound peers to make sure we do try
|
|
// to route through both.
|
|
{
|
|
{
|
|
NodeFrom: dave,
|
|
NodeTo: loopNode,
|
|
SuccessTime: testTime,
|
|
SuccessAmt: 10000,
|
|
},
|
|
{
|
|
NodeFrom: bob,
|
|
NodeTo: dave,
|
|
SuccessTime: testTime,
|
|
SuccessAmt: 999000,
|
|
},
|
|
{
|
|
NodeFrom: charlie,
|
|
NodeTo: dave,
|
|
SuccessTime: testTime,
|
|
SuccessAmt: 999000,
|
|
},
|
|
},
|
|
},
|
|
restoredMissionControlState: []lndclient.MissionControlEntry{
|
|
{
|
|
NodeFrom: dave,
|
|
NodeTo: loopNode,
|
|
SuccessTime: testTime,
|
|
SuccessAmt: 10000,
|
|
},
|
|
{
|
|
NodeFrom: bob,
|
|
NodeTo: dave,
|
|
SuccessTime: testTime,
|
|
SuccessAmt: 999000,
|
|
FailTime: testTime,
|
|
FailAmt: 999001,
|
|
},
|
|
{
|
|
NodeFrom: charlie,
|
|
NodeTo: dave,
|
|
SuccessTime: testTime,
|
|
SuccessAmt: 999000,
|
|
FailTime: testTime,
|
|
FailAmt: 999001,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "fork before loop node 2",
|
|
//
|
|
// _____Bob_____
|
|
// / \
|
|
// Alice Eugene---Frank---George---Loop
|
|
// |\___ ___//
|
|
// | Charlie /
|
|
// \ /
|
|
// \___ ___/
|
|
// Dave
|
|
//
|
|
channels: []testChan{
|
|
{alice, bob, 1, 1000, 1000, 1, 1000, 1},
|
|
{alice, charlie, 2, 1000, 1000, 1, 1000, 1},
|
|
{alice, dave, 3, 1000, 1000, 1, 1000, 1},
|
|
// Bob - Eugene (cheap)
|
|
{bob, eugene, 4, 1000, 1000, 1, 1000, 1},
|
|
// Charlie - Eugene (more expensive)
|
|
{charlie, eugene, 5, 1000, 1000, 2, 1000, 1},
|
|
// Dave - Eugene (most expensive)
|
|
{dave, eugene, 6, 1000, 1001, 2, 1000, 1},
|
|
{eugene, frank, 7, 1000, 1000, 1, 1000, 1},
|
|
},
|
|
// Private channels: Frank - George - Loop
|
|
routeHints: [][]zpay32.HopHint{{
|
|
{
|
|
NodeID: frankPubKey,
|
|
ChannelID: 8,
|
|
FeeBaseMSat: 1000,
|
|
FeeProportionalMillionths: 1,
|
|
},
|
|
{
|
|
NodeID: georgePubKey,
|
|
ChannelID: 9,
|
|
FeeBaseMSat: 1000,
|
|
FeeProportionalMillionths: 1,
|
|
},
|
|
}},
|
|
initError: nil,
|
|
missionControlState: [][]lndclient.MissionControlEntry{
|
|
// The original MC state we start with.
|
|
{
|
|
{
|
|
NodeFrom: charlie,
|
|
NodeTo: eugene,
|
|
SuccessTime: testTime,
|
|
SuccessAmt: 10000,
|
|
},
|
|
},
|
|
// MC state set on the second attempt.
|
|
{
|
|
// Discourage Bob - Eugene
|
|
{
|
|
NodeFrom: bob,
|
|
NodeTo: eugene,
|
|
FailTime: testTime,
|
|
FailAmt: 1,
|
|
},
|
|
// Encourage Charlie - Eugene
|
|
{
|
|
NodeFrom: charlie,
|
|
NodeTo: eugene,
|
|
SuccessTime: testTime,
|
|
SuccessAmt: 1000000,
|
|
},
|
|
// Encourage Dave - Eugene
|
|
{
|
|
NodeFrom: dave,
|
|
NodeTo: eugene,
|
|
SuccessTime: testTime,
|
|
SuccessAmt: 1000000,
|
|
},
|
|
},
|
|
// MC state set on the third attempt.
|
|
{
|
|
// Discourage Bob - Eugene
|
|
{
|
|
NodeFrom: bob,
|
|
NodeTo: eugene,
|
|
FailTime: testTime,
|
|
FailAmt: 1,
|
|
},
|
|
// Discourage Charlie - Eugene
|
|
{
|
|
NodeFrom: charlie,
|
|
NodeTo: eugene,
|
|
FailTime: testTime,
|
|
FailAmt: 1,
|
|
},
|
|
// Encourage Dave - Eugene
|
|
{
|
|
NodeFrom: dave,
|
|
NodeTo: eugene,
|
|
SuccessTime: testTime,
|
|
SuccessAmt: 1000000,
|
|
},
|
|
},
|
|
},
|
|
restoredMissionControlState: []lndclient.MissionControlEntry{
|
|
{
|
|
NodeFrom: bob,
|
|
NodeTo: eugene,
|
|
FailTime: testTime,
|
|
FailAmt: 1000001,
|
|
SuccessTime: testTime,
|
|
SuccessAmt: 1000000,
|
|
},
|
|
{
|
|
NodeFrom: charlie,
|
|
NodeTo: eugene,
|
|
FailTime: time.Time{},
|
|
FailAmt: 0,
|
|
SuccessTime: testTime,
|
|
SuccessAmt: 10000,
|
|
},
|
|
{
|
|
NodeFrom: dave,
|
|
NodeTo: eugene,
|
|
FailTime: testTime,
|
|
FailAmt: 1000001,
|
|
SuccessTime: testTime,
|
|
SuccessAmt: 1000000,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "fork before loop node 3",
|
|
//
|
|
// _____Bob_____
|
|
// / \
|
|
// Alice Eugene---Frank---George---Loop
|
|
// |\___ ___/ /
|
|
// | Charlie /
|
|
// \ /
|
|
// \___ ___________________/
|
|
// Dave
|
|
//
|
|
channels: []testChan{
|
|
// Alice - Bob
|
|
{alice, bob, 1, 1000, 1000, 1, 1000, 1},
|
|
// Alice - Charlie
|
|
{alice, charlie, 2, 1000, 1000, 1, 1000, 1},
|
|
// Alice - Dave
|
|
{alice, dave, 3, 1000, 1000, 1, 1000, 1},
|
|
// Bob - Eugene
|
|
{bob, eugene, 4, 1000, 1000, 1, 1000, 1},
|
|
// Charlie - Eugene
|
|
{charlie, eugene, 5, 1000, 1000, 2, 1000, 1},
|
|
// Dave - George (expensive)
|
|
{dave, george, 6, 1000, 1001, 2, 1000, 1},
|
|
// Eugene - Frank
|
|
{eugene, frank, 7, 1000, 1000, 1, 1000, 1},
|
|
// Frank - George (cheap)
|
|
{frank, george, 8, 1000, 1000, 1, 1000, 1},
|
|
// George - Loop
|
|
{george, loopNode, 9, 1000, 1000, 1, 1000, 1},
|
|
},
|
|
initError: nil,
|
|
missionControlState: [][]lndclient.MissionControlEntry{
|
|
// The original MC state we start with.
|
|
{
|
|
{
|
|
NodeFrom: charlie,
|
|
NodeTo: eugene,
|
|
SuccessTime: testTime,
|
|
SuccessAmt: 10000,
|
|
},
|
|
},
|
|
// MC state set on the second attempt.
|
|
{
|
|
{
|
|
NodeFrom: charlie,
|
|
NodeTo: eugene,
|
|
SuccessTime: testTime,
|
|
SuccessAmt: 10000,
|
|
},
|
|
// Discourage Frank - George
|
|
{
|
|
NodeFrom: frank,
|
|
NodeTo: george,
|
|
FailTime: testTime,
|
|
FailAmt: 1,
|
|
},
|
|
// Encourage Dave - George
|
|
{
|
|
NodeFrom: dave,
|
|
NodeTo: george,
|
|
SuccessTime: testTime,
|
|
SuccessAmt: 1000000,
|
|
},
|
|
},
|
|
},
|
|
restoredMissionControlState: []lndclient.MissionControlEntry{
|
|
{
|
|
NodeFrom: charlie,
|
|
NodeTo: eugene,
|
|
SuccessTime: testTime,
|
|
SuccessAmt: 10000,
|
|
},
|
|
{
|
|
NodeFrom: frank,
|
|
NodeTo: george,
|
|
FailTime: testTime,
|
|
FailAmt: 1000001,
|
|
SuccessTime: testTime,
|
|
SuccessAmt: 1000000,
|
|
},
|
|
{
|
|
NodeFrom: dave,
|
|
NodeTo: george,
|
|
FailTime: testTime,
|
|
FailAmt: 1000001,
|
|
SuccessTime: testTime,
|
|
SuccessAmt: 1000000,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
mockLnd := test.NewMockLnd()
|
|
|
|
mockLnd.Channels, mockLnd.ChannelEdges =
|
|
makeTestNetwork(tc.channels)
|
|
|
|
lnd := lndclient.LndServices{
|
|
Client: mockLnd.Client,
|
|
Router: mockLnd.Router,
|
|
}
|
|
|
|
testClock := clock.NewTestClock(testTime)
|
|
plugin := makeRoutingPlugin(
|
|
RoutingPluginLowHigh, lnd, testClock,
|
|
)
|
|
require.NotNil(t, plugin)
|
|
|
|
// Set start state for MC.
|
|
mockLnd.MissionControlState = tc.missionControlState[0]
|
|
|
|
// Initialize the routing plugin.
|
|
require.Equal(
|
|
t, tc.initError,
|
|
plugin.Init(
|
|
context.TODO(), target, tc.routeHints,
|
|
amt,
|
|
),
|
|
)
|
|
|
|
if tc.initError != nil {
|
|
// Make sure that MC state is untouched.
|
|
require.Equal(
|
|
t, tc.missionControlState[0],
|
|
mockLnd.MissionControlState,
|
|
)
|
|
|
|
return
|
|
}
|
|
|
|
maxAttempts := len(tc.missionControlState)
|
|
for i, expectedState := range tc.missionControlState {
|
|
// Check that after each step, MC state is what
|
|
// we expect it to be.
|
|
require.NoError(
|
|
t, plugin.BeforePayment(
|
|
context.TODO(),
|
|
i+1, maxAttempts,
|
|
),
|
|
)
|
|
|
|
require.ElementsMatch(
|
|
t, expectedState,
|
|
mockLnd.MissionControlState,
|
|
)
|
|
}
|
|
|
|
// Make sure we covered all inbound channels.
|
|
require.Error(
|
|
t, ErrRoutingPluginNoMoreRetries,
|
|
plugin.BeforePayment(
|
|
context.TODO(), maxAttempts, maxAttempts,
|
|
),
|
|
)
|
|
|
|
// Deinitialize the routing plugin.
|
|
require.NoError(t, plugin.Done(context.TODO()))
|
|
|
|
// Make sure that MC state is reset after Done() is
|
|
// called.
|
|
require.ElementsMatch(
|
|
t, tc.restoredMissionControlState,
|
|
mockLnd.MissionControlState,
|
|
)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRoutingPluginAcquireRelease(t *testing.T) {
|
|
mockLnd := test.NewMockLnd()
|
|
|
|
// _____Bob_____
|
|
// / \
|
|
// Alice Dave---Loop
|
|
// \___ ___/
|
|
// Charlie
|
|
//
|
|
channels := []testChan{
|
|
{alice, bob, 1, 1000, 1000, 1, 1000, 1},
|
|
{alice, charlie, 2, 1000, 1000, 1, 1000, 1},
|
|
{bob, dave, 3, 1000, 1000, 1, 1000, 1},
|
|
{charlie, dave, 4, 1000, 1000, 100, 1000, 1},
|
|
{dave, loopNode, 5, 1000, 1000, 1, 1000, 1},
|
|
}
|
|
|
|
mockLnd.Channels, mockLnd.ChannelEdges = makeTestNetwork(channels)
|
|
lnd := lndclient.LndServices{
|
|
Client: mockLnd.Client,
|
|
Router: mockLnd.Router,
|
|
}
|
|
|
|
target := loopNode
|
|
amt := btcutil.Amount(50)
|
|
ctx := context.TODO()
|
|
|
|
// RoutingPluginNone returns nil.
|
|
plugin, err := AcquireRoutingPlugin(
|
|
ctx, RoutingPluginNone, lnd, target, nil, amt,
|
|
)
|
|
require.Nil(t, plugin)
|
|
require.NoError(t, err)
|
|
|
|
// Attempting to acquire RoutingPluginNone again still returns nil.
|
|
plugin, err = AcquireRoutingPlugin(
|
|
ctx, RoutingPluginNone, lnd, target, nil, amt,
|
|
)
|
|
require.Nil(t, plugin)
|
|
require.NoError(t, err)
|
|
|
|
// Call ReleaseRoutingPlugin twice to ensure we can call it even when no
|
|
// plugin is acquired.
|
|
ReleaseRoutingPlugin(ctx)
|
|
ReleaseRoutingPlugin(ctx)
|
|
|
|
// RoutingPluginNone returns nil.
|
|
plugin2, err := AcquireRoutingPlugin(
|
|
ctx, RoutingPluginNone, lnd, target, nil, amt,
|
|
)
|
|
require.Nil(t, plugin2)
|
|
require.NoError(t, err)
|
|
|
|
// Acquire is successful.
|
|
plugin, err = AcquireRoutingPlugin(
|
|
ctx, RoutingPluginLowHigh, lnd, target, nil, amt,
|
|
)
|
|
require.NotNil(t, plugin)
|
|
require.NoError(t, err)
|
|
|
|
// Plugin already acquired, above.
|
|
plugin2, err = AcquireRoutingPlugin(
|
|
ctx, RoutingPluginLowHigh, lnd, target, nil, amt,
|
|
)
|
|
require.Nil(t, plugin2)
|
|
require.NoError(t, err)
|
|
|
|
// Release acruired plugin.
|
|
ReleaseRoutingPlugin(ctx)
|
|
|
|
// Acquire is successful.
|
|
plugin2, err = AcquireRoutingPlugin(
|
|
ctx, RoutingPluginLowHigh, lnd, target, nil, amt,
|
|
)
|
|
require.NotNil(t, plugin2)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// mockRoutingPlugin is a minimal RoutingPlugin used to capture the context
|
|
// passed to Done.
|
|
type mockRoutingPlugin struct {
|
|
doneCtxErr error
|
|
}
|
|
|
|
// Init is a no-op initializer for the mock plugin.
|
|
func (m *mockRoutingPlugin) Init(_ context.Context, _ route.Vertex,
|
|
_ [][]zpay32.HopHint, _ btcutil.Amount) error {
|
|
|
|
return nil
|
|
}
|
|
|
|
// Done records ctx.Err() so tests can assert whether teardown ran with a live
|
|
// context.
|
|
func (m *mockRoutingPlugin) Done(ctx context.Context) error {
|
|
m.doneCtxErr = ctx.Err()
|
|
return nil
|
|
}
|
|
|
|
// BeforePayment is a no-op hook for the mock plugin.
|
|
func (m *mockRoutingPlugin) BeforePayment(_ context.Context, _, _ int) error {
|
|
return nil
|
|
}
|
|
|
|
// TestReleaseRoutingPluginUsesLiveContext checks that ReleaseRoutingPlugin does
|
|
// not propagate caller cancellation to plugin teardown. The test cancels the
|
|
// caller context before release and verifies mock Done still sees nil ctx.Err.
|
|
func TestReleaseRoutingPluginUsesLiveContext(t *testing.T) {
|
|
ReleaseRoutingPlugin(context.Background())
|
|
t.Cleanup(func() {
|
|
ReleaseRoutingPlugin(context.Background())
|
|
})
|
|
|
|
mockPlugin := &mockRoutingPlugin{}
|
|
|
|
routingPluginMx.Lock()
|
|
routingPluginInstance = mockPlugin
|
|
routingPluginMx.Unlock()
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel()
|
|
|
|
ReleaseRoutingPlugin(ctx)
|
|
|
|
require.NoError(t, mockPlugin.doneCtxErr)
|
|
}
|