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",
|
"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]]
|
[[package]]
|
||||||
name = "darling"
|
name = "darling"
|
||||||
version = "0.10.2"
|
version = "0.10.2"
|
||||||
|
|||||||
@ -61,6 +61,7 @@ members = [
|
|||||||
"examples/manual-p2p/",
|
"examples/manual-p2p/",
|
||||||
"examples/rpc-db/",
|
"examples/rpc-db/",
|
||||||
"examples/trace-transaction-cli/",
|
"examples/trace-transaction-cli/",
|
||||||
|
"examples/custom-payload-builder/",
|
||||||
"testing/ef-tests/",
|
"testing/ef-tests/",
|
||||||
]
|
]
|
||||||
default-members = ["bin/reth"]
|
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