miden_node_store/state/
loader.rs1use std::future::Future;
12use std::path::Path;
13
14use miden_protocol::Word;
15use miden_protocol::block::account_tree::{AccountTree, account_id_to_smt_key};
16use miden_protocol::block::nullifier_tree::NullifierTree;
17use miden_protocol::block::{BlockHeader, BlockNumber, Blockchain};
18#[cfg(not(feature = "rocksdb"))]
19use miden_protocol::crypto::merkle::smt::MemoryStorage;
20use miden_protocol::crypto::merkle::smt::{LargeSmt, LargeSmtError, SmtStorage};
21#[cfg(feature = "rocksdb")]
22use tracing::info;
23use tracing::instrument;
24#[cfg(feature = "rocksdb")]
25use {
26 miden_crypto::merkle::smt::RocksDbStorage,
27 miden_protocol::crypto::merkle::smt::RocksDbConfig,
28};
29
30use crate::COMPONENT;
31use crate::db::Db;
32use crate::errors::{DatabaseError, StateInitializationError};
33use crate::inner_forest::InnerForest;
34
35pub const ACCOUNT_TREE_STORAGE_DIR: &str = "accounttree";
40
41pub const NULLIFIER_TREE_STORAGE_DIR: &str = "nullifiertree";
43
44#[cfg(feature = "rocksdb")]
49pub type TreeStorage = RocksDbStorage;
50#[cfg(not(feature = "rocksdb"))]
51pub type TreeStorage = MemoryStorage;
52
53pub fn account_tree_large_smt_error_to_init_error(e: LargeSmtError) -> StateInitializationError {
58 use miden_node_utils::ErrorReport;
59 match e {
60 LargeSmtError::Merkle(merkle_error) => {
61 StateInitializationError::DatabaseError(DatabaseError::MerkleError(merkle_error))
62 },
63 LargeSmtError::Storage(err) => {
64 StateInitializationError::AccountTreeIoError(err.as_report())
65 },
66 }
67}
68
69pub trait StorageLoader: SmtStorage + Sized {
81 fn create(data_dir: &Path, domain: &'static str) -> Result<Self, StateInitializationError>;
83
84 fn load_account_tree(
86 self,
87 db: &mut Db,
88 ) -> impl Future<Output = Result<AccountTree<LargeSmt<Self>>, StateInitializationError>> + Send;
89
90 fn load_nullifier_tree(
92 self,
93 db: &mut Db,
94 ) -> impl Future<Output = Result<NullifierTree<LargeSmt<Self>>, StateInitializationError>> + Send;
95}
96
97#[cfg(not(feature = "rocksdb"))]
101impl StorageLoader for MemoryStorage {
102 fn create(_data_dir: &Path, _domain: &'static str) -> Result<Self, StateInitializationError> {
103 Ok(MemoryStorage::default())
104 }
105
106 async fn load_account_tree(
107 self,
108 db: &mut Db,
109 ) -> Result<AccountTree<LargeSmt<Self>>, StateInitializationError> {
110 let account_data = db.select_all_account_commitments().await?;
111 let smt_entries = account_data
112 .into_iter()
113 .map(|(id, commitment)| (account_id_to_smt_key(id), commitment));
114 let smt = LargeSmt::with_entries(self, smt_entries)
115 .map_err(account_tree_large_smt_error_to_init_error)?;
116 AccountTree::new(smt).map_err(StateInitializationError::FailedToCreateAccountsTree)
117 }
118
119 async fn load_nullifier_tree(
120 self,
121 db: &mut Db,
122 ) -> Result<NullifierTree<LargeSmt<Self>>, StateInitializationError> {
123 let nullifiers = db.select_all_nullifiers().await?;
124 let entries = nullifiers.into_iter().map(|info| (info.nullifier, info.block_num));
125 NullifierTree::with_storage_from_entries(self, entries)
126 .map_err(StateInitializationError::FailedToCreateNullifierTree)
127 }
128}
129
130#[cfg(feature = "rocksdb")]
134impl StorageLoader for RocksDbStorage {
135 fn create(data_dir: &Path, domain: &'static str) -> Result<Self, StateInitializationError> {
136 let storage_path = data_dir.join(domain);
137
138 fs_err::create_dir_all(&storage_path)
139 .map_err(|e| StateInitializationError::AccountTreeIoError(e.to_string()))?;
140 RocksDbStorage::open(RocksDbConfig::new(storage_path))
141 .map_err(|e| StateInitializationError::AccountTreeIoError(e.to_string()))
142 }
143
144 async fn load_account_tree(
145 self,
146 db: &mut Db,
147 ) -> Result<AccountTree<LargeSmt<Self>>, StateInitializationError> {
148 let has_data = self
150 .has_leaves()
151 .map_err(|e| StateInitializationError::AccountTreeIoError(e.to_string()))?;
152 if has_data {
153 let smt = load_smt(self)?;
154 return AccountTree::new(smt)
155 .map_err(StateInitializationError::FailedToCreateAccountsTree);
156 }
157
158 info!(target: COMPONENT, "RocksDB account tree storage is empty, populating from SQLite");
159 let account_data = db.select_all_account_commitments().await?;
160 let smt_entries = account_data
161 .into_iter()
162 .map(|(id, commitment)| (account_id_to_smt_key(id), commitment));
163 let smt = LargeSmt::with_entries(self, smt_entries)
164 .map_err(account_tree_large_smt_error_to_init_error)?;
165 AccountTree::new(smt).map_err(StateInitializationError::FailedToCreateAccountsTree)
166 }
167
168 async fn load_nullifier_tree(
169 self,
170 db: &mut Db,
171 ) -> Result<NullifierTree<LargeSmt<Self>>, StateInitializationError> {
172 let has_data = self
174 .has_leaves()
175 .map_err(|e| StateInitializationError::NullifierTreeIoError(e.to_string()))?;
176 if has_data {
177 let smt = load_smt(self)?;
178 return Ok(NullifierTree::new_unchecked(smt));
179 }
180
181 info!(target: COMPONENT, "RocksDB nullifier tree storage is empty, populating from SQLite");
182 let nullifiers = db.select_all_nullifiers().await?;
183 let entries = nullifiers.into_iter().map(|info| (info.nullifier, info.block_num));
184 NullifierTree::with_storage_from_entries(self, entries)
185 .map_err(StateInitializationError::FailedToCreateNullifierTree)
186 }
187}
188
189#[cfg(feature = "rocksdb")]
194pub fn load_smt<S: SmtStorage>(storage: S) -> Result<LargeSmt<S>, StateInitializationError> {
195 LargeSmt::new(storage).map_err(account_tree_large_smt_error_to_init_error)
196}
197
198#[instrument(target = COMPONENT, skip_all)]
203pub async fn load_mmr(db: &mut Db) -> Result<Blockchain, StateInitializationError> {
204 let block_commitments: Vec<miden_protocol::Word> = db
205 .select_all_block_headers()
206 .await?
207 .iter()
208 .map(BlockHeader::commitment)
209 .collect();
210
211 let chain_mmr = Blockchain::from_mmr_unchecked(block_commitments.into());
214
215 Ok(chain_mmr)
216}
217
218#[instrument(target = COMPONENT, skip_all, fields(block_num = %block_num))]
220pub async fn load_smt_forest(
221 db: &mut Db,
222 block_num: BlockNumber,
223) -> Result<InnerForest, StateInitializationError> {
224 use miden_protocol::account::delta::AccountDelta;
225
226 let public_account_ids = db.select_all_public_account_ids().await?;
227
228 let mut forest = InnerForest::new();
230
231 for account_id in public_account_ids {
233 let account_info = db.select_account(account_id).await?;
235 let account = account_info.details.expect("public accounts always have details in DB");
236
237 let delta =
239 AccountDelta::try_from(account).expect("accounts from DB should not have seeds");
240
241 forest.update_account(block_num, &delta)?;
243 }
244
245 Ok(forest)
246}
247
248#[instrument(target = COMPONENT, skip_all)]
265pub async fn verify_tree_consistency(
266 account_tree_root: Word,
267 nullifier_tree_root: Word,
268 db: &mut Db,
269) -> Result<(), StateInitializationError> {
270 let latest_header = db.select_block_header_by_block_num(None).await?;
272
273 let (block_num, expected_account_root, expected_nullifier_root) = latest_header
274 .map(|header| (header.block_num(), header.account_root(), header.nullifier_root()))
275 .unwrap_or_default();
276
277 if account_tree_root != expected_account_root {
279 return Err(StateInitializationError::TreeStorageDiverged {
280 tree_name: "Account",
281 block_num,
282 tree_root: account_tree_root,
283 block_root: expected_account_root,
284 });
285 }
286
287 if nullifier_tree_root != expected_nullifier_root {
289 return Err(StateInitializationError::TreeStorageDiverged {
290 tree_name: "Nullifier",
291 block_num,
292 tree_root: nullifier_tree_root,
293 block_root: expected_nullifier_root,
294 });
295 }
296
297 Ok(())
298}