payments/migration1: freeze core payment code
Copy the core payments/db code into payments/db/migration1 and add the required sqlc-generated types/queries from sqldb/sqlc. This effectively freezes the migration code so it stays robust against future query or schema changes in the main payments package. Replace the delegation to channeldb.ReadElement/WriteElement with self-contained, frozen implementations that only handle the exact types required by this migration package. This removes the dependency on the live channeldb codec so that future changes to channeldb serialization cannot silently corrupt or break the migration. UnknownElementType is also defined locally for the same reason.
This commit is contained in:
17 files changed
+7992
No files matched your search
@@ -0,0 +1,198 @@
|
||||
package migration1
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
)
|
||||
|
||||
// Big endian is the preferred byte order, due to cursor scans over
|
||||
// integer keys iterating in order.
|
||||
var byteOrder = binary.BigEndian
|
||||
|
||||
// UnknownElementType is an error returned when the codec is unable to encode
|
||||
// or decode a particular type.
|
||||
//
|
||||
// NOTE: This is a frozen, self-contained copy of the serialization error type
|
||||
// to ensure that this migration package has no dependency on live packages
|
||||
// whose implementation may change after this migration was released.
|
||||
type UnknownElementType struct {
|
||||
method string
|
||||
element interface{}
|
||||
}
|
||||
|
||||
// NewUnknownElementType creates a new UnknownElementType error from the passed
|
||||
// method name and element.
|
||||
func NewUnknownElementType(method string, el interface{}) UnknownElementType {
|
||||
return UnknownElementType{method: method, element: el}
|
||||
}
|
||||
|
||||
// Error returns the name of the method that encountered the error, as well as
|
||||
// the type that was unsupported.
|
||||
func (e UnknownElementType) Error() string {
|
||||
return fmt.Sprintf("Unknown type in %s: %T", e.method, e.element)
|
||||
}
|
||||
|
||||
// WriteElement serializes a single element into the provided io.Writer using
|
||||
// big-endian byte order. Only the types required by the migration1 package are
|
||||
// supported. This is a frozen, self-contained copy of the serialization logic
|
||||
// to ensure that this migration package has no dependency on live packages
|
||||
// whose implementation may change after this migration was released.
|
||||
func WriteElement(w io.Writer, element interface{}) error {
|
||||
switch e := element.(type) {
|
||||
case paymentIndexType:
|
||||
if err := binary.Write(w, byteOrder, e); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case uint8:
|
||||
if err := binary.Write(w, byteOrder, e); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case uint32:
|
||||
if err := binary.Write(w, byteOrder, e); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case uint64:
|
||||
if err := binary.Write(w, byteOrder, e); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case lnwire.MilliSatoshi:
|
||||
if err := binary.Write(w, byteOrder, uint64(e)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case [32]byte:
|
||||
if _, err := w.Write(e[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case []byte:
|
||||
if err := wire.WriteVarBytes(w, 0, e); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
default:
|
||||
return UnknownElementType{"WriteElement", e}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteElements serializes a variadic list of elements into the given
|
||||
// io.Writer.
|
||||
func WriteElements(w io.Writer, elements ...interface{}) error {
|
||||
for _, element := range elements {
|
||||
if err := WriteElement(w, element); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadElement deserializes a single element from the provided io.Reader using
|
||||
// big-endian byte order. Only the types required by the migration1 package are
|
||||
// supported. This is a frozen, self-contained copy of the deserialization
|
||||
// logic to ensure that this migration package has no dependency on live
|
||||
// packages whose implementation may change after this migration was released.
|
||||
func ReadElement(r io.Reader, element interface{}) error {
|
||||
switch e := element.(type) {
|
||||
case *paymentIndexType:
|
||||
if err := binary.Read(r, byteOrder, e); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case *uint8:
|
||||
if err := binary.Read(r, byteOrder, e); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case *uint32:
|
||||
if err := binary.Read(r, byteOrder, e); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case *uint64:
|
||||
if err := binary.Read(r, byteOrder, e); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case *lnwire.MilliSatoshi:
|
||||
var a uint64
|
||||
if err := binary.Read(r, byteOrder, &a); err != nil {
|
||||
return err
|
||||
}
|
||||
*e = lnwire.MilliSatoshi(a)
|
||||
|
||||
case *[32]byte:
|
||||
if _, err := io.ReadFull(r, e[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case *[]byte:
|
||||
b, err := wire.ReadVarBytes(r, 0, 66000, "[]byte")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*e = b
|
||||
|
||||
default:
|
||||
return UnknownElementType{"ReadElement", e}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadElements deserializes the provided io.Reader into a variadic list of
|
||||
// target elements.
|
||||
func ReadElements(r io.Reader, elements ...interface{}) error {
|
||||
for _, element := range elements {
|
||||
if err := ReadElement(r, element); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// deserializeTime deserializes time as unix nanoseconds.
|
||||
func deserializeTime(r io.Reader) (time.Time, error) {
|
||||
var scratch [8]byte
|
||||
if _, err := io.ReadFull(r, scratch[:]); err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
|
||||
// Convert to time.Time. Interpret unix nano time zero as a zero
|
||||
// time.Time value.
|
||||
unixNano := byteOrder.Uint64(scratch[:])
|
||||
if unixNano == 0 {
|
||||
return time.Time{}, nil
|
||||
}
|
||||
|
||||
return time.Unix(0, int64(unixNano)), nil
|
||||
}
|
||||
|
||||
// serializeTime serializes time as unix nanoseconds.
|
||||
func serializeTime(w io.Writer, t time.Time) error {
|
||||
var scratch [8]byte
|
||||
|
||||
// Convert to unix nano seconds, but only if time is non-zero. Calling
|
||||
// UnixNano() on a zero time yields an undefined result.
|
||||
var unixNano int64
|
||||
if !t.IsZero() {
|
||||
unixNano = t.UnixNano()
|
||||
}
|
||||
|
||||
byteOrder.PutUint64(scratch[:], uint64(unixNano))
|
||||
_, err := w.Write(scratch[:])
|
||||
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
package migration1
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
// ErrAlreadyPaid signals we have already paid this payment hash.
|
||||
ErrAlreadyPaid = errors.New("invoice is already paid")
|
||||
|
||||
// ErrPaymentInFlight signals that payment for this payment hash is
|
||||
// already "in flight" on the network.
|
||||
ErrPaymentInFlight = errors.New("payment is in transition")
|
||||
|
||||
// ErrPaymentExists is returned when we try to initialize an already
|
||||
// existing payment that is not failed.
|
||||
ErrPaymentExists = errors.New("payment already exists")
|
||||
|
||||
// ErrPaymentInternal is returned when performing the payment has a
|
||||
// conflicting state, such as,
|
||||
// - payment has StatusSucceeded but remaining amount is not zero.
|
||||
// - payment has StatusInitiated but remaining amount is zero.
|
||||
// - payment has StatusFailed but remaining amount is zero.
|
||||
ErrPaymentInternal = errors.New("internal error")
|
||||
|
||||
// ErrPaymentNotInitiated is returned if the payment wasn't initiated.
|
||||
ErrPaymentNotInitiated = errors.New("payment isn't initiated")
|
||||
|
||||
// ErrPaymentAlreadySucceeded is returned in the event we attempt to
|
||||
// change the status of a payment already succeeded.
|
||||
ErrPaymentAlreadySucceeded = errors.New("payment is already succeeded")
|
||||
|
||||
// ErrPaymentAlreadyFailed is returned in the event we attempt to alter
|
||||
// a failed payment.
|
||||
ErrPaymentAlreadyFailed = errors.New("payment has already failed")
|
||||
|
||||
// ErrUnknownPaymentStatus is returned when we do not recognize the
|
||||
// existing state of a payment.
|
||||
ErrUnknownPaymentStatus = errors.New("unknown payment status")
|
||||
|
||||
// ErrPaymentTerminal is returned if we attempt to alter a payment that
|
||||
// already has reached a terminal condition.
|
||||
ErrPaymentTerminal = errors.New("payment has reached terminal " +
|
||||
"condition")
|
||||
|
||||
// ErrAttemptAlreadySettled is returned if we try to alter an already
|
||||
// settled HTLC attempt.
|
||||
ErrAttemptAlreadySettled = errors.New("attempt already settled")
|
||||
|
||||
// ErrAttemptAlreadyFailed is returned if we try to alter an already
|
||||
// failed HTLC attempt.
|
||||
ErrAttemptAlreadyFailed = errors.New("attempt already failed")
|
||||
|
||||
// ErrValueMismatch is returned if we try to register a non-MPP attempt
|
||||
// with an amount that doesn't match the payment amount.
|
||||
ErrValueMismatch = errors.New("attempted value doesn't match payment " +
|
||||
"amount")
|
||||
|
||||
// ErrValueExceedsAmt is returned if we try to register an attempt that
|
||||
// would take the total sent amount above the payment amount.
|
||||
ErrValueExceedsAmt = errors.New("attempted value exceeds payment " +
|
||||
"amount")
|
||||
|
||||
// ErrNonMPPayment is returned if we try to register an MPP attempt for
|
||||
// a payment that already has a non-MPP attempt registered.
|
||||
ErrNonMPPayment = errors.New("payment has non-MPP attempts")
|
||||
|
||||
// ErrMPPayment is returned if we try to register a non-MPP attempt for
|
||||
// a payment that already has an MPP attempt registered.
|
||||
ErrMPPayment = errors.New("payment has MPP attempts")
|
||||
|
||||
// ErrMPPRecordInBlindedPayment is returned if we try to register an
|
||||
// attempt with an MPP record for a payment to a blinded path.
|
||||
ErrMPPRecordInBlindedPayment = errors.New("blinded payment cannot " +
|
||||
"contain MPP records")
|
||||
|
||||
// ErrBlindedPaymentTotalAmountMismatch is returned if we try to
|
||||
// register an HTLC shard to a blinded route where the total amount
|
||||
// doesn't match existing shards.
|
||||
ErrBlindedPaymentTotalAmountMismatch = errors.New("blinded path " +
|
||||
"total amount mismatch")
|
||||
|
||||
// ErrMixedBlindedAndNonBlindedPayments is returned if we try to
|
||||
// register a non-blinded attempt to a payment which uses a blinded
|
||||
// paths or vice versa.
|
||||
ErrMixedBlindedAndNonBlindedPayments = errors.New("mixed blinded and " +
|
||||
"non-blinded payments")
|
||||
|
||||
// ErrBlindedPaymentMissingTotalAmount is returned if we try to
|
||||
// register a blinded payment attempt where the final hop doesn't set
|
||||
// the total amount.
|
||||
ErrBlindedPaymentMissingTotalAmount = errors.New("blinded payment " +
|
||||
"final hop must set total amount")
|
||||
|
||||
// ErrMPPPaymentAddrMismatch is returned if we try to register an MPP
|
||||
// shard where the payment address doesn't match existing shards.
|
||||
ErrMPPPaymentAddrMismatch = errors.New("payment address mismatch")
|
||||
|
||||
// ErrMPPTotalAmountMismatch is returned if we try to register an MPP
|
||||
// shard where the total amount doesn't match existing shards.
|
||||
ErrMPPTotalAmountMismatch = errors.New("mp payment total amount " +
|
||||
"mismatch")
|
||||
|
||||
// ErrPaymentPendingSettled is returned when we try to add a new
|
||||
// attempt to a payment that has at least one of its HTLCs settled.
|
||||
ErrPaymentPendingSettled = errors.New("payment has settled htlcs")
|
||||
|
||||
// ErrPaymentPendingFailed is returned when we try to add a new attempt
|
||||
// to a payment that already has a failure reason.
|
||||
ErrPaymentPendingFailed = errors.New("payment has failure reason")
|
||||
|
||||
// ErrSentExceedsTotal is returned if the payment's current total sent
|
||||
// amount exceed the total amount.
|
||||
ErrSentExceedsTotal = errors.New("total sent exceeds total amount")
|
||||
|
||||
// ErrNoAttemptInfo is returned when no attempt info is stored yet.
|
||||
ErrNoAttemptInfo = errors.New("unable to find attempt info for " +
|
||||
"inflight payment")
|
||||
)
|
||||
|
||||
// KV backend specific errors.
|
||||
var (
|
||||
// ErrNoSequenceNumber is returned if we look up a payment which does
|
||||
// not have a sequence number.
|
||||
ErrNoSequenceNumber = errors.New("sequence number not found")
|
||||
|
||||
// ErrDuplicateNotFound is returned when we lookup a payment by its
|
||||
// index and cannot find a payment with a matching sequence number.
|
||||
ErrDuplicateNotFound = errors.New("duplicate payment not found")
|
||||
|
||||
// ErrNoDuplicateBucket is returned when we expect to find duplicates
|
||||
// when looking up a payment from its index, but the payment does not
|
||||
// have any.
|
||||
ErrNoDuplicateBucket = errors.New("expected duplicate bucket")
|
||||
|
||||
// ErrNoDuplicateNestedBucket is returned if we do not find duplicate
|
||||
// payments in their own sub-bucket.
|
||||
ErrNoDuplicateNestedBucket = errors.New("nested duplicate bucket not " +
|
||||
"found")
|
||||
|
||||
// ErrNoDuplicateSequenceNumber is returned when a duplicate payment
|
||||
// sub-bucket does not contain the sequence number key.
|
||||
ErrNoDuplicateSequenceNumber = errors.New("duplicate payment " +
|
||||
"sequence number not found")
|
||||
|
||||
// ErrNoSequenceNrIndex is returned when an attempt to lookup a payment
|
||||
// index is made for a sequence number that is not indexed.
|
||||
//
|
||||
// NOTE: Only used for the kv backend.
|
||||
ErrNoSequenceNrIndex = errors.New("payment sequence number index " +
|
||||
"does not exist")
|
||||
|
||||
// errMaxPaymentsReached is used internally to signal that the maximum
|
||||
// number of payments has been reached during a paginated query.
|
||||
errMaxPaymentsReached = errors.New("max payments reached")
|
||||
)
|
||||
@@ -0,0 +1,140 @@
|
||||
package migration1
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
)
|
||||
|
||||
// DB represents the interface to the underlying payments database.
|
||||
type DB interface {
|
||||
PaymentReader
|
||||
PaymentWriter
|
||||
}
|
||||
|
||||
// PaymentReader represents the interface to read operations from the payments
|
||||
// database.
|
||||
type PaymentReader interface {
|
||||
// QueryPayments queries the payments database and should support
|
||||
// pagination.
|
||||
QueryPayments(ctx context.Context, query Query) (Response, error)
|
||||
|
||||
// FetchPayment fetches the payment corresponding to the given payment
|
||||
// hash.
|
||||
FetchPayment(ctx context.Context,
|
||||
paymentHash lntypes.Hash) (*MPPayment, error)
|
||||
|
||||
// FetchInFlightPayments returns all payments with status InFlight.
|
||||
FetchInFlightPayments(ctx context.Context) ([]*MPPayment, error)
|
||||
}
|
||||
|
||||
// PaymentWriter represents the interface to write operations to the payments
|
||||
// database.
|
||||
type PaymentWriter interface {
|
||||
// DeletePayment deletes a payment from the DB given its payment hash.
|
||||
DeletePayment(ctx context.Context, paymentHash lntypes.Hash,
|
||||
failedAttemptsOnly bool) error
|
||||
|
||||
// DeletePayments deletes all payments from the DB given the specified
|
||||
// flags.
|
||||
DeletePayments(ctx context.Context, failedOnly,
|
||||
failedAttemptsOnly bool) (int, error)
|
||||
|
||||
PaymentControl
|
||||
}
|
||||
|
||||
// PaymentControl represents the interface to control the payment lifecycle and
|
||||
// its database operations. This interface represents the control flow of how
|
||||
// a payment should be handled in the database. They are not just writing
|
||||
// operations but they inherently represent the flow of a payment. The methods
|
||||
// are called in the following order.
|
||||
//
|
||||
// 1. InitPayment.
|
||||
// 2. RegisterAttempt (a payment can have multiple attempts).
|
||||
// 3. SettleAttempt or FailAttempt (attempts can also fail as long as the
|
||||
// sending amount will be eventually settled).
|
||||
// 4. Payment succeeds or "Fail" is called.
|
||||
// 5. DeleteFailedAttempts is called which will delete all failed attempts
|
||||
// for a payment to clean up the database.
|
||||
type PaymentControl interface {
|
||||
// InitPayment checks that no other payment with the same payment hash
|
||||
// exists in the database before creating a new payment. However, it
|
||||
// should allow the user making a subsequent payment if the payment is
|
||||
// in a Failed state.
|
||||
InitPayment(context.Context, lntypes.Hash, *PaymentCreationInfo) error
|
||||
|
||||
// RegisterAttempt atomically records the provided HTLCAttemptInfo.
|
||||
//
|
||||
// IMPORTANT: Callers MUST serialize calls to RegisterAttempt for the
|
||||
// same payment hash. Concurrent calls will result in race conditions
|
||||
// where both calls read the same initial payment state, validate
|
||||
// against stale data, and could cause overpayment. For example:
|
||||
// - Both goroutines fetch payment with 400 sats sent
|
||||
// - Both validate sending 650 sats won't overpay (within limit)
|
||||
// - Both commit successfully
|
||||
// - Result: 1700 sats sent, exceeding the payment amount
|
||||
// The payment router/controller layer is responsible for ensuring
|
||||
// serialized access per payment hash.
|
||||
RegisterAttempt(context.Context, lntypes.Hash,
|
||||
*HTLCAttemptInfo) (*MPPayment, error)
|
||||
|
||||
// SettleAttempt marks the given attempt settled with the preimage. If
|
||||
// this is a multi shard payment, this might implicitly mean the
|
||||
// full payment succeeded.
|
||||
//
|
||||
// After invoking this method, InitPayment should always return an
|
||||
// error to prevent us from making duplicate payments to the same
|
||||
// payment hash. The provided preimage is atomically saved to the DB
|
||||
// for record keeping.
|
||||
SettleAttempt(context.Context, lntypes.Hash, uint64,
|
||||
*HTLCSettleInfo) (*MPPayment, error)
|
||||
|
||||
// FailAttempt marks the given payment attempt failed.
|
||||
FailAttempt(context.Context, lntypes.Hash, uint64,
|
||||
*HTLCFailInfo) (*MPPayment, error)
|
||||
|
||||
// Fail transitions a payment into the Failed state, and records
|
||||
// the ultimate reason the payment failed. Note that this should only
|
||||
// be called when all active attempts are already failed. After
|
||||
// invoking this method, InitPayment should return nil on its next call
|
||||
// for this payment hash, allowing the user to make a subsequent
|
||||
// payment.
|
||||
Fail(context.Context, lntypes.Hash, FailureReason) (*MPPayment, error)
|
||||
|
||||
// DeleteFailedAttempts removes all failed HTLCs from the db. It should
|
||||
// be called for a given payment whenever all inflight htlcs are
|
||||
// completed, and the payment has reached a final terminal state.
|
||||
DeleteFailedAttempts(context.Context, lntypes.Hash) error
|
||||
}
|
||||
|
||||
// DBMPPayment is an interface that represents the payment state during a
|
||||
// payment lifecycle.
|
||||
type DBMPPayment interface {
|
||||
// GetState returns the current state of the payment.
|
||||
GetState() *MPPaymentState
|
||||
|
||||
// Terminated returns true if the payment is in a final state.
|
||||
Terminated() bool
|
||||
|
||||
// GetStatus returns the current status of the payment.
|
||||
GetStatus() PaymentStatus
|
||||
|
||||
// NeedWaitAttempts specifies whether the payment needs to wait for the
|
||||
// outcome of an attempt.
|
||||
NeedWaitAttempts() (bool, error)
|
||||
|
||||
// GetHTLCs returns all HTLCs of this payment.
|
||||
GetHTLCs() []HTLCAttempt
|
||||
|
||||
// InFlightHTLCs returns all HTLCs that are in flight.
|
||||
InFlightHTLCs() []HTLCAttempt
|
||||
|
||||
// AllowMoreAttempts is used to decide whether we can safely attempt
|
||||
// more HTLCs for a given payment state. Return an error if the payment
|
||||
// is in an unexpected state.
|
||||
AllowMoreAttempts() (bool, error)
|
||||
|
||||
// TerminalInfo returns the settled HTLC attempt or the payment's
|
||||
// failure reason.
|
||||
TerminalInfo() (*HTLCAttempt, *FailureReason)
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
package migration1
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/lightningnetwork/lnd/kvdb"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
)
|
||||
|
||||
var (
|
||||
// duplicatePaymentsBucket is the name of a optional sub-bucket within
|
||||
// the payment hash bucket, that is used to hold duplicate payments to a
|
||||
// payment hash. This is needed to support information from earlier
|
||||
// versions of lnd, where it was possible to pay to a payment hash more
|
||||
// than once.
|
||||
duplicatePaymentsBucket = []byte("payment-duplicate-bucket")
|
||||
|
||||
// duplicatePaymentSettleInfoKey is a key used in the payment's
|
||||
// sub-bucket to store the settle info of the payment.
|
||||
duplicatePaymentSettleInfoKey = []byte("payment-settle-info")
|
||||
|
||||
// duplicatePaymentAttemptInfoKey is a key used in the payment's
|
||||
// sub-bucket to store the info about the latest attempt that was done
|
||||
// for the payment in question.
|
||||
duplicatePaymentAttemptInfoKey = []byte("payment-attempt-info")
|
||||
|
||||
// duplicatePaymentCreationInfoKey is a key used in the payment's
|
||||
// sub-bucket to store the creation info of the payment.
|
||||
duplicatePaymentCreationInfoKey = []byte("payment-creation-info")
|
||||
|
||||
// duplicatePaymentFailInfoKey is a key used in the payment's sub-bucket
|
||||
// to store information about the reason a payment failed.
|
||||
duplicatePaymentFailInfoKey = []byte("payment-fail-info")
|
||||
|
||||
// duplicatePaymentSequenceKey is a key used in the payment's sub-bucket
|
||||
// to store the sequence number of the payment.
|
||||
duplicatePaymentSequenceKey = []byte("payment-sequence-key")
|
||||
)
|
||||
|
||||
// duplicateHTLCAttemptInfo contains static information about a specific HTLC
|
||||
// attempt for a payment. This information is used by the router to handle any
|
||||
// errors coming back after an attempt is made, and to query the switch about
|
||||
// the status of the attempt.
|
||||
type duplicateHTLCAttemptInfo struct {
|
||||
// attemptID is the unique ID used for this attempt.
|
||||
attemptID uint64
|
||||
|
||||
// sessionKey is the ephemeral key used for this attempt.
|
||||
sessionKey [btcec.PrivKeyBytesLen]byte
|
||||
|
||||
// route is the route attempted to send the HTLC.
|
||||
route Route
|
||||
}
|
||||
|
||||
// fetchDuplicatePaymentStatus fetches the payment status of the payment. If
|
||||
// the payment isn't found, it will return error `ErrPaymentNotInitiated`.
|
||||
func fetchDuplicatePaymentStatus(bucket kvdb.RBucket) (PaymentStatus, error) {
|
||||
if bucket.Get(duplicatePaymentSettleInfoKey) != nil {
|
||||
return StatusSucceeded, nil
|
||||
}
|
||||
|
||||
if bucket.Get(duplicatePaymentFailInfoKey) != nil {
|
||||
return StatusFailed, nil
|
||||
}
|
||||
|
||||
if bucket.Get(duplicatePaymentCreationInfoKey) != nil {
|
||||
return StatusInFlight, nil
|
||||
}
|
||||
|
||||
return 0, ErrPaymentNotInitiated
|
||||
}
|
||||
|
||||
func deserializeDuplicateHTLCAttemptInfo(r io.Reader) (
|
||||
*duplicateHTLCAttemptInfo, error) {
|
||||
|
||||
a := &duplicateHTLCAttemptInfo{}
|
||||
err := ReadElements(r, &a.attemptID, &a.sessionKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
a.route, err = DeserializeRoute(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func deserializeDuplicatePaymentCreationInfo(r io.Reader) (
|
||||
*PaymentCreationInfo, error) {
|
||||
|
||||
var scratch [8]byte
|
||||
|
||||
c := &PaymentCreationInfo{}
|
||||
|
||||
if _, err := io.ReadFull(r, c.PaymentIdentifier[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := io.ReadFull(r, scratch[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.Value = lnwire.MilliSatoshi(byteOrder.Uint64(scratch[:]))
|
||||
|
||||
if _, err := io.ReadFull(r, scratch[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.CreationTime = time.Unix(int64(byteOrder.Uint64(scratch[:])), 0)
|
||||
|
||||
if _, err := io.ReadFull(r, scratch[:4]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reqLen := byteOrder.Uint32(scratch[:4])
|
||||
payReq := make([]byte, reqLen)
|
||||
if reqLen > 0 {
|
||||
if _, err := io.ReadFull(r, payReq); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
c.PaymentRequest = payReq
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func fetchDuplicatePayment(bucket kvdb.RBucket) (*MPPayment, error) {
|
||||
seqBytes := bucket.Get(duplicatePaymentSequenceKey)
|
||||
if seqBytes == nil {
|
||||
return nil, fmt.Errorf("sequence number not found")
|
||||
}
|
||||
|
||||
sequenceNum := binary.BigEndian.Uint64(seqBytes)
|
||||
|
||||
// Get the payment status.
|
||||
paymentStatus, err := fetchDuplicatePaymentStatus(bucket)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get the PaymentCreationInfo.
|
||||
b := bucket.Get(duplicatePaymentCreationInfoKey)
|
||||
if b == nil {
|
||||
return nil, fmt.Errorf("creation info not found")
|
||||
}
|
||||
|
||||
r := bytes.NewReader(b)
|
||||
creationInfo, err := deserializeDuplicatePaymentCreationInfo(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get failure reason if available.
|
||||
var failureReason *FailureReason
|
||||
b = bucket.Get(duplicatePaymentFailInfoKey)
|
||||
if b != nil {
|
||||
reason := FailureReason(b[0])
|
||||
failureReason = &reason
|
||||
}
|
||||
|
||||
payment := &MPPayment{
|
||||
SequenceNum: sequenceNum,
|
||||
Info: creationInfo,
|
||||
FailureReason: failureReason,
|
||||
Status: paymentStatus,
|
||||
}
|
||||
|
||||
// Get the HTLCAttemptInfo. It can be absent.
|
||||
b = bucket.Get(duplicatePaymentAttemptInfoKey)
|
||||
if b != nil {
|
||||
r = bytes.NewReader(b)
|
||||
attempt, err := deserializeDuplicateHTLCAttemptInfo(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
htlc := HTLCAttempt{
|
||||
HTLCAttemptInfo: HTLCAttemptInfo{
|
||||
AttemptID: attempt.attemptID,
|
||||
Route: attempt.route,
|
||||
sessionKey: attempt.sessionKey,
|
||||
},
|
||||
}
|
||||
|
||||
// Get the payment preimage. This is only found for
|
||||
// successful payments.
|
||||
b = bucket.Get(duplicatePaymentSettleInfoKey)
|
||||
if b != nil {
|
||||
var preimg lntypes.Preimage
|
||||
copy(preimg[:], b)
|
||||
|
||||
htlc.Settle = &HTLCSettleInfo{
|
||||
Preimage: preimg,
|
||||
SettleTime: time.Time{},
|
||||
}
|
||||
} else {
|
||||
// Otherwise the payment must have failed.
|
||||
htlc.Failure = &HTLCFailInfo{
|
||||
FailTime: time.Time{},
|
||||
}
|
||||
}
|
||||
|
||||
payment.HTLCs = []HTLCAttempt{htlc}
|
||||
}
|
||||
|
||||
return payment, nil
|
||||
}
|
||||
|
||||
func fetchDuplicatePayments(paymentHashBucket kvdb.RBucket) ([]*MPPayment,
|
||||
error) {
|
||||
|
||||
var payments []*MPPayment
|
||||
|
||||
// For older versions of lnd, duplicate payments to a payment has was
|
||||
// possible. These will be found in a sub-bucket indexed by their
|
||||
// sequence number if available.
|
||||
dup := paymentHashBucket.NestedReadBucket(duplicatePaymentsBucket)
|
||||
if dup == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
err := dup.ForEach(func(k, v []byte) error {
|
||||
subBucket := dup.NestedReadBucket(k)
|
||||
if subBucket == nil {
|
||||
// We one bucket for each duplicate to be found.
|
||||
return fmt.Errorf("non bucket element" +
|
||||
"in duplicate bucket")
|
||||
}
|
||||
|
||||
p, err := fetchDuplicatePayment(subBucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
payments = append(payments, p)
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return payments, nil
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,32 @@
|
||||
package migration1
|
||||
|
||||
import (
|
||||
"github.com/btcsuite/btclog/v2"
|
||||
"github.com/lightningnetwork/lnd/build"
|
||||
)
|
||||
|
||||
// log is a logger that is initialized with no output filters. This
|
||||
// means the package will not perform any logging by default until the caller
|
||||
// requests it.
|
||||
var log btclog.Logger
|
||||
|
||||
// Subsystem defines the logging identifier for this subsystem.
|
||||
const Subsystem = "PYDB"
|
||||
|
||||
// The default amount of logging is none.
|
||||
func init() {
|
||||
UseLogger(build.NewSubLogger(Subsystem, nil))
|
||||
}
|
||||
|
||||
// DisableLog disables all library log output. Logging output is disabled
|
||||
// by default until UseLogger is called.
|
||||
func DisableLog() {
|
||||
UseLogger(btclog.Disabled)
|
||||
}
|
||||
|
||||
// UseLogger uses a specified Logger to output package logging info.
|
||||
// This should be used in preference to SetLogWriter if the caller is also
|
||||
// using btclog.
|
||||
func UseLogger(logger btclog.Logger) {
|
||||
log = logger
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package migration1
|
||||
|
||||
// StoreOptions holds parameters for the KVStore.
|
||||
type StoreOptions struct {
|
||||
// NoMigration allows to open the database in readonly mode
|
||||
NoMigration bool
|
||||
}
|
||||
|
||||
// DefaultOptions returns a StoreOptions populated with default values.
|
||||
func DefaultOptions() *StoreOptions {
|
||||
return &StoreOptions{
|
||||
NoMigration: false,
|
||||
}
|
||||
}
|
||||
|
||||
// OptionModifier is a function signature for modifying the default
|
||||
// StoreOptions.
|
||||
type OptionModifier func(*StoreOptions)
|
||||
|
||||
// WithNoMigration allows the database to be opened in read only mode by
|
||||
// disabling migrations.
|
||||
func WithNoMigration(b bool) OptionModifier {
|
||||
return func(o *StoreOptions) {
|
||||
o.NoMigration = b
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,689 @@
|
||||
package migration1
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
sphinx "github.com/lightningnetwork/lightning-onion"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
)
|
||||
|
||||
// FailureReason encodes the reason a payment ultimately failed.
|
||||
type FailureReason byte
|
||||
|
||||
const (
|
||||
// FailureReasonTimeout indicates that the payment did timeout before a
|
||||
// successful payment attempt was made.
|
||||
FailureReasonTimeout FailureReason = 0
|
||||
|
||||
// FailureReasonNoRoute indicates no successful route to the
|
||||
// destination was found during path finding.
|
||||
FailureReasonNoRoute FailureReason = 1
|
||||
|
||||
// FailureReasonError indicates that an unexpected error happened during
|
||||
// payment.
|
||||
FailureReasonError FailureReason = 2
|
||||
|
||||
// FailureReasonPaymentDetails indicates that either the hash is unknown
|
||||
// or the final cltv delta or amount is incorrect.
|
||||
FailureReasonPaymentDetails FailureReason = 3
|
||||
|
||||
// FailureReasonInsufficientBalance indicates that we didn't have enough
|
||||
// balance to complete the payment.
|
||||
FailureReasonInsufficientBalance FailureReason = 4
|
||||
|
||||
// FailureReasonCanceled indicates that the payment was canceled by the
|
||||
// user.
|
||||
FailureReasonCanceled FailureReason = 5
|
||||
|
||||
// TODO(joostjager): Add failure reasons for:
|
||||
// LocalLiquidityInsufficient, RemoteCapacityInsufficient.
|
||||
)
|
||||
|
||||
// Error returns a human-readable error string for the FailureReason.
|
||||
func (r FailureReason) Error() string {
|
||||
return r.String()
|
||||
}
|
||||
|
||||
// String returns a human-readable FailureReason.
|
||||
func (r FailureReason) String() string {
|
||||
switch r {
|
||||
case FailureReasonTimeout:
|
||||
return "timeout"
|
||||
case FailureReasonNoRoute:
|
||||
return "no_route"
|
||||
case FailureReasonError:
|
||||
return "error"
|
||||
case FailureReasonPaymentDetails:
|
||||
return "incorrect_payment_details"
|
||||
case FailureReasonInsufficientBalance:
|
||||
return "insufficient_balance"
|
||||
case FailureReasonCanceled:
|
||||
return "canceled"
|
||||
}
|
||||
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
// PaymentCreationInfo is the information necessary to have ready when
|
||||
// initiating a payment, moving it into state InFlight.
|
||||
type PaymentCreationInfo struct {
|
||||
// PaymentIdentifier is the hash this payment is paying to in case of
|
||||
// non-AMP payments, and the SetID for AMP payments.
|
||||
PaymentIdentifier lntypes.Hash
|
||||
|
||||
// Value is the amount we are paying.
|
||||
Value lnwire.MilliSatoshi
|
||||
|
||||
// CreationTime is the time when this payment was initiated.
|
||||
CreationTime time.Time
|
||||
|
||||
// PaymentRequest is the full payment request, if any.
|
||||
PaymentRequest []byte
|
||||
|
||||
// FirstHopCustomRecords are the TLV records that are to be sent to the
|
||||
// first hop of this payment. These records will be transmitted via the
|
||||
// wire message (UpdateAddHTLC) only and therefore do not affect the
|
||||
// onion payload size.
|
||||
FirstHopCustomRecords lnwire.CustomRecords
|
||||
}
|
||||
|
||||
// String returns a human-readable description of the payment creation info.
|
||||
func (p *PaymentCreationInfo) String() string {
|
||||
return fmt.Sprintf("payment_id=%v, amount=%v, created_at=%v",
|
||||
p.PaymentIdentifier, p.Value, p.CreationTime)
|
||||
}
|
||||
|
||||
// HTLCAttemptInfo contains static information about a specific HTLC attempt
|
||||
// for a payment. This information is used by the router to handle any errors
|
||||
// coming back after an attempt is made, and to query the switch about the
|
||||
// status of the attempt.
|
||||
type HTLCAttemptInfo struct {
|
||||
// AttemptID is the unique ID used for this attempt.
|
||||
AttemptID uint64
|
||||
|
||||
// sessionKey is the raw bytes ephemeral key used for this attempt.
|
||||
// These bytes are lazily read off disk to save ourselves the expensive
|
||||
// EC operations used by btcec.PrivKeyFromBytes.
|
||||
sessionKey [btcec.PrivKeyBytesLen]byte
|
||||
|
||||
// cachedSessionKey is our fully deserialized sesionKey. This value
|
||||
// may be nil if the attempt has just been read from disk and its
|
||||
// session key has not been used yet.
|
||||
cachedSessionKey *btcec.PrivateKey
|
||||
|
||||
// Route is the route attempted to send the HTLC.
|
||||
Route Route
|
||||
|
||||
// AttemptTime is the time at which this HTLC was attempted.
|
||||
AttemptTime time.Time
|
||||
|
||||
// Hash is the hash used for this single HTLC attempt. For AMP payments
|
||||
// this will differ across attempts, for non-AMP payments each attempt
|
||||
// will use the same hash. This can be nil for older payment attempts,
|
||||
// in which the payment's PaymentHash in the PaymentCreationInfo should
|
||||
// be used.
|
||||
Hash *lntypes.Hash
|
||||
|
||||
// onionBlob is the cached value for onion blob created from the sphinx
|
||||
// construction.
|
||||
onionBlob [lnwire.OnionPacketSize]byte
|
||||
|
||||
// circuit is the cached value for sphinx circuit.
|
||||
circuit *sphinx.Circuit
|
||||
}
|
||||
|
||||
// SessionKey returns the ephemeral key used for a htlc attempt. This function
|
||||
// performs expensive ec-ops to obtain the session key if it is not cached.
|
||||
func (h *HTLCAttemptInfo) SessionKey() *btcec.PrivateKey {
|
||||
if h.cachedSessionKey == nil {
|
||||
h.cachedSessionKey, _ = btcec.PrivKeyFromBytes(
|
||||
h.sessionKey[:],
|
||||
)
|
||||
}
|
||||
|
||||
return h.cachedSessionKey
|
||||
}
|
||||
|
||||
// HTLCAttempt contains information about a specific HTLC attempt for a given
|
||||
// payment. It contains the HTLCAttemptInfo used to send the HTLC, as well
|
||||
// as a timestamp and any known outcome of the attempt.
|
||||
type HTLCAttempt struct {
|
||||
HTLCAttemptInfo
|
||||
|
||||
// Settle is the preimage of a successful payment. This serves as a
|
||||
// proof of payment. It will only be non-nil for settled payments.
|
||||
//
|
||||
// NOTE: Can be nil if payment is not settled.
|
||||
Settle *HTLCSettleInfo
|
||||
|
||||
// Fail is a failure reason code indicating the reason the payment
|
||||
// failed. It is only non-nil for failed payments.
|
||||
//
|
||||
// NOTE: Can be nil if payment is not failed.
|
||||
Failure *HTLCFailInfo
|
||||
}
|
||||
|
||||
// HTLCSettleInfo encapsulates the information that augments an HTLCAttempt in
|
||||
// the event that the HTLC is successful.
|
||||
type HTLCSettleInfo struct {
|
||||
// Preimage is the preimage of a successful HTLC. This serves as a proof
|
||||
// of payment.
|
||||
Preimage lntypes.Preimage
|
||||
|
||||
// SettleTime is the time at which this HTLC was settled.
|
||||
SettleTime time.Time
|
||||
}
|
||||
|
||||
// HTLCFailReason is the reason an htlc failed.
|
||||
type HTLCFailReason byte
|
||||
|
||||
const (
|
||||
// HTLCFailUnknown is recorded for htlcs that failed with an unknown
|
||||
// reason.
|
||||
HTLCFailUnknown HTLCFailReason = 0
|
||||
|
||||
// HTLCFailUnreadable is recorded for htlcs that had a failure message
|
||||
// that couldn't be decrypted.
|
||||
HTLCFailUnreadable HTLCFailReason = 1
|
||||
|
||||
// HTLCFailInternal is recorded for htlcs that failed because of an
|
||||
// internal error.
|
||||
HTLCFailInternal HTLCFailReason = 2
|
||||
|
||||
// HTLCFailMessage is recorded for htlcs that failed with a network
|
||||
// failure message.
|
||||
HTLCFailMessage HTLCFailReason = 3
|
||||
)
|
||||
|
||||
// HTLCFailInfo encapsulates the information that augments an HTLCAttempt in the
|
||||
// event that the HTLC fails.
|
||||
type HTLCFailInfo struct {
|
||||
// FailTime is the time at which this HTLC was failed.
|
||||
FailTime time.Time
|
||||
|
||||
// Message is the wire message that failed this HTLC. This field will be
|
||||
// populated when the failure reason is HTLCFailMessage.
|
||||
Message lnwire.FailureMessage
|
||||
|
||||
// Reason is the failure reason for this HTLC.
|
||||
Reason HTLCFailReason
|
||||
|
||||
// The position in the path of the intermediate or final node that
|
||||
// generated the failure message. Position zero is the sender node. This
|
||||
// field will be populated when the failure reason is either
|
||||
// HTLCFailMessage or HTLCFailUnknown.
|
||||
FailureSourceIndex uint32
|
||||
}
|
||||
|
||||
// MPPaymentState wraps a series of info needed for a given payment, which is
|
||||
// used by both MPP and AMP. This is a memory representation of the payment's
|
||||
// current state and is updated whenever the payment is read from disk.
|
||||
type MPPaymentState struct {
|
||||
// NumAttemptsInFlight specifies the number of HTLCs the payment is
|
||||
// waiting results for.
|
||||
NumAttemptsInFlight int
|
||||
|
||||
// RemainingAmt specifies how much more money to be sent.
|
||||
RemainingAmt lnwire.MilliSatoshi
|
||||
|
||||
// FeesPaid specifies the total fees paid so far that can be used to
|
||||
// calculate remaining fee budget.
|
||||
FeesPaid lnwire.MilliSatoshi
|
||||
|
||||
// HasSettledHTLC is true if at least one of the payment's HTLCs is
|
||||
// settled.
|
||||
HasSettledHTLC bool
|
||||
|
||||
// PaymentFailed is true if the payment has been marked as failed with
|
||||
// a reason.
|
||||
PaymentFailed bool
|
||||
}
|
||||
|
||||
// MPPayment is a wrapper around a payment's PaymentCreationInfo and
|
||||
// HTLCAttempts. All payments will have the PaymentCreationInfo set, any
|
||||
// HTLCs made in attempts to be completed will populated in the HTLCs slice.
|
||||
// Each populated HTLCAttempt represents an attempted HTLC, each of which may
|
||||
// have the associated Settle or Fail struct populated if the HTLC is no longer
|
||||
// in-flight.
|
||||
type MPPayment struct {
|
||||
// SequenceNum is a unique identifier used to sort the payments in
|
||||
// order of creation.
|
||||
SequenceNum uint64
|
||||
|
||||
// Info holds all static information about this payment, and is
|
||||
// populated when the payment is initiated.
|
||||
Info *PaymentCreationInfo
|
||||
|
||||
// HTLCs holds the information about individual HTLCs that we send in
|
||||
// order to make the payment.
|
||||
HTLCs []HTLCAttempt
|
||||
|
||||
// FailureReason is the failure reason code indicating the reason the
|
||||
// payment failed.
|
||||
//
|
||||
// NOTE: Will only be set once the daemon has given up on the payment
|
||||
// altogether.
|
||||
FailureReason *FailureReason
|
||||
|
||||
// Status is the current PaymentStatus of this payment.
|
||||
Status PaymentStatus
|
||||
|
||||
// State is the current state of the payment that holds a number of key
|
||||
// insights and is used to determine what to do on each payment loop
|
||||
// iteration.
|
||||
State *MPPaymentState
|
||||
}
|
||||
|
||||
// Terminated returns a bool to specify whether the payment is in a terminal
|
||||
// state.
|
||||
func (m *MPPayment) Terminated() bool {
|
||||
// If the payment is in terminal state, it cannot be updated.
|
||||
return m.Status.updatable() != nil
|
||||
}
|
||||
|
||||
// TerminalInfo returns any HTLC settle info recorded. If no settle info is
|
||||
// recorded, any payment level failure will be returned. If neither a settle
|
||||
// nor a failure is recorded, both return values will be nil.
|
||||
func (m *MPPayment) TerminalInfo() (*HTLCAttempt, *FailureReason) {
|
||||
for _, h := range m.HTLCs {
|
||||
if h.Settle != nil {
|
||||
return &h, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, m.FailureReason
|
||||
}
|
||||
|
||||
// SentAmt returns the sum of sent amount and fees for HTLCs that are either
|
||||
// settled or still in flight.
|
||||
func (m *MPPayment) SentAmt() (lnwire.MilliSatoshi, lnwire.MilliSatoshi) {
|
||||
var sent, fees lnwire.MilliSatoshi
|
||||
for _, h := range m.HTLCs {
|
||||
if h.Failure != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// The attempt was not failed, meaning the amount was
|
||||
// potentially sent to the receiver.
|
||||
sent += h.Route.ReceiverAmt()
|
||||
fees += h.Route.TotalFees()
|
||||
}
|
||||
|
||||
return sent, fees
|
||||
}
|
||||
|
||||
// InFlightHTLCs returns the HTLCs that are still in-flight, meaning they have
|
||||
// not been settled or failed.
|
||||
func (m *MPPayment) InFlightHTLCs() []HTLCAttempt {
|
||||
var inflights []HTLCAttempt
|
||||
for _, h := range m.HTLCs {
|
||||
if h.Settle != nil || h.Failure != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
inflights = append(inflights, h)
|
||||
}
|
||||
|
||||
return inflights
|
||||
}
|
||||
|
||||
// GetAttempt returns the specified htlc attempt on the payment.
|
||||
func (m *MPPayment) GetAttempt(id uint64) (*HTLCAttempt, error) {
|
||||
// TODO(yy): iteration can be slow, make it into a tree or use BS.
|
||||
for _, htlc := range m.HTLCs {
|
||||
htlc := htlc
|
||||
if htlc.AttemptID == id {
|
||||
return &htlc, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errors.New("htlc attempt not found on payment")
|
||||
}
|
||||
|
||||
// Registrable returns an error to specify whether adding more HTLCs to the
|
||||
// payment with its current status is allowed. A payment can accept new HTLC
|
||||
// registrations when it's newly created, or none of its HTLCs is in a terminal
|
||||
// state.
|
||||
func (m *MPPayment) Registrable() error {
|
||||
// If updating the payment is not allowed, we can't register new HTLCs.
|
||||
// Otherwise, the status must be either `StatusInitiated` or
|
||||
// `StatusInFlight`.
|
||||
if err := m.Status.updatable(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Exit early if this is not inflight.
|
||||
if m.Status != StatusInFlight {
|
||||
return nil
|
||||
}
|
||||
|
||||
// There are still inflight HTLCs and we need to check whether there
|
||||
// are settled HTLCs or the payment is failed. If we already have
|
||||
// settled HTLCs, we won't allow adding more HTLCs.
|
||||
if m.State.HasSettledHTLC {
|
||||
return ErrPaymentPendingSettled
|
||||
}
|
||||
|
||||
// If the payment is already failed, we won't allow adding more HTLCs.
|
||||
if m.State.PaymentFailed {
|
||||
return ErrPaymentPendingFailed
|
||||
}
|
||||
|
||||
// Otherwise we can add more HTLCs.
|
||||
return nil
|
||||
}
|
||||
|
||||
// setState creates and attaches a new MPPaymentState to the payment. It also
|
||||
// updates the payment's status based on its current state.
|
||||
func (m *MPPayment) setState() error {
|
||||
// Fetch the total amount and fees that has already been sent in
|
||||
// settled and still in-flight shards.
|
||||
sentAmt, fees := m.SentAmt()
|
||||
|
||||
// Sanity check we haven't sent a value larger than the payment amount.
|
||||
totalAmt := m.Info.Value
|
||||
if sentAmt > totalAmt {
|
||||
return fmt.Errorf("%w: sent=%v, total=%v",
|
||||
ErrSentExceedsTotal, sentAmt, totalAmt)
|
||||
}
|
||||
|
||||
// Get any terminal info for this payment.
|
||||
settle, failure := m.TerminalInfo()
|
||||
|
||||
// Now determine the payment's status.
|
||||
status, err := decidePaymentStatus(m.HTLCs, m.FailureReason)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update the payment state and status.
|
||||
m.State = &MPPaymentState{
|
||||
NumAttemptsInFlight: len(m.InFlightHTLCs()),
|
||||
RemainingAmt: totalAmt - sentAmt,
|
||||
FeesPaid: fees,
|
||||
HasSettledHTLC: settle != nil,
|
||||
PaymentFailed: failure != nil,
|
||||
}
|
||||
m.Status = status
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetState calls the internal method setState. This is a temporary method
|
||||
// to be used by the tests in routing. Once the tests are updated to use mocks,
|
||||
// this method can be removed.
|
||||
//
|
||||
// TODO(yy): delete.
|
||||
func (m *MPPayment) SetState() error {
|
||||
return m.setState()
|
||||
}
|
||||
|
||||
// NeedWaitAttempts decides whether we need to hold creating more HTLC attempts
|
||||
// and wait for the results of the payment's inflight HTLCs. Return an error if
|
||||
// the payment is in an unexpected state.
|
||||
func (m *MPPayment) NeedWaitAttempts() (bool, error) {
|
||||
// Check when the remainingAmt is not zero, which means we have more
|
||||
// money to be sent.
|
||||
if m.State.RemainingAmt != 0 {
|
||||
switch m.Status {
|
||||
// If the payment is newly created, no need to wait for HTLC
|
||||
// results.
|
||||
case StatusInitiated:
|
||||
return false, nil
|
||||
|
||||
// If we have inflight HTLCs, we'll check if we have terminal
|
||||
// states to decide if we need to wait.
|
||||
case StatusInFlight:
|
||||
// We still have money to send, and one of the HTLCs is
|
||||
// settled. We'd stop sending money and wait for all
|
||||
// inflight HTLC attempts to finish.
|
||||
if m.State.HasSettledHTLC {
|
||||
log.Warnf("payment=%v has remaining amount "+
|
||||
"%v, yet at least one of its HTLCs is "+
|
||||
"settled", m.Info.PaymentIdentifier,
|
||||
m.State.RemainingAmt)
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// The payment has a failure reason though we still
|
||||
// have money to send, we'd stop sending money and wait
|
||||
// for all inflight HTLC attempts to finish.
|
||||
if m.State.PaymentFailed {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Otherwise we don't need to wait for inflight HTLCs
|
||||
// since we still have money to be sent.
|
||||
return false, nil
|
||||
|
||||
// We need to send more money, yet the payment is already
|
||||
// succeeded. Return an error in this case as the receiver is
|
||||
// violating the protocol.
|
||||
case StatusSucceeded:
|
||||
return false, fmt.Errorf("%w: parts of the payment "+
|
||||
"already succeeded but still have remaining "+
|
||||
"amount %v", ErrPaymentInternal,
|
||||
m.State.RemainingAmt)
|
||||
|
||||
// The payment is failed and we have no inflight HTLCs, no need
|
||||
// to wait.
|
||||
case StatusFailed:
|
||||
return false, nil
|
||||
|
||||
// Unknown payment status.
|
||||
default:
|
||||
return false, fmt.Errorf("%w: %s",
|
||||
ErrUnknownPaymentStatus, m.Status)
|
||||
}
|
||||
}
|
||||
|
||||
// Now we determine whether we need to wait when the remainingAmt is
|
||||
// already zero.
|
||||
switch m.Status {
|
||||
// When the payment is newly created, yet the payment has no remaining
|
||||
// amount, return an error.
|
||||
case StatusInitiated:
|
||||
return false, fmt.Errorf("%w: %v",
|
||||
ErrPaymentInternal, m.Status)
|
||||
|
||||
// If the payment is inflight, we must wait.
|
||||
//
|
||||
// NOTE: an edge case is when all HTLCs are failed while the payment is
|
||||
// not failed we'd still be in this inflight state. However, since the
|
||||
// remainingAmt is zero here, it means we cannot be in that state as
|
||||
// otherwise the remainingAmt would not be zero.
|
||||
case StatusInFlight:
|
||||
return true, nil
|
||||
|
||||
// If the payment is already succeeded, no need to wait.
|
||||
case StatusSucceeded:
|
||||
return false, nil
|
||||
|
||||
// If the payment is already failed, yet the remaining amount is zero,
|
||||
// return an error as this indicates an error state. We will only each
|
||||
// this status when there are no inflight HTLCs and the payment is
|
||||
// marked as failed with a reason, which means the remainingAmt must
|
||||
// not be zero because our sentAmt is zero.
|
||||
case StatusFailed:
|
||||
return false, fmt.Errorf("%w: %v",
|
||||
ErrPaymentInternal, m.Status)
|
||||
|
||||
// Unknown payment status.
|
||||
default:
|
||||
return false, fmt.Errorf("%w: %s",
|
||||
ErrUnknownPaymentStatus, m.Status)
|
||||
}
|
||||
}
|
||||
|
||||
// GetState returns the internal state of the payment.
|
||||
func (m *MPPayment) GetState() *MPPaymentState {
|
||||
return m.State
|
||||
}
|
||||
|
||||
// GetStatus returns the current status of the payment.
|
||||
func (m *MPPayment) GetStatus() PaymentStatus {
|
||||
return m.Status
|
||||
}
|
||||
|
||||
// GetHTLCs returns all the HTLCs for this payment.
|
||||
func (m *MPPayment) GetHTLCs() []HTLCAttempt {
|
||||
return m.HTLCs
|
||||
}
|
||||
|
||||
// AllowMoreAttempts is used to decide whether we can safely attempt more HTLCs
|
||||
// for a given payment state. Return an error if the payment is in an
|
||||
// unexpected state.
|
||||
func (m *MPPayment) AllowMoreAttempts() (bool, error) {
|
||||
// Now check whether the remainingAmt is zero or not. If we don't have
|
||||
// any remainingAmt, no more HTLCs should be made.
|
||||
if m.State.RemainingAmt == 0 {
|
||||
// If the payment is newly created, yet we don't have any
|
||||
// remainingAmt, return an error.
|
||||
if m.Status == StatusInitiated {
|
||||
return false, fmt.Errorf("%w: initiated payment has "+
|
||||
"zero remainingAmt",
|
||||
ErrPaymentInternal)
|
||||
}
|
||||
|
||||
// Otherwise, exit early since all other statuses with zero
|
||||
// remainingAmt indicate no more HTLCs can be made.
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Otherwise, the remaining amount is not zero, we now decide whether
|
||||
// to make more attempts based on the payment's current status.
|
||||
//
|
||||
// If at least one of the payment's attempts is settled, yet we haven't
|
||||
// sent all the amount, it indicates something is wrong with the peer
|
||||
// as the preimage is received. In this case, return an error state.
|
||||
if m.Status == StatusSucceeded {
|
||||
return false, fmt.Errorf("%w: payment already succeeded but "+
|
||||
"still have remaining amount %v",
|
||||
ErrPaymentInternal, m.State.RemainingAmt)
|
||||
}
|
||||
|
||||
// Now check if we can register a new HTLC.
|
||||
err := m.Registrable()
|
||||
if err != nil {
|
||||
log.Warnf("Payment(%v): cannot register HTLC attempt: %v, "+
|
||||
"current status: %s", m.Info.PaymentIdentifier,
|
||||
err, m.Status)
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Now we know we can register new HTLCs.
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// verifyAttempt validates that a new HTLC attempt is compatible with the
|
||||
// existing payment and its in-flight HTLCs. This function checks:
|
||||
// 1. MPP (Multi-Path Payment) compatibility between attempts
|
||||
// 2. Blinded payment consistency
|
||||
// 3. Amount validation
|
||||
// 4. Total payment amount limits
|
||||
func verifyAttempt(payment *MPPayment, attempt *HTLCAttemptInfo) error {
|
||||
// If the final hop has encrypted data, then we know this is a
|
||||
// blinded payment. In blinded payments, MPP records are not set
|
||||
// for split payments and the recipient is responsible for using
|
||||
// a consistent PathID across the various encrypted data
|
||||
// payloads that we received from them for this payment. All we
|
||||
// need to check is that the total amount field for each HTLC
|
||||
// in the split payment is correct.
|
||||
isBlinded := len(attempt.Route.FinalHop().EncryptedData) != 0
|
||||
|
||||
// For blinded payments, the last hop must set the total amount.
|
||||
if isBlinded {
|
||||
if attempt.Route.FinalHop().TotalAmtMsat == 0 {
|
||||
return ErrBlindedPaymentMissingTotalAmount
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure any existing shards match the new one with regards
|
||||
// to MPP options.
|
||||
mpp := attempt.Route.FinalHop().MPP
|
||||
|
||||
// MPP records should not be set for attempts to blinded paths.
|
||||
if isBlinded && mpp != nil {
|
||||
return ErrMPPRecordInBlindedPayment
|
||||
}
|
||||
|
||||
for _, h := range payment.InFlightHTLCs() {
|
||||
hMpp := h.Route.FinalHop().MPP
|
||||
hBlinded := len(h.Route.FinalHop().EncryptedData) != 0
|
||||
|
||||
// If this is a blinded payment, then no existing HTLCs
|
||||
// should have MPP records.
|
||||
if isBlinded && hMpp != nil {
|
||||
return ErrMPPRecordInBlindedPayment
|
||||
}
|
||||
|
||||
// If the payment is blinded (previous attempts used blinded
|
||||
// paths) and the attempt is not, or vice versa, return an
|
||||
// error.
|
||||
if isBlinded != hBlinded {
|
||||
return ErrMixedBlindedAndNonBlindedPayments
|
||||
}
|
||||
|
||||
// If this is a blinded payment, then we just need to
|
||||
// check that the TotalAmtMsat field for this shard
|
||||
// is equal to that of any other shard in the same
|
||||
// payment.
|
||||
if isBlinded {
|
||||
if attempt.Route.FinalHop().TotalAmtMsat !=
|
||||
h.Route.FinalHop().TotalAmtMsat {
|
||||
|
||||
return ErrBlindedPaymentTotalAmountMismatch
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
switch {
|
||||
// We tried to register a non-MPP attempt for a MPP
|
||||
// payment.
|
||||
case mpp == nil && hMpp != nil:
|
||||
return ErrMPPayment
|
||||
|
||||
// We tried to register a MPP shard for a non-MPP
|
||||
// payment.
|
||||
case mpp != nil && hMpp == nil:
|
||||
return ErrNonMPPayment
|
||||
|
||||
// Non-MPP payment, nothing more to validate.
|
||||
case mpp == nil:
|
||||
continue
|
||||
}
|
||||
|
||||
// Check that MPP options match.
|
||||
if mpp.PaymentAddr() != hMpp.PaymentAddr() {
|
||||
return ErrMPPPaymentAddrMismatch
|
||||
}
|
||||
|
||||
if mpp.TotalMsat() != hMpp.TotalMsat() {
|
||||
return ErrMPPTotalAmountMismatch
|
||||
}
|
||||
}
|
||||
|
||||
// If this is a non-MPP attempt, it must match the total amount
|
||||
// exactly. Note that a blinded payment is considered an MPP
|
||||
// attempt.
|
||||
amt := attempt.Route.ReceiverAmt()
|
||||
if !isBlinded && mpp == nil && amt != payment.Info.Value {
|
||||
return ErrValueMismatch
|
||||
}
|
||||
|
||||
// Ensure we aren't sending more than the total payment amount.
|
||||
sentAmt, _ := payment.SentAmt()
|
||||
if sentAmt+amt > payment.Info.Value {
|
||||
return fmt.Errorf("%w: attempted=%v, payment amount=%v",
|
||||
ErrValueExceedsAmt, sentAmt+amt, payment.Info.Value)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
package migration1
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package migration1
|
||||
|
||||
const (
|
||||
// DefaultMaxPayments is the default maximum number of payments returned
|
||||
// in the payments query pagination.
|
||||
DefaultMaxPayments = 100
|
||||
)
|
||||
|
||||
// Query represents a query to the payments database starting or ending
|
||||
// at a certain offset index. The number of retrieved records can be limited.
|
||||
type Query struct {
|
||||
// IndexOffset determines the starting point of the payments query and
|
||||
// is always exclusive. In normal order, the query starts at the next
|
||||
// higher (available) index compared to IndexOffset. In reversed order,
|
||||
// the query ends at the next lower (available) index compared to the
|
||||
// IndexOffset. In the case of a zero index_offset, the query will start
|
||||
// with the oldest payment when paginating forwards, or will end with
|
||||
// the most recent payment when paginating backwards.
|
||||
IndexOffset uint64
|
||||
|
||||
// MaxPayments is the maximal number of payments returned in the
|
||||
// payments query.
|
||||
MaxPayments uint64
|
||||
|
||||
// Reversed gives a meaning to the IndexOffset. If reversed is set to
|
||||
// true, the query will fetch payments with indices lower than the
|
||||
// IndexOffset, otherwise, it will return payments with indices greater
|
||||
// than the IndexOffset.
|
||||
Reversed bool
|
||||
|
||||
// If IncludeIncomplete is true, then return payments that have not yet
|
||||
// fully completed. This means that pending payments, as well as failed
|
||||
// payments will show up if this field is set to true.
|
||||
IncludeIncomplete bool
|
||||
|
||||
// CountTotal indicates that all payments currently present in the
|
||||
// payment index (complete and incomplete) should be counted.
|
||||
CountTotal bool
|
||||
|
||||
// CreationDateStart, expressed in Unix seconds, if set, filters out
|
||||
// all payments with a creation date greater than or equal to it.
|
||||
CreationDateStart int64
|
||||
|
||||
// CreationDateEnd, expressed in Unix seconds, if set, filters out all
|
||||
// payments with a creation date less than or equal to it.
|
||||
CreationDateEnd int64
|
||||
}
|
||||
|
||||
// Response contains the result of a query to the payments database.
|
||||
// It includes the set of payments that match the query and integers which
|
||||
// represent the index of the first and last item returned in the series of
|
||||
// payments. These integers allow callers to resume their query in the event
|
||||
// that the query's response exceeds the max number of returnable events.
|
||||
type Response struct {
|
||||
// Payments is the set of payments returned from the database for the
|
||||
// Query.
|
||||
Payments []*MPPayment
|
||||
|
||||
// FirstIndexOffset is the index of the first element in the set of
|
||||
// returned MPPayments. Callers can use this to resume their query
|
||||
// in the event that the slice has too many events to fit into a single
|
||||
// response. The offset can be used to continue reverse pagination.
|
||||
FirstIndexOffset uint64
|
||||
|
||||
// LastIndexOffset is the index of the last element in the set of
|
||||
// returned MPPayments. Callers can use this to resume their query
|
||||
// in the event that the slice has too many events to fit into a single
|
||||
// response. The offset can be used to continue forward pagination.
|
||||
LastIndexOffset uint64
|
||||
|
||||
// TotalCount represents the total number of payments that are currently
|
||||
// stored in the payment database. This will only be set if the
|
||||
// CountTotal field in the query was set to true.
|
||||
TotalCount uint64
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package migration1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/record"
|
||||
"github.com/lightningnetwork/lnd/tlv"
|
||||
)
|
||||
|
||||
// Vertex is a frozen local type representing a 33-byte compressed public key,
|
||||
// equivalent to route.Vertex at the time this migration was written.
|
||||
type Vertex [33]byte
|
||||
|
||||
// NewVertex returns a new Vertex from a compressed public key.
|
||||
func NewVertex(pub *btcec.PublicKey) Vertex {
|
||||
var v Vertex
|
||||
copy(v[:], pub.SerializeCompressed())
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// NewVertexFromBytes returns a new Vertex from a serialized compressed public
|
||||
// key byte slice.
|
||||
func NewVertexFromBytes(b []byte) (Vertex, error) {
|
||||
if len(b) != 33 {
|
||||
return Vertex{}, fmt.Errorf("invalid vertex length %d, "+
|
||||
"expected 33", len(b))
|
||||
}
|
||||
|
||||
var v Vertex
|
||||
copy(v[:], b)
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// Hop is a frozen local snapshot of route.Hop, containing exactly the fields
|
||||
// that existed at the time this migration was written. This ensures the
|
||||
// migration's serialization boundary is independent of future changes to
|
||||
// route.Hop, in particular the planned removal of LegacyPayload once the KV
|
||||
// backend is phased out.
|
||||
type Hop struct {
|
||||
// PubKeyBytes is the raw bytes of the public key of the target node.
|
||||
PubKeyBytes Vertex
|
||||
|
||||
// ChannelID is the unique channel ID for the channel.
|
||||
ChannelID uint64
|
||||
|
||||
// OutgoingTimeLock is the timelock value that should be used when
|
||||
// crafting the _outgoing_ HTLC from this hop.
|
||||
OutgoingTimeLock uint32
|
||||
|
||||
// AmtToForward is the amount that this hop will forward to the next
|
||||
// hop.
|
||||
AmtToForward lnwire.MilliSatoshi
|
||||
|
||||
// MPP encapsulates the data required for option_mpp. This field should
|
||||
// only be set for the final hop.
|
||||
MPP *record.MPP
|
||||
|
||||
// AMP encapsulates the data required for option_amp. This field should
|
||||
// only be set for the final hop.
|
||||
AMP *record.AMP
|
||||
|
||||
// CustomRecords if non-nil are a set of additional TLV records that
|
||||
// should be included in the forwarding instructions for this node.
|
||||
CustomRecords record.CustomSet
|
||||
|
||||
// LegacyPayload signals that this node doesn't understand the new TLV
|
||||
// payload, so the legacy payload format must be used.
|
||||
//
|
||||
// NOTE: This field is preserved here even though it is marked for
|
||||
// removal in the live route.Hop, because old KV data may have been
|
||||
// serialized with LegacyPayload=true and the migration must be able to
|
||||
// deserialize it correctly.
|
||||
LegacyPayload bool
|
||||
|
||||
// Metadata is additional data sent along with the payment to the
|
||||
// payee.
|
||||
Metadata []byte
|
||||
|
||||
// EncryptedData is an encrypted data blob included for hops that are
|
||||
// part of a blinded route.
|
||||
EncryptedData []byte
|
||||
|
||||
// BlindingPoint is an ephemeral public key used by introduction nodes
|
||||
// in blinded routes.
|
||||
BlindingPoint *btcec.PublicKey
|
||||
|
||||
// TotalAmtMsat is the total amount for a blinded payment. This field
|
||||
// should only be set for the final hop in a blinded path.
|
||||
TotalAmtMsat lnwire.MilliSatoshi
|
||||
}
|
||||
|
||||
// Route is a frozen local snapshot of route.Route, containing exactly the
|
||||
// fields that existed at the time this migration was written.
|
||||
type Route struct {
|
||||
// TotalTimeLock is the cumulative (final) time lock across the entire
|
||||
// route.
|
||||
TotalTimeLock uint32
|
||||
|
||||
// TotalAmount is the total amount of funds required to complete a
|
||||
// payment over this route, including fees.
|
||||
TotalAmount lnwire.MilliSatoshi
|
||||
|
||||
// SourcePubKey is the pubkey of the node where this route originates.
|
||||
SourcePubKey Vertex
|
||||
|
||||
// Hops contains details concerning the specific forwarding details at
|
||||
// each hop.
|
||||
Hops []*Hop
|
||||
|
||||
// FirstHopAmount is the amount that should actually be sent to the
|
||||
// first hop. Only differs from TotalAmount for custom channels.
|
||||
FirstHopAmount tlv.RecordT[
|
||||
tlv.TlvType0, tlv.BigSizeT[lnwire.MilliSatoshi],
|
||||
]
|
||||
|
||||
// FirstHopWireCustomRecords is a set of custom TLV records to include
|
||||
// in the wire message sent to the first hop.
|
||||
FirstHopWireCustomRecords lnwire.CustomRecords
|
||||
}
|
||||
|
||||
// FinalHop returns the last hop in the route.
|
||||
func (r *Route) FinalHop() *Hop {
|
||||
return r.Hops[len(r.Hops)-1]
|
||||
}
|
||||
|
||||
// ReceiverAmt returns the amount forwarded to the final hop.
|
||||
func (r *Route) ReceiverAmt() lnwire.MilliSatoshi {
|
||||
return r.FinalHop().AmtToForward
|
||||
}
|
||||
|
||||
// TotalFees returns the total fees paid along the route.
|
||||
func (r *Route) TotalFees() lnwire.MilliSatoshi {
|
||||
return r.TotalAmount - r.ReceiverAmt()
|
||||
}
|
||||
@@ -0,0 +1,274 @@
|
||||
package migration1
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/payments/db/migration1/sqlc"
|
||||
"github.com/lightningnetwork/lnd/record"
|
||||
"github.com/lightningnetwork/lnd/tlv"
|
||||
)
|
||||
|
||||
// dbPaymentToCreationInfo converts database payment data to the
|
||||
// PaymentCreationInfo struct.
|
||||
func dbPaymentToCreationInfo(paymentIdentifier []byte, amountMsat int64,
|
||||
createdAt time.Time, intentPayload []byte,
|
||||
firstHopCustomRecords lnwire.CustomRecords) *PaymentCreationInfo {
|
||||
|
||||
// This is the payment hash for non-AMP payments and the SetID for AMP
|
||||
// payments.
|
||||
var identifier lntypes.Hash
|
||||
copy(identifier[:], paymentIdentifier)
|
||||
|
||||
return &PaymentCreationInfo{
|
||||
PaymentIdentifier: identifier,
|
||||
Value: lnwire.MilliSatoshi(amountMsat),
|
||||
// The creation time is stored in the database as UTC but here
|
||||
// we convert it to local time.
|
||||
CreationTime: createdAt.Local(),
|
||||
PaymentRequest: intentPayload,
|
||||
FirstHopCustomRecords: firstHopCustomRecords,
|
||||
}
|
||||
}
|
||||
|
||||
// dbAttemptToHTLCAttempt converts a database HTLC attempt to an HTLCAttempt.
|
||||
func dbAttemptToHTLCAttempt(dbAttempt sqlc.FetchHtlcAttemptsForPaymentsRow,
|
||||
hops []sqlc.FetchHopsForAttemptsRow,
|
||||
hopCustomRecords map[int64][]sqlc.PaymentHopCustomRecord,
|
||||
routeCustomRecords []sqlc.PaymentAttemptFirstHopCustomRecord) (
|
||||
*HTLCAttempt, error) {
|
||||
|
||||
// Convert route-level first hop custom records to CustomRecords map.
|
||||
var firstHopWireCustomRecords lnwire.CustomRecords
|
||||
if len(routeCustomRecords) > 0 {
|
||||
firstHopWireCustomRecords = make(lnwire.CustomRecords)
|
||||
for _, record := range routeCustomRecords {
|
||||
firstHopWireCustomRecords[uint64(record.Key)] =
|
||||
record.Value
|
||||
}
|
||||
}
|
||||
|
||||
// Build the route from the database data.
|
||||
route, err := dbDataToRoute(
|
||||
hops, hopCustomRecords, dbAttempt.FirstHopAmountMsat,
|
||||
dbAttempt.RouteTotalTimeLock, dbAttempt.RouteTotalAmount,
|
||||
dbAttempt.RouteSourceKey, firstHopWireCustomRecords,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert to route: %w",
|
||||
err)
|
||||
}
|
||||
|
||||
hash, err := lntypes.MakeHash(dbAttempt.PaymentHash)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse payment "+
|
||||
"hash: %w", err)
|
||||
}
|
||||
|
||||
// Create the attempt info.
|
||||
var sessionKey [32]byte
|
||||
copy(sessionKey[:], dbAttempt.SessionKey)
|
||||
|
||||
info := HTLCAttemptInfo{
|
||||
AttemptID: uint64(dbAttempt.AttemptIndex),
|
||||
sessionKey: sessionKey,
|
||||
Route: *route,
|
||||
AttemptTime: dbAttempt.AttemptTime,
|
||||
Hash: &hash,
|
||||
}
|
||||
|
||||
attempt := &HTLCAttempt{
|
||||
HTLCAttemptInfo: info,
|
||||
}
|
||||
|
||||
// If there's no resolution type, the attempt is still in-flight.
|
||||
// Return early without processing settlement or failure info.
|
||||
if !dbAttempt.ResolutionType.Valid {
|
||||
return attempt, nil
|
||||
}
|
||||
|
||||
// Add settlement info if present.
|
||||
if HTLCAttemptResolutionType(dbAttempt.ResolutionType.Int32) ==
|
||||
HTLCAttemptResolutionSettled {
|
||||
|
||||
var preimage lntypes.Preimage
|
||||
copy(preimage[:], dbAttempt.SettlePreimage)
|
||||
|
||||
attempt.Settle = &HTLCSettleInfo{
|
||||
Preimage: preimage,
|
||||
SettleTime: dbAttempt.ResolutionTime.Time,
|
||||
}
|
||||
}
|
||||
|
||||
// Add failure info if present.
|
||||
if HTLCAttemptResolutionType(dbAttempt.ResolutionType.Int32) ==
|
||||
HTLCAttemptResolutionFailed {
|
||||
|
||||
failure := &HTLCFailInfo{
|
||||
FailTime: dbAttempt.ResolutionTime.Time,
|
||||
}
|
||||
|
||||
if dbAttempt.HtlcFailReason.Valid {
|
||||
failure.Reason = HTLCFailReason(
|
||||
dbAttempt.HtlcFailReason.Int32,
|
||||
)
|
||||
}
|
||||
|
||||
if dbAttempt.FailureSourceIndex.Valid {
|
||||
failure.FailureSourceIndex = uint32(
|
||||
dbAttempt.FailureSourceIndex.Int32,
|
||||
)
|
||||
}
|
||||
|
||||
// Decode the failure message if present.
|
||||
if len(dbAttempt.FailureMsg) > 0 {
|
||||
msg, err := lnwire.DecodeFailureMessage(
|
||||
bytes.NewReader(dbAttempt.FailureMsg), 0,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode "+
|
||||
"failure message: %w", err)
|
||||
}
|
||||
failure.Message = msg
|
||||
}
|
||||
|
||||
attempt.Failure = failure
|
||||
}
|
||||
|
||||
return attempt, nil
|
||||
}
|
||||
|
||||
// dbDataToRoute converts database route data to a Route.
|
||||
func dbDataToRoute(hops []sqlc.FetchHopsForAttemptsRow,
|
||||
hopCustomRecords map[int64][]sqlc.PaymentHopCustomRecord,
|
||||
firstHopAmountMsat int64, totalTimeLock int32, totalAmount int64,
|
||||
sourceKey []byte, firstHopWireCustomRecords lnwire.CustomRecords) (
|
||||
*Route, error) {
|
||||
|
||||
if len(hops) == 0 {
|
||||
return nil, fmt.Errorf("no hops provided")
|
||||
}
|
||||
|
||||
// Hops are already sorted by hop_index from the SQL query.
|
||||
routeHops := make([]*Hop, len(hops))
|
||||
|
||||
for i, hop := range hops {
|
||||
pubKey, err := NewVertexFromBytes(hop.PubKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse pub key: %w",
|
||||
err)
|
||||
}
|
||||
|
||||
var channelID uint64
|
||||
if hop.Scid != "" {
|
||||
// The SCID is stored as a string representation
|
||||
// of the uint64.
|
||||
var err error
|
||||
channelID, err = strconv.ParseUint(hop.Scid, 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse "+
|
||||
"scid: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
routeHop := &Hop{
|
||||
PubKeyBytes: pubKey,
|
||||
ChannelID: channelID,
|
||||
OutgoingTimeLock: uint32(hop.OutgoingTimeLock),
|
||||
AmtToForward: lnwire.MilliSatoshi(hop.AmtToForward),
|
||||
}
|
||||
|
||||
// Add MPP record if present.
|
||||
if len(hop.MppPaymentAddr) > 0 {
|
||||
var paymentAddr [32]byte
|
||||
copy(paymentAddr[:], hop.MppPaymentAddr)
|
||||
routeHop.MPP = record.NewMPP(
|
||||
lnwire.MilliSatoshi(hop.MppTotalMsat.Int64),
|
||||
paymentAddr,
|
||||
)
|
||||
}
|
||||
|
||||
// Add AMP record if present.
|
||||
if len(hop.AmpRootShare) > 0 {
|
||||
var rootShare [32]byte
|
||||
copy(rootShare[:], hop.AmpRootShare)
|
||||
var setID [32]byte
|
||||
copy(setID[:], hop.AmpSetID)
|
||||
|
||||
routeHop.AMP = record.NewAMP(
|
||||
rootShare, setID,
|
||||
uint32(hop.AmpChildIndex.Int32),
|
||||
)
|
||||
}
|
||||
|
||||
// Add blinding point if present (only for introduction node
|
||||
// in blinded route).
|
||||
if len(hop.BlindingPoint) > 0 {
|
||||
pubKey, err := btcec.ParsePubKey(hop.BlindingPoint)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse "+
|
||||
"blinding point: %w", err)
|
||||
}
|
||||
routeHop.BlindingPoint = pubKey
|
||||
}
|
||||
|
||||
// Add encrypted data if present (for all blinded hops).
|
||||
if len(hop.EncryptedData) > 0 {
|
||||
routeHop.EncryptedData = hop.EncryptedData
|
||||
}
|
||||
|
||||
// Add total amount if present (only for final hop in blinded
|
||||
// route).
|
||||
if hop.BlindedPathTotalAmt.Valid {
|
||||
routeHop.TotalAmtMsat = lnwire.MilliSatoshi(
|
||||
hop.BlindedPathTotalAmt.Int64,
|
||||
)
|
||||
}
|
||||
|
||||
// Add hop-level custom records.
|
||||
if records, ok := hopCustomRecords[hop.ID]; ok {
|
||||
routeHop.CustomRecords = make(
|
||||
record.CustomSet,
|
||||
)
|
||||
for _, rec := range records {
|
||||
routeHop.CustomRecords[uint64(rec.Key)] =
|
||||
rec.Value
|
||||
}
|
||||
}
|
||||
|
||||
// Add metadata if present.
|
||||
if len(hop.MetaData) > 0 {
|
||||
routeHop.Metadata = hop.MetaData
|
||||
}
|
||||
|
||||
routeHops[i] = routeHop
|
||||
}
|
||||
|
||||
// Parse the source node public key.
|
||||
var sourceNode Vertex
|
||||
copy(sourceNode[:], sourceKey)
|
||||
|
||||
route := &Route{
|
||||
TotalTimeLock: uint32(totalTimeLock),
|
||||
TotalAmount: lnwire.MilliSatoshi(totalAmount),
|
||||
SourcePubKey: sourceNode,
|
||||
Hops: routeHops,
|
||||
FirstHopWireCustomRecords: firstHopWireCustomRecords,
|
||||
}
|
||||
|
||||
// Set the first hop amount if it is set.
|
||||
if firstHopAmountMsat != 0 {
|
||||
route.FirstHopAmount = tlv.NewRecordT[tlv.TlvType0](
|
||||
tlv.NewBigSizeT(lnwire.MilliSatoshi(
|
||||
firstHopAmountMsat,
|
||||
)),
|
||||
)
|
||||
}
|
||||
|
||||
return route, nil
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,31 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.29.0
|
||||
|
||||
package sqlc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
type DBTX interface {
|
||||
ExecContext(context.Context, string, ...interface{}) (sql.Result, error)
|
||||
PrepareContext(context.Context, string) (*sql.Stmt, error)
|
||||
QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error)
|
||||
QueryRowContext(context.Context, string, ...interface{}) *sql.Row
|
||||
}
|
||||
|
||||
func New(db DBTX) *Queries {
|
||||
return &Queries{db: db}
|
||||
}
|
||||
|
||||
type Queries struct {
|
||||
db DBTX
|
||||
}
|
||||
|
||||
func (q *Queries) WithTx(tx *sql.Tx) *Queries {
|
||||
return &Queries{
|
||||
db: tx,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package sqlc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GetTx returns the underlying DBTX (either *sql.DB or *sql.Tx) used by the
|
||||
// Queries struct.
|
||||
func (q *Queries) GetTx() DBTX {
|
||||
return q.db
|
||||
}
|
||||
|
||||
// makeQueryParams generates a string of query parameters for a SQL query. It is
|
||||
// meant to replace the `?` placeholders in a SQL query with numbered parameters
|
||||
// like `$1`, `$2`, etc. This is required for the sqlc /*SLICE:<field_name>*/
|
||||
// workaround. See scripts/gen_sqlc_docker.sh for more details.
|
||||
func makeQueryParams(numTotalArgs, numListArgs int) string {
|
||||
if numListArgs == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
var b strings.Builder
|
||||
|
||||
// Pre-allocate a rough estimation of the buffer size to avoid
|
||||
// re-allocations. A parameter like $1000, takes 6 bytes.
|
||||
b.Grow(numListArgs * 6)
|
||||
|
||||
diff := numTotalArgs - numListArgs
|
||||
for i := 0; i < numListArgs; i++ {
|
||||
if i > 0 {
|
||||
// We don't need to check the error here because the
|
||||
// WriteString method of strings.Builder always returns
|
||||
// nil.
|
||||
_, _ = b.WriteString(",")
|
||||
}
|
||||
|
||||
// We don't need to check the error here because the
|
||||
// Write method (called by fmt.Fprintf) of strings.Builder
|
||||
// always returns nil.
|
||||
_, _ = fmt.Fprintf(&b, "$%d", i+diff+1)
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// PaymentAndIntent is an interface that provides access to a payment and its
|
||||
// associated payment intent.
|
||||
type PaymentAndIntent interface {
|
||||
// GetPayment returns the Payment associated with this interface.
|
||||
GetPayment() Payment
|
||||
|
||||
// GetPaymentIntent returns the PaymentIntent associated with this
|
||||
// payment.
|
||||
GetPaymentIntent() PaymentIntent
|
||||
}
|
||||
|
||||
// GetPayment returns the Payment associated with this interface.
|
||||
//
|
||||
// NOTE: This method is part of the PaymentAndIntent interface.
|
||||
func (r FilterPaymentsRow) GetPayment() Payment {
|
||||
return r.Payment
|
||||
}
|
||||
|
||||
// GetPaymentIntent returns the PaymentIntent associated with this payment.
|
||||
// If the payment has no intent (IntentType is NULL), this returns a zero-value
|
||||
// PaymentIntent.
|
||||
//
|
||||
// NOTE: This method is part of the PaymentAndIntent interface.
|
||||
func (r FilterPaymentsRow) GetPaymentIntent() PaymentIntent {
|
||||
if !r.IntentType.Valid {
|
||||
return PaymentIntent{}
|
||||
}
|
||||
|
||||
return PaymentIntent{
|
||||
IntentType: r.IntentType.Int16,
|
||||
IntentPayload: r.IntentPayload,
|
||||
}
|
||||
}
|
||||
|
||||
// GetPayment returns the Payment associated with this interface.
|
||||
//
|
||||
// NOTE: This method is part of the PaymentAndIntent interface.
|
||||
func (r FetchPaymentRow) GetPayment() Payment {
|
||||
return r.Payment
|
||||
}
|
||||
|
||||
// GetPaymentIntent returns the PaymentIntent associated with this payment.
|
||||
// If the payment has no intent (IntentType is NULL), this returns a zero-value
|
||||
// PaymentIntent.
|
||||
//
|
||||
// NOTE: This method is part of the PaymentAndIntent interface.
|
||||
func (r FetchPaymentRow) GetPaymentIntent() PaymentIntent {
|
||||
if !r.IntentType.Valid {
|
||||
return PaymentIntent{}
|
||||
}
|
||||
|
||||
return PaymentIntent{
|
||||
IntentType: r.IntentType.Int16,
|
||||
IntentPayload: r.IntentPayload,
|
||||
}
|
||||
}
|
||||
|
||||
func (r FetchPaymentsByIDsRow) GetPayment() Payment {
|
||||
return Payment{
|
||||
ID: r.ID,
|
||||
AmountMsat: r.AmountMsat,
|
||||
CreatedAt: r.CreatedAt,
|
||||
PaymentIdentifier: r.PaymentIdentifier,
|
||||
FailReason: r.FailReason,
|
||||
}
|
||||
}
|
||||
|
||||
func (r FetchPaymentsByIDsRow) GetPaymentIntent() PaymentIntent {
|
||||
if !r.IntentType.Valid {
|
||||
return PaymentIntent{}
|
||||
}
|
||||
|
||||
return PaymentIntent{
|
||||
IntentType: r.IntentType.Int16,
|
||||
IntentPayload: r.IntentPayload,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.29.0
|
||||
|
||||
package sqlc
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Payment struct {
|
||||
ID int64
|
||||
AmountMsat int64
|
||||
CreatedAt time.Time
|
||||
PaymentIdentifier []byte
|
||||
FailReason sql.NullInt32
|
||||
}
|
||||
|
||||
type PaymentAttemptFirstHopCustomRecord struct {
|
||||
ID int64
|
||||
HtlcAttemptIndex int64
|
||||
Key int64
|
||||
Value []byte
|
||||
}
|
||||
|
||||
type PaymentDuplicate struct {
|
||||
ID int64
|
||||
PaymentID int64
|
||||
AmountMsat int64
|
||||
CreatedAt time.Time
|
||||
FailReason sql.NullInt32
|
||||
SettlePreimage []byte
|
||||
SettleTime sql.NullTime
|
||||
}
|
||||
|
||||
type PaymentFirstHopCustomRecord struct {
|
||||
ID int64
|
||||
PaymentID int64
|
||||
Key int64
|
||||
Value []byte
|
||||
}
|
||||
|
||||
type PaymentHopCustomRecord struct {
|
||||
ID int64
|
||||
HopID int64
|
||||
Key int64
|
||||
Value []byte
|
||||
}
|
||||
|
||||
type PaymentHtlcAttempt struct {
|
||||
ID int64
|
||||
AttemptIndex int64
|
||||
PaymentID int64
|
||||
SessionKey []byte
|
||||
AttemptTime time.Time
|
||||
PaymentHash []byte
|
||||
FirstHopAmountMsat int64
|
||||
RouteTotalTimeLock int32
|
||||
RouteTotalAmount int64
|
||||
RouteSourceKey []byte
|
||||
}
|
||||
|
||||
type PaymentHtlcAttemptResolution struct {
|
||||
AttemptIndex int64
|
||||
ResolutionTime time.Time
|
||||
ResolutionType int32
|
||||
SettlePreimage []byte
|
||||
FailureSourceIndex sql.NullInt32
|
||||
HtlcFailReason sql.NullInt32
|
||||
FailureMsg []byte
|
||||
}
|
||||
|
||||
type PaymentIntent struct {
|
||||
ID int64
|
||||
PaymentID int64
|
||||
IntentType int16
|
||||
IntentPayload []byte
|
||||
}
|
||||
|
||||
type PaymentRouteHop struct {
|
||||
ID int64
|
||||
HtlcAttemptIndex int64
|
||||
HopIndex int32
|
||||
PubKey []byte
|
||||
Scid string
|
||||
OutgoingTimeLock int32
|
||||
AmtToForward int64
|
||||
MetaData []byte
|
||||
}
|
||||
|
||||
type PaymentRouteHopAmp struct {
|
||||
HopID int64
|
||||
RootShare []byte
|
||||
SetID []byte
|
||||
ChildIndex int32
|
||||
}
|
||||
|
||||
type PaymentRouteHopBlinded struct {
|
||||
HopID int64
|
||||
EncryptedData []byte
|
||||
BlindingPoint []byte
|
||||
BlindedPathTotalAmt sql.NullInt64
|
||||
}
|
||||
|
||||
type PaymentRouteHopMpp struct {
|
||||
HopID int64
|
||||
PaymentAddr []byte
|
||||
TotalMsat int64
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user