Browse Source

Remove OnceCell<Mint>

Without this change, a single Mint is shared for all tests, and the first tests
to run and shutdown makes the other databases (not-reachable, as dropping the
tokio engine would also drop the database instance).

There is no real reason, other than perhaps performance. The mint should
perhaps run in their own tokio engine and share channels as API interfaces, or
a new instance should be created in each tests
Cesar Rodas 1 month ago
parent
commit
0b6757926c

+ 0 - 1
crates/cdk-integration-tests/tests/fake_wallet.rs

@@ -881,7 +881,6 @@ async fn test_fake_mint_input_output_mismatch() -> Result<()> {
         &Mnemonic::generate(12)?.to_seed_normalized(""),
         None,
     )?;
-
     let usd_active_keyset_id = wallet_usd.get_active_mint_keyset().await?.id;
 
     let inputs = proofs;

+ 6 - 9
crates/cdk-integration-tests/tests/mint.rs

@@ -21,13 +21,10 @@ use cdk::util::unix_time;
 use cdk::Mint;
 use cdk_fake_wallet::FakeWallet;
 use cdk_sqlite::mint::memory;
-use tokio::sync::OnceCell;
 use tokio::time::sleep;
 
 pub const MINT_URL: &str = "http://127.0.0.1:8088";
 
