From b61520720b7023e7186aa4747baac8b0209c209f Mon Sep 17 00:00:00 2001 From: Kolby Moroz Liebl <31669092+KolbyML@users.noreply.github.com> Date: Wed, 3 Jul 2024 20:03:26 +0900 Subject: [PATCH 01/11] feat(trin-execution): execute multiple blocks in memory + execute to merge --- portal-bridge/src/bridge/state.rs | 26 +- portal-bridge/src/main.rs | 159 +++---- .../dashboards/trin-execution-main.json | 2 +- .../metrics/prometheus/prometheus.yml | 1 + trin-execution/src/dao_fork.rs | 59 +-- trin-execution/src/era/manager.rs | 11 + trin-execution/src/execution.rs | 335 ++++++++++----- trin-execution/src/main.rs | 39 +- trin-execution/src/storage/account.rs | 67 +-- trin-execution/src/storage/error.rs | 4 +- trin-execution/src/storage/evm_db.rs | 405 +++++++++++------- .../src/storage/execution_position.rs | 30 +- trin-execution/src/storage/utils.rs | 6 + trin-execution/src/trie_walker.rs | 5 +- trin-execution/tests/content_generation.rs | 34 +- 15 files changed, 662 insertions(+), 521 deletions(-) diff --git a/portal-bridge/src/bridge/state.rs b/portal-bridge/src/bridge/state.rs index a8ab444a8..aaaf0d2d1 100644 --- a/portal-bridge/src/bridge/state.rs +++ b/portal-bridge/src/bridge/state.rs @@ -9,7 +9,7 @@ use ethportal_api::{ types::state_trie::account_state::AccountState as AccountStateInfo, StateContentKey, StateContentValue, }; -use revm::DatabaseRef; +use revm::Database; use revm_primitives::{keccak256, Bytecode, SpecId, B256}; use surf::{Client, Config}; use tokio::{ @@ -24,7 +24,6 @@ use trin_execution::{ create_account_content_key, create_account_content_value, create_contract_content_key, create_contract_content_value, create_storage_content_key, create_storage_content_value, }, - era::manager::EraManager, execution::State, spec_id::get_spec_block_number, storage::utils::setup_temp_dir, @@ -86,10 +85,10 @@ impl StateBridge { info!("Launching state bridge: {:?}", self.mode); match self.mode.clone() { BridgeMode::Single(ModeType::Block(last_block)) => { - if last_block > get_spec_block_number(SpecId::DAO_FORK) { + if last_block > get_spec_block_number(SpecId::MERGE) { panic!( "State bridge only supports blocks up to {} for the time being.", - get_spec_block_number(SpecId::DAO_FORK) + get_spec_block_number(SpecId::MERGE) ); } self.launch_state(last_block) @@ -110,14 +109,10 @@ impl StateBridge { cache_contract_storage_changes: true, block_to_trace: BlockToTrace::None, }; - let mut state = State::new(Some(temp_directory.path().to_path_buf()), state_config)?; - let starting_block = 0; - let mut era_manager = EraManager::new(starting_block).await?; - for block_number in starting_block..=last_block { + let mut state = State::new(Some(temp_directory.path().to_path_buf()), state_config).await?; + for block_number in 0..=last_block { info!("Gossipping state for block at height: {block_number}"); - let block = era_manager.get_next_block().await?; - // process block let RootWithTrieDiff { root: root_hash, @@ -126,8 +121,15 @@ impl StateBridge { true => state .initialize_genesis() .map_err(|e| anyhow!("unable to create genesis state: {e}"))?, - false => state.process_block(block)?, + false => state.process_block(block_number).await?, }; + let block = state + .era_manager + .lock() + .await + .last_fetched_block() + .await? + .clone(); let walk_diff = TrieWalker::new(root_hash, changed_nodes); @@ -162,7 +164,7 @@ impl StateBridge { // if the code_hash is empty then then don't try to gossip the contract bytecode if account.code_hash != keccak256([]) { // gossip contract bytecode - let code = state.database.code_by_hash_ref(account.code_hash)?; + let code = state.database.code_by_hash(account.code_hash)?; self.gossip_contract_bytecode( address_hash, &account_proof, diff --git a/portal-bridge/src/main.rs b/portal-bridge/src/main.rs index bfa95c96a..69639d4b2 100644 --- a/portal-bridge/src/main.rs +++ b/portal-bridge/src/main.rs @@ -18,6 +18,14 @@ async fn main() -> Result<(), Box> { let bridge_config = BridgeConfig::parse(); + if bridge_config + .portal_subnetworks + .contains(&NetworkKind::State) + && bridge_config.portal_subnetworks.len() > 1 + { + return Err("The State network doesn't support being ran with the other networks bridges at the same time".into()); + } + if let Some(addr) = bridge_config.metrics_url { prometheus_exporter::start(addr)?; } @@ -41,30 +49,6 @@ async fn main() -> Result<(), Box> { let mut bridge_tasks = Vec::new(); - // Launch Beacon Network portal bridge - if bridge_config - .portal_subnetworks - .contains(&NetworkKind::Beacon) - { - let bridge_mode = bridge_config.mode.clone(); - let consensus_api = ConsensusApi::new( - bridge_config.cl_provider, - bridge_config.cl_provider_fallback, - ) - .await?; - let portal_client_clone = portal_client.clone(); - let bridge_handle = tokio::spawn(async move { - let beacon_bridge = BeaconBridge::new(consensus_api, bridge_mode, portal_client_clone); - - beacon_bridge - .launch() - .instrument(tracing::trace_span!("beacon")) - .await; - }); - - bridge_tasks.push(bridge_handle); - } - // Launch State Network portal bridge if bridge_config .portal_subnetworks @@ -82,65 +66,88 @@ async fn main() -> Result<(), Box> { bridge_config.gossip_limit, ) .await?; - let bridge_handle = tokio::spawn(async move { - state_bridge - .launch() - .instrument(tracing::trace_span!("state")) - .await; - }); - bridge_tasks.push(bridge_handle); - } - // Launch History Network portal bridge - if bridge_config - .portal_subnetworks - .contains(&NetworkKind::History) - { - let execution_api = ExecutionApi::new( - bridge_config.el_provider, - bridge_config.el_provider_fallback, - ) - .await?; - match bridge_config.mode { - BridgeMode::FourFours(_) => { - let header_oracle = HeaderOracle::default(); - let era1_bridge = Era1Bridge::new( - bridge_config.mode, - portal_client, - header_oracle, - bridge_config.epoch_acc_path, - bridge_config.gossip_limit, - execution_api, - ) - .await?; - let bridge_handle = tokio::spawn(async move { - era1_bridge - .launch() - .instrument(tracing::trace_span!("history(era1)")) - .await; - }); - bridge_tasks.push(bridge_handle); - } - _ => { - let bridge_handle = tokio::spawn(async move { - let header_oracle = HeaderOracle::default(); + state_bridge + .launch() + .instrument(tracing::trace_span!("state")) + .await; + } else { + // Launch Beacon Network portal bridge + if bridge_config + .portal_subnetworks + .contains(&NetworkKind::Beacon) + { + let bridge_mode = bridge_config.mode.clone(); + let consensus_api = ConsensusApi::new( + bridge_config.cl_provider, + bridge_config.cl_provider_fallback, + ) + .await?; + let portal_client_clone = portal_client.clone(); + let bridge_handle = tokio::spawn(async move { + let beacon_bridge = + BeaconBridge::new(consensus_api, bridge_mode, portal_client_clone); + + beacon_bridge + .launch() + .instrument(tracing::trace_span!("beacon")) + .await; + }); + + bridge_tasks.push(bridge_handle); + } - let bridge = HistoryBridge::new( + // Launch History Network portal bridge + if bridge_config + .portal_subnetworks + .contains(&NetworkKind::History) + { + let execution_api = ExecutionApi::new( + bridge_config.el_provider, + bridge_config.el_provider_fallback, + ) + .await?; + match bridge_config.mode { + BridgeMode::FourFours(_) => { + let header_oracle = HeaderOracle::default(); + let era1_bridge = Era1Bridge::new( bridge_config.mode, - execution_api, portal_client, header_oracle, bridge_config.epoch_acc_path, bridge_config.gossip_limit, - ); - - bridge - .launch() - .instrument(tracing::trace_span!("history")) - .await; - }); - - bridge_tasks.push(bridge_handle); + execution_api, + ) + .await?; + let bridge_handle = tokio::spawn(async move { + era1_bridge + .launch() + .instrument(tracing::trace_span!("history(era1)")) + .await; + }); + bridge_tasks.push(bridge_handle); + } + _ => { + let bridge_handle = tokio::spawn(async move { + let header_oracle = HeaderOracle::default(); + + let bridge = HistoryBridge::new( + bridge_config.mode, + execution_api, + portal_client, + header_oracle, + bridge_config.epoch_acc_path, + bridge_config.gossip_limit, + ); + + bridge + .launch() + .instrument(tracing::trace_span!("history")) + .await; + }); + + bridge_tasks.push(bridge_handle); + } } } } diff --git a/trin-execution/metrics/grafana/dashboards/trin-execution-main.json b/trin-execution/metrics/grafana/dashboards/trin-execution-main.json index cff51ed0d..5b424d723 100644 --- a/trin-execution/metrics/grafana/dashboards/trin-execution-main.json +++ b/trin-execution/metrics/grafana/dashboards/trin-execution-main.json @@ -1097,4 +1097,4 @@ "uid": "fdqcqby1pyvb4d", "version": 1, "weekStart": "" - } +} diff --git a/trin-execution/metrics/prometheus/prometheus.yml b/trin-execution/metrics/prometheus/prometheus.yml index eb0351969..6fc206a30 100644 --- a/trin-execution/metrics/prometheus/prometheus.yml +++ b/trin-execution/metrics/prometheus/prometheus.yml @@ -17,3 +17,4 @@ scrape_configs: - localhost:9091 labels: instance: local_node + diff --git a/trin-execution/src/dao_fork.rs b/trin-execution/src/dao_fork.rs index 2cdbf5fea..558d0d1a7 100644 --- a/trin-execution/src/dao_fork.rs +++ b/trin-execution/src/dao_fork.rs @@ -1,12 +1,10 @@ //! DAO Fork related constants from [EIP-779](https://eips.ethereum.org/EIPS/eip-779). //! It happened on Ethereum block 1_920_000 -use alloy_rlp::Decodable; -use eth_trie::Trie; -use ethportal_api::types::state_trie::account_state::AccountState as AccountStateInfo; -use revm_primitives::{address, keccak256, Address, U256}; +use revm::{db::State as RevmState, Evm}; +use revm_primitives::{address, Address}; -use crate::storage::{account::Account, evm_db::EvmDB}; +use crate::storage::evm_db::EvmDB; /// Dao hardfork beneficiary that received ether from accounts from DAO and DAO creator children. pub static DAO_HARDFORK_BENEFICIARY: Address = address!("bf4ed7b27f1d666546e30d74d50d173d20bca754"); @@ -131,52 +129,15 @@ pub static DAO_HARDKFORK_ACCOUNTS: [Address; 116] = [ address!("807640a13483f8ac783c557fcdf27be11ea4ac7a"), ]; -pub fn process_dao_fork(database: &EvmDB) -> anyhow::Result<()> { - let mut drained_balance_sum = U256::ZERO; - - // drain dao accounts - for address in DAO_HARDKFORK_ACCOUNTS { - let address_hash = keccak256(address); - let mut account: Account = match database - .db - .get(address_hash) - .expect("Reading account from database should never fail") - { - Some(raw_account) => Decodable::decode(&mut raw_account.as_slice())?, - None => Account::default(), - }; - - drained_balance_sum += account.balance; - account.balance = U256::ZERO; - - let _ = database.trie.lock().insert( - address_hash.as_ref(), - &alloy_rlp::encode(AccountStateInfo::from(&account)), - ); - - database.db.put(address_hash, alloy_rlp::encode(account))?; - } +pub fn process_dao_fork(database: &mut Evm<(), RevmState>) -> anyhow::Result<()> { + // drain balances from DAO hardfork accounts + let drained_balances = database.db_mut().drain_balances(DAO_HARDKFORK_ACCOUNTS)?; + let drained_balance_sum: u128 = drained_balances.iter().sum(); // transfer drained balance to beneficiary - let address_hash = keccak256(DAO_HARDFORK_BENEFICIARY); - - let mut account: Account = match database - .db - .get(address_hash) - .expect("Reading account from database should never fail") - { - Some(raw_account) => Decodable::decode(&mut raw_account.as_slice())?, - None => Account::default(), - }; - - account.balance += drained_balance_sum; - - let _ = database.trie.lock().insert( - address_hash.as_ref(), - &alloy_rlp::encode(AccountStateInfo::from(&account)), - ); - - database.db.put(address_hash, alloy_rlp::encode(account))?; + database + .db_mut() + .increment_balances([(DAO_HARDFORK_BENEFICIARY, drained_balance_sum)].into_iter())?; Ok(()) } diff --git a/trin-execution/src/era/manager.rs b/trin-execution/src/era/manager.rs index 1e95135f2..4a9f4a664 100644 --- a/trin-execution/src/era/manager.rs +++ b/trin-execution/src/era/manager.rs @@ -64,6 +64,17 @@ impl EraManager { self.next_block_number } + pub async fn last_fetched_block(&self) -> anyhow::Result<&ProcessedBlock> { + let Some(current_era) = &self.current_era else { + panic!("current_era should be initialized in EraManager::new"); + }; + ensure!( + self.next_block_number > 0, + "next_block_number should be greater than 0" + ); + Ok(current_era.get_block(self.next_block_number - 1)) + } + pub async fn get_next_block(&mut self) -> anyhow::Result<&ProcessedBlock> { let processed_era = match &self.current_era { Some(processed_era) if processed_era.contains_block(self.next_block_number) => { diff --git a/trin-execution/src/execution.rs b/trin-execution/src/execution.rs index 0b299cedc..71819eda3 100644 --- a/trin-execution/src/execution.rs +++ b/trin-execution/src/execution.rs @@ -7,8 +7,9 @@ use ethportal_api::types::{ state_trie::account_state::AccountState as AccountStateInfo, }; use revm::{ + db::states::{bundle_state::BundleRetention, State as RevmState}, inspector_handle_register, - inspectors::{NoOpInspector, TracerEip3155}, + inspectors::TracerEip3155, DatabaseCommit, Evm, }; use revm_primitives::{Env, ResultAndState, SpecId}; @@ -19,14 +20,22 @@ use std::{ io::BufReader, path::{Path, PathBuf}, sync::Arc, + time::{Duration, Instant}, }; +use tokio::sync::Mutex; use tracing::info; use crate::{ block_reward::get_block_reward, dao_fork::process_dao_fork, - era::types::{ProcessedBlock, TransactionsWithSender}, - metrics::{start_timer_vec, stop_timer, BLOCK_PROCESSING_TIMES}, + era::{ + manager::EraManager, + types::{ProcessedBlock, TransactionsWithSender}, + }, + metrics::{ + set_int_gauge_vec, start_timer_vec, stop_timer, BLOCK_HEIGHT, BLOCK_PROCESSING_TIMES, + TRANSACTION_PROCESSING_TIMES, + }, spec_id::{get_spec_block_number, get_spec_id}, storage::{ account::Account, @@ -55,6 +64,7 @@ pub struct State { pub database: EvmDB, pub config: StateConfig, execution_position: ExecutionPosition, + pub era_manager: Arc>, pub node_data_directory: PathBuf, } @@ -62,7 +72,7 @@ const GENESIS_STATE_FILE: &str = "trin-execution/resources/genesis/mainnet.json" const TEST_GENESIS_STATE_FILE: &str = "resources/genesis/mainnet.json"; impl State { - pub fn new(path: Option, config: StateConfig) -> anyhow::Result { + pub async fn new(path: Option, config: StateConfig) -> anyhow::Result { let node_data_directory = match path { Some(path_buf) => path_buf, None => get_default_data_dir()?, @@ -73,9 +83,14 @@ impl State { let database = EvmDB::new(config.clone(), db, &execution_position) .expect("Failed to create EVM database"); + let era_manager = Arc::new(Mutex::new( + EraManager::new(execution_position.next_block_number()).await?, + )); + Ok(State { execution_position, config, + era_manager, database, node_data_directory, }) @@ -83,9 +98,9 @@ impl State { pub fn initialize_genesis(&mut self) -> anyhow::Result { ensure!( - self.execution_position.block_execution_number() == 0, + self.execution_position.next_block_number() == 0, "Trying to initialize genesis but received block {}", - self.execution_position.block_execution_number(), + self.execution_position.next_block_number(), ); let genesis_file = if Path::new(GENESIS_STATE_FILE).is_file() { @@ -115,22 +130,142 @@ impl State { "Root doesn't match state root from genesis file" ); - self.execution_position - .increase_block_execution_number(self.database.db.clone(), root_with_trie_diff.root)?; + self.execution_position.set_next_block_number( + self.database.db.clone(), + 1, + root_with_trie_diff.root, + )?; Ok(root_with_trie_diff) } - pub fn process_block(&mut self, block: &ProcessedBlock) -> anyhow::Result { - let timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["initialize_evm"]); - info!("State EVM processing block {}", block.header.number); + /// This is a lot faster then process_block() as it executes the range in memory, but we won't + /// return the trie diff so you can use this to sync up to the block you want, then use + /// `process_block()` to get the trie diff to gossip on the state bridge + pub async fn process_range_of_blocks( + &mut self, + start: u64, + end: u64, + ) -> anyhow::Result { + info!("Processing blocks from {} to {} (inclusive)", start, end); + let database = RevmState::builder() + .with_database(self.database.clone()) + .with_bundle_update() + .build(); + + let mut evm: Evm<(), RevmState> = Evm::builder().with_db(database).build(); + let mut cumulative_gas_used = 0; + let mut cumulative_gas_expected = 0; + let range_start = Instant::now(); + let mut last_block_executed = start - 1; + for block_number in start..=end { + if get_spec_id(block_number).is_enabled_in(SpecId::SPURIOUS_DRAGON) { + evm.db_mut().set_state_clear_flag(true); + } else { + evm.db_mut().set_state_clear_flag(false); + }; + let timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["fetching_block_from_era"]); + let block = self + .era_manager + .lock() + .await + .get_next_block() + .await? + .clone(); + stop_timer(timer); + + // insert blockhash into database and remove old one + let timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["insert_blockhash"]); + self.database.db.put( + keccak256(B256::from(U256::from(block.header.number))), + block.header.hash(), + )?; + if block.header.number >= 8192 { + self.database.db.delete(keccak256(B256::from(U256::from( + block.header.number - 8192, + ))))?; + } + stop_timer(timer); + + cumulative_gas_used += self.execute_block(&block, &mut evm)?; + cumulative_gas_expected += block.header.gas_used.to::(); + last_block_executed = block_number; + + // Commit the bundle if we have reached the limits, to prevent to much memory usage + // We won't use this during the dos attack to avoid writing empty accounts to disk + if !(block_number < 2_700_000 && block_number > 2_200_000) + && should_we_commit_block_execution_early( + block_number - start, + evm.context.evm.db.bundle_size_hint() as u64, + cumulative_gas_used, + range_start.elapsed(), + ) + { + break; + } + } + ensure!( - self.execution_position.block_execution_number() == block.header.number, - "Expected block {}, received {}", - self.execution_position.block_execution_number(), - block.header.number + cumulative_gas_used == cumulative_gas_expected, + "Cumulative gas used doesn't match gas expected! Irreversible! Block number: {}", + end ); + let timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["commit_bundle"]); + evm.db_mut().merge_transitions(BundleRetention::PlainState); + let state_bundle = evm.db_mut().take_bundle(); + self.database.commit_bundle(state_bundle)?; + stop_timer(timer); + + let timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["get_root_with_trie_diff"]); + let RootWithTrieDiff { + root, + trie_diff: changed_nodes, + } = self.get_root_with_trie_diff()?; + let header_state_root = self + .era_manager + .lock() + .await + .last_fetched_block() + .await? + .header + .state_root; + if root != header_state_root { + panic!( + "State root doesn't match! Irreversible! Block number: {}", + last_block_executed + ) + } + stop_timer(timer); + + let timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["set_block_execution_number"]); + self.execution_position.set_next_block_number( + self.database.db.clone(), + last_block_executed + 1, + root, + )?; + stop_timer(timer); + + Ok(RootWithTrieDiff { + root, + trie_diff: changed_nodes, + }) + } + + pub async fn process_block(&mut self, block_number: u64) -> anyhow::Result { + self.process_range_of_blocks(block_number, block_number) + .await + } + + pub fn execute_block( + &self, + block: &ProcessedBlock, + evm: &mut Evm<(), RevmState>, + ) -> anyhow::Result { + let execute_block_timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["execute_block"]); + let timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["initialize_evm"]); + info!("State EVM processing block {}", block.header.number); + // initialize evm environment let mut env = Env::default(); env.block.number = U256::from(block.header.number); @@ -152,115 +287,61 @@ impl State { .set_blob_excess_gas_and_price(u64::from_be_bytes(excess_blob_gas.to_be_bytes())); } - stop_timer(timer); + evm.context.evm.env = Box::new(env); + evm.handler.modify_spec_id(get_spec_id(block.header.number)); - // insert blockhash into database - let timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["insert_blockhash"]); - self.database.db.put( - keccak256(B256::from(U256::from(block.header.number))), - block.header.hash(), - )?; stop_timer(timer); // execute transactions - let mut cumulative_gas_used = U256::ZERO; + let mut cumulative_gas_used = 0; let cumulative_transaction_timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["cumulative_transaction"]); for transaction in block.transactions.iter() { let transaction_timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["transaction"]); - let evm_result = self.execute_transaction(transaction, &env)?; - cumulative_gas_used += U256::from(evm_result.result.gas_used()); - self.execution_position - .increase_transaction_index(self.database.db.clone())?; - self.database.commit(evm_result.state); + let transaction_execution_timer = + start_timer_vec(&BLOCK_PROCESSING_TIMES, &["transaction_execution"]); + let evm_result = self.execute_transaction(transaction, evm)?; + cumulative_gas_used += evm_result.result.gas_used(); + stop_timer(transaction_execution_timer); + let timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["commit_state"]); + evm.db_mut().commit(evm_result.state); + stop_timer(timer); stop_timer(transaction_timer); } stop_timer(cumulative_transaction_timer); // update beneficiary let timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["update_beneficiary"]); - for (beneficiary, reward) in get_block_reward(block) { - let mut account = self.get_account_state(&beneficiary)?; - account.balance += U256::from(reward); - self.database - .trie - .lock() - .insert(keccak256(beneficiary).as_ref(), &alloy_rlp::encode(account))?; - - match self.database.db.get(keccak256(beneficiary))? { - Some(account_bytes) => { - let mut account: Account = Decodable::decode(&mut account_bytes.as_slice())?; - account.balance += U256::from(reward); - self.database - .db - .put(keccak256(beneficiary), alloy_rlp::encode(account))?; - } - None => { - let mut account = Account::default(); - account.balance += U256::from(reward); - self.database - .db - .put(keccak256(beneficiary), alloy_rlp::encode(account))?; - } - } - } + let _ = evm.db_mut().increment_balances(get_block_reward(block)); // check if dao fork, if it is drain accounts and transfer it to beneficiary if block.header.number == get_spec_block_number(SpecId::DAO_FORK) { - process_dao_fork(&self.database)?; - } - stop_timer(timer); - - let timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["get_root_with_trie_diff"]); - let RootWithTrieDiff { - root, - trie_diff: changed_nodes, - } = self.get_root_with_trie_diff()?; - if root != block.header.state_root { - panic!( - "State root doesn't match! Irreversible! Block number: {}", - self.execution_position.block_execution_number() - ) + process_dao_fork(evm)?; } stop_timer(timer); - - let timer = start_timer_vec( - &BLOCK_PROCESSING_TIMES, - &["increase_block_execution_number"], - ); - self.execution_position - .increase_block_execution_number(self.database.db.clone(), root)?; - stop_timer(timer); - - Ok(RootWithTrieDiff { - root, - trie_diff: changed_nodes, - }) + stop_timer(execute_block_timer); + set_int_gauge_vec(&BLOCK_HEIGHT, block.header.number as i64, &[]); + Ok(cumulative_gas_used) } fn execute_transaction( - &mut self, + &self, tx: &TransactionsWithSender, - evm_evnironment: &Env, + evm: &mut Evm<(), RevmState>, ) -> anyhow::Result { - let block_number = evm_evnironment.block.number.to::(); - - let base_evm_builder = Evm::builder() - .with_ref_db(&self.database) - .with_env(Box::new(evm_evnironment.clone())) - .with_spec_id(get_spec_id(block_number)) - .modify_tx_env(|tx_env| { - tx_env.caller = tx.sender_address; - match &tx.transaction { - Transaction::Legacy(tx) => tx.modify(block_number, tx_env), - Transaction::EIP1559(tx) => tx.modify(block_number, tx_env), - Transaction::AccessList(tx) => tx.modify(block_number, tx_env), - Transaction::Blob(tx) => tx.modify(block_number, tx_env), - } - }); - - Ok(if self.config.block_to_trace.should_trace(block_number) { + let block_number = evm.context.evm.env.block.number.to::(); + + let timer = start_timer_vec(&TRANSACTION_PROCESSING_TIMES, &["modify_tx"]); + evm.context.evm.env.tx.caller = tx.sender_address; + match &tx.transaction { + Transaction::Legacy(tx) => tx.modify(block_number, &mut evm.context.evm.env.tx), + Transaction::EIP1559(tx) => tx.modify(block_number, &mut evm.context.evm.env.tx), + Transaction::AccessList(tx) => tx.modify(block_number, &mut evm.context.evm.env.tx), + Transaction::Blob(tx) => tx.modify(block_number, &mut evm.context.evm.env.tx), + } + + if self.config.block_to_trace.should_trace(block_number) { let output_path = self .node_data_directory .as_path() @@ -269,22 +350,26 @@ impl State { fs::create_dir_all(&output_path)?; let output_file = File::create(output_path.join(format!("tx_{}.json", tx.transaction.hash())))?; - base_evm_builder + let mut evm_with_tracer = Evm::builder() + .with_env(evm.context.evm.inner.env.clone()) + .with_spec_id(evm.handler.cfg.spec_id) + .with_db(&mut evm.context.evm.inner.db) .with_external_context(TracerEip3155::new(Box::new(output_file))) .append_handler_register(inspector_handle_register) - .build() - .transact()? - } else { - base_evm_builder - .with_external_context(NoOpInspector) - .append_handler_register(inspector_handle_register) - .build() - .transact()? - }) + .build(); + let result = evm_with_tracer.transact()?; + return Ok(result); + }; + stop_timer(timer); + + let timer = start_timer_vec(&TRANSACTION_PROCESSING_TIMES, &["transact"]); + let result = evm.transact()?; + stop_timer(timer); + Ok(result) } - pub fn block_execution_number(&self) -> u64 { - self.execution_position.block_execution_number() + pub fn next_block_number(&self) -> u64 { + self.execution_position.next_block_number() } pub fn get_root(&mut self) -> anyhow::Result { @@ -346,6 +431,18 @@ impl State { } } +pub fn should_we_commit_block_execution_early( + blocks_processed: u64, + changes_processed: u64, + cumulative_gas_used: u64, + elapsed: Duration, +) -> bool { + blocks_processed >= 500_000 + || changes_processed >= 5_000_000 + || cumulative_gas_used >= 30_000_000 * 50_000 + || elapsed >= Duration::from_secs(30 * 60) +} + #[cfg(test)] mod tests { use std::fs; @@ -365,26 +462,36 @@ mod tests { Some(temp_directory.path().to_path_buf()), StateConfig::default(), ) + .await .unwrap(); let _ = state.initialize_genesis().unwrap(); let raw_era1 = fs::read("../test_assets/era1/mainnet-00000-5ec1ffb8.era1").unwrap(); let processed_era = process_era1_file(raw_era1, 0).unwrap(); for block in processed_era.blocks { if block.header.number == 0 { + // initialize genesis state processes this block so we skip it + state + .era_manager + .lock() + .await + .get_next_block() + .await + .unwrap(); continue; } - state.process_block(&block).unwrap(); + state.process_block(block.header.number).await.unwrap(); assert_eq!(state.get_root().unwrap(), block.header.state_root); } } - #[test_log::test] - fn test_get_proof() { + #[tokio::test] + async fn test_get_proof() { let temp_directory = setup_temp_dir().unwrap(); let mut state = State::new( Some(temp_directory.path().to_path_buf()), StateConfig::default(), ) + .await .unwrap(); let _ = state.initialize_genesis().unwrap(); let valid_proof = state diff --git a/trin-execution/src/main.rs b/trin-execution/src/main.rs index e9dcd6a93..08809adaf 100644 --- a/trin-execution/src/main.rs +++ b/trin-execution/src/main.rs @@ -1,13 +1,10 @@ use clap::Parser; +use e2store::era1::BLOCK_TUPLE_COUNT; use revm_primitives::SpecId; use tracing::info; use trin_execution::{ - cli::TrinExecutionConfig, - era::manager::EraManager, - execution::State, - metrics::{start_timer_vec, stop_timer, BLOCK_PROCESSING_TIMES}, - spec_id::get_spec_block_number, + cli::TrinExecutionConfig, execution::State, spec_id::get_spec_block_number, storage::utils::setup_temp_dir, }; use trin_utils::log::init_tracing_logger; @@ -31,10 +28,8 @@ async fn main() -> Result<(), Box> { let mut state = State::new( directory.map(|temp_directory| temp_directory.path().to_path_buf()), trin_execution_config.into(), - )?; - - let starting_block_number = state.block_execution_number(); - let mut era_manager = EraManager::new(starting_block_number).await?; + ) + .await?; let (tx, mut rx) = tokio::sync::mpsc::channel(1); tokio::spawn(async move { @@ -44,27 +39,33 @@ async fn main() -> Result<(), Box> { .expect("signal ctrl_c should never fail"); }); - for block_number in starting_block_number..=get_spec_block_number(SpecId::MERGE) { + let mut block_number = state.next_block_number(); + + let end_block = get_spec_block_number(SpecId::MERGE); + while block_number < end_block { if rx.try_recv().is_ok() { state.database.db.flush()?; info!( - "Received SIGINT, stopping execution: {block_number} {}", + "Received SIGINT, stopping execution: {} {}", + block_number - 1, state.get_root()? ); break; } - let timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["fetching_block_from_era"]); - let block = era_manager.get_next_block().await?; - stop_timer(timer); - let timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["processing_block"]); - if block.header.number == 0 { + let end = + ((block_number / (BLOCK_TUPLE_COUNT as u64)) + 86) * (BLOCK_TUPLE_COUNT as u64) - 1; + let end = std::cmp::min(end, end_block); + + if block_number == 0 { + // initialize genesis state processes this block so we skip it + state.era_manager.lock().await.get_next_block().await?; state.initialize_genesis()?; + block_number = 1; continue; } - state.process_block(block)?; - stop_timer(timer); - assert_eq!(state.get_root()?, block.header.state_root); + state.process_range_of_blocks(block_number, end).await?; + block_number = state.next_block_number(); } Ok(()) diff --git a/trin-execution/src/storage/account.rs b/trin-execution/src/storage/account.rs index fdf0bded2..d1ca9369a 100644 --- a/trin-execution/src/storage/account.rs +++ b/trin-execution/src/storage/account.rs @@ -1,7 +1,7 @@ use alloy_primitives::{keccak256, B256, U256}; -use alloy_rlp::{Buf, Decodable, Encodable, RlpDecodable, RlpEncodable, EMPTY_STRING_CODE}; +use alloy_rlp::{RlpDecodable, RlpEncodable, EMPTY_STRING_CODE}; use ethportal_api::types::state_trie::account_state::AccountState as AccountStateInfo; -use revm_primitives::KECCAK_EMPTY; +use revm_primitives::{AccountInfo, KECCAK_EMPTY}; /// The Account State stored in the state trie. #[derive(Debug, Clone, PartialEq, Eq, RlpEncodable, RlpDecodable)] @@ -10,8 +10,6 @@ pub struct Account { pub balance: U256, pub storage_root: B256, pub code_hash: B256, - /// If account is self destructed or newly created, storage will be cleared. - pub account_state: AccountState, } impl Default for Account { @@ -21,7 +19,6 @@ impl Default for Account { balance: U256::ZERO, storage_root: keccak256([EMPTY_STRING_CODE]), code_hash: KECCAK_EMPTY, - account_state: AccountState::default(), } } } @@ -37,59 +34,13 @@ impl From<&Account> for AccountStateInfo { } } -#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)] -pub enum AccountState { - /// Before Spurious Dragon hardfork there was a difference between empty and not existing. - /// And we are flagging it here. - NotExisting, - /// EVM touched this account. For newer hardfork this means it can be cleared/removed from - /// state. - Touched, - /// EVM cleared storage of this account, mostly by selfdestruct, we don't ask database for - /// storage slots and assume they are U256::ZERO - StorageCleared, - /// EVM didn't interacted with this account - #[default] - None, -} - -impl Encodable for AccountState { - fn encode(&self, out: &mut dyn alloy_rlp::BufMut) { - match self { - AccountState::NotExisting => out.put_u8(0), - AccountState::Touched => out.put_u8(1), - AccountState::StorageCleared => out.put_u8(2), - AccountState::None => out.put_u8(3), - } - } -} - -impl Decodable for AccountState { - fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { - let account_state = AccountState::try_from(buf[0]) - .map_err(|_| alloy_rlp::Error::Custom("Unknown transaction id"))?; - buf.advance(1); - Ok(account_state) - } -} - -impl TryFrom for AccountState { - type Error = alloy_rlp::Error; - - fn try_from(value: u8) -> Result { - match value { - 0 => Ok(AccountState::NotExisting), - 1 => Ok(AccountState::Touched), - 2 => Ok(AccountState::StorageCleared), - 3 => Ok(AccountState::None), - _ => Err(alloy_rlp::Error::Custom("Invalid AccountState byte")), +impl From for Account { + fn from(account_info: AccountInfo) -> Self { + Self { + nonce: account_info.nonce, + balance: account_info.balance, + storage_root: keccak256([EMPTY_STRING_CODE]), + code_hash: account_info.code_hash, } } } - -impl AccountState { - /// Returns `true` if EVM cleared storage of this account - pub fn is_storage_cleared(&self) -> bool { - matches!(self, AccountState::StorageCleared) - } -} diff --git a/trin-execution/src/storage/error.rs b/trin-execution/src/storage/error.rs index e641f75c0..3834b28e7 100644 --- a/trin-execution/src/storage/error.rs +++ b/trin-execution/src/storage/error.rs @@ -11,8 +11,8 @@ pub enum EVMError { #[error("rocksdb error {0}")] DB(#[from] rocksdb::Error), - #[error("not found")] - NotFound, + #[error("not found database error {0}")] + NotFound(String), #[error("balance error")] BalanceError, diff --git a/trin-execution/src/storage/evm_db.rs b/trin-execution/src/storage/evm_db.rs index 706c83e6c..2c414f41a 100644 --- a/trin-execution/src/storage/evm_db.rs +++ b/trin-execution/src/storage/evm_db.rs @@ -1,22 +1,28 @@ use std::sync::Arc; -use crate::{config::StateConfig, storage::error::EVMError}; +use crate::{ + config::StateConfig, + metrics::{ + start_timer_vec, stop_timer, BUNDLE_COMMIT_PROCESSING_TIMES, TRANSACTION_PROCESSING_TIMES, + }, + storage::error::EVMError, +}; use alloy_primitives::{Address, B256, U256}; use alloy_rlp::{Decodable, EMPTY_STRING_CODE}; use eth_trie::{EthTrie, RootWithTrieDiff, Trie}; -use ethportal_api::{ - types::state_trie::account_state::AccountState as AccountStateInfo, utils::bytes::hex_encode, -}; +use ethportal_api::types::state_trie::account_state::AccountState as AccountStateInfo; use hashbrown::{HashMap as BrownHashMap, HashSet}; use parking_lot::Mutex; -use revm::{DatabaseCommit, DatabaseRef}; -use revm_primitives::{keccak256, Account, AccountInfo, Bytecode, HashMap, KECCAK_EMPTY}; +use revm::{ + db::{states::PlainStorageChangeset, BundleState, OriginalValuesKnown}, + Database, DatabaseRef, +}; +use revm_primitives::{keccak256, AccountInfo, Bytecode, HashMap, KECCAK_EMPTY}; use rocksdb::DB as RocksDB; +use tracing::info; use super::{ - account::{Account as RocksAccount, AccountState as RocksAccountState}, - account_db::AccountDB, - execution_position::ExecutionPosition, + account::Account as RocksAccount, account_db::AccountDB, execution_position::ExecutionPosition, trie_db::TrieRocksDB, }; @@ -61,30 +67,6 @@ impl EvmDB { }) } - /// Inserts the account's code into the cache. - /// - /// Accounts objects and code are stored separately in the cache, this will take the code from - /// the account and instead map it to the code hash. - /// - /// Note: This will not insert into the underlying external database. - pub fn insert_contract(&mut self, account: &mut AccountInfo) { - if let Some(code) = &account.code { - if !code.is_empty() { - if account.code_hash == KECCAK_EMPTY { - account.code_hash = code.hash_slow(); - } - - // Insert contract code into the database - self.db - .put(account.code_hash, code.original_bytes().as_ref()) - .expect("Inserting contract shouldn't fail"); - } - } - if account.code_hash == B256::ZERO { - account.code_hash = KECCAK_EMPTY; - } - } - pub fn get_storage_trie_diff(&self, address_hash: B256) -> BrownHashMap> { let mut trie_diff = BrownHashMap::new(); @@ -109,150 +91,268 @@ impl EvmDB { } trie_diff } -} -impl DatabaseCommit for EvmDB { - fn commit(&mut self, changes: HashMap) { - for (address, mut account) in changes { - if !account.is_touched() { - continue; - } + fn commit_accounts( + &mut self, + plain_account: Vec<(Address, Option)>, + ) -> anyhow::Result<()> { + for (address, account) in plain_account { let address_hash = keccak256(address); - if account.is_selfdestructed() { - let mut rocks_account: RocksAccount = match self - .db - .get(address_hash) - .expect("Committing account to database should never fail") - { - Some(raw_account) => Decodable::decode(&mut raw_account.as_slice()) - .expect("Decoding account should never fail"), + if let Some(account_info) = account { + let plain_state_some_account_timer = start_timer_vec( + &BUNDLE_COMMIT_PROCESSING_TIMES, + &["account:plain_state_some_account"], + ); + + let mut rocks_account: RocksAccount = account_info.into(); + + let timer = start_timer_vec( + &BUNDLE_COMMIT_PROCESSING_TIMES, + &["account:fetch_account_from_db"], + ); + let raw_account = self.db.get(address_hash)?.unwrap_or_default(); + stop_timer(timer); + + if !raw_account.is_empty() { + let decoded_account: RocksAccount = + Decodable::decode(&mut raw_account.as_slice())?; + rocks_account.storage_root = decoded_account.storage_root; + } + + let timer = start_timer_vec( + &BUNDLE_COMMIT_PROCESSING_TIMES, + &["account:insert_into_trie"], + ); + let _ = self.trie.lock().insert( + address_hash.as_ref(), + &alloy_rlp::encode(AccountStateInfo::from(&rocks_account)), + ); + stop_timer(timer); + + let timer = start_timer_vec( + &BUNDLE_COMMIT_PROCESSING_TIMES, + &["account:put_account_into_db"], + ); + self.db + .put(address_hash, alloy_rlp::encode(rocks_account))?; + stop_timer(timer); + + stop_timer(plain_state_some_account_timer); + } else if self.db.get(address_hash)?.is_some() { + let timer = + start_timer_vec(&BUNDLE_COMMIT_PROCESSING_TIMES, &["account:delete_account"]); + let rocks_account: RocksAccount = match self.db.get(address_hash)? { + Some(raw_account) => Decodable::decode(&mut raw_account.as_slice())?, None => RocksAccount::default(), }; if rocks_account.storage_root != keccak256([EMPTY_STRING_CODE]) { let account_db = AccountDB::new(address_hash, self.db.clone()); - let mut trie = EthTrie::from(Arc::new(account_db), rocks_account.storage_root) - .expect("Creating trie should never fail"); - trie.clear_trie_from_db() - .expect("Clearing trie should never fail"); + let mut trie = EthTrie::from(Arc::new(account_db), rocks_account.storage_root)?; + trie.clear_trie_from_db()?; } - rocks_account = RocksAccount::default(); - rocks_account.account_state = RocksAccountState::NotExisting; - self.db - .put( - keccak256(address.as_slice()), - alloy_rlp::encode(rocks_account), - ) - .expect("Inserting account should never fail"); + self.db.delete(address_hash)?; // update trie let _ = self.trie.lock().remove(address_hash.as_ref()); - continue; + stop_timer(timer); } - let is_newly_created = account.is_created(); - self.insert_contract(&mut account.info); + } + Ok(()) + } - let mut rocks_account: RocksAccount = match self - .db - .get(address_hash) - .expect("Reading account from database should never fail") - { - Some(raw_account) => { - Decodable::decode(&mut raw_account.as_slice()).unwrap_or_else(|_| { - panic!( - "Decoding account should never fail {}", - hex_encode(&raw_account) - ) - }) - } - None => RocksAccount::default(), - }; + fn commit_storage(&mut self, plain_storage: Vec) -> anyhow::Result<()> { + for PlainStorageChangeset { + address, + wipe_storage, + storage, + } in plain_storage + { + let plain_state_storage_timer = start_timer_vec( + &BUNDLE_COMMIT_PROCESSING_TIMES, + &["storage:plain_state_storage"], + ); - rocks_account.balance = account.info.balance; - rocks_account.nonce = account.info.nonce; - rocks_account.code_hash = account.info.code_hash; + let timer = start_timer_vec( + &BUNDLE_COMMIT_PROCESSING_TIMES, + &["storage:fetch_account_from_db"], + ); + let address_hash = keccak256(address); + let rocks_account: Option = + self.db.get(address_hash)?.map(|raw_account| { + Decodable::decode(&mut raw_account.as_slice()) + .expect("valid account should be decoded") + }); + stop_timer(timer); + + if wipe_storage && rocks_account.is_some() { + let timer = + start_timer_vec(&BUNDLE_COMMIT_PROCESSING_TIMES, &["storage:wipe_storage"]); + + let account_db = AccountDB::new(address_hash, self.db.clone()); + let mut rocks_account = rocks_account.expect("We already checked that it is some"); + if rocks_account.storage_root != keccak256([EMPTY_STRING_CODE]) { + let mut trie = EthTrie::from(Arc::new(account_db), rocks_account.storage_root)?; + trie.clear_trie_from_db()?; + rocks_account.storage_root = keccak256([EMPTY_STRING_CODE]); + let _ = self.trie.lock().insert( + address_hash.as_ref(), + &alloy_rlp::encode(AccountStateInfo::from(&rocks_account)), + ); + self.db + .put(address_hash, alloy_rlp::encode(rocks_account)) + .expect("Inserting account should never fail"); + } + stop_timer(timer); + } - let account_db = AccountDB::new(address_hash, self.db.clone()); + if !storage.is_empty() { + let timer = + start_timer_vec(&BUNDLE_COMMIT_PROCESSING_TIMES, &["storage:apply_updates"]); - let mut trie = if rocks_account.storage_root == keccak256([EMPTY_STRING_CODE]) { - EthTrie::new(Arc::new(account_db)) - } else { - EthTrie::from(Arc::new(account_db), rocks_account.storage_root) - .expect("Creating trie should never fail") - }; + let account_db = AccountDB::new(address_hash, self.db.clone()); + let mut rocks_account: RocksAccount = self + .db + .get(address_hash)? + .map(|raw_account| { + let rocks_account: RocksAccount = + Decodable::decode(&mut raw_account.as_slice()) + .expect("Decoding account should never fail"); + rocks_account + }) + .unwrap_or_default(); - rocks_account.account_state = if is_newly_created { - if rocks_account.storage_root != keccak256([EMPTY_STRING_CODE]) { - trie.clear_trie_from_db() - .expect("Clearing trie should never fail"); + let mut trie = if rocks_account.storage_root == keccak256([EMPTY_STRING_CODE]) { + EthTrie::new(Arc::new(account_db)) + } else { + EthTrie::from(Arc::new(account_db), rocks_account.storage_root)? }; - RocksAccountState::StorageCleared - } else if rocks_account.account_state.is_storage_cleared() { - // Preserve old account state if it already exists - RocksAccountState::StorageCleared - } else { - RocksAccountState::Touched - }; - - for (key, value) in account - .storage - .into_iter() - .filter(|(_, value)| value.is_changed()) - { - if value.present_value() > U256::ZERO { - let _ = trie.insert( - keccak256(B256::from(key)).as_ref(), - &alloy_rlp::encode(value.present_value()), - ); - } else { - let _ = trie.remove(keccak256(B256::from(key)).as_ref()); + for (key, value) in storage { + let key = B256::from(key); + trie.remove(keccak256(B256::from(key)).as_ref())?; + + if value != U256::ZERO { + trie.insert( + keccak256(B256::from(key)).as_ref(), + &alloy_rlp::encode(value), + )?; + } } - } - // update trie - let RootWithTrieDiff { - root: storage_root, - trie_diff, - } = trie - .root_hash_with_changed_nodes() - .expect("Getting the root hash should never fail"); - - if self.config.cache_contract_storage_changes { - let account_storage_cache = self.storage_cache.entry(address_hash).or_default(); - for key in trie_diff.keys() { - account_storage_cache.insert(*key); + // update trie + let RootWithTrieDiff { + root: storage_root, + trie_diff, + } = trie.root_hash_with_changed_nodes()?; + + if self.config.cache_contract_storage_changes { + let account_storage_cache = self.storage_cache.entry(address_hash).or_default(); + for key in trie_diff.keys() { + account_storage_cache.insert(*key); + } } - } - rocks_account.storage_root = storage_root; + rocks_account.storage_root = storage_root; - let _ = self.trie.lock().insert( - address_hash.as_ref(), - &alloy_rlp::encode(AccountStateInfo::from(&rocks_account)), - ); + let _ = self.trie.lock().insert( + address_hash.as_ref(), + &alloy_rlp::encode(AccountStateInfo::from(&rocks_account)), + ); + + self.db + .put(address_hash, alloy_rlp::encode(rocks_account))?; + stop_timer(timer); + } + stop_timer(plain_state_storage_timer); + } + Ok(()) + } + pub fn commit_bundle(&mut self, bundle_state: BundleState) -> anyhow::Result<()> { + // Currently we don't use reverts, so we can ignore them, but they are here for when we do. + let timer = start_timer_vec( + &BUNDLE_COMMIT_PROCESSING_TIMES, + &["generate_plain_state_and_reverts"], + ); + let (plain_state, _reverts) = + bundle_state.into_plain_state_and_reverts(OriginalValuesKnown::Yes); + stop_timer(timer); + + info!( + "Committing bundle state with {} accounts, {} contracts, {} storage changes", + plain_state.accounts.len(), + plain_state.contracts.len(), + plain_state.storage.len() + ); + + // Write Account State + let timer = start_timer_vec( + &BUNDLE_COMMIT_PROCESSING_TIMES, + &["account:committing_accounts_total"], + ); + self.commit_accounts(plain_state.accounts)?; + stop_timer(timer); + + // Write Contract Code + let timer = start_timer_vec( + &BUNDLE_COMMIT_PROCESSING_TIMES, + &["contract:committing_contracts_total"], + ); + for (hash, bytecode) in plain_state.contracts { + let timer = start_timer_vec(&BUNDLE_COMMIT_PROCESSING_TIMES, &["committing_contract"]); self.db - .put( - keccak256(address.as_slice()), - alloy_rlp::encode(rocks_account), - ) - .expect("Inserting account should never fail"); + .put(hash, bytecode.original_bytes().as_ref()) + .expect("Inserting contract code should never fail"); + stop_timer(timer); } + stop_timer(timer); + + // Write Storage + let timer = start_timer_vec( + &BUNDLE_COMMIT_PROCESSING_TIMES, + &["storage:committing_storage_total"], + ); + self.commit_storage(plain_state.storage)?; + stop_timer(timer); + + Ok(()) + } +} + +impl Database for EvmDB { + type Error = EVMError; + + #[doc = " Get basic account information."] + fn basic(&mut self, address: Address) -> Result, Self::Error> { + DatabaseRef::basic_ref(&self, address) + } + + #[doc = " Get account code by its hash."] + fn code_by_hash(&mut self, code_hash: B256) -> Result { + DatabaseRef::code_by_hash_ref(&self, code_hash) + } + + #[doc = " Get storage value of address at index."] + fn storage(&mut self, address: Address, index: U256) -> Result { + DatabaseRef::storage_ref(&self, address, index) + } + + #[doc = " Get block hash by block number."] + fn block_hash(&mut self, number: u64) -> Result { + DatabaseRef::block_hash_ref(&self, number) } } impl DatabaseRef for EvmDB { type Error = EVMError; + #[doc = " Get basic account information."] fn basic_ref(&self, address: Address) -> Result, Self::Error> { + let _timer = start_timer_vec(&TRANSACTION_PROCESSING_TIMES, &["database_get_basic"]); match self.db.get(keccak256(address))? { Some(raw_account) => { let account: RocksAccount = Decodable::decode(&mut raw_account.as_slice())?; - if account.account_state == RocksAccountState::NotExisting { - return Ok(None); - } - Ok(Some(AccountInfo { balance: account.balance, nonce: account.nonce, @@ -264,18 +364,25 @@ impl DatabaseRef for EvmDB { } } + #[doc = " Get account code by its hash."] fn code_by_hash_ref(&self, code_hash: B256) -> Result { + let _timer = start_timer_vec( + &TRANSACTION_PROCESSING_TIMES, + &["database_get_code_by_hash"], + ); match self.db.get(code_hash)? { Some(raw_code) => Ok(Bytecode::new_raw(raw_code.into())), - None => Err(Self::Error::NotFound), + None => Err(Self::Error::NotFound("code_by_hash".to_string())), } } + #[doc = " Get storage value of address at index."] fn storage_ref(&self, address: Address, index: U256) -> Result { + let _timer = start_timer_vec(&TRANSACTION_PROCESSING_TIMES, &["database_get_storage"]); let address_hash = keccak256(address); let account: RocksAccount = match self.db.get(address_hash)? { Some(raw_account) => Decodable::decode(&mut raw_account.as_slice())?, - None => return Err(Self::Error::NotFound), + None => return Err(Self::Error::NotFound("storage".to_string())), }; let account_db = AccountDB::new(address_hash, self.db.clone()); let trie = if account.storage_root == keccak256([EMPTY_STRING_CODE]) { @@ -285,23 +392,15 @@ impl DatabaseRef for EvmDB { }; match trie.get(keccak256(B256::from(index)).as_ref())? { Some(raw_value) => Ok(Decodable::decode(&mut raw_value.as_slice())?), - None => { - if matches!( - account.account_state, - RocksAccountState::StorageCleared | RocksAccountState::NotExisting - ) { - Ok(U256::ZERO) - } else { - Err(Self::Error::NotFound) - } - } + None => Ok(U256::ZERO), } } fn block_hash_ref(&self, number: u64) -> Result { + let _timer = start_timer_vec(&TRANSACTION_PROCESSING_TIMES, &["database_get_block_hash"]); match self.db.get(keccak256(B256::from(U256::from(number))))? { Some(raw_hash) => Ok(B256::from_slice(&raw_hash)), - None => Err(Self::Error::NotFound), + None => Err(Self::Error::NotFound("block_hash".to_string())), } } } diff --git a/trin-execution/src/storage/execution_position.rs b/trin-execution/src/storage/execution_position.rs index 5826b8f19..e8dde4283 100644 --- a/trin-execution/src/storage/execution_position.rs +++ b/trin-execution/src/storage/execution_position.rs @@ -15,11 +15,8 @@ pub struct ExecutionPosition { state_root: B256, - /// The block number we are currently executing - block_execution_number: u64, - - /// The index of the transaction we are currently executing - transaction_index: u64, + /// The next block number to be executed. + next_block_number: u64, } impl ExecutionPosition { @@ -30,9 +27,8 @@ impl ExecutionPosition { } None => Self { version: 0, - block_execution_number: 0, + next_block_number: 0, state_root: keccak256([EMPTY_STRING_CODE]), - transaction_index: 0, }, }) } @@ -41,32 +37,22 @@ impl ExecutionPosition { self.version } - pub fn block_execution_number(&self) -> u64 { - self.block_execution_number + pub fn next_block_number(&self) -> u64 { + self.next_block_number } pub fn state_root(&self) -> B256 { self.state_root } - pub fn transaction_index(&self) -> u64 { - self.transaction_index - } - - pub fn increase_block_execution_number( + pub fn set_next_block_number( &mut self, db: Arc, + block_number: u64, state_root: B256, ) -> anyhow::Result<()> { - self.block_execution_number += 1; + self.next_block_number = block_number; self.state_root = state_root; - self.transaction_index = 0; - db.put(EXECUTION_POSITION_DB_KEY, alloy_rlp::encode(self))?; - Ok(()) - } - - pub fn increase_transaction_index(&mut self, db: Arc) -> anyhow::Result<()> { - self.transaction_index += 1; db.put(EXECUTION_POSITION_DB_KEY, alloy_rlp::encode(self))?; Ok(()) } diff --git a/trin-execution/src/storage/utils.rs b/trin-execution/src/storage/utils.rs index 77a3c436b..44b9652fc 100644 --- a/trin-execution/src/storage/utils.rs +++ b/trin-execution/src/storage/utils.rs @@ -13,8 +13,14 @@ pub fn setup_rocksdb(path: PathBuf) -> anyhow::Result { let rocksdb_path = path.join("rocksdb"); info!(path = %rocksdb_path.display(), "Setting up RocksDB"); + let cache_size = 1024 * 1024 * 1024; // 1GB + let mut db_opts = Options::default(); db_opts.create_if_missing(true); + db_opts.set_write_buffer_size(cache_size / 4); + let mut factory = rocksdb::BlockBasedOptions::default(); + factory.set_block_cache(&rocksdb::Cache::new_lru_cache(cache_size / 2)); + db_opts.set_block_based_table_factory(&factory); // Set the max number of open files to 150. MacOs has a default limit of 256 open files per // process. This limit prevents issues with the OS running out of file descriptors. diff --git a/trin-execution/src/trie_walker.rs b/trin-execution/src/trie_walker.rs index 77117b123..c8a5602f8 100644 --- a/trin-execution/src/trie_walker.rs +++ b/trin-execution/src/trie_walker.rs @@ -170,13 +170,14 @@ mod tests { trie_walker::TrieWalker, }; - #[test_log::test] - fn test_trie_walker_builds_valid_proof() { + #[tokio::test] + async fn test_trie_walker_builds_valid_proof() { let temp_directory = setup_temp_dir().unwrap(); let mut state = State::new( Some(temp_directory.path().to_path_buf()), StateConfig::default(), ) + .await .unwrap(); let RootWithTrieDiff { trie_diff, .. } = state.initialize_genesis().unwrap(); let valid_proof = state diff --git a/trin-execution/tests/content_generation.rs b/trin-execution/tests/content_generation.rs index 6cc7681f2..2f9361f20 100644 --- a/trin-execution/tests/content_generation.rs +++ b/trin-execution/tests/content_generation.rs @@ -11,7 +11,6 @@ use trin_execution::{ create_account_content_key, create_account_content_value, create_contract_content_key, create_contract_content_value, create_storage_content_key, create_storage_content_value, }, - era::manager::EraManager, execution::State, storage::utils::setup_temp_dir, trie_walker::TrieWalker, @@ -37,33 +36,42 @@ async fn test_we_can_generate_content_key_values_up_to_x() -> Result<()> { let temp_directory = setup_temp_dir()?; - let mut era_manager = EraManager::new(0).await?; - let mut state = State::new( Some(temp_directory.path().to_path_buf()), StateConfig { cache_contract_storage_changes: true, block_to_trace: BlockToTrace::None, }, - )?; + ) + .await?; for block_number in 0..=blocks { println!("Starting block: {block_number}"); - let block = era_manager.get_next_block().await?; - ensure!( - block_number == block.header.number, - "Block number doesn't match!" - ); let RootWithTrieDiff { root: root_hash, trie_diff: changed_nodes, } = match block_number == 0 { - true => state - .initialize_genesis() - .map_err(|e| anyhow!("unable to create genesis state: {e}"))?, - false => state.process_block(block)?, + true => { + // initialize genesis state processes this block so we skip it + state.era_manager.lock().await.get_next_block().await?; + state + .initialize_genesis() + .map_err(|e| anyhow!("unable to create genesis state: {e}"))? + } + false => state.process_block(block_number).await?, }; + let block = state + .era_manager + .lock() + .await + .last_fetched_block() + .await? + .clone(); + ensure!( + block_number == block.header.number, + "Block number doesn't match!" + ); ensure!( state.get_root()? == block.header.state_root, "State root doesn't match" From d1789a43fe1e02a11889bd45c6fcbc950f8aa202 Mon Sep 17 00:00:00 2001 From: Kolby Moroz Liebl <31669092+KolbyML@users.noreply.github.com> Date: Sun, 1 Sep 2024 21:51:36 -0600 Subject: [PATCH 02/11] fix: some concerns --- trin-execution/src/execution.rs | 12 +- trin-execution/src/storage/evm_db.rs | 173 ++++++++++++++------------- 2 files changed, 101 insertions(+), 84 deletions(-) diff --git a/trin-execution/src/execution.rs b/trin-execution/src/execution.rs index 71819eda3..bb8c92ebf 100644 --- a/trin-execution/src/execution.rs +++ b/trin-execution/src/execution.rs @@ -193,7 +193,7 @@ impl State { // Commit the bundle if we have reached the limits, to prevent to much memory usage // We won't use this during the dos attack to avoid writing empty accounts to disk - if !(block_number < 2_700_000 && block_number > 2_200_000) + if !(2_200_000..2_700_000).contains(&block_number) && should_we_commit_block_execution_early( block_number - start, evm.context.evm.db.bundle_size_hint() as u64, @@ -431,14 +431,20 @@ impl State { } } +/// This function is used to determine if we should commit the block execution early. +/// We want this for a few reasons +/// - To prevent memory usage from getting too high +/// - To cap the amount of time it takes to commit everything to the database, the bigger the +/// changes the more time it takes The various limits are arbitrary and can be adjusted as needed, +/// but are based on the current state of the network and what we have seen so far pub fn should_we_commit_block_execution_early( blocks_processed: u64, - changes_processed: u64, + pending_cache_size_mb: u64, cumulative_gas_used: u64, elapsed: Duration, ) -> bool { blocks_processed >= 500_000 - || changes_processed >= 5_000_000 + || pending_cache_size_mb >= 5_000_000 || cumulative_gas_used >= 30_000_000 * 50_000 || elapsed >= Duration::from_secs(30 * 60) } diff --git a/trin-execution/src/storage/evm_db.rs b/trin-execution/src/storage/evm_db.rs index 2c414f41a..e9f19e843 100644 --- a/trin-execution/src/storage/evm_db.rs +++ b/trin-execution/src/storage/evm_db.rs @@ -92,69 +92,82 @@ impl EvmDB { trie_diff } - fn commit_accounts( + fn commit_account( &mut self, - plain_account: Vec<(Address, Option)>, + address_hash: B256, + account_info: AccountInfo, ) -> anyhow::Result<()> { - for (address, account) in plain_account { - let address_hash = keccak256(address); - if let Some(account_info) = account { - let plain_state_some_account_timer = start_timer_vec( - &BUNDLE_COMMIT_PROCESSING_TIMES, - &["account:plain_state_some_account"], - ); + let plain_state_some_account_timer = start_timer_vec( + &BUNDLE_COMMIT_PROCESSING_TIMES, + &["account:plain_state_some_account"], + ); - let mut rocks_account: RocksAccount = account_info.into(); + let mut rocks_account: RocksAccount = account_info.into(); - let timer = start_timer_vec( - &BUNDLE_COMMIT_PROCESSING_TIMES, - &["account:fetch_account_from_db"], - ); - let raw_account = self.db.get(address_hash)?.unwrap_or_default(); - stop_timer(timer); + let timer = start_timer_vec( + &BUNDLE_COMMIT_PROCESSING_TIMES, + &["account:fetch_account_from_db"], + ); + let raw_account = self.db.get(address_hash)?.unwrap_or_default(); + stop_timer(timer); - if !raw_account.is_empty() { - let decoded_account: RocksAccount = - Decodable::decode(&mut raw_account.as_slice())?; - rocks_account.storage_root = decoded_account.storage_root; - } + if !raw_account.is_empty() { + let decoded_account = RocksAccount::decode(&mut raw_account.as_slice())?; + rocks_account.storage_root = decoded_account.storage_root; + } - let timer = start_timer_vec( - &BUNDLE_COMMIT_PROCESSING_TIMES, - &["account:insert_into_trie"], - ); - let _ = self.trie.lock().insert( - address_hash.as_ref(), - &alloy_rlp::encode(AccountStateInfo::from(&rocks_account)), - ); - stop_timer(timer); + let timer = start_timer_vec( + &BUNDLE_COMMIT_PROCESSING_TIMES, + &["account:insert_into_trie"], + ); + let _ = self.trie.lock().insert( + address_hash.as_ref(), + &alloy_rlp::encode(AccountStateInfo::from(&rocks_account)), + ); + stop_timer(timer); - let timer = start_timer_vec( - &BUNDLE_COMMIT_PROCESSING_TIMES, - &["account:put_account_into_db"], - ); - self.db - .put(address_hash, alloy_rlp::encode(rocks_account))?; - stop_timer(timer); + let timer = start_timer_vec( + &BUNDLE_COMMIT_PROCESSING_TIMES, + &["account:put_account_into_db"], + ); + self.db + .put(address_hash, alloy_rlp::encode(rocks_account))?; + stop_timer(timer); - stop_timer(plain_state_some_account_timer); - } else if self.db.get(address_hash)?.is_some() { - let timer = - start_timer_vec(&BUNDLE_COMMIT_PROCESSING_TIMES, &["account:delete_account"]); - let rocks_account: RocksAccount = match self.db.get(address_hash)? { - Some(raw_account) => Decodable::decode(&mut raw_account.as_slice())?, - None => RocksAccount::default(), - }; - if rocks_account.storage_root != keccak256([EMPTY_STRING_CODE]) { - let account_db = AccountDB::new(address_hash, self.db.clone()); - let mut trie = EthTrie::from(Arc::new(account_db), rocks_account.storage_root)?; - trie.clear_trie_from_db()?; - } - self.db.delete(address_hash)?; + stop_timer(plain_state_some_account_timer); + Ok(()) + } - // update trie - let _ = self.trie.lock().remove(address_hash.as_ref()); - stop_timer(timer); + fn delete_account_and_storage( + &mut self, + address_hash: B256, + raw_account: Vec, + ) -> anyhow::Result<()> { + let timer = start_timer_vec(&BUNDLE_COMMIT_PROCESSING_TIMES, &["account:delete_account"]); + let rocks_account = RocksAccount::decode(&mut raw_account.as_slice())?; + if rocks_account.storage_root != keccak256([EMPTY_STRING_CODE]) { + let account_db = AccountDB::new(address_hash, self.db.clone()); + let mut trie = EthTrie::from(Arc::new(account_db), rocks_account.storage_root)?; + trie.clear_trie_from_db()?; + } + self.db.delete(address_hash)?; + + // update trie + let _ = self.trie.lock().remove(address_hash.as_ref()); + stop_timer(timer); + Ok(()) + } + + fn commit_accounts( + &mut self, + plain_account: Vec<(Address, Option)>, + ) -> anyhow::Result<()> { + for (address, account) in plain_account { + let address_hash = keccak256(address); + if let Some(account_info) = account { + self.commit_account(address_hash, account_info)?; + } else if let Some(raw_account) = self.db.get(address_hash)? { + self.delete_account_and_storage(address_hash, raw_account)?; } } Ok(()) @@ -228,14 +241,11 @@ impl EvmDB { }; for (key, value) in storage { - let key = B256::from(key); - trie.remove(keccak256(B256::from(key)).as_ref())?; - - if value != U256::ZERO { - trie.insert( - keccak256(B256::from(key)).as_ref(), - &alloy_rlp::encode(value), - )?; + let trie_key = keccak256(B256::from(key)); + if value.is_zero() { + trie.remove(trie_key.as_ref())?; + } else { + trie.insert(trie_key.as_ref(), &alloy_rlp::encode(value))?; } } @@ -322,22 +332,18 @@ impl EvmDB { impl Database for EvmDB { type Error = EVMError; - #[doc = " Get basic account information."] fn basic(&mut self, address: Address) -> Result, Self::Error> { DatabaseRef::basic_ref(&self, address) } - #[doc = " Get account code by its hash."] fn code_by_hash(&mut self, code_hash: B256) -> Result { DatabaseRef::code_by_hash_ref(&self, code_hash) } - #[doc = " Get storage value of address at index."] fn storage(&mut self, address: Address, index: U256) -> Result { DatabaseRef::storage_ref(&self, address, index) } - #[doc = " Get block hash by block number."] fn block_hash(&mut self, number: u64) -> Result { DatabaseRef::block_hash_ref(&self, number) } @@ -346,10 +352,9 @@ impl Database for EvmDB { impl DatabaseRef for EvmDB { type Error = EVMError; - #[doc = " Get basic account information."] fn basic_ref(&self, address: Address) -> Result, Self::Error> { - let _timer = start_timer_vec(&TRANSACTION_PROCESSING_TIMES, &["database_get_basic"]); - match self.db.get(keccak256(address))? { + let timer = start_timer_vec(&TRANSACTION_PROCESSING_TIMES, &["database_get_basic"]); + let result = match self.db.get(keccak256(address))? { Some(raw_account) => { let account: RocksAccount = Decodable::decode(&mut raw_account.as_slice())?; @@ -361,24 +366,26 @@ impl DatabaseRef for EvmDB { })) } None => Ok(None), - } + }; + stop_timer(timer); + result } - #[doc = " Get account code by its hash."] fn code_by_hash_ref(&self, code_hash: B256) -> Result { - let _timer = start_timer_vec( + let timer = start_timer_vec( &TRANSACTION_PROCESSING_TIMES, &["database_get_code_by_hash"], ); - match self.db.get(code_hash)? { + let result = match self.db.get(code_hash)? { Some(raw_code) => Ok(Bytecode::new_raw(raw_code.into())), None => Err(Self::Error::NotFound("code_by_hash".to_string())), - } + }; + stop_timer(timer); + result } - #[doc = " Get storage value of address at index."] fn storage_ref(&self, address: Address, index: U256) -> Result { - let _timer = start_timer_vec(&TRANSACTION_PROCESSING_TIMES, &["database_get_storage"]); + let timer = start_timer_vec(&TRANSACTION_PROCESSING_TIMES, &["database_get_storage"]); let address_hash = keccak256(address); let account: RocksAccount = match self.db.get(address_hash)? { Some(raw_account) => Decodable::decode(&mut raw_account.as_slice())?, @@ -390,17 +397,21 @@ impl DatabaseRef for EvmDB { } else { EthTrie::from(Arc::new(account_db), account.storage_root)? }; - match trie.get(keccak256(B256::from(index)).as_ref())? { + let result = match trie.get(keccak256(B256::from(index)).as_ref())? { Some(raw_value) => Ok(Decodable::decode(&mut raw_value.as_slice())?), None => Ok(U256::ZERO), - } + }; + stop_timer(timer); + result } fn block_hash_ref(&self, number: u64) -> Result { - let _timer = start_timer_vec(&TRANSACTION_PROCESSING_TIMES, &["database_get_block_hash"]); - match self.db.get(keccak256(B256::from(U256::from(number))))? { + let timer = start_timer_vec(&TRANSACTION_PROCESSING_TIMES, &["database_get_block_hash"]); + let result = match self.db.get(keccak256(B256::from(U256::from(number))))? { Some(raw_hash) => Ok(B256::from_slice(&raw_hash)), None => Err(Self::Error::NotFound("block_hash".to_string())), - } + }; + stop_timer(timer); + result } } From e7759293047fd962aea377e68a524ac02d152902 Mon Sep 17 00:00:00 2001 From: Kolby Moroz Liebl <31669092+KolbyML@users.noreply.github.com> Date: Sun, 1 Sep 2024 22:50:50 -0600 Subject: [PATCH 03/11] fix: fix pr concerns --- trin-execution/src/main.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/trin-execution/src/main.rs b/trin-execution/src/main.rs index 08809adaf..fd3760e27 100644 --- a/trin-execution/src/main.rs +++ b/trin-execution/src/main.rs @@ -53,10 +53,6 @@ async fn main() -> Result<(), Box> { break; } - let end = - ((block_number / (BLOCK_TUPLE_COUNT as u64)) + 86) * (BLOCK_TUPLE_COUNT as u64) - 1; - let end = std::cmp::min(end, end_block); - if block_number == 0 { // initialize genesis state processes this block so we skip it state.era_manager.lock().await.get_next_block().await?; @@ -64,7 +60,9 @@ async fn main() -> Result<(), Box> { block_number = 1; continue; } - state.process_range_of_blocks(block_number, end).await?; + state + .process_range_of_blocks(block_number, end_block) + .await?; block_number = state.next_block_number(); } From 0e05cef1248c5171b3780536a7c0b8f396f5e076 Mon Sep 17 00:00:00 2001 From: Kolby Moroz Liebl <31669092+KolbyML@users.noreply.github.com> Date: Mon, 2 Sep 2024 00:54:01 -0600 Subject: [PATCH 04/11] fix: pr concerns --- trin-execution/src/execution.rs | 5 +++-- trin-execution/src/main.rs | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/trin-execution/src/execution.rs b/trin-execution/src/execution.rs index bb8c92ebf..ee19cc675 100644 --- a/trin-execution/src/execution.rs +++ b/trin-execution/src/execution.rs @@ -70,6 +70,7 @@ pub struct State { const GENESIS_STATE_FILE: &str = "trin-execution/resources/genesis/mainnet.json"; const TEST_GENESIS_STATE_FILE: &str = "resources/genesis/mainnet.json"; +const BLOCKHASH_SERVE_WINDOW: u64 = 256; impl State { pub async fn new(path: Option, config: StateConfig) -> anyhow::Result { @@ -180,9 +181,9 @@ impl State { keccak256(B256::from(U256::from(block.header.number))), block.header.hash(), )?; - if block.header.number >= 8192 { + if block.header.number >= BLOCKHASH_SERVE_WINDOW { self.database.db.delete(keccak256(B256::from(U256::from( - block.header.number - 8192, + block.header.number - BLOCKHASH_SERVE_WINDOW, ))))?; } stop_timer(timer); diff --git a/trin-execution/src/main.rs b/trin-execution/src/main.rs index fd3760e27..5ad3370bd 100644 --- a/trin-execution/src/main.rs +++ b/trin-execution/src/main.rs @@ -1,6 +1,5 @@ use clap::Parser; -use e2store::era1::BLOCK_TUPLE_COUNT; use revm_primitives::SpecId; use tracing::info; use trin_execution::{ From d65163b497fafa257b38b225c8f7d65987e5c17b Mon Sep 17 00:00:00 2001 From: Kolby Moroz Liebl <31669092+KolbyML@users.noreply.github.com> Date: Thu, 5 Sep 2024 03:46:32 -0600 Subject: [PATCH 05/11] fix: pr concerns --- trin-execution/src/execution.rs | 4 +- trin-execution/src/storage/evm_db.rs | 173 ++++++++++++++------------- 2 files changed, 92 insertions(+), 85 deletions(-) diff --git a/trin-execution/src/execution.rs b/trin-execution/src/execution.rs index ee19cc675..f349b2d74 100644 --- a/trin-execution/src/execution.rs +++ b/trin-execution/src/execution.rs @@ -440,12 +440,12 @@ impl State { /// but are based on the current state of the network and what we have seen so far pub fn should_we_commit_block_execution_early( blocks_processed: u64, - pending_cache_size_mb: u64, + pending_state_changes: u64, cumulative_gas_used: u64, elapsed: Duration, ) -> bool { blocks_processed >= 500_000 - || pending_cache_size_mb >= 5_000_000 + || pending_state_changes >= 5_000_000 || cumulative_gas_used >= 30_000_000 * 50_000 || elapsed >= Duration::from_secs(30 * 60) } diff --git a/trin-execution/src/storage/evm_db.rs b/trin-execution/src/storage/evm_db.rs index e9f19e843..68209738d 100644 --- a/trin-execution/src/storage/evm_db.rs +++ b/trin-execution/src/storage/evm_db.rs @@ -138,24 +138,44 @@ impl EvmDB { Ok(()) } - fn delete_account_and_storage( + fn delete_account_storage( &mut self, address_hash: B256, - raw_account: Vec, - ) -> anyhow::Result<()> { - let timer = start_timer_vec(&BUNDLE_COMMIT_PROCESSING_TIMES, &["account:delete_account"]); - let rocks_account = RocksAccount::decode(&mut raw_account.as_slice())?; + rocks_account: RocksAccount, + delete_account: bool, + ) -> anyhow::Result> { + let timer_label = match delete_account { + true => "account:delete_account", + false => "storage:wipe_storage", + }; + let timer = start_timer_vec(&BUNDLE_COMMIT_PROCESSING_TIMES, &[timer_label]); if rocks_account.storage_root != keccak256([EMPTY_STRING_CODE]) { let account_db = AccountDB::new(address_hash, self.db.clone()); let mut trie = EthTrie::from(Arc::new(account_db), rocks_account.storage_root)?; trie.clear_trie_from_db()?; } - self.db.delete(address_hash)?; + let rocks_account = if delete_account { + self.db.delete(address_hash)?; - // update trie - let _ = self.trie.lock().remove(address_hash.as_ref()); + // update trie + let _ = self.trie.lock().remove(address_hash.as_ref()); + + None + } else { + let mut rocks_account = rocks_account; + rocks_account.storage_root = keccak256([EMPTY_STRING_CODE]); + let _ = self.trie.lock().insert( + address_hash.as_ref(), + &alloy_rlp::encode(AccountStateInfo::from(&rocks_account)), + ); + self.db + .put(address_hash, &alloy_rlp::encode(&rocks_account)) + .expect("Inserting account should never fail"); + + Some(rocks_account) + }; stop_timer(timer); - Ok(()) + Ok(rocks_account) } fn commit_accounts( @@ -167,9 +187,62 @@ impl EvmDB { if let Some(account_info) = account { self.commit_account(address_hash, account_info)?; } else if let Some(raw_account) = self.db.get(address_hash)? { - self.delete_account_and_storage(address_hash, raw_account)?; + let rocks_account = RocksAccount::decode(&mut raw_account.as_slice())?; + self.delete_account_storage(address_hash, rocks_account, true)?; + } + } + Ok(()) + } + + fn commit_storage_changes( + &mut self, + address_hash: B256, + rocks_account: Option, + storage: Vec<(U256, U256)>, + ) -> anyhow::Result<()> { + let timer = start_timer_vec(&BUNDLE_COMMIT_PROCESSING_TIMES, &["storage:apply_updates"]); + + let account_db = AccountDB::new(address_hash, self.db.clone()); + let mut rocks_account = rocks_account.unwrap_or_default(); + + let mut trie = if rocks_account.storage_root == keccak256([EMPTY_STRING_CODE]) { + EthTrie::new(Arc::new(account_db)) + } else { + EthTrie::from(Arc::new(account_db), rocks_account.storage_root)? + }; + + for (key, value) in storage { + let trie_key = keccak256(B256::from(key)); + if value.is_zero() { + trie.remove(trie_key.as_ref())?; + } else { + trie.insert(trie_key.as_ref(), &alloy_rlp::encode(value))?; } } + + // update trie + let RootWithTrieDiff { + root: storage_root, + trie_diff, + } = trie.root_hash_with_changed_nodes()?; + + if self.config.cache_contract_storage_changes { + let account_storage_cache = self.storage_cache.entry(address_hash).or_default(); + for key in trie_diff.keys() { + account_storage_cache.insert(*key); + } + } + + rocks_account.storage_root = storage_root; + + let _ = self.trie.lock().insert( + address_hash.as_ref(), + &alloy_rlp::encode(AccountStateInfo::from(&rocks_account)), + ); + + self.db + .put(address_hash, alloy_rlp::encode(rocks_account))?; + stop_timer(timer); Ok(()) } @@ -197,81 +270,15 @@ impl EvmDB { }); stop_timer(timer); - if wipe_storage && rocks_account.is_some() { - let timer = - start_timer_vec(&BUNDLE_COMMIT_PROCESSING_TIMES, &["storage:wipe_storage"]); - - let account_db = AccountDB::new(address_hash, self.db.clone()); - let mut rocks_account = rocks_account.expect("We already checked that it is some"); - if rocks_account.storage_root != keccak256([EMPTY_STRING_CODE]) { - let mut trie = EthTrie::from(Arc::new(account_db), rocks_account.storage_root)?; - trie.clear_trie_from_db()?; - rocks_account.storage_root = keccak256([EMPTY_STRING_CODE]); - let _ = self.trie.lock().insert( - address_hash.as_ref(), - &alloy_rlp::encode(AccountStateInfo::from(&rocks_account)), - ); - self.db - .put(address_hash, alloy_rlp::encode(rocks_account)) - .expect("Inserting account should never fail"); - } - stop_timer(timer); - } + let rocks_account = if wipe_storage && rocks_account.is_some() { + let rocks_account = rocks_account.expect("We already checked that it is some"); + self.delete_account_storage(address_hash, rocks_account, false)? + } else { + rocks_account + }; if !storage.is_empty() { - let timer = - start_timer_vec(&BUNDLE_COMMIT_PROCESSING_TIMES, &["storage:apply_updates"]); - - let account_db = AccountDB::new(address_hash, self.db.clone()); - let mut rocks_account: RocksAccount = self - .db - .get(address_hash)? - .map(|raw_account| { - let rocks_account: RocksAccount = - Decodable::decode(&mut raw_account.as_slice()) - .expect("Decoding account should never fail"); - rocks_account - }) - .unwrap_or_default(); - - let mut trie = if rocks_account.storage_root == keccak256([EMPTY_STRING_CODE]) { - EthTrie::new(Arc::new(account_db)) - } else { - EthTrie::from(Arc::new(account_db), rocks_account.storage_root)? - }; - - for (key, value) in storage { - let trie_key = keccak256(B256::from(key)); - if value.is_zero() { - trie.remove(trie_key.as_ref())?; - } else { - trie.insert(trie_key.as_ref(), &alloy_rlp::encode(value))?; - } - } - - // update trie - let RootWithTrieDiff { - root: storage_root, - trie_diff, - } = trie.root_hash_with_changed_nodes()?; - - if self.config.cache_contract_storage_changes { - let account_storage_cache = self.storage_cache.entry(address_hash).or_default(); - for key in trie_diff.keys() { - account_storage_cache.insert(*key); - } - } - - rocks_account.storage_root = storage_root; - - let _ = self.trie.lock().insert( - address_hash.as_ref(), - &alloy_rlp::encode(AccountStateInfo::from(&rocks_account)), - ); - - self.db - .put(address_hash, alloy_rlp::encode(rocks_account))?; - stop_timer(timer); + self.commit_storage_changes(address_hash, rocks_account, storage)?; } stop_timer(plain_state_storage_timer); } From 7892d5a2a2cab0b87e9d8a41ea2bac0084aa9903 Mon Sep 17 00:00:00 2001 From: Kolby Moroz Liebl <31669092+KolbyML@users.noreply.github.com> Date: Thu, 5 Sep 2024 04:25:42 -0600 Subject: [PATCH 06/11] fix: resolve some more PR concerns --- Cargo.lock | 1 + ethportal-api/Cargo.toml | 1 + .../src/types/state_trie/account_state.rs | 9 +- trin-execution/src/storage/account.rs | 19 ++-- trin-execution/src/storage/evm_db.rs | 101 +++++++----------- .../src/storage/execution_position.rs | 7 +- trin-execution/src/trie_walker.rs | 5 +- 7 files changed, 62 insertions(+), 81 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0d0661024..2282ebbca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2858,6 +2858,7 @@ dependencies = [ name = "ethportal-api" version = "0.2.2" dependencies = [ + "alloy-consensus", "alloy-primitives", "alloy-rlp", "anyhow", diff --git a/ethportal-api/Cargo.toml b/ethportal-api/Cargo.toml index f00df6a1c..c04727ba3 100644 --- a/ethportal-api/Cargo.toml +++ b/ethportal-api/Cargo.toml @@ -11,6 +11,7 @@ categories = ["cryptography::cryptocurrencies"] authors = ["https://github.com/ethereum/trin/graphs/contributors"] [dependencies] +alloy-consensus.workspace = true alloy-primitives.workspace = true alloy-rlp.workspace = true anyhow.workspace = true diff --git a/ethportal-api/src/types/state_trie/account_state.rs b/ethportal-api/src/types/state_trie/account_state.rs index 1a2a7ea36..b89ff58dc 100644 --- a/ethportal-api/src/types/state_trie/account_state.rs +++ b/ethportal-api/src/types/state_trie/account_state.rs @@ -1,5 +1,6 @@ -use alloy_primitives::{keccak256, B256, U256}; -use alloy_rlp::{RlpDecodable, RlpEncodable, EMPTY_STRING_CODE}; +use alloy_consensus::{constants::KECCAK_EMPTY, EMPTY_ROOT_HASH}; +use alloy_primitives::{B256, U256}; +use alloy_rlp::{RlpDecodable, RlpEncodable}; use serde::{Deserialize, Serialize}; /// The Account State stored in the state trie. @@ -16,8 +17,8 @@ impl Default for AccountState { Self { nonce: 0, balance: U256::ZERO, - storage_root: keccak256([EMPTY_STRING_CODE]), - code_hash: keccak256([]), + storage_root: EMPTY_ROOT_HASH, + code_hash: KECCAK_EMPTY, } } } diff --git a/trin-execution/src/storage/account.rs b/trin-execution/src/storage/account.rs index d1ca9369a..e90a1bd74 100644 --- a/trin-execution/src/storage/account.rs +++ b/trin-execution/src/storage/account.rs @@ -1,5 +1,6 @@ -use alloy_primitives::{keccak256, B256, U256}; -use alloy_rlp::{RlpDecodable, RlpEncodable, EMPTY_STRING_CODE}; +use alloy_consensus::EMPTY_ROOT_HASH; +use alloy_primitives::{B256, U256}; +use alloy_rlp::{RlpDecodable, RlpEncodable}; use ethportal_api::types::state_trie::account_state::AccountState as AccountStateInfo; use revm_primitives::{AccountInfo, KECCAK_EMPTY}; @@ -17,7 +18,7 @@ impl Default for Account { Self { nonce: 0, balance: U256::ZERO, - storage_root: keccak256([EMPTY_STRING_CODE]), + storage_root: EMPTY_ROOT_HASH, code_hash: KECCAK_EMPTY, } } @@ -34,13 +35,13 @@ impl From<&Account> for AccountStateInfo { } } -impl From for Account { - fn from(account_info: AccountInfo) -> Self { +impl Account { + pub fn from_account_info(account: AccountInfo, storage_root: B256) -> Self { Self { - nonce: account_info.nonce, - balance: account_info.balance, - storage_root: keccak256([EMPTY_STRING_CODE]), - code_hash: account_info.code_hash, + nonce: account.nonce, + balance: account.balance, + storage_root, + code_hash: account.code_hash, } } } diff --git a/trin-execution/src/storage/evm_db.rs b/trin-execution/src/storage/evm_db.rs index 68209738d..eeb912e08 100644 --- a/trin-execution/src/storage/evm_db.rs +++ b/trin-execution/src/storage/evm_db.rs @@ -7,12 +7,14 @@ use crate::{ }, storage::error::EVMError, }; +use alloy_consensus::EMPTY_ROOT_HASH; use alloy_primitives::{Address, B256, U256}; -use alloy_rlp::{Decodable, EMPTY_STRING_CODE}; +use alloy_rlp::Decodable; use eth_trie::{EthTrie, RootWithTrieDiff, Trie}; use ethportal_api::types::state_trie::account_state::AccountState as AccountStateInfo; use hashbrown::{HashMap as BrownHashMap, HashSet}; use parking_lot::Mutex; +use prometheus_exporter::prometheus::HistogramTimer; use revm::{ db::{states::PlainStorageChangeset, BundleState, OriginalValuesKnown}, Database, DatabaseRef, @@ -26,6 +28,14 @@ use super::{ trie_db::TrieRocksDB, }; +fn start_commit_timer(name: &str) -> HistogramTimer { + start_timer_vec(&BUNDLE_COMMIT_PROCESSING_TIMES, &[name]) +} + +fn start_processing_timer(name: &str) -> HistogramTimer { + start_timer_vec(&TRANSACTION_PROCESSING_TIMES, &[name]) +} + #[derive(Debug, Clone)] pub struct EvmDB { /// State config @@ -48,7 +58,7 @@ impl EvmDB { db.put(B256::ZERO, Bytecode::new().bytes().as_ref())?; let trie = Arc::new(Mutex::new( - if execution_position.state_root() == keccak256([EMPTY_STRING_CODE]) { + if execution_position.state_root() == EMPTY_ROOT_HASH { EthTrie::new(Arc::new(TrieRocksDB::new(false, db.clone()))) } else { EthTrie::from( @@ -97,39 +107,27 @@ impl EvmDB { address_hash: B256, account_info: AccountInfo, ) -> anyhow::Result<()> { - let plain_state_some_account_timer = start_timer_vec( - &BUNDLE_COMMIT_PROCESSING_TIMES, - &["account:plain_state_some_account"], - ); + let plain_state_some_account_timer = start_commit_timer("account:plain_state_some_account"); - let mut rocks_account: RocksAccount = account_info.into(); - - let timer = start_timer_vec( - &BUNDLE_COMMIT_PROCESSING_TIMES, - &["account:fetch_account_from_db"], - ); + let timer = start_commit_timer("account:fetch_account_from_db"); let raw_account = self.db.get(address_hash)?.unwrap_or_default(); stop_timer(timer); - if !raw_account.is_empty() { + let rocks_account = if !raw_account.is_empty() { let decoded_account = RocksAccount::decode(&mut raw_account.as_slice())?; - rocks_account.storage_root = decoded_account.storage_root; - } + RocksAccount::from_account_info(account_info, decoded_account.storage_root) + } else { + RocksAccount::from_account_info(account_info, EMPTY_ROOT_HASH) + }; - let timer = start_timer_vec( - &BUNDLE_COMMIT_PROCESSING_TIMES, - &["account:insert_into_trie"], - ); + let timer = start_commit_timer("account:insert_into_trie"); let _ = self.trie.lock().insert( address_hash.as_ref(), &alloy_rlp::encode(AccountStateInfo::from(&rocks_account)), ); stop_timer(timer); - let timer = start_timer_vec( - &BUNDLE_COMMIT_PROCESSING_TIMES, - &["account:put_account_into_db"], - ); + let timer = start_commit_timer("account:put_account_into_db"); self.db .put(address_hash, alloy_rlp::encode(rocks_account))?; stop_timer(timer); @@ -148,8 +146,8 @@ impl EvmDB { true => "account:delete_account", false => "storage:wipe_storage", }; - let timer = start_timer_vec(&BUNDLE_COMMIT_PROCESSING_TIMES, &[timer_label]); - if rocks_account.storage_root != keccak256([EMPTY_STRING_CODE]) { + let timer = start_commit_timer(timer_label); + if rocks_account.storage_root != EMPTY_ROOT_HASH { let account_db = AccountDB::new(address_hash, self.db.clone()); let mut trie = EthTrie::from(Arc::new(account_db), rocks_account.storage_root)?; trie.clear_trie_from_db()?; @@ -163,13 +161,13 @@ impl EvmDB { None } else { let mut rocks_account = rocks_account; - rocks_account.storage_root = keccak256([EMPTY_STRING_CODE]); + rocks_account.storage_root = EMPTY_ROOT_HASH; let _ = self.trie.lock().insert( address_hash.as_ref(), &alloy_rlp::encode(AccountStateInfo::from(&rocks_account)), ); self.db - .put(address_hash, &alloy_rlp::encode(&rocks_account)) + .put(address_hash, alloy_rlp::encode(&rocks_account)) .expect("Inserting account should never fail"); Some(rocks_account) @@ -200,12 +198,12 @@ impl EvmDB { rocks_account: Option, storage: Vec<(U256, U256)>, ) -> anyhow::Result<()> { - let timer = start_timer_vec(&BUNDLE_COMMIT_PROCESSING_TIMES, &["storage:apply_updates"]); + let timer = start_commit_timer("storage:apply_updates"); let account_db = AccountDB::new(address_hash, self.db.clone()); let mut rocks_account = rocks_account.unwrap_or_default(); - let mut trie = if rocks_account.storage_root == keccak256([EMPTY_STRING_CODE]) { + let mut trie = if rocks_account.storage_root == EMPTY_ROOT_HASH { EthTrie::new(Arc::new(account_db)) } else { EthTrie::from(Arc::new(account_db), rocks_account.storage_root)? @@ -253,15 +251,9 @@ impl EvmDB { storage, } in plain_storage { - let plain_state_storage_timer = start_timer_vec( - &BUNDLE_COMMIT_PROCESSING_TIMES, - &["storage:plain_state_storage"], - ); + let plain_state_storage_timer = start_commit_timer("storage:plain_state_storage"); - let timer = start_timer_vec( - &BUNDLE_COMMIT_PROCESSING_TIMES, - &["storage:fetch_account_from_db"], - ); + let timer = start_commit_timer("storage:fetch_account_from_db"); let address_hash = keccak256(address); let rocks_account: Option = self.db.get(address_hash)?.map(|raw_account| { @@ -287,10 +279,7 @@ impl EvmDB { pub fn commit_bundle(&mut self, bundle_state: BundleState) -> anyhow::Result<()> { // Currently we don't use reverts, so we can ignore them, but they are here for when we do. - let timer = start_timer_vec( - &BUNDLE_COMMIT_PROCESSING_TIMES, - &["generate_plain_state_and_reverts"], - ); + let timer = start_commit_timer("generate_plain_state_and_reverts"); let (plain_state, _reverts) = bundle_state.into_plain_state_and_reverts(OriginalValuesKnown::Yes); stop_timer(timer); @@ -303,20 +292,14 @@ impl EvmDB { ); // Write Account State - let timer = start_timer_vec( - &BUNDLE_COMMIT_PROCESSING_TIMES, - &["account:committing_accounts_total"], - ); + let timer = start_commit_timer("account:committing_accounts_total"); self.commit_accounts(plain_state.accounts)?; stop_timer(timer); // Write Contract Code - let timer = start_timer_vec( - &BUNDLE_COMMIT_PROCESSING_TIMES, - &["contract:committing_contracts_total"], - ); + let timer = start_commit_timer("contract:committing_contracts_total"); for (hash, bytecode) in plain_state.contracts { - let timer = start_timer_vec(&BUNDLE_COMMIT_PROCESSING_TIMES, &["committing_contract"]); + let timer = start_commit_timer("committing_contract"); self.db .put(hash, bytecode.original_bytes().as_ref()) .expect("Inserting contract code should never fail"); @@ -325,10 +308,7 @@ impl EvmDB { stop_timer(timer); // Write Storage - let timer = start_timer_vec( - &BUNDLE_COMMIT_PROCESSING_TIMES, - &["storage:committing_storage_total"], - ); + let timer = start_commit_timer("storage:committing_storage_total"); self.commit_storage(plain_state.storage)?; stop_timer(timer); @@ -360,7 +340,7 @@ impl DatabaseRef for EvmDB { type Error = EVMError; fn basic_ref(&self, address: Address) -> Result, Self::Error> { - let timer = start_timer_vec(&TRANSACTION_PROCESSING_TIMES, &["database_get_basic"]); + let timer = start_processing_timer("database_get_basic"); let result = match self.db.get(keccak256(address))? { Some(raw_account) => { let account: RocksAccount = Decodable::decode(&mut raw_account.as_slice())?; @@ -379,10 +359,7 @@ impl DatabaseRef for EvmDB { } fn code_by_hash_ref(&self, code_hash: B256) -> Result { - let timer = start_timer_vec( - &TRANSACTION_PROCESSING_TIMES, - &["database_get_code_by_hash"], - ); + let timer = start_processing_timer("database_get_code_by_hash"); let result = match self.db.get(code_hash)? { Some(raw_code) => Ok(Bytecode::new_raw(raw_code.into())), None => Err(Self::Error::NotFound("code_by_hash".to_string())), @@ -392,14 +369,14 @@ impl DatabaseRef for EvmDB { } fn storage_ref(&self, address: Address, index: U256) -> Result { - let timer = start_timer_vec(&TRANSACTION_PROCESSING_TIMES, &["database_get_storage"]); + let timer = start_processing_timer("database_get_storage"); let address_hash = keccak256(address); let account: RocksAccount = match self.db.get(address_hash)? { Some(raw_account) => Decodable::decode(&mut raw_account.as_slice())?, None => return Err(Self::Error::NotFound("storage".to_string())), }; let account_db = AccountDB::new(address_hash, self.db.clone()); - let trie = if account.storage_root == keccak256([EMPTY_STRING_CODE]) { + let trie = if account.storage_root == EMPTY_ROOT_HASH { EthTrie::new(Arc::new(account_db)) } else { EthTrie::from(Arc::new(account_db), account.storage_root)? @@ -413,7 +390,7 @@ impl DatabaseRef for EvmDB { } fn block_hash_ref(&self, number: u64) -> Result { - let timer = start_timer_vec(&TRANSACTION_PROCESSING_TIMES, &["database_get_block_hash"]); + let timer = start_processing_timer("database_get_block_hash"); let result = match self.db.get(keccak256(B256::from(U256::from(number))))? { Some(raw_hash) => Ok(B256::from_slice(&raw_hash)), None => Err(Self::Error::NotFound("block_hash".to_string())), diff --git a/trin-execution/src/storage/execution_position.rs b/trin-execution/src/storage/execution_position.rs index e8dde4283..58e53195a 100644 --- a/trin-execution/src/storage/execution_position.rs +++ b/trin-execution/src/storage/execution_position.rs @@ -1,7 +1,8 @@ use std::sync::Arc; -use alloy_rlp::{Decodable, RlpDecodable, RlpEncodable, EMPTY_STRING_CODE}; -use revm_primitives::{keccak256, B256}; +use alloy_consensus::EMPTY_ROOT_HASH; +use alloy_rlp::{Decodable, RlpDecodable, RlpEncodable}; +use revm_primitives::B256; use rocksdb::DB as RocksDB; use serde::{Deserialize, Serialize}; @@ -28,7 +29,7 @@ impl ExecutionPosition { None => Self { version: 0, next_block_number: 0, - state_root: keccak256([EMPTY_STRING_CODE]), + state_root: EMPTY_ROOT_HASH, }, }) } diff --git a/trin-execution/src/trie_walker.rs b/trin-execution/src/trie_walker.rs index c8a5602f8..13dffecf0 100644 --- a/trin-execution/src/trie_walker.rs +++ b/trin-execution/src/trie_walker.rs @@ -1,10 +1,9 @@ use std::collections::VecDeque; +use alloy_consensus::EMPTY_ROOT_HASH; use alloy_primitives::B256; -use alloy_rlp::EMPTY_STRING_CODE; use eth_trie::{decode_node, node::Node}; use hashbrown::HashMap as BrownHashMap; -use revm_primitives::keccak256; use serde::{Deserialize, Serialize}; use super::types::trie_proof::TrieProof; @@ -38,7 +37,7 @@ pub struct TrieWalker { impl TrieWalker { pub fn new(root_hash: B256, nodes: BrownHashMap>) -> Self { // if the storage root is empty then there is no storage to gossip - if root_hash == keccak256([EMPTY_STRING_CODE]) { + if root_hash == EMPTY_ROOT_HASH { return Self { nodes: BrownHashMap::new(), }; From 6517bad809cd64a072da42ee0639310dde36aa8d Mon Sep 17 00:00:00 2001 From: Kolby Moroz Liebl <31669092+KolbyML@users.noreply.github.com> Date: Fri, 6 Sep 2024 00:55:26 -0600 Subject: [PATCH 07/11] refactor: clean things up so they are more concise --- trin-execution/src/dao_fork.rs | 16 -- trin-execution/src/evm/blocking.rs | 24 ++ trin-execution/src/evm/execution_context.rs | 291 ++++++++++++++++++++ trin-execution/src/evm/mod.rs | 2 + trin-execution/src/execution.rs | 225 ++------------- trin-execution/src/lib.rs | 1 + 6 files changed, 337 insertions(+), 222 deletions(-) create mode 100644 trin-execution/src/evm/blocking.rs create mode 100644 trin-execution/src/evm/execution_context.rs create mode 100644 trin-execution/src/evm/mod.rs diff --git a/trin-execution/src/dao_fork.rs b/trin-execution/src/dao_fork.rs index 558d0d1a7..a2468bcb7 100644 --- a/trin-execution/src/dao_fork.rs +++ b/trin-execution/src/dao_fork.rs @@ -1,11 +1,8 @@ //! DAO Fork related constants from [EIP-779](https://eips.ethereum.org/EIPS/eip-779). //! It happened on Ethereum block 1_920_000 -use revm::{db::State as RevmState, Evm}; use revm_primitives::{address, Address}; -use crate::storage::evm_db::EvmDB; - /// Dao hardfork beneficiary that received ether from accounts from DAO and DAO creator children. pub static DAO_HARDFORK_BENEFICIARY: Address = address!("bf4ed7b27f1d666546e30d74d50d173d20bca754"); @@ -128,16 +125,3 @@ pub static DAO_HARDKFORK_ACCOUNTS: [Address; 116] = [ address!("bb9bc244d798123fde783fcc1c72d3bb8c189413"), address!("807640a13483f8ac783c557fcdf27be11ea4ac7a"), ]; - -pub fn process_dao_fork(database: &mut Evm<(), RevmState>) -> anyhow::Result<()> { - // drain balances from DAO hardfork accounts - let drained_balances = database.db_mut().drain_balances(DAO_HARDKFORK_ACCOUNTS)?; - let drained_balance_sum: u128 = drained_balances.iter().sum(); - - // transfer drained balance to beneficiary - database - .db_mut() - .increment_balances([(DAO_HARDFORK_BENEFICIARY, drained_balance_sum)].into_iter())?; - - Ok(()) -} diff --git a/trin-execution/src/evm/blocking.rs b/trin-execution/src/evm/blocking.rs new file mode 100644 index 000000000..2331b0fdc --- /dev/null +++ b/trin-execution/src/evm/blocking.rs @@ -0,0 +1,24 @@ +use revm::{inspector_handle_register, Database, Evm, GetInspector}; +use revm_primitives::{EVMResult, Env}; + +use crate::spec_id::get_spec_id; + +/// Executes the transaction with external context in the blocking manner. +pub fn execute_transaction_with_external_context< + DB: Database, + EXT: revm::Inspector + for<'a> GetInspector<&'a mut DB>, +>( + evm_environment: Env, + external_context: EXT, + database: &mut DB, +) -> EVMResult { + let block_number = evm_environment.block.number.to::(); + Evm::builder() + .with_env(Box::new(evm_environment)) + .with_spec_id(get_spec_id(block_number)) + .with_db(database) + .with_external_context(external_context) + .append_handler_register(inspector_handle_register) + .build() + .transact() +} diff --git a/trin-execution/src/evm/execution_context.rs b/trin-execution/src/evm/execution_context.rs new file mode 100644 index 000000000..2a4f58635 --- /dev/null +++ b/trin-execution/src/evm/execution_context.rs @@ -0,0 +1,291 @@ +use std::{ + collections::HashMap, + fs::{self, File}, + path::PathBuf, +}; + +use anyhow::ensure; +use eth_trie::{RootWithTrieDiff, Trie}; +use ethportal_api::types::execution::transaction::Transaction; +use revm::{ + db::{states::bundle_state::BundleRetention, State}, + inspectors::TracerEip3155, + DatabaseCommit, Evm, +}; +use revm_primitives::{keccak256, Account, Address, Env, ResultAndState, SpecId, B256, U256}; +use tracing::info; + +use crate::{ + block_reward::get_block_reward, + dao_fork::{DAO_HARDFORK_BENEFICIARY, DAO_HARDKFORK_ACCOUNTS}, + era::types::{ProcessedBlock, TransactionsWithSender}, + metrics::{ + set_int_gauge_vec, start_timer_vec, stop_timer, BLOCK_HEIGHT, BLOCK_PROCESSING_TIMES, + TRANSACTION_PROCESSING_TIMES, + }, + spec_id::{get_spec_block_number, get_spec_id}, + storage::evm_db::EvmDB, + transaction::TxEnvModifier, + types::block_to_trace::BlockToTrace, +}; + +use super::blocking::execute_transaction_with_external_context; + +const BLOCKHASH_SERVE_WINDOW: u64 = 256; + +pub struct ExecutionContext<'a> { + pub evm: Evm<'a, (), State>, + cumulative_gas_used: u64, + cumulative_gas_expected: u64, + block_to_trace: BlockToTrace, + node_data_directory: PathBuf, +} + +impl<'a> ExecutionContext<'a> { + pub fn new( + database: EvmDB, + block_to_trace: BlockToTrace, + node_data_directory: PathBuf, + ) -> Self { + let state_database = State::builder() + .with_database(database) + .with_bundle_update() + .build(); + let evm: Evm<(), State> = Evm::builder().with_db(state_database).build(); + + Self { + evm, + cumulative_gas_used: 0, + cumulative_gas_expected: 0, + block_to_trace, + node_data_directory, + } + } + + pub fn set_evm_environment_from_block(&mut self, block: &ProcessedBlock) { + let timer: prometheus_exporter::prometheus::HistogramTimer = + start_timer_vec(&BLOCK_PROCESSING_TIMES, &["initialize_evm"]); + if get_spec_id(block.header.number).is_enabled_in(SpecId::SPURIOUS_DRAGON) { + self.evm.db_mut().set_state_clear_flag(true); + } else { + self.evm.db_mut().set_state_clear_flag(false); + }; + + // initialize evm environment + let mut env = Env::default(); + env.block.number = U256::from(block.header.number); + env.block.coinbase = block.header.author; + env.block.timestamp = U256::from(block.header.timestamp); + if get_spec_id(block.header.number).is_enabled_in(SpecId::MERGE) { + env.block.difficulty = U256::ZERO; + env.block.prevrandao = block.header.mix_hash; + } else { + env.block.difficulty = block.header.difficulty; + env.block.prevrandao = None; + } + env.block.basefee = block.header.base_fee_per_gas.unwrap_or_default(); + env.block.gas_limit = block.header.gas_limit; + + // EIP-4844 excess blob gas of this block, introduced in Cancun + if let Some(excess_blob_gas) = block.header.excess_blob_gas { + env.block + .set_blob_excess_gas_and_price(u64::from_be_bytes(excess_blob_gas.to_be_bytes())); + } + + self.evm.context.evm.env = Box::new(env); + self.evm + .handler + .modify_spec_id(get_spec_id(block.header.number)); + stop_timer(timer); + } + + pub fn set_transaction_evm_context(&mut self, tx: &TransactionsWithSender) { + let block_number = self.block_number(); + + let timer = start_timer_vec(&TRANSACTION_PROCESSING_TIMES, &["modify_tx"]); + self.evm.context.evm.env.tx.caller = tx.sender_address; + match &tx.transaction { + Transaction::Legacy(tx) => tx.modify(block_number, &mut self.evm.context.evm.env.tx), + Transaction::EIP1559(tx) => tx.modify(block_number, &mut self.evm.context.evm.env.tx), + Transaction::AccessList(tx) => { + tx.modify(block_number, &mut self.evm.context.evm.env.tx) + } + Transaction::Blob(tx) => tx.modify(block_number, &mut self.evm.context.evm.env.tx), + } + stop_timer(timer); + } + + pub fn commit(&mut self, evm_state: HashMap) { + let timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["commit_state"]); + self.evm.db_mut().commit(evm_state); + stop_timer(timer); + } + + pub fn transact(&mut self) -> anyhow::Result { + Ok(self.evm.transact()?) + } + + pub fn increment_balances( + &mut self, + balances: impl IntoIterator, + ) -> anyhow::Result<()> { + Ok(self.evm.db_mut().increment_balances(balances)?) + } + + pub fn process_dao_fork(&mut self) -> anyhow::Result<()> { + // drain balances from DAO hardfork accounts + let drained_balances = self.evm.db_mut().drain_balances(DAO_HARDKFORK_ACCOUNTS)?; + let drained_balance_sum: u128 = drained_balances.iter().sum(); + + // transfer drained balance to beneficiary + self.evm + .db_mut() + .increment_balances([(DAO_HARDFORK_BENEFICIARY, drained_balance_sum)].into_iter())?; + + Ok(()) + } + + pub fn commit_bundle(&mut self, expected_state_root: B256) -> anyhow::Result { + ensure!( + self.cumulative_gas_used == self.cumulative_gas_expected, + "Cumulative gas used doesn't match gas expected! Irreversible! Block number: {}", + self.block_number() + ); + + let timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["commit_bundle"]); + self.evm + .db_mut() + .merge_transitions(BundleRetention::PlainState); + let state_bundle = self.evm.db_mut().take_bundle(); + self.evm.db_mut().database.commit_bundle(state_bundle)?; + stop_timer(timer); + + let timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["get_root_with_trie_diff"]); + let RootWithTrieDiff { + root, + trie_diff: changed_nodes, + } = self + .evm + .db_mut() + .database + .trie + .lock() + .root_hash_with_changed_nodes()?; + if root != expected_state_root { + panic!( + "State root doesn't match! Irreversible! Block number: {}", + self.block_number() + ) + } + stop_timer(timer); + + Ok(RootWithTrieDiff { + root, + trie_diff: changed_nodes, + }) + } + + pub fn execute_block(&mut self, block: &ProcessedBlock) -> anyhow::Result<()> { + info!("State EVM processing block {}", block.header.number); + + self.manage_block_hash_serve_window(block)?; + + let execute_block_timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["execute_block"]); + + self.set_evm_environment_from_block(block); + + // execute transactions + let cumulative_transaction_timer = + start_timer_vec(&BLOCK_PROCESSING_TIMES, &["cumulative_transaction"]); + for transaction in block.transactions.iter() { + let transaction_timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["transaction"]); + let evm_result = self.execute_transaction(transaction)?; + self.cumulative_gas_used += evm_result.result.gas_used(); + self.commit(evm_result.state); + stop_timer(transaction_timer); + } + stop_timer(cumulative_transaction_timer); + + // update beneficiary + let timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["update_beneficiary"]); + let _ = self.increment_balances(get_block_reward(block)); + + // check if dao fork, if it is drain accounts and transfer it to beneficiary + if block.header.number == get_spec_block_number(SpecId::DAO_FORK) { + self.process_dao_fork()?; + } + self.cumulative_gas_expected += block.header.gas_used.to::(); + stop_timer(timer); + stop_timer(execute_block_timer); + set_int_gauge_vec(&BLOCK_HEIGHT, block.header.number as i64, &[]); + Ok(()) + } + + fn execute_transaction( + &mut self, + tx: &TransactionsWithSender, + ) -> anyhow::Result { + self.set_transaction_evm_context(tx); + let block_number = self.block_number(); + + let timer = start_timer_vec(&TRANSACTION_PROCESSING_TIMES, &["transact"]); + + let result = if self.block_to_trace.should_trace(block_number) { + let output_path = self + .node_data_directory + .as_path() + .join("evm_traces") + .join(format!("block_{block_number}")); + fs::create_dir_all(&output_path)?; + let output_file = + File::create(output_path.join(format!("tx_{}.json", tx.transaction.hash())))?; + let tracer = TracerEip3155::new(Box::new(output_file)); + execute_transaction_with_external_context( + *self.evm.context.evm.inner.env.clone(), + tracer, + &mut self.evm.context.evm.inner.db, + )? + } else { + self.transact()? + }; + stop_timer(timer); + + Ok(result) + } + + /// insert block hash into database and remove old one + fn manage_block_hash_serve_window(&self, block: &ProcessedBlock) -> anyhow::Result<()> { + let timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["insert_blockhash"]); + self.evm.db().database.db.put( + keccak256(B256::from(U256::from(block.header.number))), + block.header.hash(), + )?; + if block.header.number >= BLOCKHASH_SERVE_WINDOW { + self.evm + .db() + .database + .db + .delete(keccak256(B256::from(U256::from( + block.header.number - BLOCKHASH_SERVE_WINDOW, + ))))?; + } + stop_timer(timer); + Ok(()) + } + + pub fn cumulative_gas_used(&self) -> u64 { + self.cumulative_gas_used + } + + pub fn cumulative_gas_expected(&self) -> u64 { + self.cumulative_gas_expected + } + + pub fn bundle_size_hint(&self) -> usize { + self.evm.db().bundle_size_hint() + } + + pub fn block_number(&self) -> u64 { + self.evm.context.evm.env.block.number.to::() + } +} diff --git a/trin-execution/src/evm/mod.rs b/trin-execution/src/evm/mod.rs new file mode 100644 index 000000000..0716f45ea --- /dev/null +++ b/trin-execution/src/evm/mod.rs @@ -0,0 +1,2 @@ +pub mod blocking; +pub mod execution_context; diff --git a/trin-execution/src/execution.rs b/trin-execution/src/execution.rs index f349b2d74..e3624aba9 100644 --- a/trin-execution/src/execution.rs +++ b/trin-execution/src/execution.rs @@ -2,21 +2,11 @@ use alloy_primitives::{keccak256, Address, Bytes, B256, U256}; use alloy_rlp::Decodable; use anyhow::{anyhow, bail, ensure}; use eth_trie::{RootWithTrieDiff, Trie}; -use ethportal_api::types::{ - execution::transaction::Transaction, - state_trie::account_state::AccountState as AccountStateInfo, -}; -use revm::{ - db::states::{bundle_state::BundleRetention, State as RevmState}, - inspector_handle_register, - inspectors::TracerEip3155, - DatabaseCommit, Evm, -}; -use revm_primitives::{Env, ResultAndState, SpecId}; +use ethportal_api::types::state_trie::account_state::AccountState as AccountStateInfo; use serde::{Deserialize, Serialize}; use std::{ collections::{BTreeSet, HashMap}, - fs::{self, File}, + fs::File, io::BufReader, path::{Path, PathBuf}, sync::Arc, @@ -26,24 +16,15 @@ use tokio::sync::Mutex; use tracing::info; use crate::{ - block_reward::get_block_reward, - dao_fork::process_dao_fork, - era::{ - manager::EraManager, - types::{ProcessedBlock, TransactionsWithSender}, - }, - metrics::{ - set_int_gauge_vec, start_timer_vec, stop_timer, BLOCK_HEIGHT, BLOCK_PROCESSING_TIMES, - TRANSACTION_PROCESSING_TIMES, - }, - spec_id::{get_spec_block_number, get_spec_id}, + era::manager::EraManager, + evm::execution_context::ExecutionContext, + metrics::{start_timer_vec, stop_timer, BLOCK_PROCESSING_TIMES}, storage::{ account::Account, evm_db::EvmDB, execution_position::ExecutionPosition, utils::{get_default_data_dir, setup_rocksdb}, }, - transaction::TxEnvModifier, }; use super::{config::StateConfig, types::trie_proof::TrieProof, utils::address_to_nibble_path}; @@ -70,7 +51,6 @@ pub struct State { const GENESIS_STATE_FILE: &str = "trin-execution/resources/genesis/mainnet.json"; const TEST_GENESIS_STATE_FILE: &str = "resources/genesis/mainnet.json"; -const BLOCKHASH_SERVE_WINDOW: u64 = 256; impl State { pub async fn new(path: Option, config: StateConfig) -> anyhow::Result { @@ -149,22 +129,14 @@ impl State { end: u64, ) -> anyhow::Result { info!("Processing blocks from {} to {} (inclusive)", start, end); - let database = RevmState::builder() - .with_database(self.database.clone()) - .with_bundle_update() - .build(); - - let mut evm: Evm<(), RevmState> = Evm::builder().with_db(database).build(); - let mut cumulative_gas_used = 0; - let mut cumulative_gas_expected = 0; + let mut execution_context = ExecutionContext::new( + self.database.clone(), + self.config.block_to_trace.clone(), + self.node_data_directory.clone(), + ); let range_start = Instant::now(); - let mut last_block_executed = start - 1; + let mut last_state_root = self.execution_position.state_root(); for block_number in start..=end { - if get_spec_id(block_number).is_enabled_in(SpecId::SPURIOUS_DRAGON) { - evm.db_mut().set_state_clear_flag(true); - } else { - evm.db_mut().set_state_clear_flag(false); - }; let timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["fetching_block_from_era"]); let block = self .era_manager @@ -175,30 +147,16 @@ impl State { .clone(); stop_timer(timer); - // insert blockhash into database and remove old one - let timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["insert_blockhash"]); - self.database.db.put( - keccak256(B256::from(U256::from(block.header.number))), - block.header.hash(), - )?; - if block.header.number >= BLOCKHASH_SERVE_WINDOW { - self.database.db.delete(keccak256(B256::from(U256::from( - block.header.number - BLOCKHASH_SERVE_WINDOW, - ))))?; - } - stop_timer(timer); - - cumulative_gas_used += self.execute_block(&block, &mut evm)?; - cumulative_gas_expected += block.header.gas_used.to::(); - last_block_executed = block_number; + execution_context.execute_block(&block)?; + last_state_root = block.header.state_root; // Commit the bundle if we have reached the limits, to prevent to much memory usage // We won't use this during the dos attack to avoid writing empty accounts to disk if !(2_200_000..2_700_000).contains(&block_number) && should_we_commit_block_execution_early( block_number - start, - evm.context.evm.db.bundle_size_hint() as u64, - cumulative_gas_used, + execution_context.bundle_size_hint() as u64, + execution_context.cumulative_gas_used(), range_start.elapsed(), ) { @@ -206,51 +164,17 @@ impl State { } } - ensure!( - cumulative_gas_used == cumulative_gas_expected, - "Cumulative gas used doesn't match gas expected! Irreversible! Block number: {}", - end - ); - - let timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["commit_bundle"]); - evm.db_mut().merge_transitions(BundleRetention::PlainState); - let state_bundle = evm.db_mut().take_bundle(); - self.database.commit_bundle(state_bundle)?; - stop_timer(timer); - - let timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["get_root_with_trie_diff"]); - let RootWithTrieDiff { - root, - trie_diff: changed_nodes, - } = self.get_root_with_trie_diff()?; - let header_state_root = self - .era_manager - .lock() - .await - .last_fetched_block() - .await? - .header - .state_root; - if root != header_state_root { - panic!( - "State root doesn't match! Irreversible! Block number: {}", - last_block_executed - ) - } - stop_timer(timer); + let root_with_trie_diff = execution_context.commit_bundle(last_state_root)?; let timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["set_block_execution_number"]); self.execution_position.set_next_block_number( self.database.db.clone(), - last_block_executed + 1, - root, + execution_context.block_number() + 1, + root_with_trie_diff.root, )?; stop_timer(timer); - Ok(RootWithTrieDiff { - root, - trie_diff: changed_nodes, - }) + Ok(root_with_trie_diff) } pub async fn process_block(&mut self, block_number: u64) -> anyhow::Result { @@ -258,117 +182,6 @@ impl State { .await } - pub fn execute_block( - &self, - block: &ProcessedBlock, - evm: &mut Evm<(), RevmState>, - ) -> anyhow::Result { - let execute_block_timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["execute_block"]); - let timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["initialize_evm"]); - info!("State EVM processing block {}", block.header.number); - - // initialize evm environment - let mut env = Env::default(); - env.block.number = U256::from(block.header.number); - env.block.coinbase = block.header.author; - env.block.timestamp = U256::from(block.header.timestamp); - if get_spec_id(block.header.number).is_enabled_in(SpecId::MERGE) { - env.block.difficulty = U256::ZERO; - env.block.prevrandao = block.header.mix_hash; - } else { - env.block.difficulty = block.header.difficulty; - env.block.prevrandao = None; - } - env.block.basefee = block.header.base_fee_per_gas.unwrap_or_default(); - env.block.gas_limit = block.header.gas_limit; - - // EIP-4844 excess blob gas of this block, introduced in Cancun - if let Some(excess_blob_gas) = block.header.excess_blob_gas { - env.block - .set_blob_excess_gas_and_price(u64::from_be_bytes(excess_blob_gas.to_be_bytes())); - } - - evm.context.evm.env = Box::new(env); - evm.handler.modify_spec_id(get_spec_id(block.header.number)); - - stop_timer(timer); - - // execute transactions - let mut cumulative_gas_used = 0; - - let cumulative_transaction_timer = - start_timer_vec(&BLOCK_PROCESSING_TIMES, &["cumulative_transaction"]); - for transaction in block.transactions.iter() { - let transaction_timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["transaction"]); - let transaction_execution_timer = - start_timer_vec(&BLOCK_PROCESSING_TIMES, &["transaction_execution"]); - let evm_result = self.execute_transaction(transaction, evm)?; - cumulative_gas_used += evm_result.result.gas_used(); - stop_timer(transaction_execution_timer); - let timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["commit_state"]); - evm.db_mut().commit(evm_result.state); - stop_timer(timer); - stop_timer(transaction_timer); - } - stop_timer(cumulative_transaction_timer); - - // update beneficiary - let timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["update_beneficiary"]); - let _ = evm.db_mut().increment_balances(get_block_reward(block)); - - // check if dao fork, if it is drain accounts and transfer it to beneficiary - if block.header.number == get_spec_block_number(SpecId::DAO_FORK) { - process_dao_fork(evm)?; - } - stop_timer(timer); - stop_timer(execute_block_timer); - set_int_gauge_vec(&BLOCK_HEIGHT, block.header.number as i64, &[]); - Ok(cumulative_gas_used) - } - - fn execute_transaction( - &self, - tx: &TransactionsWithSender, - evm: &mut Evm<(), RevmState>, - ) -> anyhow::Result { - let block_number = evm.context.evm.env.block.number.to::(); - - let timer = start_timer_vec(&TRANSACTION_PROCESSING_TIMES, &["modify_tx"]); - evm.context.evm.env.tx.caller = tx.sender_address; - match &tx.transaction { - Transaction::Legacy(tx) => tx.modify(block_number, &mut evm.context.evm.env.tx), - Transaction::EIP1559(tx) => tx.modify(block_number, &mut evm.context.evm.env.tx), - Transaction::AccessList(tx) => tx.modify(block_number, &mut evm.context.evm.env.tx), - Transaction::Blob(tx) => tx.modify(block_number, &mut evm.context.evm.env.tx), - } - - if self.config.block_to_trace.should_trace(block_number) { - let output_path = self - .node_data_directory - .as_path() - .join("evm_traces") - .join(format!("block_{block_number}")); - fs::create_dir_all(&output_path)?; - let output_file = - File::create(output_path.join(format!("tx_{}.json", tx.transaction.hash())))?; - let mut evm_with_tracer = Evm::builder() - .with_env(evm.context.evm.inner.env.clone()) - .with_spec_id(evm.handler.cfg.spec_id) - .with_db(&mut evm.context.evm.inner.db) - .with_external_context(TracerEip3155::new(Box::new(output_file))) - .append_handler_register(inspector_handle_register) - .build(); - let result = evm_with_tracer.transact()?; - return Ok(result); - }; - stop_timer(timer); - - let timer = start_timer_vec(&TRANSACTION_PROCESSING_TIMES, &["transact"]); - let result = evm.transact()?; - stop_timer(timer); - Ok(result) - } - pub fn next_block_number(&self) -> u64 { self.execution_position.next_block_number() } diff --git a/trin-execution/src/lib.rs b/trin-execution/src/lib.rs index 9ad6a1bf8..b0526b987 100644 --- a/trin-execution/src/lib.rs +++ b/trin-execution/src/lib.rs @@ -4,6 +4,7 @@ pub mod config; pub mod content; pub mod dao_fork; pub mod era; +pub mod evm; pub mod execution; pub mod metrics; pub mod spec_id; From c9f10fe3064bf4bd191a0c2142b3be902b523e19 Mon Sep 17 00:00:00 2001 From: Kolby Moroz Liebl <31669092+KolbyML@users.noreply.github.com> Date: Fri, 6 Sep 2024 17:37:42 -0600 Subject: [PATCH 08/11] fix: resolve PR concerns --- portal-bridge/src/bridge/state.rs | 14 +-- ...execution_context.rs => block_executor.rs} | 70 +++++++------- trin-execution/src/evm/mod.rs | 2 +- trin-execution/src/execution.rs | 94 +++++++++++-------- trin-execution/src/main.rs | 23 ++--- trin-execution/src/storage/evm_db.rs | 85 ++++++++--------- trin-execution/src/trie_walker.rs | 10 +- trin-execution/tests/content_generation.rs | 29 +++--- 8 files changed, 160 insertions(+), 167 deletions(-) rename trin-execution/src/evm/{execution_context.rs => block_executor.rs} (85%) diff --git a/portal-bridge/src/bridge/state.rs b/portal-bridge/src/bridge/state.rs index aaaf0d2d1..dcc8c0036 100644 --- a/portal-bridge/src/bridge/state.rs +++ b/portal-bridge/src/bridge/state.rs @@ -1,7 +1,6 @@ use std::{path::PathBuf, sync::Arc}; use alloy_rlp::Decodable; -use anyhow::anyhow; use e2store::utils::get_shuffled_era1_files; use eth_trie::{decode_node, node::Node, RootWithTrieDiff}; use ethportal_api::{ @@ -24,7 +23,7 @@ use trin_execution::{ create_account_content_key, create_account_content_value, create_contract_content_key, create_contract_content_value, create_storage_content_key, create_storage_content_value, }, - execution::State, + execution::TrinExecution, spec_id::get_spec_block_number, storage::utils::setup_temp_dir, trie_walker::TrieWalker, @@ -109,7 +108,8 @@ impl StateBridge { cache_contract_storage_changes: true, block_to_trace: BlockToTrace::None, }; - let mut state = State::new(Some(temp_directory.path().to_path_buf()), state_config).await?; + let mut state = + TrinExecution::new(Some(temp_directory.path().to_path_buf()), state_config).await?; for block_number in 0..=last_block { info!("Gossipping state for block at height: {block_number}"); @@ -117,12 +117,8 @@ impl StateBridge { let RootWithTrieDiff { root: root_hash, trie_diff: changed_nodes, - } = match block_number == 0 { - true => state - .initialize_genesis() - .map_err(|e| anyhow!("unable to create genesis state: {e}"))?, - false => state.process_block(block_number).await?, - }; + } = state.process_block(block_number).await?; + let block = state .era_manager .lock() diff --git a/trin-execution/src/evm/execution_context.rs b/trin-execution/src/evm/block_executor.rs similarity index 85% rename from trin-execution/src/evm/execution_context.rs rename to trin-execution/src/evm/block_executor.rs index 2a4f58635..4104d220a 100644 --- a/trin-execution/src/evm/execution_context.rs +++ b/trin-execution/src/evm/block_executor.rs @@ -33,15 +33,19 @@ use super::blocking::execute_transaction_with_external_context; const BLOCKHASH_SERVE_WINDOW: u64 = 256; -pub struct ExecutionContext<'a> { +/// BlockExecutor is a struct that is responsible for executing blocks or a block in memory. +/// The use case is +/// - initialize the BlockExecutor with a database +/// - execute blocks +/// - commit the changes and retrieve the result +pub struct BlockExecutor<'a> { pub evm: Evm<'a, (), State>, cumulative_gas_used: u64, - cumulative_gas_expected: u64, block_to_trace: BlockToTrace, node_data_directory: PathBuf, } -impl<'a> ExecutionContext<'a> { +impl<'a> BlockExecutor<'a> { pub fn new( database: EvmDB, block_to_trace: BlockToTrace, @@ -56,13 +60,12 @@ impl<'a> ExecutionContext<'a> { Self { evm, cumulative_gas_used: 0, - cumulative_gas_expected: 0, block_to_trace, node_data_directory, } } - pub fn set_evm_environment_from_block(&mut self, block: &ProcessedBlock) { + fn set_evm_environment_from_block(&mut self, block: &ProcessedBlock) { let timer: prometheus_exporter::prometheus::HistogramTimer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["initialize_evm"]); if get_spec_id(block.header.number).is_enabled_in(SpecId::SPURIOUS_DRAGON) { @@ -100,17 +103,17 @@ impl<'a> ExecutionContext<'a> { } pub fn set_transaction_evm_context(&mut self, tx: &TransactionsWithSender) { + let timer = start_timer_vec(&TRANSACTION_PROCESSING_TIMES, &["modify_tx"]); + let block_number = self.block_number(); + let tx_env = &mut self.evm.context.evm.env.tx; - let timer = start_timer_vec(&TRANSACTION_PROCESSING_TIMES, &["modify_tx"]); - self.evm.context.evm.env.tx.caller = tx.sender_address; + tx_env.caller = tx.sender_address; match &tx.transaction { - Transaction::Legacy(tx) => tx.modify(block_number, &mut self.evm.context.evm.env.tx), - Transaction::EIP1559(tx) => tx.modify(block_number, &mut self.evm.context.evm.env.tx), - Transaction::AccessList(tx) => { - tx.modify(block_number, &mut self.evm.context.evm.env.tx) - } - Transaction::Blob(tx) => tx.modify(block_number, &mut self.evm.context.evm.env.tx), + Transaction::Legacy(tx) => tx.modify(block_number, tx_env), + Transaction::EIP1559(tx) => tx.modify(block_number, tx_env), + Transaction::AccessList(tx) => tx.modify(block_number, tx_env), + Transaction::Blob(tx) => tx.modify(block_number, tx_env), } stop_timer(timer); } @@ -138,20 +141,12 @@ impl<'a> ExecutionContext<'a> { let drained_balance_sum: u128 = drained_balances.iter().sum(); // transfer drained balance to beneficiary - self.evm - .db_mut() - .increment_balances([(DAO_HARDFORK_BENEFICIARY, drained_balance_sum)].into_iter())?; + self.increment_balances([(DAO_HARDFORK_BENEFICIARY, drained_balance_sum)].into_iter())?; Ok(()) } - pub fn commit_bundle(&mut self, expected_state_root: B256) -> anyhow::Result { - ensure!( - self.cumulative_gas_used == self.cumulative_gas_expected, - "Cumulative gas used doesn't match gas expected! Irreversible! Block number: {}", - self.block_number() - ); - + pub fn commit_bundle(&mut self) -> anyhow::Result { let timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["commit_bundle"]); self.evm .db_mut() @@ -171,12 +166,7 @@ impl<'a> ExecutionContext<'a> { .trie .lock() .root_hash_with_changed_nodes()?; - if root != expected_state_root { - panic!( - "State root doesn't match! Irreversible! Block number: {}", - self.block_number() - ) - } + stop_timer(timer); Ok(RootWithTrieDiff { @@ -188,24 +178,30 @@ impl<'a> ExecutionContext<'a> { pub fn execute_block(&mut self, block: &ProcessedBlock) -> anyhow::Result<()> { info!("State EVM processing block {}", block.header.number); - self.manage_block_hash_serve_window(block)?; - let execute_block_timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["execute_block"]); - self.set_evm_environment_from_block(block); // execute transactions let cumulative_transaction_timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["cumulative_transaction"]); + let mut block_gas_used = 0; for transaction in block.transactions.iter() { let transaction_timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["transaction"]); let evm_result = self.execute_transaction(transaction)?; - self.cumulative_gas_used += evm_result.result.gas_used(); + block_gas_used += evm_result.result.gas_used(); self.commit(evm_result.state); stop_timer(transaction_timer); } stop_timer(cumulative_transaction_timer); + ensure!( + block_gas_used == block.header.gas_used.to::(), + "Block gas used mismatch at {} != {}", + block_gas_used, + block.header.gas_used + ); + self.cumulative_gas_used += block_gas_used; + // update beneficiary let timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["update_beneficiary"]); let _ = self.increment_balances(get_block_reward(block)); @@ -214,7 +210,9 @@ impl<'a> ExecutionContext<'a> { if block.header.number == get_spec_block_number(SpecId::DAO_FORK) { self.process_dao_fork()?; } - self.cumulative_gas_expected += block.header.gas_used.to::(); + + self.manage_block_hash_serve_window(block)?; + stop_timer(timer); stop_timer(execute_block_timer); set_int_gauge_vec(&BLOCK_HEIGHT, block.header.number as i64, &[]); @@ -277,10 +275,6 @@ impl<'a> ExecutionContext<'a> { self.cumulative_gas_used } - pub fn cumulative_gas_expected(&self) -> u64 { - self.cumulative_gas_expected - } - pub fn bundle_size_hint(&self) -> usize { self.evm.db().bundle_size_hint() } diff --git a/trin-execution/src/evm/mod.rs b/trin-execution/src/evm/mod.rs index 0716f45ea..6f352f97c 100644 --- a/trin-execution/src/evm/mod.rs +++ b/trin-execution/src/evm/mod.rs @@ -1,2 +1,2 @@ +pub mod block_executor; pub mod blocking; -pub mod execution_context; diff --git a/trin-execution/src/execution.rs b/trin-execution/src/execution.rs index e3624aba9..e614a1bda 100644 --- a/trin-execution/src/execution.rs +++ b/trin-execution/src/execution.rs @@ -17,7 +17,7 @@ use tracing::info; use crate::{ era::manager::EraManager, - evm::execution_context::ExecutionContext, + evm::block_executor::BlockExecutor, metrics::{start_timer_vec, stop_timer, BLOCK_PROCESSING_TIMES}, storage::{ account::Account, @@ -41,7 +41,7 @@ struct GenesisConfig { state_root: B256, } -pub struct State { +pub struct TrinExecution { pub database: EvmDB, pub config: StateConfig, execution_position: ExecutionPosition, @@ -52,7 +52,7 @@ pub struct State { const GENESIS_STATE_FILE: &str = "trin-execution/resources/genesis/mainnet.json"; const TEST_GENESIS_STATE_FILE: &str = "resources/genesis/mainnet.json"; -impl State { +impl TrinExecution { pub async fn new(path: Option, config: StateConfig) -> anyhow::Result { let node_data_directory = match path { Some(path_buf) => path_buf, @@ -68,7 +68,7 @@ impl State { EraManager::new(execution_position.next_block_number()).await?, )); - Ok(State { + Ok(Self { execution_position, config, era_manager, @@ -77,7 +77,7 @@ impl State { }) } - pub fn initialize_genesis(&mut self) -> anyhow::Result { + fn initialize_genesis(&mut self) -> anyhow::Result { ensure!( self.execution_position.next_block_number() == 0, "Trying to initialize genesis but received block {}", @@ -123,18 +123,40 @@ impl State { /// This is a lot faster then process_block() as it executes the range in memory, but we won't /// return the trie diff so you can use this to sync up to the block you want, then use /// `process_block()` to get the trie diff to gossip on the state bridge - pub async fn process_range_of_blocks( - &mut self, - start: u64, - end: u64, - ) -> anyhow::Result { + pub async fn process_range_of_blocks(&mut self, end: u64) -> anyhow::Result { + let start = self.execution_position.next_block_number(); + ensure!( + end >= start, + "End block number {} is less than start block number {}", + end, + start + ); + + // If we are starting from the beginning, we need to initialize the genesis state + if start == 0 { + self.era_manager.lock().await.get_next_block().await?; + let result = self.initialize_genesis()?; + if end == 0 { + return Ok(result); + } + } + + let start = self.execution_position.next_block_number(); + ensure!( + end >= start, + "End block number {} is less than start block number {}", + end, + start + ); + info!("Processing blocks from {} to {} (inclusive)", start, end); - let mut execution_context = ExecutionContext::new( + let mut block_executor = BlockExecutor::new( self.database.clone(), self.config.block_to_trace.clone(), self.node_data_directory.clone(), ); let range_start = Instant::now(); + let mut last_executed_block_number = start - 1; let mut last_state_root = self.execution_position.state_root(); for block_number in start..=end { let timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["fetching_block_from_era"]); @@ -147,7 +169,8 @@ impl State { .clone(); stop_timer(timer); - execution_context.execute_block(&block)?; + block_executor.execute_block(&block)?; + last_executed_block_number = block_number; last_state_root = block.header.state_root; // Commit the bundle if we have reached the limits, to prevent to much memory usage @@ -155,8 +178,8 @@ impl State { if !(2_200_000..2_700_000).contains(&block_number) && should_we_commit_block_execution_early( block_number - start, - execution_context.bundle_size_hint() as u64, - execution_context.cumulative_gas_used(), + block_executor.bundle_size_hint() as u64, + block_executor.cumulative_gas_used(), range_start.elapsed(), ) { @@ -164,12 +187,17 @@ impl State { } } - let root_with_trie_diff = execution_context.commit_bundle(last_state_root)?; + let root_with_trie_diff = block_executor.commit_bundle()?; + ensure!( + root_with_trie_diff.root == last_state_root, + "State root doesn't match! Irreversible! Block number: {}", + last_executed_block_number + ); let timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["set_block_execution_number"]); self.execution_position.set_next_block_number( self.database.db.clone(), - execution_context.block_number() + 1, + last_executed_block_number + 1, root_with_trie_diff.root, )?; stop_timer(timer); @@ -178,8 +206,7 @@ impl State { } pub async fn process_block(&mut self, block_number: u64) -> anyhow::Result { - self.process_range_of_blocks(block_number, block_number) - .await + self.process_range_of_blocks(block_number).await } pub fn next_block_number(&self) -> u64 { @@ -268,53 +295,44 @@ mod tests { use std::fs; use crate::{ - config::StateConfig, era::utils::process_era1_file, storage::utils::setup_temp_dir, + config::StateConfig, era::utils::process_era1_file, execution::TrinExecution, + storage::utils::setup_temp_dir, }; - use super::State; use alloy_primitives::Address; use revm_primitives::hex::FromHex; #[tokio::test] async fn test_we_generate_the_correct_state_root_for_the_first_8192_blocks() { let temp_directory = setup_temp_dir().unwrap(); - let mut state = State::new( + let mut trin_execution = TrinExecution::new( Some(temp_directory.path().to_path_buf()), StateConfig::default(), ) .await .unwrap(); - let _ = state.initialize_genesis().unwrap(); let raw_era1 = fs::read("../test_assets/era1/mainnet-00000-5ec1ffb8.era1").unwrap(); let processed_era = process_era1_file(raw_era1, 0).unwrap(); for block in processed_era.blocks { - if block.header.number == 0 { - // initialize genesis state processes this block so we skip it - state - .era_manager - .lock() - .await - .get_next_block() - .await - .unwrap(); - continue; - } - state.process_block(block.header.number).await.unwrap(); - assert_eq!(state.get_root().unwrap(), block.header.state_root); + trin_execution + .process_block(block.header.number) + .await + .unwrap(); + assert_eq!(trin_execution.get_root().unwrap(), block.header.state_root); } } #[tokio::test] async fn test_get_proof() { let temp_directory = setup_temp_dir().unwrap(); - let mut state = State::new( + let mut trin_execution = TrinExecution::new( Some(temp_directory.path().to_path_buf()), StateConfig::default(), ) .await .unwrap(); - let _ = state.initialize_genesis().unwrap(); - let valid_proof = state + trin_execution.process_block(0).await.unwrap(); + let valid_proof = trin_execution .get_proof(Address::from_hex("0x001d14804b399c6ef80e64576f657660804fec0b").unwrap()) .unwrap(); assert_eq!(valid_proof.path, [5, 9, 2, 13]); diff --git a/trin-execution/src/main.rs b/trin-execution/src/main.rs index 5ad3370bd..e777f33ce 100644 --- a/trin-execution/src/main.rs +++ b/trin-execution/src/main.rs @@ -3,7 +3,7 @@ use clap::Parser; use revm_primitives::SpecId; use tracing::info; use trin_execution::{ - cli::TrinExecutionConfig, execution::State, spec_id::get_spec_block_number, + cli::TrinExecutionConfig, execution::TrinExecution, spec_id::get_spec_block_number, storage::utils::setup_temp_dir, }; use trin_utils::log::init_tracing_logger; @@ -24,7 +24,7 @@ async fn main() -> Result<(), Box> { true => Some(setup_temp_dir()?), }; - let mut state = State::new( + let mut trin_execution = TrinExecution::new( directory.map(|temp_directory| temp_directory.path().to_path_buf()), trin_execution_config.into(), ) @@ -38,31 +38,22 @@ async fn main() -> Result<(), Box> { .expect("signal ctrl_c should never fail"); }); - let mut block_number = state.next_block_number(); + let mut block_number = trin_execution.next_block_number(); let end_block = get_spec_block_number(SpecId::MERGE); while block_number < end_block { if rx.try_recv().is_ok() { - state.database.db.flush()?; + trin_execution.database.db.flush()?; info!( "Received SIGINT, stopping execution: {} {}", block_number - 1, - state.get_root()? + trin_execution.get_root()? ); break; } - if block_number == 0 { - // initialize genesis state processes this block so we skip it - state.era_manager.lock().await.get_next_block().await?; - state.initialize_genesis()?; - block_number = 1; - continue; - } - state - .process_range_of_blocks(block_number, end_block) - .await?; - block_number = state.next_block_number(); + trin_execution.process_range_of_blocks(end_block).await?; + block_number = trin_execution.next_block_number(); } Ok(()) diff --git a/trin-execution/src/storage/evm_db.rs b/trin-execution/src/storage/evm_db.rs index eeb912e08..4696211cc 100644 --- a/trin-execution/src/storage/evm_db.rs +++ b/trin-execution/src/storage/evm_db.rs @@ -136,44 +136,43 @@ impl EvmDB { Ok(()) } - fn delete_account_storage( + fn wipe_account_storage( &mut self, address_hash: B256, - rocks_account: RocksAccount, delete_account: bool, - ) -> anyhow::Result> { - let timer_label = match delete_account { - true => "account:delete_account", - false => "storage:wipe_storage", + timer_label: &str, + ) -> anyhow::Result<()> { + // load account from db + let Some(raw_account) = self.db.get(address_hash)? else { + return Ok(()); }; let timer = start_commit_timer(timer_label); + + let mut rocks_account = RocksAccount::decode(&mut raw_account.as_slice())?; + + // wipe storage trie and db if rocks_account.storage_root != EMPTY_ROOT_HASH { let account_db = AccountDB::new(address_hash, self.db.clone()); let mut trie = EthTrie::from(Arc::new(account_db), rocks_account.storage_root)?; trie.clear_trie_from_db()?; + rocks_account.storage_root = EMPTY_ROOT_HASH; } - let rocks_account = if delete_account { - self.db.delete(address_hash)?; - // update trie + // update account trie and db + if delete_account { + self.db.delete(address_hash)?; let _ = self.trie.lock().remove(address_hash.as_ref()); - - None } else { - let mut rocks_account = rocks_account; - rocks_account.storage_root = EMPTY_ROOT_HASH; + self.db + .put(address_hash, alloy_rlp::encode(&rocks_account))?; let _ = self.trie.lock().insert( address_hash.as_ref(), &alloy_rlp::encode(AccountStateInfo::from(&rocks_account)), ); - self.db - .put(address_hash, alloy_rlp::encode(&rocks_account)) - .expect("Inserting account should never fail"); + } - Some(rocks_account) - }; stop_timer(timer); - Ok(rocks_account) + Ok(()) } fn commit_accounts( @@ -184,9 +183,8 @@ impl EvmDB { let address_hash = keccak256(address); if let Some(account_info) = account { self.commit_account(address_hash, account_info)?; - } else if let Some(raw_account) = self.db.get(address_hash)? { - let rocks_account = RocksAccount::decode(&mut raw_account.as_slice())?; - self.delete_account_storage(address_hash, rocks_account, true)?; + } else { + self.wipe_account_storage(address_hash, true, "account:delete_account")?; } } Ok(()) @@ -195,13 +193,20 @@ impl EvmDB { fn commit_storage_changes( &mut self, address_hash: B256, - rocks_account: Option, storage: Vec<(U256, U256)>, ) -> anyhow::Result<()> { let timer = start_commit_timer("storage:apply_updates"); let account_db = AccountDB::new(address_hash, self.db.clone()); - let mut rocks_account = rocks_account.unwrap_or_default(); + let mut rocks_account: RocksAccount = self + .db + .get(address_hash)? + .map(|raw_account| { + let rocks_account: RocksAccount = Decodable::decode(&mut raw_account.as_slice()) + .expect("Decoding account should never fail"); + rocks_account + }) + .unwrap_or_default(); let mut trie = if rocks_account.storage_root == EMPTY_ROOT_HASH { EthTrie::new(Arc::new(account_db)) @@ -253,24 +258,19 @@ impl EvmDB { { let plain_state_storage_timer = start_commit_timer("storage:plain_state_storage"); - let timer = start_commit_timer("storage:fetch_account_from_db"); let address_hash = keccak256(address); - let rocks_account: Option = - self.db.get(address_hash)?.map(|raw_account| { - Decodable::decode(&mut raw_account.as_slice()) - .expect("valid account should be decoded") - }); - stop_timer(timer); - let rocks_account = if wipe_storage && rocks_account.is_some() { - let rocks_account = rocks_account.expect("We already checked that it is some"); - self.delete_account_storage(address_hash, rocks_account, false)? - } else { - rocks_account - }; + if wipe_storage { + self.wipe_account_storage(address_hash, false, "storage:wipe_storage")?; + } if !storage.is_empty() { - self.commit_storage_changes(address_hash, rocks_account, storage)?; + // review comment: note that commit_storage_changes would have to load RocksAccount + // from db this means that it's possible that we will have to load + // it twice but I think it's quite uncommon, to have both + // wipe_storage and non-empty storage, so I don't think it's big + // deal + self.commit_storage_changes(address_hash, storage)?; } stop_timer(plain_state_storage_timer); } @@ -376,12 +376,13 @@ impl DatabaseRef for EvmDB { None => return Err(Self::Error::NotFound("storage".to_string())), }; let account_db = AccountDB::new(address_hash, self.db.clone()); - let trie = if account.storage_root == EMPTY_ROOT_HASH { - EthTrie::new(Arc::new(account_db)) + let raw_value = if account.storage_root == EMPTY_ROOT_HASH { + None } else { - EthTrie::from(Arc::new(account_db), account.storage_root)? + let trie = EthTrie::from(Arc::new(account_db), account.storage_root)?; + trie.get(keccak256(B256::from(index)).as_ref())? }; - let result = match trie.get(keccak256(B256::from(index)).as_ref())? { + let result = match raw_value { Some(raw_value) => Ok(Decodable::decode(&mut raw_value.as_slice())?), None => Ok(U256::ZERO), }; diff --git a/trin-execution/src/trie_walker.rs b/trin-execution/src/trie_walker.rs index 13dffecf0..c81ae296c 100644 --- a/trin-execution/src/trie_walker.rs +++ b/trin-execution/src/trie_walker.rs @@ -165,24 +165,24 @@ mod tests { use revm_primitives::hex::FromHex; use crate::{ - config::StateConfig, execution::State, storage::utils::setup_temp_dir, + config::StateConfig, execution::TrinExecution, storage::utils::setup_temp_dir, trie_walker::TrieWalker, }; #[tokio::test] async fn test_trie_walker_builds_valid_proof() { let temp_directory = setup_temp_dir().unwrap(); - let mut state = State::new( + let mut trin_execution = TrinExecution::new( Some(temp_directory.path().to_path_buf()), StateConfig::default(), ) .await .unwrap(); - let RootWithTrieDiff { trie_diff, .. } = state.initialize_genesis().unwrap(); - let valid_proof = state + let RootWithTrieDiff { trie_diff, .. } = trin_execution.process_block(0).await.unwrap(); + let valid_proof = trin_execution .get_proof(Address::from_hex("0x001d14804b399c6ef80e64576f657660804fec0b").unwrap()) .unwrap(); - let root_hash = state.get_root().unwrap(); + let root_hash = trin_execution.get_root().unwrap(); let walk_diff = TrieWalker::new(root_hash, trie_diff); let last_node = valid_proof diff --git a/trin-execution/tests/content_generation.rs b/trin-execution/tests/content_generation.rs index 2f9361f20..42e60d503 100644 --- a/trin-execution/tests/content_generation.rs +++ b/trin-execution/tests/content_generation.rs @@ -1,5 +1,5 @@ use alloy_rlp::Decodable; -use anyhow::{anyhow, ensure, Result}; +use anyhow::{ensure, Result}; use eth_trie::{decode_node, node::Node, RootWithTrieDiff}; use ethportal_api::types::state_trie::account_state::AccountState; use revm::DatabaseRef; @@ -11,7 +11,7 @@ use trin_execution::{ create_account_content_key, create_account_content_value, create_contract_content_key, create_contract_content_value, create_storage_content_key, create_storage_content_value, }, - execution::State, + execution::TrinExecution, storage::utils::setup_temp_dir, trie_walker::TrieWalker, types::block_to_trace::BlockToTrace, @@ -36,7 +36,7 @@ async fn test_we_can_generate_content_key_values_up_to_x() -> Result<()> { let temp_directory = setup_temp_dir()?; - let mut state = State::new( + let mut trin_execution = TrinExecution::new( Some(temp_directory.path().to_path_buf()), StateConfig { cache_contract_storage_changes: true, @@ -51,17 +51,8 @@ async fn test_we_can_generate_content_key_values_up_to_x() -> Result<()> { let RootWithTrieDiff { root: root_hash, trie_diff: changed_nodes, - } = match block_number == 0 { - true => { - // initialize genesis state processes this block so we skip it - state.era_manager.lock().await.get_next_block().await?; - state - .initialize_genesis() - .map_err(|e| anyhow!("unable to create genesis state: {e}"))? - } - false => state.process_block(block_number).await?, - }; - let block = state + } = trin_execution.process_block(block_number).await?; + let block = trin_execution .era_manager .lock() .await @@ -73,7 +64,7 @@ async fn test_we_can_generate_content_key_values_up_to_x() -> Result<()> { "Block number doesn't match!" ); ensure!( - state.get_root()? == block.header.state_root, + trin_execution.get_root()? == block.header.state_root, "State root doesn't match" ); @@ -106,14 +97,16 @@ async fn test_we_can_generate_content_key_values_up_to_x() -> Result<()> { // check contract code content key/value if account.code_hash != keccak256([]) { - let code = state.database.code_by_hash_ref(account.code_hash)?; + let code = trin_execution + .database + .code_by_hash_ref(account.code_hash)?; assert!(create_contract_content_key(address_hash, account.code_hash).is_ok()); assert!(create_contract_content_value(block_hash, &account_proof, code).is_ok()); content_pairs += 1; } // check contract storage content key/value - let storage_changed_nodes = state.database.get_storage_trie_diff(address_hash); + let storage_changed_nodes = trin_execution.database.get_storage_trie_diff(address_hash); let storage_walk_diff = TrieWalker::new(account.storage_root, storage_changed_nodes); for storage_node in storage_walk_diff.nodes.keys() { let storage_proof = storage_walk_diff.get_proof(*storage_node); @@ -128,7 +121,7 @@ async fn test_we_can_generate_content_key_values_up_to_x() -> Result<()> { // flush the database cache // This is used for gossiping storage trie diffs - state.database.storage_cache.clear(); + trin_execution.database.storage_cache.clear(); println!("Block {block_number} finished. Total content key/value pairs: {content_pairs}"); } Ok(()) From fe96d57fd72b47ce0c2faa7e5b51090603213aff Mon Sep 17 00:00:00 2001 From: Kolby Moroz Liebl <31669092+KolbyML@users.noreply.github.com> Date: Sat, 7 Sep 2024 02:38:17 -0600 Subject: [PATCH 09/11] fix: add comment to issue --- trin-execution/src/storage/evm_db.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/trin-execution/src/storage/evm_db.rs b/trin-execution/src/storage/evm_db.rs index 4696211cc..5ba2d6c40 100644 --- a/trin-execution/src/storage/evm_db.rs +++ b/trin-execution/src/storage/evm_db.rs @@ -297,6 +297,14 @@ impl EvmDB { stop_timer(timer); // Write Contract Code + // TODO: Delete contract code if no accounts point to it: https://github.com/ethereum/trin/issues/1428 + // This can happen in 1 of 2 cases + // - the account is deleted and the contract code is not used by any other accounts + // - we do a revert and the contract code is not used by any other accounts + // The solution would require keeping a count of how many accounts point to a contract code + // and deleting the contract code if the count is 0. + // This is very low priority due to this issue being very rare and it not taking up much + // storage let timer = start_commit_timer("contract:committing_contracts_total"); for (hash, bytecode) in plain_state.contracts { let timer = start_commit_timer("committing_contract"); From da83d48316449e9d4e82be177f10eeb933ccfbc6 Mon Sep 17 00:00:00 2001 From: Kolby Moroz Liebl <31669092+KolbyML@users.noreply.github.com> Date: Sat, 7 Sep 2024 16:14:15 -0600 Subject: [PATCH 10/11] fix: resolve PR concerns --- portal-bridge/src/bridge/state.rs | 13 +- trin-execution/src/evm/block_executor.rs | 141 ++++++++++++++------- trin-execution/src/execution.rs | 111 ++-------------- trin-execution/src/storage/error.rs | 5 +- trin-execution/src/storage/evm_db.rs | 53 +++----- trin-execution/src/trie_walker.rs | 2 +- trin-execution/tests/content_generation.rs | 2 +- 7 files changed, 143 insertions(+), 184 deletions(-) diff --git a/portal-bridge/src/bridge/state.rs b/portal-bridge/src/bridge/state.rs index dcc8c0036..4535f301f 100644 --- a/portal-bridge/src/bridge/state.rs +++ b/portal-bridge/src/bridge/state.rs @@ -108,7 +108,7 @@ impl StateBridge { cache_contract_storage_changes: true, block_to_trace: BlockToTrace::None, }; - let mut state = + let mut trin_execution = TrinExecution::new(Some(temp_directory.path().to_path_buf()), state_config).await?; for block_number in 0..=last_block { info!("Gossipping state for block at height: {block_number}"); @@ -117,9 +117,9 @@ impl StateBridge { let RootWithTrieDiff { root: root_hash, trie_diff: changed_nodes, - } = state.process_block(block_number).await?; + } = trin_execution.process_next_block().await?; - let block = state + let block = trin_execution .era_manager .lock() .await @@ -160,7 +160,7 @@ impl StateBridge { // if the code_hash is empty then then don't try to gossip the contract bytecode if account.code_hash != keccak256([]) { // gossip contract bytecode - let code = state.database.code_by_hash(account.code_hash)?; + let code = trin_execution.database.code_by_hash(account.code_hash)?; self.gossip_contract_bytecode( address_hash, &account_proof, @@ -172,7 +172,8 @@ impl StateBridge { } // gossip contract storage - let storage_changed_nodes = state.database.get_storage_trie_diff(address_hash); + let storage_changed_nodes = + trin_execution.database.get_storage_trie_diff(address_hash); let storage_walk_diff = TrieWalker::new(account.storage_root, storage_changed_nodes); @@ -191,7 +192,7 @@ impl StateBridge { // flush the database cache // This is used for gossiping storage trie diffs - state.database.storage_cache.clear(); + trin_execution.database.storage_cache.clear(); } Ok(()) } diff --git a/trin-execution/src/evm/block_executor.rs b/trin-execution/src/evm/block_executor.rs index 4104d220a..a3d5cf019 100644 --- a/trin-execution/src/evm/block_executor.rs +++ b/trin-execution/src/evm/block_executor.rs @@ -1,18 +1,26 @@ use std::{ collections::HashMap, fs::{self, File}, - path::PathBuf, + io::BufReader, + path::{Path, PathBuf}, }; -use anyhow::ensure; +use anyhow::{bail, ensure}; use eth_trie::{RootWithTrieDiff, Trie}; -use ethportal_api::types::execution::transaction::Transaction; +use ethportal_api::{ + types::{ + execution::transaction::Transaction, + state_trie::account_state::AccountState as AccountStateInfo, + }, + Header, +}; use revm::{ db::{states::bundle_state::BundleRetention, State}, inspectors::TracerEip3155, DatabaseCommit, Evm, }; -use revm_primitives::{keccak256, Account, Address, Env, ResultAndState, SpecId, B256, U256}; +use revm_primitives::{keccak256, Address, Env, ResultAndState, SpecId, B256, U256}; +use serde::{Deserialize, Serialize}; use tracing::info; use crate::{ @@ -24,7 +32,7 @@ use crate::{ TRANSACTION_PROCESSING_TIMES, }, spec_id::{get_spec_block_number, get_spec_id}, - storage::evm_db::EvmDB, + storage::{account::Account as RocksAccount, evm_db::EvmDB}, transaction::TxEnvModifier, types::block_to_trace::BlockToTrace, }; @@ -32,6 +40,20 @@ use crate::{ use super::blocking::execute_transaction_with_external_context; const BLOCKHASH_SERVE_WINDOW: u64 = 256; +const GENESIS_STATE_FILE: &str = "trin-execution/resources/genesis/mainnet.json"; +const TEST_GENESIS_STATE_FILE: &str = "resources/genesis/mainnet.json"; + +#[derive(Debug, Serialize, Deserialize)] +struct AllocBalance { + balance: U256, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +struct GenesisConfig { + alloc: HashMap, + state_root: B256, +} /// BlockExecutor is a struct that is responsible for executing blocks or a block in memory. /// The use case is @@ -39,7 +61,7 @@ const BLOCKHASH_SERVE_WINDOW: u64 = 256; /// - execute blocks /// - commit the changes and retrieve the result pub struct BlockExecutor<'a> { - pub evm: Evm<'a, (), State>, + evm: Evm<'a, (), State>, cumulative_gas_used: u64, block_to_trace: BlockToTrace, node_data_directory: PathBuf, @@ -65,10 +87,10 @@ impl<'a> BlockExecutor<'a> { } } - fn set_evm_environment_from_block(&mut self, block: &ProcessedBlock) { + fn set_evm_environment_from_block(&mut self, header: &Header) { let timer: prometheus_exporter::prometheus::HistogramTimer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["initialize_evm"]); - if get_spec_id(block.header.number).is_enabled_in(SpecId::SPURIOUS_DRAGON) { + if get_spec_id(header.number).is_enabled_in(SpecId::SPURIOUS_DRAGON) { self.evm.db_mut().set_state_clear_flag(true); } else { self.evm.db_mut().set_state_clear_flag(false); @@ -76,33 +98,31 @@ impl<'a> BlockExecutor<'a> { // initialize evm environment let mut env = Env::default(); - env.block.number = U256::from(block.header.number); - env.block.coinbase = block.header.author; - env.block.timestamp = U256::from(block.header.timestamp); - if get_spec_id(block.header.number).is_enabled_in(SpecId::MERGE) { + env.block.number = U256::from(header.number); + env.block.coinbase = header.author; + env.block.timestamp = U256::from(header.timestamp); + if get_spec_id(header.number).is_enabled_in(SpecId::MERGE) { env.block.difficulty = U256::ZERO; - env.block.prevrandao = block.header.mix_hash; + env.block.prevrandao = header.mix_hash; } else { - env.block.difficulty = block.header.difficulty; + env.block.difficulty = header.difficulty; env.block.prevrandao = None; } - env.block.basefee = block.header.base_fee_per_gas.unwrap_or_default(); - env.block.gas_limit = block.header.gas_limit; + env.block.basefee = header.base_fee_per_gas.unwrap_or_default(); + env.block.gas_limit = header.gas_limit; // EIP-4844 excess blob gas of this block, introduced in Cancun - if let Some(excess_blob_gas) = block.header.excess_blob_gas { + if let Some(excess_blob_gas) = header.excess_blob_gas { env.block .set_blob_excess_gas_and_price(u64::from_be_bytes(excess_blob_gas.to_be_bytes())); } self.evm.context.evm.env = Box::new(env); - self.evm - .handler - .modify_spec_id(get_spec_id(block.header.number)); + self.evm.handler.modify_spec_id(get_spec_id(header.number)); stop_timer(timer); } - pub fn set_transaction_evm_context(&mut self, tx: &TransactionsWithSender) { + fn set_transaction_evm_context(&mut self, tx: &TransactionsWithSender) { let timer = start_timer_vec(&TRANSACTION_PROCESSING_TIMES, &["modify_tx"]); let block_number = self.block_number(); @@ -118,24 +138,14 @@ impl<'a> BlockExecutor<'a> { stop_timer(timer); } - pub fn commit(&mut self, evm_state: HashMap) { - let timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["commit_state"]); - self.evm.db_mut().commit(evm_state); - stop_timer(timer); - } - - pub fn transact(&mut self) -> anyhow::Result { - Ok(self.evm.transact()?) - } - - pub fn increment_balances( + fn increment_balances( &mut self, balances: impl IntoIterator, ) -> anyhow::Result<()> { Ok(self.evm.db_mut().increment_balances(balances)?) } - pub fn process_dao_fork(&mut self) -> anyhow::Result<()> { + fn process_dao_fork(&mut self) -> anyhow::Result<()> { // drain balances from DAO hardfork accounts let drained_balances = self.evm.db_mut().drain_balances(DAO_HARDKFORK_ACCOUNTS)?; let drained_balance_sum: u128 = drained_balances.iter().sum(); @@ -146,7 +156,7 @@ impl<'a> BlockExecutor<'a> { Ok(()) } - pub fn commit_bundle(&mut self) -> anyhow::Result { + pub fn commit_bundle(mut self) -> anyhow::Result { let timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["commit_bundle"]); self.evm .db_mut() @@ -175,11 +185,50 @@ impl<'a> BlockExecutor<'a> { }) } + fn process_genesis(&mut self, header: &Header) -> anyhow::Result<()> { + ensure!( + header.number == 0, + "Trying to initialize genesis but received block {}", + header.number, + ); + let genesis_file = if Path::new(GENESIS_STATE_FILE).is_file() { + File::open(GENESIS_STATE_FILE)? + } else if Path::new(TEST_GENESIS_STATE_FILE).is_file() { + File::open(TEST_GENESIS_STATE_FILE)? + } else { + bail!("Genesis file not found") + }; + let genesis: GenesisConfig = serde_json::from_reader(BufReader::new(genesis_file))?; + + for (address, alloc_balance) in genesis.alloc { + let address_hash = keccak256(address); + let mut account = RocksAccount::default(); + account.balance += alloc_balance.balance; + self.evm.db().database.trie.lock().insert( + address_hash.as_ref(), + &alloy_rlp::encode(AccountStateInfo::from(&account)), + )?; + self.evm + .db() + .database + .db + .put(address_hash, alloy_rlp::encode(account))?; + } + + Ok(()) + } + pub fn execute_block(&mut self, block: &ProcessedBlock) -> anyhow::Result<()> { info!("State EVM processing block {}", block.header.number); let execute_block_timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["execute_block"]); - self.set_evm_environment_from_block(block); + + if block.header.number == 0 { + self.process_genesis(&block.header)?; + return Ok(()); + } + + self.set_evm_environment_from_block(&block.header); // execute transactions let cumulative_transaction_timer = @@ -189,7 +238,9 @@ impl<'a> BlockExecutor<'a> { let transaction_timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["transaction"]); let evm_result = self.execute_transaction(transaction)?; block_gas_used += evm_result.result.gas_used(); - self.commit(evm_result.state); + let timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["commit_state"]); + self.evm.db_mut().commit(evm_result.state); + stop_timer(timer); stop_timer(transaction_timer); } stop_timer(cumulative_transaction_timer); @@ -203,7 +254,7 @@ impl<'a> BlockExecutor<'a> { self.cumulative_gas_used += block_gas_used; // update beneficiary - let timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["update_beneficiary"]); + let timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["beneficiary_timer"]); let _ = self.increment_balances(get_block_reward(block)); // check if dao fork, if it is drain accounts and transfer it to beneficiary @@ -211,7 +262,7 @@ impl<'a> BlockExecutor<'a> { self.process_dao_fork()?; } - self.manage_block_hash_serve_window(block)?; + self.manage_block_hash_serve_window(&block.header)?; stop_timer(timer); stop_timer(execute_block_timer); @@ -244,7 +295,7 @@ impl<'a> BlockExecutor<'a> { &mut self.evm.context.evm.inner.db, )? } else { - self.transact()? + self.evm.transact()? }; stop_timer(timer); @@ -252,19 +303,19 @@ impl<'a> BlockExecutor<'a> { } /// insert block hash into database and remove old one - fn manage_block_hash_serve_window(&self, block: &ProcessedBlock) -> anyhow::Result<()> { + fn manage_block_hash_serve_window(&self, header: &Header) -> anyhow::Result<()> { let timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["insert_blockhash"]); self.evm.db().database.db.put( - keccak256(B256::from(U256::from(block.header.number))), - block.header.hash(), + keccak256(B256::from(U256::from(header.number))), + header.hash(), )?; - if block.header.number >= BLOCKHASH_SERVE_WINDOW { + if header.number >= BLOCKHASH_SERVE_WINDOW { self.evm .db() .database .db .delete(keccak256(B256::from(U256::from( - block.header.number - BLOCKHASH_SERVE_WINDOW, + header.number - BLOCKHASH_SERVE_WINDOW, ))))?; } stop_timer(timer); @@ -279,7 +330,7 @@ impl<'a> BlockExecutor<'a> { self.evm.db().bundle_size_hint() } - pub fn block_number(&self) -> u64 { + fn block_number(&self) -> u64 { self.evm.context.evm.env.block.number.to::() } } diff --git a/trin-execution/src/execution.rs b/trin-execution/src/execution.rs index e614a1bda..97ae6e3eb 100644 --- a/trin-execution/src/execution.rs +++ b/trin-execution/src/execution.rs @@ -1,14 +1,11 @@ -use alloy_primitives::{keccak256, Address, Bytes, B256, U256}; +use alloy_primitives::{keccak256, Address, Bytes, B256}; use alloy_rlp::Decodable; use anyhow::{anyhow, bail, ensure}; use eth_trie::{RootWithTrieDiff, Trie}; use ethportal_api::types::state_trie::account_state::AccountState as AccountStateInfo; -use serde::{Deserialize, Serialize}; use std::{ - collections::{BTreeSet, HashMap}, - fs::File, - io::BufReader, - path::{Path, PathBuf}, + collections::BTreeSet, + path::PathBuf, sync::Arc, time::{Duration, Instant}, }; @@ -29,18 +26,6 @@ use crate::{ use super::{config::StateConfig, types::trie_proof::TrieProof, utils::address_to_nibble_path}; -#[derive(Debug, Serialize, Deserialize)] -struct AllocBalance { - balance: U256, -} - -#[derive(Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -struct GenesisConfig { - alloc: HashMap, - state_root: B256, -} - pub struct TrinExecution { pub database: EvmDB, pub config: StateConfig, @@ -49,9 +34,6 @@ pub struct TrinExecution { pub node_data_directory: PathBuf, } -const GENESIS_STATE_FILE: &str = "trin-execution/resources/genesis/mainnet.json"; -const TEST_GENESIS_STATE_FILE: &str = "resources/genesis/mainnet.json"; - impl TrinExecution { pub async fn new(path: Option, config: StateConfig) -> anyhow::Result { let node_data_directory = match path { @@ -77,76 +59,13 @@ impl TrinExecution { }) } - fn initialize_genesis(&mut self) -> anyhow::Result { - ensure!( - self.execution_position.next_block_number() == 0, - "Trying to initialize genesis but received block {}", - self.execution_position.next_block_number(), - ); - - let genesis_file = if Path::new(GENESIS_STATE_FILE).is_file() { - File::open(GENESIS_STATE_FILE)? - } else if Path::new(TEST_GENESIS_STATE_FILE).is_file() { - File::open(TEST_GENESIS_STATE_FILE)? - } else { - bail!("Genesis file not found") - }; - let genesis: GenesisConfig = serde_json::from_reader(BufReader::new(genesis_file))?; - - for (address, alloc_balance) in genesis.alloc { - let mut account = Account::default(); - account.balance += alloc_balance.balance; - self.database.trie.lock().insert( - keccak256(address).as_ref(), - &alloy_rlp::encode(AccountStateInfo::from(&account)), - )?; - self.database - .db - .put(keccak256(address.as_slice()), alloy_rlp::encode(account))?; - } - - let root_with_trie_diff = self.get_root_with_trie_diff()?; - ensure!( - root_with_trie_diff.root == genesis.state_root, - "Root doesn't match state root from genesis file" - ); - - self.execution_position.set_next_block_number( - self.database.db.clone(), - 1, - root_with_trie_diff.root, - )?; - - Ok(root_with_trie_diff) - } - - /// This is a lot faster then process_block() as it executes the range in memory, but we won't - /// return the trie diff so you can use this to sync up to the block you want, then use - /// `process_block()` to get the trie diff to gossip on the state bridge + /// Processes up to end block number (inclusive) and returns the root with trie diff + /// If the state cache gets too big, we will commit the state to the database early pub async fn process_range_of_blocks(&mut self, end: u64) -> anyhow::Result { let start = self.execution_position.next_block_number(); ensure!( end >= start, - "End block number {} is less than start block number {}", - end, - start - ); - - // If we are starting from the beginning, we need to initialize the genesis state - if start == 0 { - self.era_manager.lock().await.get_next_block().await?; - let result = self.initialize_genesis()?; - if end == 0 { - return Ok(result); - } - } - - let start = self.execution_position.next_block_number(); - ensure!( - end >= start, - "End block number {} is less than start block number {}", - end, - start + "End block number {end} is less than start block number {start}", ); info!("Processing blocks from {} to {} (inclusive)", start, end); @@ -156,7 +75,7 @@ impl TrinExecution { self.node_data_directory.clone(), ); let range_start = Instant::now(); - let mut last_executed_block_number = start - 1; + let mut last_executed_block_number = u64::MAX; let mut last_state_root = self.execution_position.state_root(); for block_number in start..=end { let timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["fetching_block_from_era"]); @@ -190,8 +109,8 @@ impl TrinExecution { let root_with_trie_diff = block_executor.commit_bundle()?; ensure!( root_with_trie_diff.root == last_state_root, - "State root doesn't match! Irreversible! Block number: {}", - last_executed_block_number + "State root doesn't match! Irreversible! Block number: {last_executed_block_number} | Generated root: {} | Expected root: {last_state_root}", + root_with_trie_diff.root ); let timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["set_block_execution_number"]); @@ -205,8 +124,9 @@ impl TrinExecution { Ok(root_with_trie_diff) } - pub async fn process_block(&mut self, block_number: u64) -> anyhow::Result { - self.process_range_of_blocks(block_number).await + pub async fn process_next_block(&mut self) -> anyhow::Result { + self.process_range_of_blocks(self.execution_position.next_block_number()) + .await } pub fn next_block_number(&self) -> u64 { @@ -314,10 +234,7 @@ mod tests { let raw_era1 = fs::read("../test_assets/era1/mainnet-00000-5ec1ffb8.era1").unwrap(); let processed_era = process_era1_file(raw_era1, 0).unwrap(); for block in processed_era.blocks { - trin_execution - .process_block(block.header.number) - .await - .unwrap(); + trin_execution.process_next_block().await.unwrap(); assert_eq!(trin_execution.get_root().unwrap(), block.header.state_root); } } @@ -331,7 +248,7 @@ mod tests { ) .await .unwrap(); - trin_execution.process_block(0).await.unwrap(); + trin_execution.process_next_block().await.unwrap(); let valid_proof = trin_execution .get_proof(Address::from_hex("0x001d14804b399c6ef80e64576f657660804fec0b").unwrap()) .unwrap(); diff --git a/trin-execution/src/storage/error.rs b/trin-execution/src/storage/error.rs index 3834b28e7..42b5d5478 100644 --- a/trin-execution/src/storage/error.rs +++ b/trin-execution/src/storage/error.rs @@ -1,6 +1,6 @@ use thiserror::Error; -#[derive(Debug, PartialEq, Eq, Error)] +#[derive(Debug, Error)] pub enum EVMError { #[error("trie error {0}")] Trie(#[from] eth_trie::TrieError), @@ -11,6 +11,9 @@ pub enum EVMError { #[error("rocksdb error {0}")] DB(#[from] rocksdb::Error), + #[error("ethportal error {0}")] + ANYHOW(#[from] anyhow::Error), + #[error("not found database error {0}")] NotFound(String), diff --git a/trin-execution/src/storage/evm_db.rs b/trin-execution/src/storage/evm_db.rs index 5ba2d6c40..cf36944cf 100644 --- a/trin-execution/src/storage/evm_db.rs +++ b/trin-execution/src/storage/evm_db.rs @@ -110,11 +110,10 @@ impl EvmDB { let plain_state_some_account_timer = start_commit_timer("account:plain_state_some_account"); let timer = start_commit_timer("account:fetch_account_from_db"); - let raw_account = self.db.get(address_hash)?.unwrap_or_default(); + let fetched_rocks_account = self.fetch_account(address_hash)?; stop_timer(timer); - let rocks_account = if !raw_account.is_empty() { - let decoded_account = RocksAccount::decode(&mut raw_account.as_slice())?; + let rocks_account = if let Some(decoded_account) = fetched_rocks_account { RocksAccount::from_account_info(account_info, decoded_account.storage_root) } else { RocksAccount::from_account_info(account_info, EMPTY_ROOT_HASH) @@ -143,13 +142,11 @@ impl EvmDB { timer_label: &str, ) -> anyhow::Result<()> { // load account from db - let Some(raw_account) = self.db.get(address_hash)? else { + let Some(mut rocks_account) = self.fetch_account(address_hash)? else { return Ok(()); }; let timer = start_commit_timer(timer_label); - let mut rocks_account = RocksAccount::decode(&mut raw_account.as_slice())?; - // wipe storage trie and db if rocks_account.storage_root != EMPTY_ROOT_HASH { let account_db = AccountDB::new(address_hash, self.db.clone()); @@ -198,15 +195,7 @@ impl EvmDB { let timer = start_commit_timer("storage:apply_updates"); let account_db = AccountDB::new(address_hash, self.db.clone()); - let mut rocks_account: RocksAccount = self - .db - .get(address_hash)? - .map(|raw_account| { - let rocks_account: RocksAccount = Decodable::decode(&mut raw_account.as_slice()) - .expect("Decoding account should never fail"); - rocks_account - }) - .unwrap_or_default(); + let mut rocks_account = self.fetch_account(address_hash)?.unwrap_or_default(); let mut trie = if rocks_account.storage_root == EMPTY_ROOT_HASH { EthTrie::new(Arc::new(account_db)) @@ -265,11 +254,6 @@ impl EvmDB { } if !storage.is_empty() { - // review comment: note that commit_storage_changes would have to load RocksAccount - // from db this means that it's possible that we will have to load - // it twice but I think it's quite uncommon, to have both - // wipe_storage and non-empty storage, so I don't think it's big - // deal self.commit_storage_changes(address_hash, storage)?; } stop_timer(plain_state_storage_timer); @@ -322,6 +306,13 @@ impl EvmDB { Ok(()) } + + fn fetch_account(&self, address_hash: B256) -> anyhow::Result> { + match self.db.get(address_hash)? { + Some(raw_account) => Ok(Some(RocksAccount::decode(&mut raw_account.as_slice())?)), + None => Ok(None), + } + } } impl Database for EvmDB { @@ -349,17 +340,13 @@ impl DatabaseRef for EvmDB { fn basic_ref(&self, address: Address) -> Result, Self::Error> { let timer = start_processing_timer("database_get_basic"); - let result = match self.db.get(keccak256(address))? { - Some(raw_account) => { - let account: RocksAccount = Decodable::decode(&mut raw_account.as_slice())?; - - Ok(Some(AccountInfo { - balance: account.balance, - nonce: account.nonce, - code_hash: account.code_hash, - code: None, - })) - } + let result = match self.fetch_account(keccak256(address))? { + Some(account) => Ok(Some(AccountInfo { + balance: account.balance, + nonce: account.nonce, + code_hash: account.code_hash, + code: None, + })), None => Ok(None), }; stop_timer(timer); @@ -379,8 +366,8 @@ impl DatabaseRef for EvmDB { fn storage_ref(&self, address: Address, index: U256) -> Result { let timer = start_processing_timer("database_get_storage"); let address_hash = keccak256(address); - let account: RocksAccount = match self.db.get(address_hash)? { - Some(raw_account) => Decodable::decode(&mut raw_account.as_slice())?, + let account: RocksAccount = match self.fetch_account(address_hash)? { + Some(account) => account, None => return Err(Self::Error::NotFound("storage".to_string())), }; let account_db = AccountDB::new(address_hash, self.db.clone()); diff --git a/trin-execution/src/trie_walker.rs b/trin-execution/src/trie_walker.rs index c81ae296c..ae3581184 100644 --- a/trin-execution/src/trie_walker.rs +++ b/trin-execution/src/trie_walker.rs @@ -178,7 +178,7 @@ mod tests { ) .await .unwrap(); - let RootWithTrieDiff { trie_diff, .. } = trin_execution.process_block(0).await.unwrap(); + let RootWithTrieDiff { trie_diff, .. } = trin_execution.process_next_block().await.unwrap(); let valid_proof = trin_execution .get_proof(Address::from_hex("0x001d14804b399c6ef80e64576f657660804fec0b").unwrap()) .unwrap(); diff --git a/trin-execution/tests/content_generation.rs b/trin-execution/tests/content_generation.rs index 42e60d503..a2cf69fde 100644 --- a/trin-execution/tests/content_generation.rs +++ b/trin-execution/tests/content_generation.rs @@ -51,7 +51,7 @@ async fn test_we_can_generate_content_key_values_up_to_x() -> Result<()> { let RootWithTrieDiff { root: root_hash, trie_diff: changed_nodes, - } = trin_execution.process_block(block_number).await?; + } = trin_execution.process_next_block().await?; let block = trin_execution .era_manager .lock() From b792d4b1d6e956f7d9d3a8b14f18ba675bb2f1d4 Mon Sep 17 00:00:00 2001 From: Kolby Moroz Liebl <31669092+KolbyML@users.noreply.github.com> Date: Sun, 8 Sep 2024 02:47:05 -0600 Subject: [PATCH 11/11] fix: pr concerns --- trin-execution/src/evm/block_executor.rs | 13 +++----- trin-execution/src/execution.rs | 32 +++++++++---------- trin-execution/src/storage/evm_db.rs | 13 ++------ .../src/storage/execution_position.rs | 12 +++---- 4 files changed, 27 insertions(+), 43 deletions(-) diff --git a/trin-execution/src/evm/block_executor.rs b/trin-execution/src/evm/block_executor.rs index a3d5cf019..f746726c0 100644 --- a/trin-execution/src/evm/block_executor.rs +++ b/trin-execution/src/evm/block_executor.rs @@ -185,12 +185,7 @@ impl<'a> BlockExecutor<'a> { }) } - fn process_genesis(&mut self, header: &Header) -> anyhow::Result<()> { - ensure!( - header.number == 0, - "Trying to initialize genesis but received block {}", - header.number, - ); + fn process_genesis(&mut self) -> anyhow::Result<()> { let genesis_file = if Path::new(GENESIS_STATE_FILE).is_file() { File::open(GENESIS_STATE_FILE)? } else if Path::new(TEST_GENESIS_STATE_FILE).is_file() { @@ -224,7 +219,7 @@ impl<'a> BlockExecutor<'a> { let execute_block_timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["execute_block"]); if block.header.number == 0 { - self.process_genesis(&block.header)?; + self.process_genesis()?; return Ok(()); } @@ -254,17 +249,17 @@ impl<'a> BlockExecutor<'a> { self.cumulative_gas_used += block_gas_used; // update beneficiary - let timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["beneficiary_timer"]); + let beneficiary_timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["update_beneficiary"]); let _ = self.increment_balances(get_block_reward(block)); // check if dao fork, if it is drain accounts and transfer it to beneficiary if block.header.number == get_spec_block_number(SpecId::DAO_FORK) { self.process_dao_fork()?; } + stop_timer(beneficiary_timer); self.manage_block_hash_serve_window(&block.header)?; - stop_timer(timer); stop_timer(execute_block_timer); set_int_gauge_vec(&BLOCK_HEIGHT, block.header.number as i64, &[]); Ok(()) diff --git a/trin-execution/src/execution.rs b/trin-execution/src/execution.rs index 97ae6e3eb..1f36c0dac 100644 --- a/trin-execution/src/execution.rs +++ b/trin-execution/src/execution.rs @@ -2,7 +2,7 @@ use alloy_primitives::{keccak256, Address, Bytes, B256}; use alloy_rlp::Decodable; use anyhow::{anyhow, bail, ensure}; use eth_trie::{RootWithTrieDiff, Trie}; -use ethportal_api::types::state_trie::account_state::AccountState as AccountStateInfo; +use ethportal_api::{types::state_trie::account_state::AccountState as AccountStateInfo, Header}; use std::{ collections::BTreeSet, path::PathBuf, @@ -60,7 +60,8 @@ impl TrinExecution { } /// Processes up to end block number (inclusive) and returns the root with trie diff - /// If the state cache gets too big, we will commit the state to the database early + /// If the state cache gets too big, we will commit the state to the database early and return + /// the function early along with it pub async fn process_range_of_blocks(&mut self, end: u64) -> anyhow::Result { let start = self.execution_position.next_block_number(); ensure!( @@ -75,8 +76,7 @@ impl TrinExecution { self.node_data_directory.clone(), ); let range_start = Instant::now(); - let mut last_executed_block_number = u64::MAX; - let mut last_state_root = self.execution_position.state_root(); + let mut last_executed_block_header: Option
= None; for block_number in start..=end { let timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["fetching_block_from_era"]); let block = self @@ -89,8 +89,7 @@ impl TrinExecution { stop_timer(timer); block_executor.execute_block(&block)?; - last_executed_block_number = block_number; - last_state_root = block.header.state_root; + last_executed_block_header = Some(block.header); // Commit the bundle if we have reached the limits, to prevent to much memory usage // We won't use this during the dos attack to avoid writing empty accounts to disk @@ -106,27 +105,28 @@ impl TrinExecution { } } + let last_executed_block_header = + last_executed_block_header.expect("At least one block must have been executed"); + let root_with_trie_diff = block_executor.commit_bundle()?; ensure!( - root_with_trie_diff.root == last_state_root, - "State root doesn't match! Irreversible! Block number: {last_executed_block_number} | Generated root: {} | Expected root: {last_state_root}", - root_with_trie_diff.root + root_with_trie_diff.root == last_executed_block_header.state_root, + "State root doesn't match! Irreversible! Block number: {} | Generated root: {} | Expected root: {}", + last_executed_block_header.number, + root_with_trie_diff.root, + last_executed_block_header.state_root ); let timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["set_block_execution_number"]); - self.execution_position.set_next_block_number( - self.database.db.clone(), - last_executed_block_number + 1, - root_with_trie_diff.root, - )?; + self.execution_position + .update_position(self.database.db.clone(), last_executed_block_header)?; stop_timer(timer); Ok(root_with_trie_diff) } pub async fn process_next_block(&mut self) -> anyhow::Result { - self.process_range_of_blocks(self.execution_position.next_block_number()) - .await + self.process_range_of_blocks(self.next_block_number()).await } pub fn next_block_number(&self) -> u64 { diff --git a/trin-execution/src/storage/evm_db.rs b/trin-execution/src/storage/evm_db.rs index cf36944cf..3d0776fdc 100644 --- a/trin-execution/src/storage/evm_db.rs +++ b/trin-execution/src/storage/evm_db.rs @@ -110,11 +110,11 @@ impl EvmDB { let plain_state_some_account_timer = start_commit_timer("account:plain_state_some_account"); let timer = start_commit_timer("account:fetch_account_from_db"); - let fetched_rocks_account = self.fetch_account(address_hash)?; + let existing_rocks_account = self.fetch_account(address_hash)?; stop_timer(timer); - let rocks_account = if let Some(decoded_account) = fetched_rocks_account { - RocksAccount::from_account_info(account_info, decoded_account.storage_root) + let rocks_account = if let Some(existing_rocks_account) = existing_rocks_account { + RocksAccount::from_account_info(account_info, existing_rocks_account.storage_root) } else { RocksAccount::from_account_info(account_info, EMPTY_ROOT_HASH) }; @@ -282,13 +282,6 @@ impl EvmDB { // Write Contract Code // TODO: Delete contract code if no accounts point to it: https://github.com/ethereum/trin/issues/1428 - // This can happen in 1 of 2 cases - // - the account is deleted and the contract code is not used by any other accounts - // - we do a revert and the contract code is not used by any other accounts - // The solution would require keeping a count of how many accounts point to a contract code - // and deleting the contract code if the count is 0. - // This is very low priority due to this issue being very rare and it not taking up much - // storage let timer = start_commit_timer("contract:committing_contracts_total"); for (hash, bytecode) in plain_state.contracts { let timer = start_commit_timer("committing_contract"); diff --git a/trin-execution/src/storage/execution_position.rs b/trin-execution/src/storage/execution_position.rs index 58e53195a..43b2c959d 100644 --- a/trin-execution/src/storage/execution_position.rs +++ b/trin-execution/src/storage/execution_position.rs @@ -2,6 +2,7 @@ use std::sync::Arc; use alloy_consensus::EMPTY_ROOT_HASH; use alloy_rlp::{Decodable, RlpDecodable, RlpEncodable}; +use ethportal_api::Header; use revm_primitives::B256; use rocksdb::DB as RocksDB; use serde::{Deserialize, Serialize}; @@ -46,14 +47,9 @@ impl ExecutionPosition { self.state_root } - pub fn set_next_block_number( - &mut self, - db: Arc, - block_number: u64, - state_root: B256, - ) -> anyhow::Result<()> { - self.next_block_number = block_number; - self.state_root = state_root; + pub fn update_position(&mut self, db: Arc, header: Header) -> anyhow::Result<()> { + self.next_block_number = header.number + 1; + self.state_root = header.state_root; db.put(EXECUTION_POSITION_DB_KEY, alloy_rlp::encode(self))?; Ok(()) }