paymentsdb

package
v0.21.0-beta.rc1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 17, 2026 License: MIT Imports: 33 Imported by: 0

Documentation

Index

Constants

View Source
const (
	// DefaultMaxPayments is the default maximum number of payments returned
	// in the payments query pagination.
	DefaultMaxPayments = 100
)
View Source
const Subsystem = "PYDB"

Subsystem defines the logging identifier for this subsystem.

Variables

View Source
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")
)
View Source
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")
)

KV backend specific errors.

Functions

func DeserializeRoute

func DeserializeRoute(r io.Reader) (route.Route, error)

DeserializeRoute deserializes a route.

func DisableLog

func DisableLog()

DisableLog disables all library log output. Logging output is disabled by default until UseLogger is called.

func NewTestDB

func NewTestDB(t *testing.T, opts ...OptionModifier) (DB, TestHarness)

NewTestDB is a helper function that creates an BBolt database for testing.

func ReadElement

func ReadElement(r io.Reader, element interface{}) error

ReadElement deserializes a single element from the provided io.Reader.

func ReadElements

func ReadElements(r io.Reader, elements ...interface{}) error

ReadElements deserializes the provided io.Reader into a variadic list of target elements.

func SerializeRoute

func SerializeRoute(w io.Writer, r route.Route) error

SerializeRoute serializes a route.

func UseLogger

func UseLogger(logger btclog.Logger)

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 WriteElement

func WriteElement(w io.Writer, element interface{}) error

WriteElement serializes a single element into the provided io.Writer.

func WriteElements

func WriteElements(w io.Writer, elements ...interface{}) error

WriteElements serializes a variadic list of elements into the given io.Writer.

Types

type BatchedSQLQueries

type BatchedSQLQueries interface {
	SQLQueries
	sqldb.BatchedTx[SQLQueries]
}

BatchedSQLQueries is a version of the SQLQueries that's capable of batched database operations.

type DB

type DB interface {
	PaymentReader
	PaymentWriter
}

DB represents the interface to the underlying payments database.

type DBMPPayment

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)
}

DBMPPayment is an interface that represents the payment state during a payment lifecycle.

type FailureReason

type FailureReason byte

FailureReason encodes the reason a payment ultimately failed.

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
)

func (FailureReason) Error

func (r FailureReason) Error() string

Error returns a human-readable error string for the FailureReason.

func (FailureReason) String

func (r FailureReason) String() string

String returns a human-readable FailureReason.

type HTLCAttempt

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
}

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.

func NewHtlcAttempt

func NewHtlcAttempt(attemptID uint64, sessionKey *btcec.PrivateKey,
	route route.Route, attemptTime time.Time,
	hash *lntypes.Hash) (*HTLCAttempt, error)

NewHtlcAttempt creates a htlc attempt.

type HTLCAttemptInfo

type HTLCAttemptInfo struct {
	// AttemptID is the unique ID used for this attempt.
	AttemptID uint64

	// Route is the route attempted to send the HTLC.
	Route 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
	// contains filtered or unexported fields
}

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.

func (*HTLCAttemptInfo) Circuit

func (h *HTLCAttemptInfo) Circuit() (*sphinx.Circuit, error)

Circuit returns the sphinx circuit for this attempt.

func (*HTLCAttemptInfo) OnionBlob

func (h *HTLCAttemptInfo) OnionBlob() ([lnwire.OnionPacketSize]byte, error)

OnionBlob returns the onion blob created from the sphinx construction.

func (*HTLCAttemptInfo) SessionKey

func (h *HTLCAttemptInfo) SessionKey() *btcec.PrivateKey

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.

type HTLCAttemptResolutionType

type HTLCAttemptResolutionType int32

HTLCAttemptResolutionType represents the type of HTLC attempt resolution.

const (
	// HTLCAttemptResolutionSettled indicates the HTLC attempt was settled
	// successfully with a preimage.
	HTLCAttemptResolutionSettled HTLCAttemptResolutionType = 1

	// HTLCAttemptResolutionFailed indicates the HTLC attempt failed.
	HTLCAttemptResolutionFailed HTLCAttemptResolutionType = 2
)

type HTLCFailInfo

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
}

HTLCFailInfo encapsulates the information that augments an HTLCAttempt in the event that the HTLC fails.

