Skip to content

Commit 9c85075

Browse files
JereSalojuanimedonemaximopalopolijrchatruc
authored
feat(levm): implement Cache and refactor Db (#991)
**Motivation** <!-- Why does this pull request exist? What are its goals? --> We currently have an in-memory database that should soon be replaced by the node's in-disk database. For that purpose we want to allow us to switch between both kinds of databases. The need to implement this PR's features and refactor the `Db` arose while working on #904. **Description** <!-- A clear and concise general description of the changes this PR introduces --> This PR includes: - Adding a `Cache` to store warm accounts. This removes the need of having `accessed_accounts` and `accessed_storage_slots` sets in `Substate` because we know that if they are cached then they are warm. - Making our `Db` implement the `Database` trait and interact with it only using methods and not it's attributes, so in the future we can implement that trait for the actual node's database. - Fix call opcodes and remove delegate attribute from `CallFrame`. <!-- Link to issues: Resolves #111, Resolves #222 --> Part of #814. --------- Co-authored-by: Juani Medone <juan.medone@lambdaclass.com> Co-authored-by: maximopalopoli <mpalopoli@fi.uba.ar> Co-authored-by: Javier Chatruc <jrchatruc@gmail.com>
1 parent acd0365 commit 9c85075

File tree

13 files changed

+1239
-998
lines changed

13 files changed

+1239
-998
lines changed

crates/vm/levm/Cargo.toml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,3 @@ hex = "0.4.3"
1919

2020
[features]
2121
ethereum_foundation_tests = []
22-
23-
[profile.test]
24-
opt-level = 3
25-
debug-assertions = true

crates/vm/levm/docs/substate.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
## Substate
2+
3+
`accessed_addresses` and `accessed_storage_keys` follow the structure defined in [EIP 2929](https://eips.ethereum.org/EIPS/eip-2929#specification)

crates/vm/levm/src/call_frame.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,13 @@ pub struct CallFrame {
6262
pub gas_limit: U256,
6363
pub gas_used: U256,
6464
pub pc: usize,
65-
pub msg_sender: Address, // Origin address?
65+
/// Address of the account that sent the message
66+
pub msg_sender: Address,
67+
/// Address of the recipient of the message
6668
pub to: Address,
69+
/// Address of the code to execute. Usually the same as `to`, but can be different
6770
pub code_address: Address,
68-
pub delegate: Option<Address>,
71+
/// Bytecode to execute
6972
pub bytecode: Bytes,
7073
pub msg_value: U256,
7174
pub stack: Stack, // max 1024 in the future
@@ -98,7 +101,6 @@ impl CallFrame {
98101
msg_sender: Address,
99102
to: Address,
100103
code_address: Address,
101-
delegate: Option<Address>,
102104
bytecode: Bytes,
103105
msg_value: U256,
104106
calldata: Bytes,
@@ -112,7 +114,6 @@ impl CallFrame {
112114
msg_sender,
113115
to,
114116
code_address,
115-
delegate,
116117
bytecode,
117118
msg_value,
118119
calldata,

crates/vm/levm/src/constants.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ pub mod gas_cost {
4747
pub const RETURNDATACOPY_STATIC: U256 = U256([3, 0, 0, 0]);
4848
pub const RETURNDATACOPY_DYNAMIC_BASE: U256 = U256([3, 0, 0, 0]);
4949
pub const ADDRESS: U256 = U256([2, 0, 0, 0]);
50-
pub const BALANCE: U256 = U256([100, 0, 0, 0]);
5150
pub const ORIGIN: U256 = U256([2, 0, 0, 0]);
5251
pub const CALLER: U256 = U256([2, 0, 0, 0]);
5352
pub const BLOCKHASH: U256 = U256([20, 0, 0, 0]);

crates/vm/levm/src/db.rs

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
use crate::vm::{Account, AccountInfo, StorageSlot};
2+
use ethereum_types::{Address, U256};
3+
use keccak_hash::H256;
4+
use std::collections::HashMap;
5+
6+
pub trait Database {
7+
fn get_account_info(&self, address: Address) -> AccountInfo;
8+
fn get_storage_slot(&self, address: Address, key: H256) -> U256;
9+
fn get_block_hash(&self, block_number: u64) -> Option<H256>;
10+
}
11+
12+
#[derive(Debug, Default)]
13+
pub struct Db {
14+
pub accounts: HashMap<Address, Account>,
15+
pub block_hashes: HashMap<u64, H256>,
16+
}
17+
18+
// Methods here are for testing purposes only, for initializing the Db with some values
19+
impl Db {
20+
pub fn new() -> Self {
21+
Self {
22+
accounts: HashMap::new(),
23+
block_hashes: HashMap::new(),
24+
}
25+
}
26+
27+
/// Add accounts to database
28+
pub fn add_accounts(&mut self, accounts: Vec<(Address, Account)>) {
29+
self.accounts.extend(accounts);
30+
}
31+
32+
/// Add block hashes to database
33+
pub fn add_block_hashes(&mut self, block_hashes: Vec<(u64, H256)>) {
34+
self.block_hashes.extend(block_hashes);
35+
}
36+
37+
/// Builder method with accounts [for testing only]
38+
pub fn with_accounts(mut self, accounts: HashMap<Address, Account>) -> Self {
39+
self.accounts = accounts;
40+
self
41+
}
42+
43+
/// Builder method with block hashes [for testing only]
44+
pub fn with_block_hashes(mut self, block_hashes: HashMap<u64, H256>) -> Self {
45+
self.block_hashes = block_hashes;
46+
self
47+
}
48+
}
49+
50+
impl Database for Db {
51+
fn get_account_info(&self, address: Address) -> AccountInfo {
52+
self.accounts
53+
.get(&address)
54+
.unwrap_or(&Account::default())
55+
.info
56+
.clone()
57+
}
58+
59+
fn get_storage_slot(&self, address: Address, key: H256) -> U256 {
60+
// both `original_value` and `current_value` should work here because they have the same values on Db
61+
self.accounts
62+
.get(&address)
63+
.unwrap_or(&Account::default())
64+
.storage
65+
.get(&key)
66+
.unwrap_or(&StorageSlot::default())
67+
.original_value
68+
}
69+
70+
fn get_block_hash(&self, block_number: u64) -> Option<H256> {
71+
self.block_hashes.get(&block_number).cloned()
72+
}
73+
}
74+
75+
#[derive(Debug, Default, Clone)]
76+
pub struct Cache {
77+
pub accounts: HashMap<Address, Account>,
78+
}
79+
80+
impl Cache {
81+
pub fn get_account(&self, address: Address) -> Option<&Account> {
82+
self.accounts.get(&address)
83+
}
84+
85+
pub fn get_mut_account(&mut self, address: Address) -> Option<&mut Account> {
86+
self.accounts.get_mut(&address)
87+
}
88+
89+
pub fn get_storage_slot(&self, address: Address, key: H256) -> Option<StorageSlot> {
90+
self.get_account(address)
91+
.expect("Account should have been cached")
92+
.storage
93+
.get(&key)
94+
.cloned()
95+
}
96+
97+
pub fn add_account(&mut self, address: &Address, account: &Account) {
98+
self.accounts.insert(*address, account.clone());
99+
}
100+
101+
pub fn write_account_storage(&mut self, address: &Address, key: H256, slot: StorageSlot) {
102+
self.accounts
103+
.get_mut(address)
104+
.expect("Account should have been cached")
105+
.storage
106+
.insert(key, slot);
107+
}
108+
109+
pub fn increment_account_nonce(&mut self, address: &Address) {
110+
if let Some(account) = self.accounts.get_mut(address) {
111+
account.info.nonce += 1;
112+
}
113+
}
114+
115+
pub fn is_account_cached(&self, address: &Address) -> bool {
116+
self.accounts.contains_key(address)
117+
}
118+
119+
pub fn is_slot_cached(&self, address: &Address, key: H256) -> bool {
120+
self.is_account_cached(address)
121+
&& self
122+
.get_account(*address)
123+
.map(|account| account.storage.contains_key(&key))
124+
.unwrap_or(false)
125+
}
126+
}

crates/vm/levm/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
pub mod block;
22
pub mod call_frame;
33
pub mod constants;
4+
pub mod db;
45
pub mod errors;
56
pub mod memory;
67
pub mod opcode_handlers;

crates/vm/levm/src/opcode_handlers/block.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,12 @@ impl VM {
3030
return Ok(OpcodeSuccess::Continue);
3131
}
3232

33-
if let Some(block_hash) = self.db.block_hashes.get(&block_number) {
33+
let block_number = block_number.as_u64();
34+
35+
if let Some(block_hash) = self.db.get_block_hash(block_number) {
3436
current_call_frame
3537
.stack
36-
.push(U256::from_big_endian(&block_hash.0))?;
38+
.push(U256::from_big_endian(block_hash.as_bytes()))?;
3739
} else {
3840
current_call_frame.stack.push(U256::zero())?;
3941
}
@@ -125,9 +127,15 @@ impl VM {
125127
) -> Result<OpcodeSuccess, VMError> {
126128
self.increase_consumed_gas(current_call_frame, gas_cost::SELFBALANCE)?;
127129

128-
let balance = self.db.balance(&current_call_frame.code_address);
129-
current_call_frame.stack.push(balance)?;
130+
// the current account should have been cached when the contract was called
131+
let balance = self
132+
.cache
133+
.get_account(current_call_frame.code_address)
134+
.expect("The current account should always be cached")
135+
.info
136+
.balance;
130137

138+
current_call_frame.stack.push(balance)?;
131139
Ok(OpcodeSuccess::Continue)
132140
}
133141

crates/vm/levm/src/opcode_handlers/environment.rs

Lines changed: 58 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
use super::*;
22
use crate::{
3-
constants::{call_opcode, WORD_SIZE},
3+
constants::{
4+
call_opcode::{COLD_ADDRESS_ACCESS_COST, WARM_ADDRESS_ACCESS_COST},
5+
WORD_SIZE,
6+
},
47
vm::word_to_address,
58
};
69
use sha3::{Digest, Keccak256};
@@ -16,11 +19,7 @@ impl VM {
1619
) -> Result<OpcodeSuccess, VMError> {
1720
self.increase_consumed_gas(current_call_frame, gas_cost::ADDRESS)?;
1821

19-
let addr = if current_call_frame.delegate.is_some() {
20-
current_call_frame.msg_sender
21-
} else {
22-
current_call_frame.code_address
23-
};
22+
let addr = current_call_frame.to; // The recipient of the current call.
2423

2524
current_call_frame.stack.push(U256::from(addr.as_bytes()))?;
2625

@@ -32,13 +31,18 @@ impl VM {
3231
&mut self,
3332
current_call_frame: &mut CallFrame,
3433
) -> Result<OpcodeSuccess, VMError> {
35-
self.increase_consumed_gas(current_call_frame, gas_cost::BALANCE)?;
34+
let address = &word_to_address(current_call_frame.stack.pop()?);
3635

37-
let addr = current_call_frame.stack.pop()?;
36+
if self.cache.is_account_cached(address) {
37+
self.increase_consumed_gas(current_call_frame, WARM_ADDRESS_ACCESS_COST)?;
38+
} else {
39+
self.increase_consumed_gas(current_call_frame, COLD_ADDRESS_ACCESS_COST)?;
40+
self.cache_from_db(address);
41+
};
3842

39-
let balance = self.db.balance(&word_to_address(addr));
40-
current_call_frame.stack.push(balance)?;
43+
let balance = self.cache.get_account(*address).unwrap().info.balance;
4144

45+
current_call_frame.stack.push(balance)?;
4246
Ok(OpcodeSuccess::Continue)
4347
}
4448

@@ -237,17 +241,23 @@ impl VM {
237241
current_call_frame: &mut CallFrame,
238242
) -> Result<OpcodeSuccess, VMError> {
239243
let address = word_to_address(current_call_frame.stack.pop()?);
240-
let gas_cost = if self.accrued_substate.warm_addresses.contains(&address) {
241-
call_opcode::WARM_ADDRESS_ACCESS_COST
244+
245+
if self.cache.is_account_cached(&address) {
246+
self.increase_consumed_gas(current_call_frame, WARM_ADDRESS_ACCESS_COST)?;
242247
} else {
243-
call_opcode::COLD_ADDRESS_ACCESS_COST
248+
self.increase_consumed_gas(current_call_frame, COLD_ADDRESS_ACCESS_COST)?;
249+
self.cache_from_db(&address);
244250
};
245251

246-
self.increase_consumed_gas(current_call_frame, gas_cost)?;
247-
248-
let code_size = self.db.get_account_bytecode(&address).len();
249-
current_call_frame.stack.push(code_size.into())?;
252+
let bytecode = self
253+
.cache
254+
.get_account(address)
255+
.unwrap()
256+
.info
257+
.bytecode
258+
.clone();
250259

260+
current_call_frame.stack.push(bytecode.len().into())?;
251261
Ok(OpcodeSuccess::Continue)
252262
}
253263

@@ -277,26 +287,32 @@ impl VM {
277287
let memory_expansion_cost = current_call_frame
278288
.memory
279289
.expansion_cost(dest_offset + size)?;
280-
let address_access_cost = if self.accrued_substate.warm_addresses.contains(&address) {
281-
call_opcode::WARM_ADDRESS_ACCESS_COST
290+
let gas_cost =
291+
gas_cost::EXTCODECOPY_DYNAMIC_BASE * minimum_word_size + memory_expansion_cost;
292+
293+
if self.cache.is_account_cached(&address) {
294+
self.increase_consumed_gas(current_call_frame, gas_cost + WARM_ADDRESS_ACCESS_COST)?;
282295
} else {
283-
call_opcode::COLD_ADDRESS_ACCESS_COST
296+
self.increase_consumed_gas(current_call_frame, gas_cost + COLD_ADDRESS_ACCESS_COST)?;
297+
self.cache_from_db(&address);
284298
};
285-
let gas_cost = gas_cost::EXTCODECOPY_DYNAMIC_BASE * minimum_word_size
286-
+ memory_expansion_cost
287-
+ address_access_cost;
288299

289-
self.increase_consumed_gas(current_call_frame, gas_cost)?;
300+
let mut bytecode = self
301+
.cache
302+
.get_account(address)
303+
.unwrap()
304+
.info
305+
.bytecode
306+
.clone();
290307

291-
let mut code = self.db.get_account_bytecode(&address);
292-
if code.len() < offset + size {
293-
let mut extended_code = code.to_vec();
308+
if bytecode.len() < offset + size {
309+
let mut extended_code = bytecode.to_vec();
294310
extended_code.resize(offset + size, 0);
295-
code = Bytes::from(extended_code);
311+
bytecode = Bytes::from(extended_code);
296312
}
297313
current_call_frame
298314
.memory
299-
.store_bytes(dest_offset, &code[offset..offset + size]);
315+
.store_bytes(dest_offset, &bytecode[offset..offset + size]);
300316

301317
Ok(OpcodeSuccess::Continue)
302318
}
@@ -364,17 +380,24 @@ impl VM {
364380
current_call_frame: &mut CallFrame,
365381
) -> Result<OpcodeSuccess, VMError> {
366382
let address = word_to_address(current_call_frame.stack.pop()?);
367-
let gas_cost = if self.accrued_substate.warm_addresses.contains(&address) {
368-
call_opcode::WARM_ADDRESS_ACCESS_COST
383+
384+
if self.cache.is_account_cached(&address) {
385+
self.increase_consumed_gas(current_call_frame, WARM_ADDRESS_ACCESS_COST)?;
369386
} else {
370-
call_opcode::COLD_ADDRESS_ACCESS_COST
387+
self.increase_consumed_gas(current_call_frame, COLD_ADDRESS_ACCESS_COST)?;
388+
self.cache_from_db(&address);
371389
};
372390

373-
self.increase_consumed_gas(current_call_frame, gas_cost)?;
391+
let bytecode = self
392+
.cache
393+
.get_account(address)
394+
.unwrap()
395+
.info
396+
.bytecode
397+
.clone();
374398

375-
let code = self.db.get_account_bytecode(&address);
376399
let mut hasher = Keccak256::new();
377-
hasher.update(code);
400+
hasher.update(bytecode);
378401
let result = hasher.finalize();
379402
current_call_frame
380403
.stack

0 commit comments

Comments
 (0)