Skip to main content

cdk_sqlite/mint/
mod.rs

1//! SQLite Mint
2
3use cdk_sql_common::mint::SQLMintAuthDatabase;
4use cdk_sql_common::SQLMintDatabase;
5
6use crate::common::SqliteConnectionManager;
7
8pub mod memory;
9
10/// Mint SQLite implementation with rusqlite
11pub type MintSqliteDatabase = SQLMintDatabase<SqliteConnectionManager>;
12
13/// Mint Auth database with rusqlite
14pub type MintSqliteAuthDatabase = SQLMintAuthDatabase<SqliteConnectionManager>;
15
16#[cfg(test)]
17mod test {
18    use std::fs::remove_file;
19    use std::str::FromStr;
20    use std::sync::Arc;
21    use std::time::Duration;
22
23    use cdk_common::database::{self, MintAuthDatabase};
24    use cdk_common::secret::Secret;
25    use cdk_common::{mint_db_test, AuthProof, Id, SecretKey, State};
26    use cdk_sql_common::database::ConnectionWithTransaction;
27    use cdk_sql_common::pool::Pool;
28    use cdk_sql_common::stmt::query;
29
30    use super::*;
31    use crate::common::Config;
32
33    async fn provide_db(_test_name: String) -> MintSqliteDatabase {
34        memory::empty().await.unwrap()
35    }
36
37    mint_db_test!(provide_db);
38
39    #[tokio::test]
40    async fn kvstore_compare_and_swap() {
41        cdk_common::database::mint::test::kvstore_compare_and_swap(
42            provide_db("test_kvstore_compare_and_swap".to_owned()).await,
43        )
44        .await;
45    }
46
47    #[tokio::test]
48    async fn bug_opening_relative_path() {
49        let config: Config = "test.db".into();
50
51        let pool = Pool::<SqliteConnectionManager>::new(config);
52        let db = pool.get().await;
53        assert!(db.is_ok());
54        let _ = remove_file("test.db");
55    }
56
57    #[tokio::test]
58    async fn exhausted_in_memory_pool_times_out() {
59        let config: Config = ":memory:".into();
60        let pool = Pool::<SqliteConnectionManager>::new(config);
61
62        let _conn = pool.get().await.expect("valid connection");
63        let result = pool.get_timeout(Duration::from_millis(10)).await;
64
65        assert!(matches!(result, Err(cdk_sql_common::pool::Error::Timeout)));
66    }
67
68    #[tokio::test]
69    async fn cloned_config_creates_independent_in_memory_databases() {
70        let config: Config = ":memory:".into();
71        let _first = MintSqliteDatabase::new(config.clone())
72            .await
73            .expect("first database");
74        let _second = MintSqliteDatabase::new(config)
75            .await
76            .expect("independent second database");
77    }
78
79    #[test]
80    fn discarded_in_memory_connection_is_not_replaced() {
81        let config: Config = ":memory:".into();
82        let pool = Pool::<SqliteConnectionManager>::new(config);
83        let runtime = || {
84            tokio::runtime::Builder::new_current_thread()
85                .enable_all()
86                .build()
87                .expect("runtime")
88        };
89
90        // Dropping the runtime drops the unpolled rollback task, which discards
91        // the connection and with it the in-memory database.
92        let first = runtime();
93        first.block_on(async {
94            let conn = pool.get().await.expect("valid connection");
95            let tx = ConnectionWithTransaction::new(conn)
96                .await
97                .expect("transaction");
98            drop(tx);
99        });
100        drop(first);
101
102        runtime().block_on(async {
103            assert!(matches!(
104                pool.get().await,
105                Err(cdk_sql_common::pool::Error::Resource(_))
106            ));
107        });
108    }
109
110    async fn spend_auth_proof(
111        db: Arc<MintSqliteAuthDatabase>,
112        proof: AuthProof,
113    ) -> Result<(), database::Error> {
114        let mut tx = db.as_ref().begin_transaction().await?;
115        tx.add_proof(proof).await?;
116        tx.commit().await
117    }
118
119    #[tokio::test]
120    async fn concurrent_blind_auth_proof_spend_allows_one_request() {
121        let path = std::env::temp_dir().join(format!(
122            "cdk-blind-auth-replay-{}.sqlite",
123            uuid::Uuid::new_v4()
124        ));
125
126        #[cfg(not(feature = "sqlcipher"))]
127        let db = Arc::new(
128            MintSqliteAuthDatabase::new(&path)
129                .await
130                .expect("auth database"),
131        );
132        #[cfg(feature = "sqlcipher")]
133        let db = Arc::new(
134            MintSqliteAuthDatabase::new((path.clone(), "test".to_owned()))
135                .await
136                .expect("auth database"),
137        );
138
139        let proof = AuthProof {
140            keyset_id: Id::from_str("00916bbf7ef91a36").expect("valid keyset id"),
141            secret: Secret::generate(),
142            c: SecretKey::generate().public_key(),
143            dleq: None,
144        };
145        let y = proof.y().expect("proof y");
146
147        let (first, second) = tokio::join!(
148            spend_auth_proof(db.clone(), proof.clone()),
149            spend_auth_proof(db.clone(), proof)
150        );
151
152        assert!(matches!(
153            (&first, &second),
154            (Ok(()), Err(database::Error::Duplicate)) | (Err(database::Error::Duplicate), Ok(()))
155        ));
156        assert_eq!(
157            db.get_proofs_states(&[y]).await.expect("proof state"),
158            vec![Some(State::Spent)]
159        );
160
161        drop(db);
162        remove_file(path).expect("remove auth database");
163    }
164
165    #[tokio::test]
166    async fn open_legacy_and_migrate() {
167        let file = format!(
168            "{}/db.sqlite",
169            std::env::temp_dir().to_str().unwrap_or_default()
170        );
171
172        {
173            let _ = remove_file(&file);
174            #[cfg(not(feature = "sqlcipher"))]
175            let config: Config = file.as_str().into();
176            #[cfg(feature = "sqlcipher")]
177            let config: Config = (file.as_str(), "test").into();
178
179            let pool = Pool::<SqliteConnectionManager>::new(config);
180
181            let conn = pool.get().await.expect("valid connection");
182
183            query(include_str!("../../tests/legacy-sqlx.sql"))
184                .expect("query")
185                .execute(&*conn)
186                .await
187                .expect("create former db failed");
188        }
189
190        #[cfg(not(feature = "sqlcipher"))]
191        let conn = MintSqliteDatabase::new(file.as_str()).await;
192
193        #[cfg(feature = "sqlcipher")]
194        let conn = MintSqliteDatabase::new((file.as_str(), "test")).await;
195
196        assert!(conn.is_ok(), "Failed with {:?}", conn.unwrap_err());
197
198        let _ = remove_file(&file);
199    }
200}