diff --git a/docs/release-notes/release-notes-0.18.3.md b/docs/release-notes/release-notes-0.18.3.md index 8ba9eefad..f5225a211 100644 --- a/docs/release-notes/release-notes-0.18.3.md +++ b/docs/release-notes/release-notes-0.18.3.md @@ -59,7 +59,11 @@ commitment when the channel was force closed. * We'll now always send [channel updates to our remote peer for open channels](https://github.com/lightningnetwork/lnd/pull/8963). - + +* [Fix a bug](https://github.com/lightningnetwork/lnd/pull/9023) 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 8fa4352b2..82fcdd99b 100644 --- a/itest/list_on_test.go +++ b/itest/list_on_test.go @@ -578,6 +578,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 44d5879f9..a43f22a3a 100644 --- a/itest/lnd_route_blinding_test.go +++ b/itest/lnd_route_blinding_test.go @@ -1424,3 +1424,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 65ae01356..e3a777caa 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[:])