miden_node_store/accounts/
mod.rs1use std::collections::{BTreeMap, HashMap};
4
5#[cfg(feature = "rocksdb")]
6use miden_crypto::merkle::smt::RocksDbStorage;
7use miden_node_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 SmtStorageReader,
20};
21use miden_protocol::crypto::merkle::{
22 EmptySubtreeRoots,
23 MerkleError,
24 MerklePath,
25 NodeIndex,
26 SparseMerklePath,
27};
28use miden_protocol::errors::AccountTreeError;
29use miden_protocol::{EMPTY_WORD, Word};
30
31use crate::COMPONENT;
32
33#[cfg(test)]
34mod tests;
35
36pub type InMemoryAccountTree = AccountTree<LargeSmt<MemoryStorage>>;
38
39#[cfg(feature = "rocksdb")]
40pub type PersistentAccountTree = AccountTree<LargeSmt<RocksDbStorage>>;
42
43#[expect(missing_docs)]
47#[derive(thiserror::Error, Debug)]
48pub enum HistoricalError {
49 #[error(transparent)]
50 MerkleError(#[from] MerkleError),
51 #[error(transparent)]
52 AccountTreeError(#[from] AccountTreeError),
53}
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59enum HistoricalSelector {
60 Future,
62 At(BlockNumber),
64 Latest,
66 TooAncient,
68}
69
70#[derive(Debug, Clone)]
75struct HistoricalOverlay {
76 block_number: BlockNumber,
77 root: Word,
78 node_mutations: HashMap<NodeIndex, Word>,
79 account_updates: HashMap<LeafIndex<SMT_DEPTH>, (Word, Word)>,
80}
81
82impl HistoricalOverlay {
83 fn new(block_number: BlockNumber, rev_set: AccountMutationSet) -> Self {
84 let root = rev_set.as_mutation_set().root();
85 let mut_set = rev_set.into_mutation_set();
86
87 let node_mutations = mut_set
88 .node_mutations()
89 .iter()
90 .map(|(node_index, mutation)| {
91 match mutation {
92 NodeMutation::Addition(inner_node) => (*node_index, inner_node.hash()),
93 NodeMutation::Removal => {
94 let empty_root = *EmptySubtreeRoots::entry(SMT_DEPTH, node_index.depth());
97 (*node_index, empty_root)
98 },
99 }
100 })
101 .collect::<HashMap<_, _>>();
102
103 let account_updates = mut_set
104 .new_pairs()
105 .iter()
106 .map(|(&k, &v)| (LeafIndex::from(k), (k, v)))
107 .collect::<HashMap<_, _>>();
108
109 Self {
110 block_number,
111 root,
112 node_mutations,
113 account_updates,
114 }
115 }
116}
117
118#[derive(Debug)]
127pub struct AccountTreeWithHistory<S: SmtStorageReader> {
128 block_number: BlockNumber,
130 latest: AccountTree<LargeSmt<S>>,
132 overlays: BTreeMap<BlockNumber, HistoricalOverlay>,
134}
135
136impl<S: SmtStorageReader> AccountTreeWithHistory<S> {
137 pub const MAX_HISTORY: usize = 50;
139
140 pub fn new(account_tree: AccountTree<LargeSmt<S>>, block_number: BlockNumber) -> Self {
145 Self {
146 block_number,
147 latest: account_tree,
148 overlays: BTreeMap::new(),
149 }
150 }
151
152 fn drain_excess(overlays: &mut BTreeMap<BlockNumber, HistoricalOverlay>) {
154 while overlays.len() > Self::MAX_HISTORY {
155 overlays.pop_first();
156 }
157 }
158
159 pub fn block_number_latest(&self) -> BlockNumber {
164 self.block_number
165 }
166
167 pub fn root_latest(&self) -> Word {
169 self.latest.root()
170 }
171
172 pub fn root_at(&self, block_number: BlockNumber) -> Option<Word> {
176 match self.historical_selector(block_number) {
177 HistoricalSelector::Latest => Some(self.latest.root()),
178 HistoricalSelector::At(block_number) => {
179 let overlay = self.overlays.get(&block_number)?;
180 debug_assert_eq!(overlay.block_number, block_number);
181 Some(overlay.root)
182 },
183 HistoricalSelector::Future | HistoricalSelector::TooAncient => None,
184 }
185 }
186
187 pub fn num_accounts_latest(&self) -> usize {
189 self.latest.num_accounts()
190 }
191
192 pub fn history_len(&self) -> usize {
194 self.overlays.len()
195 }
196
197 #[miden_instrument(
199 target = COMPONENT,
200 )]
201 pub fn open_latest(&self, account_id: AccountId) -> AccountWitness {
202 self.latest.open(account_id)
203 }
204
205 #[miden_instrument(
214 target = COMPONENT,
215 )]
216 pub fn open_at(
217 &self,
218 account_id: AccountId,
219 block_number: BlockNumber,
220 ) -> Option<AccountWitness> {
221 match self.historical_selector(block_number) {
222 HistoricalSelector::Latest => Some(self.latest.open(account_id)),
223 HistoricalSelector::At(block_number) => {
224 self.overlays.get(&block_number)?;
226 Self::reconstruct_historical_witness(self, account_id, block_number)
227 },
228 HistoricalSelector::Future | HistoricalSelector::TooAncient => None,
229 }
230 }
231
232 pub fn get_latest_commitment(&self, account_id: AccountId) -> Word {
234 self.latest.get(account_id)
235 }
236
237 pub fn contains_account_id_prefix_in_latest(&self, prefix: AccountIdPrefix) -> bool {
239 self.latest.contains_account_id_prefix(prefix)
240 }
241
242 fn historical_selector(&self, desired_block_number: BlockNumber) -> HistoricalSelector {
247 if desired_block_number == self.block_number {
248 return HistoricalSelector::Latest;
249 }
250
251 if self.block_number.checked_sub(desired_block_number.as_u32()).is_none() {
253 return HistoricalSelector::Future;
254 }
255
256 if !self.overlays.contains_key(&desired_block_number) {
258 return HistoricalSelector::TooAncient;
259 }
260
261 HistoricalSelector::At(desired_block_number)
262 }
263
264 #[miden_instrument(
266 target = COMPONENT,
267 )]
268 fn reconstruct_historical_witness(
269 &self,
270 account_id: AccountId,
271 block_target: BlockNumber,
272 ) -> Option<AccountWitness> {
273 let latest_witness = self.latest.open(account_id);
275 let (latest_path, leaf) = latest_witness.into_proof().into_parts();
276 let path_nodes = Self::initialize_path_nodes(&latest_path);
277
278 let leaf_index = NodeIndex::from(leaf.index());
279
280 let (path, leaf) = Self::apply_reversion_overlays(
284 self.overlays.range(block_target..).rev().map(|(_, overlay)| overlay),
285 path_nodes,
286 leaf_index,
287 leaf,
288 )?;
289
290 let commitment = match leaf {
292 SmtLeaf::Empty(_) => EMPTY_WORD,
293 SmtLeaf::Single((_, value)) => value,
294 SmtLeaf::Multiple(_) => unreachable!("AccountTree uses prefix-free IDs"),
295 };
296
297 AccountWitness::new(account_id, commitment, path).ok()
298 }
299
300 fn initialize_path_nodes(path: &SparseMerklePath) -> [Word; SMT_DEPTH as usize] {
304 let mut path_nodes: [Word; SMT_DEPTH as usize] = MerklePath::from(path.clone())
305 .to_vec()
306 .try_into()
307 .expect("MerklePath should have exactly SMT_DEPTH nodes");
308 path_nodes.reverse();
309 path_nodes
310 }
311
312 #[miden_instrument(
317 target = COMPONENT,
318 )]
319 fn apply_reversion_overlays<'a>(
320 overlays: impl IntoIterator<Item = &'a HistoricalOverlay>,
321 mut path_nodes: [Word; SMT_DEPTH as usize],
322 leaf_index: NodeIndex,
323 mut leaf: SmtLeaf,
324 ) -> Option<(SparseMerklePath, SmtLeaf)> {
325 for overlay in overlays {
327 for sibling in leaf_index.proof_indices() {
329 let height = sibling
330 .depth()
331 .checked_sub(1) .expect("proof_indices should not include root")
333 as usize;
334
335 if let Some(hash) = overlay.node_mutations.get(&sibling) {
339 path_nodes[height] = *hash;
340 }
341 }
342
343 if let Some(&(key, value)) = overlay.account_updates.get(&leaf.index()) {
345 leaf = if value == EMPTY_WORD {
346 SmtLeaf::new_empty(leaf.index())
347 } else {
348 SmtLeaf::new_single(key, value)
349 };
350 }
351 }
352
353 let dense: Vec<Word> = path_nodes.iter().rev().copied().collect();
356 let path = MerklePath::new(dense);
357 let path = SparseMerklePath::try_from(path).ok()?;
358 Some((path, leaf))
359 }
360}
361
362impl<S: SmtStorage> AccountTreeWithHistory<S> {
363 pub fn compute_and_apply_mutations(
370 &mut self,
371 account_commitments: impl IntoIterator<Item = (AccountId, Word)>,
372 ) -> Result<(), HistoricalError> {
373 let mutations = self.compute_mutations(account_commitments)?;
374 self.apply_mutations(mutations)
375 }
376
377 pub fn compute_mutations(
379 &self,
380 account_commitments: impl IntoIterator<Item = (AccountId, Word)>,
381 ) -> Result<AccountMutationSet, HistoricalError> {
382 Ok(self.latest.compute_mutations(account_commitments)?)
383 }
384
385 #[miden_instrument(
393 target = COMPONENT,
394 )]
395 pub fn apply_mutations(
396 &mut self,
397 mutations: AccountMutationSet,
398 ) -> Result<(), HistoricalError> {
399 let rev = self.latest.apply_mutations_with_reversion(mutations)?;
401
402 let block_num = self.block_number;
404 let overlay = HistoricalOverlay::new(block_num, rev);
405 self.overlays.insert(block_num, overlay);
406
407 self.block_number = block_num.child();
409
410 Self::drain_excess(&mut self.overlays);
412
413 Ok(())
414 }
415
416 pub fn reader(&self) -> AccountTreeWithHistory<S::Reader> {
418 let latest = self.latest.reader().expect("snapshot creation should not fail");
419 AccountTreeWithHistory {
420 block_number: self.block_number,
421 latest,
422 overlays: self.overlays.clone(),
423 }
424 }
425}