-static INSTANCE: OnceCell<Mint> = OnceCell::const_new();
-
 async fn new_mint(fee: u64) -> Mint {
     let mut supported_units = HashMap::new();
     supported_units.insert(CurrencyUnit::Sat, (fee, 32));
@@ -62,8 +59,8 @@ async fn new_mint(fee: u64) -> Mint {
     .unwrap()
 }
 
-async fn initialize() -> &'static Mint {
-    INSTANCE.get_or_init(|| new_mint(0)).await
+async fn initialize() -> Mint {
+    new_mint(0).await
 }
 
 async fn mint_proofs(
@@ -115,7 +112,7 @@ async fn test_mint_double_spend() -> Result<()> {
     let keys = mint.pubkeys().await?.keysets.first().unwrap().clone().keys;
     let keyset_id = Id::from(&keys);
 
-    let proofs = mint_proofs(mint, 100.into(), &SplitTarget::default(), keys).await?;
+    let proofs = mint_proofs(&mint, 100.into(), &SplitTarget::default(), keys).await?;
 
     let preswap = PreMintSecrets::random(keyset_id, 100.into(), &SplitTarget::default())?;
 
@@ -149,7 +146,7 @@ async fn test_attempt_to_swap_by_overflowing() -> Result<()> {
     let keys = mint.pubkeys().await?.keysets.first().unwrap().clone().keys;
     let keyset_id = Id::from(&keys);
 
-    let proofs = mint_proofs(mint, 100.into(), &SplitTarget::default(), keys).await?;
+    let proofs = mint_proofs(&mint, 100.into(), &SplitTarget::default(), keys).await?;
 
     let amount = 2_u64.pow(63);
 
@@ -188,7 +185,7 @@ pub async fn test_p2pk_swap() -> Result<()> {
     let keys = mint.pubkeys().await?.keysets.first().unwrap().clone().keys;
     let keyset_id = Id::from(&keys);
 
-    let proofs = mint_proofs(mint, 100.into(), &SplitTarget::default(), keys).await?;
+    let proofs = mint_proofs(&mint, 100.into(), &SplitTarget::default(), keys).await?;
 
     let secret = SecretKey::generate();
 
@@ -306,7 +303,7 @@ async fn test_swap_unbalanced() -> Result<()> {
     let keys = mint.pubkeys().await?.keysets.first().unwrap().clone().keys;
     let keyset_id = Id::from(&keys);
 
-    let proofs = mint_proofs(mint, 100.into(), &SplitTarget::default(), keys).await?;
+    let proofs = mint_proofs(&mint, 100.into(), &SplitTarget::default(), keys).await?;
 
     let preswap = PreMintSecrets::random(keyset_id, 95.into(), &SplitTarget::default())?;
 

+ 77 - 0
crates/cdk-sqlite/src/common.rs

@@ -0,0 +1,77 @@
+use std::fs::remove_file;
+use std::ops::Deref;
+use std::str::FromStr;
+use std::sync::atomic::AtomicU64;
+use std::time::{Duration, SystemTime, UNIX_EPOCH};
+
+use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions};
+use sqlx::{Error, Pool, Sqlite};
+
+static FILE_ID: AtomicU64 = AtomicU64::new(0);
+
+/// A wrapper around a `Pool<Sqlite>` that may delete the database file when dropped in order to by
+/// pass the SQLx bug with pools and in-memory databases.
+///
+/// [1] https://github.com/launchbadge/sqlx/issues/362
+/// [2] https://github.com/launchbadge/sqlx/issues/2510
+#[derive(Debug, Clone)]
+pub struct SqlitePool {
+    pool: Pool<Sqlite>,
+    path: String,
+    delete: bool,
+}
+
+impl Drop for SqlitePool {
+    fn drop(&mut self) {
+        if self.delete {
+            let _ = remove_file(&self.path);
+        }
+    }
+}
+
+impl Deref for SqlitePool {
+    type Target = Pool<Sqlite>;
+
+    fn deref(&self) -> &Self::Target {
+        &self.pool
+    }
+}
+
+#[inline(always)]
+pub async fn create_sqlite_pool(path: &str) -> Result<SqlitePool, Error> {
+    let (path, delete) = if path.ends_with(":memory:") {
+        (
+            format!(
+                "in-memory-{}-{}",
+                SystemTime::now()
+                    .duration_since(UNIX_EPOCH)
+                    .unwrap()
+                    .as_nanos(),
+                FILE_ID.fetch_add(1, std::sync::atomic::Ordering::SeqCst),
+            ),
+            true,
+        )
+    } else {
+        (path.to_owned(), false)
+    };
+
+    let db_options = SqliteConnectOptions::from_str(&path)?
+        .journal_mode(if delete {
+            sqlx::sqlite::SqliteJournalMode::Memory
+        } else {
+            sqlx::sqlite::SqliteJournalMode::Wal
+        })
+        .busy_timeout(Duration::from_secs(5))
+        .read_only(false)
+        .create_if_missing(true)
+        .auto_vacuum(sqlx::sqlite::SqliteAutoVacuum::Full);
+
+    Ok(SqlitePool {
+        pool: SqlitePoolOptions::new()
+            .max_connections(1)
+            .connect_with(db_options)
+            .await?,
+        delete,
+        path,
+    })
+}

+ 2 - 0
crates/cdk-sqlite/src/lib.rs

@@ -3,6 +3,8 @@
 #![warn(missing_docs)]
 #![warn(rustdoc::bare_urls)]
 
+mod common;
+
 #[cfg(feature = "mint")]
 pub mod mint;
 #[cfg(feature = "wallet")]

+ 0 - 1
crates/cdk-sqlite/src/mint/memory.rs

@@ -30,7 +30,6 @@ pub async fn new_with_state(
     mint_info: MintInfo,
 ) -> Result<MintSqliteDatabase, database::Error> {
     let db = empty().await?;
-    db.migrate().await;
 
     for active_keyset in active_keysets {
         db.set_active_keyset(active_keyset.0, active_keyset.1)

+ 8 - 16
crates/cdk-sqlite/src/mint/mod.rs

@@ -1,9 +1,9 @@
 //! SQLite Mint
 
 use std::collections::HashMap;
+use std::ops::Deref;
 use std::path::Path;
 use std::str::FromStr;
-use std::time::Duration;
 
 use async_trait::async_trait;
 use bitcoin::bip32::DerivationPath;
@@ -20,11 +20,13 @@ use cdk_common::{
 };
 use error::Error;
 use lightning_invoice::Bolt11Invoice;
-use sqlx::sqlite::{SqliteConnectOptions, SqlitePool, SqlitePoolOptions, SqliteRow};
+use sqlx::sqlite::SqliteRow;
 use sqlx::Row;
 use uuid::fmt::Hyphenated;
 use uuid::Uuid;
 
+use crate::common::{create_sqlite_pool, SqlitePool};
+
 pub mod error;
 pub mod memory;
 
@@ -37,25 +39,15 @@ pub struct MintSqliteDatabase {
 impl MintSqliteDatabase {
     /// Create new [`MintSqliteDatabase`]
     pub async fn new<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
-        let path = path.as_ref().to_str().ok_or(Error::InvalidDbPath)?;
-        let db_options = SqliteConnectOptions::from_str(path)?
-            .busy_timeout(Duration::from_secs(5))
-            .read_only(false)
-            .create_if_missing(true)
-            .auto_vacuum(sqlx::sqlite::SqliteAutoVacuum::Full);
-
-        let pool = SqlitePoolOptions::new()
-            .max_connections(1)
-            .connect_with(db_options)
-            .await?;
-
-        Ok(Self { pool })
+        Ok(Self {
+            pool: create_sqlite_pool(path.as_ref().to_str().ok_or(Error::InvalidDbPath)?).await?,
+        })
     }
 
     /// Migrate [`MintSqliteDatabase`]
     pub async fn migrate(&self) {
         sqlx::migrate!("./src/mint/migrations")
-            .run(&self.pool)
+            .run(self.pool.deref())
             .await
             .expect("Could not run migrations");
     }

+ 34 - 40
crates/cdk-sqlite/src/wallet/mod.rs

@@ -1,6 +1,7 @@
 //! SQLite Wallet Database
 
 use std::collections::HashMap;
+use std::ops::Deref;
 use std::path::Path;
 use std::str::FromStr;
 
@@ -16,10 +17,12 @@ use cdk_common::{
     SpendingConditions, State,
 };
 use error::Error;
-use sqlx::sqlite::{SqliteConnectOptions, SqlitePool, SqliteRow};
-use sqlx::{ConnectOptions, Row};
+use sqlx::sqlite::SqliteRow;
+use sqlx::Row;
 use tracing::instrument;
 
+use crate::common::{create_sqlite_pool, SqlitePool};
+
 pub mod error;
 pub mod memory;
 
@@ -32,24 +35,15 @@ pub struct WalletSqliteDatabase {
 impl WalletSqliteDatabase {
     /// Create new [`WalletSqliteDatabase`]
     pub async fn new<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
-        let path = path.as_ref().to_str().ok_or(Error::InvalidDbPath)?;
-        let _conn = SqliteConnectOptions::from_str(path)?
-            .journal_mode(sqlx::sqlite::SqliteJournalMode::Wal)
-            .read_only(false)
-            .create_if_missing(true)
-            .auto_vacuum(sqlx::sqlite::SqliteAutoVacuum::Full)
-            .connect()
-            .await?;
-
-        let pool = SqlitePool::connect(path).await?;
-
-        Ok(Self { pool })
+        Ok(Self {
+            pool: create_sqlite_pool(path.as_ref().to_str().ok_or(Error::InvalidDbPath)?).await?,
+        })
     }
 
     /// Migrate [`WalletSqliteDatabase`]
     pub async fn migrate(&self) {
         sqlx::migrate!("./src/wallet/migrations")
-            .run(&self.pool)
+            .run(self.pool.deref())
             .await
             .expect("Could not run migrations");
     }
@@ -64,7 +58,7 @@ impl WalletSqliteDatabase {
         )
         .bind(state.to_string())
         .bind(y.to_bytes().to_vec())
-        .execute(&self.pool)
+        .execute(self.pool.deref())
         .await
         .map_err(Error::from)?;
 
@@ -161,7 +155,7 @@ ON CONFLICT(mint_url) DO UPDATE SET
         .bind(urls)
         .bind(motd)
         .bind(time.map(|v| v as i64))
-        .execute(&self.pool)
+        .execute(self.pool.deref())
         .await
         .map_err(Error::from)?;
 
@@ -177,7 +171,7 @@ WHERE mint_url=?
         "#,
         )
         .bind(mint_url.to_string())
-        .execute(&self.pool)
+        .execute(self.pool.deref())
         .await
         .map_err(Error::from)?;
 
@@ -194,7 +188,7 @@ WHERE mint_url=?;
         "#,
         )
         .bind(mint_url.to_string())
-        .fetch_one(&self.pool)
+        .fetch_one(self.pool.deref())
         .await;
 
         let rec = match rec {
@@ -216,7 +210,7 @@ SELECT *
 FROM mint
         "#,
         )
-        .fetch_all(&self.pool)
+        .fetch_all(self.pool.deref())
         .await
         .map_err(Error::from)?;
 
@@ -257,7 +251,7 @@ FROM mint
             sqlx::query(&query)
                 .bind(new_mint_url.to_string())
                 .bind(old_mint_url.to_string())
-                .execute(&self.pool)
+                .execute(self.pool.deref())
                 .await
                 .map_err(Error::from)?;
         }
@@ -288,7 +282,7 @@ FROM mint
             .bind(keyset.unit.to_string())
             .bind(keyset.active)
             .bind(keyset.input_fee_ppk as i64)
-            .execute(&self.pool)
+            .execute(self.pool.deref())
             .await
             .map_err(Error::from)?;
         }
@@ -309,7 +303,7 @@ WHERE mint_url=?
         "#,
         )
         .bind(mint_url.to_string())
-        .fetch_all(&self.pool)
+        .fetch_all(self.pool.deref())
         .await;
 
         let recs = match recs {
@@ -341,7 +335,7 @@ WHERE id=?
         "#,
         )
         .bind(keyset_id.to_string())
-        .fetch_one(&self.pool)
+        .fetch_one(self.pool.deref())
         .await;
 
         let rec = match rec {
@@ -381,7 +375,7 @@ ON CONFLICT(id) DO UPDATE SET
         .bind(quote.state.to_string())
         .bind(quote.expiry as i64)
         .bind(quote.secret_key.map(|p| p.to_string()))
-        .execute(&self.pool)
+        .execute(self.pool.deref())
         .await
         .map_err(Error::from)?;
 
@@ -398,7 +392,7 @@ WHERE id=?;
         "#,
         )
         .bind(quote_id)
-        .fetch_one(&self.pool)
+        .fetch_one(self.pool.deref())
         .await;
 
         let rec = match rec {
@@ -420,7 +414,7 @@ SELECT *
 FROM mint_quote
         "#,
         )
-        .fetch_all(&self.pool)
+        .fetch_all(self.pool.deref())
         .await
         .map_err(Error::from)?;
 
@@ -441,7 +435,7 @@ WHERE id=?
         "#,
         )
         .bind(quote_id)
-        .execute(&self.pool)
+        .execute(self.pool.deref())
         .await
         .map_err(Error::from)?;
 
@@ -472,7 +466,7 @@ ON CONFLICT(id) DO UPDATE SET
         .bind(u64::from(quote.fee_reserve) as i64)
         .bind(quote.state.to_string())
         .bind(quote.expiry as i64)
-        .execute(&self.pool)
+        .execute(self.pool.deref())
         .await
         .map_err(Error::from)?;
 
@@ -489,7 +483,7 @@ WHERE id=?;
         "#,
         )
         .bind(quote_id)
-        .fetch_one(&self.pool)
+        .fetch_one(self.pool.deref())
         .await;
 
         let rec = match rec {
@@ -512,7 +506,7 @@ WHERE id=?
         "#,
         )
         .bind(quote_id)
-        .execute(&self.pool)
+        .execute(self.pool.deref())
         .await
         .map_err(Error::from)?;
 
@@ -533,7 +527,7 @@ ON CONFLICT(id) DO UPDATE SET
         )
         .bind(Id::from(&keys).to_string())
         .bind(serde_json::to_string(&keys).map_err(Error::from)?)
-        .execute(&self.pool)
+        .execute(self.pool.deref())
         .await
         .map_err(Error::from)?;
 
@@ -550,7 +544,7 @@ WHERE id=?;
         "#,
         )
         .bind(keyset_id.to_string())
