mirror of
https://github.com/paradigmxyz/reth.git
synced 2026-04-30 03:01:58 -04:00
feat: pending block support in BlockExecutionStrategyFactory (#14730)
This commit is contained in:
@@ -6,7 +6,7 @@ use crate::{
|
||||
helpers::estimate::EstimateCall, FromEvmError, FullEthApiTypes, RpcBlock, RpcNodeCore,
|
||||
};
|
||||
use alloy_consensus::BlockHeader;
|
||||
use alloy_eips::{eip1559::calc_next_block_base_fee, eip2930::AccessListResult};
|
||||
use alloy_eips::eip2930::AccessListResult;
|
||||
use alloy_primitives::{Bytes, B256, U256};
|
||||
use alloy_rpc_types_eth::{
|
||||
simulate::{SimBlock, SimulatePayload, SimulatedBlock},
|
||||
@@ -15,20 +15,23 @@ use alloy_rpc_types_eth::{
|
||||
BlockId, Bundle, EthCallResponse, StateContext, TransactionInfo,
|
||||
};
|
||||
use futures::Future;
|
||||
use reth_chainspec::EthChainSpec;
|
||||
use reth_errors::ProviderError;
|
||||
use reth_errors::{ProviderError, RethError};
|
||||
use reth_evm::{
|
||||
ConfigureEvm, ConfigureEvmEnv, Evm, EvmEnv, HaltReasonFor, InspectorFor, SpecFor,
|
||||
TransactionEnv,
|
||||
execute::BlockExecutionStrategyFactory, ConfigureEvm, ConfigureEvmEnv, Evm, EvmEnv,
|
||||
HaltReasonFor, InspectorFor, SpecFor, TransactionEnv,
|
||||
};
|
||||
use reth_node_api::BlockBody;
|
||||
use reth_primitives::Recovered;
|
||||
use reth_primitives::{Recovered, SealedHeader};
|
||||
use reth_primitives_traits::SignedTransaction;
|
||||
use reth_provider::{BlockIdReader, ChainSpecProvider, ProviderHeader};
|
||||
use reth_revm::{database::StateProviderDatabase, db::CacheDB, DatabaseRef};
|
||||
use reth_provider::{BlockIdReader, ProviderHeader};
|
||||
use reth_revm::{
|
||||
database::StateProviderDatabase,
|
||||
db::{CacheDB, State},
|
||||
DatabaseRef,
|
||||
};
|
||||
use reth_rpc_eth_types::{
|
||||
cache::db::{StateCacheDbRefMutWrapper, StateProviderTraitObjWrapper},
|
||||
error::{api::FromEvmHalt, ensure_success},
|
||||
error::{api::FromEvmHalt, ensure_success, FromEthApiError},
|
||||
revm_utils::{apply_block_overrides, apply_state_overrides, caller_gas_allowance},
|
||||
simulate::{self, EthSimulateError},
|
||||
EthApiError, RevertError, RpcInvalidTransactionError, StateCacheDb,
|
||||
@@ -74,6 +77,8 @@ pub trait EthCall: EstimateCall + Call + LoadPendingBlock + LoadBlock + FullEthA
|
||||
return Err(EthApiError::InvalidParams("too many blocks.".to_string()).into())
|
||||
}
|
||||
|
||||
let block = block.unwrap_or_default();
|
||||
|
||||
let SimulatePayload {
|
||||
block_state_calls,
|
||||
trace_transfers,
|
||||
@@ -85,50 +90,32 @@ pub trait EthCall: EstimateCall + Call + LoadPendingBlock + LoadBlock + FullEthA
|
||||
return Err(EthApiError::InvalidParams(String::from("calls are empty.")).into())
|
||||
}
|
||||
|
||||
// Build cfg and block env, we'll reuse those.
|
||||
let (mut evm_env, block) = self.evm_env_at(block.unwrap_or_default()).await?;
|
||||
|
||||
// Gas cap for entire operation
|
||||
let total_gas_limit = self.call_gas_limit();
|
||||
|
||||
let base_block =
|
||||
self.block_with_senders(block).await?.ok_or(EthApiError::HeaderNotFound(block))?;
|
||||
let mut parent_hash = base_block.hash();
|
||||
|
||||
// Only enforce base fee if validation is enabled
|
||||
evm_env.cfg_env.disable_base_fee = !validation;
|
||||
// Always disable EIP-3607
|
||||
evm_env.cfg_env.disable_eip3607 = true;
|
||||
let mut parent = base_block.sealed_header().clone();
|
||||
|
||||
let this = self.clone();
|
||||
self.spawn_with_state_at_block(block, move |state| {
|
||||
let mut db = CacheDB::new(StateProviderDatabase::new(state));
|
||||
let mut db =
|
||||
State::builder().with_database(StateProviderDatabase::new(state)).build();
|
||||
let mut gas_used = 0;
|
||||
let mut blocks: Vec<SimulatedBlock<RpcBlock<Self::NetworkTypes>>> =
|
||||
Vec::with_capacity(block_state_calls.len());
|
||||
let mut block_state_calls = block_state_calls.into_iter().peekable();
|
||||
let chain_spec = RpcNodeCore::provider(&this).chain_spec();
|
||||
while let Some(block) = block_state_calls.next() {
|
||||
// Increase number and timestamp for every new block
|
||||
evm_env.block_env.number += 1;
|
||||
evm_env.block_env.timestamp += 1;
|
||||
for block in block_state_calls {
|
||||
let mut evm_env = this
|
||||
.evm_config()
|
||||
.next_evm_env(&parent, this.next_env_attributes(&parent)?)
|
||||
.map_err(RethError::other)
|
||||
.map_err(Self::Error::from_eth_err)?;
|
||||
|
||||
if validation {
|
||||
let base_fee_params =
|
||||
chain_spec.base_fee_params_at_timestamp(evm_env.block_env.timestamp);
|
||||
let base_fee = if let Some(latest) = blocks.last() {
|
||||
let header = &latest.inner.header;
|
||||
calc_next_block_base_fee(
|
||||
header.gas_used(),
|
||||
header.gas_limit(),
|
||||
header.base_fee_per_gas().unwrap_or_default(),
|
||||
base_fee_params,
|
||||
)
|
||||
} else {
|
||||
base_block.next_block_base_fee(base_fee_params).unwrap_or_default()
|
||||
};
|
||||
evm_env.block_env.basefee = base_fee;
|
||||
} else {
|
||||
// Always disable EIP-3607
|
||||
evm_env.cfg_env.disable_eip3607 = true;
|
||||
|
||||
if !validation {
|
||||
evm_env.cfg_env.disable_base_fee = !validation;
|
||||
evm_env.block_env.basefee = 0;
|
||||
}
|
||||
|
||||
@@ -141,6 +128,9 @@ pub trait EthCall: EstimateCall + Call + LoadPendingBlock + LoadBlock + FullEthA
|
||||
apply_state_overrides(state_overrides, &mut db)?;
|
||||
}
|
||||
|
||||
let block_env = evm_env.block_env.clone();
|
||||
let chain_id = evm_env.cfg_env.chain_id;
|
||||
|
||||
if (total_gas_limit - gas_used) < evm_env.block_env.gas_limit {
|
||||
return Err(
|
||||
EthApiError::Other(Box::new(EthSimulateError::GasLimitReached)).into()
|
||||
@@ -152,7 +142,7 @@ pub trait EthCall: EstimateCall + Call + LoadPendingBlock + LoadBlock + FullEthA
|
||||
let txs_without_gas_limit =
|
||||
calls.iter().filter(|tx| tx.gas.is_none()).count();
|
||||
|
||||
if total_specified_gas > evm_env.block_env.gas_limit {
|
||||
if total_specified_gas > block_env.gas_limit {
|
||||
return Err(EthApiError::Other(Box::new(
|
||||
EthSimulateError::BlockGasLimitExceeded,
|
||||
))
|
||||
@@ -160,78 +150,68 @@ pub trait EthCall: EstimateCall + Call + LoadPendingBlock + LoadBlock + FullEthA
|
||||
}
|
||||
|
||||
if txs_without_gas_limit > 0 {
|
||||
(evm_env.block_env.gas_limit - total_specified_gas) /
|
||||
(block_env.gas_limit - total_specified_gas) /
|
||||
txs_without_gas_limit as u64
|
||||
} else {
|
||||
0
|
||||
}
|
||||
};
|
||||
|
||||
let mut calls = calls.into_iter().peekable();
|
||||
let mut transactions = Vec::with_capacity(calls.len());
|
||||
let mut senders = Vec::with_capacity(calls.len());
|
||||
let mut results = Vec::with_capacity(calls.len());
|
||||
|
||||
while let Some(call) = calls.next() {
|
||||
// Resolve transaction, populate missing fields and enforce calls
|
||||
// correctness.
|
||||
let tx = simulate::resolve_transaction(
|
||||
call,
|
||||
let ctx = this
|
||||
.evm_config()
|
||||
.context_for_next_block(&parent, this.next_env_attributes(&parent)?);
|
||||
let (transactions, result, results) = if trace_transfers {
|
||||
// prepare inspector to capture transfer inside the evm so they are recorded
|
||||
// and included in logs
|
||||
let inspector = TransferInspector::new(false).with_logs(true);
|
||||
let evm = this
|
||||
.evm_config()
|
||||
.evm_with_env_and_inspector(&mut db, evm_env, inspector);
|
||||
let strategy = this.evm_config().create_strategy(evm, ctx);
|
||||
simulate::execute_transactions(
|
||||
strategy,
|
||||
calls,
|
||||
validation,
|
||||
default_gas_limit,
|
||||
evm_env.cfg_env.chain_id,
|
||||
&mut db,
|
||||
chain_id,
|
||||
this.tx_resp_builder(),
|
||||
)?;
|
||||
)?
|
||||
} else {
|
||||
let evm = this.evm_config().evm_with_env(&mut db, evm_env);
|
||||
let strategy = this.evm_config().create_strategy(evm, ctx);
|
||||
simulate::execute_transactions(
|
||||
strategy,
|
||||
calls,
|
||||
validation,
|
||||
default_gas_limit,
|
||||
chain_id,
|
||||
this.tx_resp_builder(),
|
||||
)?
|
||||
};
|
||||
|
||||
let tx_env = this.evm_config().tx_env(&tx);
|
||||
let senders = transactions.iter().map(|tx| tx.signer()).collect();
|
||||
|
||||
let (res, (_, tx_env)) = {
|
||||
if trace_transfers {
|
||||
this.transact_with_inspector(
|
||||
&mut db,
|
||||
evm_env.clone(),
|
||||
tx_env,
|
||||
TransferInspector::new(false)
|
||||
// capture transfer inside the evm so they are recorded and
|
||||
// included in the result
|
||||
.with_logs(true),
|
||||
)?
|
||||
} else {
|
||||
this.transact(&mut db, evm_env.clone(), tx_env.clone())?
|
||||
}
|
||||
};
|
||||
|
||||
if calls.peek().is_some() || block_state_calls.peek().is_some() {
|
||||
// need to apply the state changes of this call before executing the
|
||||
// next call
|
||||
db.commit(res.state);
|
||||
}
|
||||
|
||||
transactions.push(tx);
|
||||
senders.push(tx_env.caller());
|
||||
results.push(res.result);
|
||||
}
|
||||
|
||||
let (block, _) = this.assemble_block_and_receipts(
|
||||
&evm_env.block_env,
|
||||
parent_hash,
|
||||
let block = this.assemble_block(
|
||||
&block_env,
|
||||
&result,
|
||||
&parent,
|
||||
// state root calculation is skipped for performance reasons
|
||||
B256::ZERO,
|
||||
transactions,
|
||||
results.clone(),
|
||||
);
|
||||
|
||||
let block: SimulatedBlock<RpcBlock<Self::NetworkTypes>> =
|
||||
simulate::build_simulated_block(
|
||||
senders,
|
||||
results,
|
||||
return_full_transactions,
|
||||
this.tx_resp_builder(),
|
||||
block,
|
||||
)?;
|
||||
let block = simulate::build_simulated_block(
|
||||
senders,
|
||||
results,
|
||||
return_full_transactions,
|
||||
this.tx_resp_builder(),
|
||||
block,
|
||||
)?;
|
||||
|
||||
parent_hash = block.inner.header.hash;
|
||||
parent = SealedHeader::new(
|
||||
block.inner.header.inner.clone(),
|
||||
block.inner.header.hash,
|
||||
);
|
||||
gas_used += block.inner.header.gas_used();
|
||||
|
||||
blocks.push(block);
|
||||
|
||||
@@ -9,17 +9,18 @@ use alloy_primitives::B256;
|
||||
use alloy_rpc_types_eth::BlockNumberOrTag;
|
||||
use futures::Future;
|
||||
use reth_chainspec::{EthChainSpec, EthereumHardforks};
|
||||
use reth_errors::RethError;
|
||||
use reth_errors::{BlockExecutionError, BlockValidationError, RethError};
|
||||
use reth_evm::{
|
||||
state_change::post_block_withdrawals_balance_increments, system_calls::SystemCaller,
|
||||
ConfigureEvm, ConfigureEvmEnv, Evm, EvmEnv, EvmError, HaltReasonFor, InvalidTxError,
|
||||
NextBlockEnvAttributes,
|
||||
execute::{BlockExecutionStrategy, BlockExecutionStrategyFactory},
|
||||
ConfigureEvmEnv, Evm, NextBlockEnvAttributes,
|
||||
};
|
||||
use reth_primitives::{InvalidTransactionError, RecoveredBlock};
|
||||
use reth_node_api::NodePrimitives;
|
||||
use reth_primitives::{InvalidTransactionError, RecoveredBlock, SealedHeader};
|
||||
use reth_primitives_traits::Receipt;
|
||||
use reth_provider::{
|
||||
BlockReader, BlockReaderIdExt, ChainSpecProvider, ProviderBlock, ProviderError, ProviderHeader,
|
||||
ProviderReceipt, ProviderTx, ReceiptProvider, StateProviderFactory,
|
||||
BlockExecutionResult, BlockReader, BlockReaderIdExt, ChainSpecProvider, ProviderBlock,
|
||||
ProviderError, ProviderHeader, ProviderReceipt, ProviderTx, ReceiptProvider,
|
||||
StateProviderFactory,
|
||||
};
|
||||
use reth_revm::{
|
||||
database::StateProviderDatabase,
|
||||
@@ -30,13 +31,7 @@ use reth_transaction_pool::{
|
||||
error::InvalidPoolTransactionError, BestTransactionsAttributes, PoolTransaction,
|
||||
TransactionPool,
|
||||
};
|
||||
use revm::{
|
||||
context::BlockEnv,
|
||||
context_interface::{
|
||||
result::{ExecutionResult, ResultAndState},
|
||||
Block,
|
||||
},
|
||||
};
|
||||
use revm::{context::BlockEnv, context_interface::Block};
|
||||
use std::time::{Duration, Instant};
|
||||
use tokio::sync::Mutex;
|
||||
use tracing::debug;
|
||||
@@ -55,9 +50,12 @@ pub trait LoadPendingBlock:
|
||||
+ ChainSpecProvider<ChainSpec: EthChainSpec + EthereumHardforks>
|
||||
+ StateProviderFactory,
|
||||
Pool: TransactionPool<Transaction: PoolTransaction<Consensus = ProviderTx<Self::Provider>>>,
|
||||
Evm: ConfigureEvm<
|
||||
Header = ProviderHeader<Self::Provider>,
|
||||
Transaction = ProviderTx<Self::Provider>,
|
||||
Evm: BlockExecutionStrategyFactory<
|
||||
Primitives: NodePrimitives<
|
||||
BlockHeader = ProviderHeader<Self::Provider>,
|
||||
SignedTx = ProviderTx<Self::Provider>,
|
||||
Receipt = ProviderReceipt<Self::Provider>,
|
||||
>,
|
||||
>,
|
||||
>
|
||||
{
|
||||
@@ -69,7 +67,7 @@ pub trait LoadPendingBlock:
|
||||
&self,
|
||||
) -> &Mutex<Option<PendingBlock<ProviderBlock<Self::Provider>, ProviderReceipt<Self::Provider>>>>;
|
||||
|
||||
/// Configures the [`EvmEnv`] for the pending block
|
||||
/// Configures the [`PendingBlockEnv`] for the pending block
|
||||
///
|
||||
/// If no pending block is available, this will derive it from the `latest` block
|
||||
#[expect(clippy::type_complexity)]
|
||||
@@ -113,19 +111,26 @@ pub trait LoadPendingBlock:
|
||||
|
||||
let evm_env = self
|
||||
.evm_config()
|
||||
.next_evm_env(
|
||||
&latest,
|
||||
NextBlockEnvAttributes {
|
||||
timestamp: latest.timestamp().saturating_add(12),
|
||||
suggested_fee_recipient: latest.beneficiary(),
|
||||
prev_randao: B256::random(),
|
||||
gas_limit: latest.gas_limit(),
|
||||
},
|
||||
)
|
||||
.next_evm_env(&latest, self.next_env_attributes(&latest)?)
|
||||
.map_err(RethError::other)
|
||||
.map_err(Self::Error::from_eth_err)?;
|
||||
|
||||
Ok(PendingBlockEnv::new(evm_env, PendingBlockEnvOrigin::DerivedFromLatest(latest.hash())))
|
||||
Ok(PendingBlockEnv::new(evm_env, PendingBlockEnvOrigin::DerivedFromLatest(latest)))
|
||||
}
|
||||
|
||||
/// Returns [`NextBlockEnvAttributes`] for building a local pending block.
|
||||
fn next_env_attributes(
|
||||
&self,
|
||||
parent: &SealedHeader<ProviderHeader<Self::Provider>>,
|
||||
) -> Result<NextBlockEnvAttributes<'_>, Self::Error> {
|
||||
Ok(NextBlockEnvAttributes {
|
||||
timestamp: parent.timestamp().saturating_add(12),
|
||||
suggested_fee_recipient: parent.beneficiary(),
|
||||
prev_randao: B256::random(),
|
||||
gas_limit: parent.gas_limit(),
|
||||
parent_beacon_block_root: parent.parent_beacon_block_root(),
|
||||
withdrawals: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the locally built pending block
|
||||
@@ -146,11 +151,11 @@ pub trait LoadPendingBlock:
|
||||
{
|
||||
async move {
|
||||
let pending = self.pending_block_env_and_cfg()?;
|
||||
let parent_hash = match pending.origin {
|
||||
let parent = match pending.origin {
|
||||
PendingBlockEnvOrigin::ActualPending(block, receipts) => {
|
||||
return Ok(Some((block, receipts)));
|
||||
}
|
||||
PendingBlockEnvOrigin::DerivedFromLatest(parent_hash) => parent_hash,
|
||||
PendingBlockEnvOrigin::DerivedFromLatest(parent) => parent,
|
||||
};
|
||||
|
||||
// we couldn't find the real pending block, so we need to build it ourselves
|
||||
@@ -162,7 +167,7 @@ pub trait LoadPendingBlock:
|
||||
if let Some(pending_block) = lock.as_ref() {
|
||||
// this is guaranteed to be the `latest` header
|
||||
if pending.evm_env.block_env.number == pending_block.block.number() &&
|
||||
parent_hash == pending_block.block.parent_hash() &&
|
||||
parent.hash() == pending_block.block.parent_hash() &&
|
||||
now <= pending_block.expires_at
|
||||
{
|
||||
return Ok(Some((pending_block.block.clone(), pending_block.receipts.clone())));
|
||||
@@ -173,7 +178,7 @@ pub trait LoadPendingBlock:
|
||||
let (sealed_block, receipts) = match self
|
||||
.spawn_blocking_io(move |this| {
|
||||
// we rebuild the block
|
||||
this.build_block(pending.evm_env, parent_hash)
|
||||
this.build_block(&parent)
|
||||
})
|
||||
.await
|
||||
{
|
||||
@@ -195,47 +200,16 @@ pub trait LoadPendingBlock:
|
||||
}
|
||||
}
|
||||
|
||||
/// Assembles a receipt for a transaction, based on its [`ExecutionResult`].
|
||||
fn assemble_receipt(
|
||||
&self,
|
||||
tx: &ProviderTx<Self::Provider>,
|
||||
result: ExecutionResult<HaltReasonFor<Self::Evm>>,
|
||||
cumulative_gas_used: u64,
|
||||
) -> ProviderReceipt<Self::Provider>;
|
||||
|
||||
/// Assembles a pending block.
|
||||
fn assemble_block(
|
||||
&self,
|
||||
block_env: &BlockEnv,
|
||||
parent_hash: B256,
|
||||
result: &BlockExecutionResult<ProviderReceipt<Self::Provider>>,
|
||||
parent: &SealedHeader<ProviderHeader<Self::Provider>>,
|
||||
state_root: B256,
|
||||
transactions: Vec<Recovered<ProviderTx<Self::Provider>>>,
|
||||
receipts: &[ProviderReceipt<Self::Provider>],
|
||||
) -> ProviderBlock<Self::Provider>;
|
||||
|
||||
/// Helper to invoke both [`Self::assemble_block`] and [`Self::assemble_receipt`].
|
||||
fn assemble_block_and_receipts(
|
||||
&self,
|
||||
block_env: &BlockEnv,
|
||||
parent_hash: B256,
|
||||
state_root: B256,
|
||||
transactions: Vec<Recovered<ProviderTx<Self::Provider>>>,
|
||||
results: Vec<ExecutionResult<HaltReasonFor<Self::Evm>>>,
|
||||
) -> (ProviderBlock<Self::Provider>, Vec<ProviderReceipt<Self::Provider>>) {
|
||||
let mut cumulative_gas_used = 0;
|
||||
let mut receipts = Vec::with_capacity(results.len());
|
||||
|
||||
for (tx, outcome) in transactions.iter().zip(results) {
|
||||
cumulative_gas_used += outcome.gas_used();
|
||||
receipts.push(self.assemble_receipt(tx, outcome, cumulative_gas_used));
|
||||
}
|
||||
|
||||
let block =
|
||||
self.assemble_block(block_env, parent_hash, state_root, transactions, &receipts);
|
||||
|
||||
(block, receipts)
|
||||
}
|
||||
|
||||
/// Builds a pending block using the configured provider and pool.
|
||||
///
|
||||
/// If the origin is the actual pending block, the block is built with withdrawals.
|
||||
@@ -245,8 +219,7 @@ pub trait LoadPendingBlock:
|
||||
#[expect(clippy::type_complexity)]
|
||||
fn build_block(
|
||||
&self,
|
||||
evm_env: EvmEnv<<Self::Evm as ConfigureEvmEnv>::Spec>,
|
||||
parent_hash: B256,
|
||||
parent: &SealedHeader<ProviderHeader<Self::Provider>>,
|
||||
) -> Result<
|
||||
(RecoveredBlock<ProviderBlock<Self::Provider>>, Vec<ProviderReceipt<Self::Provider>>),
|
||||
Self::Error,
|
||||
@@ -256,34 +229,32 @@ pub trait LoadPendingBlock:
|
||||
{
|
||||
let state_provider = self
|
||||
.provider()
|
||||
.history_by_block_hash(parent_hash)
|
||||
.history_by_block_hash(parent.hash())
|
||||
.map_err(Self::Error::from_eth_err)?;
|
||||
let state = StateProviderDatabase::new(state_provider);
|
||||
let mut db = State::builder().with_database(state).with_bundle_update().build();
|
||||
|
||||
let mut strategy = self
|
||||
.evm_config()
|
||||
.strategy_for_next_block(&mut db, parent, self.next_env_attributes(parent)?)
|
||||
.map_err(RethError::other)
|
||||
.map_err(Self::Error::from_eth_err)?;
|
||||
|
||||
strategy.apply_pre_execution_changes().map_err(Self::Error::from_eth_err)?;
|
||||
|
||||
let block_env = strategy.evm_mut().block().clone();
|
||||
|
||||
let mut cumulative_gas_used = 0;
|
||||
let mut sum_blob_gas_used = 0;
|
||||
let block_gas_limit: u64 = evm_env.block_env.gas_limit;
|
||||
let base_fee = evm_env.block_env.basefee;
|
||||
let block_gas_limit: u64 = block_env.gas_limit;
|
||||
|
||||
let mut executed_txs = Vec::new();
|
||||
let mut best_txs =
|
||||
self.pool().best_transactions_with_attributes(BestTransactionsAttributes::new(
|
||||
base_fee,
|
||||
evm_env.block_env.blob_gasprice().map(|gasprice| gasprice as u64),
|
||||
block_env.basefee,
|
||||
block_env.blob_gasprice().map(|gasprice| gasprice as u64),
|
||||
));
|
||||
|
||||
let chain_spec = self.provider().chain_spec();
|
||||
|
||||
let mut system_caller = SystemCaller::new(chain_spec.clone());
|
||||
let mut evm = self.evm_config().evm_with_env(&mut db, evm_env.clone());
|
||||
|
||||
system_caller
|
||||
.apply_blockhashes_contract_call(parent_hash, &mut evm)
|
||||
.map_err(|err| EthApiError::Internal(err.into()))?;
|
||||
|
||||
let mut results = Vec::new();
|
||||
|
||||
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 {
|
||||
@@ -335,29 +306,28 @@ pub trait LoadPendingBlock:
|
||||
}
|
||||
}
|
||||
|
||||
let tx_env = self.evm_config().tx_env(&tx);
|
||||
|
||||
let ResultAndState { result, state: _ } = match evm.transact_commit(tx_env) {
|
||||
Ok(res) => res,
|
||||
Err(err) => {
|
||||
if let Some(err) = err.as_invalid_tx_err() {
|
||||
if err.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
|
||||
let gas_used = match strategy.execute_transaction(tx.as_recovered_ref()) {
|
||||
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,
|
||||
),
|
||||
);
|
||||
}
|
||||
// this is an error that we should treat as fatal for this attempt
|
||||
return Err(Self::Error::from_evm_err(err));
|
||||
continue
|
||||
}
|
||||
// this is an error that we should treat as fatal for this attempt
|
||||
Err(err) => return Err(Self::Error::from_eth_err(err)),
|
||||
};
|
||||
|
||||
// add to the total blob gas used if the transaction successfully executed
|
||||
@@ -370,28 +340,14 @@ pub trait LoadPendingBlock:
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
// append transaction to the list of executed transactions
|
||||
executed_txs.push(tx);
|
||||
results.push(result);
|
||||
}
|
||||
|
||||
// executes the withdrawals and commits them to the Database and BundleState.
|
||||
let balance_increments = post_block_withdrawals_balance_increments(
|
||||
chain_spec.as_ref(),
|
||||
evm_env.block_env.timestamp,
|
||||
&[],
|
||||
);
|
||||
|
||||
// release db
|
||||
drop(evm);
|
||||
|
||||
// increment account balances for withdrawals
|
||||
db.increment_balances(balance_increments).map_err(Self::Error::from_eth_err)?;
|
||||
let result = strategy.apply_post_execution_changes().map_err(Self::Error::from_eth_err)?;
|
||||
|
||||
// merge all transitions into bundle state.
|
||||
db.merge_transitions(BundleRetention::PlainState);
|
||||
@@ -404,14 +360,8 @@ pub trait LoadPendingBlock:
|
||||
|
||||
let senders = executed_txs.iter().map(|tx| tx.signer()).collect();
|
||||
|
||||
let (block, receipts) = self.assemble_block_and_receipts(
|
||||
&evm_env.block_env,
|
||||
parent_hash,
|
||||
state_root,
|
||||
executed_txs,
|
||||
results,
|
||||
);
|
||||
let block = self.assemble_block(&block_env, &result, parent, state_root, executed_txs);
|
||||
|
||||
Ok((RecoveredBlock::new_unhashed(block, senders), receipts))
|
||||
Ok((RecoveredBlock::new_unhashed(block, senders), result.receipts))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use alloy_rpc_types_eth::{error::EthRpcErrorCode, request::TransactionInputError
|
||||
use alloy_sol_types::{ContractError, RevertReason};
|
||||
pub use api::{AsEthApiError, FromEthApiError, FromEvmError, IntoEthApiError};
|
||||
use core::time::Duration;
|
||||
use reth_errors::RethError;
|
||||
use reth_errors::{BlockExecutionError, RethError};
|
||||
use reth_primitives_traits::transaction::signed::RecoveryError;
|
||||
use reth_rpc_server_types::result::{
|
||||
block_id_to_str, internal_rpc_err, invalid_params_rpc_err, rpc_err, rpc_error_with_code,
|
||||
@@ -255,6 +255,12 @@ impl From<RethError> for EthApiError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BlockExecutionError> for EthApiError {
|
||||
fn from(error: BlockExecutionError) -> Self {
|
||||
Self::Internal(error.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<reth_errors::ProviderError> for EthApiError {
|
||||
fn from(error: reth_errors::ProviderError) -> Self {
|
||||
use reth_errors::ProviderError;
|
||||
|
||||
@@ -9,7 +9,7 @@ use alloy_eips::{BlockId, BlockNumberOrTag};
|
||||
use alloy_primitives::B256;
|
||||
use derive_more::Constructor;
|
||||
use reth_evm::EvmEnv;
|
||||
use reth_primitives::{Receipt, RecoveredBlock};
|
||||
use reth_primitives::{Receipt, RecoveredBlock, SealedHeader};
|
||||
use reth_primitives_traits::Block;
|
||||
|
||||
/// Configured [`EvmEnv`] for a pending block.
|
||||
@@ -32,7 +32,7 @@ pub enum PendingBlockEnvOrigin<B: Block = reth_primitives::Block, R = Receipt> {
|
||||
/// - the timestamp
|
||||
/// - the block number
|
||||
/// - fees
|
||||
DerivedFromLatest(B256),
|
||||
DerivedFromLatest(SealedHeader<B::Header>),
|
||||
}
|
||||
|
||||
impl<B: Block, R> PendingBlockEnvOrigin<B, R> {
|
||||
@@ -56,7 +56,7 @@ impl<B: Block, R> PendingBlockEnvOrigin<B, R> {
|
||||
pub fn state_block_id(&self) -> BlockId {
|
||||
match self {
|
||||
Self::ActualPending(_, _) => BlockNumberOrTag::Pending.into(),
|
||||
Self::DerivedFromLatest(hash) => BlockId::Hash((*hash).into()),
|
||||
Self::DerivedFromLatest(latest) => BlockId::Hash(latest.hash().into()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ impl<B: Block, R> PendingBlockEnvOrigin<B, R> {
|
||||
pub fn build_target_hash(&self) -> B256 {
|
||||
match self {
|
||||
Self::ActualPending(block, _) => block.header().parent_hash(),
|
||||
Self::DerivedFromLatest(hash) => *hash,
|
||||
Self::DerivedFromLatest(latest) => latest.hash(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,9 @@ use alloy_rpc_types_eth::{
|
||||
Block, BlockTransactionsKind, Header,
|
||||
};
|
||||
use jsonrpsee_types::ErrorObject;
|
||||
use reth_primitives::{Recovered, RecoveredBlock};
|
||||
use reth_evm::{execute::BlockExecutionStrategy, Evm};
|
||||
use reth_execution_types::BlockExecutionResult;
|
||||
use reth_primitives::{NodePrimitives, Recovered, RecoveredBlock};
|
||||
use reth_primitives_traits::{block::BlockTx, BlockBody as _, SignedTransaction};
|
||||
use reth_rpc_server_types::result::rpc_err;
|
||||
use reth_rpc_types_compat::{block::from_block, TransactionCompat};
|
||||
@@ -48,6 +50,60 @@ impl ToRpcError for EthSimulateError {
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts all [`TransactionRequest`]s into [`Recovered`] transactions and applies them to the
|
||||
/// given [`BlockExecutionStrategy`].
|
||||
///
|
||||
/// Returns all executed transactions and the result of the execution.
|
||||
#[expect(clippy::type_complexity)]
|
||||
pub fn execute_transactions<N, S, T>(
|
||||
mut strategy: S,
|
||||
calls: Vec<TransactionRequest>,
|
||||
validation: bool,
|
||||
default_gas_limit: u64,
|
||||
chain_id: u64,
|
||||
tx_resp_builder: &T,
|
||||
) -> Result<
|
||||
(
|
||||
Vec<Recovered<N::SignedTx>>,
|
||||
BlockExecutionResult<N::Receipt>,
|
||||
Vec<ExecutionResult<<S::Evm as Evm>::HaltReason>>,
|
||||
),
|
||||
EthApiError,
|
||||
>
|
||||
where
|
||||
N: NodePrimitives,
|
||||
S: BlockExecutionStrategy<Primitives = N>,
|
||||
EthApiError: From<S::Error> + From<<<S::Evm as Evm>::DB as Database>::Error>,
|
||||
S::Evm: Evm<DB: Database<Error: Into<EthApiError>>>,
|
||||
T: TransactionCompat<N::SignedTx>,
|
||||
{
|
||||
strategy.apply_pre_execution_changes()?;
|
||||
|
||||
let mut transactions = Vec::with_capacity(calls.len());
|
||||
let mut results = Vec::with_capacity(calls.len());
|
||||
for call in calls {
|
||||
// Resolve transaction, populate missing fields and enforce calls
|
||||
// correctness.
|
||||
let tx = resolve_transaction(
|
||||
call,
|
||||
validation,
|
||||
default_gas_limit,
|
||||
chain_id,
|
||||
strategy.evm_mut().db_mut(),
|
||||
tx_resp_builder,
|
||||
)?;
|
||||
|
||||
strategy.execute_transaction_with_result_closure(tx.as_recovered_ref(), |result| {
|
||||
results.push(result.clone())
|
||||
})?;
|
||||
transactions.push(tx);
|
||||
}
|
||||
|
||||
let result = strategy.apply_post_execution_changes()?;
|
||||
|
||||
Ok((transactions, result, results))
|
||||
}
|
||||
|
||||
/// Goes over the list of [`TransactionRequest`]s and populates missing fields trying to resolve
|
||||
/// them into primitive transactions.
|
||||
///
|
||||
|
||||
@@ -7,12 +7,13 @@ use alloy_consensus::{
|
||||
use alloy_eips::{eip7685::EMPTY_REQUESTS_HASH, merge::BEACON_NONCE};
|
||||
use alloy_primitives::U256;
|
||||
use reth_chainspec::{EthChainSpec, EthereumHardforks};
|
||||
use reth_evm::{ConfigureEvm, HaltReasonFor};
|
||||
use reth_primitives::{logs_bloom, BlockBody, Receipt};
|
||||
use reth_evm::execute::BlockExecutionStrategyFactory;
|
||||
use reth_node_api::NodePrimitives;
|
||||
use reth_primitives::{logs_bloom, BlockBody, Receipt, SealedHeader};
|
||||
use reth_primitives_traits::proofs::calculate_transaction_root;
|
||||
use reth_provider::{
|
||||
BlockReader, BlockReaderIdExt, ChainSpecProvider, ProviderBlock, ProviderReceipt, ProviderTx,
|
||||
StateProviderFactory,
|
||||
BlockExecutionResult, BlockReader, BlockReaderIdExt, ChainSpecProvider, ProviderBlock,
|
||||
ProviderHeader, ProviderReceipt, ProviderTx, StateProviderFactory,
|
||||
};
|
||||
use reth_rpc_eth_api::{
|
||||
helpers::{LoadPendingBlock, SpawnBlocking},
|
||||
@@ -21,10 +22,7 @@ use reth_rpc_eth_api::{
|
||||
};
|
||||
use reth_rpc_eth_types::PendingBlock;
|
||||
use reth_transaction_pool::{PoolTransaction, TransactionPool};
|
||||
use revm::{
|
||||
context::BlockEnv,
|
||||
context_interface::{result::ExecutionResult, Block},
|
||||
};
|
||||
use revm::{context::BlockEnv, context_interface::Block};
|
||||
use revm_primitives::B256;
|
||||
|
||||
use crate::EthApi;
|
||||
@@ -46,7 +44,13 @@ where
|
||||
Pool: TransactionPool<
|
||||
Transaction: PoolTransaction<Consensus = ProviderTx<Self::Provider>>,
|
||||
>,
|
||||
Evm: ConfigureEvm<Header = Header, Transaction = ProviderTx<Self::Provider>>,
|
||||
Evm: BlockExecutionStrategyFactory<
|
||||
Primitives: NodePrimitives<
|
||||
BlockHeader = Header,
|
||||
SignedTx = ProviderTx<Self::Provider>,
|
||||
Receipt = ProviderReceipt<Self::Provider>,
|
||||
>,
|
||||
>,
|
||||
>,
|
||||
Provider: BlockReader<Block = reth_primitives::Block, Receipt = reth_primitives::Receipt>,
|
||||
{
|
||||
@@ -62,17 +66,17 @@ where
|
||||
fn assemble_block(
|
||||
&self,
|
||||
block_env: &BlockEnv,
|
||||
parent_hash: revm_primitives::B256,
|
||||
result: &BlockExecutionResult<ProviderReceipt<Self::Provider>>,
|
||||
parent: &SealedHeader<ProviderHeader<Self::Provider>>,
|
||||
state_root: revm_primitives::B256,
|
||||
transactions: Vec<Recovered<ProviderTx<Self::Provider>>>,
|
||||
receipts: &[ProviderReceipt<Self::Provider>],
|
||||
) -> reth_provider::ProviderBlock<Self::Provider> {
|
||||
let chain_spec = self.provider().chain_spec();
|
||||
|
||||
let transactions_root = calculate_transaction_root(&transactions);
|
||||
let receipts_root = Receipt::calculate_receipt_root_no_memo(receipts);
|
||||
let receipts_root = Receipt::calculate_receipt_root_no_memo(&result.receipts);
|
||||
|
||||
let logs_bloom = logs_bloom(receipts.iter().flat_map(|r| &r.logs));
|
||||
let logs_bloom = logs_bloom(result.receipts.iter().flat_map(|r| &r.logs));
|
||||
|
||||
let timestamp = block_env.timestamp;
|
||||
let is_shanghai = chain_spec.is_shanghai_active_at_timestamp(timestamp);
|
||||
@@ -80,7 +84,7 @@ where
|
||||
let is_prague = chain_spec.is_prague_active_at_timestamp(timestamp);
|
||||
|
||||
let header = Header {
|
||||
parent_hash,
|
||||
parent_hash: parent.hash(),
|
||||
ommers_hash: EMPTY_OMMER_ROOT_HASH,
|
||||
beneficiary: block_env.beneficiary,
|
||||
state_root,
|
||||
@@ -95,7 +99,7 @@ where
|
||||
number: block_env.number,
|
||||
gas_limit: block_env.gas_limit,
|
||||
difficulty: U256::ZERO,
|
||||
gas_used: receipts.last().map(|r| r.cumulative_gas_used).unwrap_or_default(),
|
||||
gas_used: result.gas_used,
|
||||
blob_gas_used: is_cancun.then(|| {
|
||||
transactions.iter().map(|tx| tx.blob_gas_used().unwrap_or_default()).sum::<u64>()
|
||||
}),
|
||||
@@ -115,18 +119,4 @@ where
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn assemble_receipt(
|
||||
&self,
|
||||
tx: &ProviderTx<Self::Provider>,
|
||||
result: ExecutionResult<HaltReasonFor<Self::Evm>>,
|
||||
cumulative_gas_used: u64,
|
||||
) -> reth_provider::ProviderReceipt<Self::Provider> {
|
||||
Receipt {
|
||||
tx_type: tx.tx_type(),
|
||||
success: result.is_success(),
|
||||
cumulative_gas_used,
|
||||
logs: result.into_logs().into_iter().collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user