From 24b0977e34a4fc84111e4db6d32d9c5566abff89 Mon Sep 17 00:00:00 2001 From: Bernhard Schuster Date: Tue, 20 Jan 2026 14:05:37 +0100 Subject: [PATCH 1/3] fix --- CHANGELOG.md | 1 + bin/node/src/commands/store.rs | 10 +- crates/block-producer/src/server/tests.rs | 2 + crates/store/src/db/mod.rs | 3 +- crates/store/src/errors.rs | 15 + crates/store/src/server/mod.rs | 3 +- crates/store/src/state/loader.rs | 307 ++++++++++++++++++++ crates/store/src/{state.rs => state/mod.rs} | 224 ++------------ 8 files changed, 355 insertions(+), 210 deletions(-) create mode 100644 crates/store/src/state/loader.rs rename crates/store/src/{state.rs => state/mod.rs} (86%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0328be9514..ff897a298c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ - The network monitor now marks the chain as unhealthy if it fails to create new blocks ([#1512](https://github.com/0xMiden/miden-node/pull/1512)). - Block producer now detects if it is desync'd from the store's chain tip and aborts ([#1520](https://github.com/0xMiden/miden-node/pull/1520)). - Pin tool versions in CI ([#1523](https://github.com/0xMiden/miden-node/pull/1523)). +- Add check to ensure tree store state is in sync with database storage ([#1532](https://github.com/0xMiden/miden-node/issues/1534)). ### Changes diff --git a/bin/node/src/commands/store.rs b/bin/node/src/commands/store.rs index 2105b14530..49fd5a7b84 100644 --- a/bin/node/src/commands/store.rs +++ b/bin/node/src/commands/store.rs @@ -119,14 +119,8 @@ impl StoreCommand { enable_otel: _, grpc_timeout, } => { - Self::start( - rpc_url, - ntx_builder_url, - block_producer_url, - data_directory, - grpc_timeout, - ) - .await + Self::start(rpc_url, ntx_builder_url, block_producer_url, data_directory, grpc_timeout) + .await }, } } diff --git a/crates/block-producer/src/server/tests.rs b/crates/block-producer/src/server/tests.rs index 91de51ddcf..453512597b 100644 --- a/crates/block-producer/src/server/tests.rs +++ b/crates/block-producer/src/server/tests.rs @@ -94,6 +94,7 @@ async fn block_producer_startup_is_robust_to_network_failures() { ntx_builder_listener, block_producer_listener, data_directory: dir, + grpc_timeout: std::time::Duration::from_secs(30), } .serve() .await @@ -159,6 +160,7 @@ async fn restart_store( ntx_builder_listener, block_producer_listener, data_directory: dir, + grpc_timeout: std::time::Duration::from_secs(30), } .serve() .await diff --git a/crates/store/src/db/mod.rs b/crates/store/src/db/mod.rs index 4bffb0af76..fc96212b56 100644 --- a/crates/store/src/db/mod.rs +++ b/crates/store/src/db/mod.rs @@ -3,8 +3,7 @@ use std::ops::RangeInclusive; use std::path::PathBuf; use anyhow::Context; -use diesel::prelude::QueryableByName; -use diesel::{Connection, RunQueryDsl, SqliteConnection}; +use diesel::{Connection, QueryableByName, RunQueryDsl, SqliteConnection}; use miden_node_proto::domain::account::{AccountInfo, AccountSummary, NetworkAccountPrefix}; use miden_node_proto::generated as proto; use miden_node_utils::tracing::OpenTelemetrySpanExt; diff --git a/crates/store/src/errors.rs b/crates/store/src/errors.rs index d0eb2142a8..32c345a985 100644 --- a/crates/store/src/errors.rs +++ b/crates/store/src/errors.rs @@ -206,6 +206,21 @@ pub enum StateInitializationError { DatabaseLoadError(#[from] DatabaseSetupError), #[error("inner forest error")] InnerForestError(#[from] InnerForestError), + #[error( + "{tree_name} SMT root ({tree_root:?}) does not match expected root from block {block_num} \ + ({block_root:?}). Delete the tree storage directories and restart the node to rebuild \ + from the database." + )] + TreeStorageDiverged { + tree_name: &'static str, + block_num: BlockNumber, + tree_root: Word, + block_root: Word, + }, + #[error("public account {0} is missing details in database")] + PublicAccountMissingDetails(AccountId), + #[error("failed to convert account to delta: {0}")] + AccountToDeltaConversionFailed(String), } #[derive(Debug, Error)] diff --git a/crates/store/src/server/mod.rs b/crates/store/src/server/mod.rs index f38f737fc9..420ec4921b 100644 --- a/crates/store/src/server/mod.rs +++ b/crates/store/src/server/mod.rs @@ -88,7 +88,8 @@ impl Store { let ntx_builder_address = self.ntx_builder_listener.local_addr()?; let block_producer_address = self.block_producer_listener.local_addr()?; info!(target: COMPONENT, rpc_endpoint=?rpc_address, ntx_builder_endpoint=?ntx_builder_address, - block_producer_endpoint=?block_producer_address, ?self.data_directory, ?self.grpc_timeout, "Loading database"); + block_producer_endpoint=?block_producer_address, ?self.data_directory, ?self.grpc_timeout, + "Loading database"); let state = Arc::new(State::load(&self.data_directory).await.context("failed to load state")?); diff --git a/crates/store/src/state/loader.rs b/crates/store/src/state/loader.rs new file mode 100644 index 0000000000..ac50726223 --- /dev/null +++ b/crates/store/src/state/loader.rs @@ -0,0 +1,307 @@ +//! Tree loading logic for the store state. +//! +//! This module handles loading and initializing the Merkle trees (account tree, nullifier tree, +//! and SMT forest) from storage backends. It supports different loading modes: +//! +//! - **Memory mode** (`rocksdb` feature disabled): Trees are rebuilt from the database on each +//! startup. +//! - **Persistent mode** (`rocksdb` feature enabled): Trees are loaded from persistent storage if +//! data exists, otherwise rebuilt from the database and persisted. + +use std::path::Path; + +use miden_protocol::Word; +use miden_protocol::block::account_tree::account_id_to_smt_key; +use miden_protocol::block::nullifier_tree::NullifierTree; +use miden_protocol::block::{BlockHeader, BlockNumber, Blockchain}; +#[cfg(not(feature = "rocksdb"))] +use miden_protocol::crypto::merkle::smt::MemoryStorage; +use miden_protocol::crypto::merkle::smt::{LargeSmt, LargeSmtError, SmtStorage}; +#[cfg(feature = "rocksdb")] +use tracing::info; +use tracing::instrument; +#[cfg(feature = "rocksdb")] +use { + miden_crypto::merkle::smt::RocksDbStorage, + miden_protocol::crypto::merkle::smt::RocksDbConfig, +}; + +use crate::COMPONENT; +use crate::db::Db; +use crate::errors::{DatabaseError, StateInitializationError}; +use crate::inner_forest::InnerForest; + +// CONSTANTS +// ================================================================================================ + +/// Directory name for the account tree storage within the data directory. +pub const ACCOUNT_TREE_STORAGE_DIR: &str = "accounttree"; + +/// Directory name for the nullifier tree storage within the data directory. +pub const NULLIFIER_TREE_STORAGE_DIR: &str = "nullifiertree"; + +// STORAGE TYPE ALIAS +// ================================================================================================ + +/// The storage backend for trees. +#[cfg(feature = "rocksdb")] +pub type TreeStorage = RocksDbStorage; +#[cfg(not(feature = "rocksdb"))] +pub type TreeStorage = MemoryStorage; + +// ERROR CONVERSION +// ================================================================================================ + +/// Converts a `LargeSmtError` into a `StateInitializationError`. +pub fn account_tree_large_smt_error_to_init_error(e: LargeSmtError) -> StateInitializationError { + use miden_node_utils::ErrorReport; + match e { + LargeSmtError::Merkle(merkle_error) => { + StateInitializationError::DatabaseError(DatabaseError::MerkleError(merkle_error)) + }, + LargeSmtError::Storage(err) => { + StateInitializationError::AccountTreeIoError(err.as_report()) + }, + } +} + +// STORAGE LOADER TRAIT +// ================================================================================================ + +/// Trait for loading trees from storage. +/// +/// For `MemoryStorage`, the tree is rebuilt from database entries on each startup. +/// For `RocksDbStorage`, the tree is loaded directly from disk (much faster for large trees). +/// +/// Missing or corrupted storage is handled by the `verify_tree_consistency` check after loading, +/// which detects divergence between persistent storage and the database. If divergence is detected, +/// the user should manually delete the tree storage directories and restart the node. +pub trait StorageLoader: SmtStorage + Sized { + /// Creates a storage backend for the given domain. + fn create(data_dir: &Path, domain: &'static str) -> Result; + + /// Loads an account tree, either from persistent storage or by rebuilding from DB. + fn load_account_tree( + self, + db: &mut Db, + ) -> impl std::future::Future, StateInitializationError>> + Send; + + /// Loads a nullifier tree, either from persistent storage or by rebuilding from DB. + fn load_nullifier_tree( + self, + db: &mut Db, + ) -> impl std::future::Future< + Output = Result>, StateInitializationError>, + > + Send; +} + +// MEMORY STORAGE IMPLEMENTATION +// ================================================================================================ + +#[cfg(not(feature = "rocksdb"))] +impl StorageLoader for MemoryStorage { + fn create(_data_dir: &Path, _domain: &'static str) -> Result { + Ok(MemoryStorage::default()) + } + + async fn load_account_tree( + self, + db: &mut Db, + ) -> Result, StateInitializationError> { + let account_data = db.select_all_account_commitments().await?; + let smt_entries = account_data + .into_iter() + .map(|(id, commitment)| (account_id_to_smt_key(id), commitment)); + LargeSmt::with_entries(self, smt_entries) + .map_err(account_tree_large_smt_error_to_init_error) + } + + async fn load_nullifier_tree( + self, + db: &mut Db, + ) -> Result>, StateInitializationError> { + let nullifiers = db.select_all_nullifiers().await?; + let entries = nullifiers.into_iter().map(|info| (info.nullifier, info.block_num)); + NullifierTree::with_storage_from_entries(self, entries) + .map_err(StateInitializationError::FailedToCreateNullifierTree) + } +} + +// ROCKSDB STORAGE IMPLEMENTATION +// ================================================================================================ + +#[cfg(feature = "rocksdb")] +impl StorageLoader for RocksDbStorage { + fn create(data_dir: &Path, domain: &'static str) -> Result { + let storage_path = data_dir.join(domain); + + fs_err::create_dir_all(&storage_path) + .map_err(|e| StateInitializationError::AccountTreeIoError(e.to_string()))?; + RocksDbStorage::open(RocksDbConfig::new(storage_path)) + .map_err(|e| StateInitializationError::AccountTreeIoError(e.to_string())) + } + + async fn load_account_tree( + self, + db: &mut Db, + ) -> Result, StateInitializationError> { + // If RocksDB storage has data, load from it directly + let has_data = self + .has_leaves() + .map_err(|e| StateInitializationError::AccountTreeIoError(e.to_string()))?; + if has_data { + return load_smt(self); + } + + info!(target: COMPONENT, "RocksDB account tree storage is empty, populating from SQLite"); + let account_data = db.select_all_account_commitments().await?; + let smt_entries = account_data + .into_iter() + .map(|(id, commitment)| (account_id_to_smt_key(id), commitment)); + LargeSmt::with_entries(self, smt_entries) + .map_err(account_tree_large_smt_error_to_init_error) + } + + async fn load_nullifier_tree( + self, + db: &mut Db, + ) -> Result>, StateInitializationError> { + // If RocksDB storage has data, load from it directly + let has_data = self + .has_leaves() + .map_err(|e| StateInitializationError::NullifierTreeIoError(e.to_string()))?; + if has_data { + let smt = load_smt(self)?; + return Ok(NullifierTree::new_unchecked(smt)); + } + + info!(target: COMPONENT, "RocksDB nullifier tree storage is empty, populating from SQLite"); + let nullifiers = db.select_all_nullifiers().await?; + let entries = nullifiers.into_iter().map(|info| (info.nullifier, info.block_num)); + NullifierTree::with_storage_from_entries(self, entries) + .map_err(StateInitializationError::FailedToCreateNullifierTree) + } +} + +// HELPER FUNCTIONS +// ================================================================================================ + +/// Loads an SMT from persistent storage. +#[cfg(feature = "rocksdb")] +pub fn load_smt(storage: S) -> Result, StateInitializationError> { + LargeSmt::new(storage).map_err(account_tree_large_smt_error_to_init_error) +} + +// TREE LOADING FUNCTIONS +// ================================================================================================ + +/// Loads the blockchain MMR from all block headers in the database. +#[instrument(target = COMPONENT, skip_all)] +pub async fn load_mmr(db: &mut Db) -> Result { + let block_commitments: Vec = db + .select_all_block_headers() + .await? + .iter() + .map(BlockHeader::commitment) + .collect(); + + // SAFETY: We assume the loaded MMR is valid and does not have more than u32::MAX + // entries. + let chain_mmr = Blockchain::from_mmr_unchecked(block_commitments.into()); + + Ok(chain_mmr) +} + +/// Loads SMT forest with storage map and vault Merkle paths for all public accounts. +#[instrument(target = COMPONENT, skip_all, fields(block_num = %block_num))] +pub async fn load_smt_forest( + db: &mut Db, + block_num: BlockNumber, +) -> Result { + use miden_protocol::account::delta::AccountDelta; + + let public_account_ids = db.select_all_public_account_ids().await?; + + // Acquire write lock once for the entire initialization + let mut forest = InnerForest::new(); + + // Process each account + for account_id in public_account_ids { + // Get the full account from the database + let account_info = db.select_account(account_id).await?; + let account = account_info.details.expect("public accounts always have details in DB"); + + // Convert the full account to a full-state delta + let delta = + AccountDelta::try_from(account).expect("accounts from DB should not have seeds"); + + // Use the unified update method (will recognize it's a full-state delta) + forest.update_account(block_num, &delta)?; + + tracing::debug!( + target: COMPONENT, + %account_id, + %block_num, + "Initialized forest for account from DB" + ); + } + + Ok(forest) +} + +// CONSISTENCY VERIFICATION +// ================================================================================================ + +/// Verifies that tree roots match the expected roots from the latest block header. +/// +/// This check ensures the database and tree storage (memory or persistent) haven't diverged due to +/// corruption or incomplete shutdown. When trees are rebuilt from the database, they will naturally +/// match; when loaded from persistent storage, this catches any inconsistencies. +/// +/// # Arguments +/// * `account_tree_root` - Root of the loaded account tree +/// * `nullifier_tree_root` - Root of the loaded nullifier tree +/// * `db` - Database connection to fetch the latest block header +/// +/// # Errors +/// Returns `StateInitializationError::TreeStorageDiverged` if any root doesn't match. +#[instrument(target = COMPONENT, skip_all)] +pub async fn verify_tree_consistency( + account_tree_root: Word, + nullifier_tree_root: Word, + db: &mut Db, +) -> Result<(), StateInitializationError> { + // Fetch the latest block header to get the expected roots + let latest_header = db.select_block_header_by_block_num(None).await?; + + let (block_num, expected_account_root, expected_nullifier_root) = latest_header + .map(|header| ( + header.block_num(), + header.account_root(), + header.nullifier_root(), + )) + .unwrap_or_default(); + + // Verify account tree root + if account_tree_root != expected_account_root { + return Err(StateInitializationError::TreeStorageDiverged { + tree_name: "Account", + block_num, + tree_root: account_tree_root, + block_root: expected_account_root, + }); + } + + // Verify nullifier tree root + if nullifier_tree_root != expected_nullifier_root { + return Err(StateInitializationError::TreeStorageDiverged { + tree_name: "Nullifier", + block_num, + tree_root: nullifier_tree_root, + block_root: expected_nullifier_root, + }); + } + + + Ok(()) +} diff --git a/crates/store/src/state.rs b/crates/store/src/state/mod.rs similarity index 86% rename from crates/store/src/state.rs rename to crates/store/src/state/mod.rs index 1b19649aa5..a798c9a820 100644 --- a/crates/store/src/state.rs +++ b/crates/store/src/state/mod.rs @@ -27,23 +27,16 @@ use miden_node_utils::formatting::format_array; use miden_protocol::Word; use miden_protocol::account::AccountId; use miden_protocol::account::delta::AccountUpdateDetails; -use miden_protocol::block::account_tree::{AccountTree, AccountWitness, account_id_to_smt_key}; -use miden_protocol::block::nullifier_tree::{NullifierTree, NullifierWitness}; +use miden_protocol::block::account_tree::{AccountTree, AccountWitness}; +use miden_protocol::block::nullifier_tree::NullifierWitness; use miden_protocol::block::{BlockHeader, BlockInputs, BlockNumber, Blockchain, ProvenBlock}; use miden_protocol::crypto::merkle::mmr::{Forest, MmrDelta, MmrPeaks, MmrProof, PartialMmr}; -#[cfg(not(feature = "rocksdb"))] -use miden_protocol::crypto::merkle::smt::MemoryStorage; -use miden_protocol::crypto::merkle::smt::{LargeSmt, LargeSmtError, SmtProof, SmtStorage}; +use miden_protocol::crypto::merkle::smt::{SmtProof, SmtStorage}; use miden_protocol::note::{NoteDetails, NoteId, NoteScript, Nullifier}; use miden_protocol::transaction::{OutputNote, PartialBlockchain}; use miden_protocol::utils::Serializable; use tokio::sync::{Mutex, RwLock, oneshot}; use tracing::{Instrument, info, info_span, instrument}; -#[cfg(feature = "rocksdb")] -use { - miden_crypto::merkle::smt::RocksDbStorage, - miden_protocol::crypto::merkle::smt::RocksDbConfig, -}; use crate::accounts::{AccountTreeWithHistory, HistoricalError}; use crate::blocks::BlockStore; @@ -72,6 +65,16 @@ use crate::errors::{ use crate::inner_forest::InnerForest; use crate::{COMPONENT, DataDirectory}; +mod loader; + +pub use loader::{ + ACCOUNT_TREE_STORAGE_DIR, + NULLIFIER_TREE_STORAGE_DIR, + StorageLoader, + TreeStorage, +}; +use loader::{load_mmr, load_smt_forest, verify_tree_consistency}; + // STRUCTURES // ================================================================================================ @@ -83,141 +86,14 @@ pub struct TransactionInputs { pub new_account_id_prefix_is_unique: Option, } -/// The storage backend for trees. -#[cfg(feature = "rocksdb")] -pub type TreeStorage = RocksDbStorage; -#[cfg(not(feature = "rocksdb"))] -pub type TreeStorage = MemoryStorage; - -/// Converts a `LargeSmtError` into a `StateInitializationError`. -fn account_tree_large_smt_error_to_init_error(e: LargeSmtError) -> StateInitializationError { - match e { - LargeSmtError::Merkle(merkle_error) => { - StateInitializationError::DatabaseError(DatabaseError::MerkleError(merkle_error)) - }, - LargeSmtError::Storage(err) => { - StateInitializationError::AccountTreeIoError(err.as_report()) - }, - } -} - -/// Loads an SMT from persistent storage. -#[cfg(feature = "rocksdb")] -fn load_smt(storage: S) -> Result, StateInitializationError> { - LargeSmt::new(storage).map_err(account_tree_large_smt_error_to_init_error) -} - -/// Trait for loading trees from storage. -/// -/// For `MemoryStorage`, the tree is rebuilt from database entries on each startup. -/// For `RocksDbStorage`, the tree is loaded directly from disk (much faster for large trees). -// TODO handle on disk rocksdb storage file being missing and/or corrupted. -trait StorageLoader: SmtStorage + Sized { - /// Creates a storage backend for the given domain. - fn create(data_dir: &Path, domain: &'static str) -> Result; - - /// Loads an account tree, either from persistent storage or by rebuilding from DB. - fn load_account_tree( - self, - db: &mut Db, - ) -> impl std::future::Future, StateInitializationError>> + Send; - - /// Loads a nullifier tree, either from persistent storage or by rebuilding from DB. - fn load_nullifier_tree( - self, - db: &mut Db, - ) -> impl std::future::Future< - Output = Result>, StateInitializationError>, - > + Send; -} - -#[cfg(not(feature = "rocksdb"))] -impl StorageLoader for MemoryStorage { - fn create(_data_dir: &Path, _domain: &'static str) -> Result { - Ok(MemoryStorage::default()) - } - - async fn load_account_tree( - self, - db: &mut Db, - ) -> Result, StateInitializationError> { - let account_data = db.select_all_account_commitments().await?; - let smt_entries = account_data - .into_iter() - .map(|(id, commitment)| (account_id_to_smt_key(id), commitment)); - LargeSmt::with_entries(self, smt_entries) - .map_err(account_tree_large_smt_error_to_init_error) - } - - async fn load_nullifier_tree( - self, - db: &mut Db, - ) -> Result>, StateInitializationError> { - let nullifiers = db.select_all_nullifiers().await?; - let entries = nullifiers.into_iter().map(|info| (info.nullifier, info.block_num)); - NullifierTree::with_storage_from_entries(self, entries) - .map_err(StateInitializationError::FailedToCreateNullifierTree) - } -} - -#[cfg(feature = "rocksdb")] -impl StorageLoader for RocksDbStorage { - fn create(data_dir: &Path, domain: &'static str) -> Result { - let storage_path = data_dir.join(domain); - fs_err::create_dir_all(&storage_path) - .map_err(|e| StateInitializationError::AccountTreeIoError(e.to_string()))?; - RocksDbStorage::open(RocksDbConfig::new(storage_path)) - .map_err(|e| StateInitializationError::AccountTreeIoError(e.to_string())) - } - - async fn load_account_tree( - self, - db: &mut Db, - ) -> Result, StateInitializationError> { - // If RocksDB storage has data, load from it directly - let has_data = self - .has_leaves() - .map_err(|e| StateInitializationError::AccountTreeIoError(e.to_string()))?; - if has_data { - return load_smt(self); - } - - info!(target: COMPONENT, "RocksDB account tree storage is empty, populating from SQLite"); - let account_data = db.select_all_account_commitments().await?; - let smt_entries = account_data - .into_iter() - .map(|(id, commitment)| (account_id_to_smt_key(id), commitment)); - LargeSmt::with_entries(self, smt_entries) - .map_err(account_tree_large_smt_error_to_init_error) - } - - async fn load_nullifier_tree( - self, - db: &mut Db, - ) -> Result>, StateInitializationError> { - // If RocksDB storage has data, load from it directly - let has_data = self - .has_leaves() - .map_err(|e| StateInitializationError::NullifierTreeIoError(e.to_string()))?; - if has_data { - let smt = load_smt(self)?; - return Ok(NullifierTree::new_unchecked(smt)); - } - - info!(target: COMPONENT, "RocksDB nullifier tree storage is empty, populating from SQLite"); - let nullifiers = db.select_all_nullifiers().await?; - let entries = nullifiers.into_iter().map(|info| (info.nullifier, info.block_num)); - NullifierTree::with_storage_from_entries(self, entries) - .map_err(StateInitializationError::FailedToCreateNullifierTree) - } -} - /// Container for state that needs to be updated atomically. struct InnerState where S: SmtStorage, { - nullifier_tree: NullifierTree>, + nullifier_tree: miden_protocol::block::nullifier_tree::NullifierTree< + miden_protocol::crypto::merkle::smt::LargeSmt, + >, blockchain: Blockchain, account_tree: AccountTreeWithHistory, } @@ -279,15 +155,21 @@ impl State { let blockchain = load_mmr(&mut db).await?; let latest_block_num = blockchain.chain_tip().unwrap_or(BlockNumber::GENESIS); - let account_storage = TreeStorage::create(data_path, "accounttree")?; + let account_storage = TreeStorage::create(data_path, ACCOUNT_TREE_STORAGE_DIR)?; let smt = account_storage.load_account_tree(&mut db).await?; let account_tree = AccountTree::new(smt).map_err(StateInitializationError::FailedToCreateAccountsTree)?; - let account_tree = AccountTreeWithHistory::new(account_tree, latest_block_num); - let nullifier_storage = TreeStorage::create(data_path, "nullifiertree")?; + let nullifier_storage = TreeStorage::create(data_path, NULLIFIER_TREE_STORAGE_DIR)?; let nullifier_tree = nullifier_storage.load_nullifier_tree(&mut db).await?; + // Verify that tree roots match the expected roots from the database. + // This catches any divergence between persistent storage and the database caused by + // corruption or incomplete shutdown. + verify_tree_consistency(account_tree.root(), nullifier_tree.root(), &mut db).await?; + + let account_tree = AccountTreeWithHistory::new(account_tree, latest_block_num); + let forest = load_smt_forest(&mut db, latest_block_num).await?; let inner = RwLock::new(InnerState { nullifier_tree, blockchain, account_tree }); @@ -1333,59 +1215,3 @@ impl State { self.db.select_transactions_records(account_ids, block_range).await } } - -// INNER STATE LOADING -// ================================================================================================ - -#[instrument(level = "info", target = COMPONENT, skip_all)] -async fn load_mmr(db: &mut Db) -> Result { - let block_commitments: Vec = db - .select_all_block_headers() - .await? - .iter() - .map(BlockHeader::commitment) - .collect(); - - // SAFETY: We assume the loaded MMR is valid and does not have more than u32::MAX - // entries. - let chain_mmr = Blockchain::from_mmr_unchecked(block_commitments.into()); - - Ok(chain_mmr) -} - -/// Loads SMT forest with storage map and vault Merkle paths for all public accounts. -#[instrument(target = COMPONENT, skip_all, fields(block_num = %block_num))] -async fn load_smt_forest( - db: &mut Db, - block_num: BlockNumber, -) -> Result { - use miden_protocol::account::delta::AccountDelta; - - let public_account_ids = db.select_all_public_account_ids().await?; - - // Acquire write lock once for the entire initialization - let mut forest = InnerForest::new(); - - // Process each account - for account_id in public_account_ids { - // Get the full account from the database - let account_info = db.select_account(account_id).await?; - let account = account_info.details.expect("public accounts always have details in DB"); - - // Convert the full account to a full-state delta - let delta = - AccountDelta::try_from(account).expect("accounts from DB should not have seeds"); - - // Use the unified update method (will recognize it's a full-state delta) - forest.update_account(block_num, &delta)?; - - tracing::debug!( - target: COMPONENT, - %account_id, - %block_num, - "Initialized forest for account from DB" - ); - } - - Ok(forest) -} From cbbad6e112c9a058bb32003443b893499c704ba8 Mon Sep 17 00:00:00 2001 From: Bernhard Schuster Date: Tue, 20 Jan 2026 14:25:24 +0100 Subject: [PATCH 2/3] fmt --- bin/node/src/commands/store.rs | 10 ++++++++-- crates/store/src/state/loader.rs | 7 +------ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/bin/node/src/commands/store.rs b/bin/node/src/commands/store.rs index 49fd5a7b84..2105b14530 100644 --- a/bin/node/src/commands/store.rs +++ b/bin/node/src/commands/store.rs @@ -119,8 +119,14 @@ impl StoreCommand { enable_otel: _, grpc_timeout, } => { - Self::start(rpc_url, ntx_builder_url, block_producer_url, data_directory, grpc_timeout) - .await + Self::start( + rpc_url, + ntx_builder_url, + block_producer_url, + data_directory, + grpc_timeout, + ) + .await }, } } diff --git a/crates/store/src/state/loader.rs b/crates/store/src/state/loader.rs index ac50726223..edf332c16f 100644 --- a/crates/store/src/state/loader.rs +++ b/crates/store/src/state/loader.rs @@ -275,11 +275,7 @@ pub async fn verify_tree_consistency( let latest_header = db.select_block_header_by_block_num(None).await?; let (block_num, expected_account_root, expected_nullifier_root) = latest_header - .map(|header| ( - header.block_num(), - header.account_root(), - header.nullifier_root(), - )) + .map(|header| (header.block_num(), header.account_root(), header.nullifier_root())) .unwrap_or_default(); // Verify account tree root @@ -302,6 +298,5 @@ pub async fn verify_tree_consistency( }); } - Ok(()) } From 1262edd12baa63697aa18b88ec867f2d06d97e57 Mon Sep 17 00:00:00 2001 From: Bernhard Schuster Date: Tue, 20 Jan 2026 14:37:54 +0100 Subject: [PATCH 3/3] log --- crates/store/src/state/loader.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/crates/store/src/state/loader.rs b/crates/store/src/state/loader.rs index edf332c16f..4aa8e2590b 100644 --- a/crates/store/src/state/loader.rs +++ b/crates/store/src/state/loader.rs @@ -237,13 +237,6 @@ pub async fn load_smt_forest( // Use the unified update method (will recognize it's a full-state delta) forest.update_account(block_num, &delta)?; - - tracing::debug!( - target: COMPONENT, - %account_id, - %block_num, - "Initialized forest for account from DB" - ); } Ok(forest)