Skip to content

Commit 0739937

Browse files
JereSalojrchatructomip01JulianVenturaavilagaston9
authored
refactor(levm): implement cache rollback (#2417)
**Motivation** <!-- Why does this pull request exist? What are its goals? --> - Implement cache rollback for avoiding cloning the cache during the execution of a transaction. **Description** - Now callframe has `cache_backup`, that stores the pre-write state of the account that the callframe is trying to mutate. If the context reverts that state is restored in the cache. Otherwise, the parent call frame inherits the changes of the child of the accounts that only the child has modified, so that if the parent callframe reverts it can revert what the child did. - Move some database related functions that don't need backup to `GeneralizedDatabase` - Move some database related functions that need backup `VM`. Basically it accesses the database backup up if there's a callframe available for doing so. - Stop popping callframe whenever possible Some other changes that it makes: - Simplify `finalize_execution`. Specifically the reversion of value transfer and removal of check for coinbase transfer of gas fee. - Move some things to `utils.rs` and `gen_db.rs` so that `vm.rs` keeps main functionalities. Closes #issue_number --------- Co-authored-by: Javier Chatruc <[email protected]> Co-authored-by: Tomás Paradelo <[email protected]> Co-authored-by: Julian Ventura <[email protected]> Co-authored-by: Avila Gastón <[email protected]> Co-authored-by: fmoletta <[email protected]> Co-authored-by: Matías Onorato <[email protected]> Co-authored-by: Edgar <[email protected]> Co-authored-by: Tomás Arjovsky <[email protected]> Co-authored-by: Martin Paulucci <[email protected]> Co-authored-by: Lucas Fiegl <[email protected]> Co-authored-by: Estéfano Bargas <[email protected]> Co-authored-by: Javier Rodríguez Chatruc <[email protected]> Co-authored-by: VolodymyrBg <[email protected]> Co-authored-by: Tomás Paradelo <[email protected]> Co-authored-by: Mauro Toscano <[email protected]> Co-authored-by: Cypher Pepe <[email protected]>
1 parent 64bca8a commit 0739937

File tree

18 files changed

+724
-738
lines changed

18 files changed

+724
-738
lines changed

cmd/ef_tests/state/runner/levm_runner.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ use ethrex_common::{
99
H256, U256,
1010
};
1111
use ethrex_levm::{
12+
db::gen_db::GeneralizedDatabase,
1213
errors::{ExecutionReport, TxValidationError, VMError},
13-
vm::{EVMConfig, GeneralizedDatabase, VM},
14+
vm::{EVMConfig, VM},
1415
Environment,
1516
};
1617
use ethrex_rlp::encode::RLPEncode;

cmd/ef_tests/state/utils.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use crate::{
55
types::{EFTest, EFTestTransaction},
66
};
77
use ethrex_common::{types::Genesis, H256, U256};
8-
use ethrex_levm::{db::CacheDB, vm::GeneralizedDatabase};
8+
use ethrex_levm::db::{gen_db::GeneralizedDatabase, CacheDB};
99
use ethrex_storage::{EngineType, Store};
1010
use ethrex_vm::{
1111
backends::revm::db::{evm_state, EvmState},

crates/l2/utils/prover/save_state.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,7 @@ pub fn block_number_has_all_needed_proofs(
388388
#[allow(clippy::expect_used)]
389389
mod tests {
390390
use ethrex_blockchain::Blockchain;
391+
use ethrex_levm::db::gen_db::GeneralizedDatabase;
391392
use ethrex_storage::{EngineType, Store};
392393
use ethrex_vm::{
393394
backends::levm::{CacheDB, LEVM},
@@ -396,7 +397,6 @@ mod tests {
396397

397398
use super::*;
398399
use crate::utils::test_data_io;
399-
use ethrex_levm::vm::GeneralizedDatabase;
400400
use std::{
401401
fs::{self},
402402
sync::Arc,

crates/vm/backends/levm/mod.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@ use ethrex_common::{
1717
},
1818
Address, H256, U256,
1919
};
20+
use ethrex_levm::db::gen_db::GeneralizedDatabase;
2021
use ethrex_levm::{
2122
errors::{ExecutionReport, TxResult, VMError},
22-
vm::{EVMConfig, GeneralizedDatabase, Substate, VM},
23+
vm::{EVMConfig, Substate, VM},
2324
Account, Environment,
2425
};
2526
use ethrex_storage::error::StoreError;

crates/vm/backends/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ use ethrex_common::types::{
1111
AccessList, Block, BlockHeader, Fork, GenericTransaction, Receipt, Transaction, Withdrawal,
1212
};
1313
use ethrex_common::{Address, H256};
14+
use ethrex_levm::db::gen_db::GeneralizedDatabase;
1415
use ethrex_levm::db::CacheDB;
15-
use ethrex_levm::vm::GeneralizedDatabase;
1616
use ethrex_storage::Store;
1717
use ethrex_storage::{error::StoreError, AccountUpdate};
1818
use levm::LEVM;

crates/vm/levm/bench/revm_comparison/src/lib.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ use ethrex_common::{
44
Address as EthrexAddress, U256,
55
};
66
use ethrex_levm::{
7-
db::{cache, CacheDB},
7+
db::{cache, gen_db::GeneralizedDatabase, CacheDB},
88
errors::{TxResult, VMError},
9-
vm::{GeneralizedDatabase, VM},
9+
vm::VM,
1010
Environment,
1111
};
1212
use ethrex_vm::db::ExecutionDB;
@@ -84,7 +84,8 @@ pub fn run_with_levm(program: &str, runs: u64, calldata: &str) {
8484
),
8585
);
8686

87-
for _ in 0..runs - 1 {
87+
// when using stateful execute() we have to use nonce when instantiating the vm. Otherwise use 0.
88+
for _nonce in 0..runs - 1 {
8889
let mut vm = new_vm_with_bytecode(&mut db, 0).unwrap();
8990
vm.call_frames.last_mut().unwrap().calldata = calldata.clone();
9091
vm.env.gas_limit = u64::MAX - 1;

crates/vm/levm/src/call_frame.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ use crate::{
44
memory::Memory,
55
opcodes::Opcode,
66
utils::get_valid_jump_destinations,
7+
Account,
78
};
89
use bytes::Bytes;
910
use ethrex_common::{types::Log, Address, U256};
10-
use std::collections::HashSet;
11+
use std::collections::{HashMap, HashSet};
1112

1213
#[derive(Debug, Clone, Default, PartialEq, Eq)]
1314
pub struct Stack {
@@ -85,8 +86,12 @@ pub struct CallFrame {
8586
pub valid_jump_destinations: HashSet<usize>,
8687
/// This is set to true if the function that created this callframe is CREATE or CREATE2
8788
pub create_op_called: bool,
89+
/// Everytime we want to write an account during execution of a callframe we store the pre-write state so that we can restore if it reverts
90+
pub cache_backup: CacheBackup,
8891
}
8992

93+
pub type CacheBackup = HashMap<Address, Option<Account>>;
94+
9095
impl CallFrame {
9196
#[allow(clippy::too_many_arguments)]
9297
pub fn new(

crates/vm/levm/src/db/gen_db.rs

+257
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
use std::sync::Arc;
2+
3+
use bytes::Bytes;
4+
use ethrex_common::types::Fork;
5+
use ethrex_common::Address;
6+
use ethrex_common::U256;
7+
use keccak_hash::H256;
8+
9+
use crate::errors::InternalError;
10+
use crate::errors::VMError;
11+
use crate::vm::Substate;
12+
use crate::vm::VM;
13+
use crate::Account;
14+
use crate::AccountInfo;
15+
use crate::StorageSlot;
16+
use std::collections::HashMap;
17+
18+
use super::cache;
19+
use super::error::DatabaseError;
20+
use super::CacheDB;
21+
use super::Database;
22+
23+
#[derive(Clone)]
24+
pub struct GeneralizedDatabase {
25+
pub store: Arc<dyn Database>,
26+
pub cache: CacheDB,
27+
}
28+
29+
impl GeneralizedDatabase {
30+
pub fn new(store: Arc<dyn Database>, cache: CacheDB) -> Self {
31+
Self { store, cache }
32+
}
33+
34+
// ================== Account related functions =====================
35+
/// Gets account, first checking the cache and then the database
36+
/// (caching in the second case)
37+
pub fn get_account(&mut self, address: Address) -> Result<Account, DatabaseError> {
38+
match cache::get_account(&self.cache, &address) {
39+
Some(acc) => Ok(acc.clone()),
40+
None => {
41+
let account_info = self.store.get_account_info(address)?;
42+
let account = Account {
43+
info: account_info,
44+
storage: HashMap::new(),
45+
};
46+
cache::insert_account(&mut self.cache, address, account.clone());
47+
Ok(account)
48+
}
49+
}
50+
}
51+
52+
/// Gets account without pushing it to the cache
53+
pub fn get_account_no_push_cache(&self, address: Address) -> Result<Account, DatabaseError> {
54+
match cache::get_account(&self.cache, &address) {
55+
Some(acc) => Ok(acc.clone()),
56+
None => {
57+
let account_info = self.store.get_account_info(address)?;
58+
Ok(Account {
59+
info: account_info,
60+
storage: HashMap::new(),
61+
})
62+
}
63+
}
64+
}
65+
66+
/// **Accesses to an account's information.**
67+
///
68+
/// Accessed accounts are stored in the `touched_accounts` set.
69+
/// Accessed accounts take place in some gas cost computation.
70+
pub fn access_account(
71+
&mut self,
72+
accrued_substate: &mut Substate,
73+
address: Address,
74+
) -> Result<(AccountInfo, bool), DatabaseError> {
75+
let address_was_cold = accrued_substate.touched_accounts.insert(address);
76+
let account = match cache::get_account(&self.cache, &address) {
77+
Some(account) => account.info.clone(),
78+
None => self.store.get_account_info(address)?,
79+
};
80+
Ok((account, address_was_cold))
81+
}
82+
}
83+
84+
impl<'a> VM<'a> {
85+
// ================== Account related functions =====================
86+
87+
pub fn get_account_mut(&mut self, address: Address) -> Result<&mut Account, VMError> {
88+
if !cache::is_account_cached(&self.db.cache, &address) {
89+
let account_info = self.db.store.get_account_info(address)?;
90+
let account = Account {
91+
info: account_info,
92+
storage: HashMap::new(),
93+
};
94+
cache::insert_account(&mut self.db.cache, address, account.clone());
95+
}
96+
97+
let backup_account = cache::get_account(&self.db.cache, &address)
98+
.ok_or(VMError::Internal(InternalError::AccountNotFound))?
99+
.clone();
100+
101+
if let Ok(frame) = self.current_call_frame_mut() {
102+
frame
103+
.cache_backup
104+
.entry(address)
105+
.or_insert_with(|| Some(backup_account));
106+
}
107+
108+
let account = cache::get_account_mut(&mut self.db.cache, &address)
109+
.ok_or(VMError::Internal(InternalError::AccountNotFound))?;
110+
111+
Ok(account)
112+
}
113+
114+
pub fn increase_account_balance(
115+
&mut self,
116+
address: Address,
117+
increase: U256,
118+
) -> Result<(), VMError> {
119+
let account = self.get_account_mut(address)?;
120+
account.info.balance = account
121+
.info
122+
.balance
123+
.checked_add(increase)
124+
.ok_or(VMError::BalanceOverflow)?;
125+
Ok(())
126+
}
127+
128+
pub fn decrease_account_balance(
129+
&mut self,
130+
address: Address,
131+
decrease: U256,
132+
) -> Result<(), VMError> {
133+
let account = self.get_account_mut(address)?;
134+
account.info.balance = account
135+
.info
136+
.balance
137+
.checked_sub(decrease)
138+
.ok_or(VMError::BalanceUnderflow)?;
139+
Ok(())
140+
}
141+
142+
/// Updates bytecode of given account.
143+
pub fn update_account_bytecode(
144+
&mut self,
145+
address: Address,
146+
new_bytecode: Bytes,
147+
) -> Result<(), VMError> {
148+
let account = self.get_account_mut(address)?;
149+
account.info.bytecode = new_bytecode;
150+
Ok(())
151+
}
152+
153+
// =================== Nonce related functions ======================
154+
pub fn increment_account_nonce(&mut self, address: Address) -> Result<u64, VMError> {
155+
let account = self.get_account_mut(address)?;
156+
account.info.nonce = account
157+
.info
158+
.nonce
159+
.checked_add(1)
160+
.ok_or(VMError::NonceOverflow)?;
161+
Ok(account.info.nonce)
162+
}
163+
164+
/// Inserts account to cache backing up the previus state of it in the CacheBackup (if it wasn't already backed up)
165+
pub fn insert_account(&mut self, address: Address, account: Account) -> Result<(), VMError> {
166+
let previous_account = cache::insert_account(&mut self.db.cache, address, account);
167+
168+
if let Ok(frame) = self.current_call_frame_mut() {
169+
frame
170+
.cache_backup
171+
.entry(address)
172+
.or_insert_with(|| previous_account.as_ref().map(|account| (*account).clone()));
173+
}
174+
175+
Ok(())
176+
}
177+
178+
/// Removes account from cache backing up the previus state of it in the CacheBackup (if it wasn't already backed up)
179+
pub fn remove_account(&mut self, address: Address) -> Result<(), VMError> {
180+
let previous_account = cache::remove_account(&mut self.db.cache, &address);
181+
182+
if let Ok(frame) = self.current_call_frame_mut() {
183+
frame
184+
.cache_backup
185+
.entry(address)
186+
.or_insert_with(|| previous_account.as_ref().map(|account| (*account).clone()));
187+
}
188+
189+
Ok(())
190+
}
191+
192+
/// Accesses to an account's storage slot.
193+
///
194+
/// Accessed storage slots are stored in the `touched_storage_slots` set.
195+
/// Accessed storage slots take place in some gas cost computation.
196+
pub fn access_storage_slot(
197+
&mut self,
198+
address: Address,
199+
key: H256,
200+
) -> Result<(StorageSlot, bool), VMError> {
201+
// [EIP-2929] - Introduced conditional tracking of accessed storage slots for Berlin and later specs.
202+
let mut storage_slot_was_cold = false;
203+
if self.env.config.fork >= Fork::Berlin {
204+
storage_slot_was_cold = self
205+
.accrued_substate
206+
.touched_storage_slots
207+
.entry(address)
208+
.or_default()
209+
.insert(key);
210+
}
211+
let storage_slot = match cache::get_account(&self.db.cache, &address) {
212+
Some(account) => match account.storage.get(&key) {
213+
Some(storage_slot) => storage_slot.clone(),
214+
None => {
215+
let value = self.db.store.get_storage_slot(address, key)?;
216+
StorageSlot {
217+
original_value: value,
218+
current_value: value,
219+
}
220+
}
221+
},
222+
None => {
223+
let value = self.db.store.get_storage_slot(address, key)?;
224+
StorageSlot {
225+
original_value: value,
226+
current_value: value,
227+
}
228+
}
229+
};
230+
231+
// When updating account storage of an account that's not yet cached we need to store the StorageSlot in the account
232+
// Note: We end up caching the account because it is the most straightforward way of doing it.
233+
let account = self.get_account_mut(address)?;
234+
account.storage.insert(key, storage_slot.clone());
235+
236+
Ok((storage_slot, storage_slot_was_cold))
237+
}
238+
239+
pub fn update_account_storage(
240+
&mut self,
241+
address: Address,
242+
key: H256,
243+
new_value: U256,
244+
) -> Result<(), VMError> {
245+
let account = self.get_account_mut(address)?;
246+
let account_original_storage_slot_value = account
247+
.storage
248+
.get(&key)
249+
.map_or(U256::zero(), |slot| slot.original_value);
250+
let slot = account.storage.entry(key).or_insert(StorageSlot {
251+
original_value: account_original_storage_slot_value,
252+
current_value: new_value,
253+
});
254+
slot.current_value = new_value;
255+
Ok(())
256+
}
257+
}

crates/vm/levm/src/db/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use ethrex_common::{types::ChainConfig, Address, H256, U256};
66
pub mod cache;
77
pub use cache::CacheDB;
88
pub mod error;
9+
pub mod gen_db;
910

1011
pub trait Database: Send + Sync {
1112
fn get_account_info(&self, address: Address) -> Result<AccountInfo, DatabaseError>;

0 commit comments

Comments
 (0)