feat: make payload builder generic over attributes type (#5948)

Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
This commit is contained in:
Dan Cline
2024-01-10 05:21:43 -05:00
committed by GitHub
parent 42326fd2a5
commit cb96fe6d09
50 changed files with 1912 additions and 999 deletions

View File

@@ -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};

View File

@@ -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> {

View File

@@ -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"))
}

View File

@@ -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) => {

View File

@@ -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())
}

View File

@@ -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