mirror of
https://github.com/paradigmxyz/reth.git
synced 2026-02-04 03:55:22 -05:00
250 lines
8.2 KiB
Rust
250 lines
8.2 KiB
Rust
//! This example shows how to implement a custom [EngineTypes].
|
|
//!
|
|
//! The [EngineTypes] trait can be implemented to configure the engine to work with custom types,
|
|
//! as long as those types implement certain traits.
|
|
//!
|
|
//! Custom payload attributes can be supported by implementing two main traits:
|
|
//!
|
|
//! [PayloadAttributes] can be implemented for payload attributes types that are used as
|
|
//! arguments to the `engine_forkchoiceUpdated` method. This type should be used to define and
|
|
//! _spawn_ payload jobs.
|
|
//!
|
|
//! [PayloadBuilderAttributes] can be implemented for payload attributes types that _describe_
|
|
//! running payload jobs.
|
|
//!
|
|
//! Once traits are implemented and custom types are defined, the [EngineTypes] trait can be
|
|
//! implemented:
|
|
|
|
use alloy_chains::Chain;
|
|
use jsonrpsee::http_client::HttpClient;
|
|
use reth::builder::spawn_node;
|
|
use reth_node_api::{
|
|
validate_version_specific_fields, AttributesValidationError, EngineApiMessageVersion,
|
|
EngineTypes, PayloadAttributes, PayloadBuilderAttributes, PayloadOrAttributes,
|
|
};
|
|
use reth_node_core::{args::RpcServerArgs, node_config::NodeConfig};
|
|
use reth_payload_builder::{EthBuiltPayload, EthPayloadBuilderAttributes};
|
|
use reth_primitives::{
|
|
revm::config::revm_spec_by_timestamp_after_merge,
|
|
revm_primitives::{BlobExcessGasAndPrice, BlockEnv, CfgEnv, CfgEnvWithHandlerCfg, SpecId},
|
|
Address, ChainSpec, Genesis, Header, Withdrawals, B256, U256,
|
|
};
|
|
use reth_rpc_api::{EngineApiClient, EthApiClient};
|
|
use reth_rpc_types::{
|
|
engine::{ForkchoiceState, PayloadAttributes as EthPayloadAttributes, PayloadId},
|
|
withdrawal::Withdrawal,
|
|
};
|
|
use serde::{Deserialize, Serialize};
|
|
use std::convert::Infallible;
|
|
use thiserror::Error;
|
|
|
|
/// A custom payload attributes type.
|
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct CustomPayloadAttributes {
|
|
/// An inner payload type
|
|
#[serde(flatten)]
|
|
pub inner: EthPayloadAttributes,
|
|
/// A custom field
|
|
pub custom: u64,
|
|
}
|
|
|
|
/// Custom error type used in payload attributes validation
|
|
#[derive(Debug, Error)]
|
|
pub enum CustomError {
|
|
#[error("Custom field is not zero")]
|
|
CustomFieldIsNotZero,
|
|
}
|
|
|
|
impl PayloadAttributes for CustomPayloadAttributes {
|
|
fn timestamp(&self) -> u64 {
|
|
self.inner.timestamp()
|
|
}
|
|
|
|
fn withdrawals(&self) -> Option<&Vec<Withdrawal>> {
|
|
self.inner.withdrawals()
|
|
}
|
|
|
|
fn parent_beacon_block_root(&self) -> Option<B256> {
|
|
self.inner.parent_beacon_block_root()
|
|
}
|
|
|
|
fn ensure_well_formed_attributes(
|
|
&self,
|
|
chain_spec: &ChainSpec,
|
|
version: EngineApiMessageVersion,
|
|
) -> Result<(), AttributesValidationError> {
|
|
validate_version_specific_fields(chain_spec, version, self.into())?;
|
|
|
|
// custom validation logic - ensure that the custom field is not zero
|
|
if self.custom == 0 {
|
|
return Err(AttributesValidationError::invalid_params(CustomError::CustomFieldIsNotZero))
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// Newtype around the payload builder attributes type
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
pub struct CustomPayloadBuilderAttributes(EthPayloadBuilderAttributes);
|
|
|
|
impl PayloadBuilderAttributes for CustomPayloadBuilderAttributes {
|
|
type RpcPayloadAttributes = CustomPayloadAttributes;
|
|
type Error = Infallible;
|
|
|
|
fn try_new(parent: B256, attributes: CustomPayloadAttributes) -> Result<Self, Infallible> {
|
|
Ok(Self(EthPayloadBuilderAttributes::new(parent, attributes.inner)))
|
|
}
|
|
|
|
fn payload_id(&self) -> PayloadId {
|
|
self.0.id
|
|
}
|
|
|
|
fn parent(&self) -> B256 {
|
|
self.0.parent
|
|
}
|
|
|
|
fn timestamp(&self) -> u64 {
|
|
self.0.timestamp
|
|
}
|
|
|
|
fn parent_beacon_block_root(&self) -> Option<B256> {
|
|
self.0.parent_beacon_block_root
|
|
}
|
|
|
|
fn suggested_fee_recipient(&self) -> Address {
|
|
self.0.suggested_fee_recipient
|
|
}
|
|
|
|
fn prev_randao(&self) -> B256 {
|
|
self.0.prev_randao
|
|
}
|
|
|
|
fn withdrawals(&self) -> &Withdrawals {
|
|
&self.0.withdrawals
|
|
}
|
|
fn cfg_and_block_env(
|
|
&self,
|
|
chain_spec: &ChainSpec,
|
|
parent: &Header,
|
|
) -> (CfgEnvWithHandlerCfg, BlockEnv) {
|
|
// configure evm env based on parent block
|
|
let mut cfg = CfgEnv::default();
|
|
cfg.chain_id = chain_spec.chain().id();
|
|
|
|
// ensure we're not missing any timestamp based hardforks
|
|
let spec_id = revm_spec_by_timestamp_after_merge(chain_spec, self.timestamp());
|
|
|
|
// if the parent block did not have excess blob gas (i.e. it was pre-cancun), but it is
|
|
// cancun now, we need to set the excess blob gas to the default value
|
|
let blob_excess_gas_and_price = parent
|
|
.next_block_excess_blob_gas()
|
|
.or_else(|| {
|
|
if spec_id == SpecId::CANCUN {
|
|
// default excess blob gas is zero
|
|
Some(0)
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
.map(BlobExcessGasAndPrice::new);
|
|
|
|
let block_env = BlockEnv {
|
|
number: U256::from(parent.number + 1),
|
|
coinbase: self.suggested_fee_recipient(),
|
|
timestamp: U256::from(self.timestamp()),
|
|
difficulty: U256::ZERO,
|
|
prevrandao: Some(self.prev_randao()),
|
|
gas_limit: U256::from(parent.gas_limit),
|
|
// calculate basefee based on parent block's gas usage
|
|
basefee: U256::from(
|
|
parent
|
|
.next_block_base_fee(chain_spec.base_fee_params(self.timestamp()))
|
|
.unwrap_or_default(),
|
|
),
|
|
// calculate excess gas based on parent block's blob gas usage
|
|
blob_excess_gas_and_price,
|
|
};
|
|
|
|
(CfgEnvWithHandlerCfg::new_with_spec_id(cfg, spec_id), block_env)
|
|
}
|
|
}
|
|
|
|
/// Custom engine types - uses a custom payload attributes RPC type, but uses the default
|
|
/// payload builder attributes type.
|
|
#[derive(Clone, Debug, Default, Deserialize)]
|
|
#[non_exhaustive]
|
|
pub struct CustomEngineTypes;
|
|
|
|
impl EngineTypes for CustomEngineTypes {
|
|
type PayloadAttributes = CustomPayloadAttributes;
|
|
type PayloadBuilderAttributes = CustomPayloadBuilderAttributes;
|
|
type BuiltPayload = EthBuiltPayload;
|
|
|
|
fn validate_version_specific_fields(
|
|
chain_spec: &ChainSpec,
|
|
version: EngineApiMessageVersion,
|
|
payload_or_attrs: PayloadOrAttributes<'_, CustomPayloadAttributes>,
|
|
) -> Result<(), AttributesValidationError> {
|
|
validate_version_specific_fields(chain_spec, version, payload_or_attrs)
|
|
}
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() -> eyre::Result<()> {
|
|
// this launches a test node with http
|
|
let rpc_args = RpcServerArgs::default().with_http();
|
|
|
|
// create optimism genesis with canyon at block 2
|
|
let spec = ChainSpec::builder()
|
|
.chain(Chain::mainnet())
|
|
.genesis(Genesis::default())
|
|
.london_activated()
|
|
.paris_activated()
|
|
.shanghai_activated()
|
|
.build();
|
|
|
|
let genesis_hash = spec.genesis_hash();
|
|
|
|
// create node config
|
|
let node_config = NodeConfig::test().with_rpc(rpc_args).with_chain(spec);
|
|
|
|
let (handle, _manager) = spawn_node(node_config).await.unwrap();
|
|
|
|
// call a function on the node
|
|
let client = handle.rpc_server_handles().auth.http_client();
|
|
let block_number = client.block_number().await.unwrap();
|
|
|
|
// it should be zero, since this is an ephemeral test node
|
|
assert_eq!(block_number, U256::ZERO);
|
|
|
|
// call the engine_forkchoiceUpdated function with payload attributes
|
|
let forkchoice_state = ForkchoiceState {
|
|
head_block_hash: genesis_hash,
|
|
safe_block_hash: genesis_hash,
|
|
finalized_block_hash: genesis_hash,
|
|
};
|
|
|
|
let payload_attributes = CustomPayloadAttributes {
|
|
inner: EthPayloadAttributes {
|
|
timestamp: 1,
|
|
prev_randao: Default::default(),
|
|
suggested_fee_recipient: Default::default(),
|
|
withdrawals: Some(vec![]),
|
|
parent_beacon_block_root: None,
|
|
},
|
|
custom: 42,
|
|
};
|
|
|
|
// call the engine_forkchoiceUpdated function with payload attributes
|
|
let res = <HttpClient as EngineApiClient<CustomEngineTypes>>::fork_choice_updated_v2(
|
|
&client,
|
|
forkchoice_state,
|
|
Some(payload_attributes),
|
|
)
|
|
.await;
|
|
assert!(res.is_ok());
|
|
|
|
Ok(())
|
|
}
|