mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 18:10:34 +01:00
390f3c8253
This commit introduces more granular statuses to better determine a payment's current state. Based on whether there are inflight HTLCs, the state of each of the HTLCs, and whether the payment is failed, a total of 5 states are derived, which can give a finer control over what action to take based on a given state. Also, `fetchPayment` now uses `decidePaymentStatus`. After applying the new function, the returned payment status would be different, ``` | inflight | settled | failed | reason | previous -> now | |:--------:|:-------:|:------:|:------:|:---------------------------------:| | true | true | true | yes | StatusInFlight(unchanged) | | true | true | true | no | StatusInFlight(unchanged) | | true | true | false | yes | StatusInFlight(unchanged) | | true | true | false | no | StatusInFlight(unchanged) | | true | false | true | yes | StatusInFlight(unchanged) | | true | false | true | no | StatusInFlight(unchanged) | | true | false | false | yes | StatusInFlight(unchanged) | | true | false | false | no | StatusInFlight(unchanged) | | false | true | true | yes | StatusSucceeded(unchanged) | | false | true | true | no | StatusSucceeded(unchanged) | | false | true | false | yes | StatusSucceeded(unchanged) | | false | true | false | no | StatusSucceeded(unchanged) | | false | false | true | yes | StatusFailed(unchanged) | | false | false | true | no | StatusInFlight(unchanged) | | false | false | false | yes | StatusFailed(unchanged) | | false | false | false | no | StatusInFlight -> StatusInitiated| ```
253 lines
8.1 KiB
Go
253 lines
8.1 KiB
Go
package channeldb
|
|
|
|
import "fmt"
|
|
|
|
// 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 ErrPaymentExists
|
|
|
|
// We already have an InFlight payment on the network. We will disallow
|
|
// any new payments.
|
|
case StatusInFlight:
|
|
return ErrPaymentInFlight
|
|
|
|
// The payment has been attempted and is succeeded so we won't allow
|
|
// creating it again.
|
|
case StatusSucceeded:
|
|
return ErrAlreadyPaid
|
|
|
|
// We allow retrying failed payments.
|
|
case StatusFailed:
|
|
return nil
|
|
|
|
default:
|
|
return fmt.Errorf("%w: %v", 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 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", 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 ErrPaymentAlreadySucceeded
|
|
|
|
case StatusFailed:
|
|
return ErrPaymentAlreadyFailed
|
|
|
|
default:
|
|
return fmt.Errorf("%w: %v", 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)
|
|
}
|
|
}
|