What changed, and why it matters
This is a small internal code cleanup in LND's payment routing logic. It changes a helper function so callers pass in an existing request context, instead of the helper creating a blank placeholder context. There is no direct security fix here, but it removes a minor anti-pattern that could theoretically hide cancellation or timeout problems in future changes.
No immediate action required. Treat as routine refactoring. Reviewers may verify that all call sites now pass a non-nil, appropriately-scoped context and that downstream control tower operations respect cancellation.
Security signals we found
Replaces context.TODO() with propagated context in failure-handling path
No functional change to failure logic or error handling
No bounds checks, cryptographic operations, or network input parsing changed
Evidence from the diff
The commit modifies routing/payment_lifecycle.go so that failAttempt now accepts a context.Context parameter from its callers, replacing the previous context.TODO() inside the function. All four call sites (sendAttempt, failPaymentAndAttempt, and two locations in handleSwitchErr) are updated to pass their existing ctx. This is a refactoring/correctness change; it does not alter payment failure semantics or fix a disclosed vulnerability.
Changed components
routing/payment_lifecycle.gopaymentLifecycle.failAttemptpaymentLifecycle.sendAttemptpaymentLifecycle.failPaymentAndAttemptpaymentLifecycle.handleSwitchErrInspect captured patch +5 / −7
diff --git a/routing/payment_lifecycle.go b/routing/payment_lifecycle.go
index b5df243..c6f6154 100644
--- a/routing/payment_lifecycle.go
+++ b/routing/payment_lifecycle.go
@@ -713,7 +713,7 @@ func (p *paymentLifecycle) sendAttempt(ctx context.Context,
"payment=%v, err:%v", attempt.AttemptID,
p.identifier, err)
- return p.failAttempt(attempt.AttemptID, err)
+ return p.failAttempt(ctx, attempt.AttemptID, err)
}
htlcAdd.OnionBlob = onionBlob
@@ -839,7 +839,7 @@ func (p *paymentLifecycle) failPaymentAndAttempt(ctx context.Context,
}
// Fail the attempt.
- return p.failAttempt(attemptID, sendErr)
+ return p.failAttempt(ctx, attemptID, sendErr)
}
// handleSwitchErr inspects the given error from the Switch and determines
@@ -878,7 +878,7 @@ func (p *paymentLifecycle) handleSwitchErr(ctx context.Context,
// Fail the attempt only if there's no reason.
if reason == nil {
// Fail the attempt.
- return p.failAttempt(attemptID, sendErr)
+ return p.failAttempt(ctx, attemptID, sendErr)
}
// Otherwise fail both the payment and the attempt.
@@ -893,7 +893,7 @@ func (p *paymentLifecycle) handleSwitchErr(ctx context.Context,
log.Warnf("Failing attempt=%v for payment=%v as it's not "+
"found in the Switch", attempt.AttemptID, p.identifier)
- return p.failAttempt(attemptID, sendErr)
+ return p.failAttempt(ctx, attemptID, sendErr)
}
if errors.Is(sendErr, htlcswitch.ErrUnreadableFailureMessage) {
@@ -1025,11 +1025,9 @@ func (p *paymentLifecycle) handleFailureMessage(rt *route.Route,
}
// failAttempt calls control tower to fail the current payment attempt.
-func (p *paymentLifecycle) failAttempt(attemptID uint64,
+func (p *paymentLifecycle) failAttempt(ctx context.Context, attemptID uint64,
sendError error) (*attemptResult, error) {
- ctx := context.TODO()
-
log.Warnf("Attempt %v for payment %v failed: %v", attemptID,
p.identifier, sendError)
Why this scored 12/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.