Skip to main content

reth_trie_common/bundle_state/
bundle_account.rs

1use super::{
2    reverts::AccountInfoRevert, AccountRevert, AccountStatus, RevertToSlot, StorageSlot,
3    StorageWithOriginalValues, TransitionAccount,
4};
5use alloy_primitives::{
6    map::{HashMap, U256Map},
7    U256,
8};
9use evm2::evm::AccountInfo;
10
11/// Account information focused on creating of database changesets
12/// and Reverts.
13///
14/// Status is needed as to know from what state we are applying the TransitionAccount.
15///
16/// Original account info is needed to know if there was a change.
17///
18/// Same thing for storage with original value.
19///
20/// On selfdestruct storage original value is ignored.
21#[derive(Clone, Debug, PartialEq, Eq)]
22#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
23pub struct BundleAccount {
24    /// Current account information.
25    pub info: Option<AccountInfo>,
26    /// Original account information before modifications.
27    pub original_info: Option<AccountInfo>,
28    /// Contains both original and present state.
29    /// When extracting changeset we compare if original value is different from present value.
30    /// If it is different we add it to changeset.
31    ///
32    /// If Account was destroyed we ignore original value and compare present state with
33    /// U256::ZERO.
34    pub storage: StorageWithOriginalValues,
35    /// Account status.
36    pub status: AccountStatus,
37}
38
39impl BundleAccount {
40    /// Create new BundleAccount.
41    pub const fn new(
42        original_info: Option<AccountInfo>,
43        present_info: Option<AccountInfo>,
44        storage: StorageWithOriginalValues,
45        status: AccountStatus,
46    ) -> Self {
47        Self { info: present_info, original_info, storage, status }
48    }
49
50    /// The approximate size of changes needed to store this account.
51    ///
52    /// `1 + storage_len`
53    pub fn size_hint(&self) -> usize {
54        1 + self.storage.len()
55    }
56
57    /// Return storage slot if it exists.
58    ///
59    /// In case we know that account is newly created or destroyed, return
60    /// `Some(U256::ZERO)`
61    pub fn storage_slot(&self, slot: U256) -> Option<U256> {
62        let slot = self.storage.get(&slot).map(|s| s.present_value);
63        if slot.is_some() {
64            slot
65        } else if self.status.is_storage_known() {
66            Some(U256::ZERO)
67        } else {
68            None
69        }
70    }
71
72    /// Fetch account info if it exists.
73    pub fn account_info(&self) -> Option<AccountInfo> {
74        self.info.clone()
75    }
76
77    /// Was this account destroyed.
78    pub const fn was_destroyed(&self) -> bool {
79        self.status.was_destroyed()
80    }
81
82    /// Return true of account info was changed.
83    pub fn is_info_changed(&self) -> bool {
84        self.info != self.original_info
85    }
86
87    /// Return true if contract was changed
88    pub fn is_contract_changed(&self) -> bool {
89        self.info.as_ref().map(|a| a.code_hash) != self.original_info.as_ref().map(|a| a.code_hash)
90    }
91
92    /// Revert account to previous state and return true if account can be removed.
93    pub fn revert(&mut self, revert: AccountRevert) -> bool {
94        self.status = revert.previous_status;
95
96        match revert.account {
97            AccountInfoRevert::DoNothing => (),
98            AccountInfoRevert::DeleteIt => {
99                self.info = None;
100                if self.original_info.is_none() {
101                    self.storage = HashMap::default();
102                    return true;
103                } else {
104                    // Set all storage to zero but preserve original values.
105                    self.storage.iter_mut().for_each(|(_, v)| {
106                        v.present_value = U256::ZERO;
107                    });
108                    return false;
109                }
110            }
111            AccountInfoRevert::RevertTo(info) => self.info = Some(info),
112        };
113        // Revert storage
114        for (key, slot) in revert.storage {
115            match slot {
116                RevertToSlot::Some(value) => {
117                    // Don't overwrite original values if present
118                    // if storage is not present set original value as current value.
119                    self.storage
120                        .entry(key)
121                        .or_insert_with(|| StorageSlot::new(value))
122                        .present_value = value;
123                }
124                RevertToSlot::Destroyed => {
125                    // If it was destroyed this means that storage was created and we need to remove
126                    // it.
127                    self.storage.remove(&key);
128                }
129            }
130        }
131        false
132    }
133
134    /// Update to new state and generate AccountRevert that if applied to new state will
135    /// revert it to previous state.
136    ///
137    /// If no revert is present, update is noop.
138    pub fn update_and_create_revert(
139        &mut self,
140        transition: TransitionAccount,
141    ) -> Option<AccountRevert> {
142        let updated_info = transition.info;
143        let updated_storage = transition.storage;
144        let updated_status = transition.status;
145
146        // The helper that extends this storage but preserves original value.
147        let extend_storage =
148            |this_storage: &mut StorageWithOriginalValues,
149             storage_update: StorageWithOriginalValues| {
150                this_storage.reserve(storage_update.len());
151                for (key, value) in storage_update {
152                    this_storage.entry(key).or_insert(value).present_value = value.present_value;
153                }
154            };
155
156        let previous_storage_from_update =
157            |updated_storage: &StorageWithOriginalValues| -> U256Map<RevertToSlot> {
158                updated_storage
159                    .iter()
160                    .filter(|s| s.1.is_changed())
161                    .map(|(key, value)| {
162                        (*key, RevertToSlot::Some(value.previous_or_original_value))
163                    })
164                    .collect()
165            };
166
167        // Needed for some reverts.
168        let info_revert = if self.info != updated_info {
169            AccountInfoRevert::RevertTo(self.info.clone().unwrap_or_default())
170        } else {
171            AccountInfoRevert::DoNothing
172        };
173
174        let account_revert = match updated_status {
175            AccountStatus::Changed => {
176                let previous_storage = previous_storage_from_update(&updated_storage);
177                match self.status {
178                    AccountStatus::Changed | AccountStatus::Loaded => {
179                        // Extend the storage. original values is not used inside bundle.
180                        extend_storage(&mut self.storage, updated_storage);
181                    }
182                    AccountStatus::LoadedEmptyEIP161 => {
183                        // Do nothing.
184                        // Only change that can happen from LoadedEmpty to Changed is if balance
185                        // is send to account. So we are only checking account change here.
186                    }
187                    _ => unreachable!("Invalid state transfer to Changed from {self:?}"),
188                };
189                let previous_status = self.status;
190                self.status = AccountStatus::Changed;
191                self.info = updated_info;
192                Some(AccountRevert {
193                    account: info_revert,
194                    storage: previous_storage,
195                    previous_status,
196                    wipe_storage: false,
197                })
198            }
199            AccountStatus::InMemoryChange => {
200                let previous_storage = previous_storage_from_update(&updated_storage);
201                let in_memory_info_revert = match self.status {
202                    AccountStatus::Loaded | AccountStatus::InMemoryChange => {
203                        // From loaded (Or LoadedEmpty) to InMemoryChange can happen if there is
204                        // balance change or new created account but Loaded
205                        // didn't have contract.
206                        extend_storage(&mut self.storage, updated_storage);
207                        info_revert
208                    }
209                    AccountStatus::LoadedEmptyEIP161 => {
210                        self.storage = updated_storage;
211                        info_revert
212                    }
213                    AccountStatus::LoadedNotExisting => {
214                        self.storage = updated_storage;
215                        AccountInfoRevert::DeleteIt
216                    }
217                    _ => unreachable!("Invalid change to InMemoryChange from {self:?}"),
218                };
219                let previous_status = self.status;
220                self.status = AccountStatus::InMemoryChange;
221                self.info = updated_info;
222                Some(AccountRevert {
223                    account: in_memory_info_revert,
224                    storage: previous_storage,
225                    previous_status,
226                    wipe_storage: false,
227                })
228            }
229            AccountStatus::Loaded |
230            AccountStatus::LoadedNotExisting |
231            AccountStatus::LoadedEmptyEIP161 => {
232                // No changeset, maybe just update data
233                // Do nothing for now.
234                None
235            }
236            AccountStatus::Destroyed => {
237                // Clear this storage and move it to the Revert.
238                let this_storage = core::mem::take(&mut self.storage);
239                let ret = match self.status {
240                    AccountStatus::InMemoryChange | AccountStatus::Changed | AccountStatus::Loaded | AccountStatus::LoadedEmptyEIP161 => {
241                        Some(AccountRevert::new_selfdestructed(self.status, info_revert, this_storage))
242                    }
243                    AccountStatus::LoadedNotExisting => {
244                        // Do nothing as we have LoadedNotExisting -> Destroyed (It is noop)
245                        None
246                    }
247                    _ => unreachable!("Invalid transition to Destroyed account from: {self:?} to {updated_info:?} {updated_status:?}"),
248                };
249
250                if ret.is_some() {
251                    self.status = AccountStatus::Destroyed;
252                    self.info = None;
253                }
254
255                // Set present to destroyed.
256                ret
257            }
258            AccountStatus::DestroyedChanged => {
259                // Previous block created account or changed.
260                // (It was destroyed on previous block or one before).
261
262                // Check common pre destroy paths.
263                // If common path is there it will drain the storage.
264                if let Some(revert_state) = AccountRevert::new_selfdestructed_from_bundle(
265                    info_revert.clone(),
266                    self,
267                    &updated_storage,
268                ) {
269                    // Set to destroyed and revert state.
270                    self.status = AccountStatus::DestroyedChanged;
271                    self.info = updated_info;
272                    self.storage = updated_storage;
273
274                    Some(revert_state)
275                } else {
276                    let ret = match self.status {
277                        AccountStatus::Destroyed | AccountStatus::LoadedNotExisting => {
278                            // From destroyed state new account is made
279                            Some(AccountRevert {
280                                account: AccountInfoRevert::DeleteIt,
281                                storage: previous_storage_from_update(&updated_storage),
282                                previous_status: self.status,
283                                wipe_storage: false,
284                            })
285                        }
286                        AccountStatus::DestroyedChanged => {
287                            // Account was destroyed in this transition. So we should clear present
288                            // storage and insert it inside revert.
289
290                            let previous_storage = if transition.storage_was_destroyed {
291                                let mut storage = core::mem::take(&mut self.storage)
292                                    .into_iter()
293                                    .map(|t| (t.0, RevertToSlot::Some(t.1.present_value)))
294                                    .collect::<U256Map<_>>();
295                                for key in updated_storage.keys() {
296                                    // As it is not existing inside Destroyed storage this means
297                                    // that previous values must be zero
298                                    storage.entry(*key).or_insert(RevertToSlot::Destroyed);
299                                }
300                                storage
301                            } else {
302                                previous_storage_from_update(&updated_storage)
303                            };
304
305                            Some(AccountRevert {
306                                account: info_revert,
307                                storage: previous_storage,
308                                previous_status: AccountStatus::DestroyedChanged,
309                                wipe_storage: false,
310                            })
311                        }
312                        AccountStatus::DestroyedAgain => {
313                            Some(AccountRevert::new_selfdestructed_again(
314                                // Destroyed again will set empty account.
315                                AccountStatus::DestroyedAgain,
316                                AccountInfoRevert::DeleteIt,
317                                HashMap::default(),
318                                updated_storage.clone(),
319                            ))
320                        }
321                        _ => unreachable!("Invalid state transfer to DestroyedNew from {self:?}"),
322                    };
323                    self.status = AccountStatus::DestroyedChanged;
324                    self.info = updated_info;
325                    // Extends current storage.
326                    extend_storage(&mut self.storage, updated_storage);
327
328                    ret
329                }
330            }
331            AccountStatus::DestroyedAgain => {
332                // Previous block created account
333                // (It was destroyed on previous block or one before).
334
335                // Check common pre destroy paths.
336                // This will drain the storage if it is common transition.
337                let ret = if let Some(revert_state) = AccountRevert::new_selfdestructed_from_bundle(
338                    info_revert,
339                    self,
340                    &HashMap::default(),
341                ) {
342                    Some(revert_state)
343                } else {
344                    match self.status {
345                        AccountStatus::Destroyed |
346                        AccountStatus::DestroyedAgain |
347                        AccountStatus::LoadedNotExisting => {
348                            // From destroyed to destroyed again is noop
349                            //
350                            // From DestroyedAgain to DestroyedAgain is noop
351                            //
352                            // From LoadedNotExisting to DestroyedAgain is noop
353                            // as account is destroyed again
354                            None
355                        }
356                        AccountStatus::DestroyedChanged => {
357                            // From destroyed changed to destroyed again.
358                            Some(AccountRevert::new_selfdestructed_again(
359                                // Destroyed again will set empty account.
360                                AccountStatus::DestroyedChanged,
361                                AccountInfoRevert::RevertTo(self.info.clone().unwrap_or_default()),
362                                core::mem::take(&mut self.storage),
363                                HashMap::default(),
364                            ))
365                        }
366                        _ => unreachable!("Invalid state to DestroyedAgain from {self:?}"),
367                    }
368                };
369                // Set to destroyed and revert state.
370                self.status = AccountStatus::DestroyedAgain;
371                self.info = None;
372                self.storage.clear();
373                ret
374            }
375        };
376
377        account_revert.filter(|acc| !acc.is_empty())
378    }
379}