diff --git a/crates/op-rbuilder/src/builder/assembly.rs b/crates/op-rbuilder/src/builder/assembly.rs new file mode 100644 index 000000000..cf1bdd620 --- /dev/null +++ b/crates/op-rbuilder/src/builder/assembly.rs @@ -0,0 +1,576 @@ +use std::sync::Arc; + +use alloy_consensus::{ + BlockBody, EMPTY_OMMER_ROOT_HASH, Header, TxReceipt, constants::EMPTY_WITHDRAWALS, proofs, +}; +use alloy_eips::{Encodable2718, eip7685::EMPTY_REQUESTS_HASH, merge::BEACON_NONCE}; +use alloy_evm::block::BlockExecutionResult; +use alloy_primitives::{Address, B256, Bloom, Bytes, U256}; +use alloy_rpc_types_eth::Withdrawals; +use op_alloy_consensus::OpReceipt; +use op_alloy_rpc_types_engine::{ + OpFlashblockPayload, OpFlashblockPayloadBase, OpFlashblockPayloadDelta, + OpFlashblockPayloadMetadata, +}; +use reth::payload::PayloadBuilderAttributes; +use reth_basic_payload_builder::PayloadConfig; +use reth_execution_types::BlockExecutionOutput; +use reth_node_api::{Block, BuiltPayloadExecutedBlock, PayloadBuilderError}; +use reth_optimism_consensus::{calculate_receipt_root_no_memo_optimism, isthmus}; +use reth_optimism_node::{OpBuiltPayload, OpPayloadBuilderAttributes}; +use reth_optimism_primitives::OpTransactionSigned; +use reth_payload_builder::PayloadId; +use reth_primitives::SealedHeader; +use reth_primitives_traits::RecoveredBlock; +use reth_provider::{ + HashedPostStateProvider, ProviderError, StateRootProvider, StorageRootProvider, +}; +use reth_revm::{ + State, + db::{BundleState, states::bundle_state::BundleRetention}, +}; +use reth_trie::{HashedPostState, updates::TrieUpdates}; +use revm::{Database, interpreter::as_u64_saturated}; +use std::{collections::BTreeMap, time::Instant}; +use tracing::{debug, info, warn}; + +use crate::{ + builder::{StateRootCalculator, payload::FlashblocksState}, + evm::OpBlockEvmFactory, + hardforks::ActiveHardforks, + metrics::OpRBuilderMetrics, + primitives::reth::ExecutionInfo, +}; + +/// Pre-resolved parameters needed by `build_block`, decoupled from +/// `OpPayloadBuilderCtx`. +pub(super) struct BlockAssemblyInput { + hardforks: ActiveHardforks, + parent_header: SealedHeader, + attributes: OpPayloadBuilderAttributes, + beneficiary: Address, + block_number: u64, + base_fee: u64, + block_gas_limit: u64, + withdrawals: Option, + extra_data: Bytes, +} + +#[derive(Clone)] +struct DerivedBlockArtifacts { + state_root: B256, + transactions_root: B256, + receipts_root: B256, + withdrawals_root: Option, + logs_bloom: Bloom, + excess_blob_gas: Option, + blob_gas_used: Option, + requests_hash: Option, +} + +impl BlockAssemblyInput { + pub(super) fn try_new( + payload_config: PayloadConfig>, + evm_factory: &OpBlockEvmFactory, + hardforks: ActiveHardforks, + ) -> Result { + let attributes = &payload_config.attributes; + let block_env = &evm_factory.evm_env().block_env; + + let block_gas_limit = attributes.gas_limit.unwrap_or(block_env.gas_limit); + let block_number = as_u64_saturated!(block_env.number); + let base_fee = block_env.basefee; + let beneficiary = block_env.beneficiary; + + let withdrawals = hardforks + .is_shanghai_active() + .then(|| attributes.payload_attributes.withdrawals.clone()); + + let extra_data = if hardforks.is_jovian_active() { + attributes + .get_jovian_extra_data(hardforks.base_fee_params()) + .map_err(PayloadBuilderError::other)? + } else if hardforks.is_holocene_active() { + attributes + .get_holocene_extra_data(hardforks.base_fee_params()) + .map_err(PayloadBuilderError::other)? + } else { + Bytes::default() + }; + + Ok(Self { + hardforks, + parent_header: (*payload_config.parent_header).clone(), + attributes: payload_config.attributes, + beneficiary, + block_number, + base_fee, + block_gas_limit, + withdrawals, + extra_data, + }) + } + + fn payload_id(&self) -> PayloadId { + self.attributes.payload_id() + } + + fn merge_transitions_into_bundle_state( + &self, + state: &mut State, + metrics: Arc, + enable_tx_tracking_debug_logs: bool, + ) where + DB: Database + AsRef

