mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-07-20 18:12:30 +02:00
itest: payment failure reason canceled
This commit is contained in:
@ -193,6 +193,10 @@ var allTestCases = []*lntest.TestCase{
|
|||||||
Name: "immediate payment after channel opened",
|
Name: "immediate payment after channel opened",
|
||||||
TestFunc: testPaymentFollowingChannelOpen,
|
TestFunc: testPaymentFollowingChannelOpen,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "payment failure reason canceled",
|
||||||
|
TestFunc: testPaymentFailureReasonCanceled,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "invoice update subscription",
|
Name: "invoice update subscription",
|
||||||
TestFunc: testInvoiceSubscriptions,
|
TestFunc: testInvoiceSubscriptions,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package itest
|
package itest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -13,7 +14,9 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
||||||
"github.com/lightningnetwork/lnd/lntest"
|
"github.com/lightningnetwork/lnd/lntest"
|
||||||
"github.com/lightningnetwork/lnd/lntest/node"
|
"github.com/lightningnetwork/lnd/lntest/node"
|
||||||
|
"github.com/lightningnetwork/lnd/lntest/rpc"
|
||||||
"github.com/lightningnetwork/lnd/lntest/wait"
|
"github.com/lightningnetwork/lnd/lntest/wait"
|
||||||
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -767,3 +770,133 @@ func assertChannelState(ht *lntest.HarnessTest, hn *node.HarnessNode,
|
|||||||
}, lntest.DefaultTimeout)
|
}, lntest.DefaultTimeout)
|
||||||
require.NoError(ht, err, "timeout while chekcing for balance")
|
require.NoError(ht, err, "timeout while chekcing for balance")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// testPaymentFailureReasonCanceled ensures that the cancellation of a
|
||||||
|
// SendPayment request results in the payment failure reason
|
||||||
|
// FAILURE_REASON_CANCELED. This failure reason indicates that the context was
|
||||||
|
// cancelled manually by the user. It does not interrupt the current payment
|
||||||
|
// attempt, but will prevent any further payment attempts. The test steps are:
|
||||||
|
// 1.) Alice pays Carol's invoice through Bob.
|
||||||
|
// 2.) Bob intercepts the htlc, keeping the payment pending.
|
||||||
|
// 3.) Alice cancels the payment context, the payment is still pending.
|
||||||
|
// 4.) Bob fails OR resumes the intercepted HTLC.
|
||||||
|
// 5.) Alice observes a failed OR succeeded payment with failure reason
|
||||||
|
// FAILURE_REASON_CANCELED which suppresses further payment attempts.
|
||||||
|
func testPaymentFailureReasonCanceled(ht *lntest.HarnessTest) {
|
||||||
|
// Initialize the test context with 3 connected nodes.
|
||||||
|
ts := newInterceptorTestScenario(ht)
|
||||||
|
|
||||||
|
alice, bob, carol := ts.alice, ts.bob, ts.carol
|
||||||
|
|
||||||
|
// Open and wait for channels.
|
||||||
|
const chanAmt = btcutil.Amount(300000)
|
||||||
|
p := lntest.OpenChannelParams{Amt: chanAmt}
|
||||||
|
reqs := []*lntest.OpenChannelRequest{
|
||||||
|
{Local: alice, Remote: bob, Param: p},
|
||||||
|
{Local: bob, Remote: carol, Param: p},
|
||||||
|
}
|
||||||
|
resp := ht.OpenMultiChannelsAsync(reqs)
|
||||||
|
cpAB, cpBC := resp[0], resp[1]
|
||||||
|
|
||||||
|
// Make sure Alice is aware of channel Bob=>Carol.
|
||||||
|
ht.AssertTopologyChannelOpen(alice, cpBC)
|
||||||
|
|
||||||
|
// First we check that the payment is successful when bob resumes the
|
||||||
|
// htlc even though the payment context was canceled before invoice
|
||||||
|
// settlement.
|
||||||
|
sendPaymentInterceptAndCancel(
|
||||||
|
ht, ts, cpAB, routerrpc.ResolveHoldForwardAction_RESUME,
|
||||||
|
lnrpc.Payment_SUCCEEDED,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Next we check that the context cancellation results in the expected
|
||||||
|
// failure reason while the htlc is being held and failed after
|
||||||
|
// cancellation.
|
||||||
|
// Note that we'd have to reset Alice's mission control if we tested the
|
||||||
|
// htlc fail case before the htlc resume case.
|
||||||
|
sendPaymentInterceptAndCancel(
|
||||||
|
ht, ts, cpAB, routerrpc.ResolveHoldForwardAction_FAIL,
|
||||||
|
lnrpc.Payment_FAILED,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Finally, close channels.
|
||||||
|
ht.CloseChannel(alice, cpAB)
|
||||||
|
ht.CloseChannel(bob, cpBC)
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendPaymentInterceptAndCancel(ht *lntest.HarnessTest,
|
||||||
|
ts *interceptorTestScenario, cpAB *lnrpc.ChannelPoint,
|
||||||
|
interceptorAction routerrpc.ResolveHoldForwardAction,
|
||||||
|
expectedPaymentStatus lnrpc.Payment_PaymentStatus) {
|
||||||
|
|
||||||
|
// Prepare the test cases.
|
||||||
|
alice, bob, carol := ts.alice, ts.bob, ts.carol
|
||||||
|
|
||||||
|
// Connect the interceptor.
|
||||||
|
interceptor, cancelInterceptor := bob.RPC.HtlcInterceptor()
|
||||||
|
|
||||||
|
// Prepare the test cases.
|
||||||
|
addResponse := carol.RPC.AddInvoice(&lnrpc.Invoice{
|
||||||
|
ValueMsat: 1000,
|
||||||
|
})
|
||||||
|
invoice := carol.RPC.LookupInvoice(addResponse.RHash)
|
||||||
|
|
||||||
|
// We initiate a payment from Alice and define the payment context
|
||||||
|
// cancellable.
|
||||||
|
ctx, cancelPaymentContext := context.WithCancel(context.Background())
|
||||||
|
var paymentStream rpc.PaymentClient
|
||||||
|
go func() {
|
||||||
|
req := &routerrpc.SendPaymentRequest{
|
||||||
|
PaymentRequest: invoice.PaymentRequest,
|
||||||
|
TimeoutSeconds: 60,
|
||||||
|
FeeLimitSat: 100000,
|
||||||
|
Cancelable: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
paymentStream = alice.RPC.SendPaymentWithContext(ctx, req)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// We start the htlc interceptor with a simple implementation that
|
||||||
|
// saves all intercepted packets. These packets are held to simulate a
|
||||||
|
// pending payment.
|
||||||
|
packet := ht.ReceiveHtlcInterceptor(interceptor)
|
||||||
|
|
||||||
|
// Here we should wait for the channel to contain a pending htlc, and
|
||||||
|
// also be shown as being active.
|
||||||
|
ht.AssertIncomingHTLCActive(bob, cpAB, invoice.RHash)
|
||||||
|
|
||||||
|
// Ensure that Alice's payment is in-flight because Bob is holding the
|
||||||
|
// htlc.
|
||||||
|
ht.AssertPaymentStatusFromStream(paymentStream, lnrpc.Payment_IN_FLIGHT)
|
||||||
|
|
||||||
|
// Cancel the payment context. This should end the payment stream
|
||||||
|
// context, but the payment should still be in state in-flight without a
|
||||||
|
// failure reason.
|
||||||
|
cancelPaymentContext()
|
||||||
|
|
||||||
|
var preimage lntypes.Preimage
|
||||||
|
copy(preimage[:], invoice.RPreimage)
|
||||||
|
payment := ht.AssertPaymentStatus(
|
||||||
|
alice, preimage, lnrpc.Payment_IN_FLIGHT,
|
||||||
|
)
|
||||||
|
reasonNone := lnrpc.PaymentFailureReason_FAILURE_REASON_NONE
|
||||||
|
require.Equal(ht, reasonNone, payment.FailureReason)
|
||||||
|
|
||||||
|
// Bob sends the interceptor action to the intercepted htlc.
|
||||||
|
err := interceptor.Send(&routerrpc.ForwardHtlcInterceptResponse{
|
||||||
|
IncomingCircuitKey: packet.IncomingCircuitKey,
|
||||||
|
Action: interceptorAction,
|
||||||
|
})
|
||||||
|
require.NoError(ht, err, "failed to send request")
|
||||||
|
|
||||||
|
// Assert that the payment status is as expected.
|
||||||
|
ht.AssertPaymentStatus(alice, preimage, expectedPaymentStatus)
|
||||||
|
|
||||||
|
// Since the payment context was cancelled, no further payment attempts
|
||||||
|
// should've been made, and we observe FAILURE_REASON_CANCELED.
|
||||||
|
expectedReason := lnrpc.PaymentFailureReason_FAILURE_REASON_CANCELED
|
||||||
|
ht.AssertPaymentFailureReason(alice, preimage, expectedReason)
|
||||||
|
|
||||||
|
// Cancel the context, which will disconnect the above interceptor.
|
||||||
|
cancelInterceptor()
|
||||||
|
}
|
||||||
|
@ -1639,6 +1639,24 @@ func (h *HarnessTest) AssertPaymentStatus(hn *node.HarnessNode,
|
|||||||
return target
|
return target
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AssertPaymentFailureReason asserts that the given node lists a payment with
|
||||||
|
// the given preimage which has the expected failure reason.
|
||||||
|
func (h *HarnessTest) AssertPaymentFailureReason(hn *node.HarnessNode,
|
||||||
|
preimage lntypes.Preimage, reason lnrpc.PaymentFailureReason) {
|
||||||
|
|
||||||
|
payHash := preimage.Hash()
|
||||||
|
err := wait.NoError(func() error {
|
||||||
|
p := h.findPayment(hn, payHash.String())
|
||||||
|
if reason == p.FailureReason {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("payment: %v failure reason not match, "+
|
||||||
|
"want %s got %s", payHash, reason, p.Status)
|
||||||
|
}, DefaultTimeout)
|
||||||
|
require.NoError(h, err, "timeout checking payment failure reason")
|
||||||
|
}
|
||||||
|
|
||||||
// AssertActiveNodesSynced asserts all active nodes have synced to the chain.
|
// AssertActiveNodesSynced asserts all active nodes have synced to the chain.
|
||||||
func (h *HarnessTest) AssertActiveNodesSynced() {
|
func (h *HarnessTest) AssertActiveNodesSynced() {
|
||||||
for _, node := range h.manager.activeNodes {
|
for _, node := range h.manager.activeNodes {
|
||||||
|
@ -36,7 +36,7 @@ func (h *HarnessRPC) SendPayment(
|
|||||||
req *routerrpc.SendPaymentRequest) PaymentClient {
|
req *routerrpc.SendPaymentRequest) PaymentClient {
|
||||||
|
|
||||||
// SendPayment needs to have the context alive for the entire test case
|
// SendPayment needs to have the context alive for the entire test case
|
||||||
// as the router relies on the context to propagate HTLCs. Thus we use
|
// as the router relies on the context to propagate HTLCs. Thus, we use
|
||||||
// runCtx here instead of a timeout context.
|
// runCtx here instead of a timeout context.
|
||||||
stream, err := h.Router.SendPaymentV2(h.runCtx, req)
|
stream, err := h.Router.SendPaymentV2(h.runCtx, req)
|
||||||
h.NoError(err, "SendPaymentV2")
|
h.NoError(err, "SendPaymentV2")
|
||||||
@ -44,6 +44,21 @@ func (h *HarnessRPC) SendPayment(
|
|||||||
return stream
|
return stream
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SendPaymentWithContext sends a payment using the given node and payment
|
||||||
|
// request and does so with the passed in context.
|
||||||
|
func (h *HarnessRPC) SendPaymentWithContext(context context.Context,
|
||||||
|
req *routerrpc.SendPaymentRequest) PaymentClient {
|
||||||
|
|
||||||
|
require.NotNil(h.T, context, "context must not be nil")
|
||||||
|
|
||||||
|
// SendPayment needs to have the context alive for the entire test case
|
||||||
|
// as the router relies on the context to propagate HTLCs.
|
||||||
|
stream, err := h.Router.SendPaymentV2(context, req)
|
||||||
|
h.NoError(err, "SendPaymentV2")
|
||||||
|
|
||||||
|
return stream
|
||||||
|
}
|
||||||
|
|
||||||
type HtlcEventsClient routerrpc.Router_SubscribeHtlcEventsClient
|
type HtlcEventsClient routerrpc.Router_SubscribeHtlcEventsClient
|
||||||
|
|
||||||
// SubscribeHtlcEvents makes a subscription to the HTLC events and returns a
|
// SubscribeHtlcEvents makes a subscription to the HTLC events and returns a
|
||||||
|
Reference in New Issue
Block a user