chore(op-reth): move op-dependent examples into crates/optimism/examples/ (#21495)

Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
This commit is contained in:
theo
2026-01-30 09:02:12 -05:00
committed by GitHub
parent 9d064be77e
commit b3d532ce9d
27 changed files with 4 additions and 5 deletions

View File

@@ -1,71 +0,0 @@
[package]
name = "example-custom-node"
version = "0.0.0"
publish = false
edition.workspace = true
license.workspace = true
[dependencies]
# reth
reth-codecs.workspace = true
reth-network-peers.workspace = true
reth-node-builder.workspace = true
reth-optimism-forks.workspace = true
reth-optimism-flashblocks.workspace = true
reth-db-api.workspace = true
reth-op = { workspace = true, features = ["node", "pool", "rpc"] }
reth-payload-builder.workspace = true
reth-rpc-api.workspace = true
reth-engine-primitives.workspace = true
reth-rpc-engine-api.workspace = true
reth-ethereum = { workspace = true, features = ["node-api", "network", "evm", "pool", "trie", "storage-api", "provider"] }
# revm
revm.workspace = true
revm-primitives.workspace = true
# alloy
alloy-consensus = { workspace = true, features = ["serde"] }
alloy-eips.workspace = true
alloy-evm.workspace = true
alloy-genesis.workspace = true
alloy-op-evm.workspace = true
alloy-primitives.workspace = true
alloy-rlp.workspace = true
alloy-serde.workspace = true
alloy-network.workspace = true
alloy-rpc-types-engine.workspace = true
alloy-rpc-types-eth.workspace = true
op-alloy-consensus.workspace = true
op-alloy-rpc-types-engine.workspace = true
op-alloy-rpc-types.workspace = true
op-revm.workspace = true
# misc
async-trait.workspace = true
derive_more.workspace = true
eyre.workspace = true
jsonrpsee.workspace = true
serde.workspace = true
thiserror.workspace = true
modular-bitfield.workspace = true
[features]
arbitrary = [
"alloy-consensus/arbitrary",
"alloy-eips/arbitrary",
"alloy-primitives/arbitrary",
"alloy-serde/arbitrary",
"op-alloy-consensus/arbitrary",
"op-alloy-rpc-types-engine/arbitrary",
"reth-codecs/arbitrary",
"reth-op/arbitrary",
"revm-primitives/arbitrary",
"revm/arbitrary",
"reth-ethereum/arbitrary",
"alloy-rpc-types-engine/arbitrary",
"reth-db-api/arbitrary",
"alloy-rpc-types-eth/arbitrary",
"op-alloy-rpc-types/arbitrary",
]
default = []

View File

@@ -1,117 +0,0 @@
use crate::primitives::CustomHeader;
use alloy_genesis::Genesis;
use reth_ethereum::{
chainspec::{EthChainSpec, EthereumHardforks, Hardfork, Hardforks},
primitives::SealedHeader,
};
use reth_network_peers::NodeRecord;
use reth_op::chainspec::OpChainSpec;
use reth_optimism_forks::OpHardforks;
#[derive(Debug, Clone)]
pub struct CustomChainSpec {
inner: OpChainSpec,
genesis_header: SealedHeader<CustomHeader>,
}
impl CustomChainSpec {
pub const fn inner(&self) -> &OpChainSpec {
&self.inner
}
}
impl Hardforks for CustomChainSpec {
fn fork<H: Hardfork>(&self, fork: H) -> reth_ethereum::chainspec::ForkCondition {
self.inner.fork(fork)
}
fn forks_iter(
&self,
) -> impl Iterator<Item = (&dyn Hardfork, reth_ethereum::chainspec::ForkCondition)> {
self.inner.forks_iter()
}
fn fork_id(&self, head: &reth_ethereum::chainspec::Head) -> reth_ethereum::chainspec::ForkId {
self.inner.fork_id(head)
}
fn latest_fork_id(&self) -> reth_ethereum::chainspec::ForkId {
self.inner.latest_fork_id()
}
fn fork_filter(
&self,
head: reth_ethereum::chainspec::Head,
) -> reth_ethereum::chainspec::ForkFilter {
self.inner.fork_filter(head)
}
}
impl EthChainSpec for CustomChainSpec {
type Header = CustomHeader;
fn chain(&self) -> reth_ethereum::chainspec::Chain {
self.inner.chain()
}
fn base_fee_params_at_timestamp(
&self,
timestamp: u64,
) -> reth_ethereum::chainspec::BaseFeeParams {
self.inner.base_fee_params_at_timestamp(timestamp)
}
fn blob_params_at_timestamp(&self, timestamp: u64) -> Option<alloy_eips::eip7840::BlobParams> {
self.inner.blob_params_at_timestamp(timestamp)
}
fn deposit_contract(&self) -> Option<&reth_ethereum::chainspec::DepositContract> {
self.inner.deposit_contract()
}
fn genesis_hash(&self) -> revm_primitives::B256 {
self.genesis_header.hash()
}
fn prune_delete_limit(&self) -> usize {
self.inner.prune_delete_limit()
}
fn display_hardforks(&self) -> Box<dyn std::fmt::Display> {
self.inner.display_hardforks()
}
fn genesis_header(&self) -> &Self::Header {
&self.genesis_header
}
fn genesis(&self) -> &Genesis {
self.inner.genesis()
}
fn bootnodes(&self) -> Option<Vec<NodeRecord>> {
self.inner.bootnodes()
}
fn final_paris_total_difficulty(&self) -> Option<revm_primitives::U256> {
self.inner.get_final_paris_total_difficulty()
}
}
impl EthereumHardforks for CustomChainSpec {
fn ethereum_fork_activation(
&self,
fork: reth_ethereum::chainspec::EthereumHardfork,
) -> reth_ethereum::chainspec::ForkCondition {
self.inner.ethereum_fork_activation(fork)
}
}
impl OpHardforks for CustomChainSpec {
fn op_fork_activation(
&self,
fork: reth_optimism_forks::OpHardfork,
) -> reth_ethereum::chainspec::ForkCondition {
self.inner.op_fork_activation(fork)
}
}

View File

@@ -1,335 +0,0 @@
use crate::{
chainspec::CustomChainSpec,
evm::CustomEvmConfig,
primitives::{CustomHeader, CustomNodePrimitives, CustomTransaction},
CustomNode,
};
use alloy_eips::eip2718::WithEncoded;
use alloy_primitives::Bytes;
use op_alloy_rpc_types_engine::{OpExecutionData, OpExecutionPayload};
use reth_engine_primitives::EngineApiValidator;
use reth_ethereum::{
node::api::{
validate_version_specific_fields, AddOnsContext, BuiltPayload, BuiltPayloadExecutedBlock,
EngineApiMessageVersion, EngineObjectValidationError, ExecutionPayload, FullNodeComponents,
NewPayloadError, NodePrimitives, PayloadAttributes, PayloadBuilderAttributes,
PayloadOrAttributes, PayloadTypes, PayloadValidator,
},
primitives::SealedBlock,
storage::StateProviderFactory,
trie::{KeccakKeyHasher, KeyHasher},
};
use reth_node_builder::{rpc::PayloadValidatorBuilder, InvalidPayloadAttributesError};
use reth_op::node::{
engine::OpEngineValidator, payload::OpAttributes, OpBuiltPayload, OpEngineTypes,
OpPayloadAttributes, OpPayloadBuilderAttributes,
};
use revm_primitives::U256;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use thiserror::Error;
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct CustomPayloadTypes;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CustomExecutionData {
pub inner: OpExecutionData,
pub extension: u64,
}
impl ExecutionPayload for CustomExecutionData {
fn parent_hash(&self) -> revm_primitives::B256 {
self.inner.parent_hash()
}
fn block_hash(&self) -> revm_primitives::B256 {
self.inner.block_hash()
}
fn block_number(&self) -> u64 {
self.inner.block_number()
}
fn withdrawals(&self) -> Option<&Vec<alloy_eips::eip4895::Withdrawal>> {
None
}
fn block_access_list(&self) -> Option<&Bytes> {
None
}
fn parent_beacon_block_root(&self) -> Option<revm_primitives::B256> {
self.inner.parent_beacon_block_root()
}
fn timestamp(&self) -> u64 {
self.inner.timestamp()
}
fn gas_used(&self) -> u64 {
self.inner.gas_used()
}
fn transaction_count(&self) -> usize {
self.inner.payload.as_v1().transactions.len()
}
}
impl TryFrom<&reth_optimism_flashblocks::FlashBlockCompleteSequence> for CustomExecutionData {
type Error = &'static str;
fn try_from(
sequence: &reth_optimism_flashblocks::FlashBlockCompleteSequence,
) -> Result<Self, Self::Error> {
let inner = OpExecutionData::try_from(sequence)?;
Ok(Self { inner, extension: sequence.last().diff.gas_used })
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CustomPayloadAttributes {
#[serde(flatten)]
inner: OpPayloadAttributes,
extension: u64,
}
impl PayloadAttributes for CustomPayloadAttributes {
fn timestamp(&self) -> u64 {
self.inner.timestamp()
}
fn withdrawals(&self) -> Option<&Vec<alloy_eips::eip4895::Withdrawal>> {
self.inner.withdrawals()
}
fn parent_beacon_block_root(&self) -> Option<revm_primitives::B256> {
self.inner.parent_beacon_block_root()
}
}
#[derive(Debug, Clone)]
pub struct CustomPayloadBuilderAttributes {
pub inner: OpPayloadBuilderAttributes<CustomTransaction>,
pub extension: u64,
}
impl PayloadBuilderAttributes for CustomPayloadBuilderAttributes {
type RpcPayloadAttributes = CustomPayloadAttributes;
type Error = alloy_rlp::Error;
fn try_new(
parent: revm_primitives::B256,
rpc_payload_attributes: Self::RpcPayloadAttributes,
version: u8,
) -> Result<Self, Self::Error>
where
Self: Sized,
{
let CustomPayloadAttributes { inner, extension } = rpc_payload_attributes;
Ok(Self { inner: OpPayloadBuilderAttributes::try_new(parent, inner, version)?, extension })
}
fn payload_id(&self) -> alloy_rpc_types_engine::PayloadId {
self.inner.payload_id()
}
fn parent(&self) -> revm_primitives::B256 {
self.inner.parent()
}
fn timestamp(&self) -> u64 {
self.inner.timestamp()
}
fn parent_beacon_block_root(&self) -> Option<revm_primitives::B256> {
self.inner.parent_beacon_block_root()
}
fn suggested_fee_recipient(&self) -> revm_primitives::Address {
self.inner.suggested_fee_recipient()
}
fn prev_randao(&self) -> revm_primitives::B256 {
self.inner.prev_randao()
}
fn withdrawals(&self) -> &alloy_eips::eip4895::Withdrawals {
self.inner.withdrawals()
}
}
impl OpAttributes for CustomPayloadBuilderAttributes {
type Transaction = CustomTransaction;
fn no_tx_pool(&self) -> bool {
self.inner.no_tx_pool
}
fn sequencer_transactions(&self) -> &[WithEncoded<Self::Transaction>] {
&self.inner.transactions
}
}
#[derive(Debug, Clone)]
pub struct CustomBuiltPayload(pub OpBuiltPayload<CustomNodePrimitives>);
impl BuiltPayload for CustomBuiltPayload {
type Primitives = CustomNodePrimitives;
fn block(&self) -> &SealedBlock<<Self::Primitives as NodePrimitives>::Block> {
self.0.block()
}
fn fees(&self) -> U256 {
self.0.fees()
}
fn executed_block(&self) -> Option<BuiltPayloadExecutedBlock<Self::Primitives>> {
self.0.executed_block()
}
fn requests(&self) -> Option<alloy_eips::eip7685::Requests> {
self.0.requests()
}
}
impl From<CustomBuiltPayload>
for alloy_consensus::Block<<CustomNodePrimitives as NodePrimitives>::SignedTx>
{
fn from(value: CustomBuiltPayload) -> Self {
value.0.into_sealed_block().into_block().map_header(|header| header.inner)
}
}
impl PayloadTypes for CustomPayloadTypes {
type ExecutionData = CustomExecutionData;
type BuiltPayload = OpBuiltPayload<CustomNodePrimitives>;
type PayloadAttributes = CustomPayloadAttributes;
type PayloadBuilderAttributes = CustomPayloadBuilderAttributes;
fn block_to_payload(
block: SealedBlock<
<<Self::BuiltPayload as BuiltPayload>::Primitives as NodePrimitives>::Block,
>,
) -> Self::ExecutionData {
let extension = block.header().extension;
let block_hash = block.hash();
let block = block.into_block().map_header(|header| header.inner);
let (payload, sidecar) = OpExecutionPayload::from_block_unchecked(block_hash, &block);
CustomExecutionData { inner: OpExecutionData { payload, sidecar }, extension }
}
}
/// Custom engine validator
#[derive(Debug, Clone)]
pub struct CustomEngineValidator<P> {
inner: OpEngineValidator<P, CustomTransaction, CustomChainSpec>,
}
impl<P> CustomEngineValidator<P>
where
P: Send + Sync + Unpin + 'static,
{
/// Instantiates a new validator.
pub fn new<KH: KeyHasher>(chain_spec: Arc<CustomChainSpec>, provider: P) -> Self {
Self { inner: OpEngineValidator::new::<KH>(chain_spec, provider) }
}
/// Returns the chain spec used by the validator.
#[inline]
fn chain_spec(&self) -> &CustomChainSpec {
self.inner.chain_spec()
}
}
impl<P> PayloadValidator<CustomPayloadTypes> for CustomEngineValidator<P>
where
P: StateProviderFactory + Send + Sync + Unpin + 'static,
{
type Block = crate::primitives::block::Block;
fn validate_payload_attributes_against_header(
&self,
_attr: &CustomPayloadAttributes,
_header: &<Self::Block as reth_ethereum::primitives::Block>::Header,
) -> Result<(), InvalidPayloadAttributesError> {
// skip default timestamp validation
Ok(())
}
fn convert_payload_to_block(
&self,
payload: CustomExecutionData,
) -> Result<SealedBlock<Self::Block>, NewPayloadError> {
let sealed_block = PayloadValidator::<OpEngineTypes>::convert_payload_to_block(
&self.inner,
payload.inner,
)?;
let (header, body) = sealed_block.split_sealed_header_body();
let header = CustomHeader { inner: header.into_header(), extension: payload.extension };
let body = body.map_ommers(|_| CustomHeader::default());
Ok(SealedBlock::<Self::Block>::from_parts_unhashed(header, body))
}
}
impl<P> EngineApiValidator<CustomPayloadTypes> for CustomEngineValidator<P>
where
P: StateProviderFactory + Send + Sync + Unpin + 'static,
{
fn validate_version_specific_fields(
&self,
version: EngineApiMessageVersion,
payload_or_attrs: PayloadOrAttributes<'_, CustomExecutionData, CustomPayloadAttributes>,
) -> Result<(), EngineObjectValidationError> {
validate_version_specific_fields(self.chain_spec(), version, payload_or_attrs)
}
fn ensure_well_formed_attributes(
&self,
version: EngineApiMessageVersion,
attributes: &CustomPayloadAttributes,
) -> Result<(), EngineObjectValidationError> {
validate_version_specific_fields(
self.chain_spec(),
version,
PayloadOrAttributes::<CustomExecutionData, _>::PayloadAttributes(attributes),
)?;
// custom validation logic - ensure that the custom field is not zero
// if attributes.extension == 0 {
// return Err(EngineObjectValidationError::invalid_params(
// CustomError::CustomFieldIsNotZero,
// ))
// }
Ok(())
}
}
/// Custom error type used in payload attributes validation
#[derive(Debug, Error)]
pub enum CustomError {
#[error("Custom field is not zero")]
CustomFieldIsNotZero,
}
/// Custom engine validator builder
#[derive(Debug, Default, Clone, Copy)]
#[non_exhaustive]
pub struct CustomEngineValidatorBuilder;
impl<N> PayloadValidatorBuilder<N> for CustomEngineValidatorBuilder
where
N: FullNodeComponents<Types = CustomNode, Evm = CustomEvmConfig>,
{
type Validator = CustomEngineValidator<N::Provider>;
async fn build(self, ctx: &AddOnsContext<'_, N>) -> eyre::Result<Self::Validator> {
Ok(CustomEngineValidator::new::<KeccakKeyHasher>(
ctx.config.chain.clone(),
ctx.node.provider().clone(),
))
}
}

View File

@@ -1,144 +0,0 @@
use crate::{
engine::{CustomExecutionData, CustomPayloadAttributes, CustomPayloadTypes},
primitives::CustomNodePrimitives,
CustomNode,
};
use alloy_rpc_types_engine::{
ExecutionPayloadV3, ForkchoiceState, ForkchoiceUpdated, PayloadId, PayloadStatus,
};
use async_trait::async_trait;
use jsonrpsee::{core::RpcResult, proc_macros::rpc, RpcModule};
use reth_ethereum::node::api::{
AddOnsContext, ConsensusEngineHandle, EngineApiMessageVersion, FullNodeComponents,
};
use reth_node_builder::rpc::EngineApiBuilder;
use reth_op::node::OpBuiltPayload;
use reth_payload_builder::PayloadStore;
use reth_rpc_api::IntoEngineApiRpcModule;
use reth_rpc_engine_api::EngineApiError;
use std::sync::Arc;
#[derive(serde::Deserialize)]
pub struct CustomExecutionPayloadInput {}
#[derive(Clone, serde::Serialize)]
pub struct CustomExecutionPayloadEnvelope {
execution_payload: ExecutionPayloadV3,
extension: u64,
}
impl From<OpBuiltPayload<CustomNodePrimitives>> for CustomExecutionPayloadEnvelope {
fn from(value: OpBuiltPayload<CustomNodePrimitives>) -> Self {
let sealed_block = value.into_sealed_block();
let hash = sealed_block.hash();
let extension = sealed_block.header().extension;
let block = sealed_block.into_block();
Self {
execution_payload: ExecutionPayloadV3::from_block_unchecked(hash, &block),
extension,
}
}
}
#[rpc(server, namespace = "engine")]
pub trait CustomEngineApi {
#[method(name = "newPayload")]
async fn new_payload(&self, payload: CustomExecutionData) -> RpcResult<PayloadStatus>;
#[method(name = "forkchoiceUpdated")]
async fn fork_choice_updated(
&self,
fork_choice_state: ForkchoiceState,
payload_attributes: Option<CustomPayloadAttributes>,
) -> RpcResult<ForkchoiceUpdated>;
#[method(name = "getPayload")]
async fn get_payload(&self, payload_id: PayloadId)
-> RpcResult<CustomExecutionPayloadEnvelope>;
}
pub struct CustomEngineApi {
inner: Arc<CustomEngineApiInner>,
}
struct CustomEngineApiInner {
beacon_consensus: ConsensusEngineHandle<CustomPayloadTypes>,
payload_store: PayloadStore<CustomPayloadTypes>,
}
impl CustomEngineApiInner {
fn new(
beacon_consensus: ConsensusEngineHandle<CustomPayloadTypes>,
payload_store: PayloadStore<CustomPayloadTypes>,
) -> Self {
Self { beacon_consensus, payload_store }
}
}
#[async_trait]
impl CustomEngineApiServer for CustomEngineApi {
async fn new_payload(&self, payload: CustomExecutionData) -> RpcResult<PayloadStatus> {
Ok(self
.inner
.beacon_consensus
.new_payload(payload)
.await
.map_err(EngineApiError::NewPayload)?)
}
async fn fork_choice_updated(
&self,
fork_choice_state: ForkchoiceState,
payload_attributes: Option<CustomPayloadAttributes>,
) -> RpcResult<ForkchoiceUpdated> {
Ok(self
.inner
.beacon_consensus
.fork_choice_updated(fork_choice_state, payload_attributes, EngineApiMessageVersion::V3)
.await
.map_err(EngineApiError::ForkChoiceUpdate)?)
}
async fn get_payload(
&self,
payload_id: PayloadId,
) -> RpcResult<CustomExecutionPayloadEnvelope> {
Ok(self
.inner
.payload_store
.resolve(payload_id)
.await
.ok_or(EngineApiError::UnknownPayload)?
.map_err(|_| EngineApiError::UnknownPayload)?
.into())
}
}
impl IntoEngineApiRpcModule for CustomEngineApi
where
Self: CustomEngineApiServer,
{
fn into_rpc_module(self) -> RpcModule<()> {
self.into_rpc().remove_context()
}
}
#[derive(Debug, Default, Clone)]
pub struct CustomEngineApiBuilder {}
impl<N> EngineApiBuilder<N> for CustomEngineApiBuilder
where
N: FullNodeComponents<Types = CustomNode>,
{
type EngineApi = CustomEngineApi;
async fn build_engine_api(self, ctx: &AddOnsContext<'_, N>) -> eyre::Result<Self::EngineApi> {
Ok(CustomEngineApi {
inner: Arc::new(CustomEngineApiInner::new(
ctx.beacon_engine_handle.clone(),
PayloadStore::new(ctx.node.payload_builder_handle().clone()),
)),
})
}
}

View File

@@ -1,126 +0,0 @@
use crate::evm::{CustomTxEnv, PaymentTxEnv};
use alloy_evm::{precompiles::PrecompilesMap, Database, Evm, EvmEnv, EvmFactory};
use alloy_op_evm::{OpEvm, OpEvmFactory};
use alloy_primitives::{Address, Bytes};
use op_revm::{
precompiles::OpPrecompiles, L1BlockInfo, OpContext, OpHaltReason, OpSpecId, OpTransaction,
OpTransactionError,
};
use reth_ethereum::evm::revm::{
context::{result::ResultAndState, BlockEnv, CfgEnv},
handler::PrecompileProvider,
interpreter::InterpreterResult,
Context, Inspector, Journal,
};
use revm::{context_interface::result::EVMError, inspector::NoOpInspector};
use std::error::Error;
/// EVM context contains data that EVM needs for execution of [`CustomTxEnv`].
pub type CustomContext<DB> =
Context<BlockEnv, OpTransaction<PaymentTxEnv>, CfgEnv<OpSpecId>, DB, Journal<DB>, L1BlockInfo>;
pub struct CustomEvm<DB: Database, I, P = OpPrecompiles> {
inner: OpEvm<DB, I, P>,
}
impl<DB: Database, I, P> CustomEvm<DB, I, P> {
pub fn new(op: OpEvm<DB, I, P>) -> Self {
Self { inner: op }
}
}
impl<DB, I, P> Evm for CustomEvm<DB, I, P>
where
DB: Database,
I: Inspector<OpContext<DB>>,
P: PrecompileProvider<OpContext<DB>, Output = InterpreterResult>,
{
type DB = DB;
type Tx = CustomTxEnv;
type Error = EVMError<DB::Error, OpTransactionError>;
type HaltReason = OpHaltReason;
type Spec = OpSpecId;
type BlockEnv = BlockEnv;
type Precompiles = P;
type Inspector = I;
fn block(&self) -> &BlockEnv {
self.inner.block()
}
fn chain_id(&self) -> u64 {
self.inner.chain_id()
}
fn transact_raw(
&mut self,
tx: Self::Tx,
) -> Result<ResultAndState<Self::HaltReason>, Self::Error> {
match tx {
CustomTxEnv::Op(tx) => self.inner.transact_raw(tx),
CustomTxEnv::Payment(..) => todo!(),
}
}
fn transact_system_call(
&mut self,
caller: Address,
contract: Address,
data: Bytes,
) -> Result<ResultAndState<Self::HaltReason>, Self::Error> {
self.inner.transact_system_call(caller, contract, data)
}
fn finish(self) -> (Self::DB, EvmEnv<Self::Spec>) {
self.inner.finish()
}
fn set_inspector_enabled(&mut self, enabled: bool) {
self.inner.set_inspector_enabled(enabled)
}
fn components(&self) -> (&Self::DB, &Self::Inspector, &Self::Precompiles) {
self.inner.components()
}
fn components_mut(&mut self) -> (&mut Self::DB, &mut Self::Inspector, &mut Self::Precompiles) {
self.inner.components_mut()
}
}
#[derive(Default, Debug, Clone, Copy)]
pub struct CustomEvmFactory(pub OpEvmFactory);
impl CustomEvmFactory {
pub fn new() -> Self {
Self::default()
}
}
impl EvmFactory for CustomEvmFactory {
type Evm<DB: Database, I: Inspector<OpContext<DB>>> = CustomEvm<DB, I, Self::Precompiles>;
type Context<DB: Database> = OpContext<DB>;
type Tx = CustomTxEnv;
type Error<DBError: Error + Send + Sync + 'static> = EVMError<DBError, OpTransactionError>;
type HaltReason = OpHaltReason;
type Spec = OpSpecId;
type BlockEnv = BlockEnv;
type Precompiles = PrecompilesMap;
fn create_evm<DB: Database>(
&self,
db: DB,
input: EvmEnv<Self::Spec>,
) -> Self::Evm<DB, NoOpInspector> {
CustomEvm::new(self.0.create_evm(db, input))
}
fn create_evm_with_inspector<DB: Database, I: Inspector<Self::Context<DB>>>(
&self,
db: DB,
input: EvmEnv<Self::Spec>,
inspector: I,
) -> Self::Evm<DB, I> {
CustomEvm::new(self.0.create_evm_with_inspector(db, input, inspector))
}
}

View File

@@ -1,41 +0,0 @@
use crate::{
chainspec::CustomChainSpec,
evm::executor::CustomBlockExecutionCtx,
primitives::{Block, CustomHeader, CustomTransaction},
};
use alloy_evm::block::{BlockExecutionError, BlockExecutorFactory};
use reth_ethereum::{
evm::primitives::execute::{BlockAssembler, BlockAssemblerInput},
primitives::Receipt,
};
use reth_op::{node::OpBlockAssembler, DepositReceipt};
use std::sync::Arc;
#[derive(Clone, Debug)]
pub struct CustomBlockAssembler {
block_assembler: OpBlockAssembler<CustomChainSpec>,
}
impl CustomBlockAssembler {
pub const fn new(chain_spec: Arc<CustomChainSpec>) -> Self {
Self { block_assembler: OpBlockAssembler::new(chain_spec) }
}
}
impl<F> BlockAssembler<F> for CustomBlockAssembler
where
F: for<'a> BlockExecutorFactory<
ExecutionCtx<'a> = CustomBlockExecutionCtx,
Transaction = CustomTransaction,
Receipt: Receipt + DepositReceipt,
>,
{
type Block = Block;
fn assemble_block(
&self,
input: BlockAssemblerInput<'_, '_, F, CustomHeader>,
) -> Result<Self::Block, BlockExecutionError> {
Ok(self.block_assembler.assemble_block(input)?.map_header(From::from))
}
}

View File

@@ -1,22 +0,0 @@
use crate::{chainspec::CustomChainSpec, evm::CustomEvmConfig, primitives::CustomNodePrimitives};
use reth_ethereum::node::api::FullNodeTypes;
use reth_node_builder::{components::ExecutorBuilder, BuilderContext, NodeTypes};
use std::{future, future::Future};
#[derive(Debug, Clone, Default)]
#[non_exhaustive]
pub struct CustomExecutorBuilder;
impl<Node: FullNodeTypes> ExecutorBuilder<Node> for CustomExecutorBuilder
where
Node::Types: NodeTypes<ChainSpec = CustomChainSpec, Primitives = CustomNodePrimitives>,
{
type EVM = CustomEvmConfig;
fn build_evm(
self,
ctx: &BuilderContext<Node>,
) -> impl Future<Output = eyre::Result<Self::EVM>> + Send {
future::ready(Ok(CustomEvmConfig::new(ctx.chain_spec())))
}
}

View File

@@ -1,180 +0,0 @@
use crate::{
chainspec::CustomChainSpec,
engine::{CustomExecutionData, CustomPayloadBuilderAttributes},
evm::{alloy::CustomEvmFactory, executor::CustomBlockExecutionCtx, CustomBlockAssembler},
primitives::{Block, CustomHeader, CustomNodePrimitives, CustomTransaction},
};
use alloy_consensus::BlockHeader;
use alloy_eips::{eip2718::WithEncoded, Decodable2718};
use alloy_evm::EvmEnv;
use alloy_op_evm::OpBlockExecutionCtx;
use alloy_rpc_types_engine::PayloadError;
use op_alloy_rpc_types_engine::flashblock::OpFlashblockPayloadBase;
use op_revm::OpSpecId;
use reth_engine_primitives::ExecutableTxIterator;
use reth_ethereum::{
chainspec::EthChainSpec,
node::api::{BuildNextEnv, ConfigureEvm, PayloadBuilderError},
primitives::{SealedBlock, SealedHeader},
};
use reth_node_builder::{ConfigureEngineEvm, NewPayloadError};
use reth_op::{
chainspec::OpHardforks,
evm::primitives::{EvmEnvFor, ExecutionCtxFor},
node::{OpEvmConfig, OpNextBlockEnvAttributes, OpRethReceiptBuilder},
primitives::SignedTransaction,
};
use reth_rpc_api::eth::helpers::pending_block::BuildPendingEnv;
use revm_primitives::Bytes;
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct CustomEvmConfig {
pub(super) inner: OpEvmConfig,
pub(super) block_assembler: CustomBlockAssembler,
pub(super) custom_evm_factory: CustomEvmFactory,
}
impl CustomEvmConfig {
pub fn new(chain_spec: Arc<CustomChainSpec>) -> Self {
Self {
inner: OpEvmConfig::new(
Arc::new(chain_spec.inner().clone()),
OpRethReceiptBuilder::default(),
),
block_assembler: CustomBlockAssembler::new(chain_spec),
custom_evm_factory: CustomEvmFactory::new(),
}
}
}
impl ConfigureEvm for CustomEvmConfig {
type Primitives = CustomNodePrimitives;
type Error = <OpEvmConfig as ConfigureEvm>::Error;
type NextBlockEnvCtx = CustomNextBlockEnvAttributes;
type BlockExecutorFactory = Self;
type BlockAssembler = CustomBlockAssembler;
fn block_executor_factory(&self) -> &Self::BlockExecutorFactory {
self
}
fn block_assembler(&self) -> &Self::BlockAssembler {
&self.block_assembler
}
fn evm_env(&self, header: &CustomHeader) -> Result<EvmEnv<OpSpecId>, Self::Error> {
self.inner.evm_env(header)
}
fn next_evm_env(
&self,
parent: &CustomHeader,
attributes: &CustomNextBlockEnvAttributes,
) -> Result<EvmEnv<OpSpecId>, Self::Error> {
self.inner.next_evm_env(parent, &attributes.inner)
}
fn context_for_block(
&self,
block: &SealedBlock<Block>,
) -> Result<CustomBlockExecutionCtx, Self::Error> {
Ok(CustomBlockExecutionCtx {
inner: OpBlockExecutionCtx {
parent_hash: block.header().parent_hash(),
parent_beacon_block_root: block.header().parent_beacon_block_root(),
extra_data: block.header().extra_data().clone(),
},
extension: block.extension,
})
}
fn context_for_next_block(
&self,
parent: &SealedHeader<CustomHeader>,
attributes: Self::NextBlockEnvCtx,
) -> Result<CustomBlockExecutionCtx, Self::Error> {
Ok(CustomBlockExecutionCtx {
inner: OpBlockExecutionCtx {
parent_hash: parent.hash(),
parent_beacon_block_root: attributes.inner.parent_beacon_block_root,
extra_data: attributes.inner.extra_data,
},
extension: attributes.extension,
})
}
}
impl ConfigureEngineEvm<CustomExecutionData> for CustomEvmConfig {
fn evm_env_for_payload(
&self,
payload: &CustomExecutionData,
) -> Result<EvmEnvFor<Self>, Self::Error> {
self.inner.evm_env_for_payload(&payload.inner)
}
fn context_for_payload<'a>(
&self,
payload: &'a CustomExecutionData,
) -> Result<ExecutionCtxFor<'a, Self>, Self::Error> {
Ok(CustomBlockExecutionCtx {
inner: self.inner.context_for_payload(&payload.inner)?,
extension: payload.extension,
})
}
fn tx_iterator_for_payload(
&self,
payload: &CustomExecutionData,
) -> Result<impl ExecutableTxIterator<Self>, Self::Error> {
let transactions = payload.inner.payload.transactions().clone();
let convert = |encoded: Bytes| {
let tx = CustomTransaction::decode_2718_exact(encoded.as_ref())
.map_err(Into::into)
.map_err(PayloadError::Decode)?;
let signer = tx.try_recover().map_err(NewPayloadError::other)?;
Ok::<_, NewPayloadError>(WithEncoded::new(encoded, tx.with_signer(signer)))
};
Ok((transactions, convert))
}
}
/// Additional parameters required for executing next block of custom transactions.
#[derive(Debug, Clone)]
pub struct CustomNextBlockEnvAttributes {
inner: OpNextBlockEnvAttributes,
extension: u64,
}
impl From<OpFlashblockPayloadBase> for CustomNextBlockEnvAttributes {
fn from(value: OpFlashblockPayloadBase) -> Self {
Self { inner: value.into(), extension: 0 }
}
}
impl BuildPendingEnv<CustomHeader> for CustomNextBlockEnvAttributes {
fn build_pending_env(parent: &SealedHeader<CustomHeader>) -> Self {
Self {
inner: OpNextBlockEnvAttributes::build_pending_env(parent),
extension: parent.extension,
}
}
}
impl<H, ChainSpec> BuildNextEnv<CustomPayloadBuilderAttributes, H, ChainSpec>
for CustomNextBlockEnvAttributes
where
H: BlockHeader,
ChainSpec: EthChainSpec + OpHardforks,
{
fn build_next_env(
attributes: &CustomPayloadBuilderAttributes,
parent: &SealedHeader<H>,
chain_spec: &ChainSpec,
) -> Result<Self, PayloadBuilderError> {
let inner =
OpNextBlockEnvAttributes::build_next_env(&attributes.inner, parent, chain_spec)?;
Ok(CustomNextBlockEnvAttributes { inner, extension: attributes.extension })
}
}

View File

@@ -1,340 +0,0 @@
use crate::primitives::{CustomTransaction, TxPayment};
use alloy_eips::{eip2930::AccessList, Typed2718};
use alloy_evm::{FromRecoveredTx, FromTxWithEncoded, IntoTxEnv};
use alloy_op_evm::block::OpTxEnv;
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};
/// An Optimism transaction extended by [`PaymentTxEnv`] that can be fed to [`Evm`].
///
/// [`Evm`]: alloy_evm::Evm
#[derive(Clone, Debug)]
pub enum CustomTxEnv {
Op(OpTransaction<TxEnv>),
Payment(PaymentTxEnv),
}
/// A transaction environment is a set of information related to an Ethereum transaction that can be
/// fed to [`Evm`] for execution.
///
/// [`Evm`]: alloy_evm::Evm
#[derive(Clone, Debug, Default)]
pub struct PaymentTxEnv(pub TxEnv);
impl revm::context::Transaction for CustomTxEnv {
type AccessListItem<'a>
= <TxEnv as revm::context::Transaction>::AccessListItem<'a>
where
Self: 'a;
type Authorization<'a>
= <TxEnv as revm::context::Transaction>::Authorization<'a>
where
Self: 'a;
fn tx_type(&self) -> u8 {
match self {
Self::Op(tx) => tx.tx_type(),
Self::Payment(tx) => tx.tx_type(),
}
}
fn caller(&self) -> Address {
match self {
Self::Op(tx) => tx.caller(),
Self::Payment(tx) => tx.caller(),
}
}
fn gas_limit(&self) -> u64 {
match self {
Self::Op(tx) => tx.gas_limit(),
Self::Payment(tx) => tx.gas_limit(),
}
}
fn value(&self) -> U256 {
match self {
Self::Op(tx) => tx.value(),
Self::Payment(tx) => tx.value(),
}
}
fn input(&self) -> &Bytes {
match self {
Self::Op(tx) => tx.input(),
Self::Payment(tx) => tx.input(),
}
}
fn nonce(&self) -> u64 {
match self {
Self::Op(tx) => revm::context::Transaction::nonce(tx),
Self::Payment(tx) => revm::context::Transaction::nonce(tx),
}
}
fn kind(&self) -> TxKind {
match self {
Self::Op(tx) => tx.kind(),
Self::Payment(tx) => tx.kind(),
}
}
fn chain_id(&self) -> Option<u64> {
match self {
Self::Op(tx) => tx.chain_id(),
Self::Payment(tx) => tx.chain_id(),
}
}
fn gas_price(&self) -> u128 {
match self {
Self::Op(tx) => tx.gas_price(),
Self::Payment(tx) => tx.gas_price(),
}
}
fn access_list(&self) -> Option<impl Iterator<Item = Self::AccessListItem<'_>>> {
Some(match self {
Self::Op(tx) => tx.base.access_list.iter(),
Self::Payment(tx) => tx.0.access_list.iter(),
})
}
fn blob_versioned_hashes(&self) -> &[B256] {
match self {
Self::Op(tx) => tx.blob_versioned_hashes(),
Self::Payment(tx) => tx.blob_versioned_hashes(),
}
}
fn max_fee_per_blob_gas(&self) -> u128 {
match self {
Self::Op(tx) => tx.max_fee_per_blob_gas(),
Self::Payment(tx) => tx.max_fee_per_blob_gas(),
}
}
fn authorization_list_len(&self) -> usize {
match self {
Self::Op(tx) => tx.authorization_list_len(),
Self::Payment(tx) => tx.authorization_list_len(),
}
}
fn authorization_list(&self) -> impl Iterator<Item = Self::Authorization<'_>> {
match self {
Self::Op(tx) => tx.base.authorization_list.iter(),
Self::Payment(tx) => tx.0.authorization_list.iter(),
}
}
fn max_priority_fee_per_gas(&self) -> Option<u128> {
match self {
Self::Op(tx) => tx.max_priority_fee_per_gas(),
Self::Payment(tx) => tx.max_priority_fee_per_gas(),
}
}
}
impl revm::context::Transaction for PaymentTxEnv {
type AccessListItem<'a>
= <TxEnv as revm::context::Transaction>::AccessListItem<'a>
where
Self: 'a;
type Authorization<'a>
= <TxEnv as revm::context::Transaction>::Authorization<'a>
where
Self: 'a;
fn tx_type(&self) -> u8 {
self.0.tx_type()
}
fn caller(&self) -> Address {
self.0.caller()
}
fn gas_limit(&self) -> u64 {
self.0.gas_limit()
}
fn value(&self) -> U256 {
self.0.value()
}
fn input(&self) -> &Bytes {
self.0.input()
}
fn nonce(&self) -> u64 {
revm::context::Transaction::nonce(&self.0)
}
fn kind(&self) -> TxKind {
self.0.kind()
}
fn chain_id(&self) -> Option<u64> {
self.0.chain_id()
}
fn gas_price(&self) -> u128 {
self.0.gas_price()
}
fn access_list(&self) -> Option<impl Iterator<Item = Self::AccessListItem<'_>>> {
self.0.access_list()
}
fn blob_versioned_hashes(&self) -> &[B256] {
self.0.blob_versioned_hashes()
}
fn max_fee_per_blob_gas(&self) -> u128 {
self.0.max_fee_per_blob_gas()
}
fn authorization_list_len(&self) -> usize {
self.0.authorization_list_len()
}
fn authorization_list(&self) -> impl Iterator<Item = Self::Authorization<'_>> {
self.0.authorization_list()
}
fn max_priority_fee_per_gas(&self) -> Option<u128> {
self.0.max_priority_fee_per_gas()
}
}
impl TransactionEnv for PaymentTxEnv {
fn set_gas_limit(&mut self, gas_limit: u64) {
self.0.set_gas_limit(gas_limit);
}
fn nonce(&self) -> u64 {
self.0.nonce()
}
fn set_nonce(&mut self, nonce: u64) {
self.0.set_nonce(nonce);
}
fn set_access_list(&mut self, access_list: AccessList) {
self.0.set_access_list(access_list);
}
}
impl TransactionEnv for CustomTxEnv {
fn set_gas_limit(&mut self, gas_limit: u64) {
match self {
Self::Op(tx) => tx.set_gas_limit(gas_limit),
Self::Payment(tx) => tx.set_gas_limit(gas_limit),
}
}
fn nonce(&self) -> u64 {
match self {
Self::Op(tx) => tx.nonce(),
Self::Payment(tx) => tx.nonce(),
}
}
fn set_nonce(&mut self, nonce: u64) {
match self {
Self::Op(tx) => tx.set_nonce(nonce),
Self::Payment(tx) => tx.set_nonce(nonce),
}
}
fn set_access_list(&mut self, access_list: AccessList) {
match self {
Self::Op(tx) => tx.set_access_list(access_list),
Self::Payment(tx) => tx.set_access_list(access_list),
}
}
}
impl FromRecoveredTx<TxPayment> for TxEnv {
fn from_recovered_tx(tx: &TxPayment, caller: Address) -> Self {
let TxPayment {
chain_id,
nonce,
gas_limit,
max_fee_per_gas,
max_priority_fee_per_gas,
to,
value,
} = tx;
Self {
tx_type: tx.ty(),
caller,
gas_limit: *gas_limit,
gas_price: *max_fee_per_gas,
gas_priority_fee: Some(*max_priority_fee_per_gas),
kind: TxKind::Call(*to),
value: *value,
nonce: *nonce,
chain_id: Some(*chain_id),
..Default::default()
}
}
}
impl FromTxWithEncoded<TxPayment> for TxEnv {
fn from_encoded_tx(tx: &TxPayment, sender: Address, _encoded: Bytes) -> Self {
Self::from_recovered_tx(tx, sender)
}
}
impl FromRecoveredTx<OpTxEnvelope> for CustomTxEnv {
fn from_recovered_tx(tx: &OpTxEnvelope, sender: Address) -> Self {
Self::Op(OpTransaction::from_recovered_tx(tx, sender))
}
}
impl FromTxWithEncoded<OpTxEnvelope> for CustomTxEnv {
fn from_encoded_tx(tx: &OpTxEnvelope, sender: Address, encoded: Bytes) -> Self {
Self::Op(OpTransaction::from_encoded_tx(tx, sender, encoded))
}
}
impl FromRecoveredTx<CustomTransaction> for CustomTxEnv {
fn from_recovered_tx(tx: &CustomTransaction, sender: Address) -> Self {
match tx {
CustomTransaction::Op(tx) => Self::from_recovered_tx(tx, sender),
CustomTransaction::Payment(tx) => {
Self::Payment(PaymentTxEnv(TxEnv::from_recovered_tx(tx.tx(), sender)))
}
}
}
}
impl FromTxWithEncoded<CustomTransaction> for CustomTxEnv {
fn from_encoded_tx(tx: &CustomTransaction, sender: Address, encoded: Bytes) -> Self {
match tx {
CustomTransaction::Op(tx) => Self::from_encoded_tx(tx, sender, encoded),
CustomTransaction::Payment(tx) => {
Self::Payment(PaymentTxEnv(TxEnv::from_encoded_tx(tx.tx(), sender, encoded)))
}
}
}
}
impl IntoTxEnv<Self> for CustomTxEnv {
fn into_tx_env(self) -> Self {
self
}
}
impl OpTxEnv for CustomTxEnv {
fn encoded_bytes(&self) -> Option<&Bytes> {
match self {
Self::Op(tx) => tx.encoded_bytes(),
Self::Payment(_) => None,
}
}
}

