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