What changed, and why it matters
This change is a straightforward code cleanup: it threads a context.Context parameter through the SettleAttempt function so callers pass in a request-scoped context instead of the function internally using a placeholder context.TODO(). It does not change payment logic, access controls, or cryptographic handling. The only functional difference is that database operations during settle can now be cancelled or timed out by the caller's context, which is generally an improvement but not a security fix on its own.
No immediate security action required. Treat as normal code-quality/maintenance review. If this commit is part of a larger fix, evaluate the combined change set for security relevance.
Security signals we found
context propagation refactor only
no change to preimage/duplicate-payment logic
no new input validation or access control
SQL store replaces context.TODO() with caller context
Evidence from the diff
The commit modifies the PaymentControl.SettleAttempt and ControlTower.SettleAttempt signatures to accept a context.Context as the first argument, then propagates that context into the SQL and KV store implementations. In the SQL store it replaces an internal context.TODO() with the supplied context, allowing the underlying SQL transaction to respect cancellation/timeout. In the KV store the context is accepted but ignored (named _). Call sites in payment_lifecycle.go and tests are updated accordingly. No logic changes to preimage validation, duplicate-payment checks, or state transitions are visible.
Changed components
payments/db/interface.gopayments/db/kv_store.gopayments/db/sql_store.gorouting/control_tower.gorouting/payment_lifecycle.goInspect captured patch +37 / −28
diff --git a/payments/db/interface.go b/payments/db/interface.go
index 3af2dfb..452a7e5 100644
--- a/payments/db/interface.go
+++ b/payments/db/interface.go
@@ -86,7 +86,8 @@ type PaymentControl interface {
// error to prevent us from making duplicate payments to the same
// payment hash. The provided preimage is atomically saved to the DB
// for record keeping.
- SettleAttempt(lntypes.Hash, uint64, *HTLCSettleInfo) (*MPPayment, error)
+ SettleAttempt(context.Context, lntypes.Hash, uint64,
+ *HTLCSettleInfo) (*MPPayment, error)
// FailAttempt marks the given payment attempt failed.
FailAttempt(lntypes.Hash, uint64, *HTLCFailInfo) (*MPPayment, error)
diff --git a/payments/db/kv_store.go b/payments/db/kv_store.go
index 5511bf8..3739232 100644
--- a/payments/db/kv_store.go
+++ b/payments/db/kv_store.go
@@ -430,7 +430,7 @@ func (p *KVStore) RegisterAttempt(_ context.Context, paymentHash lntypes.Hash,
// After invoking this method, InitPayment should always return an error to
// prevent us from making duplicate payments to the same payment hash. The
// provided preimage is atomically saved to the DB for record keeping.
-func (p *KVStore) SettleAttempt(hash lntypes.Hash,
+func (p *KVStore) SettleAttempt(_ context.Context, hash lntypes.Hash,
attemptID uint64, settleInfo *HTLCSettleInfo) (*MPPayment, error) {
var b bytes.Buffer
diff --git a/payments/db/kv_store_test.go b/payments/db/kv_store_test.go
index 0c51dcc..fb7a275 100644
--- a/payments/db/kv_store_test.go
+++ b/payments/db/kv_store_test.go
@@ -133,7 +133,7 @@ func TestKVStoreDeleteNonInFlight(t *testing.T) {
case p.success:
// Verifies that status was changed to StatusSucceeded.
_, err := paymentDB.SettleAttempt(
- info.PaymentIdentifier, attempt.AttemptID,
+ ctx, info.PaymentIdentifier, attempt.AttemptID,
&HTLCSettleInfo{
Preimage: preimg,
},
diff --git a/payments/db/payment_test.go b/payments/db/payment_test.go
index 8e2c7a4..159f970 100644
--- a/payments/db/payment_test.go
+++ b/payments/db/payment_test.go
@@ -198,7 +198,7 @@ func createTestPayments(t *testing.T, p DB, payments []*payment) {
// Settle the attempt
case StatusSucceeded:
_, err := p.SettleAttempt(
- info.PaymentIdentifier, attempt.AttemptID,
+ ctx, info.PaymentIdentifier, attempt.AttemptID,
&HTLCSettleInfo{
Preimage: preimg,
},
@@ -1643,6 +1643,7 @@ func TestSuccessesWithoutInFlight(t *testing.T) {
// Attempt to complete the payment should fail.
_, err = paymentDB.SettleAttempt(
+ t.Context(),
info.PaymentIdentifier, 0,
&HTLCSettleInfo{
Preimage: preimg,
@@ -1790,7 +1791,7 @@ func TestSwitchDoubleSend(t *testing.T) {
// After settling, the error should be ErrAlreadyPaid.
_, err = paymentDB.SettleAttempt(
- info.PaymentIdentifier, attempt.AttemptID,
+ ctx, info.PaymentIdentifier, attempt.AttemptID,
&HTLCSettleInfo{
Preimage: preimg,
},
@@ -1926,7 +1927,7 @@ func TestSwitchFail(t *testing.T) {
// Settle the attempt and verify that status was changed to
// StatusSucceeded.
payment, err = paymentDB.SettleAttempt(
- info.PaymentIdentifier, attempt.AttemptID,
+ ctx, info.PaymentIdentifier, attempt.AttemptID,
&HTLCSettleInfo{
Preimage: preimg,
},
@@ -2099,7 +2100,7 @@ func TestMultiShard(t *testing.T) {
var firstFailReason *FailureReason
if test.settleFirst {
_, err := paymentDB.SettleAttempt(
- info.PaymentIdentifier, a.AttemptID,
+ ctx, info.PaymentIdentifier, a.AttemptID,
&HTLCSettleInfo{
Preimage: preimg,
},
@@ -2193,7 +2194,7 @@ func TestMultiShard(t *testing.T) {
if test.settleLast {
// Settle the last outstanding attempt.
_, err = paymentDB.SettleAttempt(
- info.PaymentIdentifier, a.AttemptID,
+ ctx, info.PaymentIdentifier, a.AttemptID,
&HTLCSettleInfo{
Preimage: preimg,
},
@@ -2683,7 +2684,7 @@ func TestQueryPayments(t *testing.T) {
copy(preimg[:], rev[:])
_, err = paymentDB.SettleAttempt(
- lastPaymentInfo.PaymentIdentifier,
+ ctx, lastPaymentInfo.PaymentIdentifier,
attempt.AttemptID,
&HTLCSettleInfo{
Preimage: preimg,
@@ -2813,7 +2814,7 @@ func TestFetchInFlightPayments(t *testing.T) {
require.NoError(t, err)
_, err = paymentDB.SettleAttempt(
- payments[2].id, 5,
+ ctx, payments[2].id, 5,
&HTLCSettleInfo{
Preimage: preimg,
},
diff --git a/payments/db/sql_store.go b/payments/db/sql_store.go
index 8c963d3..ca5add1 100644
--- a/payments/db/sql_store.go
+++ b/payments/db/sql_store.go
@@ -1496,8 +1496,9 @@ func (s *SQLStore) insertRouteHops(ctx context.Context, db SQLQueries,
// the PaymentWriter interface and ultimately the DB interface. It represents
// step 2 in the payment lifecycle control flow, called after InitPayment and
// potentially multiple times for multi-path payments.
-func (s *SQLStore) RegisterAttempt(ctx context.Context, paymentHash lntypes.Hash,
- attempt *HTLCAttemptInfo) (*MPPayment, error) {
+func (s *SQLStore) RegisterAttempt(ctx context.Context,
+ paymentHash lntypes.Hash, attempt *HTLCAttemptInfo) (*MPPayment,
+ error) {
var mpPayment *MPPayment
@@ -1621,11 +1622,9 @@ func (s *SQLStore) RegisterAttempt(ctx context.Context, paymentHash lntypes.Hash
// the PaymentWriter interface and ultimately the DB interface. It represents
// step 3a in the payment lifecycle control flow (step 3b is FailAttempt),
// called after RegisterAttempt when an HTLC successfully completes.
-func (s *SQLStore) SettleAttempt(paymentHash lntypes.Hash,
+func (s *SQLStore) SettleAttempt(ctx context.Context, paymentHash lntypes.Hash,
attemptID uint64, settleInfo *HTLCSettleInfo) (*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 28432d7..163aec3 100644
--- a/routing/control_tower.go
+++ b/routing/control_tower.go
@@ -44,8 +44,8 @@ type ControlTower interface {
// for record keeping.
//
// NOTE: Subscribers should be notified by the new state of the payment.
- SettleAttempt(lntypes.Hash, uint64, *paymentsdb.HTLCSettleInfo) (
- *paymentsdb.HTLCAttempt, error)
+ SettleAttempt(context.Context, lntypes.Hash, uint64,
+ *paymentsdb.HTLCSettleInfo) (*paymentsdb.HTLCAttempt, error)
// FailAttempt marks the given payment attempt failed.
//
@@ -217,14 +217,17 @@ func (p *controlTower) RegisterAttempt(ctx context.Context,
// SettleAttempt marks the given attempt settled with the preimage. If
// this is a multi shard payment, this might implicitly mean the the
// full payment succeeded.
-func (p *controlTower) SettleAttempt(paymentHash lntypes.Hash,
- attemptID uint64, settleInfo *paymentsdb.HTLCSettleInfo) (
- *paymentsdb.HTLCAttempt, error) {
+func (p *controlTower) SettleAttempt(ctx context.Context,
+ paymentHash lntypes.Hash, attemptID uint64,
+ settleInfo *paymentsdb.HTLCSettleInfo) (*paymentsdb.HTLCAttempt,
+ error) {
p.paymentsMtx.Lock(paymentHash)
defer p.paymentsMtx.Unlock(paymentHash)
- payment, err := p.db.SettleAttempt(paymentHash, attemptID, settleInfo)
+ payment, err := p.db.SettleAttempt(
+ ctx, paymentHash, attemptID, settleInfo,
+ )
if err != nil {
return nil, err
}
diff --git a/routing/control_tower_test.go b/routing/control_tower_test.go
index 20bdd17..20e8e82 100644
--- a/routing/control_tower_test.go
+++ b/routing/control_tower_test.go
@@ -108,7 +108,8 @@ func TestControlTowerSubscribeSuccess(t *testing.T) {
Preimage: preimg,
}
htlcAttempt, err := pControl.SettleAttempt(
- info.PaymentIdentifier, attempt.AttemptID, &settleInfo,
+ t.Context(), info.PaymentIdentifier, attempt.AttemptID,
+ &settleInfo,
)
if err != nil {
t.Fatal(err)
@@ -246,7 +247,8 @@ func TestKVStoreSubscribeAllSuccess(t *testing.T) {
Preimage: preimg1,
}
htlcAttempt1, err := pControl.SettleAttempt(
- info1.PaymentIdentifier, attempt1.AttemptID, &settleInfo1,
+ t.Context(), info1.PaymentIdentifier, attempt1.AttemptID,
+ &settleInfo1,
)
require.NoError(t, err)
require.Equal(
@@ -259,7 +261,8 @@ func TestKVStoreSubscribeAllSuccess(t *testing.T) {
Preimage: preimg2,
}
htlcAttempt2, err := pControl.SettleAttempt(
- info2.PaymentIdentifier, attempt2.AttemptID, &settleInfo2,
+ t.Context(), info2.PaymentIdentifier, attempt2.AttemptID,
+ &settleInfo2,
)
require.NoError(t, err)
require.Equal(
diff --git a/routing/mock_test.go b/routing/mock_test.go
index daad344..77f98ab 100644
--- a/routing/mock_test.go
+++ b/routing/mock_test.go
@@ -408,8 +408,8 @@ func (m *mockControlTowerOld) RegisterAttempt(_ context.Context,
return nil
}
-func (m *mockControlTowerOld) SettleAttempt(phash lntypes.Hash,
- pid uint64, settleInfo *paymentsdb.HTLCSettleInfo) (
+func (m *mockControlTowerOld) SettleAttempt(_ context.Context,
+ phash lntypes.Hash, pid uint64, settleInfo *paymentsdb.HTLCSettleInfo) (
*paymentsdb.HTLCAttempt, error) {
if m.settleAttempt != nil {
@@ -753,7 +753,7 @@ func (m *mockControlTower) RegisterAttempt(_ context.Context,
return args.Error(0)
}
-func (m *mockControlTower) SettleAttempt(phash lntypes.Hash,
+func (m *mockControlTower) SettleAttempt(_ context.Context, phash lntypes.Hash,
pid uint64, settleInfo *paymentsdb.HTLCSettleInfo) (
*paymentsdb.HTLCAttempt, error) {
diff --git a/routing/payment_lifecycle.go b/routing/payment_lifecycle.go
index 0499475..a2e3935 100644
--- a/routing/payment_lifecycle.go
+++ b/routing/payment_lifecycle.go
@@ -1166,6 +1166,8 @@ func (p *paymentLifecycle) reloadPayment() (paymentsdb.DBMPPayment,
func (p *paymentLifecycle) handleAttemptResult(attempt *paymentsdb.HTLCAttempt,
result *htlcswitch.PaymentResult) (*attemptResult, error) {
+ ctx := context.TODO()
+
// If the result has an error, we need to further process it by failing
// the attempt and maybe fail the payment.
if result.Error != nil {
@@ -1187,7 +1189,7 @@ func (p *paymentLifecycle) handleAttemptResult(attempt *paymentsdb.HTLCAttempt,
// In case of success we atomically store settle result to the DB and
// move the shard to the settled state.
htlcAttempt, err := p.router.cfg.Control.SettleAttempt(
- p.identifier, attempt.AttemptID,
+ ctx, p.identifier, attempt.AttemptID,
&paymentsdb.HTLCSettleInfo{
Preimage: result.Preimage,
SettleTime: p.router.cfg.Clock.Now(),
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.