View File

@@ -1,120 +0,0 @@
use crate::{
evm::{
alloy::{CustomEvm, CustomEvmFactory},
CustomEvmConfig, CustomTxEnv,
},
primitives::CustomTransaction,
};
use alloy_consensus::transaction::Recovered;
use alloy_evm::{
block::{
BlockExecutionError, BlockExecutionResult, BlockExecutor, BlockExecutorFactory,
BlockExecutorFor, ExecutableTx, OnStateHook,
},
precompiles::PrecompilesMap,
Database, Evm, RecoveredTx,
};
use alloy_op_evm::{block::OpTxResult, OpBlockExecutionCtx, OpBlockExecutor};
use reth_ethereum::evm::primitives::InspectorFor;
use reth_op::{chainspec::OpChainSpec, node::OpRethReceiptBuilder, OpReceipt, OpTxType};
use revm::database::State;
use std::sync::Arc;
pub struct CustomBlockExecutor<Evm> {
inner: OpBlockExecutor<Evm, OpRethReceiptBuilder, Arc<OpChainSpec>>,
}
impl<'db, DB, E> BlockExecutor for CustomBlockExecutor<E>
where
DB: Database + 'db,
E: Evm<DB = &'db mut State<DB>, Tx = CustomTxEnv>,
{
type Transaction = CustomTransaction;
type Receipt = OpReceipt;
type Evm = E;
type Result = OpTxResult<E::HaltReason, OpTxType>;
fn apply_pre_execution_changes(&mut self) -> Result<(), BlockExecutionError> {
self.inner.apply_pre_execution_changes()
}
fn receipts(&self) -> &[Self::Receipt] {
self.inner.receipts()
}
fn execute_transaction_without_commit(
&mut self,
tx: impl ExecutableTx<Self>,
) -> Result<Self::Result, BlockExecutionError> {
let tx = tx.into_parts().1;
match tx.tx() {
CustomTransaction::Op(op_tx) => self
.inner
.execute_transaction_without_commit(Recovered::new_unchecked(op_tx, *tx.signer())),
CustomTransaction::Payment(..) => todo!(),
}
}
fn commit_transaction(&mut self, output: Self::Result) -> Result<u64, BlockExecutionError> {
self.inner.commit_transaction(output)
}
fn finish(self) -> Result<(Self::Evm, BlockExecutionResult<OpReceipt>), BlockExecutionError> {
self.inner.finish()
}
fn set_state_hook(&mut self, _hook: Option<Box<dyn OnStateHook>>) {
self.inner.set_state_hook(_hook)
}
fn evm_mut(&mut self) -> &mut Self::Evm {
self.inner.evm_mut()
}
fn evm(&self) -> &Self::Evm {
self.inner.evm()
}
}
impl BlockExecutorFactory for CustomEvmConfig {
type EvmFactory = CustomEvmFactory;
type ExecutionCtx<'a> = CustomBlockExecutionCtx;
type Transaction = CustomTransaction;
type Receipt = OpReceipt;
fn evm_factory(&self) -> &Self::EvmFactory {
&self.custom_evm_factory
}
fn create_executor<'a, DB, I>(
&'a self,
evm: CustomEvm<&'a mut State<DB>, I, PrecompilesMap>,
ctx: CustomBlockExecutionCtx,
) -> impl BlockExecutorFor<'a, Self, DB, I>
where
DB: Database + 'a,
I: InspectorFor<Self, &'a mut State<DB>> + 'a,
{
CustomBlockExecutor {
inner: OpBlockExecutor::new(
evm,
ctx.inner,
self.inner.chain_spec().clone(),
*self.inner.executor_factory.receipt_builder(),
),
}
}
}
/// Additional parameters for executing custom transactions.
#[derive(Debug, Clone)]
pub struct CustomBlockExecutionCtx {
pub inner: OpBlockExecutionCtx,
pub extension: u64,
}
impl From<CustomBlockExecutionCtx> for OpBlockExecutionCtx {
fn from(value: CustomBlockExecutionCtx) -> Self {
value.inner
}
}

