diff --git a/Cargo.lock b/Cargo.lock index 17f09a4..45bf757 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6970,13 +6970,17 @@ dependencies = [ "libc", "reth", "reth-auto-seal-consensus", + "reth-basic-payload-builder", "reth-chainspec", "reth-consensus", "reth-engine-primitives", + "reth-errors", "reth-ethereum-consensus", + "reth-ethereum-payload-builder", "reth-evm", "reth-evm-ethereum", "reth-node-ethereum", + "reth-provider", "reth-prune-types", "revm", "revm-primitives", diff --git a/Cargo.toml b/Cargo.toml index ad725f3..57292bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,10 @@ reth-chainspec = { git = "https://github.com/paradigmxyz/reth", rev = "d59939377 reth-consensus = { git = "https://github.com/paradigmxyz/reth", rev = "d599393771f9d7d137ea4abf271e1bd118184c73" } reth-auto-seal-consensus = { git = "https://github.com/paradigmxyz/reth", rev = "d599393771f9d7d137ea4abf271e1bd118184c73" } reth-prune-types = { git = "https://github.com/paradigmxyz/reth", rev = "d599393771f9d7d137ea4abf271e1bd118184c73" } +reth-basic-payload-builder = { git = "https://github.com/paradigmxyz/reth", rev = "d599393771f9d7d137ea4abf271e1bd118184c73" } +reth-ethereum-payload-builder = { git = "https://github.com/paradigmxyz/reth", rev = "d599393771f9d7d137ea4abf271e1bd118184c73" } +reth-provider = { git = "https://github.com/paradigmxyz/reth", rev = "d599393771f9d7d137ea4abf271e1bd118184c73" } +reth-errors = { git = "https://github.com/paradigmxyz/reth", rev = "d599393771f9d7d137ea4abf271e1bd118184c73" } eyre = "0.6.12" clap = { version = "4.5.6", features = ["derive"] } alloy-sol-macro = "0.7.6" diff --git a/src/execute.rs b/src/execute.rs index 0ecb725..851706f 100644 --- a/src/execute.rs +++ b/src/execute.rs @@ -1,6 +1,7 @@ use crate::ethereum::{EthEvmExecutor, EthExecuteOutput}; use crate::gnosis::{apply_block_rewards_contract_call, apply_withdrawals_contract_call}; use eyre::eyre; +use reth::primitives::Withdrawals; use reth::providers::ExecutionOutcome; use reth::{ api::ConfigureEvm, @@ -20,8 +21,9 @@ use reth_evm::execute::{ BlockExecutorProvider, BlockValidationError, Executor, }; use reth_prune_types::PruneModes; +use revm::Evm; use std::fmt::Display; -use std::{collections::HashMap, sync::Arc}; +use std::sync::Arc; #[derive(Debug, Clone)] pub struct GnosisExecutorProvider { @@ -206,7 +208,7 @@ where self.state.set_state_clear_flag(state_clear_flag); } - /// Apply post execution state changes that do not require an such as: block + /// Apply post execution state changes that do not require an evm such as: block /// rewards, withdrawals, and irregular DAO hardfork state change // [Gnosis/fork:DIFF] pub fn post_execution( @@ -225,43 +227,92 @@ where // - Call block rewards contract for bridged xDAI mint let chain_spec = self.chain_spec_clone(); + let mut cfg = CfgEnvWithHandlerCfg::new(Default::default(), Default::default()); + let mut block_env = BlockEnv::default(); + self.executor.evm_config.fill_cfg_and_block_env( + &mut cfg, + &mut block_env, + self.chain_spec(), + &block.header, + total_difficulty, + ); - if chain_spec.is_shanghai_active_at_timestamp(block.timestamp) { - if let Some(withdrawals) = block.withdrawals.as_ref() { - let env = self.evm_env_for_block(&block.header, total_difficulty); - let mut evm = self.executor.evm_config.evm_with_env(&mut self.state, env); - - apply_withdrawals_contract_call( - &chain_spec, - block.timestamp, - withdrawals, - &mut evm, - )?; - } - } - - // TODO: Only post merge? - let balance_increments: HashMap = { - let env = self.evm_env_for_block(&block.header, total_difficulty); - let mut evm = self.executor.evm_config.evm_with_env(&mut self.state, env); - - apply_block_rewards_contract_call( - self.block_rewards_contract, - block.timestamp, - block.beneficiary, - &mut evm, - )? - }; - - // increment balances - self.state - .increment_balances(balance_increments) - .map_err(|_| BlockValidationError::IncrementBalanceFailed)?; + gnosis_post_block_system_calls::( + &chain_spec, + &self.executor.evm_config, + &mut self.state, + &cfg, + &block_env, + self.block_rewards_contract, + block.timestamp, + block.withdrawals.as_ref(), + block.beneficiary, + )?; Ok(()) } } +#[allow(clippy::too_many_arguments)] +pub(crate) fn gnosis_post_block_system_calls( + chain_spec: &ChainSpec, + evm_config: &EvmConfig, + db: &mut State, + initialized_cfg: &CfgEnvWithHandlerCfg, + initialized_block_env: &BlockEnv, + block_rewards_contract: Address, + block_timestamp: u64, + withdrawals: Option<&Withdrawals>, + coinbase: Address, +) -> Result<(), BlockExecutionError> +where + EvmConfig: ConfigureEvm, + DB: Database + Display>, +{ + let mut evm = 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(); + + // [Gnosis/fork:DIFF]: Upstream code in EthBlockExecutor computes balance changes for: + // - Pre-merge omer and block rewards + // - Beacon withdrawal mints + // - DAO hardfork drain balances + // + // For gnosis instead: + // - Do NOT credit withdrawals as native token mint + // - Call into deposit contract with withdrawal data + // - Call block rewards contract for bridged xDAI mint + + if chain_spec.is_shanghai_active_at_timestamp(block_timestamp) { + let withdrawals = withdrawals.ok_or(BlockExecutionError::Other( + "block has no withdrawals field".to_owned().into(), + ))?; + apply_withdrawals_contract_call(evm_config, chain_spec, withdrawals, &mut evm)?; + } + + let balance_increments = apply_block_rewards_contract_call( + evm_config, + block_rewards_contract, + block_timestamp, + coinbase, + &mut evm, + )?; + + // increment balances + evm.context + .evm + .db + .increment_balances(balance_increments) + .map_err(|_| BlockValidationError::IncrementBalanceFailed)?; + + Ok(()) +} + // Trait required by BlockExecutorProvider associated type Executor impl Executor for GnosisBlockExecutor where diff --git a/src/gnosis.rs b/src/gnosis.rs index 0d68b3b..8202bbb 100644 --- a/src/gnosis.rs +++ b/src/gnosis.rs @@ -3,15 +3,15 @@ use std::collections::HashMap; use alloy_sol_macro::sol; use alloy_sol_types::SolCall; use reth::{ - primitives::{address, Address, Bytes, Withdrawal, U256}, + primitives::{address, Address, Withdrawal, U256}, revm::{ interpreter::Host, - primitives::{Env, ExecutionResult, Output, ResultAndState, TransactTo, TxEnv}, + primitives::{ExecutionResult, Output, ResultAndState}, Database, DatabaseCommit, Evm, }, }; use reth_chainspec::ChainSpec; -use reth_evm::execute::BlockExecutionError; +use reth_evm::{execute::BlockExecutionError, ConfigureEvm}; pub const SYSTEM_ADDRESS: Address = address!("fffffffffffffffffffffffffffffffffffffffe"); @@ -39,14 +39,16 @@ sol!( /// /// Ref: #[inline] -pub fn apply_withdrawals_contract_call( +pub fn apply_withdrawals_contract_call( + evm_config: &EvmConfig, chain_spec: &ChainSpec, - _block_timestamp: u64, withdrawals: &[Withdrawal], evm: &mut Evm<'_, EXT, DB>, ) -> Result<(), BlockExecutionError> where + DB: Database + DatabaseCommit, DB::Error: std::fmt::Display, + EvmConfig: ConfigureEvm, { // TODO: how is the deposit contract address passed to here? let withdrawal_contract_address = chain_spec @@ -64,7 +66,7 @@ where let previous_env = Box::new(evm.context.env().clone()); // modify env for pre block call - fill_tx_env_with_system_contract_call( + evm_config.fill_tx_env_system_contract_call( &mut evm.context.evm.env, SYSTEM_ADDRESS, withdrawal_contract_address, @@ -104,20 +106,23 @@ where /// /// Ref: #[inline] -pub fn apply_block_rewards_contract_call( +pub fn apply_block_rewards_contract_call( + evm_config: &EvmConfig, block_rewards_contract: Address, _block_timestamp: u64, coinbase: Address, evm: &mut Evm<'_, EXT, DB>, ) -> Result, BlockExecutionError> where + DB: Database + DatabaseCommit, DB::Error: std::fmt::Display, + EvmConfig: ConfigureEvm, { // get previous env let previous_env = Box::new(evm.context.env().clone()); // modify env for pre block call - fill_tx_env_with_system_contract_call( + evm_config.fill_tx_env_system_contract_call( &mut evm.context.evm.env, SYSTEM_ADDRESS, block_rewards_contract, @@ -189,48 +194,3 @@ where Ok(balance_increments) } - -/// Fill transaction environment with the system caller and the system contract address and message -/// data. -/// -/// This is a system operation and therefore: -/// * the call must execute to completion -/// * the call does not count against the block’s gas limit -/// * the call does not follow the EIP-1559 burn semantics - no value should be transferred as part -/// of the call -/// * if no code exists at the provided address, the call will fail silently -fn fill_tx_env_with_system_contract_call( - env: &mut Env, - caller: Address, - contract: Address, - data: Bytes, -) { - env.tx = TxEnv { - caller, - transact_to: TransactTo::Call(contract), - // Explicitly set nonce to None so revm does not do any nonce checks - nonce: None, - gas_limit: 30_000_000, - value: U256::ZERO, - data, - // Setting the gas price to zero enforces that no value is transferred as part of the call, - // and that the call will not count against the block's gas limit - gas_price: U256::ZERO, - // The chain ID check is not relevant here and is disabled if set to None - chain_id: None, - // Setting the gas priority fee to None ensures the effective gas price is derived from the - // `gas_price` field, which we need to be zero - gas_priority_fee: None, - access_list: Vec::new(), - // blob fields can be None for this tx - blob_hashes: Vec::new(), - max_fee_per_blob_gas: None, - authorization_list: None, - }; - - // ensure the block gas limit is >= the tx - env.block.gas_limit = U256::from(env.tx.gas_limit); - - // disable the base fee check for this call by setting the base fee to zero - env.block.basefee = U256::ZERO; -} diff --git a/src/lib.rs b/src/lib.rs index 79d9082..7f8e51b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,8 @@ use consensus::GnosisBeaconConsensus; use evm_config::GnosisEvmConfig; use execute::GnosisExecutorProvider; use eyre::eyre; +use gnosis::SYSTEM_ADDRESS; +use payload_builder::GnosisPayloadServiceBuilder; use reth::{ api::NodeTypes, builder::{ @@ -11,10 +13,7 @@ use reth::{ }, }; use reth_node_ethereum::{ - node::{ - EthereumConsensusBuilder, EthereumNetworkBuilder, EthereumPayloadBuilder, - EthereumPoolBuilder, - }, + node::{EthereumNetworkBuilder, EthereumPoolBuilder}, EthEngineTypes, EthereumNode, }; use std::sync::Arc; @@ -24,6 +23,7 @@ mod ethereum; mod evm_config; mod execute; mod gnosis; +mod payload_builder; #[derive(Debug, Clone, Default, PartialEq, Eq, clap::Args)] #[command(next_help_heading = "Gnosis")] @@ -33,10 +33,10 @@ pub struct GnosisArgs { pub sample_arg: Option, } -/// Type configuration for a regular Optimism node. +/// Type configuration for a regular Gnosis node. #[derive(Debug, Default, Clone)] pub struct GnosisNode { - /// Additional Optimism args + /// Additional Gnosis args pub args: GnosisArgs, } @@ -51,15 +51,21 @@ impl GnosisNode { ) -> ComponentsBuilder< Node, EthereumPoolBuilder, - EthereumPayloadBuilder, + GnosisPayloadServiceBuilder, EthereumNetworkBuilder, GnosisExecutorBuilder, - EthereumConsensusBuilder, + GnosisConsensusBuilder, > where Node: FullNodeTypes, { - EthereumNode::components().executor(GnosisExecutorBuilder::default()) + EthereumNode::components() + .payload(GnosisPayloadServiceBuilder::new(GnosisEvmConfig { + // TODO: fix address + collector_address: SYSTEM_ADDRESS, + })) + .executor(GnosisExecutorBuilder::default()) + .consensus(GnosisConsensusBuilder::default()) } } @@ -76,10 +82,10 @@ where type ComponentsBuilder = ComponentsBuilder< N, EthereumPoolBuilder, - EthereumPayloadBuilder, + GnosisPayloadServiceBuilder, EthereumNetworkBuilder, GnosisExecutorBuilder, - EthereumConsensusBuilder, + GnosisConsensusBuilder, >; fn components_builder(self) -> Self::ComponentsBuilder { @@ -88,7 +94,7 @@ where } } -/// A regular optimism evm and executor builder. +/// A regular Gnosis evm and executor builder. #[derive(Debug, Default, Clone, Copy)] #[non_exhaustive] pub struct GnosisExecutorBuilder; @@ -125,7 +131,7 @@ where } } -/// A basic optimism consensus builder. +/// A basic Gnosis consensus builder. #[derive(Debug, Default, Clone)] #[non_exhaustive] pub struct GnosisConsensusBuilder; diff --git a/src/payload_builder.rs b/src/payload_builder.rs new file mode 100644 index 0000000..ab983fd --- /dev/null +++ b/src/payload_builder.rs @@ -0,0 +1,643 @@ +use eyre::eyre; +use reth::{ + api::FullNodeTypes, + builder::{components::PayloadServiceBuilder, BuilderContext, PayloadBuilderConfig}, + payload::{ + error::PayloadBuilderError, EthBuiltPayload, EthPayloadBuilderAttributes, + PayloadBuilderHandle, PayloadBuilderService, + }, + primitives::{ + constants::{ + eip4844::MAX_DATA_GAS_PER_BLOCK, BEACON_NONCE, EMPTY_RECEIPTS, EMPTY_TRANSACTIONS, + EMPTY_WITHDRAWALS, + }, + eip4844::calculate_excess_blob_gas, + proofs::{self, calculate_requests_root}, + Block, Header, IntoRecoveredTransaction, Receipt, EMPTY_OMMER_ROOT_HASH, + }, + revm::{database::StateProviderDatabase, state_change::apply_blockhashes_update}, + transaction_pool::{BestTransactionsAttributes, TransactionPool}, +}; +use reth_basic_payload_builder::{ + commit_withdrawals, is_better_payload, BasicPayloadJobGenerator, + BasicPayloadJobGeneratorConfig, BuildArguments, BuildOutcome, PayloadBuilder, PayloadConfig, + WithdrawalsOutcome, +}; +use reth_chainspec::EthereumHardforks; +use reth_errors::RethError; +use reth_evm::{ + system_calls::{ + post_block_withdrawal_requests_contract_call, pre_block_beacon_root_contract_call, + }, + ConfigureEvm, +}; +use reth_evm_ethereum::eip6110::parse_deposits_from_receipts; +use reth_node_ethereum::EthEngineTypes; +use reth_provider::{CanonStateSubscriptions, ExecutionOutcome, StateProviderFactory}; +use revm::{db::states::bundle_state::BundleRetention, DatabaseCommit, State}; +use revm_primitives::{ + Address, EVMError, EnvWithHandlerCfg, InvalidTransaction, ResultAndState, U256, +}; + +use crate::{evm_config::GnosisEvmConfig, execute::gnosis_post_block_system_calls}; + +/// A basic Gnosis payload service builder +#[derive(Debug, Default, Clone)] +pub struct GnosisPayloadServiceBuilder { + /// The EVM configuration to use for the payload builder. + pub evm_config: EVM, +} + +impl GnosisPayloadServiceBuilder { + /// Create a new instance with the given evm config. + pub const fn new(evm_config: EVM) -> Self { + Self { evm_config } + } +} + +impl PayloadServiceBuilder for GnosisPayloadServiceBuilder +where + Node: FullNodeTypes, + Pool: TransactionPool + Unpin + 'static, + EVM: ConfigureEvm, +{ + async fn spawn_payload_service( + self, + ctx: &BuilderContext, + pool: Pool, + ) -> eyre::Result> { + let chain_spec = ctx.chain_spec(); + let block_rewards_contract = chain_spec + .genesis() + .config + .extra_fields + .get("blockRewardsContract") + .ok_or(eyre!("blockRewardsContract not defined"))?; + let block_rewards_contract: Address = + serde_json::from_value(block_rewards_contract.clone())?; + + let payload_builder = GnosisPayloadBuilder::new(self.evm_config, block_rewards_contract); + let conf = ctx.payload_builder_config(); + + let payload_job_config = BasicPayloadJobGeneratorConfig::default() + .interval(conf.interval()) + .deadline(conf.deadline()) + .max_payload_tasks(conf.max_payload_tasks()) + .extradata(conf.extradata_bytes()); + + let payload_generator = BasicPayloadJobGenerator::with_builder( + ctx.provider().clone(), + pool, + ctx.task_executor().clone(), + payload_job_config, + ctx.chain_spec(), + payload_builder, + ); + let (payload_service, payload_builder) = + PayloadBuilderService::new(payload_generator, ctx.provider().canonical_state_stream()); + + ctx.task_executor() + .spawn_critical("payload builder service", Box::pin(payload_service)); + + Ok(payload_builder) + } +} + +/// Ethereum payload builder +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct GnosisPayloadBuilder { + /// The type responsible for creating the evm. + evm_config: EvmConfig, + /// AuRa BlockRewards contract address for its system call + block_rewards_contract: Address, +} + +impl GnosisPayloadBuilder { + pub const fn new(evm_config: EvmConfig, block_rewards_contract: Address) -> Self { + Self { + evm_config, + block_rewards_contract, + } + } +} + +// Default implementation of [PayloadBuilder] for unit type +impl PayloadBuilder for GnosisPayloadBuilder +where + EvmConfig: ConfigureEvm, + Client: StateProviderFactory, + Pool: TransactionPool, +{ + type Attributes = EthPayloadBuilderAttributes; + type BuiltPayload = EthBuiltPayload; + + fn try_build( + &self, + args: BuildArguments, + ) -> Result, PayloadBuilderError> { + default_ethereum_payload_builder(self.evm_config.clone(), args, self.block_rewards_contract) + } + + fn build_empty_payload( + &self, + client: &Client, + config: PayloadConfig, + ) -> Result { + let extra_data = config.extra_data(); + let PayloadConfig { + initialized_block_env, + parent_block, + attributes, + chain_spec, + initialized_cfg, + .. + } = config; + + let state = client.state_by_block_hash(parent_block.hash())?; + let mut db = State::builder() + .with_database(StateProviderDatabase::new(state)) + .with_bundle_update() + .build(); + + let base_fee = initialized_block_env.basefee.to::(); + let block_number = initialized_block_env.number.to::(); + let block_gas_limit = initialized_block_env + .gas_limit + .try_into() + .unwrap_or(u64::MAX); + + // apply eip-4788 pre block contract call + pre_block_beacon_root_contract_call( + &mut db, + &self.evm_config, + &chain_spec, + &initialized_cfg, + &initialized_block_env, + block_number, + attributes.timestamp, + attributes.parent_beacon_block_root, + ) + .map_err(|err| PayloadBuilderError::Internal(err.into()))?; + + // apply eip-2935 blockhashes update + apply_blockhashes_update( + &mut db, + &chain_spec, + initialized_block_env.timestamp.to::(), + block_number, + parent_block.hash(), + ) + .map_err(|err| PayloadBuilderError::Internal(err.into()))?; + + let WithdrawalsOutcome { + withdrawals_root, + withdrawals, + } = commit_withdrawals( + &mut db, + &chain_spec, + attributes.timestamp, + attributes.withdrawals.clone(), + )?; + + // merge all transitions into bundle state, this would apply the withdrawal balance + // changes and 4788 contract call + db.merge_transitions(BundleRetention::PlainState); + + // calculate the state root + let bundle_state = db.take_bundle(); + let state_root = db.database.state_root(&bundle_state)?; + + let mut excess_blob_gas = None; + let mut blob_gas_used = None; + + if chain_spec.is_cancun_active_at_timestamp(attributes.timestamp) { + excess_blob_gas = if chain_spec.is_cancun_active_at_timestamp(parent_block.timestamp) { + let parent_excess_blob_gas = parent_block.excess_blob_gas.unwrap_or_default(); + let parent_blob_gas_used = parent_block.blob_gas_used.unwrap_or_default(); + Some(calculate_excess_blob_gas( + parent_excess_blob_gas, + parent_blob_gas_used, + )) + } else { + // for the first post-fork block, both parent.blob_gas_used and + // parent.excess_blob_gas are evaluated as 0 + Some(calculate_excess_blob_gas(0, 0)) + }; + + blob_gas_used = Some(0); + } + + // Calculate the requests and the requests root. + let (requests, requests_root) = if chain_spec + .is_prague_active_at_timestamp(attributes.timestamp) + { + // We do not calculate the EIP-6110 deposit requests because there are no + // transactions in an empty payload. + let withdrawal_requests = post_block_withdrawal_requests_contract_call::( + &self.evm_config, + &mut db, + &initialized_cfg, + &initialized_block_env, + ) + .map_err(|err| PayloadBuilderError::Internal(err.into()))?; + + let requests = withdrawal_requests; + let requests_root = calculate_requests_root(&requests); + (Some(requests.into()), Some(requests_root)) + } else { + (None, None) + }; + + let header = Header { + parent_hash: parent_block.hash(), + ommers_hash: EMPTY_OMMER_ROOT_HASH, + beneficiary: initialized_block_env.coinbase, + state_root, + transactions_root: EMPTY_TRANSACTIONS, + withdrawals_root, + receipts_root: EMPTY_RECEIPTS, + logs_bloom: Default::default(), + timestamp: attributes.timestamp, + mix_hash: attributes.prev_randao, + nonce: BEACON_NONCE, + base_fee_per_gas: Some(base_fee), + number: parent_block.number + 1, + gas_limit: block_gas_limit, + difficulty: U256::ZERO, + gas_used: 0, + extra_data, + blob_gas_used, + excess_blob_gas, + parent_beacon_block_root: attributes.parent_beacon_block_root, + requests_root, + }; + + let block = Block { + header, + body: vec![], + ommers: vec![], + withdrawals, + requests, + }; + let sealed_block = block.seal_slow(); + + Ok(EthBuiltPayload::new( + attributes.payload_id(), + sealed_block, + U256::ZERO, + )) + } +} + +/// Constructs an Ethereum transaction payload using the best transactions from the pool. +/// +/// Given build arguments including an Ethereum client, transaction pool, +/// and configuration, this function creates a transaction payload. Returns +/// a result indicating success with the payload or an error in case of failure. +#[inline] +pub fn default_ethereum_payload_builder( + evm_config: EvmConfig, + args: BuildArguments, + block_rewards_contract: Address, +) -> Result, PayloadBuilderError> +where + EvmConfig: ConfigureEvm, + Client: StateProviderFactory, + Pool: TransactionPool, +{ + let BuildArguments { + client, + pool, + mut cached_reads, + config, + cancel, + best_payload, + } = args; + + let state_provider = client.state_by_block_hash(config.parent_block.hash())?; + let state = StateProviderDatabase::new(state_provider); + let mut db = State::builder() + .with_database_ref(cached_reads.as_db(state)) + .with_bundle_update() + .build(); + let extra_data = config.extra_data(); + let PayloadConfig { + initialized_block_env, + initialized_cfg, + parent_block, + attributes, + chain_spec, + .. + } = config; + + let mut cumulative_gas_used = 0; + let mut sum_blob_gas_used = 0; + let block_gas_limit: u64 = initialized_block_env + .gas_limit + .try_into() + .unwrap_or(u64::MAX); + let base_fee = initialized_block_env.basefee.to::(); + + let mut executed_txs = Vec::new(); + + let mut best_txs = pool.best_transactions_with_attributes(BestTransactionsAttributes::new( + base_fee, + initialized_block_env + .get_blob_gasprice() + .map(|gasprice| gasprice as u64), + )); + + let mut total_fees = U256::ZERO; + + let block_number = initialized_block_env.number.to::(); + + // apply eip-4788 pre block contract call + pre_block_beacon_root_contract_call( + &mut db, + &evm_config, + &chain_spec, + &initialized_cfg, + &initialized_block_env, + block_number, + attributes.timestamp, + attributes.parent_beacon_block_root, + ) + .map_err(|err| PayloadBuilderError::Internal(err.into()))?; + + // apply eip-2935 blockhashes update + apply_blockhashes_update( + &mut db, + &chain_spec, + initialized_block_env.timestamp.to::(), + block_number, + parent_block.hash(), + ) + .map_err(|err| PayloadBuilderError::Internal(err.into()))?; + + let mut receipts = Vec::new(); + while let Some(pool_tx) = best_txs.next() { + // ensure we still have capacity for this transaction + if cumulative_gas_used + pool_tx.gas_limit() > block_gas_limit { + // we can't fit this transaction into the block, so we need to mark it as invalid + // which also removes all dependent transaction from the iterator before we can + // continue + best_txs.mark_invalid(&pool_tx); + continue; + } + + // check if the job was cancelled, if so we can exit early + if cancel.is_cancelled() { + return Ok(BuildOutcome::Cancelled); + } + + // convert tx to a signed transaction + let tx = pool_tx.to_recovered_transaction(); + + // There's only limited amount of blob space available per block, so we need to check if + // the EIP-4844 can still fit in the block + if let Some(blob_tx) = tx.transaction.as_eip4844() { + let tx_blob_gas = blob_tx.blob_gas(); + if sum_blob_gas_used + tx_blob_gas > MAX_DATA_GAS_PER_BLOCK { + // we can't fit this _blob_ transaction into the block, so we mark it as + // invalid, which removes its dependent transactions from + // the iterator. This is similar to the gas limit condition + // for regular transactions above. + best_txs.mark_invalid(&pool_tx); + continue; + } + } + + let env = EnvWithHandlerCfg::new_with_cfg_env( + initialized_cfg.clone(), + initialized_block_env.clone(), + evm_config.tx_env(&tx), + ); + + // Configure the environment for the block. + let mut evm = evm_config.evm_with_env(&mut db, env); + + let ResultAndState { result, state } = match evm.transact() { + Ok(res) => res, + Err(err) => { + match err { + EVMError::Transaction(err) => { + if matches!(err, InvalidTransaction::NonceTooLow { .. }) { + // if the nonce is too low, we can skip this transaction + } else { + // if the transaction is invalid, we can skip it and all of its + // descendants + best_txs.mark_invalid(&pool_tx); + } + + continue; + } + err => { + // this is an error that we should treat as fatal for this attempt + return Err(PayloadBuilderError::EvmExecutionError(err)); + } + } + } + }; + // drop evm so db is released. + drop(evm); + // commit changes + db.commit(state); + + // add to the total blob gas used if the transaction successfully executed + if let Some(blob_tx) = tx.transaction.as_eip4844() { + let tx_blob_gas = blob_tx.blob_gas(); + sum_blob_gas_used += tx_blob_gas; + + // if we've reached the max data gas per block, we can skip blob txs entirely + if sum_blob_gas_used == MAX_DATA_GAS_PER_BLOCK { + best_txs.skip_blobs(); + } + } + + let gas_used = result.gas_used(); + + // add gas used by the transaction to cumulative gas used, before creating the receipt + cumulative_gas_used += gas_used; + + // Push transaction changeset and calculate header bloom filter for receipt. + #[allow(clippy::needless_update)] // side-effect of optimism fields + receipts.push(Some(Receipt { + tx_type: tx.tx_type(), + success: result.is_success(), + cumulative_gas_used, + logs: result.into_logs().into_iter().map(Into::into).collect(), + ..Default::default() + })); + + // update add to total fees + let miner_fee = tx + .effective_tip_per_gas(Some(base_fee)) + .expect("fee is always valid; execution succeeded"); + total_fees += U256::from(miner_fee) * U256::from(gas_used); + + // append transaction to the list of executed transactions + executed_txs.push(tx.into_signed()); + } + + // check if we have a better block + if !is_better_payload(best_payload.as_ref(), total_fees) { + // can skip building the block + return Ok(BuildOutcome::Aborted { + fees: total_fees, + cached_reads, + }); + } + + // calculate the requests and the requests root + let (requests, requests_root) = if chain_spec + .is_prague_active_at_timestamp(attributes.timestamp) + { + let deposit_requests = parse_deposits_from_receipts(&chain_spec, receipts.iter().flatten()) + .map_err(|err| PayloadBuilderError::Internal(RethError::Execution(err.into())))?; + let withdrawal_requests = post_block_withdrawal_requests_contract_call( + &evm_config, + &mut db, + &initialized_cfg, + &initialized_block_env, + ) + .map_err(|err| PayloadBuilderError::Internal(err.into()))?; + + let requests = [deposit_requests, withdrawal_requests].concat(); + let requests_root = calculate_requests_root(&requests); + (Some(requests.into()), Some(requests_root)) + } else { + (None, None) + }; + + // Compute the withdrawals root independent of how they are applied + let withdrawals_root = if !chain_spec.is_shanghai_active_at_timestamp(attributes.timestamp) { + None + } else if attributes.withdrawals.is_empty() { + Some(EMPTY_WITHDRAWALS) + } else { + Some(proofs::calculate_withdrawals_root(&attributes.withdrawals)) + }; + + gnosis_post_block_system_calls( + &chain_spec, + &evm_config, + &mut db, + &initialized_cfg, + &initialized_block_env, + block_rewards_contract, + attributes.timestamp, + Some(&attributes.withdrawals), + attributes.suggested_fee_recipient, + ) + .map_err(|err| PayloadBuilderError::Internal(err.into()))?; + + let WithdrawalsOutcome { withdrawals, .. } = commit_withdrawals( + &mut db, + &chain_spec, + attributes.timestamp, + attributes.withdrawals, + )?; + + // merge all transitions into bundle state, this would apply the withdrawal balance changes + // and 4788 contract call + db.merge_transitions(BundleRetention::PlainState); + + let execution_outcome = ExecutionOutcome::new( + db.take_bundle(), + vec![receipts].into(), + block_number, + vec![requests.clone().unwrap_or_default()], + ); + let receipts_root = execution_outcome + .receipts_root_slow(block_number) + .expect("Number is in range"); + let logs_bloom = execution_outcome + .block_logs_bloom(block_number) + .expect("Number is in range"); + + // calculate the state root + let state_root = { + let state_provider = db.database.0.inner.borrow_mut(); + state_provider.db.state_root(execution_outcome.state())? + }; + + // create the block header + let transactions_root = proofs::calculate_transaction_root(&executed_txs); + + // initialize empty blob sidecars at first. If cancun is active then this will + let mut blob_sidecars = Vec::new(); + let mut excess_blob_gas = None; + let mut blob_gas_used = None; + + // only determine cancun fields when active + if chain_spec.is_cancun_active_at_timestamp(attributes.timestamp) { + // grab the blob sidecars from the executed txs + blob_sidecars = pool.get_all_blobs_exact( + executed_txs + .iter() + .filter(|tx| tx.is_eip4844()) + .map(|tx| tx.hash) + .collect(), + )?; + + excess_blob_gas = if chain_spec.is_cancun_active_at_timestamp(parent_block.timestamp) { + let parent_excess_blob_gas = parent_block.excess_blob_gas.unwrap_or_default(); + let parent_blob_gas_used = parent_block.blob_gas_used.unwrap_or_default(); + Some(calculate_excess_blob_gas( + parent_excess_blob_gas, + parent_blob_gas_used, + )) + } else { + // for the first post-fork block, both parent.blob_gas_used and + // parent.excess_blob_gas are evaluated as 0 + Some(calculate_excess_blob_gas(0, 0)) + }; + + blob_gas_used = Some(sum_blob_gas_used); + } + + let header = Header { + parent_hash: parent_block.hash(), + ommers_hash: EMPTY_OMMER_ROOT_HASH, + beneficiary: initialized_block_env.coinbase, + state_root, + transactions_root, + receipts_root, + withdrawals_root, + logs_bloom, + timestamp: attributes.timestamp, + mix_hash: attributes.prev_randao, + nonce: BEACON_NONCE, + base_fee_per_gas: Some(base_fee), + number: parent_block.number + 1, + gas_limit: block_gas_limit, + difficulty: U256::ZERO, + gas_used: cumulative_gas_used, + extra_data, + parent_beacon_block_root: attributes.parent_beacon_block_root, + blob_gas_used, + excess_blob_gas, + requests_root, + }; + + // seal the block + let block = Block { + header, + body: executed_txs, + ommers: vec![], + withdrawals, + requests, + }; + + let sealed_block = block.seal_slow(); + + let mut payload = EthBuiltPayload::new(attributes.id, sealed_block, total_fees); + + // extend the payload with the blob sidecars from the executed txs + payload.extend_sidecars(blob_sidecars); + + Ok(BuildOutcome::Better { + payload, + cached_reads, + }) +}