mirror of
https://github.com/lightninglabs/loop.git
synced 2026-08-13 12:33:03 +02:00
Update LND, Aperture, and Taproot Assets to revisions using the btcd v2 modules, and update lndclient to v0.21.0-3. Migrate Loop chain, transaction, and address types to their corresponding v2 packages. The lndclient release includes the migration from: https://github.com/lightninglabs/lndclient/pull/280 Taproot Assets is temporarily replaced with its btcd v2 revision because the v0.8 release branch has not adopted the new modules. This raises the minimum Go version to 1.26 and changes exported address types.
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)
|
|
}
|