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:
Slyghtning committed 2024-06-17 09:32:26 +02:00
1 parent e729084149
commit bba01cf634
4 files changed
+163 -69

No files matched your search

+90 -24
View File
@@ -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)