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,
)