-        .fetch_one(&self.pool)
+        .fetch_one(self.pool.deref())
         .await;
 
         let rec = match rec {
@@ -575,7 +569,7 @@ WHERE id=?
         "#,
         )
         .bind(id.to_string())
-        .execute(&self.pool)
+        .execute(self.pool.deref())
         .await
         .map_err(Error::from)?;
 
@@ -625,7 +619,7 @@ WHERE id=?
                     .witness
                     .map(|w| serde_json::to_string(&w).unwrap()),
             )
-            .execute(&self.pool)
+            .execute(self.pool.deref())
             .await
             .map_err(Error::from)?;
         }
@@ -639,7 +633,7 @@ WHERE id=?
             "#,
             )
             .bind(y.to_bytes().to_vec())
-            .execute(&self.pool)
+            .execute(self.pool.deref())
             .await
             .map_err(Error::from)?;
         }
@@ -685,7 +679,7 @@ SELECT *
 FROM proof;
         "#,
         )
-        .fetch_all(&self.pool)
+        .fetch_all(self.pool.deref())
         .await;
 
         let recs = match recs {
@@ -755,7 +749,7 @@ WHERE id=?;
         "#,
         )
         .bind(keyset_id.to_string())
-        .fetch_one(&self.pool)
+        .fetch_one(self.pool.deref())
         .await;
 
         let count = match rec {
@@ -785,7 +779,7 @@ WHERE key=?;
         "#,
         )
         .bind(verifying_key.to_bytes().to_vec())
