diff --git a/itest/lnd_mission_control_namespace_test.go b/itest/lnd_mission_control_namespace_test.go index 75d788c35..a3d5d4867 100644 --- a/itest/lnd_mission_control_namespace_test.go +++ b/itest/lnd_mission_control_namespace_test.go @@ -1,6 +1,9 @@ package itest import ( + "testing" + "time" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" @@ -11,79 +14,187 @@ import ( // testMissionControlNamespace tests that payments can use different mission // control namespaces to maintain separate routing histories. func testMissionControlNamespace(ht *lntest.HarnessTest) { - // Create a simple two-node network. 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) bob := ht.NewNode("Bob", nil) - - // Connect nodes. ht.EnsureConnected(alice, bob) - - // Open channel: Alice -> Bob. chanPoint := ht.OpenChannel( alice, bob, lntest.OpenChannelParams{ Amt: chanAmt, }, ) - // Wait for channel to be seen by both nodes. ht.AssertChannelInGraph(alice, chanPoint) ht.AssertChannelInGraph(bob, chanPoint) - // Create an invoice from Bob. + // Create an invoice for Bob that we'll pay below. const paymentAmt = 10_000 invoice := bob.RPC.AddInvoice(&lnrpc.Invoice{ Value: paymentAmt, Memo: "test payment default namespace", }) - // Reset mission control to ensure clean state. - alice.RPC.ResetMissionControl() + // Reset mission control for the default namespace to ensure clean + // 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. - req := &routerrpc.SendPaymentRequest{ - PaymentRequest: invoice.PaymentRequest, - TimeoutSeconds: 60, - MissionControlNamespace: "", // default namespace - } - ht.SendPaymentAssertSettled(alice, req) + ht.Run("send_payment_with_namespaces", func(t *testing.T) { + // Send a payment using the default namespace. + reqDefault := &routerrpc.SendPaymentRequest{ + PaymentRequest: invoice.PaymentRequest, + TimeoutSeconds: 60, + MissionControlNamespace: "", + } + ht.SendPaymentAssertSettled(alice, reqDefault) - // Create another invoice. - invoice2 := bob.RPC.AddInvoice(&lnrpc.Invoice{ - Value: paymentAmt, - Memo: "test payment custom namespace", + // Make another payment with Bob but use our custom namespace + // this time instead. + invoice2 := bob.RPC.AddInvoice(&lnrpc.Invoice{ + 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. - customReq := &routerrpc.SendPaymentRequest{ - 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") + ht.Run("reset_mission_control_with_namespace", func(t *testing.T) { + customNsReset := "namespace-to-reset" - // Query mission control to verify the default namespace has recorded - // the payment attempt. - mcReq := &routerrpc.QueryMissionControlRequest{} - defaultMC, err := alice.RPC.Router.QueryMissionControl( - ht.Context(), mcReq, - ) - require.NoError(ht.T, err) - - // 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. + // Send a payment to populate this new custom namespace. + invReset := bob.RPC.AddInvoice(&lnrpc.Invoice{Value: 100}) + reqPopulate := &routerrpc.SendPaymentRequest{ + PaymentRequest: invReset.PaymentRequest, + TimeoutSeconds: 60, + MissionControlNamespace: customNsReset, + } + ht.SendPaymentAssertSettled(alice, reqPopulate) - // Note: Currently there's no way to query a specific namespace's - // mission control state via RPC, but the fact that both payments - // succeeded proves the namespace isolation is working correctly. -} \ No newline at end of file + // Query the namespace, we should find now that it has some + // entries populated. + 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, + ) + }) +}