View File

@@ -1,13 +0,0 @@
mod alloy;
mod assembler;
mod builder;
mod config;
mod env;
mod executor;
pub use alloy::{CustomContext, CustomEvm};
pub use assembler::CustomBlockAssembler;
pub use builder::CustomExecutorBuilder;
pub use config::CustomEvmConfig;
pub use env::{CustomTxEnv, PaymentTxEnv};
pub use executor::CustomBlockExecutor;

View File

@@ -1,86 +0,0 @@
//! This example shows how to implement a custom node.
//!
//! A node consists of:
//! - primitives: block,header,transactions
//! - components: network,pool,evm
//! - engine: advances the node
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
use crate::{
engine::{CustomEngineValidatorBuilder, CustomPayloadTypes},
engine_api::CustomEngineApiBuilder,
evm::CustomExecutorBuilder,
pool::CustomPooledTransaction,
primitives::CustomTransaction,
rpc::CustomRpcTypes,
};
use chainspec::CustomChainSpec;
use primitives::CustomNodePrimitives;
use reth_ethereum::node::api::{FullNodeTypes, NodeTypes};
use reth_node_builder::{
components::{BasicPayloadServiceBuilder, ComponentsBuilder},
Node, NodeAdapter,
};
use reth_op::{
node::{
node::{OpConsensusBuilder, OpNetworkBuilder, OpPayloadBuilder, OpPoolBuilder},
txpool, OpAddOns, OpNode,
},
rpc::OpEthApiBuilder,
};
pub mod chainspec;
pub mod engine;
pub mod engine_api;
pub mod evm;
pub mod pool;
pub mod primitives;
pub mod rpc;
#[derive(Debug, Clone)]
pub struct CustomNode {
inner: OpNode,
}
impl NodeTypes for CustomNode {
type Primitives = CustomNodePrimitives;
type ChainSpec = CustomChainSpec;
type Storage = <OpNode as NodeTypes>::Storage;
type Payload = CustomPayloadTypes;
}
impl<N> Node<N> for CustomNode
where
N: FullNodeTypes<Types = Self>,
{
type ComponentsBuilder = ComponentsBuilder<
N,
OpPoolBuilder<txpool::OpPooledTransaction<CustomTransaction, CustomPooledTransaction>>,
BasicPayloadServiceBuilder<OpPayloadBuilder>,
OpNetworkBuilder,
CustomExecutorBuilder,
OpConsensusBuilder,
>;
type AddOns = OpAddOns<
NodeAdapter<N>,
OpEthApiBuilder<CustomRpcTypes>,
CustomEngineValidatorBuilder,
CustomEngineApiBuilder,
>;
fn components_builder(&self) -> Self::ComponentsBuilder {
ComponentsBuilder::default()
.node_types::<N>()
.pool(OpPoolBuilder::default())
.executor(CustomExecutorBuilder::default())
.payload(BasicPayloadServiceBuilder::new(OpPayloadBuilder::new(false)))
.network(OpNetworkBuilder::new(false, false))
.consensus(OpConsensusBuilder::default())
}
fn add_ons(&self) -> Self::AddOns {
self.inner.add_ons_builder().build()
}
}

