1#![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_execution_cache::SavedCache;
18use reth_payload_builder::{
19 BuildNewPayload, KeepPayloadJobAlive, PayloadId, PayloadJob, PayloadJobGenerator,
20};
21use reth_payload_builder_primitives::PayloadBuilderError;
22use reth_payload_primitives::{BuiltPayload, PayloadAttributes, PayloadKind};
23use reth_primitives_traits::{HeaderTy, NodePrimitives, SealedHeader};
24use reth_revm::{cached::CachedReads, cancelled::CancelOnDrop};
25use reth_storage_api::{BlockReaderIdExt, StateProviderFactory};
26use reth_tasks::Runtime;
27use reth_trie_parallel::state_root_task::PayloadStateRootHandle;
28use std::{
29 fmt,
30 future::Future,
31 ops::Deref,
32 pin::Pin,
33 sync::Arc,
34 task::{Context, Poll},
35 time::{Duration, SystemTime, UNIX_EPOCH},
36};
37use tokio::{
38 sync::{oneshot, Semaphore},
39 time::{Interval, Sleep},
40};
41use tracing::{debug, trace, warn};
42
43mod better_payload_emitter;
44mod metrics;
45mod stack;
46
47pub use better_payload_emitter::BetterPayloadEmitter;
48pub use stack::PayloadBuilderStack;
49
50const PAYLOAD_BUILDER_THREAD_NAME: &str = "payload-builder";
51
52pub type HeaderForPayload<P> = <<P as BuiltPayload>::Primitives as NodePrimitives>::BlockHeader;
54
55#[derive(Debug)]
57pub struct BasicPayloadJobGenerator<Client, Builder> {
58 client: Client,
60 executor: Runtime,
62 config: BasicPayloadJobGeneratorConfig,
64 payload_task_guard: PayloadTaskGuard,
66 builder: Builder,
70 pre_cached: Option<PrecachedState>,
72 pre_cached_parent_block_info: Option<PrecachedParentBlockInfo>,
74}
75
76impl<Client, Builder> BasicPayloadJobGenerator<Client, Builder> {
79 pub fn with_builder(
82 client: Client,
83 executor: Runtime,
84 config: BasicPayloadJobGeneratorConfig,
85 builder: Builder,
86 ) -> Self {
87 Self {
88 client,
89 executor,
90 payload_task_guard: PayloadTaskGuard::new(config.max_payload_tasks),
91 config,
92 builder,
93 pre_cached: None,
94 pre_cached_parent_block_info: None,
95 }
96 }
97
98 #[inline]
107 fn max_job_duration(&self, unix_timestamp: u64) -> Duration {
108 let duration_until_timestamp = duration_until(unix_timestamp);
109
110 let duration_until_timestamp = duration_until_timestamp.min(self.config.deadline * 3);
112
113 self.config.deadline + duration_until_timestamp
114 }
115
116 #[inline]
119 fn job_deadline(&self, unix_timestamp: u64) -> tokio::time::Instant {
120 tokio::time::Instant::now() + self.max_job_duration(unix_timestamp)
121 }
122
123 pub const fn tasks(&self) -> &Runtime {
125 &self.executor
126 }
127
128 fn maybe_pre_cached(&self, parent: B256) -> Option<CachedReads> {
131 if !self.config.pre_cache_state {
132 return None
133 }
134
135 self.pre_cached.as_ref().filter(|pc| pc.block == parent).map(|pc| pc.cached.clone())
136 }
137
138 fn maybe_parent_block_info(&self, parent: B256) -> Option<PayloadParentBlockInfo> {
140 self.pre_cached_parent_block_info
141 .as_ref()
142 .filter(|info| info.block == parent)
143 .map(|info| info.parent_block_info)
144 }
145}
146
147impl<Client, Builder> PayloadJobGenerator for BasicPayloadJobGenerator<Client, Builder>
150where
151 Client: StateProviderFactory
152 + BlockReaderIdExt<Header = HeaderForPayload<Builder::BuiltPayload>>
153 + Clone
154 + Unpin
155 + 'static,
156 Builder: PayloadBuilder + Unpin + 'static,
157 Builder::Attributes: Unpin + Clone,
158 Builder::BuiltPayload: Unpin + Clone,
159{
160 type Job = BasicPayloadJob<Builder>;
161
162 fn new_payload_job(
163 &self,
164 input: BuildNewPayload<Builder::Attributes>,
165 id: PayloadId,
166 ) -> Result<Self::Job, PayloadBuilderError> {
167 let BuildNewPayload { attributes, parent_hash, mut resources } = input;
168 let parent_header = if parent_hash.is_zero() {
169 self.client
171 .latest_header()
172 .map_err(PayloadBuilderError::from)?
173 .ok_or_else(|| PayloadBuilderError::MissingParentHeader(B256::ZERO))?
174 } else {
175 self.client
177 .sealed_header_by_hash(parent_hash)
178 .map_err(PayloadBuilderError::from)?
179 .ok_or_else(|| PayloadBuilderError::MissingParentHeader(parent_hash))?
180 };
181
182 let parent_hash = parent_header.hash();
183 let cached_reads = self.maybe_pre_cached(parent_hash);
184 let parent_block_info = self.maybe_parent_block_info(parent_hash);
185
186 let config = PayloadConfig::new(Arc::new(parent_header), attributes, id)
187 .with_parent_block_info(parent_block_info);
188
189 let until = self.job_deadline(config.attributes.timestamp());
190 let deadline = Box::pin(tokio::time::sleep_until(until));
191
192 let mut job = BasicPayloadJob {
193 config,
194 executor: self.executor.clone(),
195 deadline,
196 interval: tokio::time::interval(self.config.interval),
198 best_payload: PayloadState::Missing,
199 pending_block: None,
200 cached_reads,
201 execution_cache: resources.take_execution_cache(),
202 state_root_handle: resources.take_state_root_handle(),
203 payload_task_guard: self.payload_task_guard.clone(),
204 metrics: Default::default(),
205 builder: self.builder.clone(),
206 };
207
208 job.spawn_build_job();
210
211 Ok(job)
212 }
213
214 fn on_new_state<N: NodePrimitives>(&mut self, new_state: CanonStateNotification<N>) {
215 if !self.config.pre_cache_state {
216 self.pre_cached = None;
217 return
218 }
219
220 let mut cached = CachedReads::default();
221
222 let committed = new_state.committed();
224 let new_execution_outcome = committed.execution_outcome();
225 for (addr, acc) in new_execution_outcome.bundle_accounts_iter() {
226 if let Some(info) = acc.info.clone() {
227 let storage =
230 acc.storage.iter().map(|(key, slot)| (*key, slot.present_value)).collect();
231 cached.insert_account(addr, info, storage);
232 }
233 }
234
235 let tip = committed.tip();
236 let block = tip.hash();
237 let parent_block_info =
238 PayloadParentBlockInfo { transaction_count: tip.transaction_count() };
239
240 self.pre_cached = Some(PrecachedState { block, cached });
241 self.pre_cached_parent_block_info =
242 Some(PrecachedParentBlockInfo { block, parent_block_info });
243 }
244}
245
246#[derive(Debug, Clone)]
250pub struct PrecachedState {
251 pub block: B256,
253 pub cached: CachedReads,
255}
256
257#[derive(Debug, Clone, Copy)]
259struct PrecachedParentBlockInfo {
260 block: B256,
262 parent_block_info: PayloadParentBlockInfo,
264}
265
266#[derive(Debug, Clone)]
268pub struct PayloadTaskGuard(Arc<Semaphore>);
269
270impl Deref for PayloadTaskGuard {
271 type Target = Semaphore;
272
273 fn deref(&self) -> &Self::Target {
274 &self.0
275 }
276}
277
278impl PayloadTaskGuard {
281 pub fn new(max_payload_tasks: usize) -> Self {
283 Self(Arc::new(Semaphore::new(max_payload_tasks)))
284 }
285
286 async fn acquire_owned(&self) -> tokio::sync::OwnedSemaphorePermit {
288 self.0.clone().acquire_owned().await.expect("payload task semaphore closed")
289 }
290}
291
292#[derive(Debug, Clone)]
294pub struct BasicPayloadJobGeneratorConfig {
295 interval: Duration,
297 deadline: Duration,
301 max_payload_tasks: usize,
303 pre_cache_state: bool,
305}
306
307impl BasicPayloadJobGeneratorConfig {
310 pub const fn interval(mut self, interval: Duration) -> Self {
312 self.interval = interval;
313 self
314 }
315
316 pub const fn deadline(mut self, deadline: Duration) -> Self {
318 self.deadline = deadline;
319 self
320 }
321
322 pub fn max_payload_tasks(mut self, max_payload_tasks: usize) -> Self {
328 assert!(max_payload_tasks > 0, "max_payload_tasks must be greater than 0");
329 self.max_payload_tasks = max_payload_tasks;
330 self
331 }
332
333 pub const fn pre_cache_state(mut self, pre_cache_state: bool) -> Self {
338 self.pre_cache_state = pre_cache_state;
339 self
340 }
341}
342
343impl Default for BasicPayloadJobGeneratorConfig {
344 fn default() -> Self {
345 Self {
346 interval: Duration::from_secs(1),
347 deadline: SLOT_DURATION,
349 max_payload_tasks: 3,
350 pre_cache_state: true,
351 }
352 }
353}
354
355#[derive(Debug)]
366pub struct BasicPayloadJob<Builder>
367where
368 Builder: PayloadBuilder,
369{
370 config: PayloadConfig<Builder::Attributes, HeaderForPayload<Builder::BuiltPayload>>,
372 executor: Runtime,
374 deadline: Pin<Box<Sleep>>,
376 interval: Interval,
378 best_payload: PayloadState<Builder::BuiltPayload>,
380 pending_block: Option<PendingPayload<Builder::BuiltPayload>>,
382 payload_task_guard: PayloadTaskGuard,
384 cached_reads: Option<CachedReads>,
389 execution_cache: Option<SavedCache>,
391 state_root_handle: Option<PayloadStateRootHandle>,
393 metrics: PayloadBuilderMetrics,
395 builder: Builder,
399}
400
401impl<Builder> BasicPayloadJob<Builder>
402where
403 Builder: PayloadBuilder + Unpin + 'static,
404 Builder::Attributes: Unpin + Clone,
405 Builder::BuiltPayload: Unpin + Clone,
406{
407 fn spawn_build_job(&mut self) {
409 trace!(target: "payload_builder", id = %self.config.payload_id(), "spawn new payload build task");
410 let (tx, rx) = oneshot::channel();
411 let cancel = CancelOnDrop::default();
412 let _cancel = cancel.clone();
413 let guard = self.payload_task_guard.clone();
414 let payload_config = self.config.clone();
415 let best_payload = self.best_payload.payload().cloned();
416 self.metrics.inc_initiated_payload_builds();
417 let cached_reads = self.cached_reads.take().unwrap_or_default();
418 let execution_cache = self.execution_cache.clone();
419 let state_root_handle = self.state_root_handle.take();
420 let builder = self.builder.clone();
421 let executor = self.executor.clone();
422 self.executor.spawn_task(async move {
423 let permit = guard.acquire_owned().await;
425 executor.spawn_blocking_named_or_tokio(PAYLOAD_BUILDER_THREAD_NAME, move || {
426 let _permit = permit;
427 let args = BuildArguments {
428 cached_reads,
429 execution_cache,
430 state_root_handle,
431 config: payload_config,
432 cancel,
433 best_payload,
434 };
435 let result = builder.try_build(args);
436 let _ = tx.send(result);
437 });
438 });
439
440 self.pending_block = Some(PendingPayload { _cancel, payload: rx });
441 }
442}
443
444impl<Builder> Future for BasicPayloadJob<Builder>
445where
446 Builder: PayloadBuilder + Unpin + 'static,
447 Builder::Attributes: Unpin + Clone,
448 Builder::BuiltPayload: Unpin + Clone,
449{
450 type Output = Result<(), PayloadBuilderError>;
451
452 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
453 let this = self.get_mut();
454
455 if this.deadline.as_mut().poll(cx).is_ready() {
457 trace!(target: "payload_builder", "payload building deadline reached");
458 return Poll::Ready(Ok(()))
459 }
460
461 loop {
462 if let Some(mut fut) = this.pending_block.take() {
468 match fut.poll_unpin(cx) {
469 Poll::Ready(Ok(outcome)) => match outcome {
470 BuildOutcome::Better { payload, cached_reads } => {
471 this.cached_reads = Some(cached_reads);
472 debug!(target: "payload_builder", value = %payload.fees(), "built better payload");
473 this.best_payload = PayloadState::Best(payload);
474 }
475 BuildOutcome::Freeze(payload) => {
476 debug!(target: "payload_builder", "payload frozen, no further building will occur");
477 this.best_payload = PayloadState::Frozen(payload);
478 }
479 BuildOutcome::Aborted { fees, cached_reads } => {
480 this.cached_reads = Some(cached_reads);
481 trace!(target: "payload_builder", worse_fees = %fees, "skipped payload build of worse block");
482 }
483 BuildOutcome::Cancelled => {
484 unreachable!("the cancel signal never fired")
485 }
486 },
487 Poll::Ready(Err(error)) => {
488 debug!(target: "payload_builder", %error, "payload build attempt failed");
490 this.metrics.inc_failed_payload_builds();
491 }
492 Poll::Pending => {
493 this.pending_block = Some(fut);
494 return Poll::Pending
495 }
496 }
497 }
498
499 if this.best_payload.is_frozen() {
500 return Poll::Pending
501 }
502
503 ready!(this.interval.poll_tick(cx));
509 this.spawn_build_job()
510 }
511 }
512}
513
514impl<Builder> PayloadJob for BasicPayloadJob<Builder>
515where
516 Builder: PayloadBuilder + Unpin + 'static,
517 Builder::Attributes: Unpin + Clone,
518 Builder::BuiltPayload: Unpin + Clone,
519{
520 type PayloadAttributes = Builder::Attributes;
521 type ResolvePayloadFuture = ResolveBestPayload<Self::BuiltPayload>;
522 type BuiltPayload = Builder::BuiltPayload;
523
524 fn best_payload(&self) -> Result<Self::BuiltPayload, PayloadBuilderError> {
525 if let Some(payload) = self.best_payload.payload() {
526 Ok(payload.clone())
527 } else {
528 self.metrics.inc_requested_empty_payload();
535 self.builder.build_empty_payload(self.config.clone())
536 }
537 }
538
539 fn payload_attributes(&self) -> Result<Self::PayloadAttributes, PayloadBuilderError> {
540 Ok(self.config.attributes.clone())
541 }
542
543 fn payload_timestamp(&self) -> Result<u64, PayloadBuilderError> {
544 Ok(self.config.attributes.timestamp())
545 }
546
547 fn resolve_kind(
548 &mut self,
549 kind: PayloadKind,
550 ) -> (Self::ResolvePayloadFuture, KeepPayloadJobAlive) {
551 let best_payload = self.best_payload.payload().cloned();
552 if best_payload.is_none() && self.pending_block.is_none() {
553 self.spawn_build_job();
555 }
556
557 let maybe_better = self.pending_block.take();
558 let mut empty_payload = None;
559
560 if best_payload.is_none() {
561 debug!(target: "payload_builder", id=%self.config.payload_id(), "no best payload yet to resolve, building empty payload");
562
563 let args = BuildArguments {
564 cached_reads: self.cached_reads.take().unwrap_or_default(),
565 execution_cache: self.execution_cache.clone(),
566 state_root_handle: None,
567 config: self.config.clone(),
568 cancel: CancelOnDrop::default(),
569 best_payload: None,
570 };
571
572 match self.builder.on_missing_payload(args) {
573 MissingPayloadBehaviour::AwaitInProgress => {
574 debug!(target: "payload_builder", id=%self.config.payload_id(), "awaiting in progress payload build job");
575 }
576 MissingPayloadBehaviour::RaceEmptyPayload => {
577 debug!(target: "payload_builder", id=%self.config.payload_id(), "racing empty payload");
578
579 self.metrics.inc_requested_empty_payload();
581 let (tx, rx) = oneshot::channel();
583 let config = self.config.clone();
584 let builder = self.builder.clone();
585 self.executor.spawn_blocking_named_or_tokio(
586 PAYLOAD_BUILDER_THREAD_NAME,
587 move || {
588 let res = builder.build_empty_payload(config);
589 let _ = tx.send(res);
590 },
591 );
592
593 empty_payload = Some(rx);
594 }
595 MissingPayloadBehaviour::RacePayload(job) => {
596 debug!(target: "payload_builder", id=%self.config.payload_id(), "racing fallback payload");
597 let (tx, rx) = oneshot::channel();
599 self.executor.spawn_blocking_named_or_tokio(
600 PAYLOAD_BUILDER_THREAD_NAME,
601 move || {
602 let _ = tx.send(job());
603 },
604 );
605 empty_payload = Some(rx);
606 }
607 };
608 }
609
610 let fut = ResolveBestPayload {
611 best_payload,
612 maybe_better,
613 empty_payload: empty_payload.filter(|_| kind != PayloadKind::WaitForPending),
614 };
615
616 (fut, KeepPayloadJobAlive::No)
617 }
618}
619
620#[derive(Debug, Clone)]
622pub enum PayloadState<P> {
623 Missing,
625 Best(P),
627 Frozen(P),
631}
632
633impl<P> PayloadState<P> {
634 pub const fn is_frozen(&self) -> bool {
636 matches!(self, Self::Frozen(_))
637 }
638
639 pub const fn payload(&self) -> Option<&P> {
641 match self {
642 Self::Missing => None,
643 Self::Best(p) | Self::Frozen(p) => Some(p),
644 }
645 }
646}
647
648#[derive(Debug)]
658pub struct ResolveBestPayload<Payload> {
659 pub best_payload: Option<Payload>,
661 pub maybe_better: Option<PendingPayload<Payload>>,
663 pub empty_payload: Option<oneshot::Receiver<Result<Payload, PayloadBuilderError>>>,
665}
666
667impl<Payload> ResolveBestPayload<Payload> {
668 const fn is_empty(&self) -> bool {
669 self.best_payload.is_none() && self.maybe_better.is_none() && self.empty_payload.is_none()
670 }
671}
672
673impl<Payload> Future for ResolveBestPayload<Payload>
674where
675 Payload: Unpin,
676{
677 type Output = Result<Payload, PayloadBuilderError>;
678
679 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
680 let this = self.get_mut();
681
682 if let Some(fut) = Pin::new(&mut this.maybe_better).as_pin_mut() &&
684 let Poll::Ready(res) = fut.poll(cx)
685 {
686 this.maybe_better = None;
687 if let Ok(Some(payload)) = res.map(|out| out.into_payload()).inspect_err(
688 |err| warn!(target: "payload_builder", %err, "failed to resolve pending payload"),
689 ) {
690 debug!(target: "payload_builder", "resolving better payload");
691 return Poll::Ready(Ok(payload))
692 }
693 }
694
695 if let Some(best) = this.best_payload.take() {
696 debug!(target: "payload_builder", "resolving best payload");
697 return Poll::Ready(Ok(best))
698 }
699
700 if let Some(fut) = Pin::new(&mut this.empty_payload).as_pin_mut() &&
701 let Poll::Ready(res) = fut.poll(cx)
702 {
703 this.empty_payload = None;
704 return match res {
705 Ok(res) => {
706 if let Err(err) = &res {
707 warn!(target: "payload_builder", %err, "failed to resolve empty payload");
708 } else {
709 debug!(target: "payload_builder", "resolving empty payload");
710 }
711 Poll::Ready(res)
712 }
713 Err(err) => Poll::Ready(Err(err.into())),
714 }
715 }
716
717 if this.is_empty() {
718 return Poll::Ready(Err(PayloadBuilderError::MissingPayload))
719 }
720
721 Poll::Pending
722 }
723}
724
725#[derive(Debug)]
727pub struct PendingPayload<P> {
728 _cancel: CancelOnDrop,
730 payload: oneshot::Receiver<Result<BuildOutcome<P>, PayloadBuilderError>>,
732}
733
734impl<P> PendingPayload<P> {
735 pub const fn new(
737 cancel: CancelOnDrop,
738 payload: oneshot::Receiver<Result<BuildOutcome<P>, PayloadBuilderError>>,
739 ) -> Self {
740 Self { _cancel: cancel, payload }
741 }
742}
743
744impl<P> Future for PendingPayload<P> {
745 type Output = Result<BuildOutcome<P>, PayloadBuilderError>;
746
747 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
748 let res = ready!(self.payload.poll_unpin(cx));
749 Poll::Ready(res.map_err(Into::into).and_then(|res| res))
750 }
751}
752
753#[derive(Clone, Debug)]
755pub struct PayloadConfig<Attributes, Header = alloy_consensus::Header> {
756 pub parent_header: Arc<SealedHeader<Header>>,
758 pub parent_block_info: Option<PayloadParentBlockInfo>,
760 pub attributes: Attributes,
762 pub payload_id: PayloadId,
764}
765
766#[derive(Clone, Copy, Debug, PartialEq, Eq)]
768pub struct PayloadParentBlockInfo {
769 pub transaction_count: usize,
771}
772
773impl<Attributes, Header> PayloadConfig<Attributes, Header>
774where
775 Attributes: PayloadAttributes,
776{
777 pub const fn new(
779 parent_header: Arc<SealedHeader<Header>>,
780 attributes: Attributes,
781 payload_id: PayloadId,
782 ) -> Self {
783 Self { parent_header, parent_block_info: None, attributes, payload_id }
784 }
785
786 pub const fn with_parent_block_info(
788 mut self,
789 parent_block_info: Option<PayloadParentBlockInfo>,
790 ) -> Self {
791 self.parent_block_info = parent_block_info;
792 self
793 }
794
795 pub const fn payload_id(&self) -> PayloadId {
797 self.payload_id
798 }
799}
800
801#[derive(Debug)]
803pub enum BuildOutcome<Payload> {
804 Better {
806 payload: Payload,
808 cached_reads: CachedReads,
810 },
811 Aborted {
813 fees: U256,
815 cached_reads: CachedReads,
817 },
818 Cancelled,
820
821 Freeze(Payload),
823}
824
825impl<Payload> BuildOutcome<Payload> {
826 pub fn into_payload(self) -> Option<Payload> {
828 match self {
829 Self::Better { payload, .. } | Self::Freeze(payload) => Some(payload),
830 _ => None,
831 }
832 }
833
834 pub const fn payload(&self) -> Option<&Payload> {
836 match self {
837 Self::Better { payload, .. } | Self::Freeze(payload) => Some(payload),
838 _ => None,
839 }
840 }
841
842 pub const fn is_better(&self) -> bool {
844 matches!(self, Self::Better { .. })
845 }
846
847 pub const fn is_frozen(&self) -> bool {
849 matches!(self, Self::Freeze { .. })
850 }
851
852 pub const fn is_aborted(&self) -> bool {
854 matches!(self, Self::Aborted { .. })
855 }
856
857 pub const fn is_cancelled(&self) -> bool {
859 matches!(self, Self::Cancelled)
860 }
861
862 pub fn map_payload<F, P>(self, f: F) -> BuildOutcome<P>
864 where
865 F: FnOnce(Payload) -> P,
866 {
867 match self {
868 Self::Better { payload, cached_reads } => {
869 BuildOutcome::Better { payload: f(payload), cached_reads }
870 }
871 Self::Aborted { fees, cached_reads } => BuildOutcome::Aborted { fees, cached_reads },
872 Self::Cancelled => BuildOutcome::Cancelled,
873 Self::Freeze(payload) => BuildOutcome::Freeze(f(payload)),
874 }
875 }
876}
877
878#[derive(Debug)]
880pub enum BuildOutcomeKind<Payload> {
881 Better {
883 payload: Payload,
885 },
886 Aborted {
888 fees: U256,
890 },
891 Cancelled,
893 Freeze(Payload),
895}
896
897impl<Payload> BuildOutcomeKind<Payload> {
898 pub fn with_cached_reads(self, cached_reads: CachedReads) -> BuildOutcome<Payload> {
900 match self {
901 Self::Better { payload } => BuildOutcome::Better { payload, cached_reads },
902 Self::Aborted { fees } => BuildOutcome::Aborted { fees, cached_reads },
903 Self::Cancelled => BuildOutcome::Cancelled,
904 Self::Freeze(payload) => BuildOutcome::Freeze(payload),
905 }
906 }
907}
908
909#[derive(Debug)]
915pub struct BuildArguments<Attributes, Payload: BuiltPayload> {
916 pub cached_reads: CachedReads,
918 pub execution_cache: Option<SavedCache>,
920 pub state_root_handle: Option<PayloadStateRootHandle>,
927 pub config: PayloadConfig<Attributes, HeaderTy<Payload::Primitives>>,
929 pub cancel: CancelOnDrop,
931 pub best_payload: Option<Payload>,
933}
934
935impl<Attributes, Payload: BuiltPayload> BuildArguments<Attributes, Payload> {
936 pub const fn new(
938 cached_reads: CachedReads,
939 execution_cache: Option<SavedCache>,
940 state_root_handle: Option<PayloadStateRootHandle>,
941 config: PayloadConfig<Attributes, HeaderTy<Payload::Primitives>>,
942 cancel: CancelOnDrop,
943 best_payload: Option<Payload>,
944 ) -> Self {
945 Self { cached_reads, execution_cache, state_root_handle, config, cancel, best_payload }
946 }
947}
948
949pub trait PayloadBuilder: Send + Sync + Clone {
958 type Attributes: PayloadAttributes;
960 type BuiltPayload: BuiltPayload;
962
963 fn try_build(
976 &self,
977 args: BuildArguments<Self::Attributes, Self::BuiltPayload>,
978 ) -> Result<BuildOutcome<Self::BuiltPayload>, PayloadBuilderError>;
979
980 fn on_missing_payload(
984 &self,
985 _args: BuildArguments<Self::Attributes, Self::BuiltPayload>,
986 ) -> MissingPayloadBehaviour<Self::BuiltPayload> {
987 MissingPayloadBehaviour::RaceEmptyPayload
988 }
989
990 fn build_empty_payload(
992 &self,
993 config: PayloadConfig<Self::Attributes, HeaderForPayload<Self::BuiltPayload>>,
994 ) -> Result<Self::BuiltPayload, PayloadBuilderError>;
995}
996
997#[derive(Default)]
1001pub enum MissingPayloadBehaviour<Payload> {
1002 AwaitInProgress,
1004 #[default]
1006 RaceEmptyPayload,
1007 RacePayload(Box<dyn FnOnce() -> Result<Payload, PayloadBuilderError> + Send>),
1009}
1010
1011impl<Payload> fmt::Debug for MissingPayloadBehaviour<Payload> {
1012 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1013 match self {
1014 Self::AwaitInProgress => write!(f, "AwaitInProgress"),
1015 Self::RaceEmptyPayload => {
1016 write!(f, "RaceEmptyPayload")
1017 }
1018 Self::RacePayload(_) => write!(f, "RacePayload"),
1019 }
1020 }
1021}
1022
1023#[inline(always)]
1027pub fn is_better_payload<T: BuiltPayload>(best_payload: Option<&T>, new_fees: U256) -> bool {
1028 if let Some(best_payload) = best_payload {
1029 new_fees > best_payload.fees()
1030 } else {
1031 true
1032 }
1033}
1034
1035fn duration_until(unix_timestamp_secs: u64) -> Duration {
1039 let unix_now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default();
1040 let timestamp = Duration::from_secs(unix_timestamp_secs);
1041 timestamp.saturating_sub(unix_now)
1042}