feat: store melt_request (#1045)

This commit is contained in:
thesimplekid
2025-09-16 10:55:36 +01:00
committed by GitHub
parent 5650050f32
commit 2dbb418db7
9 changed files with 311 additions and 61 deletions

View File

@@ -4,6 +4,7 @@ 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"#)),
("postgres", "20250901090000_add_kv_store.sql", include_str!(r#"./migrations/postgres/20250901090000_add_kv_store.sql"#)),
("postgres", "20250902140000_add_melt_request_and_blinded_messages.sql", include_str!(r#"./migrations/postgres/20250902140000_add_melt_request_and_blinded_messages.sql"#)),
("postgres", "20250903200000_add_signatory_amounts.sql", include_str!(r#"./migrations/postgres/20250903200000_add_signatory_amounts.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"#)),
@@ -30,5 +31,6 @@ pub static MIGRATIONS: &[(&str, &str, &str)] = &[
("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"#)),
("sqlite", "20250901090000_add_kv_store.sql", include_str!(r#"./migrations/sqlite/20250901090000_add_kv_store.sql"#)),
("sqlite", "20250902140000_add_melt_request_and_blinded_messages.sql", include_str!(r#"./migrations/sqlite/20250902140000_add_melt_request_and_blinded_messages.sql"#)),
("sqlite", "20250903200000_add_signatory_amounts.sql", include_str!(r#"./migrations/sqlite/20250903200000_add_signatory_amounts.sql"#)),
];

View File

@@ -0,0 +1,20 @@
-- Drop existing melt_request table and recreate with new schema
DROP TABLE IF EXISTS melt_request;
CREATE TABLE melt_request (
quote_id TEXT PRIMARY KEY,
inputs_amount INTEGER NOT NULL,
inputs_fee INTEGER NOT NULL,
FOREIGN KEY (quote_id) REFERENCES melt_quote(id)
);
-- Add blinded_messages table
CREATE TABLE blinded_messages (
quote_id TEXT NOT NULL,
blinded_message BYTEA NOT NULL,
keyset_id TEXT NOT NULL,
amount INTEGER NOT NULL,
FOREIGN KEY (quote_id) REFERENCES melt_request(quote_id) ON DELETE CASCADE
);
-- Add index for faster lookups on blinded_messages
CREATE INDEX blinded_messages_quote_id_index ON blinded_messages(quote_id);

View File

@@ -0,0 +1,23 @@
-- Drop existing melt_request table and recreate with new schema
DROP TABLE IF EXISTS melt_request;
CREATE TABLE melt_request (
quote_id TEXT PRIMARY KEY,
inputs_amount INTEGER NOT NULL,
inputs_fee INTEGER NOT NULL,
FOREIGN KEY (quote_id) REFERENCES melt_quote(id)
);
-- Add blinded_messages table
CREATE TABLE blinded_messages (
quote_id TEXT NOT NULL,
blinded_message BLOB NOT NULL,
amount INTEGER NOT NULL DEFAULT 0,
keyset_id TEXT NOT NULL,
FOREIGN KEY (quote_id) REFERENCES melt_request(quote_id) ON DELETE CASCADE
);
-- Add index for faster lookups on blinded_messages
CREATE INDEX blinded_messages_quote_id_index ON blinded_messages(quote_id);
-- Create an index on keyset_id for better query performance
CREATE INDEX blinded_messages_keyset_id_index ON blinded_messages(keyset_id);

View File

@@ -32,8 +32,8 @@ use cdk_common::secret::Secret;
use cdk_common::state::check_state_transition;
use cdk_common::util::unix_time;
use cdk_common::{
Amount, BlindSignature, BlindSignatureDleq, CurrencyUnit, Id, MeltQuoteState, MintInfo,
PaymentMethod, Proof, Proofs, PublicKey, SecretKey, State,
Amount, BlindSignature, BlindSignatureDleq, BlindedMessage, CurrencyUnit, Id, MeltQuoteState,
MintInfo, PaymentMethod, Proof, Proofs, PublicKey, SecretKey, State,
};
use lightning_invoice::Bolt11Invoice;
use migrations::MIGRATIONS;
@@ -277,6 +277,33 @@ where
Ok(())
}
async fn get_proof_ys_by_quote_id(
&self,
quote_id: &QuoteId,
) -> Result<Vec<PublicKey>, Self::Err> {
Ok(query(
r#"
SELECT
amount,
keyset_id,
secret,
c,
witness
FROM
proof
WHERE
quote_id = :quote_id
"#,
)?
.bind("quote_id", quote_id.to_string())
.fetch_all(&self.inner)
.await?
.into_iter()
.map(sql_row_to_proof)
.collect::<Result<Vec<Proof>, _>>()?
.ys()?)
}
}
#[async_trait]
@@ -564,6 +591,123 @@ where
{
type Err = Error;
async fn add_melt_request_and_blinded_messages(
&mut self,
quote_id: &QuoteId,
inputs_amount: Amount,
inputs_fee: Amount,
blinded_messages: &[BlindedMessage],
) -> Result<(), Self::Err> {
query(
r#"
INSERT INTO melt_request
(quote_id, inputs_amount, inputs_fee)
VALUES
(:quote_id, :inputs_amount, :inputs_fee)
"#,
)?
.bind("quote_id", quote_id.to_string())
.bind("inputs_amount", inputs_amount.to_i64())
.bind("inputs_fee", inputs_fee.to_i64())
.execute(&self.inner)
.await?;
for message in blinded_messages {
query(
r#"
INSERT INTO blinded_messages
(quote_id, blinded_message, keyset_id, amount)
VALUES
(:quote_id, :blinded_message, :keyset_id, :amount)
"#,
)?
.bind("quote_id", quote_id.to_string())
.bind(
"blinded_message",
message.blinded_secret.to_bytes().to_vec(),
)
.bind("keyset_id", message.keyset_id.to_string())
.bind("amount", message.amount.to_i64())
.execute(&self.inner)
.await?;
}
Ok(())
}
async fn get_melt_request_and_blinded_messages(
&mut self,
quote_id: &QuoteId,
) -> Result<Option<database::mint::MeltRequestInfo>, Self::Err> {
let melt_request_row = query(
r#"
SELECT inputs_amount, inputs_fee
FROM melt_request
WHERE quote_id = :quote_id
FOR UPDATE
"#,
)?
.bind("quote_id", quote_id.to_string())
.fetch_one(&self.inner)
.await?;
if let Some(row) = melt_request_row {
let inputs_amount: u64 = column_as_number!(row[0].clone());
let inputs_fee: u64 = column_as_number!(row[1].clone());
let blinded_messages_rows = query(
r#"
SELECT blinded_message, keyset_id, amount
FROM blinded_messages
WHERE quote_id = :quote_id
"#,
)?
.bind("quote_id", quote_id.to_string())
.fetch_all(&self.inner)
.await?;
let blinded_messages: Result<Vec<BlindedMessage>, Error> = blinded_messages_rows
.into_iter()
.map(|row| -> Result<BlindedMessage, Error> {
let blinded_message_key =
column_as_string!(&row[0], PublicKey::from_hex, PublicKey::from_slice);
let keyset_id = column_as_string!(&row[1], Id::from_str, Id::from_bytes);
let amount: u64 = column_as_number!(row[2].clone());
Ok(BlindedMessage {
blinded_secret: blinded_message_key,
keyset_id,
amount: Amount::from(amount),
witness: None, // Not storing witness in database currently
})
})
.collect();
let blinded_messages = blinded_messages?;
Ok(Some(database::mint::MeltRequestInfo {
inputs_amount: Amount::from(inputs_amount),
inputs_fee: Amount::from(inputs_fee),
change_outputs: blinded_messages,
}))
} else {
Ok(None)
}
}
async fn delete_melt_request(&mut self, quote_id: &QuoteId) -> Result<(), Self::Err> {
query(
r#"
DELETE FROM melt_request
WHERE quote_id = :quote_id
"#,
)?
.bind("quote_id", quote_id.to_string())
.execute(&self.inner)
.await?;
Ok(())
}
#[instrument(skip(self))]
async fn increment_mint_quote_amount_paid(
&mut self,
@@ -788,8 +932,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
// Now insert the new quote
query(
r#"
@@ -916,6 +1058,10 @@ VALUES (:quote_id, :amount, :timestamp);
let old_state = quote.state;
quote.state = state;
if state == MeltQuoteState::Unpaid || state == MeltQuoteState::Failed {
self.delete_melt_request(quote_id).await?;
}
Ok((old_state, quote))
}