feat(examples): add custom engine API to custom-node example (#15436)

This commit is contained in:
Federico Gimenez
2025-04-02 11:38:53 +02:00
committed by GitHub
parent 69df27e9b5
commit 6f8082af34
5 changed files with 111 additions and 94 deletions

4
Cargo.lock generated
View File

@@ -3468,7 +3468,10 @@ dependencies = [
"alloy-primitives",
"alloy-rlp",
"alloy-rpc-types-engine",
"async-trait",
"derive_more 2.0.1",
"eyre",
"jsonrpsee",
"op-alloy-consensus",
"op-alloy-rpc-types-engine",
"reth-chain-state",
@@ -3482,6 +3485,7 @@ dependencies = [
"reth-optimism-node",
"reth-optimism-primitives",
"reth-primitives-traits",
"reth-rpc-api",
"revm-primitives",
"serde",
]

View File

@@ -18,6 +18,7 @@ reth-optimism-forks.workspace = true
reth-optimism-node.workspace = true
reth-optimism-primitives = { workspace = true, features = ["serde", "reth-codec"] }
reth-primitives-traits.workspace = true
reth-rpc-api.workspace = true
# revm
revm-primitives.workspace = true
@@ -33,8 +34,11 @@ op-alloy-consensus.workspace = true
op-alloy-rpc-types-engine.workspace = true
# misc
async-trait.workspace = true
derive_more.workspace = true
eyre.workspace = true
serde.workspace = true
jsonrpsee.workspace = true
[features]
default = []

View File

@@ -1,25 +1,18 @@
use crate::primitives::CustomNodePrimitives;
use alloy_rpc_types_engine::{
BlobsBundleV1, ExecutionPayloadV1, ExecutionPayloadV2, ExecutionPayloadV3,
};
use op_alloy_rpc_types_engine::{
OpExecutionData, OpExecutionPayload, OpExecutionPayloadEnvelopeV3,
OpExecutionPayloadEnvelopeV4, OpExecutionPayloadV4,
};
use op_alloy_rpc_types_engine::{OpExecutionData, OpExecutionPayload};
use reth_chain_state::ExecutedBlockWithTrieUpdates;
use reth_node_api::{
BuiltPayload, EngineTypes, ExecutionPayload, NodePrimitives, PayloadAttributes,
PayloadBuilderAttributes, PayloadTypes,
BuiltPayload, ExecutionPayload, NodePrimitives, PayloadAttributes, PayloadBuilderAttributes,
PayloadTypes,
};
use reth_optimism_node::{OpBuiltPayload, OpPayloadAttributes, OpPayloadBuilderAttributes};
use reth_optimism_primitives::OpTransactionSigned;
use reth_primitives_traits::SealedBlock;
use revm_primitives::U256;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct CustomEngineTypes;
pub struct CustomPayloadTypes;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CustomExecutionData {
@@ -164,79 +157,7 @@ impl From<CustomBuiltPayload>
}
}
impl From<CustomBuiltPayload> for ExecutionPayloadV1 {
fn from(value: CustomBuiltPayload) -> Self {
Self::from_block_unchecked(value.block().hash(), &value.into())
}
}
impl From<CustomBuiltPayload> for ExecutionPayloadV2 {
fn from(value: CustomBuiltPayload) -> Self {
Self::from_block_unchecked(value.block().hash(), &value.into())
}
}
impl From<CustomBuiltPayload> for OpExecutionPayloadEnvelopeV3 {
fn from(value: CustomBuiltPayload) -> Self {
Self {
block_value: value.fees(),
// From the engine API spec:
//
// > Client software **MAY** use any heuristics to decide whether to set
// `shouldOverrideBuilder` flag or not. If client software does not implement any
// heuristic this flag **SHOULD** be set to `false`.
//
// Spec:
// <https://github.com/ethereum/execution-apis/blob/fe8e13c288c592ec154ce25c534e26cb7ce0530d/src/engine/cancun.md#specification-2>
should_override_builder: false,
// No blobs for OP.
blobs_bundle: BlobsBundleV1 { blobs: vec![], commitments: vec![], proofs: vec![] },
parent_beacon_block_root: value.0.block().parent_beacon_block_root.unwrap_or_default(),
execution_payload: ExecutionPayloadV3::from_block_unchecked(
value.0.block().hash(),
&value.into(),
),
}
}
}
impl From<CustomBuiltPayload> for OpExecutionPayloadEnvelopeV4 {
fn from(value: CustomBuiltPayload) -> Self {
let fees = value.0.fees();
let block = value.0.into_sealed_block();
let parent_beacon_block_root = block.parent_beacon_block_root.unwrap_or_default();
let l2_withdrawals_root = block.withdrawals_root.unwrap_or_default();
let payload_v3 = ExecutionPayloadV3::from_block_unchecked(
block.hash(),
&Arc::unwrap_or_clone(block.into()).into_block(),
);
Self {
execution_payload: OpExecutionPayloadV4::from_v3_with_withdrawals_root(
payload_v3,
l2_withdrawals_root,
),
block_value: fees,
// From the engine API spec:
//
// > Client software **MAY** use any heuristics to decide whether to set
// `shouldOverrideBuilder` flag or not. If client software does not implement any
// heuristic this flag **SHOULD** be set to `false`.
//
// Spec:
// <https://github.com/ethereum/execution-apis/blob/fe8e13c288c592ec154ce25c534e26cb7ce0530d/src/engine/cancun.md#specification-2>
should_override_builder: false,
// No blobs for OP.
blobs_bundle: BlobsBundleV1 { blobs: vec![], commitments: vec![], proofs: vec![] },
parent_beacon_block_root,
execution_requests: vec![],
}
}
}
impl PayloadTypes for CustomEngineTypes {
impl PayloadTypes for CustomPayloadTypes {
type BuiltPayload = CustomBuiltPayload;
type PayloadAttributes = CustomPayloadAttributes;
type PayloadBuilderAttributes = CustomPayloadBuilderAttributes;
@@ -254,10 +175,3 @@ impl PayloadTypes for CustomEngineTypes {
CustomExecutionData { inner: OpExecutionData { payload, sidecar }, extension }
}
}
impl EngineTypes for CustomEngineTypes {
type ExecutionPayloadEnvelopeV1 = ExecutionPayloadV1;
type ExecutionPayloadEnvelopeV2 = ExecutionPayloadV2;
type ExecutionPayloadEnvelopeV3 = OpExecutionPayloadEnvelopeV3;
type ExecutionPayloadEnvelopeV4 = OpExecutionPayloadEnvelopeV4;
}

View File

@@ -0,0 +1,94 @@
use crate::{
chainspec::CustomChainSpec,
engine::{CustomPayloadAttributes, CustomPayloadTypes},
primitives::CustomNodePrimitives,
};
use alloy_rpc_types_engine::{
ForkchoiceState, ForkchoiceUpdated, PayloadId, PayloadStatus, PayloadStatusEnum,
};
use async_trait::async_trait;
use jsonrpsee::{core::RpcResult, proc_macros::rpc, RpcModule};
use reth_node_api::{AddOnsContext, FullNodeComponents, NodeTypes};
use reth_node_builder::rpc::EngineApiBuilder;
use reth_optimism_node::node::OpStorage;
use reth_rpc_api::IntoEngineApiRpcModule;
#[derive(serde::Deserialize)]
pub struct CustomExecutionPayloadInput {}
#[derive(Clone, serde::Serialize)]
pub struct CustomExecutionPayloadEnvelope {}
#[rpc(server, namespace = "engine")]
pub trait CustomEngineApi {
#[method(name = "newPayload")]
async fn new_payload(&self, payload: CustomExecutionPayloadInput) -> 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 {}
#[async_trait]
impl CustomEngineApiServer for CustomEngineApi {
async fn new_payload(&self, _payload: CustomExecutionPayloadInput) -> RpcResult<PayloadStatus> {
Ok(PayloadStatus::from_status(PayloadStatusEnum::Valid))
}
async fn fork_choice_updated(
&self,
_fork_choice_state: ForkchoiceState,
_payload_attributes: Option<CustomPayloadAttributes>,
) -> RpcResult<ForkchoiceUpdated> {
Ok(ForkchoiceUpdated {
payload_status: PayloadStatus::from_status(PayloadStatusEnum::Valid),
payload_id: Some(PayloadId::default()),
})
}
async fn get_payload(
&self,
_payload_id: PayloadId,
) -> RpcResult<CustomExecutionPayloadEnvelope> {
Ok(CustomExecutionPayloadEnvelope {})
}
}
impl IntoEngineApiRpcModule for CustomEngineApi
where
Self: CustomEngineApiServer,
{
fn into_rpc_module(self) -> RpcModule<()> {
self.into_rpc().remove_context()
}
}
#[derive(Debug, Default)]
pub struct CustomEngineApiBuilder {}
impl<N> EngineApiBuilder<N> for CustomEngineApiBuilder
where
N: FullNodeComponents<
Types: NodeTypes<
Payload = CustomPayloadTypes,
ChainSpec = CustomChainSpec,
Primitives = CustomNodePrimitives,
Storage = OpStorage,
>,
>,
{
type EngineApi = CustomEngineApi;
async fn build_engine_api(self, _ctx: &AddOnsContext<'_, N>) -> eyre::Result<Self::EngineApi> {
Ok(CustomEngineApi {})
}
}

View File

@@ -8,7 +8,7 @@
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
use chainspec::CustomChainSpec;
use engine::CustomEngineTypes;
use engine::CustomPayloadTypes;
use primitives::CustomNodePrimitives;
use reth_node_api::{FullNodeTypes, NodeTypes};
use reth_node_builder::{components::ComponentsBuilder, Node, NodeComponentsBuilder};
@@ -19,6 +19,7 @@ use reth_optimism_node::{
pub mod chainspec;
pub mod engine;
pub mod engine_api;
pub mod primitives;
#[derive(Debug, Clone)]
@@ -29,14 +30,14 @@ impl NodeTypes for CustomNode {
type ChainSpec = CustomChainSpec;
type StateCommitment = <OpNode as NodeTypes>::StateCommitment;
type Storage = <OpNode as NodeTypes>::Storage;
type Payload = CustomEngineTypes;
type Payload = CustomPayloadTypes;
}
impl<N> Node<N> for CustomNode
where
N: FullNodeTypes<
Types: NodeTypes<
Payload = CustomEngineTypes,
Payload = CustomPayloadTypes,
ChainSpec = CustomChainSpec,
Primitives = CustomNodePrimitives,
Storage = OpStorage,