feature: move node-api example into examples (#6390)

Signed-off-by: jsvisa <delweng@gmail.com>
Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
This commit is contained in:
Delweng
2024-02-05 20:12:33 +08:00
committed by GitHub
parent 35ab1b1586
commit b7e511dca2
6 changed files with 246 additions and 141 deletions

View File

@ -0,0 +1,24 @@
[package]
name = "custom-node"
version = "0.0.0"
publish = false
edition.workspace = true
license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
reth.workspace = true
reth-rpc-api.workspace = true
reth-rpc-types.workspace = true
reth-node-api.workspace = true
reth-node-core.workspace = true
reth-primitives.workspace = true
reth-payload-builder.workspace = true
alloy-chains.workspace = true
jsonrpsee.workspace = true
eyre.workspace = true
tokio.workspace = true
thiserror.workspace = true
serde.workspace = true

View File

@ -0,0 +1,200 @@
//! 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::{Address, ChainSpec, Genesis, 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
}
}
/// 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(())
}