diff --git a/CHANGELOG.md b/CHANGELOG.md index e3e108822..18eb83ec8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ * Added WebClient bindings and RPC helpers for additional account, note, and validation workflows ([#1638](https://github.com/0xMiden/miden-client/pull/1638)). * [BREAKING] Modified JS binding for `AccountComponent::compile` which now takes an `AccountComponentCode` built with the newly added binding `CodeBuilder::compile_account_component_code` ([#1627](https://github.com/0xMiden/miden-client/pull/1627)). * [BREAKING] Replaced `TransactionRequestBuilder::unauthenticated_input_notes` & `TransactionRequestBuilder::authenticated_input_notes` for `TransactionRequestBuilder::input_notes`, now the user passes a list of notes which the `Client` itself determines the authentication status of ([#1624](https://github.com/0xMiden/miden-client/issues/1624)). +* Updated `SqliteStore`: replaced `MerkleStore` with `SmtForest` and introduced `AccountSmtForest`; simplified queries ([#1526](https://github.com/0xMiden/miden-client/pull/1526), [#1663](https://github.com/0xMiden/miden-client/pull/1663)). ## 0.12.5 (2025-12-01) diff --git a/Cargo.lock b/Cargo.lock index f53240e53..4db823e61 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -27,6 +27,17 @@ dependencies = [ "generic-array", ] +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom 0.2.16", + "once_cell", + "version_check", +] + [[package]] name = "ahash" version = "0.8.12" @@ -1661,6 +1672,9 @@ name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.8", +] [[package]] name = "hashbrown" @@ -2651,9 +2665,9 @@ dependencies = [ [[package]] name = "miden-crypto" -version = "0.19.2" +version = "0.19.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc7981c1d907bb9864e24f2bd6304c4fca03a41fc4606c09edd6a7f5a8fc80fc" +checksum = "f0b49de9b0d8370c992ee04791f68a4509078198b6f42e5f72a262e7d4456487" dependencies = [ "blake3", "cc", @@ -3837,7 +3851,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ef622051fbb2cb98a524df3a8112f02d0919ccda600a44d705ec550f1a28fe2" dependencies = [ - "ahash", + "ahash 0.8.12", "async-trait", "blake2", "bytes", @@ -3873,7 +3887,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76f63d3f67d99c95a1f85623fc43242fd644dd12ccbaa18c38a54e1580c6846a" dependencies = [ - "ahash", + "ahash 0.8.12", "async-trait", "brotli", "bytes", @@ -3963,7 +3977,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b93c897e8cc04ff0d077ee2a655142910618222aeefc83f7f99f5b9fc59ccb13" dependencies = [ - "ahash", + "ahash 0.8.12", ] [[package]] @@ -3995,7 +4009,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba89e4400cb978f0d7be1c14bd7ab4168c8e2c00d97ff19f964fc0048780237c" dependencies = [ "arrayvec", - "hashbrown 0.16.1", + "hashbrown 0.12.3", "parking_lot", "rand 0.8.5", ] diff --git a/crates/rust-client/src/store/data_store.rs b/crates/rust-client/src/store/data_store.rs index 4a33dd66f..6c4492ea3 100644 --- a/crates/rust-client/src/store/data_store.rs +++ b/crates/rust-client/src/store/data_store.rs @@ -113,7 +113,7 @@ impl DataStore for ClientDataStore { ) -> Result, DataStoreError> { let mut asset_witnesses = vec![]; for vault_key in vault_keys { - match self.store.get_account_asset(account_id, vault_key.faucet_id_prefix()).await { + match self.store.get_account_asset(account_id, vault_key).await { Ok(Some((_, asset_witness))) => { asset_witnesses.push(asset_witness); }, diff --git a/crates/rust-client/src/store/mod.rs b/crates/rust-client/src/store/mod.rs index 65bdaddd5..f1accd602 100644 --- a/crates/rust-client/src/store/mod.rs +++ b/crates/rust-client/src/store/mod.rs @@ -31,7 +31,6 @@ use miden_protocol::account::{ AccountCode, AccountHeader, AccountId, - AccountIdPrefix, AccountStorage, StorageMapWitness, StorageSlot, @@ -39,7 +38,7 @@ use miden_protocol::account::{ StorageSlotName, }; use miden_protocol::address::Address; -use miden_protocol::asset::{Asset, AssetVault, AssetWitness}; +use miden_protocol::asset::{Asset, AssetVault, AssetVaultKey, AssetWitness}; use miden_protocol::block::{BlockHeader, BlockNumber}; use miden_protocol::crypto::merkle::mmr::{InOrderIndex, MmrPeaks, PartialMmr}; use miden_protocol::note::{NoteId, NoteScript, NoteTag, Nullifier}; @@ -454,20 +453,21 @@ pub trait Store: Send + Sync { /// Retrieves the asset vault for a specific account. async fn get_account_vault(&self, account_id: AccountId) -> Result; - /// Retrieves a specific asset from the account's vault along with its Merkle witness. + /// Retrieves a specific asset (by vault key) from the account's vault along with its Merkle + /// witness. /// /// The default implementation of this method uses [`Store::get_account_vault`]. async fn get_account_asset( &self, account_id: AccountId, - faucet_id_prefix: AccountIdPrefix, + vault_key: AssetVaultKey, ) -> Result, StoreError> { let vault = self.get_account_vault(account_id).await?; - let Some(asset) = vault.assets().find(|a| a.faucet_id_prefix() == faucet_id_prefix) else { + let Some(asset) = vault.assets().find(|a| a.vault_key() == vault_key) else { return Ok(None); }; - let witness = AssetWitness::new(vault.open(asset.vault_key()).into())?; + let witness = AssetWitness::new(vault.open(vault_key).into())?; Ok(Some((asset, witness))) } diff --git a/crates/sqlite-store/src/account.rs b/crates/sqlite-store/src/account.rs index 8d67cbf75..8ce44a7ff 100644 --- a/crates/sqlite-store/src/account.rs +++ b/crates/sqlite-store/src/account.rs @@ -25,7 +25,6 @@ use miden_client::account::{ StorageSlotType, }; use miden_client::asset::{Asset, AssetVault, AssetWitness, FungibleAsset, NonFungibleDeltaAction}; -use miden_client::crypto::{MerkleStore, SmtLeaf, SmtProof}; use miden_client::store::{ AccountRecord, AccountRecordData, @@ -35,28 +34,22 @@ use miden_client::store::{ }; use miden_client::sync::NoteTagRecord; use miden_client::utils::{Deserializable, Serializable}; -use miden_client::{AccountError, Felt, Word}; +use miden_client::{AccountError, EMPTY_WORD, Felt, Word}; use miden_protocol::account::{AccountStorageHeader, StorageMapWitness, StorageSlotHeader}; use miden_protocol::asset::{AssetVaultKey, PartialVault}; -use miden_protocol::crypto::merkle::SparseMerklePath; +use miden_protocol::crypto::merkle::MerkleError; use rusqlite::types::Value; use rusqlite::{Connection, Params, Transaction, named_params, params}; use super::{SqliteStore, column_value_as_u64, u64_to_value}; -use crate::merkle_store::{ - get_asset_proof, - get_storage_map_item_proof, - insert_asset_nodes, - insert_storage_map_nodes, - update_asset_nodes, - update_storage_map_nodes, -}; +use crate::smt_forest::AccountSmtForest; use crate::sql_error::SqlResultExt; use crate::sync::{add_note_tag_tx, remove_note_tag_tx}; use crate::{insert_sql, subst}; // TYPES // ================================================================================================ + struct SerializedHeaderData { id: String, nonce: u64, @@ -199,7 +192,7 @@ impl SqliteStore { pub(crate) fn get_minimal_partial_account( conn: &mut Connection, - merkle_store: &Arc>, + smt_forest: &Arc>, account_id: AccountId, ) -> Result, StoreError> { let Some((header, status)) = Self::get_account_header(conn, account_id)? else { @@ -207,7 +200,6 @@ impl SqliteStore { }; // Partial vault retrieval - let merkle_store = merkle_store.read().expect("merkle_store read lock not poisoned"); let partial_vault = PartialVault::new(header.vault_root()); // Partial storage retrieval @@ -229,15 +221,9 @@ impl SqliteStore { let mut query = query_storage_maps(conn, "root = ?", [value.to_hex()])?; if let Some(map) = query.remove(&value) { - for (k, v) in map.entries() { - let (_, path) = get_storage_map_item_proof(&merkle_store, value, *k)?; - let path = SparseMerklePath::try_from(path) - .map_err(StoreError::MerkleStoreError)?; - let leaf = SmtLeaf::Single((StorageMap::hash_key(*k), *v)); - let proof = SmtProof::new(path, leaf).map_err(StoreError::SmtProofError)?; - - let witness = StorageMapWitness::new(proof, vec![*k]) - .map_err(StoreError::StorageMapError)?; + let smt_forest = smt_forest.read().expect("smt_forest read lock not poisoned"); + for (k, _v) in map.entries() { + let witness = smt_forest.get_storage_map_item_witness(value, *k)?; partial_storage_map.add(witness).map_err(StoreError::MerkleStoreError)?; } } @@ -270,7 +256,7 @@ impl SqliteStore { pub(crate) fn insert_account( conn: &mut Connection, - merkle_store: &Arc>, + smt_forest: &Arc>, account: &Account, initial_address: &Address, ) -> Result<(), StoreError> { @@ -291,16 +277,15 @@ impl SqliteStore { tx.commit().into_store_error()?; - let mut merkle_store = merkle_store.write().expect("merkle_store write lock not poisoned"); - insert_storage_map_nodes(&mut merkle_store, account.storage()); - insert_asset_nodes(&mut merkle_store, account.vault()); + let mut smt_forest = smt_forest.write().expect("smt_forest write lock not poisoned"); + smt_forest.insert_account_state(account.vault(), account.storage())?; Ok(()) } pub(crate) fn update_account( conn: &mut Connection, - merkle_store: &Arc>, + smt_forest: &Arc>, new_account_state: &Account, ) -> Result<(), StoreError> { const QUERY: &str = "SELECT id FROM accounts WHERE id = ?"; @@ -326,9 +311,9 @@ impl SqliteStore { return Err(StoreError::AccountDataNotFound(new_account_state.id())); } - let mut merkle_store = merkle_store.write().expect("merkle_store write lock not poisoned"); + let mut smt_forest = smt_forest.write().expect("smt_forest write lock not poisoned"); let tx = conn.transaction().into_store_error()?; - Self::update_account_state(&tx, &mut merkle_store, new_account_state)?; + Self::update_account_state(&tx, &mut smt_forest, new_account_state)?; tx.commit().into_store_error() } @@ -424,40 +409,30 @@ impl SqliteStore { } /// Fetches a specific asset from the account's vault without the need of loading the entire - /// vault. The Merkle proof is also retrieved from the [`MerkleStore`]. + /// vault. The witness is retrieved from the [`AccountSmtForest`]. pub(crate) fn get_account_asset( conn: &mut Connection, - merkle_store: &Arc>, + smt_forest: &Arc>, account_id: AccountId, - faucet_id_prefix: AccountIdPrefix, + vault_key: AssetVaultKey, ) -> Result, StoreError> { let header = Self::get_account_header(conn, account_id)? .ok_or(StoreError::AccountDataNotFound(account_id))? .0; - let Some(asset) = query_vault_assets( - conn, - "faucet_id_prefix = ? AND root = ?", - params![faucet_id_prefix.to_hex(), header.vault_root().to_hex()], - )? - .into_iter() - .next() else { - return Ok(None); - }; - - let merkle_store = merkle_store.read().expect("merkle_store read lock not poisoned"); - - let proof = get_asset_proof(&merkle_store, header.vault_root(), &asset)?; - let witness = AssetWitness::new(proof)?; - - Ok(Some((asset, witness))) + let smt_forest = smt_forest.read().expect("smt_forest read lock not poisoned"); + match smt_forest.get_asset_and_witness(header.vault_root(), vault_key.into()) { + Ok((asset, witness)) => Ok(Some((asset, witness))), + Err(StoreError::MerkleStoreError(MerkleError::UntrackedKey(_))) => Ok(None), + Err(err) => Err(err), + } } /// Retrieves a specific item from the account's storage map without loading the entire storage. - /// The Merkle proof is also retrieved from the [`MerkleStore`]. + /// The witness is retrieved from the [`AccountSmtForest`]. pub(crate) fn get_account_map_item( conn: &mut Connection, - merkle_store: &Arc>, + smt_forest: &Arc>, account_id: AccountId, slot_name: StorageSlotName, key: Word, @@ -466,28 +441,21 @@ impl SqliteStore { .ok_or(StoreError::AccountDataNotFound(account_id))? .0; - let StorageSlotContent::Map(map) = query_storage_slots( + let mut storage_values = query_storage_values( conn, "commitment = ? AND slot_name = ?", params![header.storage_commitment().to_hex(), slot_name.to_string()], - )? - .remove(&slot_name) - .ok_or(StoreError::AccountStorageRootNotFound(header.storage_commitment()))? - .into_parts() - .1 - else { + )?; + let (slot_type, map_root) = storage_values + .remove(&slot_name) + .ok_or(StoreError::AccountStorageRootNotFound(header.storage_commitment()))?; + if slot_type != StorageSlotType::Map { return Err(StoreError::AccountError(AccountError::StorageSlotNotMap(slot_name))); - }; - - let item = map.get(&key); - let merkle_store = merkle_store.read().expect("merkle_store read lock not poisoned"); - - // TODO: change the api of get_storage_map_item_proof - let path = get_storage_map_item_proof(&merkle_store, map.root(), key)?.1.try_into()?; - let leaf = SmtLeaf::new_single(StorageMap::hash_key(key), item); - let proof = SmtProof::new(path, leaf)?; + } - let witness = StorageMapWitness::new(proof, [key])?; + let smt_forest = smt_forest.read().expect("smt_forest read lock not poisoned"); + let witness = smt_forest.get_storage_map_item_witness(map_root, key)?; + let item = witness.get(&key).unwrap_or(EMPTY_WORD); Ok((item, witness)) } @@ -540,21 +508,50 @@ impl SqliteStore { /// `updated_fungible_assets` and `updated_storage_maps` parameters. pub(super) fn apply_account_delta( tx: &Transaction<'_>, - merkle_store: &mut MerkleStore, + smt_forest: &mut AccountSmtForest, init_account_state: &AccountHeader, final_account_state: &AccountHeader, - mut updated_fungible_assets: BTreeMap, - mut updated_storage_maps: BTreeMap, + updated_fungible_assets: BTreeMap, + updated_storage_maps: BTreeMap, delta: &AccountDelta, ) -> Result<(), StoreError> { // Copy over the storage and vault from the previous state. Non-relevant data will not be // modified. Self::copy_account_state(tx, init_account_state, final_account_state)?; + Self::apply_account_vault_delta( + tx, + smt_forest, + init_account_state, + final_account_state, + updated_fungible_assets, + delta, + )?; + + let updated_storage_slots = + Self::apply_account_storage_delta(smt_forest, updated_storage_maps, delta)?; + + Self::insert_storage_slots( + tx, + final_account_state.storage_commitment(), + updated_storage_slots.values(), + )?; + + Ok(()) + } + + fn apply_account_vault_delta( + tx: &Transaction<'_>, + smt_forest: &mut AccountSmtForest, + init_account_state: &AccountHeader, + final_account_state: &AccountHeader, + mut updated_fungible_assets: BTreeMap, + delta: &AccountDelta, + ) -> Result<(), StoreError> { // Apply vault delta. This map will contain all updated assets (indexed by vault key), both // fungible and non-fungible. let mut updated_assets: BTreeMap = BTreeMap::new(); - let mut removed_vault_keys: Vec = Vec::new(); + let mut removed_vault_keys: Vec = Vec::new(); // We first process the fungible assets. Adding or subtracting them from the vault as // requested. @@ -579,7 +576,7 @@ impl SqliteStore { if asset.amount() > 0 { updated_assets.insert(asset.vault_key(), Asset::Fungible(asset)); } else { - removed_vault_keys.push(asset.vault_key()); + removed_vault_keys.push(Word::from(asset.vault_key())); } } @@ -596,8 +593,11 @@ impl SqliteStore { .map(|(asset, _)| (asset.vault_key(), Asset::NonFungible(*asset))), ); - removed_vault_keys - .extend(removed_nonfungible_assets.iter().map(|(asset, _)| asset.vault_key())); + removed_vault_keys.extend( + removed_nonfungible_assets + .iter() + .map(|(asset, _)| Word::from(asset.vault_key())), + ); const DELETE_QUERY: &str = "DELETE FROM account_assets WHERE root = ? AND vault_key IN rarray(?)"; @@ -608,24 +608,41 @@ impl SqliteStore { final_account_state.vault_root().to_hex(), Rc::new( removed_vault_keys - .into_iter() - .map(|k| { - let k_word: Word = k.into(); - Value::from(k_word.to_hex()) - }) + .iter() + .map(|k| Value::from(k.to_hex())) .collect::>(), ), ], ) .into_store_error()?; - update_asset_nodes( - merkle_store, + let updated_assets_values: Vec = updated_assets.values().copied().collect(); + Self::insert_assets( + tx, + final_account_state.vault_root(), + updated_assets_values.iter().copied(), + )?; + + let new_vault_root = smt_forest.update_asset_nodes( init_account_state.vault_root(), - updated_assets.values().copied(), + updated_assets_values.iter().copied(), + removed_vault_keys.iter().copied(), )?; - Self::insert_assets(tx, final_account_state.vault_root(), updated_assets.into_values())?; + if new_vault_root != final_account_state.vault_root() { + return Err(StoreError::MerkleStoreError(MerkleError::ConflictingRoots { + expected_root: final_account_state.vault_root(), + actual_root: new_vault_root, + })); + } + + Ok(()) + } + fn apply_account_storage_delta( + smt_forest: &mut AccountSmtForest, + mut updated_storage_maps: BTreeMap, + delta: &AccountDelta, + ) -> Result, StoreError> { // Apply storage delta. This map will contain all updated storage slots, both values and // maps. It gets initialized with value type updates which contain the new value and // don't depend on previous state. @@ -641,28 +658,28 @@ impl SqliteStore { // previously retrieved storage maps. for (slot_name, map_delta) in delta.storage().maps() { let mut map = updated_storage_maps.remove(slot_name).unwrap_or_default(); + let map_root = map.root(); + let entries: Vec<(Word, Word)> = + map_delta.entries().iter().map(|(key, value)| ((*key).into(), *value)).collect(); - update_storage_map_nodes( - merkle_store, - map.root(), - map_delta.entries().iter().map(|(key, value)| ((*key).into(), *value)), - )?; + for (key, value) in &entries { + map.insert(*key, *value)?; + } - for (key, value) in map_delta.entries() { - map.insert((*key).into(), *value)?; + let expected_root = map.root(); + let new_root = smt_forest.update_storage_map_nodes(map_root, entries.into_iter())?; + if new_root != expected_root { + return Err(StoreError::MerkleStoreError(MerkleError::ConflictingRoots { + expected_root, + actual_root: new_root, + })); } updated_storage_slots .insert(slot_name.clone(), StorageSlot::with_map(slot_name.clone(), map)); } - Self::insert_storage_slots( - tx, - final_account_state.storage_commitment(), - updated_storage_slots.values(), - )?; - - Ok(()) + Ok(updated_storage_slots) } /// Fetches the relevant fungible assets of an account that will be updated by the account @@ -788,31 +805,31 @@ impl SqliteStore { // HELPERS // -------------------------------------------------------------------------------------------- - /// Update previously-existing account after a transaction execution. Apart from updating the - /// `SQLite` database, this function also updates the [`MerkleStore`] by adding the vault and - /// storage SMT's nodes. - /// - /// Because the Client retrieves the account by account ID before applying the delta, we don't - /// need to check that it exists here. This inserts a new row into the accounts table. - /// We can later identify the proper account state by looking at the nonce. pub(super) fn update_account_state( tx: &Transaction<'_>, - merkle_store: &mut MerkleStore, + smt_forest: &mut AccountSmtForest, new_account_state: &Account, ) -> Result<(), StoreError> { - insert_storage_map_nodes(merkle_store, new_account_state.storage()); + // Get old SMT roots before updating so we can prune them after + let old_roots = Self::get_smt_roots_by_account_id(tx, new_account_state.id())?; + + smt_forest.insert_account_state(new_account_state.vault(), new_account_state.storage())?; Self::insert_storage_slots( tx, new_account_state.storage().to_commitment(), new_account_state.storage().slots().iter(), )?; - insert_asset_nodes(merkle_store, new_account_state.vault()); Self::insert_assets( tx, new_account_state.vault().root(), new_account_state.vault().assets(), )?; - Self::insert_account_header(tx, &new_account_state.into(), None) + Self::insert_account_header(tx, &new_account_state.into(), None)?; + + // Pop old roots to free memory for nodes no longer reachable + smt_forest.pop_roots(old_roots); + + Ok(()) } /// Locks the account if the mismatched digest doesn't belong to a previous account state (stale @@ -836,8 +853,82 @@ impl SqliteStore { .into_store_error()?; Ok(()) } + /// Returns all SMT roots (vault root + storage map roots) for the given account ID's latest + /// state. + fn get_smt_roots_by_account_id( + tx: &Transaction<'_>, + account_id: AccountId, + ) -> Result, StoreError> { + const LATEST_ACCOUNT_QUERY: &str = r" + SELECT vault_root, storage_commitment + FROM accounts + WHERE id = ?1 + ORDER BY nonce DESC + LIMIT 1 + "; + + const STORAGE_MAP_ROOTS_QUERY: &str = r" + SELECT slot_value + FROM account_storage + WHERE commitment = ?1 + AND slot_type = ?2 + AND slot_value IS NOT NULL + "; + + let map_slot_type = StorageSlotType::Map as u8; + + // 1) Fetch latest vault root + storage commitment. + let (vault_root, storage_commitment): (String, String) = tx + .query_row(LATEST_ACCOUNT_QUERY, params![account_id.to_hex()], |row| { + Ok((row.get(0)?, row.get(1)?)) + }) + .into_store_error()?; + + let mut roots = Vec::new(); - /// Removes account states with the specified hashes from the database. + // Always include the vault root. + if let Ok(root) = Word::try_from(vault_root.as_str()) { + roots.push(root); + } + + // 2) Fetch storage map roots for the latest storage commitment. + let mut stmt = tx.prepare(STORAGE_MAP_ROOTS_QUERY).into_store_error()?; + let iter = stmt + .query_map(params![storage_commitment, map_slot_type], |row| row.get::<_, String>(0)) + .into_store_error()?; + + roots.extend(iter.filter_map(Result::ok).filter_map(|r| Word::try_from(r.as_str()).ok())); + + Ok(roots) + } + + /// Returns all SMT roots (vault root + storage map roots) for the given account commitments. + fn get_smt_roots_by_account_commitment( + tx: &Transaction<'_>, + account_hash_params: &Rc>, + ) -> Result, StoreError> { + const ROOTS_QUERY: &str = " + SELECT vault_root FROM accounts WHERE account_commitment IN rarray(?1) + UNION ALL + SELECT slot_value FROM account_storage + WHERE commitment IN ( + SELECT storage_commitment FROM accounts WHERE account_commitment IN rarray(?1) + ) AND slot_type = ?2"; + + let map_slot_type = StorageSlotType::Map as u8; + let mut stmt = tx.prepare(ROOTS_QUERY).into_store_error()?; + let roots = stmt + .query_map(params![account_hash_params, map_slot_type], |row| row.get::<_, String>(0)) + .into_store_error()? + .filter_map(Result::ok) + .filter_map(|r| Word::try_from(r.as_str()).ok()) + .collect(); + + Ok(roots) + } + + /// Removes account states with the specified hashes from the database and pops their + /// SMT roots from the forest to free up memory. /// /// This is used to rollback account changes when a transaction is discarded, /// effectively undoing the account state changes that were applied by the transaction. @@ -846,12 +937,25 @@ impl SqliteStore { /// implementation to handle transaction rollbacks. pub(super) fn undo_account_state( tx: &Transaction<'_>, - account_hashes: &[Word], + smt_forest: &mut AccountSmtForest, + account_commitments: &[Word], ) -> Result<(), StoreError> { - const QUERY: &str = "DELETE FROM accounts WHERE account_commitment IN rarray(?)"; + if account_commitments.is_empty() { + return Ok(()); + } + + let account_hash_params = Rc::new( + account_commitments.iter().map(|h| Value::from(h.to_hex())).collect::>(), + ); + + // Query all SMT roots before deletion so we can pop them from the forest + let smt_roots = Self::get_smt_roots_by_account_commitment(tx, &account_hash_params)?; + + const DELETE_QUERY: &str = "DELETE FROM accounts WHERE account_commitment IN rarray(?)"; + tx.execute(DELETE_QUERY, params![account_hash_params]).into_store_error()?; - let params = account_hashes.iter().map(|h| Value::from(h.to_hex())).collect::>(); - tx.execute(QUERY, params![Rc::new(params)]).into_store_error()?; + // Pop the roots from the forest to release memory for nodes that are no longer reachable + smt_forest.pop_roots(smt_roots); Ok(()) } @@ -944,7 +1048,7 @@ impl SqliteStore { commitment.to_hex(), slot.name().to_string(), slot.value().to_hex(), - slot.slot_type().to_bytes() + slot.slot_type() as u8 ], ) .into_store_error()?; @@ -1063,7 +1167,7 @@ fn query_storage_slots( .query_map(params, |row| { let slot_name: String = row.get(0)?; let value: String = row.get(1)?; - let slot_type: Vec = row.get(2)?; + let slot_type: u8 = row.get(2)?; Ok((slot_name, value, slot_type)) }) .into_store_error()? @@ -1071,7 +1175,9 @@ fn query_storage_slots( let (slot_name, value, slot_type) = result.into_store_error()?; let slot_name = StorageSlotName::new(slot_name) .map_err(|err| StoreError::ParsingError(err.to_string()))?; - Ok((slot_name, Word::try_from(value)?, StorageSlotType::read_from_bytes(&slot_type)?)) + let slot_type = StorageSlotType::try_from(slot_type) + .map_err(|e| StoreError::ParsingError(e.to_string()))?; + Ok((slot_name, Word::try_from(value)?, slot_type)) }) .collect::, StoreError>>()?; @@ -1143,7 +1249,7 @@ fn query_storage_values( .query_map(params, |row| { let slot_name: String = row.get(0)?; let value: String = row.get(1)?; - let slot_type: Vec = row.get(2)?; + let slot_type: u8 = row.get(2)?; Ok((slot_name, value, slot_type)) }) .into_store_error()? @@ -1151,10 +1257,9 @@ fn query_storage_values( let (slot_name, value, slot_type) = result.into_store_error()?; let slot_name = StorageSlotName::new(slot_name) .map_err(|err| StoreError::ParsingError(err.to_string()))?; - Ok(( - slot_name, - (StorageSlotType::read_from_bytes(&slot_type)?, Word::try_from(value)?), - )) + let slot_type = StorageSlotType::try_from(slot_type) + .map_err(|e| StoreError::ParsingError(e.to_string()))?; + Ok((slot_name, (slot_type, Word::try_from(value)?))) }) .collect() } @@ -1406,16 +1511,16 @@ mod tests { let account_id = account.id(); let final_state: AccountHeader = (&account_after_delta).into(); - let merkle_store = store.merkle_store.clone(); + let smt_forest = store.smt_forest.clone(); store .interact_with_connection(move |conn| { let tx = conn.transaction().into_store_error()?; - let mut merkle_store = - merkle_store.write().expect("merkle_store write lock not poisoned"); + let mut smt_forest = + smt_forest.write().expect("smt_forest write lock not poisoned"); SqliteStore::apply_account_delta( &tx, - &mut merkle_store, + &mut smt_forest, &account.into(), &final_state, BTreeMap::default(), @@ -1497,7 +1602,7 @@ mod tests { let account_id = account.id(); let final_state: AccountHeader = (&account_after_delta).into(); - let merkle_store = store.merkle_store.clone(); + let smt_forest = store.smt_forest.clone(); store .interact_with_connection(move |conn| { let fungible_assets = SqliteStore::get_account_fungible_assets_for_delta( @@ -1511,12 +1616,12 @@ mod tests { &delta, )?; let tx = conn.transaction().into_store_error()?; - let mut merkle_store = - merkle_store.write().expect("merkle_store write lock not poisoned"); + let mut smt_forest = + smt_forest.write().expect("smt_forest write lock not poisoned"); SqliteStore::apply_account_delta( &tx, - &mut merkle_store, + &mut smt_forest, &account.into(), &final_state, fungible_assets, diff --git a/crates/sqlite-store/src/lib.rs b/crates/sqlite-store/src/lib.rs index f3fcc24cd..23f38ca51 100644 --- a/crates/sqlite-store/src/lib.rs +++ b/crates/sqlite-store/src/lib.rs @@ -25,14 +25,13 @@ use miden_client::account::{ AccountCode, AccountHeader, AccountId, - AccountIdPrefix, AccountStorage, Address, StorageSlotName, }; use miden_client::asset::{Asset, AssetVault, AssetWitness}; use miden_client::block::BlockHeader; -use miden_client::crypto::{InOrderIndex, MerkleStore, MmrPeaks}; +use miden_client::crypto::{InOrderIndex, MmrPeaks}; use miden_client::note::{BlockNumber, NoteScript, NoteTag, Nullifier}; use miden_client::store::{ AccountRecord, @@ -50,18 +49,19 @@ use miden_client::store::{ use miden_client::sync::{NoteTagRecord, StateSyncUpdate}; use miden_client::transaction::{TransactionRecord, TransactionStoreUpdate}; use miden_protocol::account::StorageMapWitness; +use miden_protocol::asset::AssetVaultKey; use rusqlite::Connection; use rusqlite::types::Value; use sql_error::SqlResultExt; -use crate::merkle_store::{insert_asset_nodes, insert_storage_map_nodes}; +use crate::smt_forest::AccountSmtForest; mod account; mod builder; mod chain_data; mod db_management; -mod merkle_store; mod note; +mod smt_forest; mod sql_error; mod sync; mod transaction; @@ -77,7 +77,7 @@ pub use builder::ClientBuilderSqliteExt; /// Current table definitions can be found at `store.sql` migration file. pub struct SqliteStore { pub(crate) pool: Pool, - merkle_store: Arc>, + smt_forest: Arc>, } impl SqliteStore { @@ -100,18 +100,16 @@ impl SqliteStore { let store = SqliteStore { pool, - merkle_store: Arc::new(RwLock::new(MerkleStore::new())), + smt_forest: Arc::new(RwLock::new(AccountSmtForest::new())), }; - // Initialize merkle store + // Initialize SMT forest for id in store.get_account_ids().await? { let vault = store.get_account_vault(id).await?; let storage = store.get_account_storage(id, AccountStorageFilter::All).await?; - let mut merkle_store = - store.merkle_store.write().expect("merkle_store write lock not poisoned"); - insert_asset_nodes(&mut merkle_store, &vault); - insert_storage_map_nodes(&mut merkle_store, &storage); + let mut smt_forest = store.smt_forest.write().expect("smt write lock not poisoned"); + smt_forest.insert_account_state(&vault, &storage)?; } Ok(store) @@ -171,9 +169,9 @@ impl Store for SqliteStore { } async fn apply_state_sync(&self, state_sync_update: StateSyncUpdate) -> Result<(), StoreError> { - let merkle_store = self.merkle_store.clone(); + let smt_forest = self.smt_forest.clone(); self.interact_with_connection(move |conn| { - SqliteStore::apply_state_sync(conn, &merkle_store, state_sync_update) + SqliteStore::apply_state_sync(conn, &smt_forest, state_sync_update) }) .await } @@ -189,9 +187,9 @@ impl Store for SqliteStore { } async fn apply_transaction(&self, tx_update: TransactionStoreUpdate) -> Result<(), StoreError> { - let merkle_store = self.merkle_store.clone(); + let smt_forest = self.smt_forest.clone(); self.interact_with_connection(move |conn| { - SqliteStore::apply_transaction(conn, &merkle_store, &tx_update) + SqliteStore::apply_transaction(conn, &smt_forest, &tx_update) }) .await } @@ -306,20 +304,20 @@ impl Store for SqliteStore { initial_address: Address, ) -> Result<(), StoreError> { let cloned_account = account.clone(); - let merkle_store = self.merkle_store.clone(); + let smt_forest = self.smt_forest.clone(); self.interact_with_connection(move |conn| { - SqliteStore::insert_account(conn, &merkle_store, &cloned_account, &initial_address) + SqliteStore::insert_account(conn, &smt_forest, &cloned_account, &initial_address) }) .await } async fn update_account(&self, account: &Account) -> Result<(), StoreError> { let cloned_account = account.clone(); - let merkle_store = self.merkle_store.clone(); + let smt_forest = self.smt_forest.clone(); self.interact_with_connection(move |conn| { - SqliteStore::update_account(conn, &merkle_store, &cloned_account) + SqliteStore::update_account(conn, &smt_forest, &cloned_account) }) .await } @@ -411,11 +409,11 @@ impl Store for SqliteStore { async fn get_account_asset( &self, account_id: AccountId, - faucet_id_prefix: AccountIdPrefix, + vault_key: AssetVaultKey, ) -> Result, StoreError> { - let merkle_store = self.merkle_store.clone(); + let smt_forest = self.smt_forest.clone(); self.interact_with_connection(move |conn| { - SqliteStore::get_account_asset(conn, &merkle_store, account_id, faucet_id_prefix) + SqliteStore::get_account_asset(conn, &smt_forest, account_id, vault_key) }) .await } @@ -437,10 +435,10 @@ impl Store for SqliteStore { slot_name: StorageSlotName, key: Word, ) -> Result<(Word, StorageMapWitness), StoreError> { - let merkle_store = self.merkle_store.clone(); + let smt_forest = self.smt_forest.clone(); self.interact_with_connection(move |conn| { - SqliteStore::get_account_map_item(conn, &merkle_store, account_id, slot_name, key) + SqliteStore::get_account_map_item(conn, &smt_forest, account_id, slot_name, key) }) .await } @@ -483,10 +481,10 @@ impl Store for SqliteStore { &self, account_id: AccountId, ) -> Result, StoreError> { - let merkle_store = self.merkle_store.clone(); + let smt_forest = self.smt_forest.clone(); self.interact_with_connection(move |conn| { - SqliteStore::get_minimal_partial_account(conn, &merkle_store, account_id) + SqliteStore::get_minimal_partial_account(conn, &smt_forest, account_id) }) .await } diff --git a/crates/sqlite-store/src/merkle_store.rs b/crates/sqlite-store/src/merkle_store.rs deleted file mode 100644 index 4b09a93b3..000000000 --- a/crates/sqlite-store/src/merkle_store.rs +++ /dev/null @@ -1,116 +0,0 @@ -use miden_client::Word; -use miden_client::account::{AccountStorage, StorageMap, StorageSlotContent}; -use miden_client::asset::{Asset, AssetVault}; -use miden_client::crypto::{MerklePath, MerkleStore, NodeIndex, SMT_DEPTH, SmtLeaf, SmtProof}; -use miden_client::store::StoreError; -use miden_protocol::asset::AssetVaultKey; -use miden_protocol::crypto::merkle::smt::Smt; - -/// Retrieves the Merkle proof for a specific asset in the merkle store. -pub fn get_asset_proof( - merkle_store: &MerkleStore, - vault_root: Word, - asset: &Asset, -) -> Result { - let path = merkle_store - .get_path(vault_root, get_node_index(asset.vault_key())?)? - .path - .try_into()?; - let vault_key: Word = asset.vault_key().into(); - let leaf = SmtLeaf::new_single(vault_key, (*asset).into()); - - Ok(SmtProof::new(path, leaf)?) -} - -/// Updates the merkle store with the new asset values. -pub fn update_asset_nodes( - merkle_store: &mut MerkleStore, - mut root: Word, - assets: impl Iterator, -) -> Result { - for asset in assets { - root = merkle_store - .set_node( - root, - get_node_index(asset.vault_key())?, - get_node_value(asset.vault_key(), asset.into()), - )? - .root; - } - - Ok(root) -} - -/// Inserts the asset vault SMT nodes to the merkle store. -pub fn insert_asset_nodes(merkle_store: &mut MerkleStore, vault: &AssetVault) { - // We need to build the SMT from the vault iterable entries as - // we don't have direct access to the vault's SMT nodes. - // Safe unwrap as we are sure that the vault's SMT nodes are valid. - let smt = - Smt::with_entries(vault.assets().map(|asset| (asset.vault_key().into(), asset.into()))) - .unwrap(); - merkle_store.extend(smt.inner_nodes()); -} - -/// Retrieves the Merkle proof for a specific storage map item in the merkle store. -pub fn get_storage_map_item_proof( - merkle_store: &MerkleStore, - map_root: Word, - key: Word, -) -> Result<(Word, MerklePath), StoreError> { - let hashed_key = AssetVaultKey::new_unchecked(StorageMap::hash_key(key)); - let vp = merkle_store.get_path(map_root, get_node_index(hashed_key)?)?; - Ok((vp.value, vp.path)) -} - -/// Updates the merkle store with the new storage map entries. -pub fn update_storage_map_nodes( - merkle_store: &mut MerkleStore, - mut root: Word, - entries: impl Iterator, -) -> Result { - for (key, value) in entries { - let hashed_key = AssetVaultKey::new_unchecked(StorageMap::hash_key(key)); - root = merkle_store - .set_node(root, get_node_index(hashed_key)?, get_node_value(hashed_key, value))? - .root; - } - - Ok(root) -} - -/// Inserts all storage map SMT nodes to the merkle store. -pub fn insert_storage_map_nodes(merkle_store: &mut MerkleStore, storage: &AccountStorage) { - let maps = storage.slots().iter().filter_map(|slot| match slot.content() { - StorageSlotContent::Map(map) => Some(map), - StorageSlotContent::Value(_) => None, - }); - - for map in maps { - merkle_store.extend(map.inner_nodes()); - } -} - -// HELPERS -// ================================================================================================ - -/// Builds the merkle node index for the given key. -/// -/// This logic is based on the way [`miden_protocol::crypto::merkle::Smt`] is structured internally. -/// It has a set depth and uses the third felt as the position. The reason we want to copy the smt's -/// internal structure is so that merkle paths and roots match. For more information, see the -/// [`miden_protocol::crypto::merkle::Smt`] documentation and implementation. -fn get_node_index(key: AssetVaultKey) -> Result { - let vault_key_word: Word = key.into(); - Ok(NodeIndex::new(SMT_DEPTH, vault_key_word[3].as_int())?) -} - -/// Builds the merkle node value for the given key and value. -/// -/// This logic is based on the way [`miden_protocol::crypto::merkle::Smt`] generates the values for -/// its internal merkle tree. It generates an [`SmtLeaf`] from the key and value, and then hashes it -/// to produce the node value. -fn get_node_value(key: AssetVaultKey, value: Word) -> Word { - let vault_key_word: Word = key.into(); - SmtLeaf::Single((vault_key_word, value)).hash() -} diff --git a/crates/sqlite-store/src/smt_forest.rs b/crates/sqlite-store/src/smt_forest.rs new file mode 100644 index 000000000..f55943aa0 --- /dev/null +++ b/crates/sqlite-store/src/smt_forest.rs @@ -0,0 +1,145 @@ +use miden_client::account::{AccountStorage, StorageMap, StorageSlotContent}; +use miden_client::asset::{Asset, AssetVault, AssetWitness}; +use miden_client::crypto::SMT_DEPTH; +use miden_client::store::StoreError; +use miden_client::{EMPTY_WORD, Word}; +use miden_protocol::account::StorageMapWitness; +use miden_protocol::crypto::merkle::smt::{Smt, SmtForest}; +use miden_protocol::crypto::merkle::{EmptySubtreeRoots, MerkleError}; + +/// Thin wrapper around `SmtForest` for account vault/storage proofs and updates. +#[derive(Debug, Default, Clone, Eq, PartialEq)] +pub struct AccountSmtForest { + forest: SmtForest, +} + +impl AccountSmtForest { + pub fn new() -> Self { + Self::default() + } + + /// Retrieves the vault asset and its witness for a specific vault key. + pub fn get_asset_and_witness( + &self, + vault_root: Word, + vault_key: Word, + ) -> Result<(Asset, AssetWitness), StoreError> { + let proof = self.forest.open(vault_root, vault_key)?; + let asset_word = proof.get(&vault_key).ok_or(MerkleError::UntrackedKey(vault_key))?; + if asset_word == EMPTY_WORD { + return Err(MerkleError::UntrackedKey(vault_key).into()); + } + + let asset = Asset::try_from(asset_word)?; + let witness = AssetWitness::new(proof)?; + Ok((asset, witness)) + } + + /// Retrieves the storage map witness for a specific map item. + pub fn get_storage_map_item_witness( + &self, + map_root: Word, + key: Word, + ) -> Result { + let hashed_key = StorageMap::hash_key(key); + let proof = self.forest.open(map_root, hashed_key).map_err(StoreError::from)?; + Ok(StorageMapWitness::new(proof, [key])?) + } + + // MUTATORS + // -------------------------------------------------------------------------------------------- + + /// Updates the SMT forest with the new asset values. + pub fn update_asset_nodes( + &mut self, + root: Word, + assets: impl Iterator, + removed_vault_keys: impl Iterator, + ) -> Result { + let entries: Vec<(Word, Word)> = assets + .map(|asset| { + let key: Word = asset.vault_key().into(); + let value: Word = asset.into(); + (key, value) + }) + .chain(removed_vault_keys.map(|key| (key, EMPTY_WORD))) + .collect(); + + if entries.is_empty() { + return Ok(root); + } + + let new_root = self.forest.batch_insert(root, entries).map_err(StoreError::from)?; + Ok(new_root) + } + + /// Updates the SMT forest with the new storage map values. + pub fn update_storage_map_nodes( + &mut self, + root: Word, + entries: impl Iterator, + ) -> Result { + let entries: Vec<(Word, Word)> = + entries.map(|(key, value)| (StorageMap::hash_key(key), value)).collect(); + + if entries.is_empty() { + return Ok(root); + } + + let new_root = self.forest.batch_insert(root, entries).map_err(StoreError::from)?; + Ok(new_root) + } + + /// Inserts the asset vault SMT nodes to the SMT forest. + pub fn insert_asset_nodes(&mut self, vault: &AssetVault) -> Result<(), StoreError> { + // We need to build the SMT from the vault iterable entries as we don't have direct access + // to the vault's SMT nodes. + let smt = Smt::with_entries(vault.assets().map(|asset| { + let key: Word = asset.vault_key().into(); + let value: Word = asset.into(); + (key, value) + })) + .map_err(StoreError::from)?; + + let empty_root = *EmptySubtreeRoots::entry(SMT_DEPTH, 0); + let entries: Vec<(Word, Word)> = smt.entries().map(|(k, v)| (*k, *v)).collect(); + let new_root = self.forest.batch_insert(empty_root, entries).map_err(StoreError::from)?; + debug_assert_eq!(new_root, smt.root()); + Ok(()) + } + + /// Inserts all storage map SMT nodes to the SMT forest. + pub fn insert_storage_map_nodes(&mut self, storage: &AccountStorage) { + let maps = storage.slots().iter().filter_map(|slot| match slot.content() { + StorageSlotContent::Map(map) => Some(map), + StorageSlotContent::Value(_) => None, + }); + + for map in maps { + self.insert_storage_map_nodes_for_map(map); + } + } + + pub fn insert_account_state( + &mut self, + vault: &AssetVault, + storage: &AccountStorage, + ) -> Result<(), StoreError> { + self.insert_storage_map_nodes(storage); + self.insert_asset_nodes(vault)?; + Ok(()) + } + + pub fn insert_storage_map_nodes_for_map(&mut self, map: &StorageMap) { + let empty_root = *EmptySubtreeRoots::entry(SMT_DEPTH, 0); + let entries: Vec<(Word, Word)> = + map.entries().map(|(k, v)| (StorageMap::hash_key(*k), *v)).collect(); + self.forest.batch_insert(empty_root, entries).unwrap(); // TODO: handle unwrap + } + + /// Removes the specified SMT roots from the forest, releasing memory used by nodes + /// that are no longer reachable from any remaining root. + pub fn pop_roots(&mut self, roots: impl IntoIterator) { + self.forest.pop_smts(roots); + } +} diff --git a/crates/sqlite-store/src/store.sql b/crates/sqlite-store/src/store.sql index e28a10427..cc6f957de 100644 --- a/crates/sqlite-store/src/store.sql +++ b/crates/sqlite-store/src/store.sql @@ -29,7 +29,7 @@ CREATE TABLE account_storage ( commitment TEXT NOT NULL, -- commitment to the account storage slot_name TEXT NOT NULL, -- name of the storage slot slot_value TEXT NULL, -- top-level value of the slot (e.g., if the slot is a map it contains the root) - slot_type BLOB NOT NULL, -- type of the slot, serialized + slot_type INTEGER NOT NULL, -- type of the slot (0 = Value, 1 = Map) PRIMARY KEY (commitment, slot_name) ) WITHOUT ROWID; @@ -54,6 +54,7 @@ CREATE TABLE account_assets ( ) WITHOUT ROWID; CREATE INDEX idx_account_assets_root ON account_assets(root); +CREATE INDEX idx_account_assets_root_faucet_prefix ON account_assets(root, faucet_id_prefix); -- Create foreign_account_code table CREATE TABLE foreign_account_code( diff --git a/crates/sqlite-store/src/sync.rs b/crates/sqlite-store/src/sync.rs index 497a1ee23..62f2611bc 100644 --- a/crates/sqlite-store/src/sync.rs +++ b/crates/sqlite-store/src/sync.rs @@ -5,7 +5,6 @@ use std::sync::{Arc, RwLock}; use std::vec::Vec; use miden_client::Word; -use miden_client::crypto::MerkleStore; use miden_client::note::{BlockNumber, NoteTag}; use miden_client::store::StoreError; use miden_client::sync::{NoteTagRecord, NoteTagSource, StateSyncUpdate}; @@ -14,6 +13,7 @@ use rusqlite::{Connection, Transaction, params}; use super::SqliteStore; use crate::note::apply_note_updates_tx; +use crate::smt_forest::AccountSmtForest; use crate::sql_error::SqlResultExt; use crate::transaction::upsert_transaction_record; use crate::{insert_sql, subst}; @@ -99,7 +99,7 @@ impl SqliteStore { pub(super) fn apply_state_sync( conn: &mut Connection, - merkle_store: &Arc>, + smt_forest: &Arc>, state_sync_update: StateSyncUpdate, ) -> Result<(), StoreError> { let StateSyncUpdate { @@ -166,14 +166,14 @@ impl SqliteStore { .map(|tx| tx.details.final_account_state) .collect(); - Self::undo_account_state(&tx, &account_hashes_to_delete)?; + let mut smt_forest = smt_forest.write().expect("smt_forest write lock not poisoned"); + Self::undo_account_state(&tx, &mut smt_forest, &account_hashes_to_delete)?; // Update public accounts on the db that have been updated onchain - let mut merkle_store = merkle_store.write().expect("merkle_store write lock not poisoned"); for account in account_updates.updated_public_accounts() { - Self::update_account_state(&tx, &mut merkle_store, account)?; + Self::update_account_state(&tx, &mut smt_forest, account)?; } - drop(merkle_store); + drop(smt_forest); for (account_id, digest) in account_updates.mismatched_private_accounts() { Self::lock_account_on_unexpected_commitment(&tx, account_id, digest)?; diff --git a/crates/sqlite-store/src/transaction.rs b/crates/sqlite-store/src/transaction.rs index b0389fa1d..fd4bc1700 100644 --- a/crates/sqlite-store/src/transaction.rs +++ b/crates/sqlite-store/src/transaction.rs @@ -6,7 +6,6 @@ use std::sync::{Arc, RwLock}; use std::vec::Vec; use miden_client::Word; -use miden_client::crypto::MerkleStore; use miden_client::note::ToInputNoteCommitments; use miden_client::store::{StoreError, TransactionFilter}; use miden_client::transaction::{ @@ -24,6 +23,7 @@ use rusqlite::{Connection, Transaction, params}; use super::SqliteStore; use super::note::apply_note_updates_tx; use super::sync::add_note_tag_tx; +use crate::smt_forest::AccountSmtForest; use crate::sql_error::SqlResultExt; use crate::{insert_sql, subst}; @@ -107,7 +107,7 @@ impl SqliteStore { /// Inserts a transaction and updates the current state based on the `tx_result` changes. pub fn apply_transaction( conn: &mut Connection, - merkle_store: &Arc>, + smt_forest: &Arc>, tx_update: &TransactionStoreUpdate, ) -> Result<(), StoreError> { let executed_transaction = tx_update.executed_transaction(); @@ -158,17 +158,17 @@ impl SqliteStore { upsert_transaction_record(&tx, &transaction_record)?; // Account Data - let mut merkle_store = merkle_store.write().expect("merkle_store write lock not poisoned"); + let mut smt_forest = smt_forest.write().expect("smt_forest write lock not poisoned"); Self::apply_account_delta( &tx, - &mut merkle_store, + &mut smt_forest, &executed_transaction.initial_account().into(), executed_transaction.final_account(), updated_fungible_assets, updated_storage_maps, executed_transaction.account_delta(), )?; - drop(merkle_store); + drop(smt_forest); // Note Updates apply_note_updates_tx(&tx, tx_update.note_updates())?; diff --git a/crates/testing/miden-client-tests/src/tests.rs b/crates/testing/miden-client-tests/src/tests.rs index 19d369b67..3d30d68dd 100644 --- a/crates/testing/miden-client-tests/src/tests.rs +++ b/crates/testing/miden-client-tests/src/tests.rs @@ -78,7 +78,7 @@ use miden_protocol::account::{ StorageSlotContent, StorageSlotName, }; -use miden_protocol::asset::{Asset, AssetWitness, FungibleAsset, TokenSymbol}; +use miden_protocol::asset::{Asset, AssetVaultKey, AssetWitness, FungibleAsset, TokenSymbol}; use miden_protocol::crypto::rand::{FeltRng, RpoRandomCoin}; use miden_protocol::note::{ Note, @@ -2249,9 +2249,11 @@ async fn storage_and_vault_proofs() { assert_eq!(account.vault().root(), vault.root()); // Check that specific asset proof matches the one in the vault + let vault_key = + AssetVaultKey::from_account_id(faucet_account_id).expect("faucet id is fungible"); let (asset, witness) = client .test_store() - .get_account_asset(account_id, faucet_account_id.prefix()) + .get_account_asset(account_id, vault_key) .await .unwrap() .unwrap(); @@ -2822,9 +2824,11 @@ async fn storage_and_vault_proofs_ecdsa() { assert_eq!(account.vault().root(), vault.root()); // Check that specific asset proof matches the one in the vault + let vault_key = + AssetVaultKey::from_account_id(faucet_account_id).expect("faucet id is fungible"); let (asset, witness) = client .test_store() - .get_account_asset(account_id, faucet_account_id.prefix()) + .get_account_asset(account_id, vault_key) .await .unwrap() .unwrap();