diff --git a/crates/ethereum/evm/src/execute.rs b/crates/ethereum/evm/src/execute.rs index c83931df9a36..cfee186c6334 100644 --- a/crates/ethereum/evm/src/execute.rs +++ b/crates/ethereum/evm/src/execute.rs @@ -11,7 +11,10 @@ use reth_evm::{ BatchExecutor, BlockExecutionError, BlockExecutionInput, BlockExecutionOutput, BlockExecutorProvider, BlockValidationError, Executor, ProviderError, }, - system_calls::{apply_beacon_root_contract_call, apply_withdrawal_requests_contract_call}, + system_calls::{ + apply_beacon_root_contract_call, apply_consolidation_requests_contract_call, + apply_withdrawal_requests_contract_call, + }, ConfigureEvm, }; use reth_execution_types::ExecutionOutcome; @@ -223,7 +226,11 @@ where let withdrawal_requests = apply_withdrawal_requests_contract_call(&self.evm_config, &mut evm)?; - [deposit_requests, withdrawal_requests].concat() + // Collect all EIP-7251 requests + let consolidation_requests = + apply_consolidation_requests_contract_call(&self.evm_config, &mut evm)?; + + [deposit_requests, withdrawal_requests, consolidation_requests].concat() } else { vec![] }; diff --git a/crates/evm/execution-errors/src/lib.rs b/crates/evm/execution-errors/src/lib.rs index 9cc4d2ec1285..1fdee985606b 100644 --- a/crates/evm/execution-errors/src/lib.rs +++ b/crates/evm/execution-errors/src/lib.rs @@ -98,6 +98,14 @@ pub enum BlockValidationError { /// The error message. message: String, }, + /// EVM error during consolidation requests contract call [EIP-7251] + /// + /// [EIP-7251]: https://eips.ethereum.org/EIPS/eip-7251 + #[error("failed to apply consolidation requests contract call: {message}")] + ConsolidationRequestsContractCall { + /// The error message. + message: String, + }, /// Error when decoding deposit requests from receipts [EIP-6110] /// /// [EIP-6110]: https://eips.ethereum.org/EIPS/eip-6110 diff --git a/crates/evm/src/system_calls.rs b/crates/evm/src/system_calls.rs index bba763f18bb2..9d493f51795e 100644 --- a/crates/evm/src/system_calls.rs +++ b/crates/evm/src/system_calls.rs @@ -4,6 +4,7 @@ use crate::ConfigureEvm; use alloy_eips::{ eip4788::BEACON_ROOTS_ADDRESS, eip7002::{WithdrawalRequest, WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS}, + eip7251::{ConsolidationRequest, CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS}, }; use reth_chainspec::{ChainSpec, EthereumHardforks}; use reth_execution_errors::{BlockExecutionError, BlockValidationError}; @@ -264,3 +265,138 @@ where Ok(withdrawal_requests) } + +/// Apply the [EIP-7251](https://eips.ethereum.org/EIPS/eip-7251) post block contract call. +/// +/// This constructs a new [Evm] with the given DB, and environment +/// ([`CfgEnvWithHandlerCfg`] and [`BlockEnv`]) to execute the post block contract call. +/// +/// This uses [`apply_consolidation_requests_contract_call`] to ultimately calculate the +/// [requests](Request). +pub fn post_block_consolidation_requests_contract_call( + evm_config: &EvmConfig, + db: &mut DB, + initialized_cfg: &CfgEnvWithHandlerCfg, + initialized_block_env: &BlockEnv, +) -> Result, BlockExecutionError> +where + DB: Database + DatabaseCommit, + DB::Error: std::fmt::Display, + EvmConfig: ConfigureEvm, +{ + // apply post-block EIP-7251 contract call + let mut evm_post_block = Evm::builder() + .with_db(db) + .with_env_with_handler_cfg(EnvWithHandlerCfg::new_with_cfg_env( + initialized_cfg.clone(), + initialized_block_env.clone(), + Default::default(), + )) + .build(); + + // initialize a block from the env, because the post block call needs the block itself + apply_consolidation_requests_contract_call::(evm_config, &mut evm_post_block) +} + +/// Applies the post-block call to the EIP-7251 consolidation requests contract. +/// +/// If Prague is not active at the given timestamp, then this is a no-op, and an empty vector is +/// returned. Otherwise, the consolidation requests are returned. +#[inline] +pub fn apply_consolidation_requests_contract_call( + evm_config: &EvmConfig, + evm: &mut Evm<'_, EXT, DB>, +) -> Result, BlockExecutionError> +where + DB: Database + DatabaseCommit, + DB::Error: core::fmt::Display, + EvmConfig: ConfigureEvm, +{ + // get previous env + let previous_env = Box::new(evm.context.env().clone()); + + // Fill transaction environment with the EIP-7251 consolidation requests contract message data. + // + // This requirement for the consolidation requests contract call defined by + // [EIP-7251](https://eips.ethereum.org/EIPS/eip-7251) is: + // + // At the end of processing any execution block where block.timestamp >= FORK_TIMESTAMP (i.e. + // after processing all transactions and after performing the block body requests validations) + // clienst software MUST [..] call the contract as `SYSTEM_ADDRESS` and empty input data to + // trigger the system subroutine execute. + evm_config.fill_tx_env_system_contract_call( + &mut evm.context.evm.env, + alloy_eips::eip7002::SYSTEM_ADDRESS, + CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, + Bytes::new(), + ); + + let ResultAndState { result, mut state } = match evm.transact() { + Ok(res) => res, + Err(e) => { + evm.context.evm.env = previous_env; + return Err(BlockValidationError::ConsolidationRequestsContractCall { + message: format!("execution failed: {e}"), + } + .into()) + } + }; + + // cleanup the state + state.remove(&alloy_eips::eip7002::SYSTEM_ADDRESS); + state.remove(&evm.block().coinbase); + evm.context.evm.db.commit(state); + + // re-set the previous env + evm.context.evm.env = previous_env; + + let mut data = match result { + ExecutionResult::Success { output, .. } => Ok(output.into_data()), + ExecutionResult::Revert { output, .. } => { + Err(BlockValidationError::ConsolidationRequestsContractCall { + message: format!("execution reverted: {output}"), + }) + } + ExecutionResult::Halt { reason, .. } => { + Err(BlockValidationError::ConsolidationRequestsContractCall { + message: format!("execution halted: {reason:?}"), + }) + } + }?; + + // Consolidations are encoded as a series of consolidation requests, each with the following + // format: + // + // +------+--------+---------------+ + // | addr | pubkey | target pubkey | + // +------+--------+---------------+ + // 20 48 48 + + const CONSOLIDATION_REQUEST_SIZE: usize = 20 + 48 + 48; + let mut consolidation_requests = Vec::with_capacity(data.len() / CONSOLIDATION_REQUEST_SIZE); + while data.has_remaining() { + if data.remaining() < CONSOLIDATION_REQUEST_SIZE { + return Err(BlockValidationError::ConsolidationRequestsContractCall { + message: "invalid consolidation request length".to_string(), + } + .into()) + } + + let mut source_address = Address::ZERO; + data.copy_to_slice(source_address.as_mut_slice()); + + let mut source_pubkey = FixedBytes::<48>::ZERO; + data.copy_to_slice(source_pubkey.as_mut_slice()); + + let mut target_pubkey = FixedBytes::<48>::ZERO; + data.copy_to_slice(target_pubkey.as_mut_slice()); + + consolidation_requests.push(Request::ConsolidationRequest(ConsolidationRequest { + source_address, + source_pubkey, + target_pubkey, + })); + } + + Ok(consolidation_requests) +}