type HTLCFailReason

type HTLCFailReason byte

HTLCFailReason is the reason an htlc failed.

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
)

type HTLCSettleInfo

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
}

HTLCSettleInfo encapsulates the information that augments an HTLCAttempt in the event that the HTLC is successful.

type KVStore

type KVStore struct {
	// contains filtered or unexported fields
}

KVStore implements persistence for payments and payment attempts.

func NewKVStore

func NewKVStore(db kvdb.Backend,
	options ...OptionModifier) (*KVStore, error)

NewKVStore creates a new KVStore for payments.

func NewKVTestDB

func NewKVTestDB(t *testing.T, opts ...OptionModifier) *KVStore

NewKVTestDB is a helper function that creates an BBolt database for testing and there is no need to convert the interface to the KVStore because for some unit tests we still need access to the kvdb interface.

func (*KVStore) DeleteFailedAttempts

func (p *KVStore) DeleteFailedAttempts(ctx context.Context,
	hash lntypes.Hash) error

DeleteFailedAttempts deletes all failed htlcs for a payment.

func (*KVStore) DeletePayment

func (p *KVStore) DeletePayment(_ context.Context, paymentHash lntypes.Hash,
	failedHtlcsOnly bool) error

DeletePayment deletes a payment from the DB given its payment hash. If failedHtlcsOnly is set, only failed HTLC attempts of the payment will be deleted.

func (*KVStore) DeletePayments

func (p *KVStore) DeletePayments(_ context.Context, failedOnly,
	failedHtlcsOnly bool) (int, error)

DeletePayments deletes all completed and failed payments from the DB. If failedOnly is set, only failed payments will be considered for deletion. If failedHtlcsOnly is set, the payment itself won't be deleted, only failed HTLC attempts. The method returns the number of deleted payments, which is always 0 if failedHtlcsOnly is set.

func (*KVStore) Fail

func (p *KVStore) Fail(_ context.Context, paymentHash lntypes.Hash,
	reason FailureReason) (*MPPayment, error)

Fail transitions a payment into the Failed state, and records the reason the payment failed. After invoking this method, InitPayment should return nil on its next call for this payment hash, allowing the switch to make a subsequent payment.

func (*KVStore) FailAttempt

func (p *KVStore) FailAttempt(_ context.Context, hash lntypes.Hash,
	attemptID uint64, failInfo *HTLCFailInfo) (*MPPayment, error)

FailAttempt marks the given payment attempt failed.

func (*KVStore) FetchInFlightPayments

func (p *KVStore) FetchInFlightPayments(_ context.Context) ([]*MPPayment,
	error)

FetchInFlightPayments returns all payments with status InFlight.

func (*KVStore) FetchPayment

func (p *KVStore) FetchPayment(_ context.Context,
	paymentHash lntypes.Hash) (*MPPayment, error)

FetchPayment returns information about a payment from the database.

func (*KVStore) FetchPayments

func (p *KVStore) FetchPayments() ([]*MPPayment, error)

FetchPayments returns all sent payments found in the DB.

func (*KVStore) InitPayment

func (p *KVStore) InitPayment(_ context.Context, paymentHash lntypes.Hash,
	info *PaymentCreationInfo) error

InitPayment checks or records the given PaymentCreationInfo with the DB, making sure it does not already exist as an in-flight payment. When this method returns successfully, the payment is guaranteed to be in the InFlight state.

func (*KVStore) QueryPayments

func (p *KVStore) QueryPayments(_ context.Context,
	query Query) (Response, error)

QueryPayments is a query to the payments database which is restricted to a subset of payments by the payments query, containing an offset index and a maximum number of returned payments.

func (*KVStore) RegisterAttempt

func (p *KVStore) RegisterAttempt(_ context.Context, paymentHash lntypes.Hash,
	attempt *HTLCAttemptInfo) (*MPPayment, error)

RegisterAttempt atomically records the provided HTLCAttemptInfo to the DB.

func (*KVStore) SettleAttempt

func (p *KVStore) SettleAttempt(_ context.Context, hash lntypes.Hash,
	attemptID uint64, settleInfo *HTLCSettleInfo) (*MPPayment, error)

SettleAttempt marks the given attempt settled with the preimage. If this is a multi shard payment, this might implicitly mean that 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.

