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