What changed, and why it matters
This change is a routine code cleanup: it threads a context.Context argument through the FailAttempt function and its callers. Contexts let operations respect cancellation and timeouts. The patch does not fix a known vulnerability; it removes two temporary context.TODO() placeholders and makes the function signature consistent with related functions like SettleAttempt. There is no security-relevant behavior change visible in the diff.
No immediate security action required. Treat as normal refactoring. If desired, follow up by replacing the remaining context.TODO() values at call sites with properly scoped contexts to improve cancellation and timeout handling.
Security signals we found
No security-relevant functional change: only API signature updated to accept context.Context
Two context.TODO() calls removed from SQLStore.FailAttempt and replaced with caller-provided context
Two new context.TODO() calls added at call sites in payment_lifecycle.go and router.go
No input validation, cryptographic, authorization, or state-machine logic changes
No mention of security, CVE, bug, vulnerability, or reporter in commit title/message
Evidence from the diff
The commit modifies the FailAttempt method signature across the payment database interfaces and implementations (KVStore, SQLStore, PaymentControl) and the routing ControlTower to accept a context.Context parameter. The SQL implementation previously used context.TODO() inside the method; now it uses the passed-in context. Call sites in payment_lifecycle.go and router.go still create context.TODO() locally, so cancellation behavior is unchanged for those paths. Test files and mocks are updated to match the new signature. This is a refactoring/technical-debt change, not a security patch.
Changed components
payments/db/interface.gopayments/db/kv_store.gopayments/db/sql_store.gorouting/control_tower.gorouting/payment_lifecycle.gorouting/router.goassociated test and mock filesInspect captured patch +33 / −26
diff --git a/payments/db/interface.go b/payments/db/interface.go
index 452a7e5..45d0e9a 100644
--- a/payments/db/interface.go
+++ b/payments/db/interface.go
@@ -90,7 +90,8 @@ type PaymentControl interface {
*HTLCSettleInfo) (*MPPayment, error)
// FailAttempt marks the given payment attempt failed.
- FailAttempt(lntypes.Hash, uint64, *HTLCFailInfo) (*MPPayment, error)
+ FailAttempt(context.Context, lntypes.Hash, uint64,
+ *HTLCFailInfo) (*MPPayment, error)
// Fail transitions a payment into the Failed state, and records
// the ultimate reason the payment failed. Note that this should only
diff --git a/payments/db/kv_store.go b/payments/db/kv_store.go
index 3739232..59fe24f 100644
--- a/payments/db/kv_store.go
+++ b/payments/db/kv_store.go
@@ -443,7 +443,7 @@ func (p *KVStore) SettleAttempt(_ context.Context, hash lntypes.Hash,
}
// FailAttempt marks the given payment attempt failed.
-func (p *KVStore) FailAttempt(hash lntypes.Hash,
+func (p *KVStore) FailAttempt(_ context.Context, hash lntypes.Hash,
attemptID uint64, failInfo *HTLCFailInfo) (*MPPayment, error) {
var b bytes.Buffer
diff --git a/payments/db/kv_store_test.go b/payments/db/kv_store_test.go
index fb7a275..ee8412a 100644
--- a/payments/db/kv_store_test.go
+++ b/payments/db/kv_store_test.go
@@ -100,7 +100,7 @@ func TestKVStoreDeleteNonInFlight(t *testing.T) {
// Fail the payment attempt.
htlcFailure := HTLCFailUnreadable
_, err := paymentDB.FailAttempt(
- info.PaymentIdentifier, attempt.AttemptID,
+ ctx, info.PaymentIdentifier, attempt.AttemptID,
&HTLCFailInfo{
Reason: htlcFailure,
},
diff --git a/payments/db/payment_test.go b/payments/db/payment_test.go
index 159f970..879dfb1 100644
--- a/payments/db/payment_test.go
+++ b/payments/db/payment_test.go
@@ -156,7 +156,7 @@ func createTestPayments(t *testing.T, p DB, payments []*payment) {
htlcFailure := HTLCFailUnreadable
_, err = p.FailAttempt(
- info.PaymentIdentifier, attempt.AttemptID,
+ ctx, info.PaymentIdentifier, attempt.AttemptID,
&HTLCFailInfo{
Reason: htlcFailure,
},
@@ -183,7 +183,7 @@ func createTestPayments(t *testing.T, p DB, payments []*payment) {
case StatusFailed:
htlcFailure := HTLCFailUnreadable
_, err = p.FailAttempt(
- info.PaymentIdentifier, attempt.AttemptID,
+ ctx, info.PaymentIdentifier, attempt.AttemptID,
&HTLCFailInfo{
Reason: htlcFailure,
},
@@ -1885,7 +1885,7 @@ func TestSwitchFail(t *testing.T) {
htlcReason := HTLCFailUnreadable
_, err = paymentDB.FailAttempt(
- info.PaymentIdentifier, attempt.AttemptID,
+ ctx, info.PaymentIdentifier, attempt.AttemptID,
&HTLCFailInfo{
Reason: htlcReason,
},
@@ -2069,7 +2069,7 @@ func TestMultiShard(t *testing.T) {
a := attempts[1]
htlcFail := HTLCFailUnreadable
_, err = paymentDB.FailAttempt(
- info.PaymentIdentifier, a.AttemptID,
+ ctx, info.PaymentIdentifier, a.AttemptID,
&HTLCFailInfo{
Reason: htlcFail,
},
@@ -2118,7 +2118,7 @@ func TestMultiShard(t *testing.T) {
)
} else {
_, err := paymentDB.FailAttempt(
- info.PaymentIdentifier, a.AttemptID,
+ ctx, info.PaymentIdentifier, a.AttemptID,
&HTLCFailInfo{
Reason: htlcFail,
},
@@ -2209,7 +2209,7 @@ func TestMultiShard(t *testing.T) {
} else {
// Fail the attempt.
_, err := paymentDB.FailAttempt(
- info.PaymentIdentifier, a.AttemptID,
+ ctx, info.PaymentIdentifier, a.AttemptID,
&HTLCFailInfo{
Reason: htlcFail,
},
diff --git a/payments/db/sql_store.go b/payments/db/sql_store.go
index ca5add1..a921a12 100644
--- a/payments/db/sql_store.go
+++ b/payments/db/sql_store.go
@@ -1695,11 +1695,9 @@ func (s *SQLStore) SettleAttempt(ctx context.Context, paymentHash lntypes.Hash,
// the PaymentWriter interface and ultimately the DB interface. It represents
// step 3b in the payment lifecycle control flow (step 3a is SettleAttempt),
// called after RegisterAttempt when an HTLC fails.
-func (s *SQLStore) FailAttempt(paymentHash lntypes.Hash,
+func (s *SQLStore) FailAttempt(ctx context.Context, paymentHash lntypes.Hash,
attemptID uint64, failInfo *HTLCFailInfo) (*MPPayment, error) {
- ctx := context.TODO()
-
var mpPayment *MPPayment
err := s.db.ExecTx(ctx, sqldb.WriteTxOpt(), func(db SQLQueries) error {
diff --git a/routing/control_tower.go b/routing/control_tower.go
index 163aec3..cbb79d4 100644
--- a/routing/control_tower.go
+++ b/routing/control_tower.go
@@ -50,8 +50,8 @@ type ControlTower interface {
// FailAttempt marks the given payment attempt failed.
//
// NOTE: Subscribers should be notified by the new state of the payment.
- FailAttempt(lntypes.Hash, uint64, *paymentsdb.HTLCFailInfo) (
- *paymentsdb.HTLCAttempt, error)
+ FailAttempt(context.Context, lntypes.Hash, uint64,
+ *paymentsdb.HTLCFailInfo) (*paymentsdb.HTLCAttempt, error)
// FetchPayment fetches the payment corresponding to the given payment
// hash.
@@ -239,14 +239,14 @@ func (p *controlTower) SettleAttempt(ctx context.Context,
}
// FailAttempt marks the given payment attempt failed.
-func (p *controlTower) FailAttempt(paymentHash lntypes.Hash,
- attemptID uint64, failInfo *paymentsdb.HTLCFailInfo) (
- *paymentsdb.HTLCAttempt, error) {
+func (p *controlTower) FailAttempt(ctx context.Context,
+ paymentHash lntypes.Hash, attemptID uint64,
+ failInfo *paymentsdb.HTLCFailInfo) (*paymentsdb.HTLCAttempt, error) {
p.paymentsMtx.Lock(paymentHash)
defer p.paymentsMtx.Unlock(paymentHash)
- payment, err := p.db.FailAttempt(paymentHash, attemptID, failInfo)
+ payment, err := p.db.FailAttempt(ctx, paymentHash, attemptID, failInfo)
if err != nil {
return nil, err
}
diff --git a/routing/control_tower_test.go b/routing/control_tower_test.go
index 20e8e82..5241d81 100644
--- a/routing/control_tower_test.go
+++ b/routing/control_tower_test.go
@@ -448,7 +448,8 @@ func TestKVStoreUnsubscribeSuccess(t *testing.T) {
Reason: paymentsdb.HTLCFailInternal,
}
_, err = pControl.FailAttempt(
- info.PaymentIdentifier, attempt.AttemptID, &failInfo,
+ t.Context(), info.PaymentIdentifier, attempt.AttemptID,
+ &failInfo,
)
require.NoError(t, err, "unable to fail htlc")
@@ -502,7 +503,8 @@ func testKVStoreSubscribeFail(t *testing.T, registerAttempt,
Reason: paymentsdb.HTLCFailInternal,
}
htlcAttempt, err := pControl.FailAttempt(
- info.PaymentIdentifier, attempt.AttemptID, &failInfo,
+ t.Context(), info.PaymentIdentifier, attempt.AttemptID,
+ &failInfo,
)
if err != nil {
t.Fatalf("unable to fail htlc: %v", err)
diff --git a/routing/mock_test.go b/routing/mock_test.go
index 77f98ab..f10c38a 100644
--- a/routing/mock_test.go
+++ b/routing/mock_test.go
@@ -451,8 +451,9 @@ func (m *mockControlTowerOld) SettleAttempt(_ context.Context,
return nil, fmt.Errorf("pid not found")
}
-func (m *mockControlTowerOld) FailAttempt(phash lntypes.Hash, pid uint64,
- failInfo *paymentsdb.HTLCFailInfo) (*paymentsdb.HTLCAttempt, error) {
+func (m *mockControlTowerOld) FailAttempt(_ context.Context, phash lntypes.Hash,
+ pid uint64, failInfo *paymentsdb.HTLCFailInfo) (*paymentsdb.HTLCAttempt,
+ error) {
if m.failAttempt != nil {
m.failAttempt <- failAttemptArgs{failInfo}
@@ -767,8 +768,9 @@ func (m *mockControlTower) SettleAttempt(_ context.Context, phash lntypes.Hash,
return attempt.(*paymentsdb.HTLCAttempt), args.Error(1)
}
-func (m *mockControlTower) FailAttempt(phash lntypes.Hash, pid uint64,
- failInfo *paymentsdb.HTLCFailInfo) (*paymentsdb.HTLCAttempt, error) {
+func (m *mockControlTower) FailAttempt(_ context.Context, phash lntypes.Hash,
+ pid uint64, failInfo *paymentsdb.HTLCFailInfo) (*paymentsdb.HTLCAttempt,
+ error) {
args := m.Called(phash, pid, failInfo)
diff --git a/routing/payment_lifecycle.go b/routing/payment_lifecycle.go
index a2e3935..904d399 100644
--- a/routing/payment_lifecycle.go
+++ b/routing/payment_lifecycle.go
@@ -1003,6 +1003,8 @@ func (p *paymentLifecycle) handleFailureMessage(rt *route.Route,
func (p *paymentLifecycle) failAttempt(attemptID uint64,
sendError error) (*attemptResult, error) {
+ ctx := context.TODO()
+
log.Warnf("Attempt %v for payment %v failed: %v", attemptID,
p.identifier, sendError)
@@ -1019,7 +1021,7 @@ func (p *paymentLifecycle) failAttempt(attemptID uint64,
}
attempt, err := p.router.cfg.Control.FailAttempt(
- p.identifier, attemptID, failInfo,
+ ctx, p.identifier, attemptID, failInfo,
)
if err != nil {
return nil, err
diff --git a/routing/router.go b/routing/router.go
index bb03143..9319260 100644
--- a/routing/router.go
+++ b/routing/router.go
@@ -1531,6 +1531,8 @@ func (r *ChannelRouter) resumePayments() error {
func (r *ChannelRouter) failStaleAttempt(a paymentsdb.HTLCAttempt,
payHash lntypes.Hash) {
+ ctx := context.TODO()
+
// We can only fail inflight HTLCs so we skip the settled/failed ones.
if a.Failure != nil || a.Settle != nil {
return
@@ -1614,7 +1616,7 @@ func (r *ChannelRouter) failStaleAttempt(a paymentsdb.HTLCAttempt,
Reason: paymentsdb.HTLCFailUnknown,
FailTime: r.cfg.Clock.Now(),
}
- _, err = r.cfg.Control.FailAttempt(payHash, a.AttemptID, failInfo)
+ _, err = r.cfg.Control.FailAttempt(ctx, payHash, a.AttemptID, failInfo)
if err != nil {
log.Errorf("Fail attempt=%v got error: %v", a.AttemptID, err)
}
Why this scored 18/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.