type MPPayment

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
}

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.

func (*MPPayment) AllowMoreAttempts

func (m *MPPayment) AllowMoreAttempts() (bool, error)

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 (*MPPayment) GetAttempt

func (m *MPPayment) GetAttempt(id uint64) (*HTLCAttempt, error)

GetAttempt returns the specified htlc attempt on the payment.

func (*MPPayment) GetHTLCs

func (m *MPPayment) GetHTLCs() []HTLCAttempt

GetHTLCs returns all the HTLCs for this payment.

func (*MPPayment) GetState

func (m *MPPayment) GetState() *MPPaymentState

GetState returns the internal state of the payment.

func (*MPPayment) GetStatus

func (m *MPPayment) GetStatus() PaymentStatus

GetStatus returns the current status of the payment.

func (*MPPayment) InFlightHTLCs

func (m *MPPayment) InFlightHTLCs() []HTLCAttempt

InFlightHTLCs returns the HTLCs that are still in-flight, meaning they have not been settled or failed.

func (*MPPayment) NeedWaitAttempts

func (m *MPPayment) NeedWaitAttempts() (bool, error)

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 (*MPPayment) Registrable

func (m *MPPayment) Registrable() error

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 (*MPPayment) SentAmt

SentAmt returns the sum of sent amount and fees for HTLCs that are either settled or still in flight.

func (*MPPayment) SetState

func (m *MPPayment) SetState() error

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 (*MPPayment) TerminalInfo

func (m *MPPayment) TerminalInfo() (*HTLCAttempt, *FailureReason)

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 (*MPPayment) Terminated

func (m *MPPayment) Terminated() bool

Terminated returns a bool to specify whether the payment is in a terminal state.

type MPPaymentState

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
}

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 OptionModifier

type OptionModifier func(*StoreOptions)

OptionModifier is a function signature for modifying the default StoreOptions.

func WithNoMigration

func WithNoMigration(b bool) OptionModifier

WithNoMigration allows the database to be opened in read only mode by disabling migrations.

type PaymentControl

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
}

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 PaymentCreationInfo

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
}

PaymentCreationInfo is the information necessary to have ready when initiating a payment, moving it into state InFlight.

func (*PaymentCreationInfo) String

func (p *PaymentCreationInfo) String() string

String returns a human-readable description of the payment creation info.

type PaymentIntentType

type PaymentIntentType int16

PaymentIntentType represents the type of payment intent.

const (
	// PaymentIntentTypeBolt11 indicates a BOLT11 invoice payment.
	PaymentIntentTypeBolt11 PaymentIntentType = 0
)

type PaymentReader

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)
}

PaymentReader represents the interface to read operations from the payments database.

type PaymentStatus

type PaymentStatus byte

PaymentStatus represent current status of payment.

const (

	// 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
)

func (PaymentStatus) String

func (ps PaymentStatus) String() string

String returns readable representation of payment status.

type PaymentWriter

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
}

PaymentWriter represents the interface to write operations to the payments database.

type Query

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

	// OmitHops skips loading hop and hop-level custom record data for
	// HTLC attempts when set to true.
	OmitHops bool
}

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 Response

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
}

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 SQLMigrationQueries

type SQLMigrationQueries interface {
	SQLQueries

	// FetchPaymentsByIDsMig is a migration-only batch fetch that returns
	// payment data along with HTLC attempt counts for structural
	// validation.
	FetchPaymentsByIDsMig(ctx context.Context, paymentIDs []int64) ([]sqlc.FetchPaymentsByIDsMigRow, error)

	// InsertPaymentMig is a migration-only variant of InsertPayment that
	// allows setting fail_reason when inserting historical payments, since
	// for real payments they have not failed at creation time and so no
	// failure reason would exist yet.
	InsertPaymentMig(ctx context.Context, arg sqlc.InsertPaymentMigParams) (int64, error)

	// InsertPaymentDuplicateMig inserts a duplicate payment record during
	// migration.
	InsertPaymentDuplicateMig(ctx context.Context, arg sqlc.InsertPaymentDuplicateMigParams) (int64, error)
}

SQLMigrationQueries extends SQLQueries with the additional queries needed for the one-time migration from KV to SQL. Keeping them in a separate interface makes it clear which code paths are migration-only and prevents the regular store from accidentally depending on them.

