miden_node_store/accounts/
mod.rs1use std::collections::{BTreeMap, HashMap};
4
5#[cfg(feature = "rocksdb")]
6use miden_large_smt_backend_rocksdb::RocksDbStorage;
7use miden_node_utils::tracing::miden_instrument;
8use miden_protocol::account::{AccountId, AccountIdPrefix};
9use miden_protocol::block::BlockNumber;
10use miden_protocol::block::account_tree::{AccountMutationSet, AccountTree, AccountWitness};
11use miden_protocol::crypto::merkle::smt::{
12 LargeSmt,
13 LeafIndex,
14 MemoryStorage,
15 NodeMutation,
16 SMT_DEPTH,
17 SmtLeaf,
18 SmtStorage,
19};
20use miden_protocol::crypto::merkle::{
21 EmptySubtreeRoots,
22 MerkleError,
23 MerklePath,
24 NodeIndex,
25 SparseMerklePath,
26};
27use miden_protocol::errors::AccountTreeError;
28use miden_protocol::{EMPTY_WORD, Word};
29
30use crate::COMPONENT;
31
32#[cfg(test)]
33mod tests;
34
35pub type InMemoryAccountTree = AccountTree<LargeSmt<MemoryStorage>>;
37
38#[cfg(feature = "rocksdb")]
39pub type PersistentAccountTree = AccountTree<LargeSmt<RocksDbStorage>>;
41
42#[expect(missing_docs)]
46#[derive(thiserror::Error, Debug)]
47pub enum HistoricalError {
48 #[error(transparent)]
49 MerkleError(#[from] MerkleError),
50 #[error(transparent)]
51 AccountTreeError(#[from] AccountTreeError),
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58enum HistoricalSelector {
59 Future,
61 At(BlockNumber),
63 Latest,
65 TooAncient,
67}
68
69#[derive(Debug, Clone)]
74struct HistoricalOverlay {
75 block_number: BlockNumber,
76 root: Word,
77 node_mutations: HashMap<NodeIndex, Word>,
78 account_updates: HashMap<LeafIndex<SMT_DEPTH>, (Word, Word)>,
79}
80
81impl HistoricalOverlay {
82 fn new(block_number: BlockNumber, rev_set: AccountMutationSet) -> Self {
83 let root = rev_set.as_mutation_set().root();
84 let mut_set = rev_set.into_mutation_set();
85
86 let node_mutations =
87 HashMap::from_iter(mut_set.node_mutations().iter().map(|(node_index, mutation)| {
88 match mutation {
89 NodeMutation::Addition(inner_node) => (*node_index, inner_node.hash()),
90 NodeMutation::Removal => {
91 let empty_root = *EmptySubtreeRoots::entry(SMT_DEPTH, node_index.depth());
94 (*node_index, empty_root)
95 },
96 }
97 }));
98
99 let account_updates = HashMap::from_iter(
100 mut_set.new_pairs().iter().map(|(&k, &v)| (LeafIndex::from(k), (k, v))),
101 );
102
103 Self {
104 block_number,
105 root,
106 node_mutations,
107 account_updates,
108 }
109 }
110}
111
112#[derive(Debug)]
121pub struct AccountTreeWithHistory<S: SmtStorage> {
122 block_number: BlockNumber,
124 latest: AccountTree<LargeSmt<S>>,
126 overlays: BTreeMap<BlockNumber, HistoricalOverlay>,
128}
129
130impl<S: SmtStorage> AccountTreeWithHistory<S> {
131 pub const MAX_HISTORY: usize = 50;
133
134 pub fn new(account_tree: AccountTree<LargeSmt<S>>, block_number: BlockNumber) -> Self {
139 Self {
140 block_number,
141 latest: account_tree,
142 overlays: BTreeMap::new(),
143 }
144 }
145
146 fn drain_excess(overlays: &mut BTreeMap<BlockNumber, HistoricalOverlay>) {
148 while overlays.len() > Self::MAX_HISTORY {
149 overlays.pop_first();
150 }
151 }
152
153 pub fn block_number_latest(&self) -> BlockNumber {
158 self.block_number
159 }
160
161 pub fn root_latest(&self) -> Word {
163 self.latest.root()
164 }
165
166 pub fn root_at(&self, block_number: BlockNumber) -> Option<Word> {
170 match self.historical_selector(block_number) {
171 HistoricalSelector::Latest => Some(self.latest.root()),
172 HistoricalSelector::At(block_number) => {
173 let overlay = self.overlays.get(&block_number)?;
174 debug_assert_eq!(overlay.block_number, block_number);
175 Some(overlay.root)
176 },
177 HistoricalSelector::Future | HistoricalSelector::TooAncient => None,
178 }
179 }
180
181 pub fn num_accounts_latest(&self) -> usize {
183 self.latest.num_accounts()
184 }
185
186 pub fn history_len(&self) -> usize {
188 self.overlays.len()
189 }
190
191 #[miden_instrument(
193 target = COMPONENT,
194 skip_all,
195 )]
196 pub fn open_latest(&self, account_id: AccountId) -> AccountWitness {
197 self.latest.open(account_id)
198 }
199
200 #[miden_instrument(
209 target = COMPONENT,
210 skip_all,
211 )]
212 pub fn open_at(
213 &self,
214 account_id: AccountId,
215 block_number: BlockNumber,
216 ) -> Option<AccountWitness> {
217 match self.historical_selector(block_number) {
218 HistoricalSelector::Latest => Some(self.latest.open(account_id)),
219 HistoricalSelector::At(block_number) => {
220 self.overlays.get(&block_number)?;
222 Self::reconstruct_historical_witness(self, account_id, block_number)
223 },
224 HistoricalSelector::Future | HistoricalSelector::TooAncient => None,
225 }
226 }
227
228 pub fn get_latest_commitment(&self, account_id: AccountId) -> Word {
230 self.latest.get(account_id)
231 }
232
233 pub fn contains_account_id_prefix_in_latest(&self, prefix: AccountIdPrefix) -> bool {
235 self.latest.contains_account_id_prefix(prefix)
236 }
237
238 fn historical_selector(&self, desired_block_number: BlockNumber) -> HistoricalSelector {
243 if desired_block_number == self.block_number {
244 return HistoricalSelector::Latest;
245 }
246
247 if self.block_number.checked_sub(desired_block_number.as_u32()).is_none() {
249 return HistoricalSelector::Future;
250 }
251
252 if !self.overlays.contains_key(&desired_block_number) {
254 return HistoricalSelector::TooAncient;
255 }
256
257 HistoricalSelector::At(desired_block_number)
258 }
259
260 #[miden_instrument(
262 target = COMPONENT,
263 skip_all,
264 )]
265 fn reconstruct_historical_witness(
266 &self,
267 account_id: AccountId,
268 block_target: BlockNumber,
269 ) -> Option<AccountWitness> {
270 let latest_witness = self.latest.open(account_id);
272 let (latest_path, leaf) = latest_witness.into_proof().into_parts();
273 let path_nodes = Self::initialize_path_nodes(&latest_path);
274
275 let leaf_index = NodeIndex::from(leaf.index());
276
277 let (path, leaf) = Self::apply_reversion_overlays(
281 self.overlays.range(block_target..).rev().map(|(_, overlay)| overlay),
282 path_nodes,
283 leaf_index,
284 leaf,
285 )?;
286
287 let commitment = match leaf {
289 SmtLeaf::Empty(_) => EMPTY_WORD,
290 SmtLeaf::Single((_, value)) => value,
291 SmtLeaf::Multiple(_) => unreachable!("AccountTree uses prefix-free IDs"),
292 };
293
294 AccountWitness::new(account_id, commitment, path).ok()
295 }
296
297 fn initialize_path_nodes(path: &SparseMerklePath) -> [Word; SMT_DEPTH as usize] {
301 let mut path_nodes: [Word; SMT_DEPTH as usize] = MerklePath::from(path.clone())
302 .to_vec()
303 .try_into()
304 .expect("MerklePath should have exactly SMT_DEPTH nodes");
305 path_nodes.reverse();
306 path_nodes
307 }
308
309 #[miden_instrument(
314 target = COMPONENT,
315 skip_all,
316 )]
317 fn apply_reversion_overlays<'a>(
318 overlays: impl IntoIterator<Item = &'a HistoricalOverlay>,
319 mut path_nodes: [Word; SMT_DEPTH as usize],
320 leaf_index: NodeIndex,
321 mut leaf: SmtLeaf,
322 ) -> Option<(SparseMerklePath, SmtLeaf)> {
323 for overlay in overlays {
325 for sibling in leaf_index.proof_indices() {
327 let height = sibling
328 .depth()
329 .checked_sub(1) .expect("proof_indices should not include root")
331 as usize;
332
333 if let Some(hash) = overlay.node_mutations.get(&sibling) {
337 path_nodes[height] = *hash;
338 }
339 }
340
341 if let Some(&(key, value)) = overlay.account_updates.get(&leaf.index()) {
343 leaf = if value == EMPTY_WORD {
344 SmtLeaf::new_empty(leaf.index())
345 } else {
346 SmtLeaf::new_single(key, value)
347 };
348 }
349 }
350
351 let dense: Vec<Word> = path_nodes.iter().rev().copied().collect();
354 let path = MerklePath::new(dense);
355 let path = SparseMerklePath::try_from(path).ok()?;
356 Some((path, leaf))
357 }
358
359 pub fn compute_and_apply_mutations(
366 &mut self,
367 account_commitments: impl IntoIterator<Item = (AccountId, Word)>,
368 ) -> Result<(), HistoricalError> {
369 let mutations = self.compute_mutations(account_commitments)?;
370 self.apply_mutations(mutations)
371 }
372
373 pub fn compute_mutations(
375 &self,
376 account_commitments: impl IntoIterator<Item = (AccountId, Word)>,
377 ) -> Result<AccountMutationSet, HistoricalError> {
378 Ok(self.latest.compute_mutations(account_commitments)?)
379 }
380
381 #[miden_instrument(
389 target = COMPONENT,
390 skip_all,
391 )]
392 pub fn apply_mutations(
393 &mut self,
394 mutations: AccountMutationSet,
395 ) -> Result<(), HistoricalError> {
396 let rev = self.latest.apply_mutations_with_reversion(mutations)?;
398
399 let block_num = self.block_number;
401 let overlay = HistoricalOverlay::new(block_num, rev);
402 self.overlays.insert(block_num, overlay);
403
404 self.block_number = block_num.child();
406
407 Self::drain_excess(&mut self.overlays);
409
410 Ok(())
411 }
412}