View File

@@ -1,93 +0,0 @@
use crate::primitives::{CustomTransaction, TxPayment};
use alloy_consensus::{
crypto::RecoveryError,
error::ValueError,
transaction::{SignerRecoverable, TxHashRef},
Signed, TransactionEnvelope,
};
use alloy_primitives::{Address, Sealed, B256};
use op_alloy_consensus::{OpPooledTransaction, OpTransaction, TxDeposit};
use reth_ethereum::primitives::{
serde_bincode_compat::RlpBincode, InMemorySize, SignedTransaction,
};
#[derive(Clone, Debug, TransactionEnvelope)]
#[envelope(tx_type_name = CustomPooledTxType)]
pub enum CustomPooledTransaction {
/// A regular Optimism transaction as defined by [`OpPooledTransaction`].
#[envelope(flatten)]
Op(OpPooledTransaction),
/// A [`TxPayment`] tagged with type 0x2A (decimal 42).
#[envelope(ty = 42)]
Payment(Signed<TxPayment>),
}
impl From<CustomPooledTransaction> for CustomTransaction {
fn from(tx: CustomPooledTransaction) -> Self {
match tx {
CustomPooledTransaction::Op(tx) => Self::Op(tx.into()),
CustomPooledTransaction::Payment(tx) => Self::Payment(tx),
}
}
}
impl TryFrom<CustomTransaction> for CustomPooledTransaction {
type Error = ValueError<CustomTransaction>;
fn try_from(tx: CustomTransaction) -> Result<Self, Self::Error> {
match tx {
CustomTransaction::Op(op) => Ok(Self::Op(
OpPooledTransaction::try_from(op).map_err(|op| op.map(CustomTransaction::Op))?,
)),
CustomTransaction::Payment(payment) => Ok(Self::Payment(payment)),
}
}
}
impl RlpBincode for CustomPooledTransaction {}
impl OpTransaction for CustomPooledTransaction {
fn is_deposit(&self) -> bool {
false
}
fn as_deposit(&self) -> Option<&Sealed<TxDeposit>> {
None
}
}
impl SignerRecoverable for CustomPooledTransaction {
fn recover_signer(&self) -> Result<Address, RecoveryError> {
match self {
CustomPooledTransaction::Op(tx) => SignerRecoverable::recover_signer(tx),
CustomPooledTransaction::Payment(tx) => SignerRecoverable::recover_signer(tx),
}
}
fn recover_signer_unchecked(&self) -> Result<Address, RecoveryError> {
match self {
CustomPooledTransaction::Op(tx) => SignerRecoverable::recover_signer_unchecked(tx),
CustomPooledTransaction::Payment(tx) => SignerRecoverable::recover_signer_unchecked(tx),
}
}
}
impl TxHashRef for CustomPooledTransaction {
fn tx_hash(&self) -> &B256 {
match self {
CustomPooledTransaction::Op(tx) => tx.tx_hash(),
CustomPooledTransaction::Payment(tx) => tx.hash(),
}
}
}
impl SignedTransaction for CustomPooledTransaction {}
impl InMemorySize for CustomPooledTransaction {
fn size(&self) -> usize {
match self {
CustomPooledTransaction::Op(tx) => InMemorySize::size(tx),
CustomPooledTransaction::Payment(tx) => InMemorySize::size(tx),
}
}
}

