diff --git a/examples/custom-platform.rs b/examples/custom-platform.rs index 5fab523..bee6b8c 100644 --- a/examples/custom-platform.rs +++ b/examples/custom-platform.rs @@ -19,7 +19,6 @@ use { ethereum::primitives::SignedTransaction, optimism::primitives::OpTransactionSigned, primitives::Recovered, - providers::StateProvider, revm::db::BundleState, }, serde::{Deserialize, Serialize}, @@ -57,12 +56,12 @@ impl Platform for CustomPlatform { fn build_payload

( payload: Checkpoint

, - provider: &dyn StateProvider, + provider_factory: types::ProviderFactory

, ) -> Result, PayloadBuilderError> where P: traits::PlatformExecCtxBounds, { - Optimism::build_payload::

(payload, provider) + Optimism::build_payload::

(payload, provider_factory) } } diff --git a/src/payload/block.rs b/src/payload/block.rs index 9a8c8be..77873d4 100644 --- a/src/payload/block.rs +++ b/src/payload/block.rs @@ -3,9 +3,10 @@ use { prelude::*, reth::{ api::ConfigureEvm, + errors::ProviderError, evm::{block::BlockExecutionError, execute::BlockBuilder}, primitives::SealedHeader, - providers::{StateProvider, StateProviderBox}, + providers::{StateProvider, StateProviderBox, StateProviderFactory}, revm::{State, database::StateProviderDatabase}, }, }, @@ -20,6 +21,9 @@ pub enum Error { #[error("Block execution error: {0}")] BlockExecution(#[from] BlockExecutionError), + + #[error("Provider error: {0}")] + Provider(#[from] ProviderError), } /// This type represents the beginning of the payload building process. It @@ -67,7 +71,7 @@ impl BlockContext

{ pub fn new( parent: SealedHeader>, attribs: types::PayloadBuilderAttributes

, - base_state: StateProviderBox, + provider_factory: types::ProviderFactory

, chainspec: Arc>, cached: Option, ) -> Result> { @@ -83,11 +87,15 @@ impl BlockContext

{ .next_evm_env(&parent, &block_env) .map_err(Error::EvmEnv)?; + let state_provider = provider_factory + .state_by_block_hash(parent.hash()) + .map_err(Error::Provider)?; + let execution_cached = cached.unwrap_or_default(); - let provider = - CachedStateProvider::new_with_caches(base_state, execution_cached); + let state_provider = + CachedStateProvider::new_with_caches(state_provider, execution_cached); let mut base_state = State::builder() - .with_database(StateProviderDatabase(provider)) + .with_database(StateProviderDatabase::new(state_provider)) .with_bundle_update() .build(); @@ -106,6 +114,7 @@ impl BlockContext

{ base_state, evm_config, chainspec, + provider_factory, }), }) } @@ -132,6 +141,12 @@ impl BlockContext

{ self.inner.base_state.database.as_ref() } + /// Returns the state provider factory used to create state providers for the + /// block being built. + pub fn provider_factory(&self) -> types::ProviderFactory

{ + self.inner.provider_factory.clone() + } + /// Returns the EVM configuration used to create EVM instances for executing /// transactions in the payload under construction. pub fn evm_config(&self) -> &P::EvmConfig { @@ -223,6 +238,10 @@ struct BlockContextInner { /// are used to configure the EVM environment and the next block environment /// for the block that is being built. chainspec: Arc>, + + /// The state provider factory used to create state providers for the block + /// being built. + provider_factory: types::ProviderFactory

, } impl core::fmt::Debug for BlockContext