type SQLQueries

type SQLQueries interface {
	/*
		Payment DB read operations.
	*/
	FilterPayments(ctx context.Context, query sqlc.FilterPaymentsParams) ([]sqlc.FilterPaymentsRow, error)
	FilterPaymentsDesc(ctx context.Context, query sqlc.FilterPaymentsDescParams) ([]sqlc.FilterPaymentsDescRow, error)
	FetchPayment(ctx context.Context, paymentIdentifier []byte) (sqlc.FetchPaymentRow, error)
	FetchPaymentsByIDs(ctx context.Context, paymentIDs []int64) ([]sqlc.FetchPaymentsByIDsRow, error)
	FetchNonTerminalPayments(ctx context.Context, arg sqlc.FetchNonTerminalPaymentsParams) ([]sqlc.FetchNonTerminalPaymentsRow, error)

	CountPayments(ctx context.Context) (int64, error)

	FetchHtlcAttemptsForPayments(ctx context.Context, paymentIDs []int64) ([]sqlc.FetchHtlcAttemptsForPaymentsRow, error)
	FetchHtlcAttemptResolutionsForPayments(ctx context.Context, paymentIDs []int64) ([]sqlc.FetchHtlcAttemptResolutionsForPaymentsRow, error)
	FetchHopsForAttempts(ctx context.Context, htlcAttemptIndices []int64) ([]sqlc.FetchHopsForAttemptsRow, error)

	FetchPaymentDuplicates(ctx context.Context, paymentID int64) ([]sqlc.PaymentDuplicate, error)

	FetchPaymentLevelFirstHopCustomRecords(ctx context.Context, paymentIDs []int64) ([]sqlc.PaymentFirstHopCustomRecord, error)
	FetchRouteLevelFirstHopCustomRecords(ctx context.Context, htlcAttemptIndices []int64) ([]sqlc.PaymentAttemptFirstHopCustomRecord, error)
	FetchHopLevelCustomRecords(ctx context.Context, hopIDs []int64) ([]sqlc.PaymentHopCustomRecord, error)

	/*
		Payment DB write operations.
	*/
	InsertPaymentIntent(ctx context.Context, arg sqlc.InsertPaymentIntentParams) (int64, error)
	InsertPayment(ctx context.Context, arg sqlc.InsertPaymentParams) (int64, error)
	InsertPaymentFirstHopCustomRecord(ctx context.Context, arg sqlc.InsertPaymentFirstHopCustomRecordParams) error

	InsertHtlcAttempt(ctx context.Context, arg sqlc.InsertHtlcAttemptParams) (int64, error)
	InsertRouteHop(ctx context.Context, arg sqlc.InsertRouteHopParams) (int64, error)
	InsertRouteHopMpp(ctx context.Context, arg sqlc.InsertRouteHopMppParams) error
	InsertRouteHopAmp(ctx context.Context, arg sqlc.InsertRouteHopAmpParams) error
	InsertRouteHopBlinded(ctx context.Context, arg sqlc.InsertRouteHopBlindedParams) error

	InsertPaymentAttemptFirstHopCustomRecord(ctx context.Context, arg sqlc.InsertPaymentAttemptFirstHopCustomRecordParams) error
	InsertPaymentHopCustomRecord(ctx context.Context, arg sqlc.InsertPaymentHopCustomRecordParams) error

	SettleAttempt(ctx context.Context, arg sqlc.SettleAttemptParams) error
	FailAttempt(ctx context.Context, arg sqlc.FailAttemptParams) error

	FailPayment(ctx context.Context, arg sqlc.FailPaymentParams) (sql.Result, error)

	DeletePayment(ctx context.Context, paymentID int64) error

	// DeleteFailedAttempts removes all failed HTLCs from the db for a
	// given payment.
	DeleteFailedAttempts(ctx context.Context, paymentID int64) error
}

SQLQueries is a subset of the sqlc.Querier interface that can be used to execute queries against the SQL payments tables.

type SQLStore

type SQLStore struct {
	// contains filtered or unexported fields
}

SQLStore represents a storage backend.

func NewSQLStore

func NewSQLStore(cfg *SQLStoreConfig, db BatchedSQLQueries,
	options ...OptionModifier) (*SQLStore, error)

