diff --git a/crates/blockchain/blockchain.rs b/crates/blockchain/blockchain.rs index 4ad3f2db64e..3c756341ef2 100644 --- a/crates/blockchain/blockchain.rs +++ b/crates/blockchain/blockchain.rs @@ -19,9 +19,9 @@ use ethrex_common::types::fee_config::FeeConfig; use ethrex_common::types::requests::{EncodedRequests, Requests, compute_requests_hash}; use ethrex_common::types::{ AccountState, AccountUpdate, Block, BlockHash, BlockHeader, BlockNumber, ChainConfig, Code, - EIP4844Transaction, Fork::*, Receipt, Transaction, WrappedEIP4844Transaction, - compute_receipts_root, validate_block_header, validate_cancun_header_fields, - validate_prague_header_fields, validate_pre_cancun_header_fields, + EIP4844Transaction, Receipt, Transaction, WrappedEIP4844Transaction, compute_receipts_root, + validate_block_header, validate_cancun_header_fields, validate_prague_header_fields, + validate_pre_cancun_header_fields, }; use ethrex_common::types::{ELASTICITY_MULTIPLIER, P2PTransaction}; use ethrex_common::types::{Fork, MempoolTransaction}; @@ -1497,7 +1497,7 @@ impl Blockchain { // NOTE: We could add a tx size limit here, but it's not in the actual spec // Check init code size - if config.is_fork_activated(Shanghai, header.timestamp) + if config.is_shanghai_activated(header.timestamp) && tx.is_contract_creation() && tx.data().len() > MAX_INITCODE_SIZE as usize { @@ -1508,8 +1508,7 @@ impl Blockchain { return Err(MempoolError::TxMaxDataSizeError); } - if config.is_fork_activated(Osaka, header.timestamp) - && tx.gas_limit() > POST_OSAKA_GAS_LIMIT_CAP + if config.is_osaka_activated(header.timestamp) && tx.gas_limit() > POST_OSAKA_GAS_LIMIT_CAP { // https://eips.ethereum.org/EIPS/eip-7825 return Err(MempoolError::TxMaxGasLimitExceededError( @@ -1643,7 +1642,7 @@ impl Blockchain { .storage .get_block_header(latest_block_number)? .ok_or(StoreError::Custom("Latest block not in DB".to_string()))?; - Ok(chain_config.get_fork(latest_block.timestamp)) + Ok(chain_config.fork(latest_block.timestamp)) } } @@ -1666,7 +1665,7 @@ pub fn validate_requests_hash( chain_config: &ChainConfig, requests: &[Requests], ) -> Result<(), ChainError> { - if !chain_config.is_fork_activated(Prague, header.timestamp) { + if !chain_config.is_prague_activated(header.timestamp) { return Ok(()); } @@ -1756,7 +1755,7 @@ pub fn validate_block( validate_block_header(&block.header, parent_header, elasticity_multiplier) .map_err(InvalidBlockError::from)?; - if chain_config.is_fork_activated(Osaka, block.header.timestamp) { + if chain_config.is_osaka_activated(block.header.timestamp) { let block_rlp_size = block.length(); if block_rlp_size > MAX_RLP_BLOCK_SIZE as usize { return Err(error::ChainError::InvalidBlock( @@ -1767,14 +1766,14 @@ pub fn validate_block( )); } } - if chain_config.is_fork_activated(Prague, block.header.timestamp) { + if chain_config.is_prague_activated(block.header.timestamp) { validate_prague_header_fields(&block.header, parent_header, chain_config) .map_err(InvalidBlockError::from)?; verify_blob_gas_usage(block, chain_config)?; - if chain_config.is_fork_activated(Osaka, block.header.timestamp) { + if chain_config.is_osaka_activated(block.header.timestamp) { verify_transaction_max_gas_limit(block)?; } - } else if chain_config.is_fork_activated(Cancun, block.header.timestamp) { + } else if chain_config.is_cancun_activated(block.header.timestamp) { validate_cancun_header_fields(&block.header, parent_header, chain_config) .map_err(InvalidBlockError::from)?; verify_blob_gas_usage(block, chain_config)?; @@ -1816,7 +1815,7 @@ fn verify_blob_gas_usage(block: &Block, config: &ChainConfig) -> Result<(), Chai let mut blob_gas_used = 0_u32; let mut blobs_in_block = 0_u32; let max_blob_number_per_block = config - .get_blob_schedule_for_time(block.header.timestamp) + .get_fork_blob_schedule(block.header.timestamp) .map(|schedule| schedule.max) .ok_or(ChainError::Custom("Provided block fork is invalid".into()))?; let max_blob_gas_per_block = max_blob_number_per_block * GAS_PER_BLOB; diff --git a/crates/blockchain/constants.rs b/crates/blockchain/constants.rs index 4cc8dcdcda2..e0ba1f698d9 100644 --- a/crates/blockchain/constants.rs +++ b/crates/blockchain/constants.rs @@ -18,6 +18,9 @@ pub const TX_ACCESS_LIST_ADDRESS_GAS: u64 = 2400; // Gas cost for each storage key specified on access lists pub const TX_ACCESS_LIST_STORAGE_KEY_GAS: u64 = 1900; +// Gas cost for each non zero byte on transaction data +pub const TX_DATA_NON_ZERO_GAS: u64 = 68; + // === EIP-170 constants === // Max bytecode size @@ -34,7 +37,7 @@ pub const MAX_TRANSACTION_DATA_SIZE: u32 = 4 * 32 * 1024; // 128 Kb // === EIP-2028 constants === // Gas cost for each non zero byte on transaction data -pub const TX_DATA_NON_ZERO_GAS: u64 = 16; +pub const TX_DATA_NON_ZERO_GAS_EIP2028: u64 = 16; // === EIP-4844 constants === diff --git a/crates/blockchain/mempool.rs b/crates/blockchain/mempool.rs index 7d21af6ff36..82ff5f53929 100644 --- a/crates/blockchain/mempool.rs +++ b/crates/blockchain/mempool.rs @@ -6,15 +6,14 @@ use std::{ use crate::{ constants::{ TX_ACCESS_LIST_ADDRESS_GAS, TX_ACCESS_LIST_STORAGE_KEY_GAS, TX_CREATE_GAS_COST, - TX_DATA_NON_ZERO_GAS, TX_DATA_ZERO_GAS_COST, TX_GAS_COST, TX_INIT_CODE_WORD_GAS_COST, + TX_DATA_NON_ZERO_GAS, TX_DATA_NON_ZERO_GAS_EIP2028, TX_DATA_ZERO_GAS_COST, TX_GAS_COST, + TX_INIT_CODE_WORD_GAS_COST, }, error::MempoolError, }; use ethrex_common::{ Address, H160, H256, U256, - types::{ - BlobsBundle, BlockHeader, ChainConfig, Fork::*, MempoolTransaction, Transaction, TxType, - }, + types::{BlobsBundle, BlockHeader, ChainConfig, MempoolTransaction, Transaction, TxType}, }; use ethrex_storage::error::StoreError; use std::collections::HashSet; @@ -428,10 +427,16 @@ pub fn transaction_intrinsic_gas( let data_len = tx.data().len() as u64; if data_len > 0 { + let non_zero_gas_cost = if config.is_istanbul_activated(header.number) { + TX_DATA_NON_ZERO_GAS_EIP2028 + } else { + TX_DATA_NON_ZERO_GAS + }; + let non_zero_count = tx.data().iter().filter(|&&x| x != 0u8).count() as u64; gas = gas - .checked_add(non_zero_count * TX_DATA_NON_ZERO_GAS) + .checked_add(non_zero_count * non_zero_gas_cost) .ok_or(MempoolError::TxGasOverflowError)?; let zero_count = data_len - non_zero_count; @@ -440,7 +445,7 @@ pub fn transaction_intrinsic_gas( .checked_add(zero_count * TX_DATA_ZERO_GAS_COST) .ok_or(MempoolError::TxGasOverflowError)?; - if is_contract_creation && config.is_fork_activated(Shanghai, header.timestamp) { + if is_contract_creation && config.is_shanghai_activated(header.timestamp) { // Len in 32 bytes sized words let len_in_words = data_len.saturating_add(31) / 32; @@ -473,7 +478,8 @@ mod tests { use crate::error::MempoolError; use crate::mempool::{ Mempool, TX_ACCESS_LIST_ADDRESS_GAS, TX_ACCESS_LIST_STORAGE_KEY_GAS, TX_CREATE_GAS_COST, - TX_DATA_NON_ZERO_GAS, TX_DATA_ZERO_GAS_COST, TX_GAS_COST, TX_INIT_CODE_WORD_GAS_COST, + TX_DATA_NON_ZERO_GAS, TX_DATA_NON_ZERO_GAS_EIP2028, TX_DATA_ZERO_GAS_COST, TX_GAS_COST, + TX_INIT_CODE_WORD_GAS_COST, }; use std::collections::HashMap; @@ -567,6 +573,29 @@ mod tests { assert_eq!(intrinsic_gas, expected_gas_cost); } + #[test] + fn transaction_intrinsic_data_gas_pre_istanbul() { + let (config, header) = build_basic_config_and_header(false, false); + + let tx = EIP1559Transaction { + nonce: 3, + max_priority_fee_per_gas: 0, + max_fee_per_gas: 0, + gas_limit: 100_000, + to: TxKind::Call(Address::from_low_u64_be(1)), // Normal tx + value: U256::zero(), // Value zero + data: Bytes::from(vec![0x0, 0x1, 0x1, 0x0, 0x1, 0x1]), // 6 bytes of data + access_list: Default::default(), // No access list + ..Default::default() + }; + + let tx = Transaction::EIP1559Transaction(tx); + let expected_gas_cost = TX_GAS_COST + 2 * TX_DATA_ZERO_GAS_COST + 4 * TX_DATA_NON_ZERO_GAS; + let intrinsic_gas = + transaction_intrinsic_gas(&tx, &header, &config).expect("Intrinsic gas"); + assert_eq!(intrinsic_gas, expected_gas_cost); + } + #[test] fn transaction_intrinsic_data_gas_post_istanbul() { let (config, header) = build_basic_config_and_header(true, false); @@ -584,7 +613,8 @@ mod tests { }; let tx = Transaction::EIP1559Transaction(tx); - let expected_gas_cost = TX_GAS_COST + 2 * TX_DATA_ZERO_GAS_COST + 4 * TX_DATA_NON_ZERO_GAS; + let expected_gas_cost = + TX_GAS_COST + 2 * TX_DATA_ZERO_GAS_COST + 4 * TX_DATA_NON_ZERO_GAS_EIP2028; let intrinsic_gas = transaction_intrinsic_gas(&tx, &header, &config).expect("Intrinsic gas"); assert_eq!(intrinsic_gas, expected_gas_cost); diff --git a/crates/blockchain/payload.rs b/crates/blockchain/payload.rs index 6c24e94edb2..1e62a5cc692 100644 --- a/crates/blockchain/payload.rs +++ b/crates/blockchain/payload.rs @@ -11,9 +11,7 @@ use ethrex_common::{ constants::{DEFAULT_OMMERS_HASH, DEFAULT_REQUESTS_HASH, GAS_PER_BLOB, MAX_RLP_BLOCK_SIZE}, types::{ AccountUpdate, BlobsBundle, Block, BlockBody, BlockHash, BlockHeader, BlockNumber, - ChainConfig, - Fork::*, - MempoolTransaction, Receipt, Transaction, TxType, Withdrawal, bloom_from_logs, + ChainConfig, MempoolTransaction, Receipt, Transaction, TxType, Withdrawal, bloom_from_logs, calc_excess_blob_gas, calculate_base_fee_per_blob_gas, calculate_base_fee_per_gas, compute_receipts_root, compute_transactions_root, compute_withdrawals_root, requests::{EncodedRequests, compute_requests_hash}, @@ -130,10 +128,10 @@ pub fn create_payload( .get_block_header_by_hash(args.parent)? .ok_or_else(|| ChainError::ParentNotFound)?; let chain_config = storage.get_chain_config(); - let fork = chain_config.get_fork(args.timestamp); + let fork = chain_config.fork(args.timestamp); let gas_limit = calc_gas_limit(parent_block.gas_limit, args.gas_ceil); let excess_blob_gas = chain_config - .get_blob_schedule_for_time(args.timestamp) + .get_fork_blob_schedule(args.timestamp) .map(|schedule| calc_excess_blob_gas(&parent_block, schedule, fork)); let header = BlockHeader { @@ -160,17 +158,17 @@ pub fn create_payload( args.elasticity_multiplier, ), withdrawals_root: chain_config - .is_fork_activated(Shanghai, args.timestamp) + .is_shanghai_activated(args.timestamp) .then_some(compute_withdrawals_root( args.withdrawals.as_ref().unwrap_or(&Vec::new()), )), blob_gas_used: chain_config - .is_fork_activated(Cancun, args.timestamp) + .is_cancun_activated(args.timestamp) .then_some(0), excess_blob_gas, parent_beacon_block_root: args.beacon_root, requests_hash: chain_config - .is_fork_activated(Prague, args.timestamp) + .is_prague_activated(args.timestamp) .then_some(*DEFAULT_REQUESTS_HASH), ..Default::default() }; @@ -231,7 +229,7 @@ impl PayloadBuildContext { let base_fee_per_blob_gas = calculate_base_fee_per_blob_gas( payload.header.excess_blob_gas.unwrap_or_default(), config - .get_blob_schedule_for_time(payload.header.timestamp) + .get_fork_blob_schedule(payload.header.timestamp) .map(|schedule| schedule.base_fee_update_fraction) .unwrap_or_default(), ); @@ -248,7 +246,7 @@ impl PayloadBuildContext { remaining_gas: payload.header.gas_limit, receipts: vec![], requests: config - .is_fork_activated(Prague, payload.header.timestamp) + .is_prague_activated(payload.header.timestamp) .then_some(Vec::new()), block_value: U256::zero(), base_fee_per_blob_gas, @@ -495,7 +493,7 @@ impl Blockchain { pub fn fill_transactions(&self, context: &mut PayloadBuildContext) -> Result<(), ChainError> { let chain_config = context.chain_config(); let max_blob_number_per_block = chain_config - .get_blob_schedule_for_time(context.payload.header.timestamp) + .get_fork_blob_schedule(context.payload.header.timestamp) .map(|schedule| schedule.max) .unwrap_or_default() as usize; @@ -544,7 +542,7 @@ impl Blockchain { context.payload_size + head_tx.encode_canonical_to_vec().len() as u64; if context .chain_config() - .is_fork_activated(Osaka, context.payload.header.timestamp) + .is_osaka_activated(context.payload.header.timestamp) && potential_rlp_block_size > MAX_RLP_BLOCK_SIZE { break; @@ -554,6 +552,16 @@ impl Blockchain { // TODO: maybe fetch hash too when filtering mempool so we don't have to compute it here (we can do this in the same refactor as adding timestamp) let tx_hash = head_tx.tx.hash(); + // Check whether the tx is replay-protected + if head_tx.tx.protected() && !chain_config.is_eip155_activated(context.block_number()) { + // Ignore replay protected tx & all txs from the sender + // Pull transaction from the mempool + debug!("Ignoring replay-protected transaction: {}", tx_hash); + txs.pop(); + self.remove_transaction_from_pool(&tx_hash)?; + continue; + } + // Execute tx let receipt = match self.apply_transaction(&head_tx, context) { Ok(receipt) => { @@ -601,7 +609,7 @@ impl Blockchain { let tx_hash = head.tx.hash(); let chain_config = context.chain_config(); let max_blob_number_per_block = chain_config - .get_blob_schedule_for_time(context.payload.header.timestamp) + .get_fork_blob_schedule(context.payload.header.timestamp) .map(|schedule| schedule.max) .unwrap_or_default() as usize; let Some(blobs_bundle) = self.mempool.get_blobs_bundle(tx_hash)? else { @@ -627,7 +635,7 @@ impl Blockchain { pub fn extract_requests(&self, context: &mut PayloadBuildContext) -> Result<(), EvmError> { if !context .chain_config() - .is_fork_activated(Prague, context.payload.header.timestamp) + .is_prague_activated(context.payload.header.timestamp) { return Ok(()); }; diff --git a/crates/common/types/block.rs b/crates/common/types/block.rs index 226e2a4983f..802ae66b459 100644 --- a/crates/common/types/block.rs +++ b/crates/common/types/block.rs @@ -731,13 +731,9 @@ fn validate_excess_blob_gas( chain_config: &ChainConfig, ) -> Result<(), InvalidBlockHeaderError> { let expected_excess_blob_gas = chain_config - .get_blob_schedule_for_time(header.timestamp) + .get_fork_blob_schedule(header.timestamp) .map(|schedule| { - calc_excess_blob_gas( - parent_header, - schedule, - chain_config.get_fork(header.timestamp), - ) + calc_excess_blob_gas(parent_header, schedule, chain_config.fork(header.timestamp)) }) .unwrap_or_default(); if header diff --git a/crates/common/types/genesis.rs b/crates/common/types/genesis.rs index cb15ee6d691..eb0c33abc1d 100644 --- a/crates/common/types/genesis.rs +++ b/crates/common/types/genesis.rs @@ -1,4 +1,3 @@ -use self::Fork::*; use bytes::Bytes; use ethereum_types::{Address, Bloom, H256, U256}; use ethrex_crypto::keccak::keccak_hash; @@ -14,8 +13,8 @@ use std::{ use tracing::warn; use super::{ - AccountState, Block, BlockBody, BlockHeader, INITIAL_BASE_FEE, compute_receipts_root, - compute_transactions_root, compute_withdrawals_root, + AccountState, Block, BlockBody, BlockHeader, BlockNumber, INITIAL_BASE_FEE, + compute_receipts_root, compute_transactions_root, compute_withdrawals_root, }; use crate::{ constants::{DEFAULT_OMMERS_HASH, DEFAULT_REQUESTS_HASH}, @@ -284,11 +283,11 @@ lazy_static::lazy_static! { #[derive(Debug, PartialEq, Eq, PartialOrd, Default, Hash, Clone, Copy, Serialize, Deserialize)] pub enum Fork { Frontier = 0, - Homestead = 1, - DaoFork = 2, - EIP150 = 3, - EIP155 = 4, - EIP158 = 5, + FrontierThawing = 1, + Homestead = 2, + DaoFork = 3, + Tangerine = 4, + SpuriousDragon = 5, Byzantium = 6, Constantinople = 7, Petersburg = 8, @@ -311,43 +310,15 @@ pub enum Fork { BPO5 = 24, } -pub const FORKS: [Fork; 25] = [ - Frontier, - Homestead, - DaoFork, - EIP150, - EIP155, - EIP158, - Byzantium, - Constantinople, - Petersburg, - Istanbul, - MuirGlacier, - Berlin, - London, - ArrowGlacier, - GrayGlacier, - Paris, - Shanghai, - Cancun, - Prague, - Osaka, - BPO1, - BPO2, - BPO3, - BPO4, - BPO5, -]; - impl From for &str { fn from(fork: Fork) -> Self { match fork { Fork::Frontier => "Frontier", + Fork::FrontierThawing => "FrontierThawing", Fork::Homestead => "Homestead", Fork::DaoFork => "DaoFork", - Fork::EIP150 => "EIP150", - Fork::EIP155 => "EIP155", - Fork::EIP158 => "EIP158", + Fork::Tangerine => "Tangerine", + Fork::SpuriousDragon => "SpuriousDragon", Fork::Byzantium => "Byzantium", Fork::Constantinople => "Constantinople", Fork::Petersburg => "Petersburg", @@ -372,53 +343,53 @@ impl From for &str { } impl ChainConfig { - pub fn fork_activation_time_or_block(&self, fork: Fork) -> Option { - match fork { - Fork::Frontier => Some(0), - Fork::Homestead => self.homestead_block, - Fork::DaoFork => self.dao_fork_block, - Fork::EIP150 => self.eip150_block, - Fork::EIP155 => self.eip155_block, - Fork::EIP158 => self.eip158_block, - Fork::Byzantium => self.byzantium_block, - Fork::Constantinople => self.constantinople_block, - Fork::Petersburg => self.petersburg_block, - Fork::Istanbul => self.istanbul_block, - Fork::MuirGlacier => self.muir_glacier_block, - Fork::Berlin => self.berlin_block, - Fork::London => self.london_block, - Fork::ArrowGlacier => self.arrow_glacier_block, - Fork::GrayGlacier => self.gray_glacier_block, - Fork::Paris => self.merge_netsplit_block, - Fork::Shanghai => self.shanghai_time, - Fork::Cancun => self.cancun_time, - Fork::Prague => self.prague_time, - Fork::Osaka => self.osaka_time, - Fork::BPO1 => self.bpo1_time, - Fork::BPO2 => self.bpo2_time, - Fork::BPO3 => self.bpo3_time, - Fork::BPO4 => self.bpo4_time, - Fork::BPO5 => self.bpo5_time, - } + pub fn is_bpo1_activated(&self, block_timestamp: u64) -> bool { + self.bpo1_time.is_some_and(|time| time <= block_timestamp) } - pub fn get_blob_schedule_for_fork(&self, fork: Fork) -> Option { - match fork { - Fork::Cancun => Some(self.blob_schedule.cancun), - Fork::Prague => Some(self.blob_schedule.prague), - Fork::Osaka => Some(self.blob_schedule.osaka), - Fork::BPO1 => Some(self.blob_schedule.bpo1), - Fork::BPO2 => Some(self.blob_schedule.bpo2), - Fork::BPO3 => self.blob_schedule.bpo3, - Fork::BPO4 => self.blob_schedule.bpo4, - Fork::BPO5 => self.blob_schedule.bpo5, - _ => None, - } + pub fn is_bpo2_activated(&self, block_timestamp: u64) -> bool { + self.bpo2_time.is_some_and(|time| time <= block_timestamp) + } + + pub fn is_bpo3_activated(&self, block_timestamp: u64) -> bool { + self.bpo3_time.is_some_and(|time| time <= block_timestamp) + } + + pub fn is_bpo4_activated(&self, block_timestamp: u64) -> bool { + self.bpo4_time.is_some_and(|time| time <= block_timestamp) } - pub fn is_fork_activated(&self, fork: Fork, timestamp_or_block: u64) -> bool { - self.fork_activation_time_or_block(fork) - .is_some_and(|time_or_number| time_or_number <= timestamp_or_block) + pub fn is_bpo5_activated(&self, block_timestamp: u64) -> bool { + self.bpo5_time.is_some_and(|time| time <= block_timestamp) + } + + pub fn is_osaka_activated(&self, block_timestamp: u64) -> bool { + self.osaka_time.is_some_and(|time| time <= block_timestamp) + } + + pub fn is_prague_activated(&self, block_timestamp: u64) -> bool { + self.prague_time.is_some_and(|time| time <= block_timestamp) + } + + pub fn is_shanghai_activated(&self, block_timestamp: u64) -> bool { + self.shanghai_time + .is_some_and(|time| time <= block_timestamp) + } + + pub fn is_cancun_activated(&self, block_timestamp: u64) -> bool { + self.cancun_time.is_some_and(|time| time <= block_timestamp) + } + + pub fn is_istanbul_activated(&self, block_number: BlockNumber) -> bool { + self.istanbul_block.is_some_and(|num| num <= block_number) + } + + pub fn is_london_activated(&self, block_number: BlockNumber) -> bool { + self.london_block.is_some_and(|num| num <= block_number) + } + + pub fn is_eip155_activated(&self, block_number: BlockNumber) -> bool { + self.eip155_block.is_some_and(|num| num <= block_number) } pub fn display_config(&self) -> String { @@ -429,6 +400,7 @@ impl ChainConfig { ("Shanghai", self.shanghai_time), ("Cancun", self.cancun_time), ("Prague", self.prague_time), + ("Verkle", self.verkle_time), ("Osaka", self.osaka_time), ]; @@ -449,39 +421,144 @@ impl ChainConfig { } pub fn get_fork(&self, block_timestamp: u64) -> Fork { - FORKS - .into_iter() - .rfind(|fork| self.is_fork_activated(*fork, block_timestamp)) - .unwrap_or(Paris) - } - - pub fn get_blob_schedule_for_time(&self, block_timestamp: u64) -> Option { - if let Some(fork_with_current_blob_schedule) = FORKS.into_iter().rfind(|fork| { - self.get_blob_schedule_for_fork(*fork).is_some() - && self.is_fork_activated(*fork, block_timestamp) - }) { - self.get_blob_schedule_for_fork(fork_with_current_blob_schedule) + if self.is_bpo5_activated(block_timestamp) { + Fork::BPO5 + } else if self.is_bpo4_activated(block_timestamp) { + Fork::BPO4 + } else if self.is_bpo3_activated(block_timestamp) { + Fork::BPO3 + } else if self.is_bpo2_activated(block_timestamp) { + Fork::BPO2 + } else if self.is_bpo1_activated(block_timestamp) { + Fork::BPO1 + } else if self.is_osaka_activated(block_timestamp) { + Fork::Osaka + } else if self.is_prague_activated(block_timestamp) { + Fork::Prague + } else if self.is_cancun_activated(block_timestamp) { + Fork::Cancun + } else if self.is_shanghai_activated(block_timestamp) { + Fork::Shanghai + } else { + Fork::Paris + } + } + + pub fn get_fork_blob_schedule(&self, block_timestamp: u64) -> Option { + if self.is_bpo5_activated(block_timestamp) { + Some(self.blob_schedule.bpo5.unwrap_or_default()) + } else if self.is_bpo4_activated(block_timestamp) { + Some(self.blob_schedule.bpo4.unwrap_or_default()) + } else if self.is_bpo3_activated(block_timestamp) { + Some(self.blob_schedule.bpo3.unwrap_or_default()) + } else if self.is_bpo2_activated(block_timestamp) { + Some(self.blob_schedule.bpo2) + } else if self.is_bpo1_activated(block_timestamp) { + Some(self.blob_schedule.bpo1) + } else if self.is_osaka_activated(block_timestamp) { + Some(self.blob_schedule.osaka) + } else if self.is_prague_activated(block_timestamp) { + Some(self.blob_schedule.prague) + } else if self.is_cancun_activated(block_timestamp) { + Some(self.blob_schedule.cancun) } else { None } } + pub fn fork(&self, block_timestamp: u64) -> Fork { + self.get_fork(block_timestamp) + } + pub fn next_fork(&self, block_timestamp: u64) -> Option { - let current_fork = self.get_fork(block_timestamp); - if (current_fork as usize) < FORKS.len() { - FORKS.into_iter().find(|fork| { - *fork > current_fork && self.fork_activation_time_or_block(*fork).is_some() - }) + let next = if self.is_bpo5_activated(block_timestamp) { + None + } else if self.is_bpo4_activated(block_timestamp) && self.bpo5_time.is_some() { + Some(Fork::BPO5) + } else if self.is_bpo3_activated(block_timestamp) && self.bpo4_time.is_some() { + Some(Fork::BPO4) + } else if self.is_bpo2_activated(block_timestamp) && self.bpo3_time.is_some() { + Some(Fork::BPO3) + } else if self.is_bpo1_activated(block_timestamp) && self.bpo2_time.is_some() { + Some(Fork::BPO2) + } else if self.is_osaka_activated(block_timestamp) && self.bpo1_time.is_some() { + Some(Fork::BPO1) + } else if self.is_prague_activated(block_timestamp) && self.osaka_time.is_some() { + Some(Fork::Osaka) + } else if self.is_cancun_activated(block_timestamp) && self.prague_time.is_some() { + Some(Fork::Prague) + } else if self.is_shanghai_activated(block_timestamp) && self.cancun_time.is_some() { + Some(Fork::Cancun) } else { None + }; + match next { + Some(fork) if fork > self.fork(block_timestamp) => next, + _ => None, } } pub fn get_last_scheduled_fork(&self) -> Fork { - FORKS - .into_iter() - .rfind(|fork| self.fork_activation_time_or_block(*fork).is_some()) - .unwrap_or(Paris) + if self.bpo5_time.is_some() { + Fork::BPO5 + } else if self.bpo4_time.is_some() { + Fork::BPO4 + } else if self.bpo3_time.is_some() { + Fork::BPO3 + } else if self.bpo2_time.is_some() { + Fork::BPO2 + } else if self.bpo1_time.is_some() { + Fork::BPO1 + } else if self.osaka_time.is_some() { + Fork::Osaka + } else if self.prague_time.is_some() { + Fork::Prague + } else if self.cancun_time.is_some() { + Fork::Cancun + } else { + Fork::Paris + } + } + + pub fn get_activation_timestamp_for_fork(&self, fork: Fork) -> Option { + match fork { + Fork::Cancun => self.cancun_time, + Fork::Prague => self.prague_time, + Fork::Osaka => self.osaka_time, + Fork::BPO1 => self.bpo1_time, + Fork::BPO2 => self.bpo2_time, + Fork::BPO3 => self.bpo3_time, + Fork::BPO4 => self.bpo4_time, + Fork::BPO5 => self.bpo5_time, + Fork::Homestead => self.homestead_block, + Fork::DaoFork => self.dao_fork_block, + Fork::Byzantium => self.byzantium_block, + Fork::Constantinople => self.constantinople_block, + Fork::Petersburg => self.petersburg_block, + Fork::Istanbul => self.istanbul_block, + Fork::MuirGlacier => self.muir_glacier_block, + Fork::Berlin => self.berlin_block, + Fork::London => self.london_block, + Fork::ArrowGlacier => self.arrow_glacier_block, + Fork::GrayGlacier => self.gray_glacier_block, + Fork::Paris => self.merge_netsplit_block, + Fork::Shanghai => self.shanghai_time, + _ => None, + } + } + + pub fn get_blob_schedule_for_fork(&self, fork: Fork) -> Option { + match fork { + Fork::Cancun => Some(self.blob_schedule.cancun), + Fork::Prague => Some(self.blob_schedule.prague), + Fork::Osaka => Some(self.blob_schedule.osaka), + Fork::BPO1 => Some(self.blob_schedule.bpo1), + Fork::BPO2 => Some(self.blob_schedule.bpo2), + Fork::BPO3 => self.blob_schedule.bpo3, + Fork::BPO4 => self.blob_schedule.bpo4, + Fork::BPO5 => self.blob_schedule.bpo5, + _ => None, + } } pub fn gather_forks(&self, genesis_header: BlockHeader) -> (Vec, Vec) { @@ -572,23 +649,23 @@ impl Genesis { } let base_fee_per_gas = self.base_fee_per_gas.or_else(|| { self.config - .is_fork_activated(London, 0) + .is_london_activated(0) .then_some(INITIAL_BASE_FEE) }); let withdrawals_root = self .config - .is_fork_activated(Shanghai, self.timestamp) + .is_shanghai_activated(self.timestamp) .then_some(compute_withdrawals_root(&[])); let parent_beacon_block_root = self .config - .is_fork_activated(Cancun, self.timestamp) + .is_cancun_activated(self.timestamp) .then_some(H256::zero()); let requests_hash = self .config - .is_fork_activated(Prague, self.timestamp) + .is_prague_activated(self.timestamp) .then_some(self.requests_hash.unwrap_or(*DEFAULT_REQUESTS_HASH)); BlockHeader { diff --git a/crates/l2/sequencer/block_producer/payload_builder.rs b/crates/l2/sequencer/block_producer/payload_builder.rs index 515d5a9f35c..84110eb4345 100644 --- a/crates/l2/sequencer/block_producer/payload_builder.rs +++ b/crates/l2/sequencer/block_producer/payload_builder.rs @@ -101,6 +101,7 @@ pub async fn fill_transactions( }; let mut acc_encoded_size = context.payload.length(); let fee_config_len = fee_config.to_vec().len(); + let chain_config = store.get_chain_config(); debug!("Fetching transactions from mempool"); // Fetch mempool transactions @@ -176,6 +177,16 @@ pub async fn fill_transactions( // TODO: maybe fetch hash too when filtering mempool so we don't have to compute it here (we can do this in the same refactor as adding timestamp) let tx_hash = head_tx.tx.hash(); + // Check whether the tx is replay-protected + if head_tx.tx.protected() && !chain_config.is_eip155_activated(context.block_number()) { + // Ignore replay protected tx & all txs from the sender + // Pull transaction from the mempool + debug!("Ignoring replay-protected transaction: {}", tx_hash); + txs.pop(); + blockchain.remove_transaction_from_pool(&tx_hash)?; + continue; + } + let maybe_sender_acc_info = store .get_account_info(latest_block_number, head_tx.tx.sender()) .await?; diff --git a/crates/networking/rpc/engine/blobs.rs b/crates/networking/rpc/engine/blobs.rs index 532807f8c3d..7a1c68408ac 100644 --- a/crates/networking/rpc/engine/blobs.rs +++ b/crates/networking/rpc/engine/blobs.rs @@ -1,9 +1,7 @@ use ethrex_common::{ H256, serde_utils::{self}, - types::{ - Blob, CELLS_PER_EXT_BLOB, Fork::*, Proof, blobs_bundle::kzg_commitment_to_versioned_hash, - }, + types::{Blob, CELLS_PER_EXT_BLOB, Proof, blobs_bundle::kzg_commitment_to_versioned_hash}, }; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -122,7 +120,7 @@ impl RpcHandler for BlobsV2Request { && !context .storage .get_chain_config() - .is_fork_activated(Osaka, current_block_header.timestamp) + .is_osaka_activated(current_block_header.timestamp) { // validation requested in https://github.com/ethereum/execution-apis/blob/a1d95fb555cd91efb3e0d6555e4ab556d9f5dd06/src/engine/osaka.md?plain=1#L130 return Err(RpcErr::UnsuportedFork( diff --git a/crates/networking/rpc/engine/fork_choice.rs b/crates/networking/rpc/engine/fork_choice.rs index 84ab7ea8545..e400d72f3d3 100644 --- a/crates/networking/rpc/engine/fork_choice.rs +++ b/crates/networking/rpc/engine/fork_choice.rs @@ -3,7 +3,7 @@ use ethrex_blockchain::{ fork_choice::apply_fork_choice, payload::{BuildPayloadArgs, create_payload}, }; -use ethrex_common::types::{BlockHeader, ELASTICITY_MULTIPLIER, Fork::*}; +use ethrex_common::types::{BlockHeader, ELASTICITY_MULTIPLIER}; use serde_json::Value; use tracing::{info, warn}; @@ -37,7 +37,7 @@ impl RpcHandler for ForkChoiceUpdatedV1 { handle_forkchoice(&self.fork_choice_state, context.clone(), 1).await?; if let (Some(head_block), Some(attributes)) = (head_block_opt, &self.payload_attributes) { let chain_config = context.storage.get_chain_config(); - if chain_config.is_fork_activated(Cancun, attributes.timestamp) { + if chain_config.is_cancun_activated(attributes.timestamp) { return Err(RpcErr::UnsuportedFork( "forkChoiceV1 used to build Cancun payload".to_string(), )); @@ -70,11 +70,11 @@ impl RpcHandler for ForkChoiceUpdatedV2 { handle_forkchoice(&self.fork_choice_state, context.clone(), 2).await?; if let (Some(head_block), Some(attributes)) = (head_block_opt, &self.payload_attributes) { let chain_config = context.storage.get_chain_config(); - if chain_config.is_fork_activated(Cancun, attributes.timestamp) { + if chain_config.is_cancun_activated(attributes.timestamp) { return Err(RpcErr::UnsuportedFork( "forkChoiceV2 used to build Cancun payload".to_string(), )); - } else if chain_config.is_fork_activated(Shanghai, attributes.timestamp) { + } else if chain_config.is_shanghai_activated(attributes.timestamp) { validate_attributes_v2(attributes, &head_block)?; } else { // Behave as a v1 @@ -355,7 +355,7 @@ fn validate_attributes_v3( "Attribute parent_beacon_block_root is null".to_string(), )); } - if !chain_config.is_fork_activated(Cancun, attributes.timestamp) { + if !chain_config.is_cancun_activated(attributes.timestamp) { return Err(RpcErr::UnsuportedFork( "forkChoiceV3 used to build pre-Cancun payload".to_string(), )); diff --git a/crates/networking/rpc/engine/payload.rs b/crates/networking/rpc/engine/payload.rs index 5374f94e91c..6d64d06336f 100644 --- a/crates/networking/rpc/engine/payload.rs +++ b/crates/networking/rpc/engine/payload.rs @@ -2,7 +2,7 @@ use ethrex_blockchain::error::ChainError; use ethrex_blockchain::payload::PayloadBuildResult; use ethrex_common::types::payload::PayloadBundle; use ethrex_common::types::requests::{EncodedRequests, compute_requests_hash}; -use ethrex_common::types::{Block, BlockBody, BlockHash, BlockNumber, Fork, Fork::*}; +use ethrex_common::types::{Block, BlockBody, BlockHash, BlockNumber, Fork}; use ethrex_common::{H256, U256}; use ethrex_p2p::sync::SyncMode; use ethrex_rlp::error::RLPDecodeError; @@ -62,7 +62,7 @@ impl RpcHandler for NewPayloadV2Request { async fn handle(&self, context: RpcApiContext) -> Result { let chain_config = &context.storage.get_chain_config(); - if chain_config.is_fork_activated(Shanghai, self.payload.timestamp) { + if chain_config.is_shanghai_activated(self.payload.timestamp) { validate_execution_payload_v2(&self.payload)?; } else { // Behave as a v1 @@ -206,7 +206,7 @@ impl RpcHandler for NewPayloadV4Request { let chain_config = context.storage.get_chain_config(); - if !chain_config.is_fork_activated(Prague, block.header.timestamp) { + if !chain_config.is_prague_activated(block.header.timestamp) { return Err(RpcErr::UnsuportedFork(format!( "{:?}", chain_config.get_fork(block.header.timestamp) @@ -334,13 +334,13 @@ impl RpcHandler for GetPayloadV4Request { let payload_bundle = get_payload(self.payload_id, &context).await?; let chain_config = &context.storage.get_chain_config(); - if !chain_config.is_fork_activated(Prague, payload_bundle.block.header.timestamp) { + if !chain_config.is_prague_activated(payload_bundle.block.header.timestamp) { return Err(RpcErr::UnsuportedFork(format!( "{:?}", chain_config.get_fork(payload_bundle.block.header.timestamp) ))); } - if chain_config.is_fork_activated(Osaka, payload_bundle.block.header.timestamp) { + if chain_config.is_osaka_activated(payload_bundle.block.header.timestamp) { return Err(RpcErr::UnsuportedFork(format!("{:?}", Fork::Osaka))); } @@ -386,7 +386,7 @@ impl RpcHandler for GetPayloadV5Request { let payload_bundle = get_payload(self.payload_id, &context).await?; let chain_config = &context.storage.get_chain_config(); - if !chain_config.is_fork_activated(Osaka, payload_bundle.block.header.timestamp) { + if !chain_config.is_osaka_activated(payload_bundle.block.header.timestamp) { return Err(RpcErr::UnsuportedFork(format!( "{:?}", chain_config.get_fork(payload_bundle.block.header.timestamp) @@ -543,7 +543,7 @@ fn validate_execution_payload_v3(payload: &ExecutionPayload) -> Result<(), RpcEr fn validate_payload_v1_v2(block: &Block, context: &RpcApiContext) -> Result<(), RpcErr> { let chain_config = &context.storage.get_chain_config(); - if chain_config.is_fork_activated(Cancun, block.header.timestamp) { + if chain_config.is_cancun_activated(block.header.timestamp) { return Err(RpcErr::UnsuportedFork( "Cancun payload received".to_string(), )); diff --git a/crates/networking/rpc/eth/block.rs b/crates/networking/rpc/eth/block.rs index 1f5636c2378..bf1c56d51d0 100644 --- a/crates/networking/rpc/eth/block.rs +++ b/crates/networking/rpc/eth/block.rs @@ -320,7 +320,7 @@ impl RpcHandler for GetBlobBaseFee { let blob_base_fee = calculate_base_fee_per_blob_gas( parent_header.excess_blob_gas.unwrap_or_default(), config - .get_blob_schedule_for_time(header.timestamp) + .get_fork_blob_schedule(header.timestamp) .map(|schedule| schedule.base_fee_update_fraction) .unwrap_or_default(), ); @@ -347,7 +347,7 @@ pub async fn get_all_block_rpc_receipts( let blob_base_fee = calculate_base_fee_per_blob_gas( header.excess_blob_gas.unwrap_or_default(), config - .get_blob_schedule_for_time(header.timestamp) + .get_fork_blob_schedule(header.timestamp) .map(|schedule| schedule.base_fee_update_fraction) .unwrap_or_default(), ); diff --git a/crates/networking/rpc/eth/client.rs b/crates/networking/rpc/eth/client.rs index 58b92c6c441..271cb4cd495 100644 --- a/crates/networking/rpc/eth/client.rs +++ b/crates/networking/rpc/eth/client.rs @@ -145,7 +145,7 @@ async fn get_config_for_fork( context: &RpcApiContext, ) -> Result { let chain_config = context.storage.get_chain_config(); - let activation_time = chain_config.fork_activation_time_or_block(fork); + let activation_time = chain_config.get_activation_timestamp_for_fork(fork); let genesis_header = context .storage .get_block_by_number(0) diff --git a/crates/networking/rpc/eth/fee_market.rs b/crates/networking/rpc/eth/fee_market.rs index 6f15c52332a..8e992ddee62 100644 --- a/crates/networking/rpc/eth/fee_market.rs +++ b/crates/networking/rpc/eth/fee_market.rs @@ -123,7 +123,7 @@ impl RpcHandler for FeeHistoryRequest { )))?; let max_blob_gas_per_block = config - .get_blob_schedule_for_time(header.timestamp) + .get_fork_blob_schedule(header.timestamp) .map(|schedule| schedule.max * GAS_PER_BLOB); let blob_gas_used_r = match (header.blob_gas_used, max_blob_gas_per_block) { (Some(blob_gas_used), Some(max_blob_gas)) => { @@ -133,7 +133,7 @@ impl RpcHandler for FeeHistoryRequest { }; let blob_schedule = config - .get_blob_schedule_for_time(header.timestamp) + .get_fork_blob_schedule(header.timestamp) .unwrap_or_default(); let fork = config.get_fork(header.timestamp); diff --git a/crates/networking/rpc/eth/transaction.rs b/crates/networking/rpc/eth/transaction.rs index df8e52a3c0c..87ea64fb372 100644 --- a/crates/networking/rpc/eth/transaction.rs +++ b/crates/networking/rpc/eth/transaction.rs @@ -447,7 +447,7 @@ impl RpcHandler for EstimateGasRequest { _ => return Ok(Value::Null), }; - let current_fork = chain_config.get_fork(block_header.timestamp); + let current_fork = chain_config.fork(block_header.timestamp); let transaction = match self.transaction.nonce { Some(_nonce) => self.transaction.clone(), diff --git a/crates/vm/backends/levm/mod.rs b/crates/vm/backends/levm/mod.rs index 87f026f8082..5a303f987e4 100644 --- a/crates/vm/backends/levm/mod.rs +++ b/crates/vm/backends/levm/mod.rs @@ -473,7 +473,7 @@ impl LEVM { ) -> Result<(), EvmError> { let chain_config = db.store.get_chain_config()?; let block_header = &block.header; - let fork = chain_config.get_fork(block_header.timestamp); + let fork = chain_config.fork(block_header.timestamp); // TODO: I don't like deciding the behavior based on the VMType here. if let VMType::L2(_) = vm_type { @@ -585,7 +585,7 @@ pub fn extract_all_requests_levm( } let chain_config = db.store.get_chain_config()?; - let fork = chain_config.get_fork(header.timestamp); + let fork = chain_config.fork(header.timestamp); if fork < Fork::Prague { return Ok(Default::default()); diff --git a/crates/vm/backends/mod.rs b/crates/vm/backends/mod.rs index a83e20a9ce3..8bbacc2aebd 100644 --- a/crates/vm/backends/mod.rs +++ b/crates/vm/backends/mod.rs @@ -125,7 +125,7 @@ impl Evm { /// This function is used to run/apply all the system contracts to the state. pub fn apply_system_calls(&mut self, block_header: &BlockHeader) -> Result<(), EvmError> { let chain_config = self.db.store.get_chain_config()?; - let fork = chain_config.get_fork(block_header.timestamp); + let fork = chain_config.fork(block_header.timestamp); if block_header.parent_beacon_block_root.is_some() && fork >= Fork::Cancun { LEVM::beacon_root_contract_call(block_header, &mut self.db, self.vm_type)?; diff --git a/crates/vm/levm/src/environment.rs b/crates/vm/levm/src/environment.rs index 5ce7f4120bf..18d37280b75 100644 --- a/crates/vm/levm/src/environment.rs +++ b/crates/vm/levm/src/environment.rs @@ -65,10 +65,10 @@ impl EVMConfig { } pub fn new_from_chain_config(chain_config: &ChainConfig, block_header: &BlockHeader) -> Self { - let fork = chain_config.get_fork(block_header.timestamp); + let fork = chain_config.fork(block_header.timestamp); let blob_schedule = chain_config - .get_blob_schedule_for_time(block_header.timestamp) + .get_fork_blob_schedule(block_header.timestamp) .unwrap_or_else(|| EVMConfig::canonical_values(fork)); EVMConfig::new(fork, blob_schedule) diff --git a/tooling/ef_tests/state/deserialize.rs b/tooling/ef_tests/state/deserialize.rs index ef8e1f5748b..f4772d7fc90 100644 --- a/tooling/ef_tests/state/deserialize.rs +++ b/tooling/ef_tests/state/deserialize.rs @@ -314,10 +314,8 @@ where "Prague" => Fork::Prague, "Osaka" => Fork::Osaka, "Byzantium" => Fork::Byzantium, - "EIP158" => Fork::EIP158, - "EIP150" => Fork::EIP150, - "EIP155" => Fork::EIP155, - + "EIP158" => Fork::SpuriousDragon, + "EIP150" => Fork::Tangerine, other => { return Err(serde::de::Error::custom(format!( "Unknown fork name: {other}", diff --git a/tooling/ef_tests/state/runner/revm_runner.rs b/tooling/ef_tests/state/runner/revm_runner.rs index d1805d20ccf..c04d74c19aa 100644 --- a/tooling/ef_tests/state/runner/revm_runner.rs +++ b/tooling/ef_tests/state/runner/revm_runner.rs @@ -676,11 +676,11 @@ pub async fn _ensure_post_state_revm( pub fn fork_to_spec_id(fork: Fork) -> SpecId { match fork { Fork::Frontier => SpecId::FRONTIER, + Fork::FrontierThawing => SpecId::FRONTIER_THAWING, Fork::Homestead => SpecId::HOMESTEAD, Fork::DaoFork => SpecId::DAO_FORK, - Fork::EIP150 => SpecId::TANGERINE, - Fork::EIP155 => SpecId::SPURIOUS_DRAGON, - Fork::EIP158 => SpecId::SPURIOUS_DRAGON, + Fork::Tangerine => SpecId::TANGERINE, + Fork::SpuriousDragon => SpecId::SPURIOUS_DRAGON, Fork::Byzantium => SpecId::BYZANTIUM, Fork::Constantinople => SpecId::CONSTANTINOPLE, Fork::Petersburg => SpecId::PETERSBURG, diff --git a/tooling/ef_tests/state_v2/src/modules/deserialize.rs b/tooling/ef_tests/state_v2/src/modules/deserialize.rs index 018569722ec..d8d61457577 100644 --- a/tooling/ef_tests/state_v2/src/modules/deserialize.rs +++ b/tooling/ef_tests/state_v2/src/modules/deserialize.rs @@ -151,8 +151,8 @@ where "Cancun" => Fork::Cancun, "Prague" => Fork::Prague, "Byzantium" => Fork::Byzantium, - "EIP158" => Fork::EIP158, - "EIP150" => Fork::EIP150, + "EIP158" => Fork::SpuriousDragon, + "EIP150" => Fork::Tangerine, other => { return Err(serde::de::Error::custom(format!( "Unknown fork name: {other}",