11package itest
22
33import (
4+ "context"
45 "crypto/sha256"
56 "encoding/hex"
67 "fmt"
@@ -13,7 +14,9 @@ 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
@@ -767,3 +770,133 @@ func assertChannelState(ht *lntest.HarnessTest, hn *node.HarnessNode,
767770 }, lntest .DefaultTimeout )
768771 require .NoError (ht , err , "timeout while chekcing for balance" )
769772}
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