NewSQLStore creates a new SQLStore instance given an open BatchedSQLQueries storage backend.

func (*SQLStore) DeleteFailedAttempts

func (s *SQLStore) DeleteFailedAttempts(ctx context.Context,
	paymentHash lntypes.Hash) error

DeleteFailedAttempts removes all failed HTLC attempts from the database for the specified payment, while preserving the payment record itself and any successful or in-flight attempts.

The method performs the following validations before deletion:

  • StatusInitiated: Can delete failed attempts
  • StatusInFlight: Cannot delete, returns ErrPaymentInFlight (active HTLCs still on the network)
  • StatusSucceeded: Can delete failed attempts (payment completed)
  • StatusFailed: Can delete failed attempts (payment permanently failed)

This method is idempotent - calling it multiple times on the same payment has no adverse effects.

This method is part of the PaymentControl interface, which is embedded in the PaymentWriter interface and ultimately the DB interface. It represents the final step (step 5) in the payment lifecycle control flow and should be called after a payment reaches a terminal state (succeeded or permanently failed) to clean up historical failed attempts.

func (*SQLStore) DeletePayment

func (s *SQLStore) DeletePayment(ctx context.Context, paymentHash lntypes.Hash,
	failedHtlcsOnly bool) error

DeletePayment removes a payment or its failed HTLC attempts from the database based on the failedAttemptsOnly flag.

If failedAttemptsOnly is true, this method deletes only the failed HTLC attempts for the payment while preserving the payment record itself and any successful or in-flight attempts. This is useful for cleaning up historical failed attempts after a payment reaches a terminal state.

If failedAttemptsOnly is false, this method deletes the entire payment record including all payment metadata, payment creation info, all HTLC attempts (both failed and successful), and associated data such as payment intents and custom records.

Before deletion, this method validates the payment status to ensure it's safe to delete:

  • StatusInitiated: Can be deleted (no HTLCs sent yet)
  • StatusInFlight: Cannot be deleted, returns ErrPaymentInFlight (active HTLCs on the network)
  • StatusSucceeded: Can be deleted (payment completed successfully)
  • StatusFailed: Can be deleted (payment has failed permanently)

Returns an error if the payment has in-flight HTLCs or if the payment doesn't exist.

This method is part of the PaymentWriter interface, which is embedded in the DB interface.

func (*SQLStore) DeletePayments

func (s *SQLStore) DeletePayments(ctx context.Context, failedOnly,
	failedHtlcsOnly bool) (int, error)

DeletePayments performs a batch deletion of payments or their failed HTLC attempts from the database based on the specified flags. This is a bulk operation that iterates through all payments and selectively deletes based on the criteria. The behavior is controlled by two flags:

If failedAttemptsOnly is true, only failed HTLC attempts are deleted while preserving the payment records and any successful or in-flight attempts. The return value is always 0 when deleting attempts only.

If failedAttemptsOnly is false, entire payment records are deleted including all associated data (HTLCs, metadata, intents). The return value is the number of payments deleted.

The failedOnly flag further filters which payments are processed:

  • failedOnly=true, failedAttemptsOnly=true: Delete failed attempts for StatusFailed payments only
  • failedOnly=false, failedAttemptsOnly=true: Delete failed attempts for all removable payments
  • failedOnly=true, failedAttemptsOnly=false: Delete entire payment records for StatusFailed payments only
  • failedOnly=false, failedAttemptsOnly=false: Delete all removable payment records (StatusInitiated, StatusSucceeded, StatusFailed)

Safety checks applied to all operations:

  • Payments with StatusInFlight are always skipped (cannot be safely deleted while HTLCs are on the network)
  • The payment status must pass the removable() check

Returns the number of complete payments deleted (0 if only deleting failed attempts). This is useful for cleanup operations, administrative maintenance, or freeing up database storage.

This method is part of the PaymentWriter interface, which is embedded in the DB interface.

TODO(ziggie): batch and use iterator instead, moreover we dont need to fetch the complete payment data for each payment, we can just fetch the payment ID and the resolution types to decide if the payment is removable.

func (*SQLStore) Fail

func (s *SQLStore) Fail(ctx context.Context, paymentHash lntypes.Hash,
	reason FailureReason) (*MPPayment, error)

