diff --git a/Cargo.lock b/Cargo.lock index edf9471c9819..504a4cd9ae64 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9623,22 +9623,28 @@ dependencies = [ name = "reth-taiko-payload-builder" version = "1.1.4" dependencies = [ + "alloy-consensus", + "alloy-eips", "alloy-primitives 0.8.14", "alloy-rlp", "reth-basic-payload-builder", + "reth-chain-state", "reth-chainspec", "reth-errors", "reth-evm", "reth-payload-builder", + "reth-payload-primitives", "reth-primitives", "reth-provider", "reth-revm", + "reth-taiko-chainspec", "reth-taiko-consensus", "reth-taiko-engine-primitives", "reth-taiko-evm", "reth-taiko-primitives", "reth-taiko-provider", "reth-transaction-pool", + "revm-primitives", "thiserror 2.0.5", "tracing", ] diff --git a/crates/evm/execution-types/src/execute.rs b/crates/evm/execution-types/src/execute.rs index 1e8a10b7e1e6..851759c32077 100644 --- a/crates/evm/execution-types/src/execute.rs +++ b/crates/evm/execution-types/src/execute.rs @@ -1,6 +1,6 @@ use alloy_eips::eip7685::Requests; use alloy_primitives::U256; -use reth_primitives::TransactionSigned; +use reth_primitives::{BlockWithSenders, TransactionSigned}; use revm::db::BundleState; /// A helper type for ethereum block inputs that consists of a block and the total difficulty. @@ -72,3 +72,13 @@ pub struct BlockExecutionOutput { /// The skipped transactions when `BlockExecutionInput::enable_skip`. pub skipped_list: Vec, } + +impl BlockExecutionOutput { + /// Remote the skipped transactions from the block. + pub fn apply_skip(&self, block: &mut BlockWithSenders) { + for i in &self.skipped_list { + block.senders.remove(*i); + block.body.transactions.remove(*i); + } + } +} diff --git a/crates/evm/src/execute.rs b/crates/evm/src/execute.rs index 0e179f7c7318..ae361352382e 100644 --- a/crates/evm/src/execute.rs +++ b/crates/evm/src/execute.rs @@ -293,6 +293,7 @@ where /// A generic block executor provider that can create executors using a strategy factory. #[allow(missing_debug_implementations)] +#[derive(Debug)] pub struct BasicBlockExecutorProvider { strategy_factory: F, } diff --git a/crates/payload/primitives/src/error.rs b/crates/payload/primitives/src/error.rs index ffe4e027e966..28753d886f3e 100644 --- a/crates/payload/primitives/src/error.rs +++ b/crates/payload/primitives/src/error.rs @@ -2,7 +2,7 @@ use alloy_primitives::B256; use alloy_rpc_types_engine::ForkchoiceUpdateError; -use reth_errors::{ProviderError, RethError}; +use reth_errors::{BlockExecutionError, ProviderError, RethError}; use revm_primitives::EVMError; use tokio::sync::oneshot; @@ -30,6 +30,9 @@ pub enum PayloadBuilderError { /// Any other payload building errors. #[error(transparent)] Other(Box), + /// Error during block execution. + #[error(transparent)] + BlockExecutionError(#[from] BlockExecutionError), } impl PayloadBuilderError { diff --git a/crates/taiko/consensus/proposer/src/lib.rs b/crates/taiko/consensus/proposer/src/lib.rs index 990497145a1a..0fee5ba61afe 100644 --- a/crates/taiko/consensus/proposer/src/lib.rs +++ b/crates/taiko/consensus/proposer/src/lib.rs @@ -210,7 +210,7 @@ impl Storage { excess_blob_gas: None, extra_data: Default::default(), parent_beacon_block_root: None, - requests_root: requests.map(|r| proofs::calculate_requests_root(&r.0)), + requests_hash: requests.map(|r| r.requests_hash()), }; if chain_spec.is_cancun_active_at_timestamp(timestamp) { diff --git a/crates/taiko/evm/src/execute.rs b/crates/taiko/evm/src/execute.rs index a5f5010ae3ab..2eb730978081 100644 --- a/crates/taiko/evm/src/execute.rs +++ b/crates/taiko/evm/src/execute.rs @@ -27,7 +27,7 @@ use reth_revm::{Database, State}; use reth_taiko_chainspec::TaikoChainSpec; use reth_taiko_consensus::{check_anchor_tx, decode_ontake_extra_data}; use reth_taiko_forks::TaikoHardforks; -use revm::{interpreter::Host, Evm, JournaledState}; +use revm::{interpreter::Host, JournaledState}; use revm_primitives::{db::DatabaseCommit, EnvWithHandlerCfg, HashSet, ResultAndState, U256}; use std::io::{self, Write}; use tracing::debug; @@ -135,14 +135,14 @@ where let mut buf_len: u64 = 0; for i in 0..block.body.transactions.len() { - let ref transaction = block.body.transactions[i]; + let transaction = &block.body.transactions[i]; let sender = block.senders.get(i).unwrap(); let block_available_gas = block.header.gas_limit - cumulative_gas_used; if transaction.gas_limit() > block_available_gas { break; } - self.evm_config.fill_tx_env(evm.tx_mut(), &transaction, *sender); + self.evm_config.fill_tx_env(evm.tx_mut(), transaction, *sender); // Execute transaction. let ResultAndState { result, state } = match evm.transact() { @@ -259,7 +259,7 @@ where let mut evm = self.evm_config.evm_with_env(&mut self.state, env); let mut cumulative_gas_used = 0; let mut receipts = Vec::with_capacity(block.body.transactions.len()); - let mut skipped_transactions = Vec::with_capacity(block.body.transactions.len()); + let mut skipped_list = Vec::with_capacity(block.body.transactions.len()); let treasury = self.chain_spec.treasury(); for (idx, (sender, transaction)) in block.transactions_with_sender().enumerate() { @@ -282,7 +282,7 @@ where if transaction.gas_limit() > block_available_gas { if !is_anchor && enable_skip { debug!(target: "taiko::executor", hash = ?transaction.hash(), want = ?transaction.gas_limit(), got = block_available_gas, "Invalid gas limit for tx"); - skipped_transactions.push(idx); + skipped_list.push(idx); continue; } return Err(BlockValidationError::TransactionGasLimitMoreThanAvailableBlockGas { @@ -326,7 +326,7 @@ where HashSet::default(), ); debug!(target: "taiko::executor", hash = ?transaction.hash(), error = ?err, "Invalid execute for tx"); - skipped_transactions.push(idx); + skipped_list.push(idx); continue; } return Err(err.into()); @@ -358,7 +358,7 @@ where Ok(ExecuteOutput { receipts, gas_used: cumulative_gas_used, - skipped_list: skipped_transactions, + skipped_list, target_list: vec![], }) } diff --git a/crates/taiko/payload/builder/Cargo.toml b/crates/taiko/payload/builder/Cargo.toml index 720ba8563a3e..82fe3cac47e9 100644 --- a/crates/taiko/payload/builder/Cargo.toml +++ b/crates/taiko/payload/builder/Cargo.toml @@ -22,6 +22,10 @@ reth-basic-payload-builder.workspace = true reth-evm = { workspace = true } reth-errors.workspace = true reth-chainspec = { workspace = true } +reth-payload-primitives.workspace = true +reth-chain-state.workspace = true + +revm-primitives = { workspace = true, features = ["taiko"] } # taiko reth-taiko-evm.workspace = true @@ -29,10 +33,13 @@ reth-taiko-engine-primitives.workspace = true reth-taiko-primitives.workspace = true reth-taiko-provider.workspace = true reth-taiko-consensus.workspace = true +reth-taiko-chainspec.workspace = true # crypto alloy-rlp.workspace = true alloy-primitives.workspace = true +alloy-eips.workspace = true +alloy-consensus.workspace = true # misc tracing.workspace = true diff --git a/crates/taiko/payload/builder/src/builder.rs b/crates/taiko/payload/builder/src/builder.rs index ba0ed07ea265..eb798b9bfa7f 100644 --- a/crates/taiko/payload/builder/src/builder.rs +++ b/crates/taiko/payload/builder/src/builder.rs @@ -3,56 +3,88 @@ use std::sync::Arc; use crate::error::TaikoPayloadBuilderError; +use alloy_consensus::EMPTY_OMMER_ROOT_HASH; +use alloy_eips::merge::BEACON_NONCE; +use alloy_primitives::{Bloom, U256}; use reth_basic_payload_builder::*; +use reth_chain_state::ExecutedBlock; use reth_chainspec::ChainSpec; use reth_errors::{BlockExecutionError, BlockValidationError}; use reth_evm::{ - execute::{BlockExecutionOutput, BlockExecutorProvider, Executor}, - ConfigureEvm, + execute::{BasicBlockExecutorProvider, BlockExecutionOutput, BlockExecutorProvider, Executor}, + system_calls::SystemCaller, + ConfigureEvm, NextBlockEnvAttributes, }; -use reth_payload_builder::error::PayloadBuilderError; +use reth_payload_builder::EthBuiltPayload; +use reth_payload_primitives::{PayloadBuilderAttributes, PayloadBuilderError}; use reth_primitives::{ - constants::BEACON_NONCE, eip4844::calculate_excess_blob_gas, proofs, Block, Bloom, Header, - TransactionSigned, EMPTY_OMMER_ROOT_HASH, U256, + proofs, Block, BlockBody, BlockExt, EthereumHardforks, Header, TransactionSigned, }; -use reth_provider::{ExecutionOutcome, StateProviderFactory}; -use reth_revm::database::StateProviderDatabase; -use reth_transaction_pool::TransactionPool; -use taiko_reth_engine_primitives::{TaikoBuiltPayload, TaikoPayloadBuilderAttributes}; -use taiko_reth_evm::{execute::TaikoExecutorProvider, TaikoEvmConfig}; -use taiko_reth_primitives::L1Origin; -use taiko_reth_provider::L1OriginWriter; -use tracing::debug; +use reth_provider::{ChainSpecProvider, ExecutionOutcome, StateProviderFactory}; +use reth_revm::{ + database::StateProviderDatabase, + db::State, + primitives::{BlockEnv, CfgEnvWithHandlerCfg}, +}; +use reth_taiko_chainspec::TaikoChainSpec; +use reth_taiko_engine_primitives::TaikoPayloadBuilderAttributes; +use reth_taiko_evm::{TaikoEvmConfig, TaikoExecutionStrategyFactory, TaikoExecutorProvider}; +use reth_taiko_primitives::L1Origin; +use reth_taiko_provider::L1OriginWriter; +use reth_transaction_pool::{noop::NoopTransactionPool, PoolTransaction, TransactionPool}; +use revm_primitives::calc_excess_blob_gas; +use tracing::{debug, warn}; /// Taiko's payload builder #[derive(Debug, Clone)] pub struct TaikoPayloadBuilder { + evm_config: EvmConfig, /// The type responsible for creating the evm. - block_executor: TaikoExecutorProvider, + block_executor: BasicBlockExecutorProvider>, +} + +impl TaikoPayloadBuilder { + /// `TaikoPayloadBuilder` constructor. + pub fn new(chain_spec: Arc) -> Self { + let block_executor = TaikoExecutorProvider::taiko(chain_spec.clone()); + Self { block_executor, evm_config: TaikoEvmConfig::new(chain_spec) } + } } -impl TaikoPayloadBuilder { - /// `OptimismPayloadBuilder` constructor. - pub const fn new(evm_config: EvmConfig, chain_spec: Arc) -> Self { - let block_executor = TaikoExecutorProvider::new(chain_spec, evm_config); - Self { block_executor } +impl TaikoPayloadBuilder +where + EvmConfig: ConfigureEvm
, +{ + /// Returns the configured [`CfgEnvWithHandlerCfg`] and [`BlockEnv`] for the targeted payload + /// (that has the `parent` as its parent). + fn cfg_and_block_env( + &self, + config: &PayloadConfig, + parent: &Header, + ) -> Result<(CfgEnvWithHandlerCfg, BlockEnv), EvmConfig::Error> { + let next_attributes = NextBlockEnvAttributes { + timestamp: config.attributes.timestamp(), + suggested_fee_recipient: config.attributes.suggested_fee_recipient(), + prev_randao: config.attributes.prev_randao(), + }; + self.evm_config.next_cfg_and_block_env(parent, next_attributes) } } /// Implementation of the [`PayloadBuilder`] trait for [`TaikoPayloadBuilder`]. impl PayloadBuilder for TaikoPayloadBuilder where - Client: StateProviderFactory + L1OriginWriter, - Pool: TransactionPool, - EvmConfig: ConfigureEvm, + EvmConfig: ConfigureEvm
, + Client: StateProviderFactory + ChainSpecProvider + L1OriginWriter, + Pool: TransactionPool>, { type Attributes = TaikoPayloadBuilderAttributes; - type BuiltPayload = TaikoBuiltPayload; + type BuiltPayload = EthBuiltPayload; fn try_build( &self, - args: BuildArguments, - ) -> Result, PayloadBuilderError> { + args: BuildArguments, + ) -> Result, PayloadBuilderError> { let BuildArguments { cached_reads, .. } = args; Ok(BuildOutcome::Aborted { fees: U256::ZERO, cached_reads }) } @@ -61,8 +93,21 @@ where &self, client: &Client, config: PayloadConfig, - ) -> Result { - taiko_payload_builder(client, &self.block_executor, config) + ) -> Result { + let args = BuildArguments::new( + client, + // we use defaults here because for the empty payload we don't need to execute anything + NoopTransactionPool::default(), + Default::default(), + config, + Default::default(), + None, + ); + let (_cfg_env, block_env) = self + .cfg_and_block_env(&args.config, &args.config.parent_header) + .map_err(PayloadBuilderError::other)?; + + taiko_payload_builder(&self.block_executor, args, block_env) } } @@ -72,20 +117,26 @@ where /// and configuration, this function creates a transaction payload. Returns /// a result indicating success with the payload or an error in case of failure. #[inline] -fn taiko_payload_builder( - client: &Client, - executor: &TaikoExecutorProvider, - config: PayloadConfig, -) -> Result +fn taiko_payload_builder( + executor: &BasicBlockExecutorProvider>, + args: BuildArguments, + initialized_block_env: BlockEnv, +) -> Result where - EvmConfig: ConfigureEvm, - Client: StateProviderFactory + L1OriginWriter, + EvmConfig: ConfigureEvm
, + Client: StateProviderFactory + ChainSpecProvider + L1OriginWriter, + Pool: TransactionPool>, { - let state_provider = client.state_by_block_hash(config.parent_block.hash())?; - let mut db = StateProviderDatabase::new(state_provider); - let PayloadConfig { initialized_block_env, parent_block, attributes, chain_spec, .. } = config; + let BuildArguments { client, pool: _, mut cached_reads, config, cancel: _, best_payload: _ } = + args; + let chain_spec = client.chain_spec(); + let state_provider = client.state_by_block_hash(config.parent_header.hash())?; + let state = StateProviderDatabase::new(state_provider); + let mut db = + State::builder().with_database(cached_reads.as_db_mut(state)).with_bundle_update().build(); + let PayloadConfig { parent_header, attributes, extra_data: _ } = config; - debug!(target: "taiko_payload_builder", id=%attributes.payload_attributes.payload_id(), parent_hash = ?parent_block.hash(), parent_number = parent_block.number, "building new payload"); + debug!(target: "taiko_payload_builder", id=%attributes.payload_attributes.payload_id(), parent_hash = ?parent_header.hash(), parent_number = parent_header.number, "building new payload"); let block_gas_limit: u64 = initialized_block_env.gas_limit.try_into().unwrap_or(u64::MAX); let base_fee = initialized_block_env.basefee.to::(); @@ -96,17 +147,15 @@ where .map_err(|_| PayloadBuilderError::other(TaikoPayloadBuilderError::FailedToDecodeTx))?; let blob_gas_used = - if chain_spec.is_cancun_active_at_timestamp(attributes.payload_attributes.timestamp) { + chain_spec.is_cancun_active_at_timestamp(attributes.timestamp()).then(|| { let mut sum_blob_gas_used = 0; for tx in &transactions { if let Some(blob_tx) = tx.transaction.as_eip4844() { sum_blob_gas_used += blob_tx.blob_gas(); } } - Some(sum_blob_gas_used) - } else { - None - }; + sum_blob_gas_used + }); // if shanghai is active, include empty withdrawals let withdrawals = chain_spec @@ -114,7 +163,7 @@ where .then_some(attributes.payload_attributes.withdrawals); let mut header = Header { - parent_hash: parent_block.hash(), + parent_hash: parent_header.hash(), ommers_hash: EMPTY_OMMER_ROOT_HASH, beneficiary: initialized_block_env.coinbase, state_root: Default::default(), @@ -124,7 +173,7 @@ where logs_bloom: Default::default(), timestamp: attributes.payload_attributes.timestamp, mix_hash: initialized_block_env.prevrandao.unwrap(), - nonce: BEACON_NONCE, + nonce: BEACON_NONCE.into(), base_fee_per_gas: Some(base_fee), number: block_number, gas_limit: block_gas_limit, @@ -134,53 +183,58 @@ where parent_beacon_block_root: attributes.payload_attributes.parent_beacon_block_root, blob_gas_used, excess_blob_gas: None, - requests_root: Default::default(), + requests_hash: None, + target_blobs_per_block: None, }; if chain_spec.is_cancun_active_at_timestamp(attributes.payload_attributes.timestamp) { - header.parent_beacon_block_root = parent_block.parent_beacon_block_root; + header.parent_beacon_block_root = parent_header.parent_beacon_block_root; header.blob_gas_used = Some(0); let (parent_excess_blob_gas, parent_blob_gas_used) = - if chain_spec.is_cancun_active_at_timestamp(parent_block.timestamp) { + if chain_spec.is_cancun_active_at_timestamp(parent_header.timestamp) { ( - parent_block.excess_blob_gas.unwrap_or_default(), - parent_block.blob_gas_used.unwrap_or_default(), + parent_header.excess_blob_gas.unwrap_or_default(), + parent_header.blob_gas_used.unwrap_or_default(), ) } else { (0, 0) }; header.excess_blob_gas = - Some(calculate_excess_blob_gas(parent_excess_blob_gas, parent_blob_gas_used)) + Some(calc_excess_blob_gas(parent_excess_blob_gas, parent_blob_gas_used)) } // seal the block - let mut block = Block { - header, - body: transactions, - ommers: vec![], - withdrawals, - requests: Default::default(), - } - .with_recovered_senders() - .ok_or(BlockExecutionError::Validation(BlockValidationError::SenderRecoveryError))?; + let mut block = Block { header, body: BlockBody { transactions, ommers: vec![], withdrawals } } + .with_recovered_senders() + .ok_or(BlockExecutionError::Validation(BlockValidationError::SenderRecoveryError))?; // execute the block - let BlockExecutionOutput { state, receipts, requests, gas_used, .. } = - executor.executor(&mut db).execute((&mut block, U256::ZERO).into())?; - + let output = executor.executor(&mut db).execute((&block, U256::ZERO).into())?; + output.apply_skip(&mut block); + let BlockExecutionOutput { state, receipts, requests, gas_used, .. } = output; let execution_outcome = - ExecutionOutcome::new(state, receipts.into(), block.number, vec![requests.clone().into()]); + ExecutionOutcome::new(state, receipts.into(), block.number, vec![requests.clone()]); // if prague is active, include empty requests let requests = chain_spec .is_prague_active_at_timestamp(attributes.payload_attributes.timestamp) .then_some(requests); - block.header.requests_root = requests.map(|r| proofs::calculate_receipt_root_no_memo(&r)); + block.header.requests_hash = requests.as_ref().map(|r| r.requests_hash()); // now we need to update certain header fields with the results of the execution - block.header.transactions_root = proofs::calculate_transaction_root(&block.body); - block.header.state_root = db.state_root(execution_outcome.state())?; + block.header.transactions_root = proofs::calculate_transaction_root(&block.body.transactions); + let hashed_state = db.database.db.hashed_post_state(execution_outcome.state()); + let (state_root, trie_output) = { + db.database.inner().state_root_with_updates(hashed_state.clone()).inspect_err(|err| { + warn!(target: "payload_builder", + parent_hash=%parent_header.hash(), + %err, + "failed to calculate state root for payload" + ); + })? + }; + block.header.state_root = state_root; block.header.gas_used = gas_used; let receipts = execution_outcome.receipts_by_block(block.header.number); @@ -194,7 +248,7 @@ where block.header.receipts_root = execution_outcome.receipts_root_slow(block.header.number).expect("Receipts is present"); - let sealed_block = block.block.seal_slow(); + let sealed_block = Arc::new(block.block.seal_slow()); // L1Origin **MUST NOT** be nil, it's a required field in PayloadAttributesV1. let l1_origin = L1Origin { @@ -209,7 +263,20 @@ where debug!(target: "taiko_payload_builder", ?sealed_block, "sealed built block"); - let payload = - TaikoBuiltPayload::new(attributes.payload_attributes.id, sealed_block, U256::ZERO); + // create the executed block data + let executed = ExecutedBlock { + block: sealed_block.clone(), + senders: Arc::new(block.senders), + execution_output: Arc::new(execution_outcome), + hashed_state: Arc::new(hashed_state), + trie: Arc::new(trie_output), + }; + let payload = EthBuiltPayload::new( + attributes.payload_attributes.id, + sealed_block, + U256::ZERO, + Some(executed), + requests, + ); Ok(payload) }