routing+routerrpc: cancelable context in SendPaymentV2
In this commit we set up the payment loop context according to user-provided parameters. The `cancelable` parameter indicates whether the user is able to interrupt the payment loop by cancelling the server stream context. We'll additionally wrap the context in a deadline if the user provided a payment timeout. We remove the timeout channel of the payment_lifecycle.go and in favor of the deadline context.
This commit is contained in:
4 files changed
+163
-69
No files matched your search
@@ -1,6 +1,7 @@
|
||||
package routing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -88,7 +89,7 @@ func newTestPaymentLifecycle(t *testing.T) (*paymentLifecycle, *mockers) {
|
||||
// Create a test payment lifecycle with no fee limit and no timeout.
|
||||
p := newPaymentLifecycle(
|
||||
rt, noFeeLimit, paymentHash, mockPaymentSession,
|
||||
mockShardTracker, 0, 0,
|
||||
mockShardTracker, 0,
|
||||
)
|
||||
|
||||
// Create a mock payment which is returned from mockControlTower.
|
||||
@@ -151,9 +152,9 @@ type resumePaymentResult struct {
|
||||
err error
|
||||
}
|
||||
|
||||
// sendPaymentAndAssertFailed calls `resumePayment` and asserts that an error
|
||||
// is returned.
|
||||
func sendPaymentAndAssertFailed(t *testing.T,
|
||||
// sendPaymentAndAssertError calls `resumePayment` and asserts that an error is
|
||||
// returned.
|
||||
func sendPaymentAndAssertError(t *testing.T, ctx context.Context,
|
||||
p *paymentLifecycle, errExpected error) {
|
||||
|
||||
resultChan := make(chan *resumePaymentResult, 1)
|
||||
@@ -161,7 +162,7 @@ func sendPaymentAndAssertFailed(t *testing.T,
|
||||
// We now make a call to `resumePayment` and expect it to return the
|
||||
// error.
|
||||
go func() {
|
||||
preimage, _, err := p.resumePayment()
|
||||
preimage, _, err := p.resumePayment(ctx)
|
||||
resultChan <- &resumePaymentResult{
|
||||
preimage: preimage,
|
||||
err: err,
|
||||
@@ -189,7 +190,7 @@ func sendPaymentAndAssertSucceeded(t *testing.T,
|
||||
// We now make a call to `resumePayment` and expect it to return the
|
||||
// preimage.
|
||||
go func() {
|
||||
preimage, _, err := p.resumePayment()
|
||||
preimage, _, err := p.resumePayment(context.Background())
|
||||
resultChan <- &resumePaymentResult{
|
||||
preimage: preimage,
|
||||
err: err,
|
||||
@@ -278,6 +279,10 @@ func makeAttemptInfo(t *testing.T, amt int) channeldb.HTLCAttemptInfo {
|
||||
func TestCheckTimeoutTimedOut(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
deadline := time.Now().Add(time.Nanosecond)
|
||||
ctx, cancel := context.WithDeadline(context.Background(), deadline)
|
||||
defer cancel()
|
||||
|
||||
p := createTestPaymentLifecycle()
|
||||
|
||||
// Mock the control tower's `FailPayment` method.
|
||||
@@ -288,14 +293,11 @@ func TestCheckTimeoutTimedOut(t *testing.T) {
|
||||
// Mount the mocked control tower.
|
||||
p.router.cfg.Control = ct
|
||||
|
||||
// Make the timeout happens instantly.
|
||||
p.timeoutChan = time.After(1 * time.Nanosecond)
|
||||
|
||||
// Sleep one millisecond to make sure it timed out.
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
|
||||
// Call the function and expect no error.
|
||||
err := p.checkTimeout()
|
||||
err := p.checkContext(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Assert that `FailPayment` is called as expected.
|
||||
@@ -313,13 +315,15 @@ func TestCheckTimeoutTimedOut(t *testing.T) {
|
||||
p.router.cfg.Control = ct
|
||||
|
||||
// Make the timeout happens instantly.
|
||||
p.timeoutChan = time.After(1 * time.Nanosecond)
|
||||
deadline = time.Now().Add(time.Nanosecond)
|
||||
ctx, cancel = context.WithDeadline(context.Background(), deadline)
|
||||
defer cancel()
|
||||
|
||||
// Sleep one millisecond to make sure it timed out.
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
|
||||
// Call the function and expect an error.
|
||||
err = p.checkTimeout()
|
||||
err = p.checkContext(ctx)
|
||||
require.ErrorIs(t, err, errDummy)
|
||||
|
||||
// Assert that `FailPayment` is called as expected.
|
||||
@@ -331,10 +335,13 @@ func TestCheckTimeoutTimedOut(t *testing.T) {
|
||||
func TestCheckTimeoutOnRouterQuit(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
p := createTestPaymentLifecycle()
|
||||
|
||||
close(p.router.quit)
|
||||
err := p.checkTimeout()
|
||||
err := p.checkContext(ctx)
|
||||
require.ErrorIs(t, err, ErrRouterShuttingDown)
|
||||
}
|
||||
|
||||
@@ -627,7 +634,7 @@ func TestResumePaymentFailOnFetchPayment(t *testing.T) {
|
||||
m.control.On("FetchPayment", p.identifier).Return(nil, errDummy)
|
||||
|
||||
// Send the payment and assert it failed.
|
||||
sendPaymentAndAssertFailed(t, p, errDummy)
|
||||
sendPaymentAndAssertError(t, context.Background(), p, errDummy)
|
||||
|
||||
// Expected collectResultAsync to not be called.
|
||||
require.Zero(t, m.collectResultsCount)
|
||||
@@ -656,14 +663,15 @@ func TestResumePaymentFailOnTimeout(t *testing.T) {
|
||||
}
|
||||
m.payment.On("GetState").Return(ps).Once()
|
||||
|
||||
// NOTE: GetStatus is only used to populate the logs which is
|
||||
// not critical so we loosen the checks on how many times it's
|
||||
// been called.
|
||||
// NOTE: GetStatus is only used to populate the logs which is not
|
||||
// critical, so we loosen the checks on how many times it's been called.
|
||||
m.payment.On("GetStatus").Return(channeldb.StatusInFlight)
|
||||
|
||||
// 3. make the timeout happens instantly and sleep one millisecond to
|
||||
// make sure it timed out.
|
||||
p.timeoutChan = time.After(1 * time.Nanosecond)
|
||||
deadline := time.Now().Add(time.Nanosecond)
|
||||
ctx, cancel := context.WithDeadline(context.Background(), deadline)
|
||||
defer cancel()
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
|
||||
// 4. the payment should be failed with reason timeout.
|
||||
@@ -683,7 +691,7 @@ func TestResumePaymentFailOnTimeout(t *testing.T) {
|
||||
m.payment.On("TerminalInfo").Return(nil, &reason)
|
||||
|
||||
// Send the payment and assert it failed with the timeout reason.
|
||||
sendPaymentAndAssertFailed(t, p, reason)
|
||||
sendPaymentAndAssertError(t, ctx, p, reason)
|
||||
|
||||
// Expected collectResultAsync to not be called.
|
||||
require.Zero(t, m.collectResultsCount)
|
||||
@@ -721,7 +729,65 @@ func TestResumePaymentFailOnTimeoutErr(t *testing.T) {
|
||||
close(p.router.quit)
|
||||
|
||||
// Send the payment and assert it failed when router is shutting down.
|
||||
sendPaymentAndAssertFailed(t, p, ErrRouterShuttingDown)
|
||||
sendPaymentAndAssertError(
|
||||
t, context.Background(), p, ErrRouterShuttingDown,
|
||||
)
|
||||
|
||||
// Expected collectResultAsync to not be called.
|
||||
require.Zero(t, m.collectResultsCount)
|
||||
}
|
||||
|
||||
// TestResumePaymentFailContextCancel checks that the lifecycle fails when the
|
||||
// context is canceled.
|
||||
//
|
||||
// NOTE: No parallel test because it overwrites global variables.
|
||||
//
|
||||
//nolint:paralleltest
|
||||
func TestResumePaymentFailContextCancel(t *testing.T) {
|
||||
// Create a test paymentLifecycle with the initial two calls mocked.
|
||||
p, m := setupTestPaymentLifecycle(t)
|
||||
|
||||
// Create the cancelable payment context.
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
paymentAmt := lnwire.MilliSatoshi(10000)
|
||||
|
||||
// We now enter the payment lifecycle loop.
|
||||
//
|
||||
// 1. calls `FetchPayment` and return the payment.
|
||||
m.control.On("FetchPayment", p.identifier).Return(m.payment, nil).Once()
|
||||
|
||||
// 2. calls `GetState` and return the state.
|
||||
ps := &channeldb.MPPaymentState{
|
||||
RemainingAmt: paymentAmt,
|
||||
}
|
||||
m.payment.On("GetState").Return(ps).Once()
|
||||
|
||||
// NOTE: GetStatus is only used to populate the logs which is not
|
||||
// critical, so we loosen the checks on how many times it's been called.
|
||||
m.payment.On("GetStatus").Return(channeldb.StatusInFlight)
|
||||
|
||||
// 3. Cancel the context and skip the FailPayment error to trigger the
|
||||
// context cancellation of the payment.
|
||||
cancel()
|
||||
|
||||
m.control.On(
|
||||
"FailPayment", p.identifier, channeldb.FailureReasonTimeout,
|
||||
).Return(nil).Once()
|
||||
|
||||
// 5. decideNextStep now returns stepExit.
|
||||
m.payment.On("AllowMoreAttempts").Return(false, nil).Once().
|
||||
On("NeedWaitAttempts").Return(false, nil).Once()
|
||||
|
||||
// 6. Control tower deletes failed attempts.
|
||||
m.control.On("DeleteFailedAttempts", p.identifier).Return(nil).Once()
|
||||
|
||||
// 7. We will observe FailureReasonError if the context was cancelled.
|
||||
reason := channeldb.FailureReasonError
|
||||
m.payment.On("TerminalInfo").Return(nil, &reason)
|
||||
|
||||
// Send the payment and assert it failed with the timeout reason.
|
||||
sendPaymentAndAssertError(t, ctx, p, reason)
|
||||
|
||||
// Expected collectResultAsync to not be called.
|
||||
require.Zero(t, m.collectResultsCount)
|
||||
@@ -759,7 +825,7 @@ func TestResumePaymentFailOnStepErr(t *testing.T) {
|
||||
m.payment.On("AllowMoreAttempts").Return(false, errDummy).Once()
|
||||
|
||||
// Send the payment and assert it failed.
|
||||
sendPaymentAndAssertFailed(t, p, errDummy)
|
||||
sendPaymentAndAssertError(t, context.Background(), p, errDummy)
|
||||
|
||||
// Expected collectResultAsync to not be called.
|
||||
require.Zero(t, m.collectResultsCount)
|
||||
@@ -803,7 +869,7 @@ func TestResumePaymentFailOnRequestRouteErr(t *testing.T) {
|
||||
).Return(nil, errDummy).Once()
|
||||
|
||||
// Send the payment and assert it failed.
|
||||
sendPaymentAndAssertFailed(t, p, errDummy)
|
||||
sendPaymentAndAssertError(t, context.Background(), p, errDummy)
|
||||
|
||||
// Expected collectResultAsync to not be called.
|
||||
require.Zero(t, m.collectResultsCount)
|
||||
@@ -863,7 +929,7 @@ func TestResumePaymentFailOnRegisterAttemptErr(t *testing.T) {
|
||||
).Return(nil, errDummy).Once()
|
||||
|
||||
// Send the payment and assert it failed.
|
||||
sendPaymentAndAssertFailed(t, p, errDummy)
|
||||
sendPaymentAndAssertError(t, context.Background(), p, errDummy)
|
||||
|
||||
// Expected collectResultAsync to not be called.
|
||||
require.Zero(t, m.collectResultsCount)
|
||||
@@ -955,7 +1021,7 @@ func TestResumePaymentFailOnSendAttemptErr(t *testing.T) {
|
||||
).Return(nil, errDummy).Once()
|
||||
|
||||
// Send the payment and assert it failed.
|
||||
sendPaymentAndAssertFailed(t, p, errDummy)
|
||||
sendPaymentAndAssertError(t, context.Background(), p, errDummy)
|
||||
|
||||
// Expected collectResultAsync to not be called.
|
||||
require.Zero(t, m.collectResultsCount)
|
||||
|
||||
Reference in new issue
Block a user