11package itest
22
33import (
4+ "bytes"
45 "fmt"
6+ "reflect"
57 "strings"
68 "time"
79
@@ -13,6 +15,7 @@ import (
1315 "github.com/lightningnetwork/lnd/lntest/node"
1416 "github.com/lightningnetwork/lnd/lntest/wait"
1517 "github.com/lightningnetwork/lnd/lntypes"
18+ "github.com/lightningnetwork/lnd/lnwire"
1619 "github.com/lightningnetwork/lnd/routing/route"
1720 "github.com/stretchr/testify/require"
1821 "google.golang.org/grpc/codes"
2427 customTestValue = []byte {1 , 3 , 5 }
2528
2629 actionResumeModify = routerrpc .ResolveHoldForwardAction_RESUME_MODIFIED
30+ actionResume = routerrpc .ResolveHoldForwardAction_RESUME
2731)
2832
2933type interceptorTestCase struct {
@@ -436,33 +440,309 @@ func testForwardInterceptorModifiedHtlc(ht *lntest.HarnessTest) {
436440 ht .CloseChannel (bob , cpBC )
437441}
438442
443+ // testForwardInterceptorWireRecords tests that the interceptor can read any
444+ // wire custom records provided by the sender of a payment as part of the
445+ // update_add_htlc message.
446+ func testForwardInterceptorWireRecords (ht * lntest.HarnessTest ) {
447+ // Initialize the test context with 3 connected nodes.
448+ ts := newInterceptorTestScenario (ht )
449+
450+ alice , bob , carol , dave := ts .alice , ts .bob , ts .carol , ts .dave
451+
452+ // Open and wait for channels.
453+ const chanAmt = btcutil .Amount (300000 )
454+ p := lntest.OpenChannelParams {Amt : chanAmt }
455+ reqs := []* lntest.OpenChannelRequest {
456+ {Local : alice , Remote : bob , Param : p },
457+ {Local : bob , Remote : carol , Param : p },
458+ {Local : carol , Remote : dave , Param : p },
459+ }
460+ resp := ht .OpenMultiChannelsAsync (reqs )
461+ cpAB , cpBC , cpCD := resp [0 ], resp [1 ], resp [2 ]
462+
463+ // Make sure Alice is aware of channel Bob=>Carol.
464+ ht .AssertTopologyChannelOpen (alice , cpBC )
465+
466+ // Connect an interceptor to Bob's node.
467+ bobInterceptor , cancelBobInterceptor := bob .RPC .HtlcInterceptor ()
468+ defer cancelBobInterceptor ()
469+
470+ // Also connect an interceptor on Carol's node to check whether we're
471+ // relaying the TLVs send in update_add_htlc over Alice -> Bob on the
472+ // Bob -> Carol link.
473+ carolInterceptor , cancelCarolInterceptor := carol .RPC .HtlcInterceptor ()
474+ defer cancelCarolInterceptor ()
475+
476+ req := & lnrpc.Invoice {ValueMsat : 1000 }
477+ addResponse := dave .RPC .AddInvoice (req )
478+ invoice := dave .RPC .LookupInvoice (addResponse .RHash )
479+
480+ sendReq := & routerrpc.SendPaymentRequest {
481+ PaymentRequest : invoice .PaymentRequest ,
482+ TimeoutSeconds : int32 (wait .PaymentTimeout .Seconds ()),
483+ FeeLimitMsat : noFeeLimitMsat ,
484+ FirstHopCustomRecords : map [uint64 ][]byte {
485+ 65537 : []byte ("test" ),
486+ },
487+ }
488+
489+ _ = alice .RPC .SendPayment (sendReq )
490+
491+ // We start the htlc interceptor with a simple implementation that saves
492+ // all intercepted packets. These packets are held to simulate a
493+ // pending payment.
494+ packet := ht .ReceiveHtlcInterceptor (bobInterceptor )
495+
496+ require .Len (ht , packet .InWireCustomRecords , 1 )
497+
498+ val , ok := packet .InWireCustomRecords [65537 ]
499+ require .True (ht , ok , "expected custom record" )
500+ require .Equal (ht , []byte ("test" ), val )
501+
502+ // TODO(guggero): Actually modify the amount once we have the invoice
503+ // interceptor and can accept a lower amount.
504+ newOutAmountMsat := packet .OutgoingAmountMsat
505+
506+ err := bobInterceptor .Send (& routerrpc.ForwardHtlcInterceptResponse {
507+ IncomingCircuitKey : packet .IncomingCircuitKey ,
508+ OutAmountMsat : newOutAmountMsat ,
509+ Action : actionResumeModify ,
510+ })
511+ require .NoError (ht , err , "failed to send request" )
512+
513+ // Assert that the Alice -> Bob custom records in update_add_htlc are
514+ // not propagated on the Bob -> Carol link.
515+ packet = ht .ReceiveHtlcInterceptor (carolInterceptor )
516+ require .Len (ht , packet .InWireCustomRecords , 0 )
517+
518+ // Just resume the payment on Carol.
519+ err = carolInterceptor .Send (& routerrpc.ForwardHtlcInterceptResponse {
520+ IncomingCircuitKey : packet .IncomingCircuitKey ,
521+ Action : actionResume ,
522+ })
523+ require .NoError (ht , err , "carol interceptor response" )
524+
525+ // Assert that the payment was successful.
526+ var preimage lntypes.Preimage
527+ copy (preimage [:], invoice .RPreimage )
528+ ht .AssertPaymentStatus (
529+ alice , preimage , lnrpc .Payment_SUCCEEDED ,
530+ func (p * lnrpc.Payment ) error {
531+ recordsEqual := reflect .DeepEqual (
532+ p .FirstHopCustomRecords ,
533+ sendReq .FirstHopCustomRecords ,
534+ )
535+ if ! recordsEqual {
536+ return fmt .Errorf ("expected custom records to " +
537+ "be equal, got %v expected %v" ,
538+ p .FirstHopCustomRecords ,
539+ sendReq .FirstHopCustomRecords )
540+ }
541+
542+ return nil
543+ },
544+ )
545+
546+ // Finally, close channels.
547+ ht .CloseChannel (alice , cpAB )
548+ ht .CloseChannel (bob , cpBC )
549+ ht .CloseChannel (carol , cpCD )
550+ }
551+
552+ // testForwardInterceptorRestart tests that the interceptor can read any wire
553+ // custom records provided by the sender of a payment as part of the
554+ // update_add_htlc message and that those records are persisted correctly and
555+ // re-sent on node restart.
556+ func testForwardInterceptorRestart (ht * lntest.HarnessTest ) {
557+ // Initialize the test context with 3 connected nodes.
558+ ts := newInterceptorTestScenario (ht )
559+
560+ alice , bob , carol , dave := ts .alice , ts .bob , ts .carol , ts .dave
561+
562+ // Open and wait for channels.
563+ const chanAmt = btcutil .Amount (300000 )
564+ p := lntest.OpenChannelParams {Amt : chanAmt }
565+ reqs := []* lntest.OpenChannelRequest {
566+ {Local : alice , Remote : bob , Param : p },
567+ {Local : bob , Remote : carol , Param : p },
568+ {Local : carol , Remote : dave , Param : p },
569+ }
570+ resp := ht .OpenMultiChannelsAsync (reqs )
571+ cpAB , cpBC , cpCD := resp [0 ], resp [1 ], resp [2 ]
572+
573+ // Make sure Alice is aware of channels Bob=>Carol and Carol=>Dave.
574+ ht .AssertTopologyChannelOpen (alice , cpBC )
575+ ht .AssertTopologyChannelOpen (alice , cpCD )
576+
577+ // Connect an interceptor to Bob's node.
578+ bobInterceptor , cancelBobInterceptor := bob .RPC .HtlcInterceptor ()
579+
580+ // Also connect an interceptor on Carol's node to check whether we're
581+ // relaying the TLVs send in update_add_htlc over Alice -> Bob on the
582+ // Bob -> Carol link.
583+ carolInterceptor , cancelCarolInterceptor := carol .RPC .HtlcInterceptor ()
584+ defer cancelCarolInterceptor ()
585+
586+ req := & lnrpc.Invoice {ValueMsat : 50_000_000 }
587+ addResponse := dave .RPC .AddInvoice (req )
588+ invoice := dave .RPC .LookupInvoice (addResponse .RHash )
589+
590+ customRecords := map [uint64 ][]byte {
591+ 65537 : []byte ("test" ),
592+ }
593+
594+ sendReq := & routerrpc.SendPaymentRequest {
595+ PaymentRequest : invoice .PaymentRequest ,
596+ TimeoutSeconds : int32 (wait .PaymentTimeout .Seconds ()),
597+ FeeLimitMsat : noFeeLimitMsat ,
598+ FirstHopCustomRecords : customRecords ,
599+ }
600+
601+ _ = alice .RPC .SendPayment (sendReq )
602+
603+ // We start the htlc interceptor with a simple implementation that saves
604+ // all intercepted packets. These packets are held to simulate a
605+ // pending payment.
606+ packet := ht .ReceiveHtlcInterceptor (bobInterceptor )
607+
608+ require .Len (ht , packet .InWireCustomRecords , 1 )
609+ require .Equal (ht , customRecords , packet .InWireCustomRecords )
610+
611+ // We accept the payment at Bob and resume it, so it gets to Carol.
612+ // This means the HTLC should now be fully locked in on Alice's side and
613+ // any restart of the node should cause the payment to be resumed and
614+ // the data to be persisted across restarts.
615+ err := bobInterceptor .Send (& routerrpc.ForwardHtlcInterceptResponse {
616+ IncomingCircuitKey : packet .IncomingCircuitKey ,
617+ Action : actionResume ,
618+ })
619+ require .NoError (ht , err , "failed to send request" )
620+
621+ // We don't resume the payment on Carol, so it should be held there.
622+
623+ // The payment should now be in flight.
624+ var preimage lntypes.Preimage
625+ copy (preimage [:], invoice .RPreimage )
626+ ht .AssertPaymentStatus (alice , preimage , lnrpc .Payment_IN_FLIGHT )
627+
628+ // We don't resume the payment on Carol, so it should be held there.
629+ // We now restart first Bob, then Alice, so we can make sure we've
630+ // started the interceptor again on Bob before Alice resumes the
631+ // payment.
632+ cancelBobInterceptor ()
633+ restartBob := ht .SuspendNode (bob )
634+ restartAlice := ht .SuspendNode (alice )
635+
636+ require .NoError (ht , restartBob (), "failed to restart bob" )
637+ bobInterceptor , cancelBobInterceptor = bob .RPC .HtlcInterceptor ()
638+ defer cancelBobInterceptor ()
639+
640+ require .NoError (ht , restartAlice (), "failed to restart alice" )
641+
642+ // We should get another notification about the held HTLC.
643+ packet = ht .ReceiveHtlcInterceptor (bobInterceptor )
644+
645+ require .Len (ht , packet .InWireCustomRecords , 1 )
646+ require .Equal (ht , customRecords , packet .InWireCustomRecords )
647+
648+ err = carolInterceptor .Send (& routerrpc.ForwardHtlcInterceptResponse {
649+ IncomingCircuitKey : packet .IncomingCircuitKey ,
650+ Action : actionResume ,
651+ })
652+ require .NoError (ht , err , "failed to send request" )
653+
654+ // And now we forward the payment at Carol.
655+ packet = ht .ReceiveHtlcInterceptor (carolInterceptor )
656+ require .Len (ht , packet .InWireCustomRecords , 0 )
657+ err = carolInterceptor .Send (& routerrpc.ForwardHtlcInterceptResponse {
658+ IncomingCircuitKey : packet .IncomingCircuitKey ,
659+ Action : actionResume ,
660+ })
661+ require .NoError (ht , err , "failed to send request" )
662+
663+ // Assert that the payment was successful.
664+ ht .AssertPaymentStatus (
665+ alice , preimage , lnrpc .Payment_SUCCEEDED ,
666+ func (p * lnrpc.Payment ) error {
667+ recordsEqual := reflect .DeepEqual (
668+ p .FirstHopCustomRecords ,
669+ sendReq .FirstHopCustomRecords ,
670+ )
671+ if ! recordsEqual {
672+ return fmt .Errorf ("expected custom records to " +
673+ "be equal, got %v expected %v" ,
674+ p .FirstHopCustomRecords ,
675+ sendReq .FirstHopCustomRecords )
676+ }
677+
678+ if len (p .Htlcs ) != 1 {
679+ return fmt .Errorf ("expected 1 htlc, got %d" ,
680+ len (p .Htlcs ))
681+ }
682+
683+ htlc := p .Htlcs [0 ]
684+ rt := htlc .Route
685+ if rt .FirstHopAmountMsat != rt .TotalAmtMsat {
686+ return fmt .Errorf ("expected first hop amount " +
687+ "to be %d, got %d" , rt .TotalAmtMsat ,
688+ rt .FirstHopAmountMsat )
689+ }
690+
691+ cr := lnwire .CustomRecords (p .FirstHopCustomRecords )
692+ recordData , err := cr .Serialize ()
693+ if err != nil {
694+ return err
695+ }
696+
697+ if ! bytes .Equal (rt .CustomChannelData , recordData ) {
698+ return fmt .Errorf ("expected custom records to " +
699+ "be equal, got %x expected %x" ,
700+ rt .CustomChannelData , recordData )
701+ }
702+
703+ return nil
704+ },
705+ )
706+
707+ // Finally, close channels.
708+ ht .CloseChannel (alice , cpAB )
709+ ht .CloseChannel (bob , cpBC )
710+ ht .CloseChannel (carol , cpCD )
711+ }
712+
439713// interceptorTestScenario is a helper struct to hold the test context and
440714// provide the needed functionality.
441715type interceptorTestScenario struct {
442- ht * lntest.HarnessTest
443- alice , bob , carol * node.HarnessNode
716+ ht * lntest.HarnessTest
717+ alice , bob , carol , dave * node.HarnessNode
444718}
445719
446720// newInterceptorTestScenario initializes a new test scenario with three nodes
447721// and connects them to have the following topology,
448722//
449- // Alice --> Bob --> Carol
723+ // Alice --> Bob --> Carol --> Dave
450724//
451725// Among them, Alice and Bob are standby nodes and Carol is a new node.
452726func newInterceptorTestScenario (
453727 ht * lntest.HarnessTest ) * interceptorTestScenario {
454728
455729 alice , bob := ht .Alice , ht .Bob
456730 carol := ht .NewNode ("carol" , nil )
731+ dave := ht .NewNode ("dave" , nil )
457732
458733 ht .EnsureConnected (alice , bob )
459734 ht .EnsureConnected (bob , carol )
735+ ht .EnsureConnected (carol , dave )
736+
737+ // So that carol can open channels.
738+ ht .FundCoins (btcutil .SatoshiPerBitcoin , carol )
460739
461740 return & interceptorTestScenario {
462741 ht : ht ,
463742 alice : alice ,
464743 bob : bob ,
465744 carol : carol ,
745+ dave : dave ,
466746 }
467747}
468748
0 commit comments