lntest: dispatch and intercept payment to blinded route

We don't support receiving blinded in this PR - just intercept and
settle instead. The HTLC's arrival on the interceptor indicates that
it was successfully forwarded on a blinded hop.
This commit is contained in:
Carla Kirk-Cohen 2022-12-14 11:38:26 -05:00
parent 69e1162dd1
commit 0d9a184df8
No known key found for this signature in database
GPG Key ID: 4CA7FE54A6213C91

View File

@ -1,9 +1,11 @@
package itest
import (
"bytes"
"context"
"crypto/sha256"
"encoding/hex"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
@ -13,6 +15,7 @@ import (
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lntest"
"github.com/lightningnetwork/lnd/lntest/node"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/record"
"github.com/lightningnetwork/lnd/routing"
@ -323,6 +326,10 @@ type blindedForwardTest struct {
dave *node.HarnessNode
channels []*lnrpc.ChannelPoint
carolInterceptor routerrpc.Router_HtlcInterceptorClient
preimage [32]byte
// cancel will cancel the test's top level context.
cancel func()
}
@ -333,16 +340,28 @@ func newBlindedForwardTest(ht *lntest.HarnessTest) (context.Context,
ctx, cancel := context.WithCancel(context.Background())
return ctx, &blindedForwardTest{
ht: ht,
cancel: cancel,
ht: ht,
cancel: cancel,
preimage: [32]byte{1, 2, 3},
}
}
// setup spins up additional nodes needed for our test and creates a four hop
// network for testing blinded forwarding and returns a blinded route from
// Bob -> Carol -> Dave, with Bob acting as the introduction point.
func (b *blindedForwardTest) setup() *routing.BlindedPayment {
b.carol = b.ht.NewNode("Carol", nil)
// Bob -> Carol -> Dave, with Bob acting as the introduction point and an
// interceptor on Carol's node to manage HTLCs (as Dave does not yet support
// receiving).
func (b *blindedForwardTest) setup(
ctx context.Context) *routing.BlindedPayment {
b.carol = b.ht.NewNode("Carol", []string{
"requireinterceptor",
})
var err error
b.carolInterceptor, err = b.carol.RPC.Router.HtlcInterceptor(ctx)
require.NoError(b.ht, err, "interceptor")
b.dave = b.ht.NewNode("Dave", nil)
b.channels = setupFourHopNetwork(b.ht, b.carol, b.dave)
@ -429,6 +448,82 @@ func (b *blindedForwardTest) createRouteToBlinded(paymentAmt int64,
return resp.Routes[0]
}
// sendBlindedPayment dispatches a payment to the route provided.
func (b *blindedForwardTest) sendBlindedPayment(ctx context.Context,
route *lnrpc.Route) {
hash := sha256.Sum256(b.preimage[:])
sendReq := &routerrpc.SendToRouteRequest{
PaymentHash: hash[:],
Route: route,
}
// Dispatch in a goroutine because this call is blocking - we assume
// that we'll have assertions that this payment is sent by the caller.
go func() {
b.ht.Alice.RPC.SendToRouteV2(sendReq)
}()
}
// interceptFinalHop launches a goroutine to intercept Carol's htlcs and
// returns a closure that can be used to resolve intercepted htlcs.
//
//nolint:lll
func (b *blindedForwardTest) interceptFinalHop() func(routerrpc.ResolveHoldForwardAction) {
hash := sha256.Sum256(b.preimage[:])
htlcReceived := make(chan *routerrpc.ForwardHtlcInterceptRequest)
// Launch a goroutine which will receive from the interceptor and pipe
// it into our request channel.
go func() {
forward, err := b.carolInterceptor.Recv()
if err != nil {
b.ht.Fatalf("intercept receive failed: %v", err)
}
if !bytes.Equal(forward.PaymentHash, hash[:]) {
b.ht.Fatalf("unexpected payment hash: %v", hash)
}
select {
case htlcReceived <- forward:
case <-time.After(lntest.DefaultTimeout):
b.ht.Fatal("timeout waiting to send intercepted htlc")
}
}()
// Create a closure that will wait for the intercept request and
// resolve the HTLC with the appropriate action.
resolve := func(action routerrpc.ResolveHoldForwardAction) {
select {
case forward := <-htlcReceived:
resp := &routerrpc.ForwardHtlcInterceptResponse{
IncomingCircuitKey: forward.IncomingCircuitKey,
}
switch action {
case routerrpc.ResolveHoldForwardAction_FAIL:
resp.Action = routerrpc.ResolveHoldForwardAction_FAIL
case routerrpc.ResolveHoldForwardAction_SETTLE:
resp.Action = routerrpc.ResolveHoldForwardAction_SETTLE
resp.Preimage = b.preimage[:]
case routerrpc.ResolveHoldForwardAction_RESUME:
resp.Action = routerrpc.ResolveHoldForwardAction_RESUME
}
require.NoError(b.ht, b.carolInterceptor.Send(resp))
case <-time.After(lntest.DefaultTimeout):
b.ht.Fatal("timeout waiting for htlc intercept")
}
}
return resolve
}
// setupFourHopNetwork creates a network with the following topology and
// liquidity:
// Alice (100k)----- Bob (100k) ----- Carol (100k) ----- Dave
@ -654,9 +749,42 @@ func getForwardingEdge(ht *lntest.HarnessTest,
// testForwardBlindedRoute tests lnd's ability to forward payments in a blinded
// route.
func testForwardBlindedRoute(ht *lntest.HarnessTest) {
_, testCase := newBlindedForwardTest(ht)
ctx, testCase := newBlindedForwardTest(ht)
defer testCase.cleanup()
route := testCase.setup()
testCase.createRouteToBlinded(100_000, route)
route := testCase.setup(ctx)
blindedRoute := testCase.createRouteToBlinded(10_000_000, route)
// Receiving via blinded routes is not yet supported, so Dave won't be
// able to process the payment.
//
// We have an interceptor at our disposal that will catch htlcs as they
// are forwarded (ie, it won't intercept a HTLC that dave is receiving,
// since no forwarding occurs). We initiate this interceptor with
// Carol, so that we can catch it and settle on the outgoing link to
// Dave. Once we hit the outgoing link, we know that we successfully
// parsed the htlc, so this is an acceptable compromise.
// Assert that our interceptor has exited without an error.
resolveHTLC := testCase.interceptFinalHop()
// Once our interceptor is set up, we can send the blinded payment.
testCase.sendBlindedPayment(ctx, blindedRoute)
// Wait for the HTLC to be active on Alice's channel.
hash := sha256.Sum256(testCase.preimage[:])
ht.AssertOutgoingHTLCActive(ht.Alice, testCase.channels[0], hash[:])
ht.AssertOutgoingHTLCActive(ht.Bob, testCase.channels[1], hash[:])
// Intercept and settle the HTLC.
resolveHTLC(routerrpc.ResolveHoldForwardAction_SETTLE)
// Wait for the HTLC to reflect as settled for Alice.
preimage, err := lntypes.MakePreimage(testCase.preimage[:])
require.NoError(ht, err)
ht.AssertPaymentStatus(ht.Alice, preimage, lnrpc.Payment_SUCCEEDED)
// Assert that the HTLC has settled before test cleanup runs so that
// we can cooperatively close all channels.
ht.AssertHLTCNotActive(ht.Bob, testCase.channels[1], hash[:])
ht.AssertHLTCNotActive(ht.Alice, testCase.channels[0], hash[:])
}