use super::{executor::HlBlockExecutor, factory::HlEvmFactory}; use crate::{ HlBlock, HlBlockBody, HlPrimitives, chainspec::HlChainSpec, evm::{spec::HlSpecId, transaction::HlTxEnv}, hardforks::HlHardforks, node::{ evm::{executor::is_system_transaction, receipt_builder::RethReceiptBuilder}, primitives::{BlockBody, TransactionSigned}, rpc::engine_api::validator::HlExecutionData, types::HlExtras, }, }; use alloy_consensus::{BlockHeader, EMPTY_OMMER_ROOT_HASH, Header, Transaction as _, TxReceipt}; use alloy_eips::{Encodable2718, merge::BEACON_NONCE}; use alloy_primitives::{Log, U256}; use reth_chainspec::{EthChainSpec, EthereumHardforks, Hardforks}; use reth_evm::{ ConfigureEngineEvm, ConfigureEvm, EvmEnv, EvmEnvFor, EvmFactory, ExecutableTxIterator, ExecutionCtxFor, FromRecoveredTx, FromTxWithEncoded, IntoTxEnv, NextBlockEnvAttributes, block::{BlockExecutionError, BlockExecutorFactory, BlockExecutorFor}, eth::{EthBlockExecutionCtx, receipt_builder::ReceiptBuilder}, execute::{BlockAssembler, BlockAssemblerInput}, precompiles::PrecompilesMap, }; use reth_evm_ethereum::EthBlockAssembler; use reth_payload_primitives::NewPayloadError; use reth_primitives::{BlockTy, HeaderTy, Receipt, SealedBlock, SealedHeader, logs_bloom}; use reth_primitives_traits::{SignerRecoverable, WithEncoded, proofs}; use reth_provider::BlockExecutionResult; use reth_revm::State; use revm::{ Inspector, context::{BlockEnv, CfgEnv, TxEnv}, context_interface::block::BlobExcessGasAndPrice, primitives::hardfork::SpecId, }; use std::{borrow::Cow, convert::Infallible, sync::Arc}; #[derive(Debug, Clone)] pub struct HlBlockAssembler { inner: EthBlockAssembler, } impl BlockAssembler for HlBlockAssembler where F: for<'a> BlockExecutorFactory< ExecutionCtx<'a> = HlBlockExecutionCtx<'a>, Transaction = TransactionSigned, Receipt = Receipt, >, { type Block = HlBlock; fn assemble_block( &self, input: BlockAssemblerInput<'_, '_, F>, ) -> Result { // TODO: Copy of EthBlockAssembler::assemble_block let inner = &self.inner; let BlockAssemblerInput { evm_env, execution_ctx: ctx, parent, transactions, output: BlockExecutionResult { receipts, requests, gas_used }, state_root, .. } = input; let timestamp = evm_env.block_env.timestamp.saturating_to(); // Filter out system tx receipts let transactions_for_root: Vec<_> = transactions.iter().filter(|t| !is_system_transaction(t)).cloned().collect(); let receipts_for_root: Vec<_> = receipts.iter().filter(|r| r.cumulative_gas_used() != 0).cloned().collect(); let transactions_root = proofs::calculate_transaction_root(&transactions_for_root); let receipts_root = Receipt::calculate_receipt_root_no_memo(&receipts_for_root); let logs_bloom = logs_bloom(receipts.iter().flat_map(|r| r.logs())); let withdrawals = inner .chain_spec .is_shanghai_active_at_timestamp(timestamp) .then(|| ctx.ctx.withdrawals.map(|w| w.into_owned()).unwrap_or_default()); let withdrawals_root = withdrawals.as_deref().map(|w| proofs::calculate_withdrawals_root(w)); let requests_hash = inner .chain_spec .is_prague_active_at_timestamp(timestamp) .then(|| requests.requests_hash()); let mut excess_blob_gas = None; let mut blob_gas_used = None; // only determine cancun fields when active if inner.chain_spec.is_cancun_active_at_timestamp(timestamp) { blob_gas_used = Some(transactions.iter().map(|tx| tx.blob_gas_used().unwrap_or_default()).sum()); excess_blob_gas = if inner.chain_spec.is_cancun_active_at_timestamp(parent.timestamp) { parent.maybe_next_block_excess_blob_gas( inner.chain_spec.blob_params_at_timestamp(timestamp), ) } else { // for the first post-fork block, both parent.blob_gas_used and // parent.excess_blob_gas are evaluated as 0 Some( alloy_eips::eip7840::BlobParams::cancun() .next_block_excess_blob_gas_osaka(0, 0, 0), ) }; } let header = Header { parent_hash: ctx.ctx.parent_hash, ommers_hash: EMPTY_OMMER_ROOT_HASH, beneficiary: evm_env.block_env.beneficiary, state_root, transactions_root, receipts_root, withdrawals_root, logs_bloom, timestamp, mix_hash: evm_env.block_env.prevrandao.unwrap_or_default(), nonce: BEACON_NONCE.into(), base_fee_per_gas: Some(evm_env.block_env.basefee), number: evm_env.block_env.number.saturating_to(), gas_limit: evm_env.block_env.gas_limit, difficulty: evm_env.block_env.difficulty, gas_used: *gas_used, extra_data: inner.extra_data.clone(), parent_beacon_block_root: ctx.ctx.parent_beacon_block_root, blob_gas_used, excess_blob_gas, requests_hash, }; Ok(Self::Block { header, body: HlBlockBody { inner: BlockBody { transactions, ommers: Default::default(), withdrawals }, sidecars: None, read_precompile_calls: ctx.extras.read_precompile_calls.clone(), highest_precompile_address: ctx.extras.highest_precompile_address, }, }) } } impl HlBlockAssembler { pub fn new(chain_spec: Arc) -> Self { Self { inner: EthBlockAssembler::new(chain_spec) } } } /// Ethereum-related EVM configuration. #[derive(Debug, Clone)] pub struct HlEvmConfig { /// Inner [`HlBlockExecutorFactory`]. pub executor_factory: HlBlockExecutorFactory, HlEvmFactory>, /// Ethereum block assembler. pub block_assembler: HlBlockAssembler, } impl HlEvmConfig { /// Creates a new Ethereum EVM configuration with the given chain spec. pub fn new(chain_spec: Arc) -> Self { Self::hl(chain_spec) } /// Creates a new Ethereum EVM configuration. pub fn hl(chain_spec: Arc) -> Self { Self::new_with_evm_factory(chain_spec, HlEvmFactory::default()) } } impl HlEvmConfig { /// Creates a new Ethereum EVM configuration with the given chain spec and EVM factory. pub fn new_with_evm_factory(chain_spec: Arc, evm_factory: HlEvmFactory) -> Self { Self { block_assembler: HlBlockAssembler::new(chain_spec.clone()), executor_factory: HlBlockExecutorFactory::new( RethReceiptBuilder::default(), chain_spec, evm_factory, ), } } /// Returns the chain spec associated with this configuration. pub const fn chain_spec(&self) -> &Arc { self.executor_factory.spec() } } /// Ethereum block executor factory. #[derive(Debug, Clone, Default, Copy)] pub struct HlBlockExecutorFactory< R = RethReceiptBuilder, Spec = Arc, EvmFactory = HlEvmFactory, > { /// Receipt builder. receipt_builder: R, /// Chain specification. spec: Spec, /// EVM factory. evm_factory: EvmFactory, } impl HlBlockExecutorFactory { /// Creates a new [`HlBlockExecutorFactory`] with the given spec, [`EvmFactory`], and /// [`ReceiptBuilder`]. pub const fn new(receipt_builder: R, spec: Spec, evm_factory: EvmFactory) -> Self { Self { receipt_builder, spec, evm_factory } } /// Exposes the receipt builder. pub const fn receipt_builder(&self) -> &R { &self.receipt_builder } /// Exposes the chain specification. pub const fn spec(&self) -> &Spec { &self.spec } } #[derive(Debug, Clone)] pub struct HlBlockExecutionCtx<'a> { ctx: EthBlockExecutionCtx<'a>, pub extras: HlExtras, } impl BlockExecutorFactory for HlBlockExecutorFactory where R: ReceiptBuilder>, Spec: EthereumHardforks + HlHardforks + EthChainSpec + Hardforks + Clone, EvmF: EvmFactory< Tx: FromRecoveredTx + FromTxWithEncoded, Precompiles = PrecompilesMap, >, R::Transaction: From + Clone, Self: 'static, HlTxEnv: IntoTxEnv<::Tx>, { type EvmFactory = EvmF; type ExecutionCtx<'a> = HlBlockExecutionCtx<'a>; type Transaction = TransactionSigned; type Receipt = R::Receipt; fn evm_factory(&self) -> &Self::EvmFactory { &self.evm_factory } fn create_executor<'a, DB, I>( &'a self, evm: ::Evm<&'a mut State, I>, ctx: Self::ExecutionCtx<'a>, ) -> impl BlockExecutorFor<'a, Self, DB, I> where DB: alloy_evm::Database + 'a, I: Inspector<::Context<&'a mut State>> + 'a, { HlBlockExecutor::new(evm, ctx, self.spec().clone(), self.receipt_builder()) } } impl ConfigureEvm for HlEvmConfig where Self: Send + Sync + Unpin + Clone + 'static, { type Primitives = HlPrimitives; type Error = Infallible; type NextBlockEnvCtx = NextBlockEnvAttributes; type BlockExecutorFactory = HlBlockExecutorFactory; type BlockAssembler = Self; fn block_executor_factory(&self) -> &Self::BlockExecutorFactory { &self.executor_factory } fn block_assembler(&self) -> &Self::BlockAssembler { self } fn evm_env(&self, header: &Header) -> Result, Self::Error> { let blob_params = self.chain_spec().blob_params_at_timestamp(header.timestamp); let spec = revm_spec_by_timestamp_and_block_number( self.chain_spec().clone(), header.timestamp(), header.number(), ); // configure evm env based on parent block let mut cfg_env = CfgEnv::new().with_chain_id(self.chain_spec().chain().id()).with_spec(spec); if let Some(blob_params) = &blob_params { cfg_env.set_max_blobs_per_tx(blob_params.max_blobs_per_tx); } // TODO: enable only for system transactions cfg_env.disable_base_fee = true; cfg_env.disable_eip3607 = true; // derive the EIP-4844 blob fees from the header's `excess_blob_gas` and the current // blobparams let blob_excess_gas_and_price = header.excess_blob_gas.zip(blob_params).map(|(excess_blob_gas, params)| { let blob_gasprice = params.calc_blob_fee(excess_blob_gas); BlobExcessGasAndPrice { excess_blob_gas, blob_gasprice } }); let eth_spec = spec.into_eth_spec(); let block_env = BlockEnv { number: U256::from(header.number()), beneficiary: header.beneficiary(), timestamp: U256::from(header.timestamp()), difficulty: if eth_spec >= SpecId::MERGE { U256::ZERO } else { header.difficulty() }, prevrandao: if eth_spec >= SpecId::MERGE { header.mix_hash() } else { None }, gas_limit: header.gas_limit(), basefee: header.base_fee_per_gas().unwrap_or_default(), blob_excess_gas_and_price, }; Ok(EvmEnv { cfg_env, block_env }) } fn next_evm_env( &self, parent: &Header, attributes: &Self::NextBlockEnvCtx, ) -> Result, Self::Error> { // ensure we're not missing any timestamp based hardforks let spec_id = revm_spec_by_timestamp_and_block_number( self.chain_spec().clone(), attributes.timestamp, parent.number() + 1, ); // configure evm env based on parent block let cfg_env = CfgEnv::new().with_chain_id(self.chain_spec().chain().id()).with_spec(spec_id); // if the parent block did not have excess blob gas (i.e. it was pre-cancun), but it is // cancun now, we need to set the excess blob gas to the default value(0) let blob_excess_gas_and_price = spec_id .into_eth_spec() .is_enabled_in(SpecId::CANCUN) .then_some(BlobExcessGasAndPrice { excess_blob_gas: 0, blob_gasprice: 0 }); let basefee = parent.next_block_base_fee( self.chain_spec().base_fee_params_at_timestamp(attributes.timestamp), ); let block_env = BlockEnv { number: U256::from(parent.number() + 1), beneficiary: attributes.suggested_fee_recipient, timestamp: U256::from(attributes.timestamp), difficulty: U256::ZERO, prevrandao: Some(attributes.prev_randao), gas_limit: attributes.gas_limit, // calculate basefee based on parent block's gas usage basefee: basefee.unwrap_or_default(), // calculate excess gas based on parent block's blob gas usage blob_excess_gas_and_price, }; Ok(EvmEnv { cfg_env, block_env }) } fn context_for_block<'a>( &self, block: &'a SealedBlock>, ) -> Result, Self::Error> { let block_body = block.body(); Ok(HlBlockExecutionCtx { ctx: EthBlockExecutionCtx { parent_hash: block.header().parent_hash, parent_beacon_block_root: block.header().parent_beacon_block_root, ommers: &block.body().ommers, withdrawals: block.body().withdrawals.as_ref().map(Cow::Borrowed), }, extras: HlExtras { read_precompile_calls: block_body.read_precompile_calls.clone(), highest_precompile_address: block_body.highest_precompile_address, }, }) } fn context_for_next_block( &self, parent: &SealedHeader>, attributes: Self::NextBlockEnvCtx, ) -> Result, Self::Error> { Ok(HlBlockExecutionCtx { ctx: EthBlockExecutionCtx { parent_hash: parent.hash(), parent_beacon_block_root: attributes.parent_beacon_block_root, ommers: &[], withdrawals: attributes.withdrawals.map(Cow::Owned), }, extras: HlExtras::default(), // TODO: hacky, double check if this is correct }) } } impl ConfigureEngineEvm for HlEvmConfig { fn evm_env_for_payload(&self, payload: &HlExecutionData) -> EvmEnvFor { self.evm_env(&payload.0.header).unwrap() } fn context_for_payload<'a>(&self, payload: &'a HlExecutionData) -> ExecutionCtxFor<'a, Self> { let block = &payload.0; HlBlockExecutionCtx { ctx: EthBlockExecutionCtx { parent_hash: block.header.parent_hash, parent_beacon_block_root: block.header.parent_beacon_block_root, ommers: &block.body.ommers, withdrawals: block.body.withdrawals.as_ref().map(Cow::Borrowed), }, extras: HlExtras { read_precompile_calls: block.body.read_precompile_calls.clone(), highest_precompile_address: block.body.highest_precompile_address, }, } } fn tx_iterator_for_payload( &self, payload: &HlExecutionData, ) -> impl ExecutableTxIterator { payload.0.body.transactions.clone().into_iter().map(move |tx| { let recovered = tx.try_into_recovered().map_err(NewPayloadError::other)?; Ok::<_, NewPayloadError>(WithEncoded::new(recovered.encoded_2718().into(), recovered)) }) } } /// Map the latest active hardfork at the given timestamp or block number to a [`HlSpecId`]. pub fn revm_spec_by_timestamp_and_block_number( _chain_spec: impl HlHardforks, _timestamp: u64, _block_number: u64, ) -> HlSpecId { HlSpecId::V1 }