From badbe3d81d9f27d9999e9419bede04359c20b9cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Hodul=C3=A1k?= Date: Fri, 23 May 2025 15:43:00 +0200 Subject: [PATCH] feat(examples): Implement `BlockAssembler` and `BlockExecutor` for custom blocks in `custom_node` example (#16435) --- examples/custom-node/src/evm/alloy.rs | 3 +- examples/custom-node/src/evm/assembler.rs | 105 ++++++++++++++++-- examples/custom-node/src/evm/config.rs | 38 ++++--- examples/custom-node/src/evm/env.rs | 21 +++- examples/custom-node/src/evm/executor.rs | 47 ++++---- examples/custom-node/src/primitives/header.rs | 6 +- 6 files changed, 165 insertions(+), 55 deletions(-) diff --git a/examples/custom-node/src/evm/alloy.rs b/examples/custom-node/src/evm/alloy.rs index bebecb2d6f..45cd285969 100644 --- a/examples/custom-node/src/evm/alloy.rs +++ b/examples/custom-node/src/evm/alloy.rs @@ -99,7 +99,8 @@ where } } -pub struct CustomEvmFactory(OpEvmFactory); +#[derive(Debug, Clone)] +pub struct CustomEvmFactory(pub OpEvmFactory); impl EvmFactory for CustomEvmFactory { type Evm>> = CustomEvm; diff --git a/examples/custom-node/src/evm/assembler.rs b/examples/custom-node/src/evm/assembler.rs index 6cf4c072f9..2a1afde30b 100644 --- a/examples/custom-node/src/evm/assembler.rs +++ b/examples/custom-node/src/evm/assembler.rs @@ -1,35 +1,118 @@ -use crate::chainspec::CustomChainSpec; -use alloy_consensus::Block; -use alloy_evm::block::{BlockExecutionError, BlockExecutorFactory}; +use crate::{ + chainspec::CustomChainSpec, + primitives::{Block, BlockBody, CustomHeader, CustomTransaction}, +}; +use alloy_consensus::{ + constants::EMPTY_WITHDRAWALS, proofs, Header, TxReceipt, EMPTY_OMMER_ROOT_HASH, +}; +use alloy_eips::{eip7685::EMPTY_REQUESTS_HASH, merge::BEACON_NONCE}; +use alloy_evm::block::{BlockExecutionError, BlockExecutionResult, BlockExecutorFactory}; use alloy_op_evm::OpBlockExecutionCtx; +use alloy_primitives::logs_bloom; use reth_ethereum::{ evm::primitives::execute::{BlockAssembler, BlockAssemblerInput}, primitives::Receipt, }; -use reth_op::{node::OpBlockAssembler, DepositReceipt, OpTransactionSigned}; +use reth_op::DepositReceipt; +use reth_optimism_consensus::{calculate_receipt_root_no_memo_optimism, isthmus}; +use reth_optimism_forks::OpHardforks; #[derive(Clone, Debug)] pub struct CustomBlockAssembler { - inner: OpBlockAssembler, + chain_spec: CustomChainSpec, } impl BlockAssembler for CustomBlockAssembler where F: for<'a> BlockExecutorFactory< ExecutionCtx<'a> = OpBlockExecutionCtx, - Transaction = OpTransactionSigned, + Transaction = CustomTransaction, Receipt: Receipt + DepositReceipt, >, { - // TODO: use custom block here - type Block = Block; + type Block = Block; fn assemble_block( &self, - input: BlockAssemblerInput<'_, '_, F>, + input: BlockAssemblerInput<'_, '_, F, CustomHeader>, ) -> Result { - let block = self.inner.assemble_block(input)?; + let BlockAssemblerInput { + evm_env, + execution_ctx: ctx, + transactions, + output: BlockExecutionResult { receipts, gas_used, .. }, + bundle_state, + state_root, + state_provider, + .. + } = input; - Ok(block) + let timestamp = evm_env.block_env.timestamp; + + let transactions_root = proofs::calculate_transaction_root(&transactions); + let receipts_root = + calculate_receipt_root_no_memo_optimism(receipts, &self.chain_spec, timestamp); + let logs_bloom = logs_bloom(receipts.iter().flat_map(|r| r.logs())); + + let mut requests_hash = None; + + let withdrawals_root = if self.chain_spec.is_isthmus_active_at_timestamp(timestamp) { + // always empty requests hash post isthmus + requests_hash = Some(EMPTY_REQUESTS_HASH); + + // withdrawals root field in block header is used for storage root of L2 predeploy + // `l2tol1-message-passer` + Some( + isthmus::withdrawals_root(bundle_state, state_provider) + .map_err(BlockExecutionError::other)?, + ) + } else if self.chain_spec.is_canyon_active_at_timestamp(timestamp) { + Some(EMPTY_WITHDRAWALS) + } else { + None + }; + + let (excess_blob_gas, blob_gas_used) = + if self.chain_spec.is_ecotone_active_at_timestamp(timestamp) { + (Some(0), Some(0)) + } else { + (None, None) + }; + + let header = Header { + parent_hash: ctx.parent_hash, + ommers_hash: EMPTY_OMMER_ROOT_HASH, + beneficiary: evm_env.block_env.beneficiary, + state_root, + transactions_root, + receipts_root, + withdrawals_root, + logs_bloom, + timestamp, + mix_hash: evm_env.block_env.prevrandao.unwrap_or_default(), + nonce: BEACON_NONCE.into(), + base_fee_per_gas: Some(evm_env.block_env.basefee), + number: evm_env.block_env.number, + gas_limit: evm_env.block_env.gas_limit, + difficulty: evm_env.block_env.difficulty, + gas_used: *gas_used, + extra_data: ctx.extra_data, + parent_beacon_block_root: ctx.parent_beacon_block_root, + blob_gas_used, + excess_blob_gas, + requests_hash, + }; + + Ok(Block::new( + header.into(), + BlockBody { + transactions, + ommers: Default::default(), + withdrawals: self + .chain_spec + .is_canyon_active_at_timestamp(timestamp) + .then(Default::default), + }, + )) } } diff --git a/examples/custom-node/src/evm/config.rs b/examples/custom-node/src/evm/config.rs index 944c465c28..0d3e1383e7 100644 --- a/examples/custom-node/src/evm/config.rs +++ b/examples/custom-node/src/evm/config.rs @@ -1,5 +1,8 @@ -use crate::evm::CustomBlockAssembler; -use alloy_consensus::{Block, Header}; +use crate::{ + evm::{alloy::CustomEvmFactory, CustomBlockAssembler}, + primitives::{Block, CustomHeader, CustomNodePrimitives}, +}; +use alloy_consensus::BlockHeader; use alloy_evm::EvmEnv; use alloy_op_evm::OpBlockExecutionCtx; use op_revm::OpSpecId; @@ -7,19 +10,17 @@ use reth_ethereum::{ node::api::ConfigureEvm, primitives::{SealedBlock, SealedHeader}, }; -use reth_op::{ - node::{OpEvmConfig, OpNextBlockEnvAttributes}, - OpPrimitives, OpTransactionSigned, -}; +use reth_op::node::{OpEvmConfig, OpNextBlockEnvAttributes}; #[derive(Debug, Clone)] pub struct CustomEvmConfig { pub(super) inner: OpEvmConfig, pub(super) block_assembler: CustomBlockAssembler, + pub(super) custom_evm_factory: CustomEvmFactory, } impl ConfigureEvm for CustomEvmConfig { - type Primitives = OpPrimitives; + type Primitives = CustomNodePrimitives; type Error = ::Error; type NextBlockEnvCtx = ::NextBlockEnvCtx; type BlockExecutorFactory = Self; @@ -33,30 +34,35 @@ impl ConfigureEvm for CustomEvmConfig { &self.block_assembler } - fn evm_env(&self, header: &Header) -> EvmEnv { + fn evm_env(&self, header: &CustomHeader) -> EvmEnv { self.inner.evm_env(header) } fn next_evm_env( &self, - parent: &Header, + parent: &CustomHeader, attributes: &OpNextBlockEnvAttributes, ) -> Result, Self::Error> { self.inner.next_evm_env(parent, attributes) } - fn context_for_block( - &self, - block: &SealedBlock>, - ) -> OpBlockExecutionCtx { - self.inner.context_for_block(block) + fn context_for_block(&self, block: &SealedBlock) -> OpBlockExecutionCtx { + OpBlockExecutionCtx { + parent_hash: block.header().parent_hash(), + parent_beacon_block_root: block.header().parent_beacon_block_root(), + extra_data: block.header().extra_data().clone(), + } } fn context_for_next_block( &self, - parent: &SealedHeader, + parent: &SealedHeader, attributes: Self::NextBlockEnvCtx, ) -> OpBlockExecutionCtx { - self.inner.context_for_next_block(parent, attributes) + OpBlockExecutionCtx { + parent_hash: parent.hash(), + parent_beacon_block_root: attributes.parent_beacon_block_root, + extra_data: attributes.extra_data, + } } } diff --git a/examples/custom-node/src/evm/env.rs b/examples/custom-node/src/evm/env.rs index 36b42f20ad..6e42eaeaba 100644 --- a/examples/custom-node/src/evm/env.rs +++ b/examples/custom-node/src/evm/env.rs @@ -2,6 +2,7 @@ use crate::primitives::{CustomTransaction, CustomTransactionEnvelope, TxPayment} use alloy_eips::{eip2930::AccessList, Typed2718}; use alloy_evm::{FromRecoveredTx, FromTxWithEncoded, IntoTxEnv}; use alloy_primitives::{Address, Bytes, TxKind, B256, U256}; +use op_alloy_consensus::OpTxEnvelope; use op_revm::OpTransaction; use reth_ethereum::evm::{primitives::TransactionEnv, revm::context::TxEnv}; @@ -317,12 +318,22 @@ impl FromTxWithEncoded for TxEnv { } } +impl FromRecoveredTx for CustomEvmTransaction { + fn from_recovered_tx(tx: &OpTxEnvelope, sender: Address) -> Self { + Self::Op(OpTransaction::from_recovered_tx(tx, sender)) + } +} + +impl FromTxWithEncoded for CustomEvmTransaction { + fn from_encoded_tx(tx: &OpTxEnvelope, sender: Address, encoded: Bytes) -> Self { + Self::Op(OpTransaction::from_encoded_tx(tx, sender, encoded)) + } +} + impl FromRecoveredTx for CustomEvmTransaction { fn from_recovered_tx(tx: &CustomTransaction, sender: Address) -> Self { match tx { - CustomTransaction::BuiltIn(tx) => { - Self::Op(OpTransaction::from_recovered_tx(tx, sender)) - } + CustomTransaction::BuiltIn(tx) => Self::from_recovered_tx(tx, sender), CustomTransaction::Other(tx) => { Self::Payment(CustomTxEnv(TxEnv::from_recovered_tx(tx, sender))) } @@ -333,9 +344,7 @@ impl FromRecoveredTx for CustomEvmTransaction { impl FromTxWithEncoded for CustomEvmTransaction { fn from_encoded_tx(tx: &CustomTransaction, sender: Address, encoded: Bytes) -> Self { match tx { - CustomTransaction::BuiltIn(tx) => { - Self::Op(OpTransaction::from_encoded_tx(tx, sender, encoded)) - } + CustomTransaction::BuiltIn(tx) => Self::from_encoded_tx(tx, sender, encoded), CustomTransaction::Other(tx) => { Self::Payment(CustomTxEnv(TxEnv::from_encoded_tx(tx, sender, encoded))) } diff --git a/examples/custom-node/src/evm/executor.rs b/examples/custom-node/src/evm/executor.rs index 0714900e26..5b26e5ced5 100644 --- a/examples/custom-node/src/evm/executor.rs +++ b/examples/custom-node/src/evm/executor.rs @@ -1,4 +1,11 @@ -use crate::evm::CustomEvmConfig; +use crate::{ + evm::{ + alloy::{CustomEvm, CustomEvmFactory}, + CustomEvmConfig, CustomEvmTransaction, + }, + primitives::CustomTransaction, +}; +use alloy_consensus::transaction::Recovered; use alloy_evm::{ block::{ BlockExecutionError, BlockExecutionResult, BlockExecutor, BlockExecutorFactory, @@ -7,18 +14,10 @@ use alloy_evm::{ precompiles::PrecompilesMap, Database, Evm, }; -use alloy_op_evm::{OpBlockExecutionCtx, OpBlockExecutor, OpEvm}; -use op_revm::OpTransaction; -use reth_ethereum::{evm::primitives::InspectorFor, node::api::ConfigureEvm}; -use reth_op::{ - chainspec::OpChainSpec, - node::{OpEvmFactory, OpRethReceiptBuilder}, - OpReceipt, OpTransactionSigned, -}; -use revm::{ - context::{result::ExecutionResult, TxEnv}, - database::State, -}; +use alloy_op_evm::{OpBlockExecutionCtx, OpBlockExecutor}; +use reth_ethereum::evm::primitives::InspectorFor; +use reth_op::{chainspec::OpChainSpec, node::OpRethReceiptBuilder, OpReceipt}; +use revm::{context::result::ExecutionResult, database::State}; use std::sync::Arc; pub struct CustomBlockExecutor { @@ -28,9 +27,9 @@ pub struct CustomBlockExecutor { impl<'db, DB, E> BlockExecutor for CustomBlockExecutor where DB: Database + 'db, - E: Evm, Tx = OpTransaction>, + E: Evm, Tx = CustomEvmTransaction>, { - type Transaction = OpTransactionSigned; + type Transaction = CustomTransaction; type Receipt = OpReceipt; type Evm = E; @@ -43,7 +42,15 @@ where tx: impl ExecutableTx, f: impl FnOnce(&ExecutionResult<::HaltReason>) -> CommitChanges, ) -> Result, BlockExecutionError> { - self.inner.execute_transaction_with_commit_condition(tx, f) + match tx.tx() { + CustomTransaction::BuiltIn(op_tx) => { + self.inner.execute_transaction_with_commit_condition( + Recovered::new_unchecked(op_tx, *tx.signer()), + f, + ) + } + CustomTransaction::Other(..) => todo!(), + } } fn finish(self) -> Result<(Self::Evm, BlockExecutionResult), BlockExecutionError> { @@ -64,18 +71,18 @@ where } impl BlockExecutorFactory for CustomEvmConfig { - type EvmFactory = OpEvmFactory; + type EvmFactory = CustomEvmFactory; type ExecutionCtx<'a> = OpBlockExecutionCtx; - type Transaction = OpTransactionSigned; + type Transaction = CustomTransaction; type Receipt = OpReceipt; fn evm_factory(&self) -> &Self::EvmFactory { - self.inner.evm_factory() + &self.custom_evm_factory } fn create_executor<'a, DB, I>( &'a self, - evm: OpEvm<&'a mut State, I, PrecompilesMap>, + evm: CustomEvm<&'a mut State, I, PrecompilesMap>, ctx: OpBlockExecutionCtx, ) -> impl BlockExecutorFor<'a, Self, DB, I> where diff --git a/examples/custom-node/src/primitives/header.rs b/examples/custom-node/src/primitives/header.rs index 7bdb4a8d73..ae96e3e571 100644 --- a/examples/custom-node/src/primitives/header.rs +++ b/examples/custom-node/src/primitives/header.rs @@ -36,7 +36,11 @@ pub struct CustomHeader { pub extension: u64, } -impl CustomHeader {} +impl From
for CustomHeader { + fn from(value: Header) -> Self { + CustomHeader { inner: value, extension: 0 } + } +} impl AsRef for CustomHeader { fn as_ref(&self) -> &Self {