diff --git a/docs/release-notes/release-notes-0.18.3.md b/docs/release-notes/release-notes-0.18.3.md index 1e8279137..087ded883 100644 --- a/docs/release-notes/release-notes-0.18.3.md +++ b/docs/release-notes/release-notes-0.18.3.md @@ -76,6 +76,11 @@ blinded path expiry. * [Fixed](https://github.com/lightningnetwork/lnd/pull/9021) an issue with some command-line arguments not being passed when running `make itest-parallel`. + +* [Fix a bug](https://github.com/lightningnetwork/lnd/pull/9039) that would + cause UpdateAddHTLC message with blinding point fields to not be re-forwarded + correctly on restart. + # New Features ## Functional Enhancements ## RPC Additions diff --git a/itest/list_on_test.go b/itest/list_on_test.go index f57e28743..7286b1ded 100644 --- a/itest/list_on_test.go +++ b/itest/list_on_test.go @@ -582,6 +582,10 @@ var allTestCases = []*lntest.TestCase{ Name: "update pending open channels", TestFunc: testUpdateOnPendingOpenChannels, }, + { + Name: "blinded payment htlc re-forward", + TestFunc: testBlindedPaymentHTLCReForward, + }, { Name: "query blinded route", TestFunc: testQueryBlindedRoutes, diff --git a/itest/lnd_route_blinding_test.go b/itest/lnd_route_blinding_test.go index fbfca0f27..372466fe4 100644 --- a/itest/lnd_route_blinding_test.go +++ b/itest/lnd_route_blinding_test.go @@ -1420,3 +1420,68 @@ func testMPPToMultipleBlindedPaths(ht *lntest.HarnessTest) { ht.AssertNumWaitingClose(hn, 0) } } + +// testBlindedPaymentHTLCReForward tests that an UpdateAddHTLC message is +// correctly persisted, reloaded and resent to the next peer on restart in the +// case where the sending peer does not get a chance to send the message before +// restarting. This test specifically tests that this is done correctly for +// a blinded path payment since this adds a new blinding point field to +// UpdateAddHTLC which we need to ensure gets included in the message on +// restart. +func testBlindedPaymentHTLCReForward(ht *lntest.HarnessTest) { + // Setup a test case. + ctx, testCase := newBlindedForwardTest(ht) + defer testCase.cleanup() + + // Set up network with carol interceptor. + testCase.setupNetwork(ctx, true) + + // Let dave create invoice. + blindedPaymentPath := testCase.buildBlindedPath() + route := testCase.createRouteToBlinded(10_000_000, blindedPaymentPath) + + // Once our interceptor is set up, we can send the blinded payment. + hash := sha256.Sum256(testCase.preimage[:]) + sendReq := &routerrpc.SendToRouteRequest{ + PaymentHash: hash[:], + Route: route, + } + + // In a goroutine, we let Alice pay the invoice from dave. + done := make(chan struct{}) + go func() { + defer close(done) + + htlcAttempt, err := testCase.ht.Alice.RPC.Router.SendToRouteV2( + ctx, sendReq, + ) + require.NoError(testCase.ht, err) + require.Equal( + testCase.ht, lnrpc.HTLCAttempt_SUCCEEDED, + htlcAttempt.Status, + ) + }() + + // Wait for the HTLC to be active on Alice and Bob's channels. + ht.AssertOutgoingHTLCActive(ht.Alice, testCase.channels[0], hash[:]) + ht.AssertOutgoingHTLCActive(ht.Bob, testCase.channels[1], hash[:]) + + // Intercept the forward on Carol's link. At this point, we know she + // has received the HTLC and so will persist this packet. + _, err := testCase.carolInterceptor.Recv() + require.NoError(ht, err) + + // Now, restart Carol. This time, don't require an interceptor. This + // means that Carol should load up any previously persisted + // UpdateAddHTLC messages and continue the processing of them. + ht.RestartNodeWithExtraArgs( + testCase.carol, []string{"--bitcoin.timelockdelta=18"}, + ) + + // Now, wait for the payment to complete. + select { + case <-done: + case <-time.After(defaultTimeout): + require.Fail(ht, "timeout waiting for sending payment") + } +} diff --git a/lnwallet/channel.go b/lnwallet/channel.go index 7a6278522..7bd382d2c 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -205,7 +205,7 @@ func PayDescsFromRemoteLogUpdates(chanID lnwire.ShortChannelID, height uint64, Height: height, Index: uint16(i), }, - BlindingPoint: pd.BlindingPoint, + BlindingPoint: wireMsg.BlindingPoint, } pd.OnionBlob = make([]byte, len(wireMsg.OnionBlob)) copy(pd.OnionBlob[:], wireMsg.OnionBlob[:])