multi: add relevant queries for QueryPayments implemenation
What changed, and why it matters
This commit adds new database read-only queries for the QueryPayments feature in LND. It does not change any existing logic, fix a bug, or alter security-sensitive behavior. It is purely adding data retrieval capabilities for payments, HTLC attempts, route hops, and custom records.
No security action required. Review as normal feature code.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit introduces SQL queries and generated Go code (via sqlc) to support QueryPayments implementation. It adds read-only queries: FilterPayments, FetchPayment, FetchPaymentsByIDs, CountPayments, FetchHtlcAttemptsForPayments, FetchAllInflightAttempts, FetchHopsForAttempts, and custom record fetchers. These are added to the SQLQueries interface and Querier interface. No writes, no state changes, no authentication/authorization changes, no cryptographic changes.
Changed components
payments/db/sql_store.gosqldb/sqlc/payments.sql.gosqldb/sqlc/querier.gosqldb/sqlc/queries/payments.sqlInspect captured patch +776 / −0
diff --git a/payments/db/sql_store.go b/payments/db/sql_store.go
index 12585ca..ced061a 100644
--- a/payments/db/sql_store.go
+++ b/payments/db/sql_store.go
@@ -1,14 +1,32 @@
package paymentsdb
import (
+ "context"
"fmt"
"github.com/lightningnetwork/lnd/sqldb"
+ "github.com/lightningnetwork/lnd/sqldb/sqlc"
)
// SQLQueries is a subset of the sqlc.Querier interface that can be used to
// execute queries against the SQL payments tables.
type SQLQueries interface {
+ /*
+ Payment DB read operations.
+ */
+ FilterPayments(ctx context.Context, query sqlc.FilterPaymentsParams) ([]sqlc.FilterPaymentsRow, error)
+ FetchPayment(ctx context.Context, paymentIdentifier []byte) (sqlc.FetchPaymentRow, error)
+ FetchPaymentsByIDs(ctx context.Context, paymentIDs []int64) ([]sqlc.FetchPaymentsByIDsRow, error)
+
+ CountPayments(ctx context.Context) (int64, error)
+
+ FetchHtlcAttemptsForPayments(ctx context.Context, paymentIDs []int64) ([]sqlc.FetchHtlcAttemptsForPaymentsRow, error)
+ FetchAllInflightAttempts(ctx context.Context) ([]sqlc.PaymentHtlcAttempt, error)
+ FetchHopsForAttempts(ctx context.Context, htlcAttemptIndices []int64) ([]sqlc.FetchHopsForAttemptsRow, 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)
}
// BatchedSQLQueries is a version of the SQLQueries that's capable
diff --git a/sqldb/sqlc/payments.sql.go b/sqldb/sqlc/payments.sql.go
new file mode 100644
index 0000000..83c1c7f
--- /dev/null
+++ b/sqldb/sqlc/payments.sql.go
@@ -0,0 +1,594 @@
+// 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 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
+)
+ORDER BY ha.attempt_index ASC
+`
+
+// Fetch all inflight attempts across all payments
+func (q *Queries) FetchAllInflightAttempts(ctx context.Context) ([]PaymentHtlcAttempt, error) {
+ rows, err := q.db.QueryContext(ctx, fetchAllInflightAttempts)
+ 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 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.intent_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.id = p.intent_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.IntentID,
+ &i.Payment.AmountMsat,
+ &i.Payment.CreatedAt,
+ &i.Payment.PaymentIdentifier,
+ &i.Payment.FailReason,
+ &i.IntentType,
+ &i.IntentPayload,
+ )
+ return i, err
+}
+
+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.intent_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.id = p.intent_id
+WHERE p.id IN (/*SLICE:payment_ids*/?)
+`
+
+type FetchPaymentsByIDsRow struct {
+ Payment Payment
+ IntentType sql.NullInt16
+ IntentPayload []byte
+}
+
+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.Payment.ID,
+ &i.Payment.IntentID,
+ &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 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.intent_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.id = p.intent_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.IntentID,
+ &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
+}
diff --git a/sqldb/sqlc/querier.go b/sqldb/sqlc/querier.go
index 5f2fc65..f4c7673 100644
--- a/sqldb/sqlc/querier.go
+++ b/sqldb/sqlc/querier.go
@@ -15,6 +15,7 @@ type Querier interface {
AddV1ChannelProof(ctx context.Context, arg AddV1ChannelProofParams) (sql.Result, error)
AddV2ChannelProof(ctx context.Context, arg AddV2ChannelProofParams) (sql.Result, error)
ClearKVInvoiceHashIndex(ctx context.Context) error
+ CountPayments(ctx context.Context) (int64, error)
CountZombieChannels(ctx context.Context, version int16) (int64, error)
CreateChannel(ctx context.Context, arg CreateChannelParams) (int64, error)
DeleteCanceledInvoices(ctx context.Context) (sql.Result, error)
@@ -31,10 +32,19 @@ type Querier interface {
DeleteZombieChannel(ctx context.Context, arg DeleteZombieChannelParams) (sql.Result, error)
FetchAMPSubInvoiceHTLCs(ctx context.Context, arg FetchAMPSubInvoiceHTLCsParams) ([]FetchAMPSubInvoiceHTLCsRow, error)
FetchAMPSubInvoices(ctx context.Context, arg FetchAMPSubInvoicesParams) ([]AmpSubInvoice, error)
+ // Fetch all inflight attempts across all payments
+ FetchAllInflightAttempts(ctx context.Context) ([]PaymentHtlcAttempt, error)
+ FetchHopLevelCustomRecords(ctx context.Context, hopIds []int64) ([]PaymentHopCustomRecord, error)
+ FetchHopsForAttempts(ctx context.Context, htlcAttemptIndices []int64) ([]FetchHopsForAttemptsRow, error)
+ FetchHtlcAttemptsForPayments(ctx context.Context, paymentIds []int64) ([]FetchHtlcAttemptsForPaymentsRow, error)
+ FetchPayment(ctx context.Context, paymentIdentifier []byte) (FetchPaymentRow, error)
+ FetchPaymentLevelFirstHopCustomRecords(ctx context.Context, paymentIds []int64) ([]PaymentFirstHopCustomRecord, error)
+ FetchPaymentsByIDs(ctx context.Context, paymentIds []int64) ([]FetchPaymentsByIDsRow, error)
// FetchPendingInvoices returns all invoices in a pending state (open or
// accepted). The invoices_state_idx index on the state column makes this a
// fast index scan rather than a full table scan.
FetchPendingInvoices(ctx context.Context, arg FetchPendingInvoicesParams) ([]Invoice, error)
+ FetchRouteLevelFirstHopCustomRecords(ctx context.Context, htlcAttemptIndices []int64) ([]PaymentAttemptFirstHopCustomRecord, error)
FetchSettledAMPSubInvoices(ctx context.Context, arg FetchSettledAMPSubInvoicesParams) ([]FetchSettledAMPSubInvoicesRow, error)
// FilterInvoicesByAddIndex returns invoices whose add_index (primary key id)
// is greater than or equal to the given value, ordered by id. Because id is
@@ -58,6 +68,7 @@ type Querier interface {
// It returns invoices in descending id order up to and including add_index_let.
// See FilterInvoicesForward for the expected Go-side defaults.
FilterInvoicesReverse(ctx context.Context, arg FilterInvoicesReverseParams) ([]Invoice, error)
+ FilterPayments(ctx context.Context, arg FilterPaymentsParams) ([]FilterPaymentsRow, error)
GetAMPInvoiceID(ctx context.Context, setID []byte) (int64, error)
GetChannelAndNodesBySCID(ctx context.Context, arg GetChannelAndNodesBySCIDParams) (GetChannelAndNodesBySCIDRow, error)
GetChannelByOutpointWithPolicies(ctx context.Context, arg GetChannelByOutpointWithPoliciesParams) (GetChannelByOutpointWithPoliciesRow, error)
diff --git a/sqldb/sqlc/queries/payments.sql b/sqldb/sqlc/queries/payments.sql
new file mode 100644
index 0000000..ce43a3e
--- /dev/null
+++ b/sqldb/sqlc/queries/payments.sql
@@ -0,0 +1,153 @@
+/* ─────────────────────────────────────────────
+ fetch queries
+ ─────────────────────────────────────────────
+*/
+
+-- name: FilterPayments :many
+SELECT
+ sqlc.embed(p),
+ i.intent_type AS "intent_type",
+ i.intent_payload AS "intent_payload"
+FROM payments p
+LEFT JOIN payment_intents i ON i.id = p.intent_id
+WHERE (
+ p.id > sqlc.narg('index_offset_get') OR
+ sqlc.narg('index_offset_get') IS NULL
+) AND (
+ p.id < sqlc.narg('index_offset_let') OR
+ sqlc.narg('index_offset_let') IS NULL
+) AND (
+ p.created_at >= sqlc.narg('created_after') OR
+ sqlc.narg('created_after') IS NULL
+) AND (
+ p.created_at <= sqlc.narg('created_before') OR
+ sqlc.narg('created_before') IS NULL
+) AND (
+ i.intent_type = sqlc.narg('intent_type') OR
+ sqlc.narg('intent_type') IS NULL OR i.intent_type IS NULL
+)
+ORDER BY
+ CASE WHEN sqlc.narg('reverse') = false OR sqlc.narg('reverse') IS NULL THEN p.id END ASC,
+ CASE WHEN sqlc.narg('reverse') = true THEN p.id END DESC
+LIMIT @num_limit;
+
+-- name: FetchPayment :one
+SELECT
+ sqlc.embed(p),
+ i.intent_type AS "intent_type",
+ i.intent_payload AS "intent_payload"
+FROM payments p
+LEFT JOIN payment_intents i ON i.id = p.intent_id
+WHERE p.payment_identifier = $1;
+
+-- name: FetchPaymentsByIDs :many
+SELECT
+ sqlc.embed(p),
+ i.intent_type AS "intent_type",
+ i.intent_payload AS "intent_payload"
+FROM payments p
+LEFT JOIN payment_intents i ON i.id = p.intent_id
+WHERE p.id IN (sqlc.slice('payment_ids')/*SLICE:payment_ids*/);
+
+-- name: CountPayments :one
+SELECT COUNT(*) FROM payments;
+
+-- 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 (sqlc.slice('payment_ids')/*SLICE:payment_ids*/)
+ORDER BY ha.payment_id ASC, ha.attempt_time ASC;
+
+-- name: FetchAllInflightAttempts :many
+-- Fetch all inflight attempts across all payments
+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
+)
+ORDER BY ha.attempt_index ASC;
+
+-- 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 (sqlc.slice('htlc_attempt_indices')/*SLICE:htlc_attempt_indices*/)
+ORDER BY h.htlc_attempt_index ASC, h.hop_index ASC;
+
+
+-- 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 (sqlc.slice('payment_ids')/*SLICE:payment_ids*/)
+ORDER BY l.payment_id ASC, l.key ASC;
+
+-- 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 (sqlc.slice('htlc_attempt_indices')/*SLICE:htlc_attempt_indices*/)
+ORDER BY l.htlc_attempt_index ASC, l.key ASC;
+
+-- name: FetchHopLevelCustomRecords :many
+SELECT
+ l.id,
+ l.hop_id,
+ l.key,
+ l.value
+FROM payment_hop_custom_records l
+WHERE l.hop_id IN (sqlc.slice('hop_ids')/*SLICE:hop_ids*/)
+ORDER BY l.hop_id ASC, l.key ASC;
+
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.