mirror of
https://github.com/aljazceru/cdk.git
synced 2025-12-20 14:14:49 +01:00
feat: Add migration for keyset_id as foreign key in SQLite database (#634)
This commit is contained in:
@@ -50,6 +50,12 @@ pub enum Error {
|
||||
/// Unknown quote TTL
|
||||
#[error("Unknown quote TTL")]
|
||||
UnknownQuoteTTL,
|
||||
/// Proof not found
|
||||
#[error("Proof not found")]
|
||||
ProofNotFound,
|
||||
/// Invalid keyset ID
|
||||
#[error("Invalid keyset ID")]
|
||||
InvalidKeysetId,
|
||||
}
|
||||
|
||||
impl From<Error> for cdk_common::database::Error {
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
-- Add foreign key constraints for keyset_id in SQLite
|
||||
-- SQLite requires recreating tables to add foreign keys
|
||||
|
||||
-- First, ensure we have the right schema information
|
||||
PRAGMA foreign_keys = OFF;
|
||||
|
||||
-- Create new proof table with foreign key constraint
|
||||
CREATE TABLE proof_new (
|
||||
y BLOB PRIMARY KEY,
|
||||
amount INTEGER NOT NULL,
|
||||
keyset_id TEXT NOT NULL REFERENCES keyset(id),
|
||||
secret TEXT NOT NULL,
|
||||
c BLOB NOT NULL,
|
||||
witness TEXT,
|
||||
state TEXT CHECK (state IN ('SPENT', 'PENDING', 'UNSPENT', 'RESERVED', 'UNKNOWN')) NOT NULL,
|
||||
quote_id TEXT
|
||||
);
|
||||
|
||||
-- Copy data from old proof table to new one
|
||||
INSERT INTO proof_new SELECT * FROM proof;
|
||||
|
||||
-- Create new blind_signature table with foreign key constraint
|
||||
CREATE TABLE blind_signature_new (
|
||||
y BLOB PRIMARY KEY,
|
||||
amount INTEGER NOT NULL,
|
||||
keyset_id TEXT NOT NULL REFERENCES keyset(id),
|
||||
c BLOB NOT NULL,
|
||||
dleq_e TEXT,
|
||||
dleq_s TEXT,
|
||||
quote_id TEXT
|
||||
);
|
||||
|
||||
-- Copy data from old blind_signature table to new one
|
||||
INSERT INTO blind_signature_new SELECT * FROM blind_signature;
|
||||
|
||||
-- Drop old tables
|
||||
DROP TABLE IF EXISTS proof;
|
||||
DROP TABLE IF EXISTS blind_signature;
|
||||
|
||||
-- Rename new tables to original names
|
||||
ALTER TABLE proof_new RENAME TO proof;
|
||||
ALTER TABLE blind_signature_new RENAME TO blind_signature;
|
||||
|
||||
-- Recreate all indexes
|
||||
CREATE INDEX IF NOT EXISTS proof_keyset_id_index ON proof(keyset_id);
|
||||
CREATE INDEX IF NOT EXISTS state_index ON proof(state);
|
||||
CREATE INDEX IF NOT EXISTS secret_index ON proof(secret);
|
||||
CREATE INDEX IF NOT EXISTS blind_signature_keyset_id_index ON blind_signature(keyset_id);
|
||||
|
||||
-- Re-enable foreign keys
|
||||
PRAGMA foreign_keys = ON;
|
||||
@@ -855,9 +855,9 @@ FROM keyset;
|
||||
async fn add_proofs(&self, proofs: Proofs, quote_id: Option<Uuid>) -> Result<(), Self::Err> {
|
||||
let mut transaction = self.pool.begin().await.map_err(Error::from)?;
|
||||
for proof in proofs {
|
||||
if let Err(err) = sqlx::query(
|
||||
let result = sqlx::query(
|
||||
r#"
|
||||
INSERT INTO proof
|
||||
INSERT OR IGNORE INTO proof
|
||||
(y, amount, keyset_id, secret, c, witness, state, quote_id)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?);
|
||||
"#,
|
||||
@@ -871,10 +871,25 @@ VALUES (?, ?, ?, ?, ?, ?, ?, ?);
|
||||
.bind("UNSPENT")
|
||||
.bind(quote_id.map(|q| q.hyphenated()))
|
||||
.execute(&mut transaction)
|
||||
.await
|
||||
.map_err(Error::from)
|
||||
{
|
||||
tracing::debug!("Attempting to add known proof. Skipping.... {:?}", err);
|
||||
.await;
|
||||
|
||||
// We still need to check for foreign key constraint errors
|
||||
if let Err(err) = result {
|
||||
if let sqlx::Error::Database(db_err) = &err {
|
||||
if db_err.message().contains("FOREIGN KEY constraint failed") {
|
||||
tracing::error!(
|
||||
"Foreign key constraint failed when adding proof: {:?}",
|
||||
err
|
||||
);
|
||||
transaction.rollback().await.map_err(Error::from)?;
|
||||
return Err(database::Error::InvalidKeysetId);
|
||||
}
|
||||
}
|
||||
|
||||
// For any other error, roll back and return the error
|
||||
tracing::error!("Error adding proof: {:?}", err);
|
||||
transaction.rollback().await.map_err(Error::from)?;
|
||||
return Err(Error::from(err).into());
|
||||
}
|
||||
}
|
||||
transaction.commit().await.map_err(Error::from)?;
|
||||
@@ -1077,7 +1092,7 @@ WHERE keyset_id=?;
|
||||
"?,".repeat(ys.len()).trim_end_matches(',')
|
||||
);
|
||||
|
||||
let mut current_states = ys
|
||||
let rows = ys
|
||||
.iter()
|
||||
.fold(sqlx::query(&sql), |query, y| {
|
||||
query.bind(y.to_bytes().to_vec())
|
||||
@@ -1087,7 +1102,16 @@ WHERE keyset_id=?;
|
||||
.map_err(|err| {
|
||||
tracing::error!("SQLite could not get state of proof: {err:?}");
|
||||
Error::SQLX(err)
|
||||
})?
|
||||
})?;
|
||||
|
||||
// Check if all proofs exist
|
||||
if rows.len() != ys.len() {
|
||||
transaction.rollback().await.map_err(Error::from)?;
|
||||
tracing::warn!("Attempted to update state of non-existent proof");
|
||||
return Err(database::Error::ProofNotFound);
|
||||
}
|
||||
|
||||
let mut current_states = rows
|
||||
.into_iter()
|
||||
.map(|row| {
|
||||
PublicKey::from_slice(row.get("y"))
|
||||
@@ -1694,6 +1718,7 @@ fn sqlite_row_to_melt_request(row: SqliteRow) -> Result<(MeltBolt11Request<Uuid>
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use cdk_common::mint::MintKeySetInfo;
|
||||
use cdk_common::Amount;
|
||||
|
||||
use super::*;
|
||||
@@ -1702,8 +1727,20 @@ mod tests {
|
||||
async fn test_remove_spent_proofs() {
|
||||
let db = memory::empty().await.unwrap();
|
||||
|
||||
// Create some test proofs
|
||||
// Create a keyset and add it to the database
|
||||
let keyset_id = Id::from_str("00916bbf7ef91a36").unwrap();
|
||||
let keyset_info = MintKeySetInfo {
|
||||
id: keyset_id.clone(),
|
||||
unit: CurrencyUnit::Sat,
|
||||
active: true,
|
||||
valid_from: 0,
|
||||
valid_to: None,
|
||||
derivation_path: bitcoin::bip32::DerivationPath::from_str("m/0'/0'/0'").unwrap(),
|
||||
derivation_path_index: Some(0),
|
||||
max_order: 32,
|
||||
input_fee_ppk: 0,
|
||||
};
|
||||
db.add_keyset_info(keyset_info).await.unwrap();
|
||||
|
||||
let proofs = vec![
|
||||
Proof {
|
||||
@@ -1758,8 +1795,20 @@ mod tests {
|
||||
async fn test_update_spent_proofs() {
|
||||
let db = memory::empty().await.unwrap();
|
||||
|
||||
// Create some test proofs
|
||||
// Create a keyset and add it to the database
|
||||
let keyset_id = Id::from_str("00916bbf7ef91a36").unwrap();
|
||||
let keyset_info = MintKeySetInfo {
|
||||
id: keyset_id.clone(),
|
||||
unit: CurrencyUnit::Sat,
|
||||
active: true,
|
||||
valid_from: 0,
|
||||
valid_to: None,
|
||||
derivation_path: bitcoin::bip32::DerivationPath::from_str("m/0'/0'/0'").unwrap(),
|
||||
derivation_path_index: Some(0),
|
||||
max_order: 32,
|
||||
input_fee_ppk: 0,
|
||||
};
|
||||
db.add_keyset_info(keyset_info).await.unwrap();
|
||||
|
||||
let proofs = vec![
|
||||
Proof {
|
||||
|
||||
Reference in New Issue
Block a user