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