Skip to main content

reth_basic_payload_builder/
lib.rs

1//! A basic payload generator for reth.
2
3#![doc(
4    html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png",
5    html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256",
6    issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/"
7)]
8#![cfg_attr(not(test), warn(unused_crate_dependencies))]
9#![cfg_attr(docsrs, feature(doc_cfg))]
10
11use crate::metrics::PayloadBuilderMetrics;
12use alloy_eips::merge::SLOT_DURATION;
13use alloy_primitives::{B256, U256};
14use futures_core::ready;
15use futures_util::FutureExt;
16use reth_chain_state::CanonStateNotification;
17use reth_payload_builder::{KeepPayloadJobAlive, PayloadId, PayloadJob, PayloadJobGenerator};
18use reth_payload_builder_primitives::PayloadBuilderError;
19use reth_payload_primitives::{BuiltPayload, PayloadBuilderAttributes, PayloadKind};
20use reth_primitives_traits::{HeaderTy, NodePrimitives, SealedHeader};
21use reth_revm::{cached::CachedReads, cancelled::CancelOnDrop};
22use reth_storage_api::{BlockReaderIdExt, StateProviderFactory};
23use reth_tasks::Runtime;
24use std::{
25    fmt,
26    future::Future,
27    ops::Deref,
28    pin::Pin,
29    sync::Arc,
30    task::{Context, Poll},
31    time::{Duration, SystemTime, UNIX_EPOCH},
32};
33use tokio::{
34    sync::{oneshot, Semaphore},
35    time::{Interval, Sleep},
36};
37use tracing::{debug, trace, warn};
38
39mod better_payload_emitter;
40mod metrics;
41mod stack;
42
43pub use better_payload_emitter::BetterPayloadEmitter;
44pub use stack::PayloadBuilderStack;
45
46/// Helper to access [`NodePrimitives::BlockHeader`] from [`PayloadBuilder::BuiltPayload`].
47pub type HeaderForPayload<P> = <<P as BuiltPayload>::Primitives as NodePrimitives>::BlockHeader;
48
49/// The [`PayloadJobGenerator`] that creates [`BasicPayloadJob`]s.
50#[derive(Debug)]
51pub struct BasicPayloadJobGenerator<Client, Builder> {
52    /// The client that can interact with the chain.
53    client: Client,
54    /// The task executor to spawn payload building tasks on.
55    executor: Runtime,
56    /// The configuration for the job generator.
57    config: BasicPayloadJobGeneratorConfig,
58    /// Restricts how many generator tasks can be executed at once.
59    payload_task_guard: PayloadTaskGuard,
60    /// The type responsible for building payloads.
61    ///
62    /// See [`PayloadBuilder`]
63    builder: Builder,
64    /// Stored `cached_reads` for new payload jobs.
65    pre_cached: Option<PrecachedState>,
66}
67
68// === impl BasicPayloadJobGenerator ===
69
70impl<Client, Builder> BasicPayloadJobGenerator<Client, Builder> {
71    /// Creates a new [`BasicPayloadJobGenerator`] with the given config and custom
72    /// [`PayloadBuilder`]
73    pub fn with_builder(
74        client: Client,
75        executor: Runtime,
76        config: BasicPayloadJobGeneratorConfig,
77        builder: Builder,
78    ) -> Self {
79        Self {
80            client,
81            executor,
82            payload_task_guard: PayloadTaskGuard::new(config.max_payload_tasks),
83            config,
84            builder,
85            pre_cached: None,
86        }
87    }
88
89    /// Returns the maximum duration a job should be allowed to run.
90    ///
91    /// This adheres to the following specification:
92    /// > Client software SHOULD stop the updating process when either a call to engine_getPayload
93    /// > with the build process's payloadId is made or SECONDS_PER_SLOT (12s in the Mainnet
94    /// > configuration) have passed since the point in time identified by the timestamp parameter.
95    ///
96    /// See also <https://github.com/ethereum/execution-apis/blob/431cf72fd3403d946ca3e3afc36b973fc87e0e89/src/engine/paris.md?plain=1#L137>
97    #[inline]
98    fn max_job_duration(&self, unix_timestamp: u64) -> Duration {
99        let duration_until_timestamp = duration_until(unix_timestamp);
100
101        // safety in case clocks are bad
102        let duration_until_timestamp = duration_until_timestamp.min(self.config.deadline * 3);
103
104        self.config.deadline + duration_until_timestamp
105    }
106
107    /// Returns the [Instant](tokio::time::Instant) at which the job should be terminated because it
108    /// is considered timed out.
109    #[inline]
110    fn job_deadline(&self, unix_timestamp: u64) -> tokio::time::Instant {
111        tokio::time::Instant::now() + self.max_job_duration(unix_timestamp)
112    }
113
114    /// Returns a reference to the tasks type
115    pub const fn tasks(&self) -> &Runtime {
116        &self.executor
117    }
118
119    /// Returns the pre-cached reads for the given parent header if it matches the cached state's
120    /// block.
121    fn maybe_pre_cached(&self, parent: B256) -> Option<CachedReads> {
122        self.pre_cached.as_ref().filter(|pc| pc.block == parent).map(|pc| pc.cached.clone())
123    }
124}
125
126// === impl BasicPayloadJobGenerator ===
127
128impl<Client, Builder> PayloadJobGenerator for BasicPayloadJobGenerator<Client, Builder>
129where
130    Client: StateProviderFactory
131        + BlockReaderIdExt<Header = HeaderForPayload<Builder::BuiltPayload>>
132        + Clone
133        + Unpin
134        + 'static,
135    Builder: PayloadBuilder + Unpin + 'static,
136    Builder::Attributes: Unpin + Clone,
137    Builder::BuiltPayload: Unpin + Clone,
138{
139    type Job = BasicPayloadJob<Builder>;
140
141    fn new_payload_job(
142        &self,
143        attributes: <Self::Job as PayloadJob>::PayloadAttributes,
144    ) -> Result<Self::Job, PayloadBuilderError> {
145        let parent_header = if attributes.parent().is_zero() {
146            // Use latest header for genesis block case
147            self.client
148                .latest_header()
149                .map_err(PayloadBuilderError::from)?
150                .ok_or_else(|| PayloadBuilderError::MissingParentHeader(B256::ZERO))?
151        } else {
152            // Fetch specific header by hash
153            self.client
154                .sealed_header_by_hash(attributes.parent())
155                .map_err(PayloadBuilderError::from)?
156                .ok_or_else(|| PayloadBuilderError::MissingParentHeader(attributes.parent()))?
157        };
158
159        let cached_reads = self.maybe_pre_cached(parent_header.hash());
160
161        let config = PayloadConfig::new(Arc::new(parent_header), attributes);
162
163        let until = self.job_deadline(config.attributes.timestamp());
164        let deadline = Box::pin(tokio::time::sleep_until(until));
165
166        let mut job = BasicPayloadJob {
167            config,
168            executor: self.executor.clone(),
169            deadline,
170            // ticks immediately
171            interval: tokio::time::interval(self.config.interval),
172            best_payload: PayloadState::Missing,
173            pending_block: None,
174            cached_reads,
175            payload_task_guard: self.payload_task_guard.clone(),
176            metrics: Default::default(),
177            builder: self.builder.clone(),
178        };
179
180        // start the first job right away
181        job.spawn_build_job();
182
183        Ok(job)
184    }
185
186    fn on_new_state<N: NodePrimitives>(&mut self, new_state: CanonStateNotification<N>) {
187        let mut cached = CachedReads::default();
188
189        // extract the state from the notification and put it into the cache
190        let committed = new_state.committed();
191        let new_execution_outcome = committed.execution_outcome();
192        for (addr, acc) in new_execution_outcome.bundle_accounts_iter() {
193            if let Some(info) = acc.info.clone() {
194                // we want pre cache existing accounts and their storage
195                // this only includes changed accounts and storage but is better than nothing
196                let storage =
197                    acc.storage.iter().map(|(key, slot)| (*key, slot.present_value)).collect();
198                cached.insert_account(addr, info, storage);
199            }
200        }
201
202        self.pre_cached = Some(PrecachedState { block: committed.tip().hash(), cached });
203    }
204}
205
206/// Pre-filled [`CachedReads`] for a specific block.
207///
208/// This is extracted from the [`CanonStateNotification`] for the tip block.
209#[derive(Debug, Clone)]
210pub struct PrecachedState {
211    /// The block for which the state is pre-cached.
212    pub block: B256,
213    /// Cached state for the block.
214    pub cached: CachedReads,
215}
216
217/// Restricts how many generator tasks can be executed at once.
218#[derive(Debug, Clone)]
219pub struct PayloadTaskGuard(Arc<Semaphore>);
220
221impl Deref for PayloadTaskGuard {
222    type Target = Semaphore;
223
224    fn deref(&self) -> &Self::Target {
225        &self.0
226    }
227}
228
229// === impl PayloadTaskGuard ===
230
231impl PayloadTaskGuard {
232    /// Constructs `Self` with a maximum task count of `max_payload_tasks`.
233    pub fn new(max_payload_tasks: usize) -> Self {
234        Self(Arc::new(Semaphore::new(max_payload_tasks)))
235    }
236}
237
238/// Settings for the [`BasicPayloadJobGenerator`].
239#[derive(Debug, Clone)]
240pub struct BasicPayloadJobGeneratorConfig {
241    /// The interval at which the job should build a new payload after the last.
242    interval: Duration,
243    /// The deadline for when the payload builder job should resolve.
244    ///
245    /// By default this is [`SLOT_DURATION`]: 12s
246    deadline: Duration,
247    /// Maximum number of tasks to spawn for building a payload.
248    max_payload_tasks: usize,
249}
250
251// === impl BasicPayloadJobGeneratorConfig ===
252
253impl BasicPayloadJobGeneratorConfig {
254    /// Sets the interval at which the job should build a new payload after the last.
255    pub const fn interval(mut self, interval: Duration) -> Self {
256        self.interval = interval;
257        self
258    }
259
260    /// Sets the deadline when this job should resolve.
261    pub const fn deadline(mut self, deadline: Duration) -> Self {
262        self.deadline = deadline;
263        self
264    }
265
266    /// Sets the maximum number of tasks to spawn for building a payload(s).
267    ///
268    /// # Panics
269    ///
270    /// If `max_payload_tasks` is 0.
271    pub fn max_payload_tasks(mut self, max_payload_tasks: usize) -> Self {
272        assert!(max_payload_tasks > 0, "max_payload_tasks must be greater than 0");
273        self.max_payload_tasks = max_payload_tasks;
274        self
275    }
276}
277
278impl Default for BasicPayloadJobGeneratorConfig {
279    fn default() -> Self {
280        Self {
281            interval: Duration::from_secs(1),
282            // 12s slot time
283            deadline: SLOT_DURATION,
284            max_payload_tasks: 3,
285        }
286    }
287}
288
289/// A basic payload job that continuously builds a payload with the best transactions from the pool.
290///
291/// This type is a [`PayloadJob`] and [`Future`] that terminates when the deadline is reached or
292/// when the job is resolved: [`PayloadJob::resolve`].
293///
294/// This basic job implementation will trigger new payload build task continuously until the job is
295/// resolved or the deadline is reached, or until the built payload is marked as frozen:
296/// [`BuildOutcome::Freeze`]. Once a frozen payload is returned, no additional payloads will be
297/// built and this future will wait to be resolved: [`PayloadJob::resolve`] or terminated if the
298/// deadline is reached.
299#[derive(Debug)]
300pub struct BasicPayloadJob<Builder>
301where
302    Builder: PayloadBuilder,
303{
304    /// The configuration for how the payload will be created.
305    config: PayloadConfig<Builder::Attributes, HeaderForPayload<Builder::BuiltPayload>>,
306    /// How to spawn building tasks
307    executor: Runtime,
308    /// The deadline when this job should resolve.
309    deadline: Pin<Box<Sleep>>,
310    /// The interval at which the job should build a new payload after the last.
311    interval: Interval,
312    /// The best payload so far and its state.
313    best_payload: PayloadState<Builder::BuiltPayload>,
314    /// Receiver for the block that is currently being built.
315    pending_block: Option<PendingPayload<Builder::BuiltPayload>>,
316    /// Restricts how many generator tasks can be executed at once.
317    payload_task_guard: PayloadTaskGuard,
318    /// Caches all disk reads for the state the new payloads builds on
319    ///
320    /// This is used to avoid reading the same state over and over again when new attempts are
321    /// triggered, because during the building process we'll repeatedly execute the transactions.
322    cached_reads: Option<CachedReads>,
323    /// metrics for this type
324    metrics: PayloadBuilderMetrics,
325    /// The type responsible for building payloads.
326    ///
327    /// See [`PayloadBuilder`]
328    builder: Builder,
329}
330
331impl<Builder> BasicPayloadJob<Builder>
332where
333    Builder: PayloadBuilder + Unpin + 'static,
334    Builder::Attributes: Unpin + Clone,
335    Builder::BuiltPayload: Unpin + Clone,
336{
337    /// Spawns a new payload build task.
338    fn spawn_build_job(&mut self) {
339        trace!(target: "payload_builder", id = %self.config.payload_id(), "spawn new payload build task");
340        let (tx, rx) = oneshot::channel();
341        let cancel = CancelOnDrop::default();
342        let _cancel = cancel.clone();
343        let guard = self.payload_task_guard.clone();
344        let payload_config = self.config.clone();
345        let best_payload = self.best_payload.payload().cloned();
346        self.metrics.inc_initiated_payload_builds();
347        let cached_reads = self.cached_reads.take().unwrap_or_default();
348        let builder = self.builder.clone();
349        self.executor.spawn_blocking_task(async move {
350            // acquire the permit for executing the task
351            let _permit = guard.acquire().await;
352            let args =
353                BuildArguments { cached_reads, config: payload_config, cancel, best_payload };
354            let result = builder.try_build(args);
355            let _ = tx.send(result);
356        });
357
358        self.pending_block = Some(PendingPayload { _cancel, payload: rx });
359    }
360}
361
362impl<Builder> Future for BasicPayloadJob<Builder>
363where
364    Builder: PayloadBuilder + Unpin + 'static,
365    Builder::Attributes: Unpin + Clone,
366    Builder::BuiltPayload: Unpin + Clone,
367{
368    type Output = Result<(), PayloadBuilderError>;
369
370    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
371        let this = self.get_mut();
372
373        // check if the deadline is reached
374        if this.deadline.as_mut().poll(cx).is_ready() {
375            trace!(target: "payload_builder", "payload building deadline reached");
376            return Poll::Ready(Ok(()))
377        }
378
379        loop {
380            // Wait for any pending build to complete before polling the next tick.
381            //
382            // This avoids consuming interval ticks while a build is still in-flight,
383            // which would delay the follow-up build by a full interval even though
384            // the current attempt has already finished.
385            if let Some(mut fut) = this.pending_block.take() {
386                match fut.poll_unpin(cx) {
387                    Poll::Ready(Ok(outcome)) => match outcome {
388                        BuildOutcome::Better { payload, cached_reads } => {
389                            this.cached_reads = Some(cached_reads);
390                            debug!(target: "payload_builder", value = %payload.fees(), "built better payload");
391                            this.best_payload = PayloadState::Best(payload);
392                        }
393                        BuildOutcome::Freeze(payload) => {
394                            debug!(target: "payload_builder", "payload frozen, no further building will occur");
395                            this.best_payload = PayloadState::Frozen(payload);
396                        }
397                        BuildOutcome::Aborted { fees, cached_reads } => {
398                            this.cached_reads = Some(cached_reads);
399                            trace!(target: "payload_builder", worse_fees = %fees, "skipped payload build of worse block");
400                        }
401                        BuildOutcome::Cancelled => {
402                            unreachable!("the cancel signal never fired")
403                        }
404                    },
405                    Poll::Ready(Err(error)) => {
406                        // job failed, but we simply try again next interval
407                        debug!(target: "payload_builder", %error, "payload build attempt failed");
408                        this.metrics.inc_failed_payload_builds();
409                    }
410                    Poll::Pending => {
411                        this.pending_block = Some(fut);
412                        return Poll::Pending
413                    }
414                }
415            }
416
417            if this.best_payload.is_frozen() {
418                return Poll::Pending
419            }
420
421            // Wait for the next build interval tick.
422            //
423            // The loop is needed because `poll_tick` does not register a waker
424            // when it returns `Ready`, so we must loop back after spawning a job
425            // to reach a point that *does* register one (the pending block poll above).
426            ready!(this.interval.poll_tick(cx));
427            this.spawn_build_job()
428        }
429    }
430}
431
432impl<Builder> PayloadJob for BasicPayloadJob<Builder>
433where
434    Builder: PayloadBuilder + Unpin + 'static,
435    Builder::Attributes: Unpin + Clone,
436    Builder::BuiltPayload: Unpin + Clone,
437{
438    type PayloadAttributes = Builder::Attributes;
439    type ResolvePayloadFuture = ResolveBestPayload<Self::BuiltPayload>;
440    type BuiltPayload = Builder::BuiltPayload;
441
442    fn best_payload(&self) -> Result<Self::BuiltPayload, PayloadBuilderError> {
443        if let Some(payload) = self.best_payload.payload() {
444            Ok(payload.clone())
445        } else {
446            // No payload has been built yet, but we need to return something that the CL then
447            // can deliver, so we need to return an empty payload.
448            //
449            // Note: it is assumed that this is unlikely to happen, as the payload job is
450            // started right away and the first full block should have been
451            // built by the time CL is requesting the payload.
452            self.metrics.inc_requested_empty_payload();
453            self.builder.build_empty_payload(self.config.clone())
454        }
455    }
456
457    fn payload_attributes(&self) -> Result<Self::PayloadAttributes, PayloadBuilderError> {
458        Ok(self.config.attributes.clone())
459    }
460
461    fn payload_timestamp(&self) -> Result<u64, PayloadBuilderError> {
462        Ok(self.config.attributes.timestamp())
463    }
464
465    fn resolve_kind(
466        &mut self,
467        kind: PayloadKind,
468    ) -> (Self::ResolvePayloadFuture, KeepPayloadJobAlive) {
469        let best_payload = self.best_payload.payload().cloned();
470        if best_payload.is_none() && self.pending_block.is_none() {
471            // ensure we have a job scheduled if we don't have a best payload yet and none is active
472            self.spawn_build_job();
473        }
474
475        let maybe_better = self.pending_block.take();
476        let mut empty_payload = None;
477
478        if best_payload.is_none() {
479            debug!(target: "payload_builder", id=%self.config.payload_id(), "no best payload yet to resolve, building empty payload");
480
481            let args = BuildArguments {
482                cached_reads: self.cached_reads.take().unwrap_or_default(),
483                config: self.config.clone(),
484                cancel: CancelOnDrop::default(),
485                best_payload: None,
486            };
487
488            match self.builder.on_missing_payload(args) {
489                MissingPayloadBehaviour::AwaitInProgress => {
490                    debug!(target: "payload_builder", id=%self.config.payload_id(), "awaiting in progress payload build job");
491                }
492                MissingPayloadBehaviour::RaceEmptyPayload => {
493                    debug!(target: "payload_builder", id=%self.config.payload_id(), "racing empty payload");
494
495                    // if no payload has been built yet
496                    self.metrics.inc_requested_empty_payload();
497                    // no payload built yet, so we need to return an empty payload
498                    let (tx, rx) = oneshot::channel();
499                    let config = self.config.clone();
500                    let builder = self.builder.clone();
501                    self.executor.spawn_blocking_task(async move {
502                        let res = builder.build_empty_payload(config);
503                        let _ = tx.send(res);
504                    });
505
506                    empty_payload = Some(rx);
507                }
508                MissingPayloadBehaviour::RacePayload(job) => {
509                    debug!(target: "payload_builder", id=%self.config.payload_id(), "racing fallback payload");
510                    // race the in progress job with this job
511                    let (tx, rx) = oneshot::channel();
512                    self.executor.spawn_blocking_task(async move {
513                        let _ = tx.send(job());
514                    });
515                    empty_payload = Some(rx);
516                }
517            };
518        }
519
520        let fut = ResolveBestPayload {
521            best_payload,
522            maybe_better,
523            empty_payload: empty_payload.filter(|_| kind != PayloadKind::WaitForPending),
524        };
525
526        (fut, KeepPayloadJobAlive::No)
527    }
528}
529
530/// Represents the current state of a payload being built.
531#[derive(Debug, Clone)]
532pub enum PayloadState<P> {
533    /// No payload has been built yet.
534    Missing,
535    /// The best payload built so far, which may still be improved upon.
536    Best(P),
537    /// The payload is frozen and no further building should occur.
538    ///
539    /// Contains the final payload `P` that should be used.
540    Frozen(P),
541}
542
543impl<P> PayloadState<P> {
544    /// Checks if the payload is frozen.
545    pub const fn is_frozen(&self) -> bool {
546        matches!(self, Self::Frozen(_))
547    }
548
549    /// Returns the payload if it exists (either Best or Frozen).
550    pub const fn payload(&self) -> Option<&P> {
551        match self {
552            Self::Missing => None,
553            Self::Best(p) | Self::Frozen(p) => Some(p),
554        }
555    }
556}
557
558/// The future that returns the best payload to be served to the consensus layer.
559///
560/// This returns the payload that's supposed to be sent to the CL.
561///
562/// If payload has been built so far, it will return that, but it will check if there's a better
563/// payload available from an in progress build job. If so it will return that.
564///
565/// If no payload has been built so far, it will either return an empty payload or the result of the
566/// in progress build job, whatever finishes first.
567#[derive(Debug)]
568pub struct ResolveBestPayload<Payload> {
569    /// Best payload so far.
570    pub best_payload: Option<Payload>,
571    /// Regular payload job that's currently running that might produce a better payload.
572    pub maybe_better: Option<PendingPayload<Payload>>,
573    /// The empty payload building job in progress, if any.
574    pub empty_payload: Option<oneshot::Receiver<Result<Payload, PayloadBuilderError>>>,
575}
576
577impl<Payload> ResolveBestPayload<Payload> {
578    const fn is_empty(&self) -> bool {
579        self.best_payload.is_none() && self.maybe_better.is_none() && self.empty_payload.is_none()
580    }
581}
582
583impl<Payload> Future for ResolveBestPayload<Payload>
584where
585    Payload: Unpin,
586{
587    type Output = Result<Payload, PayloadBuilderError>;
588
589    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
590        let this = self.get_mut();
591
592        // check if there is a better payload before returning the best payload
593        if let Some(fut) = Pin::new(&mut this.maybe_better).as_pin_mut() &&
594            let Poll::Ready(res) = fut.poll(cx)
595        {
596            this.maybe_better = None;
597            if let Ok(Some(payload)) = res.map(|out| out.into_payload()).inspect_err(
598                |err| warn!(target: "payload_builder", %err, "failed to resolve pending payload"),
599            ) {
600                debug!(target: "payload_builder", "resolving better payload");
601                return Poll::Ready(Ok(payload))
602            }
603        }
604
605        if let Some(best) = this.best_payload.take() {
606            debug!(target: "payload_builder", "resolving best payload");
607            return Poll::Ready(Ok(best))
608        }
609
610        if let Some(fut) = Pin::new(&mut this.empty_payload).as_pin_mut() &&
611            let Poll::Ready(res) = fut.poll(cx)
612        {
613            this.empty_payload = None;
614            return match res {
615                Ok(res) => {
616                    if let Err(err) = &res {
617                        warn!(target: "payload_builder", %err, "failed to resolve empty payload");
618                    } else {
619                        debug!(target: "payload_builder", "resolving empty payload");
620                    }
621                    Poll::Ready(res)
622                }
623                Err(err) => Poll::Ready(Err(err.into())),
624            }
625        }
626
627        if this.is_empty() {
628            return Poll::Ready(Err(PayloadBuilderError::MissingPayload))
629        }
630
631        Poll::Pending
632    }
633}
634
635/// A future that resolves to the result of the block building job.
636#[derive(Debug)]
637pub struct PendingPayload<P> {
638    /// The marker to cancel the job on drop
639    _cancel: CancelOnDrop,
640    /// The channel to send the result to.
641    payload: oneshot::Receiver<Result<BuildOutcome<P>, PayloadBuilderError>>,
642}
643
644impl<P> PendingPayload<P> {
645    /// Constructs a `PendingPayload` future.
646    pub const fn new(
647        cancel: CancelOnDrop,
648        payload: oneshot::Receiver<Result<BuildOutcome<P>, PayloadBuilderError>>,
649    ) -> Self {
650        Self { _cancel: cancel, payload }
651    }
652}
653
654impl<P> Future for PendingPayload<P> {
655    type Output = Result<BuildOutcome<P>, PayloadBuilderError>;
656
657    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
658        let res = ready!(self.payload.poll_unpin(cx));
659        Poll::Ready(res.map_err(Into::into).and_then(|res| res))
660    }
661}
662
663/// Static config for how to build a payload.
664#[derive(Clone, Debug)]
665pub struct PayloadConfig<Attributes, Header = alloy_consensus::Header> {
666    /// The parent header.
667    pub parent_header: Arc<SealedHeader<Header>>,
668    /// Requested attributes for the payload.
669    pub attributes: Attributes,
670}
671
672impl<Attributes, Header> PayloadConfig<Attributes, Header>
673where
674    Attributes: PayloadBuilderAttributes,
675{
676    /// Create new payload config.
677    pub const fn new(parent_header: Arc<SealedHeader<Header>>, attributes: Attributes) -> Self {
678        Self { parent_header, attributes }
679    }
680
681    /// Returns the payload id.
682    pub fn payload_id(&self) -> PayloadId {
683        self.attributes.payload_id()
684    }
685}
686
687/// The possible outcomes of a payload building attempt.
688#[derive(Debug)]
689pub enum BuildOutcome<Payload> {
690    /// Successfully built a better block.
691    Better {
692        /// The new payload that was built.
693        payload: Payload,
694        /// The cached reads that were used to build the payload.
695        cached_reads: CachedReads,
696    },
697    /// Aborted payload building because resulted in worse block wrt. fees.
698    Aborted {
699        /// The total fees associated with the attempted payload.
700        fees: U256,
701        /// The cached reads that were used to build the payload.
702        cached_reads: CachedReads,
703    },
704    /// Build job was cancelled
705    Cancelled,
706
707    /// The payload is final and no further building should occur
708    Freeze(Payload),
709}
710
711impl<Payload> BuildOutcome<Payload> {
712    /// Consumes the type and returns the payload if the outcome is `Better` or `Freeze`.
713    pub fn into_payload(self) -> Option<Payload> {
714        match self {
715            Self::Better { payload, .. } | Self::Freeze(payload) => Some(payload),
716            _ => None,
717        }
718    }
719
720    /// Consumes the type and returns the payload if the outcome is `Better` or `Freeze`.
721    pub const fn payload(&self) -> Option<&Payload> {
722        match self {
723            Self::Better { payload, .. } | Self::Freeze(payload) => Some(payload),
724            _ => None,
725        }
726    }
727
728    /// Returns true if the outcome is `Better`.
729    pub const fn is_better(&self) -> bool {
730        matches!(self, Self::Better { .. })
731    }
732
733    /// Returns true if the outcome is `Freeze`.
734    pub const fn is_frozen(&self) -> bool {
735        matches!(self, Self::Freeze { .. })
736    }
737
738    /// Returns true if the outcome is `Aborted`.
739    pub const fn is_aborted(&self) -> bool {
740        matches!(self, Self::Aborted { .. })
741    }
742
743    /// Returns true if the outcome is `Cancelled`.
744    pub const fn is_cancelled(&self) -> bool {
745        matches!(self, Self::Cancelled)
746    }
747
748    /// Applies a fn on the current payload.
749    pub fn map_payload<F, P>(self, f: F) -> BuildOutcome<P>
750    where
751        F: FnOnce(Payload) -> P,
752    {
753        match self {
754            Self::Better { payload, cached_reads } => {
755                BuildOutcome::Better { payload: f(payload), cached_reads }
756            }
757            Self::Aborted { fees, cached_reads } => BuildOutcome::Aborted { fees, cached_reads },
758            Self::Cancelled => BuildOutcome::Cancelled,
759            Self::Freeze(payload) => BuildOutcome::Freeze(f(payload)),
760        }
761    }
762}
763
764/// The possible outcomes of a payload building attempt without reused [`CachedReads`]
765#[derive(Debug)]
766pub enum BuildOutcomeKind<Payload> {
767    /// Successfully built a better block.
768    Better {
769        /// The new payload that was built.
770        payload: Payload,
771    },
772    /// Aborted payload building because resulted in worse block wrt. fees.
773    Aborted {
774        /// The total fees associated with the attempted payload.
775        fees: U256,
776    },
777    /// Build job was cancelled
778    Cancelled,
779    /// The payload is final and no further building should occur
780    Freeze(Payload),
781}
782
783impl<Payload> BuildOutcomeKind<Payload> {
784    /// Attaches the [`CachedReads`] to the outcome.
785    pub fn with_cached_reads(self, cached_reads: CachedReads) -> BuildOutcome<Payload> {
786        match self {
787            Self::Better { payload } => BuildOutcome::Better { payload, cached_reads },
788            Self::Aborted { fees } => BuildOutcome::Aborted { fees, cached_reads },
789            Self::Cancelled => BuildOutcome::Cancelled,
790            Self::Freeze(payload) => BuildOutcome::Freeze(payload),
791        }
792    }
793}
794
795/// A collection of arguments used for building payloads.
796///
797/// This struct encapsulates the essential components and configuration required for the payload
798/// building process. It holds references to the Ethereum client, transaction pool, cached reads,
799/// payload configuration, cancellation status, and the best payload achieved so far.
800#[derive(Debug)]
801pub struct BuildArguments<Attributes, Payload: BuiltPayload> {
802    /// Previously cached disk reads
803    pub cached_reads: CachedReads,
804    /// How to configure the payload.
805    pub config: PayloadConfig<Attributes, HeaderTy<Payload::Primitives>>,
806    /// A marker that can be used to cancel the job.
807    pub cancel: CancelOnDrop,
808    /// The best payload achieved so far.
809    pub best_payload: Option<Payload>,
810}
811
812impl<Attributes, Payload: BuiltPayload> BuildArguments<Attributes, Payload> {
813    /// Create new build arguments.
814    pub const fn new(
815        cached_reads: CachedReads,
816        config: PayloadConfig<Attributes, HeaderTy<Payload::Primitives>>,
817        cancel: CancelOnDrop,
818        best_payload: Option<Payload>,
819    ) -> Self {
820        Self { cached_reads, config, cancel, best_payload }
821    }
822}
823
824/// A trait for building payloads that encapsulate Ethereum transactions.
825///
826/// This trait provides the `try_build` method to construct a transaction payload
827/// using `BuildArguments`. It returns a `Result` indicating success or a
828/// `PayloadBuilderError` if building fails.
829///
830/// Generic parameters `Pool` and `Client` represent the transaction pool and
831/// Ethereum client types.
832pub trait PayloadBuilder: Send + Sync + Clone {
833    /// The payload attributes type to accept for building.
834    type Attributes: PayloadBuilderAttributes;
835    /// The type of the built payload.
836    type BuiltPayload: BuiltPayload;
837
838    /// Tries to build a transaction payload using provided arguments.
839    ///
840    /// Constructs a transaction payload based on the given arguments,
841    /// returning a `Result` indicating success or an error if building fails.
842    ///
843    /// # Arguments
844    ///
845    /// - `args`: Build arguments containing necessary components.
846    ///
847    /// # Returns
848    ///
849    /// A `Result` indicating the build outcome or an error.
850    fn try_build(
851        &self,
852        args: BuildArguments<Self::Attributes, Self::BuiltPayload>,
853    ) -> Result<BuildOutcome<Self::BuiltPayload>, PayloadBuilderError>;
854
855    /// Invoked when the payload job is being resolved and there is no payload yet.
856    ///
857    /// This can happen if the CL requests a payload before the first payload has been built.
858    fn on_missing_payload(
859        &self,
860        _args: BuildArguments<Self::Attributes, Self::BuiltPayload>,
861    ) -> MissingPayloadBehaviour<Self::BuiltPayload> {
862        MissingPayloadBehaviour::RaceEmptyPayload
863    }
864
865    /// Builds an empty payload without any transaction.
866    fn build_empty_payload(
867        &self,
868        config: PayloadConfig<Self::Attributes, HeaderForPayload<Self::BuiltPayload>>,
869    ) -> Result<Self::BuiltPayload, PayloadBuilderError>;
870}
871
872/// Tells the payload builder how to react to payload request if there's no payload available yet.
873///
874/// This situation can occur if the CL requests a payload before the first payload has been built.
875#[derive(Default)]
876pub enum MissingPayloadBehaviour<Payload> {
877    /// Await the regular scheduled payload process.
878    AwaitInProgress,
879    /// Race the in progress payload process with an empty payload.
880    #[default]
881    RaceEmptyPayload,
882    /// Race the in progress payload process with this job.
883    RacePayload(Box<dyn FnOnce() -> Result<Payload, PayloadBuilderError> + Send>),
884}
885
886impl<Payload> fmt::Debug for MissingPayloadBehaviour<Payload> {
887    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
888        match self {
889            Self::AwaitInProgress => write!(f, "AwaitInProgress"),
890            Self::RaceEmptyPayload => {
891                write!(f, "RaceEmptyPayload")
892            }
893            Self::RacePayload(_) => write!(f, "RacePayload"),
894        }
895    }
896}
897
898/// Checks if the new payload is better than the current best.
899///
900/// This compares the total fees of the blocks, higher is better.
901#[inline(always)]
902pub fn is_better_payload<T: BuiltPayload>(best_payload: Option<&T>, new_fees: U256) -> bool {
903    if let Some(best_payload) = best_payload {
904        new_fees > best_payload.fees()
905    } else {
906        true
907    }
908}
909
910/// Returns the duration until the given unix timestamp in seconds.
911///
912/// Returns `Duration::ZERO` if the given timestamp is in the past.
913fn duration_until(unix_timestamp_secs: u64) -> Duration {
914    let unix_now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default();
915    let timestamp = Duration::from_secs(unix_timestamp_secs);
916    timestamp.saturating_sub(unix_now)
917}