Add custom beacon withdrawals example (#9497)

Co-authored-by: Federico Gimenez <federico.gimenez@gmail.com>
This commit is contained in:
Lion - dapplion
2024-10-22 00:02:44 +03:00
committed by GitHub
parent 88a38de4d7
commit f2ac547666
4 changed files with 338 additions and 8 deletions

33
Cargo.lock generated
View File

@ -609,9 +609,9 @@ dependencies = [
[[package]]
name = "alloy-sol-macro"
version = "0.8.8"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b2395336745358cc47207442127c47c63801a7065ecc0aa928da844f8bb5576"
checksum = "b0900b83f4ee1f45c640ceee596afbc118051921b9438fdb5a3175c1a7e05f8b"
dependencies = [
"alloy-sol-macro-expander",
"alloy-sol-macro-input",
@ -623,9 +623,9 @@ dependencies = [
[[package]]
name = "alloy-sol-macro-expander"
version = "0.8.8"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed5047c9a241df94327879c2b0729155b58b941eae7805a7ada2e19436e6b39"
checksum = "a41b1e78dde06b5e12e6702fa8c1d30621bf07728ba75b801fb801c9c6a0ba10"
dependencies = [
"alloy-sol-macro-input",
"const-hex",
@ -641,9 +641,9 @@ dependencies = [
[[package]]
name = "alloy-sol-macro-input"
version = "0.8.8"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5dee02a81f529c415082235129f0df8b8e60aa1601b9c9298ffe54d75f57210b"
checksum = "91dc311a561a306664393407b88d3e53ae58581624128afd8a15faa5de3627dc"
dependencies = [
"const-hex",
"dunce",
@ -2783,6 +2783,23 @@ dependencies = [
"tokio-stream",
]
[[package]]
name = "example-custom-beacon-withdrawals"
version = "0.0.0"
dependencies = [
"alloy-consensus",
"alloy-eips",
"alloy-sol-macro",
"alloy-sol-types",
"eyre",
"reth",
"reth-chainspec",
"reth-evm",
"reth-evm-ethereum",
"reth-node-ethereum",
"reth-primitives",
]
[[package]]
name = "example-custom-dev-node"
version = "0.0.0"
@ -10355,9 +10372,9 @@ dependencies = [
[[package]]
name = "syn-solidity"
version = "0.8.8"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebfc1bfd06acc78f16d8fd3ef846bc222ee7002468d10a7dce8d703d6eab89a3"
checksum = "9d5e0c2ea8db64b2898b62ea2fbd60204ca95e0b2c6bdf53ff768bbe916fbe4d"
dependencies = [
"paste",
"proc-macro2",

View File

@ -145,6 +145,7 @@ members = [
"examples/rpc-db/",
"examples/stateful-precompile/",
"examples/txpool-tracing/",
"examples/custom-beacon-withdrawals",
"testing/ef-tests/",
"testing/testing-utils",
]

View File

@ -0,0 +1,26 @@
[package]
name = "example-custom-beacon-withdrawals"
version = "0.0.0"
publish = false
edition.workspace = true
license.workspace = true
[dependencies]
reth.workspace = true
reth-node-ethereum.workspace = true
reth-evm-ethereum.workspace = true
reth-chainspec.workspace = true
reth-evm.workspace = true
reth-primitives.workspace = true
alloy-sol-macro = "0.8.9"
alloy-sol-types.workspace = true
alloy-eips.workspace = true
alloy-consensus.workspace = true
eyre.workspace = true
[features]
optimism = [
"reth-primitives/optimism"
]

View File

@ -0,0 +1,286 @@
//! Example for how to modify a block post-execution step. It credits beacon withdrawals with a
//! custom mechanism instead of minting native tokens
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
use alloy_eips::eip7685::Requests;
use alloy_sol_macro::sol;
use alloy_sol_types::SolCall;
#[cfg(feature = "optimism")]
use reth::revm::primitives::OptimismFields;
use reth::{
api::{ConfigureEvm, ConfigureEvmEnv, NodeTypesWithEngine},
builder::{components::ExecutorBuilder, BuilderContext, FullNodeTypes},
cli::Cli,
providers::ProviderError,
revm::{
interpreter::Host,
primitives::{Env, TransactTo, TxEnv},
Database, DatabaseCommit, Evm, State,
},
};
use reth_chainspec::{ChainSpec, EthereumHardforks};
use reth_evm::execute::{
BlockExecutionError, BlockExecutionStrategy, BlockExecutionStrategyFactory, ExecuteOutput,
InternalBlockExecutionError,
};
use reth_evm_ethereum::EthEvmConfig;
use reth_node_ethereum::{node::EthereumAddOns, BasicBlockExecutorProvider, EthereumNode};
use reth_primitives::{
revm_primitives::{
address, Address, BlockEnv, Bytes, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, U256,
},
BlockWithSenders, Receipt, Withdrawal,
};
use std::{fmt::Display, sync::Arc};
pub const SYSTEM_ADDRESS: Address = address!("fffffffffffffffffffffffffffffffffffffffe");
pub const WITHDRAWALS_ADDRESS: Address = address!("4200000000000000000000000000000000000000");
fn main() {
Cli::parse_args()
.run(|builder, _| async move {
let handle = builder
// use the default ethereum node types
.with_types::<EthereumNode>()
// Configure the components of the node
// use default ethereum components but use our custom pool
.with_components(
EthereumNode::components().executor(CustomExecutorBuilder::default()),
)
.with_add_ons(EthereumAddOns::default())
.launch()
.await?;
handle.wait_for_node_exit().await
})
.unwrap();
}
/// A custom executor builder
#[derive(Debug, Default, Clone, Copy)]
#[non_exhaustive]
pub struct CustomExecutorBuilder;
impl<Types, Node> ExecutorBuilder<Node> for CustomExecutorBuilder
where
Types: NodeTypesWithEngine<ChainSpec = ChainSpec>,
Node: FullNodeTypes<Types = Types>,
{
type EVM = EthEvmConfig;
type Executor = BasicBlockExecutorProvider<CustomExecutorStrategyFactory>;
async fn build_evm(
self,
ctx: &BuilderContext<Node>,
) -> eyre::Result<(Self::EVM, Self::Executor)> {
let chain_spec = ctx.chain_spec();
let evm_config = EthEvmConfig::new(ctx.chain_spec());
let strategy_factory =
CustomExecutorStrategyFactory { chain_spec, evm_config: evm_config.clone() };
let executor = BasicBlockExecutorProvider::new(strategy_factory);
Ok((evm_config, executor))
}
}
#[derive(Clone)]
pub struct CustomExecutorStrategyFactory {
/// The chainspec
chain_spec: Arc<ChainSpec>,
/// How to create an EVM.
evm_config: EthEvmConfig,
}
impl BlockExecutionStrategyFactory for CustomExecutorStrategyFactory {
type Strategy<DB: Database<Error: Into<ProviderError> + Display>> = CustomExecutorStrategy<DB>;
fn create_strategy<DB>(&self, db: DB) -> Self::Strategy<DB>
where
DB: Database<Error: Into<ProviderError> + Display>,
{
let state =
State::builder().with_database(db).with_bundle_update().without_state_clear().build();
CustomExecutorStrategy {
state,
chain_spec: self.chain_spec.clone(),
evm_config: self.evm_config.clone(),
}
}
}
pub struct CustomExecutorStrategy<DB>
where
DB: Database<Error: Into<ProviderError> + Display>,
{
/// The chainspec
chain_spec: Arc<ChainSpec>,
/// How to create an EVM.
evm_config: EthEvmConfig,
/// Current state for block execution.
state: State<DB>,
}
impl<DB> CustomExecutorStrategy<DB>
where
DB: Database<Error: Into<ProviderError> + Display>,
{
/// Configures a new evm configuration and block environment for the given block.
///
/// # Caution
///
/// This does not initialize the tx environment.
fn evm_env_for_block(
&self,
header: &alloy_consensus::Header,
total_difficulty: U256,
) -> EnvWithHandlerCfg {
let mut cfg = CfgEnvWithHandlerCfg::new(Default::default(), Default::default());
let mut block_env = BlockEnv::default();
self.evm_config.fill_cfg_and_block_env(&mut cfg, &mut block_env, header, total_difficulty);
EnvWithHandlerCfg::new_with_cfg_env(cfg, block_env, Default::default())
}
}
impl<DB> BlockExecutionStrategy<DB> for CustomExecutorStrategy<DB>
where
DB: Database<Error: Into<ProviderError> + Display>,
{
type Error = BlockExecutionError;
fn apply_pre_execution_changes(
&mut self,
block: &BlockWithSenders,
_total_difficulty: U256,
) -> Result<(), Self::Error> {
// Set state clear flag if the block is after the Spurious Dragon hardfork.
let state_clear_flag =
(*self.chain_spec).is_spurious_dragon_active_at_block(block.header.number);
self.state.set_state_clear_flag(state_clear_flag);
Ok(())
}
fn execute_transactions(
&mut self,
_block: &BlockWithSenders,
_total_difficulty: U256,
) -> Result<ExecuteOutput, Self::Error> {
Ok(ExecuteOutput { receipts: vec![], gas_used: 0 })
}
fn apply_post_execution_changes(
&mut self,
block: &BlockWithSenders,
total_difficulty: U256,
_receipts: &[Receipt],
) -> Result<Requests, Self::Error> {
let env = self.evm_env_for_block(&block.header, total_difficulty);
let mut evm = self.evm_config.evm_with_env(&mut self.state, env);
if let Some(withdrawals) = block.body.withdrawals.as_ref() {
apply_withdrawals_contract_call(withdrawals, &mut evm)?;
}
Ok(Requests::default())
}
fn state_ref(&self) -> &State<DB> {
&self.state
}
fn state_mut(&mut self) -> &mut State<DB> {
&mut self.state
}
}
sol!(
function withdrawals(
uint64[] calldata amounts,
address[] calldata addresses
);
);
/// Applies the post-block call to the withdrawal / deposit contract, using the given block,
/// [`ChainSpec`], EVM.
pub fn apply_withdrawals_contract_call<EXT, DB: Database + DatabaseCommit>(
withdrawals: &[Withdrawal],
evm: &mut Evm<'_, EXT, DB>,
) -> Result<(), BlockExecutionError>
where
DB::Error: std::fmt::Display,
{
// get previous env
let previous_env = Box::new(evm.context.env().clone());
// modify env for pre block call
fill_tx_env_with_system_contract_call(
&mut evm.context.evm.env,
SYSTEM_ADDRESS,
WITHDRAWALS_ADDRESS,
withdrawalsCall {
amounts: withdrawals.iter().map(|w| w.amount).collect::<Vec<_>>(),
addresses: withdrawals.iter().map(|w| w.address).collect::<Vec<_>>(),
}
.abi_encode()
.into(),
);
let mut state = match evm.transact() {
Ok(res) => res.state,
Err(e) => {
evm.context.evm.env = previous_env;
return Err(BlockExecutionError::Internal(InternalBlockExecutionError::Other(
format!("withdrawal contract system call revert: {}", e).into(),
)))
}
};
// Clean-up post system tx context
state.remove(&SYSTEM_ADDRESS);
state.remove(&evm.block().coinbase);
evm.context.evm.db.commit(state);
// re-set the previous env
evm.context.evm.env = previous_env;
Ok(())
}
fn fill_tx_env_with_system_contract_call(
env: &mut Env,
caller: Address,
contract: Address,
data: Bytes,
) {
env.tx = TxEnv {
caller,
transact_to: TransactTo::Call(contract),
// Explicitly set nonce to None so revm does not do any nonce checks
nonce: None,
gas_limit: 30_000_000,
value: U256::ZERO,
data,
// 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,
authorization_list: None,
#[cfg(feature = "optimism")]
optimism: OptimismFields::default(),
};
// 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;
}