View File

@@ -1,7 +0,0 @@
use crate::primitives::{CustomHeader, CustomTransaction};
/// The Block type of this node
pub type Block = alloy_consensus::Block<CustomTransaction, CustomHeader>;
/// The body type of this node
pub type BlockBody = alloy_consensus::BlockBody<CustomTransaction, CustomHeader>;

View File

@@ -1,184 +0,0 @@
use alloy_consensus::Header;
use alloy_primitives::{Address, BlockNumber, Bloom, Bytes, Sealable, B256, B64, U256};
use alloy_rlp::{Encodable, RlpDecodable, RlpEncodable};
use reth_codecs::Compact;
use reth_ethereum::primitives::{serde_bincode_compat::RlpBincode, BlockHeader, InMemorySize};
use revm_primitives::keccak256;
use serde::{Deserialize, Serialize};
/// The header type of this node
///
/// This type extends the regular ethereum header with an extension.
#[derive(
Clone,
Debug,
PartialEq,
Eq,
Hash,
derive_more::AsRef,
derive_more::Deref,
Default,
RlpEncodable,
RlpDecodable,
Serialize,
Deserialize,
)]
#[serde(rename_all = "camelCase")]
pub struct CustomHeader {
/// The regular eth header
#[as_ref]
#[deref]
#[serde(flatten)]
pub inner: Header,
/// The extended header
pub extension: u64,
}
impl From<Header> for CustomHeader {
fn from(value: Header) -> Self {
CustomHeader { inner: value, extension: 0 }
}
}
impl AsRef<Self> for CustomHeader {
fn as_ref(&self) -> &Self {
self
}
}
impl Sealable for CustomHeader {
fn hash_slow(&self) -> B256 {
let mut out = Vec::new();
self.encode(&mut out);
keccak256(&out)
}
}
impl alloy_consensus::BlockHeader for CustomHeader {
fn parent_hash(&self) -> B256 {
self.inner.parent_hash()
}
fn ommers_hash(&self) -> B256 {
self.inner.ommers_hash()
}
fn beneficiary(&self) -> Address {
self.inner.beneficiary()
}
fn state_root(&self) -> B256 {
self.inner.state_root()
}
fn transactions_root(&self) -> B256 {
self.inner.transactions_root()
}
fn receipts_root(&self) -> B256 {
self.inner.receipts_root()
}
fn withdrawals_root(&self) -> Option<B256> {
self.inner.withdrawals_root()
}
fn logs_bloom(&self) -> Bloom {
self.inner.logs_bloom()
}
fn difficulty(&self) -> U256 {
self.inner.difficulty()
}
fn number(&self) -> BlockNumber {
self.inner.number()
}
fn gas_limit(&self) -> u64 {
self.inner.gas_limit()
}
fn gas_used(&self) -> u64 {
self.inner.gas_used()
}
fn timestamp(&self) -> u64 {
self.inner.timestamp()
}
fn mix_hash(&self) -> Option<B256> {
self.inner.mix_hash()
}
fn nonce(&self) -> Option<B64> {
self.inner.nonce()
}
fn base_fee_per_gas(&self) -> Option<u64> {
self.inner.base_fee_per_gas()
}
fn blob_gas_used(&self) -> Option<u64> {
self.inner.blob_gas_used()
}
fn excess_blob_gas(&self) -> Option<u64> {
self.inner.excess_blob_gas()
}
fn parent_beacon_block_root(&self) -> Option<B256> {
self.inner.parent_beacon_block_root()
}
fn requests_hash(&self) -> Option<B256> {
self.inner.requests_hash()
}
fn extra_data(&self) -> &Bytes {
self.inner.extra_data()
}
}
impl InMemorySize for CustomHeader {
fn size(&self) -> usize {
self.inner.size() + self.extension.size()
}
}
impl reth_codecs::Compact for CustomHeader {
fn to_compact<B>(&self, buf: &mut B) -> usize
where
B: alloy_rlp::bytes::BufMut + AsMut<[u8]>,
{
let identifier = self.inner.to_compact(buf);
self.extension.to_compact(buf);
identifier
}
fn from_compact(buf: &[u8], identifier: usize) -> (Self, &[u8]) {
let (eth_header, buf) = Compact::from_compact(buf, identifier);
let (extension, buf) = Compact::from_compact(buf, buf.len());
(Self { inner: eth_header, extension }, buf)
}
}
impl reth_db_api::table::Compress for CustomHeader {
type Compressed = Vec<u8>;
fn compress_to_buf<B: alloy_primitives::bytes::BufMut + AsMut<[u8]>>(&self, buf: &mut B) {
let _ = Compact::to_compact(self, buf);
}
}
impl reth_db_api::table::Decompress for CustomHeader {
fn decompress(value: &[u8]) -> Result<Self, reth_db_api::DatabaseError> {
let (obj, _) = Compact::from_compact(value, value.len());
Ok(obj)
}
}
impl BlockHeader for CustomHeader {}
impl RlpBincode for CustomHeader {}

