miden_node_store/accounts/
mod.rs1use std::collections::{BTreeMap, HashMap};
4
5#[cfg(feature = "rocksdb")]
6use miden_crypto::merkle::smt::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 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 =
88 HashMap::from_iter(mut_set.node_mutations().iter().map(|(node_index, mutation)| {
89 match mutation {
90 NodeMutation::Addition(inner_node) => (*node_index, inner_node.hash()),
91 NodeMutation::Removal => {
92 let empty_root = *EmptySubtreeRoots::entry(SMT_DEPTH, node_index.depth());
95 (*node_index, empty_root)
96 },
97 }
98 }));
99
100 let account_updates = HashMap::from_iter(
101 mut_set.new_pairs().iter().map(|(&k, &v)| (LeafIndex::from(k), (k, v))),
102 );
103
104 Self {
105 block_number,
106 root,
107 node_mutations,
108 account_updates,
109 }
110 }
111}
112
113#[derive(Debug)]
122pub struct AccountTreeWithHistory<S: SmtStorageReader> {
123 block_number: BlockNumber,
125 latest: AccountTree<LargeSmt<S>>,
127 overlays: BTreeMap<BlockNumber, HistoricalOverlay>,
129}
130
131impl<S: SmtStorageReader> AccountTreeWithHistory<S> {
132 pub const MAX_HISTORY: usize = 50;
134
135 pub fn new(account_tree: AccountTree<LargeSmt<S>>, block_number: BlockNumber) -> Self {
140 Self {
141 block_number,
142 latest: account_tree,
143 overlays: BTreeMap::new(),
144 }
145 }
146
147 fn drain_excess(overlays: &mut BTreeMap<BlockNumber, HistoricalOverlay>) {
149 while overlays.len() > Self::MAX_HISTORY {
150 overlays.pop_first();
151 }
152 }
153
154 pub fn block_number_latest(&self) -> BlockNumber {
159 self.block_number
160 }
161
162 pub fn root_latest(&self) -> Word {
164 self.latest.root()
165 }
166
167 pub fn root_at(&self, block_number: BlockNumber) -> Option<Word> {
171 match self.historical_selector(block_number) {
172 HistoricalSelector::Latest => Some(self.latest.root()),
173 HistoricalSelector::At(block_number) => {
174 let overlay = self.overlays.get(&block_number)?;
175 debug_assert_eq!(overlay.block_number, block_number);
176 Some(overlay.root)
177 },
178 HistoricalSelector::Future | HistoricalSelector::TooAncient => None,
179 }
180 }
181
182 pub fn num_accounts_latest(&self) -> usize {
184 self.latest.num_accounts()
185 }
186
187 pub fn history_len(&self) -> usize {
189 self.overlays.len()
190 }
191
192 #[miden_instrument(
194 target = COMPONENT,
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 )]
211 pub fn open_at(
212 &self,
213 account_id: AccountId,
214 block_number: BlockNumber,
215 ) -> Option<AccountWitness> {
216 match self.historical_selector(block_number) {
217 HistoricalSelector::Latest => Some(self.latest.open(account_id)),
218 HistoricalSelector::At(block_number) => {
219 self.overlays.get(&block_number)?;
221 Self::reconstruct_historical_witness(self, account_id, block_number)
222 },
223 HistoricalSelector::Future | HistoricalSelector::TooAncient => None,
224 }
225 }
226
227 pub fn get_latest_commitment(&self, account_id: AccountId) -> Word {
229 self.latest.get(account_id)
230 }
231
232 pub fn contains_account_id_prefix_in_latest(&self, prefix: AccountIdPrefix) -> bool {
234 self.latest.contains_account_id_prefix(prefix)
235 }
236
237 fn historical_selector(&self, desired_block_number: BlockNumber) -> HistoricalSelector {
242 if desired_block_number == self.block_number {
243 return HistoricalSelector::Latest;
244 }
245
246 if self.block_number.checked_sub(desired_block_number.as_u32()).is_none() {
248 return HistoricalSelector::Future;
249 }
250
251 if !self.overlays.contains_key(&desired_block_number) {
253 return HistoricalSelector::TooAncient;
254 }
255
256 HistoricalSelector::At(desired_block_number)
257 }
258
259 #[miden_instrument(
261 target = COMPONENT,
262 )]
263 fn reconstruct_historical_witness(
264 &self,
265 account_id: AccountId,
266 block_target: BlockNumber,
267 ) -> Option<AccountWitness> {
268 let latest_witness = self.latest.open(account_id);
270 let (latest_path, leaf) = latest_witness.into_proof().into_parts();
271 let path_nodes = Self::initialize_path_nodes(&latest_path);
272
273 let leaf_index = NodeIndex::from(leaf.index());
274
275 let (path, leaf) = Self::apply_reversion_overlays(
279 self.overlays.range(block_target..).rev().map(|(_, overlay)| overlay),
280 path_nodes,
281 leaf_index,
282 leaf,
283 )?;
284
285 let commitment = match leaf {
287 SmtLeaf::Empty(_) => EMPTY_WORD,
288 SmtLeaf::Single((_, value)) => value,
289 SmtLeaf::Multiple(_) => unreachable!("AccountTree uses prefix-free IDs"),
290 };
291
292 AccountWitness::new(account_id, commitment, path).ok()
293 }
294
295 fn initialize_path_nodes(path: &SparseMerklePath) -> [Word; SMT_DEPTH as usize] {
299 let mut path_nodes: [Word; SMT_DEPTH as usize] = MerklePath::from(path.clone())
300 .to_vec()
301 .try_into()
302 .expect("MerklePath should have exactly SMT_DEPTH nodes");
303 path_nodes.reverse();
304 path_nodes
305 }
306
307 #[miden_instrument(
312 target = COMPONENT,
313 )]
314 fn apply_reversion_overlays<'a>(
315 overlays: impl IntoIterator<Item = &'a HistoricalOverlay>,
316 mut path_nodes: [Word; SMT_DEPTH as usize],
317 leaf_index: NodeIndex,
318 mut leaf: SmtLeaf,
319 ) -> Option<(SparseMerklePath, SmtLeaf)> {
320 for overlay in overlays {
322 for sibling in leaf_index.proof_indices() {
324 let height = sibling
325 .depth()
326 .checked_sub(1) .expect("proof_indices should not include root")
328 as usize;
329
330 if let Some(hash) = overlay.node_mutations.get(&sibling) {
334 path_nodes[height] = *hash;
335 }
336 }
337
338 if let Some(&(key, value)) = overlay.account_updates.get(&leaf.index()) {
340 leaf = if value == EMPTY_WORD {
341 SmtLeaf::new_empty(leaf.index())
342 } else {
343 SmtLeaf::new_single(key, value)
344 };
345 }
346 }
347
348 let dense: Vec<Word> = path_nodes.iter().rev().copied().collect();
351 let path = MerklePath::new(dense);
352 let path = SparseMerklePath::try_from(path).ok()?;
353 Some((path, leaf))
354 }
355}
356
357impl<S: SmtStorage> AccountTreeWithHistory<S> {
358 pub fn compute_and_apply_mutations(
365 &mut self,
366 account_commitments: impl IntoIterator<Item = (AccountId, Word)>,
367 ) -> Result<(), HistoricalError> {
368 let mutations = self.compute_mutations(account_commitments)?;
369 self.apply_mutations(mutations)
370 }
371
372 pub fn compute_mutations(
374 &self,
375 account_commitments: impl IntoIterator<Item = (AccountId, Word)>,
376 ) -> Result<AccountMutationSet, HistoricalError> {
377 Ok(self.latest.compute_mutations(account_commitments)?)
378 }
379
380 #[miden_instrument(
388 target = COMPONENT,
389 )]
390 pub fn apply_mutations(
391 &mut self,
392 mutations: AccountMutationSet,
393 ) -> Result<(), HistoricalError> {
394 let rev = self.latest.apply_mutations_with_reversion(mutations)?;
396
397 let block_num = self.block_number;
399 let overlay = HistoricalOverlay::new(block_num, rev);
400 self.overlays.insert(block_num, overlay);
401
402 self.block_number = block_num.child();
404
405 Self::drain_excess(&mut self.overlays);
407
408 Ok(())
409 }
410
411 pub fn reader(&self) -> AccountTreeWithHistory<S::Reader> {
413 let latest = self.latest.reader().expect("snapshot creation should not fail");
414 AccountTreeWithHistory {
415 block_number: self.block_number,
416 latest,
417 overlays: self.overlays.clone(),
418 }
419 }
420}