diff --git a/cmd/ef_tests/blockchain/tests/prague.rs b/cmd/ef_tests/blockchain/tests/prague.rs index db0522d6ed..6c5cce0661 100644 --- a/cmd/ef_tests/blockchain/tests/prague.rs +++ b/cmd/ef_tests/blockchain/tests/prague.rs @@ -20,10 +20,8 @@ fn parse_and_execute(path: &Path) -> datatest_stable::Result<()> { Ok(()) } -// TODO: Delete main function and uncomment the following line to allow prague tests to be parsed - -// datatest_stable::harness!(parse_and_execute, "vectors/prague/", r".*/.*/.*\.json"); - -fn main() { - //Do nothing -} +datatest_stable::harness!( + parse_and_execute, + "vectors/prague/eip2935_historical_block_hashes_from_state", + r".*/.*\.json" +); diff --git a/crates/blockchain/payload.rs b/crates/blockchain/payload.rs index c0b6f20fb2..319fb4d4a4 100644 --- a/crates/blockchain/payload.rs +++ b/crates/blockchain/payload.rs @@ -239,7 +239,7 @@ pub fn build_payload( debug!("Building payload"); let mut evm_state = evm_state(store.clone(), payload.header.parent_hash); let mut context = PayloadBuildContext::new(payload, &mut evm_state)?; - make_beacon_root_call(&mut context)?; + apply_system_operations(&mut context)?; apply_withdrawals(&mut context)?; fill_transactions(&mut context)?; finalize_payload(&mut context)?; @@ -301,7 +301,10 @@ pub fn apply_withdrawals(context: &mut PayloadBuildContext) -> Result<(), EvmErr Ok(()) } -pub fn make_beacon_root_call(context: &mut PayloadBuildContext) -> Result<(), EvmError> { +// This function applies system level operations: +// - Call beacon root contract, and obtain the new state root +// - Call block hash process contract, and store parent block hash +pub fn apply_system_operations(context: &mut PayloadBuildContext) -> Result<(), EvmError> { match EVM_BACKEND.get() { Some(EVM::LEVM) => { let fork = context @@ -312,6 +315,7 @@ pub fn make_beacon_root_call(context: &mut PayloadBuildContext) -> Result<(), Ev .get_fork_blob_schedule(context.payload.header.timestamp) .unwrap_or(EVMConfig::canonical_values(fork)); let config = EVMConfig::new(fork, blob_schedule); + let mut new_state = HashMap::new(); if context.payload.header.parent_beacon_block_root.is_some() && fork >= Fork::Cancun { let store_wrapper = Arc::new(StoreWrapper { @@ -319,23 +323,37 @@ pub fn make_beacon_root_call(context: &mut PayloadBuildContext) -> Result<(), Ev block_hash: context.payload.header.parent_hash, }); let report = backends::levm::beacon_root_contract_call_levm( - store_wrapper.clone(), + store_wrapper, &context.payload.header, config, )?; - let mut new_state = report.new_state.clone(); + new_state.extend(report.new_state); + } - // Now original_value is going to be the same as the current_value, for the next transaction. - // It should have only one value but it is convenient to keep on using our CacheDB structure - for account in new_state.values_mut() { - for storage_slot in account.storage.values_mut() { - storage_slot.original_value = storage_slot.current_value; - } - } + if fork >= Fork::Prague { + let store_wrapper = Arc::new(StoreWrapper { + store: context.evm_state.database().unwrap().clone(), + block_hash: context.payload.header.parent_hash, + }); + let report = backends::levm::process_block_hash_history( + store_wrapper, + &context.payload.header, + config, + )?; - context.block_cache.extend(new_state); + new_state.extend(report.new_state); } + + // Now original_value is going to be the same as the current_value, for the next transaction. + // It should have only one value but it is convenient to keep on using our CacheDB structure + for account in new_state.values_mut() { + for storage_slot in account.storage.values_mut() { + storage_slot.original_value = storage_slot.current_value; + } + } + + context.block_cache.extend(new_state); } // This means we are using REVM as default for tests Some(EVM::REVM) | None => { @@ -350,6 +368,14 @@ pub fn make_beacon_root_call(context: &mut PayloadBuildContext) -> Result<(), Ev spec_id, )?; } + + if spec_id >= SpecId::PRAGUE { + backends::revm::process_block_hash_history( + context.evm_state, + &context.payload.header, + spec_id, + )?; + } } } Ok(()) diff --git a/crates/vm/backends/constants.rs b/crates/vm/backends/constants.rs new file mode 100644 index 0000000000..2e98c6d227 --- /dev/null +++ b/crates/vm/backends/constants.rs @@ -0,0 +1,3 @@ +pub const SYSTEM_ADDRESS_STR: &str = "fffffffffffffffffffffffffffffffffffffffe"; +pub const BEACON_ROOTS_ADDRESS_STR: &str = "000F3df6D732807Ef1319fB7B8bB8522d0Beac02"; +pub const HISTORY_STORAGE_ADDRESS_STR: &str = "0000F90827F1C53a10cb7A02335B175320002935"; diff --git a/crates/vm/backends/levm.rs b/crates/vm/backends/levm.rs index a9ee3207a4..a11c6b256d 100644 --- a/crates/vm/backends/levm.rs +++ b/crates/vm/backends/levm.rs @@ -1,3 +1,4 @@ +use super::constants::{BEACON_ROOTS_ADDRESS_STR, HISTORY_STORAGE_ADDRESS_STR, SYSTEM_ADDRESS_STR}; use crate::db::StoreWrapper; use crate::EvmError; use crate::EvmState; @@ -47,6 +48,12 @@ pub fn execute_block( let report = beacon_root_contract_call_levm(store_wrapper.clone(), block_header, config)?; block_cache.extend(report.new_state); } + + if fork >= Fork::Prague { + //eip 2935: stores parent block hash in system contract + let report = process_block_hash_history(store_wrapper.clone(), block_header, config)?; + block_cache.extend(report.new_state); + } } } @@ -259,9 +266,9 @@ pub fn beacon_root_contract_call_levm( ) -> Result { lazy_static! { static ref SYSTEM_ADDRESS: Address = - Address::from_slice(&hex::decode("fffffffffffffffffffffffffffffffffffffffe").unwrap()); + Address::from_slice(&hex::decode(SYSTEM_ADDRESS_STR).unwrap()); static ref CONTRACT_ADDRESS: Address = - Address::from_slice(&hex::decode("000F3df6D732807Ef1319fB7B8bB8522d0Beac02").unwrap(),); + Address::from_slice(&hex::decode(BEACON_ROOTS_ADDRESS_STR).unwrap()); }; // This is OK let beacon_root = match block_header.parent_beacon_block_root { @@ -312,3 +319,58 @@ pub fn beacon_root_contract_call_levm( Ok(report) } + +/// Calls the EIP-2935 process block hashes history system call contract +/// NOTE: This was implemented by making use of an EVM system contract, but can be changed to a +/// direct state trie update after the verkle fork, as explained in https://eips.ethereum.org/EIPS/eip-2935 +pub fn process_block_hash_history( + store_wrapper: Arc, + block_header: &BlockHeader, + config: EVMConfig, +) -> Result { + lazy_static! { + static ref SYSTEM_ADDRESS: Address = + Address::from_slice(&hex::decode(SYSTEM_ADDRESS_STR).unwrap()); + static ref CONTRACT_ADDRESS: Address = + Address::from_slice(&hex::decode(HISTORY_STORAGE_ADDRESS_STR).unwrap(),); + }; + + let env = Environment { + origin: *SYSTEM_ADDRESS, + gas_limit: 30_000_000, + block_number: block_header.number.into(), + coinbase: block_header.coinbase, + timestamp: block_header.timestamp.into(), + prev_randao: Some(block_header.prev_randao), + base_fee_per_gas: U256::zero(), + gas_price: U256::zero(), + block_excess_blob_gas: block_header.excess_blob_gas.map(U256::from), + block_blob_gas_used: block_header.blob_gas_used.map(U256::from), + block_gas_limit: 30_000_000, + transient_storage: HashMap::new(), + config, + ..Default::default() + }; + + let calldata = Bytes::copy_from_slice(block_header.parent_hash.as_bytes()).into(); + + // Here execute with LEVM but just return transaction report. And I will handle it in the calling place. + + let mut vm = VM::new( + TxKind::Call(*CONTRACT_ADDRESS), + env, + U256::zero(), + calldata, + store_wrapper, + CacheDB::new(), + vec![], + None, + ) + .map_err(EvmError::from)?; + + let mut report = vm.execute().map_err(EvmError::from)?; + + report.new_state.remove(&*SYSTEM_ADDRESS); + + Ok(report) +} diff --git a/crates/vm/backends/mod.rs b/crates/vm/backends/mod.rs index e89fe5e38e..a5955c779b 100644 --- a/crates/vm/backends/mod.rs +++ b/crates/vm/backends/mod.rs @@ -1,3 +1,4 @@ +mod constants; pub mod levm; pub mod revm; diff --git a/crates/vm/backends/revm.rs b/crates/vm/backends/revm.rs index c0725ee183..3714a23403 100644 --- a/crates/vm/backends/revm.rs +++ b/crates/vm/backends/revm.rs @@ -1,3 +1,4 @@ +use super::constants::{BEACON_ROOTS_ADDRESS_STR, HISTORY_STORAGE_ADDRESS_STR, SYSTEM_ADDRESS_STR}; use crate::spec_id; use crate::EvmError; use crate::EvmState; @@ -40,8 +41,14 @@ pub fn execute_block(block: &Block, state: &mut EvmState) -> Result if block_header.parent_beacon_block_root.is_some() && spec_id >= SpecId::CANCUN { beacon_root_contract_call(state, block_header, spec_id)?; } + + //eip 2935: stores parent block hash in system contract + if spec_id >= SpecId::PRAGUE { + process_block_hash_history(state, block_header, spec_id)?; + } } } + let mut receipts = Vec::new(); let mut cumulative_gas_used = 0; @@ -437,12 +444,10 @@ pub fn beacon_root_contract_call( spec_id: SpecId, ) -> Result { lazy_static! { - static ref SYSTEM_ADDRESS: RevmAddress = RevmAddress::from_slice( - &hex::decode("fffffffffffffffffffffffffffffffffffffffe").unwrap() - ); - static ref CONTRACT_ADDRESS: RevmAddress = RevmAddress::from_slice( - &hex::decode("000F3df6D732807Ef1319fB7B8bB8522d0Beac02").unwrap(), - ); + static ref SYSTEM_ADDRESS: RevmAddress = + RevmAddress::from_slice(&hex::decode(SYSTEM_ADDRESS_STR).unwrap()); + static ref CONTRACT_ADDRESS: RevmAddress = + RevmAddress::from_slice(&hex::decode(BEACON_ROOTS_ADDRESS_STR).unwrap()); }; let beacon_root = match header.parent_beacon_block_root { None => { @@ -496,3 +501,61 @@ pub fn beacon_root_contract_call( } } } + +/// Calls the EIP-2935 process block hashes history system call contract +/// NOTE: This was implemented by making use of an EVM system contract, but can be changed to a +/// direct state trie update after the verkle fork, as explained in https://eips.ethereum.org/EIPS/eip-2935 +pub fn process_block_hash_history( + state: &mut EvmState, + header: &BlockHeader, + spec_id: SpecId, +) -> Result { + lazy_static! { + static ref SYSTEM_ADDRESS: RevmAddress = + RevmAddress::from_slice(&hex::decode(SYSTEM_ADDRESS_STR).unwrap()); + static ref CONTRACT_ADDRESS: RevmAddress = + RevmAddress::from_slice(&hex::decode(HISTORY_STORAGE_ADDRESS_STR).unwrap(),); + }; + let tx_env = TxEnv { + caller: *SYSTEM_ADDRESS, + transact_to: RevmTxKind::Call(*CONTRACT_ADDRESS), + gas_limit: 30_000_000, + data: revm::primitives::Bytes::copy_from_slice(header.parent_hash.as_bytes()), + ..Default::default() + }; + let mut block_env = block_env(header); + block_env.basefee = RevmU256::ZERO; + block_env.gas_limit = RevmU256::from(30_000_000); + + match state { + EvmState::Store(db) => { + let mut evm = Evm::builder() + .with_db(db) + .with_block_env(block_env) + .with_tx_env(tx_env) + .with_spec_id(spec_id) + .build(); + + let transaction_result = evm.transact()?; + let mut result_state = transaction_result.state; + result_state.remove(&*SYSTEM_ADDRESS); + result_state.remove(&evm.block().coinbase); + + evm.context.evm.db.commit(result_state); + + Ok(transaction_result.result.into()) + } + EvmState::Execution(db) => { + let mut evm = Evm::builder() + .with_db(db) + .with_block_env(block_env) + .with_tx_env(tx_env) + .with_spec_id(spec_id) + .build(); + + // Not necessary to commit to DB + let transaction_result = evm.transact()?; + Ok(transaction_result.result.into()) + } + } +} diff --git a/test_data/genesis-execution-api.json b/test_data/genesis-execution-api.json index 951e53525d..22379f4dfe 100644 --- a/test_data/genesis-execution-api.json +++ b/test_data/genesis-execution-api.json @@ -29,6 +29,11 @@ "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "coinbase": "0x0000000000000000000000000000000000000000", "alloc": { + "0F792be4B0c0cb4DAE440Ef133E90C0eCD48CCCC": { + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe14604657602036036042575f35600143038111604257611fff81430311604257611fff9006545f5260205ff35b5f5ffd5b5f35611fff60014303065500", + "balance": "0x0", + "nonce": "0x1" + }, "000f3df6d732807ef1319fb7b8bb8522d0beac02": { "code": "0x3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500", "balance": "0x2a" diff --git a/test_data/genesis-l1.json b/test_data/genesis-l1.json index ff44978f17..c415a9258b 100644 --- a/test_data/genesis-l1.json +++ b/test_data/genesis-l1.json @@ -19,6 +19,11 @@ "pragueTime": 1718232101 }, "alloc": { + "0x0F792be4B0c0cb4DAE440Ef133E90C0eCD48CCCC": { + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe14604657602036036042575f35600143038111604257611fff81430311604257611fff9006545f5260205ff35b5f5ffd5b5f35611fff60014303065500", + "balance": "0", + "nonce": "0x1" + }, "0x4e59b44847b379578588920cA78FbF26c0B4956C": { "balance": "0", "code": "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3"