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

feat: add pre-block EIP-4788 beacon root contract call #4457

Merged
merged 30 commits into from
Sep 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
cfb497d
feat: add EIP-4788 pre block call
Rjected Aug 31, 2023
23f0e8f
remove unused dep
Rjected Sep 1, 2023
c9bae72
cargo fmt
Rjected Sep 1, 2023
9214d88
make clippy happy
Rjected Sep 5, 2023
e2749f9
move pre block call to BlockExecutor impl
Rjected Sep 5, 2023
e89d595
use `Database` methods on State<DB> instead of underlying StateProvider
Rjected Sep 5, 2023
a827fc2
remove reth-interfaces usage in tests
Rjected Sep 5, 2023
6e2c9d4
assert exact error on cancun block without beacon block root
Rjected Sep 5, 2023
434103b
apply pre block contract call in payload builder
Rjected Sep 5, 2023
2c0291d
disable block base fee check and set block gas limit
Rjected Sep 6, 2023
66abd72
stop using optional base fee feature
Rjected Sep 6, 2023
6936f3e
init env correctly
Rjected Sep 6, 2023
30b6320
fix usages of State
Rjected Sep 7, 2023
66b29ee
fix beacon root contract code
Rjected Sep 7, 2023
3788bf7
set system address nonce to zero
Rjected Sep 14, 2023
f0af29b
re-set beacon roots address
Rjected Sep 14, 2023
2f6970c
remove system address from output
Rjected Sep 14, 2023
a8d3c14
remove beneficiary account from result state
Rjected Sep 14, 2023
8d05d0b
change apply_pre_block_call to apply_beacon_root_contract_call
Rjected Sep 15, 2023
0865e7a
use cancun helper fn
Rjected Sep 15, 2023
0034849
add note about EIP161 state clear
Rjected Sep 15, 2023
bafd150
rename pre block call method in payload builder
Rjected Sep 15, 2023
f854021
use Database + DatabaseCommit instead of returning State
Rjected Sep 15, 2023
1b539c4
remove old contract address
Rjected Sep 15, 2023
8858761
add test for no nonce increments on empty system account
Rjected Sep 15, 2023
493482a
add docs for pre_block_beacon_root_contract_call
Rjected Sep 15, 2023
37d2e1a
explicitly set every TxEnv field before beacon root contract call
Rjected Sep 15, 2023
63d8d48
fix tests
Rjected Sep 18, 2023
71d6a17
set blob fields in beacon root txenv
Rjected Sep 18, 2023
ae9c5cc
remove todo for client consensus
Rjected Sep 18, 2023
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
2 changes: 2 additions & 0 deletions crates/consensus/auto-seal/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,8 @@ impl StorageInner {
senders: Vec<Address>,
) -> Result<(BundleStateWithReceipts, u64), BlockExecutionError> {
trace!(target: "consensus::auto", transactions=?&block.body, "executing transactions");
// TODO: there isn't really a parent beacon block root here, so not sure whether or not to
// call the 4788 beacon contract
Comment on lines +308 to +309
Copy link
Collaborator

Choose a reason for hiding this comment

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

yeah I think we need to figure out what to do here, but no blocker for this pr


let (receipts, gas_used) =
executor.execute_transactions(block, U256::ZERO, Some(senders))?;
Expand Down
4 changes: 4 additions & 0 deletions crates/interfaces/src/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ pub enum BlockValidationError {
BlockPreMerge { hash: H256 },
#[error("Missing total difficulty")]
MissingTotalDifficulty { hash: H256 },
#[error("EIP-4788 Parent beacon block root missing for active Cancun block")]
MissingParentBeaconBlockRoot,
#[error("The parent beacon block root is not zero for Cancun genesis block")]
CancunGenesisParentBeaconBlockRootNotZero,
}

/// BlockExecutor Errors
Expand Down
79 changes: 74 additions & 5 deletions crates/payload/basic/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,10 @@ use reth_primitives::{
};
use reth_provider::{BlockReaderIdExt, BlockSource, BundleStateWithReceipts, StateProviderFactory};
use reth_revm::{
database::StateProviderDatabase, env::tx_env_with_recovered, into_reth_log,
state_change::post_block_withdrawals_balance_increments,
database::StateProviderDatabase,
env::tx_env_with_recovered,
into_reth_log,
state_change::{apply_beacon_root_contract_call, post_block_withdrawals_balance_increments},
};
use reth_rlp::Encodable;
use reth_tasks::TaskSpawner;
Expand All @@ -45,6 +47,7 @@ use revm::{
Database, DatabaseCommit, State,
};
use std::{
fmt::Debug,
future::Future,
pin::Pin,
sync::{atomic::AtomicBool, Arc},
Expand Down Expand Up @@ -664,6 +667,16 @@ where

let block_number = initialized_block_env.number.to::<u64>();

// apply eip-4788 pre block contract call
pre_block_beacon_root_contract_call(
&mut db,
&chain_spec,
block_number,
&initialized_cfg,
&initialized_block_env,
&attributes,
)?;

let mut receipts = Vec::new();
while let Some(pool_tx) = best_txs.next() {
// ensure we still have capacity for this transaction
Expand Down Expand Up @@ -771,7 +784,8 @@ where
let WithdrawalsOutcome { withdrawals_root, withdrawals } =
commit_withdrawals(&mut db, &chain_spec, attributes.timestamp, attributes.withdrawals)?;

// merge all transitions into bundle state.
// merge all transitions into bundle state, this would apply the withdrawal balance changes and
// 4788 contract call
db.merge_transitions(BundleRetention::PlainState);

let bundle = BundleStateWithReceipts::new(db.take_bundle(), vec![receipts], block_number);
Expand Down Expand Up @@ -861,7 +875,7 @@ where
extra_data,
attributes,
chain_spec,
..
initialized_cfg,
} = config;

debug!(parent_hash=?parent_block.hash, parent_number=parent_block.number, "building empty payload");
Expand All @@ -876,10 +890,21 @@ where
let block_number = initialized_block_env.number.to::<u64>();
let block_gas_limit: u64 = initialized_block_env.gas_limit.try_into().unwrap_or(u64::MAX);

// apply eip-4788 pre block contract call
pre_block_beacon_root_contract_call(
&mut db,
&chain_spec,
block_number,
&initialized_cfg,
&initialized_block_env,
&attributes,
)?;

let WithdrawalsOutcome { withdrawals_root, withdrawals } =
commit_withdrawals(&mut db, &chain_spec, attributes.timestamp, attributes.withdrawals)?;

// merge transition, this will apply the withdrawal balance changes.
// merge all transitions into bundle state, this would apply the withdrawal balance changes and
// 4788 contract call
db.merge_transitions(BundleRetention::PlainState);

// calculate the state root
Expand Down Expand Up @@ -967,6 +992,50 @@ fn commit_withdrawals<DB: Database<Error = Error>>(
})
}

