1use std::boxed::Box;
8use std::collections::{BTreeMap, BTreeSet};
9use std::path::PathBuf;
10use std::string::{String, ToString};
11use std::sync::{Arc, RwLock};
12use std::vec::Vec;
13
14use db_management::pool_manager::{Pool, SqlitePoolManager};
15use db_management::utils::{
16 apply_migrations,
17 get_setting,
18 list_setting_keys,
19 remove_setting,
20 set_setting,
21};
22use miden_client::Word;
23use miden_client::account::{
24 Account,
25 AccountCode,
26 AccountHeader,
27 AccountId,
28 AccountStorage,
29 Address,
30 StorageSlotName,
31};
32use miden_client::asset::{Asset, AssetVault, AssetWitness};
33use miden_client::block::BlockHeader;
34use miden_client::crypto::{InOrderIndex, MmrPeaks};
35use miden_client::note::{BlockNumber, NoteScript, NoteTag, Nullifier};
36use miden_client::store::{
37 AccountRecord,
38 AccountStatus,
39 AccountStorageFilter,
40 BlockRelevance,
41 InputNoteRecord,
42 NoteFilter,
43 OutputNoteRecord,
44 PartialBlockchainFilter,
45 Store,
46 StoreError,
47 TransactionFilter,
48};
49use miden_client::sync::{NoteTagRecord, StateSyncUpdate};
50use miden_client::transaction::{TransactionRecord, TransactionStoreUpdate};
51use miden_protocol::account::StorageMapWitness;
52use miden_protocol::asset::AssetVaultKey;
53use rusqlite::Connection;
54use rusqlite::types::Value;
55use sql_error::SqlResultExt;
56
57use crate::smt_forest::AccountSmtForest;
58
59mod account;
60mod builder;
61mod chain_data;
62mod db_management;
63mod note;
64mod smt_forest;
65mod sql_error;
66mod sync;
67mod transaction;
68
69pub use builder::ClientBuilderSqliteExt;
70
71pub struct SqliteStore {
79 pub(crate) pool: Pool,
80 smt_forest: Arc<RwLock<AccountSmtForest>>,
81}
82
83impl SqliteStore {
84 pub async fn new(database_filepath: PathBuf) -> Result<Self, StoreError> {
89 let sqlite_pool_manager = SqlitePoolManager::new(database_filepath);
90 let pool = Pool::builder(sqlite_pool_manager)
91 .build()
92 .map_err(|e| StoreError::DatabaseError(e.to_string()))?;
93
94 let conn = pool.get().await.map_err(|e| StoreError::DatabaseError(e.to_string()))?;
95
96 conn.interact(apply_migrations)
97 .await
98 .map_err(|e| StoreError::DatabaseError(e.to_string()))?
99 .map_err(|e| StoreError::DatabaseError(e.to_string()))?;
100
101 let store = SqliteStore {
102 pool,
103 smt_forest: Arc::new(RwLock::new(AccountSmtForest::new())),
104 };
105
106 for id in store.get_account_ids().await? {
108 let vault = store.get_account_vault(id).await?;
109 let storage = store.get_account_storage(id, AccountStorageFilter::All).await?;
110 let header = store.get_account_header(id).await?;
111
112 let mut smt_forest = store.smt_forest.write().expect("smt write lock not poisoned");
113 if header.is_some() {
114 smt_forest.insert_and_register_account_state(id, &vault, &storage)?;
115 } else {
116 smt_forest.insert_account_state(&vault, &storage)?;
117 }
118 }
119
120 Ok(store)
121 }
122
123 async fn interact_with_connection<F, R>(&self, f: F) -> Result<R, StoreError>
130 where
131 F: FnOnce(&mut Connection) -> Result<R, StoreError> + Send + 'static,
132 R: Send + 'static,
133 {
134 self.pool
135 .get()
136 .await
137 .map_err(|err| StoreError::DatabaseError(err.to_string()))?
138 .interact(f)
139 .await
140 .map_err(|err| StoreError::DatabaseError(err.to_string()))?
141 }
142}
143
144#[async_trait::async_trait]
149impl Store for SqliteStore {
150 fn get_current_timestamp(&self) -> Option<u64> {
151 Some(current_timestamp_u64())
152 }
153
154 async fn get_note_tags(&self) -> Result<Vec<NoteTagRecord>, StoreError> {
155 self.interact_with_connection(SqliteStore::get_note_tags).await
156 }
157
158 async fn get_unique_note_tags(&self) -> Result<BTreeSet<NoteTag>, StoreError> {
159 self.interact_with_connection(SqliteStore::get_unique_note_tags).await
160 }
161
162 async fn add_note_tag(&self, tag: NoteTagRecord) -> Result<bool, StoreError> {
163 self.interact_with_connection(move |conn| SqliteStore::add_note_tag(conn, tag))
164 .await
165 }
166
167 async fn remove_note_tag(&self, tag: NoteTagRecord) -> Result<usize, StoreError> {
168 self.interact_with_connection(move |conn| SqliteStore::remove_note_tag(conn, tag))
169 .await
170 }
171
172 async fn get_sync_height(&self) -> Result<BlockNumber, StoreError> {
173 self.interact_with_connection(SqliteStore::get_sync_height).await
174 }
175
176 async fn apply_state_sync(&self, state_sync_update: StateSyncUpdate) -> Result<(), StoreError> {
177 let smt_forest = self.smt_forest.clone();
178 self.interact_with_connection(move |conn| {
179 SqliteStore::apply_state_sync(conn, &smt_forest, state_sync_update)
180 })
181 .await
182 }
183
184 async fn get_transactions(
185 &self,
186 transaction_filter: TransactionFilter,
187 ) -> Result<Vec<TransactionRecord>, StoreError> {
188 self.interact_with_connection(move |conn| {
189 SqliteStore::get_transactions(conn, &transaction_filter)
190 })
191 .await
192 }
193
194 async fn apply_transaction(&self, tx_update: TransactionStoreUpdate) -> Result<(), StoreError> {
195 let smt_forest = self.smt_forest.clone();
196 self.interact_with_connection(move |conn| {
197 SqliteStore::apply_transaction(conn, &smt_forest, &tx_update)
198 })
199 .await
200 }
201
202 async fn get_input_notes(
203 &self,
204 filter: NoteFilter,
205 ) -> Result<Vec<InputNoteRecord>, StoreError> {
206 self.interact_with_connection(move |conn| SqliteStore::get_input_notes(conn, &filter))
207 .await
208 }
209
210 async fn get_output_notes(
211 &self,
212 note_filter: NoteFilter,
213 ) -> Result<Vec<OutputNoteRecord>, StoreError> {
214 self.interact_with_connection(move |conn| SqliteStore::get_output_notes(conn, ¬e_filter))
215 .await
216 }
217
218 async fn upsert_input_notes(&self, notes: &[InputNoteRecord]) -> Result<(), StoreError> {
219 let notes = notes.to_vec();
220 self.interact_with_connection(move |conn| SqliteStore::upsert_input_notes(conn, ¬es))
221 .await
222 }
223
224 async fn get_note_script(&self, script_root: Word) -> Result<NoteScript, StoreError> {
225 self.interact_with_connection(move |conn| SqliteStore::get_note_script(conn, script_root))
226 .await
227 }
228
229 async fn upsert_note_scripts(&self, note_scripts: &[NoteScript]) -> Result<(), StoreError> {
230 let note_scripts = note_scripts.to_vec();
231 self.interact_with_connection(move |conn| {
232 SqliteStore::upsert_note_scripts(conn, ¬e_scripts)
233 })
234 .await
235 }
236
237 async fn insert_block_header(
238 &self,
239 block_header: &BlockHeader,
240 partial_blockchain_peaks: MmrPeaks,
241 has_client_notes: bool,
242 ) -> Result<(), StoreError> {
243 let block_header = block_header.clone();
244 self.interact_with_connection(move |conn| {
245 SqliteStore::insert_block_header(
246 conn,
247 &block_header,
248 &partial_blockchain_peaks,
249 has_client_notes,
250 )
251 })
252 .await
253 }
254
255 async fn prune_irrelevant_blocks(&self) -> Result<(), StoreError> {
256 self.interact_with_connection(SqliteStore::prune_irrelevant_blocks).await
257 }
258
259 async fn get_block_headers(
260 &self,
261 block_numbers: &BTreeSet<BlockNumber>,
262 ) -> Result<Vec<(BlockHeader, BlockRelevance)>, StoreError> {
263 let block_numbers = block_numbers.clone();
264 Ok(self
265 .interact_with_connection(move |conn| {
266 SqliteStore::get_block_headers(conn, &block_numbers)
267 })
268 .await?)
269 }
270
271 async fn get_tracked_block_headers(&self) -> Result<Vec<BlockHeader>, StoreError> {
272 self.interact_with_connection(SqliteStore::get_tracked_block_headers).await
273 }
274
275 async fn get_partial_blockchain_nodes(
276 &self,
277 filter: PartialBlockchainFilter,
278 ) -> Result<BTreeMap<InOrderIndex, Word>, StoreError> {
279 self.interact_with_connection(move |conn| {
280 SqliteStore::get_partial_blockchain_nodes(conn, &filter)
281 })
282 .await
283 }
284
285 async fn insert_partial_blockchain_nodes(
286 &self,
287 nodes: &[(InOrderIndex, Word)],
288 ) -> Result<(), StoreError> {
289 let nodes = nodes.to_vec();
290 self.interact_with_connection(move |conn| {
291 SqliteStore::insert_partial_blockchain_nodes(conn, &nodes)
292 })
293 .await
294 }
295
296 async fn get_partial_blockchain_peaks_by_block_num(
297 &self,
298 block_num: BlockNumber,
299 ) -> Result<MmrPeaks, StoreError> {
300 self.interact_with_connection(move |conn| {
301 SqliteStore::get_partial_blockchain_peaks_by_block_num(conn, block_num)
302 })
303 .await
304 }
305
306 async fn insert_account(
307 &self,
308 account: &Account,
309 initial_address: Address,
310 ) -> Result<(), StoreError> {
311 let cloned_account = account.clone();
312 let smt_forest = self.smt_forest.clone();
313
314 self.interact_with_connection(move |conn| {
315 SqliteStore::insert_account(conn, &smt_forest, &cloned_account, &initial_address)
316 })
317 .await
318 }
319
320 async fn update_account(&self, account: &Account) -> Result<(), StoreError> {
321 let cloned_account = account.clone();
322 let smt_forest = self.smt_forest.clone();
323
324 self.interact_with_connection(move |conn| {
325 SqliteStore::update_account(conn, &smt_forest, &cloned_account)
326 })
327 .await
328 }
329
330 async fn get_account_ids(&self) -> Result<Vec<AccountId>, StoreError> {
331 self.interact_with_connection(SqliteStore::get_account_ids).await
332 }
333
334 async fn get_account_headers(&self) -> Result<Vec<(AccountHeader, AccountStatus)>, StoreError> {
335 self.interact_with_connection(SqliteStore::get_account_headers).await
336 }
337
338 async fn get_account_header(
339 &self,
340 account_id: AccountId,
341 ) -> Result<Option<(AccountHeader, AccountStatus)>, StoreError> {
342 self.interact_with_connection(move |conn| SqliteStore::get_account_header(conn, account_id))
343 .await
344 }
345
346 async fn get_account_header_by_commitment(
347 &self,
348 account_commitment: Word,
349 ) -> Result<Option<AccountHeader>, StoreError> {
350 self.interact_with_connection(move |conn| {
351 SqliteStore::get_account_header_by_commitment(conn, account_commitment)
352 })
353 .await
354 }
355
356 async fn get_account(
357 &self,
358 account_id: AccountId,
359 ) -> Result<Option<AccountRecord>, StoreError> {
360 self.interact_with_connection(move |conn| SqliteStore::get_account(conn, account_id))
361 .await
362 }
363
364 async fn upsert_foreign_account_code(
365 &self,
366 account_id: AccountId,
367 code: AccountCode,
368 ) -> Result<(), StoreError> {
369 self.interact_with_connection(move |conn| {
370 SqliteStore::upsert_foreign_account_code(conn, account_id, &code)
371 })
372 .await
373 }
374
375 async fn get_foreign_account_code(
376 &self,
377 account_ids: Vec<AccountId>,
378 ) -> Result<BTreeMap<AccountId, AccountCode>, StoreError> {
379 self.interact_with_connection(move |conn| {
380 SqliteStore::get_foreign_account_code(conn, account_ids)
381 })
382 .await
383 }
384
385 async fn set_setting(&self, key: String, value: Vec<u8>) -> Result<(), StoreError> {
386 self.interact_with_connection(move |conn| {
387 set_setting(conn, &key, &value).into_store_error()
388 })
389 .await
390 }
391
392 async fn get_setting(&self, key: String) -> Result<Option<Vec<u8>>, StoreError> {
393 self.interact_with_connection(move |conn| get_setting(conn, &key)).await
394 }
395
396 async fn remove_setting(&self, key: String) -> Result<(), StoreError> {
397 self.interact_with_connection(move |conn| remove_setting(conn, &key)).await
398 }
399
400 async fn list_setting_keys(&self) -> Result<Vec<String>, StoreError> {
401 self.interact_with_connection(move |conn| list_setting_keys(conn)).await
402 }
403
404 async fn get_unspent_input_note_nullifiers(&self) -> Result<Vec<Nullifier>, StoreError> {
405 self.interact_with_connection(SqliteStore::get_unspent_input_note_nullifiers)
406 .await
407 }
408
409 async fn get_account_vault(&self, account_id: AccountId) -> Result<AssetVault, StoreError> {
410 self.interact_with_connection(move |conn| SqliteStore::get_account_vault(conn, account_id))
411 .await
412 }
413
414 async fn get_account_asset(
415 &self,
416 account_id: AccountId,
417 vault_key: AssetVaultKey,
418 ) -> Result<Option<(Asset, AssetWitness)>, StoreError> {
419 let smt_forest = self.smt_forest.clone();
420 self.interact_with_connection(move |conn| {
421 SqliteStore::get_account_asset(conn, &smt_forest, account_id, vault_key)
422 })
423 .await
424 }
425
426 async fn get_account_storage(
427 &self,
428 account_id: AccountId,
429 filter: AccountStorageFilter,
430 ) -> Result<AccountStorage, StoreError> {
431 self.interact_with_connection(move |conn| {
432 SqliteStore::get_account_storage(conn, account_id, &filter)
433 })
434 .await
435 }
436
437 async fn get_account_map_item(
438 &self,
439 account_id: AccountId,
440 slot_name: StorageSlotName,
441 key: Word,
442 ) -> Result<(Word, StorageMapWitness), StoreError> {
443 let smt_forest = self.smt_forest.clone();
444
445 self.interact_with_connection(move |conn| {
446 SqliteStore::get_account_map_item(conn, &smt_forest, account_id, slot_name, key)
447 })
448 .await
449 }
450
451 async fn get_addresses_by_account_id(
452 &self,
453 account_id: AccountId,
454 ) -> Result<Vec<Address>, StoreError> {
455 self.interact_with_connection(move |conn| {
456 SqliteStore::get_account_addresses(conn, account_id)
457 })
458 .await
459 }
460
461 async fn insert_address(
462 &self,
463 address: Address,
464 account_id: AccountId,
465 ) -> Result<(), StoreError> {
466 self.interact_with_connection(move |conn| {
467 let tx = conn.transaction().into_store_error()?;
468 SqliteStore::insert_address(&tx, &address, account_id)?;
469 tx.commit().into_store_error()
470 })
471 .await
472 }
473
474 async fn remove_address(
475 &self,
476 address: Address,
477 account_id: AccountId,
478 ) -> Result<(), StoreError> {
479 self.interact_with_connection(move |conn| {
480 SqliteStore::remove_address(conn, &address, account_id)
481 })
482 .await
483 }
484
485 async fn get_minimal_partial_account(
486 &self,
487 account_id: AccountId,
488 ) -> Result<Option<AccountRecord>, StoreError> {
489 let smt_forest = self.smt_forest.clone();
490
491 self.interact_with_connection(move |conn| {
492 SqliteStore::get_minimal_partial_account(conn, &smt_forest, account_id)
493 })
494 .await
495 }
496}
497
498pub(crate) fn current_timestamp_u64() -> u64 {
503 let now = chrono::Utc::now();
504 u64::try_from(now.timestamp()).expect("timestamp is always after epoch")
505}
506
507pub fn column_value_as_u64<I: rusqlite::RowIndex>(
512 row: &rusqlite::Row<'_>,
513 index: I,
514) -> rusqlite::Result<u64> {
515 let value: i64 = row.get(index)?;
516 #[allow(
517 clippy::cast_sign_loss,
518 reason = "We store u64 as i64 as sqlite only allows the latter."
519 )]
520 Ok(value as u64)
521}
522
523pub fn u64_to_value(v: u64) -> Value {
528 #[allow(
529 clippy::cast_possible_wrap,
530 reason = "We store u64 as i64 as sqlite only allows the latter."
531 )]
532 Value::Integer(v as i64)
533}
534
535#[cfg(test)]
539pub mod tests {
540 use std::boxed::Box;
541
542 use miden_client::store::Store;
543 use miden_client::testing::common::create_test_store_path;
544
545 use super::SqliteStore;
546
547 fn assert_send_sync<T: Send + Sync>() {}
548
549 #[test]
550 fn is_send_sync() {
551 assert_send_sync::<SqliteStore>();
552 assert_send_sync::<Box<dyn Store>>();
553 }
554
555 async fn dyn_trait_send_fut(store: Box<dyn Store>) {
557 let res = store.get_tracked_block_headers().await;
559 assert!(res.is_ok());
560 }
561
562 #[tokio::test]
563 async fn future_is_send() {
564 let client = SqliteStore::new(create_test_store_path()).await.unwrap();
565 let client: Box<SqliteStore> = client.into();
566 tokio::task::spawn(async move { dyn_trait_send_fut(client).await });
567 }
568
569 pub(crate) async fn create_test_store() -> SqliteStore {
570 SqliteStore::new(create_test_store_path()).await.unwrap()
571 }
572}