mirror of
https://github.com/aljazceru/cdk.git
synced 2025-12-18 21:25:09 +01:00
Add PostgreSQL support for mint and wallet (#878)
* Add PostgreSQL support for mint and wallet * Fixed bug to avoid empty calls `get_proofs_states` * Fixed SQL bug * Avoid redudant clone() * Add more tests for the storage layer * Minor enhacements * Add a generic function to execute db operations This function would log slow operations and log errors * Provision a postgres db for tests * Update deps for msrv * Add postgres to pipeline * feat: add psgl to example and docker * feat: db url fmt --------- Co-authored-by: thesimplekid <tsk@thesimplekid.com>
This commit is contained in:
@@ -182,7 +182,7 @@ where
|
||||
|
||||
/// Begin a transaction
|
||||
async fn begin(conn: &mut W) -> Result<(), Error> {
|
||||
query("BEGIN")?.execute(conn).await?;
|
||||
query("START TRANSACTION")?.execute(conn).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/// @generated
|
||||
/// Auto-generated by build.rs
|
||||
pub static MIGRATIONS: &[(&str, &str, &str)] = &[
|
||||
("postgres", "1_init.sql", include_str!(r#"./migrations/postgres/1_init.sql"#)),
|
||||
("sqlite", "1_fix_sqlx_migration.sql", include_str!(r#"./migrations/sqlite/1_fix_sqlx_migration.sql"#)),
|
||||
("sqlite", "20250109143347_init.sql", include_str!(r#"./migrations/sqlite/20250109143347_init.sql"#)),
|
||||
];
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
CREATE TABLE IF NOT EXISTS proof (
|
||||
y BYTEA PRIMARY KEY,
|
||||
keyset_id TEXT NOT NULL,
|
||||
secret TEXT NOT NULL,
|
||||
c BYTEA NOT NULL,
|
||||
state TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS state_index ON proof(state);
|
||||
CREATE INDEX IF NOT EXISTS secret_index ON proof(secret);
|
||||
|
||||
|
||||
-- Keysets Table
|
||||
|
||||
CREATE TABLE IF NOT EXISTS keyset (
|
||||
id TEXT PRIMARY KEY,
|
||||
unit TEXT NOT NULL,
|
||||
active BOOL NOT NULL,
|
||||
valid_from INTEGER NOT NULL,
|
||||
valid_to INTEGER,
|
||||
derivation_path TEXT NOT NULL,
|
||||
max_order INTEGER NOT NULL,
|
||||
derivation_path_index INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS unit_index ON keyset(unit);
|
||||
CREATE INDEX IF NOT EXISTS active_index ON keyset(active);
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS blind_signature (
|
||||
y BYTEA PRIMARY KEY,
|
||||
amount INTEGER NOT NULL,
|
||||
keyset_id TEXT NOT NULL,
|
||||
c BYTEA NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS keyset_id_index ON blind_signature(keyset_id);
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS protected_endpoints (
|
||||
endpoint TEXT PRIMARY KEY,
|
||||
auth TEXT NOT NULL
|
||||
);
|
||||
@@ -262,9 +262,10 @@ where
|
||||
FROM
|
||||
keyset
|
||||
WHERE
|
||||
active = 1;
|
||||
active = :active;
|
||||
"#,
|
||||
)?
|
||||
.bind("active", true)
|
||||
.pluck(&*conn)
|
||||
.await?
|
||||
.map(|id| Ok::<_, Error>(column_as_string!(id, Id::from_str, Id::from_bytes)))
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/// @generated
|
||||
/// Auto-generated by build.rs
|
||||
pub static MIGRATIONS: &[(&str, &str, &str)] = &[
|
||||
("postgres", "1_initial.sql", include_str!(r#"./migrations/postgres/1_initial.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"#)),
|
||||
|
||||
100
crates/cdk-sql-common/src/mint/migrations/postgres/1_initial.sql
Normal file
100
crates/cdk-sql-common/src/mint/migrations/postgres/1_initial.sql
Normal file
@@ -0,0 +1,100 @@
|
||||
CREATE TABLE keyset (
|
||||
id TEXT PRIMARY KEY, unit TEXT NOT NULL,
|
||||
active BOOL NOT NULL, valid_from INTEGER NOT NULL,
|
||||
valid_to INTEGER, derivation_path TEXT NOT NULL,
|
||||
max_order INTEGER NOT NULL, input_fee_ppk INTEGER,
|
||||
derivation_path_index INTEGER
|
||||
);
|
||||
CREATE INDEX unit_index ON keyset(unit);
|
||||
CREATE INDEX active_index ON keyset(active);
|
||||
CREATE TABLE melt_quote (
|
||||
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 NOT NULL DEFAULT 'payment_hash'
|
||||
);
|
||||
CREATE INDEX melt_quote_state_index ON melt_quote(state);
|
||||
CREATE UNIQUE INDEX unique_request_lookup_id_melt ON melt_quote(request_lookup_id);
|
||||
CREATE TABLE melt_request (
|
||||
id TEXT PRIMARY KEY, inputs TEXT NOT NULL,
|
||||
outputs TEXT, method TEXT NOT NULL,
|
||||
unit TEXT NOT NULL
|
||||
);
|
||||
CREATE TABLE config (
|
||||
id TEXT PRIMARY KEY, value TEXT NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS "proof" (
|
||||
y BYTEA PRIMARY KEY,
|
||||
amount INTEGER NOT NULL,
|
||||
keyset_id TEXT NOT NULL,
|
||||
secret TEXT NOT NULL,
|
||||
c BYTEA NOT NULL,
|
||||
witness TEXT,
|
||||
state TEXT CHECK (
|
||||
state IN (
|
||||
'SPENT', 'PENDING', 'UNSPENT', 'RESERVED',
|
||||
'UNKNOWN'
|
||||
)
|
||||
) NOT NULL,
|
||||
quote_id TEXT,
|
||||
created_time INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS "blind_signature" (
|
||||
blinded_message BYTEA PRIMARY KEY,
|
||||
amount INTEGER NOT NULL,
|
||||
keyset_id TEXT NOT NULL,
|
||||
c BYTEA NOT NULL,
|
||||
dleq_e TEXT,
|
||||
dleq_s TEXT,
|
||||
quote_id TEXT,
|
||||
created_time INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS "mint_quote" (
|
||||
id TEXT PRIMARY KEY, amount INTEGER,
|
||||
unit TEXT NOT NULL, request TEXT NOT NULL,
|
||||
expiry INTEGER NOT NULL, request_lookup_id TEXT UNIQUE,
|
||||
pubkey TEXT, created_time INTEGER NOT NULL DEFAULT 0,
|
||||
amount_paid INTEGER NOT NULL DEFAULT 0,
|
||||
amount_issued INTEGER NOT NULL DEFAULT 0,
|
||||
payment_method TEXT NOT NULL DEFAULT 'BOLT11',
|
||||
request_lookup_id_kind TEXT NOT NULL DEFAULT 'payment_hash'
|
||||
);
|
||||
CREATE INDEX idx_mint_quote_created_time ON mint_quote(created_time);
|
||||
CREATE INDEX idx_mint_quote_expiry ON mint_quote(expiry);
|
||||
CREATE INDEX idx_mint_quote_request_lookup_id ON mint_quote(request_lookup_id);
|
||||
CREATE INDEX idx_mint_quote_request_lookup_id_and_kind ON mint_quote(
|
||||
request_lookup_id, request_lookup_id_kind
|
||||
);
|
||||
CREATE TABLE mint_quote_payments (
|
||||
id SERIAL PRIMARY KEY,
|
||||
quote_id TEXT NOT NULL,
|
||||
payment_id TEXT NOT NULL UNIQUE,
|
||||
timestamp INTEGER NOT NULL,
|
||||
amount INTEGER NOT NULL,
|
||||
FOREIGN KEY (quote_id) REFERENCES mint_quote(id)
|
||||
);
|
||||
CREATE INDEX idx_mint_quote_payments_payment_id ON mint_quote_payments(payment_id);
|
||||
CREATE INDEX idx_mint_quote_payments_quote_id ON mint_quote_payments(quote_id);
|
||||
CREATE TABLE mint_quote_issued (
|
||||
id SERIAL PRIMARY KEY,
|
||||
quote_id TEXT NOT NULL,
|
||||
amount INTEGER NOT NULL,
|
||||
timestamp INTEGER NOT NULL,
|
||||
FOREIGN KEY (quote_id) REFERENCES mint_quote(id)
|
||||
);
|
||||
CREATE INDEX idx_mint_quote_issued_quote_id ON mint_quote_issued(quote_id);
|
||||
CREATE INDEX idx_melt_quote_request_lookup_id_and_kind ON mint_quote(
|
||||
request_lookup_id, request_lookup_id_kind
|
||||
);
|
||||
@@ -255,6 +255,9 @@ where
|
||||
ys: &[PublicKey],
|
||||
_quote_id: Option<Uuid>,
|
||||
) -> Result<(), Self::Err> {
|
||||
if ys.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
let total_deleted = query(
|
||||
r#"
|
||||
DELETE FROM proof WHERE y IN (:ys) AND state NOT IN (:exclude_state)
|
||||
@@ -314,10 +317,15 @@ where
|
||||
// Get payment IDs and timestamps from the mint_quote_payments table
|
||||
query(
|
||||
r#"
|
||||
SELECT payment_id, timestamp, amount
|
||||
FROM mint_quote_payments
|
||||
WHERE quote_id=:quote_id;
|
||||
"#,
|
||||
SELECT
|
||||
payment_id,
|
||||
timestamp,
|
||||
amount
|
||||
FROM
|
||||
mint_quote_payments
|
||||
WHERE
|
||||
quote_id=:quote_id
|
||||
"#,
|
||||
)?
|
||||
.bind("quote_id", quote_id.as_hyphenated().to_string())
|
||||
.fetch_all(conn)
|
||||
@@ -407,12 +415,12 @@ where
|
||||
}
|
||||
|
||||
async fn set_active_keyset(&mut self, unit: CurrencyUnit, id: Id) -> Result<(), Error> {
|
||||
query(r#"UPDATE keyset SET active=FALSE WHERE unit IS :unit"#)?
|
||||
query(r#"UPDATE keyset SET active=FALSE WHERE unit = :unit"#)?
|
||||
.bind("unit", unit.to_string())
|
||||
.execute(&self.inner)
|
||||
.await?;
|
||||
|
||||
query(r#"UPDATE keyset SET active=TRUE WHERE unit IS :unit AND id IS :id"#)?
|
||||
query(r#"UPDATE keyset SET active=TRUE WHERE unit = :unit AND id = :id"#)?
|
||||
.bind("unit", unit.to_string())
|
||||
.bind("id", id.to_string())
|
||||
.execute(&self.inner)
|
||||
@@ -443,7 +451,8 @@ where
|
||||
async fn get_active_keyset_id(&self, unit: &CurrencyUnit) -> Result<Option<Id>, Self::Err> {
|
||||
let conn = self.pool.get().map_err(|e| Error::Database(Box::new(e)))?;
|
||||
Ok(
|
||||
query(r#" SELECT id FROM keyset WHERE active = 1 AND unit IS :unit"#)?
|
||||
query(r#" SELECT id FROM keyset WHERE active = :active AND unit = :unit"#)?
|
||||
.bind("active", true)
|
||||
.bind("unit", unit.to_string())
|
||||
.pluck(&*conn)
|
||||
.await?
|
||||
@@ -458,17 +467,20 @@ where
|
||||
|
||||
async fn get_active_keysets(&self) -> Result<HashMap<CurrencyUnit, Id>, Self::Err> {
|
||||
let conn = self.pool.get().map_err(|e| Error::Database(Box::new(e)))?;
|
||||
Ok(query(r#"SELECT id, unit FROM keyset WHERE active = 1"#)?
|
||||
.fetch_all(&*conn)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|row| {
|
||||
Ok((
|
||||
column_as_string!(&row[1], CurrencyUnit::from_str),
|
||||
column_as_string!(&row[0], Id::from_str, Id::from_bytes),
|
||||
))
|
||||
})
|
||||
.collect::<Result<HashMap<_, _>, Error>>()?)
|
||||
Ok(
|
||||
query(r#"SELECT id, unit FROM keyset WHERE active = :active"#)?
|
||||
.bind("active", true)
|
||||
.fetch_all(&*conn)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|row| {
|
||||
Ok((
|
||||
column_as_string!(&row[1], CurrencyUnit::from_str),
|
||||
column_as_string!(&row[0], Id::from_str, Id::from_bytes),
|
||||
))
|
||||
})
|
||||
.collect::<Result<HashMap<_, _>, Error>>()?,
|
||||
)
|
||||
}
|
||||
|
||||
async fn get_keyset_info(&self, id: &Id) -> Result<Option<MintKeySetInfo>, Self::Err> {
|
||||
@@ -658,7 +670,6 @@ where
|
||||
UPDATE mint_quote
|
||||
SET amount_issued = :amount_issued
|
||||
WHERE id = :quote_id
|
||||
FOR UPDATE
|
||||
"#,
|
||||
)?
|
||||
.bind("amount_issued", new_amount_issued.to_i64())
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/// @generated
|
||||
/// Auto-generated by build.rs
|
||||
pub static MIGRATIONS: &[(&str, &str, &str)] = &[
|
||||
("postgres", "1_initial.sql", include_str!(r#"./migrations/postgres/1_initial.sql"#)),
|
||||
("sqlite", "1_fix_sqlx_migration.sql", include_str!(r#"./migrations/sqlite/1_fix_sqlx_migration.sql"#)),
|
||||
("sqlite", "20240612132920_init.sql", include_str!(r#"./migrations/sqlite/20240612132920_init.sql"#)),
|
||||
("sqlite", "20240618200350_quote_state.sql", include_str!(r#"./migrations/sqlite/20240618200350_quote_state.sql"#)),
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
CREATE TABLE mint (
|
||||
mint_url TEXT PRIMARY KEY, name TEXT,
|
||||
pubkey BYTEA, version TEXT, description TEXT,
|
||||
description_long TEXT, contact TEXT,
|
||||
nuts TEXT, motd TEXT, icon_url TEXT,
|
||||
mint_time INTEGER, urls TEXT, tos_url TEXT
|
||||
);
|
||||
CREATE TABLE keyset (
|
||||
id TEXT PRIMARY KEY,
|
||||
mint_url TEXT NOT NULL,
|
||||
unit TEXT NOT NULL,
|
||||
active BOOL NOT NULL,
|
||||
counter INTEGER NOT NULL DEFAULT 0,
|
||||
input_fee_ppk INTEGER,
|
||||
final_expiry INTEGER DEFAULT NULL,
|
||||
FOREIGN KEY(mint_url) REFERENCES mint(mint_url) ON UPDATE CASCADE ON DELETE CASCADE
|
||||
);
|
||||
CREATE TABLE melt_quote (
|
||||
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
|
||||
);
|
||||
CREATE TABLE key (
|
||||
id TEXT PRIMARY KEY, keys TEXT NOT NULL
|
||||
);
|
||||
CREATE INDEX melt_quote_state_index ON melt_quote(state);
|
||||
CREATE TABLE IF NOT EXISTS "proof" (
|
||||
y BYTEA PRIMARY KEY,
|
||||
mint_url TEXT NOT NULL,
|
||||
state TEXT CHECK (
|
||||
state IN (
|
||||
'SPENT', 'UNSPENT', 'PENDING', 'RESERVED',
|
||||
'PENDING_SPENT'
|
||||
)
|
||||
) NOT NULL,
|
||||
spending_condition TEXT,
|
||||
unit TEXT NOT NULL,
|
||||
amount INTEGER NOT NULL,
|
||||
keyset_id TEXT NOT NULL,
|
||||
secret TEXT NOT NULL,
|
||||
c BYTEA NOT NULL,
|
||||
witness TEXT,
|
||||
dleq_e BYTEA,
|
||||
dleq_s BYTEA,
|
||||
dleq_r BYTEA
|
||||
);
|
||||
CREATE TABLE transactions (
|
||||
id BYTEA PRIMARY KEY,
|
||||
mint_url TEXT NOT NULL,
|
||||
direction TEXT CHECK (
|
||||
direction IN ('Incoming', 'Outgoing')
|
||||
) NOT NULL,
|
||||
amount INTEGER NOT NULL,
|
||||
fee INTEGER NOT NULL,
|
||||
unit TEXT NOT NULL,
|
||||
ys BYTEA NOT NULL,
|
||||
timestamp INTEGER NOT NULL,
|
||||
memo TEXT,
|
||||
metadata TEXT
|
||||
);
|
||||
CREATE INDEX mint_url_index ON transactions(mint_url);
|
||||
CREATE INDEX direction_index ON transactions(direction);
|
||||
CREATE INDEX unit_index ON transactions(unit);
|
||||
CREATE INDEX timestamp_index ON transactions(timestamp);
|
||||
CREATE TABLE IF NOT EXISTS "mint_quote" (
|
||||
id TEXT PRIMARY KEY, mint_url TEXT NOT NULL,
|
||||
payment_method TEXT NOT NULL DEFAULT 'bolt11',
|
||||
amount INTEGER, unit TEXT NOT NULL,
|
||||
request TEXT NOT NULL, state TEXT NOT NULL,
|
||||
expiry INTEGER NOT NULL, amount_paid INTEGER NOT NULL DEFAULT 0,
|
||||
amount_issued INTEGER NOT NULL DEFAULT 0,
|
||||
secret_key TEXT
|
||||
);
|
||||
Reference in New Issue
Block a user