/// 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.
///
/// The parent beacon block root used for the call is gathered from the given
/// [PayloadBuilderAttributes].
///
/// 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,
attributes: &PayloadBuilderAttributes,
) -> Result<(), PayloadBuilderError>
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,
attributes.timestamp,
block_number,
attributes.parent_beacon_block_root,
&mut evm_pre_block,
)
.map_err(|err| PayloadBuilderError::Internal(err.into()))
}

/// Checks if the new payload is better than the current best.
///
/// This compares the total fees of the blocks, higher is better.
Expand Down
9 changes: 8 additions & 1 deletion crates/primitives/src/constants/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Ethereum protocol-related constants

use crate::{H256, U256};
use crate::{H160, H256, U256};
use hex_literal::hex;
use std::time::Duration;

Expand Down Expand Up @@ -132,6 +132,13 @@ pub const BEACON_CONSENSUS_REORG_UNWIND_DEPTH: u64 = 3;
/// <https://github.com/ethereum/go-ethereum/blob/a196f3e8a22b6ad22ced5c2e3baf32bc3ebd4ec9/consensus/ethash/consensus.go#L227-L229>
pub const ALLOWED_FUTURE_BLOCK_TIME_SECONDS: u64 = 15;

/// The address for the beacon roots contract defined in EIP-4788.
pub const BEACON_ROOTS_ADDRESS: H160 = H160(hex!("bEac00dDB15f3B6d645C48263dC93862413A222D"));

/// The caller to be used when calling the EIP-4788 beacon roots contract at the beginning of the
/// block.
pub const SYSTEM_ADDRESS: H160 = H160(hex!("fffffffffffffffffffffffffffffffffffffffe"));

