Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(levm): implement Cache and refactor Db #991

Merged
merged 50 commits into from
Oct 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
3492657
db, warm addresses and cache scaffold
JereSalo Oct 26, 2024
13d7d47
ongoing changes on db
JereSalo Oct 28, 2024
5fd1345
Add `Database` api
juanimedone Oct 28, 2024
c0f8cf8
some changes in Database and Db
JereSalo Oct 28, 2024
2e9cf34
add method get_from_db_then_cache()
JereSalo Oct 28, 2024
1ddefdf
fix some tests
JereSalo Oct 28, 2024
b5eebd4
Fix environment opcodes
juanimedone Oct 28, 2024
99b4fc3
make changes to vm testing function
JereSalo Oct 28, 2024
6b381a8
Merge branch 'levm/refactor/db' of github.com:lambdaclass/lambda_ethe…
JereSalo Oct 28, 2024
594ef74
fix more tests
JereSalo Oct 28, 2024
cdd786d
fix some issues in vm
JereSalo Oct 28, 2024
0b8614b
Add `get_block_hash`
juanimedone Oct 28, 2024
5c380f1
fix more tests i guess..
JereSalo Oct 28, 2024
db12190
Merge branch 'levm/refactor/db' of github.com:lambdaclass/lambda_ethe…
JereSalo Oct 28, 2024
ffad514
'fix' errors in call opcode
JereSalo Oct 28, 2024
61d36c8
change to create
JereSalo Oct 28, 2024
eaf4616
Fix `SLOAD` and `SSTORE`
juanimedone Oct 28, 2024
f34b493
changes in caching from db
JereSalo Oct 28, 2024
1fa59e0
Merge branch 'levm/refactor/db' of github.com:lambdaclass/lambda_ethe…
JereSalo Oct 28, 2024
c3b21ff
Fix lint
juanimedone Oct 28, 2024
b2fe679
fix errors in tests
JereSalo Oct 28, 2024
b1c9be0
Merge branch 'levm/refactor/db' of github.com:lambdaclass/lambda_ethe…
JereSalo Oct 28, 2024
96e66cc
cache all data added to db in tests
JereSalo Oct 28, 2024
e66aa3e
fix one test...
JereSalo Oct 28, 2024
741a543
fix call tests
JereSalo Oct 28, 2024
91ab731
fix sstore
JereSalo Oct 28, 2024
7c7ada9
fix create tests
JereSalo Oct 28, 2024
842a530
Fix `extcodehash_account_with_empty_code`
juanimedone Oct 28, 2024
965bc4c
Fix compiling warning
juanimedone Oct 28, 2024
439c2ac
change variables names for clarity
JereSalo Oct 29, 2024
abd5789
fix call opcodes, remove delegate and change opcodes that used it
JereSalo Oct 29, 2024
96a83c9
comment delegatecall tests for being messy :)
JereSalo Oct 29, 2024
63d67bc
add comments to callframe attributes
JereSalo Oct 29, 2024
08cc426
run cargo fmt
JereSalo Oct 29, 2024
dd64d70
remove unused object
JereSalo Oct 29, 2024
b463d30
remove commented methods from Db implementation of Database trait
JereSalo Oct 29, 2024
5233c57
delete debug trait for database and vm, for integration to work
JereSalo Oct 29, 2024
e37a47e
change storage key from U256 to H256
JereSalo Oct 29, 2024
9d7e700
change block_number from U256 to u64
JereSalo Oct 29, 2024
c42f9f0
run cargo fmt
JereSalo Oct 29, 2024
18868f6
Merge branch 'main' into levm/refactor/db
juanimedone Oct 29, 2024
84330a6
Merge fixes
juanimedone Oct 30, 2024
97a6e11
Improve call/create transactions handling
maximopalopoli Oct 30, 2024
8a49f1f
Move validations in new to transact
maximopalopoli Oct 30, 2024
f0440fc
Delete commented code
maximopalopoli Oct 30, 2024
55ab9eb
starting changes, doesnt work
maximopalopoli Oct 30, 2024
3cee7e5
environment changes
JereSalo Oct 30, 2024
c04a00f
Fix tests
jrchatruc Oct 30, 2024
fedefff
Fix mutable variable
jrchatruc Oct 30, 2024
8f42bc8
fix clippy lint
JereSalo Oct 30, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions crates/vm/levm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,3 @@ hex = "0.4.3"