View File

@@ -1,27 +0,0 @@
//! Contains the primitive types of this node.
pub mod header;
pub use header::*;
pub mod block;
pub use block::*;
pub mod tx;
pub use tx::*;
pub mod tx_type;
pub use tx_type::*;
pub mod tx_custom;
pub use tx_custom::*;
use reth_ethereum::primitives::NodePrimitives;
use reth_op::OpReceipt;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct CustomNodePrimitives;
impl NodePrimitives for CustomNodePrimitives {
type Block = Block;
type BlockHeader = CustomHeader;
type BlockBody = BlockBody;
type SignedTx = CustomTransaction;
type Receipt = OpReceipt;
}

View File

@@ -1,144 +0,0 @@
use super::TxPayment;
use alloy_consensus::{
crypto::RecoveryError,
transaction::{SignerRecoverable, TxHashRef},
Signed, TransactionEnvelope,
};
use alloy_eips::Encodable2718;
use alloy_primitives::{Sealed, Signature, B256};
use alloy_rlp::BufMut;
use op_alloy_consensus::{OpTxEnvelope, TxDeposit};
use reth_codecs::{
alloy::transaction::{CompactEnvelope, FromTxCompact, ToTxCompact},
Compact,
};
use reth_ethereum::primitives::{serde_bincode_compat::RlpBincode, InMemorySize};
use reth_op::{primitives::SignedTransaction, OpTransaction};
use revm_primitives::Address;
/// Either [`OpTxEnvelope`] or [`TxPayment`].
#[derive(Debug, Clone, TransactionEnvelope)]
#[envelope(tx_type_name = TxTypeCustom)]
pub enum CustomTransaction {
/// A regular Optimism transaction as defined by [`OpTxEnvelope`].
#[envelope(flatten)]
Op(OpTxEnvelope),
/// A [`TxPayment`] tagged with type 0x2A (decimal 42).
#[envelope(ty = 42)]
Payment(Signed<TxPayment>),
}
impl RlpBincode for CustomTransaction {}
impl reth_codecs::alloy::transaction::Envelope for CustomTransaction {
fn signature(&self) -> &Signature {
match self {
CustomTransaction::Op(tx) => reth_codecs::alloy::transaction::Envelope::signature(tx),
CustomTransaction::Payment(tx) => tx.signature(),
}
}
fn tx_type(&self) -> Self::TxType {
match self {
CustomTransaction::Op(tx) => TxTypeCustom::Op(tx.tx_type()),
CustomTransaction::Payment(_) => TxTypeCustom::Payment,
}
}
}
impl FromTxCompact for CustomTransaction {
type TxType = TxTypeCustom;
fn from_tx_compact(buf: &[u8], tx_type: Self::TxType, signature: Signature) -> (Self, &[u8])
where
Self: Sized,
{
match tx_type {
TxTypeCustom::Op(tx_type) => {
let (tx, buf) = OpTxEnvelope::from_tx_compact(buf, tx_type, signature);
(Self::Op(tx), buf)
}
TxTypeCustom::Payment => {
let (tx, buf) = TxPayment::from_compact(buf, buf.len());
let tx = Signed::new_unhashed(tx, signature);
(Self::Payment(tx), buf)
}
}
}
}
impl ToTxCompact for CustomTransaction {
fn to_tx_compact(&self, buf: &mut (impl BufMut + AsMut<[u8]>)) {
match self {
CustomTransaction::Op(tx) => tx.to_tx_compact(buf),
CustomTransaction::Payment(tx) => {
tx.tx().to_compact(buf);
}
}
}
}
impl Compact for CustomTransaction {
fn to_compact<B>(&self, buf: &mut B) -> usize
where
B: BufMut + AsMut<[u8]>,
{
<Self as CompactEnvelope>::to_compact(self, buf)
}
fn from_compact(buf: &[u8], len: usize) -> (Self, &[u8]) {
<Self as CompactEnvelope>::from_compact(buf, len)
}
}
impl OpTransaction for CustomTransaction {
fn is_deposit(&self) -> bool {
match self {
CustomTransaction::Op(op) => op.is_deposit(),
CustomTransaction::Payment(_) => false,
}
}
fn as_deposit(&self) -> Option<&Sealed<TxDeposit>> {
match self {
CustomTransaction::Op(op) => op.as_deposit(),
CustomTransaction::Payment(_) => None,
}
}
}
impl SignerRecoverable for CustomTransaction {
fn recover_signer(&self) -> Result<Address, RecoveryError> {
match self {
CustomTransaction::Op(tx) => SignerRecoverable::recover_signer(tx),
CustomTransaction::Payment(tx) => SignerRecoverable::recover_signer(tx),
}
}
fn recover_signer_unchecked(&self) -> Result<Address, RecoveryError> {
match self {
CustomTransaction::Op(tx) => SignerRecoverable::recover_signer_unchecked(tx),
CustomTransaction::Payment(tx) => SignerRecoverable::recover_signer_unchecked(tx),
}
}
}
impl TxHashRef for CustomTransaction {
fn tx_hash(&self) -> &B256 {
match self {
CustomTransaction::Op(tx) => TxHashRef::tx_hash(tx),
CustomTransaction::Payment(tx) => tx.hash(),
}
}
}
impl SignedTransaction for CustomTransaction {}
impl InMemorySize for CustomTransaction {
fn size(&self) -> usize {
match self {
CustomTransaction::Op(tx) => InMemorySize::size(tx),
CustomTransaction::Payment(tx) => InMemorySize::size(tx),
}
}
}

