paymentsdb: preserve AMP attempt hashes in sql store
What changed, and why it matters
This commit fixes a database bug in LND's new SQL payment store. For a special payment type called AMP (Atomic Multi-Path), each individual payment shard has its own unique hash, but the SQL store was incorrectly saving the overall payment identifier (the SetID) as the hash for every shard. This meant that when LND later reloaded AMP payment attempts from the SQL database, the per-shard hashes were wrong, which could break retry logic, correlation of HTLCs, or reporting. The fix saves the shard's own hash when available, and only falls back to the payment identifier when there is no shard hash. It is a data-integrity bug, not a direct funds-loss vulnerability, and only affects users running the experimental SQL backend.
Treat as a data-integrity bug fix rather than a security vulnerability. Users running the SQL payment store backend should upgrade to avoid incorrect AMP attempt-hash reloads, which could affect payment tracking and retry behavior. No immediate emergency response is indicated by the commit materials.
Security signals we found
Data integrity mismatch between runtime object and persisted SQL row
AMP-specific incorrect hash persistence in SQL backend only
Test added/updated to enforce parity with KV store behavior
No mention of funds loss, remote exploit, or cryptographic failure
Evidence from the diff
In payments/db/sql_store.go, RegisterAttempt previously always wrote paymentHash[:] into the PaymentHash column of the HTLC attempt row. For legacy payments paymentHash is the actual payment hash, but for AMP payments paymentHash is the SetID, while attempt.Hash holds the per-shard HTLC hash. The patch stores attempt.Hash[:] when attempt.Hash != nil, otherwise falling back to paymentHash[:]. This restores parity with the older KV store, where the test TestRegisterAttemptPreservesAttemptHash already passes. The bug is a persistence-layer mismatch that corrupts the reload-time view of AMP attempts.
Changed components
payments/db/sql_store.goSQL payment store RegisterAttemptAMP (Atomic Multi-Path) payment attemptsInspect captured patch +5 / −1
diff --git a/payments/db/sql_store.go b/payments/db/sql_store.go
index 5726504..4c4f0f3 100644
--- a/payments/db/sql_store.go
+++ b/payments/db/sql_store.go
@@ -1597,13 +1597,17 @@ func (s *SQLStore) RegisterAttempt(ctx context.Context,
// Register the plain HTLC attempt next.
sessionKey := attempt.SessionKey()
sessionKeyBytes := sessionKey.Serialize()
+ attemptHash := paymentHash[:]
+ if attempt.Hash != nil {
+ attemptHash = attempt.Hash[:]
+ }
_, err = db.InsertHtlcAttempt(ctx, sqlc.InsertHtlcAttemptParams{
PaymentID: dbPayment.Payment.ID,
AttemptIndex: int64(attempt.AttemptID),
SessionKey: sessionKeyBytes,
AttemptTime: attempt.AttemptTime,
- PaymentHash: paymentHash[:],
+ PaymentHash: attemptHash,
FirstHopAmountMsat: int64(
attempt.Route.FirstHopAmount.Val.Int(),
),
Why this scored 43/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.