paymentsdb: add a wrapper to the fetchpayment method
What changed, and why it matters
This commit refactors how LND's payment database looks up payments. It introduces a shared helper that converts a 'no rows found' database result into a specific, expected error (ErrPaymentNotInitiated). Several existing functions are switched to use this helper, and a few places that previously wrapped the raw database error with extra text now return the error directly. The change is mostly a cleanup, but it also tightens error handling so missing payments are treated consistently rather than as unexpected database failures.
Review whether callers of these payment DB methods handle ErrPaymentNotInitiated correctly, and consider applying the same wrapper to RegisterAttempt for consistency. No urgent patch is indicated by the diff alone.
Security signals we found
Missing-payment lookups now consistently return ErrPaymentNotInitiated instead of generic database errors
Raw sql.ErrNoRows is no longer propagated as an internal database failure
Error wrapping removed at some call sites, changing how errors surface to callers
RegisterAttempt still uses the unwrapped db.FetchPayment, leaving an inconsistency
Evidence from the diff
The patch adds fetchPaymentByHash in payments/db/sql_store.go, a thin wrapper around db.FetchPayment that maps sql.ErrNoRows to ErrPaymentNotInitiated. FetchPayment, DeleteFailedAttempts, DeletePayment, SettleAttempt, and FailAttempt are updated to call the wrapper. RegisterAttempt is left calling db.FetchPayment directly but its error wrapping is removed. Several call sites switch from dbPayment.Payment.ID to dbPayment.GetPayment().ID. The commit normalizes missing-payment handling and removes redundant error wrapping, but does not itself fix a known vulnerability.
Changed components
payments/db/sql_store.goFetchPaymentDeleteFailedAttemptsDeletePaymentSettleAttemptFailAttemptRegisterAttemptInspect captured patch +34 / −20
diff --git a/payments/db/sql_store.go b/payments/db/sql_store.go
index 3de8524..fc7d4bc 100644
--- a/payments/db/sql_store.go
+++ b/payments/db/sql_store.go
@@ -801,6 +801,24 @@ func (s *SQLStore) QueryPayments(ctx context.Context, query Query) (Response,
}, nil
}
+// fetchPaymentByHash fetches a payment by its hash from the database. It is a
+// convenience wrapper around the FetchPayment method and checks for
+// no rows error and returns ErrPaymentNotInitiated if no payment is found.
+func fetchPaymentByHash(ctx context.Context, db SQLQueries,
+ paymentHash lntypes.Hash) (sqlc.FetchPaymentRow, error) {
+
+ dbPayment, err := db.FetchPayment(ctx, paymentHash[:])
+ if err != nil && !errors.Is(err, sql.ErrNoRows) {
+ return dbPayment, fmt.Errorf("failed to fetch payment: %w", err)
+ }
+
+ if errors.Is(err, sql.ErrNoRows) {
+ return dbPayment, ErrPaymentNotInitiated
+ }
+
+ return dbPayment, nil
+}
+
// FetchPayment retrieves a complete payment record from the database by its
// payment hash. The returned MPPayment includes all payment metadata such as
// creation info, payment status, current state, all HTLC attempts (both
@@ -816,13 +834,9 @@ func (s *SQLStore) FetchPayment(paymentHash lntypes.Hash) (*MPPayment, error) {
var mpPayment *MPPayment
err := s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error {
- dbPayment, err := db.FetchPayment(ctx, paymentHash[:])
- if err != nil && !errors.Is(err, sql.ErrNoRows) {
- return fmt.Errorf("failed to fetch payment: %w", err)
- }
-
- if errors.Is(err, sql.ErrNoRows) {
- return ErrPaymentNotInitiated
+ dbPayment, err := fetchPaymentByHash(ctx, db, paymentHash)
+ if err != nil {
+ return err
}
mpPayment, err = fetchPaymentWithCompleteData(
@@ -878,9 +892,9 @@ func (s *SQLStore) DeleteFailedAttempts(paymentHash lntypes.Hash) error {
}
err := s.db.ExecTx(ctx, sqldb.WriteTxOpt(), func(db SQLQueries) error {
- dbPayment, err := db.FetchPayment(ctx, paymentHash[:])
+ dbPayment, err := fetchPaymentByHash(ctx, db, paymentHash)
if err != nil {
- return fmt.Errorf("failed to fetch payment: %w", err)
+ return err
}
paymentStatus, err := computePaymentStatusFromDB(
@@ -897,7 +911,7 @@ func (s *SQLStore) DeleteFailedAttempts(paymentHash lntypes.Hash) error {
}
// Then we delete the failed attempts for this payment.
- return db.DeleteFailedAttempts(ctx, dbPayment.Payment.ID)
+ return db.DeleteFailedAttempts(ctx, dbPayment.GetPayment().ID)
}, sqldb.NoOpReset)
if err != nil {
return fmt.Errorf("failed to delete failed attempts for "+
@@ -967,10 +981,9 @@ func (s *SQLStore) DeletePayment(paymentHash lntypes.Hash,
ctx := context.TODO()
err := s.db.ExecTx(ctx, sqldb.WriteTxOpt(), func(db SQLQueries) error {
- dbPayment, err := db.FetchPayment(ctx, paymentHash[:])
+ dbPayment, err := fetchPaymentByHash(ctx, db, paymentHash)
if err != nil {
- return fmt.Errorf("failed to fetch "+
- "payment: %w", err)
+ return err
}
paymentStatus, err := computePaymentStatusFromDB(
@@ -989,13 +1002,13 @@ func (s *SQLStore) DeletePayment(paymentHash lntypes.Hash,
// If we are only deleting failed HTLCs, we delete them.
if failedHtlcsOnly {
return db.DeleteFailedAttempts(
- ctx, dbPayment.Payment.ID,
+ ctx, dbPayment.GetPayment().ID,
)
}
// In case we are not deleting failed HTLCs, we delete the
// payment which will cascade delete all related data.
- return db.DeletePayment(ctx, dbPayment.Payment.ID)
+ return db.DeletePayment(ctx, dbPayment.GetPayment().ID)
}, sqldb.NoOpReset)
if err != nil {
return fmt.Errorf("failed to delete failed attempts for "+
@@ -1269,7 +1282,7 @@ func (s *SQLStore) RegisterAttempt(paymentHash lntypes.Hash,
// Make sure the payment exists.
dbPayment, err := db.FetchPayment(ctx, paymentHash[:])
if err != nil {
- return fmt.Errorf("failed to fetch payment: %w", err)
+ return err
}
// We fetch the complete payment to determine if the payment is
@@ -1393,9 +1406,9 @@ func (s *SQLStore) SettleAttempt(paymentHash lntypes.Hash,
var mpPayment *MPPayment
err := s.db.ExecTx(ctx, sqldb.WriteTxOpt(), func(db SQLQueries) error {
- dbPayment, err := db.FetchPayment(ctx, paymentHash[:])
+ dbPayment, err := fetchPaymentByHash(ctx, db, paymentHash)
if err != nil {
- return fmt.Errorf("failed to fetch payment: %w", err)
+ return err
}
paymentStatus, err := computePaymentStatusFromDB(
@@ -1468,9 +1481,10 @@ func (s *SQLStore) FailAttempt(paymentHash lntypes.Hash,
var mpPayment *MPPayment
err := s.db.ExecTx(ctx, sqldb.WriteTxOpt(), func(db SQLQueries) error {
- dbPayment, err := db.FetchPayment(ctx, paymentHash[:])
+ // Make sure the payment exists.
+ dbPayment, err := fetchPaymentByHash(ctx, db, paymentHash)
if err != nil {
- return fmt.Errorf("failed to fetch payment: %w", err)
+ return err
}
paymentStatus, err := computePaymentStatusFromDB(
Why this scored 35/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.