1#[cfg(feature = "trie-debug")]
2use crate::debug_recorder::TrieDebugRecorder;
3use crate::{
4 lfu::BucketedLfu,
5 provider::{TrieNodeProvider, TrieNodeProviderFactory},
6 traits::SparseTrie as SparseTrieTrait,
7 ParallelSparseTrie, RevealableSparseTrie,
8};
9use alloc::vec::Vec;
10use alloy_primitives::{map::B256Map, B256};
11use alloy_rlp::{Decodable, Encodable};
12use reth_execution_errors::{SparseStateTrieResult, SparseTrieErrorKind};
13use reth_primitives_traits::Account;
14use reth_trie_common::{
15 updates::{StorageTrieUpdates, TrieUpdates},
16 BranchNodeMasks, DecodedMultiProof, MultiProof, Nibbles, ProofTrieNodeV2, TrieAccount,
17 TrieNodeV2, EMPTY_ROOT_HASH, TRIE_ACCOUNT_RLP_MAX_SIZE,
18};
19#[cfg(feature = "std")]
20use tracing::debug;
21use tracing::{instrument, trace};
22
23#[derive(Debug, Default)]
28pub struct DeferredDrops {
29 pub proof_nodes_bufs: Vec<Vec<ProofTrieNodeV2>>,
31}
32
33#[derive(Debug)]
34pub struct SparseStateTrie<
36 A = ParallelSparseTrie, S = ParallelSparseTrie, > {
39 state: RevealableSparseTrie<A>,
41 storage: StorageTries<S>,
43 retain_updates: bool,
45 account_rlp_buf: Vec<u8>,
47 deferred_drops: DeferredDrops,
49 hot_slots_lfu: BucketedLfu<HotSlotKey>,
51 hot_accounts_lfu: BucketedLfu<B256>,
53 #[cfg(feature = "metrics")]
55 metrics: crate::metrics::SparseStateTrieMetrics,
56}
57
58impl<A, S> Default for SparseStateTrie<A, S>
59where
60 A: Default,
61 S: Default,
62{
63 fn default() -> Self {
64 Self {
65 state: Default::default(),
66 storage: Default::default(),
67 retain_updates: false,
68 account_rlp_buf: Vec::with_capacity(TRIE_ACCOUNT_RLP_MAX_SIZE),
69 deferred_drops: DeferredDrops::default(),
70 hot_slots_lfu: BucketedLfu::default(),
71 hot_accounts_lfu: BucketedLfu::default(),
72 #[cfg(feature = "metrics")]
73 metrics: Default::default(),
74 }
75 }
76}
77
78#[cfg(test)]
79impl SparseStateTrie {
80 pub fn from_state(state: RevealableSparseTrie) -> Self {
82 Self { state, ..Default::default() }
83 }
84}
85
86impl<A, S> SparseStateTrie<A, S> {
87 pub const fn set_updates(&mut self, retain_updates: bool) {
89 self.retain_updates = retain_updates;
90 }
91
92 pub const fn with_updates(mut self, retain_updates: bool) -> Self {
94 self.set_updates(retain_updates);
95 self
96 }
97
98 pub fn set_hot_cache_capacities(&mut self, max_hot_slots: usize, max_hot_accounts: usize) {
103 self.hot_slots_lfu.decay_and_evict(max_hot_slots);
104 self.hot_accounts_lfu.decay_and_evict(max_hot_accounts);
105 }
106
107 pub fn with_hot_cache_capacities(
109 mut self,
110 max_hot_slots: usize,
111 max_hot_accounts: usize,
112 ) -> Self {
113 self.set_hot_cache_capacities(max_hot_slots, max_hot_accounts);
114 self
115 }
116
117 pub fn set_accounts_trie(&mut self, trie: RevealableSparseTrie<A>) {
119 self.state = trie;
120 }
121
122 pub fn with_accounts_trie(mut self, trie: RevealableSparseTrie<A>) -> Self {
124 self.set_accounts_trie(trie);
125 self
126 }
127
128 pub fn set_default_storage_trie(&mut self, trie: RevealableSparseTrie<S>) {
131 self.storage.default_trie = trie;
132 }
133
134 pub fn with_default_storage_trie(mut self, trie: RevealableSparseTrie<S>) -> Self {
137 self.set_default_storage_trie(trie);
138 self
139 }
140
141 pub fn take_deferred_drops(&mut self) -> DeferredDrops {
146 core::mem::take(&mut self.deferred_drops)
147 }
148}
149
150impl SparseStateTrie {
151 pub fn new() -> Self {
153 Self::default()
154 }
155}
156
157impl<A: SparseTrieTrait, S: SparseTrieTrait> SparseStateTrie<A, S> {
158 #[cfg(feature = "trie-debug")]
163 pub fn take_debug_recorders(&mut self) -> alloc::vec::Vec<(Option<B256>, TrieDebugRecorder)> {
164 let mut recorders = alloc::vec::Vec::new();
165 if let Some(trie) = self.state.as_revealed_mut() {
166 recorders.push((None, trie.take_debug_recorder()));
167 }
168 for (address, trie) in &mut self.storage.tries {
169 if let Some(trie) = trie.as_revealed_mut() {
170 recorders.push((Some(*address), trie.take_debug_recorder()));
171 }
172 }
173 recorders
174 }
175}
176
177impl<A, S> SparseStateTrie<A, S>
178where
179 A: SparseTrieTrait + Default,
180 S: SparseTrieTrait + Default + Clone,
181{
182 pub const fn trie_mut(&mut self) -> &mut RevealableSparseTrie<A> {
184 &mut self.state
185 }
186
187 pub fn is_account_revealed(&self, account: B256) -> bool {
189 let path = Nibbles::unpack(account);
190 let trie = match self.state_trie_ref() {
191 Some(t) => t,
192 None => return false,
193 };
194
195 trie.find_leaf(&path, None).is_ok()
196 }
197
198 pub fn check_valid_storage_witness(&self, address: B256, slot: B256) -> bool {
200 let path = Nibbles::unpack(slot);
201 let trie = match self.storage_trie_ref(&address) {
202 Some(t) => t,
203 None => return false,
204 };
205
206 trie.find_leaf(&path, None).is_ok()
207 }
208
209 #[inline]
211 pub fn record_slot_touch(&mut self, account: B256, slot: B256) {
212 self.hot_slots_lfu.touch(HotSlotKey { address: account, slot });
213 }
214
215 #[inline]
217 pub fn record_account_touch(&mut self, account: B256) {
218 self.hot_accounts_lfu.touch(account);
219 }
220
221 pub fn get_account_value(&self, account: &B256) -> Option<&Vec<u8>> {
223 self.state.as_revealed_ref()?.get_leaf_value(&Nibbles::unpack(account))
224 }
225
226 pub fn get_storage_slot_value(&self, account: &B256, slot: &B256) -> Option<&Vec<u8>> {
228 self.storage.tries.get(account)?.as_revealed_ref()?.get_leaf_value(&Nibbles::unpack(slot))
229 }
230
231 pub const fn state_trie_ref(&self) -> Option<&A> {
233 self.state.as_revealed_ref()
234 }
235
236 pub fn storage_trie_ref(&self, address: &B256) -> Option<&S> {
238 self.storage.tries.get(address).and_then(|e| e.as_revealed_ref())
239 }
240
241 pub fn storage_trie_mut(&mut self, address: &B256) -> Option<&mut S> {
243 self.storage.tries.get_mut(address).and_then(|e| e.as_revealed_mut())
244 }
245
246 pub const fn storage_tries_mut(&mut self) -> &mut B256Map<RevealableSparseTrie<S>> {
248 &mut self.storage.tries
249 }
250
251 pub fn take_storage_trie(&mut self, address: &B256) -> Option<RevealableSparseTrie<S>> {
253 self.storage.tries.remove(address)
254 }
255
256 pub fn take_or_create_storage_trie(&mut self, address: &B256) -> RevealableSparseTrie<S> {
258 self.storage.tries.remove(address).unwrap_or_else(|| {
259 self.storage.cleared_tries.pop().unwrap_or_else(|| self.storage.default_trie.clone())
260 })
261 }
262
263 pub fn insert_storage_trie(&mut self, address: B256, storage_trie: RevealableSparseTrie<S>) {
265 self.storage.tries.insert(address, storage_trie);
266 }
267
268 pub fn get_or_create_storage_trie_mut(
270 &mut self,
271 address: B256,
272 ) -> &mut RevealableSparseTrie<S> {
273 self.storage.get_or_create_trie_mut(address)
274 }
275
276 pub fn reveal_multiproof(&mut self, multiproof: MultiProof) -> SparseStateTrieResult<()> {
279 let decoded_multiproof = multiproof.try_into()?;
281
282 self.reveal_decoded_multiproof(decoded_multiproof)
284 }
285
286 #[instrument(level = "debug", target = "trie::sparse", skip_all)]
289 pub fn reveal_decoded_multiproof(
290 &mut self,
291 multiproof: DecodedMultiProof,
292 ) -> SparseStateTrieResult<()> {
293 self.reveal_decoded_multiproof_v2(multiproof.into())
294 }
295
296 #[instrument(level = "debug", target = "trie::sparse", skip_all)]
301 pub fn reveal_decoded_multiproof_v2(
302 &mut self,
303 multiproof: reth_trie_common::DecodedMultiProofV2,
304 ) -> SparseStateTrieResult<()> {
305 if !multiproof.account_proofs.is_empty() {
313 self.reveal_account_v2_proof_nodes(multiproof.account_proofs)?;
314 }
315
316 #[cfg(not(feature = "std"))]
317 {
319 for (account, storage_proofs) in multiproof.storage_proofs {
320 self.reveal_storage_v2_proof_nodes(account, storage_proofs)?;
321 }
322
323 Ok(())
324 }
325
326 #[cfg(feature = "std")]
327 {
329 use rayon::iter::ParallelIterator;
330 use reth_primitives_traits::ParallelBridgeBuffered;
331
332 let retain_updates = self.retain_updates;
333
334 let results: Vec<_> = multiproof
338 .storage_proofs
339 .into_iter()
340 .map(|(account, storage_proofs)| {
341 let trie = self.storage.take_or_create_trie(&account);
342 (account, storage_proofs, trie)
343 })
344 .par_bridge_buffered()
345 .map(|(account, storage_proofs, mut trie)| {
346 let mut bufs = Vec::new();
347 let result = Self::reveal_storage_v2_proof_nodes_inner(
348 account,
349 storage_proofs,
350 &mut trie,
351 &mut bufs,
352 retain_updates,
353 );
354 (account, result, trie, bufs)
355 })
356 .collect();
357
358 let mut any_err = Ok(());
359 for (account, result, trie, bufs) in results {
360 self.storage.tries.insert(account, trie);
361 if let Ok(_total_nodes) = result {
362 #[cfg(feature = "metrics")]
363 {
364 self.metrics.increment_total_storage_nodes(_total_nodes as u64);
365 }
366 } else {
367 any_err = result.map(|_| ());
368 }
369
370 self.deferred_drops.proof_nodes_bufs.extend(bufs);
372 }
373
374 any_err
375 }
376 }
377
378 pub fn reveal_account_v2_proof_nodes(
383 &mut self,
384 mut nodes: Vec<ProofTrieNodeV2>,
385 ) -> SparseStateTrieResult<()> {
386 let capacity = estimate_v2_proof_capacity(&nodes);
387
388 #[cfg(feature = "metrics")]
389 self.metrics.increment_total_account_nodes(nodes.len() as u64);
390
391 let root_node = nodes.iter().find(|n| n.path.is_empty());
392 let trie = if let Some(root_node) = root_node {
393 trace!(target: "trie::sparse", ?root_node, "Revealing root account node from V2 proof");
394 self.state.reveal_root(root_node.node.clone(), root_node.masks, self.retain_updates)?
395 } else {
396 self.state.as_revealed_mut().ok_or(SparseTrieErrorKind::Blind)?
397 };
398 trie.reserve_nodes(capacity);
399 trace!(target: "trie::sparse", total_nodes = ?nodes.len(), "Revealing account nodes from V2 proof");
400 trie.reveal_nodes(&mut nodes)?;
401
402 self.deferred_drops.proof_nodes_bufs.push(nodes);
403 Ok(())
404 }
405
406 pub fn reveal_storage_v2_proof_nodes(
411 &mut self,
412 account: B256,
413 nodes: Vec<ProofTrieNodeV2>,
414 ) -> SparseStateTrieResult<()> {
415 let trie = self.storage.get_or_create_trie_mut(account);
416 let _total_nodes = Self::reveal_storage_v2_proof_nodes_inner(
417 account,
418 nodes,
419 trie,
420 &mut self.deferred_drops.proof_nodes_bufs,
421 self.retain_updates,
422 )?;
423
424 #[cfg(feature = "metrics")]
425 {
426 self.metrics.increment_total_storage_nodes(_total_nodes as u64);
427 }
428
429 Ok(())
430 }
431
432 fn reveal_storage_v2_proof_nodes_inner(
435 account: B256,
436 mut nodes: Vec<ProofTrieNodeV2>,
437 trie: &mut RevealableSparseTrie<S>,
438 bufs: &mut Vec<Vec<ProofTrieNodeV2>>,
439 retain_updates: bool,
440 ) -> SparseStateTrieResult<usize> {
441 let capacity = estimate_v2_proof_capacity(&nodes);
442 let total_nodes = nodes.len();
443
444 let root_node = nodes.iter().find(|n| n.path.is_empty());
445 let trie = if let Some(root_node) = root_node {
446 trace!(target: "trie::sparse", ?account, ?root_node, "Revealing root storage node from V2 proof");
447 trie.reveal_root(root_node.node.clone(), root_node.masks, retain_updates)?
448 } else {
449 trie.as_revealed_mut().ok_or(SparseTrieErrorKind::Blind)?
450 };
451 trie.reserve_nodes(capacity);
452 trace!(target: "trie::sparse", ?account, total_nodes, "Revealing storage nodes from V2 proof");
453 trie.reveal_nodes(&mut nodes)?;
454
455 bufs.push(nodes);
456 Ok(total_nodes)
457 }
458
459 pub fn wipe_storage(&mut self, address: B256) -> SparseStateTrieResult<()> {
461 if let Some(trie) = self.storage.tries.get_mut(&address) {
462 trie.wipe()?;
463 }
464 Ok(())
465 }
466
467 #[instrument(level = "debug", target = "trie::sparse", skip_all)]
471 pub fn calculate_subtries(&mut self) {
472 if let RevealableSparseTrie::Revealed(trie) = &mut self.state {
473 trie.update_subtrie_hashes();
474 }
475 }
476
477 pub fn storage_root(&mut self, account: &B256) -> Option<B256> {
479 self.storage.tries.get_mut(account).and_then(|trie| trie.root())
480 }
481
482 fn revealed_trie_mut(
486 &mut self,
487 provider_factory: impl TrieNodeProviderFactory,
488 ) -> SparseStateTrieResult<&mut A> {
489 match self.state {
490 RevealableSparseTrie::Blind(_) => {
491 let (root_node, hash_mask, tree_mask) = provider_factory
492 .account_node_provider()
493 .trie_node(&Nibbles::default())?
494 .map(|node| {
495 TrieNodeV2::decode(&mut &node.node[..])
496 .map(|decoded| (decoded, node.hash_mask, node.tree_mask))
497 })
498 .transpose()?
499 .unwrap_or((TrieNodeV2::EmptyRoot, None, None));
500 let masks = BranchNodeMasks::from_optional(hash_mask, tree_mask);
501 self.state.reveal_root(root_node, masks, self.retain_updates).map_err(Into::into)
502 }
503 RevealableSparseTrie::Revealed(ref mut trie) => Ok(trie),
504 }
505 }
506
507 pub fn root(
511 &mut self,
512 provider_factory: impl TrieNodeProviderFactory,
513 ) -> SparseStateTrieResult<B256> {
514 #[cfg(feature = "metrics")]
516 self.metrics.record();
517
518 Ok(self.revealed_trie_mut(provider_factory)?.root())
519 }
520
521 #[instrument(level = "debug", target = "trie::sparse", skip_all)]
523 pub fn root_with_updates(
524 &mut self,
525 provider_factory: impl TrieNodeProviderFactory,
526 ) -> SparseStateTrieResult<(B256, TrieUpdates)> {
527 #[cfg(feature = "metrics")]
529 self.metrics.record();
530
531 let storage_tries = self.storage_trie_updates();
532 let revealed = self.revealed_trie_mut(provider_factory)?;
533
534 let (root, updates) = (revealed.root(), revealed.take_updates());
535 let updates = TrieUpdates {
536 account_nodes: updates.updated_nodes,
537 removed_nodes: updates.removed_nodes,
538 storage_tries,
539 };
540 Ok((root, updates))
541 }
542
543 pub fn storage_trie_updates(&mut self) -> B256Map<StorageTrieUpdates> {
547 self.storage
548 .tries
549 .iter_mut()
550 .map(|(address, trie)| {
551 let trie = trie.as_revealed_mut().unwrap();
552 let updates = trie.take_updates();
553 let updates = StorageTrieUpdates {
554 is_deleted: updates.wiped,
555 storage_nodes: updates.updated_nodes,
556 removed_nodes: updates.removed_nodes,
557 };
558 (*address, updates)
559 })
560 .filter(|(_, updates)| !updates.is_empty())
561 .collect()
562 }
563
564 pub fn take_trie_updates(&mut self) -> Option<TrieUpdates> {
568 let storage_tries = self.storage_trie_updates();
569 self.state.as_revealed_mut().map(|state| {
570 let updates = state.take_updates();
571 TrieUpdates {
572 account_nodes: updates.updated_nodes,
573 removed_nodes: updates.removed_nodes,
574 storage_tries,
575 }
576 })
577 }
578
579 pub fn update_account_leaf(
581 &mut self,
582 path: Nibbles,
583 value: Vec<u8>,
584 provider_factory: impl TrieNodeProviderFactory,
585 ) -> SparseStateTrieResult<()> {
586 let provider = provider_factory.account_node_provider();
587 self.state.update_leaf(path, value, provider)?;
588 Ok(())
589 }
590
591 pub fn update_storage_leaf(
593 &mut self,
594 address: B256,
595 slot: Nibbles,
596 value: Vec<u8>,
597 provider_factory: impl TrieNodeProviderFactory,
598 ) -> SparseStateTrieResult<()> {
599 let provider = provider_factory.storage_node_provider(address);
600 self.storage
601 .tries
602 .get_mut(&address)
603 .ok_or(SparseTrieErrorKind::Blind)?
604 .update_leaf(slot, value, provider)?;
605 Ok(())
606 }
607
608 #[instrument(level = "trace", target = "trie::sparse", skip_all)]
614 pub fn update_account(
615 &mut self,
616 address: B256,
617 account: Account,
618 provider_factory: impl TrieNodeProviderFactory,
619 ) -> SparseStateTrieResult<bool> {
620 let storage_root = if let Some(storage_trie) = self.storage.tries.get_mut(&address) {
621 trace!(target: "trie::sparse", ?address, "Calculating storage root to update account");
622 storage_trie.root().ok_or(SparseTrieErrorKind::Blind)?
623 } else if self.is_account_revealed(address) {
624 trace!(target: "trie::sparse", ?address, "Retrieving storage root from account leaf to update account");
625 if let Some(value) = self.get_account_value(&address) {
627 TrieAccount::decode(&mut &value[..])?.storage_root
629 } else {
630 EMPTY_ROOT_HASH
632 }
633 } else {
634 return Err(SparseTrieErrorKind::Blind.into())
635 };
636
637 if account.is_empty() && storage_root == EMPTY_ROOT_HASH {
638 return Ok(false);
639 }
640
641 trace!(target: "trie::sparse", ?address, "Updating account");
642 let nibbles = Nibbles::unpack(address);
643 self.account_rlp_buf.clear();
644 account.into_trie_account(storage_root).encode(&mut self.account_rlp_buf);
645 self.update_account_leaf(nibbles, self.account_rlp_buf.clone(), provider_factory)?;
646
647 Ok(true)
648 }
649
650 #[instrument(level = "trace", target = "trie::sparse", skip_all)]
658 pub fn update_account_stateless(
659 &mut self,
660 address: B256,
661 account: Option<Account>,
662 provider_factory: impl TrieNodeProviderFactory,
663 ) -> SparseStateTrieResult<()> {
664 let nibbles = Nibbles::unpack(address);
665
666 let Some(account) = account else {
667 trace!(target: "trie::sparse", ?address, "Removing account");
668 return self.remove_account_leaf(&nibbles, provider_factory);
669 };
670
671 let storage_root = if let Some(storage_trie) = self.storage.tries.get_mut(&address) {
672 trace!(target: "trie::sparse", ?address, "Calculating storage root to update account");
673 storage_trie.root().ok_or(SparseTrieErrorKind::Blind)?
674 } else if let Some(value) = self.get_account_value(&address) {
675 trace!(target: "trie::sparse", ?address, "Retrieving storage root from account leaf to update account");
676 TrieAccount::decode(&mut &value[..])?.storage_root
677 } else {
678 EMPTY_ROOT_HASH
679 };
680
681 trace!(target: "trie::sparse", ?address, "Updating account");
682 self.account_rlp_buf.clear();
683 account.into_trie_account(storage_root).encode(&mut self.account_rlp_buf);
684 self.update_account_leaf(nibbles, self.account_rlp_buf.clone(), provider_factory)?;
685
686 Ok(())
687 }
688
689 #[instrument(level = "debug", target = "trie::sparse", skip_all)]
696 pub fn update_account_storage_root(
697 &mut self,
698 address: B256,
699 provider_factory: impl TrieNodeProviderFactory,
700 ) -> SparseStateTrieResult<bool> {
701 if !self.is_account_revealed(address) {
702 return Err(SparseTrieErrorKind::Blind.into())
703 }
704
705 let Some(mut trie_account) = self
707 .get_account_value(&address)
708 .map(|v| TrieAccount::decode(&mut &v[..]))
709 .transpose()?
710 else {
711 trace!(target: "trie::sparse", ?address, "Account not found in trie, skipping storage root update");
712 return Ok(true)
713 };
714
715 let storage_root = if let Some(storage_trie) = self.storage.tries.get_mut(&address) {
718 trace!(target: "trie::sparse", ?address, "Calculating storage root to update account");
719 storage_trie.root().ok_or(SparseTrieErrorKind::Blind)?
720 } else {
721 EMPTY_ROOT_HASH
722 };
723
724 trie_account.storage_root = storage_root;
726
727 if trie_account == TrieAccount::default() {
729 return Ok(false)
730 }
731
732 trace!(target: "trie::sparse", ?address, "Updating account with the new storage root");
734 let nibbles = Nibbles::unpack(address);
735 self.account_rlp_buf.clear();
736 trie_account.encode(&mut self.account_rlp_buf);
737 self.update_account_leaf(nibbles, self.account_rlp_buf.clone(), provider_factory)?;
738
739 Ok(true)
740 }
741
742 #[instrument(level = "debug", target = "trie::sparse", skip_all)]
744 pub fn remove_account_leaf(
745 &mut self,
746 path: &Nibbles,
747 provider_factory: impl TrieNodeProviderFactory,
748 ) -> SparseStateTrieResult<()> {
749 let provider = provider_factory.account_node_provider();
750 self.state.remove_leaf(path, provider)?;
751 Ok(())
752 }
753
754 pub fn remove_storage_leaf(
756 &mut self,
757 address: B256,
758 slot: &Nibbles,
759 provider_factory: impl TrieNodeProviderFactory,
760 ) -> SparseStateTrieResult<()> {
761 let storage_trie =
762 self.storage.tries.get_mut(&address).ok_or(SparseTrieErrorKind::Blind)?;
763
764 let provider = provider_factory.storage_node_provider(address);
765 storage_trie.remove_leaf(slot, provider)?;
766 Ok(())
767 }
768}
769
770impl<A, S> SparseStateTrie<A, S>
771where
772 A: SparseTrieTrait + Default,
773 S: SparseTrieTrait + Default + Clone,
774{
775 pub fn clear(&mut self) {
780 self.state.clear();
781 self.storage.clear();
782 self.account_rlp_buf.clear();
783 }
784
785 pub fn memory_size(&self) -> usize {
790 let mut size = core::mem::size_of::<Self>();
791
792 size += match &self.state {
793 RevealableSparseTrie::Revealed(t) | RevealableSparseTrie::Blind(Some(t)) => {
794 t.memory_size()
795 }
796 RevealableSparseTrie::Blind(None) => 0,
797 };
798
799 for trie in self.storage.tries.values() {
800 size += match trie {
801 RevealableSparseTrie::Revealed(t) | RevealableSparseTrie::Blind(Some(t)) => {
802 t.memory_size()
803 }
804 RevealableSparseTrie::Blind(None) => 0,
805 };
806 }
807 for trie in &self.storage.cleared_tries {
808 size += match trie {
809 RevealableSparseTrie::Revealed(t) | RevealableSparseTrie::Blind(Some(t)) => {
810 t.memory_size()
811 }
812 RevealableSparseTrie::Blind(None) => 0,
813 };
814 }
815
816 size
817 }
818
819 pub fn retained_storage_tries_count(&self) -> usize {
821 self.storage.tries.len() + self.storage.cleared_tries.len()
822 }
823
824 pub fn shrink_to(&mut self, max_nodes: usize, max_values: usize) {
829 let storage_tries_count = self.storage.tries.len() + self.storage.cleared_tries.len();
831
832 let total_tries = 1 + storage_tries_count;
834
835 let nodes_per_trie = max_nodes / total_tries;
837 let values_per_trie = max_values / total_tries;
838
839 self.state.shrink_nodes_to(nodes_per_trie);
841 self.state.shrink_values_to(values_per_trie);
842
843 let storage_nodes = max_nodes.saturating_sub(nodes_per_trie);
845 let storage_values = max_values.saturating_sub(values_per_trie);
846
847 self.storage.shrink_to(storage_nodes, storage_values);
849 }
850
851 #[cfg(feature = "std")]
866 #[instrument(
867 level = "debug",
868 name = "SparseStateTrie::prune",
869 target = "trie::sparse",
870 skip_all,
871 fields(%max_hot_slots, %max_hot_accounts)
872 )]
873 pub fn prune(&mut self, max_hot_slots: usize, max_hot_accounts: usize) {
874 self.hot_slots_lfu.decay_and_evict(max_hot_slots);
875 self.hot_accounts_lfu.decay_and_evict(max_hot_accounts);
876 let retained = self.hot_slots_lfu.retained_slots_by_address();
877
878 let retained_account_paths: Vec<Nibbles> =
879 self.hot_accounts_lfu.keys().map(|k| Nibbles::unpack(*k)).collect();
880
881 let retained_accounts = retained_account_paths.len();
882 let retained_storage_tries = retained.len();
883 let total_storage_tries_before = self.storage.tries.len();
884
885 let (account_nodes_pruned, storage_tries_evicted) = rayon::join(
887 || {
888 self.state
889 .as_revealed_mut()
890 .map(|trie| trie.prune(&retained_account_paths))
891 .unwrap_or(0)
892 },
893 || self.storage.prune_by_retained_slots(retained),
894 );
895
896 debug!(
897 target: "trie::sparse",
898 retained_accounts,
899 retained_storage_tries,
900 account_nodes_pruned,
901 storage_tries_evicted,
902 storage_tries_after = total_storage_tries_before - storage_tries_evicted,
903 "SparseStateTrie::prune completed"
904 );
905 }
906
907 pub fn commit_updates(&mut self, updates: &TrieUpdates) {
909 if let Some(trie) = self.state.as_revealed_mut() {
910 trie.commit_updates(&updates.account_nodes, &updates.removed_nodes);
911 }
912 for (address, updates) in &updates.storage_tries {
913 if let Some(trie) =
914 self.storage.tries.get_mut(address).and_then(|t| t.as_revealed_mut())
915 {
916 trie.commit_updates(&updates.storage_nodes, &updates.removed_nodes);
917 }
918 }
919 }
920}
921
922#[derive(Debug, Default)]
925struct StorageTries<S = ParallelSparseTrie> {
926 tries: B256Map<RevealableSparseTrie<S>>,
928 cleared_tries: Vec<RevealableSparseTrie<S>>,
930 default_trie: RevealableSparseTrie<S>,
932}
933
934#[cfg(feature = "std")]
935impl<S: SparseTrieTrait> StorageTries<S> {
936 fn prune_by_retained_slots(&mut self, retained_slots: B256Map<Vec<Nibbles>>) -> usize {
941 {
943 use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator};
944 self.tries.par_iter_mut().for_each(|(address, trie)| {
945 if let Some(slots) = retained_slots.get(address) {
946 if let Some(t) = trie.as_revealed_mut() {
947 t.prune(slots);
948 }
949 } else {
950 trie.clear();
951 }
952 });
953 }
954
955 let addresses_to_evict: Vec<B256> = self
957 .tries
958 .keys()
959 .filter(|address| !retained_slots.contains_key(*address))
960 .copied()
961 .collect();
962
963 let evicted = addresses_to_evict.len();
964 self.cleared_tries.reserve(evicted);
965 for address in &addresses_to_evict {
966 if let Some(trie) = self.tries.remove(address) {
967 self.cleared_tries.push(trie);
968 }
969 }
970
971 evicted
972 }
973}
974
975impl<S: SparseTrieTrait> StorageTries<S> {
976 fn clear(&mut self) {
979 self.cleared_tries.extend(self.tries.drain().map(|(_, mut trie)| {
980 trie.clear();
981 trie
982 }));
983 }
984
985 fn shrink_to(&mut self, max_nodes: usize, max_values: usize) {
989 let total_tries = self.tries.len() + self.cleared_tries.len();
990 if total_tries == 0 {
991 return;
992 }
993
994 let nodes_per_trie = max_nodes / total_tries;
996 let values_per_trie = max_values / total_tries;
997
998 for trie in self.tries.values_mut() {
1000 trie.shrink_nodes_to(nodes_per_trie);
1001 trie.shrink_values_to(values_per_trie);
1002 }
1003
1004 for trie in &mut self.cleared_tries {
1006 trie.shrink_nodes_to(nodes_per_trie);
1007 trie.shrink_values_to(values_per_trie);
1008 }
1009 }
1010}
1011
1012impl<S: SparseTrieTrait + Clone> StorageTries<S> {
1013 fn get_or_create_trie_mut(&mut self, address: B256) -> &mut RevealableSparseTrie<S> {
1015 self.tries.entry(address).or_insert_with(|| {
1016 self.cleared_tries.pop().unwrap_or_else(|| self.default_trie.clone())
1017 })
1018 }
1019
1020 #[cfg(feature = "std")]
1023 fn take_or_create_trie(&mut self, account: &B256) -> RevealableSparseTrie<S> {
1024 self.tries.remove(account).unwrap_or_else(|| {
1025 self.cleared_tries.pop().unwrap_or_else(|| self.default_trie.clone())
1026 })
1027 }
1028}
1029
1030#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1032struct HotSlotKey {
1033 address: B256,
1034 slot: B256,
1035}
1036
1037#[cfg(feature = "std")]
1039impl BucketedLfu<HotSlotKey> {
1040 fn retained_slots_by_address(&self) -> B256Map<Vec<Nibbles>> {
1042 let mut grouped =
1043 B256Map::<Vec<Nibbles>>::with_capacity_and_hasher(self.len(), Default::default());
1044 for key in self.keys() {
1045 grouped.entry(key.address).or_default().push(Nibbles::unpack(key.slot));
1046 }
1047
1048 for slots in grouped.values_mut() {
1049 slots.sort_unstable();
1050 slots.dedup();
1051 }
1052
1053 grouped
1054 }
1055}
1056
1057fn estimate_v2_proof_capacity(nodes: &[ProofTrieNodeV2]) -> usize {
1062 let mut capacity = nodes.len();
1063
1064 for node in nodes {
1065 if let TrieNodeV2::Branch(branch) = &node.node {
1066 capacity += branch.state_mask.count_ones() as usize;
1067 }
1068 }
1069
1070 capacity
1071}
1072
1073#[cfg(test)]
1074mod tests {
1075 use super::*;
1076 use crate::{provider::DefaultTrieNodeProviderFactory, LeafLookup, ParallelSparseTrie};
1077 use alloy_primitives::{
1078 b256,
1079 map::{HashMap, HashSet},
1080 U256,
1081 };
1082 use arbitrary::Arbitrary;
1083 use rand::{rngs::StdRng, Rng, SeedableRng};
1084 use reth_primitives_traits::Account;
1085 use reth_trie::{updates::StorageTrieUpdates, HashBuilder, MultiProof, EMPTY_ROOT_HASH};
1086 use reth_trie_common::{
1087 proof::{ProofNodes, ProofRetainer},
1088 BranchNodeMasks, BranchNodeMasksMap, BranchNodeV2, LeafNode, RlpNode, StorageMultiProof,
1089 TrieMask,
1090 };
1091
1092 fn leaf_key(suffix: impl AsRef<[u8]>, total_len: usize) -> Nibbles {
1094 let suffix = suffix.as_ref();
1095 let mut nibbles = Nibbles::from_nibbles(suffix);
1096 nibbles.extend(&Nibbles::from_nibbles_unchecked(vec![0; total_len - suffix.len()]));
1097 nibbles
1098 }
1099
1100 #[test]
1101 fn reveal_account_path_twice() {
1102 let provider_factory = DefaultTrieNodeProviderFactory;
1103 let mut sparse = SparseStateTrie::<ParallelSparseTrie>::default();
1104
1105 let full_path_0 = leaf_key([0x0], 64);
1107 let _full_path_1 = leaf_key([0x1], 64);
1108
1109 let leaf_value = alloy_rlp::encode(TrieAccount::default());
1110 let leaf_1 = alloy_rlp::encode(TrieNodeV2::Leaf(LeafNode::new(
1112 leaf_key([], 63),
1113 leaf_value.clone(),
1114 )));
1115 let leaf_2 = alloy_rlp::encode(TrieNodeV2::Leaf(LeafNode::new(
1116 leaf_key([], 63),
1117 leaf_value.clone(),
1118 )));
1119
1120 let multiproof = MultiProof {
1121 account_subtree: ProofNodes::from_iter([
1122 (
1123 Nibbles::default(),
1124 alloy_rlp::encode(TrieNodeV2::Branch(BranchNodeV2 {
1125 key: Nibbles::default(),
1126 stack: vec![RlpNode::from_rlp(&leaf_1), RlpNode::from_rlp(&leaf_2)],
1127 state_mask: TrieMask::new(0b11),
1128 branch_rlp_node: None,
1129 }))
1130 .into(),
1131 ),
1132 (Nibbles::from_nibbles([0x0]), leaf_1.clone().into()),
1133 (Nibbles::from_nibbles([0x1]), leaf_1.clone().into()),
1134 ]),
1135 ..Default::default()
1136 };
1137
1138 sparse.reveal_decoded_multiproof(multiproof.try_into().unwrap()).unwrap();
1140 assert!(matches!(
1141 sparse.state_trie_ref().unwrap().find_leaf(&full_path_0, None),
1142 Ok(LeafLookup::Exists)
1143 ));
1144 assert_eq!(
1145 sparse.state_trie_ref().unwrap().get_leaf_value(&full_path_0),
1146 Some(&leaf_value)
1147 );
1148
1149 sparse.remove_account_leaf(&full_path_0, &provider_factory).unwrap();
1152 assert!(matches!(
1153 sparse.state_trie_ref().unwrap().find_leaf(&full_path_0, None),
1154 Ok(LeafLookup::NonExistent)
1155 ));
1156 assert!(sparse.state_trie_ref().unwrap().get_leaf_value(&full_path_0).is_none());
1157 }
1158
1159 #[test]
1160 fn reveal_storage_path_twice() {
1161 let provider_factory = DefaultTrieNodeProviderFactory;
1162 let mut sparse = SparseStateTrie::<ParallelSparseTrie>::default();
1163
1164 let full_path_0 = leaf_key([0x0], 64);
1166
1167 let leaf_value = alloy_rlp::encode(TrieAccount::default());
1168 let leaf_1 = alloy_rlp::encode(TrieNodeV2::Leaf(LeafNode::new(
1169 leaf_key([], 63),
1170 leaf_value.clone(),
1171 )));
1172 let leaf_2 = alloy_rlp::encode(TrieNodeV2::Leaf(LeafNode::new(
1173 leaf_key([], 63),
1174 leaf_value.clone(),
1175 )));
1176
1177 let multiproof = MultiProof {
1178 storages: HashMap::from_iter([(
1179 B256::ZERO,
1180 StorageMultiProof {
1181 root: B256::ZERO,
1182 subtree: ProofNodes::from_iter([
1183 (
1184 Nibbles::default(),
1185 alloy_rlp::encode(TrieNodeV2::Branch(BranchNodeV2 {
1186 key: Nibbles::default(),
1187 stack: vec![RlpNode::from_rlp(&leaf_1), RlpNode::from_rlp(&leaf_2)],
1188 state_mask: TrieMask::new(0b11),
1189 branch_rlp_node: None,
1190 }))
1191 .into(),
1192 ),
1193 (Nibbles::from_nibbles([0x0]), leaf_1.clone().into()),
1194 (Nibbles::from_nibbles([0x1]), leaf_1.clone().into()),
1195 ]),
1196 branch_node_masks: Default::default(),
1197 },
1198 )]),
1199 ..Default::default()
1200 };
1201
1202 sparse.reveal_decoded_multiproof(multiproof.try_into().unwrap()).unwrap();
1204 assert!(matches!(
1205 sparse.storage_trie_ref(&B256::ZERO).unwrap().find_leaf(&full_path_0, None),
1206 Ok(LeafLookup::Exists)
1207 ));
1208 assert_eq!(
1209 sparse.storage_trie_ref(&B256::ZERO).unwrap().get_leaf_value(&full_path_0),
1210 Some(&leaf_value)
1211 );
1212
1213 sparse.remove_storage_leaf(B256::ZERO, &full_path_0, &provider_factory).unwrap();
1216 assert!(matches!(
1217 sparse.storage_trie_ref(&B256::ZERO).unwrap().find_leaf(&full_path_0, None),
1218 Ok(LeafLookup::NonExistent)
1219 ));
1220 assert!(sparse
1221 .storage_trie_ref(&B256::ZERO)
1222 .unwrap()
1223 .get_leaf_value(&full_path_0)
1224 .is_none());
1225 }
1226
1227 #[test]
1228 fn seeded_hot_cache_capacities_preserve_first_cycle_touches() {
1229 let account = b256!("0x1000000000000000000000000000000000000000000000000000000000000000");
1230 let slot = b256!("0x2000000000000000000000000000000000000000000000000000000000000000");
1231 let mut sparse = SparseStateTrie::<ParallelSparseTrie>::default();
1232
1233 sparse.set_hot_cache_capacities(1, 1);
1234 sparse.record_account_touch(account);
1235 sparse.record_slot_touch(account, slot);
1236 sparse.prune(1, 1);
1237
1238 assert_eq!(sparse.hot_accounts_lfu.keys().copied().collect::<Vec<_>>(), vec![account]);
1239 assert_eq!(
1240 sparse.hot_slots_lfu.keys().copied().collect::<Vec<_>>(),
1241 vec![HotSlotKey { address: account, slot }]
1242 );
1243 }
1244
1245 #[test]
1246 fn reveal_v2_proof_nodes() {
1247 let provider_factory = DefaultTrieNodeProviderFactory;
1248 let mut sparse = SparseStateTrie::<ParallelSparseTrie>::default();
1249
1250 let full_path_0 = leaf_key([0x0], 64);
1252
1253 let leaf_value = alloy_rlp::encode(TrieAccount::default());
1254 let leaf_1_node = TrieNodeV2::Leaf(LeafNode::new(leaf_key([], 63), leaf_value.clone()));
1255 let leaf_2_node = TrieNodeV2::Leaf(LeafNode::new(leaf_key([], 63), leaf_value.clone()));
1256
1257 let branch_node = TrieNodeV2::Branch(BranchNodeV2 {
1258 key: Nibbles::default(),
1259 stack: vec![
1260 RlpNode::from_rlp(&alloy_rlp::encode(&leaf_1_node)),
1261 RlpNode::from_rlp(&alloy_rlp::encode(&leaf_2_node)),
1262 ],
1263 state_mask: TrieMask::new(0b11),
1264 branch_rlp_node: None,
1265 });
1266
1267 let v2_proof_nodes = vec![
1269 ProofTrieNodeV2 {
1270 path: Nibbles::default(),
1271 node: branch_node,
1272 masks: Some(BranchNodeMasks {
1273 hash_mask: TrieMask::default(),
1274 tree_mask: TrieMask::default(),
1275 }),
1276 },
1277 ProofTrieNodeV2 { path: Nibbles::from_nibbles([0x0]), node: leaf_1_node, masks: None },
1278 ProofTrieNodeV2 { path: Nibbles::from_nibbles([0x1]), node: leaf_2_node, masks: None },
1279 ];
1280
1281 sparse.reveal_account_v2_proof_nodes(v2_proof_nodes).unwrap();
1283
1284 assert!(matches!(
1286 sparse.state_trie_ref().unwrap().find_leaf(&full_path_0, None),
1287 Ok(LeafLookup::Exists)
1288 ));
1289 assert_eq!(
1290 sparse.state_trie_ref().unwrap().get_leaf_value(&full_path_0),
1291 Some(&leaf_value)
1292 );
1293
1294 sparse.remove_account_leaf(&full_path_0, &provider_factory).unwrap();
1296 assert!(sparse.state_trie_ref().unwrap().get_leaf_value(&full_path_0).is_none());
1297 }
1298
1299 #[test]
1300 fn reveal_storage_v2_proof_nodes() {
1301 let provider_factory = DefaultTrieNodeProviderFactory;
1302 let mut sparse = SparseStateTrie::<ParallelSparseTrie>::default();
1303
1304 let full_path_0 = leaf_key([0x0], 64);
1306
1307 let storage_value: Vec<u8> = alloy_rlp::encode_fixed_size(&U256::from(42)).to_vec();
1308 let leaf_1_node = TrieNodeV2::Leaf(LeafNode::new(leaf_key([], 63), storage_value.clone()));
1309 let leaf_2_node = TrieNodeV2::Leaf(LeafNode::new(leaf_key([], 63), storage_value.clone()));
1310
1311 let branch_node = TrieNodeV2::Branch(BranchNodeV2 {
1312 key: Nibbles::default(),
1313 stack: vec![
1314 RlpNode::from_rlp(&alloy_rlp::encode(&leaf_1_node)),
1315 RlpNode::from_rlp(&alloy_rlp::encode(&leaf_2_node)),
1316 ],
1317 state_mask: TrieMask::new(0b11),
1318 branch_rlp_node: None,
1319 });
1320
1321 let v2_proof_nodes = vec![
1322 ProofTrieNodeV2 { path: Nibbles::default(), node: branch_node, masks: None },
1323 ProofTrieNodeV2 { path: Nibbles::from_nibbles([0x0]), node: leaf_1_node, masks: None },
1324 ProofTrieNodeV2 { path: Nibbles::from_nibbles([0x1]), node: leaf_2_node, masks: None },
1325 ];
1326
1327 sparse.reveal_storage_v2_proof_nodes(B256::ZERO, v2_proof_nodes).unwrap();
1329
1330 assert!(matches!(
1332 sparse.storage_trie_ref(&B256::ZERO).unwrap().find_leaf(&full_path_0, None),
1333 Ok(LeafLookup::Exists)
1334 ));
1335 assert_eq!(
1336 sparse.storage_trie_ref(&B256::ZERO).unwrap().get_leaf_value(&full_path_0),
1337 Some(&storage_value)
1338 );
1339
1340 sparse.remove_storage_leaf(B256::ZERO, &full_path_0, &provider_factory).unwrap();
1342 assert!(sparse
1343 .storage_trie_ref(&B256::ZERO)
1344 .unwrap()
1345 .get_leaf_value(&full_path_0)
1346 .is_none());
1347 }
1348
1349 #[test]
1350 fn take_trie_updates() {
1351 reth_tracing::init_test_tracing();
1352
1353 let mut rng = StdRng::seed_from_u64(1);
1355
1356 let mut bytes = [0u8; 1024];
1357 rng.fill(bytes.as_mut_slice());
1358
1359 let slot_1 = b256!("0x1000000000000000000000000000000000000000000000000000000000000000");
1360 let slot_path_1 = Nibbles::unpack(slot_1);
1361 let value_1 = U256::from(rng.random::<u64>());
1362 let slot_2 = b256!("0x1100000000000000000000000000000000000000000000000000000000000000");
1363 let slot_path_2 = Nibbles::unpack(slot_2);
1364 let value_2 = U256::from(rng.random::<u64>());
1365 let slot_3 = b256!("0x2000000000000000000000000000000000000000000000000000000000000000");
1366 let slot_path_3 = Nibbles::unpack(slot_3);
1367 let value_3 = U256::from(rng.random::<u64>());
1368
1369 let mut storage_hash_builder = HashBuilder::default()
1370 .with_proof_retainer(ProofRetainer::from_iter([slot_path_1, slot_path_2]));
1371 storage_hash_builder.add_leaf(slot_path_1, &alloy_rlp::encode_fixed_size(&value_1));
1372 storage_hash_builder.add_leaf(slot_path_2, &alloy_rlp::encode_fixed_size(&value_2));
1373
1374 let storage_root = storage_hash_builder.root();
1375 let storage_proof_nodes = storage_hash_builder.take_proof_nodes();
1376 let storage_branch_node_masks = BranchNodeMasksMap::from_iter([
1377 (
1378 Nibbles::default(),
1379 BranchNodeMasks { hash_mask: TrieMask::new(0b010), tree_mask: TrieMask::default() },
1380 ),
1381 (
1382 Nibbles::from_nibbles([0x1]),
1383 BranchNodeMasks { hash_mask: TrieMask::new(0b11), tree_mask: TrieMask::default() },
1384 ),
1385 ]);
1386
1387 let address_1 = b256!("0x1000000000000000000000000000000000000000000000000000000000000000");
1388 let address_path_1 = Nibbles::unpack(address_1);
1389 let account_1 = Account::arbitrary(&mut arbitrary::Unstructured::new(&bytes)).unwrap();
1390 let mut trie_account_1 = account_1.into_trie_account(storage_root);
1391 let address_2 = b256!("0x1100000000000000000000000000000000000000000000000000000000000000");
1392 let address_path_2 = Nibbles::unpack(address_2);
1393 let account_2 = Account::arbitrary(&mut arbitrary::Unstructured::new(&bytes)).unwrap();
1394 let mut trie_account_2 = account_2.into_trie_account(EMPTY_ROOT_HASH);
1395
1396 let mut hash_builder = HashBuilder::default()
1397 .with_proof_retainer(ProofRetainer::from_iter([address_path_1, address_path_2]));
1398 hash_builder.add_leaf(address_path_1, &alloy_rlp::encode(trie_account_1));
1399 hash_builder.add_leaf(address_path_2, &alloy_rlp::encode(trie_account_2));
1400
1401 let root = hash_builder.root();
1402 let proof_nodes = hash_builder.take_proof_nodes();
1403
1404 let provider_factory = DefaultTrieNodeProviderFactory;
1405 let mut sparse = SparseStateTrie::<ParallelSparseTrie>::default().with_updates(true);
1406 sparse
1407 .reveal_decoded_multiproof(
1408 MultiProof {
1409 account_subtree: proof_nodes,
1410 branch_node_masks: BranchNodeMasksMap::from_iter([(
1411 Nibbles::from_nibbles([0x1]),
1412 BranchNodeMasks {
1413 hash_mask: TrieMask::new(0b00),
1414 tree_mask: TrieMask::default(),
1415 },
1416 )]),
1417 storages: HashMap::from_iter([
1418 (
1419 address_1,
1420 StorageMultiProof {
1421 root,
1422 subtree: storage_proof_nodes.clone(),
1423 branch_node_masks: storage_branch_node_masks.clone(),
1424 },
1425 ),
1426 (
1427 address_2,
1428 StorageMultiProof {
1429 root,
1430 subtree: storage_proof_nodes,
1431 branch_node_masks: storage_branch_node_masks,
1432 },
1433 ),
1434 ]),
1435 }
1436 .try_into()
1437 .unwrap(),
1438 )
1439 .unwrap();
1440
1441 assert_eq!(sparse.root(&provider_factory).unwrap(), root);
1442
1443 let address_3 = b256!("0x2000000000000000000000000000000000000000000000000000000000000000");
1444 let address_path_3 = Nibbles::unpack(address_3);
1445 let account_3 = Account { nonce: account_1.nonce + 1, ..account_1 };
1446 let trie_account_3 = account_3.into_trie_account(EMPTY_ROOT_HASH);
1447
1448 sparse
1449 .update_account_leaf(
1450 address_path_3,
1451 alloy_rlp::encode(trie_account_3),
1452 &provider_factory,
1453 )
1454 .unwrap();
1455
1456 sparse
1457 .update_storage_leaf(
1458 address_1,
1459 slot_path_3,
1460 alloy_rlp::encode(value_3),
1461 &provider_factory,
1462 )
1463 .unwrap();
1464 trie_account_1.storage_root = sparse.storage_root(&address_1).unwrap();
1465 sparse
1466 .update_account_leaf(
1467 address_path_1,
1468 alloy_rlp::encode(trie_account_1),
1469 &provider_factory,
1470 )
1471 .unwrap();
1472
1473 sparse.wipe_storage(address_2).unwrap();
1474 trie_account_2.storage_root = sparse.storage_root(&address_2).unwrap();
1475 sparse
1476 .update_account_leaf(
1477 address_path_2,
1478 alloy_rlp::encode(trie_account_2),
1479 &provider_factory,
1480 )
1481 .unwrap();
1482
1483 sparse.root(&provider_factory).unwrap();
1484
1485 let sparse_updates = sparse.take_trie_updates().unwrap();
1486 pretty_assertions::assert_eq!(
1488 sparse_updates,
1489 TrieUpdates {
1490 account_nodes: HashMap::default(),
1491 storage_tries: HashMap::from_iter([(
1492 b256!("0x1100000000000000000000000000000000000000000000000000000000000000"),
1493 StorageTrieUpdates {
1494 is_deleted: true,
1495 storage_nodes: HashMap::default(),
1496 removed_nodes: HashSet::default()
1497 }
1498 )]),
1499 removed_nodes: HashSet::default()
1500 }
1501 );
1502 }
1503}