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

fix(l2): replace ExecutionDB from_exec() impl to use a CacheDB based approach #1709

Merged
merged 27 commits into from
Jan 24, 2025
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
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
3 changes: 2 additions & 1 deletion crates/l2/proposer/prover_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,8 @@ impl ProverServer {

let block = Block::new(header, body);

let db = ExecutionDB::from_exec(&block, &self.store).map_err(EvmError::ExecutionDB)?;
let db =
ExecutionDB::from_store(&block, self.store.clone()).map_err(EvmError::ExecutionDB)?;

let parent_block_header = self
.store
Expand Down
2 changes: 1 addition & 1 deletion crates/l2/prover/tests/perf_zkvm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ async fn setup() -> (ProgramInput, Block) {
}
let block_to_prove = blocks.last().unwrap();

let db = ExecutionDB::from_exec(block_to_prove, &store).unwrap();
let db = ExecutionDB::from_store(block_to_prove, store.clone()).unwrap();

let parent_block_header = store
.get_block_header_by_hash(block_to_prove.header.parent_hash)
Expand Down
16 changes: 10 additions & 6 deletions crates/l2/prover/zkvm/interface/risc0/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use ethrex_blockchain::{validate_block, validate_gas_used};
use ethrex_vm::{execute_block, get_state_transitions, EvmState};
use zkvm_interface::{
io::{ProgramInput, ProgramOutput},
trie::update_tries,
trie::{update_tries, verify_db},
};

fn main() {
Expand All @@ -15,36 +15,40 @@ fn main() {
} = env::read();
let mut state = EvmState::from(db.clone());

// Validate the block pre-execution
// Validate the block
validate_block(&block, &parent_block_header, &state).expect("invalid block");

// Validate the initial state
// Tries used for validating initial and final state root
let (mut state_trie, mut storage_tries) = db
.build_tries()
.get_tries()
.expect("failed to build state and storage tries or state is not valid");

// Validate the initial state
let initial_state_hash = state_trie.hash_no_commit();
if initial_state_hash != parent_block_header.state_root {
panic!("invalid initial state trie");
}
if !verify_db(&db, &state_trie, &storage_tries).expect("failed to validate database") {
panic!("invalid database")
};

let receipts = execute_block(&block, &mut state).expect("failed to execute block");
validate_gas_used(&receipts, &block.header).expect("invalid gas used");

// Output gas for measurement purposes
let cumulative_gas_used = receipts
.last()
.map(|last_receipt| last_receipt.cumulative_gas_used)
.unwrap_or_default();

env::write(&cumulative_gas_used);

let account_updates = get_state_transitions(&mut state);

// Update tries and calculate final state root hash
update_tries(&mut state_trie, &mut storage_tries, &account_updates)
.expect("failed to update state and storage tries");
let final_state_hash = state_trie.hash_no_commit();

let final_state_hash = state_trie.hash_no_commit();
if final_state_hash != block.header.state_root {
panic!("invalid final state trie");
}
Expand Down
17 changes: 10 additions & 7 deletions crates/l2/prover/zkvm/interface/sp1/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use ethrex_blockchain::{validate_block, validate_gas_used};
use ethrex_vm::{execute_block, get_state_transitions, EvmState};
use zkvm_interface::{
io::{ProgramInput, ProgramOutput},
trie::update_tries,
trie::{update_tries, verify_db},
};

sp1_zkvm::entrypoint!(main);
Expand All @@ -15,39 +15,42 @@ pub fn main() {
parent_block_header,
db,
} = sp1_zkvm::io::read::<ProgramInput>();

let mut state = EvmState::from(db.clone());

// Validate the block pre-execution
// Validate the block
validate_block(&block, &parent_block_header, &state).expect("invalid block");

// Validate the initial state
// Tries used for validating initial and final state root
let (mut state_trie, mut storage_tries) = db
.build_tries()
.get_tries()
.expect("failed to build state and storage tries or state is not valid");

// Validate the initial state
let initial_state_hash = state_trie.hash_no_commit();
if initial_state_hash != parent_block_header.state_root {
panic!("invalid initial state trie");
}
if !verify_db(&db, &state_trie, &storage_tries).expect("failed to validate database") {
panic!("invalid database")
};

let receipts = execute_block(&block, &mut state).expect("failed to execute block");
validate_gas_used(&receipts, &block.header).expect("invalid gas used");

// Output gas for measurement purposes
let cumulative_gas_used = receipts
.last()
.map(|last_receipt| last_receipt.cumulative_gas_used)
.unwrap_or_default();

sp1_zkvm::io::commit(&cumulative_gas_used);

let account_updates = get_state_transitions(&mut state);

// Update tries and calculate final state root hash
update_tries(&mut state_trie, &mut storage_tries, &account_updates)
.expect("failed to update state and storage tries");
let final_state_hash = state_trie.hash_no_commit();

let final_state_hash = state_trie.hash_no_commit();
if final_state_hash != block.header.state_root {
panic!("invalid final state trie");
}
Expand Down
54 changes: 52 additions & 2 deletions crates/l2/prover/zkvm/interface/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,11 @@ pub mod io {
pub mod trie {
use std::collections::HashMap;

use ethrex_core::{types::AccountState, H160};
use ethrex_core::{types::AccountState, Address, H160, H256, U256};
use ethrex_rlp::{decode::RLPDecode, encode::RLPEncode, error::RLPDecodeError};
use ethrex_storage::{hash_address, hash_key, AccountUpdate};
use ethrex_trie::{Trie, TrieError};
use ethrex_vm::execution_db::ExecutionDB;
use thiserror::Error;

#[derive(Debug, Error)]
Expand All @@ -91,9 +92,58 @@ pub mod trie {
#[error(transparent)]
RLPDecode(#[from] RLPDecodeError),
#[error("Missing storage trie for address {0}")]
MissingStorageTrie(H160),
#[error("Missing storage for address {0}")]
StorageNotFound(H160),
}

pub fn verify_db(
db: &ExecutionDB,
state_trie: &Trie,
storage_tries: &HashMap<H160, Trie>,
) -> Result<bool, Error> {
for (address, account_info) in &db.accounts {
let storage_trie = storage_tries
.get(&address)
.ok_or(Error::MissingStorageTrie(*address))?;
let storage_root = storage_trie.hash_no_commit();

// verify account and storage trie are valid
let trie_account_state = match state_trie.get(&hash_address(&address)) {
Ok(Some(encoded_state)) => AccountState::decode(&encoded_state)?,
Ok(None) | Err(TrieError::InconsistentTree) => return Ok(false), // account not in trie
Err(err) => return Err(err.into()),
};
let db_account_state = AccountState {
nonce: account_info.nonce,
balance: account_info.balance,
code_hash: account_info.code_hash,
storage_root,
};
if db_account_state != trie_account_state {
return Ok(false);
}

// verify storage
for (key, db_value) in db
.storage
.get(address)
.ok_or(Error::StorageNotFound(*address))?
{
let trie_value = match storage_trie.get(&hash_key(key)) {
Ok(Some(encoded)) => U256::decode(&encoded)?,
Ok(None) | Err(TrieError::InconsistentTree) => return Ok(false), // value not in trie
Err(err) => return Err(err.into()),
};
if *db_value != trie_value {
return Ok(false);
}
}
}

Ok(true)
}

pub fn update_tries(
state_trie: &mut Trie,
storage_tries: &mut HashMap<H160, Trie>,
Expand Down Expand Up @@ -133,7 +183,7 @@ pub mod trie {
} else {
storage_tries
.get_mut(&update.address)
.ok_or(Error::StorageNotFound(update.address))?
.ok_or(Error::MissingStorageTrie(update.address))?
};
for (storage_key, storage_value) in &update.added_storage {
let hashed_key = hash_key(storage_key);
Expand Down
2 changes: 1 addition & 1 deletion crates/l2/utils/test_data_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ pub fn generate_program_input(
.ok_or(ProverInputError::InvalidParentBlock(
block.header.parent_hash,
))?;
let db = ExecutionDB::from_exec(&block, &store)?;
let db = ExecutionDB::from_store(&block, store)?;

Ok(ProgramInput {
db,
Expand Down
51 changes: 51 additions & 0 deletions crates/vm/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,54 @@ impl revm::Database for StoreWrapper {
.ok_or_else(|| StoreError::Custom(format!("Block {number} not found")))
}
}

impl revm::DatabaseRef for StoreWrapper {
type Error = StoreError;

fn basic_ref(&self, address: RevmAddress) -> Result<Option<RevmAccountInfo>, Self::Error> {
let acc_info = match self
.store
.get_account_info_by_hash(self.block_hash, CoreAddress::from(address.0.as_ref()))?
{
None => return Ok(None),
Some(acc_info) => acc_info,
};
let code = self
.store
.get_account_code(acc_info.code_hash)?
.map(|b| RevmBytecode::new_raw(RevmBytes(b)));

Ok(Some(RevmAccountInfo {
balance: RevmU256::from_limbs(acc_info.balance.0),
nonce: acc_info.nonce,
code_hash: RevmB256::from(acc_info.code_hash.0),
code,
}))
}

fn code_by_hash_ref(&self, code_hash: RevmB256) -> Result<RevmBytecode, Self::Error> {
self.store
.get_account_code(CoreH256::from(code_hash.as_ref()))?
.map(|b| RevmBytecode::new_raw(RevmBytes(b)))
.ok_or_else(|| StoreError::Custom(format!("No code for hash {code_hash}")))
}

fn storage_ref(&self, address: RevmAddress, index: RevmU256) -> Result<RevmU256, Self::Error> {
Ok(self
.store
.get_storage_at_hash(
self.block_hash,
CoreAddress::from(address.0.as_ref()),
CoreH256::from(index.to_be_bytes()),
)?
.map(|value| RevmU256::from_limbs(value.0))
.unwrap_or_else(|| RevmU256::ZERO))
}

fn block_hash_ref(&self, number: u64) -> Result<RevmB256, Self::Error> {
self.store
.get_block_header(number)?
.map(|header| RevmB256::from_slice(&header.compute_block_hash().0))
.ok_or_else(|| StoreError::Custom(format!("Block {number} not found")))
}
}
12 changes: 8 additions & 4 deletions crates/vm/errors.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use ethereum_types::{H160, H256};
use ethrex_core::types::BlockHash;
use ethrex_core::{types::BlockHash, Address};
use ethrex_storage::error::StoreError;
use ethrex_trie::TrieError;
use revm::primitives::{
Expand Down Expand Up @@ -43,14 +43,18 @@ pub enum ExecutionDBError {
StorageValueNotFound(RevmAddress, RevmU256),
#[error("Hash of block with number {0} not found")]
BlockHashNotFound(u64),
#[error("Missing account {0} info while trying to create ExecutionDB")]
NewMissingAccountInfo(RevmAddress),
#[error("Missing state trie of block {0} while trying to create ExecutionDB")]
NewMissingStateTrie(BlockHash),
#[error(
"Missing storage trie of block {0} and address {1} while trying to create ExecutionDB"
)]
NewMissingStorageTrie(BlockHash, H160),
NewMissingStorageTrie(BlockHash, Address),
#[error("Missing account {0} info while trying to create ExecutionDB")]
NewMissingAccountInfo(Address),
#[error("Missing storage of address {0} and key {1} while trying to create ExecutionDB")]
NewMissingStorage(Address, H256),
#[error("Missing code of hash {0} while trying to create ExecutionDB")]
NewMissingCode(H256),
#[error("The account {0} is not included in the stored pruned state trie")]
MissingAccountInStateTrie(H160),
#[error("Missing storage trie of account {0}")]
Expand Down
Loading
Loading