Skip to main content

reth_execution_types/
compat.rs

1//! State conversion at the evm2 execution boundary.
2
3use alloc::vec::Vec;
4use alloy_primitives::{
5    keccak256,
6    map::{hash_map::Entry, AddressMap, B256Map, U256Map},
7    Address, B256, U256,
8};
9use evm2::{bytecode::Bytecode, evm::AccountInfo};
10use reth_primitives_traits::Account;
11use reth_trie_common::{
12    bundle_state::{
13        AccountStatus, BundleRetention, BundleState, StorageSlot, TransitionAccount,
14        TransitionState,
15    },
16    HashedPostState, HashedStorage,
17};
18
19/// One block's account transitions, finalized into a [`BundleState`] with their reverts.
20#[derive(Debug, Default)]
21pub struct BlockState {
22    transitions: AddressMap<TransitionAccount>,
23    contracts: B256Map<Bytecode>,
24}
25
26impl BlockState {
27    /// Creates an empty block accumulator.
28    pub fn new() -> Self {
29        Self::default()
30    }
31
32    /// Applies a collected transaction to the block's transitions.
33    pub fn commit(&mut self, changes: &TransactionChanges) {
34        self.contracts.extend(changes.contracts.iter().map(|(hash, code)| (*hash, code.clone())));
35        for (address, account) in &changes.accounts {
36            self.commit_account(
37                *address,
38                account.original.clone(),
39                (!account.selfdestructed).then(|| account.info.clone()),
40                account.created,
41                account.wiped,
42                account
43                    .storage
44                    .iter()
45                    .filter_map(|(key, slot)| slot.is_changed().then_some((*key, *slot))),
46            );
47        }
48    }
49
50    /// Commits one account's finalized evm2 transaction changes into the block without
51    /// materializing EVM state. Accounts that were only loaded are skipped.
52    ///
53    /// When `update` is set, the committed account is also recorded into it for execution state
54    /// hooks.
55    pub fn commit_account_changes(
56        &mut self,
57        changes: evm2::evm::AccountChanges<'_>,
58        update: Option<&mut StateUpdate>,
59    ) {
60        if !changes.is_changed() {
61            return;
62        }
63        if let Some((hash, code)) = changes.code {
64            self.contracts.entry(hash).or_insert_with(|| code.clone());
65        }
66        let address = changes.address;
67        let created = changes.created;
68        let wiped = changes.storage.wiped;
69        let original = changes.original.map(without_code);
70        let current = changes.current.map(without_code);
71        let slots = changes
72            .storage
73            .changed_slots()
74            .map(|(&key, value)| (key, StorageSlot::new_changed(value.original, value.current)));
75        let Some(update) = update else {
76            self.commit_account(address, original, current, created, wiped, slots);
77            return;
78        };
79        let storage: Vec<_> = slots.collect();
80        self.commit_account(
81            address,
82            original.clone(),
83            current.clone(),
84            created,
85            wiped,
86            storage.iter().copied(),
87        );
88        update.accounts.push(AccountUpdate { address, original, current, created, wiped, storage });
89    }
90
91    fn commit_account(
92        &mut self,
93        address: Address,
94        original: Option<AccountInfo>,
95        current: Option<AccountInfo>,
96        created: bool,
97        wiped: bool,
98        storage: impl Iterator<Item = (U256, StorageSlot)>,
99    ) {
100        let entry = self.transitions.entry(address);
101        let previous_status = match &entry {
102            Entry::Occupied(entry) => entry.get().status,
103            Entry::Vacant(_) => loaded_status(original.as_ref()),
104        };
105        let status = if current.is_none() {
106            previous_status.on_selfdestructed()
107        } else if wiped && !created {
108            AccountStatus::DestroyedChanged
109        } else if created {
110            previous_status.on_created()
111        } else {
112            previous_status.on_changed(
113                original.as_ref().is_none_or(|i| i.nonce == 0 && i.is_empty_code_hash()),
114            )
115        };
116        match entry {
117            Entry::Vacant(entry) => {
118                entry.insert(TransitionAccount {
119                    info: current,
120                    status,
121                    previous_info: original,
122                    previous_status,
123                    storage: storage.collect(),
124                    storage_was_destroyed: wiped,
125                });
126            }
127            Entry::Occupied(mut entry) => {
128                let account = entry.get_mut();
129                account.info = current;
130                account.status = status;
131                // Deletion starts a new storage lifetime.
132                if matches!(status, AccountStatus::Destroyed | AccountStatus::DestroyedAgain) {
133                    account.storage.clear();
134                    account.storage.extend(storage);
135                    account.storage_was_destroyed = true;
136                } else {
137                    for (key, slot) in storage {
138                        match account.storage.entry(key) {
139                            Entry::Vacant(entry) => {
140                                entry.insert(slot);
141                            }
142                            Entry::Occupied(mut entry) => {
143                                if entry.get().original_value() == slot.present_value() {
144                                    entry.remove();
145                                } else {
146                                    entry.get_mut().present_value = slot.present_value();
147                                }
148                            }
149                        }
150                    }
151                }
152            }
153        }
154    }
155
156    /// Finalizes one block's transitions and reverts.
157    pub fn into_bundle(self) -> BundleState {
158        let mut bundle = BundleState::default();
159        bundle.apply_transitions_and_create_reverts(
160            TransitionState { transitions: self.transitions },
161            BundleRetention::Reverts,
162        );
163        bundle.contracts.extend(self.contracts);
164        bundle
165    }
166}
167
168/// Transaction changes collected from an evm2 change stream for [`BlockState::commit`].
169#[derive(Debug, Default)]
170pub struct TransactionChanges {
171    accounts: AddressMap<TransactionAccount>,
172    contracts: B256Map<Bytecode>,
173}
174
175/// One account's changes within a [`TransactionChanges`].
176#[derive(Debug)]
177struct TransactionAccount {
178    /// Account at the start of the transaction, without code. `None` means it did not exist.
179    original: Option<AccountInfo>,
180    /// Account after the transaction, without code.
181    info: AccountInfo,
182    created: bool,
183    selfdestructed: bool,
184    wiped: bool,
185    storage: U256Map<StorageSlot>,
186}
187
188impl Default for TransactionAccount {
189    fn default() -> Self {
190        Self {
191            original: Some(AccountInfo::empty()),
192            info: AccountInfo::empty(),
193            created: false,
194            selfdestructed: false,
195            wiped: false,
196            storage: U256Map::default(),
197        }
198    }
199}
200
201impl TransactionAccount {
202    fn update(&mut self, change: evm2::evm::AccountChangeRef<'_>) {
203        self.original = change.original.map(without_code);
204        self.info = change.current.map(without_code).unwrap_or_else(AccountInfo::empty);
205        self.created |= change.created;
206        self.selfdestructed |= change.current.is_none();
207    }
208}
209
210impl evm2::evm::StateChangeSink for TransactionChanges {
211    type Error = core::convert::Infallible;
212    fn bytecode(&mut self, hash: B256, code: &Bytecode) -> Result<(), Self::Error> {
213        self.contracts.entry(hash).or_insert_with(|| code.clone());
214        Ok(())
215    }
216    fn storage_wipe(&mut self, address: Address) -> Result<(), Self::Error> {
217        let account = self.accounts.entry(address).or_default();
218        account.wiped = true;
219        account.storage.clear();
220        Ok(())
221    }
222    fn storage(&mut self, change: evm2::evm::StorageChange) -> Result<(), Self::Error> {
223        self.accounts
224            .entry(change.address)
225            .or_default()
226            .storage
227            .insert(change.key, StorageSlot::new_changed(change.original, change.current));
228        Ok(())
229    }
230    fn account(&mut self, change: evm2::evm::AccountChangeRef<'_>) -> Result<(), Self::Error> {
231        self.accounts.entry(change.address).or_default().update(change);
232        Ok(())
233    }
234    fn account_read(
235        &mut self,
236        address: Address,
237        info: Option<&AccountInfo>,
238    ) -> Result<(), Self::Error> {
239        if let Some(account) = self.accounts.get_mut(&address) {
240            account.update(evm2::evm::AccountChangeRef {
241                address,
242                original: info,
243                current: info,
244                created: false,
245                selfdestructed: false,
246            });
247        }
248        Ok(())
249    }
250}
251
252/// Per-transaction state changes streamed to execution state hooks.
253///
254/// Holds only the accounts a transaction committed, with account info but without bytecode, so
255/// building it does not copy the transaction's state maps.
256#[derive(Clone, Debug, Default, PartialEq, Eq)]
257pub struct StateUpdate {
258    /// Committed accounts in stream order.
259    pub accounts: Vec<AccountUpdate>,
260}
261
262impl StateUpdate {
263    /// Returns whether the update commits no accounts.
264    pub const fn is_empty(&self) -> bool {
265        self.accounts.is_empty()
266    }
267
268    /// Returns the update for `address`, if the transaction committed it.
269    pub fn account(&self, address: &Address) -> Option<&AccountUpdate> {
270        self.accounts.iter().find(|account| account.address == *address)
271    }
272}
273
274/// One committed account in a [`StateUpdate`].
275#[derive(Clone, Debug, PartialEq, Eq)]
276pub struct AccountUpdate {
277    /// Account address.
278    pub address: Address,
279    /// Account at the start of the transaction, without code. `None` means it did not exist.
280    pub original: Option<AccountInfo>,
281    /// Account after the transaction, without code. `None` is a deletion.
282    pub current: Option<AccountInfo>,
283    /// Whether the account was created during the transaction.
284    pub created: bool,
285    /// Whether the account's prior storage was wiped.
286    pub wiped: bool,
287    /// Storage slots whose value changed.
288    pub storage: Vec<(U256, StorageSlot)>,
289}
290
291/// Hashes a transaction's state update into the trie update consumed by state root tasks.
292pub fn state_update_to_hashed_post_state(update: &StateUpdate) -> HashedPostState {
293    let mut hashed_state = HashedPostState::with_capacity(update.accounts.len());
294    for account in &update.accounts {
295        let hashed_address = keccak256(account.address);
296        let destroyed = account.current.is_none();
297        let info = account.current.clone().unwrap_or_default();
298        let unchanged = account
299            .original
300            .as_ref()
301            .map_or_else(|| info == AccountInfo::default(), |original| info == *original);
302        // EIP-161: a touched account that ends up empty is deleted, unless it never existed.
303        if destroyed || (info.is_empty() && account.original.is_some()) {
304            hashed_state.accounts.insert(hashed_address, None);
305        } else if !unchanged {
306            hashed_state.accounts.insert(hashed_address, Some(Account::from(&info)));
307        }
308        if !destroyed && !account.storage.is_empty() {
309            hashed_state.storages.insert(
310                hashed_address,
311                HashedStorage::from_iter(
312                    account
313                        .storage
314                        .iter()
315                        .map(|(key, slot)| (keccak256(B256::from(*key)), slot.present_value)),
316                ),
317            );
318        }
319    }
320    hashed_state
321}
322
323fn without_code(info: &AccountInfo) -> AccountInfo {
324    AccountInfo { code: None, ..info.clone() }
325}
326
327/// Visits a persisted bundle as native changes when seeding an execution cache.
328#[derive(Debug)]
329pub struct BundleSource<'a>(pub &'a BundleState);
330
331impl evm2::evm::StateChangeSource for BundleSource<'_> {
332    fn visit<S: evm2::evm::StateChangeSink>(&self, sink: &mut S) -> Result<(), S::Error> {
333        for (hash, code) in &self.0.contracts {
334            sink.bytecode(*hash, code)?;
335        }
336        for (address, account) in &self.0.state {
337            let current = account.info.as_ref();
338            if account.status.was_destroyed() {
339                sink.storage_wipe(*address)?;
340            }
341            sink.account(evm2::evm::AccountChangeRef {
342                address: *address,
343                original: account.original_info.as_ref(),
344                current,
345                created: account.status.is_storage_known() && current.is_some(),
346                selfdestructed: current.is_none(),
347            })?;
348            for (key, value) in &account.storage {
349                sink.storage(evm2::evm::StorageChange {
350                    address: *address,
351                    key: *key,
352                    original: value.original_value(),
353                    current: value.present_value(),
354                })?;
355            }
356        }
357        Ok(())
358    }
359}
360
361fn loaded_status(info: Option<&AccountInfo>) -> AccountStatus {
362    match info {
363        None => AccountStatus::LoadedNotExisting,
364        Some(info) if info.is_empty() => AccountStatus::LoadedEmptyEIP161,
365        Some(_) => AccountStatus::Loaded,
366    }
367}
368
369#[cfg(test)]
370mod tests {
371    use super::*;
372    use evm2::evm::{
373        AccountChangeRef, AccountChanges, StateChangeSink, StorageChange, StorageOverlay,
374        StorageSlot as NativeSlot, Tracked,
375    };
376
377    /// Builds a storage overlay from `(key, original, current)` slots.
378    fn overlay(wiped: bool, slots: &[(U256, U256, U256)]) -> StorageOverlay {
379        let mut storage = StorageOverlay { wiped, ..Default::default() };
380        for &(key, original, current) in slots {
381            let slot =
382                NativeSlot { value: Tracked::from_parts(original, current), ..Default::default() };
383            storage.slots.insert(key, slot);
384        }
385        storage
386    }
387
388    #[cfg(feature = "account-ext")]
389    #[test]
390    fn account_extensions_survive_native_updates_and_reverts() {
391        let address = Address::with_last_byte(1);
392        let original = AccountInfo {
393            balance: U256::from(10),
394            extension: evm2::evm::AccountExtension::copy_from_slice(b"original extension"),
395            ..Default::default()
396        };
397        let mut updated = original.clone();
398        updated.balance = U256::from(20);
399        updated.extension = evm2::evm::AccountExtension::copy_from_slice(b"updated extension");
400        let mut state = BlockState::new();
401        state.commit_account_changes(
402            AccountChanges {
403                address,
404                original: Some(&original),
405                current: Some(&updated),
406                created: false,
407                selfdestructed: false,
408                touched: false,
409                code: None,
410                storage: &StorageOverlay::default(),
411            },
412            None,
413        );
414        let mut bundle = state.into_bundle();
415        let account = bundle.state.get(&address).unwrap();
416        assert_eq!(
417            account.original_info.as_ref().unwrap().extension.as_ref(),
418            b"original extension"
419        );
420        assert_eq!(account.info.as_ref().unwrap().extension.as_ref(), b"updated extension");
421        assert!(bundle.revert_latest());
422        assert_eq!(
423            bundle.state.get(&address).unwrap().info.as_ref().unwrap().extension.as_ref(),
424            b"original extension"
425        );
426    }
427
428    #[test]
429    fn native_sink_preserves_storage_lifetimes_and_reverts() {
430        let address = Address::with_last_byte(1);
431        let code = evm2::bytecode::Bytecode::new_raw(alloy_primitives::bytes!("60015b00"));
432        let info = AccountInfo::default().with_nonce(1).with_code(code.clone());
433        let mut converted = BlockState::new();
434        let mut native = BlockState::new();
435        // Storage-only writes, restoration, deletion, recreation, a surviving storage wipe,
436        // and another deletion exercise both occupied and vacant transition entries.
437        for step in 0..7 {
438            let slots: &[_] = if step < 2 || step == 3 || step == 4 {
439                let (original, current) = if step == 1 { (8, 7) } else { (7, 8) };
440                &[(U256::from(3), U256::from(original), U256::from(current))]
441            } else {
442                &[]
443            };
444            let storage = overlay(step >= 2, slots);
445            let changes = if step < 2 {
446                AccountChanges {
447                    address,
448                    original: Some(&info),
449                    current: Some(&info),
450                    created: false,
451                    selfdestructed: false,
452                    touched: false,
453                    code: None,
454                    storage: &storage,
455                }
456            } else {
457                AccountChanges {
458                    address,
459                    original: (step != 3 && step != 6).then_some(&info),
460                    current: (step == 3 || step == 4).then_some(&info),
461                    created: step == 3,
462                    selfdestructed: step != 3,
463                    touched: false,
464                    code: (step == 3).then(|| (code.hash_slow(), &code)),
465                    storage: &storage,
466                }
467            };
468            let empty = StorageOverlay::default();
469            // Reads alone must never introduce an account transition.
470            let read = AccountChanges {
471                address: Address::with_last_byte(2),
472                original: Some(&info),
473                current: Some(&info),
474                created: false,
475                selfdestructed: false,
476                touched: false,
477                code: None,
478                storage: &empty,
479            };
480            let mut transaction = TransactionChanges::default();
481            let Ok(()) = changes.visit(&mut transaction);
482            let Ok(()) = read.visit(&mut transaction);
483            converted.commit(&transaction);
484            native.commit_account_changes(changes, None);
485            native.commit_account_changes(read, None);
486            assert_eq!(native.transitions, converted.transitions, "step {step}");
487            assert_eq!(native.contracts, converted.contracts);
488        }
489        let mut native = native.into_bundle();
490        let mut converted = converted.into_bundle();
491        assert_eq!(native, converted);
492        assert!(native.revert_latest());
493        assert!(converted.revert_latest());
494        assert_eq!(native, converted);
495    }
496
497    #[test]
498    fn cached_bytecode_does_not_change_bundle_output() {
499        let address = Address::with_last_byte(1);
500        let code = evm2::bytecode::Bytecode::new_raw(alloy_primitives::bytes!("60015b00"));
501        let info = AccountInfo {
502            balance: U256::from(5),
503            code_hash: code.hash_slow(),
504            ..Default::default()
505        };
506        let bundle = |info: &AccountInfo| {
507            let mut changes = TransactionChanges::default();
508            changes
509                .account(AccountChangeRef {
510                    address,
511                    original: None,
512                    current: Some(info),
513                    created: false,
514                    selfdestructed: false,
515                })
516                .unwrap();
517            let mut block = BlockState::new();
518            block.commit(&changes);
519            block.into_bundle()
520        };
521        let uncached = bundle(&info);
522        let cached = bundle(&AccountInfo { code: Some(code), ..info });
523        assert_eq!(uncached, cached);
524        assert!(cached.contracts.is_empty());
525    }
526
527    #[test]
528    fn deployed_bytecode_is_retained_separately_from_account_metadata() {
529        let address = Address::with_last_byte(1);
530        let code = evm2::bytecode::Bytecode::new_raw(alloy_primitives::bytes!("60015b00"));
531        let hash = code.hash_slow();
532        let info = AccountInfo {
533            nonce: 1,
534            code_hash: hash,
535            code: Some(code.clone()),
536            ..Default::default()
537        };
538        let mut changes = TransactionChanges::default();
539        changes.bytecode(hash, &code).unwrap();
540        changes
541            .account(AccountChangeRef {
542                address,
543                original: None,
544                current: Some(&info),
545                created: true,
546                selfdestructed: false,
547            })
548            .unwrap();
549        let mut block = BlockState::new();
550        block.commit(&changes);
551        let bundle = block.into_bundle();
552        assert_eq!(bundle.contracts[&hash].original_bytes(), code.original_bytes());
553        assert_eq!(bundle.account(&address).unwrap().info.as_ref().unwrap().code_hash, hash);
554    }
555
556    #[test]
557    fn storage_only_change_retains_account_and_reverts() {
558        let address = Address::with_last_byte(1);
559        let info = AccountInfo { nonce: 1, ..Default::default() };
560        let mut changes = TransactionChanges::default();
561        changes
562            .storage(StorageChange {
563                address,
564                key: U256::from(3),
565                original: U256::from(7),
566                current: U256::from(8),
567            })
568            .unwrap();
569        changes.account_read(address, Some(&info)).unwrap();
570        let mut block = BlockState::new();
571        block.commit(&changes);
572        let mut bundle = block.into_bundle();
573        assert_eq!(bundle.account(&address).unwrap().info.as_ref().unwrap().nonce, 1);
574        assert_eq!(bundle.storage(&address, U256::from(3)), Some(U256::from(8)));
575        assert_eq!(bundle.reverts.len(), 1);
576        assert!(bundle.revert_latest());
577        assert_eq!(bundle.storage(&address, U256::from(3)), Some(U256::from(7)));
578    }
579
580    #[test]
581    fn destroy_then_recreate_retains_parent_storage_wipe() {
582        let address = Address::with_last_byte(1);
583        let original = AccountInfo { nonce: 1, balance: U256::from(7), ..Default::default() };
584        let recreated = AccountInfo { nonce: 1, balance: U256::from(9), ..Default::default() };
585        let mut block = BlockState::new();
586        let mut destroyed = TransactionChanges::default();
587        destroyed
588            .account(AccountChangeRef {
589                address,
590                original: Some(&original),
591                current: None,
592                created: false,
593                selfdestructed: true,
594            })
595            .unwrap();
596        block.commit(&destroyed);
597        let mut created = TransactionChanges::default();
598        created.storage_wipe(address).unwrap();
599        created
600            .storage(StorageChange {
601                address,
602                key: U256::from(3),
603                original: U256::ZERO,
604                current: U256::from(8),
605            })
606            .unwrap();
607        created
608            .account(AccountChangeRef {
609                address,
610                original: None,
611                current: Some(&recreated),
612                created: true,
613                selfdestructed: false,
614            })
615            .unwrap();
616        block.commit(&created);
617        let bundle = block.into_bundle();
618        let account = bundle.account(&address).unwrap();
619        assert!(account.was_destroyed());
620        assert_eq!(account.original_info.as_ref().unwrap().balance, U256::from(7));
621        assert_eq!(account.info.as_ref().unwrap().balance, U256::from(9));
622        assert_eq!(account.storage_slot(U256::from(4)), Some(U256::ZERO));
623        assert_eq!(account.storage_slot(U256::from(3)), Some(U256::from(8)));
624        assert!(bundle.reverts[0][0].1.wipe_storage);
625    }
626}