Skip to content

Commit b0f5ca8

Browse files
committed
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.
1 parent 277cf41 commit b0f5ca8

File tree

1 file changed

+125
-8
lines changed

1 file changed

+125
-8
lines changed

itest/lnd_route_blinding.go

+125-8
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package itest
22

33
import (
4+
"bytes"
45
"context"
56
"crypto/sha256"
67
"encoding/hex"
8+
"time"
79

810
"github.com/btcsuite/btcd/btcec/v2"
911
"github.com/btcsuite/btcd/btcutil"
@@ -323,6 +325,10 @@ type blindedForwardTest struct {
323325
dave *node.HarnessNode
324326
channels []*lnrpc.ChannelPoint
325327

328+
carolInterceptor routerrpc.Router_HtlcInterceptorClient
329+
330+
preimage [33]byte
331+
326332
// cancel will cancel the test's top level context.
327333
cancel func()
328334
}
@@ -333,16 +339,28 @@ func newBlindedForwardTest(ht *lntest.HarnessTest) (context.Context,
333339
ctx, cancel := context.WithCancel(context.Background())
334340

335341
return ctx, &blindedForwardTest{
336-
ht: ht,
337-
cancel: cancel,
342+
ht: ht,
343+
cancel: cancel,
344+
preimage: [33]byte{1, 2, 3},
338345
}
339346
}
340347

341348
// setup spins up additional nodes needed for our test and creates a four hop
342349
// network for testing blinded forwarding and returns a blinded route from
343-
// Bob -> Carol -> Dave, with Bob acting as the introduction point.
344-
func (b *blindedForwardTest) setup() *routing.BlindedPayment {
345-
b.carol = b.ht.NewNode("Carol", nil)
350+
// Bob -> Carol -> Dave, with Bob acting as the introduction point and an
351+
// interceptor on Carol's node to manage HTLCs (as Dave does not yet support
352+
// receiving).
353+
func (b *blindedForwardTest) setup(
354+
ctx context.Context) *routing.BlindedPayment {
355+
356+
b.carol = b.ht.NewNode("Carol", []string{
357+
"requireinterceptor",
358+
})
359+
360+
var err error
361+
b.carolInterceptor, err = b.carol.RPC.Router.HtlcInterceptor(ctx)
362+
require.NoError(b.ht, err, "interceptor")
363+
346364
b.dave = b.ht.NewNode("Dave", nil)
347365

348366
b.channels = setupFourHopNetwork(b.ht, b.carol, b.dave)
@@ -431,6 +449,77 @@ func (b *blindedForwardTest) createRouteToBlinded(paymentAmt int64,
431449
return resp.Routes[0]
432450
}
433451

452+
// sendBlindedPayment dispatches a payment to the route provided. The streaming
453+
// client for the send is returned with a cancel function that can be used to
454+
// terminate the stream.
455+
func (b *blindedForwardTest) sendBlindedPayment(ctx context.Context,
456+
route *lnrpc.Route) (lnrpc.Lightning_SendToRouteClient, func()) {
457+
458+
hash := sha256.Sum256(b.preimage[:])
459+
460+
ctxt, cancel := context.WithCancel(ctx)
461+
sendReq := &lnrpc.SendToRouteRequest{
462+
PaymentHash: hash[:],
463+
Route: route,
464+
}
465+
466+
sendClient, err := b.ht.Alice.RPC.LN.SendToRoute(ctxt)
467+
require.NoError(b.ht, err, "send to route client")
468+
469+
err = sendClient.SendMsg(sendReq)
470+
require.NoError(b.ht, err, "send to route request")
471+
472+
return sendClient, cancel
473+
}
474+
475+
// interceptFinalHop launches a goroutine to intercept Carol's htlcs and
476+
// returns a closure that can be used to settle intercepted htlcs.
477+
func (b *blindedForwardTest) interceptFinalHop() func() {
478+
hash := sha256.Sum256(b.preimage[:])
479+
htlcReceived := make(chan *routerrpc.ForwardHtlcInterceptRequest)
480+
481+
// Launch a goroutine which will receive from the interceptor and pipe
482+
// it into our request channel.
483+
go func() {
484+
forward, err := b.carolInterceptor.Recv()
485+
if err != nil {
486+
b.ht.Fatalf("intercept receive failed: %v", err)
487+
}
488+
489+
if !bytes.Equal(forward.PaymentHash, hash[:]) {
490+
b.ht.Fatalf("unexpected payment hash: %v", hash)
491+
}
492+
493+
select {
494+
case htlcReceived <- forward:
495+
496+
case <-time.After(lntest.DefaultTimeout):
497+
b.ht.Fatal("timeout waiting to send intercepted htlc")
498+
}
499+
}()
500+
501+
// Create a closure that will wait for the intercept request and settle
502+
// it with our preimage.
503+
settleHTLC := func() {
504+
select {
505+
case forward := <-htlcReceived:
506+
action := routerrpc.ResolveHoldForwardAction_SETTLE
507+
resp := &routerrpc.ForwardHtlcInterceptResponse{
508+
IncomingCircuitKey: forward.IncomingCircuitKey,
509+
Action: action,
510+
Preimage: b.preimage[:],
511+
}
512+
513+
require.NoError(b.ht, b.carolInterceptor.Send(resp))
514+
515+
case <-time.After(lntest.DefaultTimeout):
516+
b.ht.Fatal("timeout waiting for htlc intercept")
517+
}
518+
}
519+
520+
return settleHTLC
521+
}
522+
434523
// setupFourHopNetwork creates a network with the following topology and
435524
// liquidity:
436525
// Alice (100k)----- Bob (100k) ----- Carol (100k) ----- Dave
@@ -615,9 +704,37 @@ func getForwardingEdge(ht *lntest.HarnessTest,
615704
// testForwardBlindedRoute tests lnd's ability to forward payments in a blinded
616705
// route.
617706
func testForwardBlindedRoute(ht *lntest.HarnessTest) {
618-
_, testCase := newBlindedForwardTest(ht)
707+
ctx, testCase := newBlindedForwardTest(ht)
619708
defer testCase.cleanup()
620709

621-
route := testCase.setup()
622-
testCase.createRouteToBlinded(100_000, route)
710+
route := testCase.setup(ctx)
711+
blindedRoute := testCase.createRouteToBlinded(100_000, route)
712+
713+
// Receiving via blinded routes is not yet supported, so Dave won't be
714+
// able to process the payment.
715+
//
716+
// We have an interceptor at our disposal that will catch htlcs as they
717+
// are forwarded (ie, it won't intercept a HTLC that dave is receiving,
718+
// since no forwarding occurs). We initiate this interceptor with
719+
// Carol, so that we can catch it and settle on the outgoing link to
720+
// Dave. Once we hit the outgoing link, we know that we successfully
721+
// parsed the htlc, so this is an acceptable compromise.
722+
// Assert that our interceptor has exited without an error.
723+
settleHTLC := testCase.interceptFinalHop()
724+
725+
// Once our interceptor is set up, we can send the blinded payment.
726+
testCase.sendBlindedPayment(ctx, blindedRoute)
727+
728+
// Wait for the HTLC to be active on Alice's channel.
729+
hash := sha256.Sum256(testCase.preimage[:])
730+
ht.AssertOutgoingHTLCActive(ht.Alice, testCase.channels[0], hash[:])
731+
ht.AssertOutgoingHTLCActive(ht.Bob, testCase.channels[1], hash[:])
732+
733+
// Intercept and settle the HTLC.
734+
settleHTLC()
735+
736+
// Assert that the HTLC has settled before test cleanup runs so that
737+
// we can cooperatively close all channels.
738+
ht.AssertHLTCNotActive(ht.Bob, testCase.channels[1], hash[:])
739+
ht.AssertHLTCNotActive(ht.Alice, testCase.channels[0], hash[:])
623740
}

0 commit comments

Comments
 (0)