mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
Example/custom payload builder (#6269)
This commit is contained in:
16
Cargo.lock
generated
16
Cargo.lock
generated
@ -1831,6 +1831,22 @@ dependencies = [
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "custom-payload-builder"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"eyre",
|
||||
"futures-util",
|
||||
"reth",
|
||||
"reth-basic-payload-builder",
|
||||
"reth-node-api",
|
||||
"reth-payload-builder",
|
||||
"reth-primitives",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.10.2"
|
||||
|
||||
@ -61,6 +61,7 @@ members = [
|
||||
"examples/manual-p2p/",
|
||||
"examples/rpc-db/",
|
||||
"examples/trace-transaction-cli/",
|
||||
"examples/custom-payload-builder/",
|
||||
"testing/ef-tests/",
|
||||
]
|
||||
default-members = ["bin/reth"]
|
||||
|
||||
21
examples/custom-payload-builder/Cargo.toml
Normal file
21
examples/custom-payload-builder/Cargo.toml
Normal file
@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "custom-payload-builder"
|
||||
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-primitives.workspace = true
|
||||
reth-node-api.workspace = true
|
||||
reth-basic-payload-builder.workspace = true
|
||||
reth-payload-builder.workspace = true
|
||||
|
||||
tracing.workspace = true
|
||||
clap = { workspace = true, features = ["derive"] }
|
||||
futures-util.workspace = true
|
||||
eyre.workspace = true
|
||||
tokio.workspace = true
|
||||
96
examples/custom-payload-builder/src/generator.rs
Normal file
96
examples/custom-payload-builder/src/generator.rs
Normal file
@ -0,0 +1,96 @@
|
||||
use crate::job::EmptyBlockPayloadJob;
|
||||
use reth::{
|
||||
providers::{BlockReaderIdExt, BlockSource, StateProviderFactory},
|
||||
tasks::TaskSpawner,
|
||||
transaction_pool::TransactionPool,
|
||||
};
|
||||
use reth_basic_payload_builder::{BasicPayloadJobGeneratorConfig, PayloadBuilder, PayloadConfig};
|
||||
use reth_node_api::PayloadBuilderAttributes;
|
||||
use reth_payload_builder::{error::PayloadBuilderError, PayloadJobGenerator};
|
||||
use reth_primitives::{BlockNumberOrTag, Bytes, ChainSpec};
|
||||
use std::sync::Arc;
|
||||
|
||||
/// The generator type that creates new jobs that builds empty blocks.
|
||||
#[derive(Debug)]
|
||||
pub struct EmptyBlockPayloadJobGenerator<Client, Pool, Tasks, Builder> {
|
||||
/// The client that can interact with the chain.
|
||||
client: Client,
|
||||
/// txpool
|
||||
pool: Pool,
|
||||
/// How to spawn building tasks
|
||||
executor: Tasks,
|
||||
/// The configuration for the job generator.
|
||||
_config: BasicPayloadJobGeneratorConfig,
|
||||
/// The chain spec.
|
||||
chain_spec: Arc<ChainSpec>,
|
||||
/// The type responsible for building payloads.
|
||||
///
|
||||
/// See [PayloadBuilder]
|
||||
builder: Builder,
|
||||
}
|
||||
|
||||
// === impl EmptyBlockPayloadJobGenerator ===
|
||||
|
||||
impl<Client, Pool, Tasks, Builder> EmptyBlockPayloadJobGenerator<Client, Pool, Tasks, Builder> {
|
||||
/// Creates a new [EmptyBlockPayloadJobGenerator] with the given config and custom
|
||||
/// [PayloadBuilder]
|
||||
pub fn with_builder(
|
||||
client: Client,
|
||||
pool: Pool,
|
||||
executor: Tasks,
|
||||
config: BasicPayloadJobGeneratorConfig,
|
||||
chain_spec: Arc<ChainSpec>,
|
||||
builder: Builder,
|
||||
) -> Self {
|
||||
Self { client, pool, executor, _config: config, builder, chain_spec }
|
||||
}
|
||||
}
|
||||
|
||||
impl<Client, Pool, Tasks, Builder> PayloadJobGenerator
|
||||
for EmptyBlockPayloadJobGenerator<Client, Pool, Tasks, Builder>
|
||||
where
|
||||
Client: StateProviderFactory + BlockReaderIdExt + Clone + Unpin + 'static,
|
||||
Pool: TransactionPool + Unpin + 'static,
|
||||
Tasks: TaskSpawner + Clone + Unpin + 'static,
|
||||
Builder: PayloadBuilder<Pool, Client> + Unpin + 'static,
|
||||
<Builder as PayloadBuilder<Pool, Client>>::Attributes: Unpin + Clone,
|
||||
<Builder as PayloadBuilder<Pool, Client>>::BuiltPayload: Unpin + Clone,
|
||||
{
|
||||
type Job = EmptyBlockPayloadJob<Client, Pool, Tasks, Builder>;
|
||||
|
||||
/// This is invoked when the node receives payload attributes from the beacon node via
|
||||
/// `engine_forkchoiceUpdatedV1`
|
||||
fn new_payload_job(
|
||||
&self,
|
||||
attributes: <Builder as PayloadBuilder<Pool, Client>>::Attributes,
|
||||
) -> Result<Self::Job, PayloadBuilderError> {
|
||||
let parent_block = if attributes.parent().is_zero() {
|
||||
// use latest block if parent is zero: genesis block
|
||||
self.client
|
||||
.block_by_number_or_tag(BlockNumberOrTag::Latest)?
|
||||
.ok_or_else(|| PayloadBuilderError::MissingParentBlock(attributes.parent()))?
|
||||
.seal_slow()
|
||||
} else {
|
||||
let block = self
|
||||
.client
|
||||
.find_block_by_hash(attributes.parent(), BlockSource::Any)?
|
||||
.ok_or_else(|| PayloadBuilderError::MissingParentBlock(attributes.parent()))?;
|
||||
|
||||
// we already know the hash, so we can seal it
|
||||
block.seal(attributes.parent())
|
||||
};
|
||||
let config = PayloadConfig::new(
|
||||
Arc::new(parent_block),
|
||||
Bytes::default(),
|
||||
attributes,
|
||||
Arc::clone(&self.chain_spec),
|
||||
);
|
||||
Ok(EmptyBlockPayloadJob {
|
||||
client: self.client.clone(),
|
||||
_pool: self.pool.clone(),
|
||||
_executor: self.executor.clone(),
|
||||
_builder: self.builder.clone(),
|
||||
config,
|
||||
})
|
||||
}
|
||||
}
|
||||
76
examples/custom-payload-builder/src/job.rs
Normal file
76
examples/custom-payload-builder/src/job.rs
Normal file
@ -0,0 +1,76 @@
|
||||
use futures_util::Future;
|
||||
use reth::{
|
||||
providers::StateProviderFactory, tasks::TaskSpawner, transaction_pool::TransactionPool,
|
||||
};
|
||||
use reth_basic_payload_builder::{PayloadBuilder, PayloadConfig};
|
||||
use reth_payload_builder::{error::PayloadBuilderError, KeepPayloadJobAlive, PayloadJob};
|
||||
|
||||
use std::{
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
/// A [PayloadJob] that builds empty blocks.
|
||||
pub struct EmptyBlockPayloadJob<Client, Pool, Tasks, Builder>
|
||||
where
|
||||
Builder: PayloadBuilder<Pool, Client>,
|
||||
{
|
||||
/// The configuration for how the payload will be created.
|
||||
pub(crate) config: PayloadConfig<Builder::Attributes>,
|
||||
/// The client that can interact with the chain.
|
||||
pub(crate) client: Client,
|
||||
/// The transaction pool.
|
||||
pub(crate) _pool: Pool,
|
||||
/// How to spawn building tasks
|
||||
pub(crate) _executor: Tasks,
|
||||
/// The type responsible for building payloads.
|
||||
///
|
||||
/// See [PayloadBuilder]
|
||||
pub(crate) _builder: Builder,
|
||||
}
|
||||
|
||||
impl<Client, Pool, Tasks, Builder> PayloadJob for EmptyBlockPayloadJob<Client, Pool, Tasks, Builder>
|
||||
where
|
||||
Client: StateProviderFactory + Clone + Unpin + 'static,
|
||||
Pool: TransactionPool + Unpin + 'static,
|
||||
Tasks: TaskSpawner + Clone + 'static,
|
||||
Builder: PayloadBuilder<Pool, Client> + Unpin + 'static,
|
||||
<Builder as PayloadBuilder<Pool, Client>>::Attributes: Unpin + Clone,
|
||||
<Builder as PayloadBuilder<Pool, Client>>::BuiltPayload: Unpin + Clone,
|
||||
{
|
||||
type PayloadAttributes = Builder::Attributes;
|
||||
type ResolvePayloadFuture =
|
||||
futures_util::future::Ready<Result<Self::BuiltPayload, PayloadBuilderError>>;
|
||||
type BuiltPayload = Builder::BuiltPayload;
|
||||
|
||||
fn best_payload(&self) -> Result<Self::BuiltPayload, PayloadBuilderError> {
|
||||
let payload = Builder::build_empty_payload(&self.client, self.config.clone())?;
|
||||
Ok(payload)
|
||||
}
|
||||
|
||||
fn payload_attributes(&self) -> Result<Self::PayloadAttributes, PayloadBuilderError> {
|
||||
Ok(self.config.attributes.clone())
|
||||
}
|
||||
|
||||
fn resolve(&mut self) -> (Self::ResolvePayloadFuture, KeepPayloadJobAlive) {
|
||||
let payload = self.best_payload();
|
||||
(futures_util::future::ready(payload), KeepPayloadJobAlive::No)
|
||||
}
|
||||
}
|
||||
|
||||
/// A [PayloadJob] is a a future that's being polled by the `PayloadBuilderService`
|
||||
impl<Client, Pool, Tasks, Builder> Future for EmptyBlockPayloadJob<Client, Pool, Tasks, Builder>
|
||||
where
|
||||
Client: StateProviderFactory + Clone + Unpin + 'static,
|
||||
Pool: TransactionPool + Unpin + 'static,
|
||||
Tasks: TaskSpawner + Clone + 'static,
|
||||
Builder: PayloadBuilder<Pool, Client> + Unpin + 'static,
|
||||
<Builder as PayloadBuilder<Pool, Client>>::Attributes: Unpin + Clone,
|
||||
<Builder as PayloadBuilder<Pool, Client>>::BuiltPayload: Unpin + Clone,
|
||||
{
|
||||
type Output = Result<(), PayloadBuilderError>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
91
examples/custom-payload-builder/src/main.rs
Normal file
91
examples/custom-payload-builder/src/main.rs
Normal file
@ -0,0 +1,91 @@
|
||||
//! Example for how hook into the node via the CLI extension mechanism without registering
|
||||
//! additional arguments
|
||||
//!
|
||||
//! Run with
|
||||
//!
|
||||
//! ```not_rust
|
||||
//! cargo run -p custom-payload-builder -- node
|
||||
//! ```
|
||||
//!
|
||||
//! This launch the regular reth node overriding the engine api payload builder with our custom.
|
||||
use clap::Parser;
|
||||
use generator::EmptyBlockPayloadJobGenerator;
|
||||
use reth::{
|
||||
cli::{
|
||||
components::RethNodeComponents,
|
||||
config::PayloadBuilderConfig,
|
||||
ext::{NoArgsCliExt, RethNodeCommandConfig},
|
||||
Cli,
|
||||
},
|
||||
payload::PayloadBuilderHandle,
|
||||
providers::CanonStateSubscriptions,
|
||||
tasks::TaskSpawner,
|
||||
};
|
||||
use reth_basic_payload_builder::{BasicPayloadJobGeneratorConfig, PayloadBuilder};
|
||||
use reth_node_api::EngineTypes;
|
||||
use reth_payload_builder::PayloadBuilderService;
|
||||
|
||||
pub mod generator;
|
||||
pub mod job;
|
||||
|
||||
fn main() {
|
||||
Cli::<NoArgsCliExt<MyCustomBuilder>>::parse()
|
||||
.with_node_extension(MyCustomBuilder::default())
|
||||
.run()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// Our custom cli args extension that adds one flag to reth default CLI.
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
#[non_exhaustive]
|
||||
struct MyCustomBuilder;
|
||||
|
||||
impl RethNodeCommandConfig for MyCustomBuilder {
|
||||
fn spawn_payload_builder_service<Conf, Reth, Builder, Engine>(
|
||||
&mut self,
|
||||
conf: &Conf,
|
||||
components: &Reth,
|
||||
payload_builder: Builder,
|
||||
) -> eyre::Result<PayloadBuilderHandle<Engine>>
|
||||
where
|
||||
Conf: PayloadBuilderConfig,
|
||||
Reth: RethNodeComponents,
|
||||
Engine: EngineTypes + 'static,
|
||||
Builder: PayloadBuilder<
|
||||
Reth::Pool,
|
||||
Reth::Provider,
|
||||
Attributes = Engine::PayloadBuilderAttributes,
|
||||
BuiltPayload = Engine::BuiltPayload,
|
||||
> + Unpin
|
||||
+ 'static,
|
||||
{
|
||||
tracing::info!("Spawning a custom payload builder");
|
||||
|
||||
let payload_job_config = BasicPayloadJobGeneratorConfig::default()
|
||||
.interval(conf.interval())
|
||||
.deadline(conf.deadline())
|
||||
.max_payload_tasks(conf.max_payload_tasks())
|
||||
.extradata(conf.extradata_rlp_bytes())
|
||||
.max_gas_limit(conf.max_gas_limit());
|
||||
|
||||
let payload_generator = EmptyBlockPayloadJobGenerator::with_builder(
|
||||
components.provider(),
|
||||
components.pool(),
|
||||
components.task_executor(),
|
||||
payload_job_config,
|
||||
components.chain_spec().clone(),
|
||||
payload_builder,
|
||||
);
|
||||
|
||||
let (payload_service, payload_builder) = PayloadBuilderService::new(
|
||||
payload_generator,
|
||||
components.events().canonical_state_stream(),
|
||||
);
|
||||
|
||||
components
|
||||
.task_executor()
|
||||
.spawn_critical("custom payload builder service", Box::pin(payload_service));
|
||||
|
||||
Ok(payload_builder)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user