mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-08-30 15:40:59 +02:00
We introduce a new package paymentsDB and start by moving the payment specifc errors from the channeldb package to the paymentsDB package. We also fix linter issues which showed up due to changing this code part.
260 lines
8.3 KiB
Go
260 lines
8.3 KiB
Go
package channeldb
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
paymentsdb "github.com/lightningnetwork/lnd/payments/db"
|
|
)
|
|
|
|
// PaymentStatus represent current status of payment.
|
|
type PaymentStatus byte
|
|
|
|
const (
|
|
// NOTE: PaymentStatus = 0 was previously used for status unknown and
|
|
// is now deprecated.
|
|
|
|
// StatusInitiated is the status where a payment has just been
|
|
// initiated.
|
|
StatusInitiated PaymentStatus = 1
|
|
|
|
// StatusInFlight is the status where a payment has been initiated, but
|
|
// a response has not been received.
|
|
StatusInFlight PaymentStatus = 2
|
|
|
|
// StatusSucceeded is the status where a payment has been initiated and
|
|
// the payment was completed successfully.
|
|
StatusSucceeded PaymentStatus = 3
|
|
|
|
// StatusFailed is the status where a payment has been initiated and a
|
|
// failure result has come back.
|
|
StatusFailed PaymentStatus = 4
|
|
)
|
|
|
|
// errPaymentStatusUnknown is returned when a payment has an unknown status.
|
|
var errPaymentStatusUnknown = fmt.Errorf("unknown payment status")
|
|
|
|
// String returns readable representation of payment status.
|
|
func (ps PaymentStatus) String() string {
|
|
switch ps {
|
|
case StatusInitiated:
|
|
return "Initiated"
|
|
|
|
case StatusInFlight:
|
|
return "In Flight"
|
|
|
|
case StatusSucceeded:
|
|
return "Succeeded"
|
|
|
|
case StatusFailed:
|
|
return "Failed"
|
|
|
|
default:
|
|
return "Unknown"
|
|
}
|
|
}
|
|
|
|
// initializable returns an error to specify whether initiating the payment
|
|
// with its current status is allowed. A payment can only be initialized if it
|
|
// hasn't been created yet or already failed.
|
|
func (ps PaymentStatus) initializable() error {
|
|
switch ps {
|
|
// The payment has been created already. We will disallow creating it
|
|
// again in case other goroutines have already been creating HTLCs for
|
|
// it.
|
|
case StatusInitiated:
|
|
return paymentsdb.ErrPaymentExists
|
|
|
|
// We already have an InFlight payment on the network. We will disallow
|
|
// any new payments.
|
|
case StatusInFlight:
|
|
return paymentsdb.ErrPaymentInFlight
|
|
|
|
// The payment has been attempted and is succeeded so we won't allow
|
|
// creating it again.
|
|
case StatusSucceeded:
|
|
return paymentsdb.ErrAlreadyPaid
|
|
|
|
// We allow retrying failed payments.
|
|
case StatusFailed:
|
|
return nil
|
|
|
|
default:
|
|
return fmt.Errorf("%w: %v", paymentsdb.ErrUnknownPaymentStatus,
|
|
ps)
|
|
}
|
|
}
|
|
|
|
// removable returns an error to specify whether deleting the payment with its
|
|
// current status is allowed. A payment cannot be safely deleted if it has
|
|
// inflight HTLCs.
|
|
func (ps PaymentStatus) removable() error {
|
|
switch ps {
|
|
// The payment has been created but has no HTLCs and can be removed.
|
|
case StatusInitiated:
|
|
return nil
|
|
|
|
// There are still inflight HTLCs and the payment needs to wait for the
|
|
// final outcomes.
|
|
case StatusInFlight:
|
|
return paymentsdb.ErrPaymentInFlight
|
|
|
|
// The payment has been attempted and is succeeded and is allowed to be
|
|
// removed.
|
|
case StatusSucceeded:
|
|
return nil
|
|
|
|
// Failed payments are allowed to be removed.
|
|
case StatusFailed:
|
|
return nil
|
|
|
|
default:
|
|
return fmt.Errorf("%w: %v", paymentsdb.ErrUnknownPaymentStatus,
|
|
ps)
|
|
}
|
|
}
|
|
|
|
// updatable returns an error to specify whether the payment's HTLCs can be
|
|
// updated. A payment can update its HTLCs when it has inflight HTLCs.
|
|
func (ps PaymentStatus) updatable() error {
|
|
switch ps {
|
|
// Newly created payments can be updated.
|
|
case StatusInitiated:
|
|
return nil
|
|
|
|
// Inflight payments can be updated.
|
|
case StatusInFlight:
|
|
return nil
|
|
|
|
// If the payment has a terminal condition, we won't allow any updates.
|
|
case StatusSucceeded:
|
|
return paymentsdb.ErrPaymentAlreadySucceeded
|
|
|
|
case StatusFailed:
|
|
return paymentsdb.ErrPaymentAlreadyFailed
|
|
|
|
default:
|
|
return fmt.Errorf("%w: %v", paymentsdb.ErrUnknownPaymentStatus,
|
|
ps)
|
|
}
|
|
}
|
|
|
|
// decidePaymentStatus uses the payment's DB state to determine a memory status
|
|
// that's used by the payment router to decide following actions.
|
|
// Together, we use four variables to determine the payment's status,
|
|
// - inflight: whether there are any pending HTLCs.
|
|
// - settled: whether any of the HTLCs has been settled.
|
|
// - htlc failed: whether any of the HTLCs has been failed.
|
|
// - payment failed: whether the payment has been marked as failed.
|
|
//
|
|
// Based on the above variables, we derive the status using the following
|
|
// table,
|
|
// | inflight | settled | htlc failed | payment failed | status |
|
|
// |:--------:|:-------:|:-----------:|:--------------:|:--------------------:|
|
|
// | true | true | true | true | StatusInFlight |
|
|
// | true | true | true | false | StatusInFlight |
|
|
// | true | true | false | true | StatusInFlight |
|
|
// | true | true | false | false | StatusInFlight |
|
|
// | true | false | true | true | StatusInFlight |
|
|
// | true | false | true | false | StatusInFlight |
|
|
// | true | false | false | true | StatusInFlight |
|
|
// | true | false | false | false | StatusInFlight |
|
|
// | false | true | true | true | StatusSucceeded |
|
|
// | false | true | true | false | StatusSucceeded |
|
|
// | false | true | false | true | StatusSucceeded |
|
|
// | false | true | false | false | StatusSucceeded |
|
|
// | false | false | true | true | StatusFailed |
|
|
// | false | false | true | false | StatusInFlight |
|
|
// | false | false | false | true | StatusFailed |
|
|
// | false | false | false | false | StatusInitiated |
|
|
//
|
|
// When `inflight`, `settled`, `htlc failed`, and `payment failed` are false,
|
|
// this indicates the payment is newly created and hasn't made any HTLCs yet.
|
|
// When `inflight` and `settled` are false, `htlc failed` is true yet `payment
|
|
// failed` is false, this indicates all the payment's HTLCs have occurred a
|
|
// temporarily failure and the payment is still in-flight.
|
|
func decidePaymentStatus(htlcs []HTLCAttempt,
|
|
reason *FailureReason) (PaymentStatus, error) {
|
|
|
|
var (
|
|
inflight bool
|
|
htlcSettled bool
|
|
htlcFailed bool
|
|
paymentFailed bool
|
|
)
|
|
|
|
// If we have a failure reason, the payment is failed.
|
|
if reason != nil {
|
|
paymentFailed = true
|
|
}
|
|
|
|
// Go through all HTLCs for this payment, check whether we have any
|
|
// settled HTLC, and any still in-flight.
|
|
for _, h := range htlcs {
|
|
if h.Failure != nil {
|
|
htlcFailed = true
|
|
continue
|
|
}
|
|
|
|
if h.Settle != nil {
|
|
htlcSettled = true
|
|
continue
|
|
}
|
|
|
|
// If any of the HTLCs are not failed nor settled, we
|
|
// still have inflight HTLCs.
|
|
inflight = true
|
|
}
|
|
|
|
// Use the DB state to determine the status of the payment.
|
|
switch {
|
|
// If we have inflight HTLCs, no matter we have settled or failed
|
|
// HTLCs, or the payment failed, we still consider it inflight so we
|
|
// inform upper systems to wait for the results.
|
|
case inflight:
|
|
return StatusInFlight, nil
|
|
|
|
// If we have no in-flight HTLCs, and at least one of the HTLCs is
|
|
// settled, the payment succeeded.
|
|
//
|
|
// NOTE: when reaching this case, paymentFailed could be true, which
|
|
// means we have a conflicting state for this payment. We choose to
|
|
// mark the payment as succeeded because it's the receiver's
|
|
// responsibility to only settle the payment iff all HTLCs are
|
|
// received.
|
|
case htlcSettled:
|
|
return StatusSucceeded, nil
|
|
|
|
// If we have no in-flight HTLCs, and the payment failure is set, the
|
|
// payment is considered failed.
|
|
//
|
|
// NOTE: when reaching this case, settled must be false.
|
|
case paymentFailed:
|
|
return StatusFailed, nil
|
|
|
|
// If we have no in-flight HTLCs, yet the payment is NOT failed, it
|
|
// means all the HTLCs are failed. In this case we can attempt more
|
|
// HTLCs.
|
|
//
|
|
// NOTE: when reaching this case, both settled and paymentFailed must
|
|
// be false.
|
|
case htlcFailed:
|
|
return StatusInFlight, nil
|
|
|
|
// If none of the HTLCs is either settled or failed, and we have no
|
|
// inflight HTLCs, this means the payment has no HTLCs created yet.
|
|
//
|
|
// NOTE: when reaching this case, both settled and paymentFailed must
|
|
// be false.
|
|
case !htlcFailed:
|
|
return StatusInitiated, nil
|
|
|
|
// Otherwise an impossible state is reached.
|
|
//
|
|
// NOTE: we should never end up here.
|
|
default:
|
|
log.Error("Impossible payment state reached")
|
|
return 0, fmt.Errorf("%w: payment is corrupted",
|
|
errPaymentStatusUnknown)
|
|
}
|
|
}
|