paymentsdb: implement FailAttempt for sql backend
This commit is contained in:
1 file changed
+93
@@ -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.
|
||||||
|
|||||||
Reference in new issue
Block a user