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
111 let mut smt_forest = store.smt_forest.write().expect("smt write lock not poisoned");
112 smt_forest.insert_account_state(&vault, &storage)?;
113 }
114
115 Ok(store)
116 }
117
118 async fn interact_with_connection<F, R>(&self, f: F) -> Result<R, StoreError>
125 where
126 F: FnOnce(&mut Connection) -> Result<R, StoreError> + Send + 'static,
127 R: Send + 'static,
128 {
129 self.pool
130 .get()
131 .await
132 .map_err(|err| StoreError::DatabaseError(err.to_string()))?
133 .interact(f)
134 .await
135 .map_err(|err| StoreError::DatabaseError(err.to_string()))?
136 }
137}
138
139#[async_trait::async_trait]
144impl Store for SqliteStore {
145 fn get_current_timestamp(&self) -> Option<u64> {
146 Some(current_timestamp_u64())
147 }
148
149 async fn get_note_tags(&self) -> Result<Vec<NoteTagRecord>, StoreError> {
150 self.interact_with_connection(SqliteStore::get_note_tags).await
151 }
152
153 async fn get_unique_note_tags(&self) -> Result<BTreeSet<NoteTag>, StoreError> {
154 self.interact_with_connection(SqliteStore::get_unique_note_tags).await
155 }
156
157 async fn add_note_tag(&self, tag: NoteTagRecord) -> Result<bool, StoreError> {
158 self.interact_with_connection(move |conn| SqliteStore::add_note_tag(conn, tag))
159 .await
160 }
161
162 async fn remove_note_tag(&self, tag: NoteTagRecord) -> Result<usize, StoreError> {
163 self.interact_with_connection(move |conn| SqliteStore::remove_note_tag(conn, tag))
164 .await
165 }
166
167 async fn get_sync_height(&self) -> Result<BlockNumber, StoreError> {
168 self.interact_with_connection(SqliteStore::get_sync_height).await
169 }
170
171 async fn apply_state_sync(&self, state_sync_update: StateSyncUpdate) -> Result<(), StoreError> {
172 let smt_forest = self.smt_forest.clone();
173 self.interact_with_connection(move |conn| {
174 SqliteStore::apply_state_sync(conn, &smt_forest, state_sync_update)
175 })
176 .await
177 }
178
179 async fn get_transactions(
180 &self,
181 transaction_filter: TransactionFilter,
182 ) -> Result<Vec<TransactionRecord>, StoreError> {
183 self.interact_with_connection(move |conn| {
184 SqliteStore::get_transactions(conn, &transaction_filter)
185 })
186 .await
187 }
188
189 async fn apply_transaction(&self, tx_update: TransactionStoreUpdate) -> Result<(), StoreError> {
190 let smt_forest = self.smt_forest.clone();
191 self.interact_with_connection(move |conn| {
192 SqliteStore::apply_transaction(conn, &smt_forest, &tx_update)
193 })
194 .await
195 }
196
197 async fn get_input_notes(
198 &self,
199 filter: NoteFilter,
200 ) -> Result<Vec<InputNoteRecord>, StoreError> {
201 self.interact_with_connection(move |conn| SqliteStore::get_input_notes(conn, &filter))
202 .await
203 }
204
205 async fn get_output_notes(
206 &self,
207 note_filter: NoteFilter,
208 ) -> Result<Vec<OutputNoteRecord>, StoreError> {
209 self.interact_with_connection(move |conn| SqliteStore::get_output_notes(conn, ¬e_filter))
210 .await
211 }
212
213 async fn upsert_input_notes(&self, notes: &[InputNoteRecord]) -> Result<(), StoreError> {
214 let notes = notes.to_vec();
215 self.interact_with_connection(move |conn| SqliteStore::upsert_input_notes(conn, ¬es))
216 .await
217 }
218
219 async fn get_note_script(&self, script_root: Word) -> Result<NoteScript, StoreError> {
220 self.interact_with_connection(move |conn| SqliteStore::get_note_script(conn, script_root))
221 .await
222 }
223
224 async fn upsert_note_scripts(&self, note_scripts: &[NoteScript]) -> Result<(), StoreError> {
225 let note_scripts = note_scripts.to_vec();
226 self.interact_with_connection(move |conn| {
227 SqliteStore::upsert_note_scripts(conn, ¬e_scripts)
228 })
229 .await
230 }
231
232 async fn insert_block_header(
233 &self,
234 block_header: &BlockHeader,
235 partial_blockchain_peaks: MmrPeaks,
236 has_client_notes: bool,
237 ) -> Result<(), StoreError> {
238 let block_header = block_header.clone();
239 self.interact_with_connection(move |conn| {
240 SqliteStore::insert_block_header(
241 conn,
242 &block_header,
243 &partial_blockchain_peaks,
244 has_client_notes,
245 )
246 })
247 .await
248 }
249
250 async fn prune_irrelevant_blocks(&self) -> Result<(), StoreError> {
251 self.interact_with_connection(SqliteStore::prune_irrelevant_blocks).await
252 }
253
254 async fn get_block_headers(
255 &self,
256 block_numbers: &BTreeSet<BlockNumber>,
257 ) -> Result<Vec<(BlockHeader, BlockRelevance)>, StoreError> {
258 let block_numbers = block_numbers.clone();
259 Ok(self
260 .interact_with_connection(move |conn| {
261 SqliteStore::get_block_headers(conn, &block_numbers)
262 })
263 .await?)
264 }
265
266 async fn get_tracked_block_headers(&self) -> Result<Vec<BlockHeader>, StoreError> {
267 self.interact_with_connection(SqliteStore::get_tracked_block_headers).await
268 }
269
270 async fn get_partial_blockchain_nodes(
271 &self,
272 filter: PartialBlockchainFilter,
273 ) -> Result<BTreeMap<InOrderIndex, Word>, StoreError> {
274 self.interact_with_connection(move |conn| {
275 SqliteStore::get_partial_blockchain_nodes(conn, &filter)
276 })
277 .await
278 }
279
280 async fn insert_partial_blockchain_nodes(
281 &self,
282 nodes: &[(InOrderIndex, Word)],
283 ) -> Result<(), StoreError> {
284 let nodes = nodes.to_vec();
285 self.interact_with_connection(move |conn| {
286 SqliteStore::insert_partial_blockchain_nodes(conn, &nodes)
287 })
288 .await
289 }
290
291 async fn get_partial_blockchain_peaks_by_block_num(
292 &self,
293 block_num: BlockNumber,
294 ) -> Result<MmrPeaks, StoreError> {
295 self.interact_with_connection(move |conn| {
296 SqliteStore::get_partial_blockchain_peaks_by_block_num(conn, block_num)
297 })
298 .await
299 }
300
301 async fn insert_account(
302 &self,
303 account: &Account,
304 initial_address: Address,
305 ) -> Result<(), StoreError> {
306 let cloned_account = account.clone();
307 let smt_forest = self.smt_forest.clone();
308
309 self.interact_with_connection(move |conn| {
310 SqliteStore::insert_account(conn, &smt_forest, &cloned_account, &initial_address)
311 })
312 .await
313 }
314
315 async fn update_account(&self, account: &Account) -> Result<(), StoreError> {
316 let cloned_account = account.clone();
317 let smt_forest = self.smt_forest.clone();
318
319 self.interact_with_connection(move |conn| {
320 SqliteStore::update_account(conn, &smt_forest, &cloned_account)
321 })
322 .await
323 }
324
325 async fn get_account_ids(&self) -> Result<Vec<AccountId>, StoreError> {
326 self.interact_with_connection(SqliteStore::get_account_ids).await
327 }
328
329 async fn get_account_headers(&self) -> Result<Vec<(AccountHeader, AccountStatus)>, StoreError> {
330 self.interact_with_connection(SqliteStore::get_account_headers).await
331 }
332
333 async fn get_account_header(
334 &self,
335 account_id: AccountId,
336 ) -> Result<Option<(AccountHeader, AccountStatus)>, StoreError> {
337 self.interact_with_connection(move |conn| SqliteStore::get_account_header(conn, account_id))
338 .await
339 }
340
341 async fn get_account_header_by_commitment(
342 &self,
343 account_commitment: Word,
344 ) -> Result<Option<AccountHeader>, StoreError> {
345 self.interact_with_connection(move |conn| {
346 SqliteStore::get_account_header_by_commitment(conn, account_commitment)
347 })
348 .await
349 }
350
351 async fn get_account(
352 &self,
353 account_id: AccountId,
354 ) -> Result<Option<AccountRecord>, StoreError> {
355 self.interact_with_connection(move |conn| SqliteStore::get_account(conn, account_id))
356 .await
357 }
358
359 async fn upsert_foreign_account_code(
360 &self,
361 account_id: AccountId,
362 code: AccountCode,
363 ) -> Result<(), StoreError> {
364 self.interact_with_connection(move |conn| {
365 SqliteStore::upsert_foreign_account_code(conn, account_id, &code)
366 })
367 .await
368 }
369
370 async fn get_foreign_account_code(
371 &self,
372 account_ids: Vec<AccountId>,
373 ) -> Result<BTreeMap<AccountId, AccountCode>, StoreError> {
374 self.interact_with_connection(move |conn| {
375 SqliteStore::get_foreign_account_code(conn, account_ids)
376 })
377 .await
378 }
379
380 async fn set_setting(&self, key: String, value: Vec<u8>) -> Result<(), StoreError> {
381 self.interact_with_connection(move |conn| {
382 set_setting(conn, &key, &value).into_store_error()
383 })
384 .await
385 }
386
387 async fn get_setting(&self, key: String) -> Result<Option<Vec<u8>>, StoreError> {
388 self.interact_with_connection(move |conn| get_setting(conn, &key)).await
389 }
390
391 async fn remove_setting(&self, key: String) -> Result<(), StoreError> {
392 self.interact_with_connection(move |conn| remove_setting(conn, &key)).await
393 }
394
395 async fn list_setting_keys(&self) -> Result<Vec<String>, StoreError> {
396 self.interact_with_connection(move |conn| list_setting_keys(conn)).await
397 }
398
399 async fn get_unspent_input_note_nullifiers(&self) -> Result<Vec<Nullifier>, StoreError> {
400 self.interact_with_connection(SqliteStore::get_unspent_input_note_nullifiers)
401 .await
402 }
403
404 async fn get_account_vault(&self, account_id: AccountId) -> Result<AssetVault, StoreError> {
405 self.interact_with_connection(move |conn| SqliteStore::get_account_vault(conn, account_id))
406 .await
407 }
408
409 async fn get_account_asset(
410 &self,
411 account_id: AccountId,
412 vault_key: AssetVaultKey,
413 ) -> Result<Option<(Asset, AssetWitness)>, StoreError> {
414 let smt_forest = self.smt_forest.clone();
415 self.interact_with_connection(move |conn| {
416 SqliteStore::get_account_asset(conn, &smt_forest, account_id, vault_key)
417 })
418 .await
419 }
420
421 async fn get_account_storage(
422 &self,
423 account_id: AccountId,
424 filter: AccountStorageFilter,
425 ) -> Result<AccountStorage, StoreError> {
426 self.interact_with_connection(move |conn| {
427 SqliteStore::get_account_storage(conn, account_id, &filter)
428 })
429 .await
430 }
431
432 async fn get_account_map_item(
433 &self,
434 account_id: AccountId,
435 slot_name: StorageSlotName,
436 key: Word,
437 ) -> Result<(Word, StorageMapWitness), StoreError> {
438 let smt_forest = self.smt_forest.clone();
439
440 self.interact_with_connection(move |conn| {
441 SqliteStore::get_account_map_item(conn, &smt_forest, account_id, slot_name, key)
442 })
443 .await
444 }
445
446 async fn get_addresses_by_account_id(
447 &self,
448 account_id: AccountId,
449 ) -> Result<Vec<Address>, StoreError> {
450 self.interact_with_connection(move |conn| {
451 SqliteStore::get_account_addresses(conn, account_id)
452 })
453 .await
454 }
455
456 async fn insert_address(
457 &self,
458 address: Address,
459 account_id: AccountId,
460 ) -> Result<(), StoreError> {
461 self.interact_with_connection(move |conn| {
462 let tx = conn.transaction().into_store_error()?;
463 SqliteStore::insert_address(&tx, &address, account_id)?;
464 tx.commit().into_store_error()
465 })
466 .await
467 }
468
469 async fn remove_address(
470 &self,
471 address: Address,
472 account_id: AccountId,
473 ) -> Result<(), StoreError> {
474 self.interact_with_connection(move |conn| {
475 SqliteStore::remove_address(conn, &address, account_id)
476 })
477 .await
478 }
479
480 async fn get_minimal_partial_account(
481 &self,
482 account_id: AccountId,
483 ) -> Result<Option<AccountRecord>, StoreError> {
484 let smt_forest = self.smt_forest.clone();
485
486 self.interact_with_connection(move |conn| {
487 SqliteStore::get_minimal_partial_account(conn, &smt_forest, account_id)
488 })
489 .await
490 }
491}
492
493pub(crate) fn current_timestamp_u64() -> u64 {
498 let now = chrono::Utc::now();
499 u64::try_from(now.timestamp()).expect("timestamp is always after epoch")
500}
501
502pub fn column_value_as_u64<I: rusqlite::RowIndex>(
507 row: &rusqlite::Row<'_>,
508 index: I,
509) -> rusqlite::Result<u64> {
510 let value: i64 = row.get(index)?;
511 #[allow(
512 clippy::cast_sign_loss,
513 reason = "We store u64 as i64 as sqlite only allows the latter."
514 )]
515 Ok(value as u64)
516}
517
518pub fn u64_to_value(v: u64) -> Value {
523 #[allow(
524 clippy::cast_possible_wrap,
525 reason = "We store u64 as i64 as sqlite only allows the latter."
526 )]
527 Value::Integer(v as i64)
528}
529
530#[cfg(test)]
534pub mod tests {
535 use std::boxed::Box;
536
537 use miden_client::store::Store;
538 use miden_client::testing::common::create_test_store_path;
539
540 use super::SqliteStore;
541
542 fn assert_send_sync<T: Send + Sync>() {}
543
544 #[test]
545 fn is_send_sync() {
546 assert_send_sync::<SqliteStore>();
547 assert_send_sync::<Box<dyn Store>>();
548 }
549
550 async fn dyn_trait_send_fut(store: Box<dyn Store>) {
552 let res = store.get_tracked_block_headers().await;
554 assert!(res.is_ok());
555 }
556
557 #[tokio::test]
558 async fn future_is_send() {
559 let client = SqliteStore::new(create_test_store_path()).await.unwrap();
560 let client: Box<SqliteStore> = client.into();
561 tokio::task::spawn(async move { dyn_trait_send_fut(client).await });
562 }
563
564 pub(crate) async fn create_test_store() -> SqliteStore {
565 SqliteStore::new(create_test_store_path()).await.unwrap()
566 }
567}