View File

@@ -1,280 +0,0 @@
use crate::primitives::PAYMENT_TX_TYPE_ID;
use alloy_consensus::{
transaction::{RlpEcdsaDecodableTx, RlpEcdsaEncodableTx},
SignableTransaction, Transaction,
};
use alloy_eips::{eip2930::AccessList, eip7702::SignedAuthorization, Typed2718};
use alloy_primitives::{Address, Bytes, ChainId, Signature, TxKind, B256, U256};
use alloy_rlp::{BufMut, Decodable, Encodable};
use reth_ethereum::primitives::{serde_bincode_compat::RlpBincode, InMemorySize};
/// A transaction with a priority fee ([EIP-1559](https://eips.ethereum.org/EIPS/eip-1559)).
#[derive(
Clone,
Debug,
Default,
PartialEq,
Eq,
Hash,
serde::Serialize,
serde::Deserialize,
reth_codecs::Compact,
)]
#[serde(rename_all = "camelCase")]
#[doc(alias = "PaymentTransaction", alias = "TransactionPayment", alias = "PaymentTx")]
pub struct TxPayment {
/// EIP-155: Simple replay attack protection
#[serde(with = "alloy_serde::quantity")]
pub chain_id: ChainId,
/// A scalar value equal to the number of transactions sent by the sender; formally Tn.
#[serde(with = "alloy_serde::quantity")]
pub nonce: u64,
/// A scalar value equal to the maximum
/// amount of gas that should be used in executing
/// this transaction. This is paid up-front, before any
/// computation is done and may not be increased
/// later; formally Tg.
#[serde(with = "alloy_serde::quantity", rename = "gas", alias = "gasLimit")]
pub gas_limit: u64,
/// A scalar value equal to the maximum
/// amount of gas that should be used in executing
/// this transaction. This is paid up-front, before any
/// computation is done and may not be increased
/// later; formally Tg.
///
/// As ethereum circulation is around 120mil eth as of 2022 that is around
/// 120000000000000000000000000 wei we are safe to use u128 as its max number is:
/// 340282366920938463463374607431768211455
///
/// This is also known as `GasFeeCap`
#[serde(with = "alloy_serde::quantity")]
pub max_fee_per_gas: u128,
/// Max Priority fee that transaction is paying
///
/// As ethereum circulation is around 120mil eth as of 2022 that is around
/// 120000000000000000000000000 wei we are safe to use u128 as its max number is:
/// 340282366920938463463374607431768211455
///
/// This is also known as `GasTipCap`
#[serde(with = "alloy_serde::quantity")]
pub max_priority_fee_per_gas: u128,
/// The 160-bit address of the message calls recipient.
pub to: Address,
/// A scalar value equal to the number of Wei to
/// be transferred to the message calls recipient or,
/// in the case of contract creation, as an endowment
/// to the newly created account; formally Tv.
pub value: U256,
}
impl TxPayment {
/// Get the transaction type
#[doc(alias = "transaction_type")]
pub const fn tx_type() -> super::tx::TxTypeCustom {
super::tx::TxTypeCustom::Payment
}
/// Calculates a heuristic for the in-memory size of the [TxPayment] transaction.
#[inline]
pub fn size(&self) -> usize {
size_of::<Self>()
}
}
impl RlpEcdsaEncodableTx for TxPayment {
/// Outputs the length of the transaction's fields, without a RLP header.
fn rlp_encoded_fields_length(&self) -> usize {
self.chain_id.length() +
self.nonce.length() +
self.max_priority_fee_per_gas.length() +
self.max_fee_per_gas.length() +
self.gas_limit.length() +
self.to.length() +
self.value.length()
}
/// Encodes only the transaction's fields into the desired buffer, without
/// a RLP header.
fn rlp_encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) {
self.chain_id.encode(out);
self.nonce.encode(out);
self.max_priority_fee_per_gas.encode(out);
self.max_fee_per_gas.encode(out);
self.gas_limit.encode(out);
self.to.encode(out);
self.value.encode(out);
}
}
impl RlpEcdsaDecodableTx for TxPayment {
const DEFAULT_TX_TYPE: u8 = { PAYMENT_TX_TYPE_ID };
/// Decodes the inner [TxPayment] fields from RLP bytes.
///
/// NOTE: This assumes a RLP header has already been decoded, and _just_
/// decodes the following RLP fields in the following order:
///
/// - `chain_id`
/// - `nonce`
/// - `max_priority_fee_per_gas`
/// - `max_fee_per_gas`
/// - `gas_limit`
/// - `to`
/// - `value`
/// - `data` (`input`)
/// - `access_list`
fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
Ok(Self {
chain_id: Decodable::decode(buf)?,
nonce: Decodable::decode(buf)?,
max_priority_fee_per_gas: Decodable::decode(buf)?,
max_fee_per_gas: Decodable::decode(buf)?,
gas_limit: Decodable::decode(buf)?,
to: Decodable::decode(buf)?,
value: Decodable::decode(buf)?,
})
}
}
impl Transaction for TxPayment {
#[inline]
fn chain_id(&self) -> Option<ChainId> {
Some(self.chain_id)
}
#[inline]
fn nonce(&self) -> u64 {
self.nonce
}
#[inline]
fn gas_limit(&self) -> u64 {
self.gas_limit
}
#[inline]
fn gas_price(&self) -> Option<u128> {
None
}
#[inline]
fn max_fee_per_gas(&self) -> u128 {
self.max_fee_per_gas
}
#[inline]
fn max_priority_fee_per_gas(&self) -> Option<u128> {
Some(self.max_priority_fee_per_gas)
}
#[inline]
fn max_fee_per_blob_gas(&self) -> Option<u128> {
None
}
#[inline]
fn priority_fee_or_price(&self) -> u128 {
self.max_priority_fee_per_gas
}
fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
base_fee.map_or(self.max_fee_per_gas, |base_fee| {
// if the tip is greater than the max priority fee per gas, set it to the max
// priority fee per gas + base fee
let tip = self.max_fee_per_gas.saturating_sub(base_fee as u128);
if tip > self.max_priority_fee_per_gas {
self.max_priority_fee_per_gas + base_fee as u128
} else {
// otherwise return the max fee per gas
self.max_fee_per_gas
}
})
}
#[inline]
fn is_dynamic_fee(&self) -> bool {
true
}
#[inline]
fn kind(&self) -> TxKind {
TxKind::Call(self.to)
}
#[inline]
fn is_create(&self) -> bool {
false
}
#[inline]
fn value(&self) -> U256 {
self.value
}
#[inline]
fn input(&self) -> &Bytes {
// No input data
static EMPTY_BYTES: Bytes = Bytes::new();
&EMPTY_BYTES
}
#[inline]
fn access_list(&self) -> Option<&AccessList> {
None
}
#[inline]
fn blob_versioned_hashes(&self) -> Option<&[B256]> {
None
}
#[inline]
fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
None
}
}
impl Typed2718 for TxPayment {
fn ty(&self) -> u8 {
PAYMENT_TX_TYPE_ID
}
}
impl SignableTransaction<Signature> for TxPayment {
fn set_chain_id(&mut self, chain_id: ChainId) {
self.chain_id = chain_id;
}
fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut) {
out.put_u8(Self::tx_type().ty());
self.encode(out)
}
fn payload_len_for_signature(&self) -> usize {
self.length() + 1
}
}
impl Encodable for TxPayment {
fn encode(&self, out: &mut dyn BufMut) {
self.rlp_encode(out);
}
fn length(&self) -> usize {
self.rlp_encoded_length()
}
}
impl Decodable for TxPayment {
fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
Self::rlp_decode(buf)
}
}
impl InMemorySize for TxPayment {
fn size(&self) -> usize {
TxPayment::size(self)
}
}
impl RlpBincode for TxPayment {}

View File

@@ -1,39 +0,0 @@
use crate::primitives::TxTypeCustom;
use alloy_primitives::bytes::{Buf, BufMut};
use reth_codecs::{txtype::COMPACT_EXTENDED_IDENTIFIER_FLAG, Compact};
pub const PAYMENT_TX_TYPE_ID: u8 = 42;
impl Compact for TxTypeCustom {
fn to_compact<B>(&self, buf: &mut B) -> usize
where
B: BufMut + AsMut<[u8]>,
{
match self {
Self::Op(ty) => ty.to_compact(buf),
Self::Payment => {
buf.put_u8(PAYMENT_TX_TYPE_ID);
COMPACT_EXTENDED_IDENTIFIER_FLAG
}
}
}
fn from_compact(mut buf: &[u8], identifier: usize) -> (Self, &[u8]) {
match identifier {
COMPACT_EXTENDED_IDENTIFIER_FLAG => (
{
let extended_identifier = buf.get_u8();
match extended_identifier {
PAYMENT_TX_TYPE_ID => Self::Payment,
_ => panic!("Unsupported TxType identifier: {extended_identifier}"),
}
},
buf,
),
v => {
let (inner, buf) = TxTypeCustom::from_compact(buf, v);
(inner, buf)
}
}
}
}

View File

