paymentsdb: implement FailAttempt for sql backend

This commit is contained in:
ziggie committed 2026-02-25 08:02:15 +01:00
1 parent fd6796e561
commit 53a7877af0
1 file changed
+93
+93
View File
@@ -1,6 +1,7 @@
package paymentsdb package paymentsdb
import ( import (
"bytes"
"context" "context"
"database/sql" "database/sql"
"errors" "errors"
@@ -76,6 +77,7 @@ type SQLQueries interface {
InsertPaymentHopCustomRecord(ctx context.Context, arg sqlc.InsertPaymentHopCustomRecordParams) error InsertPaymentHopCustomRecord(ctx context.Context, arg sqlc.InsertPaymentHopCustomRecordParams) error
SettleAttempt(ctx context.Context, arg sqlc.SettleAttemptParams) error SettleAttempt(ctx context.Context, arg sqlc.SettleAttemptParams) error
FailAttempt(ctx context.Context, arg sqlc.FailAttemptParams) error
FailPayment(ctx context.Context, arg sqlc.FailPaymentParams) (sql.Result, error) FailPayment(ctx context.Context, arg sqlc.FailPaymentParams) (sql.Result, error)
@@ -1363,6 +1365,97 @@ func (s *SQLStore) SettleAttempt(paymentHash lntypes.Hash,
return mpPayment, nil return mpPayment, nil
} }
// FailAttempt marks the specified HTLC attempt as failed, recording the
// failure reason, failure time, optional failure message, and the index of the
// node in the route that generated the failure. This information is atomically
// saved to the database for debugging and route optimization purposes.
//
// For single-path payments, failing the only attempt may lead to the payment
// being retried or ultimately failed via the Fail method. For multi-shard
// (MPP/AMP) payments, individual shard failures don't necessarily fail the
// entire payment; additional attempts can be registered until sufficient shards
// succeed or the payment is permanently failed.
//
// Returns the updated MPPayment with the attempt marked as failed and the
// payment state recalculated. The payment status remains StatusInFlight if
// other attempts are still in flight, or may transition based on the overall
// payment state.
//
// This method is part of the PaymentControl interface, which is embedded in
// 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,
attemptID uint64, failInfo *HTLCFailInfo) (*MPPayment, error) {
ctx := context.TODO()
var mpPayment *MPPayment
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)
}
// We check if the payment is updatable before failing the
// attempt.
if err := paymentStatus.updatable(); err != nil {
return fmt.Errorf("payment is not updatable: %w", err)
}
var failureMsg bytes.Buffer
if failInfo.Message != nil {
err := lnwire.EncodeFailureMessage(
&failureMsg, failInfo.Message, 0,
)
if err != nil {
return fmt.Errorf("failed to encode "+
"failure message: %w", err)
}
}
err = db.FailAttempt(ctx, sqlc.FailAttemptParams{
AttemptIndex: int64(attemptID),
ResolutionTime: time.Now(),
ResolutionType: int32(HTLCAttemptResolutionFailed),
FailureSourceIndex: sqldb.SQLInt32(
failInfo.FailureSourceIndex,
),
HtlcFailReason: sqldb.SQLInt32(failInfo.Reason),
FailureMsg: failureMsg.Bytes(),
})
if err != nil {
return fmt.Errorf("failed to fail attempt: %w", err)
}
mpPayment, err = fetchPaymentWithCompleteData(
ctx, s.cfg.QueryCfg, db, dbPayment,
)
if err != nil {
return fmt.Errorf("failed to fetch payment with "+
"complete data: %w", err)
}
return nil
}, func() {
mpPayment = nil
})
if err != nil {
return nil, fmt.Errorf("failed to fail attempt: %w", err)
}
return mpPayment, nil
}
// Fail records the ultimate reason why a payment failed. This method stores // Fail records the ultimate reason why a payment failed. This method stores
// the failure reason for record keeping but does not enforce that all HTLC // the failure reason for record keeping but does not enforce that all HTLC
// attempts are resolved - HTLCs may still be in flight when this is called. // attempts are resolved - HTLCs may still be in flight when this is called.