1#![allow(clippy::items_after_statements)]
2
3use std::collections::BTreeMap;
4use std::rc::Rc;
5use std::string::{String, ToString};
6use std::sync::{Arc, RwLock};
7use std::vec::Vec;
8
9use miden_client::account::{
10 Account,
11 AccountCode,
12 AccountDelta,
13 AccountHeader,
14 AccountId,
15 AccountIdPrefix,
16 AccountStorage,
17 Address,
18 StorageMap,
19 StorageSlot,
20 StorageSlotType,
21};
22use miden_client::asset::{Asset, AssetVault, AssetWitness, FungibleAsset, NonFungibleDeltaAction};
23use miden_client::crypto::{MerkleStore, SmtLeaf, SmtProof};
24use miden_client::store::{AccountRecord, AccountStatus, StoreError};
25use miden_client::sync::NoteTagRecord;
26use miden_client::utils::{Deserializable, Serializable};
27use miden_client::{AccountError, Felt, Word};
28use miden_objects::account::StorageMapWitness;
29use miden_objects::asset::AssetVaultKey;
30use rusqlite::types::Value;
31use rusqlite::{Connection, Params, Transaction, named_params, params};
32
33use super::{SqliteStore, column_value_as_u64, u64_to_value};
34use crate::merkle_store::{
35 get_asset_proof,
36 get_storage_map_item_proof,
37 insert_asset_nodes,
38 insert_storage_map_nodes,
39 update_asset_nodes,
40 update_storage_map_nodes,
41};
42use crate::sql_error::SqlResultExt;
43use crate::sync::{add_note_tag_tx, remove_note_tag_tx};
44use crate::{insert_sql, subst};
45
46struct SerializedHeaderData {
49 id: String,
50 nonce: u64,
51 vault_root: String,
52 storage_commitment: String,
53 code_commitment: String,
54 account_seed: Option<Vec<u8>>,
55 locked: bool,
56}
57
58impl SqliteStore {
59 pub(super) fn get_account_ids(conn: &mut Connection) -> Result<Vec<AccountId>, StoreError> {
63 const QUERY: &str = "SELECT id FROM tracked_accounts";
64
65 conn.prepare(QUERY)
66 .into_store_error()?
67 .query_map([], |row| row.get(0))
68 .expect("no binding parameters used in query")
69 .map(|result| {
70 let id: String = result.map_err(|e| StoreError::ParsingError(e.to_string()))?;
71 Ok(AccountId::from_hex(&id).expect("account id is valid"))
72 })
73 .collect::<Result<Vec<AccountId>, StoreError>>()
74 }
75
76 pub(super) fn get_account_headers(
77 conn: &mut Connection,
78 ) -> Result<Vec<(AccountHeader, AccountStatus)>, StoreError> {
79 const QUERY: &str = "
80 SELECT
81 a.id,
82 a.nonce,
83 a.vault_root,
84 a.storage_commitment,
85 a.code_commitment,
86 a.account_seed,
87 a.locked
88 FROM accounts AS a
89 JOIN (
90 SELECT id, MAX(nonce) AS nonce
91 FROM accounts
92 GROUP BY id
93 ) AS latest
94 ON a.id = latest.id
95 AND a.nonce = latest.nonce
96 ORDER BY a.id;
97 ";
98
99 conn.prepare(QUERY)
100 .into_store_error()?
101 .query_map(params![], |row| {
102 let id: String = row.get(0)?;
103 let nonce: u64 = column_value_as_u64(row, 1)?;
104 let vault_root: String = row.get(2)?;
105 let storage_commitment: String = row.get(3)?;
106 let code_commitment: String = row.get(4)?;
107 let account_seed: Option<Vec<u8>> = row.get(5)?;
108 let locked: bool = row.get(6)?;
109
110 Ok(SerializedHeaderData {
111 id,
112 nonce,
113 vault_root,
114 storage_commitment,
115 code_commitment,
116 account_seed,
117 locked,
118 })
119 })
120 .into_store_error()?
121 .map(|result| parse_accounts(result.into_store_error()?))
122 .collect::<Result<Vec<(AccountHeader, AccountStatus)>, StoreError>>()
123 }
124
125 pub(crate) fn get_account_header(
126 conn: &mut Connection,
127 account_id: AccountId,
128 ) -> Result<Option<(AccountHeader, AccountStatus)>, StoreError> {
129 Ok(query_account_headers(
130 conn,
131 "id = ? ORDER BY nonce DESC LIMIT 1",
132 params![account_id.to_hex()],
133 )?
134 .pop())
135 }
136
137 pub(crate) fn get_account_header_by_commitment(
138 conn: &mut Connection,
139 account_commitment: Word,
140 ) -> Result<Option<AccountHeader>, StoreError> {
141 let account_commitment_str: String = account_commitment.to_string();
142 Ok(
143 query_account_headers(conn, "account_commitment = ?", params![account_commitment_str])?
144 .pop()
145 .map(|(header, _)| header),
146 )
147 }
148
149 pub(crate) fn get_account(
150 conn: &mut Connection,
151 account_id: AccountId,
152 ) -> Result<Option<AccountRecord>, StoreError> {
153 let Some((header, status)) = Self::get_account_header(conn, account_id)? else {
154 return Ok(None);
155 };
156
157 let assets = query_vault_assets(conn, "root = ?", params![header.vault_root().to_hex()])?;
158 let vault = AssetVault::new(&assets)?;
159
160 let slots = query_storage_slots(
161 conn,
162 "commitment = ?",
163 params![header.storage_commitment().to_hex()],
164 )?
165 .into_values()
166 .collect();
167
168 let storage = AccountStorage::new(slots)?;
169
170 let Some(account_code) = query_account_code(conn, header.code_commitment())? else {
171 return Ok(None);
172 };
173
174 let account = Account::new_unchecked(
175 header.id(),
176 vault,
177 storage,
178 account_code,
179 header.nonce(),
180 status.seed().copied(),
181 );
182
183 let addresses = query_account_addresses(conn, header.id())?;
184 Ok(Some(AccountRecord::new(account, status, addresses)))
185 }
186
187 pub(crate) fn insert_account(
188 conn: &mut Connection,
189 merkle_store: &Arc<RwLock<MerkleStore>>,
190 account: &Account,
191 initial_address: &Address,
192 ) -> Result<(), StoreError> {
193 let tx = conn.transaction().into_store_error()?;
194
195 Self::insert_account_code(&tx, account.code())?;
196
197 Self::insert_storage_slots(
198 &tx,
199 account.storage().commitment(),
200 account.storage().slots().iter().enumerate(),
201 )?;
202
203 Self::insert_assets(&tx, account.vault().root(), account.vault().assets())?;
204 Self::insert_account_header(&tx, &account.into(), account.seed())?;
205
206 Self::insert_address(&tx, initial_address, account.id())?;
207
208 tx.commit().into_store_error()?;
209
210 let mut merkle_store = merkle_store.write().expect("merkle_store write lock not poisoned");
211 insert_storage_map_nodes(&mut merkle_store, account.storage());
212 insert_asset_nodes(&mut merkle_store, account.vault());
213
214 Ok(())
215 }
216
217 pub(crate) fn update_account(
218 conn: &mut Connection,
219 merkle_store: &Arc<RwLock<MerkleStore>>,
220 new_account_state: &Account,
221 ) -> Result<(), StoreError> {
222 const QUERY: &str = "SELECT id FROM accounts WHERE id = ?";
223 if conn
224 .prepare(QUERY)
225 .into_store_error()?
226 .query_map(params![new_account_state.id().to_hex()], |row| row.get(0))
227 .into_store_error()?
228 .map(|result| {
229 result.map_err(|err| StoreError::ParsingError(err.to_string())).and_then(
230 |id: String| {
231 AccountId::from_hex(&id).map_err(|err| {
232 StoreError::AccountError(
233 AccountError::FinalAccountHeaderIdParsingFailed(err),
234 )
235 })
236 },
237 )
238 })
239 .next()
240 .is_none()
241 {
242 return Err(StoreError::AccountDataNotFound(new_account_state.id()));
243 }
244
245 let mut merkle_store = merkle_store.write().expect("merkle_store write lock not poisoned");
246 let tx = conn.transaction().into_store_error()?;
247 Self::update_account_state(&tx, &mut merkle_store, new_account_state)?;
248 tx.commit().into_store_error()
249 }
250
251 pub fn upsert_foreign_account_code(
252 conn: &mut Connection,
253 account_id: AccountId,
254 code: &AccountCode,
255 ) -> Result<(), StoreError> {
256 let tx = conn.transaction().into_store_error()?;
257
258 Self::insert_account_code(&tx, code)?;
259
260 const QUERY: &str =
261 insert_sql!(foreign_account_code { account_id, code_commitment } | REPLACE);
262
263 tx.execute(QUERY, params![account_id.to_hex(), code.commitment().to_string()])
264 .into_store_error()?;
265
266 Self::insert_account_code(&tx, code)?;
267 tx.commit().into_store_error()
268 }
269
270 pub fn get_foreign_account_code(
271 conn: &mut Connection,
272 account_ids: Vec<AccountId>,
273 ) -> Result<BTreeMap<AccountId, AccountCode>, StoreError> {
274 let params: Vec<Value> =
275 account_ids.into_iter().map(|id| Value::from(id.to_hex())).collect();
276 const QUERY: &str = "
277 SELECT account_id, code
278 FROM foreign_account_code JOIN account_code ON foreign_account_code.code_commitment = account_code.commitment
279 WHERE account_id IN rarray(?)";
280
281 conn.prepare(QUERY)
282 .into_store_error()?
283 .query_map([Rc::new(params)], |row| Ok((row.get(0)?, row.get(1)?)))
284 .expect("no binding parameters used in query")
285 .map(|result| {
286 result.map_err(|err| StoreError::ParsingError(err.to_string())).and_then(
287 |(id, code): (String, Vec<u8>)| {
288 Ok((
289 AccountId::from_hex(&id).map_err(|err| {
290 StoreError::AccountError(
291 AccountError::FinalAccountHeaderIdParsingFailed(err),
292 )
293 })?,
294 AccountCode::from_bytes(&code).map_err(StoreError::AccountError)?,
295 ))
296 },
297 )
298 })
299 .collect::<Result<BTreeMap<AccountId, AccountCode>, _>>()
300 }
301
302 pub fn get_account_vault(
304 conn: &Connection,
305 account_id: AccountId,
306 ) -> Result<AssetVault, StoreError> {
307 let assets = query_vault_assets(
308 conn,
309 "root = (SELECT vault_root FROM accounts WHERE id = ? ORDER BY nonce DESC LIMIT 1)",
310 params![account_id.to_hex()],
311 )?;
312
313 Ok(AssetVault::new(&assets)?)
314 }
315
316 pub fn get_account_storage(
318 conn: &Connection,
319 account_id: AccountId,
320 ) -> Result<AccountStorage, StoreError> {
321 let slots = query_storage_slots(
322 conn,
323 "commitment = (SELECT storage_commitment FROM accounts WHERE id = ? ORDER BY nonce DESC LIMIT 1)",
324 params![account_id.to_hex()],
325 )?
326 .into_values()
327 .collect();
328
329 Ok(AccountStorage::new(slots)?)
330 }
331
332 pub(crate) fn get_account_asset(
335 conn: &mut Connection,
336 merkle_store: &Arc<RwLock<MerkleStore>>,
337 account_id: AccountId,
338 faucet_id_prefix: AccountIdPrefix,
339 ) -> Result<Option<(Asset, AssetWitness)>, StoreError> {
340 let header = Self::get_account_header(conn, account_id)?
341 .ok_or(StoreError::AccountDataNotFound(account_id))?
342 .0;
343
344 let Some(asset) = query_vault_assets(
345 conn,
346 "faucet_id_prefix = ? AND root = ?",
347 params![faucet_id_prefix.to_hex(), header.vault_root().to_hex()],
348 )?
349 .into_iter()
350 .next() else {
351 return Ok(None);
352 };
353
354 let merkle_store = merkle_store.read().expect("merkle_store read lock not poisoned");
355
356 let proof = get_asset_proof(&merkle_store, header.vault_root(), &asset)?;
357 let witness = AssetWitness::new(proof)?;
358
359 Ok(Some((asset, witness)))
360 }
361
362 pub(crate) fn get_account_map_item(
365 conn: &mut Connection,
366 merkle_store: &Arc<RwLock<MerkleStore>>,
367 account_id: AccountId,
368 index: u8,
369 key: Word,
370 ) -> Result<(Word, StorageMapWitness), StoreError> {
371 let header = Self::get_account_header(conn, account_id)?
372 .ok_or(StoreError::AccountDataNotFound(account_id))?
373 .0;
374
375 let StorageSlot::Map(map) = query_storage_slots(
376 conn,
377 "commitment = ? AND slot_index = ?",
378 params![header.storage_commitment().to_hex(), index],
379 )?
380 .remove(&index)
381 .ok_or(StoreError::AccountStorageNotFound(header.storage_commitment()))?
382 else {
383 return Err(StoreError::AccountError(AccountError::StorageSlotNotMap(index)));
384 };
385
386 let item = map.get(&key);
387 let merkle_store = merkle_store.read().expect("merkle_store read lock not poisoned");
388
389 let path = get_storage_map_item_proof(&merkle_store, map.root(), key)?.1.try_into()?;
391 let leaf = SmtLeaf::new_single(StorageMap::hash_key(key), item);
392 let proof = SmtProof::new(path, leaf)?;
393
394 let witness = StorageMapWitness::new(proof, [key])?;
395
396 Ok((item, witness))
397 }
398
399 pub(crate) fn get_account_addresses(
400 conn: &mut Connection,
401 account_id: AccountId,
402 ) -> Result<Vec<Address>, StoreError> {
403 query_account_addresses(conn, account_id)
404 }
405
406 pub(crate) fn insert_address(
407 tx: &Transaction<'_>,
408 address: &Address,
409 account_id: AccountId,
410 ) -> Result<(), StoreError> {
411 let derived_note_tag = address.to_note_tag();
412 let note_tag_record = NoteTagRecord::with_account_source(derived_note_tag, account_id);
413
414 add_note_tag_tx(tx, ¬e_tag_record)?;
415 Self::insert_address_internal(tx, address, account_id)?;
416
417 Ok(())
418 }
419
420 pub(crate) fn remove_address(
421 conn: &mut Connection,
422 address: &Address,
423 account_id: AccountId,
424 ) -> Result<(), StoreError> {
425 let derived_note_tag = address.to_note_tag();
426 let note_tag_record = NoteTagRecord::with_account_source(derived_note_tag, account_id);
427
428 let tx = conn.transaction().into_store_error()?;
429 remove_note_tag_tx(&tx, note_tag_record)?;
430 Self::remove_address_internal(&tx, address)?;
431
432 tx.commit().into_store_error()
433 }
434
435 pub(super) fn apply_account_delta(
446 tx: &Transaction<'_>,
447 merkle_store: &mut MerkleStore,
448 init_account_state: &AccountHeader,
449 final_account_state: &AccountHeader,
450 mut updated_fungible_assets: BTreeMap<AccountIdPrefix, FungibleAsset>,
451 mut updated_storage_maps: BTreeMap<u8, StorageMap>,
452 delta: &AccountDelta,
453 ) -> Result<(), StoreError> {
454 Self::copy_account_state(tx, init_account_state, final_account_state)?;
457
458 let mut updated_assets: BTreeMap<AssetVaultKey, Asset> = BTreeMap::new();
461 let mut removed_vault_keys: Vec<AssetVaultKey> = Vec::new();
462
463 for (faucet_id, delta) in delta.vault().fungible().iter() {
466 let delta_asset = FungibleAsset::new(*faucet_id, delta.unsigned_abs())?;
467
468 let asset = match updated_fungible_assets.remove(&faucet_id.prefix()) {
469 Some(asset) => {
470 if *delta >= 0 {
472 asset.add(delta_asset)?
473 } else {
474 asset.sub(delta_asset)?
475 }
476 },
477 None => {
478 delta_asset
480 },
481 };
482
483 if asset.amount() > 0 {
484 updated_assets.insert(asset.vault_key(), Asset::Fungible(asset));
485 } else {
486 removed_vault_keys.push(asset.vault_key());
487 }
488 }
489
490 let (added_nonfungible_assets, removed_nonfungible_assets) =
493 delta.vault().non_fungible().iter().partition::<Vec<_>, _>(|(_, action)| {
494 matches!(action, NonFungibleDeltaAction::Add)
495 });
496
497 updated_assets.extend(
498 added_nonfungible_assets
499 .into_iter()
500 .map(|(asset, _)| (asset.vault_key(), Asset::NonFungible(*asset))),
501 );
502
503 removed_vault_keys
504 .extend(removed_nonfungible_assets.iter().map(|(asset, _)| asset.vault_key()));
505
506 const DELETE_QUERY: &str =
507 "DELETE FROM account_assets WHERE root = ? AND vault_key IN rarray(?)";
508
509 tx.execute(
510 DELETE_QUERY,
511 params![
512 final_account_state.vault_root().to_hex(),
513 Rc::new(
514 removed_vault_keys
515 .into_iter()
516 .map(|k| {
517 let k_word: Word = k.into();
518 Value::from(k_word.to_hex())
519 })
520 .collect::<Vec<Value>>(),
521 ),
522 ],
523 )
524 .into_store_error()?;
525
526 update_asset_nodes(
527 merkle_store,
528 init_account_state.vault_root(),
529 updated_assets.values().copied(),
530 )?;
531 Self::insert_assets(tx, final_account_state.vault_root(), updated_assets.into_values())?;
532
533 let mut updated_storage_slots: BTreeMap<u8, StorageSlot> = delta
537 .storage()
538 .values()
539 .iter()
540 .map(|(index, slot)| (*index, StorageSlot::Value(*slot)))
541 .collect();
542
543 for (index, map_delta) in delta.storage().maps() {
546 let mut map = updated_storage_maps.remove(index).unwrap_or_default();
547
548 update_storage_map_nodes(
549 merkle_store,
550 map.root(),
551 map_delta.entries().iter().map(|(key, value)| ((*key).into(), *value)),
552 )?;
553
554 for (key, value) in map_delta.entries() {
555 map.insert((*key).into(), *value)?;
556 }
557
558 updated_storage_slots.insert(*index, StorageSlot::Map(map));
559 }
560
561 Self::insert_storage_slots(
562 tx,
563 final_account_state.storage_commitment(),
564 updated_storage_slots.iter().map(|(index, slot)| (*index as usize, slot)),
565 )?;
566
567 Ok(())
568 }
569
570 pub(super) fn get_account_fungible_assets_for_delta(
573 conn: &Connection,
574 header: &AccountHeader,
575 delta: &AccountDelta,
576 ) -> Result<BTreeMap<AccountIdPrefix, FungibleAsset>, StoreError> {
577 let fungible_faucet_prefixes = delta
578 .vault()
579 .fungible()
580 .iter()
581 .map(|(faucet_id, _)| Value::Text(faucet_id.prefix().to_hex()))
582 .collect::<Vec<Value>>();
583
584 Ok(query_vault_assets(
585 conn,
586 "root = ? AND faucet_id_prefix IN rarray(?)",
587 params![header.vault_root().to_hex(), Rc::new(fungible_faucet_prefixes)]
588 )?
589 .into_iter()
590 .map(|asset| (asset.faucet_id_prefix(), asset.unwrap_fungible()))
592 .collect())
593 }
594
595 pub(super) fn get_account_storage_maps_for_delta(
598 conn: &Connection,
599 header: &AccountHeader,
600 delta: &AccountDelta,
601 ) -> Result<BTreeMap<u8, StorageMap>, StoreError> {
602 let updated_map_indexes = delta
603 .storage()
604 .maps()
605 .keys()
606 .map(|k| Value::Integer(i64::from(*k)))
607 .collect::<Vec<Value>>();
608
609 query_storage_slots(
610 conn,
611 "commitment = ? AND slot_index IN rarray(?)",
612 params![header.storage_commitment().to_hex(), Rc::new(updated_map_indexes)],
613 )?
614 .into_iter()
615 .map(|(index, slot)| {
616 let StorageSlot::Map(map) = slot else {
617 return Err(StoreError::AccountError(AccountError::StorageSlotNotMap(index)));
618 };
619
620 Ok((index, map))
621 })
622 .collect()
623 }
624
625 fn copy_account_state(
629 tx: &Transaction<'_>,
630 init_account_header: &AccountHeader,
631 final_account_header: &AccountHeader,
632 ) -> Result<(), StoreError> {
633 Self::insert_account_header(tx, final_account_header, None)?;
634
635 if init_account_header.vault_root() != final_account_header.vault_root() {
636 const VAULT_QUERY: &str = "
637 INSERT OR IGNORE INTO account_assets (
638 root,
639 vault_key,
640 faucet_id_prefix,
641 asset
642 )
643 SELECT
644 ?, --new root
645 vault_key,
646 faucet_id_prefix,
647 asset
648 FROM account_assets
649 WHERE root = (SELECT vault_root FROM accounts WHERE account_commitment = ?)
650 ";
651 tx.execute(
652 VAULT_QUERY,
653 params![
654 final_account_header.vault_root().to_hex(),
655 init_account_header.commitment().to_hex()
656 ],
657 )
658 .into_store_error()?;
659 }
660
661 if init_account_header.storage_commitment() != final_account_header.storage_commitment() {
662 const STORAGE_QUERY: &str = "
663 INSERT OR IGNORE INTO account_storage (
664 commitment,
665 slot_index,
666 slot_value,
667 slot_type
668 )
669 SELECT
670 ?, -- new commitment
671 slot_index,
672 slot_value,
673 slot_type
674 FROM account_storage
675 WHERE commitment = (SELECT storage_commitment FROM accounts WHERE account_commitment = ?)
676 ";
677
678 tx.execute(
679 STORAGE_QUERY,
680 params![
681 final_account_header.storage_commitment().to_hex(),
682 init_account_header.commitment().to_hex()
683 ],
684 )
685 .into_store_error()?;
686 }
687
688 Ok(())
689 }
690
691 pub(super) fn update_account_state(
702 tx: &Transaction<'_>,
703 merkle_store: &mut MerkleStore,
704 new_account_state: &Account,
705 ) -> Result<(), StoreError> {
706 insert_storage_map_nodes(merkle_store, new_account_state.storage());
707 Self::insert_storage_slots(
708 tx,
709 new_account_state.storage().commitment(),
710 new_account_state.storage().slots().iter().enumerate(),
711 )?;
712 insert_asset_nodes(merkle_store, new_account_state.vault());
713 Self::insert_assets(
714 tx,
715 new_account_state.vault().root(),
716 new_account_state.vault().assets(),
717 )?;
718 Self::insert_account_header(tx, &new_account_state.into(), None)
719 }
720
721 pub(super) fn lock_account_on_unexpected_commitment(
724 tx: &Transaction<'_>,
725 account_id: &AccountId,
726 mismatched_digest: &Word,
727 ) -> Result<(), StoreError> {
728 const QUERY: &str = "UPDATE accounts SET locked = true WHERE id = :account_id AND NOT EXISTS (SELECT 1 FROM accounts WHERE id = :account_id AND account_commitment = :digest)";
732 tx.execute(
733 QUERY,
734 named_params! {
735 ":account_id": account_id.to_hex(),
736 ":digest": mismatched_digest.to_string()
737 },
738 )
739 .into_store_error()?;
740 Ok(())
741 }
742
743 pub(super) fn undo_account_state(
751 tx: &Transaction<'_>,
752 account_hashes: &[Word],
753 ) -> Result<(), StoreError> {
754 const QUERY: &str = "DELETE FROM accounts WHERE account_commitment IN rarray(?)";
755
756 let params = account_hashes.iter().map(|h| Value::from(h.to_hex())).collect::<Vec<_>>();
757 tx.execute(QUERY, params![Rc::new(params)]).into_store_error()?;
758
759 Ok(())
760 }
761
762 fn insert_account_header(
764 tx: &Transaction<'_>,
765 account: &AccountHeader,
766 account_seed: Option<Word>,
767 ) -> Result<(), StoreError> {
768 let id: String = account.id().to_hex();
769 let code_commitment = account.code_commitment().to_string();
770 let storage_commitment = account.storage_commitment().to_string();
771 let vault_root = account.vault_root().to_string();
772 let nonce = u64_to_value(account.nonce().as_int());
773 let commitment = account.commitment().to_string();
774
775 let account_seed = account_seed.map(|seed| seed.to_bytes());
776
777 const QUERY: &str = insert_sql!(
778 accounts {
779 id,
780 code_commitment,
781 storage_commitment,
782 vault_root,
783 nonce,
784 account_seed,
785 account_commitment,
786 locked
787 } | REPLACE
788 );
789
790 tx.execute(
791 QUERY,
792 params![
793 id,
794 code_commitment,
795 storage_commitment,
796 vault_root,
797 nonce,
798 account_seed,
799 commitment,
800 false,
801 ],
802 )
803 .into_store_error()?;
804
805 Self::insert_tracked_account_id_tx(tx, account.id())?;
806 Ok(())
807 }
808
809 fn insert_account_code(
811 tx: &Transaction<'_>,
812 account_code: &AccountCode,
813 ) -> Result<(), StoreError> {
814 const QUERY: &str = insert_sql!(account_code { commitment, code } | IGNORE);
815 tx.execute(QUERY, params![account_code.commitment().to_hex(), account_code.to_bytes()])
816 .into_store_error()?;
817 Ok(())
818 }
819
820 fn insert_tracked_account_id_tx(
821 tx: &Transaction<'_>,
822 account_id: AccountId,
823 ) -> Result<(), StoreError> {
824 const QUERY: &str = insert_sql!(tracked_accounts { id } | IGNORE);
825 tx.execute(QUERY, params![account_id.to_hex()]).into_store_error()?;
826 Ok(())
827 }
828
829 fn insert_storage_slots<'a>(
830 tx: &Transaction<'_>,
831 commitment: Word,
832 account_storage: impl Iterator<Item = (usize, &'a StorageSlot)>,
833 ) -> Result<(), StoreError> {
834 for (index, slot) in account_storage {
835 const QUERY: &str = insert_sql!(
836 account_storage {
837 commitment,
838 slot_index,
839 slot_value,
840 slot_type
841 } | REPLACE
842 );
843
844 tx.execute(
845 QUERY,
846 params![
847 commitment.to_hex(),
848 index,
849 slot.value().to_hex(),
850 slot.slot_type().to_bytes()
851 ],
852 )
853 .into_store_error()?;
854
855 if let StorageSlot::Map(map) = slot {
856 const MAP_QUERY: &str =
857 insert_sql!(storage_map_entries { root, key, value } | REPLACE);
858 for (key, value) in map.entries() {
859 tx.execute(
861 MAP_QUERY,
862 params![map.root().to_hex(), key.to_hex(), value.to_hex()],
863 )
864 .into_store_error()?;
865 }
866 }
867 }
868
869 Ok(())
870 }
871
872 fn insert_assets(
873 tx: &Transaction<'_>,
874 root: Word,
875 assets: impl Iterator<Item = Asset>,
876 ) -> Result<(), StoreError> {
877 for asset in assets {
878 let vault_key_word: Word = asset.vault_key().into();
879 const QUERY: &str =
880 insert_sql!(account_assets { root, vault_key, faucet_id_prefix, asset } | REPLACE);
881 tx.execute(
882 QUERY,
883 params![
884 root.to_hex(),
885 vault_key_word.to_hex(),
886 asset.faucet_id_prefix().to_hex(),
887 Word::from(asset).to_hex(),
888 ],
889 )
890 .into_store_error()?;
891 }
892
893 Ok(())
894 }
895
896 fn insert_address_internal(
897 tx: &Transaction<'_>,
898 address: &Address,
899 account_id: AccountId,
900 ) -> Result<(), StoreError> {
901 const QUERY: &str = insert_sql!(addresses { address, account_id } | REPLACE);
902 let serialized_address = address.to_bytes();
903 tx.execute(QUERY, params![serialized_address, account_id.to_hex(),])
904 .into_store_error()?;
905
906 Ok(())
907 }
908
909 fn remove_address_internal(tx: &Transaction<'_>, address: &Address) -> Result<(), StoreError> {
910 let serialized_address = address.to_bytes();
911
912 const DELETE_QUERY: &str = "DELETE FROM addresses WHERE address = ?";
913 tx.execute(DELETE_QUERY, params![serialized_address]).into_store_error()?;
914
915 Ok(())
916 }
917}
918
919fn parse_accounts(
924 serialized_account_parts: SerializedHeaderData,
925) -> Result<(AccountHeader, AccountStatus), StoreError> {
926 let SerializedHeaderData {
927 id,
928 nonce,
929 vault_root,
930 storage_commitment,
931 code_commitment,
932 account_seed,
933 locked,
934 } = serialized_account_parts;
935 let account_seed = account_seed.map(|seed| Word::read_from_bytes(&seed)).transpose()?;
936
937 let status = match (account_seed, locked) {
938 (_, true) => AccountStatus::Locked,
939 (Some(seed), _) => AccountStatus::New { seed },
940 _ => AccountStatus::Tracked,
941 };
942
943 Ok((
944 AccountHeader::new(
945 AccountId::from_hex(&id).expect("Conversion from stored AccountID should not panic"),
946 Felt::new(nonce),
947 Word::try_from(&vault_root)?,
948 Word::try_from(&storage_commitment)?,
949 Word::try_from(&code_commitment)?,
950 ),
951 status,
952 ))
953}
954
955fn query_storage_slots(
956 conn: &Connection,
957 where_clause: &str,
958 params: impl Params,
959) -> Result<BTreeMap<u8, StorageSlot>, StoreError> {
960 const STORAGE_QUERY: &str = "SELECT slot_index, slot_value, slot_type FROM account_storage";
961
962 let query = format!("{STORAGE_QUERY} WHERE {where_clause}");
963 let storage_values = conn
964 .prepare(&query)
965 .into_store_error()?
966 .query_map(params, |row| {
967 let index: u8 = row.get(0)?;
968 let value: String = row.get(1)?;
969 let slot_type: Vec<u8> = row.get(2)?;
970 Ok((index, value, slot_type))
971 })
972 .into_store_error()?
973 .map(|result| {
974 let (index, value, slot_type) = result.into_store_error()?;
975 Ok((index, Word::try_from(value)?, StorageSlotType::read_from_bytes(&slot_type)?))
976 })
977 .collect::<Result<Vec<(u8, Word, StorageSlotType)>, StoreError>>()?;
978
979 let possible_roots: Vec<Value> =
980 storage_values.iter().map(|(_, value, _)| Value::from(value.to_hex())).collect();
981
982 let mut storage_maps =
983 query_storage_maps(conn, "root IN rarray(?)", [Rc::new(possible_roots)])?;
984
985 Ok(storage_values
986 .into_iter()
987 .map(|(index, value, slot_type)| {
988 let slot = match slot_type {
989 StorageSlotType::Value => StorageSlot::Value(value),
990 StorageSlotType::Map => {
991 StorageSlot::Map(storage_maps.remove(&value).unwrap_or_default())
992 },
993 };
994 (index, slot)
995 })
996 .collect())
997}
998
999fn query_storage_maps(
1000 conn: &Connection,
1001 where_clause: &str,
1002 params: impl Params,
1003) -> Result<BTreeMap<Word, StorageMap>, StoreError> {
1004 const STORAGE_MAP_SELECT: &str = "SELECT root, key, value FROM storage_map_entries";
1005 let query = format!("{STORAGE_MAP_SELECT} WHERE {where_clause}");
1006
1007 let map_entries = conn
1008 .prepare(&query)
1009 .into_store_error()?
1010 .query_map(params, |row| {
1011 let root: String = row.get(0)?;
1012 let key: String = row.get(1)?;
1013 let value: String = row.get(2)?;
1014
1015 Ok((root, key, value))
1016 })
1017 .into_store_error()?
1018 .map(|result| {
1019 let (root, key, value) = result.into_store_error()?;
1020 Ok((Word::try_from(root)?, Word::try_from(key)?, Word::try_from(value)?))
1021 })
1022 .collect::<Result<Vec<(Word, Word, Word)>, StoreError>>()?;
1023
1024 let mut maps = BTreeMap::new();
1025 for (root, key, value) in map_entries {
1026 let map = maps.entry(root).or_insert_with(StorageMap::new);
1027 map.insert(key, value)?;
1028 }
1029
1030 Ok(maps)
1031}
1032
1033fn query_vault_assets(
1034 conn: &Connection,
1035 where_clause: &str,
1036 params: impl Params,
1037) -> Result<Vec<Asset>, StoreError> {
1038 const VAULT_QUERY: &str = "SELECT asset FROM account_assets";
1039
1040 let query = format!("{VAULT_QUERY} WHERE {where_clause}");
1041 conn.prepare(&query)
1042 .into_store_error()?
1043 .query_map(params, |row| {
1044 let asset: String = row.get(0)?;
1045 Ok(asset)
1046 })
1047 .into_store_error()?
1048 .map(|result| {
1049 let asset_str: String = result.into_store_error()?;
1050 let word = Word::try_from(asset_str)?;
1051 Ok(Asset::try_from(word)?)
1052 })
1053 .collect::<Result<Vec<Asset>, StoreError>>()
1054}
1055
1056fn query_account_code(
1057 conn: &Connection,
1058 commitment: Word,
1059) -> Result<Option<AccountCode>, StoreError> {
1060 const CODE_QUERY: &str = "SELECT code FROM account_code WHERE commitment = ?";
1063
1064 conn.prepare(CODE_QUERY)
1065 .into_store_error()?
1066 .query_map(params![commitment.to_hex()], |row| {
1067 let code: Vec<u8> = row.get(0)?;
1068 Ok(code)
1069 })
1070 .into_store_error()?
1071 .map(|result| {
1072 let bytes: Vec<u8> = result.into_store_error()?;
1073 Ok(AccountCode::from_bytes(&bytes)?)
1074 })
1075 .next()
1076 .transpose()
1077}
1078
1079fn query_account_headers(
1080 conn: &Connection,
1081 where_clause: &str,
1082 params: impl Params,
1083) -> Result<Vec<(AccountHeader, AccountStatus)>, StoreError> {
1084 const SELECT_QUERY: &str = "SELECT id, nonce, vault_root, storage_commitment, code_commitment, account_seed, locked \
1085 FROM accounts";
1086 let query = format!("{SELECT_QUERY} WHERE {where_clause}");
1087 conn.prepare(&query)
1088 .into_store_error()?
1089 .query_map(params, |row| {
1090 let id: String = row.get(0)?;
1091 let nonce: u64 = column_value_as_u64(row, 1)?;
1092 let vault_root: String = row.get(2)?;
1093 let storage_commitment: String = row.get(3)?;
1094 let code_commitment: String = row.get(4)?;
1095 let account_seed: Option<Vec<u8>> = row.get(5)?;
1096 let locked: bool = row.get(6)?;
1097
1098 Ok(SerializedHeaderData {
1099 id,
1100 nonce,
1101 vault_root,
1102 storage_commitment,
1103 code_commitment,
1104 account_seed,
1105 locked,
1106 })
1107 })
1108 .into_store_error()?
1109 .map(|result| parse_accounts(result.into_store_error()?))
1110 .collect::<Result<Vec<(AccountHeader, AccountStatus)>, StoreError>>()
1111}
1112
1113fn query_account_addresses(
1114 conn: &Connection,
1115 account_id: AccountId,
1116) -> Result<Vec<Address>, StoreError> {
1117 const ADDRESS_QUERY: &str = "SELECT address FROM addresses";
1118
1119 let query = format!("{ADDRESS_QUERY} WHERE ACCOUNT_ID = '{}'", account_id.to_hex());
1120 conn.prepare(&query)
1121 .into_store_error()?
1122 .query_map([], |row| {
1123 let address: Vec<u8> = row.get(0)?;
1124 Ok(address)
1125 })
1126 .into_store_error()?
1127 .map(|result| {
1128 let serialized_address = result.into_store_error()?;
1129 let address = Address::read_from_bytes(&serialized_address)?;
1130 Ok(address)
1131 })
1132 .collect::<Result<Vec<Address>, StoreError>>()
1133}
1134
1135#[cfg(test)]
1136mod tests {
1137 use std::collections::BTreeMap;
1138 use std::vec::Vec;
1139
1140 use anyhow::Context;
1141 use miden_client::account::component::AccountComponent;
1142 use miden_client::account::{
1143 Account,
1144 AccountBuilder,
1145 AccountCode,
1146 AccountDelta,
1147 AccountHeader,
1148 AccountId,
1149 AccountType,
1150 Address,
1151 StorageMap,
1152 StorageSlot,
1153 };
1154 use miden_client::asset::{
1155 AccountStorageDelta,
1156 AccountVaultDelta,
1157 Asset,
1158 FungibleAsset,
1159 NonFungibleAsset,
1160 NonFungibleAssetDetails,
1161 };
1162 use miden_client::store::Store;
1163 use miden_client::testing::account_id::{
1164 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
1165 ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET,
1166 };
1167 use miden_client::testing::constants::NON_FUNGIBLE_ASSET_DATA;
1168 use miden_client::transaction::TransactionKernel;
1169 use miden_client::{EMPTY_WORD, ONE, ZERO};
1170 use miden_lib::account::auth::AuthRpoFalcon512;
1171 use miden_lib::account::components::basic_wallet_library;
1172 use miden_objects::account::auth::PublicKeyCommitment;
1173
1174 use crate::SqliteStore;
1175 use crate::sql_error::SqlResultExt;
1176 use crate::tests::create_test_store;
1177
1178 #[tokio::test]
1179 async fn account_code_insertion_no_duplicates() -> anyhow::Result<()> {
1180 let store = create_test_store().await;
1181 let assembler = TransactionKernel::assembler();
1182 let account_component = AccountComponent::compile(
1183 "
1184 export.::miden::contracts::wallets::basic::receive_asset
1185 export.::miden::contracts::wallets::basic::move_asset_to_note
1186 ",
1187 assembler,
1188 vec![],
1189 )?
1190 .with_supports_all_types();
1191 let account_code = AccountCode::from_components(
1192 &[
1193 AuthRpoFalcon512::new(PublicKeyCommitment::from(EMPTY_WORD)).into(),
1194 account_component,
1195 ],
1196 AccountType::RegularAccountUpdatableCode,
1197 )?;
1198
1199 store
1200 .interact_with_connection(move |conn| {
1201 let tx = conn.transaction().into_store_error()?;
1202
1203 let mut actual: usize = tx
1205 .query_row("SELECT Count(*) FROM account_code", [], |row| row.get(0))
1206 .into_store_error()?;
1207 assert_eq!(actual, 0);
1208
1209 SqliteStore::insert_account_code(&tx, &account_code)?;
1211 actual = tx
1212 .query_row("SELECT Count(*) FROM account_code", [], |row| row.get(0))
1213 .into_store_error()?;
1214 assert_eq!(actual, 1);
1215
1216 assert!(SqliteStore::insert_account_code(&tx, &account_code).is_ok());
1218 actual = tx
1219 .query_row("SELECT Count(*) FROM account_code", [], |row| row.get(0))
1220 .into_store_error()?;
1221 assert_eq!(actual, 1);
1222
1223 Ok(())
1224 })
1225 .await?;
1226
1227 Ok(())
1228 }
1229
1230 #[tokio::test]
1231 async fn apply_account_delta_additions() -> anyhow::Result<()> {
1232 let store = create_test_store().await;
1233
1234 let dummy_component = AccountComponent::new(
1235 basic_wallet_library(),
1236 vec![StorageSlot::empty_value(), StorageSlot::empty_map()],
1237 )?
1238 .with_supports_all_types();
1239
1240 let account = AccountBuilder::new([0; 32])
1242 .account_type(AccountType::RegularAccountImmutableCode)
1243 .with_auth_component(AuthRpoFalcon512::new(PublicKeyCommitment::from(EMPTY_WORD)))
1244 .with_component(dummy_component)
1245 .build()?;
1246
1247 let default_address = Address::new(account.id());
1248 store.insert_account(&account, default_address).await?;
1249
1250 let mut storage_delta = AccountStorageDelta::new();
1251 storage_delta.set_item(1, [ZERO, ZERO, ZERO, ONE].into());
1252 storage_delta.set_map_item(2, [ONE, ZERO, ZERO, ZERO].into(), [ONE, ONE, ONE, ONE].into());
1253
1254 let vault_delta = AccountVaultDelta::from_iters(
1255 vec![
1256 FungibleAsset::new(AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET)?, 100)?
1257 .into(),
1258 NonFungibleAsset::new(&NonFungibleAssetDetails::new(
1259 AccountId::try_from(ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET)?.prefix(),
1260 NON_FUNGIBLE_ASSET_DATA.into(),
1261 )?)?
1262 .into(),
1263 ],
1264 [],
1265 );
1266
1267 let delta = AccountDelta::new(account.id(), storage_delta, vault_delta, ONE)?;
1268
1269 let mut account_after_delta = account.clone();
1270 account_after_delta.apply_delta(&delta)?;
1271
1272 let account_id = account.id();
1273 let final_state: AccountHeader = (&account_after_delta).into();
1274 let merkle_store = store.merkle_store.clone();
1275 store
1276 .interact_with_connection(move |conn| {
1277 let tx = conn.transaction().into_store_error()?;
1278 let mut merkle_store =
1279 merkle_store.write().expect("merkle_store write lock not poisoned");
1280
1281 SqliteStore::apply_account_delta(
1282 &tx,
1283 &mut merkle_store,
1284 &account.into(),
1285 &final_state,
1286 BTreeMap::default(),
1287 BTreeMap::default(),
1288 &delta,
1289 )?;
1290
1291 tx.commit().into_store_error()?;
1292 Ok(())
1293 })
1294 .await?;
1295
1296 let updated_account: Account = store
1297 .get_account(account_id)
1298 .await?
1299 .context("failed to find inserted account")?
1300 .into();
1301
1302 assert_eq!(updated_account, account_after_delta);
1303
1304 Ok(())
1305 }
1306
1307 #[tokio::test]
1308 async fn apply_account_delta_removals() -> anyhow::Result<()> {
1309 let store = create_test_store().await;
1310
1311 let mut dummy_map = StorageMap::new();
1312 dummy_map.insert([ONE, ZERO, ZERO, ZERO].into(), [ONE, ONE, ONE, ONE].into())?;
1313
1314 let dummy_component = AccountComponent::new(
1315 basic_wallet_library(),
1316 vec![StorageSlot::Value([ZERO, ZERO, ZERO, ONE].into()), StorageSlot::Map(dummy_map)],
1317 )?
1318 .with_supports_all_types();
1319
1320 let assets: Vec<Asset> = vec![
1322 FungibleAsset::new(AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET)?, 100)?
1323 .into(),
1324 NonFungibleAsset::new(&NonFungibleAssetDetails::new(
1325 AccountId::try_from(ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET)?.prefix(),
1326 NON_FUNGIBLE_ASSET_DATA.into(),
1327 )?)?
1328 .into(),
1329 ];
1330 let account = AccountBuilder::new([0; 32])
1331 .account_type(AccountType::RegularAccountImmutableCode)
1332 .with_auth_component(AuthRpoFalcon512::new(PublicKeyCommitment::from(EMPTY_WORD)))
1333 .with_component(dummy_component)
1334 .with_assets(assets.clone())
1335 .build_existing()?;
1336 let default_address = Address::new(account.id());
1337 store.insert_account(&account, default_address).await?;
1338
1339 let mut storage_delta = AccountStorageDelta::new();
1340 storage_delta.set_item(1, EMPTY_WORD);
1341 storage_delta.set_map_item(2, [ONE, ZERO, ZERO, ZERO].into(), EMPTY_WORD);
1342
1343 let vault_delta = AccountVaultDelta::from_iters([], assets.clone());
1344
1345 let delta = AccountDelta::new(account.id(), storage_delta, vault_delta, ONE)?;
1346
1347 let mut account_after_delta = account.clone();
1348 account_after_delta.apply_delta(&delta)?;
1349
1350 let account_id = account.id();
1351 let final_state: AccountHeader = (&account_after_delta).into();
1352
1353 let merkle_store = store.merkle_store.clone();
1354 store
1355 .interact_with_connection(move |conn| {
1356 let fungible_assets = SqliteStore::get_account_fungible_assets_for_delta(
1357 conn,
1358 &(&account).into(),
1359 &delta,
1360 )?;
1361 let storage_maps = SqliteStore::get_account_storage_maps_for_delta(
1362 conn,
1363 &(&account).into(),
1364 &delta,
1365 )?;
1366 let tx = conn.transaction().into_store_error()?;
1367 let mut merkle_store =
1368 merkle_store.write().expect("merkle_store write lock not poisoned");
1369
1370 SqliteStore::apply_account_delta(
1371 &tx,
1372 &mut merkle_store,
1373 &account.into(),
1374 &final_state,
1375 fungible_assets,
1376 storage_maps,
1377 &delta,
1378 )?;
1379
1380 tx.commit().into_store_error()?;
1381 Ok(())
1382 })
1383 .await?;
1384
1385 let updated_account: Account = store
1386 .get_account(account_id)
1387 .await?
1388 .context("failed to find inserted account")?
1389 .into();
1390
1391 assert_eq!(updated_account, account_after_delta);
1392 assert!(updated_account.vault().is_empty());
1393 assert_eq!(updated_account.storage().get_item(1)?, EMPTY_WORD);
1394 let StorageSlot::Map(ref updated_map) = updated_account.storage().slots()[2] else {
1395 panic!("Expected map slot");
1396 };
1397 assert_eq!(updated_map.entries().count(), 0);
1398
1399 Ok(())
1400 }
1401}