mirror of
https://github.com/paradigmxyz/reth.git
synced 2026-04-30 03:01:58 -04:00
feat: make payload builder generic over attributes type (#5948)
Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
This commit is contained in:
@@ -28,7 +28,7 @@
|
||||
//! use std::pin::Pin;
|
||||
//! use std::sync::Arc;
|
||||
//! use std::task::{Context, Poll};
|
||||
//! use reth_payload_builder::{BuiltPayload, KeepPayloadJobAlive, PayloadBuilderAttributes, PayloadJob, PayloadJobGenerator};
|
||||
//! use reth_payload_builder::{BuiltPayload, KeepPayloadJobAlive, EthPayloadBuilderAttributes, PayloadJob, PayloadJobGenerator};
|
||||
//! use reth_payload_builder::error::PayloadBuilderError;
|
||||
//! use reth_primitives::{Block, Header, U256};
|
||||
//!
|
||||
@@ -39,7 +39,7 @@
|
||||
//! type Job = EmptyBlockPayloadJob;
|
||||
//!
|
||||
//! /// This is invoked when the node receives payload attributes from the beacon node via `engine_forkchoiceUpdatedV1`
|
||||
//! fn new_payload_job(&self, attr: PayloadBuilderAttributes) -> Result<Self::Job, PayloadBuilderError> {
|
||||
//! fn new_payload_job(&self, attr: EthPayloadBuilderAttributes) -> Result<Self::Job, PayloadBuilderError> {
|
||||
//! Ok(EmptyBlockPayloadJob{ attributes: attr,})
|
||||
//! }
|
||||
//!
|
||||
@@ -47,10 +47,11 @@
|
||||
//!
|
||||
//! /// A [PayloadJob] that builds empty blocks.
|
||||
//! pub struct EmptyBlockPayloadJob {
|
||||
//! attributes: PayloadBuilderAttributes,
|
||||
//! attributes: EthPayloadBuilderAttributes,
|
||||
//! }
|
||||
//!
|
||||
//! impl PayloadJob for EmptyBlockPayloadJob {
|
||||
//! type PayloadAttributes = EthPayloadBuilderAttributes;
|
||||
//! type ResolvePayloadFuture = futures_util::future::Ready<Result<Arc<BuiltPayload>, PayloadBuilderError>>;
|
||||
//!
|
||||
//! fn best_payload(&self) -> Result<Arc<BuiltPayload>, PayloadBuilderError> {
|
||||
@@ -68,7 +69,7 @@
|
||||
//! Ok(Arc::new(payload))
|
||||
//! }
|
||||
//!
|
||||
//! fn payload_attributes(&self) -> Result<PayloadBuilderAttributes, PayloadBuilderError> {
|
||||
//! fn payload_attributes(&self) -> Result<EthPayloadBuilderAttributes, PayloadBuilderError> {
|
||||
//! Ok(self.attributes.clone())
|
||||
//! }
|
||||
//!
|
||||
@@ -111,7 +112,7 @@ pub mod noop;
|
||||
#[cfg(any(test, feature = "test-utils"))]
|
||||
pub mod test_utils;
|
||||
|
||||
pub use payload::{BuiltPayload, PayloadBuilderAttributes};
|
||||
pub use payload::{BuiltPayload, EthPayloadBuilderAttributes, OptimismPayloadBuilderAttributes};
|
||||
pub use reth_rpc_types::engine::PayloadId;
|
||||
pub use service::{PayloadBuilderHandle, PayloadBuilderService, PayloadStore};
|
||||
pub use traits::{KeepPayloadJobAlive, PayloadJob, PayloadJobGenerator};
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
use crate::{service::PayloadServiceCommand, PayloadBuilderHandle};
|
||||
use futures_util::{ready, StreamExt};
|
||||
use reth_node_api::{EngineTypes, PayloadBuilderAttributes};
|
||||
use std::{
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
@@ -12,21 +13,27 @@ use tokio_stream::wrappers::UnboundedReceiverStream;
|
||||
|
||||
/// A service task that does not build any payloads.
|
||||
#[derive(Debug)]
|
||||
pub struct NoopPayloadBuilderService {
|
||||
pub struct NoopPayloadBuilderService<Engine: EngineTypes> {
|
||||
/// Receiver half of the command channel.
|
||||
command_rx: UnboundedReceiverStream<PayloadServiceCommand>,
|
||||
command_rx: UnboundedReceiverStream<PayloadServiceCommand<Engine::PayloadBuilderAttributes>>,
|
||||
}
|
||||
|
||||
impl NoopPayloadBuilderService {
|
||||
impl<Engine> NoopPayloadBuilderService<Engine>
|
||||
where
|
||||
Engine: EngineTypes,
|
||||
{
|
||||
/// Creates a new [NoopPayloadBuilderService].
|
||||
pub fn new() -> (Self, PayloadBuilderHandle) {
|
||||
pub fn new() -> (Self, PayloadBuilderHandle<Engine>) {
|
||||
let (service_tx, command_rx) = mpsc::unbounded_channel();
|
||||
let handle = PayloadBuilderHandle::new(service_tx);
|
||||
(Self { command_rx: UnboundedReceiverStream::new(command_rx) }, handle)
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for NoopPayloadBuilderService {
|
||||
impl<Engine> Future for NoopPayloadBuilderService<Engine>
|
||||
where
|
||||
Engine: EngineTypes,
|
||||
{
|
||||
type Output = ();
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
//! Contains types required for building a payload.
|
||||
|
||||
use std::convert::Infallible;
|
||||
|
||||
use alloy_rlp::{Encodable, Error as DecodeError};
|
||||
use reth_node_api::PayloadBuilderAttributes;
|
||||
use reth_primitives::{
|
||||
revm::config::revm_spec_by_timestamp_after_merge,
|
||||
revm_primitives::{BlobExcessGasAndPrice, BlockEnv, CfgEnv, SpecId},
|
||||
Address, BlobTransactionSidecar, ChainSpec, Header, SealedBlock, Withdrawal, B256, U256,
|
||||
Address, BlobTransactionSidecar, ChainSpec, Header, SealedBlock, TransactionSigned, Withdrawal,
|
||||
B256, U256,
|
||||
};
|
||||
use reth_rpc_types::engine::{
|
||||
ExecutionPayloadEnvelopeV2, ExecutionPayloadEnvelopeV3, ExecutionPayloadV1, PayloadAttributes,
|
||||
PayloadId,
|
||||
ExecutionPayloadEnvelopeV2, ExecutionPayloadEnvelopeV3, ExecutionPayloadV1,
|
||||
OptimismPayloadAttributes, PayloadAttributes, PayloadId,
|
||||
};
|
||||
|
||||
use reth_rpc_types_compat::engine::payload::{
|
||||
block_to_payload_v3, convert_block_to_payload_field_v2,
|
||||
convert_standalone_withdraw_to_withdrawal, try_block_to_payload_v1,
|
||||
};
|
||||
|
||||
#[cfg(feature = "optimism")]
|
||||
use reth_primitives::TransactionSigned;
|
||||
|
||||
/// Contains the built payload.
|
||||
///
|
||||
/// According to the [engine API specification](https://github.com/ethereum/execution-apis/blob/main/src/engine/README.md) the execution layer should build the initial version of the payload with an empty transaction set and then keep update it in order to maximize the revenue.
|
||||
@@ -123,7 +123,7 @@ impl From<BuiltPayload> for ExecutionPayloadEnvelopeV3 {
|
||||
|
||||
/// Container type for all components required to build a payload.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct PayloadBuilderAttributes {
|
||||
pub struct EthPayloadBuilderAttributes {
|
||||
/// Id of the payload
|
||||
pub id: PayloadId,
|
||||
/// Parent block to build the payload on top
|
||||
@@ -140,71 +140,11 @@ pub struct PayloadBuilderAttributes {
|
||||
pub withdrawals: Vec<Withdrawal>,
|
||||
/// Root of the parent beacon block
|
||||
pub parent_beacon_block_root: Option<B256>,
|
||||
/// Optimism Payload Builder Attributes
|
||||
#[cfg(feature = "optimism")]
|
||||
pub optimism_payload_attributes: OptimismPayloadBuilderAttributes,
|
||||
}
|
||||
|
||||
/// Optimism Payload Builder Attributes
|
||||
#[cfg(feature = "optimism")]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct OptimismPayloadBuilderAttributes {
|
||||
/// NoTxPool option for the generated payload
|
||||
pub no_tx_pool: bool,
|
||||
/// Transactions for the generated payload
|
||||
pub transactions: Vec<TransactionSigned>,
|
||||
/// The gas limit for the generated payload
|
||||
pub gas_limit: Option<u64>,
|
||||
}
|
||||
// === impl EthPayloadBuilderAttributes ===
|
||||
|
||||
// === impl PayloadBuilderAttributes ===
|
||||
|
||||
impl PayloadBuilderAttributes {
|
||||
/// Creates a new payload builder for the given parent block and the attributes.
|
||||
///
|
||||
/// Derives the unique [PayloadId] for the given parent and attributes
|
||||
pub fn try_new(parent: B256, attributes: PayloadAttributes) -> Result<Self, DecodeError> {
|
||||
#[cfg(not(feature = "optimism"))]
|
||||
let id = payload_id(&parent, &attributes);
|
||||
|
||||
#[cfg(feature = "optimism")]
|
||||
let (id, transactions) = {
|
||||
let transactions: Vec<_> = attributes
|
||||
.optimism_payload_attributes
|
||||
.transactions
|
||||
.as_deref()
|
||||
.unwrap_or(&[])
|
||||
.iter()
|
||||
.map(|tx| TransactionSigned::decode_enveloped(&mut tx.as_ref()))
|
||||
.collect::<Result<_, _>>()?;
|
||||
(payload_id(&parent, &attributes, &transactions), transactions)
|
||||
};
|
||||
|
||||
let withdraw = attributes.withdrawals.map(
|
||||
|withdrawals: Vec<reth_rpc_types::engine::payload::Withdrawal>| {
|
||||
withdrawals
|
||||
.into_iter()
|
||||
.map(convert_standalone_withdraw_to_withdrawal) // Removed the parentheses here
|
||||
.collect::<Vec<_>>()
|
||||
},
|
||||
);
|
||||
|
||||
Ok(Self {
|
||||
id,
|
||||
parent,
|
||||
timestamp: attributes.timestamp,
|
||||
suggested_fee_recipient: attributes.suggested_fee_recipient,
|
||||
prev_randao: attributes.prev_randao,
|
||||
withdrawals: withdraw.unwrap_or_default(),
|
||||
parent_beacon_block_root: attributes.parent_beacon_block_root,
|
||||
#[cfg(feature = "optimism")]
|
||||
optimism_payload_attributes: OptimismPayloadBuilderAttributes {
|
||||
no_tx_pool: attributes.optimism_payload_attributes.no_tx_pool.unwrap_or_default(),
|
||||
transactions,
|
||||
gas_limit: attributes.optimism_payload_attributes.gas_limit,
|
||||
},
|
||||
})
|
||||
}
|
||||
impl EthPayloadBuilderAttributes {
|
||||
/// Returns the configured [CfgEnv] and [BlockEnv] for the targeted payload (that has the
|
||||
/// `parent` as its parent).
|
||||
///
|
||||
@@ -268,16 +208,205 @@ impl PayloadBuilderAttributes {
|
||||
pub fn payload_id(&self) -> PayloadId {
|
||||
self.id
|
||||
}
|
||||
|
||||
/// Creates a new payload builder for the given parent block and the attributes.
|
||||
///
|
||||
/// Derives the unique [PayloadId] for the given parent and attributes
|
||||
pub fn new(parent: B256, attributes: PayloadAttributes) -> Self {
|
||||
let id = payload_id(&parent, &attributes);
|
||||
|
||||
let withdraw = attributes.withdrawals.map(
|
||||
|withdrawals: Vec<reth_rpc_types::engine::payload::Withdrawal>| {
|
||||
withdrawals
|
||||
.into_iter()
|
||||
.map(convert_standalone_withdraw_to_withdrawal) // Removed the parentheses here
|
||||
.collect::<Vec<_>>()
|
||||
},
|
||||
);
|
||||
|
||||
Self {
|
||||
id,
|
||||
parent,
|
||||
timestamp: attributes.timestamp,
|
||||
suggested_fee_recipient: attributes.suggested_fee_recipient,
|
||||
prev_randao: attributes.prev_randao,
|
||||
withdrawals: withdraw.unwrap_or_default(),
|
||||
parent_beacon_block_root: attributes.parent_beacon_block_root,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates the payload id for the configured payload
|
||||
impl PayloadBuilderAttributes for EthPayloadBuilderAttributes {
|
||||
type RpcPayloadAttributes = PayloadAttributes;
|
||||
type Error = Infallible;
|
||||
|
||||
/// Creates a new payload builder for the given parent block and the attributes.
|
||||
///
|
||||
/// Derives the unique [PayloadId] for the given parent and attributes
|
||||
fn try_new(parent: B256, attributes: PayloadAttributes) -> Result<Self, Infallible> {
|
||||
Ok(Self::new(parent, attributes))
|
||||
}
|
||||
|
||||
fn parent(&self) -> B256 {
|
||||
self.parent
|
||||
}
|
||||
|
||||
fn payload_id(&self) -> PayloadId {
|
||||
self.id
|
||||
}
|
||||
|
||||
fn timestamp(&self) -> u64 {
|
||||
self.timestamp
|
||||
}
|
||||
|
||||
fn parent_beacon_block_root(&self) -> Option<B256> {
|
||||
self.parent_beacon_block_root
|
||||
}
|
||||
|
||||
fn suggested_fee_recipient(&self) -> Address {
|
||||
self.suggested_fee_recipient
|
||||
}
|
||||
|
||||
fn prev_randao(&self) -> B256 {
|
||||
self.prev_randao
|
||||
}
|
||||
|
||||
fn withdrawals(&self) -> &Vec<Withdrawal> {
|
||||
&self.withdrawals
|
||||
}
|
||||
}
|
||||
|
||||
/// Optimism Payload Builder Attributes
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct OptimismPayloadBuilderAttributes {
|
||||
/// Inner ethereum payload builder attributes
|
||||
pub payload_attributes: EthPayloadBuilderAttributes,
|
||||
/// NoTxPool option for the generated payload
|
||||
pub no_tx_pool: bool,
|
||||
/// Transactions for the generated payload
|
||||
pub transactions: Vec<TransactionSigned>,
|
||||
/// The gas limit for the generated payload
|
||||
pub gas_limit: Option<u64>,
|
||||
}
|
||||
|
||||
impl PayloadBuilderAttributes for OptimismPayloadBuilderAttributes {
|
||||
type RpcPayloadAttributes = OptimismPayloadAttributes;
|
||||
type Error = DecodeError;
|
||||
|
||||
/// Creates a new payload builder for the given parent block and the attributes.
|
||||
///
|
||||
/// Derives the unique [PayloadId] for the given parent and attributes
|
||||
fn try_new(parent: B256, attributes: OptimismPayloadAttributes) -> Result<Self, DecodeError> {
|
||||
let (id, transactions) = {
|
||||
let transactions: Vec<_> = attributes
|
||||
.transactions
|
||||
.as_deref()
|
||||
.unwrap_or(&[])
|
||||
.iter()
|
||||
.map(|tx| TransactionSigned::decode_enveloped(&mut tx.as_ref()))
|
||||
.collect::<Result<_, _>>()?;
|
||||
(payload_id_optimism(&parent, &attributes, &transactions), transactions)
|
||||
};
|
||||
|
||||
let withdraw = attributes.payload_attributes.withdrawals.map(
|
||||
|withdrawals: Vec<reth_rpc_types::engine::payload::Withdrawal>| {
|
||||
withdrawals
|
||||
.into_iter()
|
||||
.map(convert_standalone_withdraw_to_withdrawal) // Removed the parentheses here
|
||||
.collect::<Vec<_>>()
|
||||
},
|
||||
);
|
||||
|
||||
let payload_attributes = EthPayloadBuilderAttributes {
|
||||
id,
|
||||
parent,
|
||||
timestamp: attributes.payload_attributes.timestamp,
|
||||
suggested_fee_recipient: attributes.payload_attributes.suggested_fee_recipient,
|
||||
prev_randao: attributes.payload_attributes.prev_randao,
|
||||
withdrawals: withdraw.unwrap_or_default(),
|
||||
parent_beacon_block_root: attributes.payload_attributes.parent_beacon_block_root,
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
payload_attributes,
|
||||
no_tx_pool: attributes.no_tx_pool.unwrap_or_default(),
|
||||
transactions,
|
||||
gas_limit: attributes.gas_limit,
|
||||
})
|
||||
}
|
||||
|
||||
fn parent(&self) -> B256 {
|
||||
self.payload_attributes.parent
|
||||
}
|
||||
|
||||
fn payload_id(&self) -> PayloadId {
|
||||
self.payload_attributes.id
|
||||
}
|
||||
|
||||
fn timestamp(&self) -> u64 {
|
||||
self.payload_attributes.timestamp
|
||||
}
|
||||
|
||||
fn parent_beacon_block_root(&self) -> Option<B256> {
|
||||
self.payload_attributes.parent_beacon_block_root
|
||||
}
|
||||
|
||||
fn suggested_fee_recipient(&self) -> Address {
|
||||
self.payload_attributes.suggested_fee_recipient
|
||||
}
|
||||
|
||||
fn prev_randao(&self) -> B256 {
|
||||
self.payload_attributes.prev_randao
|
||||
}
|
||||
|
||||
fn withdrawals(&self) -> &Vec<Withdrawal> {
|
||||
&self.payload_attributes.withdrawals
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates the payload id for the configured payload from the [OptimismPayloadAttributes].
|
||||
///
|
||||
/// Returns an 8-byte identifier by hashing the payload components with sha256 hash.
|
||||
pub(crate) fn payload_id(
|
||||
pub(crate) fn payload_id_optimism(
|
||||
parent: &B256,
|
||||
attributes: &PayloadAttributes,
|
||||
#[cfg(feature = "optimism")] txs: &[TransactionSigned],
|
||||
attributes: &OptimismPayloadAttributes,
|
||||
txs: &[TransactionSigned],
|
||||
) -> PayloadId {
|
||||
use sha2::Digest;
|
||||
let mut hasher = sha2::Sha256::new();
|
||||
hasher.update(parent.as_slice());
|
||||
hasher.update(&attributes.payload_attributes.timestamp.to_be_bytes()[..]);
|
||||
hasher.update(attributes.payload_attributes.prev_randao.as_slice());
|
||||
hasher.update(attributes.payload_attributes.suggested_fee_recipient.as_slice());
|
||||
if let Some(withdrawals) = &attributes.payload_attributes.withdrawals {
|
||||
let mut buf = Vec::new();
|
||||
withdrawals.encode(&mut buf);
|
||||
hasher.update(buf);
|
||||
}
|
||||
|
||||
if let Some(parent_beacon_block) = attributes.payload_attributes.parent_beacon_block_root {
|
||||
hasher.update(parent_beacon_block);
|
||||
}
|
||||
|
||||
let no_tx_pool = attributes.no_tx_pool.unwrap_or_default();
|
||||
if no_tx_pool || !txs.is_empty() {
|
||||
hasher.update([no_tx_pool as u8]);
|
||||
hasher.update(txs.len().to_be_bytes());
|
||||
txs.iter().for_each(|tx| hasher.update(tx.hash()));
|
||||
}
|
||||
|
||||
if let Some(gas_limit) = attributes.gas_limit {
|
||||
hasher.update(gas_limit.to_be_bytes());
|
||||
}
|
||||
|
||||
let out = hasher.finalize();
|
||||
PayloadId::new(out.as_slice()[..8].try_into().expect("sufficient length"))
|
||||
}
|
||||
|
||||
/// Generates the payload id for the configured payload from the [PayloadAttributes].
|
||||
///
|
||||
/// Returns an 8-byte identifier by hashing the payload components with sha256 hash.
|
||||
pub(crate) fn payload_id(parent: &B256, attributes: &PayloadAttributes) -> PayloadId {
|
||||
use sha2::Digest;
|
||||
let mut hasher = sha2::Sha256::new();
|
||||
hasher.update(parent.as_slice());
|
||||
@@ -294,20 +423,6 @@ pub(crate) fn payload_id(
|
||||
hasher.update(parent_beacon_block);
|
||||
}
|
||||
|
||||
#[cfg(feature = "optimism")]
|
||||
{
|
||||
let no_tx_pool = attributes.optimism_payload_attributes.no_tx_pool.unwrap_or_default();
|
||||
if no_tx_pool || !txs.is_empty() {
|
||||
hasher.update([no_tx_pool as u8]);
|
||||
hasher.update(txs.len().to_be_bytes());
|
||||
txs.iter().for_each(|tx| hasher.update(tx.hash()));
|
||||
}
|
||||
|
||||
if let Some(gas_limit) = attributes.optimism_payload_attributes.gas_limit {
|
||||
hasher.update(gas_limit.to_be_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
let out = hasher.finalize();
|
||||
PayloadId::new(out.as_slice()[..8].try_into().expect("sufficient length"))
|
||||
}
|
||||
|
||||
@@ -5,9 +5,10 @@
|
||||
|
||||
use crate::{
|
||||
error::PayloadBuilderError, metrics::PayloadBuilderServiceMetrics, traits::PayloadJobGenerator,
|
||||
BuiltPayload, KeepPayloadJobAlive, PayloadBuilderAttributes, PayloadJob,
|
||||
BuiltPayload, KeepPayloadJobAlive, PayloadJob,
|
||||
};
|
||||
use futures_util::{future::FutureExt, Stream, StreamExt};
|
||||
use reth_node_api::{EngineTypes, PayloadBuilderAttributes};
|
||||
use reth_provider::CanonStateNotification;
|
||||
use reth_rpc_types::engine::PayloadId;
|
||||
use std::{
|
||||
@@ -23,13 +24,16 @@ use tracing::{debug, info, trace, warn};
|
||||
|
||||
/// A communication channel to the [PayloadBuilderService] that can retrieve payloads.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PayloadStore {
|
||||
inner: PayloadBuilderHandle,
|
||||
pub struct PayloadStore<Engine: EngineTypes> {
|
||||
inner: PayloadBuilderHandle<Engine>,
|
||||
}
|
||||
|
||||
// === impl PayloadStore ===
|
||||
|
||||
impl PayloadStore {
|
||||
impl<Engine> PayloadStore<Engine>
|
||||
where
|
||||
Engine: EngineTypes,
|
||||
{
|
||||
/// Resolves the payload job and returns the best payload that has been built so far.
|
||||
///
|
||||
/// Note: depending on the installed [PayloadJobGenerator], this may or may not terminate the
|
||||
@@ -57,13 +61,16 @@ impl PayloadStore {
|
||||
pub async fn payload_attributes(
|
||||
&self,
|
||||
id: PayloadId,
|
||||
) -> Option<Result<PayloadBuilderAttributes, PayloadBuilderError>> {
|
||||
) -> Option<Result<Engine::PayloadBuilderAttributes, PayloadBuilderError>> {
|
||||
self.inner.payload_attributes(id).await
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PayloadBuilderHandle> for PayloadStore {
|
||||
fn from(inner: PayloadBuilderHandle) -> Self {
|
||||
impl<Engine> From<PayloadBuilderHandle<Engine>> for PayloadStore<Engine>
|
||||
where
|
||||
Engine: EngineTypes,
|
||||
{
|
||||
fn from(inner: PayloadBuilderHandle<Engine>) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
@@ -72,18 +79,24 @@ impl From<PayloadBuilderHandle> for PayloadStore {
|
||||
///
|
||||
/// This is the API used to create new payloads and to get the current state of existing ones.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PayloadBuilderHandle {
|
||||
pub struct PayloadBuilderHandle<Engine: EngineTypes> {
|
||||
/// Sender half of the message channel to the [PayloadBuilderService].
|
||||
to_service: mpsc::UnboundedSender<PayloadServiceCommand>,
|
||||
to_service: mpsc::UnboundedSender<PayloadServiceCommand<Engine::PayloadBuilderAttributes>>,
|
||||
}
|
||||
|
||||
// === impl PayloadBuilderHandle ===
|
||||
|
||||
impl PayloadBuilderHandle {
|
||||
impl<Engine> PayloadBuilderHandle<Engine>
|
||||
where
|
||||
Engine: EngineTypes,
|
||||
{
|
||||
/// Creates a new payload builder handle for the given channel.
|
||||
///
|
||||
/// Note: this is only used internally by the [PayloadBuilderService] to manage the payload
|
||||
/// building flow See [PayloadBuilderService::poll] for implementation details.
|
||||
pub fn new(to_service: mpsc::UnboundedSender<PayloadServiceCommand>) -> Self {
|
||||
pub fn new(
|
||||
to_service: mpsc::UnboundedSender<PayloadServiceCommand<Engine::PayloadBuilderAttributes>>,
|
||||
) -> Self {
|
||||
Self { to_service }
|
||||
}
|
||||
|
||||
@@ -91,7 +104,7 @@ impl PayloadBuilderHandle {
|
||||
///
|
||||
/// Note: depending on the installed [PayloadJobGenerator], this may or may not terminate the
|
||||
/// job, See [PayloadJob::resolve].
|
||||
pub async fn resolve(
|
||||
async fn resolve(
|
||||
&self,
|
||||
id: PayloadId,
|
||||
) -> Option<Result<Arc<BuiltPayload>, PayloadBuilderError>> {
|
||||
@@ -104,7 +117,7 @@ impl PayloadBuilderHandle {
|
||||
}
|
||||
|
||||
/// Returns the best payload for the given identifier.
|
||||
pub async fn best_payload(
|
||||
async fn best_payload(
|
||||
&self,
|
||||
id: PayloadId,
|
||||
) -> Option<Result<Arc<BuiltPayload>, PayloadBuilderError>> {
|
||||
@@ -116,10 +129,10 @@ impl PayloadBuilderHandle {
|
||||
/// Returns the payload attributes associated with the given identifier.
|
||||
///
|
||||
/// Note: this returns the attributes of the payload and does not resolve the job.
|
||||
pub async fn payload_attributes(
|
||||
async fn payload_attributes(
|
||||
&self,
|
||||
id: PayloadId,
|
||||
) -> Option<Result<PayloadBuilderAttributes, PayloadBuilderError>> {
|
||||
) -> Option<Result<Engine::PayloadBuilderAttributes, PayloadBuilderError>> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
self.to_service.send(PayloadServiceCommand::PayloadAttributes(id, tx)).ok()?;
|
||||
rx.await.ok()?
|
||||
@@ -131,7 +144,7 @@ impl PayloadBuilderHandle {
|
||||
/// returns the receiver instead
|
||||
pub fn send_new_payload(
|
||||
&self,
|
||||
attr: PayloadBuilderAttributes,
|
||||
attr: Engine::PayloadBuilderAttributes,
|
||||
) -> oneshot::Receiver<Result<PayloadId, PayloadBuilderError>> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let _ = self.to_service.send(PayloadServiceCommand::BuildNewPayload(attr, tx));
|
||||
@@ -145,7 +158,7 @@ impl PayloadBuilderHandle {
|
||||
/// Note: if there's already payload in progress with same identifier, it will be returned.
|
||||
pub async fn new_payload(
|
||||
&self,
|
||||
attr: PayloadBuilderAttributes,
|
||||
attr: Engine::PayloadBuilderAttributes,
|
||||
) -> Result<PayloadId, PayloadBuilderError> {
|
||||
self.send_new_payload(attr).await?
|
||||
}
|
||||
@@ -161,18 +174,20 @@ impl PayloadBuilderHandle {
|
||||
/// does know nothing about how to build them, it just drives their jobs to completion.
|
||||
#[derive(Debug)]
|
||||
#[must_use = "futures do nothing unless you `.await` or poll them"]
|
||||
pub struct PayloadBuilderService<Gen, St>
|
||||
pub struct PayloadBuilderService<Gen, St, Engine>
|
||||
where
|
||||
Engine: EngineTypes,
|
||||
Gen: PayloadJobGenerator,
|
||||
Gen::Job: PayloadJob<PayloadAttributes = Engine::PayloadBuilderAttributes>,
|
||||
{
|
||||
/// The type that knows how to create new payloads.
|
||||
generator: Gen,
|
||||
/// All active payload jobs.
|
||||
payload_jobs: Vec<(Gen::Job, PayloadId)>,
|
||||
/// Copy of the sender half, so new [`PayloadBuilderHandle`] can be created on demand.
|
||||
service_tx: mpsc::UnboundedSender<PayloadServiceCommand>,
|
||||
service_tx: mpsc::UnboundedSender<PayloadServiceCommand<Engine::PayloadBuilderAttributes>>,
|
||||
/// Receiver half of the command channel.
|
||||
command_rx: UnboundedReceiverStream<PayloadServiceCommand>,
|
||||
command_rx: UnboundedReceiverStream<PayloadServiceCommand<Engine::PayloadBuilderAttributes>>,
|
||||
/// Metrics for the payload builder service
|
||||
metrics: PayloadBuilderServiceMetrics,
|
||||
/// Chain events notification stream
|
||||
@@ -181,16 +196,18 @@ where
|
||||
|
||||
// === impl PayloadBuilderService ===
|
||||
|
||||
impl<Gen, St> PayloadBuilderService<Gen, St>
|
||||
impl<Gen, St, Engine> PayloadBuilderService<Gen, St, Engine>
|
||||
where
|
||||
Engine: EngineTypes,
|
||||
Gen: PayloadJobGenerator,
|
||||
Gen::Job: PayloadJob<PayloadAttributes = Engine::PayloadBuilderAttributes>,
|
||||
{
|
||||
/// Creates a new payload builder service and returns the [PayloadBuilderHandle] to interact
|
||||
/// with it.
|
||||
///
|
||||
/// This also takes a stream of chain events that will be forwarded to the generator to apply
|
||||
/// additional logic when new state is committed. See also [PayloadJobGenerator::on_new_state].
|
||||
pub fn new(generator: Gen, chain_events: St) -> (Self, PayloadBuilderHandle) {
|
||||
pub fn new(generator: Gen, chain_events: St) -> (Self, PayloadBuilderHandle<Engine>) {
|
||||
let (service_tx, command_rx) = mpsc::unbounded_channel();
|
||||
let service = Self {
|
||||
generator,
|
||||
@@ -206,7 +223,7 @@ where
|
||||
}
|
||||
|
||||
/// Returns a handle to the service.
|
||||
pub fn handle(&self) -> PayloadBuilderHandle {
|
||||
pub fn handle(&self) -> PayloadBuilderHandle<Engine> {
|
||||
PayloadBuilderHandle::new(self.service_tx.clone())
|
||||
}
|
||||
|
||||
@@ -232,24 +249,6 @@ where
|
||||
res
|
||||
}
|
||||
|
||||
/// Returns the payload attributes for the given payload.
|
||||
fn payload_attributes(
|
||||
&self,
|
||||
id: PayloadId,
|
||||
) -> Option<Result<PayloadBuilderAttributes, PayloadBuilderError>> {
|
||||
let attributes = self
|
||||
.payload_jobs
|
||||
.iter()
|
||||
.find(|(_, job_id)| *job_id == id)
|
||||
.map(|(j, _)| j.payload_attributes());
|
||||
|
||||
if attributes.is_none() {
|
||||
trace!(%id, "no matching payload job found to get attributes for");
|
||||
}
|
||||
|
||||
attributes
|
||||
}
|
||||
|
||||
/// Returns the best payload for the given identifier that has been built so far and terminates
|
||||
/// the job if requested.
|
||||
fn resolve(&mut self, id: PayloadId) -> Option<PayloadFuture> {
|
||||
@@ -279,11 +278,38 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<Gen, St> Future for PayloadBuilderService<Gen, St>
|
||||
impl<Gen, St, Engine> PayloadBuilderService<Gen, St, Engine>
|
||||
where
|
||||
Engine: EngineTypes,
|
||||
Gen: PayloadJobGenerator,
|
||||
Gen::Job: PayloadJob<PayloadAttributes = Engine::PayloadBuilderAttributes>,
|
||||
{
|
||||
/// Returns the payload attributes for the given payload.
|
||||
fn payload_attributes(
|
||||
&self,
|
||||
id: PayloadId,
|
||||
) -> Option<Result<<Gen::Job as PayloadJob>::PayloadAttributes, PayloadBuilderError>> {
|
||||
let attributes = self
|
||||
.payload_jobs
|
||||
.iter()
|
||||
.find(|(_, job_id)| *job_id == id)
|
||||
.map(|(j, _)| j.payload_attributes());
|
||||
|
||||
if attributes.is_none() {
|
||||
trace!(%id, "no matching payload job found to get attributes for");
|
||||
}
|
||||
|
||||
attributes
|
||||
}
|
||||
}
|
||||
|
||||
impl<Gen, St, Engine> Future for PayloadBuilderService<Gen, St, Engine>
|
||||
where
|
||||
Engine: EngineTypes,
|
||||
Gen: PayloadJobGenerator + Unpin + 'static,
|
||||
<Gen as PayloadJobGenerator>::Job: Unpin + 'static,
|
||||
St: Stream<Item = CanonStateNotification> + Send + Unpin + 'static,
|
||||
Gen::Job: PayloadJob<PayloadAttributes = Engine::PayloadBuilderAttributes>,
|
||||
{
|
||||
type Output = ();
|
||||
|
||||
@@ -330,10 +356,10 @@ where
|
||||
let mut res = Ok(id);
|
||||
|
||||
if this.contains_payload(id) {
|
||||
debug!(%id, parent = %attr.parent, "Payload job already in progress, ignoring.");
|
||||
debug!(%id, parent = %attr.parent(), "Payload job already in progress, ignoring.");
|
||||
} else {
|
||||
// no job for this payload yet, create one
|
||||
let parent = attr.parent;
|
||||
let parent = attr.parent();
|
||||
match this.generator.new_payload_job(attr) {
|
||||
Ok(job) => {
|
||||
info!(%id, %parent, "New payload job created");
|
||||
@@ -371,28 +397,26 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: make generic over built payload type
|
||||
type PayloadFuture =
|
||||
Pin<Box<dyn Future<Output = Result<Arc<BuiltPayload>, PayloadBuilderError>> + Send + Sync>>;
|
||||
|
||||
/// Message type for the [PayloadBuilderService].
|
||||
pub enum PayloadServiceCommand {
|
||||
pub enum PayloadServiceCommand<T> {
|
||||
/// Start building a new payload.
|
||||
BuildNewPayload(
|
||||
PayloadBuilderAttributes,
|
||||
oneshot::Sender<Result<PayloadId, PayloadBuilderError>>,
|
||||
),
|
||||
BuildNewPayload(T, oneshot::Sender<Result<PayloadId, PayloadBuilderError>>),
|
||||
/// Get the best payload so far
|
||||
BestPayload(PayloadId, oneshot::Sender<Option<Result<Arc<BuiltPayload>, PayloadBuilderError>>>),
|
||||
/// Get the payload attributes for the given payload
|
||||
PayloadAttributes(
|
||||
PayloadId,
|
||||
oneshot::Sender<Option<Result<PayloadBuilderAttributes, PayloadBuilderError>>>,
|
||||
),
|
||||
PayloadAttributes(PayloadId, oneshot::Sender<Option<Result<T, PayloadBuilderError>>>),
|
||||
/// Resolve the payload and return the payload
|
||||
Resolve(PayloadId, oneshot::Sender<Option<PayloadFuture>>),
|
||||
}
|
||||
|
||||
impl fmt::Debug for PayloadServiceCommand {
|
||||
impl<T> fmt::Debug for PayloadServiceCommand<T>
|
||||
where
|
||||
T: fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
PayloadServiceCommand::BuildNewPayload(f0, f1) => {
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
|
||||
use crate::{
|
||||
error::PayloadBuilderError, traits::KeepPayloadJobAlive, BuiltPayload,
|
||||
PayloadBuilderAttributes, PayloadBuilderHandle, PayloadBuilderService, PayloadJob,
|
||||
EthPayloadBuilderAttributes, PayloadBuilderHandle, PayloadBuilderService, PayloadJob,
|
||||
PayloadJobGenerator,
|
||||
};
|
||||
use reth_node_api::EngineTypes;
|
||||
use reth_primitives::{Block, U256};
|
||||
use reth_provider::CanonStateNotification;
|
||||
use std::{
|
||||
@@ -15,18 +16,25 @@ use std::{
|
||||
};
|
||||
|
||||
/// Creates a new [PayloadBuilderService] for testing purposes.
|
||||
pub fn test_payload_service() -> (
|
||||
pub fn test_payload_service<Engine>() -> (
|
||||
PayloadBuilderService<
|
||||
TestPayloadJobGenerator,
|
||||
futures_util::stream::Empty<CanonStateNotification>,
|
||||
Engine,
|
||||
>,
|
||||
PayloadBuilderHandle,
|
||||
) {
|
||||
PayloadBuilderHandle<Engine>,
|
||||
)
|
||||
where
|
||||
Engine: EngineTypes<PayloadBuilderAttributes = EthPayloadBuilderAttributes>,
|
||||
{
|
||||
PayloadBuilderService::new(Default::default(), futures_util::stream::empty())
|
||||
}
|
||||
|
||||
/// Creates a new [PayloadBuilderService] for testing purposes and spawns it in the background.
|
||||
pub fn spawn_test_payload_service() -> PayloadBuilderHandle {
|
||||
pub fn spawn_test_payload_service<Engine>() -> PayloadBuilderHandle<Engine>
|
||||
where
|
||||
Engine: EngineTypes<PayloadBuilderAttributes = EthPayloadBuilderAttributes> + 'static,
|
||||
{
|
||||
let (service, handle) = test_payload_service();
|
||||
tokio::spawn(service);
|
||||
handle
|
||||
@@ -42,7 +50,7 @@ impl PayloadJobGenerator for TestPayloadJobGenerator {
|
||||
|
||||
fn new_payload_job(
|
||||
&self,
|
||||
attr: PayloadBuilderAttributes,
|
||||
attr: EthPayloadBuilderAttributes,
|
||||
) -> Result<Self::Job, PayloadBuilderError> {
|
||||
Ok(TestPayloadJob { attr })
|
||||
}
|
||||
@@ -51,7 +59,7 @@ impl PayloadJobGenerator for TestPayloadJobGenerator {
|
||||
/// A [PayloadJobGenerator] for testing purposes
|
||||
#[derive(Debug)]
|
||||
pub struct TestPayloadJob {
|
||||
attr: PayloadBuilderAttributes,
|
||||
attr: EthPayloadBuilderAttributes,
|
||||
}
|
||||
|
||||
impl Future for TestPayloadJob {
|
||||
@@ -63,6 +71,7 @@ impl Future for TestPayloadJob {
|
||||
}
|
||||
|
||||
impl PayloadJob for TestPayloadJob {
|
||||
type PayloadAttributes = EthPayloadBuilderAttributes;
|
||||
type ResolvePayloadFuture =
|
||||
futures_util::future::Ready<Result<Arc<BuiltPayload>, PayloadBuilderError>>;
|
||||
|
||||
@@ -74,7 +83,7 @@ impl PayloadJob for TestPayloadJob {
|
||||
)))
|
||||
}
|
||||
|
||||
fn payload_attributes(&self) -> Result<PayloadBuilderAttributes, PayloadBuilderError> {
|
||||
fn payload_attributes(&self) -> Result<EthPayloadBuilderAttributes, PayloadBuilderError> {
|
||||
Ok(self.attr.clone())
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
//! Trait abstractions used by the payload crate.
|
||||
|
||||
use crate::{error::PayloadBuilderError, BuiltPayload};
|
||||
use reth_node_api::PayloadBuilderAttributes;
|
||||
use reth_provider::CanonStateNotification;
|
||||
|
||||
use crate::{error::PayloadBuilderError, BuiltPayload, PayloadBuilderAttributes};
|
||||
use std::{future::Future, sync::Arc};
|
||||
|
||||
/// A type that can build a payload.
|
||||
@@ -17,6 +17,8 @@ use std::{future::Future, sync::Arc};
|
||||
///
|
||||
/// Note: A `PayloadJob` need to be cancel safe because it might be dropped after the CL has requested the payload via `engine_getPayloadV1` (see also [engine API docs](https://github.com/ethereum/execution-apis/blob/6709c2a795b707202e93c4f2867fa0bf2640a84f/src/engine/paris.md#engine_getpayloadv1))
|
||||
pub trait PayloadJob: Future<Output = Result<(), PayloadBuilderError>> + Send + Sync {
|
||||
/// Represents the payload attributes type that is used to spawn this payload job.
|
||||
type PayloadAttributes: PayloadBuilderAttributes + std::fmt::Debug;
|
||||
/// Represents the future that resolves the block that's returned to the CL.
|
||||
type ResolvePayloadFuture: Future<Output = Result<Arc<BuiltPayload>, PayloadBuilderError>>
|
||||
+ Send
|
||||
@@ -29,7 +31,7 @@ pub trait PayloadJob: Future<Output = Result<(), PayloadBuilderError>> + Send +
|
||||
fn best_payload(&self) -> Result<Arc<BuiltPayload>, PayloadBuilderError>;
|
||||
|
||||
/// Returns the payload attributes for the payload being built.
|
||||
fn payload_attributes(&self) -> Result<PayloadBuilderAttributes, PayloadBuilderError>;
|
||||
fn payload_attributes(&self) -> Result<Self::PayloadAttributes, PayloadBuilderError>;
|
||||
|
||||
/// Called when the payload is requested by the CL.
|
||||
///
|
||||
@@ -80,7 +82,7 @@ pub trait PayloadJobGenerator: Send + Sync {
|
||||
/// returned directly.
|
||||
fn new_payload_job(
|
||||
&self,
|
||||
attr: PayloadBuilderAttributes,
|
||||
attr: <Self::Job as PayloadJob>::PayloadAttributes,
|
||||
) -> Result<Self::Job, PayloadBuilderError>;
|
||||
|
||||
/// Handles new chain state events
|
||||
|
||||
Reference in New Issue
Block a user