Fail records the ultimate reason why a payment failed. This method stores the failure reason for record keeping but does not enforce that all HTLC attempts are resolved - HTLCs may still be in flight when this is called.

The payment's actual status transition to StatusFailed is determined by the payment state calculation, which considers both the recorded failure reason and the current state of all HTLC attempts. The status will transition to StatusFailed once all HTLCs are resolved and/or a failure reason is recorded.

NOTE: According to the interface contract, this should only be called when all active attempts are already failed. However, the implementation allows concurrent calls and does not validate this precondition, enabling the last failing attempt to record the failure reason without synchronization.

This method is part of the PaymentControl interface, which is embedded in the PaymentWriter interface and ultimately the DB interface. It represents step 4 in the payment lifecycle control flow.

func (*SQLStore) FailAttempt

func (s *SQLStore) FailAttempt(ctx context.Context, paymentHash lntypes.Hash,
	attemptID uint64, failInfo *HTLCFailInfo) (*MPPayment, error)

FailAttempt marks the specified HTLC attempt as failed, recording the failure reason, failure time, optional failure message, and the index of the node in the route that generated the failure. This information is atomically saved to the database for debugging and route optimization purposes.

For single-path payments, failing the only attempt may lead to the payment being retried or ultimately failed via the Fail method. For multi-shard (MPP/AMP) payments, individual shard failures don't necessarily fail the entire payment; additional attempts can be registered until sufficient shards succeed or the payment is permanently failed.

Returns the updated MPPayment with the attempt marked as failed and the payment state recalculated. The payment status remains StatusInFlight if other attempts are still in flight, or may transition based on the overall payment state.

This method is part of the PaymentControl interface, which is embedded in the PaymentWriter interface and ultimately the DB interface. It represents step 3b in the payment lifecycle control flow (step 3a is SettleAttempt), called after RegisterAttempt when an HTLC fails.

func (*SQLStore) FetchInFlightPayments

func (s *SQLStore) FetchInFlightPayments(ctx context.Context) ([]*MPPayment,
	error)

FetchInFlightPayments retrieves all payments that have HTLC attempts currently in flight (not yet settled or failed). These are payments with at least one HTLC attempt that has been registered but has no resolution record.

The SQLStore implementation provides a significant performance improvement over the KVStore implementation by using targeted SQL queries instead of scanning all payments.

This method is part of the PaymentReader interface, which is embedded in the DB interface. It's typically called during node startup to resume monitoring of pending payments and ensure HTLCs are properly tracked.

TODO(ziggie): Consider changing the interface to use a callback or iterator pattern instead of returning all payments at once. This would allow processing payments one at a time without holding them all in memory simultaneously:

  • Callback: func FetchInFlightPayments(ctx, func(*MPPayment) error) error
  • Iterator: func FetchInFlightPayments(ctx) (PaymentIterator, error)

While inflight payments are typically a small subset, this would improve memory efficiency for nodes with unusually high numbers of concurrent payments and would better leverage the existing pagination infrastructure.

func (*SQLStore) FetchPayment

func (s *SQLStore) FetchPayment(ctx context.Context,
	paymentHash lntypes.Hash) (*MPPayment, error)

FetchPayment retrieves a complete payment record from the database by its payment hash. The returned MPPayment includes all payment metadata such as creation info, payment status, current state, all HTLC attempts (both successful and failed), and the failure reason if the payment has been marked as failed.

Returns ErrPaymentNotInitiated if no payment with the given hash exists.

This is part of the DB interface.

func (*SQLStore) InitPayment

func (s *SQLStore) InitPayment(ctx context.Context, paymentHash lntypes.Hash,
	paymentCreationInfo *PaymentCreationInfo) error

InitPayment creates a new payment record in the database with the given payment hash and creation info.

Before creating the payment, this method checks if a payment with the same hash already exists and validates whether initialization is allowed based on the existing payment's status:

  • StatusInitiated: Returns ErrPaymentExists (payment already created, HTLCs may be in flight)
  • StatusInFlight: Returns ErrPaymentInFlight (payment currently being attempted)
  • StatusSucceeded: Returns ErrAlreadyPaid (payment already succeeded)
  • StatusFailed: Allows retry by deleting the old payment record and creating a new one