#[cfg(test)]
mod tests {
use super::*;
Expand Down
4 changes: 2 additions & 2 deletions crates/revm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ repository.workspace = true
description = "reth specific revm utilities"

[dependencies]
# reth
# reth
reth-primitives.workspace = true
reth-interfaces.workspace = true
reth-provider.workspace = true
Expand All @@ -21,4 +21,4 @@ reth-consensus-common = { path = "../consensus/common" }
revm.workspace = true

# common
tracing.workspace = true
tracing.workspace = true
4 changes: 3 additions & 1 deletion crates/revm/revm-primitives/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ pub fn revm_spec_by_timestamp_after_merge(

/// return revm_spec from spec configuration.
pub fn revm_spec(chain_spec: &ChainSpec, block: Head) -> revm::primitives::SpecId {
if chain_spec.fork(Hardfork::Shanghai).active_at_head(&block) {
if chain_spec.fork(Hardfork::Cancun).active_at_head(&block) {
revm::primitives::CANCUN
} else if chain_spec.fork(Hardfork::Shanghai).active_at_head(&block) {
revm::primitives::SHANGHAI
} else if chain_spec.fork(Hardfork::Paris).active_at_head(&block) {
revm::primitives::MERGE
Expand Down
50 changes: 48 additions & 2 deletions crates/revm/revm-primitives/src/env.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use crate::config::revm_spec;
use reth_primitives::{
constants::{BEACON_ROOTS_ADDRESS, SYSTEM_ADDRESS},
recover_signer, Address, Bytes, Chain, ChainSpec, Head, Header, Transaction, TransactionKind,
TransactionSignedEcRecovered, TxEip1559, TxEip2930, TxEip4844, TxLegacy, U256,
TransactionSignedEcRecovered, TxEip1559, TxEip2930, TxEip4844, TxLegacy, H256, U256,
};
use revm::primitives::{AnalysisKind, BlockEnv, CfgEnv, SpecId, TransactTo, TxEnv};
use revm::primitives::{AnalysisKind, BlockEnv, CfgEnv, Env, SpecId, TransactTo, TxEnv};

/// Convenience function to call both [fill_cfg_env] and [fill_block_env]
pub fn fill_cfg_and_block_env(
Expand Down Expand Up @@ -106,6 +107,51 @@ pub fn tx_env_with_recovered(transaction: &TransactionSignedEcRecovered) -> TxEn
tx_env
}

/// Fill transaction environment with the EIP-4788 system contract message data.
///
/// This requirements for the beacon root contract call defined by
/// [EIP-4788](https://eips.ethereum.org/EIPS/eip-4788) are:
///
/// At the start of processing any execution block where `block.timestamp >= FORK_TIMESTAMP` (i.e.
/// before processing any transactions), call `BEACON_ROOTS_ADDRESS` as `SYSTEM_ADDRESS` with the
/// 32-byte input of `header.parent_beacon_block_root`, a gas limit of `30_000_000`, and `0` value.
/// This will trigger the `set()` routine of the beacon roots contract. This is a system operation
/// and therefore:
/// * the call must execute to completion
/// * the call does not count against the block’s gas limit
/// * the call does not follow the EIP-1559 burn semantics - no value should be transferred as
/// part of the call
/// * if no code exists at `BEACON_ROOTS_ADDRESS`, the call must fail silently
pub fn fill_tx_env_with_beacon_root_contract_call(env: &mut Env, parent_beacon_block_root: H256) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

this is great

env.tx = TxEnv {
caller: SYSTEM_ADDRESS,
transact_to: TransactTo::Call(BEACON_ROOTS_ADDRESS),
// Explicitly set nonce to None so revm does not do any nonce checks
nonce: None,
gas_limit: 30_000_000,
value: U256::ZERO,
data: parent_beacon_block_root.to_fixed_bytes().to_vec().into(),
// Setting the gas price to zero enforces that no value is transferred as part of the call,
// and that the call will not count against the block's gas limit
gas_price: U256::ZERO,
// The chain ID check is not relevant here and is disabled if set to None
chain_id: None,
// Setting the gas priority fee to None ensures the effective gas price is derived from the
// `gas_price` field, which we need to be zero
gas_priority_fee: None,
access_list: Vec::new(),
// blob fields can be None for this tx
blob_hashes: Vec::new(),
max_fee_per_blob_gas: None,
};

// ensure the block gas limit is >= the tx
env.block.gas_limit = U256::from(env.tx.gas_limit);

// disable the base fee check for this call by setting the base fee to zero
env.block.basefee = U256::ZERO;
}

/// Fill transaction environment from [TransactionSignedEcRecovered].
pub fn fill_tx_env_with_recovered(tx_env: &mut TxEnv, transaction: &TransactionSignedEcRecovered) {
fill_tx_env(tx_env, transaction.as_ref(), transaction.signer())
Expand Down
Loading
Loading