From 28b983403e62f686dbdc87423864aaa0ec093d15 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 20 Mar 2024 19:17:23 +0100 Subject: [PATCH] chore: move op types; less op cfgs (#7255) --- Cargo.lock | 6 +- Cargo.toml | 1 + bin/reth/Cargo.toml | 3 - .../src/commands/debug_cmd/build_block.rs | 20 +- .../src/commands/debug_cmd/replay_engine.rs | 9 +- crates/consensus/beacon/Cargo.toml | 2 - crates/net/network/Cargo.toml | 1 - .../network/src/transactions/validation.rs | 4 +- crates/node-core/Cargo.toml | 1 - crates/node-optimism/Cargo.toml | 3 +- crates/node-optimism/src/engine.rs | 2 +- crates/node-optimism/src/lib.rs | 4 + crates/payload/builder/Cargo.toml | 10 +- crates/payload/builder/src/lib.rs | 2 - crates/payload/optimism/Cargo.toml | 12 +- crates/payload/optimism/src/builder.rs | 575 +++++++++++++++++ crates/payload/optimism/src/lib.rs | 602 +----------------- .../optimism.rs => optimism/src/payload.rs} | 19 +- crates/rpc/rpc-engine-api/Cargo.toml | 2 +- crates/rpc/rpc-types-compat/Cargo.toml | 2 +- crates/rpc/rpc-types/Cargo.toml | 1 - 21 files changed, 627 insertions(+), 654 deletions(-) create mode 100644 crates/payload/optimism/src/builder.rs rename crates/payload/{builder/src/optimism.rs => optimism/src/payload.rs} (96%) diff --git a/Cargo.lock b/Cargo.lock index 8997df1e4..e0d75aa29 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5456,7 +5456,6 @@ dependencies = [ "reth-node-core", "reth-node-ethereum", "reth-node-optimism", - "reth-optimism-payload-builder", "reth-payload-builder", "reth-payload-validator", "reth-primitives", @@ -6226,13 +6225,18 @@ dependencies = [ name = "reth-optimism-payload-builder" version = "0.2.0-beta.3" dependencies = [ + "alloy-rlp", "reth-basic-payload-builder", + "reth-node-api", "reth-payload-builder", "reth-primitives", "reth-provider", "reth-revm", + "reth-rpc-types", + "reth-rpc-types-compat", "reth-transaction-pool", "revm", + "sha2", "thiserror", "tracing", ] diff --git a/Cargo.toml b/Cargo.toml index 6f33b6e51..e19923690 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -249,6 +249,7 @@ syn = "2.0" nybbles = "0.2.1" smallvec = "1" dyn-clone = "1.0.17" +sha2 = { version = "0.10", default-features = false } # proc-macros proc-macro2 = "1.0" diff --git a/bin/reth/Cargo.toml b/bin/reth/Cargo.toml index 129042b9a..4e138e9cd 100644 --- a/bin/reth/Cargo.toml +++ b/bin/reth/Cargo.toml @@ -37,7 +37,6 @@ reth-network-api.workspace = true reth-downloaders.workspace = true reth-tracing.workspace = true reth-tasks.workspace = true -reth-optimism-payload-builder = { workspace = true, optional = true } reth-ethereum-payload-builder.workspace = true reth-payload-builder.workspace = true reth-payload-validator.workspace = true @@ -129,8 +128,6 @@ optimism = [ "reth-network/optimism", "reth-network-api/optimism", "reth-blockchain-tree/optimism", - "reth-payload-builder/optimism", - "reth-optimism-payload-builder/optimism", "dep:reth-node-optimism", "reth-node-core/optimism", ] diff --git a/bin/reth/src/commands/debug_cmd/build_block.rs b/bin/reth/src/commands/debug_cmd/build_block.rs index 363ca09d4..55acbaa37 100644 --- a/bin/reth/src/commands/debug_cmd/build_block.rs +++ b/bin/reth/src/commands/debug_cmd/build_block.rs @@ -23,13 +23,7 @@ use reth_interfaces::{consensus::Consensus, RethResult}; use reth_node_api::PayloadBuilderAttributes; #[cfg(not(feature = "optimism"))] use reth_node_ethereum::EthEvmConfig; -#[cfg(feature = "optimism")] -use reth_node_optimism::OptimismEvmConfig; use reth_payload_builder::database::CachedReads; -#[cfg(not(feature = "optimism"))] -use reth_payload_builder::EthPayloadBuilderAttributes; -#[cfg(feature = "optimism")] -use reth_payload_builder::OptimismPayloadBuilderAttributes; use reth_primitives::{ constants::eip4844::{LoadKzgSettingsError, MAINNET_KZG_TRUSTED_SETUP}, fs, @@ -167,7 +161,7 @@ impl Command { let consensus: Arc = Arc::new(BeaconConsensus::new(Arc::clone(&self.chain))); #[cfg(feature = "optimism")] - let evm_config = OptimismEvmConfig::default(); + let evm_config = reth_node_optimism::OptimismEvmConfig::default(); #[cfg(not(feature = "optimism"))] let evm_config = EthEvmConfig::default(); @@ -265,7 +259,7 @@ impl Command { Arc::clone(&best_block), Bytes::default(), #[cfg(feature = "optimism")] - OptimismPayloadBuilderAttributes::try_new( + reth_node_optimism::OptimismPayloadBuilderAttributes::try_new( best_block.hash(), OptimismPayloadAttributes { payload_attributes: payload_attrs, @@ -275,7 +269,10 @@ impl Command { }, )?, #[cfg(not(feature = "optimism"))] - EthPayloadBuilderAttributes::try_new(best_block.hash(), payload_attrs)?, + reth_payload_builder::EthPayloadBuilderAttributes::try_new( + best_block.hash(), + payload_attrs, + )?, self.chain.clone(), ); @@ -289,9 +286,8 @@ impl Command { ); #[cfg(feature = "optimism")] - let payload_builder = - reth_optimism_payload_builder::OptimismPayloadBuilder::new(self.chain.clone()) - .compute_pending_block(); + let payload_builder = reth_node_optimism::OptimismPayloadBuilder::new(self.chain.clone()) + .compute_pending_block(); #[cfg(not(feature = "optimism"))] let payload_builder = reth_ethereum_payload_builder::EthereumPayloadBuilder::default(); diff --git a/bin/reth/src/commands/debug_cmd/replay_engine.rs b/bin/reth/src/commands/debug_cmd/replay_engine.rs index de658fcb6..6c5cfa98f 100644 --- a/bin/reth/src/commands/debug_cmd/replay_engine.rs +++ b/bin/reth/src/commands/debug_cmd/replay_engine.rs @@ -22,8 +22,6 @@ use reth_network_api::NetworkInfo; use reth_node_core::engine_api_store::{EngineApiStore, StoredEngineApiMessage}; #[cfg(not(feature = "optimism"))] use reth_node_ethereum::{EthEngineTypes, EthEvmConfig}; -#[cfg(feature = "optimism")] -use reth_node_optimism::{OptimismEngineTypes, OptimismEvmConfig}; use reth_payload_builder::{PayloadBuilderHandle, PayloadBuilderService}; use reth_primitives::{fs, ChainSpec, PruneModes}; use reth_provider::{providers::BlockchainProvider, CanonStateSubscriptions, ProviderFactory}; @@ -129,7 +127,7 @@ impl Command { let evm_config = EthEvmConfig::default(); #[cfg(feature = "optimism")] - let evm_config = OptimismEvmConfig::default(); + let evm_config = reth_node_optimism::OptimismEvmConfig::default(); // Configure blockchain tree let tree_externals = TreeExternals::new( @@ -163,8 +161,7 @@ impl Command { // Optimism's payload builder is implemented on the OptimismPayloadBuilder type. #[cfg(feature = "optimism")] - let payload_builder = - reth_optimism_payload_builder::OptimismPayloadBuilder::new(self.chain.clone()); + let payload_builder = reth_node_optimism::OptimismPayloadBuilder::new(self.chain.clone()); let payload_generator = BasicPayloadJobGenerator::with_builder( blockchain_db.clone(), @@ -178,7 +175,7 @@ impl Command { #[cfg(feature = "optimism")] let (payload_service, payload_builder): ( _, - PayloadBuilderHandle, + PayloadBuilderHandle, ) = PayloadBuilderService::new(payload_generator, blockchain_db.canonical_state_stream()); #[cfg(not(feature = "optimism"))] diff --git a/crates/consensus/beacon/Cargo.toml b/crates/consensus/beacon/Cargo.toml index 0c5aafa9c..52389143c 100644 --- a/crates/consensus/beacon/Cargo.toml +++ b/crates/consensus/beacon/Cargo.toml @@ -64,7 +64,5 @@ optimism = [ "reth-primitives/optimism", "reth-interfaces/optimism", "reth-provider/optimism", - "reth-rpc-types/optimism", - "reth-payload-builder/optimism", "reth-blockchain-tree/optimism", ] diff --git a/crates/net/network/Cargo.toml b/crates/net/network/Cargo.toml index ccbd121c3..7332fb9ea 100644 --- a/crates/net/network/Cargo.toml +++ b/crates/net/network/Cargo.toml @@ -106,7 +106,6 @@ optimism = [ "reth-primitives/optimism", "reth-provider/optimism", "reth-network-api/optimism", - "reth-rpc-types/optimism", ] [[bench]] diff --git a/crates/net/network/src/transactions/validation.rs b/crates/net/network/src/transactions/validation.rs index f2049bbca..e508b2b24 100644 --- a/crates/net/network/src/transactions/validation.rs +++ b/crates/net/network/src/transactions/validation.rs @@ -244,11 +244,11 @@ impl ValidateTx68 for EthMessageFilter { fn max_encoded_tx_length(&self, ty: TxType) -> Option { // the biggest transaction so far is a blob transaction, which is currently max 2^17, // encoded length, nonetheless, the blob tx may become bigger in the future. + #[allow(unreachable_patterns)] match ty { TxType::Legacy | TxType::Eip2930 | TxType::Eip1559 => Some(MAX_MESSAGE_SIZE), TxType::Eip4844 => None, - #[cfg(feature = "optimism")] - TxType::Deposit => None, + _ => None, } } diff --git a/crates/node-core/Cargo.toml b/crates/node-core/Cargo.toml index bec106784..e8c26e526 100644 --- a/crates/node-core/Cargo.toml +++ b/crates/node-core/Cargo.toml @@ -103,7 +103,6 @@ optimism = [ "reth-provider/optimism", "reth-network/optimism", "reth-network-api/optimism", - "reth-rpc-types/optimism", "reth-rpc-types-compat/optimism", "reth-auto-seal-consensus/optimism", "reth-consensus-common/optimism", diff --git a/crates/node-optimism/Cargo.toml b/crates/node-optimism/Cargo.toml index a874b46b0..090324fd3 100644 --- a/crates/node-optimism/Cargo.toml +++ b/crates/node-optimism/Cargo.toml @@ -41,10 +41,9 @@ reth-db.workspace = true optimism = [ "reth-network/optimism", "reth-primitives/optimism", - "reth-rpc-types/optimism", "reth-provider/optimism", "reth-rpc-types-compat/optimism", "reth-rpc/optimism", - "reth-optimism-payload-builder/optimism", "reth-revm/optimism", + "reth-optimism-payload-builder/optimism", ] diff --git a/crates/node-optimism/src/engine.rs b/crates/node-optimism/src/engine.rs index 66aeecfb2..f5c53d98e 100644 --- a/crates/node-optimism/src/engine.rs +++ b/crates/node-optimism/src/engine.rs @@ -3,7 +3,7 @@ use reth_node_api::{ EngineObjectValidationError, EngineTypes, MessageValidationKind, PayloadOrAttributes, VersionSpecificValidationError, }; -use reth_payload_builder::{OptimismBuiltPayload, OptimismPayloadBuilderAttributes}; +use reth_optimism_payload_builder::{OptimismBuiltPayload, OptimismPayloadBuilderAttributes}; use reth_primitives::{ChainSpec, Hardfork}; use reth_rpc_types::{ engine::{ diff --git a/crates/node-optimism/src/lib.rs b/crates/node-optimism/src/lib.rs index 9805400ae..0159d2ed2 100644 --- a/crates/node-optimism/src/lib.rs +++ b/crates/node-optimism/src/lib.rs @@ -26,3 +26,7 @@ pub mod node; pub use node::OptimismNode; pub mod txpool; + +pub use reth_optimism_payload_builder::{ + OptimismBuiltPayload, OptimismPayloadBuilder, OptimismPayloadBuilderAttributes, +}; diff --git a/crates/payload/builder/Cargo.toml b/crates/payload/builder/Cargo.toml index 19c7e1482..207fbc43f 100644 --- a/crates/payload/builder/Cargo.toml +++ b/crates/payload/builder/Cargo.toml @@ -36,7 +36,7 @@ metrics.workspace = true # misc thiserror.workspace = true -sha2 = { version = "0.10", default-features = false } +sha2.workspace = true tracing.workspace = true [dev-dependencies] @@ -44,10 +44,4 @@ revm.workspace = true serde_json.workspace = true [features] -test-utils = [] -optimism = [ - "reth-primitives/optimism", - "reth-rpc-types/optimism", - "reth-rpc-types-compat/optimism", - "reth-interfaces/optimism", -] +test-utils = [] \ No newline at end of file diff --git a/crates/payload/builder/src/lib.rs b/crates/payload/builder/src/lib.rs index e4be5808b..7982946cb 100644 --- a/crates/payload/builder/src/lib.rs +++ b/crates/payload/builder/src/lib.rs @@ -105,7 +105,6 @@ pub mod database; pub mod error; mod events; mod metrics; -mod optimism; mod payload; mod service; mod traits; @@ -115,7 +114,6 @@ pub mod noop; #[cfg(any(test, feature = "test-utils"))] pub mod test_utils; -pub use optimism::{OptimismBuiltPayload, OptimismPayloadBuilderAttributes}; pub use payload::{EthBuiltPayload, EthPayloadBuilderAttributes}; pub use reth_rpc_types::engine::PayloadId; pub use service::{PayloadBuilderHandle, PayloadBuilderService, PayloadStore}; diff --git a/crates/payload/optimism/Cargo.toml b/crates/payload/optimism/Cargo.toml index 30ff67c44..8cafb343e 100644 --- a/crates/payload/optimism/Cargo.toml +++ b/crates/payload/optimism/Cargo.toml @@ -17,23 +17,25 @@ reth-primitives.workspace = true reth-revm.workspace = true reth-transaction-pool.workspace = true reth-provider.workspace = true +reth-rpc-types.workspace = true +reth-rpc-types-compat.workspace = true +reth-node-api.workspace = true reth-payload-builder.workspace = true reth-basic-payload-builder.workspace = true # ethereum revm.workspace = true +alloy-rlp.workspace = true # misc tracing.workspace = true thiserror.workspace = true - +sha2.workspace = true [features] -# This is a workaround for reth-cli crate to allow this as mandatory dependency without breaking the build even if unused. -# This makes managing features and testing workspace easier because clippy always builds all members if --workspace is provided optimism = [ "reth-primitives/optimism", "reth-revm/optimism", "reth-provider/optimism", - "reth-payload-builder/optimism", -] + "reth-rpc-types-compat/optimism", +] \ No newline at end of file diff --git a/crates/payload/optimism/src/builder.rs b/crates/payload/optimism/src/builder.rs new file mode 100644 index 000000000..062240cb0 --- /dev/null +++ b/crates/payload/optimism/src/builder.rs @@ -0,0 +1,575 @@ +//! Optimism payload builder implementation. + +use crate::{ + error::OptimismPayloadBuilderError, + payload::{OptimismBuiltPayload, OptimismPayloadBuilderAttributes}, +}; +use reth_basic_payload_builder::*; +use reth_payload_builder::error::PayloadBuilderError; +use reth_primitives::{ + constants::{BEACON_NONCE, EMPTY_RECEIPTS, EMPTY_TRANSACTIONS}, + eip4844::calculate_excess_blob_gas, + proofs, + revm::env::tx_env_with_recovered, + Block, ChainSpec, Hardfork, Header, IntoRecoveredTransaction, Receipt, Receipts, TxType, + EMPTY_OMMER_ROOT_HASH, U256, +}; +use reth_provider::{BundleStateWithReceipts, StateProviderFactory}; +use reth_revm::database::StateProviderDatabase; +use reth_transaction_pool::{BestTransactionsAttributes, TransactionPool}; +use revm::{ + db::states::bundle_state::BundleRetention, + primitives::{EVMError, EnvWithHandlerCfg, InvalidTransaction, ResultAndState}, + DatabaseCommit, State, +}; +use std::sync::Arc; +use tracing::{debug, trace, warn}; + +/// Optimism's payload builder +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct OptimismPayloadBuilder { + /// The rollup's compute pending block configuration option. + // TODO(clabby): Implement this feature. + compute_pending_block: bool, + /// The rollup's chain spec. + chain_spec: Arc, +} + +impl OptimismPayloadBuilder { + /// OptimismPayloadBuilder constructor. + pub fn new(chain_spec: Arc) -> Self { + Self { compute_pending_block: true, chain_spec } + } + + /// Sets the rollup's compute pending block configuration option. + pub fn set_compute_pending_block(mut self, compute_pending_block: bool) -> Self { + self.compute_pending_block = compute_pending_block; + self + } + + /// Enables the rollup's compute pending block configuration option. + pub fn compute_pending_block(self) -> Self { + self.set_compute_pending_block(true) + } + + /// Returns the rollup's compute pending block configuration option. + pub fn is_compute_pending_block(&self) -> bool { + self.compute_pending_block + } + + /// Sets the rollup's chainspec. + pub fn set_chain_spec(mut self, chain_spec: Arc) -> Self { + self.chain_spec = chain_spec; + self + } +} + +/// Implementation of the [PayloadBuilder] trait for [OptimismPayloadBuilder]. +impl PayloadBuilder for OptimismPayloadBuilder +where + Client: StateProviderFactory, + Pool: TransactionPool, +{ + type Attributes = OptimismPayloadBuilderAttributes; + type BuiltPayload = OptimismBuiltPayload; + + fn try_build( + &self, + args: BuildArguments, + ) -> Result, PayloadBuilderError> { + optimism_payload_builder(args, self.compute_pending_block) + } + + fn on_missing_payload( + &self, + args: BuildArguments, + ) -> Option { + // In Optimism, the PayloadAttributes can specify a `no_tx_pool` option that implies we + // should not pull transactions from the tx pool. In this case, we build the payload + // upfront with the list of transactions sent in the attributes without caring about + // the results of the polling job, if a best payload has not already been built. + if args.config.attributes.no_tx_pool { + if let Ok(BuildOutcome::Better { payload, .. }) = self.try_build(args) { + trace!(target: "payload_builder", "[OPTIMISM] Forced best payload"); + return Some(payload) + } + } + + None + } + + fn build_empty_payload( + client: &Client, + config: PayloadConfig, + ) -> Result { + let extra_data = config.extra_data(); + let PayloadConfig { + initialized_block_env, + parent_block, + attributes, + chain_spec, + initialized_cfg, + .. + } = config; + + debug!(target: "payload_builder", parent_hash = ?parent_block.hash(), parent_number = parent_block.number, "building empty payload"); + + let state = client.state_by_block_hash(parent_block.hash()).map_err(|err| { + warn!(target: "payload_builder", parent_hash=%parent_block.hash(), %err, "failed to get state for empty payload"); + err + })?; + let mut db = State::builder() + .with_database_boxed(Box::new(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: u64 = 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, + &chain_spec, + block_number, + &initialized_cfg, + &initialized_block_env, + &attributes, + ).map_err(|err| { + warn!(target: "payload_builder", parent_hash=%parent_block.hash(), %err, "failed to apply beacon root contract call for empty payload"); + err + })?; + + let WithdrawalsOutcome { withdrawals_root, withdrawals } = + commit_withdrawals(&mut db, &chain_spec, attributes.payload_attributes.timestamp, attributes.payload_attributes.withdrawals.clone()).map_err(|err| { + warn!(target: "payload_builder", parent_hash=%parent_block.hash(), %err, "failed to commit withdrawals for empty payload"); + err + })?; + + // 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 = state.state_root(&bundle_state).map_err(|err| { + warn!(target: "payload_builder", parent_hash=%parent_block.hash(), %err, "failed to calculate state root for empty payload"); + err + })?; + + let mut excess_blob_gas = None; + let mut blob_gas_used = None; + + if chain_spec.is_cancun_active_at_timestamp(attributes.payload_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); + } + + 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.payload_attributes.timestamp, + mix_hash: attributes.payload_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.payload_attributes.parent_beacon_block_root, + }; + + let block = Block { header, body: vec![], ommers: vec![], withdrawals }; + let sealed_block = block.seal_slow(); + + Ok(OptimismBuiltPayload::new( + attributes.payload_attributes.payload_id(), + sealed_block, + U256::ZERO, + chain_spec, + attributes, + )) + } +} + +/// Constructs an Ethereum transaction payload from the transactions sent through the +/// Payload attributes by the sequencer. If the `no_tx_pool` argument is passed in +/// the payload attributes, the transaction pool will be ignored and the only transactions +/// included in the payload will be those sent through the attributes. +/// +/// 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(crate) fn optimism_payload_builder( + args: BuildArguments, + _compute_pending_block: bool, +) -> Result, PayloadBuilderError> +where + 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; + + debug!(target: "payload_builder", id=%attributes.payload_attributes.payload_id(), parent_hash = ?parent_block.hash(), parent_number = parent_block.number, "building new payload"); + let mut cumulative_gas_used = 0; + let block_gas_limit: u64 = attributes + .gas_limit + .unwrap_or_else(|| 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::(); + + let is_regolith = chain_spec + .is_fork_active_at_timestamp(Hardfork::Regolith, attributes.payload_attributes.timestamp); + + // apply eip-4788 pre block contract call + pre_block_beacon_root_contract_call( + &mut db, + &chain_spec, + block_number, + &initialized_cfg, + &initialized_block_env, + &attributes, + )?; + + // Ensure that the create2deployer is force-deployed at the canyon transition. Optimism + // blocks will always have at least a single transaction in them (the L1 info transaction), + // so we can safely assume that this will always be triggered upon the transition and that + // the above check for empty blocks will never be hit on OP chains. + reth_revm::optimism::ensure_create2_deployer( + chain_spec.clone(), + attributes.payload_attributes.timestamp, + &mut db, + ) + .map_err(|_| { + PayloadBuilderError::other(OptimismPayloadBuilderError::ForceCreate2DeployerFail) + })?; + + let mut receipts = Vec::new(); + for sequencer_tx in &attributes.transactions { + // Check if the job was cancelled, if so we can exit early. + if cancel.is_cancelled() { + return Ok(BuildOutcome::Cancelled) + } + + // A sequencer's block should never contain blob transactions. + if matches!(sequencer_tx.tx_type(), TxType::Eip4844) { + return Err(PayloadBuilderError::other( + OptimismPayloadBuilderError::BlobTransactionRejected, + )) + } + + // Convert the transaction to a [TransactionSignedEcRecovered]. This is + // purely for the purposes of utilizing the [tx_env_with_recovered] function. + // Deposit transactions do not have signatures, so if the tx is a deposit, this + // will just pull in its `from` address. + let sequencer_tx = sequencer_tx.clone().try_into_ecrecovered().map_err(|_| { + PayloadBuilderError::other(OptimismPayloadBuilderError::TransactionEcRecoverFailed) + })?; + + // Cache the depositor account prior to the state transition for the deposit nonce. + // + // Note that this *only* needs to be done post-regolith hardfork, as deposit nonces + // were not introduced in Bedrock. In addition, regular transactions don't have deposit + // nonces, so we don't need to touch the DB for those. + let depositor = (is_regolith && sequencer_tx.is_deposit()) + .then(|| { + db.load_cache_account(sequencer_tx.signer()) + .map(|acc| acc.account_info().unwrap_or_default()) + }) + .transpose() + .map_err(|_| { + PayloadBuilderError::other(OptimismPayloadBuilderError::AccountLoadFailed( + sequencer_tx.signer(), + )) + })?; + + let mut evm = revm::Evm::builder() + .with_db(&mut db) + .with_env_with_handler_cfg(EnvWithHandlerCfg::new_with_cfg_env( + initialized_cfg.clone(), + initialized_block_env.clone(), + tx_env_with_recovered(&sequencer_tx), + )) + .build(); + + let ResultAndState { result, state } = match evm.transact() { + Ok(res) => res, + Err(err) => { + match err { + EVMError::Transaction(err) => { + trace!(target: "payload_builder", %err, ?sequencer_tx, "Error in sequencer transaction, skipping."); + continue + } + err => { + // this is an error that we should treat as fatal for this attempt + return Err(PayloadBuilderError::EvmExecutionError(err)) + } + } + } + }; + + // to release the db reference drop evm. + drop(evm); + // commit changes + db.commit(state); + + 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. + receipts.push(Some(Receipt { + tx_type: sequencer_tx.tx_type(), + success: result.is_success(), + cumulative_gas_used, + logs: result.into_logs().into_iter().map(Into::into).collect(), + deposit_nonce: depositor.map(|account| account.nonce), + // The deposit receipt version was introduced in Canyon to indicate an update to how + // receipt hashes should be computed when set. The state transition process + // ensures this is only set for post-Canyon deposit transactions. + deposit_receipt_version: chain_spec + .is_fork_active_at_timestamp( + Hardfork::Canyon, + attributes.payload_attributes.timestamp, + ) + .then_some(1), + })); + + // append transaction to the list of executed transactions + executed_txs.push(sequencer_tx.into_signed()); + } + + if !attributes.no_tx_pool { + 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 + } + + // A sequencer's block should never contain blob transactions. + if pool_tx.tx_type() == TxType::Eip4844 as u8 { + return Err(PayloadBuilderError::other( + OptimismPayloadBuilderError::BlobTransactionRejected, + )) + } + + // 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(); + + // Configure the environment for the block. + let mut evm = revm::Evm::builder() + .with_db(&mut db) + .with_env_with_handler_cfg(EnvWithHandlerCfg::new_with_cfg_env( + initialized_cfg.clone(), + initialized_block_env.clone(), + tx_env_with_recovered(&tx), + )) + .build(); + + 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 + trace!(target: "payload_builder", %err, ?tx, "skipping nonce too low transaction"); + } else { + // if the transaction is invalid, we can skip it and all of its + // descendants + trace!(target: "payload_builder", %err, ?tx, "skipping invalid transaction and 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); + + 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. + 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(), + deposit_nonce: None, + deposit_receipt_version: None, + })); + + // 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 }) + } + + let WithdrawalsOutcome { withdrawals_root, withdrawals } = commit_withdrawals( + &mut db, + &chain_spec, + attributes.payload_attributes.timestamp, + attributes.clone().payload_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 bundle = BundleStateWithReceipts::new( + db.take_bundle(), + Receipts::from_vec(vec![receipts]), + block_number, + ); + let receipts_root = bundle + .optimism_receipts_root_slow( + block_number, + chain_spec.as_ref(), + attributes.payload_attributes.timestamp, + ) + .expect("Number is in range"); + let logs_bloom = bundle.block_logs_bloom(block_number).expect("Number is in range"); + + // calculate the state root + let state_root = state_provider.state_root(bundle.state())?; + + // create the block header + let transactions_root = proofs::calculate_transaction_root(&executed_txs); + + // initialize empty blob sidecars. There are no blob transactions on L2. + let 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.payload_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); + } + + 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.payload_attributes.timestamp, + mix_hash: attributes.payload_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.payload_attributes.parent_beacon_block_root, + blob_gas_used, + excess_blob_gas, + }; + + // seal the block + let block = Block { header, body: executed_txs, ommers: vec![], withdrawals }; + + let sealed_block = block.seal_slow(); + debug!(target: "payload_builder", ?sealed_block, "sealed built block"); + + let mut payload = OptimismBuiltPayload::new( + attributes.payload_attributes.id, + sealed_block, + total_fees, + chain_spec, + attributes, + ); + + // extend the payload with the blob sidecars from the executed txs + payload.extend_sidecars(blob_sidecars); + + Ok(BuildOutcome::Better { payload, cached_reads }) +} diff --git a/crates/payload/optimism/src/lib.rs b/crates/payload/optimism/src/lib.rs index 638f52b78..a546d0021 100644 --- a/crates/payload/optimism/src/lib.rs +++ b/crates/payload/optimism/src/lib.rs @@ -8,601 +8,11 @@ #![cfg_attr(all(not(test), feature = "optimism"), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #![allow(clippy::useless_let_if_seq)] +// The `optimism` feature must be enabled to use this crate. +#![cfg(feature = "optimism")] -#[cfg(feature = "optimism")] -pub use builder::*; - +pub mod builder; +pub use builder::OptimismPayloadBuilder; pub mod error; - -#[cfg(feature = "optimism")] -mod builder { - use crate::error::OptimismPayloadBuilderError; - use reth_basic_payload_builder::*; - use reth_payload_builder::{ - error::PayloadBuilderError, OptimismBuiltPayload, OptimismPayloadBuilderAttributes, - }; - use reth_primitives::{ - constants::{BEACON_NONCE, EMPTY_RECEIPTS, EMPTY_TRANSACTIONS}, - eip4844::calculate_excess_blob_gas, - proofs, - revm::env::tx_env_with_recovered, - Block, ChainSpec, Hardfork, Header, IntoRecoveredTransaction, Receipt, Receipts, TxType, - EMPTY_OMMER_ROOT_HASH, U256, - }; - use reth_provider::{BundleStateWithReceipts, StateProviderFactory}; - use reth_revm::database::StateProviderDatabase; - use reth_transaction_pool::{BestTransactionsAttributes, TransactionPool}; - use revm::{ - db::states::bundle_state::BundleRetention, - primitives::{EVMError, EnvWithHandlerCfg, InvalidTransaction, ResultAndState}, - DatabaseCommit, State, - }; - use std::sync::Arc; - use tracing::{debug, trace, warn}; - - /// Optimism's payload builder - #[derive(Debug, Clone, PartialEq, Eq)] - pub struct OptimismPayloadBuilder { - /// The rollup's compute pending block configuration option. - // TODO(clabby): Implement this feature. - compute_pending_block: bool, - /// The rollup's chain spec. - chain_spec: Arc, - } - - impl OptimismPayloadBuilder { - /// OptimismPayloadBuilder constructor. - pub fn new(chain_spec: Arc) -> Self { - Self { compute_pending_block: true, chain_spec } - } - - /// Sets the rollup's compute pending block configuration option. - pub fn set_compute_pending_block(mut self, compute_pending_block: bool) -> Self { - self.compute_pending_block = compute_pending_block; - self - } - - /// Enables the rollup's compute pending block configuration option. - pub fn compute_pending_block(self) -> Self { - self.set_compute_pending_block(true) - } - - /// Returns the rollup's compute pending block configuration option. - pub fn is_compute_pending_block(&self) -> bool { - self.compute_pending_block - } - - /// Sets the rollup's chainspec. - pub fn set_chain_spec(mut self, chain_spec: Arc) -> Self { - self.chain_spec = chain_spec; - self - } - } - - /// Implementation of the [PayloadBuilder] trait for [OptimismPayloadBuilder]. - impl PayloadBuilder for OptimismPayloadBuilder - where - Client: StateProviderFactory, - Pool: TransactionPool, - { - type Attributes = OptimismPayloadBuilderAttributes; - type BuiltPayload = OptimismBuiltPayload; - - fn try_build( - &self, - args: BuildArguments< - Pool, - Client, - OptimismPayloadBuilderAttributes, - OptimismBuiltPayload, - >, - ) -> Result, PayloadBuilderError> { - optimism_payload_builder(args, self.compute_pending_block) - } - - fn on_missing_payload( - &self, - args: BuildArguments< - Pool, - Client, - OptimismPayloadBuilderAttributes, - OptimismBuiltPayload, - >, - ) -> Option { - // In Optimism, the PayloadAttributes can specify a `no_tx_pool` option that implies we - // should not pull transactions from the tx pool. In this case, we build the payload - // upfront with the list of transactions sent in the attributes without caring about - // the results of the polling job, if a best payload has not already been built. - if args.config.attributes.no_tx_pool { - if let Ok(BuildOutcome::Better { payload, .. }) = self.try_build(args) { - trace!(target: "payload_builder", "[OPTIMISM] Forced best payload"); - return Some(payload) - } - } - - None - } - - fn build_empty_payload( - client: &Client, - config: PayloadConfig, - ) -> Result { - let extra_data = config.extra_data(); - let PayloadConfig { - initialized_block_env, - parent_block, - attributes, - chain_spec, - initialized_cfg, - .. - } = config; - - debug!(target: "payload_builder", parent_hash = ?parent_block.hash(), parent_number = parent_block.number, "building empty payload"); - - let state = client.state_by_block_hash(parent_block.hash()).map_err(|err| { - warn!(target: "payload_builder", parent_hash=%parent_block.hash(), %err, "failed to get state for empty payload"); - err - })?; - let mut db = State::builder() - .with_database_boxed(Box::new(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: u64 = - 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, - &chain_spec, - block_number, - &initialized_cfg, - &initialized_block_env, - &attributes, - ).map_err(|err| { - warn!(target: "payload_builder", parent_hash=%parent_block.hash(), %err, "failed to apply beacon root contract call for empty payload"); - err - })?; - - let WithdrawalsOutcome { withdrawals_root, withdrawals } = - commit_withdrawals(&mut db, &chain_spec, attributes.payload_attributes.timestamp, attributes.payload_attributes.withdrawals.clone()).map_err(|err| { - warn!(target: "payload_builder", parent_hash=%parent_block.hash(), %err, "failed to commit withdrawals for empty payload"); - err - })?; - - // 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 = state.state_root(&bundle_state).map_err(|err| { - warn!(target: "payload_builder", parent_hash=%parent_block.hash(), %err, "failed to calculate state root for empty payload"); - err - })?; - - let mut excess_blob_gas = None; - let mut blob_gas_used = None; - - if chain_spec.is_cancun_active_at_timestamp(attributes.payload_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); - } - - 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.payload_attributes.timestamp, - mix_hash: attributes.payload_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.payload_attributes.parent_beacon_block_root, - }; - - let block = Block { header, body: vec![], ommers: vec![], withdrawals }; - let sealed_block = block.seal_slow(); - - Ok(OptimismBuiltPayload::new( - attributes.payload_attributes.payload_id(), - sealed_block, - U256::ZERO, - chain_spec, - attributes, - )) - } - } - - /// Constructs an Ethereum transaction payload from the transactions sent through the - /// Payload attributes by the sequencer. If the `no_tx_pool` argument is passed in - /// the payload attributes, the transaction pool will be ignored and the only transactions - /// included in the payload will be those sent through the attributes. - /// - /// 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(crate) fn optimism_payload_builder( - args: BuildArguments, - _compute_pending_block: bool, - ) -> Result, PayloadBuilderError> - where - 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; - - debug!(target: "payload_builder", id=%attributes.payload_attributes.payload_id(), parent_hash = ?parent_block.hash(), parent_number = parent_block.number, "building new payload"); - let mut cumulative_gas_used = 0; - let block_gas_limit: u64 = attributes - .gas_limit - .unwrap_or_else(|| 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::(); - - let is_regolith = chain_spec.is_fork_active_at_timestamp( - Hardfork::Regolith, - attributes.payload_attributes.timestamp, - ); - - // apply eip-4788 pre block contract call - pre_block_beacon_root_contract_call( - &mut db, - &chain_spec, - block_number, - &initialized_cfg, - &initialized_block_env, - &attributes, - )?; - - // Ensure that the create2deployer is force-deployed at the canyon transition. Optimism - // blocks will always have at least a single transaction in them (the L1 info transaction), - // so we can safely assume that this will always be triggered upon the transition and that - // the above check for empty blocks will never be hit on OP chains. - reth_revm::optimism::ensure_create2_deployer( - chain_spec.clone(), - attributes.payload_attributes.timestamp, - &mut db, - ) - .map_err(|_| { - PayloadBuilderError::other(OptimismPayloadBuilderError::ForceCreate2DeployerFail) - })?; - - let mut receipts = Vec::new(); - for sequencer_tx in &attributes.transactions { - // Check if the job was cancelled, if so we can exit early. - if cancel.is_cancelled() { - return Ok(BuildOutcome::Cancelled) - } - - // A sequencer's block should never contain blob transactions. - if matches!(sequencer_tx.tx_type(), TxType::Eip4844) { - return Err(PayloadBuilderError::other( - OptimismPayloadBuilderError::BlobTransactionRejected, - )) - } - - // Convert the transaction to a [TransactionSignedEcRecovered]. This is - // purely for the purposes of utilizing the [tx_env_with_recovered] function. - // Deposit transactions do not have signatures, so if the tx is a deposit, this - // will just pull in its `from` address. - let sequencer_tx = sequencer_tx.clone().try_into_ecrecovered().map_err(|_| { - PayloadBuilderError::other(OptimismPayloadBuilderError::TransactionEcRecoverFailed) - })?; - - // Cache the depositor account prior to the state transition for the deposit nonce. - // - // Note that this *only* needs to be done post-regolith hardfork, as deposit nonces - // were not introduced in Bedrock. In addition, regular transactions don't have deposit - // nonces, so we don't need to touch the DB for those. - let depositor = (is_regolith && sequencer_tx.is_deposit()) - .then(|| { - db.load_cache_account(sequencer_tx.signer()) - .map(|acc| acc.account_info().unwrap_or_default()) - }) - .transpose() - .map_err(|_| { - PayloadBuilderError::other(OptimismPayloadBuilderError::AccountLoadFailed( - sequencer_tx.signer(), - )) - })?; - - let mut evm = revm::Evm::builder() - .with_db(&mut db) - .with_env_with_handler_cfg(EnvWithHandlerCfg::new_with_cfg_env( - initialized_cfg.clone(), - initialized_block_env.clone(), - tx_env_with_recovered(&sequencer_tx), - )) - .build(); - - let ResultAndState { result, state } = match evm.transact() { - Ok(res) => res, - Err(err) => { - match err { - EVMError::Transaction(err) => { - trace!(target: "payload_builder", %err, ?sequencer_tx, "Error in sequencer transaction, skipping."); - continue - } - err => { - // this is an error that we should treat as fatal for this attempt - return Err(PayloadBuilderError::EvmExecutionError(err)) - } - } - } - }; - - // to release the db reference drop evm. - drop(evm); - // commit changes - db.commit(state); - - 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. - receipts.push(Some(Receipt { - tx_type: sequencer_tx.tx_type(), - success: result.is_success(), - cumulative_gas_used, - logs: result.into_logs().into_iter().map(Into::into).collect(), - deposit_nonce: depositor.map(|account| account.nonce), - // The deposit receipt version was introduced in Canyon to indicate an update to how - // receipt hashes should be computed when set. The state transition process - // ensures this is only set for post-Canyon deposit transactions. - deposit_receipt_version: chain_spec - .is_fork_active_at_timestamp( - Hardfork::Canyon, - attributes.payload_attributes.timestamp, - ) - .then_some(1), - })); - - // append transaction to the list of executed transactions - executed_txs.push(sequencer_tx.into_signed()); - } - - if !attributes.no_tx_pool { - 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 - } - - // A sequencer's block should never contain blob transactions. - if pool_tx.tx_type() == TxType::Eip4844 as u8 { - return Err(PayloadBuilderError::other( - OptimismPayloadBuilderError::BlobTransactionRejected, - )) - } - - // 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(); - - // Configure the environment for the block. - let mut evm = revm::Evm::builder() - .with_db(&mut db) - .with_env_with_handler_cfg(EnvWithHandlerCfg::new_with_cfg_env( - initialized_cfg.clone(), - initialized_block_env.clone(), - tx_env_with_recovered(&tx), - )) - .build(); - - 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 - trace!(target: "payload_builder", %err, ?tx, "skipping nonce too low transaction"); - } else { - // if the transaction is invalid, we can skip it and all of its - // descendants - trace!(target: "payload_builder", %err, ?tx, "skipping invalid transaction and 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); - - 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. - 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(), - deposit_nonce: None, - deposit_receipt_version: None, - })); - - // 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 }) - } - - let WithdrawalsOutcome { withdrawals_root, withdrawals } = commit_withdrawals( - &mut db, - &chain_spec, - attributes.payload_attributes.timestamp, - attributes.clone().payload_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 bundle = BundleStateWithReceipts::new( - db.take_bundle(), - Receipts::from_vec(vec![receipts]), - block_number, - ); - let receipts_root = bundle - .optimism_receipts_root_slow( - block_number, - chain_spec.as_ref(), - attributes.payload_attributes.timestamp, - ) - .expect("Number is in range"); - let logs_bloom = bundle.block_logs_bloom(block_number).expect("Number is in range"); - - // calculate the state root - let state_root = state_provider.state_root(bundle.state())?; - - // create the block header - let transactions_root = proofs::calculate_transaction_root(&executed_txs); - - // initialize empty blob sidecars. There are no blob transactions on L2. - let 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.payload_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); - } - - 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.payload_attributes.timestamp, - mix_hash: attributes.payload_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.payload_attributes.parent_beacon_block_root, - blob_gas_used, - excess_blob_gas, - }; - - // seal the block - let block = Block { header, body: executed_txs, ommers: vec![], withdrawals }; - - let sealed_block = block.seal_slow(); - debug!(target: "payload_builder", ?sealed_block, "sealed built block"); - - let mut payload = OptimismBuiltPayload::new( - attributes.payload_attributes.id, - sealed_block, - total_fees, - chain_spec, - attributes, - ); - - // extend the payload with the blob sidecars from the executed txs - payload.extend_sidecars(blob_sidecars); - - Ok(BuildOutcome::Better { payload, cached_reads }) - } -} +pub mod payload; +pub use payload::{OptimismBuiltPayload, OptimismPayloadBuilderAttributes}; diff --git a/crates/payload/builder/src/optimism.rs b/crates/payload/optimism/src/payload.rs similarity index 96% rename from crates/payload/builder/src/optimism.rs rename to crates/payload/optimism/src/payload.rs index 1adde5a84..7a25689c4 100644 --- a/crates/payload/builder/src/optimism.rs +++ b/crates/payload/optimism/src/payload.rs @@ -1,6 +1,10 @@ -use crate::EthPayloadBuilderAttributes; -use alloy_rlp::{Encodable, Error as DecodeError}; +//! Payload related types + +//! Optimism builder support + +use alloy_rlp::Encodable; use reth_node_api::{BuiltPayload, PayloadBuilderAttributes}; +use reth_payload_builder::EthPayloadBuilderAttributes; use reth_primitives::{ revm::config::revm_spec_by_timestamp_after_merge, revm_primitives::{BlobExcessGasAndPrice, BlockEnv, CfgEnv, CfgEnvWithHandlerCfg, SpecId}, @@ -15,6 +19,7 @@ use reth_rpc_types_compat::engine::payload::{ block_to_payload_v3, convert_block_to_payload_field_v2, convert_standalone_withdraw_to_withdrawal, try_block_to_payload_v1, }; +use revm::primitives::HandlerCfg; use std::sync::Arc; /// Optimism Payload Builder Attributes @@ -32,12 +37,12 @@ pub struct OptimismPayloadBuilderAttributes { impl PayloadBuilderAttributes for OptimismPayloadBuilderAttributes { type RpcPayloadAttributes = OptimismPayloadAttributes; - type Error = DecodeError; + type Error = alloy_rlp::Error; /// Creates a new payload builder for the given parent block and the attributes. /// /// Derives the unique [PayloadId] for the given parent and attributes - fn try_new(parent: B256, attributes: OptimismPayloadAttributes) -> Result { + fn try_new(parent: B256, attributes: OptimismPayloadAttributes) -> Result { let (id, transactions) = { let transactions: Vec<_> = attributes .transactions @@ -148,11 +153,7 @@ impl PayloadBuilderAttributes for OptimismPayloadBuilderAttributes { { cfg_with_handler_cfg = CfgEnvWithHandlerCfg { cfg_env: cfg, - handler_cfg: revm_primitives::HandlerCfg { - spec_id, - #[cfg(feature = "optimism")] - is_optimism: chain_spec.is_optimism(), - }, + handler_cfg: HandlerCfg { spec_id, is_optimism: chain_spec.is_optimism() }, }; } diff --git a/crates/rpc/rpc-engine-api/Cargo.toml b/crates/rpc/rpc-engine-api/Cargo.toml index 342600c32..5ffb09294 100644 --- a/crates/rpc/rpc-engine-api/Cargo.toml +++ b/crates/rpc/rpc-engine-api/Cargo.toml @@ -49,4 +49,4 @@ reth-payload-builder = { workspace = true, features = ["test-utils"] } assert_matches.workspace = true [features] -optimism = ["reth-primitives/optimism", "reth-rpc-types/optimism"] +optimism = ["reth-primitives/optimism"] diff --git a/crates/rpc/rpc-types-compat/Cargo.toml b/crates/rpc/rpc-types-compat/Cargo.toml index 78fdbac24..fa21f15c5 100644 --- a/crates/rpc/rpc-types-compat/Cargo.toml +++ b/crates/rpc/rpc-types-compat/Cargo.toml @@ -21,4 +21,4 @@ alloy-rpc-types.workspace = true serde_json.workspace = true [features] -optimism = ["reth-primitives/optimism", "reth-rpc-types/optimism"] +optimism = ["reth-primitives/optimism"] diff --git a/crates/rpc/rpc-types/Cargo.toml b/crates/rpc/rpc-types/Cargo.toml index e3a73e8b2..4da62f7ca 100644 --- a/crates/rpc/rpc-types/Cargo.toml +++ b/crates/rpc/rpc-types/Cargo.toml @@ -42,7 +42,6 @@ proptest-derive = { workspace = true, optional = true } default = ["jsonrpsee-types"] arbitrary = ["dep:arbitrary", "dep:proptest-derive", "dep:proptest", "alloy-primitives/arbitrary", "alloy-rpc-types/arbitrary"] ssz = ["dep:ethereum_ssz" ,"dep:ethereum_ssz_derive", "alloy-primitives/ssz", "alloy-rpc-types/ssz", "alloy-rpc-engine-types/ssz"] -optimism = [] [dev-dependencies]