Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions cmd/ef_tests/blockchain/test_runner.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::{collections::HashMap, path::Path};

use crate::types::{BlockWithRLP, TestUnit};
use ethrex_blockchain::{add_block, fork_choice::apply_fork_choice};
use ethrex_blockchain::{fork_choice::apply_fork_choice, Blockchain};
use ethrex_common::types::{
Account as CoreAccount, Block as CoreBlock, BlockHeader as CoreBlockHeader,
};
Expand All @@ -20,8 +20,8 @@ pub fn run_ef_test(test_key: &str, test: &TestUnit) {
// Check world_state
check_prestate_against_db(test_key, test, &store);

let blockchain = Blockchain::default_with_store(store.clone());
// Execute all blocks in test

for block_fixture in test.blocks.iter() {
let expects_exception = block_fixture.expect_exception.is_some();
if exception_in_rlp_decoding(block_fixture) {
Expand All @@ -33,7 +33,7 @@ pub fn run_ef_test(test_key: &str, test: &TestUnit) {
let hash = block.hash();

// Attempt to add the block as the head of the chain
let chain_result = add_block(block, &store);
let chain_result = blockchain.add_block(block);
match chain_result {
Err(error) => {
assert!(
Expand Down
67 changes: 11 additions & 56 deletions cmd/ethrex/ethrex.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use bytes::Bytes;
use directories::ProjectDirs;
use ethrex_blockchain::{add_block, fork_choice::apply_fork_choice};
use ethrex_blockchain::Blockchain;
use ethrex_common::types::{Block, Genesis};
use ethrex_p2p::{
kademlia::KademliaTable,
Expand All @@ -10,7 +10,7 @@ use ethrex_p2p::{
};
use ethrex_rlp::decode::RLPDecode;
use ethrex_storage::{EngineType, Store};
use ethrex_vm::{backends::EVM, EVM_BACKEND};
use ethrex_vm::backends::EVM;
use k256::ecdsa::SigningKey;
use local_ip_address::local_ip;
use rand::rngs::OsRng;
Expand Down Expand Up @@ -152,8 +152,6 @@ async fn main() {
let sync_mode = sync_mode(&matches);

let evm = matches.get_one::<EVM>("evm").unwrap_or(&EVM::REVM);
let evm = EVM_BACKEND.get_or_init(|| evm.clone());
info!("EVM_BACKEND set to: {:?}", evm);

let path = path::PathBuf::from(data_dir.clone());
let store: Store = if path.ends_with("memory") {
Expand All @@ -172,6 +170,7 @@ async fn main() {
}
Store::new(&data_dir, engine_type).expect("Failed to create Store")
};
let blockchain = Blockchain::new(evm.clone(), store.clone());

let genesis = read_genesis_file(&network);
store
Expand All @@ -181,7 +180,7 @@ async fn main() {
if let Some(chain_rlp_path) = matches.get_one::<String>("import") {
info!("Importing blocks from chain file: {}", chain_rlp_path);
let blocks = read_chain_file(chain_rlp_path);
import_blocks(&store, &blocks);
blockchain.import_blocks(&blocks);
}

if let Some(blocks_path) = matches.get_one::<String>("import_dir") {
Expand All @@ -200,7 +199,7 @@ async fn main() {
blocks.push(read_block_file(s));
}

import_blocks(&store, &blocks);
blockchain.import_blocks(&blocks);
}

let jwt_secret = read_jwtsecret_file(authrpc_jwtsecret);
Expand Down Expand Up @@ -251,7 +250,12 @@ async fn main() {
// Create a cancellation_token for long_living tasks
let cancel_token = tokio_util::sync::CancellationToken::new();
// Create SyncManager
let syncer = SyncManager::new(peer_table.clone(), sync_mode, cancel_token.clone());
let syncer = SyncManager::new(
peer_table.clone(),
sync_mode,
cancel_token.clone(),
blockchain,
);

// TODO: Check every module starts properly.
let tracker = TaskTracker::new();
Expand Down Expand Up @@ -419,55 +423,6 @@ fn set_datadir(datadir: &str) -> String {
.to_owned()
}

fn import_blocks(store: &Store, blocks: &Vec<Block>) {
let size = blocks.len();
for block in blocks {
let hash = block.hash();
info!(
"Adding block {} with hash {:#x}.",
block.header.number, hash
);
let result = add_block(block, store);
if let Some(error) = result.err() {
warn!(
"Failed to add block {} with hash {:#x}: {}.",
block.header.number, hash, error
);
}
if store
.update_latest_block_number(block.header.number)
.is_err()
{
error!("Fatal: added block {} but could not update the block number -- aborting block import", block.header.number);
break;
};
if store
.set_canonical_block(block.header.number, hash)
.is_err()
{
error!(
"Fatal: added block {} but could not set it as canonical -- aborting block import",
block.header.number
);
break;
};
}
if let Some(last_block) = blocks.last() {
let hash = last_block.hash();
match EVM_BACKEND.get() {
Some(EVM::LEVM) => {
// We are allowing this not to unwrap so that tests can run even if block execution results in the wrong root hash with LEVM.
let _ = apply_fork_choice(store, hash, hash, hash);
}
// This means we are using REVM as default
Some(EVM::REVM) | None => {
apply_fork_choice(store, hash, hash, hash).unwrap();
}
}
}
info!("Added {} blocks to blockchain", size);
}

async fn store_known_peers(table: Arc<Mutex<KademliaTable>>, file_path: PathBuf) {
let mut connected_peers = vec![];

Expand Down
1 change: 0 additions & 1 deletion cmd/ethrex_l2/src/commands/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ use crate::config::EthrexL2Config;
use bytes::Bytes;
use clap::Subcommand;
use ethereum_types::{Address, H256, U256};
use ethrex_common::types::Transaction;
use ethrex_l2_sdk::calldata::{encode_calldata, Value};
use ethrex_l2_sdk::merkle_tree::merkle_proof;
use ethrex_l2_sdk::{get_withdrawal_hash, COMMON_BRIDGE_L2_ADDRESS, L2_WITHDRAW_SIGNATURE};
Expand Down
184 changes: 126 additions & 58 deletions crates/blockchain/blockchain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@ pub mod mempool;
pub mod payload;
mod smoke_test;

use std::{ops::Div, time::Instant};
use tracing::info;

use error::{ChainError, InvalidBlockError};
use ethrex_common::constants::GAS_PER_BLOB;
use ethrex_common::types::requests::{compute_requests_hash, EncodedRequests, Requests};
Expand All @@ -17,71 +14,142 @@ use ethrex_common::types::{
BlockHeader, BlockNumber, ChainConfig, EIP4844Transaction, Receipt, Transaction,
};
use ethrex_common::H256;
use std::{ops::Div, time::Instant};

use ethrex_storage::error::StoreError;
use ethrex_storage::Store;
use ethrex_vm::backends::BlockExecutionResult;
use ethrex_vm::backends::EVM;
use ethrex_vm::db::evm_state;
use ethrex_vm::{backends::BlockExecutionResult, get_evm_backend_or_default};
use fork_choice::apply_fork_choice;
use tracing::{error, info, warn};

//TODO: Implement a struct Chain or BlockChain to encapsulate
//functionality and canonical chain state and config

/// Adds a new block to the store. It may or may not be canonical, as long as its ancestry links
/// with the canonical chain and its parent's post-state is calculated. It doesn't modify the
/// canonical chain/head. Fork choice needs to be updated for that in a separate step.
///
/// Performs pre and post execution validation, and updates the database with the post state.
pub fn add_block(block: &Block, storage: &Store) -> Result<(), ChainError> {
let since = Instant::now();

let block_hash = block.header.compute_block_hash();

// Validate if it can be the new head and find the parent
let Ok(parent_header) = find_parent_header(&block.header, storage) else {
// If the parent is not present, we store it as pending.
storage.add_pending_block(block.clone())?;
return Err(ChainError::ParentNotFound);
};
let mut state = evm_state(storage.clone(), block.header.parent_hash);
let chain_config = state.chain_config().map_err(ChainError::from)?;

// Validate the block pre-execution
validate_block(block, &parent_header, &chain_config)?;
let BlockExecutionResult {
receipts,
requests,
account_updates,
} = get_evm_backend_or_default().execute_block(block, &mut state)?;

validate_gas_used(&receipts, &block.header)?;

// Apply the account updates over the last block's state and compute the new state root
let new_state_root = state
.database()
.ok_or(ChainError::StoreError(StoreError::MissingStore))?
.apply_account_updates(block.header.parent_hash, &account_updates)?
.ok_or(ChainError::ParentStateNotFound)?;

// Check state root matches the one in block header after execution
validate_state_root(&block.header, new_state_root)?;

// Check receipts root matches the one in block header after execution
validate_receipts_root(&block.header, &receipts)?;

// Processes requests from receipts, computes the requests_hash and compares it against the header
validate_requests_hash(&block.header, &chain_config, &requests)?;

store_block(storage, block.clone())?;
store_receipts(storage, receipts, block_hash)?;

let interval = Instant::now().duration_since(since).as_millis();
if interval != 0 {
let as_gigas = (block.header.gas_used as f64).div(10_f64.powf(9_f64));
let throughput = (as_gigas) / (interval as f64) * 1000_f64;
info!("[METRIC] BLOCK EXECUTION THROUGHPUT: {throughput} Gigagas/s TIME SPENT: {interval} msecs");
#[derive(Debug, Clone)]
pub struct Blockchain {
pub vm: EVM,
pub storage: Store,
}

impl Blockchain {
pub fn new(evm: EVM, store: Store) -> Self {
Self {
vm: evm,
storage: store,
}
}

Ok(())
pub fn default_with_store(store: Store) -> Self {
Self {
vm: Default::default(),
storage: store,
}
}

pub fn add_block(&self, block: &Block) -> Result<(), ChainError> {
let since = Instant::now();

let block_hash = block.header.compute_block_hash();

// Validate if it can be the new head and find the parent
let Ok(parent_header) = find_parent_header(&block.header, &self.storage) else {
// If the parent is not present, we store it as pending.
self.storage.add_pending_block(block.clone())?;
return Err(ChainError::ParentNotFound);
};
let mut state = evm_state(self.storage.clone(), block.header.parent_hash);
let chain_config = state.chain_config().map_err(ChainError::from)?;

// Validate the block pre-execution
validate_block(block, &parent_header, &chain_config)?;
let BlockExecutionResult {
receipts,
requests,
account_updates,
} = self.vm.execute_block(block, &mut state)?;

validate_gas_used(&receipts, &block.header)?;

// Apply the account updates over the last block's state and compute the new state root
let new_state_root = state
.database()
.ok_or(ChainError::StoreError(StoreError::MissingStore))?
.apply_account_updates(block.header.parent_hash, &account_updates)?
.ok_or(ChainError::ParentStateNotFound)?;

// Check state root matches the one in block header after execution
validate_state_root(&block.header, new_state_root)?;

// Check receipts root matches the one in block header after execution
validate_receipts_root(&block.header, &receipts)?;

// Processes requests from receipts, computes the requests_hash and compares it against the header
validate_requests_hash(&block.header, &chain_config, &requests)?;

store_block(&self.storage, block.clone())?;
store_receipts(&self.storage, receipts, block_hash)?;

let interval = Instant::now().duration_since(since).as_millis();
if interval != 0 {
let as_gigas = (block.header.gas_used as f64).div(10_f64.powf(9_f64));
let throughput = (as_gigas) / (interval as f64) * 1000_f64;
info!("[METRIC] BLOCK EXECUTION THROUGHPUT: {throughput} Gigagas/s TIME SPENT: {interval} msecs");
}

Ok(())
}

//TODO: Forkchoice Update shouldn't be part of this function
pub fn import_blocks(&self, blocks: &Vec<Block>) {
let size = blocks.len();
for block in blocks {
let hash = block.hash();
info!(
"Adding block {} with hash {:#x}.",
block.header.number, hash
);
if let Err(error) = self.add_block(block) {
warn!(
"Failed to add block {} with hash {:#x}: {}.",
block.header.number, hash, error
);
}
if self
.storage
.update_latest_block_number(block.header.number)
.is_err()
{
error!("Fatal: added block {} but could not update the block number -- aborting block import", block.header.number);
break;
};
if self
.storage
.set_canonical_block(block.header.number, hash)
.is_err()
{
error!(
"Fatal: added block {} but could not set it as canonical -- aborting block import",
block.header.number
);
break;
};
}
if let Some(last_block) = blocks.last() {
let hash = last_block.hash();
match self.vm {
EVM::LEVM => {
// We are allowing this not to unwrap so that tests can run even if block execution results in the wrong root hash with LEVM.
let _ = apply_fork_choice(&self.storage, hash, hash, hash);
}
EVM::REVM => {
apply_fork_choice(&self.storage, hash, hash, hash).unwrap();
}
}
}
info!("Added {size} blocks to blockchain");
}
}

pub fn validate_requests_hash(
Expand Down
Loading