support EIP-4844 transaction when building pending block (#4688)

Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
This commit is contained in:
Alessandro Mazza
2023-09-23 00:00:21 +02:00
committed by GitHub
parent b6e452b2cf
commit 3018054772
2 changed files with 138 additions and 21 deletions

View File

@ -1,7 +1,7 @@
use reth_consensus_common::calc;
use reth_interfaces::executor::{BlockExecutionError, BlockValidationError};
use reth_primitives::{
constants::SYSTEM_ADDRESS, Address, ChainSpec, Hardfork, Header, Withdrawal, H256, U256,
constants::SYSTEM_ADDRESS, Address, ChainSpec, Header, Withdrawal, H256, U256,
};
use reth_revm_primitives::{env::fill_tx_env_with_beacon_root_contract_call, Database};
use revm::{primitives::ResultAndState, DatabaseCommit, EVM};
@ -109,8 +109,8 @@ where
Ok(())
}
/// Returns a map of addresses to their balance increments if shanghai is active at the given
/// timestamp.
/// Returns a map of addresses to their balance increments if the Shanghai hardfork is active at the
/// given timestamp.
#[inline]
pub fn post_block_withdrawals_balance_increments(
chain_spec: &ChainSpec,
@ -137,7 +137,7 @@ pub fn insert_post_block_withdrawals_balance_increments(
balance_increments: &mut HashMap<Address, u128>,
) {
// Process withdrawals
if chain_spec.fork(Hardfork::Shanghai).active_at_timestamp(block_timestamp) {
if chain_spec.is_shanghai_activated_at_timestamp(block_timestamp) {
if let Some(withdrawals) = withdrawals {
for withdrawal in withdrawals {
*balance_increments.entry(withdrawal.address).or_default() +=

View File

@ -1,16 +1,24 @@
//! Support for building a pending block via local txpool.
use crate::eth::error::EthResult;
use crate::eth::error::{EthApiError, EthResult};
use core::fmt::Debug;
use reth_primitives::{
constants::{BEACON_NONCE, EMPTY_WITHDRAWALS},
proofs, Block, Header, IntoRecoveredTransaction, Receipt, SealedBlock, SealedHeader,
constants::{eip4844::MAX_DATA_GAS_PER_BLOCK, BEACON_NONCE},
proofs, Block, ChainSpec, Header, IntoRecoveredTransaction, Receipt, SealedBlock, SealedHeader,
EMPTY_OMMER_ROOT, H256, U256,
};
use reth_provider::{BundleStateWithReceipts, StateProviderFactory};
use reth_revm::{database::StateProviderDatabase, env::tx_env_with_recovered, into_reth_log};
use reth_provider::{BundleStateWithReceipts, ChainSpecProvider, StateProviderFactory};
use reth_revm::{
database::StateProviderDatabase,
env::tx_env_with_recovered,
into_reth_log,
state_change::{apply_beacon_root_contract_call, post_block_withdrawals_balance_increments},
};
use reth_transaction_pool::TransactionPool;
use revm::{db::states::bundle_state::BundleRetention, DatabaseCommit, State};
use revm_primitives::{BlockEnv, CfgEnv, EVMError, Env, InvalidTransaction, ResultAndState};
use revm::{db::states::bundle_state::BundleRetention, Database, DatabaseCommit, State};
use revm_primitives::{
BlockEnv, CfgEnv, EVMError, Env, InvalidTransaction, ResultAndState, SpecId,
};
use std::time::Instant;
/// Configured [BlockEnv] and [CfgEnv] for a pending block
@ -25,14 +33,19 @@ pub(crate) struct PendingBlockEnv {
}
impl PendingBlockEnv {
/// Builds a pending block from the given client and pool.
/// Builds a pending block using the given client and pool.
///
/// If the origin is the actual pending block, the block is built with withdrawals.
///
/// After Cancun, if the origin is the actual pending block, the block includes the EIP-4788 pre
/// block contract call using the parent beacon block root received from the CL.
pub(crate) fn build_block<Client, Pool>(
self,
client: &Client,
pool: &Pool,
) -> EthResult<SealedBlock>
where
Client: StateProviderFactory,
Client: StateProviderFactory + ChainSpecProvider,
Pool: TransactionPool,
{
let Self { cfg, block_env, origin } = self;
@ -43,13 +56,39 @@ impl PendingBlockEnv {
let mut db = State::builder().with_database(Box::new(state)).with_bundle_update().build();
let mut cumulative_gas_used = 0;
let block_gas_limit: u64 = block_env.gas_limit.try_into().unwrap_or(u64::MAX);
let mut sum_blob_gas_used = 0;
let block_gas_limit: u64 = block_env.gas_limit.to::<u64>();
let base_fee = block_env.basefee.to::<u64>();
let block_number = block_env.number.to::<u64>();
let mut executed_txs = Vec::new();
let mut best_txs = pool.best_transactions_with_base_fee(base_fee);
let (withdrawals, withdrawals_root) = match origin {
PendingBlockEnvOrigin::ActualPending(ref block) => {
(block.withdrawals.clone(), block.withdrawals_root)
}
PendingBlockEnvOrigin::DerivedFromLatest(_) => (None, None),
};
let chain_spec = client.chain_spec();
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
pre_block_beacon_root_contract_call(
&mut db,
chain_spec.as_ref(),
block_number,
&cfg,
&block_env,
origin.header().parent_beacon_block_root,
)?;
origin.header().parent_beacon_block_root
} else {
None
};
let mut receipts = Vec::new();
while let Some(pool_tx) = best_txs.next() {
@ -65,6 +104,28 @@ impl PendingBlockEnv {
// convert tx to a signed transaction
let tx = pool_tx.to_recovered_transaction();
// 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
if let Some(blob_tx) = tx.transaction.as_eip4844() {
let tx_blob_gas = blob_tx.blob_gas();
if sum_blob_gas_used + tx_blob_gas > MAX_DATA_GAS_PER_BLOCK {
// 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);
continue
} else {
// add to the data gas if we're going to execute the transaction
sum_blob_gas_used += tx_blob_gas;
// if we've reached the max data gas per block, we can skip blob txs entirely
if sum_blob_gas_used == MAX_DATA_GAS_PER_BLOCK {
best_txs.skip_blobs();
}
}
}
// Configure the environment for the block.
let env =
Env { cfg: cfg.clone(), block: block_env.clone(), tx: tx_env_with_recovered(&tx) };
@ -93,10 +154,10 @@ impl PendingBlockEnv {
}
}
};
// commit changes
db.commit(state);
let gas_used = result.gas_used();
// commit changes
db.commit(state);
// add gas used by the transaction to cumulative gas used, before creating the receipt
cumulative_gas_used += gas_used;
@ -112,6 +173,17 @@ impl PendingBlockEnv {
// append transaction to the list of executed transactions
executed_txs.push(tx.into_signed());
}
// executes the withdrawals and commits them to the Database and BundleState.
let balance_increments = post_block_withdrawals_balance_increments(
&chain_spec,
block_env.timestamp.try_into().unwrap_or(u64::MAX),
withdrawals.clone().unwrap_or_default().as_ref(),
);
// increment account balances for withdrawals
db.increment_balances(balance_increments)?;
// merge all transitions into bundle state.
db.merge_transitions(BundleRetention::PlainState);
@ -126,6 +198,10 @@ impl PendingBlockEnv {
// create the block header
let transactions_root = proofs::calculate_transaction_root(&executed_txs);
// check if cancun is activated to set eip4844 header fields correctly
let blob_gas_used =
if cfg.spec_id >= SpecId::CANCUN { Some(sum_blob_gas_used) } else { None };
let header = Header {
parent_hash,
ommers_hash: EMPTY_OMMER_ROOT,
@ -133,7 +209,7 @@ impl PendingBlockEnv {
state_root,
transactions_root,
receipts_root,
withdrawals_root: Some(EMPTY_WITHDRAWALS),
withdrawals_root,
logs_bloom,
timestamp: block_env.timestamp.to::<u64>(),
mix_hash: block_env.prevrandao.unwrap_or_default(),
@ -143,20 +219,61 @@ impl PendingBlockEnv {
gas_limit: block_gas_limit,
difficulty: U256::ZERO,
gas_used: cumulative_gas_used,
blob_gas_used: None,
excess_blob_gas: None,
blob_gas_used,
excess_blob_gas: block_env.get_blob_excess_gas(),
extra_data: Default::default(),
parent_beacon_block_root: None,
parent_beacon_block_root,
};
// seal the block
let block = Block { header, body: executed_txs, ommers: vec![], withdrawals: Some(vec![]) };
let block = Block { header, body: executed_txs, ommers: vec![], withdrawals };
let sealed_block = block.seal_slow();
Ok(sealed_block)
}
}
/// Apply the [EIP-4788](https://eips.ethereum.org/EIPS/eip-4788) pre block contract call.
///
/// This constructs a new [EVM](revm::EVM) with the given DB, and environment ([CfgEnv] and
/// [BlockEnv]) to execute the pre block contract call.
///
/// This uses [apply_beacon_root_contract_call] to ultimately apply the beacon root contract state
/// change.
fn pre_block_beacon_root_contract_call<DB>(
db: &mut DB,
chain_spec: &ChainSpec,
block_number: u64,
initialized_cfg: &CfgEnv,
initialized_block_env: &BlockEnv,
parent_beacon_block_root: Option<H256>,
) -> EthResult<()>
where
DB: Database + DatabaseCommit,
<DB as Database>::Error: Debug,
{
// Configure the environment for the block.
let env = Env {
cfg: initialized_cfg.clone(),
block: initialized_block_env.clone(),
..Default::default()
};
// apply pre-block EIP-4788 contract call
let mut evm_pre_block = revm::EVM::with_env(env);
evm_pre_block.database(db);
// initialize a block from the env, because the pre block call needs the block itself
apply_beacon_root_contract_call(
chain_spec,
initialized_block_env.timestamp.to::<u64>(),
block_number,
parent_beacon_block_root,
&mut evm_pre_block,
)
.map_err(|err| EthApiError::Internal(err.into()))
}
/// The origin for a configured [PendingBlockEnv]
#[derive(Clone, Debug)]
pub(crate) enum PendingBlockEnvOrigin {