-        .fetch_one(&self.pool)
+        .fetch_one(self.pool.deref())
         .await;
 
         let count = match rec {
@@ -820,7 +814,7 @@ ON CONFLICT(key) DO UPDATE SET
         )
         .bind(verifying_key.to_bytes().to_vec())
         .bind(last_checked)
-        .execute(&self.pool)
+        .execute(self.pool.deref())
         .await
         .map_err(Error::from)?;
 

+ 2 - 0
crates/cdk/src/wallet/keysets.rs

@@ -89,6 +89,8 @@ impl Wallet {
     /// keysets
     #[instrument(skip(self))]
     pub async fn get_active_mint_keyset(&self) -> Result<KeySetInfo, Error> {
+        // Important
+        let _ = self.get_mint_info().await?;
         let active_keysets = self.get_active_mint_keysets().await?;
 
         let keyset_with_lowest_fee = active_keysets

+ 1 - 1
crates/cdk/src/wallet/melt.rs

@@ -260,7 +260,7 @@ impl Wallet {
     ///  let mint_url = "https://testnut.cashu.space";
     ///  let unit = CurrencyUnit::Sat;
     ///
-    ///  let localstore = memory::empty().await?();
+    ///  let localstore = memory::empty().await?;
     ///  let wallet = Wallet::new(mint_url, unit, Arc::new(localstore), &seed, None).unwrap();
     ///  let bolt11 = "lnbc100n1pnvpufspp5djn8hrq49r8cghwye9kqw752qjncwyfnrprhprpqk43mwcy4yfsqdq5g9kxy7fqd9h8vmmfvdjscqzzsxqyz5vqsp5uhpjt36rj75pl7jq2sshaukzfkt7uulj456s4mh7uy7l6vx7lvxs9qxpqysgqedwz08acmqwtk8g4vkwm2w78suwt2qyzz6jkkwcgrjm3r3hs6fskyhvud4fan3keru7emjm8ygqpcrwtlmhfjfmer3afs5hhwamgr4cqtactdq".to_string();
     ///  let quote = wallet.melt_quote(bolt11, None).await?;

+ 2 - 2
crates/cdk/src/wallet/mint.rs

@@ -31,7 +31,7 @@ impl Wallet {
     ///     let mint_url = "https://testnut.cashu.space";
     ///     let unit = CurrencyUnit::Sat;
     ///
-    ///     let localstore = memory::empty().await?();
+    ///     let localstore = memory::empty().await?;
     ///     let wallet = Wallet::new(mint_url, unit, Arc::new(localstore), &seed, None)?;
     ///     let amount = Amount::from(100);
     ///
@@ -156,7 +156,7 @@ impl Wallet {
     ///     let mint_url = "https://testnut.cashu.space";
     ///     let unit = CurrencyUnit::Sat;
     ///
-    ///     let localstore = memory::empty().await?();
+    ///     let localstore = memory::empty().await?;
     ///     let wallet = Wallet::new(mint_url, unit, Arc::new(localstore), &seed, None).unwrap();
     ///     let amount = Amount::from(100);
     ///

+ 8 - 5
crates/cdk/src/wallet/mod.rs

@@ -124,12 +124,15 @@ impl Wallet {
     /// use cdk::wallet::Wallet;
     /// use rand::Rng;
     ///
-    /// let seed = rand::thread_rng().gen::<[u8; 32]>();
-    /// let mint_url = "https://testnut.cashu.space";
-    /// let unit = CurrencyUnit::Sat;
+    /// async fn test() -> anyhow::Result<()> {
+    ///     let seed = rand::thread_rng().gen::<[u8; 32]>();
+    ///     let mint_url = "https://testnut.cashu.space";
+    ///     let unit = CurrencyUnit::Sat;
     ///
-    /// let localstore = memory::empty().await?();
-    /// let wallet = Wallet::new(mint_url, unit, Arc::new(localstore), &seed, None);
+    ///     let localstore = memory::empty().await?;
+    ///     let wallet = Wallet::new(mint_url, unit, Arc::new(localstore), &seed, None);
+    ///     Ok(())
+    /// }
     /// ```
     pub fn new(
         mint_url: &str,

+ 2 - 2
crates/cdk/src/wallet/receive.rs

@@ -175,7 +175,7 @@ impl Wallet {
     ///  let mint_url = "https://testnut.cashu.space";
     ///  let unit = CurrencyUnit::Sat;
     ///
-    ///  let localstore = memory::empty().await?();
+    ///  let localstore = memory::empty().await?;
     ///  let wallet = Wallet::new(mint_url, unit, Arc::new(localstore), &seed, None).unwrap();
     ///  let token = "cashuAeyJ0b2tlbiI6W3sicHJvb2ZzIjpbeyJhbW91bnQiOjEsInNlY3JldCI6ImI0ZjVlNDAxMDJhMzhiYjg3NDNiOTkwMzU5MTU1MGYyZGEzZTQxNWEzMzU0OTUyN2M2MmM5ZDc5MGVmYjM3MDUiLCJDIjoiMDIzYmU1M2U4YzYwNTMwZWVhOWIzOTQzZmRhMWEyY2U3MWM3YjNmMGNmMGRjNmQ4NDZmYTc2NWFhZjc3OWZhODFkIiwiaWQiOiIwMDlhMWYyOTMyNTNlNDFlIn1dLCJtaW50IjoiaHR0cHM6Ly90ZXN0bnV0LmNhc2h1LnNwYWNlIn1dLCJ1bml0Ijoic2F0In0=";
     ///  let amount_receive = wallet.receive(token, SplitTarget::default(), &[], &[]).await?;
@@ -235,7 +235,7 @@ impl Wallet {
     ///  let mint_url = "https://testnut.cashu.space";
     ///  let unit = CurrencyUnit::Sat;
     ///
-    ///  let localstore = memory::empty().await?();
+    ///  let localstore = memory::empty().await?;
     ///  let wallet = Wallet::new(mint_url, unit, Arc::new(localstore), &seed, None).unwrap();
     ///  let token_raw = hex::decode("6372617742a4617481a261694800ad268c4d1f5826617081a3616101617378403961366462623834376264323332626137366462306466313937323136623239643362386363313435353363643237383237666331636339343266656462346561635821038618543ffb6b8695df4ad4babcde92a34a96bdcd97dcee0d7ccf98d4721267926164695468616e6b20796f75616d75687474703a2f2f6c6f63616c686f73743a33333338617563736174").unwrap();
     ///  let amount_receive = wallet.receive_raw(&token_raw, SplitTarget::default(), &[], &[]).await?;