{ diff --git a/src/payload/checkpoint.rs b/src/payload/checkpoint.rs index e73278b..48ac8b2 100644 --- a/src/payload/checkpoint.rs +++ b/src/payload/checkpoint.rs @@ -232,7 +232,7 @@ impl Checkpoint

{ pub fn build_payload( &self, ) -> Result, PayloadBuilderError> { - P::build_payload(self.clone(), self.block().base_state()) + P::build_payload(self.clone(), self.block().provider_factory()) } } @@ -751,7 +751,7 @@ mod tests { #[test] fn test_build_payload() { let block = BlockContext::::mocked(); - let provider = block.base_state(); + let provider = block.provider_factory(); let root = block.start(); let txs = test_txs::(0, 0, 10); diff --git a/src/pipelines/mod.rs b/src/pipelines/mod.rs index 0e18c2b..b7f1b2d 100644 --- a/src/pipelines/mod.rs +++ b/src/pipelines/mod.rs @@ -362,24 +362,35 @@ pub mod traits { { } - pub trait ProviderBounds: + /// Bounds for the provider factory required to build payloads. + pub trait ProviderFactoryBounds: StateProviderFactory + ChainSpecProvider> - + HeaderProvider

> - + Clone + Send + Sync - + 'static { } - impl ProviderBounds

for T where + impl ProviderFactoryBounds

for T where T: StateProviderFactory + ChainSpecProvider> - + HeaderProvider

> - + Clone + Send + Sync + { + } + + pub trait ProviderBounds: + ProviderFactoryBounds

+ + HeaderProvider

> + + Clone + + 'static + { + } + + impl ProviderBounds

for T where + T: ProviderFactoryBounds

+ + HeaderProvider

> + + Clone + 'static { } diff --git a/src/pipelines/service.rs b/src/pipelines/service.rs index 7230ef5..4cb2337 100644 --- a/src/pipelines/service.rs +++ b/src/pipelines/service.rs @@ -205,14 +205,14 @@ where })?; let hash = header.hash(); - let base_state = self.service.provider().state_by_block_hash(hash)?; + let factory: Arc = Arc::new(self.service.provider().clone()); // This is the beginning of the state manipulation API usage from within // the pipelines API. let block_ctx = BlockContext::new( header, attribs, - base_state, + factory, self.service.chain_spec().clone(), self.maybe_pre_cached(hash), ) diff --git a/src/platform/ethereum/mod.rs b/src/platform/ethereum/mod.rs index 82462b6..9be5df7 100644 --- a/src/platform/ethereum/mod.rs +++ b/src/platform/ethereum/mod.rs @@ -1,55 +1,18 @@ use { super::*, crate::{ - alloy::{ - consensus::BlockHeader, - evm::revm::database::State, - primitives::U256, - }, + alloy::consensus::BlockHeader, reth::{ - chainspec::EthChainSpec, - errors::ConsensusError, - ethereum::{ - EthPrimitives, - TransactionSigned, - chainspec::EthereumHardforks, - consensus::validation::MAX_RLP_BLOCK_SIZE, - evm::{EthEvmConfig, revm::database::StateProviderDatabase}, - node::EthereumNode, - primitives::transaction::error::InvalidTransactionError, - }, - evm::{ - ConfigureEvm, - Evm, - NextBlockEnvAttributes, - block::{BlockExecutionError, BlockValidationError}, - execute::{BlockBuilder, BlockBuilderOutcome}, - }, - payload::{ - BlobSidecars, - EthBuiltPayload, - EthPayloadBuilderAttributes, - PayloadBuilderAttributes, - builder::{ - BuildArguments, - BuildOutcome, - EthereumBuilderConfig, - PayloadConfig, - is_better_payload, - }, - }, - revm::{ - cached::CachedReads, - cancelled::CancelOnDrop, - context::Block, - primitives::alloy_primitives::private::alloy_rlp::Encodable, - }, - rpc::types::TransactionTrait, - transaction_pool::{ - error::{Eip4844PoolTransactionError, InvalidPoolTransactionError}, - noop::NoopTransactionPool, - *, + ethereum::{evm::EthEvmConfig, node::EthereumNode}, + evm::NextBlockEnvAttributes, + payload::builder::{ + BuildArguments, + EthereumBuilderConfig, + PayloadConfig, + default_ethereum_payload, }, + revm::{cached::CachedReads, cancelled::CancelOnDrop}, + transaction_pool::{noop::NoopTransactionPool, *}, }, }, limits::EthereumDefaultLimits, @@ -98,13 +61,12 @@ impl Platform for Ethereum { fn build_payload

( payload: Checkpoint

, - provider: &dyn StateProvider, + provider_factory: types::ProviderFactory

, ) -> Result, PayloadBuilderError> where P: traits::PlatformExecBounds, { let evm_config = payload.block().evm_config().clone(); - let chain_spec = payload.block().chainspec(); let payload_config = PayloadConfig { parent_header: Arc::new(payload.block().parent().clone()), attributes: payload.block().attributes().clone(), @@ -126,10 +88,9 @@ impl Platform for Ethereum { default_ethereum_payload( evm_config, - chain_spec, - provider, + provider_factory, NoopTransactionPool::default(), - &builder_config, + builder_config, build_args, |_| { transactions @@ -148,285 +109,3 @@ impl Platform for Ethereum { impl PlatformWithRpcTypes for Ethereum { type RpcTypes = alloy::network::Ethereum; } - -/// Ethereum payload builder code from [`default_ethereum_payload`] adapted to -/// use the given `StateProvider` and chainspec -/// -/// Constructs an Ethereum transaction payload using the best transactions -/// -/// Given build arguments including latest state provider, best transactions, -/// and configuration, this function creates a transaction payload. -/// Returns a result indicating success with the payload or an error in case of -/// failure. -/// -/// # Panics -/// see [`default_ethereum_payload`] -/// -/// [`default_ethereum_payload`]: reth_ethereum_payload_builder::default_ethereum_payload -#[allow(clippy::too_many_lines, clippy::cast_possible_truncation)] -#[inline] -pub fn default_ethereum_payload( - evm_config: EvmConfig, - chain_spec: &Arc>, - state_provider: &dyn StateProvider, - pool: Pool, - builder_config: &EthereumBuilderConfig, - args: BuildArguments, - best_txs: F, -) -> Result, PayloadBuilderError> -where - EvmConfig: ConfigureEvm< - Primitives = EthPrimitives, - NextBlockEnvCtx = NextBlockEnvAttributes, - >, - Pool: TransactionPool< - Transaction: PoolTransaction, - >, - F: FnOnce(BestTransactionsAttributes) -> BestTransactionsFor, -{ - let BuildArguments { - mut cached_reads, - config, - cancel, - best_payload, - } = args; - let PayloadConfig { - parent_header, - attributes, - } = config; - - let state = StateProviderDatabase::new(&state_provider); - let mut db = State::builder() - .with_database(cached_reads.as_db_mut(state)) - .with_bundle_update() - .build(); - - let mut builder = evm_config - .builder_for_next_block(&mut db, &parent_header, NextBlockEnvAttributes { - timestamp: attributes.timestamp(), - suggested_fee_recipient: attributes.suggested_fee_recipient(), - prev_randao: attributes.prev_randao(), - gas_limit: builder_config.gas_limit(parent_header.gas_limit), - parent_beacon_block_root: attributes.parent_beacon_block_root(), - withdrawals: Some(attributes.withdrawals().clone()), - }) - .map_err(PayloadBuilderError::other)?; - - let mut cumulative_gas_used = 0; - let block_gas_limit: u64 = builder.evm_mut().block().gas_limit(); - let base_fee = builder.evm_mut().block().basefee(); - - let mut best_txs = best_txs(BestTransactionsAttributes::new( - base_fee, - builder - .evm_mut() - .block() - .blob_gasprice() - .map(|gasprice| gasprice as u64), - )); - let mut total_fees = U256::ZERO; - - builder - .apply_pre_execution_changes() - .map_err(|err| PayloadBuilderError::Internal(err.into()))?; - - // initialize empty blob sidecars at first. If cancun is active then this will - // be populated by blob sidecars if any. - let mut blob_sidecars = BlobSidecars::Empty; - - let mut block_blob_count = 0; - let mut block_transactions_rlp_length = 0; - - let blob_params = chain_spec.blob_params_at_timestamp(attributes.timestamp); - let max_blob_count = blob_params - .as_ref() - .map(|params| params.max_blob_count) - .unwrap_or_default(); - - let is_osaka = chain_spec.is_osaka_active_at_timestamp(attributes.timestamp); - - 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, - InvalidPoolTransactionError::ExceedsGasLimit( - pool_tx.gas_limit(), - block_gas_limit, - ), - ); - continue; - } - - // check if the job was cancelled, if so we can exit early - if cancel.is_cancelled() { - return Ok(BuildOutcome::Cancelled); - } - - // convert tx to a signed transaction - let tx = pool_tx.to_consensus(); - - let estimated_block_size_with_tx = block_transactions_rlp_length - + tx.inner().length() - + attributes.withdrawals().length() - + 1024; // 1Kb of overhead for the block header - - if is_osaka && estimated_block_size_with_tx > MAX_RLP_BLOCK_SIZE { - best_txs.mark_invalid( - &pool_tx, - InvalidPoolTransactionError::OversizedData { - size: estimated_block_size_with_tx, - limit: MAX_RLP_BLOCK_SIZE, - }, - ); - continue; - } - - // There's only limited amount of blob space available per block, so we need - // to check if the EIP-4844 can still fit in the block - let mut blob_tx_sidecar = None; - if let Some(blob_tx) = tx.as_eip4844() { - let tx_blob_count = blob_tx.tx().blob_versioned_hashes.len() as u64; - - if block_blob_count + tx_blob_count > max_blob_count { - // we can't fit this _blob_ transaction into the block, so we mark it as - // invalid, which removes its dependent transactions from - // the iterator. This is similar to the gas limit condition - // for regular transactions above. - best_txs.mark_invalid( - &pool_tx, - InvalidPoolTransactionError::Eip4844( - Eip4844PoolTransactionError::TooManyEip4844Blobs { - have: block_blob_count + tx_blob_count, - permitted: max_blob_count, - }, - ), - ); - continue; - } - - let blob_sidecar_result = 'sidecar: { - let Some(sidecar) = pool - .get_blob(*tx.hash()) - .map_err(PayloadBuilderError::other)? - else { - break 'sidecar Err( - Eip4844PoolTransactionError::MissingEip4844BlobSidecar, - ); - }; - - if is_osaka { - if sidecar.is_eip7594() { - Ok(sidecar) - } else { - Err(Eip4844PoolTransactionError::UnexpectedEip4844SidecarAfterOsaka) - } - } else if sidecar.is_eip4844() { - Ok(sidecar) - } else { - Err(Eip4844PoolTransactionError::UnexpectedEip7594SidecarBeforeOsaka) - } - }; - - blob_tx_sidecar = match blob_sidecar_result { - Ok(sidecar) => Some(sidecar), - Err(error) => { - best_txs.mark_invalid( - &pool_tx, - InvalidPoolTransactionError::Eip4844(error), - ); - continue; - } - }; - } - - let gas_used = match builder.execute_transaction(tx.clone()) { - Ok(gas_used) => gas_used, - Err(BlockExecutionError::Validation( - BlockValidationError::InvalidTx { error, .. }, - )) => { - if error.is_nonce_too_low() { - // if the nonce is too low, we can skip this transaction - } else { - // if the transaction is invalid, we can skip it and all of its - // descendants - best_txs.mark_invalid( - &pool_tx, - InvalidPoolTransactionError::Consensus( - InvalidTransactionError::TxTypeNotSupported, - ), - ); - } - continue; - } - // this is an error that we should treat as fatal for this attempt - Err(err) => return Err(PayloadBuilderError::evm(err)), - }; - - // add to the total blob gas used if the transaction successfully executed - if let Some(blob_tx) = tx.as_eip4844() { - block_blob_count += blob_tx.tx().blob_versioned_hashes.len() as u64; - - // if we've reached the max blob count, we can skip blob txs entirely - if block_blob_count == max_blob_count { - best_txs.skip_blobs(); - } - } - - block_transactions_rlp_length += tx.inner().length(); - - // update and add to total fees - let miner_fee = tx - .effective_tip_per_gas(base_fee) - .expect("fee is always valid; execution succeeded"); - total_fees += U256::from(miner_fee) * U256::from(gas_used); - cumulative_gas_used += gas_used; - - // Add blob tx sidecar to the payload. - if let Some(sidecar) = blob_tx_sidecar { - blob_sidecars.push_sidecar_variant(sidecar.as_ref().clone()); - } - } - - // check if we have a better block - if !is_better_payload(best_payload.as_ref(), total_fees) { - // Release db - drop(builder); - // can skip building the block - return Ok(BuildOutcome::Aborted { - fees: total_fees, - cached_reads, - }); - } - - let BlockBuilderOutcome { - execution_result, - block, - .. - } = builder.finish(state_provider)?; - - let requests = chain_spec - .is_prague_active_at_timestamp(attributes.timestamp) - .then_some(execution_result.requests); - - let sealed_block = Arc::new(block.sealed_block().clone()); - - if is_osaka && sealed_block.rlp_length() > MAX_RLP_BLOCK_SIZE { - return Err(PayloadBuilderError::other(ConsensusError::BlockTooLarge { - rlp_length: sealed_block.rlp_length(), - max_rlp_length: MAX_RLP_BLOCK_SIZE, - })); - } - - let payload = EthBuiltPayload::new(attributes.id, sealed_block, total_fees, requests) - // add blob sidecars from the executed txs - .with_sidecars(blob_sidecars); - - Ok(BuildOutcome::Better { - payload, - cached_reads, - }) -} diff --git a/src/platform/mod.rs b/src/platform/mod.rs index cbef2b2..9a72c48 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -12,7 +12,7 @@ use { network::Network as AlloyNetwork, signers::Signature, }, - reth::{ethereum::primitives::SignedTransaction, providers::StateProvider}, + reth::ethereum::primitives::SignedTransaction, serde::{Serialize, de::DeserializeOwned}, std::sync::Arc, }; @@ -119,7 +119,7 @@ pub trait Platform: /// client as a response to the `ForkchoiceUpdated` request. fn build_payload

( payload: Checkpoint

, - provider: &dyn StateProvider, + provider_factory: types::ProviderFactory

, ) -> Result, PayloadBuilderError> where P: traits::PlatformExecCtxBounds; diff --git a/src/platform/optimism/mod.rs b/src/platform/optimism/mod.rs index 60993c7..93c33b8 100644 --- a/src/platform/optimism/mod.rs +++ b/src/platform/optimism/mod.rs @@ -19,7 +19,6 @@ use { }, payload::{builder::*, util::PayloadTransactionsFixed}, primitives::Recovered, - providers::StateProvider, revm::{cancelled::CancelOnDrop, database::StateProviderDatabase}, }, serde::{Deserialize, Serialize}, @@ -90,7 +89,7 @@ impl Platform for Optimism { fn build_payload

( payload: Checkpoint

, - provider: &dyn StateProvider, + provider_factory: types::ProviderFactory

, ) -> Result, PayloadBuilderError> where P: traits::PlatformExecCtxBounds, @@ -122,9 +121,15 @@ impl Platform for Optimism { builder_config: OpBuilderConfig::default(), }; + let state_provider = + provider_factory.history_by_block_hash(block.parent().hash())?; + // Invoke the builder implementation from reth-optimism-node. - let build_outcome = - op_builder.build(StateProviderDatabase(&provider), &provider, context)?; + let build_outcome = op_builder.build( + StateProviderDatabase(&state_provider), + &state_provider, + context, + )?; // extract the built payload from the build outcome. let built_payload = match build_outcome { diff --git a/src/platform/types.rs b/src/platform/types.rs index b3222b2..d6aa812 100644 --- a/src/platform/types.rs +++ b/src/platform/types.rs @@ -136,3 +136,6 @@ pub type TxEnvelope = pub type UnsignedTx = as AlloyNetwork>::UnsignedTx; + +/// Used to get state providers during payload building. +pub type ProviderFactory

= Arc>; diff --git a/src/test_utils/exts/mock.rs b/src/test_utils/exts/mock.rs index 19de981..565b209 100644 --- a/src/test_utils/exts/mock.rs +++ b/src/test_utils/exts/mock.rs @@ -133,7 +133,7 @@ where fn mocked() -> BlockContext

{ let chainspec = P::dev_chainspec(); let provider_factory = GenesisProviderFactory::

::new(chainspec.clone()); - let base_state = provider_factory.state_provider(); + let provider_factory = Arc::new(provider_factory); let parent = SealedHeader::new( chainspec.genesis_header().clone(), @@ -148,7 +148,7 @@ where BlockContext::

::new( parent, payload_attributes, - base_state, + provider_factory, chainspec.clone(), None, )