itest: enhance mission control namespace itest

In this commit, we enhance the itess to exercise the new
query+reset+import namespace features. This also rounds out the test as
well to make sure that the mc payment portion works properly.
This commit is contained in:
Olaoluwa Osuntokun
2025-06-05 18:27:12 -07:00
parent 976ca31a25
commit 29ba9686f2

View File

@@ -1,6 +1,9 @@
package itest package itest
import ( import (
"testing"
"time"
"github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil"
"github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc"
@@ -11,79 +14,187 @@ import (
// testMissionControlNamespace tests that payments can use different mission // testMissionControlNamespace tests that payments can use different mission
// control namespaces to maintain separate routing histories. // control namespaces to maintain separate routing histories.
func testMissionControlNamespace(ht *lntest.HarnessTest) { func testMissionControlNamespace(ht *lntest.HarnessTest) {
// Create a simple two-node network.
const chanAmt = btcutil.Amount(1_000_000) const chanAmt = btcutil.Amount(1_000_000)
// Create two nodes. Alice gets funded. // We'll create a simple two node network for this tesat.
alice := ht.NewNodeWithCoins("Alice", nil) alice := ht.NewNodeWithCoins("Alice", nil)
bob := ht.NewNode("Bob", nil) bob := ht.NewNode("Bob", nil)
// Connect nodes.
ht.EnsureConnected(alice, bob) ht.EnsureConnected(alice, bob)
// Open channel: Alice -> Bob.
chanPoint := ht.OpenChannel( chanPoint := ht.OpenChannel(
alice, bob, lntest.OpenChannelParams{ alice, bob, lntest.OpenChannelParams{
Amt: chanAmt, Amt: chanAmt,
}, },
) )
// Wait for channel to be seen by both nodes.
ht.AssertChannelInGraph(alice, chanPoint) ht.AssertChannelInGraph(alice, chanPoint)
ht.AssertChannelInGraph(bob, chanPoint) ht.AssertChannelInGraph(bob, chanPoint)
// Create an invoice from Bob. // Create an invoice for Bob that we'll pay below.
const paymentAmt = 10_000 const paymentAmt = 10_000
invoice := bob.RPC.AddInvoice(&lnrpc.Invoice{ invoice := bob.RPC.AddInvoice(&lnrpc.Invoice{
Value: paymentAmt, Value: paymentAmt,
Memo: "test payment default namespace", Memo: "test payment default namespace",
}) })
// Reset mission control to ensure clean state. // Reset mission control for the default namespace to ensure clean
alice.RPC.ResetMissionControl() // state.
_, err := alice.RPC.Router.ResetMissionControl(
ht.Context(), &routerrpc.ResetMissionControlRequest{
MissionControlNamespace: "",
},
)
require.NoError(ht.T, err, "failed to reset default mission control")
// Send a payment using the default namespace. ht.Run("send_payment_with_namespaces", func(t *testing.T) {
req := &routerrpc.SendPaymentRequest{ // Send a payment using the default namespace.
PaymentRequest: invoice.PaymentRequest, reqDefault := &routerrpc.SendPaymentRequest{
TimeoutSeconds: 60, PaymentRequest: invoice.PaymentRequest,
MissionControlNamespace: "", // default namespace TimeoutSeconds: 60,
} MissionControlNamespace: "",
ht.SendPaymentAssertSettled(alice, req) }
ht.SendPaymentAssertSettled(alice, reqDefault)
// Create another invoice. // Make another payment with Bob but use our custom namespace
invoice2 := bob.RPC.AddInvoice(&lnrpc.Invoice{ // this time instead.
Value: paymentAmt, invoice2 := bob.RPC.AddInvoice(&lnrpc.Invoice{
Memo: "test payment custom namespace", Value: paymentAmt,
Memo: "test payment custom namespace",
})
customNs := "custom-namespace-send"
reqCustom := &routerrpc.SendPaymentRequest{
PaymentRequest: invoice2.PaymentRequest,
TimeoutSeconds: 60,
MissionControlNamespace: customNs,
}
payment := ht.SendPaymentAssertSettled(alice, reqCustom)
require.Equal(t, lnrpc.Payment_SUCCEEDED, payment.Status,
"payment with custom namespace should succeed")
// If we query for the default mc, then we should find that the
// records are populated.
mcReqDefault := &routerrpc.QueryMissionControlRequest{
MissionControlNamespace: "",
}
defaultMC, err := alice.RPC.Router.QueryMissionControl(
ht.Context(), mcReqDefault,
)
require.NoError(t, err)
require.NotEmpty(t, defaultMC.Pairs,
"default namespace should have recorded channel usage")
// Similarly, we should find that the custom namespace also has
// entries.
mcReqCustom := &routerrpc.QueryMissionControlRequest{
MissionControlNamespace: customNs,
}
customMC, err := alice.RPC.Router.QueryMissionControl(
ht.Context(), mcReqCustom,
)
require.NoError(t, err)
require.NotEmpty(t, customMC.Pairs,
"custom namespace should have recorded channel usage")
require.True(t, len(defaultMC.Pairs) > 0, "Default MC empty")
require.True(t, len(customMC.Pairs) > 0, "Custom MC empty")
}) })
// Send a payment using a custom namespace. ht.Run("reset_mission_control_with_namespace", func(t *testing.T) {
customReq := &routerrpc.SendPaymentRequest{ customNsReset := "namespace-to-reset"
PaymentRequest: invoice2.PaymentRequest,
TimeoutSeconds: 60,
MissionControlNamespace: "custom-namespace",
}
payment := ht.SendPaymentAssertSettled(alice, customReq)
require.Equal(ht.T, lnrpc.Payment_SUCCEEDED, payment.Status,
"payment with custom namespace should succeed")
// Query mission control to verify the default namespace has recorded // Send a payment to populate this new custom namespace.
// the payment attempt. invReset := bob.RPC.AddInvoice(&lnrpc.Invoice{Value: 100})
mcReq := &routerrpc.QueryMissionControlRequest{} reqPopulate := &routerrpc.SendPaymentRequest{
defaultMC, err := alice.RPC.Router.QueryMissionControl( PaymentRequest: invReset.PaymentRequest,
ht.Context(), mcReq, TimeoutSeconds: 60,
) MissionControlNamespace: customNsReset,
require.NoError(ht.T, err) }
ht.SendPaymentAssertSettled(alice, reqPopulate)
// The default namespace should have recorded at least one pair.
require.NotEmpty(ht.T, defaultMC.Pairs,
"default namespace should have recorded channel usage")
// The test passes if we have any pairs recorded, which proves
// mission control is working with the default namespace.
// The custom namespace payment also succeeded, demonstrating
// namespace isolation.
// Note: Currently there's no way to query a specific namespace's // Query the namespace, we should find now that it has some
// mission control state via RPC, but the fact that both payments // entries populated.
// succeeded proves the namespace isolation is working correctly. mcBeforeReset, err := alice.RPC.Router.QueryMissionControl(
} ht.Context(), &routerrpc.QueryMissionControlRequest{
MissionControlNamespace: customNsReset,
},
)
require.NoError(t, err)
require.NotEmpty(
t, mcBeforeReset.Pairs, "MC should be populated "+
"before reset",
)
// Now, we'll reset the ns, then check below that it's actually
// empty.
_, errReset := alice.RPC.Router.ResetMissionControl(
ht.Context(), &routerrpc.ResetMissionControlRequest{
MissionControlNamespace: customNsReset,
},
)
require.NoError(t, errReset)
// Query again to confirm it's empty.
mcAfterReset, err := alice.RPC.Router.QueryMissionControl(
ht.Context(), &routerrpc.QueryMissionControlRequest{
MissionControlNamespace: customNsReset,
},
)
require.NoError(t, err)
require.Empty(
t, mcAfterReset.Pairs, "MC should be empty after reset",
)
// Now we'll test isolation: the default namespace shouldn't
// have been affected.
defaultMC, err := alice.RPC.Router.QueryMissionControl(
ht.Context(), &routerrpc.QueryMissionControlRequest{
MissionControlNamespace: "",
},
)
require.NoError(t, err)
require.NotEmpty(
t, defaultMC.Pairs, "Default MC should not be empty",
)
})
ht.Run("ximport_mission_control_with_namespace", func(t *testing.T) {
customNs := "namespace-to-import"
// We'll make some fake data to import as weights.
importData := &routerrpc.XImportMissionControlRequest{ //nolint:ll
MissionControlNamespace: customNs,
Pairs: []*routerrpc.PairHistory{
{
NodeFrom: alice.PubKey[:],
NodeTo: bob.PubKey[:],
History: &routerrpc.PairData{
FailTime: time.Now().Unix() - 1000,
FailAmtMsat: 5000,
SuccessTime: time.Now().Unix(),
SuccessAmtMsat: 10000,
},
},
},
Force: true,
}
_, err := alice.RPC.Router.XImportMissionControl(
ht.Context(), importData,
)
require.NoError(t, err)
// That data that we created above should be found now.
mcAfterImport, err := alice.RPC.Router.QueryMissionControl(
ht.Context(), &routerrpc.QueryMissionControlRequest{
MissionControlNamespace: customNs,
},
)
require.NoError(t, err)
require.NotEmpty(
t, mcAfterImport.Pairs, "MC should have imported data",
)
require.Equal(
t, int64(10000),
mcAfterImport.Pairs[0].History.SuccessAmtMsat,
)
})
}