@@ -1,53 +0,0 @@
use crate::{
evm::CustomTxEnv,
primitives::{CustomHeader, CustomTransaction},
};
use alloy_consensus::error::ValueError;
use alloy_evm::EvmEnv;
use alloy_network::TxSigner;
use op_alloy_consensus::OpTxEnvelope;
use op_alloy_rpc_types::{OpTransactionReceipt, OpTransactionRequest};
use reth_op::rpc::RpcTypes;
use reth_rpc_api::eth::{
EthTxEnvError, SignTxRequestError, SignableTxRequest, TryIntoSimTx, TryIntoTxEnv,
};
use revm::context::BlockEnv;
#[derive(Debug, Clone, Copy, Default)]
#[non_exhaustive]
pub struct CustomRpcTypes;
impl RpcTypes for CustomRpcTypes {
type Header = alloy_rpc_types_eth::Header<CustomHeader>;
type Receipt = OpTransactionReceipt;
type TransactionRequest = OpTransactionRequest;
type TransactionResponse = op_alloy_rpc_types::Transaction<CustomTransaction>;
}
impl TryIntoSimTx<CustomTransaction> for OpTransactionRequest {
fn try_into_sim_tx(self) -> Result<CustomTransaction, ValueError<Self>> {
Ok(CustomTransaction::Op(self.try_into_sim_tx()?))
}
}
impl TryIntoTxEnv<CustomTxEnv> for OpTransactionRequest {
type Err = EthTxEnvError;
fn try_into_tx_env<Spec>(
self,
evm_env: &EvmEnv<Spec, BlockEnv>,
) -> Result<CustomTxEnv, Self::Err> {
Ok(CustomTxEnv::Op(self.try_into_tx_env(evm_env)?))
}
}
impl SignableTxRequest<CustomTransaction> for OpTransactionRequest {
async fn try_build_and_sign(
self,
signer: impl TxSigner<alloy_primitives::Signature> + Send,
) -> Result<CustomTransaction, SignTxRequestError> {
Ok(CustomTransaction::Op(
SignableTxRequest::<OpTxEnvelope>::try_build_and_sign(self, signer).await?,
))
}
}

View File

@@ -1,15 +0,0 @@
[package]
name = "example-engine-api-access"
version = "0.0.0"
publish = false
edition.workspace = true
license.workspace = true
[dependencies]
# reth
reth-db = { workspace = true, features = ["op", "test-utils"] }
reth-node-builder.workspace = true
reth-optimism-node.workspace = true
reth-optimism-chainspec.workspace = true
tokio = { workspace = true, features = ["sync"] }

View File

@@ -1,47 +0,0 @@
//! Example demonstrating how to access the Engine API instance during construction.
//!
//! Run with
//!
//! ```sh
//! cargo run -p example-engine-api-access
//! ```
use reth_db::test_utils::create_test_rw_db;
use reth_node_builder::{EngineApiExt, FullNodeComponents, NodeBuilder, NodeConfig};
use reth_optimism_chainspec::BASE_MAINNET;
use reth_optimism_node::{
args::RollupArgs, node::OpEngineValidatorBuilder, OpAddOns, OpEngineApiBuilder, OpNode,
};
use tokio::sync::oneshot;
#[tokio::main]
async fn main() {
// Op node configuration and setup
let config = NodeConfig::new(BASE_MAINNET.clone());
let db = create_test_rw_db();
let args = RollupArgs::default();
let op_node = OpNode::new(args);
let (engine_api_tx, _engine_api_rx) = oneshot::channel();
let engine_api =
EngineApiExt::new(OpEngineApiBuilder::<OpEngineValidatorBuilder>::default(), move |api| {
let _ = engine_api_tx.send(api);
});
let _builder = NodeBuilder::new(config)
.with_database(db)
.with_types::<OpNode>()
.with_components(op_node.components())
.with_add_ons(OpAddOns::default().with_engine_api(engine_api))
.on_component_initialized(move |ctx| {
let _provider = ctx.provider();
Ok(())
})
.on_node_started(|_full_node| Ok(()))
.on_rpc_started(|_ctx, handles| {
let _client = handles.rpc.http_client();
Ok(())
})
.check_launch();
}

View File

@@ -1,17 +0,0 @@
[package]
name = "example-exex-hello-world"
version = "0.0.0"
publish = false
edition.workspace = true
license.workspace = true
[dependencies]
# reth
reth-ethereum = { workspace = true, features = ["full", "cli"] }
reth-tracing.workspace = true
eyre.workspace = true
futures.workspace = true
clap = { workspace = true, features = ["derive"] }
reth-op = { workspace = true, features = ["full", "cli"] }
tokio = { workspace = true, features = ["sync"] }

View File

@@ -1,145 +0,0 @@
//! Example for a simple Execution Extension
//!
//! Run with
//!
//! ```sh
//! cargo run -p example-exex-hello-world -- node --dev --dev.block-time 5s
//! ```
use clap::Parser;
use futures::TryStreamExt;
use reth_ethereum::{
chainspec::EthereumHardforks,
exex::{ExExContext, ExExEvent, ExExNotification},
node::{
api::{FullNodeComponents, NodeTypes},
builder::rpc::RpcHandle,
EthereumNode,
},
rpc::api::eth::helpers::FullEthApi,
};
use reth_tracing::tracing::info;
use tokio::sync::oneshot;
/// Additional CLI arguments
#[derive(Parser)]
struct ExExArgs {
/// whether to launch an op-reth node
#[arg(long)]
optimism: bool,
}
/// A basic subscription loop of new blocks.
async fn my_exex<Node: FullNodeComponents>(mut ctx: ExExContext<Node>) -> eyre::Result<()> {
while let Some(notification) = ctx.notifications.try_next().await? {
match &notification {
ExExNotification::ChainCommitted { new } => {
info!(committed_chain = ?new.range(), "Received commit");
}
ExExNotification::ChainReorged { old, new } => {
info!(from_chain = ?old.range(), to_chain = ?new.range(), "Received reorg");
}
ExExNotification::ChainReverted { old } => {
info!(reverted_chain = ?old.range(), "Received revert");
}
};
if let Some(committed_chain) = notification.committed_chain() {
ctx.events.send(ExExEvent::FinishedHeight(committed_chain.tip().num_hash()))?;
}
}
Ok(())
}
/// This is an example of how to access the [`RpcHandle`] inside an ExEx. It receives the
/// [`RpcHandle`] once the node is launched fully.
///
/// This function supports both Opstack Eth API and ethereum Eth API.
///
/// The received handle gives access to the `EthApi` has full access to all eth api functionality
/// [`FullEthApi`]. And also gives access to additional eth-related rpc method handlers, such as eth
/// filter.
async fn ethapi_exex<Node, EthApi>(
mut ctx: ExExContext<Node>,
rpc_handle: oneshot::Receiver<RpcHandle<Node, EthApi>>,
) -> eyre::Result<()>
where
Node: FullNodeComponents<Types: NodeTypes<ChainSpec: EthereumHardforks>>,
EthApi: FullEthApi,
{
// Wait for the ethapi to be sent from the main function
let rpc_handle = rpc_handle.await?;
info!("Received rpc handle inside exex");
// obtain the ethapi from the rpc handle
let ethapi = rpc_handle.eth_api();
// EthFilter type that provides all eth_getlogs related logic
let _eth_filter = rpc_handle.eth_handlers().filter.clone();
// EthPubSub type that provides all eth_subscribe logic
let _eth_pubsub = rpc_handle.eth_handlers().pubsub.clone();
// The TraceApi type that provides all the trace_ handlers
let _trace_api = rpc_handle.trace_api();
// The DebugApi type that provides all the debug_ handlers
let _debug_api = rpc_handle.debug_api();
while let Some(notification) = ctx.notifications.try_next().await? {
if let Some(committed_chain) = notification.committed_chain() {
ctx.events.send(ExExEvent::FinishedHeight(committed_chain.tip().num_hash()))?;
// can use the eth api to interact with the node
let _rpc_block = ethapi.rpc_block(committed_chain.tip().hash().into(), true).await?;
}
}
Ok(())
}
fn main() -> eyre::Result<()> {
let args = ExExArgs::parse();
if args.optimism {
reth_op::cli::Cli::parse_args().run(|builder, _| {
let (rpc_handle_tx, rpc_handle_rx) = oneshot::channel();
Box::pin(async move {
let handle = builder
.node(reth_op::node::OpNode::default())
.install_exex("my-exex", async move |ctx| Ok(my_exex(ctx)))
.install_exex("ethapi-exex", async move |ctx| {
Ok(ethapi_exex(ctx, rpc_handle_rx))
})
.launch()
.await?;
// Retrieve the rpc handle from the node and send it to the exex
rpc_handle_tx
.send(handle.node.add_ons_handle.clone())
.expect("Failed to send ethapi to ExEx");
handle.wait_for_node_exit().await
})
})
} else {
reth_ethereum::cli::Cli::parse_args().run(|builder, _| {
Box::pin(async move {
let (rpc_handle_tx, rpc_handle_rx) = oneshot::channel();
let handle = builder
.node(EthereumNode::default())
.install_exex("my-exex", async move |ctx| Ok(my_exex(ctx)))
.install_exex("ethapi-exex", async move |ctx| {
Ok(ethapi_exex(ctx, rpc_handle_rx))
})
.launch()
.await?;
// Retrieve the rpc handle from the node and send it to the exex
rpc_handle_tx
.send(handle.node.add_ons_handle.clone())
.expect("Failed to send ethapi to ExEx");
handle.wait_for_node_exit().await
})
})
}
}

View File

@@ -1,11 +0,0 @@
[package]
name = "example-op-db-access"
version = "0.0.0"
publish = false
edition.workspace = true
license.workspace = true
[dependencies]
reth-op = { workspace = true, features = ["node"] }
eyre.workspace = true

View File

@@ -1,23 +0,0 @@
//! Shows how to manually access the database
use reth_op::{chainspec::BASE_MAINNET, node::OpNode, provider::providers::ReadOnlyConfig};
// Providers are zero-cost abstractions on top of an opened MDBX Transaction
// exposing a familiar API to query the chain's information without requiring knowledge
// of the inner tables.
//
// These abstractions do not include any caching and the user is responsible for doing that.
// Other parts of the code which include caching are parts of the `EthApi` abstraction.
fn main() -> eyre::Result<()> {
// The path to data directory, e.g. "~/.local/reth/share/base"
let datadir = std::env::var("RETH_DATADIR")?;
// Instantiate a provider factory for Ethereum mainnet using the provided datadir path.
let factory = OpNode::provider_factory_builder()
.open_read_only(BASE_MAINNET.clone(), ReadOnlyConfig::from_datadir(datadir))?;
// obtain a provider access that has direct access to the database.
let _provider = factory.provider();
Ok(())
}