mirror of
https://github.com/lightningnetwork/lnd.git
synced 2026-08-15 12:50:15 +02:00
Older LND versions could create multiple payments for the same hash. We need to preserve those historical records during KV→SQL migration, but they don’t fit the normal payment schema because we enforce a unique payment hash constraint. Introduce a lean payment_duplicates table to store only the essential fields (identifier, amount, timestamps, settle/fail data). This keeps the primary payment records stable and makes the migration deterministic even when duplicate records lack attempt info. The table is intentionally minimal and can be dropped after migration if no duplicate payments exist. For now there is no logic in place which allows the noderunner to fetch duplicate payments after the migration.
1289 lines
31 KiB
Go
1289 lines
31 KiB
Go
// Code generated by sqlc. DO NOT EDIT.
|
|
// versions:
|
|
// sqlc v1.29.0
|
|
// source: payments.sql
|
|
|
|
package sqlc
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
const countPayments = `-- name: CountPayments :one
|
|
SELECT COUNT(*) FROM payments
|
|
`
|
|
|
|
func (q *Queries) CountPayments(ctx context.Context) (int64, error) {
|
|
row := q.db.QueryRowContext(ctx, countPayments)
|
|
var count int64
|
|
err := row.Scan(&count)
|
|
return count, err
|
|
}
|
|
|
|
const deleteFailedAttempts = `-- name: DeleteFailedAttempts :exec
|
|
DELETE FROM payment_htlc_attempts WHERE payment_id = $1 AND attempt_index IN (
|
|
SELECT attempt_index FROM payment_htlc_attempt_resolutions WHERE resolution_type = 2
|
|
)
|
|
`
|
|
|
|
// Delete all failed HTLC attempts for the given payment. Resolution type 2
|
|
// indicates a failed attempt.
|
|
func (q *Queries) DeleteFailedAttempts(ctx context.Context, paymentID int64) error {
|
|
_, err := q.db.ExecContext(ctx, deleteFailedAttempts, paymentID)
|
|
return err
|
|
}
|
|
|
|
const deletePayment = `-- name: DeletePayment :exec
|
|
DELETE FROM payments WHERE id = $1
|
|
`
|
|
|
|
func (q *Queries) DeletePayment(ctx context.Context, id int64) error {
|
|
_, err := q.db.ExecContext(ctx, deletePayment, id)
|
|
return err
|
|
}
|
|
|
|
const failAttempt = `-- name: FailAttempt :exec
|
|
INSERT INTO payment_htlc_attempt_resolutions (
|
|
attempt_index,
|
|
resolution_time,
|
|
resolution_type,
|
|
failure_source_index,
|
|
htlc_fail_reason,
|
|
failure_msg
|
|
)
|
|
VALUES (
|
|
$1,
|
|
$2,
|
|
$3,
|
|
$4,
|
|
$5,
|
|
$6
|
|
)
|
|
`
|
|
|
|
type FailAttemptParams struct {
|
|
AttemptIndex int64
|
|
ResolutionTime time.Time
|
|
ResolutionType int32
|
|
FailureSourceIndex sql.NullInt32
|
|
HtlcFailReason sql.NullInt32
|
|
FailureMsg []byte
|
|
}
|
|
|
|
func (q *Queries) FailAttempt(ctx context.Context, arg FailAttemptParams) error {
|
|
_, err := q.db.ExecContext(ctx, failAttempt,
|
|
arg.AttemptIndex,
|
|
arg.ResolutionTime,
|
|
arg.ResolutionType,
|
|
arg.FailureSourceIndex,
|
|
arg.HtlcFailReason,
|
|
arg.FailureMsg,
|
|
)
|
|
return err
|
|
}
|
|
|
|
const failPayment = `-- name: FailPayment :execresult
|
|
UPDATE payments SET fail_reason = $1 WHERE payment_identifier = $2
|
|
`
|
|
|
|
type FailPaymentParams struct {
|
|
FailReason sql.NullInt32
|
|
PaymentIdentifier []byte
|
|
}
|
|
|
|
func (q *Queries) FailPayment(ctx context.Context, arg FailPaymentParams) (sql.Result, error) {
|
|
return q.db.ExecContext(ctx, failPayment, arg.FailReason, arg.PaymentIdentifier)
|
|
}
|
|
|
|
const fetchAllInflightAttempts = `-- name: FetchAllInflightAttempts :many
|
|
SELECT
|
|
ha.id,
|
|
ha.attempt_index,
|
|
ha.payment_id,
|
|
ha.session_key,
|
|
ha.attempt_time,
|
|
ha.payment_hash,
|
|
ha.first_hop_amount_msat,
|
|
ha.route_total_time_lock,
|
|
ha.route_total_amount,
|
|
ha.route_source_key
|
|
FROM payment_htlc_attempts ha
|
|
WHERE NOT EXISTS (
|
|
SELECT 1 FROM payment_htlc_attempt_resolutions hr
|
|
WHERE hr.attempt_index = ha.attempt_index
|
|
)
|
|
AND ha.attempt_index > $1
|
|
ORDER BY ha.attempt_index ASC
|
|
LIMIT $2
|
|
`
|
|
|
|
type FetchAllInflightAttemptsParams struct {
|
|
AttemptIndex int64
|
|
Limit int32
|
|
}
|
|
|
|
// Fetch all inflight attempts with their payment data using pagination.
|
|
// Returns attempt data joined with payment and intent data to avoid separate queries.
|
|
func (q *Queries) FetchAllInflightAttempts(ctx context.Context, arg FetchAllInflightAttemptsParams) ([]PaymentHtlcAttempt, error) {
|
|
rows, err := q.db.QueryContext(ctx, fetchAllInflightAttempts, arg.AttemptIndex, arg.Limit)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []PaymentHtlcAttempt
|
|
for rows.Next() {
|
|
var i PaymentHtlcAttempt
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.AttemptIndex,
|
|
&i.PaymentID,
|
|
&i.SessionKey,
|
|
&i.AttemptTime,
|
|
&i.PaymentHash,
|
|
&i.FirstHopAmountMsat,
|
|
&i.RouteTotalTimeLock,
|
|
&i.RouteTotalAmount,
|
|
&i.RouteSourceKey,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Close(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const fetchHopLevelCustomRecords = `-- name: FetchHopLevelCustomRecords :many
|
|
SELECT
|
|
l.id,
|
|
l.hop_id,
|
|
l.key,
|
|
l.value
|
|
FROM payment_hop_custom_records l
|
|
WHERE l.hop_id IN (/*SLICE:hop_ids*/?)
|
|
ORDER BY l.hop_id ASC, l.key ASC
|
|
`
|
|
|
|
func (q *Queries) FetchHopLevelCustomRecords(ctx context.Context, hopIds []int64) ([]PaymentHopCustomRecord, error) {
|
|
query := fetchHopLevelCustomRecords
|
|
var queryParams []interface{}
|
|
if len(hopIds) > 0 {
|
|
for _, v := range hopIds {
|
|
queryParams = append(queryParams, v)
|
|
}
|
|
query = strings.Replace(query, "/*SLICE:hop_ids*/?", makeQueryParams(len(queryParams), len(hopIds)), 1)
|
|
} else {
|
|
query = strings.Replace(query, "/*SLICE:hop_ids*/?", "NULL", 1)
|
|
}
|
|
rows, err := q.db.QueryContext(ctx, query, queryParams...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []PaymentHopCustomRecord
|
|
for rows.Next() {
|
|
var i PaymentHopCustomRecord
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.HopID,
|
|
&i.Key,
|
|
&i.Value,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Close(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const fetchHopsForAttempts = `-- name: FetchHopsForAttempts :many
|
|
SELECT
|
|
h.id,
|
|
h.htlc_attempt_index,
|
|
h.hop_index,
|
|
h.pub_key,
|
|
h.scid,
|
|
h.outgoing_time_lock,
|
|
h.amt_to_forward,
|
|
h.meta_data,
|
|
m.payment_addr AS mpp_payment_addr,
|
|
m.total_msat AS mpp_total_msat,
|
|
a.root_share AS amp_root_share,
|
|
a.set_id AS amp_set_id,
|
|
a.child_index AS amp_child_index,
|
|
b.encrypted_data,
|
|
b.blinding_point,
|
|
b.blinded_path_total_amt
|
|
FROM payment_route_hops h
|
|
LEFT JOIN payment_route_hop_mpp m ON m.hop_id = h.id
|
|
LEFT JOIN payment_route_hop_amp a ON a.hop_id = h.id
|
|
LEFT JOIN payment_route_hop_blinded b ON b.hop_id = h.id
|
|
WHERE h.htlc_attempt_index IN (/*SLICE:htlc_attempt_indices*/?)
|
|
ORDER BY h.htlc_attempt_index ASC, h.hop_index ASC
|
|
`
|
|
|
|
type FetchHopsForAttemptsRow struct {
|
|
ID int64
|
|
HtlcAttemptIndex int64
|
|
HopIndex int32
|
|
PubKey []byte
|
|
Scid string
|
|
OutgoingTimeLock int32
|
|
AmtToForward int64
|
|
MetaData []byte
|
|
MppPaymentAddr []byte
|
|
MppTotalMsat sql.NullInt64
|
|
AmpRootShare []byte
|
|
AmpSetID []byte
|
|
AmpChildIndex sql.NullInt32
|
|
EncryptedData []byte
|
|
BlindingPoint []byte
|
|
BlindedPathTotalAmt sql.NullInt64
|
|
}
|
|
|
|
func (q *Queries) FetchHopsForAttempts(ctx context.Context, htlcAttemptIndices []int64) ([]FetchHopsForAttemptsRow, error) {
|
|
query := fetchHopsForAttempts
|
|
var queryParams []interface{}
|
|
if len(htlcAttemptIndices) > 0 {
|
|
for _, v := range htlcAttemptIndices {
|
|
queryParams = append(queryParams, v)
|
|
}
|
|
query = strings.Replace(query, "/*SLICE:htlc_attempt_indices*/?", makeQueryParams(len(queryParams), len(htlcAttemptIndices)), 1)
|
|
} else {
|
|
query = strings.Replace(query, "/*SLICE:htlc_attempt_indices*/?", "NULL", 1)
|
|
}
|
|
rows, err := q.db.QueryContext(ctx, query, queryParams...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []FetchHopsForAttemptsRow
|
|
for rows.Next() {
|
|
var i FetchHopsForAttemptsRow
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.HtlcAttemptIndex,
|
|
&i.HopIndex,
|
|
&i.PubKey,
|
|
&i.Scid,
|
|
&i.OutgoingTimeLock,
|
|
&i.AmtToForward,
|
|
&i.MetaData,
|
|
&i.MppPaymentAddr,
|
|
&i.MppTotalMsat,
|
|
&i.AmpRootShare,
|
|
&i.AmpSetID,
|
|
&i.AmpChildIndex,
|
|
&i.EncryptedData,
|
|
&i.BlindingPoint,
|
|
&i.BlindedPathTotalAmt,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Close(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const fetchHtlcAttemptResolutionsForPayments = `-- name: FetchHtlcAttemptResolutionsForPayments :many
|
|
SELECT
|
|
ha.payment_id,
|
|
hr.resolution_type
|
|
FROM payment_htlc_attempts ha
|
|
LEFT JOIN payment_htlc_attempt_resolutions hr ON hr.attempt_index = ha.attempt_index
|
|
WHERE ha.payment_id IN (/*SLICE:payment_ids*/?)
|
|
`
|
|
|
|
type FetchHtlcAttemptResolutionsForPaymentsRow struct {
|
|
PaymentID int64
|
|
ResolutionType sql.NullInt32
|
|
}
|
|
|
|
// Batch query to fetch only HTLC resolution status for multiple payments.
|
|
// We don't need to order by payment_id and attempt_time because we will
|
|
// group the resolutions by payment_id in the background.
|
|
func (q *Queries) FetchHtlcAttemptResolutionsForPayments(ctx context.Context, paymentIds []int64) ([]FetchHtlcAttemptResolutionsForPaymentsRow, error) {
|
|
query := fetchHtlcAttemptResolutionsForPayments
|
|
var queryParams []interface{}
|
|
if len(paymentIds) > 0 {
|
|
for _, v := range paymentIds {
|
|
queryParams = append(queryParams, v)
|
|
}
|
|
query = strings.Replace(query, "/*SLICE:payment_ids*/?", makeQueryParams(len(queryParams), len(paymentIds)), 1)
|
|
} else {
|
|
query = strings.Replace(query, "/*SLICE:payment_ids*/?", "NULL", 1)
|
|
}
|
|
rows, err := q.db.QueryContext(ctx, query, queryParams...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []FetchHtlcAttemptResolutionsForPaymentsRow
|
|
for rows.Next() {
|
|
var i FetchHtlcAttemptResolutionsForPaymentsRow
|
|
if err := rows.Scan(&i.PaymentID, &i.ResolutionType); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Close(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const fetchHtlcAttemptsForPayments = `-- name: FetchHtlcAttemptsForPayments :many
|
|
SELECT
|
|
ha.id,
|
|
ha.attempt_index,
|
|
ha.payment_id,
|
|
ha.session_key,
|
|
ha.attempt_time,
|
|
ha.payment_hash,
|
|
ha.first_hop_amount_msat,
|
|
ha.route_total_time_lock,
|
|
ha.route_total_amount,
|
|
ha.route_source_key,
|
|
hr.resolution_type,
|
|
hr.resolution_time,
|
|
hr.failure_source_index,
|
|
hr.htlc_fail_reason,
|
|
hr.failure_msg,
|
|
hr.settle_preimage
|
|
FROM payment_htlc_attempts ha
|
|
LEFT JOIN payment_htlc_attempt_resolutions hr ON hr.attempt_index = ha.attempt_index
|
|
WHERE ha.payment_id IN (/*SLICE:payment_ids*/?)
|
|
ORDER BY ha.payment_id ASC, ha.attempt_time ASC
|
|
`
|
|
|
|
type FetchHtlcAttemptsForPaymentsRow struct {
|
|
ID int64
|
|
AttemptIndex int64
|
|
PaymentID int64
|
|
SessionKey []byte
|
|
AttemptTime time.Time
|
|
PaymentHash []byte
|
|
FirstHopAmountMsat int64
|
|
RouteTotalTimeLock int32
|
|
RouteTotalAmount int64
|
|
RouteSourceKey []byte
|
|
ResolutionType sql.NullInt32
|
|
ResolutionTime sql.NullTime
|
|
FailureSourceIndex sql.NullInt32
|
|
HtlcFailReason sql.NullInt32
|
|
FailureMsg []byte
|
|
SettlePreimage []byte
|
|
}
|
|
|
|
func (q *Queries) FetchHtlcAttemptsForPayments(ctx context.Context, paymentIds []int64) ([]FetchHtlcAttemptsForPaymentsRow, error) {
|
|
query := fetchHtlcAttemptsForPayments
|
|
var queryParams []interface{}
|
|
if len(paymentIds) > 0 {
|
|
for _, v := range paymentIds {
|
|
queryParams = append(queryParams, v)
|
|
}
|
|
query = strings.Replace(query, "/*SLICE:payment_ids*/?", makeQueryParams(len(queryParams), len(paymentIds)), 1)
|
|
} else {
|
|
query = strings.Replace(query, "/*SLICE:payment_ids*/?", "NULL", 1)
|
|
}
|
|
rows, err := q.db.QueryContext(ctx, query, queryParams...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []FetchHtlcAttemptsForPaymentsRow
|
|
for rows.Next() {
|
|
var i FetchHtlcAttemptsForPaymentsRow
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.AttemptIndex,
|
|
&i.PaymentID,
|
|
&i.SessionKey,
|
|
&i.AttemptTime,
|
|
&i.PaymentHash,
|
|
&i.FirstHopAmountMsat,
|
|
&i.RouteTotalTimeLock,
|
|
&i.RouteTotalAmount,
|
|
&i.RouteSourceKey,
|
|
&i.ResolutionType,
|
|
&i.ResolutionTime,
|
|
&i.FailureSourceIndex,
|
|
&i.HtlcFailReason,
|
|
&i.FailureMsg,
|
|
&i.SettlePreimage,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Close(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const fetchPayment = `-- name: FetchPayment :one
|
|
SELECT
|
|
p.id, p.amount_msat, p.created_at, p.payment_identifier, p.fail_reason,
|
|
i.intent_type AS "intent_type",
|
|
i.intent_payload AS "intent_payload"
|
|
FROM payments p
|
|
LEFT JOIN payment_intents i ON i.payment_id = p.id
|
|
WHERE p.payment_identifier = $1
|
|
`
|
|
|
|
type FetchPaymentRow struct {
|
|
Payment Payment
|
|
IntentType sql.NullInt16
|
|
IntentPayload []byte
|
|
}
|
|
|
|
func (q *Queries) FetchPayment(ctx context.Context, paymentIdentifier []byte) (FetchPaymentRow, error) {
|
|
row := q.db.QueryRowContext(ctx, fetchPayment, paymentIdentifier)
|
|
var i FetchPaymentRow
|
|
err := row.Scan(
|
|
&i.Payment.ID,
|
|
&i.Payment.AmountMsat,
|
|
&i.Payment.CreatedAt,
|
|
&i.Payment.PaymentIdentifier,
|
|
&i.Payment.FailReason,
|
|
&i.IntentType,
|
|
&i.IntentPayload,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const fetchPaymentDuplicates = `-- name: FetchPaymentDuplicates :many
|
|
SELECT
|
|
id,
|
|
payment_id,
|
|
amount_msat,
|
|
created_at,
|
|
fail_reason,
|
|
settle_preimage,
|
|
settle_time
|
|
FROM payment_duplicates
|
|
WHERE payment_id = $1
|
|
ORDER BY id ASC
|
|
`
|
|
|
|
// Fetch all duplicate payment records from the payment_duplicates table for
|
|
// a given payment ID.
|
|
func (q *Queries) FetchPaymentDuplicates(ctx context.Context, paymentID int64) ([]PaymentDuplicate, error) {
|
|
rows, err := q.db.QueryContext(ctx, fetchPaymentDuplicates, paymentID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []PaymentDuplicate
|
|
for rows.Next() {
|
|
var i PaymentDuplicate
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.PaymentID,
|
|
&i.AmountMsat,
|
|
&i.CreatedAt,
|
|
&i.FailReason,
|
|
&i.SettlePreimage,
|
|
&i.SettleTime,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Close(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const fetchPaymentLevelFirstHopCustomRecords = `-- name: FetchPaymentLevelFirstHopCustomRecords :many
|
|
SELECT
|
|
l.id,
|
|
l.payment_id,
|
|
l.key,
|
|
l.value
|
|
FROM payment_first_hop_custom_records l
|
|
WHERE l.payment_id IN (/*SLICE:payment_ids*/?)
|
|
ORDER BY l.payment_id ASC, l.key ASC
|
|
`
|
|
|
|
func (q *Queries) FetchPaymentLevelFirstHopCustomRecords(ctx context.Context, paymentIds []int64) ([]PaymentFirstHopCustomRecord, error) {
|
|
query := fetchPaymentLevelFirstHopCustomRecords
|
|
var queryParams []interface{}
|
|
if len(paymentIds) > 0 {
|
|
for _, v := range paymentIds {
|
|
queryParams = append(queryParams, v)
|
|
}
|
|
query = strings.Replace(query, "/*SLICE:payment_ids*/?", makeQueryParams(len(queryParams), len(paymentIds)), 1)
|
|
} else {
|
|
query = strings.Replace(query, "/*SLICE:payment_ids*/?", "NULL", 1)
|
|
}
|
|
rows, err := q.db.QueryContext(ctx, query, queryParams...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []PaymentFirstHopCustomRecord
|
|
for rows.Next() {
|
|
var i PaymentFirstHopCustomRecord
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.PaymentID,
|
|
&i.Key,
|
|
&i.Value,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Close(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const fetchPaymentsByIDs = `-- name: FetchPaymentsByIDs :many
|
|
SELECT
|
|
p.id,
|
|
p.amount_msat,
|
|
p.created_at,
|
|
p.payment_identifier,
|
|
p.fail_reason,
|
|
pi.intent_type,
|
|
pi.intent_payload
|
|
FROM payments p
|
|
LEFT JOIN payment_intents pi ON pi.payment_id = p.id
|
|
WHERE p.id IN (/*SLICE:payment_ids*/?)
|
|
ORDER BY p.id ASC
|
|
`
|
|
|
|
type FetchPaymentsByIDsRow struct {
|
|
ID int64
|
|
AmountMsat int64
|
|
CreatedAt time.Time
|
|
PaymentIdentifier []byte
|
|
FailReason sql.NullInt32
|
|
IntentType sql.NullInt16
|
|
IntentPayload []byte
|
|
}
|
|
|
|
// Batch fetch payment and intent data for a set of payment IDs.
|
|
// Used to avoid fetching redundant payment data when processing multiple
|
|
// attempts for the same payment.
|
|
func (q *Queries) FetchPaymentsByIDs(ctx context.Context, paymentIds []int64) ([]FetchPaymentsByIDsRow, error) {
|
|
query := fetchPaymentsByIDs
|
|
var queryParams []interface{}
|
|
if len(paymentIds) > 0 {
|
|
for _, v := range paymentIds {
|
|
queryParams = append(queryParams, v)
|
|
}
|
|
query = strings.Replace(query, "/*SLICE:payment_ids*/?", makeQueryParams(len(queryParams), len(paymentIds)), 1)
|
|
} else {
|
|
query = strings.Replace(query, "/*SLICE:payment_ids*/?", "NULL", 1)
|
|
}
|
|
rows, err := q.db.QueryContext(ctx, query, queryParams...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []FetchPaymentsByIDsRow
|
|
for rows.Next() {
|
|
var i FetchPaymentsByIDsRow
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.AmountMsat,
|
|
&i.CreatedAt,
|
|
&i.PaymentIdentifier,
|
|
&i.FailReason,
|
|
&i.IntentType,
|
|
&i.IntentPayload,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Close(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const fetchPaymentsByIDsMig = `-- name: FetchPaymentsByIDsMig :many
|
|
SELECT
|
|
p.id,
|
|
p.amount_msat,
|
|
p.created_at,
|
|
p.payment_identifier,
|
|
p.fail_reason,
|
|
COUNT(ha.id) AS htlc_attempt_count
|
|
FROM payments p
|
|
LEFT JOIN payment_htlc_attempts ha ON ha.payment_id = p.id
|
|
WHERE p.id IN (/*SLICE:payment_ids*/?)
|
|
GROUP BY p.id, p.amount_msat, p.created_at, p.payment_identifier, p.fail_reason
|
|
ORDER BY p.id ASC
|
|
`
|
|
|
|
type FetchPaymentsByIDsMigRow struct {
|
|
ID int64
|
|
AmountMsat int64
|
|
CreatedAt time.Time
|
|
PaymentIdentifier []byte
|
|
FailReason sql.NullInt32
|
|
HtlcAttemptCount int64
|
|
}
|
|
|
|
// Migration-specific batch fetch that returns payment data along with HTLC
|
|
// attempt counts for structural validation during KV to SQL migration.
|
|
func (q *Queries) FetchPaymentsByIDsMig(ctx context.Context, paymentIds []int64) ([]FetchPaymentsByIDsMigRow, error) {
|
|
query := fetchPaymentsByIDsMig
|
|
var queryParams []interface{}
|
|
if len(paymentIds) > 0 {
|
|
for _, v := range paymentIds {
|
|
queryParams = append(queryParams, v)
|
|
}
|
|
query = strings.Replace(query, "/*SLICE:payment_ids*/?", makeQueryParams(len(queryParams), len(paymentIds)), 1)
|
|
} else {
|
|
query = strings.Replace(query, "/*SLICE:payment_ids*/?", "NULL", 1)
|
|
}
|
|
rows, err := q.db.QueryContext(ctx, query, queryParams...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []FetchPaymentsByIDsMigRow
|
|
for rows.Next() {
|
|
var i FetchPaymentsByIDsMigRow
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.AmountMsat,
|
|
&i.CreatedAt,
|
|
&i.PaymentIdentifier,
|
|
&i.FailReason,
|
|
&i.HtlcAttemptCount,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Close(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const fetchRouteLevelFirstHopCustomRecords = `-- name: FetchRouteLevelFirstHopCustomRecords :many
|
|
SELECT
|
|
l.id,
|
|
l.htlc_attempt_index,
|
|
l.key,
|
|
l.value
|
|
FROM payment_attempt_first_hop_custom_records l
|
|
WHERE l.htlc_attempt_index IN (/*SLICE:htlc_attempt_indices*/?)
|
|
ORDER BY l.htlc_attempt_index ASC, l.key ASC
|
|
`
|
|
|
|
func (q *Queries) FetchRouteLevelFirstHopCustomRecords(ctx context.Context, htlcAttemptIndices []int64) ([]PaymentAttemptFirstHopCustomRecord, error) {
|
|
query := fetchRouteLevelFirstHopCustomRecords
|
|
var queryParams []interface{}
|
|
if len(htlcAttemptIndices) > 0 {
|
|
for _, v := range htlcAttemptIndices {
|
|
queryParams = append(queryParams, v)
|
|
}
|
|
query = strings.Replace(query, "/*SLICE:htlc_attempt_indices*/?", makeQueryParams(len(queryParams), len(htlcAttemptIndices)), 1)
|
|
} else {
|
|
query = strings.Replace(query, "/*SLICE:htlc_attempt_indices*/?", "NULL", 1)
|
|
}
|
|
rows, err := q.db.QueryContext(ctx, query, queryParams...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []PaymentAttemptFirstHopCustomRecord
|
|
for rows.Next() {
|
|
var i PaymentAttemptFirstHopCustomRecord
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.HtlcAttemptIndex,
|
|
&i.Key,
|
|
&i.Value,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Close(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const filterPayments = `-- name: FilterPayments :many
|
|
/* ─────────────────────────────────────────────
|
|
fetch queries
|
|
─────────────────────────────────────────────
|
|
*/
|
|
|
|
SELECT
|
|
p.id, p.amount_msat, p.created_at, p.payment_identifier, p.fail_reason,
|
|
i.intent_type AS "intent_type",
|
|
i.intent_payload AS "intent_payload"
|
|
FROM payments p
|
|
LEFT JOIN payment_intents i ON i.payment_id = p.id
|
|
WHERE (
|
|
p.id > $1 OR
|
|
$1 IS NULL
|
|
) AND (
|
|
p.id < $2 OR
|
|
$2 IS NULL
|
|
) AND (
|
|
p.created_at >= $3 OR
|
|
$3 IS NULL
|
|
) AND (
|
|
p.created_at <= $4 OR
|
|
$4 IS NULL
|
|
) AND (
|
|
i.intent_type = $5 OR
|
|
$5 IS NULL OR i.intent_type IS NULL
|
|
)
|
|
ORDER BY
|
|
CASE WHEN $6 = false OR $6 IS NULL THEN p.id END ASC,
|
|
CASE WHEN $6 = true THEN p.id END DESC
|
|
LIMIT $7
|
|
`
|
|
|
|
type FilterPaymentsParams struct {
|
|
IndexOffsetGet sql.NullInt64
|
|
IndexOffsetLet sql.NullInt64
|
|
CreatedAfter sql.NullTime
|
|
CreatedBefore sql.NullTime
|
|
IntentType sql.NullInt16
|
|
Reverse interface{}
|
|
NumLimit int32
|
|
}
|
|
|
|
type FilterPaymentsRow struct {
|
|
Payment Payment
|
|
IntentType sql.NullInt16
|
|
IntentPayload []byte
|
|
}
|
|
|
|
func (q *Queries) FilterPayments(ctx context.Context, arg FilterPaymentsParams) ([]FilterPaymentsRow, error) {
|
|
rows, err := q.db.QueryContext(ctx, filterPayments,
|
|
arg.IndexOffsetGet,
|
|
arg.IndexOffsetLet,
|
|
arg.CreatedAfter,
|
|
arg.CreatedBefore,
|
|
arg.IntentType,
|
|
arg.Reverse,
|
|
arg.NumLimit,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []FilterPaymentsRow
|
|
for rows.Next() {
|
|
var i FilterPaymentsRow
|
|
if err := rows.Scan(
|
|
&i.Payment.ID,
|
|
&i.Payment.AmountMsat,
|
|
&i.Payment.CreatedAt,
|
|
&i.Payment.PaymentIdentifier,
|
|
&i.Payment.FailReason,
|
|
&i.IntentType,
|
|
&i.IntentPayload,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Close(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const insertHtlcAttempt = `-- name: InsertHtlcAttempt :one
|
|
INSERT INTO payment_htlc_attempts (
|
|
payment_id,
|
|
attempt_index,
|
|
session_key,
|
|
attempt_time,
|
|
payment_hash,
|
|
first_hop_amount_msat,
|
|
route_total_time_lock,
|
|
route_total_amount,
|
|
route_source_key)
|
|
VALUES (
|
|
$1,
|
|
$2,
|
|
$3,
|
|
$4,
|
|
$5,
|
|
$6,
|
|
$7,
|
|
$8,
|
|
$9)
|
|
RETURNING id
|
|
`
|
|
|
|
type InsertHtlcAttemptParams struct {
|
|
PaymentID int64
|
|
AttemptIndex int64
|
|
SessionKey []byte
|
|
AttemptTime time.Time
|
|
PaymentHash []byte
|
|
FirstHopAmountMsat int64
|
|
RouteTotalTimeLock int32
|
|
RouteTotalAmount int64
|
|
RouteSourceKey []byte
|
|
}
|
|
|
|
func (q *Queries) InsertHtlcAttempt(ctx context.Context, arg InsertHtlcAttemptParams) (int64, error) {
|
|
row := q.db.QueryRowContext(ctx, insertHtlcAttempt,
|
|
arg.PaymentID,
|
|
arg.AttemptIndex,
|
|
arg.SessionKey,
|
|
arg.AttemptTime,
|
|
arg.PaymentHash,
|
|
arg.FirstHopAmountMsat,
|
|
arg.RouteTotalTimeLock,
|
|
arg.RouteTotalAmount,
|
|
arg.RouteSourceKey,
|
|
)
|
|
var id int64
|
|
err := row.Scan(&id)
|
|
return id, err
|
|
}
|
|
|
|
const insertPayment = `-- name: InsertPayment :one
|
|
INSERT INTO payments (
|
|
amount_msat,
|
|
created_at,
|
|
payment_identifier,
|
|
fail_reason)
|
|
VALUES (
|
|
$1,
|
|
$2,
|
|
$3,
|
|
NULL
|
|
)
|
|
RETURNING id
|
|
`
|
|
|
|
type InsertPaymentParams struct {
|
|
AmountMsat int64
|
|
CreatedAt time.Time
|
|
PaymentIdentifier []byte
|
|
}
|
|
|
|
// Insert a new payment and return its ID.
|
|
// When creating a payment we don't have a fail reason because we start the
|
|
// payment process.
|
|
func (q *Queries) InsertPayment(ctx context.Context, arg InsertPaymentParams) (int64, error) {
|
|
row := q.db.QueryRowContext(ctx, insertPayment, arg.AmountMsat, arg.CreatedAt, arg.PaymentIdentifier)
|
|
var id int64
|
|
err := row.Scan(&id)
|
|
return id, err
|
|
}
|
|
|
|
const insertPaymentAttemptFirstHopCustomRecord = `-- name: InsertPaymentAttemptFirstHopCustomRecord :exec
|
|
INSERT INTO payment_attempt_first_hop_custom_records (
|
|
htlc_attempt_index,
|
|
key,
|
|
value
|
|
)
|
|
VALUES (
|
|
$1,
|
|
$2,
|
|
$3
|
|
)
|
|
`
|
|
|
|
type InsertPaymentAttemptFirstHopCustomRecordParams struct {
|
|
HtlcAttemptIndex int64
|
|
Key int64
|
|
Value []byte
|
|
}
|
|
|
|
func (q *Queries) InsertPaymentAttemptFirstHopCustomRecord(ctx context.Context, arg InsertPaymentAttemptFirstHopCustomRecordParams) error {
|
|
_, err := q.db.ExecContext(ctx, insertPaymentAttemptFirstHopCustomRecord, arg.HtlcAttemptIndex, arg.Key, arg.Value)
|
|
return err
|
|
}
|
|
|
|
const insertPaymentDuplicateMig = `-- name: InsertPaymentDuplicateMig :one
|
|
INSERT INTO payment_duplicates (
|
|
payment_id,
|
|
amount_msat,
|
|
created_at,
|
|
fail_reason,
|
|
settle_preimage,
|
|
settle_time
|
|
)
|
|
VALUES (
|
|
$1,
|
|
$2,
|
|
$3,
|
|
$4,
|
|
$5,
|
|
$6
|
|
)
|
|
RETURNING id
|
|
`
|
|
|
|
type InsertPaymentDuplicateMigParams struct {
|
|
PaymentID int64
|
|
AmountMsat int64
|
|
CreatedAt time.Time
|
|
FailReason sql.NullInt32
|
|
SettlePreimage []byte
|
|
SettleTime sql.NullTime
|
|
}
|
|
|
|
// Insert a duplicate payment record into the payment_duplicates table and
|
|
// return its ID.
|
|
func (q *Queries) InsertPaymentDuplicateMig(ctx context.Context, arg InsertPaymentDuplicateMigParams) (int64, error) {
|
|
row := q.db.QueryRowContext(ctx, insertPaymentDuplicateMig,
|
|
arg.PaymentID,
|
|
arg.AmountMsat,
|
|
arg.CreatedAt,
|
|
arg.FailReason,
|
|
arg.SettlePreimage,
|
|
arg.SettleTime,
|
|
)
|
|
var id int64
|
|
err := row.Scan(&id)
|
|
return id, err
|
|
}
|
|
|
|
const insertPaymentFirstHopCustomRecord = `-- name: InsertPaymentFirstHopCustomRecord :exec
|
|
INSERT INTO payment_first_hop_custom_records (
|
|
payment_id,
|
|
key,
|
|
value
|
|
)
|
|
VALUES (
|
|
$1,
|
|
$2,
|
|
$3
|
|
)
|
|
`
|
|
|
|
type InsertPaymentFirstHopCustomRecordParams struct {
|
|
PaymentID int64
|
|
Key int64
|
|
Value []byte
|
|
}
|
|
|
|
func (q *Queries) InsertPaymentFirstHopCustomRecord(ctx context.Context, arg InsertPaymentFirstHopCustomRecordParams) error {
|
|
_, err := q.db.ExecContext(ctx, insertPaymentFirstHopCustomRecord, arg.PaymentID, arg.Key, arg.Value)
|
|
return err
|
|
}
|
|
|
|
const insertPaymentHopCustomRecord = `-- name: InsertPaymentHopCustomRecord :exec
|
|
INSERT INTO payment_hop_custom_records (
|
|
hop_id,
|
|
key,
|
|
value
|
|
)
|
|
VALUES (
|
|
$1,
|
|
$2,
|
|
$3
|
|
)
|
|
`
|
|
|
|
type InsertPaymentHopCustomRecordParams struct {
|
|
HopID int64
|
|
Key int64
|
|
Value []byte
|
|
}
|
|
|
|
func (q *Queries) InsertPaymentHopCustomRecord(ctx context.Context, arg InsertPaymentHopCustomRecordParams) error {
|
|
_, err := q.db.ExecContext(ctx, insertPaymentHopCustomRecord, arg.HopID, arg.Key, arg.Value)
|
|
return err
|
|
}
|
|
|
|
const insertPaymentIntent = `-- name: InsertPaymentIntent :one
|
|
INSERT INTO payment_intents (
|
|
payment_id,
|
|
intent_type,
|
|
intent_payload)
|
|
VALUES (
|
|
$1,
|
|
$2,
|
|
$3
|
|
)
|
|
RETURNING id
|
|
`
|
|
|
|
type InsertPaymentIntentParams struct {
|
|
PaymentID int64
|
|
IntentType int16
|
|
IntentPayload []byte
|
|
}
|
|
|
|
// Insert a payment intent for a given payment and return its ID.
|
|
func (q *Queries) InsertPaymentIntent(ctx context.Context, arg InsertPaymentIntentParams) (int64, error) {
|
|
row := q.db.QueryRowContext(ctx, insertPaymentIntent, arg.PaymentID, arg.IntentType, arg.IntentPayload)
|
|
var id int64
|
|
err := row.Scan(&id)
|
|
return id, err
|
|
}
|
|
|
|
const insertPaymentMig = `-- name: InsertPaymentMig :one
|
|
/* ─────────────────────────────────────────────
|
|
Migration-specific queries
|
|
|
|
These queries are used ONLY for the one-time migration from KV to SQL.
|
|
─────────────────────────────────────────────
|
|
*/
|
|
|
|
INSERT INTO payments (
|
|
amount_msat,
|
|
created_at,
|
|
payment_identifier,
|
|
fail_reason)
|
|
VALUES (
|
|
$1,
|
|
$2,
|
|
$3,
|
|
$4
|
|
)
|
|
RETURNING id
|
|
`
|
|
|
|
type InsertPaymentMigParams struct {
|
|
AmountMsat int64
|
|
CreatedAt time.Time
|
|
PaymentIdentifier []byte
|
|
FailReason sql.NullInt32
|
|
}
|
|
|
|
// Migration-specific payment insert that allows setting fail_reason.
|
|
// Normal InsertPayment forces fail_reason to NULL since new payments
|
|
// aren't failed yet. During migration, we're inserting historical data
|
|
// that may already be failed.
|
|
func (q *Queries) InsertPaymentMig(ctx context.Context, arg InsertPaymentMigParams) (int64, error) {
|
|
row := q.db.QueryRowContext(ctx, insertPaymentMig,
|
|
arg.AmountMsat,
|
|
arg.CreatedAt,
|
|
arg.PaymentIdentifier,
|
|
arg.FailReason,
|
|
)
|
|
var id int64
|
|
err := row.Scan(&id)
|
|
return id, err
|
|
}
|
|
|
|
const insertRouteHop = `-- name: InsertRouteHop :one
|
|
INSERT INTO payment_route_hops (
|
|
htlc_attempt_index,
|
|
hop_index,
|
|
pub_key,
|
|
scid,
|
|
outgoing_time_lock,
|
|
amt_to_forward,
|
|
meta_data
|
|
)
|
|
VALUES (
|
|
$1,
|
|
$2,
|
|
$3,
|
|
$4,
|
|
$5,
|
|
$6,
|
|
$7
|
|
)
|
|
RETURNING id
|
|
`
|
|
|
|
type InsertRouteHopParams struct {
|
|
HtlcAttemptIndex int64
|
|
HopIndex int32
|
|
PubKey []byte
|
|
Scid string
|
|
OutgoingTimeLock int32
|
|
AmtToForward int64
|
|
MetaData []byte
|
|
}
|
|
|
|
func (q *Queries) InsertRouteHop(ctx context.Context, arg InsertRouteHopParams) (int64, error) {
|
|
row := q.db.QueryRowContext(ctx, insertRouteHop,
|
|
arg.HtlcAttemptIndex,
|
|
arg.HopIndex,
|
|
arg.PubKey,
|
|
arg.Scid,
|
|
arg.OutgoingTimeLock,
|
|
arg.AmtToForward,
|
|
arg.MetaData,
|
|
)
|
|
var id int64
|
|
err := row.Scan(&id)
|
|
return id, err
|
|
}
|
|
|
|
const insertRouteHopAmp = `-- name: InsertRouteHopAmp :exec
|
|
INSERT INTO payment_route_hop_amp (
|
|
hop_id,
|
|
root_share,
|
|
set_id,
|
|
child_index
|
|
)
|
|
VALUES (
|
|
$1,
|
|
$2,
|
|
$3,
|
|
$4
|
|
)
|
|
`
|
|
|
|
type InsertRouteHopAmpParams struct {
|
|
HopID int64
|
|
RootShare []byte
|
|
SetID []byte
|
|
ChildIndex int32
|
|
}
|
|
|
|
func (q *Queries) InsertRouteHopAmp(ctx context.Context, arg InsertRouteHopAmpParams) error {
|
|
_, err := q.db.ExecContext(ctx, insertRouteHopAmp,
|
|
arg.HopID,
|
|
arg.RootShare,
|
|
arg.SetID,
|
|
arg.ChildIndex,
|
|
)
|
|
return err
|
|
}
|
|
|
|
const insertRouteHopBlinded = `-- name: InsertRouteHopBlinded :exec
|
|
INSERT INTO payment_route_hop_blinded (
|
|
hop_id,
|
|
encrypted_data,
|
|
blinding_point,
|
|
blinded_path_total_amt
|
|
)
|
|
VALUES (
|
|
$1,
|
|
$2,
|
|
$3,
|
|
$4
|
|
)
|
|
`
|
|
|
|
type InsertRouteHopBlindedParams struct {
|
|
HopID int64
|
|
EncryptedData []byte
|
|
BlindingPoint []byte
|
|
BlindedPathTotalAmt sql.NullInt64
|
|
}
|
|
|
|
func (q *Queries) InsertRouteHopBlinded(ctx context.Context, arg InsertRouteHopBlindedParams) error {
|
|
_, err := q.db.ExecContext(ctx, insertRouteHopBlinded,
|
|
arg.HopID,
|
|
arg.EncryptedData,
|
|
arg.BlindingPoint,
|
|
arg.BlindedPathTotalAmt,
|
|
)
|
|
return err
|
|
}
|
|
|
|
const insertRouteHopMpp = `-- name: InsertRouteHopMpp :exec
|
|
INSERT INTO payment_route_hop_mpp (
|
|
hop_id,
|
|
payment_addr,
|
|
total_msat
|
|
)
|
|
VALUES (
|
|
$1,
|
|
$2,
|
|
$3
|
|
)
|
|
`
|
|
|
|
type InsertRouteHopMppParams struct {
|
|
HopID int64
|
|
PaymentAddr []byte
|
|
TotalMsat int64
|
|
}
|
|
|
|
func (q *Queries) InsertRouteHopMpp(ctx context.Context, arg InsertRouteHopMppParams) error {
|
|
_, err := q.db.ExecContext(ctx, insertRouteHopMpp, arg.HopID, arg.PaymentAddr, arg.TotalMsat)
|
|
return err
|
|
}
|
|
|
|
const settleAttempt = `-- name: SettleAttempt :exec
|
|
INSERT INTO payment_htlc_attempt_resolutions (
|
|
attempt_index,
|
|
resolution_time,
|
|
resolution_type,
|
|
settle_preimage
|
|
)
|
|
VALUES (
|
|
$1,
|
|
$2,
|
|
$3,
|
|
$4
|
|
)
|
|
`
|
|
|
|
type SettleAttemptParams struct {
|
|
AttemptIndex int64
|
|
ResolutionTime time.Time
|
|
ResolutionType int32
|
|
SettlePreimage []byte
|
|
}
|
|
|
|
func (q *Queries) SettleAttempt(ctx context.Context, arg SettleAttemptParams) error {
|
|
_, err := q.db.ExecContext(ctx, settleAttempt,
|
|
arg.AttemptIndex,
|
|
arg.ResolutionTime,
|
|
arg.ResolutionType,
|
|
arg.SettlePreimage,
|
|
)
|
|
return err
|
|
}
|