reth_trie_common/
hashed_state.rs

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