The 000009 schema version slot is now taken by 000009_graph_v2_columns which was merged ahead of the payments schema. Bump the payments schema file number to 000010 to avoid the collision. Moreover update the migration_dev.go file to reflect this change and update the order of migration.
443 lines
21 KiB
SQL
443 lines
21 KiB
SQL
-- ─────────────────────────────────────────────
|
|
-- Payment System Schema Migration
|
|
-- ─────────────────────────────────────────────
|
|
-- This migration creates the complete payment system schema including:
|
|
-- - Payment intents (only BOLT 11 invoices for now)
|
|
-- - Payment attempts and HTLC tracking
|
|
-- - Route hops and custom TLV records
|
|
-- - Resolution tracking for settled/failed payments
|
|
-- ─────────────────────────────────────────────
|
|
|
|
-- ─────────────────────────────────────────────
|
|
-- Payments Table
|
|
-- ─────────────────────────────────────────────
|
|
-- Stores all payments including all known payment types:
|
|
-- - Legacy payments
|
|
-- - Multi-Path Payments (MPP)
|
|
-- - Atomic Multi-Path Payments (AMP)
|
|
-- - Blinded payments
|
|
-- - Keysend payments
|
|
-- - Spontaneous AMP payments
|
|
-- ─────────────────────────────────────────────
|
|
|
|
CREATE TABLE IF NOT EXISTS payments (
|
|
-- Primary key for the payment record
|
|
id INTEGER PRIMARY KEY,
|
|
|
|
-- The amount of the payment in millisatoshis
|
|
amount_msat BIGINT NOT NULL,
|
|
|
|
-- Timestamp when the payment was created
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
-- Logical identifier for the payment
|
|
-- For legacy + MPP: matches the HTLC hash
|
|
-- For AMP: the setID
|
|
-- For future intent types: any unique payment-level key
|
|
payment_identifier BLOB NOT NULL,
|
|
|
|
-- The reason for payment failure (only set if payment has failed)
|
|
-- Integer enum type indicating failure reason
|
|
fail_reason INTEGER,
|
|
|
|
-- Ensure payment identifiers are unique across all payments
|
|
CONSTRAINT idx_payments_payment_identifier_unique
|
|
UNIQUE (payment_identifier)
|
|
);
|
|
|
|
-- Index for efficient querying by creation time (for chronological ordering)
|
|
CREATE INDEX IF NOT EXISTS idx_payments_created_at
|
|
ON payments(created_at);
|
|
|
|
-- ─────────────────────────────────────────────
|
|
-- Payment Intents Table
|
|
-- ─────────────────────────────────────────────
|
|
-- Stores the descriptor of what the payment is paying for.
|
|
-- Depending on the type, the payload might contain:
|
|
-- - BOLT 11 invoice data
|
|
-- - BOLT 12 offer data
|
|
-- - NULL for legacy hash-only/keysend style payments
|
|
-- ─────────────────────────────────────────────
|
|
|
|
CREATE TABLE IF NOT EXISTS payment_intents (
|
|
-- Primary key for the intent record
|
|
id INTEGER PRIMARY KEY,
|
|
|
|
-- Reference to the payment this intent belongs to (one-to-one relationship)
|
|
-- When the payment is deleted, the intent is automatically deleted
|
|
payment_id BIGINT NOT NULL REFERENCES payments (id) ON DELETE CASCADE,
|
|
|
|
-- The type of intent (e.g. 0 = bolt11_invoice, 1 = bolt12_invoice)
|
|
-- Uses SMALLINT (int16) for efficient storage of enum values
|
|
intent_type SMALLINT NOT NULL,
|
|
|
|
-- The serialized payload for the payment intent
|
|
-- Content depends on type - could be invoice, offer, or NULL
|
|
intent_payload BLOB,
|
|
|
|
-- Ensure one-to-one relationship: each payment has at most one intent.
|
|
-- Currently we only support one intent per payment this makes sure we do
|
|
-- not accidentally pay the same request multiple times. This currently
|
|
-- only has bolt 11 payment requests/invoices. But in the future this can
|
|
-- also include BOLT 12 offers/invoices.
|
|
CONSTRAINT idx_payment_intents_payment_id_unique
|
|
UNIQUE (payment_id)
|
|
);
|
|
|
|
-- Index for efficient querying by intent type
|
|
CREATE INDEX IF NOT EXISTS idx_payment_intents_type
|
|
ON payment_intents(intent_type);
|
|
|
|
-- ─────────────────────────────────────────────
|
|
-- Payment HTLC Attempts Table
|
|
-- ─────────────────────────────────────────────
|
|
-- Stores all HTLC attempts for a payment. A payment can have multiple
|
|
-- HTLC attempts depending on whether the payment is split and also
|
|
-- if some attempts fail and need to be retried.
|
|
-- ─────────────────────────────────────────────
|
|
|
|
CREATE TABLE IF NOT EXISTS payment_htlc_attempts (
|
|
-- Primary key for the HTLC attempt record
|
|
id INTEGER PRIMARY KEY,
|
|
|
|
-- The index of the HTLC attempt
|
|
-- TODO: This will be removed and the primary key will be used only
|
|
attempt_index BIGINT NOT NULL,
|
|
|
|
-- Reference to the parent payment
|
|
payment_id BIGINT NOT NULL REFERENCES payments (id) ON DELETE CASCADE,
|
|
|
|
-- The session key of the HTLC attempt (also known as ephemeral key
|
|
-- of the Sphinx packet used for onion routing)
|
|
session_key BLOB NOT NULL,
|
|
|
|
-- Timestamp when the HTLC attempt was created
|
|
attempt_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
-- The payment hash for the payment attempt
|
|
-- The hash the HTLC will be locked to - this does not need to be
|
|
-- equal to the payment level identifier (e.g., for AMP payments)
|
|
payment_hash BLOB NOT NULL,
|
|
|
|
-- First hop amount in millisatoshis of the HTLC attempt
|
|
-- Normally the same as the total amount of the route, but when using
|
|
-- custom channels this might be different
|
|
first_hop_amount_msat BIGINT NOT NULL,
|
|
|
|
-- ─────────────────────────────────────────────
|
|
-- Route Information for the HTLC Attempt
|
|
-- ─────────────────────────────────────────────
|
|
-- Every attempt has one route, so there is a 1:1 relationship between
|
|
-- attempts and routes. The route itself can be found in the hops table.
|
|
-- ─────────────────────────────────────────────
|
|
|
|
-- The total time lock of the route (in blocks)
|
|
route_total_time_lock INTEGER NOT NULL,
|
|
|
|
-- The total amount of the route in millisatoshis
|
|
route_total_amount BIGINT NOT NULL,
|
|
|
|
-- The source key of the route (our node's public key)
|
|
route_source_key BLOB NOT NULL,
|
|
|
|
-- Ensure attempt indices are unique across all attempts
|
|
CONSTRAINT idx_htlc_attempt_index_unique
|
|
UNIQUE (attempt_index),
|
|
|
|
-- Ensure session keys are unique (each attempt has unique session key)
|
|
CONSTRAINT idx_htlc_session_key_unique
|
|
UNIQUE (session_key)
|
|
);
|
|
|
|
-- Index for efficient querying by payment ID (find all attempts for a payment)
|
|
CREATE INDEX IF NOT EXISTS idx_htlc_payment_id
|
|
ON payment_htlc_attempts(payment_id);
|
|
|
|
-- Index for efficient querying by attempt index (for lookups and joins)
|
|
CREATE INDEX IF NOT EXISTS idx_htlc_attempt_index
|
|
ON payment_htlc_attempts(attempt_index);
|
|
|
|
-- Index for efficient querying by payment hash (for HTLC matching)
|
|
CREATE INDEX IF NOT EXISTS idx_htlc_payment_hash
|
|
ON payment_htlc_attempts(payment_hash);
|
|
|
|
-- Index for efficient querying by attempt time (for chronological ordering)
|
|
CREATE INDEX IF NOT EXISTS idx_htlc_attempt_time
|
|
ON payment_htlc_attempts(attempt_time);
|
|
|
|
-- ─────────────────────────────────────────────
|
|
-- HTLC Attempt Resolutions Table
|
|
-- ─────────────────────────────────────────────
|
|
-- Stores resolution metadata for HTLC attempts. Rows appear once an
|
|
-- attempt settles or fails, providing the final outcome and timing.
|
|
-- ─────────────────────────────────────────────
|
|
|
|
CREATE TABLE IF NOT EXISTS payment_htlc_attempt_resolutions (
|
|
-- Primary key referencing the HTLC attempt
|
|
-- TODO: This will be removed and the primary key will be used only
|
|
attempt_index INTEGER PRIMARY KEY
|
|
REFERENCES payment_htlc_attempts (attempt_index) ON DELETE CASCADE,
|
|
|
|
-- Timestamp when the attempt was resolved (settled or failed)
|
|
resolution_time TIMESTAMP NOT NULL,
|
|
|
|
-- Outcome of the attempt: 1 = settled, 2 = failed
|
|
resolution_type INTEGER NOT NULL CHECK (resolution_type IN (1, 2)),
|
|
|
|
-- Settlement payload (only populated for settled attempts)
|
|
-- Contains the preimage that proves payment completion
|
|
settle_preimage BLOB,
|
|
|
|
-- Failure payload (only populated for failed attempts)
|
|
-- Index of the node that sent the failure
|
|
failure_source_index INTEGER,
|
|
|
|
-- HTLC failure reason code
|
|
htlc_fail_reason INTEGER,
|
|
|
|
-- Failure message from the failing node, this message is binary encoded
|
|
-- using the lightning wire protocol, see also lnwire/onion_error.go
|
|
failure_msg BLOB,
|
|
|
|
-- Ensure data integrity: settled attempts must have preimage,
|
|
-- failed attempts must not have preimage
|
|
CHECK (
|
|
(resolution_type = 1 AND settle_preimage IS NOT NULL AND
|
|
failure_source_index IS NULL AND htlc_fail_reason IS NULL AND
|
|
failure_msg IS NULL)
|
|
OR
|
|
(resolution_type = 2 AND settle_preimage IS NULL)
|
|
)
|
|
);
|
|
|
|
-- Index for efficient querying by resolution type (settled vs failed)
|
|
CREATE INDEX IF NOT EXISTS idx_htlc_resolutions_type
|
|
ON payment_htlc_attempt_resolutions(resolution_type);
|
|
|
|
-- Index for efficient querying by resolution time (for chronological analysis)
|
|
CREATE INDEX IF NOT EXISTS idx_htlc_resolutions_time
|
|
ON payment_htlc_attempt_resolutions(resolution_time);
|
|
|
|
-- ─────────────────────────────────────────────
|
|
-- Payment Route Hops Table
|
|
-- ─────────────────────────────────────────────
|
|
-- Stores the individual hops of a payment route. An attempt has only
|
|
-- one route, but a route can consist of several hops through the network.
|
|
-- ─────────────────────────────────────────────
|
|
|
|
CREATE TABLE IF NOT EXISTS payment_route_hops (
|
|
-- Primary key for the hop record
|
|
id INTEGER PRIMARY KEY,
|
|
|
|
-- Reference to the HTLC attempt this hop belongs to
|
|
htlc_attempt_index BIGINT NOT NULL
|
|
REFERENCES payment_htlc_attempts (attempt_index) ON DELETE CASCADE,
|
|
|
|
-- The order/index of this hop within the route (0-based)
|
|
hop_index INTEGER NOT NULL,
|
|
|
|
-- The public key of the hop (node's public key)
|
|
pub_key BLOB,
|
|
|
|
-- The short channel ID of the hop (channel identifier)
|
|
scid TEXT NOT NULL,
|
|
|
|
-- The outgoing time lock of the hop (in blocks)
|
|
outgoing_time_lock INTEGER NOT NULL,
|
|
|
|
-- The amount to forward to the next hop (in millisatoshis)
|
|
amt_to_forward BIGINT NOT NULL,
|
|
|
|
-- The metadata blob transmitted to the hop (onion payload)
|
|
meta_data BLOB,
|
|
|
|
-- Ensure each attempt can only have one hop at each hop index
|
|
-- This prevents duplicate hops in the same position
|
|
CONSTRAINT idx_route_hops_unique_hop_per_attempt
|
|
UNIQUE (htlc_attempt_index, hop_index)
|
|
);
|
|
|
|
-- Index for efficient querying by attempt index (find all hops for an attempt)
|
|
CREATE INDEX IF NOT EXISTS idx_route_hops_htlc_attempt_index
|
|
ON payment_route_hops(htlc_attempt_index);
|
|
|
|
-- ─────────────────────────────────────────────
|
|
-- Per-Hop Payload Tables
|
|
-- ─────────────────────────────────────────────
|
|
-- These tables store specialized payload data for different payment types.
|
|
-- Each table is only populated for hops that require that specific payload.
|
|
-- ─────────────────────────────────────────────
|
|
|
|
-- ─────────────────────────────────────────────
|
|
-- MPP (Multi-Path Payment) Payload Table
|
|
-- ─────────────────────────────────────────────
|
|
-- Stores MPP-specific payload data. Only present for the final hop
|
|
-- of an MPP attempt, containing payment address and total amount info.
|
|
-- ─────────────────────────────────────────────
|
|
|
|
CREATE TABLE IF NOT EXISTS payment_route_hop_mpp (
|
|
-- Primary key referencing the hop
|
|
hop_id INTEGER PRIMARY KEY
|
|
REFERENCES payment_route_hops (id) ON DELETE CASCADE,
|
|
|
|
-- The payment address of the MPP path (for payment correlation)
|
|
payment_addr BLOB NOT NULL,
|
|
|
|
-- The total amount of the MPP payment in millisatoshis
|
|
-- This is the sum of all parts in the multi-path payment
|
|
total_msat BIGINT NOT NULL
|
|
);
|
|
|
|
-- ─────────────────────────────────────────────
|
|
-- AMP (Atomic Multi-Path Payment) Payload Table
|
|
-- ─────────────────────────────────────────────
|
|
-- Stores AMP-specific payload data. Only present for the final hop
|
|
-- of an AMP attempt, containing share information for atomicity.
|
|
-- ─────────────────────────────────────────────
|
|
|
|
CREATE TABLE IF NOT EXISTS payment_route_hop_amp (
|
|
-- Primary key referencing the hop
|
|
hop_id INTEGER PRIMARY KEY
|
|
REFERENCES payment_route_hops (id) ON DELETE CASCADE,
|
|
|
|
-- The root share of the AMP path (for share reconstruction)
|
|
root_share BLOB NOT NULL,
|
|
|
|
-- The set ID of the AMP path (groups related AMP parts)
|
|
set_id BLOB NOT NULL,
|
|
|
|
-- The child index of the AMP path (identifies this part)
|
|
child_index INTEGER NOT NULL
|
|
);
|
|
|
|
-- ─────────────────────────────────────────────
|
|
-- Blinded Route Payload Table
|
|
-- ─────────────────────────────────────────────
|
|
-- Stores blinded route payload data. Rows only exist for hops that
|
|
-- are part of a blinded path, providing privacy-preserving routing.
|
|
-- ─────────────────────────────────────────────
|
|
|
|
CREATE TABLE IF NOT EXISTS payment_route_hop_blinded (
|
|
-- Primary key referencing the hop
|
|
hop_id INTEGER PRIMARY KEY
|
|
REFERENCES payment_route_hops (id) ON DELETE CASCADE,
|
|
|
|
-- The encrypted payload for the blinded hop
|
|
encrypted_data BLOB NOT NULL,
|
|
|
|
-- Only set for the introduction point of the blinded path
|
|
-- Contains the blinding point for the introduction node
|
|
blinding_point BLOB,
|
|
|
|
-- Only set for the final hop in the blinded path
|
|
-- Contains the total amount for the entire blinded path
|
|
blinded_path_total_amt BIGINT
|
|
);
|
|
|
|
-- ─────────────────────────────────────────────
|
|
-- Custom TLV Records Tables
|
|
-- ─────────────────────────────────────────────
|
|
-- These tables store custom TLV (Type-Length-Value) records associated
|
|
-- with payments, attempts, and hops. This is a denormalized structure
|
|
-- designed to simplify cascade deletions, as each record is owned by
|
|
-- a single parent entity.
|
|
-- ─────────────────────────────────────────────
|
|
|
|
-- ─────────────────────────────────────────────
|
|
-- Payment-Level First Hop Custom Records
|
|
-- ─────────────────────────────────────────────
|
|
-- Stores custom TLV records that are part of the first hop of a payment.
|
|
-- These records are sent to the first hop and are payment-level data.
|
|
--
|
|
-- NOTE: This relates to the custom tlv record data which is sent to the first
|
|
-- hop in the wire message (UpdateAddHTLC) NOT the onion packet.
|
|
--
|
|
-- TODO(ziggie): We store mostly redundant data here and on the attempt level.
|
|
-- This might be improved in the future to reduce duplication.
|
|
-- ─────────────────────────────────────────────
|
|
|
|
CREATE TABLE IF NOT EXISTS payment_first_hop_custom_records (
|
|
-- Primary key for the custom record
|
|
id INTEGER PRIMARY KEY,
|
|
|
|
-- Reference to the parent payment
|
|
payment_id BIGINT NOT NULL REFERENCES payments (id) ON DELETE CASCADE,
|
|
|
|
-- The TLV type identifier (must be >= 65536 for custom records)
|
|
key BIGINT NOT NULL,
|
|
|
|
-- The TLV value data
|
|
value BLOB NOT NULL,
|
|
|
|
-- Ensure we only store custom TLV records (not standard ones)
|
|
CHECK (key >= 65536),
|
|
|
|
-- Ensure each payment can only have one record per TLV type
|
|
CONSTRAINT idx_payment_first_hop_custom_records_unique
|
|
UNIQUE (payment_id, key)
|
|
);
|
|
|
|
-- ─────────────────────────────────────────────
|
|
-- Attempt-Level First Hop Custom Records
|
|
-- ─────────────────────────────────────────────
|
|
-- Stores custom TLV records for the first hop on the route level.
|
|
-- These might be different from the payment-level first hop records
|
|
-- in case of custom channels or route-specific modifications.
|
|
--
|
|
-- NOTE: This relates to the custom tlv record data which is sent to the first
|
|
-- hop in the wire message (UpdateAddHTLC) NOT the onion packet.
|
|
--
|
|
-- TODO(ziggie): We store mostly redundant data here and on the payment level.
|
|
-- This might be improved in the future to reduce duplication.
|
|
-- ─────────────────────────────────────────────
|
|
|
|
CREATE TABLE IF NOT EXISTS payment_attempt_first_hop_custom_records (
|
|
-- Primary key for the custom record
|
|
id INTEGER PRIMARY KEY,
|
|
|
|
-- Reference to the parent HTLC attempt
|
|
htlc_attempt_index BIGINT NOT NULL
|
|
REFERENCES payment_htlc_attempts (attempt_index) ON DELETE CASCADE,
|
|
|
|
-- The TLV type identifier (must be >= 65536 for custom records)
|
|
key BIGINT NOT NULL,
|
|
|
|
-- The TLV value data
|
|
value BLOB NOT NULL,
|
|
|
|
-- Ensure we only store custom TLV records (not standard ones)
|
|
CHECK (key >= 65536),
|
|
|
|
-- Ensure each attempt can only have one record per TLV type
|
|
CONSTRAINT idx_payment_attempt_first_hop_custom_records_unique
|
|
UNIQUE (htlc_attempt_index, key)
|
|
);
|
|
|
|
-- ─────────────────────────────────────────────
|
|
-- Hop-Level Custom Records
|
|
-- ─────────────────────────────────────────────
|
|
-- Stores custom TLV records associated with a specific hop within
|
|
-- a payment route. These records are sent to that specific hop
|
|
-- and are hop-specific data.
|
|
-- ─────────────────────────────────────────────
|
|
|
|
CREATE TABLE IF NOT EXISTS payment_hop_custom_records (
|
|
-- Primary key for the custom record
|
|
id INTEGER PRIMARY KEY,
|
|
|
|
-- Reference to the parent hop
|
|
hop_id BIGINT NOT NULL REFERENCES payment_route_hops (id) ON DELETE CASCADE,
|
|
|
|
-- The TLV type identifier (must be >= 65536 for custom records)
|
|
key BIGINT NOT NULL,
|
|
|
|
-- The TLV value data
|
|
value BLOB NOT NULL,
|
|
|
|
-- Ensure we only store custom TLV records (not standard ones)
|
|
CHECK (key >= 65536),
|
|
|
|
-- Ensure each hop can only have one record per TLV type
|
|
CONSTRAINT idx_payment_hop_custom_records_unique
|
|
UNIQUE (hop_id, key)
|
|
); |