From cd2971ca20271b8cb951744281294e8e261c8949 Mon Sep 17 00:00:00 2001 From: KOVACS Krisztian Date: Thu, 14 May 2026 13:58:25 +0200 Subject: [PATCH 1/3] fix(store/state): perform nullifier and account tree access in a blocking task The account and nullifier trees may be backed by `RocksDB`, so tree access must not run on an async worker thread directly. --- crates/store/src/state/apply_block.rs | 150 +++++++++---------- crates/store/src/state/mod.rs | 202 +++++++++++++++----------- 2 files changed, 183 insertions(+), 169 deletions(-) diff --git a/crates/store/src/state/apply_block.rs b/crates/store/src/state/apply_block.rs index c3cc48826..d56df074c 100644 --- a/crates/store/src/state/apply_block.rs +++ b/crates/store/src/state/apply_block.rs @@ -123,17 +123,7 @@ impl State { // Awaiting the block saving task to complete without errors. block_save_task.await??; - // Scope to update the in-memory data. - async move { - // We need to hold the write lock here to prevent inconsistency between the in-memory - // state and the DB state. Thus, we need to wait for the DB update task to complete - // successfully. - let mut inner = self - .inner - .write() - .instrument(info_span!(target: COMPONENT, "acquire_inner_write_lock")) - .await; - + self.with_inner_write_blocking(|inner| { // We need to check that neither the nullifier tree nor the account tree have changed // while we were waiting for the DB preparation task to complete. If either of them // did change, we do not proceed with in-memory and database updates, since it may @@ -154,8 +144,8 @@ impl State { // Await for successful commit of the DB transaction. If the commit fails, we mustn't // change in-memory state, so we return a block applying error and don't proceed with // in-memory updates. - db_update_task - .await? + tokio::runtime::Handle::current() + .block_on(db_update_task)? .map_err(|err| ApplyBlockError::DbUpdateTaskFailed(err.as_report()))?; // Update the in-memory data structures after successful commit of the DB transaction @@ -171,9 +161,7 @@ impl State { inner.blockchain.push(block_commitment); Ok(()) - } - .in_current_span() - .await?; + })?; self.forest .write() @@ -237,78 +225,74 @@ impl State { header: &BlockHeader, body: &BlockBody, ) -> Result<(Word, NullifierMutationSet, Word, AccountMutationSet), ApplyBlockError> { - let inner = self - .inner - .read() - .instrument(info_span!(target: COMPONENT, "acquire_inner_read_lock")) - .await; - - let block_num = header.block_num(); - - // nullifiers can be produced only once - let duplicate_nullifiers: Vec<_> = body - .created_nullifiers() - .iter() - .filter(|&nullifier| inner.nullifier_tree.get_block_num(nullifier).is_some()) - .copied() - .collect(); - if !duplicate_nullifiers.is_empty() { - return Err(InvalidBlockError::DuplicatedNullifiers(duplicate_nullifiers).into()); - } + self.with_inner_read_blocking(|inner| { + let block_num = header.block_num(); + + // nullifiers can be produced only once + let duplicate_nullifiers: Vec<_> = body + .created_nullifiers() + .iter() + .filter(|&nullifier| inner.nullifier_tree.get_block_num(nullifier).is_some()) + .copied() + .collect(); + if !duplicate_nullifiers.is_empty() { + return Err(InvalidBlockError::DuplicatedNullifiers(duplicate_nullifiers).into()); + } - // new_block.chain_root must be equal to the chain MMR root prior to the update - let peaks = inner.blockchain.peaks(); - if peaks.hash_peaks() != header.chain_commitment() { - return Err(InvalidBlockError::NewBlockInvalidChainCommitment.into()); - } + // new_block.chain_root must be equal to the chain MMR root prior to the update + let peaks = inner.blockchain.peaks(); + if peaks.hash_peaks() != header.chain_commitment() { + return Err(InvalidBlockError::NewBlockInvalidChainCommitment.into()); + } - // compute update for nullifier tree - let nullifier_tree_update = inner - .nullifier_tree - .compute_mutations( - body.created_nullifiers().iter().map(|nullifier| (*nullifier, block_num)), - ) - .map_err(InvalidBlockError::NewBlockNullifierAlreadySpent)?; - - if nullifier_tree_update.as_mutation_set().root() != header.nullifier_root() { - // We do our best here to notify the serve routine, if it doesn't care (dropped the - // receiver) we can't do much. - let _ = self.termination_ask.try_send(ApplyBlockError::InvalidBlockError( - InvalidBlockError::NewBlockInvalidNullifierRoot, - )); - return Err(InvalidBlockError::NewBlockInvalidNullifierRoot.into()); - } + // compute update for nullifier tree + let nullifier_tree_update = inner + .nullifier_tree + .compute_mutations( + body.created_nullifiers().iter().map(|nullifier| (*nullifier, block_num)), + ) + .map_err(InvalidBlockError::NewBlockNullifierAlreadySpent)?; + + if nullifier_tree_update.as_mutation_set().root() != header.nullifier_root() { + // We do our best here to notify the serve routine, if it doesn't care (dropped the + // receiver) we can't do much. + let _ = self.termination_ask.try_send(ApplyBlockError::InvalidBlockError( + InvalidBlockError::NewBlockInvalidNullifierRoot, + )); + return Err(InvalidBlockError::NewBlockInvalidNullifierRoot.into()); + } - // compute update for account tree - let account_tree_update = inner - .account_tree - .compute_mutations( - body.updated_accounts() - .iter() - .map(|update| (update.account_id(), update.final_state_commitment())), - ) - .map_err(|e| match e { - HistoricalError::AccountTreeError(err) => { - InvalidBlockError::NewBlockDuplicateAccountIdPrefix(err) - }, - HistoricalError::MerkleError(_) => { - panic!("Unexpected MerkleError during account tree mutation computation") - }, - })?; + // compute update for account tree + let account_tree_update = inner + .account_tree + .compute_mutations( + body.updated_accounts() + .iter() + .map(|update| (update.account_id(), update.final_state_commitment())), + ) + .map_err(|e| match e { + HistoricalError::AccountTreeError(err) => { + InvalidBlockError::NewBlockDuplicateAccountIdPrefix(err) + }, + HistoricalError::MerkleError(_) => { + panic!("Unexpected MerkleError during account tree mutation computation") + }, + })?; - if account_tree_update.as_mutation_set().root() != header.account_root() { - let _ = self.termination_ask.try_send(ApplyBlockError::InvalidBlockError( - InvalidBlockError::NewBlockInvalidAccountRoot, - )); - return Err(InvalidBlockError::NewBlockInvalidAccountRoot.into()); - } + if account_tree_update.as_mutation_set().root() != header.account_root() { + let _ = self.termination_ask.try_send(ApplyBlockError::InvalidBlockError( + InvalidBlockError::NewBlockInvalidAccountRoot, + )); + return Err(InvalidBlockError::NewBlockInvalidAccountRoot.into()); + } - Ok(( - inner.nullifier_tree.root(), - nullifier_tree_update, - inner.account_tree.root_latest(), - account_tree_update, - )) + Ok(( + inner.nullifier_tree.root(), + nullifier_tree_update, + inner.account_tree.root_latest(), + account_tree_update, + )) + }) } /// Builds note records with inclusion proofs from the block body. diff --git a/crates/store/src/state/mod.rs b/crates/store/src/state/mod.rs index 6fac9b269..75288d780 100644 --- a/crates/store/src/state/mod.rs +++ b/crates/store/src/state/mod.rs @@ -36,7 +36,7 @@ use miden_protocol::crypto::merkle::smt::{LargeSmt, SmtStorage}; use miden_protocol::note::{NoteId, NoteScript, Nullifier}; use miden_protocol::transaction::PartialBlockchain; use tokio::sync::{Mutex, RwLock, watch}; -use tracing::{Instrument, info, instrument}; +use tracing::{Instrument, Span, info, instrument}; use crate::account_state_forest::{ AccountStateForest, @@ -110,6 +110,13 @@ pub struct TransactionInputs { pub new_account_id_prefix_is_unique: Option, } +type BlockInputWitnesses = ( + BlockNumber, + BTreeMap, + BTreeMap, + PartialMmr, +); + /// Container for state that needs to be updated atomically. struct InnerState where @@ -288,6 +295,34 @@ impl State { self.proven_tip.subscribe() } + /// Runs a synchronous read-only operation over the inner state on Tokio's blocking path. + /// + /// The account and nullifier trees may be backed by `RocksDB`, so tree access must not run on + /// an async worker thread directly. This helper preserves the current tracing span while + /// moving the blocking lock acquisition and closure body into `block_in_place`. + fn with_inner_read_blocking(&self, f: impl FnOnce(&InnerState) -> R) -> R { + let span = Span::current(); + tokio::task::block_in_place(|| { + span.in_scope(|| { + let inner = self.inner.blocking_read(); + f(&inner) + }) + }) + } + + /// Runs a synchronous mutable operation over the inner state on Tokio's blocking path. + /// + /// See [`Self::with_inner_read_blocking`] for why this uses `block_in_place`. + fn with_inner_write_blocking(&self, f: impl FnOnce(&mut InnerState) -> R) -> R { + let span = Span::current(); + tokio::task::block_in_place(|| { + span.in_scope(|| { + let mut inner = self.inner.blocking_write(); + f(&mut inner) + }) + }) + } + // STATE ACCESSORS // -------------------------------------------------------------------------------------------- @@ -501,7 +536,7 @@ impl State { blocks.extend(note_proof_reference_blocks); let (latest_block_number, account_witnesses, nullifier_witnesses, partial_mmr) = - self.get_block_inputs_witnesses(&mut blocks, account_ids, nullifiers).await?; + self.get_block_inputs_witnesses(&mut blocks, &account_ids, &nullifiers)?; // Fetch the block headers for all blocks in the partial MMR plus the latest one which will // be used as the previous block header of the block being built. @@ -550,68 +585,60 @@ impl State { /// /// This method acquires the lock to the inner state and does not access the DB so we release /// the lock asap. - async fn get_block_inputs_witnesses( + fn get_block_inputs_witnesses( &self, blocks: &mut BTreeSet, - account_ids: Vec, - nullifiers: Vec, - ) -> Result< - ( - BlockNumber, - BTreeMap, - BTreeMap, - PartialMmr, - ), - GetBlockInputsError, - > { - let inner = self.inner.read().await; - - let latest_block_number = inner.latest_block_num(); - - // If `blocks` is empty, use the latest block number which will never trigger the error. - let highest_block_number = blocks.last().copied().unwrap_or(latest_block_number); - if highest_block_number > latest_block_number { - return Err(GetBlockInputsError::UnknownBatchBlockReference { - highest_block_number, - latest_block_number, - }); - } + account_ids: &[AccountId], + nullifiers: &[Nullifier], + ) -> Result { + self.with_inner_read_blocking(|inner| { + let latest_block_number = inner.latest_block_num(); + + // If `blocks` is empty, use the latest block number which will never trigger the error. + let highest_block_number = blocks.last().copied().unwrap_or(latest_block_number); + if highest_block_number > latest_block_number { + return Err(GetBlockInputsError::UnknownBatchBlockReference { + highest_block_number, + latest_block_number, + }); + } - // The latest block is not yet in the chain MMR, so we can't (and don't need to) prove its - // inclusion in the chain. - blocks.remove(&latest_block_number); + // The latest block is not yet in the chain MMR, so we can't (and don't need to) prove + // its inclusion in the chain. + blocks.remove(&latest_block_number); - // Fetch the partial MMR at the state of the latest block with authentication paths for the - // provided set of blocks. - // - // SAFETY: - // - The latest block num was retrieved from the inner blockchain from which we will also - // retrieve the proofs, so it is guaranteed to exist in that chain. - // - We have checked that no block number in the blocks set is greater than latest block - // number *and* latest block num was removed from the set. Therefore only block numbers - // smaller than latest block num remain in the set. Therefore all the block numbers are - // guaranteed to exist in the chain state at latest block num. - let partial_mmr = - inner.blockchain.partial_mmr_from_blocks(blocks, latest_block_number).expect( - "latest block num should exist and all blocks in set should be < than latest block", - ); - - // Fetch witnesses for all accounts. - let account_witnesses = account_ids - .iter() - .copied() - .map(|account_id| (account_id, inner.account_tree.open_latest(account_id))) - .collect::>(); + // Fetch the partial MMR at the state of the latest block with authentication paths for + // the provided set of blocks. + // + // SAFETY: + // - The latest block num was retrieved from the inner blockchain from which we will + // also retrieve the proofs, so it is guaranteed to exist in that chain. + // - We have checked that no block number in the blocks set is greater than latest block + // number *and* latest block num was removed from the set. Therefore only block + // numbers smaller than latest block num remain in the set. Therefore all the block + // numbers are guaranteed to exist in the chain state at latest block num. + let partial_mmr = + inner.blockchain.partial_mmr_from_blocks(blocks, latest_block_number).expect( + "latest block num should exist and all blocks in set should be < than latest block", + ); - // Fetch witnesses for all nullifiers. We don't check whether the nullifiers are spent or - // not as this is done as part of proposing the block. - let nullifier_witnesses: BTreeMap = nullifiers - .iter() - .copied() - .map(|nullifier| (nullifier, inner.nullifier_tree.open(&nullifier))) - .collect(); + // Fetch witnesses for all accounts. + let account_witnesses = account_ids + .iter() + .copied() + .map(|account_id| (account_id, inner.account_tree.open_latest(account_id))) + .collect::>(); - Ok((latest_block_number, account_witnesses, nullifier_witnesses, partial_mmr)) + // Fetch witnesses for all nullifiers. We don't check whether the nullifiers are spent + // or not as this is done as part of proposing the block. + let nullifier_witnesses: BTreeMap = nullifiers + .iter() + .copied() + .map(|nullifier| (nullifier, inner.nullifier_tree.open(&nullifier))) + .collect(); + + Ok((latest_block_number, account_witnesses, nullifier_witnesses, partial_mmr)) + }) } /// Returns data needed by the block producer to verify transactions validity. @@ -624,9 +651,7 @@ impl State { ) -> Result { info!(target: COMPONENT, account_id = %account_id.to_string(), nullifiers = %format_array(nullifiers)); - let (account_commitment, nullifiers, new_account_id_prefix_is_unique) = { - let inner = self.inner.read().await; - + let tree_inputs = self.with_inner_read_blocking(|inner| { let account_commitment = inner.account_tree.get_latest_commitment(account_id); let new_account_id_prefix_is_unique = if account_commitment.is_empty() { @@ -637,7 +662,7 @@ impl State { // Non-unique account Id prefixes for new accounts are not allowed. if let Some(false) = new_account_id_prefix_is_unique { - return Ok(TransactionInputs { + return Err(TransactionInputs { new_account_id_prefix_is_unique, ..Default::default() }); @@ -651,7 +676,11 @@ impl State { }) .collect(); - (account_commitment, nullifiers, new_account_id_prefix_is_unique) + Ok((account_commitment, nullifiers, new_account_id_prefix_is_unique)) + }); + let (account_commitment, nullifiers, new_account_id_prefix_is_unique) = match tree_inputs { + Ok(inputs) => inputs, + Err(inputs) => return Ok(inputs), }; let found_unauthenticated_notes = self @@ -736,30 +765,31 @@ impl State { block_num: Option, account_id: AccountId, ) -> Result<(BlockNumber, AccountWitness), GetAccountError> { - let inner_state = - self.inner.read().instrument(tracing::info_span!("acquire_inner_state")).await; - - // Determine which block to query - let (block_num, witness) = if let Some(requested_block) = block_num { - // Historical query: use the account tree with history - let witness = - inner_state.account_tree.open_at(account_id, requested_block).ok_or_else(|| { - let latest_block = inner_state.account_tree.block_number_latest(); - if requested_block > latest_block { - GetAccountError::UnknownBlock(requested_block) - } else { - GetAccountError::BlockPruned(requested_block) - } - })?; - (requested_block, witness) - } else { - // Latest query: use the latest state - let block_num = inner_state.account_tree.block_number_latest(); - let witness = inner_state.account_tree.open_latest(account_id); - (block_num, witness) - }; + self.with_inner_read_blocking(|inner_state| { + // Determine which block to query + let (block_num, witness) = if let Some(requested_block) = block_num { + // Historical query: use the account tree with history + let witness = inner_state + .account_tree + .open_at(account_id, requested_block) + .ok_or_else(|| { + let latest_block = inner_state.account_tree.block_number_latest(); + if requested_block > latest_block { + GetAccountError::UnknownBlock(requested_block) + } else { + GetAccountError::BlockPruned(requested_block) + } + })?; + (requested_block, witness) + } else { + // Latest query: use the latest state + let block_num = inner_state.account_tree.block_number_latest(); + let witness = inner_state.account_tree.open_latest(account_id); + (block_num, witness) + }; - Ok((block_num, witness)) + Ok((block_num, witness)) + }) } /// Returns storage map details from the forest for a specific account and storage slot. From 7a3c4588c1b9fc0ed35a075fbf7251fe08267beb Mon Sep 17 00:00:00 2001 From: KOVACS Krisztian Date: Thu, 14 May 2026 15:08:09 +0200 Subject: [PATCH 2/3] fix(store/state): perform account state forest access in a blocking task The account state forest may be backed by `RocksDB`, so access must not run on an async worker thread directly. --- crates/store/src/server/ntx_builder.rs | 2 - crates/store/src/state/apply_block.rs | 8 +- crates/store/src/state/mod.rs | 149 ++++++++++++++----------- 3 files changed, 88 insertions(+), 71 deletions(-) diff --git a/crates/store/src/server/ntx_builder.rs b/crates/store/src/server/ntx_builder.rs index e0cc425c9..02face987 100644 --- a/crates/store/src/server/ntx_builder.rs +++ b/crates/store/src/server/ntx_builder.rs @@ -253,7 +253,6 @@ impl ntx_builder_server::NtxBuilder for StoreApi { let asset_witnesses = self .state .get_vault_asset_witnesses(account_id, block_num, vault_keys) - .await .map_err(internal_error)?; // Convert AssetWitness to protobuf format by extracting witness data. @@ -307,7 +306,6 @@ impl ntx_builder_server::NtxBuilder for StoreApi { let storage_witness = self .state .get_storage_map_witness(account_id, &slot_name, block_num, map_key) - .await .map_err(internal_error)?; // Convert StorageMapWitness to protobuf format by extracting witness data. diff --git a/crates/store/src/state/apply_block.rs b/crates/store/src/state/apply_block.rs index d56df074c..64f4ffb92 100644 --- a/crates/store/src/state/apply_block.rs +++ b/crates/store/src/state/apply_block.rs @@ -163,11 +163,9 @@ impl State { Ok(()) })?; - self.forest - .write() - .instrument(info_span!(target: COMPONENT, "acquire_forest_write_lock")) - .await - .apply_block_updates(block_num, account_deltas)?; + self.with_forest_write_blocking(|forest| { + forest.apply_block_updates(block_num, account_deltas) + })?; // Push to cache and notify replica subscribers. self.block_cache.push(block_num, BlockNotification::new(block_num, cache_bytes)); diff --git a/crates/store/src/state/mod.rs b/crates/store/src/state/mod.rs index 75288d780..489fab524 100644 --- a/crates/store/src/state/mod.rs +++ b/crates/store/src/state/mod.rs @@ -295,6 +295,9 @@ impl State { self.proven_tip.subscribe() } + // HELPER FUNCTIONS TO AVOID BLOCKING CALLS IN ASYNC CONTEXT + // -------------------------------------------------------------------------------------------- + /// Runs a synchronous read-only operation over the inner state on Tokio's blocking path. /// /// The account and nullifier trees may be backed by `RocksDB`, so tree access must not run on @@ -323,6 +326,40 @@ impl State { }) } + /// Runs a synchronous read-only operation over the account state forest on Tokio's blocking + /// path. + /// + /// The forest may be backed by `RocksDB`, so accesses to the underlying `LargeSmtForest` must + /// not run directly on an async worker thread. + fn with_forest_read_blocking( + &self, + f: impl FnOnce(&AccountStateForest) -> R, + ) -> R { + let span = Span::current(); + tokio::task::block_in_place(|| { + span.in_scope(|| { + let forest = self.forest.blocking_read(); + f(&forest) + }) + }) + } + + /// Runs a synchronous mutable operation over the account state forest on Tokio's blocking path. + /// + /// See [`Self::with_forest_read_blocking`] for why this uses `block_in_place`. + fn with_forest_write_blocking( + &self, + f: impl FnOnce(&mut AccountStateForest) -> R, + ) -> R { + let span = Span::current(); + tokio::task::block_in_place(|| { + span.in_scope(|| { + let mut forest = self.forest.blocking_write(); + f(&mut forest) + }) + }) + } + // STATE ACCESSORS // -------------------------------------------------------------------------------------------- @@ -799,29 +836,26 @@ impl State { /// that the caller should fall back to reconstructing the storage map details from the /// database. #[instrument(target = COMPONENT, skip_all)] - async fn get_storage_map_details_from_forest( + fn get_storage_map_details_from_forest( &self, account_id: AccountId, - slot_name: StorageSlotName, + slot_name: &StorageSlotName, block_num: BlockNumber, ) -> Result, DatabaseError> { - let forest_guard = self - .forest - .read() - .instrument(tracing::info_span!("acquire_forest_for_storage_map_entries")) - .await; - match forest_guard - .get_storage_map_details_for_all_entries(account_id, slot_name.clone(), block_num) - .map_err(DatabaseError::MerkleError)? - { - AccountStorageMapResult::NotFound => Err(DatabaseError::StorageRootNotFound { - account_id, - slot_name: slot_name.to_string(), - block_num, - }), - AccountStorageMapResult::Details(details) => Ok(Some(details)), - AccountStorageMapResult::CannotReconstructKeysFromCache => Ok(None), - } + self.with_forest_read_blocking(|forest| { + match forest + .get_storage_map_details_for_all_entries(account_id, slot_name.clone(), block_num) + .map_err(DatabaseError::MerkleError)? + { + AccountStorageMapResult::NotFound => Err(DatabaseError::StorageRootNotFound { + account_id, + slot_name: slot_name.to_string(), + block_num, + }), + AccountStorageMapResult::Details(details) => Ok(Some(details)), + AccountStorageMapResult::CannotReconstructKeysFromCache => Ok(None), + } + }) } /// Returns storage map details by reconstructing the storage map from the database. @@ -910,17 +944,13 @@ impl State { Some(commitment) if commitment == account_header.vault_root() => { AccountVaultDetails::empty() }, - Some(_) => self - .forest - .read() - .instrument(tracing::info_span!("acquire_forest_for_vault")) - .await - .get_vault_details(account_id, block_num) - .map_err(|err| { + Some(_) => self.with_forest_read_blocking(|forest| { + forest.get_vault_details(account_id, block_num).map_err(|err| { DatabaseError::DataCorrupted(format!( "failed to reconstruct vault for account {account_id} at block {block_num}: {err}" )) - })?, + }) + })?, None => AccountVaultDetails::empty(), }; @@ -947,33 +977,30 @@ impl State { let mut storage_map_details_by_index = vec![None; storage_request_slots.len()]; if !map_keys_requests.is_empty() { - let forest_guard = self - .forest - .read() - .instrument(tracing::info_span!("acquire_forest_for_storage_map")) - .await; - for (index, slot_name, keys) in map_keys_requests { - let details = forest_guard - .get_storage_map_details_for_keys( - account_id, - slot_name.clone(), - block_num, - &keys, - ) - .ok_or_else(|| DatabaseError::StorageRootNotFound { - account_id, - slot_name: slot_name.to_string(), - block_num, - })? - .map_err(DatabaseError::MerkleError)?; - storage_map_details_by_index[index] = Some(details); - } + self.with_forest_read_blocking(|forest| { + for (index, slot_name, keys) in map_keys_requests { + let details = forest + .get_storage_map_details_for_keys( + account_id, + slot_name.clone(), + block_num, + &keys, + ) + .ok_or_else(|| DatabaseError::StorageRootNotFound { + account_id, + slot_name: slot_name.to_string(), + block_num, + })? + .map_err(DatabaseError::MerkleError)?; + storage_map_details_by_index[index] = Some(details); + } + Ok::<(), DatabaseError>(()) + })?; } for (index, slot_name) in all_entries_requests { let details = match self - .get_storage_map_details_from_forest(account_id, slot_name.clone(), block_num) - .await? + .get_storage_map_details_from_forest(account_id, &slot_name, block_num)? { Some(details) => details, None => { @@ -1065,18 +1092,15 @@ impl State { } /// Returns vault asset witnesses for the specified account and block number. - pub async fn get_vault_asset_witnesses( + pub fn get_vault_asset_witnesses( &self, account_id: AccountId, block_num: BlockNumber, vault_keys: BTreeSet, ) -> Result, WitnessError> { - let witnesses = self - .forest - .read() - .await - .get_vault_asset_witnesses(account_id, block_num, vault_keys)?; - Ok(witnesses) + self.with_forest_read_blocking(|forest| { + forest.get_vault_asset_witnesses(account_id, block_num, vault_keys) + }) } /// Returns a storage map witness for the specified account and storage entry at the block @@ -1084,18 +1108,15 @@ impl State { /// /// Note that the `raw_key` is the raw, user-provided key that needs to be hashed in order to /// get the actual key into the storage map. - pub async fn get_storage_map_witness( + pub fn get_storage_map_witness( &self, account_id: AccountId, slot_name: &StorageSlotName, block_num: BlockNumber, raw_key: StorageMapKey, ) -> Result { - let witness = self - .forest - .read() - .await - .get_storage_map_witness(account_id, slot_name, block_num, raw_key)?; - Ok(witness) + self.with_forest_read_blocking(|forest| { + forest.get_storage_map_witness(account_id, slot_name, block_num, raw_key) + }) } } From 9be817d03e684e8dd65c2b73d6404f9c08431ad7 Mon Sep 17 00:00:00 2001 From: KOVACS Krisztian Date: Thu, 14 May 2026 15:32:24 +0200 Subject: [PATCH 3/3] chore: update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d577f88fd..95834b8dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ - [BREAKING] Renamed `--url` CLI flags and `*_URL` env vars to `--listen` / `*_LISTEN` across all components. - [BREAKING] Removed `miden-node validator` subcommand and created a separate `miden-validator` binary ([#2053](https://github.com/0xMiden/node/pull/2053)). - [BREAKING] Removed `miden-node ntx-builder` subcommand and created a separate `miden-ntx-builder` binary ([#2067](https://github.com/0xMiden/node/pull/2067)). +- Replaced blocking-in-async LargeSmt and account state forest operations in the store with wrappers using Tokio's `block_in_place()` ([#2076](https://github.com/0xMiden/node/pull/2076)). ## v0.14.10 (2026-05-29)