[features]
ethereum_foundation_tests = []

[profile.test]
opt-level = 3
debug-assertions = true
3 changes: 3 additions & 0 deletions crates/vm/levm/docs/substate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## Substate

`accessed_addresses` and `accessed_storage_keys` follow the structure defined in [EIP 2929](https://eips.ethereum.org/EIPS/eip-2929#specification)
9 changes: 5 additions & 4 deletions crates/vm/levm/src/call_frame.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,13 @@ pub struct CallFrame {
pub gas_limit: U256,
pub gas_used: U256,
pub pc: usize,
pub msg_sender: Address, // Origin address?
/// Address of the account that sent the message
pub msg_sender: Address,
/// Address of the recipient of the message
pub to: Address,
/// Address of the code to execute. Usually the same as `to`, but can be different
pub code_address: Address,
pub delegate: Option<Address>,
/// Bytecode to execute
pub bytecode: Bytes,
pub msg_value: U256,
pub stack: Stack, // max 1024 in the future
Expand Down Expand Up @@ -98,7 +101,6 @@ impl CallFrame {
msg_sender: Address,
to: Address,
code_address: Address,
delegate: Option<Address>,
bytecode: Bytes,
msg_value: U256,
calldata: Bytes,
Expand All @@ -112,7 +114,6 @@ impl CallFrame {
msg_sender,
to,
code_address,
delegate,
bytecode,
msg_value,
calldata,
Expand Down
1 change: 0 additions & 1 deletion crates/vm/levm/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ pub mod gas_cost {
pub const RETURNDATACOPY_STATIC: U256 = U256([3, 0, 0, 0]);
pub const RETURNDATACOPY_DYNAMIC_BASE: U256 = U256([3, 0, 0, 0]);
pub const ADDRESS: U256 = U256([2, 0, 0, 0]);
pub const BALANCE: U256 = U256([100, 0, 0, 0]);
pub const ORIGIN: U256 = U256([2, 0, 0, 0]);
pub const CALLER: U256 = U256([2, 0, 0, 0]);
pub const BLOCKHASH: U256 = U256([20, 0, 0, 0]);
Expand Down
126 changes: 126 additions & 0 deletions crates/vm/levm/src/db.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
use crate::vm::{Account, AccountInfo, StorageSlot};
use ethereum_types::{Address, U256};
use keccak_hash::H256;
use std::collections::HashMap;

pub trait Database {
fn get_account_info(&self, address: Address) -> AccountInfo;
fn get_storage_slot(&self, address: Address, key: H256) -> U256;
fn get_block_hash(&self, block_number: u64) -> Option<H256>;
}

#[derive(Debug, Default)]
pub struct Db {
pub accounts: HashMap<Address, Account>,
pub block_hashes: HashMap<u64, H256>,
}

// Methods here are for testing purposes only, for initializing the Db with some values
impl Db {
pub fn new() -> Self {
Self {
accounts: HashMap::new(),
block_hashes: HashMap::new(),
}
}

/// Add accounts to database
pub fn add_accounts(&mut self, accounts: Vec<(Address, Account)>) {
self.accounts.extend(accounts);
}

/// Add block hashes to database
pub fn add_block_hashes(&mut self, block_hashes: Vec<(u64, H256)>) {
self.block_hashes.extend(block_hashes);
}

/// Builder method with accounts [for testing only]
pub fn with_accounts(mut self, accounts: HashMap<Address, Account>) -> Self {
self.accounts = accounts;
self
}

/// Builder method with block hashes [for testing only]
pub fn with_block_hashes(mut self, block_hashes: HashMap<u64, H256>) -> Self {
self.block_hashes = block_hashes;
self
}
}

impl Database for Db {
fn get_account_info(&self, address: Address) -> AccountInfo {
self.accounts
.get(&address)
.unwrap_or(&Account::default())
.info
.clone()
}

fn get_storage_slot(&self, address: Address, key: H256) -> U256 {
// both `original_value` and `current_value` should work here because they have the same values on Db
self.accounts
.get(&address)
.unwrap_or(&Account::default())
.storage
.get(&key)
.unwrap_or(&StorageSlot::default())
.original_value
}

fn get_block_hash(&self, block_number: u64) -> Option<H256> {
self.block_hashes.get(&block_number).cloned()
}
}

#[derive(Debug, Default, Clone)]
pub struct Cache {
pub accounts: HashMap<Address, Account>,
}

impl Cache {
pub fn get_account(&self, address: Address) -> Option<&Account> {
self.accounts.get(&address)
}

pub fn get_mut_account(&mut self, address: Address) -> Option<&mut Account> {
self.accounts.get_mut(&address)
}

pub fn get_storage_slot(&self, address: Address, key: H256) -> Option<StorageSlot> {
self.get_account(address)
.expect("Account should have been cached")
.storage
.get(&key)
.cloned()
}

pub fn add_account(&mut self, address: &Address, account: &Account) {
self.accounts.insert(*address, account.clone());
}

pub fn write_account_storage(&mut self, address: &Address, key: H256, slot: StorageSlot) {
self.accounts
.get_mut(address)
.expect("Account should have been cached")
.storage
.insert(key, slot);
}

pub fn increment_account_nonce(&mut self, address: &Address) {
if let Some(account) = self.accounts.get_mut(address) {
account.info.nonce += 1;
}
}

pub fn is_account_cached(&self, address: &Address) -> bool {
self.accounts.contains_key(address)
}

pub fn is_slot_cached(&self, address: &Address, key: H256) -> bool {
self.is_account_cached(address)
&& self
.get_account(*address)
.map(|account| account.storage.contains_key(&key))
.unwrap_or(false)
}
}
1 change: 1 addition & 0 deletions crates/vm/levm/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pub mod block;
pub mod call_frame;
pub mod constants;
pub mod db;
pub mod errors;
pub mod memory;
pub mod opcode_handlers;
Expand Down
16 changes: 12 additions & 4 deletions crates/vm/levm/src/opcode_handlers/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,12 @@ impl VM {
return Ok(OpcodeSuccess::Continue);
}

if let Some(block_hash) = self.db.block_hashes.get(&block_number) {
let block_number = block_number.as_u64();

if let Some(block_hash) = self.db.get_block_hash(block_number) {
current_call_frame
.stack
.push(U256::from_big_endian(&block_hash.0))?;
.push(U256::from_big_endian(block_hash.as_bytes()))?;
} else {
current_call_frame.stack.push(U256::zero())?;
}
Expand Down Expand Up @@ -125,9 +127,15 @@ impl VM {
) -> Result<OpcodeSuccess, VMError> {
self.increase_consumed_gas(current_call_frame, gas_cost::SELFBALANCE)?;

let balance = self.db.balance(&current_call_frame.code_address);
current_call_frame.stack.push(balance)?;
// the current account should have been cached when the contract was called
let balance = self
.cache
.get_account(current_call_frame.code_address)
.expect("The current account should always be cached")
.info
.balance;

current_call_frame.stack.push(balance)?;
Ok(OpcodeSuccess::Continue)
}

Expand Down
93 changes: 58 additions & 35 deletions crates/vm/levm/src/opcode_handlers/environment.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use super::*;
use crate::{
constants::{call_opcode, WORD_SIZE},
constants::{
call_opcode::{COLD_ADDRESS_ACCESS_COST, WARM_ADDRESS_ACCESS_COST},
WORD_SIZE,
},
vm::word_to_address,
};
use sha3::{Digest, Keccak256};
Expand All @@ -16,11 +19,7 @@ impl VM {
) -> Result<OpcodeSuccess, VMError> {
self.increase_consumed_gas(current_call_frame, gas_cost::ADDRESS)?;

let addr = if current_call_frame.delegate.is_some() {
current_call_frame.msg_sender
} else {
current_call_frame.code_address
};
let addr = current_call_frame.to; // The recipient of the current call.

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

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

let addr = current_call_frame.stack.pop()?;
if self.cache.is_account_cached(address) {
self.increase_consumed_gas(current_call_frame, WARM_ADDRESS_ACCESS_COST)?;
} else {
self.increase_consumed_gas(current_call_frame, COLD_ADDRESS_ACCESS_COST)?;
self.cache_from_db(address);
};

let balance = self.db.balance(&word_to_address(addr));
current_call_frame.stack.push(balance)?;
let balance = self.cache.get_account(*address).unwrap().info.balance;

current_call_frame.stack.push(balance)?;
Ok(OpcodeSuccess::Continue)
}

Expand Down Expand Up @@ -237,17 +241,23 @@ impl VM {
current_call_frame: &mut CallFrame,
) -> Result<OpcodeSuccess, VMError> {
let address = word_to_address(current_call_frame.stack.pop()?);
let gas_cost = if self.accrued_substate.warm_addresses.contains(&address) {
call_opcode::WARM_ADDRESS_ACCESS_COST

if self.cache.is_account_cached(&address) {
self.increase_consumed_gas(current_call_frame, WARM_ADDRESS_ACCESS_COST)?;
} else {
call_opcode::COLD_ADDRESS_ACCESS_COST
self.increase_consumed_gas(current_call_frame, COLD_ADDRESS_ACCESS_COST)?;
self.cache_from_db(&address);
};

self.increase_consumed_gas(current_call_frame, gas_cost)?;

let code_size = self.db.get_account_bytecode(&address).len();
current_call_frame.stack.push(code_size.into())?;
let bytecode = self
.cache
.get_account(address)
.unwrap()
.info
.bytecode
.clone();

current_call_frame.stack.push(bytecode.len().into())?;
Ok(OpcodeSuccess::Continue)
}

Expand Down Expand Up @@ -277,26 +287,32 @@ impl VM {
let memory_expansion_cost = current_call_frame
.memory
.expansion_cost(dest_offset + size)?;
let address_access_cost = if self.accrued_substate.warm_addresses.contains(&address) {
call_opcode::WARM_ADDRESS_ACCESS_COST
let gas_cost =
gas_cost::EXTCODECOPY_DYNAMIC_BASE * minimum_word_size + memory_expansion_cost;

if self.cache.is_account_cached(&address) {
self.increase_consumed_gas(current_call_frame, gas_cost + WARM_ADDRESS_ACCESS_COST)?;
} else {
call_opcode::COLD_ADDRESS_ACCESS_COST
self.increase_consumed_gas(current_call_frame, gas_cost + COLD_ADDRESS_ACCESS_COST)?;
self.cache_from_db(&address);
};
let gas_cost = gas_cost::EXTCODECOPY_DYNAMIC_BASE * minimum_word_size
+ memory_expansion_cost
+ address_access_cost;

self.increase_consumed_gas(current_call_frame, gas_cost)?;
let mut bytecode = self
.cache
.get_account(address)
.unwrap()
.info
.bytecode
.clone();

let mut code = self.db.get_account_bytecode(&address);
if code.len() < offset + size {
let mut extended_code = code.to_vec();
if bytecode.len() < offset + size {
let mut extended_code = bytecode.to_vec();
extended_code.resize(offset + size, 0);
code = Bytes::from(extended_code);
bytecode = Bytes::from(extended_code);
}
current_call_frame
.memory
.store_bytes(dest_offset, &code[offset..offset + size]);
.store_bytes(dest_offset, &bytecode[offset..offset + size]);

Ok(OpcodeSuccess::Continue)
}
Expand Down Expand Up @@ -364,17 +380,24 @@ impl VM {
current_call_frame: &mut CallFrame,
) -> Result<OpcodeSuccess, VMError> {
let address = word_to_address(current_call_frame.stack.pop()?);
let gas_cost = if self.accrued_substate.warm_addresses.contains(&address) {
call_opcode::WARM_ADDRESS_ACCESS_COST

if self.cache.is_account_cached(&address) {
self.increase_consumed_gas(current_call_frame, WARM_ADDRESS_ACCESS_COST)?;
} else {
call_opcode::COLD_ADDRESS_ACCESS_COST
self.increase_consumed_gas(current_call_frame, COLD_ADDRESS_ACCESS_COST)?;
self.cache_from_db(&address);
};

self.increase_consumed_gas(current_call_frame, gas_cost)?;
let bytecode = self
.cache
.get_account(address)
.unwrap()
.info
.bytecode
.clone();

let code = self.db.get_account_bytecode(&address);
let mut hasher = Keccak256::new();
hasher.update(code);
hasher.update(bytecode);
let result = hasher.finalize();
current_call_frame
.stack
Expand Down
Loading
Loading