reth_trie_common/
hashed_state.rs

1use core::ops::Not;
2
3use crate::{
4    added_removed_keys::MultiAddedRemovedKeys,
5    prefix_set::{PrefixSetMut, TriePrefixSetsMut},
6    KeyHasher, MultiProofTargets, Nibbles,
7};
8use alloc::{borrow::Cow, vec::Vec};
9use alloy_primitives::{
10    keccak256,
11    map::{hash_map, B256Map, B256Set, HashMap, HashSet},
12    Address, B256, U256,
13};
14use itertools::Itertools;
15#[cfg(feature = "rayon")]
16pub use rayon::*;
17use reth_primitives_traits::Account;
18
19#[cfg(feature = "rayon")]
20use rayon::prelude::{IntoParallelIterator, ParallelIterator};
21
22use revm_database::{AccountStatus, BundleAccount};
23
24/// Representation of in-memory hashed state.
25#[derive(PartialEq, Eq, Clone, Default, Debug)]
26#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
27pub struct HashedPostState {
28    /// Mapping of hashed address to account info, `None` if destroyed.
29    pub accounts: B256Map<Option<Account>>,
30    /// Mapping of hashed address to hashed storage.
31    pub storages: B256Map<HashedStorage>,
32}
33
34impl HashedPostState {
35    /// Create new instance of [`HashedPostState`].
36    pub fn with_capacity(capacity: usize) -> Self {
37        Self {
38            accounts: B256Map::with_capacity_and_hasher(capacity, Default::default()),
39            storages: B256Map::with_capacity_and_hasher(capacity, Default::default()),
40        }
41    }
42
43    /// Initialize [`HashedPostState`] from bundle state.
44    /// Hashes all changed accounts and storage entries that are currently stored in the bundle
45    /// state.
46    #[inline]
47    #[cfg(feature = "rayon")]
48    pub fn from_bundle_state<'a, KH: KeyHasher>(
49        state: impl IntoParallelIterator<Item = (&'a Address, &'a BundleAccount)>,
50    ) -> Self {
51        let hashed = state
52            .into_par_iter()
53            .map(|(address, account)| {
54                let hashed_address = KH::hash_key(address);
55                let hashed_account = account.info.as_ref().map(Into::into);
56                let hashed_storage = HashedStorage::from_plain_storage(
57                    account.status,
58                    account.storage.iter().map(|(slot, value)| (slot, &value.present_value)),
59                );
60                (hashed_address, (hashed_account, hashed_storage))
61            })
62            .collect::<Vec<(B256, (Option<Account>, HashedStorage))>>();
63
64        let mut accounts = HashMap::with_capacity_and_hasher(hashed.len(), Default::default());
65        let mut storages = HashMap::with_capacity_and_hasher(hashed.len(), Default::default());
66        for (address, (account, storage)) in hashed {
67            accounts.insert(address, account);
68            if !storage.is_empty() {
69                storages.insert(address, storage);
70            }
71        }
72        Self { accounts, storages }
73    }
74
75    /// Initialize [`HashedPostState`] from bundle state.
76    /// Hashes all changed accounts and storage entries that are currently stored in the bundle
77    /// state.
78    #[cfg(not(feature = "rayon"))]
79    pub fn from_bundle_state<'a, KH: KeyHasher>(
80        state: impl IntoIterator<Item = (&'a Address, &'a BundleAccount)>,
81    ) -> Self {
82        let hashed = state
83            .into_iter()
84            .map(|(address, account)| {
85                let hashed_address = KH::hash_key(address);
86                let hashed_account = account.info.as_ref().map(Into::into);
87                let hashed_storage = HashedStorage::from_plain_storage(
88                    account.status,
89                    account.storage.iter().map(|(slot, value)| (slot, &value.present_value)),
90                );
91                (hashed_address, (hashed_account, hashed_storage))
92            })
93            .collect::<Vec<(B256, (Option<Account>, HashedStorage))>>();
94
95        let mut accounts = HashMap::with_capacity_and_hasher(hashed.len(), Default::default());
96        let mut storages = HashMap::with_capacity_and_hasher(hashed.len(), Default::default());
97        for (address, (account, storage)) in hashed {
98            accounts.insert(address, account);
99            if !storage.is_empty() {
100                storages.insert(address, storage);
101            }
102        }
103        Self { accounts, storages }
104    }
105
106    /// Construct [`HashedPostState`] from a single [`HashedStorage`].
107    pub fn from_hashed_storage(hashed_address: B256, storage: HashedStorage) -> Self {
108        Self {
109            accounts: HashMap::default(),
110            storages: HashMap::from_iter([(hashed_address, storage)]),
111        }
112    }
113
114    /// Set account entries on hashed state.
115    pub fn with_accounts(
116        mut self,
117        accounts: impl IntoIterator<Item = (B256, Option<Account>)>,
118    ) -> Self {
119        self.accounts = HashMap::from_iter(accounts);
120        self
121    }
122
123    /// Set storage entries on hashed state.
124    pub fn with_storages(
125        mut self,
126        storages: impl IntoIterator<Item = (B256, HashedStorage)>,
127    ) -> Self {
128        self.storages = HashMap::from_iter(storages);
129        self
130    }
131
132    /// Returns `true` if the hashed state is empty.
133    pub fn is_empty(&self) -> bool {
134        self.accounts.is_empty() && self.storages.is_empty()
135    }
136
137    /// Construct [`TriePrefixSetsMut`] from hashed post state.
138    /// The prefix sets contain the hashed account and storage keys that have been changed in the
139    /// post state.
140    pub fn construct_prefix_sets(&self) -> TriePrefixSetsMut {
141        // Populate account prefix set.
142        let mut account_prefix_set = PrefixSetMut::with_capacity(self.accounts.len());
143        let mut destroyed_accounts = HashSet::default();
144        for (hashed_address, account) in &self.accounts {
145            account_prefix_set.insert(Nibbles::unpack(hashed_address));
146
147            if account.is_none() {
148                destroyed_accounts.insert(*hashed_address);
149            }
150        }
151
152        // Populate storage prefix sets.
153        let mut storage_prefix_sets =
154            HashMap::with_capacity_and_hasher(self.storages.len(), Default::default());
155        for (hashed_address, hashed_storage) in &self.storages {
156            account_prefix_set.insert(Nibbles::unpack(hashed_address));
157            storage_prefix_sets.insert(*hashed_address, hashed_storage.construct_prefix_set());
158        }
159
160        TriePrefixSetsMut { account_prefix_set, storage_prefix_sets, destroyed_accounts }
161    }
162
163    /// Create multiproof targets for this state.
164    pub fn multi_proof_targets(&self) -> MultiProofTargets {
165        // Pre-allocate minimum capacity for the targets.
166        let mut targets = MultiProofTargets::with_capacity(self.accounts.len());
167        for hashed_address in self.accounts.keys() {
168            targets.insert(*hashed_address, Default::default());
169        }
170        for (hashed_address, storage) in &self.storages {
171            targets.entry(*hashed_address).or_default().extend(storage.storage.keys().copied());
172        }
173        targets
174    }
175
176    /// Create multiproof targets difference for this state,
177    /// i.e., the targets that are in targets create from `self` but not in `excluded`.
178    ///
179    /// This method is preferred to first calling `Self::multi_proof_targets` and the calling
180    /// `MultiProofTargets::retain_difference`, because it does not over allocate the targets map.
181    pub fn multi_proof_targets_difference(
182        &self,
183        excluded: &MultiProofTargets,
184    ) -> MultiProofTargets {
185        let mut targets = MultiProofTargets::default();
186        for hashed_address in self.accounts.keys() {
187            if !excluded.contains_key(hashed_address) {
188                targets.insert(*hashed_address, Default::default());
189            }
190        }
191        for (hashed_address, storage) in &self.storages {
192            let maybe_excluded_storage = excluded.get(hashed_address);
193            let mut hashed_slots_targets = storage
194                .storage
195                .keys()
196                .filter(|slot| !maybe_excluded_storage.is_some_and(|f| f.contains(*slot)))
197                .peekable();
198            if hashed_slots_targets.peek().is_some() {
199                targets.entry(*hashed_address).or_default().extend(hashed_slots_targets);
200            }
201        }
202        targets
203    }
204
205    /// Partition the state update into two state updates:
206    /// - First with accounts and storages slots that are present in the provided targets.
207    /// - Second with all other.
208    ///
209    /// CAUTION: The state updates are expected to be applied in order, so that the storage wipes
210    /// are done correctly.
211    pub fn partition_by_targets(
212        mut self,
213        targets: &MultiProofTargets,
214        added_removed_keys: &MultiAddedRemovedKeys,
215    ) -> (Self, Self) {
216        let mut state_updates_not_in_targets = Self::default();
217
218        self.storages.retain(|&address, storage| {
219            let storage_added_removed_keys = added_removed_keys.get_storage(&address);
220
221            let (retain, storage_not_in_targets) = match targets.get(&address) {
222                Some(storage_in_targets) => {
223                    let mut storage_not_in_targets = HashedStorage::default();
224                    storage.storage.retain(|&slot, value| {
225                        if storage_in_targets.contains(&slot) &&
226                            !storage_added_removed_keys.is_some_and(|k| k.is_removed(&slot))
227                        {
228                            return true
229                        }
230
231                        storage_not_in_targets.storage.insert(slot, *value);
232                        false
233                    });
234
235                    // We do not check the wiped flag here, because targets only contain addresses
236                    // and storage slots. So if there are no storage slots left, the storage update
237                    // can be fully removed.
238                    let retain = !storage.storage.is_empty();
239
240                    // Since state updates are expected to be applied in order, we can only set the
241                    // wiped flag in the second storage update if the first storage update is empty
242                    // and will not be retained.
243                    if !retain {
244                        storage_not_in_targets.wiped = storage.wiped;
245                    }
246
247                    (
248                        retain,
249                        storage_not_in_targets.is_empty().not().then_some(storage_not_in_targets),
250                    )
251                }
252                None => (false, Some(core::mem::take(storage))),
253            };
254
255            if let Some(storage_not_in_targets) = storage_not_in_targets {
256                state_updates_not_in_targets.storages.insert(address, storage_not_in_targets);
257            }
258
259            retain
260        });
261        self.accounts.retain(|&address, account| {
262            if targets.contains_key(&address) {
263                return true
264            }
265
266            state_updates_not_in_targets.accounts.insert(address, *account);
267            false
268        });
269
270        (self, state_updates_not_in_targets)
271    }
272
273    /// Returns an iterator that yields chunks of the specified size.
274    ///
275    /// See [`ChunkedHashedPostState`] for more information.
276    pub fn chunks(self, size: usize) -> ChunkedHashedPostState {
277        ChunkedHashedPostState::new(self, size)
278    }
279
280    /// Extend this hashed post state with contents of another.
281    /// Entries in the second hashed post state take precedence.
282    pub fn extend(&mut self, other: Self) {
283        self.extend_inner(Cow::Owned(other));
284    }
285
286    /// Extend this hashed post state with contents of another.
287    /// Entries in the second hashed post state take precedence.
288    ///
289    /// Slightly less efficient than [`Self::extend`], but preferred to `extend(other.clone())`.
290    pub fn extend_ref(&mut self, other: &Self) {
291        self.extend_inner(Cow::Borrowed(other));
292    }
293
294    fn extend_inner(&mut self, other: Cow<'_, Self>) {
295        self.accounts.extend(other.accounts.iter().map(|(&k, &v)| (k, v)));
296
297        self.storages.reserve(other.storages.len());
298        match other {
299            Cow::Borrowed(other) => {
300                self.extend_storages(other.storages.iter().map(|(k, v)| (*k, Cow::Borrowed(v))))
301            }
302            Cow::Owned(other) => {
303                self.extend_storages(other.storages.into_iter().map(|(k, v)| (k, Cow::Owned(v))))
304            }
305        }
306    }
307
308    fn extend_storages<'a>(
309        &mut self,
310        storages: impl IntoIterator<Item = (B256, Cow<'a, HashedStorage>)>,
311    ) {
312        for (hashed_address, storage) in storages {
313            match self.storages.entry(hashed_address) {
314                hash_map::Entry::Vacant(entry) => {
315                    entry.insert(storage.into_owned());
316                }
317                hash_map::Entry::Occupied(mut entry) => {
318                    entry.get_mut().extend(&storage);
319                }
320            }
321        }
322    }
323
324    /// Converts hashed post state into [`HashedPostStateSorted`].
325    pub fn into_sorted(self) -> HashedPostStateSorted {
326        let mut updated_accounts = Vec::new();
327        let mut destroyed_accounts = HashSet::default();
328        for (hashed_address, info) in self.accounts {
329            if let Some(info) = info {
330                updated_accounts.push((hashed_address, info));
331            } else {
332                destroyed_accounts.insert(hashed_address);
333            }
334        }
335        updated_accounts.sort_unstable_by_key(|(address, _)| *address);
336        let accounts = HashedAccountsSorted { accounts: updated_accounts, destroyed_accounts };
337
338        let storages = self
339            .storages
340            .into_iter()
341            .map(|(hashed_address, storage)| (hashed_address, storage.into_sorted()))
342            .collect();
343
344        HashedPostStateSorted { accounts, storages }
345    }
346
347    /// Converts hashed post state into [`HashedPostStateSorted`], but keeping the maps allocated by
348    /// draining.
349    ///
350    /// This effectively clears all the fields in the [`HashedPostStateSorted`].
351    ///
352    /// This allows us to reuse the allocated space. This allocates new space for the sorted hashed
353    /// post state, like `into_sorted`.
354    pub fn drain_into_sorted(&mut self) -> HashedPostStateSorted {
355        let mut updated_accounts = Vec::new();
356        let mut destroyed_accounts = HashSet::default();
357        for (hashed_address, info) in self.accounts.drain() {
358            if let Some(info) = info {
359                updated_accounts.push((hashed_address, info));
360            } else {
361                destroyed_accounts.insert(hashed_address);
362            }
363        }
364        updated_accounts.sort_unstable_by_key(|(address, _)| *address);
365        let accounts = HashedAccountsSorted { accounts: updated_accounts, destroyed_accounts };
366
367        let storages = self
368            .storages
369            .drain()
370            .map(|(hashed_address, storage)| (hashed_address, storage.into_sorted()))
371            .collect();
372
373        HashedPostStateSorted { accounts, storages }
374    }
375
376    /// Clears the account and storage maps of this `HashedPostState`.
377    pub fn clear(&mut self) {
378        self.accounts.clear();
379        self.storages.clear();
380    }
381}
382
383/// Representation of in-memory hashed storage.
384#[derive(PartialEq, Eq, Clone, Debug, Default)]
385#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
386pub struct HashedStorage {
387    /// Flag indicating whether the storage was wiped or not.
388    pub wiped: bool,
389    /// Mapping of hashed storage slot to storage value.
390    pub storage: B256Map<U256>,
391}
392
393impl HashedStorage {
394    /// Create new instance of [`HashedStorage`].
395    pub fn new(wiped: bool) -> Self {
396        Self { wiped, storage: HashMap::default() }
397    }
398
399    /// Check if self is empty.
400    pub fn is_empty(&self) -> bool {
401        !self.wiped && self.storage.is_empty()
402    }
403
404    /// Create new hashed storage from iterator.
405    pub fn from_iter(wiped: bool, iter: impl IntoIterator<Item = (B256, U256)>) -> Self {
406        Self { wiped, storage: HashMap::from_iter(iter) }
407    }
408
409    /// Create new hashed storage from account status and plain storage.
410    pub fn from_plain_storage<'a>(
411        status: AccountStatus,
412        storage: impl IntoIterator<Item = (&'a U256, &'a U256)>,
413    ) -> Self {
414        Self::from_iter(
415            status.was_destroyed(),
416            storage.into_iter().map(|(key, value)| (keccak256(B256::from(*key)), *value)),
417        )
418    }
419
420    /// Construct [`PrefixSetMut`] from hashed storage.
421    pub fn construct_prefix_set(&self) -> PrefixSetMut {
422        if self.wiped {
423            PrefixSetMut::all()
424        } else {
425            let mut prefix_set = PrefixSetMut::with_capacity(self.storage.len());
426            for hashed_slot in self.storage.keys() {
427                prefix_set.insert(Nibbles::unpack(hashed_slot));
428            }
429            prefix_set
430        }
431    }
432
433    /// Extend hashed storage with contents of other.
434    /// The entries in second hashed storage take precedence.
435    pub fn extend(&mut self, other: &Self) {
436        if other.wiped {
437            self.wiped = true;
438            self.storage.clear();
439        }
440        self.storage.extend(other.storage.iter().map(|(&k, &v)| (k, v)));
441    }
442
443    /// Converts hashed storage into [`HashedStorageSorted`].
444    pub fn into_sorted(self) -> HashedStorageSorted {
445        let mut non_zero_valued_slots = Vec::new();
446        let mut zero_valued_slots = HashSet::default();
447        for (hashed_slot, value) in self.storage {
448            if value.is_zero() {
449                zero_valued_slots.insert(hashed_slot);
450            } else {
451                non_zero_valued_slots.push((hashed_slot, value));
452            }
453        }
454        non_zero_valued_slots.sort_unstable_by_key(|(key, _)| *key);
455
456        HashedStorageSorted { non_zero_valued_slots, zero_valued_slots, wiped: self.wiped }
457    }
458}
459
460/// Sorted hashed post state optimized for iterating during state trie calculation.
461#[derive(PartialEq, Eq, Clone, Default, Debug)]
462pub struct HashedPostStateSorted {
463    /// Updated state of accounts.
464    pub accounts: HashedAccountsSorted,
465    /// Map of hashed addresses to hashed storage.
466    pub storages: B256Map<HashedStorageSorted>,
467}
468
469impl HashedPostStateSorted {
470    /// Create new instance of [`HashedPostStateSorted`]
471    pub const fn new(
472        accounts: HashedAccountsSorted,
473        storages: B256Map<HashedStorageSorted>,
474    ) -> Self {
475        Self { accounts, storages }
476    }
477
478    /// Returns reference to hashed accounts.
479    pub const fn accounts(&self) -> &HashedAccountsSorted {
480        &self.accounts
481    }
482
483    /// Returns reference to hashed account storages.
484    pub const fn account_storages(&self) -> &B256Map<HashedStorageSorted> {
485        &self.storages
486    }
487}
488
489impl AsRef<Self> for HashedPostStateSorted {
490    fn as_ref(&self) -> &Self {
491        self
492    }
493}
494
495/// Sorted account state optimized for iterating during state trie calculation.
496#[derive(Clone, Eq, PartialEq, Default, Debug)]
497pub struct HashedAccountsSorted {
498    /// Sorted collection of hashed addresses and their account info.
499    pub accounts: Vec<(B256, Account)>,
500    /// Set of destroyed account keys.
501    pub destroyed_accounts: B256Set,
502}
503
504impl HashedAccountsSorted {
505    /// Returns a sorted iterator over updated accounts.
506    pub fn accounts_sorted(&self) -> impl Iterator<Item = (B256, Option<Account>)> {
507        self.accounts
508            .iter()
509            .map(|(address, account)| (*address, Some(*account)))
510            .chain(self.destroyed_accounts.iter().map(|address| (*address, None)))
511            .sorted_by_key(|entry| *entry.0)
512    }
513}
514
515/// Sorted hashed storage optimized for iterating during state trie calculation.
516#[derive(Clone, Eq, PartialEq, Debug)]
517pub struct HashedStorageSorted {
518    /// Sorted hashed storage slots with non-zero value.
519    pub non_zero_valued_slots: Vec<(B256, U256)>,
520    /// Slots that have been zero valued.
521    pub zero_valued_slots: B256Set,
522    /// Flag indicating whether the storage was wiped or not.
523    pub wiped: bool,
524}
525
526impl HashedStorageSorted {
527    /// Returns `true` if the account was wiped.
528    pub const fn is_wiped(&self) -> bool {
529        self.wiped
530    }
531
532    /// Returns a sorted iterator over updated storage slots.
533    pub fn storage_slots_sorted(&self) -> impl Iterator<Item = (B256, U256)> {
534        self.non_zero_valued_slots
535            .iter()
536            .map(|(hashed_slot, value)| (*hashed_slot, *value))
537            .chain(self.zero_valued_slots.iter().map(|hashed_slot| (*hashed_slot, U256::ZERO)))
538            .sorted_by_key(|entry| *entry.0)
539    }
540}
541
542/// An iterator that yields chunks of the state updates of at most `size` account and storage
543/// targets.
544///
545/// # Notes
546/// 1. Chunks are expected to be applied in order, because of storage wipes. If applied out of
547///    order, it's possible to wipe more storage than in the original state update.
548/// 2. For each account, chunks with storage updates come first, followed by account updates.
549#[derive(Debug)]
550pub struct ChunkedHashedPostState {
551    flattened: alloc::vec::IntoIter<(B256, FlattenedHashedPostStateItem)>,
552    size: usize,
553}
554
555#[derive(Debug)]
556enum FlattenedHashedPostStateItem {
557    Account(Option<Account>),
558    StorageWipe,
559    StorageUpdate { slot: B256, value: U256 },
560}
561
562impl ChunkedHashedPostState {
563    fn new(hashed_post_state: HashedPostState, size: usize) -> Self {
564        let flattened = hashed_post_state
565            .storages
566            .into_iter()
567            .flat_map(|(address, storage)| {
568                // Storage wipes should go first
569                Some((address, FlattenedHashedPostStateItem::StorageWipe))
570                    .filter(|_| storage.wiped)
571                    .into_iter()
572                    .chain(
573                        storage.storage.into_iter().sorted_unstable_by_key(|(slot, _)| *slot).map(
574                            move |(slot, value)| {
575                                (
576                                    address,
577                                    FlattenedHashedPostStateItem::StorageUpdate { slot, value },
578                                )
579                            },
580                        ),
581                    )
582            })
583            .chain(hashed_post_state.accounts.into_iter().map(|(address, account)| {
584                (address, FlattenedHashedPostStateItem::Account(account))
585            }))
586            // We need stable sort here to preserve the order for each address:
587            // 1. Storage wipes
588            // 2. Storage updates
589            // 3. Account update
590            .sorted_by_key(|(address, _)| *address);
591
592        Self { flattened, size }
593    }
594}
595
596impl Iterator for ChunkedHashedPostState {
597    type Item = HashedPostState;
598
599    fn next(&mut self) -> Option<Self::Item> {
600        let mut chunk = HashedPostState::default();
601
602        let mut current_size = 0;
603        while current_size < self.size {
604            let Some((address, item)) = self.flattened.next() else { break };
605
606            match item {
607                FlattenedHashedPostStateItem::Account(account) => {
608                    chunk.accounts.insert(address, account);
609                }
610                FlattenedHashedPostStateItem::StorageWipe => {
611                    chunk.storages.entry(address).or_default().wiped = true;
612                }
613                FlattenedHashedPostStateItem::StorageUpdate { slot, value } => {
614                    chunk.storages.entry(address).or_default().storage.insert(slot, value);
615                }
616            }
617
618            current_size += 1;
619        }
620
621        if chunk.is_empty() {
622            None
623        } else {
624            Some(chunk)
625        }
626    }
627}
628
629#[cfg(test)]
630mod tests {
631    use super::*;
632    use crate::KeccakKeyHasher;
633    use alloy_primitives::Bytes;
634    use revm_database::{states::StorageSlot, StorageWithOriginalValues};
635    use revm_state::{AccountInfo, Bytecode};
636
637    #[test]
638    fn hashed_state_wiped_extension() {
639        let hashed_address = B256::default();
640        let hashed_slot = B256::with_last_byte(64);
641        let hashed_slot2 = B256::with_last_byte(65);
642
643        // Initialize post state storage
644        let original_slot_value = U256::from(123);
645        let mut hashed_state = HashedPostState::default().with_storages([(
646            hashed_address,
647            HashedStorage::from_iter(
648                false,
649                [(hashed_slot, original_slot_value), (hashed_slot2, original_slot_value)],
650            ),
651        )]);
652
653        // Update single slot value
654        let updated_slot_value = U256::from(321);
655        let extension = HashedPostState::default().with_storages([(
656            hashed_address,
657            HashedStorage::from_iter(false, [(hashed_slot, updated_slot_value)]),
658        )]);
659        hashed_state.extend(extension);
660
661        let account_storage = hashed_state.storages.get(&hashed_address);
662        assert_eq!(
663            account_storage.and_then(|st| st.storage.get(&hashed_slot)),
664            Some(&updated_slot_value)
665        );
666        assert_eq!(
667            account_storage.and_then(|st| st.storage.get(&hashed_slot2)),
668            Some(&original_slot_value)
669        );
670        assert_eq!(account_storage.map(|st| st.wiped), Some(false));
671
672        // Wipe account storage
673        let wiped_extension =
674            HashedPostState::default().with_storages([(hashed_address, HashedStorage::new(true))]);
675        hashed_state.extend(wiped_extension);
676
677        let account_storage = hashed_state.storages.get(&hashed_address);
678        assert_eq!(account_storage.map(|st| st.storage.is_empty()), Some(true));
679        assert_eq!(account_storage.map(|st| st.wiped), Some(true));
680
681        // Reinitialize single slot value
682        hashed_state.extend(HashedPostState::default().with_storages([(
683            hashed_address,
684            HashedStorage::from_iter(false, [(hashed_slot, original_slot_value)]),
685        )]));
686        let account_storage = hashed_state.storages.get(&hashed_address);
687        assert_eq!(
688            account_storage.and_then(|st| st.storage.get(&hashed_slot)),
689            Some(&original_slot_value)
690        );
691        assert_eq!(account_storage.and_then(|st| st.storage.get(&hashed_slot2)), None);
692        assert_eq!(account_storage.map(|st| st.wiped), Some(true));
693
694        // Reinitialize single slot value
695        hashed_state.extend(HashedPostState::default().with_storages([(
696            hashed_address,
697            HashedStorage::from_iter(false, [(hashed_slot2, updated_slot_value)]),
698        )]));
699        let account_storage = hashed_state.storages.get(&hashed_address);
700        assert_eq!(
701            account_storage.and_then(|st| st.storage.get(&hashed_slot)),
702            Some(&original_slot_value)
703        );
704        assert_eq!(
705            account_storage.and_then(|st| st.storage.get(&hashed_slot2)),
706            Some(&updated_slot_value)
707        );
708        assert_eq!(account_storage.map(|st| st.wiped), Some(true));
709    }
710
711    #[test]
712    fn test_hashed_post_state_from_bundle_state() {
713        // Prepare a random Ethereum address as a key for the account.
714        let address = Address::random();
715
716        // Create a mock account info object.
717        let account_info = AccountInfo {
718            balance: U256::from(123),
719            nonce: 42,
720            code_hash: B256::random(),
721            code: Some(Bytecode::new_raw(Bytes::from(vec![1, 2]))),
722        };
723
724        let mut storage = StorageWithOriginalValues::default();
725        storage.insert(
726            U256::from(1),
727            StorageSlot { present_value: U256::from(4), ..Default::default() },
728        );
729
730        // Create a `BundleAccount` struct to represent the account and its storage.
731        let account = BundleAccount {
732            status: AccountStatus::Changed,
733            info: Some(account_info.clone()),
734            storage,
735            original_info: None,
736        };
737
738        // Create a vector of tuples representing the bundle state.
739        let state = vec![(&address, &account)];
740
741        // Convert the bundle state into a hashed post state.
742        let hashed_state = HashedPostState::from_bundle_state::<KeccakKeyHasher>(state);
743
744        // Validate the hashed post state.
745        assert_eq!(hashed_state.accounts.len(), 1);
746        assert_eq!(hashed_state.storages.len(), 1);
747
748        // Validate the account info.
749        assert_eq!(
750            *hashed_state.accounts.get(&keccak256(address)).unwrap(),
751            Some(account_info.into())
752        );
753    }
754
755    #[test]
756    fn test_hashed_post_state_with_accounts() {
757        // Prepare random addresses and mock account info.
758        let address_1 = Address::random();
759        let address_2 = Address::random();
760
761        let account_info_1 = AccountInfo {
762            balance: U256::from(1000),
763            nonce: 1,
764            code_hash: B256::random(),
765            code: None,
766        };
767
768        // Create hashed accounts with addresses.
769        let account_1 = (keccak256(address_1), Some(account_info_1.into()));
770        let account_2 = (keccak256(address_2), None);
771
772        // Add accounts to the hashed post state.
773        let hashed_state = HashedPostState::default().with_accounts(vec![account_1, account_2]);
774
775        // Validate the hashed post state.
776        assert_eq!(hashed_state.accounts.len(), 2);
777        assert!(hashed_state.accounts.contains_key(&keccak256(address_1)));
778        assert!(hashed_state.accounts.contains_key(&keccak256(address_2)));
779    }
780
781    #[test]
782    fn test_hashed_post_state_with_storages() {
783        // Prepare random addresses and mock storage entries.
784        let address_1 = Address::random();
785        let address_2 = Address::random();
786
787        let storage_1 = (keccak256(address_1), HashedStorage::new(false));
788        let storage_2 = (keccak256(address_2), HashedStorage::new(true));
789
790        // Add storages to the hashed post state.
791        let hashed_state = HashedPostState::default().with_storages(vec![storage_1, storage_2]);
792
793        // Validate the hashed post state.
794        assert_eq!(hashed_state.storages.len(), 2);
795        assert!(hashed_state.storages.contains_key(&keccak256(address_1)));
796        assert!(hashed_state.storages.contains_key(&keccak256(address_2)));
797    }
798
799    #[test]
800    fn test_hashed_post_state_is_empty() {
801        // Create an empty hashed post state and validate it's empty.
802        let empty_state = HashedPostState::default();
803        assert!(empty_state.is_empty());
804
805        // Add an account and validate the state is no longer empty.
806        let non_empty_state = HashedPostState::default()
807            .with_accounts(vec![(keccak256(Address::random()), Some(Account::default()))]);
808        assert!(!non_empty_state.is_empty());
809    }
810
811    fn create_state_for_multi_proof_targets() -> HashedPostState {
812        let mut state = HashedPostState::default();
813
814        let addr1 = B256::random();
815        let addr2 = B256::random();
816        state.accounts.insert(addr1, Some(Default::default()));
817        state.accounts.insert(addr2, Some(Default::default()));
818
819        let mut storage = HashedStorage::default();
820        let slot1 = B256::random();
821        let slot2 = B256::random();
822        storage.storage.insert(slot1, U256::ZERO);
823        storage.storage.insert(slot2, U256::from(1));
824        state.storages.insert(addr1, storage);
825
826        state
827    }
828
829    #[test]
830    fn test_multi_proof_targets_difference_empty_state() {
831        let state = HashedPostState::default();
832        let excluded = MultiProofTargets::default();
833
834        let targets = state.multi_proof_targets_difference(&excluded);
835        assert!(targets.is_empty());
836    }
837
838    #[test]
839    fn test_multi_proof_targets_difference_new_account_targets() {
840        let state = create_state_for_multi_proof_targets();
841        let excluded = MultiProofTargets::default();
842
843        // should return all accounts as targets since excluded is empty
844        let targets = state.multi_proof_targets_difference(&excluded);
845        assert_eq!(targets.len(), state.accounts.len());
846        for addr in state.accounts.keys() {
847            assert!(targets.contains_key(addr));
848        }
849    }
850
851    #[test]
852    fn test_multi_proof_targets_difference_new_storage_targets() {
853        let state = create_state_for_multi_proof_targets();
854        let excluded = MultiProofTargets::default();
855
856        let targets = state.multi_proof_targets_difference(&excluded);
857
858        // verify storage slots are included for accounts with storage
859        for (addr, storage) in &state.storages {
860            assert!(targets.contains_key(addr));
861            let target_slots = &targets[addr];
862            assert_eq!(target_slots.len(), storage.storage.len());
863            for slot in storage.storage.keys() {
864                assert!(target_slots.contains(slot));
865            }
866        }
867    }
868
869    #[test]
870    fn test_multi_proof_targets_difference_filter_excluded_accounts() {
871        let state = create_state_for_multi_proof_targets();
872        let mut excluded = MultiProofTargets::default();
873
874        // select an account that has no storage updates
875        let excluded_addr = state
876            .accounts
877            .keys()
878            .find(|&&addr| !state.storages.contains_key(&addr))
879            .expect("Should have an account without storage");
880
881        // mark the account as excluded
882        excluded.insert(*excluded_addr, HashSet::default());
883
884        let targets = state.multi_proof_targets_difference(&excluded);
885
886        // should not include the already excluded account since it has no storage updates
887        assert!(!targets.contains_key(excluded_addr));
888        // other accounts should still be included
889        assert_eq!(targets.len(), state.accounts.len() - 1);
890    }
891
892    #[test]
893    fn test_multi_proof_targets_difference_filter_excluded_storage() {
894        let state = create_state_for_multi_proof_targets();
895        let mut excluded = MultiProofTargets::default();
896
897        // mark one storage slot as excluded
898        let (addr, storage) = state.storages.iter().next().unwrap();
899        let mut excluded_slots = HashSet::default();
900        let excluded_slot = *storage.storage.keys().next().unwrap();
901        excluded_slots.insert(excluded_slot);
902        excluded.insert(*addr, excluded_slots);
903
904        let targets = state.multi_proof_targets_difference(&excluded);
905
906        // should not include the excluded storage slot
907        let target_slots = &targets[addr];
908        assert!(!target_slots.contains(&excluded_slot));
909        assert_eq!(target_slots.len(), storage.storage.len() - 1);
910    }
911
912    #[test]
913    fn test_multi_proof_targets_difference_mixed_excluded_state() {
914        let mut state = HashedPostState::default();
915        let mut excluded = MultiProofTargets::default();
916
917        let addr1 = B256::random();
918        let addr2 = B256::random();
919        let slot1 = B256::random();
920        let slot2 = B256::random();
921
922        state.accounts.insert(addr1, Some(Default::default()));
923        state.accounts.insert(addr2, Some(Default::default()));
924
925        let mut storage = HashedStorage::default();
926        storage.storage.insert(slot1, U256::ZERO);
927        storage.storage.insert(slot2, U256::from(1));
928        state.storages.insert(addr1, storage);
929
930        let mut excluded_slots = HashSet::default();
931        excluded_slots.insert(slot1);
932        excluded.insert(addr1, excluded_slots);
933
934        let targets = state.multi_proof_targets_difference(&excluded);
935
936        assert!(targets.contains_key(&addr2));
937        assert!(!targets[&addr1].contains(&slot1));
938        assert!(targets[&addr1].contains(&slot2));
939    }
940
941    #[test]
942    fn test_multi_proof_targets_difference_unmodified_account_with_storage() {
943        let mut state = HashedPostState::default();
944        let excluded = MultiProofTargets::default();
945
946        let addr = B256::random();
947        let slot1 = B256::random();
948        let slot2 = B256::random();
949
950        // don't add the account to state.accounts (simulating unmodified account)
951        // but add storage updates for this account
952        let mut storage = HashedStorage::default();
953        storage.storage.insert(slot1, U256::from(1));
954        storage.storage.insert(slot2, U256::from(2));
955        state.storages.insert(addr, storage);
956
957        assert!(!state.accounts.contains_key(&addr));
958        assert!(!excluded.contains_key(&addr));
959
960        let targets = state.multi_proof_targets_difference(&excluded);
961
962        // verify that we still get the storage slots for the unmodified account
963        assert!(targets.contains_key(&addr));
964
965        let target_slots = &targets[&addr];
966        assert_eq!(target_slots.len(), 2);
967        assert!(target_slots.contains(&slot1));
968        assert!(target_slots.contains(&slot2));
969    }
970
971    #[test]
972    fn test_partition_by_targets() {
973        let addr1 = B256::random();
974        let addr2 = B256::random();
975        let slot1 = B256::random();
976        let slot2 = B256::random();
977
978        let state = HashedPostState {
979            accounts: B256Map::from_iter([
980                (addr1, Some(Default::default())),
981                (addr2, Some(Default::default())),
982            ]),
983            storages: B256Map::from_iter([(
984                addr1,
985                HashedStorage {
986                    wiped: true,
987                    storage: B256Map::from_iter([(slot1, U256::ZERO), (slot2, U256::from(1))]),
988                },
989            )]),
990        };
991        let targets = MultiProofTargets::from_iter([(addr1, HashSet::from_iter([slot1]))]);
992
993        let (with_targets, without_targets) =
994            state.partition_by_targets(&targets, &MultiAddedRemovedKeys::new());
995
996        assert_eq!(
997            with_targets,
998            HashedPostState {
999                accounts: B256Map::from_iter([(addr1, Some(Default::default()))]),
1000                storages: B256Map::from_iter([(
1001                    addr1,
1002                    HashedStorage {
1003                        wiped: true,
1004                        storage: B256Map::from_iter([(slot1, U256::ZERO)])
1005                    }
1006                )]),
1007            }
1008        );
1009        assert_eq!(
1010            without_targets,
1011            HashedPostState {
1012                accounts: B256Map::from_iter([(addr2, Some(Default::default()))]),
1013                storages: B256Map::from_iter([(
1014                    addr1,
1015                    HashedStorage {
1016                        wiped: false,
1017                        storage: B256Map::from_iter([(slot2, U256::from(1))])
1018                    }
1019                )]),
1020            }
1021        );
1022    }
1023
1024    #[test]
1025    fn test_chunks() {
1026        let addr1 = B256::from([1; 32]);
1027        let addr2 = B256::from([2; 32]);
1028        let slot1 = B256::from([1; 32]);
1029        let slot2 = B256::from([2; 32]);
1030
1031        let state = HashedPostState {
1032            accounts: B256Map::from_iter([
1033                (addr1, Some(Default::default())),
1034                (addr2, Some(Default::default())),
1035            ]),
1036            storages: B256Map::from_iter([(
1037                addr2,
1038                HashedStorage {
1039                    wiped: true,
1040                    storage: B256Map::from_iter([(slot1, U256::ZERO), (slot2, U256::from(1))]),
1041                },
1042            )]),
1043        };
1044
1045        let mut chunks = state.chunks(2);
1046        assert_eq!(
1047            chunks.next(),
1048            Some(HashedPostState {
1049                accounts: B256Map::from_iter([(addr1, Some(Default::default()))]),
1050                storages: B256Map::from_iter([(addr2, HashedStorage::new(true)),])
1051            })
1052        );
1053        assert_eq!(
1054            chunks.next(),
1055            Some(HashedPostState {
1056                accounts: B256Map::default(),
1057                storages: B256Map::from_iter([(
1058                    addr2,
1059                    HashedStorage {
1060                        wiped: false,
1061                        storage: B256Map::from_iter([(slot1, U256::ZERO), (slot2, U256::from(1))]),
1062                    },
1063                )])
1064            })
1065        );
1066        assert_eq!(
1067            chunks.next(),
1068            Some(HashedPostState {
1069                accounts: B256Map::from_iter([(addr2, Some(Default::default()))]),
1070                storages: B256Map::default()
1071            })
1072        );
1073        assert_eq!(chunks.next(), None);
1074    }
1075}