, + P: StateRootProvider + HashedPostStateProvider + StorageRootProvider, + { + let state_merge_start_time = Instant::now(); + state.merge_transitions(BundleRetention::Reverts); + let state_transition_merge_time = state_merge_start_time.elapsed(); + + metrics + .state_transition_merge_duration + .record(state_transition_merge_time); + metrics + .state_transition_merge_gauge + .set(state_transition_merge_time); + + if enable_tx_tracking_debug_logs { + debug!( + target: "tx_trace", + block_number = self.block_number, + duration_us = state_transition_merge_time.as_micros() as u64, + stage = "state_merge" + ); + } + } + + fn check_block_number(&self) -> Result<(), PayloadBuilderError> { + let block_number = self.block_number; + let expected = self.parent_header.number + 1; + if block_number != expected { + return Err(PayloadBuilderError::Other( + eyre::eyre!( + "build context block number mismatch: expected {}, got {}", + expected, + self.block_number + ) + .into(), + )); + } + + Ok(()) + } + + fn request_hash(&self) -> Option { + if self.hardforks.is_isthmus_active() { + // always empty requests hash post isthmus + Some(EMPTY_REQUESTS_HASH) + } else { + None + } + } + + fn withdrawals_root( + &self, + state_updates: &BundleState, + state: impl StorageRootProvider, + ) -> Result, PayloadBuilderError> { + let withdrawals_root = if self.hardforks.is_isthmus_active() { + // withdrawals root field in block header is used for storage root + // of L2 predeploy `l2tol1-message-passer` + Some( + isthmus::withdrawals_root(state_updates, state) + .map_err(PayloadBuilderError::other)?, + ) + } else if self.hardforks.is_canyon_active() { + Some(EMPTY_WITHDRAWALS) + } else { + None + }; + + Ok(withdrawals_root) + } + + fn blob_fields(&self, info: &ExecutionInfo) -> (Option, Option) { + if let Some(blob_fields) = info.optional_blob_fields { + return blob_fields; + } + + if self.hardforks.is_jovian_active() { + let scalar = info + .da_footprint_scalar + .expect("Scalar must be defined for Jovian blocks"); + let result = info.cumulative_da_bytes_used * scalar as u64; + (Some(0), Some(result)) + } else if self.hardforks.is_ecotone_active() { + (Some(0), Some(0)) + } else { + (None, None) + } + } + + fn compute_derived_artifacts( + &self, + state: &State, + info: &ExecutionInfo, + state_root: B256, + ) -> Result + where + DB: Database + AsRef

, + P: StateRootProvider + HashedPostStateProvider + StorageRootProvider, + { + let receipts_root = calculate_receipt_root_no_memo_optimism( + &info.receipts, + &self.hardforks, + self.attributes.timestamp(), + ); + let transactions_root = proofs::calculate_transaction_root(&info.executed_transactions); + let withdrawals_root = + self.withdrawals_root(&state.bundle_state, state.database.as_ref())?; + let logs_bloom = alloy_primitives::logs_bloom(info.receipts.iter().flat_map(|r| r.logs())); + let (excess_blob_gas, blob_gas_used) = self.blob_fields(info); + let requests_hash = self.request_hash(); + + Ok(DerivedBlockArtifacts { + state_root, + transactions_root, + receipts_root, + withdrawals_root, + logs_bloom, + excess_blob_gas, + blob_gas_used, + requests_hash, + }) + } + + fn construct_block( + &self, + info: &ExecutionInfo, + derived_block_artifacts: DerivedBlockArtifacts, + ) -> alloy_consensus::Block { + let DerivedBlockArtifacts { + state_root, + transactions_root, + receipts_root, + withdrawals_root, + logs_bloom, + excess_blob_gas, + blob_gas_used, + requests_hash, + } = derived_block_artifacts; + + let header = Header { + parent_hash: self.parent_header.hash(), + ommers_hash: EMPTY_OMMER_ROOT_HASH, + beneficiary: self.beneficiary, + state_root, + transactions_root, + receipts_root, + withdrawals_root, + logs_bloom, + timestamp: self.attributes.payload_attributes.timestamp, + mix_hash: self.attributes.payload_attributes.prev_randao, + nonce: BEACON_NONCE.into(), + base_fee_per_gas: Some(self.base_fee), + number: self.parent_header.number + 1, + gas_limit: self.block_gas_limit, + difficulty: U256::ZERO, + gas_used: info.cumulative_gas_used, + extra_data: self.extra_data.clone(), + parent_beacon_block_root: self.attributes.payload_attributes.parent_beacon_block_root, + blob_gas_used, + excess_blob_gas, + requests_hash, + }; + + alloy_consensus::Block::::new( + header, + BlockBody { + transactions: info.executed_transactions.clone(), + ommers: vec![], + withdrawals: self.withdrawals.clone(), + }, + ) + } + + pub(super) fn assemble( + self, + state: &mut State, + mut fb_state: Option<&mut FlashblocksState>, + info: &mut ExecutionInfo, + metrics: Arc, + calculate_state_root: bool, + enable_tx_tracking_debug_logs: bool, + ) -> Result<(OpBuiltPayload, OpFlashblockPayload), PayloadBuilderError> + where + DB: Database + AsRef

, + P: StateRootProvider + HashedPostStateProvider + StorageRootProvider, + { + // We use it to preserve state, so we run merge_transitions on transition state at most once + let untouched_transition_state = state.transition_state.clone(); + self.merge_transitions_into_bundle_state( + state, + metrics.clone(), + enable_tx_tracking_debug_logs, + ); + + self.check_block_number()?; + + // TODO: maybe recreate state with bundle in here + let state_root_start_time = Instant::now(); + let mut state_root = B256::ZERO; + let mut hashed_state = HashedPostState::default(); + let mut trie_updates_to_cache: Option> = None; + + let flashblock_index_for_trace = fb_state + .as_deref() + .map(|s| s.flashblock_index()) + .unwrap_or(0); + + if calculate_state_root { + let state_provider = state.database.as_ref(); + let flashblock_index = fb_state + .as_deref() + .map(|s| s.flashblock_index()) + .unwrap_or(0); + + hashed_state = state_provider.hashed_post_state(&state.bundle_state); + + let mut default_calc = StateRootCalculator::default(); + let calc = match fb_state.as_deref_mut() { + Some(s) => &mut s.state_root_calculator_mut(), + None => &mut default_calc, + }; + + debug!( + target: "payload_builder", + flashblock_index, + incremental = calc.has_cached_trie(), + "Computing state root" + ); + + let output = calc + .compute(state_provider, hashed_state.clone()) + .inspect_err(|err| { + warn!( + target: "payload_builder", + parent_header = %self.parent_header.hash(), + %err, + "failed to calculate state root for payload" + ); + }) + .map_err(PayloadBuilderError::other)?; + state_root = output.state_root; + trie_updates_to_cache = Some(output.trie_updates); + + let state_root_calculation_time = state_root_start_time.elapsed(); + metrics + .state_root_calculation_duration + .record(state_root_calculation_time); + metrics + .state_root_calculation_gauge + .set(state_root_calculation_time); + + debug!( + target: "payload_builder", + flashblock_index, + state_root = %state_root, + duration_ms = state_root_calculation_time.as_millis(), + "State root calculation completed" + ); + + if enable_tx_tracking_debug_logs { + debug!( + target: "tx_trace", + block_number = self.block_number, + flashblock_index = flashblock_index_for_trace, + duration_ms = state_root_calculation_time.as_millis() as u64, + incremental = calc.has_cached_trie(), + cumulative_gas = info.cumulative_gas_used, + num_txs = info.executed_transactions.len(), + stage = "state_root_computed" + ); + } + } + + let derived_block_artifacts = self.compute_derived_artifacts(state, info, state_root)?; + + let block = self.construct_block(info, derived_block_artifacts.clone()); + + let seal_start = Instant::now(); + let sealed_block = Arc::new(block.clone().seal_slow()); + let seal_duration = seal_start.elapsed(); + + let block_hash = sealed_block.hash(); + + let target_flashblock_count_for_trace = fb_state + .as_deref() + .map(|s| s.target_flashblock_count()) + .unwrap_or(0); + + info!( + target: "payload_builder", + id = %self.payload_id(), + block_number = self.block_number, + block_hash = %block_hash, + flashblock_index = flashblock_index_for_trace, + target_flashblocks = target_flashblock_count_for_trace, + tx_count = info.executed_transactions.len(), + gas_used = info.cumulative_gas_used, + da_used = info.cumulative_da_bytes_used, + state_root = %state_root, + seal_duration_us = seal_duration.as_micros() as u64, + "Block sealed" + ); + + if enable_tx_tracking_debug_logs { + debug!( + target: "tx_trace", + block_number = self.block_number, + flashblock_index = flashblock_index_for_trace, + block_hash = ?block_hash, + seal_duration_us = seal_duration.as_micros() as u64, + build_block_total_time_since_state_root_start_us = state_root_start_time.elapsed().as_micros() as u64, + cumulative_gas = info.cumulative_gas_used, + num_txs = info.executed_transactions.len(), + stage = "block_sealed" + ); + } + + // need to read balances before take_bundle() below + let new_account_balances = state + .bundle_state + .state + .iter() + .filter_map(|(address, account)| { + account.info.as_ref().map(|info| (*address, info.balance)) + }) + .collect::>(); + + let bundle_state = state.take_bundle(); + let execution_output = BlockExecutionOutput { + state: bundle_state, + result: BlockExecutionResult { + receipts: info.receipts.clone(), + requests: Default::default(), + gas_used: info.cumulative_gas_used, + blob_gas_used: derived_block_artifacts.blob_gas_used.unwrap_or_default(), + }, + }; + + let recovered_block = RecoveredBlock::new_unhashed(block, info.executed_senders.clone()); + + // create the executed block data + let executed = BuiltPayloadExecutedBlock { + recovered_block: Arc::new(recovered_block), + execution_output: Arc::new(execution_output), + trie_updates: either::Either::Left( + trie_updates_to_cache.unwrap_or_else(|| Arc::new(TrieUpdates::default())), + ), + hashed_state: either::Either::Left(Arc::new(hashed_state)), + }; + debug!( + target: "payload_builder", + id = %self.payload_id(), + "Executed block created" + ); + + // pick the new transactions from the info field and update the last flashblock index + let (new_transactions, new_receipts) = if let Some(fb_state) = fb_state { + let new_txs = fb_state.slice_new_transactions(&info.executed_transactions); + let new_receipts = fb_state.slice_new_receipts(&info.receipts); + fb_state.set_last_flashblock_tx_index(info.executed_transactions.len()); + (new_txs, new_receipts) + } else { + ( + info.executed_transactions.as_slice(), + info.receipts.as_slice(), + ) + }; + + let new_transactions_encoded: Vec = new_transactions + .iter() + .map(|tx| tx.encoded_2718().into()) + .collect(); + + let receipts_with_hash: BTreeMap = new_transactions + .iter() + .zip(new_receipts.iter()) + .map(|(tx, receipt)| { + // TODO: remove this once reth updates to use the op-alloy defined type as well. + let converted_receipt = match receipt { + OpReceipt::Legacy(r) => op_alloy_consensus::OpReceipt::Legacy(r.clone()), + OpReceipt::Eip2930(r) => op_alloy_consensus::OpReceipt::Eip2930(r.clone()), + OpReceipt::Eip1559(r) => op_alloy_consensus::OpReceipt::Eip1559(r.clone()), + OpReceipt::Eip7702(r) => op_alloy_consensus::OpReceipt::Eip7702(r.clone()), + OpReceipt::Deposit(r) => op_alloy_consensus::OpReceipt::Deposit( + op_alloy_consensus::OpDepositReceipt { + inner: r.inner.clone(), + deposit_nonce: r.deposit_nonce, + deposit_receipt_version: r.deposit_receipt_version, + }, + ), + }; + (tx.tx_hash(), converted_receipt) + }) + .collect(); + + let metadata = OpFlashblockPayloadMetadata { + receipts: receipts_with_hash, + new_account_balances, + block_number: self.parent_header.number + 1, + }; + + let (_, blob_gas_used) = self.blob_fields(info); + + // Prepare the flashblocks message + let fb_payload = OpFlashblockPayload { + payload_id: self.payload_id(), + index: 0, + base: Some(OpFlashblockPayloadBase { + parent_beacon_block_root: self + .attributes + .payload_attributes + .parent_beacon_block_root + .ok_or_else(|| { + PayloadBuilderError::Other( + eyre::eyre!("parent beacon block root not found").into(), + ) + })?, + parent_hash: self.parent_header.hash(), + fee_recipient: self.attributes.suggested_fee_recipient(), + prev_randao: self.attributes.payload_attributes.prev_randao, + block_number: self.parent_header.number + 1, + gas_limit: self.block_gas_limit, + timestamp: self.attributes.payload_attributes.timestamp, + extra_data: self.extra_data.clone(), + base_fee_per_gas: U256::from(self.base_fee), + }), + diff: OpFlashblockPayloadDelta { + state_root, + receipts_root: derived_block_artifacts.receipts_root, + logs_bloom: derived_block_artifacts.logs_bloom, + gas_used: info.cumulative_gas_used, + block_hash, + transactions: new_transactions_encoded, + withdrawals: self.withdrawals.clone().unwrap_or_default().to_vec(), + withdrawals_root: derived_block_artifacts.withdrawals_root.unwrap_or_default(), + blob_gas_used, + }, + metadata, + }; + // Need to ensure `state.bundle = None`, was done previously with `state.take_bundle()` + state.transition_state = untouched_transition_state; + + Ok(( + OpBuiltPayload::new( + self.payload_id(), + sealed_block, + info.total_fees, + Some(executed), + ), + fb_payload, + )) + } +} diff --git a/crates/op-rbuilder/src/builder/context.rs b/crates/op-rbuilder/src/builder/context.rs index 14805a930..2f851abbd 100644 --- a/crates/op-rbuilder/src/builder/context.rs +++ b/crates/op-rbuilder/src/builder/context.rs @@ -1,11 +1,9 @@ use alloy_consensus::{Transaction, conditional::BlockConditionalAttributes}; use alloy_eips::{Encodable2718, Typed2718}; use alloy_evm::Database; -use alloy_primitives::{B256, BlockHash, Bytes, U256}; -use alloy_rpc_types_eth::Withdrawals; +use alloy_primitives::{B256, BlockHash, U256}; use op_revm::L1BlockInfo; use reth_basic_payload_builder::PayloadConfig; -use reth_chainspec::EthChainSpec; use reth_evm::{Evm, EvmError, InvalidTxError, eth::receipt_builder::ReceiptBuilderCtx}; use reth_node_api::PayloadBuilderError; use reth_optimism_chainspec::OpChainSpec; @@ -136,23 +134,11 @@ impl OpPayloadJobCtx { self.parent().hash() } - /// Returns the timestamp - pub fn timestamp(&self) -> u64 { - self.attributes().timestamp() - } - /// Returns the builder attributes. pub(super) const fn attributes(&self) -> &OpPayloadBuilderAttributes { &self.config.attributes } - /// Returns the withdrawals if shanghai is active. - pub fn withdrawals(&self) -> Option<&Withdrawals> { - self.hardforks - .is_shanghai_active() - .then(|| &self.attributes().payload_attributes.withdrawals) - } - /// Returns the block gas limit to target. pub fn block_gas_limit(&self) -> u64 { match self.gas_limit_config.gas_limit() { @@ -183,55 +169,6 @@ impl OpPayloadJobCtx { .map(|gasprice| gasprice as u64) } - /// Returns the blob fields for the header. - /// - /// This will return the culmative DA bytes * scalar after Jovian - /// after Ecotone, this will always return Some(0) as blobs aren't supported - /// pre Ecotone, these fields aren't used. - pub fn blob_fields(&self, info: &ExecutionInfo) -> (Option, Option) { - // For payload validation - if let Some(blob_fields) = info.optional_blob_fields { - return blob_fields; - } - // Compute from execution info - if self.hardforks.is_jovian_active() { - let scalar = info - .da_footprint_scalar - .expect("Scalar must be defined for Jovian blocks"); - let result = info.cumulative_da_bytes_used * scalar as u64; - (Some(0), Some(result)) - } else if self.hardforks.is_ecotone_active() { - (Some(0), Some(0)) - } else { - (None, None) - } - } - - /// Returns the extra data for the block. - /// - /// After holocene this extracts the extradata from the payload - pub fn extra_data(&self) -> Result { - if self.hardforks.is_jovian_active() { - self.attributes() - .get_jovian_extra_data( - self.chain_spec.base_fee_params_at_timestamp( - self.attributes().payload_attributes.timestamp, - ), - ) - .map_err(PayloadBuilderError::other) - } else if self.hardforks.is_holocene_active() { - self.attributes() - .get_holocene_extra_data( - self.chain_spec.base_fee_params_at_timestamp( - self.attributes().payload_attributes.timestamp, - ), - ) - .map_err(PayloadBuilderError::other) - } else { - Ok(Default::default()) - } - } - /// Returns the current fee settings for transactions from the mempool pub fn best_transaction_attributes(&self) -> BestTransactionsAttributes { BestTransactionsAttributes::new(self.base_fee(), self.get_blob_gasprice()) diff --git a/crates/op-rbuilder/src/builder/mod.rs b/crates/op-rbuilder/src/builder/mod.rs index 1d43540f3..b81bfe665 100644 --- a/crates/op-rbuilder/src/builder/mod.rs +++ b/crates/op-rbuilder/src/builder/mod.rs @@ -9,6 +9,7 @@ use crate::{ tx_signer::Signer, }; +mod assembly; mod best_txs; mod builder_tx; pub(crate) mod cancellation; @@ -22,7 +23,7 @@ mod payload_handler; mod receipt; mod service; mod state_root; -mod syncer_ctx; +mod syncer_config; mod timing; mod wspub; diff --git a/crates/op-rbuilder/src/builder/payload.rs b/crates/op-rbuilder/src/builder/payload.rs index bcff05bcf..ec20933e1 100644 --- a/crates/op-rbuilder/src/builder/payload.rs +++ b/crates/op-rbuilder/src/builder/payload.rs @@ -2,6 +2,7 @@ use super::{state_root::StateRootCalculator, wspub::WebSocketPublisher}; use crate::{ builder::{ BuilderConfig, + assembly::BlockAssemblyInput, best_txs::{FlashblockPoolTxCursor, FlashblockTxTracker}, builder_tx::{BuilderTransactions, reserve_builder_tx_budget}, context::{OpPayloadBuilderCtx, OpPayloadJobCtx}, @@ -17,28 +18,16 @@ use crate::{ tokio_metrics::FlashblocksTaskMetrics, traits::{ClientBounds, PoolBounds}, }; -use alloy_consensus::{ - BlockBody, EMPTY_OMMER_ROOT_HASH, Header, TxReceipt, constants::EMPTY_WITHDRAWALS, proofs, -}; -use alloy_eips::{Encodable2718, eip7685::EMPTY_REQUESTS_HASH, merge::BEACON_NONCE}; -use alloy_evm::block::BlockExecutionResult; -use alloy_primitives::{Address, B256, Bytes, U256}; use eyre::WrapErr as _; -use op_alloy_rpc_types_engine::{ - OpFlashblockPayload, OpFlashblockPayloadBase, OpFlashblockPayloadDelta, - OpFlashblockPayloadMetadata, -}; +use op_alloy_rpc_types_engine::OpFlashblockPayload; use reth_chainspec::EthChainSpec; use reth_evm::{ConfigureEvm, execute::BlockBuilder}; -use reth_execution_types::BlockExecutionOutput; -use reth_node_api::{Block, BuiltPayloadExecutedBlock, PayloadBuilderError}; -use reth_optimism_consensus::{calculate_receipt_root_no_memo_optimism, isthmus}; +use reth_node_api::PayloadBuilderError; use reth_optimism_evm::{OpEvmConfig, OpNextBlockEnvAttributes}; use reth_optimism_node::{OpBuiltPayload, OpPayloadBuilderAttributes}; use reth_optimism_primitives::{OpReceipt, OpTransactionSigned}; use reth_payload_primitives::PayloadBuilderAttributes; use reth_payload_util::BestPayloadTransactions; -use reth_primitives_traits::RecoveredBlock; use reth_provider::{ HashedPostStateProvider, ProviderError, StateRootProvider, StorageRootProvider, }; @@ -46,35 +35,16 @@ use reth_revm::{ State, cached::CachedReads, database::StateProviderDatabase, - db::{CacheState, TransitionState, states::bundle_state::BundleRetention}, + db::{CacheState, TransitionState}, }; use reth_tasks::Runtime; use reth_transaction_pool::TransactionPool; -use reth_trie::{HashedPostState, updates::TrieUpdates}; use revm::Database; -use std::{collections::BTreeMap, ops::Deref, sync::Arc, time::Instant}; +use std::{ops::Deref, sync::Arc, time::Instant}; use tokio::sync::{mpsc, watch}; use tokio_util::sync::CancellationToken; use tracing::{debug, error, info, info_span, metadata::Level, span, warn}; -/// Converts a reth OpReceipt to an op-alloy OpReceipt -/// TODO: remove this once reth updates to use the op-alloy defined type as well. -fn convert_receipt(receipt: &OpReceipt) -> op_alloy_consensus::OpReceipt { - match receipt { - OpReceipt::Legacy(r) => op_alloy_consensus::OpReceipt::Legacy(r.clone()), - OpReceipt::Eip2930(r) => op_alloy_consensus::OpReceipt::Eip2930(r.clone()), - OpReceipt::Eip1559(r) => op_alloy_consensus::OpReceipt::Eip1559(r.clone()), - OpReceipt::Eip7702(r) => op_alloy_consensus::OpReceipt::Eip7702(r.clone()), - OpReceipt::Deposit(r) => { - op_alloy_consensus::OpReceipt::Deposit(op_alloy_consensus::OpDepositReceipt { - inner: r.inner.clone(), - deposit_nonce: r.deposit_nonce, - deposit_receipt_version: r.deposit_receipt_version, - }) - } - } -} - type NextFlashblockPoolTxCursor<'a, Pool> = FlashblockPoolTxCursor< 'a, ::Transaction, @@ -222,11 +192,11 @@ impl FlashblocksState { self } - fn flashblock_index(&self) -> u64 { + pub(super) fn flashblock_index(&self) -> u64 { self.flashblock_index } - fn target_flashblock_count(&self) -> u64 { + pub(super) fn target_flashblock_count(&self) -> u64 { self.target_flashblock_count } @@ -250,12 +220,16 @@ impl FlashblocksState { self.target_da_footprint_for_batch } - fn set_last_flashblock_tx_index(&mut self, index: usize) { + pub(super) fn set_last_flashblock_tx_index(&mut self, index: usize) { self.last_flashblock_tx_index = index; } + pub(super) fn state_root_calculator_mut(&mut self) -> &mut StateRootCalculator { + &mut self.state_root_calculator + } + /// Extracts new transactions since the last flashblock - fn slice_new_transactions<'a>( + pub(super) fn slice_new_transactions<'a>( &self, all_transactions: &'a [OpTransactionSigned], ) -> &'a [OpTransactionSigned] { @@ -263,7 +237,7 @@ impl FlashblocksState { } /// Extracts new receipts since the last flashblock - fn slice_new_receipts<'a>(&self, all_receipts: &'a [OpReceipt]) -> &'a [OpReceipt] { + pub(super) fn slice_new_receipts<'a>(&self, all_receipts: &'a [OpReceipt]) -> &'a [OpReceipt] { &all_receipts[self.last_flashblock_tx_index..] } } @@ -899,12 +873,18 @@ where ); }; - let (payload, fb_payload) = build_block( + let (payload, fb_payload) = BlockAssemblyInput::try_new( + ctx.config.clone(), + &ctx.evm_factory, + ctx.hardforks.clone(), + )? + .assemble( &mut state, - &ctx, Some(&mut fb_state), &mut info, + ctx.metrics.clone(), !ctx.disable_state_root || ctx.attributes().no_tx_pool, // need to calculate state root for CL sync + ctx.enable_tx_tracking_debug_logs, )?; // we can safely take from state as we drop it at the end of the scope @@ -1051,12 +1031,18 @@ where } let total_block_built_duration = Instant::now(); - let build_result = build_block( + let build_result = BlockAssemblyInput::try_new( + ctx.config.clone(), + &ctx.evm_factory, + ctx.hardforks.clone(), + )? + .assemble( state, - ctx, Some(fb_state), info, + ctx.metrics.clone(), !ctx.disable_state_root || ctx.attributes().no_tx_pool, + ctx.enable_tx_tracking_debug_logs, ); let total_block_built_duration = total_block_built_duration.elapsed(); ctx.metrics @@ -1261,348 +1247,6 @@ where Ok(info) } -#[tracing::instrument(level = "info", name = "seal_block", skip_all)] -pub(super) fn build_block( - state: &mut State, - ctx: &OpPayloadJobCtx, - mut fb_state: Option<&mut FlashblocksState>, - info: &mut ExecutionInfo, - calculate_state_root: bool, -) -> Result<(OpBuiltPayload, OpFlashblockPayload), PayloadBuilderError> -where - DB: Database + AsRef

, - P: StateRootProvider + HashedPostStateProvider + StorageRootProvider, -{ - // We use it to preserve state, so we run merge_transitions on transition state at most once - let untouched_transition_state = state.transition_state.clone(); - let state_merge_start_time = Instant::now(); - state.merge_transitions(BundleRetention::Reverts); - let state_transition_merge_time = state_merge_start_time.elapsed(); - ctx.metrics - .state_transition_merge_duration - .record(state_transition_merge_time); - ctx.metrics - .state_transition_merge_gauge - .set(state_transition_merge_time); - - if ctx.builder_ctx.enable_tx_tracking_debug_logs { - debug!( - target: "tx_trace", - block_number = ctx.block_number(), - duration_us = state_transition_merge_time.as_micros() as u64, - stage = "state_merge" - ); - } - - let block_number = ctx.block_number(); - let expected = ctx.parent().number + 1; - if block_number != expected { - return Err(PayloadBuilderError::Other( - eyre::eyre!( - "build context block number mismatch: expected {}, got {}", - expected, - block_number - ) - .into(), - )); - } - - let receipts_root = calculate_receipt_root_no_memo_optimism( - &info.receipts, - &ctx.chain_spec, - ctx.attributes().timestamp(), - ); - let logs_bloom = alloy_primitives::logs_bloom(info.receipts.iter().flat_map(|r| r.logs())); - - // TODO: maybe recreate state with bundle in here - // calculate the state root - let state_root_start_time = Instant::now(); - let mut state_root = B256::ZERO; - let mut hashed_state = HashedPostState::default(); - let mut trie_updates_to_cache: Option> = None; - - let flashblock_index_for_trace = fb_state - .as_deref() - .map(|s| s.flashblock_index()) - .unwrap_or(0); - let target_flashblock_count_for_trace = fb_state - .as_deref() - .map(|s| s.target_flashblock_count()) - .unwrap_or(0); - - if calculate_state_root { - let _state_root_span = span!(Level::INFO, "state_root").entered(); - let state_provider = state.database.as_ref(); - - let flashblock_index = fb_state - .as_deref() - .map(|s| s.flashblock_index()) - .unwrap_or(0); - - hashed_state = state_provider.hashed_post_state(&state.bundle_state); - - let mut default_calc = StateRootCalculator::default(); - let calc = match fb_state.as_deref_mut() { - Some(s) => &mut s.state_root_calculator, - None => &mut default_calc, - }; - - debug!( - target: "payload_builder", - flashblock_index, - incremental = calc.has_cached_trie(), - "Computing state root" - ); - - let output = calc - .compute(state_provider, hashed_state.clone()) - .inspect_err(|err| { - warn!( - target: "payload_builder", - parent_header=%ctx.parent().hash(), - %err, - "failed to calculate state root for payload" - ); - }) - .map_err(PayloadBuilderError::other)?; - state_root = output.state_root; - trie_updates_to_cache = Some(output.trie_updates); - - let state_root_calculation_time = state_root_start_time.elapsed(); - ctx.metrics - .state_root_calculation_duration - .record(state_root_calculation_time); - ctx.metrics - .state_root_calculation_gauge - .set(state_root_calculation_time); - - debug!( - target: "payload_builder", - flashblock_index, - state_root = %state_root, - duration_ms = state_root_calculation_time.as_millis(), - "State root calculation completed" - ); - - if ctx.builder_ctx.enable_tx_tracking_debug_logs { - debug!( - target: "tx_trace", - block_number = ctx.block_number(), - flashblock_index = flashblock_index_for_trace, - duration_ms = state_root_calculation_time.as_millis() as u64, - incremental = fb_state.as_deref().is_some_and(|s| s.state_root_calculator.has_cached_trie()), - cumulative_gas = info.cumulative_gas_used, - num_txs = info.executed_transactions.len(), - stage = "state_root_computed" - ); - } - } - - let mut requests_hash = None; - let withdrawals_root = if ctx.hardforks.is_isthmus_active() { - // always empty requests hash post isthmus - requests_hash = Some(EMPTY_REQUESTS_HASH); - - // withdrawals root field in block header is used for storage root of L2 predeploy - // `l2tol1-message-passer` - Some( - isthmus::withdrawals_root(&state.bundle_state, state.database.as_ref()) - .map_err(PayloadBuilderError::other)?, - ) - } else if ctx.hardforks.is_canyon_active() { - Some(EMPTY_WITHDRAWALS) - } else { - None - }; - - // create the block header - let transactions_root = proofs::calculate_transaction_root(&info.executed_transactions); - - let (excess_blob_gas, blob_gas_used) = ctx.blob_fields(info); - let extra_data = ctx.extra_data()?; - - // need to read balances before take_bundle() below - let new_account_balances = state - .bundle_state - .state - .iter() - .filter_map(|(address, account)| account.info.as_ref().map(|info| (*address, info.balance))) - .collect::>(); - - let bundle_state = state.take_bundle(); - let execution_output = BlockExecutionOutput { - state: bundle_state, - result: BlockExecutionResult { - receipts: info.receipts.clone(), - requests: Default::default(), - gas_used: info.cumulative_gas_used, - blob_gas_used: blob_gas_used.unwrap_or_default(), - }, - }; - - let header = Header { - parent_hash: ctx.parent().hash(), - ommers_hash: EMPTY_OMMER_ROOT_HASH, - beneficiary: ctx.evm_factory.evm_env().block_env.beneficiary, - state_root, - transactions_root, - receipts_root, - withdrawals_root, - logs_bloom, - timestamp: ctx.attributes().payload_attributes.timestamp, - mix_hash: ctx.attributes().payload_attributes.prev_randao, - nonce: BEACON_NONCE.into(), - base_fee_per_gas: Some(ctx.base_fee()), - number: ctx.parent().number + 1, - gas_limit: ctx.block_gas_limit(), - difficulty: U256::ZERO, - gas_used: info.cumulative_gas_used, - extra_data, - parent_beacon_block_root: ctx.attributes().payload_attributes.parent_beacon_block_root, - blob_gas_used, - excess_blob_gas, - requests_hash, - }; - - // seal the block - let block = alloy_consensus::Block::::new( - header, - BlockBody { - transactions: info.executed_transactions.clone(), - ommers: vec![], - withdrawals: ctx.withdrawals().cloned(), - }, - ); - - let recovered_block = - RecoveredBlock::new_unhashed(block.clone(), info.executed_senders.clone()); - // create the executed block data - - let executed = BuiltPayloadExecutedBlock { - recovered_block: Arc::new(recovered_block), - execution_output: Arc::new(execution_output), - trie_updates: either::Either::Left( - trie_updates_to_cache.unwrap_or_else(|| Arc::new(TrieUpdates::default())), - ), - hashed_state: either::Either::Left(Arc::new(hashed_state)), - }; - - let seal_start = Instant::now(); - let sealed_block = Arc::new(block.seal_slow()); - let seal_duration = seal_start.elapsed(); - let block_hash = sealed_block.hash(); - - info!( - target: "payload_builder", - id = %ctx.payload_id(), - block_number = ctx.block_number(), - block_hash = %block_hash, - flashblock_index = flashblock_index_for_trace, - target_flashblocks = target_flashblock_count_for_trace, - tx_count = info.executed_transactions.len(), - gas_used = info.cumulative_gas_used, - da_used = info.cumulative_da_bytes_used, - state_root = %state_root, - seal_duration_us = seal_duration.as_micros() as u64, - "Block sealed" - ); - - if ctx.builder_ctx.enable_tx_tracking_debug_logs { - debug!( - target: "tx_trace", - block_number = ctx.block_number(), - flashblock_index = flashblock_index_for_trace, - block_hash = ?block_hash, - seal_duration_us = seal_duration.as_micros() as u64, - build_block_total_time_since_state_root_start_us = state_root_start_time.elapsed().as_micros() as u64, - cumulative_gas = info.cumulative_gas_used, - num_txs = info.executed_transactions.len(), - stage = "block_sealed" - ); - } - - // pick the new transactions from the info field and update the last flashblock index - let (new_transactions, new_receipts) = if let Some(fb_state) = fb_state { - let new_txs = fb_state.slice_new_transactions(&info.executed_transactions); - let new_receipts = fb_state.slice_new_receipts(&info.receipts); - fb_state.set_last_flashblock_tx_index(info.executed_transactions.len()); - (new_txs, new_receipts) - } else { - ( - info.executed_transactions.as_slice(), - info.receipts.as_slice(), - ) - }; - - let new_transactions_encoded: Vec = new_transactions - .iter() - .map(|tx| tx.encoded_2718().into()) - .collect(); - - let receipts_with_hash: BTreeMap = new_transactions - .iter() - .zip(new_receipts.iter()) - .map(|(tx, receipt)| (tx.tx_hash(), convert_receipt(receipt))) - .collect(); - - let metadata = OpFlashblockPayloadMetadata { - receipts: receipts_with_hash, - new_account_balances, - block_number: ctx.parent().number + 1, - }; - - let (_, blob_gas_used) = ctx.blob_fields(info); - - // Prepare the flashblocks message - let fb_payload = OpFlashblockPayload { - payload_id: ctx.payload_id(), - index: 0, - base: Some(OpFlashblockPayloadBase { - parent_beacon_block_root: ctx - .attributes() - .payload_attributes - .parent_beacon_block_root - .ok_or_else(|| { - PayloadBuilderError::Other( - eyre::eyre!("parent beacon block root not found").into(), - ) - })?, - parent_hash: ctx.parent().hash(), - fee_recipient: ctx.attributes().suggested_fee_recipient(), - prev_randao: ctx.attributes().payload_attributes.prev_randao, - block_number: ctx.parent().number + 1, - gas_limit: ctx.block_gas_limit(), - timestamp: ctx.attributes().payload_attributes.timestamp, - extra_data: ctx.extra_data()?, - base_fee_per_gas: U256::from(ctx.base_fee()), - }), - diff: OpFlashblockPayloadDelta { - state_root, - receipts_root, - logs_bloom, - gas_used: info.cumulative_gas_used, - block_hash, - transactions: new_transactions_encoded, - withdrawals: ctx.withdrawals().cloned().unwrap_or_default().to_vec(), - withdrawals_root: withdrawals_root.unwrap_or_default(), - blob_gas_used, - }, - metadata, - }; - // Need to ensure `state.bundle = None`, was done previously with `state.take_bundle()` - state.transition_state = untouched_transition_state; - - Ok(( - OpBuiltPayload::new( - ctx.payload_id(), - sealed_block, - info.total_fees, - Some(executed), - ), - fb_payload, - )) -} - #[cfg(test)] mod tests { use super::*; diff --git a/crates/op-rbuilder/src/builder/payload_handler.rs b/crates/op-rbuilder/src/builder/payload_handler.rs index c2b011c24..5be3bfdd9 100644 --- a/crates/op-rbuilder/src/builder/payload_handler.rs +++ b/crates/op-rbuilder/src/builder/payload_handler.rs @@ -1,20 +1,27 @@ use crate::{ builder::{ - OpPayloadJobCtx, p2p::Message, payload::build_block, receipt::build_receipt, - syncer_ctx::OpPayloadSyncerCtx, + assembly::BlockAssemblyInput, p2p::Message, receipt::build_receipt, + syncer_config::OpPayloadSyncerConfig, }, evm::OpBlockEvmFactory, hardforks::ActiveHardforks, + metrics::OpRBuilderMetrics, primitives::reth::ExecutionInfo, traits::ClientBounds, }; -use alloy_evm::eth::receipt_builder::ReceiptBuilderCtx; +use alloy_consensus::BlockHeader; +use alloy_eips::Encodable2718; +use alloy_evm::{Evm, eth::receipt_builder::ReceiptBuilderCtx}; use alloy_primitives::B64; use eyre::{WrapErr as _, bail}; use op_alloy_rpc_types_engine::OpFlashblockPayload; use op_revm::L1BlockInfo; -use reth::revm::{State, database::StateProviderDatabase}; +use reth::{ + consensus::HeaderValidator, + revm::{State, database::StateProviderDatabase}, +}; use reth_basic_payload_builder::PayloadConfig; +use reth_evm::{ConfigureEvm, execute::BlockBuilder}; use reth_node_builder::Events; use reth_optimism_chainspec::OpChainSpec; use reth_optimism_consensus::OpBeaconConsensus; @@ -22,8 +29,9 @@ use reth_optimism_evm::OpNextBlockEnvAttributes; use reth_optimism_node::{OpEngineTypes, OpPayloadBuilderAttributes}; use reth_optimism_payload_builder::OpBuiltPayload; use reth_payload_builder::EthPayloadBuilderAttributes; -use reth_primitives_traits::SealedHeader; +use reth_primitives_traits::{SealedHeader, SignedTransaction}; use reth_tasks::Runtime; +use revm::{DatabaseCommit, context::result::ResultAndState}; use std::sync::Arc; use tokio::sync::mpsc; use tracing::{error, info, trace, warn}; @@ -44,12 +52,13 @@ pub(crate) struct PayloadHandler { // sends a `Events::BuiltPayload` to the reth payload builder when a new payload is received. payload_events_handle: tokio::sync::broadcast::Sender>, // context required for execution of blocks during syncing - ctx: OpPayloadSyncerCtx, + syncer_config: OpPayloadSyncerConfig, // chain client client: Client, // task executor task_executor: Runtime, - cancel: tokio_util::sync::CancellationToken, + // metrics + metrics: Arc, } impl PayloadHandler @@ -63,10 +72,10 @@ where p2p_rx: mpsc::Receiver, p2p_tx: mpsc::Sender, payload_events_handle: tokio::sync::broadcast::Sender>, - ctx: OpPayloadSyncerCtx, + syncer_config: OpPayloadSyncerConfig, client: Client, task_executor: Runtime, - cancel: tokio_util::sync::CancellationToken, + metrics: Arc, ) -> Self { Self { built_fb_payload_rx, @@ -74,10 +83,10 @@ where p2p_rx, p2p_tx, payload_events_handle, - ctx, + syncer_config, client, task_executor, - cancel, + metrics, } } @@ -88,10 +97,10 @@ where mut p2p_rx, p2p_tx, payload_events_handle, - ctx, + syncer_config, client, task_executor, - cancel, + metrics, } = self; info!(target: "payload_builder", "flashblocks payload handler started"); @@ -116,15 +125,15 @@ where match message { Message::OpBuiltPayload(payload) => { let payload: OpBuiltPayload = payload.into(); - let ctx = ctx.clone(); + let syncer_config = syncer_config.clone(); let client = client.clone(); let payload_events_handle = payload_events_handle.clone(); - let cancel = cancel.clone(); + let metrics = metrics.clone(); // execute the flashblock on a thread where blocking is acceptable, // as it's potentially a heavy operation let handle = task_executor.spawn_blocking(move || { - execute_flashblock(payload, ctx, client, cancel) + execute_flashblock(payload, syncer_config, client, metrics) }); task_executor.spawn_task(async move { match handle.await { @@ -170,17 +179,13 @@ where fn execute_flashblock( payload: OpBuiltPayload, - ctx: OpPayloadSyncerCtx, + syncer_config: OpPayloadSyncerConfig, client: Client, - cancel: tokio_util::sync::CancellationToken, + metrics: Arc, ) -> eyre::Result<(OpBuiltPayload, OpFlashblockPayload)> where Client: ClientBounds, { - use alloy_consensus::BlockHeader as _; - use reth::primitives::SealedHeader; - use reth_evm::{ConfigureEvm as _, execute::BlockBuilder as _}; - let start = tokio::time::Instant::now(); info!( @@ -221,11 +226,11 @@ where extra_data: payload.block().sealed_header().extra_data.clone(), }; - let evm_env = ctx - .evm_config() + let evm_env = syncer_config + .evm_config .next_evm_env(&parent_header, &block_env_attributes) .wrap_err("failed to create next evm env")?; - let evm_factory = OpBlockEvmFactory::new(ctx.evm_config().clone(), evm_env); + let evm_factory = OpBlockEvmFactory::new(syncer_config.evm_config.clone(), evm_env); evm_factory .evm_config() @@ -311,16 +316,11 @@ where }, ); - let job_ctx = ctx.into_op_payload_job_ctx( - payload_config, - evm_factory, - block_env_attributes, - hardforks, - cancel, - ); - execute_transactions( - &job_ctx, + &evm_factory, + &hardforks, + syncer_config.max_gas_per_txn, + syncer_config.max_uncompressed_block_size, &mut info, &mut state, payload.block().body().transactions.clone(), @@ -328,13 +328,20 @@ where ) .wrap_err("failed to execute best transactions")?; - let (built_payload, fb_payload) = build_block(&mut state, &job_ctx, None, &mut info, true) - .wrap_err("failed to build flashblock")?; - - job_ctx - .metrics - .flashblock_sync_duration - .record(start.elapsed()); + let enable_tx_tracking_debug_logs = syncer_config.enable_tx_tracking_debug_logs; + let (built_payload, fb_payload) = + BlockAssemblyInput::try_new(payload_config, &evm_factory, hardforks)? + .assemble( + &mut state, + None, + &mut info, + metrics.clone(), + true, + enable_tx_tracking_debug_logs, + ) + .wrap_err("failed to build flashblock")?; + + metrics.flashblock_sync_duration.record(start.elapsed()); if built_payload.block().hash() != payload.block().hash() { error!( @@ -343,11 +350,11 @@ where got = %built_payload.block().hash(), "flashblock hash mismatch after execution" ); - job_ctx.metrics.invalid_synced_blocks_count.increment(1); + metrics.invalid_synced_blocks_count.increment(1); bail!("flashblock hash mismatch after execution"); } - job_ctx.metrics.block_synced_success.increment(1); + metrics.block_synced_success.increment(1); info!( target: "payload_builder", @@ -360,18 +367,16 @@ where #[allow(clippy::too_many_arguments)] fn execute_transactions( - ctx: &OpPayloadJobCtx, + evm_factory: &OpBlockEvmFactory, + hardforks: &ActiveHardforks, + max_gas_per_txn: Option, + max_uncompressed_block_size: Option, info: &mut ExecutionInfo, state: &mut State, txs: Vec, gas_limit: u64, ) -> eyre::Result<()> { - use alloy_eips::Encodable2718; - use alloy_evm::Evm as _; - use reth_primitives_traits::SignedTransaction; - use revm::{DatabaseCommit as _, context::result::ResultAndState}; - - let mut evm = ctx.evm_factory.evm(&mut *state); + let mut evm = evm_factory.evm(&mut *state); for tx in txs { // Convert to recovered transaction @@ -385,7 +390,7 @@ fn execute_transactions( // 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_nonce = (ctx.hardforks.is_regolith_active() && tx_recovered.is_deposit()) + let depositor_nonce = (hardforks.is_regolith_active() && tx_recovered.is_deposit()) .then(|| { evm.db_mut() .load_cache_account(sender) @@ -399,7 +404,7 @@ fn execute_transactions( .wrap_err("failed to execute transaction")?; let tx_gas_used = result.gas_used(); - if let Some(max_gas_per_txn) = ctx.builder_ctx.max_gas_per_txn + if let Some(max_gas_per_txn) = max_gas_per_txn && tx_gas_used > max_gas_per_txn { return Err(eyre::eyre!( @@ -426,7 +431,7 @@ fn execute_transactions( "total uncompressed bytes overflowed when executing flashblock transactions" ) })?; - if let Some(limit) = ctx.builder_ctx.max_uncompressed_block_size + if let Some(limit) = max_uncompressed_block_size && info.cumulative_uncompressed_bytes > limit { bail!("flashblock exceeded max uncompressed block size when executing transactions"); @@ -441,8 +446,8 @@ fn execute_transactions( }; info.receipts.push(build_receipt( - &ctx.evm_factory, - &ctx.hardforks, + evm_factory, + hardforks, receipt_ctx, depositor_nonce, )); @@ -455,7 +460,7 @@ fn execute_transactions( } // Fetch DA footprint gas scalar for Jovian blocks - let da_footprint_gas_scalar = ctx.hardforks.is_jovian_active().then(|| { + let da_footprint_gas_scalar = hardforks.is_jovian_active().then(|| { L1BlockInfo::fetch_da_footprint_gas_scalar(evm.db_mut()) .expect("DA footprint should always be available from the database post jovian") }); @@ -474,8 +479,6 @@ fn validate_pre_execution( parent_hash: alloy_primitives::B256, chain_spec: Arc, ) -> eyre::Result<()> { - use reth::consensus::HeaderValidator; - let consensus = OpBeaconConsensus::new(chain_spec); let parent_sealed = SealedHeader::new(parent_header.clone(), parent_hash); diff --git a/crates/op-rbuilder/src/builder/service.rs b/crates/op-rbuilder/src/builder/service.rs index f11af9c7c..983b35ebc 100644 --- a/crates/op-rbuilder/src/builder/service.rs +++ b/crates/op-rbuilder/src/builder/service.rs @@ -7,7 +7,7 @@ use crate::{ generator::BlockPayloadJobGenerator, p2p::{AGENT_VERSION, FLASHBLOCKS_STREAM_PROTOCOL, Message}, payload_handler::PayloadHandler, - syncer_ctx::OpPayloadSyncerCtx, + syncer_config::OpPayloadSyncerConfig, wspub::WebSocketPublisher, }, flashtestations::service::bootstrap_flashtestations, @@ -80,7 +80,7 @@ impl FlashblocksServiceBuilder { .with_protocol(FLASHBLOCKS_STREAM_PROTOCOL) .with_known_peers(known_peers) .with_port(flashblocks_config.p2p_port) - .with_cancellation_token(cancel.clone()) + .with_cancellation_token(cancel) .with_max_peer_count(flashblocks_config.p2p_max_peer_count) .try_build::() .wrap_err("failed to build flashblocks p2p node")?; @@ -145,13 +145,9 @@ impl FlashblocksServiceBuilder { let (payload_service, payload_builder_handle) = PayloadBuilderService::new(payload_generator, ctx.provider().canonical_state_stream()); - let syncer_ctx = OpPayloadSyncerCtx::new( - &ctx.provider().clone(), - self.0, - OpEvmConfig::optimism(ctx.chain_spec()), - metrics, - ) - .wrap_err("failed to create flashblocks payload builder context")?; + let syncer_config = + OpPayloadSyncerConfig::new(self.0, OpEvmConfig::optimism(ctx.chain_spec())) + .wrap_err("failed to create flashblocks payload builder context")?; let payload_handler = PayloadHandler::new( built_fb_payload_rx, @@ -159,10 +155,10 @@ impl FlashblocksServiceBuilder { incoming_message_rx, outgoing_message_tx, payload_service.payload_events_handle(), - syncer_ctx, + syncer_config, ctx.provider().clone(), ctx.task_executor().clone(), - cancel, + metrics, ); ctx.task_executor().spawn_critical_task( diff --git a/crates/op-rbuilder/src/builder/syncer_config.rs b/crates/op-rbuilder/src/builder/syncer_config.rs new file mode 100644 index 000000000..1fbc24382 --- /dev/null +++ b/crates/op-rbuilder/src/builder/syncer_config.rs @@ -0,0 +1,28 @@ +use crate::builder::BuilderConfig; +use reth_optimism_evm::OpEvmConfig; + +#[derive(Debug, Clone)] +pub(super) struct OpPayloadSyncerConfig { + /// The type that knows how to perform system calls and configure the evm. + pub(super) evm_config: OpEvmConfig, + /// Max gas that can be used by a transaction. + pub(super) max_gas_per_txn: Option, + /// Maximum cumulative uncompressed (EIP-2718 encoded) block size in bytes. + pub(super) max_uncompressed_block_size: Option, + /// Enable transaction tracking logs + pub(super) enable_tx_tracking_debug_logs: bool, +} + +impl OpPayloadSyncerConfig { + pub(super) fn new( + builder_config: BuilderConfig, + evm_config: OpEvmConfig, + ) -> eyre::Result { + Ok(Self { + evm_config, + max_gas_per_txn: builder_config.max_gas_per_txn, + max_uncompressed_block_size: builder_config.max_uncompressed_block_size, + enable_tx_tracking_debug_logs: builder_config.enable_tx_tracking_debug_logs, + }) + } +} diff --git a/crates/op-rbuilder/src/builder/syncer_ctx.rs b/crates/op-rbuilder/src/builder/syncer_ctx.rs deleted file mode 100644 index c6c74943a..000000000 --- a/crates/op-rbuilder/src/builder/syncer_ctx.rs +++ /dev/null @@ -1,84 +0,0 @@ -use crate::{ - builder::{BuilderConfig, OpPayloadJobCtx, context::OpPayloadBuilderCtx}, - evm::OpBlockEvmFactory, - hardforks::ActiveHardforks, - limiter::{ - AddressLimiter, - args::{ComputeLimiterArgs, GasLimiterArgs}, - }, - metrics::OpRBuilderMetrics, - traits::ClientBounds, -}; -use reth_basic_payload_builder::PayloadConfig; -use reth_optimism_evm::{OpEvmConfig, OpNextBlockEnvAttributes}; -use reth_optimism_payload_builder::{OpPayloadBuilderAttributes, config::OpGasLimitConfig}; -use reth_optimism_primitives::OpTransactionSigned; -use std::sync::Arc; -use tokio_util::sync::CancellationToken; - -#[derive(Debug, Clone)] -pub(super) struct OpPayloadSyncerCtx { - builder_ctx: Arc, -} - -impl OpPayloadSyncerCtx { - pub(super) fn new( - client: &Client, - builder_config: BuilderConfig, - evm_config: OpEvmConfig, - metrics: Arc, - ) -> eyre::Result - where - Client: ClientBounds, - { - let chain_spec = client.chain_spec(); - let builder_ctx = Arc::new(OpPayloadBuilderCtx { - evm_config, - da_config: builder_config.da_config, - gas_limit_config: OpGasLimitConfig::default(), - chain_spec, - metrics, - max_gas_per_txn: builder_config.max_gas_per_txn, - max_uncompressed_block_size: builder_config.max_uncompressed_block_size, - address_limiter: AddressLimiter::new( - GasLimiterArgs::default(), - ComputeLimiterArgs::default(), - ), - backrun_bundle_pool: builder_config.backrun_bundle_pool, - backrun_bundle_args: builder_config.backrun_bundle_args, - exclude_reverts_between_flashblocks: builder_config.exclude_reverts_between_flashblocks, - enable_tx_tracking_debug_logs: builder_config.enable_tx_tracking_debug_logs, - disable_state_root: false, - enable_incremental_state_root: false, - }); - Ok(Self { builder_ctx }) - } - - pub(super) fn evm_config(&self) -> &OpEvmConfig { - &self.builder_ctx.evm_config - } - - pub(super) fn into_op_payload_job_ctx( - self, - payload_config: PayloadConfig>, - evm_factory: OpBlockEvmFactory, - block_env_attributes: OpNextBlockEnvAttributes, - hardforks: ActiveHardforks, - cancel: CancellationToken, - ) -> OpPayloadJobCtx { - let backrun_pool = self - .builder_ctx - .backrun_bundle_pool - .block_pool(payload_config.parent_header.number + 1); - - OpPayloadJobCtx { - builder_ctx: self.builder_ctx, - evm_factory, - config: payload_config, - block_env_attributes, - hardforks, - cancel, - backrun_pool, - } - } -} diff --git a/crates/op-rbuilder/src/hardforks.rs b/crates/op-rbuilder/src/hardforks.rs index 98fe2e0d0..6232d586e 100644 --- a/crates/op-rbuilder/src/hardforks.rs +++ b/crates/op-rbuilder/src/hardforks.rs @@ -1,8 +1,10 @@ use std::sync::Arc; -use reth_chainspec::{EthChainSpec, EthereumHardforks}; +use reth_chainspec::{ + BaseFeeParams, EthChainSpec, EthereumHardfork, EthereumHardforks, ForkCondition, +}; use reth_optimism_chainspec::OpChainSpec; -use reth_optimism_forks::OpHardforks; +use reth_optimism_forks::{OpHardfork, OpHardforks}; /// Binds an [`OpChainSpec`] with a block timestamp so that hardfork activation /// checks become simple zero-argument method calls. @@ -26,38 +28,47 @@ impl ActiveHardforks { self.chain_spec.chain_id() } + pub fn base_fee_params(&self) -> BaseFeeParams { + self.chain_spec.base_fee_params_at_timestamp(self.timestamp) + } + pub fn is_regolith_active(&self) -> bool { - self.chain_spec - .is_regolith_active_at_timestamp(self.timestamp) + self.is_regolith_active_at_timestamp(self.timestamp) } pub fn is_canyon_active(&self) -> bool { - self.chain_spec - .is_canyon_active_at_timestamp(self.timestamp) + self.is_canyon_active_at_timestamp(self.timestamp) } pub fn is_ecotone_active(&self) -> bool { - self.chain_spec - .is_ecotone_active_at_timestamp(self.timestamp) + self.is_ecotone_active_at_timestamp(self.timestamp) } pub fn is_holocene_active(&self) -> bool { - self.chain_spec - .is_holocene_active_at_timestamp(self.timestamp) + self.is_holocene_active_at_timestamp(self.timestamp) } pub fn is_isthmus_active(&self) -> bool { - self.chain_spec - .is_isthmus_active_at_timestamp(self.timestamp) + self.is_isthmus_active_at_timestamp(self.timestamp) } pub fn is_jovian_active(&self) -> bool { - self.chain_spec - .is_jovian_active_at_timestamp(self.timestamp) + self.is_jovian_active_at_timestamp(self.timestamp) } pub fn is_shanghai_active(&self) -> bool { - self.chain_spec - .is_shanghai_active_at_timestamp(self.timestamp) + self.is_shanghai_active_at_timestamp(self.timestamp) + } +} + +impl EthereumHardforks for ActiveHardforks { + fn ethereum_fork_activation(&self, fork: EthereumHardfork) -> ForkCondition { + self.chain_spec.ethereum_fork_activation(fork) + } +} + +impl OpHardforks for ActiveHardforks { + fn op_fork_activation(&self, fork: OpHardfork) -> ForkCondition { + self.chain_spec.op_fork_activation(fork) } }