refactor(cdk): defer BOLT12 invoice fetching to payment execution (#978)

* refactor(cdk): defer BOLT12 invoice fetching to payment execution

Move BOLT12 invoice generation from quote creation to payment time, make
request_lookup_id optional for offers, and simplify payment structures by
removing unnecessary boxing and intermediate storage of invoices.
This commit is contained in:
thesimplekid
2025-08-20 11:20:30 +01:00
committed by GitHub
parent 5c5075af71
commit ecdc78135d
17 changed files with 193 additions and 220 deletions

View File

@@ -2,6 +2,7 @@
/// Auto-generated by build.rs
pub static MIGRATIONS: &[(&str, &str, &str)] = &[
("postgres", "1_initial.sql", include_str!(r#"./migrations/postgres/1_initial.sql"#)),
("postgres", "2_remove_request_lookup_kind_constraints.sql", include_str!(r#"./migrations/postgres/2_remove_request_lookup_kind_constraints.sql"#)),
("sqlite", "1_fix_sqlx_migration.sql", include_str!(r#"./migrations/sqlite/1_fix_sqlx_migration.sql"#)),
("sqlite", "20240612124932_init.sql", include_str!(r#"./migrations/sqlite/20240612124932_init.sql"#)),
("sqlite", "20240618195700_quote_state.sql", include_str!(r#"./migrations/sqlite/20240618195700_quote_state.sql"#)),
@@ -25,4 +26,5 @@ pub static MIGRATIONS: &[(&str, &str, &str)] = &[
("sqlite", "20250626120251_rename_blind_message_y_to_b.sql", include_str!(r#"./migrations/sqlite/20250626120251_rename_blind_message_y_to_b.sql"#)),
("sqlite", "20250706101057_bolt12.sql", include_str!(r#"./migrations/sqlite/20250706101057_bolt12.sql"#)),
("sqlite", "20250812132015_drop_melt_request.sql", include_str!(r#"./migrations/sqlite/20250812132015_drop_melt_request.sql"#)),
("sqlite", "20250819200000_remove_request_lookup_kind_constraints.sql", include_str!(r#"./migrations/sqlite/20250819200000_remove_request_lookup_kind_constraints.sql"#)),
];

View File

@@ -0,0 +1,6 @@
-- Set existing NULL or empty request_lookup_id_kind values to 'payment_hash' in melt_quote
UPDATE melt_quote SET request_lookup_id_kind = 'payment_hash' WHERE request_lookup_id_kind IS NULL OR request_lookup_id_kind = '';
-- Remove NOT NULL constraint and default value from request_lookup_id_kind in melt_quote table
ALTER TABLE melt_quote ALTER COLUMN request_lookup_id_kind DROP NOT NULL;
ALTER TABLE melt_quote ALTER COLUMN request_lookup_id_kind DROP DEFAULT;

View File

@@ -0,0 +1,35 @@
-- Set existing NULL or empty request_lookup_id_kind values to 'payment_hash' in melt_quote
UPDATE melt_quote SET request_lookup_id_kind = 'payment_hash' WHERE request_lookup_id_kind IS NULL OR request_lookup_id_kind = '';
-- Remove NOT NULL constraint and default value from request_lookup_id_kind in melt_quote table
CREATE TABLE melt_quote_temp (
id TEXT PRIMARY KEY,
unit TEXT NOT NULL,
amount INTEGER NOT NULL,
request TEXT NOT NULL,
fee_reserve INTEGER NOT NULL,
expiry INTEGER NOT NULL,
state TEXT CHECK (
state IN ('UNPAID', 'PENDING', 'PAID')
) NOT NULL DEFAULT 'UNPAID',
payment_preimage TEXT,
request_lookup_id TEXT,
created_time INTEGER NOT NULL DEFAULT 0,
paid_time INTEGER,
payment_method TEXT NOT NULL DEFAULT 'bolt11',
options TEXT,
request_lookup_id_kind TEXT
);
INSERT INTO melt_quote_temp (id, unit, amount, request, fee_reserve, expiry, state, payment_preimage, request_lookup_id, created_time, paid_time, payment_method, options, request_lookup_id_kind)
SELECT id, unit, amount, request, fee_reserve, expiry, state, payment_preimage, request_lookup_id, created_time, paid_time, payment_method, options, request_lookup_id_kind
FROM melt_quote;
DROP TABLE melt_quote;
ALTER TABLE melt_quote_temp RENAME TO melt_quote;
-- Recreate indexes for melt_quote
CREATE INDEX IF NOT EXISTS melt_quote_state_index ON melt_quote(state);
CREATE UNIQUE INDEX IF NOT EXISTS unique_request_lookup_id_melt ON melt_quote(request_lookup_id);
CREATE INDEX IF NOT EXISTS idx_melt_quote_request_lookup_id_and_kind ON melt_quote(request_lookup_id, request_lookup_id_kind);

View File

@@ -739,24 +739,6 @@ VALUES (:quote_id, :amount, :timestamp);
async fn add_melt_quote(&mut self, quote: mint::MeltQuote) -> Result<(), Self::Err> {
// First try to find and replace any expired UNPAID quotes with the same request_lookup_id
let current_time = unix_time();
let row_affected = query(
r#"
DELETE FROM melt_quote
WHERE request_lookup_id = :request_lookup_id
AND state = :state
AND expiry < :current_time
"#,
)?
.bind("request_lookup_id", quote.request_lookup_id.to_string())
.bind("state", MeltQuoteState::Unpaid.to_string())
.bind("current_time", current_time as i64)
.execute(&self.inner)
.await?;
if row_affected > 0 {
tracing::info!("Received new melt quote for existing invoice with expired quote.");
}
// Now insert the new quote
query(
@@ -783,14 +765,20 @@ VALUES (:quote_id, :amount, :timestamp);
.bind("state", quote.state.to_string())
.bind("expiry", quote.expiry as i64)
.bind("payment_preimage", quote.payment_preimage)
.bind("request_lookup_id", quote.request_lookup_id.to_string())
.bind(
"request_lookup_id",
quote.request_lookup_id.as_ref().map(|id| id.to_string()),
)
.bind("created_time", quote.created_time as i64)
.bind("paid_time", quote.paid_time.map(|t| t as i64))
.bind(
"options",
quote.options.map(|o| serde_json::to_string(&o).ok()),
)
.bind("request_lookup_id_kind", quote.request_lookup_id.kind())
.bind(
"request_lookup_id_kind",
quote.request_lookup_id.map(|id| id.kind()),
)
.bind("payment_method", quote.payment_method.to_string())
.execute(&self.inner)
.await?;
@@ -1713,19 +1701,24 @@ fn sql_row_to_melt_quote(row: Vec<Column>) -> Result<mint::MeltQuote, Error> {
let unit = column_as_string!(unit);
let request = column_as_string!(request);
let mut request_lookup_id_kind = column_as_string!(request_lookup_id_kind);
let request_lookup_id_kind = column_as_nullable_string!(request_lookup_id_kind);
let request_lookup_id = column_as_nullable_string!(&request_lookup_id).unwrap_or_else(|| {
let request_lookup_id = column_as_nullable_string!(&request_lookup_id).or_else(|| {
Bolt11Invoice::from_str(&request)
.ok()
.map(|invoice| invoice.payment_hash().to_string())
.unwrap_or_else(|_| {
request_lookup_id_kind = "custom".to_string();
request.clone()
})
});
let request_lookup_id = PaymentIdentifier::new(&request_lookup_id_kind, &request_lookup_id)
.map_err(|_| ConversionError::MissingParameter("Payment id".to_string()))?;
let request_lookup_id = if let (Some(id_kind), Some(request_lookup_id)) =
(request_lookup_id_kind, request_lookup_id)
{
Some(
PaymentIdentifier::new(&id_kind, &request_lookup_id)
.map_err(|_| ConversionError::MissingParameter("Payment id".to_string()))?,
)
} else {
None
};
let request = match serde_json::from_str(&request) {
Ok(req) => req,