Skip to content

Commit de4ac09

Browse files
JereSaloilitteri
andcommitted
refactor(l1): transform crate blockchain into struct (#2047)
**Motivation** <!-- Why does this pull request exist? What are its goals? --> **Description** <!-- A clear and concise general description of the changes this PR introduces --> - Move methods like `add_block` to `Blockchain` struct. - I use `Blockchain::default_with_store(store)` in the case of tests. Default VM is REVM. The Store that I use for tests is the `InMemory` Engine Type. - Blockchain has attributes: VM and Store Note: I don't know if I should test these changes in some other way rather than just running the tests that we have. <!-- Link to issues: Resolves #111, Resolves #222 --> Closes #2018 --------- Co-authored-by: Ivan Litteri <[email protected]>
1 parent fec93bd commit de4ac09

File tree

12 files changed

+529
-446
lines changed

12 files changed

+529
-446
lines changed

cmd/ef_tests/blockchain/test_runner.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::{collections::HashMap, path::Path};
22

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

23+
let blockchain = Blockchain::default_with_store(store.clone());
2324
// Execute all blocks in test
24-
2525
for block_fixture in test.blocks.iter() {
2626
let expects_exception = block_fixture.expect_exception.is_some();
2727
if exception_in_rlp_decoding(block_fixture) {
@@ -33,7 +33,7 @@ pub fn run_ef_test(test_key: &str, test: &TestUnit) {
3333
let hash = block.hash();
3434

3535
// Attempt to add the block as the head of the chain
36-
let chain_result = add_block(block, &store);
36+
let chain_result = blockchain.add_block(block);
3737
match chain_result {
3838
Err(error) => {
3939
assert!(

cmd/ethrex/ethrex.rs

Lines changed: 11 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use bytes::Bytes;
22
use directories::ProjectDirs;
3-
use ethrex_blockchain::{add_block, fork_choice::apply_fork_choice};
3+
use ethrex_blockchain::Blockchain;
44
use ethrex_common::types::{Block, Genesis};
55
use ethrex_p2p::{
66
kademlia::KademliaTable,
@@ -10,7 +10,7 @@ use ethrex_p2p::{
1010
};
1111
use ethrex_rlp::decode::RLPDecode;
1212
use ethrex_storage::{EngineType, Store};
13-
use ethrex_vm::{backends::EVM, EVM_BACKEND};
13+
use ethrex_vm::backends::EVM;
1414
use k256::ecdsa::SigningKey;
1515
use local_ip_address::local_ip;
1616
use rand::rngs::OsRng;
@@ -159,8 +159,6 @@ async fn main() {
159159
let sync_mode = sync_mode(&matches);
160160

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

165163
let path = path::PathBuf::from(data_dir.clone());
166164
let store: Store = if path.ends_with("memory") {
@@ -179,6 +177,7 @@ async fn main() {
179177
}
180178
Store::new(&data_dir, engine_type).expect("Failed to create Store")
181179
};
180+
let blockchain = Blockchain::new(evm.clone(), store.clone());
182181

183182
let genesis = read_genesis_file(&network);
184183
store
@@ -188,7 +187,7 @@ async fn main() {
188187
if let Some(chain_rlp_path) = matches.get_one::<String>("import") {
189188
info!("Importing blocks from chain file: {}", chain_rlp_path);
190189
let blocks = read_chain_file(chain_rlp_path);
191-
import_blocks(&store, &blocks);
190+
blockchain.import_blocks(&blocks);
192191
}
193192

194193
if let Some(blocks_path) = matches.get_one::<String>("import_dir") {
@@ -207,7 +206,7 @@ async fn main() {
207206
blocks.push(read_block_file(s));
208207
}
209208

210-
import_blocks(&store, &blocks);
209+
blockchain.import_blocks(&blocks);
211210
}
212211

213212
let jwt_secret = read_jwtsecret_file(authrpc_jwtsecret);
@@ -258,7 +257,12 @@ async fn main() {
258257
// Create a cancellation_token for long_living tasks
259258
let cancel_token = tokio_util::sync::CancellationToken::new();
260259
// Create SyncManager
261-
let syncer = SyncManager::new(peer_table.clone(), sync_mode, cancel_token.clone());
260+
let syncer = SyncManager::new(
261+
peer_table.clone(),
262+
sync_mode,
263+
cancel_token.clone(),
264+
blockchain,
265+
);
262266

263267
// TODO: Check every module starts properly.
264268
let tracker = TaskTracker::new();
@@ -426,55 +430,6 @@ fn set_datadir(datadir: &str) -> String {
426430
.to_owned()
427431
}
428432

429-
fn import_blocks(store: &Store, blocks: &Vec<Block>) {
430-
let size = blocks.len();
431-
for block in blocks {
432-
let hash = block.hash();
433-
info!(
434-
"Adding block {} with hash {:#x}.",
435-
block.header.number, hash
436-
);
437-
let result = add_block(block, store);
438-
if let Some(error) = result.err() {
439-
warn!(
440-
"Failed to add block {} with hash {:#x}: {}.",
441-
block.header.number, hash, error
442-
);
443-
}
444-
if store
445-
.update_latest_block_number(block.header.number)
446-
.is_err()
447-
{
448-
error!("Fatal: added block {} but could not update the block number -- aborting block import", block.header.number);
449-
break;
450-
};
451-
if store
452-
.set_canonical_block(block.header.number, hash)
453-
.is_err()
454-
{
455-
error!(
456-
"Fatal: added block {} but could not set it as canonical -- aborting block import",
457-
block.header.number
458-
);
459-
break;
460-
};
461-
}
462-
if let Some(last_block) = blocks.last() {
463-
let hash = last_block.hash();
464-
match EVM_BACKEND.get() {
465-
Some(EVM::LEVM) => {
466-
// We are allowing this not to unwrap so that tests can run even if block execution results in the wrong root hash with LEVM.
467-
let _ = apply_fork_choice(store, hash, hash, hash);
468-
}
469-
// This means we are using REVM as default
470-
Some(EVM::REVM) | None => {
471-
apply_fork_choice(store, hash, hash, hash).unwrap();
472-
}
473-
}
474-
}
475-
info!("Added {} blocks to blockchain", size);
476-
}
477-
478433
async fn store_known_peers(table: Arc<Mutex<KademliaTable>>, file_path: PathBuf) {
479434
let mut connected_peers = vec![];
480435

cmd/ethrex_l2/src/commands/wallet.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ use crate::config::EthrexL2Config;
22
use bytes::Bytes;
33
use clap::Subcommand;
44
use ethereum_types::{Address, H256, U256};
5-
use ethrex_common::types::Transaction;
65
use ethrex_l2_sdk::calldata::{encode_calldata, Value};
76
use ethrex_l2_sdk::merkle_tree::merkle_proof;
87
use ethrex_l2_sdk::{get_withdrawal_hash, COMMON_BRIDGE_L2_ADDRESS, L2_WITHDRAW_SIGNATURE};

crates/blockchain/blockchain.rs

Lines changed: 126 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,6 @@ pub mod mempool;
55
pub mod payload;
66
mod smoke_test;
77

8-
use std::{ops::Div, time::Instant};
9-
use tracing::info;
10-
118
use error::{ChainError, InvalidBlockError};
129
use ethrex_common::constants::GAS_PER_BLOB;
1310
use ethrex_common::types::requests::{compute_requests_hash, EncodedRequests, Requests};
@@ -17,71 +14,142 @@ use ethrex_common::types::{
1714
BlockHeader, BlockNumber, ChainConfig, EIP4844Transaction, Receipt, Transaction,
1815
};
1916
use ethrex_common::H256;
17+
use std::{ops::Div, time::Instant};
2018

2119
use ethrex_storage::error::StoreError;
2220
use ethrex_storage::Store;
21+
use ethrex_vm::backends::BlockExecutionResult;
22+
use ethrex_vm::backends::EVM;
2323
use ethrex_vm::db::evm_state;
24-
use ethrex_vm::{backends::BlockExecutionResult, get_evm_backend_or_default};
24+
use fork_choice::apply_fork_choice;
25+
use tracing::{error, info, warn};
2526

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

29-
/// Adds a new block to the store. It may or may not be canonical, as long as its ancestry links
30-
/// with the canonical chain and its parent's post-state is calculated. It doesn't modify the
31-
/// canonical chain/head. Fork choice needs to be updated for that in a separate step.
32-
///
33-
/// Performs pre and post execution validation, and updates the database with the post state.
34-
pub fn add_block(block: &Block, storage: &Store) -> Result<(), ChainError> {
35-
let since = Instant::now();
36-
37-
let block_hash = block.header.compute_block_hash();
38-
39-
// Validate if it can be the new head and find the parent
40-
let Ok(parent_header) = find_parent_header(&block.header, storage) else {
41-
// If the parent is not present, we store it as pending.
42-
storage.add_pending_block(block.clone())?;
43-
return Err(ChainError::ParentNotFound);
44-
};
45-
let mut state = evm_state(storage.clone(), block.header.parent_hash);
46-
let chain_config = state.chain_config().map_err(ChainError::from)?;
47-
48-
// Validate the block pre-execution
49-
validate_block(block, &parent_header, &chain_config)?;
50-
let BlockExecutionResult {
51-
receipts,
52-
requests,
53-
account_updates,
54-
} = get_evm_backend_or_default().execute_block(block, &mut state)?;
55-
56-
validate_gas_used(&receipts, &block.header)?;
57-
58-
// Apply the account updates over the last block's state and compute the new state root
59-
let new_state_root = state
60-
.database()
61-
.ok_or(ChainError::StoreError(StoreError::MissingStore))?
62-
.apply_account_updates(block.header.parent_hash, &account_updates)?
63-
.ok_or(ChainError::ParentStateNotFound)?;
64-
65-
// Check state root matches the one in block header after execution
66-
validate_state_root(&block.header, new_state_root)?;
67-
68-
// Check receipts root matches the one in block header after execution
69-
validate_receipts_root(&block.header, &receipts)?;
70-
71-
// Processes requests from receipts, computes the requests_hash and compares it against the header
72-
validate_requests_hash(&block.header, &chain_config, &requests)?;
73-
74-
store_block(storage, block.clone())?;
75-
store_receipts(storage, receipts, block_hash)?;
76-
77-
let interval = Instant::now().duration_since(since).as_millis();
78-
if interval != 0 {
79-
let as_gigas = (block.header.gas_used as f64).div(10_f64.powf(9_f64));
80-
let throughput = (as_gigas) / (interval as f64) * 1000_f64;
81-
info!("[METRIC] BLOCK EXECUTION THROUGHPUT: {throughput} Gigagas/s TIME SPENT: {interval} msecs");
30+
#[derive(Debug, Clone)]
31+
pub struct Blockchain {
32+
pub vm: EVM,
33+
pub storage: Store,
34+
}
35+
36+
impl Blockchain {
37+
pub fn new(evm: EVM, store: Store) -> Self {
38+
Self {
39+
vm: evm,
40+
storage: store,
41+
}
8242
}
8343

84-
Ok(())
44+
pub fn default_with_store(store: Store) -> Self {
45+
Self {
46+
vm: Default::default(),
47+
storage: store,
48+
}
49+
}
50+
51+
pub fn add_block(&self, block: &Block) -> Result<(), ChainError> {
52+
let since = Instant::now();
53+
54+
let block_hash = block.header.compute_block_hash();
55+
56+
// Validate if it can be the new head and find the parent
57+
let Ok(parent_header) = find_parent_header(&block.header, &self.storage) else {
58+
// If the parent is not present, we store it as pending.
59+
self.storage.add_pending_block(block.clone())?;
60+
return Err(ChainError::ParentNotFound);
61+
};
62+
let mut state = evm_state(self.storage.clone(), block.header.parent_hash);
63+
let chain_config = state.chain_config().map_err(ChainError::from)?;
64+
65+
// Validate the block pre-execution
66+
validate_block(block, &parent_header, &chain_config)?;
67+
let BlockExecutionResult {
68+
receipts,
69+
requests,
70+
account_updates,
71+
} = self.vm.execute_block(block, &mut state)?;
72+
73+
validate_gas_used(&receipts, &block.header)?;
74+
75+
// Apply the account updates over the last block's state and compute the new state root
76+
let new_state_root = state
77+
.database()
78+
.ok_or(ChainError::StoreError(StoreError::MissingStore))?
79+
.apply_account_updates(block.header.parent_hash, &account_updates)?
80+
.ok_or(ChainError::ParentStateNotFound)?;
81+
82+
// Check state root matches the one in block header after execution
83+
validate_state_root(&block.header, new_state_root)?;
84+
85+
// Check receipts root matches the one in block header after execution
86+
validate_receipts_root(&block.header, &receipts)?;
87+
88+
// Processes requests from receipts, computes the requests_hash and compares it against the header
89+
validate_requests_hash(&block.header, &chain_config, &requests)?;
90+
91+
store_block(&self.storage, block.clone())?;
92+
store_receipts(&self.storage, receipts, block_hash)?;
93+
94+
let interval = Instant::now().duration_since(since).as_millis();
95+
if interval != 0 {
96+
let as_gigas = (block.header.gas_used as f64).div(10_f64.powf(9_f64));
97+
let throughput = (as_gigas) / (interval as f64) * 1000_f64;
98+
info!("[METRIC] BLOCK EXECUTION THROUGHPUT: {throughput} Gigagas/s TIME SPENT: {interval} msecs");
99+
}
100+
101+
Ok(())
102+
}
103+
104+
//TODO: Forkchoice Update shouldn't be part of this function
105+
pub fn import_blocks(&self, blocks: &Vec<Block>) {
106+
let size = blocks.len();
107+
for block in blocks {
108+
let hash = block.hash();
109+
info!(
110+
"Adding block {} with hash {:#x}.",
111+
block.header.number, hash
112+
);
113+
if let Err(error) = self.add_block(block) {
114+
warn!(
115+
"Failed to add block {} with hash {:#x}: {}.",
116+
block.header.number, hash, error
117+
);
118+
}
119+
if self
120+
.storage
121+
.update_latest_block_number(block.header.number)
122+
.is_err()
123+
{
124+
error!("Fatal: added block {} but could not update the block number -- aborting block import", block.header.number);
125+
break;
126+
};
127+
if self
128+
.storage
129+
.set_canonical_block(block.header.number, hash)
130+
.is_err()
131+
{
132+
error!(
133+
"Fatal: added block {} but could not set it as canonical -- aborting block import",
134+
block.header.number
135+
);
136+
break;
137+
};
138+
}
139+
if let Some(last_block) = blocks.last() {
140+
let hash = last_block.hash();
141+
match self.vm {
142+
EVM::LEVM => {
143+
// We are allowing this not to unwrap so that tests can run even if block execution results in the wrong root hash with LEVM.
144+
let _ = apply_fork_choice(&self.storage, hash, hash, hash);
145+
}
146+
EVM::REVM => {
147+
apply_fork_choice(&self.storage, hash, hash, hash).unwrap();
148+
}
149+
}
150+
}
151+
info!("Added {size} blocks to blockchain");
152+
}
85153
}
86154

87155
pub fn validate_requests_hash(

0 commit comments

Comments
 (0)