diff --git a/itest/list_on_test.go b/itest/list_on_test.go index 4ad61629ae..588a46bb0b 100644 --- a/itest/list_on_test.go +++ b/itest/list_on_test.go @@ -430,6 +430,10 @@ var allTestCases = []*lntest.TestCase{ Name: "forward interceptor modified htlc", TestFunc: testForwardInterceptorModifiedHtlc, }, + { + Name: "forward interceptor wire records", + TestFunc: testForwardInterceptorWireRecords, + }, { Name: "zero conf channel open", TestFunc: testZeroConfChannelOpen, diff --git a/itest/lnd_forward_interceptor_test.go b/itest/lnd_forward_interceptor_test.go index 213f777bef..e2950adb8e 100644 --- a/itest/lnd_forward_interceptor_test.go +++ b/itest/lnd_forward_interceptor_test.go @@ -410,10 +410,10 @@ func testForwardInterceptorModifiedHtlc(ht *lntest.HarnessTest) { newOutgoingAmountMsat := packet.OutgoingAmountMsat + 4000 err := bobInterceptor.Send(&routerrpc.ForwardHtlcInterceptResponse{ - IncomingCircuitKey: packet.IncomingCircuitKey, - OutgoingAmountMsat: newOutgoingAmountMsat, - CustomRecords: customRecords, - Action: action, + IncomingCircuitKey: packet.IncomingCircuitKey, + OutgoingAmountMsat: newOutgoingAmountMsat, + OutgoingHtlcWireCustomRecords: customRecords, + Action: action, }) require.NoError(ht, err, "failed to send request") @@ -479,6 +479,80 @@ func testForwardInterceptorModifiedHtlc(ht *lntest.HarnessTest) { ht.CloseChannel(bob, cpBC) } +// testForwardInterceptorWireRecords tests that the interceptor can read any +// wire custom records provided by the sender of a payment as part of the +// update_add_htlc message. +func testForwardInterceptorWireRecords(ht *lntest.HarnessTest) { + // Initialize the test context with 3 connected nodes. + ts := newInterceptorTestScenario(ht) + + alice, bob, carol := ts.alice, ts.bob, ts.carol + + // Open and wait for channels. + const chanAmt = btcutil.Amount(300000) + p := lntest.OpenChannelParams{Amt: chanAmt} + reqs := []*lntest.OpenChannelRequest{ + {Local: alice, Remote: bob, Param: p}, + {Local: bob, Remote: carol, Param: p}, + } + resp := ht.OpenMultiChannelsAsync(reqs) + cpAB, cpBC := resp[0], resp[1] + + // Make sure Alice is aware of channel Bob=>Carol. + ht.AssertTopologyChannelOpen(alice, cpBC) + + // Connect an interceptor to Bob's node. + bobInterceptor, cancelBobInterceptor := bob.RPC.HtlcInterceptor() + + req := &lnrpc.Invoice{ValueMsat: 1000} + addResponse := carol.RPC.AddInvoice(req) + invoice := carol.RPC.LookupInvoice(addResponse.RHash) + + sendReq := &routerrpc.SendPaymentRequest{ + PaymentRequest: invoice.PaymentRequest, + TimeoutSeconds: int32(wait.PaymentTimeout.Seconds()), + FeeLimitMsat: noFeeLimitMsat, + FirstHopCustomRecords: map[uint64][]byte{ + 65537: []byte("test"), + }, + } + + _ = alice.RPC.SendPayment(sendReq) + + // We start the htlc interceptor with a simple implementation that saves + // all intercepted packets. These packets are held to simulate a + // pending payment. + packet := ht.ReceiveHtlcInterceptor(bobInterceptor) + + require.Len(ht, packet.IncomingHtlcWireCustomRecords, 1) + + val, ok := packet.IncomingHtlcWireCustomRecords[65537] + require.True(ht, ok, "expected custom record") + require.Equal(ht, []byte("test"), val) + + action := routerrpc.ResolveHoldForwardAction_RESUME_MODIFIED + newOutgoingAmountMsat := packet.OutgoingAmountMsat + 800 + + err := bobInterceptor.Send(&routerrpc.ForwardHtlcInterceptResponse{ + IncomingCircuitKey: packet.IncomingCircuitKey, + OutgoingAmountMsat: newOutgoingAmountMsat, + Action: action, + }) + require.NoError(ht, err, "failed to send request") + + // Cancel the context, which will disconnect Bob's interceptor. + cancelBobInterceptor() + + // Assert that the payment was successful. + var preimage lntypes.Preimage + copy(preimage[:], invoice.RPreimage) + ht.AssertPaymentStatus(alice, preimage, lnrpc.Payment_SUCCEEDED) + + // Finally, close channels. + ht.CloseChannel(alice, cpAB) + ht.CloseChannel(bob, cpBC) +} + // interceptorTestScenario is a helper struct to hold the test context and // provide the needed functionality. type interceptorTestScenario struct {