paymentsdb: implement DeleteFailedAttempts for sql backend

This commit is contained in:
ziggie committed 2026-02-25 08:02:09 +01:00
1 parent 6faf68c5fa
commit 3012eb2ca0
4 files changed
+126

No files matched your search

+2
View File
@@ -291,6 +291,8 @@ func (p *KVStore) InitPayment(paymentHash lntypes.Hash,
// DeleteFailedAttempts deletes all failed htlcs for a payment if configured // DeleteFailedAttempts deletes all failed htlcs for a payment if configured
// by the KVStore db. // by the KVStore db.
func (p *KVStore) DeleteFailedAttempts(hash lntypes.Hash) error { func (p *KVStore) DeleteFailedAttempts(hash lntypes.Hash) error {
// TODO(ziggie): Refactor to not mix application logic with database
// logic. This decision should be made in the application layer.
if !p.keepFailedPaymentAttempts { if !p.keepFailedPaymentAttempts {
const failedHtlcsOnly = true const failedHtlcsOnly = true
err := p.DeletePayment(hash, failedHtlcsOnly) err := p.DeletePayment(hash, failedHtlcsOnly)
+120
View File
@@ -712,3 +712,123 @@ func (s *SQLStore) FetchPayment(paymentHash lntypes.Hash) (*MPPayment, error) {
return mpPayment, nil return mpPayment, nil
} }
// DeleteFailedAttempts removes all failed HTLC attempts from the database for
// the specified payment, while preserving the payment record itself and any
// successful or in-flight attempts.
//
// The method performs the following validations before deletion:
// - StatusInitiated: Can delete failed attempts
// - StatusInFlight: Cannot delete, returns ErrPaymentInFlight (active HTLCs
// still on the network)
// - StatusSucceeded: Can delete failed attempts (payment completed)
// - StatusFailed: Can delete failed attempts (payment permanently failed)
//
// If the keepFailedPaymentAttempts configuration flag is enabled, this method
// returns immediately without deleting anything, allowing failed attempts to
// be retained for debugging or auditing purposes.
//
// This method is idempotent - calling it multiple times on the same payment
// has no adverse effects.
//
// This method is part of the PaymentControl interface, which is embedded in
// the PaymentWriter interface and ultimately the DB interface. It represents
// the final step (step 5) in the payment lifecycle control flow and should be
// called after a payment reaches a terminal state (succeeded or permanently
// failed) to clean up historical failed attempts.
func (s *SQLStore) DeleteFailedAttempts(paymentHash lntypes.Hash) error {
ctx := context.TODO()
// In case we are configured to keep failed payment attempts, we exit
// early.
//
// TODO(ziggie): Refactor to not mix application logic with database
// logic. This decision should be made in the application layer.
if s.keepFailedPaymentAttempts {
return nil
}
err := s.db.ExecTx(ctx, sqldb.WriteTxOpt(), func(db SQLQueries) error {
dbPayment, err := db.FetchPayment(ctx, paymentHash[:])
if err != nil {
return fmt.Errorf("failed to fetch payment: %w", err)
}
paymentStatus, err := computePaymentStatusFromDB(
ctx, db, dbPayment,
)
if err != nil {
return fmt.Errorf("failed to compute payment "+
"status: %w", err)
}
if err := paymentStatus.removable(); err != nil {
return fmt.Errorf("cannot delete failed "+
"attempts for payment %v: %w", paymentHash, err)
}
// Then we delete the failed attempts for this payment.
return db.DeleteFailedAttempts(ctx, dbPayment.Payment.ID)
}, sqldb.NoOpReset)
if err != nil {
return fmt.Errorf("failed to delete failed attempts for "+
"payment %v: %w", paymentHash, err)
}
return nil
}
// computePaymentStatusFromDB computes the payment status by fetching minimal
// data from the database. This is a lightweight query optimized for SQL that
// doesn't load route data, making it significantly more efficient than
// FetchPayment when only the status is needed.
func computePaymentStatusFromDB(ctx context.Context, db SQLQueries,
dbPayment sqlc.PaymentAndIntent) (PaymentStatus, error) {
payment := dbPayment.GetPayment()
resolutionTypes, err := db.FetchHtlcAttemptResolutionsForPayment(
ctx, payment.ID,
)
if err != nil {
return 0, fmt.Errorf("failed to fetch htlc resolutions: %w",
err)
}
// Build minimal HTLCAttempt slice with only resolution info.
htlcs := make([]HTLCAttempt, len(resolutionTypes))
for i, resType := range resolutionTypes {
if !resType.Valid {
// NULL resolution_type means in-flight (no Settle, no
// Failure).
continue
}
switch HTLCAttemptResolutionType(resType.Int32) {
case HTLCAttemptResolutionSettled:
// Mark as settled (preimage details not needed for
// status).
htlcs[i].Settle = &HTLCSettleInfo{}
case HTLCAttemptResolutionFailed:
// Mark as failed (failure details not needed for
// status).
htlcs[i].Failure = &HTLCFailInfo{}
}
}
// Convert fail reason to FailureReason pointer.
var failureReason *FailureReason
if payment.FailReason.Valid {
reason := FailureReason(payment.FailReason.Int32)
failureReason = &reason
}
// Use the existing status decision logic.
status, err := decidePaymentStatus(htlcs, failureReason)
if err != nil {
return 0, fmt.Errorf("failed to decide payment status: %w", err)
}
return status, nil
}
+2
View File
@@ -29,6 +29,8 @@ DELETE FROM payment_htlc_attempts WHERE payment_id = $1 AND attempt_index IN (
) )
` `
// Delete all failed HTLC attempts for the given payment. Resolution type 2
// indicates a failed attempt.
func (q *Queries) DeleteFailedAttempts(ctx context.Context, paymentID int64) error { func (q *Queries) DeleteFailedAttempts(ctx context.Context, paymentID int64) error {
_, err := q.db.ExecContext(ctx, deleteFailedAttempts, paymentID) _, err := q.db.ExecContext(ctx, deleteFailedAttempts, paymentID)
return err return err
+2
View File
@@ -22,6 +22,8 @@ type Querier interface {
DeleteChannelPolicyExtraTypes(ctx context.Context, channelPolicyID int64) error DeleteChannelPolicyExtraTypes(ctx context.Context, channelPolicyID int64) error
DeleteChannels(ctx context.Context, ids []int64) error DeleteChannels(ctx context.Context, ids []int64) error
DeleteExtraNodeType(ctx context.Context, arg DeleteExtraNodeTypeParams) error DeleteExtraNodeType(ctx context.Context, arg DeleteExtraNodeTypeParams) error
// Delete all failed HTLC attempts for the given payment. Resolution type 2
// indicates a failed attempt.
DeleteFailedAttempts(ctx context.Context, paymentID int64) error DeleteFailedAttempts(ctx context.Context, paymentID int64) error
DeleteInvoice(ctx context.Context, arg DeleteInvoiceParams) (sql.Result, error) DeleteInvoice(ctx context.Context, arg DeleteInvoiceParams) (sql.Result, error)
DeleteNode(ctx context.Context, id int64) error DeleteNode(ctx context.Context, id int64) error