If no existing payment is found, a new payment record is created with StatusInitiated and stored with all associated metadata.

This method is part of the PaymentControl interface, which is embedded in the PaymentWriter interface and ultimately the DB interface, representing the first step in the payment lifecycle control flow.

func (*SQLStore) QueryPayments

func (s *SQLStore) QueryPayments(ctx context.Context, query Query) (Response,
	error)

QueryPayments queries and retrieves payments from the database with support for filtering, pagination, and efficient batch loading of related data.

The function accepts a Query parameter that controls:

  • Pagination: IndexOffset specifies where to start (exclusive), and MaxPayments limits the number of results returned
  • Ordering: Reversed flag determines if results are returned in reverse chronological order
  • Filtering: CreationDateStart/End filter by creation time, and IncludeIncomplete controls whether non-succeeded payments are included
  • Metadata: CountTotal flag determines if the total payment count should be calculated

The function optimizes performance by loading all related data (HTLCs, sequences, failure reasons, etc.) for multiple payments in a single batch query, rather than fetching each payment's data individually.

Returns a Response containing:

  • Payments: the list of matching payments with complete data
  • FirstIndexOffset/LastIndexOffset: pagination cursors for the first and last payment in the result set
  • TotalCount: total number of payments in the database (if CountTotal was requested, otherwise 0)

This is part of the DB interface.

func (*SQLStore) RegisterAttempt

func (s *SQLStore) RegisterAttempt(ctx context.Context,
	paymentHash lntypes.Hash, attempt *HTLCAttemptInfo) (*MPPayment,
	error)

RegisterAttempt atomically records a new HTLC attempt for the specified payment. The attempt includes the attempt ID, session key, route information (hops, timelocks, amounts), and optional data such as MPP/AMP parameters, blinded route data, and custom records.

Returns the updated MPPayment with the new attempt appended to the HTLCs slice, and the payment state recalculated. Returns an error if the payment doesn't exist or validation fails.

This method is part of the PaymentControl interface, which is embedded in the PaymentWriter interface and ultimately the DB interface. It represents step 2 in the payment lifecycle control flow, called after InitPayment and potentially multiple times for multi-path payments.

func (*SQLStore) SettleAttempt

func (s *SQLStore) SettleAttempt(ctx context.Context, paymentHash lntypes.Hash,
	attemptID uint64, settleInfo *HTLCSettleInfo) (*MPPayment, error)

SettleAttempt marks the specified HTLC attempt as successfully settled, recording the payment preimage and settlement time. The preimage serves as cryptographic proof of payment and is atomically saved to the database.

This method is part of the PaymentControl interface, which is embedded in the PaymentWriter interface and ultimately the DB interface. It represents step 3a in the payment lifecycle control flow (step 3b is FailAttempt), called after RegisterAttempt when an HTLC successfully completes.

type SQLStoreConfig

type SQLStoreConfig struct {
	// QueryConfig holds configuration values for SQL queries.
	QueryCfg *sqldb.QueryConfig
}

SQLStoreConfig holds the configuration for the SQLStore.

type StoreOptions

type StoreOptions struct {
	// NoMigration allows to open the database in readonly mode
	NoMigration bool
}

StoreOptions holds parameters for the KVStore.

func DefaultOptions

func DefaultOptions() *StoreOptions

DefaultOptions returns a StoreOptions populated with default values.

type TestHarness

type TestHarness interface {
	// AssertPaymentIndex checks that a payment is correctly indexed.
	// For KV: verifies the payment index bucket entry exists and points
	// to the correct payment hash.
	// For SQL: no-op (SQL doesn't use a separate index bucket).
	AssertPaymentIndex(t *testing.T, expectedHash lntypes.Hash)

	// AssertNoIndex checks that an index for a sequence number doesn't
	// exist.
	// For KV: verifies the index bucket entry is deleted.
	// For SQL: no-op.
	AssertNoIndex(t *testing.T, seqNr uint64)
}

TestHarness provides implementation-specific test utilities for the payments database. Different database backends (KV, SQL) have different internal structures and indexing mechanisms, so this interface allows tests to verify implementation-specific behavior without coupling the test logic to a particular backend.

type UnknownElementType

type UnknownElementType = channeldb.UnknownElementType

UnknownElementType is an alias for channeldb.UnknownElementType.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL