11package itest
22
33import (
4+ "context"
45 "crypto/sha256"
56 "encoding/hex"
67 "fmt"
@@ -13,19 +14,21 @@ import (
1314 "github.com/lightningnetwork/lnd/lnrpc/routerrpc"
1415 "github.com/lightningnetwork/lnd/lntest"
1516 "github.com/lightningnetwork/lnd/lntest/node"
17+ "github.com/lightningnetwork/lnd/lntest/rpc"
1618 "github.com/lightningnetwork/lnd/lntest/wait"
19+ "github.com/lightningnetwork/lnd/lntypes"
1720 "github.com/stretchr/testify/require"
1821)
1922
20- // testSendDirectPayment creates a topology Alice->Bob and then tests that
21- // Alice can send a direct payment to Bob. This test modifies the fee estimator
22- // to return floor fee rate(1 sat/vb).
23+ // testSendDirectPayment creates a topology Alice->Bob and then tests that Alice
24+ // can send a direct payment to Bob. This test modifies the fee estimator to
25+ // return floor fee rate(1 sat/vb).
2326func testSendDirectPayment (ht * lntest.HarnessTest ) {
2427 // Grab Alice and Bob's nodes for convenience.
2528 alice , bob := ht .Alice , ht .Bob
2629
2730 // Create a list of commitment types we want to test.
28- commitTyes := []lnrpc.CommitmentType {
31+ commitmentTypes := []lnrpc.CommitmentType {
2932 lnrpc .CommitmentType_ANCHORS ,
3033 lnrpc .CommitmentType_SIMPLE_TAPROOT ,
3134 }
@@ -109,7 +112,7 @@ func testSendDirectPayment(ht *lntest.HarnessTest) {
109112 }
110113
111114 // Run the test cases.
112- for _ , ct := range commitTyes {
115+ for _ , ct := range commitmentTypes {
113116 ht .Run (ct .String (), func (t * testing.T ) {
114117 st := ht .Subtest (t )
115118
@@ -132,8 +135,9 @@ func testSendDirectPayment(ht *lntest.HarnessTest) {
132135 }
133136
134137 // Open private channel for taproot channels.
135- params .Private = ct ==
136- lnrpc .CommitmentType_SIMPLE_TAPROOT
138+ if ct == lnrpc .CommitmentType_SIMPLE_TAPROOT {
139+ params .Private = true
140+ }
137141
138142 testSendPayment (st , params )
139143 })
@@ -429,7 +433,7 @@ func runAsyncPayments(ht *lntest.HarnessTest, alice, bob *node.HarnessNode,
429433 // likely be lower, but we can't guarantee that any more HTLCs will
430434 // succeed due to the limited path diversity and inability of the router
431435 // to retry via another path.
432- numInvoices := int ( input .MaxHTLCNumber / 2 )
436+ numInvoices := input .MaxHTLCNumber / 2
433437
434438 bobAmt := int64 (numInvoices * paymentAmt )
435439 aliceAmt := info .LocalBalance - bobAmt
@@ -534,10 +538,10 @@ func testBidirectionalAsyncPayments(ht *lntest.HarnessTest) {
534538
535539 // We'll create a number of invoices equal the max number of HTLCs that
536540 // can be carried in one direction. The number on the commitment will
537- // likely be lower, but we can't guarantee that any more HTLCs will
538- // succeed due to the limited path diversity and inability of the router
539- // to retry via another path.
540- numInvoices := int ( input .MaxHTLCNumber / 2 )
541+ // likely be lower, but we can't guarantee that more HTLCs will succeed
542+ // due to the limited path diversity and inability of the router to
543+ // retry via another path.
544+ numInvoices := input .MaxHTLCNumber / 2
541545
542546 // Nodes should exchange the same amount of money and because of this
543547 // at the end balances should remain the same.
@@ -597,7 +601,7 @@ func testBidirectionalAsyncPayments(ht *lntest.HarnessTest) {
597601 assertChannelState (ht , alice , chanPoint , aliceAmt , bobAmt )
598602
599603 // Next query for Bob's and Alice's channel states, in order to confirm
600- // that all payment have been successful transmitted.
604+ // that all payment have been successfully transmitted.
601605 assertChannelState (ht , bob , chanPoint , bobAmt , aliceAmt )
602606
603607 // Finally, immediately close the channel. This function will also
@@ -662,7 +666,7 @@ func testInvoiceSubscriptions(ht *lntest.HarnessTest) {
662666
663667 // Now that the set of invoices has been added, we'll re-register for
664668 // streaming invoice notifications for Bob, this time specifying the
665- // add invoice of the last prior invoice.
669+ // add index of the last prior invoice.
666670 req = & lnrpc.InvoiceSubscription {AddIndex : lastAddIndex }
667671 bobInvoiceSubscription = bob .RPC .SubscribeInvoices (req )
668672
@@ -766,3 +770,133 @@ func assertChannelState(ht *lntest.HarnessTest, hn *node.HarnessNode,
766770 }, lntest .DefaultTimeout )
767771 require .NoError (ht , err , "timeout while chekcing for balance" )
768772}
773+
774+ // testPaymentFailureReasonCanceled ensures that the cancellation of a
775+ // SendPayment request results in the payment failure reason
776+ // FAILURE_REASON_CANCELED. This failure reason indicates that the context was
777+ // cancelled manually by the user. It does not interrupt the current payment
778+ // attempt, but will prevent any further payment attempts. The test steps are:
779+ // 1.) Alice pays Carol's invoice through Bob.
780+ // 2.) Bob intercepts the htlc, keeping the payment pending.
781+ // 3.) Alice cancels the payment context, the payment is still pending.
782+ // 4.) Bob fails OR resumes the intercepted HTLC.
783+ // 5.) Alice observes a failed OR succeeded payment with failure reason
784+ // FAILURE_REASON_CANCELED which suppresses further payment attempts.
785+ func testPaymentFailureReasonCanceled (ht * lntest.HarnessTest ) {
786+ // Initialize the test context with 3 connected nodes.
787+ ts := newInterceptorTestScenario (ht )
788+
789+ alice , bob , carol := ts .alice , ts .bob , ts .carol
790+
791+ // Open and wait for channels.
792+ const chanAmt = btcutil .Amount (300000 )
793+ p := lntest.OpenChannelParams {Amt : chanAmt }
794+ reqs := []* lntest.OpenChannelRequest {
795+ {Local : alice , Remote : bob , Param : p },
796+ {Local : bob , Remote : carol , Param : p },
797+ }
798+ resp := ht .OpenMultiChannelsAsync (reqs )
799+ cpAB , cpBC := resp [0 ], resp [1 ]
800+
801+ // Make sure Alice is aware of channel Bob=>Carol.
802+ ht .AssertTopologyChannelOpen (alice , cpBC )
803+
804+ // First we check that the payment is successful when bob resumes the
805+ // htlc even though the payment context was canceled before invoice
806+ // settlement.
807+ sendPaymentInterceptAndCancel (
808+ ht , ts , cpAB , routerrpc .ResolveHoldForwardAction_RESUME ,
809+ lnrpc .Payment_SUCCEEDED ,
810+ )
811+
812+ // Next we check that the context cancellation results in the expected
813+ // failure reason while the htlc is being held and failed after
814+ // cancellation.
815+ // Note that we'd have to reset Alice's mission control if we tested the
816+ // htlc fail case before the htlc resume case.
817+ sendPaymentInterceptAndCancel (
818+ ht , ts , cpAB , routerrpc .ResolveHoldForwardAction_FAIL ,
819+ lnrpc .Payment_FAILED ,
820+ )
821+
822+ // Finally, close channels.
823+ ht .CloseChannel (alice , cpAB )
824+ ht .CloseChannel (bob , cpBC )
825+ }
826+
827+ func sendPaymentInterceptAndCancel (ht * lntest.HarnessTest ,
828+ ts * interceptorTestScenario , cpAB * lnrpc.ChannelPoint ,
829+ interceptorAction routerrpc.ResolveHoldForwardAction ,
830+ expectedPaymentStatus lnrpc.Payment_PaymentStatus ) {
831+
832+ // Prepare the test cases.
833+ alice , bob , carol := ts .alice , ts .bob , ts .carol
834+
835+ // Connect the interceptor.
836+ interceptor , cancelInterceptor := bob .RPC .HtlcInterceptor ()
837+
838+ // Prepare the test cases.
839+ addResponse := carol .RPC .AddInvoice (& lnrpc.Invoice {
840+ ValueMsat : 1000 ,
841+ })
842+ invoice := carol .RPC .LookupInvoice (addResponse .RHash )
843+
844+ // We initiate a payment from Alice and define the payment context
845+ // cancellable.
846+ ctx , cancelPaymentContext := context .WithCancel (context .Background ())
847+ var paymentStream rpc.PaymentClient
848+ go func () {
849+ req := & routerrpc.SendPaymentRequest {
850+ PaymentRequest : invoice .PaymentRequest ,
851+ TimeoutSeconds : 60 ,
852+ FeeLimitSat : 100000 ,
853+ Cancelable : true ,
854+ }
855+
856+ paymentStream = alice .RPC .SendPaymentWithContext (ctx , req )
857+ }()
858+
859+ // We start the htlc interceptor with a simple implementation that
860+ // saves all intercepted packets. These packets are held to simulate a
861+ // pending payment.
862+ packet := ht .ReceiveHtlcInterceptor (interceptor )
863+
864+ // Here we should wait for the channel to contain a pending htlc, and
865+ // also be shown as being active.
866+ ht .AssertIncomingHTLCActive (bob , cpAB , invoice .RHash )
867+
868+ // Ensure that Alice's payment is in-flight because Bob is holding the
869+ // htlc.
870+ ht .AssertPaymentStatusFromStream (paymentStream , lnrpc .Payment_IN_FLIGHT )
871+
872+ // Cancel the payment context. This should end the payment stream
873+ // context, but the payment should still be in state in-flight without a
874+ // failure reason.
875+ cancelPaymentContext ()
876+
877+ var preimage lntypes.Preimage
878+ copy (preimage [:], invoice .RPreimage )
879+ payment := ht .AssertPaymentStatus (
880+ alice , preimage , lnrpc .Payment_IN_FLIGHT ,
881+ )
882+ reasonNone := lnrpc .PaymentFailureReason_FAILURE_REASON_NONE
883+ require .Equal (ht , reasonNone , payment .FailureReason )
884+
885+ // Bob sends the interceptor action to the intercepted htlc.
886+ err := interceptor .Send (& routerrpc.ForwardHtlcInterceptResponse {
887+ IncomingCircuitKey : packet .IncomingCircuitKey ,
888+ Action : interceptorAction ,
889+ })
890+ require .NoError (ht , err , "failed to send request" )
891+
892+ // Assert that the payment status is as expected.
893+ ht .AssertPaymentStatus (alice , preimage , expectedPaymentStatus )
894+
895+ // Since the payment context was cancelled, no further payment attempts
896+ // should've been made, and we observe FAILURE_REASON_CANCELED.
897+ expectedReason := lnrpc .PaymentFailureReason_FAILURE_REASON_CANCELED
898+ ht .AssertPaymentFailureReason (alice , preimage , expectedReason )
899+
900+ // Cancel the context, which will disconnect the above interceptor.
901+ cancelInterceptor ()
902+ }
0 commit comments