Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: pending block construction #13109

Merged
merged 3 commits into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 61 additions & 84 deletions crates/rpc/rpc-eth-api/src/helpers/pending_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,18 @@ use alloy_eips::{
eip4844::MAX_DATA_GAS_PER_BLOCK, eip7685::EMPTY_REQUESTS_HASH, merge::BEACON_NONCE,
};
use alloy_primitives::{BlockNumber, B256, U256};
use alloy_rpc_types_eth::BlockNumberOrTag;
use alloy_rpc_types_eth::{BlockNumberOrTag, Withdrawals};
use futures::Future;
use reth_chainspec::{EthChainSpec, EthereumHardforks};
use reth_errors::RethError;
use reth_evm::{
state_change::post_block_withdrawals_balance_increments, system_calls::SystemCaller,
ConfigureEvm, ConfigureEvmEnv,
ConfigureEvm, ConfigureEvmEnv, NextBlockEnvAttributes,
};
use reth_execution_types::ExecutionOutcome;
use reth_primitives::{
proofs::calculate_transaction_root, Block, BlockBody, BlockExt, InvalidTransactionError,
Receipt, RecoveredTx, SealedBlockWithSenders, SealedHeader,
Receipt, RecoveredTx, SealedBlockWithSenders,
};
use reth_provider::{
BlockReader, BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, ProviderError,
Expand All @@ -27,7 +28,7 @@ use reth_provider::{
use reth_revm::{
database::StateProviderDatabase,
primitives::{
BlockEnv, CfgEnv, CfgEnvWithHandlerCfg, EVMError, Env, ExecutionResult, InvalidTransaction,
BlockEnv, CfgEnvWithHandlerCfg, EVMError, Env, ExecutionResult, InvalidTransaction,
ResultAndState, SpecId,
},
};
Expand Down Expand Up @@ -69,55 +70,56 @@ pub trait LoadPendingBlock:
///
/// If no pending block is available, this will derive it from the `latest` block
fn pending_block_env_and_cfg(&self) -> Result<PendingBlockEnv, Self::Error> {
let origin: PendingBlockEnvOrigin = if let Some(pending) =
if let Some(block) =
self.provider().pending_block_with_senders().map_err(Self::Error::from_eth_err)?
{
PendingBlockEnvOrigin::ActualPending(pending)
} else {
// no pending block from the CL yet, so we use the latest block and modify the env
// values that we can
let latest = self
if let Some(receipts) = self
.provider()
.latest_header()
.receipts_by_block(block.hash().into())
.map_err(Self::Error::from_eth_err)?
.ok_or(EthApiError::HeaderNotFound(BlockNumberOrTag::Latest.into()))?;

let (mut latest_header, block_hash) = latest.split();
// child block
latest_header.number += 1;
// assumed child block is in the next slot: 12s
latest_header.timestamp += 12;
// base fee of the child block
let chain_spec = self.provider().chain_spec();

latest_header.base_fee_per_gas = latest_header.next_block_base_fee(
chain_spec.base_fee_params_at_timestamp(latest_header.timestamp()),
);

// update excess blob gas consumed above target
latest_header.excess_blob_gas = latest_header.next_block_excess_blob_gas();

// we're reusing the same block hash because we need this to lookup the block's state
let latest = SealedHeader::new(latest_header, block_hash);

PendingBlockEnvOrigin::DerivedFromLatest(latest)
};
{
// Note: for the PENDING block we assume it is past the known merge block and
// thus this will not fail when looking up the total
// difficulty value for the blockenv.
let (cfg, block_env) = self
.provider()
.env_with_header(block.header(), self.evm_config().clone())
.map_err(Self::Error::from_eth_err)?;

return Ok(PendingBlockEnv::new(
cfg,
block_env,
PendingBlockEnvOrigin::ActualPending(block, receipts),
));
}
}

let mut cfg = CfgEnvWithHandlerCfg::new_with_spec_id(CfgEnv::default(), SpecId::LATEST);

let mut block_env = BlockEnv::default();
// Note: for the PENDING block we assume it is past the known merge block and thus this will
// not fail when looking up the total difficulty value for the blockenv.
self.provider()
.fill_env_with_header(
&mut cfg,
&mut block_env,
origin.header(),
self.evm_config().clone(),
// no pending block from the CL yet, so we use the latest block and modify the env
// values that we can
let latest = self
.provider()
.latest_header()
.map_err(Self::Error::from_eth_err)?
.ok_or(EthApiError::HeaderNotFound(BlockNumberOrTag::Latest.into()))?;

let (cfg, block_env) = self
.evm_config()
.next_cfg_and_block_env(
&latest,
NextBlockEnvAttributes {
timestamp: latest.timestamp() + 12,
suggested_fee_recipient: latest.beneficiary(),
prev_randao: B256::random(),
},
Comment on lines +107 to +113
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great

)
.map_err(RethError::other)
.map_err(Self::Error::from_eth_err)?;

Ok(PendingBlockEnv::new(cfg, block_env, origin))
Ok(PendingBlockEnv::new(
cfg,
block_env,
PendingBlockEnvOrigin::DerivedFromLatest(latest.hash()),
))
}

/// Returns the locally built pending block
Expand All @@ -138,18 +140,12 @@ pub trait LoadPendingBlock:
{
async move {
let pending = self.pending_block_env_and_cfg()?;
if pending.origin.is_actual_pending() {
if let Some(block) = pending.origin.clone().into_actual_pending() {
// we have the real pending block, so we should also have its receipts
if let Some(receipts) = self
.provider()
.receipts_by_block(block.hash().into())
.map_err(Self::Error::from_eth_err)?
{
return Ok(Some((block, receipts)))
}
let parent_hash = match pending.origin {
PendingBlockEnvOrigin::ActualPending(block, receipts) => {
return Ok(Some((block, receipts)));
}
}
PendingBlockEnvOrigin::DerivedFromLatest(parent_hash) => parent_hash,
};

// we couldn't find the real pending block, so we need to build it ourselves
let mut lock = self.pending_block().lock().await;
Expand All @@ -160,7 +156,7 @@ pub trait LoadPendingBlock:
if let Some(pending_block) = lock.as_ref() {
// this is guaranteed to be the `latest` header
if pending.block_env.number.to::<u64>() == pending_block.block.number &&
pending.origin.header().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())));
Expand All @@ -171,7 +167,7 @@ pub trait LoadPendingBlock:
let (sealed_block, receipts) = match self
.spawn_blocking_io(move |this| {
// we rebuild the block
this.build_block(pending)
this.build_block(pending.cfg, pending.block_env, parent_hash)
})
.await
{
Expand Down Expand Up @@ -230,14 +226,13 @@ pub trait LoadPendingBlock:
/// block contract call using the parent beacon block root received from the CL.
fn build_block(
&self,
env: PendingBlockEnv,
cfg: CfgEnvWithHandlerCfg,
block_env: BlockEnv,
parent_hash: B256,
) -> Result<(SealedBlockWithSenders, Vec<Receipt>), Self::Error>
where
EthApiError: From<ProviderError>,
{
let PendingBlockEnv { cfg, block_env, origin } = env;

let parent_hash = origin.build_target_hash();
let state_provider = self
.provider()
.history_by_block_hash(parent_hash)
Expand All @@ -259,34 +254,16 @@ pub trait LoadPendingBlock:
block_env.get_blob_gasprice().map(|gasprice| gasprice as u64),
));

let (withdrawals, withdrawals_root) = match origin {
PendingBlockEnvOrigin::ActualPending(ref block) => {
(block.body.withdrawals.clone(), block.withdrawals_root)
}
PendingBlockEnvOrigin::DerivedFromLatest(_) => (None, None),
};
let withdrawals: Option<Withdrawals> = None;
let withdrawals_root = None;

let chain_spec = self.provider().chain_spec();

let mut system_caller = SystemCaller::new(self.evm_config().clone(), chain_spec.clone());

let parent_beacon_block_root = if origin.is_actual_pending() {
// apply eip-4788 pre block contract call if we got the block from the CL with the real
// parent beacon block root
system_caller
.pre_block_beacon_root_contract_call(
&mut db,
&cfg,
&block_env,
origin.header().parent_beacon_block_root,
)
.map_err(|err| EthApiError::Internal(err.into()))?;
origin.header().parent_beacon_block_root
} else {
None
};
let parent_beacon_block_root = None;
system_caller
.pre_block_blockhashes_contract_call(&mut db, &cfg, &block_env, origin.header().hash())
.pre_block_blockhashes_contract_call(&mut db, &cfg, &block_env, parent_hash)
.map_err(|err| EthApiError::Internal(err.into()))?;

let mut receipts = Vec::new();
Expand Down
30 changes: 11 additions & 19 deletions crates/rpc/rpc-eth-types/src/pending_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use alloy_consensus::BlockHeader;
use alloy_eips::{BlockId, BlockNumberOrTag};
use alloy_primitives::B256;
use derive_more::Constructor;
use reth_primitives::{Receipt, SealedBlockWithSenders, SealedHeader};
use reth_primitives::{Receipt, SealedBlockWithSenders};
use reth_primitives_traits::Block;
use revm_primitives::{BlockEnv, CfgEnvWithHandlerCfg};

Expand All @@ -25,28 +25,28 @@ pub struct PendingBlockEnv {

/// The origin for a configured [`PendingBlockEnv`]
#[derive(Clone, Debug)]
pub enum PendingBlockEnvOrigin<B: Block = reth_primitives::Block> {
pub enum PendingBlockEnvOrigin<B: Block = reth_primitives::Block, R = Receipt> {
/// The pending block as received from the CL.
ActualPending(SealedBlockWithSenders<B>),
ActualPending(SealedBlockWithSenders<B>, Vec<R>),
/// The _modified_ header of the latest block.
///
/// This derives the pending state based on the latest header by modifying:
/// - the timestamp
/// - the block number
/// - fees
DerivedFromLatest(SealedHeader<B::Header>),
DerivedFromLatest(B256),
}

impl<B: Block> PendingBlockEnvOrigin<B> {
impl<B: Block, R> PendingBlockEnvOrigin<B, R> {
/// Returns true if the origin is the actual pending block as received from the CL.
pub const fn is_actual_pending(&self) -> bool {
matches!(self, Self::ActualPending(_))
matches!(self, Self::ActualPending(_, _))
}

/// Consumes the type and returns the actual pending block.
pub fn into_actual_pending(self) -> Option<SealedBlockWithSenders<B>> {
match self {
Self::ActualPending(block) => Some(block),
Self::ActualPending(block, _) => Some(block),
_ => None,
}
}
Expand All @@ -57,8 +57,8 @@ impl<B: Block> PendingBlockEnvOrigin<B> {
/// identify the block by its hash (latest block).
pub fn state_block_id(&self) -> BlockId {
match self {
Self::ActualPending(_) => BlockNumberOrTag::Pending.into(),
Self::DerivedFromLatest(header) => BlockId::Hash(header.hash().into()),
Self::ActualPending(_, _) => BlockNumberOrTag::Pending.into(),
Self::DerivedFromLatest(hash) => BlockId::Hash((*hash).into()),
}
}

Expand All @@ -69,16 +69,8 @@ impl<B: Block> PendingBlockEnvOrigin<B> {
/// header.
pub fn build_target_hash(&self) -> B256 {
match self {
Self::ActualPending(block) => block.header().parent_hash(),
Self::DerivedFromLatest(header) => header.hash(),
}
}

/// Returns the header this pending block is based on.
pub fn header(&self) -> &SealedHeader<B::Header> {
match self {
Self::ActualPending(block) => &block.header,
Self::DerivedFromLatest(header) => header,
Self::ActualPending(block, _) => block.header().parent_hash(),
Self::DerivedFromLatest(hash) => *hash,
}
}
}
Expand Down
Loading