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