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_evm::cached::CachedReads;
18use reth_execution_cache::SavedCache;
19use reth_payload_builder::{
20 BuildNewPayload, KeepPayloadJobAlive, PayloadBuilderLease, PayloadId, PayloadJob,
21 PayloadJobGenerator,
22};
23use reth_payload_builder_primitives::PayloadBuilderError;
24use reth_payload_primitives::{BuiltPayload, PayloadAttributes, PayloadKind};
25use reth_primitives_traits::{AlloyBlockHeader, HeaderTy, NodePrimitives, SealedHeader};
26use reth_storage_api::{BlockReaderIdExt, StateProviderFactory};
27use reth_tasks::{CancelOnDrop, Runtime};
28use reth_trie_parallel::state_root_task::PayloadStateRootHandle;
29use std::{
30 fmt,
31 future::Future,
32 ops::Deref,
33 pin::Pin,
34 sync::Arc,
35 task::{Context, Poll},
36 time::{Duration, SystemTime, UNIX_EPOCH},
37};
38use tokio::{
39 sync::{oneshot, Semaphore},
40 time::{Interval, Sleep},
41};
42use tracing::{debug, debug_span, trace, warn, Span};
43
44mod better_payload_emitter;
45mod metrics;
46mod stack;
47
48pub use better_payload_emitter::BetterPayloadEmitter;
49pub use stack::PayloadBuilderStack;
50
51const PAYLOAD_BUILDER_THREAD_NAME: &str = "payload-builder";
52
53pub type HeaderForPayload<P> = <<P as BuiltPayload>::Primitives as NodePrimitives>::BlockHeader;
55
56#[derive(Debug)]
58pub struct BasicPayloadJobGenerator<Client, Builder> {
59 client: Client,
61 executor: Runtime,
63 config: BasicPayloadJobGeneratorConfig,
65 payload_task_guard: PayloadTaskGuard,
67 builder: Builder,
71 pre_cached: Option<PrecachedState>,
73 pre_cached_parent_block_info: Option<PrecachedParentBlockInfo>,
75}
76
77impl<Client, Builder> BasicPayloadJobGenerator<Client, Builder> {
80 pub fn with_builder(
83 client: Client,
84 executor: Runtime,
85 config: BasicPayloadJobGeneratorConfig,
86 builder: Builder,
87 ) -> Self {
88 Self {
89 client,
90 executor,
91 payload_task_guard: PayloadTaskGuard::new(config.max_payload_tasks),
92 config,
93 builder,
94 pre_cached: None,
95 pre_cached_parent_block_info: None,
96 }
97 }
98
99 #[inline]
108 fn max_job_duration(&self, unix_timestamp: u64) -> Duration {
109 let duration_until_timestamp = duration_until(unix_timestamp);
110
111 let duration_until_timestamp = duration_until_timestamp.min(self.config.deadline * 3);
113
114 self.config.deadline + duration_until_timestamp
115 }
116
117 #[inline]
120 fn job_deadline(&self, unix_timestamp: u64) -> tokio::time::Instant {
121 tokio::time::Instant::now() + self.max_job_duration(unix_timestamp)
122 }
123
124 pub const fn tasks(&self) -> &Runtime {
126 &self.executor
127 }
128
129 fn maybe_pre_cached(&self, parent: B256) -> Option<CachedReads> {
132 if !self.config.pre_cache_state {
133 return None
134 }
135
136 self.pre_cached.as_ref().filter(|pc| pc.block == parent).map(|pc| pc.cached.clone())
137 }
138
139 fn maybe_parent_block_info(&self, parent: B256) -> Option<PayloadParentBlockInfo> {
141 self.pre_cached_parent_block_info
142 .as_ref()
143 .filter(|info| info.block == parent)
144 .map(|info| info.parent_block_info)
145 }
146}
147
148impl<Client, Builder> PayloadJobGenerator for BasicPayloadJobGenerator<Client, Builder>
151where
152 Client: StateProviderFactory
153 + BlockReaderIdExt<Header = HeaderForPayload<Builder::BuiltPayload>>
154 + Clone
155 + Unpin
156 + 'static,
157 Builder: PayloadBuilder + Unpin + 'static,
158 Builder::Attributes: Unpin + Clone,
159 Builder::BuiltPayload: Unpin + Clone,
160{
161 type Job = BasicPayloadJob<Builder>;
162
163 fn new_payload_job(
164 &self,
165 input: BuildNewPayload<Builder::Attributes>,
166 id: PayloadId,
167 ) -> Result<Self::Job, PayloadBuilderError> {
168 let BuildNewPayload { attributes, parent_hash, mut resources } = input;
169 let parent_header = if parent_hash.is_zero() {
170 self.client
172 .latest_header()?
173 .ok_or_else(|| PayloadBuilderError::MissingParentHeader(B256::ZERO))?
174 } else {
175 self.client
177 .sealed_header_by_hash(parent_hash)?
178 .ok_or_else(|| PayloadBuilderError::MissingParentHeader(parent_hash))?
179 };
180
181 let parent_hash = parent_header.hash();
182 let cached_reads = self.maybe_pre_cached(parent_hash);
183 let parent_block_info = self.maybe_parent_block_info(parent_hash);
184
185 let config = PayloadConfig::new(Arc::new(parent_header), attributes, id)
186 .with_parent_block_info(parent_block_info);
187
188 let until = self.job_deadline(config.attributes.timestamp());
189 let deadline = Box::pin(tokio::time::sleep_until(until));
190
191 let mut job = BasicPayloadJob {
192 config,
193 executor: self.executor.clone(),
194 deadline,
195 interval: tokio::time::interval(self.config.interval),
197 best_payload: PayloadState::Missing,
198 pending_block: None,
199 cached_reads,
200 execution_cache: resources.take_execution_cache(),
201 state_root_handle: resources.take_state_root_handle(),
202 leases: resources.take_leases(),
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 leases: Vec<PayloadBuilderLease>,
398 metrics: PayloadBuilderMetrics,
400 builder: Builder,
404}
405
406impl<Builder> BasicPayloadJob<Builder>
407where
408 Builder: PayloadBuilder + Unpin + 'static,
409 Builder::Attributes: Unpin + Clone,
410 Builder::BuiltPayload: Unpin + Clone,
411{
412 fn spawn_build_job(&mut self) {
414 trace!(target: "payload_builder", id = %self.config.payload_id(), "spawn new payload build task");
415 let (tx, rx) = oneshot::channel();
416 let cancel = CancelOnDrop::default();
417 let pending_cancel = cancel.clone();
418 let guard = self.payload_task_guard.clone();
419 let payload_config = self.config.clone();
420 let best_payload = self.best_payload.payload().cloned();
421 self.metrics.inc_initiated_payload_builds();
422 let cached_reads = self.cached_reads.take().unwrap_or_default();
423 let execution_cache = self.execution_cache.clone();
424 let mut state_root_handle = self.state_root_handle.take();
425 let on_payload_built =
426 state_root_handle.as_mut().and_then(PayloadStateRootHandle::take_on_payload_built);
427 let leases = self.leases.clone();
428 let builder = self.builder.clone();
429 let executor = self.executor.clone();
430 let span = Span::current();
431 self.executor.spawn_task(async move {
432 let permit = guard.acquire_owned().await;
434 executor.spawn_blocking_named_or_tokio(PAYLOAD_BUILDER_THREAD_NAME, move || {
435 let _parent = span.enter();
437 let _span = debug_span!(target: "payload_builder", "build_payload").entered();
438 let _permit = permit;
439 let args = BuildArguments {
440 cached_reads,
441 execution_cache,
442 state_root_handle,
443 config: payload_config,
444 cancel,
445 best_payload,
446 };
447 let result = builder.try_build(args);
448 if let Some(on_payload_built) = on_payload_built &&
449 let Ok(outcome) = &result &&
450 let Some(payload) = outcome.payload()
451 {
452 on_payload_built(payload.block().hash(), payload.block().state_root());
453 }
454 drop(leases);
455 let _ = tx.send(result);
456 });
457 });
458
459 self.pending_block = Some(PendingPayload { cancel: pending_cancel, payload: rx });
460 }
461}
462
463impl<Builder> Future for BasicPayloadJob<Builder>
464where
465 Builder: PayloadBuilder + Unpin + 'static,
466 Builder::Attributes: Unpin + Clone,
467 Builder::BuiltPayload: Unpin + Clone,
468{
469 type Output = Result<(), PayloadBuilderError>;
470
471 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
472 let this = self.get_mut();
473
474 if this.deadline.as_mut().poll(cx).is_ready() {
476 trace!(target: "payload_builder", "payload building deadline reached");
477 return Poll::Ready(Ok(()))
478 }
479
480 loop {
481 if let Some(mut fut) = this.pending_block.take() {
487 match fut.poll_unpin(cx) {
488 Poll::Ready(Ok(outcome)) => match outcome {
489 BuildOutcome::Better { payload, cached_reads } => {
490 this.cached_reads = Some(cached_reads);
491 debug!(target: "payload_builder", value = %payload.fees(), "built better payload");
492 this.best_payload = PayloadState::Best(payload);
493 }
494 BuildOutcome::Freeze(payload) => {
495 debug!(target: "payload_builder", "payload frozen, no further building will occur");
496 this.best_payload = PayloadState::Frozen(payload);
497 }
498 BuildOutcome::Aborted { fees, cached_reads } => {
499 this.cached_reads = Some(cached_reads);
500 trace!(target: "payload_builder", worse_fees = %fees, "skipped payload build of worse block");
501 }
502 BuildOutcome::Cancelled => {
503 unreachable!("the cancel signal never fired")
504 }
505 },
506 Poll::Ready(Err(error)) => {
507 debug!(target: "payload_builder", %error, "payload build attempt failed");
509 this.metrics.inc_failed_payload_builds();
510 }
511 Poll::Pending => {
512 this.pending_block = Some(fut);
513 return Poll::Pending
514 }
515 }
516 }
517
518 if this.best_payload.is_frozen() {
519 return Poll::Pending
520 }
521
522 ready!(this.interval.poll_tick(cx));
528 this.spawn_build_job()
529 }
530 }
531}
532
533impl<Builder> PayloadJob for BasicPayloadJob<Builder>
534where
535 Builder: PayloadBuilder + Unpin + 'static,
536 Builder::Attributes: Unpin + Clone,
537 Builder::BuiltPayload: Unpin + Clone,
538{
539 type PayloadAttributes = Builder::Attributes;
540 type ResolvePayloadFuture = ResolveBestPayload<Self::BuiltPayload>;
541 type BuiltPayload = Builder::BuiltPayload;
542
543 fn best_payload(&self) -> Result<Self::BuiltPayload, PayloadBuilderError> {
544 if let Some(payload) = self.best_payload.payload() {
545 Ok(payload.clone())
546 } else {
547 self.metrics.inc_requested_empty_payload();
554 self.builder.build_empty_payload(self.config.clone())
555 }
556 }
557
558 fn payload_attributes(&self) -> Result<Self::PayloadAttributes, PayloadBuilderError> {
559 Ok(self.config.attributes.clone())
560 }
561
562 fn payload_timestamp(&self) -> Result<u64, PayloadBuilderError> {
563 Ok(self.config.attributes.timestamp())
564 }
565
566 fn resolve_kind(
567 &mut self,
568 kind: PayloadKind,
569 ) -> (Self::ResolvePayloadFuture, KeepPayloadJobAlive) {
570 let best_payload = self.best_payload.payload().cloned();
571 if best_payload.is_none() && self.pending_block.is_none() {
572 self.spawn_build_job();
574 }
575
576 let maybe_better = self.pending_block.take();
577 let mut empty_payload = None;
578
579 if best_payload.is_none() {
580 if let Some(pending) = maybe_better.as_ref() {
581 pending.cancel.request_finalization();
582 }
583
584 debug!(target: "payload_builder", id=%self.config.payload_id(), "no best payload yet to resolve, building empty payload");
585
586 let args = BuildArguments {
587 cached_reads: self.cached_reads.take().unwrap_or_default(),
588 execution_cache: self.execution_cache.clone(),
589 state_root_handle: None,
590 config: self.config.clone(),
591 cancel: CancelOnDrop::default(),
592 best_payload: None,
593 };
594
595 match self.builder.on_missing_payload(args) {
596 MissingPayloadBehaviour::AwaitInProgress => {
597 debug!(target: "payload_builder", id=%self.config.payload_id(), "awaiting in progress payload build job");
598 }
599 MissingPayloadBehaviour::RaceEmptyPayload => {
600 debug!(target: "payload_builder", id=%self.config.payload_id(), "racing empty payload");
601
602 self.metrics.inc_requested_empty_payload();
604 let (tx, rx) = oneshot::channel();
606 let config = self.config.clone();
607 let builder = self.builder.clone();
608 let span = Span::current();
609 self.executor.spawn_blocking_named_or_tokio(
610 PAYLOAD_BUILDER_THREAD_NAME,
611 move || {
612 let _parent = span.enter();
613 let _span =
614 debug_span!(target: "payload_builder", "build_empty_payload")
615 .entered();
616 let res = builder.build_empty_payload(config);
617 let _ = tx.send(res);
618 },
619 );
620
621 empty_payload = Some(rx);
622 }
623 MissingPayloadBehaviour::RacePayload(job) => {
624 debug!(target: "payload_builder", id=%self.config.payload_id(), "racing fallback payload");
625 let (tx, rx) = oneshot::channel();
627 let span = Span::current();
628 self.executor.spawn_blocking_named_or_tokio(
629 PAYLOAD_BUILDER_THREAD_NAME,
630 move || {
631 let _parent = span.enter();
632 let _span =
633 debug_span!(target: "payload_builder", "build_fallback_payload")
634 .entered();
635 let _ = tx.send(job());
636 },
637 );
638 empty_payload = Some(rx);
639 }
640 };
641 }
642
643 let fut = ResolveBestPayload {
644 best_payload,
645 maybe_better,
646 empty_payload: empty_payload.filter(|_| kind != PayloadKind::WaitForPending),
647 };
648
649 (fut, KeepPayloadJobAlive::No)
650 }
651}
652
653#[derive(Debug, Clone)]
655pub enum PayloadState<P> {
656 Missing,
658 Best(P),
660 Frozen(P),
664}
665
666impl<P> PayloadState<P> {
667 pub const fn is_frozen(&self) -> bool {
669 matches!(self, Self::Frozen(_))
670 }
671
672 pub const fn payload(&self) -> Option<&P> {
674 match self {
675 Self::Missing => None,
676 Self::Best(p) | Self::Frozen(p) => Some(p),
677 }
678 }
679}
680
681#[derive(Debug)]
691pub struct ResolveBestPayload<Payload> {
692 pub best_payload: Option<Payload>,
694 pub maybe_better: Option<PendingPayload<Payload>>,
696 pub empty_payload: Option<oneshot::Receiver<Result<Payload, PayloadBuilderError>>>,
698}
699
700impl<Payload> ResolveBestPayload<Payload> {
701 const fn is_empty(&self) -> bool {
702 self.best_payload.is_none() && self.maybe_better.is_none() && self.empty_payload.is_none()
703 }
704}
705
706impl<Payload> Future for ResolveBestPayload<Payload>
707where
708 Payload: Unpin,
709{
710 type Output = Result<Payload, PayloadBuilderError>;
711
712 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
713 let this = self.get_mut();
714
715 if let Some(fut) = Pin::new(&mut this.maybe_better).as_pin_mut() &&
717 let Poll::Ready(res) = fut.poll(cx)
718 {
719 this.maybe_better = None;
720 if let Ok(Some(payload)) = res.map(|out| out.into_payload()).inspect_err(
721 |err| warn!(target: "payload_builder", %err, "failed to resolve pending payload"),
722 ) {
723 debug!(target: "payload_builder", "resolving better payload");
724 return Poll::Ready(Ok(payload))
725 }
726 }
727
728 if let Some(best) = this.best_payload.take() {
729 debug!(target: "payload_builder", "resolving best payload");
730 return Poll::Ready(Ok(best))
731 }
732
733 if let Some(fut) = Pin::new(&mut this.empty_payload).as_pin_mut() &&
734 let Poll::Ready(res) = fut.poll(cx)
735 {
736 this.empty_payload = None;
737 return match res {
738 Ok(res) => {
739 if let Err(err) = &res {
740 warn!(target: "payload_builder", %err, "failed to resolve empty payload");
741 } else {
742 debug!(target: "payload_builder", "resolving empty payload");
743 }
744 Poll::Ready(res)
745 }
746 Err(err) => Poll::Ready(Err(err.into())),
747 }
748 }
749
750 if this.is_empty() {
751 return Poll::Ready(Err(PayloadBuilderError::MissingPayload))
752 }
753
754 Poll::Pending
755 }
756}
757
758#[derive(Debug)]
760pub struct PendingPayload<P> {
761 cancel: CancelOnDrop,
763 payload: oneshot::Receiver<Result<BuildOutcome<P>, PayloadBuilderError>>,
765}
766
767impl<P> PendingPayload<P> {
768 pub const fn new(
770 cancel: CancelOnDrop,
771 payload: oneshot::Receiver<Result<BuildOutcome<P>, PayloadBuilderError>>,
772 ) -> Self {
773 Self { cancel, payload }
774 }
775}
776
777impl<P> Future for PendingPayload<P> {
778 type Output = Result<BuildOutcome<P>, PayloadBuilderError>;
779
780 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
781 let res = ready!(self.payload.poll_unpin(cx));
782 Poll::Ready(res.map_err(Into::into).and_then(|res| res))
783 }
784}
785
786#[derive(Clone, Debug)]
788pub struct PayloadConfig<Attributes, Header = alloy_consensus::Header> {
789 pub parent_header: Arc<SealedHeader<Header>>,
791 pub parent_block_info: Option<PayloadParentBlockInfo>,
793 pub attributes: Attributes,
795 pub payload_id: PayloadId,
797}
798
799#[derive(Clone, Copy, Debug, PartialEq, Eq)]
801pub struct PayloadParentBlockInfo {
802 pub transaction_count: usize,
804}
805
806impl<Attributes, Header> PayloadConfig<Attributes, Header>
807where
808 Attributes: PayloadAttributes,
809{
810 pub const fn new(
812 parent_header: Arc<SealedHeader<Header>>,
813 attributes: Attributes,
814 payload_id: PayloadId,
815 ) -> Self {
816 Self { parent_header, parent_block_info: None, attributes, payload_id }
817 }
818
819 pub const fn with_parent_block_info(
821 mut self,
822 parent_block_info: Option<PayloadParentBlockInfo>,
823 ) -> Self {
824 self.parent_block_info = parent_block_info;
825 self
826 }
827
828 pub const fn payload_id(&self) -> PayloadId {
830 self.payload_id
831 }
832}
833
834#[derive(Debug)]
836pub enum BuildOutcome<Payload> {
837 Better {
839 payload: Payload,
841 cached_reads: CachedReads,
843 },
844 Aborted {
846 fees: U256,
848 cached_reads: CachedReads,
850 },
851 Cancelled,
853
854 Freeze(Payload),
856}
857
858impl<Payload> BuildOutcome<Payload> {
859 pub fn into_payload(self) -> Option<Payload> {
861 match self {
862 Self::Better { payload, .. } | Self::Freeze(payload) => Some(payload),
863 _ => None,
864 }
865 }
866
867 pub const fn payload(&self) -> Option<&Payload> {
869 match self {
870 Self::Better { payload, .. } | Self::Freeze(payload) => Some(payload),
871 _ => None,
872 }
873 }
874
875 pub const fn is_better(&self) -> bool {
877 matches!(self, Self::Better { .. })
878 }
879
880 pub const fn is_frozen(&self) -> bool {
882 matches!(self, Self::Freeze { .. })
883 }
884
885 pub const fn is_aborted(&self) -> bool {
887 matches!(self, Self::Aborted { .. })
888 }
889
890 pub const fn is_cancelled(&self) -> bool {
892 matches!(self, Self::Cancelled)
893 }
894
895 pub fn map_payload<F, P>(self, f: F) -> BuildOutcome<P>
897 where
898 F: FnOnce(Payload) -> P,
899 {
900 match self {
901 Self::Better { payload, cached_reads } => {
902 BuildOutcome::Better { payload: f(payload), cached_reads }
903 }
904 Self::Aborted { fees, cached_reads } => BuildOutcome::Aborted { fees, cached_reads },
905 Self::Cancelled => BuildOutcome::Cancelled,
906 Self::Freeze(payload) => BuildOutcome::Freeze(f(payload)),
907 }
908 }
909}
910
911#[derive(Debug)]
913pub enum BuildOutcomeKind<Payload> {
914 Better {
916 payload: Payload,
918 },
919 Aborted {
921 fees: U256,
923 },
924 Cancelled,
926 Freeze(Payload),
928}
929
930impl<Payload> BuildOutcomeKind<Payload> {
931 pub fn with_cached_reads(self, cached_reads: CachedReads) -> BuildOutcome<Payload> {
933 match self {
934 Self::Better { payload } => BuildOutcome::Better { payload, cached_reads },
935 Self::Aborted { fees } => BuildOutcome::Aborted { fees, cached_reads },
936 Self::Cancelled => BuildOutcome::Cancelled,
937 Self::Freeze(payload) => BuildOutcome::Freeze(payload),
938 }
939 }
940}
941
942#[derive(Debug)]
948pub struct BuildArguments<Attributes, Payload: BuiltPayload> {
949 pub cached_reads: CachedReads,
951 pub execution_cache: Option<SavedCache>,
953 pub state_root_handle: Option<PayloadStateRootHandle>,
958 pub config: PayloadConfig<Attributes, HeaderTy<Payload::Primitives>>,
960 pub cancel: CancelOnDrop,
962 pub best_payload: Option<Payload>,
964}
965
966impl<Attributes, Payload: BuiltPayload> BuildArguments<Attributes, Payload> {
967 pub const fn new(
969 cached_reads: CachedReads,
970 execution_cache: Option<SavedCache>,
971 state_root_handle: Option<PayloadStateRootHandle>,
972 config: PayloadConfig<Attributes, HeaderTy<Payload::Primitives>>,
973 cancel: CancelOnDrop,
974 best_payload: Option<Payload>,
975 ) -> Self {
976 Self { cached_reads, execution_cache, state_root_handle, config, cancel, best_payload }
977 }
978}
979
980pub trait PayloadBuilder: Send + Sync + Clone {
989 type Attributes: PayloadAttributes;
991 type BuiltPayload: BuiltPayload;
993
994 fn try_build(
1007 &self,
1008 args: BuildArguments<Self::Attributes, Self::BuiltPayload>,
1009 ) -> Result<BuildOutcome<Self::BuiltPayload>, PayloadBuilderError>;
1010
1011 fn on_missing_payload(
1015 &self,
1016 _args: BuildArguments<Self::Attributes, Self::BuiltPayload>,
1017 ) -> MissingPayloadBehaviour<Self::BuiltPayload> {
1018 MissingPayloadBehaviour::RaceEmptyPayload
1019 }
1020
1021 fn build_empty_payload(
1023 &self,
1024 config: PayloadConfig<Self::Attributes, HeaderForPayload<Self::BuiltPayload>>,
1025 ) -> Result<Self::BuiltPayload, PayloadBuilderError>;
1026}
1027
1028#[derive(Default)]
1032pub enum MissingPayloadBehaviour<Payload> {
1033 AwaitInProgress,
1035 #[default]
1037 RaceEmptyPayload,
1038 RacePayload(Box<dyn FnOnce() -> Result<Payload, PayloadBuilderError> + Send>),
1040}
1041
1042impl<Payload> fmt::Debug for MissingPayloadBehaviour<Payload> {
1043 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1044 match self {
1045 Self::AwaitInProgress => write!(f, "AwaitInProgress"),
1046 Self::RaceEmptyPayload => {
1047 write!(f, "RaceEmptyPayload")
1048 }
1049 Self::RacePayload(_) => write!(f, "RacePayload"),
1050 }
1051 }
1052}
1053
1054#[inline(always)]
1058pub fn is_better_payload<T: BuiltPayload>(best_payload: Option<&T>, new_fees: U256) -> bool {
1059 best_payload.is_none_or(|best_payload| new_fees > best_payload.fees())
1060}
1061
1062fn duration_until(unix_timestamp_secs: u64) -> Duration {
1066 let unix_now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default();
1067 let timestamp = Duration::from_secs(unix_timestamp_secs);
1068 timestamp.saturating_sub(unix_now)
1069}