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/SeismicSystems/seismic-reth/issues/"
7)]
8#![cfg_attr(not(test), warn(unused_crate_dependencies))]
9#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
10
11use crate::metrics::PayloadBuilderMetrics;
12use alloy_consensus::constants::EMPTY_WITHDRAWALS;
13use alloy_eips::{eip4895::Withdrawals, merge::SLOT_DURATION};
14use alloy_primitives::{Bytes, B256, U256};
15use futures_core::ready;
16use futures_util::FutureExt;
17use reth_chainspec::EthereumHardforks;
18use reth_evm::state_change::post_block_withdrawals_balance_increments;
19use reth_payload_builder::{KeepPayloadJobAlive, PayloadId, PayloadJob, PayloadJobGenerator};
20use reth_payload_builder_primitives::PayloadBuilderError;
21use reth_payload_primitives::{BuiltPayload, PayloadBuilderAttributes, PayloadKind};
22use reth_primitives::{proofs, SealedHeader};
23use reth_primitives_traits::constants::RETH_CLIENT_VERSION;
24use reth_provider::{BlockReaderIdExt, CanonStateNotification, StateProviderFactory};
25use reth_revm::cached::CachedReads;
26use reth_tasks::TaskSpawner;
27use reth_transaction_pool::TransactionPool;
28use revm::{Database, State};
29use std::{
30 fmt,
31 future::Future,
32 ops::Deref,
33 pin::Pin,
34 sync::{atomic::AtomicBool, 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, trace, warn};
43
44mod metrics;
45mod stack;
46
47pub use stack::PayloadBuilderStack;
48
49#[derive(Debug)]
51pub struct BasicPayloadJobGenerator<Client, Pool, Tasks, Builder> {
52 client: Client,
54 pool: Pool,
56 executor: Tasks,
58 config: BasicPayloadJobGeneratorConfig,
60 payload_task_guard: PayloadTaskGuard,
62 builder: Builder,
66 pre_cached: Option<PrecachedState>,
68}
69
70impl<Client, Pool, Tasks, Builder> BasicPayloadJobGenerator<Client, Pool, Tasks, Builder> {
73 pub fn with_builder(
76 client: Client,
77 pool: Pool,
78 executor: Tasks,
79 config: BasicPayloadJobGeneratorConfig,
80 builder: Builder,
81 ) -> Self {
82 Self {
83 client,
84 pool,
85 executor,
86 payload_task_guard: PayloadTaskGuard::new(config.max_payload_tasks),
87 config,
88 builder,
89 pre_cached: None,
90 }
91 }
92
93 #[inline]
102 fn max_job_duration(&self, unix_timestamp: u64) -> Duration {
103 let duration_until_timestamp = duration_until(unix_timestamp);
104
105 let duration_until_timestamp = duration_until_timestamp.min(self.config.deadline * 3);
107
108 self.config.deadline + duration_until_timestamp
109 }
110
111 #[inline]
114 fn job_deadline(&self, unix_timestamp: u64) -> tokio::time::Instant {
115 tokio::time::Instant::now() + self.max_job_duration(unix_timestamp)
116 }
117
118 pub const fn tasks(&self) -> &Tasks {
120 &self.executor
121 }
122
123 fn maybe_pre_cached(&self, parent: B256) -> Option<CachedReads> {
126 self.pre_cached.as_ref().filter(|pc| pc.block == parent).map(|pc| pc.cached.clone())
127 }
128}
129
130impl<Client, Pool, Tasks, Builder> PayloadJobGenerator
133 for BasicPayloadJobGenerator<Client, Pool, Tasks, Builder>
134where
135 Client: StateProviderFactory
136 + BlockReaderIdExt<Header = alloy_consensus::Header>
137 + Clone
138 + Unpin
139 + 'static,
140 Pool: TransactionPool + Unpin + 'static,
141 Tasks: TaskSpawner + Clone + Unpin + 'static,
142 Builder: PayloadBuilder<Pool, Client> + Unpin + 'static,
143 <Builder as PayloadBuilder<Pool, Client>>::Attributes: Unpin + Clone,
144 <Builder as PayloadBuilder<Pool, Client>>::BuiltPayload: Unpin + Clone,
145{
146 type Job = BasicPayloadJob<Client, Pool, Tasks, Builder>;
147
148 fn new_payload_job(
149 &self,
150 attributes: <Self::Job as PayloadJob>::PayloadAttributes,
151 ) -> Result<Self::Job, PayloadBuilderError> {
152 let parent_header = if attributes.parent().is_zero() {
153 self.client
155 .latest_header()
156 .map_err(PayloadBuilderError::from)?
157 .ok_or_else(|| PayloadBuilderError::MissingParentHeader(B256::ZERO))?
158 } else {
159 self.client
161 .sealed_header_by_hash(attributes.parent())
162 .map_err(PayloadBuilderError::from)?
163 .ok_or_else(|| PayloadBuilderError::MissingParentHeader(attributes.parent()))?
164 };
165
166 let config = PayloadConfig::new(Arc::new(parent_header.clone()), attributes);
167
168 let until = self.job_deadline(config.attributes.timestamp());
169 let deadline = Box::pin(tokio::time::sleep_until(until));
170
171 let cached_reads = self.maybe_pre_cached(parent_header.hash());
172
173 let mut job = BasicPayloadJob {
174 config,
175 client: self.client.clone(),
176 pool: self.pool.clone(),
177 executor: self.executor.clone(),
178 deadline,
179 interval: tokio::time::interval(self.config.interval),
181 best_payload: PayloadState::Missing,
182 pending_block: None,
183 cached_reads,
184 payload_task_guard: self.payload_task_guard.clone(),
185 metrics: Default::default(),
186 builder: self.builder.clone(),
187 };
188
189 job.spawn_build_job();
191
192 Ok(job)
193 }
194
195 fn on_new_state(&mut self, new_state: CanonStateNotification) {
196 let mut cached = CachedReads::default();
197
198 let committed = new_state.committed();
200 let new_execution_outcome = committed.execution_outcome();
201 for (addr, acc) in new_execution_outcome.bundle_accounts_iter() {
202 if let Some(info) = acc.info.clone() {
203 let storage =
206 acc.storage.iter().map(|(key, slot)| (*key, slot.present_value)).collect();
207 cached.insert_account(addr, info, storage);
208 }
209 }
210
211 self.pre_cached = Some(PrecachedState { block: committed.tip().hash(), cached });
212 }
213}
214
215#[derive(Debug, Clone)]
219pub struct PrecachedState {
220 pub block: B256,
222 pub cached: CachedReads,
224}
225
226#[derive(Debug, Clone)]
228pub struct PayloadTaskGuard(Arc<Semaphore>);
229
230impl Deref for PayloadTaskGuard {
231 type Target = Semaphore;
232
233 fn deref(&self) -> &Self::Target {
234 &self.0
235 }
236}
237
238impl PayloadTaskGuard {
241 pub fn new(max_payload_tasks: usize) -> Self {
243 Self(Arc::new(Semaphore::new(max_payload_tasks)))
244 }
245}
246
247#[derive(Debug, Clone)]
249pub struct BasicPayloadJobGeneratorConfig {
250 extradata: Bytes,
252 interval: Duration,
254 deadline: Duration,
258 max_payload_tasks: usize,
260}
261
262impl BasicPayloadJobGeneratorConfig {
265 pub const fn interval(mut self, interval: Duration) -> Self {
267 self.interval = interval;
268 self
269 }
270
271 pub const fn deadline(mut self, deadline: Duration) -> Self {
273 self.deadline = deadline;
274 self
275 }
276
277 pub fn max_payload_tasks(mut self, max_payload_tasks: usize) -> Self {
283 assert!(max_payload_tasks > 0, "max_payload_tasks must be greater than 0");
284 self.max_payload_tasks = max_payload_tasks;
285 self
286 }
287
288 pub fn extradata(mut self, extradata: Bytes) -> Self {
292 self.extradata = extradata;
293 self
294 }
295}
296
297impl Default for BasicPayloadJobGeneratorConfig {
298 fn default() -> Self {
299 Self {
300 extradata: alloy_rlp::encode(RETH_CLIENT_VERSION.as_bytes()).into(),
301 interval: Duration::from_secs(1),
302 deadline: SLOT_DURATION,
304 max_payload_tasks: 3,
305 }
306 }
307}
308
309#[derive(Debug)]
311pub struct BasicPayloadJob<Client, Pool, Tasks, Builder>
312where
313 Builder: PayloadBuilder<Pool, Client>,
314{
315 config: PayloadConfig<Builder::Attributes>,
317 client: Client,
319 pool: Pool,
321 executor: Tasks,
323 deadline: Pin<Box<Sleep>>,
325 interval: Interval,
327 best_payload: PayloadState<Builder::BuiltPayload>,
329 pending_block: Option<PendingPayload<Builder::BuiltPayload>>,
331 payload_task_guard: PayloadTaskGuard,
333 cached_reads: Option<CachedReads>,
338 metrics: PayloadBuilderMetrics,
340 builder: Builder,
344}
345
346impl<Client, Pool, Tasks, Builder> BasicPayloadJob<Client, Pool, Tasks, Builder>
347where
348 Client: StateProviderFactory + Clone + Unpin + 'static,
349 Pool: TransactionPool + Unpin + 'static,
350 Tasks: TaskSpawner + Clone + 'static,
351 Builder: PayloadBuilder<Pool, Client> + Unpin + 'static,
352 <Builder as PayloadBuilder<Pool, Client>>::Attributes: Unpin + Clone,
353 <Builder as PayloadBuilder<Pool, Client>>::BuiltPayload: Unpin + Clone,
354{
355 fn spawn_build_job(&mut self) {
357 trace!(target: "payload_builder", id = %self.config.payload_id(), "spawn new payload build task");
358 let (tx, rx) = oneshot::channel();
359 let client = self.client.clone();
360 let pool = self.pool.clone();
361 let cancel = Cancelled::default();
362 let _cancel = cancel.clone();
363 let guard = self.payload_task_guard.clone();
364 let payload_config = self.config.clone();
365 let best_payload = self.best_payload.payload().cloned();
366 self.metrics.inc_initiated_payload_builds();
367 let cached_reads = self.cached_reads.take().unwrap_or_default();
368 let builder = self.builder.clone();
369 self.executor.spawn_blocking(Box::pin(async move {
370 let _permit = guard.acquire().await;
372 let args = BuildArguments {
373 client,
374 pool,
375 cached_reads,
376 config: payload_config,
377 cancel,
378 best_payload,
379 };
380 let result = builder.try_build(args);
381 let _ = tx.send(result);
382 }));
383
384 self.pending_block = Some(PendingPayload { _cancel, payload: rx });
385 }
386}
387
388impl<Client, Pool, Tasks, Builder> Future for BasicPayloadJob<Client, Pool, Tasks, Builder>
389where
390 Client: StateProviderFactory + Clone + Unpin + 'static,
391 Pool: TransactionPool + Unpin + 'static,
392 Tasks: TaskSpawner + Clone + 'static,
393 Builder: PayloadBuilder<Pool, Client> + Unpin + 'static,
394 <Builder as PayloadBuilder<Pool, Client>>::Attributes: Unpin + Clone,
395 <Builder as PayloadBuilder<Pool, Client>>::BuiltPayload: Unpin + Clone,
396{
397 type Output = Result<(), PayloadBuilderError>;
398
399 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
400 let this = self.get_mut();
401
402 if this.deadline.as_mut().poll(cx).is_ready() {
404 trace!(target: "payload_builder", "payload building deadline reached");
405 return Poll::Ready(Ok(()))
406 }
407
408 while this.interval.poll_tick(cx).is_ready() {
410 if this.pending_block.is_none() && !this.best_payload.is_frozen() {
413 this.spawn_build_job();
414 }
415 }
416
417 if let Some(mut fut) = this.pending_block.take() {
419 match fut.poll_unpin(cx) {
420 Poll::Ready(Ok(outcome)) => match outcome {
421 BuildOutcome::Better { payload, cached_reads } => {
422 this.cached_reads = Some(cached_reads);
423 debug!(target: "payload_builder", value = %payload.fees(), "built better payload");
424 this.best_payload = PayloadState::Best(payload);
425 }
426 BuildOutcome::Freeze(payload) => {
427 debug!(target: "payload_builder", "payload frozen, no further building will occur");
428 this.best_payload = PayloadState::Frozen(payload);
429 }
430 BuildOutcome::Aborted { fees, cached_reads } => {
431 this.cached_reads = Some(cached_reads);
432 trace!(target: "payload_builder", worse_fees = %fees, "skipped payload build of worse block");
433 }
434 BuildOutcome::Cancelled => {
435 unreachable!("the cancel signal never fired")
436 }
437 },
438 Poll::Ready(Err(error)) => {
439 debug!(target: "payload_builder", %error, "payload build attempt failed");
441 this.metrics.inc_failed_payload_builds();
442 }
443 Poll::Pending => {
444 this.pending_block = Some(fut);
445 }
446 }
447 }
448
449 Poll::Pending
450 }
451}
452
453impl<Client, Pool, Tasks, Builder> PayloadJob for BasicPayloadJob<Client, Pool, Tasks, Builder>
454where
455 Client: StateProviderFactory + Clone + Unpin + 'static,
456 Pool: TransactionPool + Unpin + 'static,
457 Tasks: TaskSpawner + Clone + 'static,
458 Builder: PayloadBuilder<Pool, Client> + Unpin + 'static,
459 <Builder as PayloadBuilder<Pool, Client>>::Attributes: Unpin + Clone,
460 <Builder as PayloadBuilder<Pool, Client>>::BuiltPayload: Unpin + Clone,
461{
462 type PayloadAttributes = Builder::Attributes;
463 type ResolvePayloadFuture = ResolveBestPayload<Self::BuiltPayload>;
464 type BuiltPayload = Builder::BuiltPayload;
465
466 fn best_payload(&self) -> Result<Self::BuiltPayload, PayloadBuilderError> {
467 if let Some(payload) = self.best_payload.payload() {
468 Ok(payload.clone())
469 } else {
470 self.metrics.inc_requested_empty_payload();
477 self.builder.build_empty_payload(&self.client, self.config.clone())
478 }
479 }
480
481 fn payload_attributes(&self) -> Result<Self::PayloadAttributes, PayloadBuilderError> {
482 Ok(self.config.attributes.clone())
483 }
484
485 fn resolve_kind(
486 &mut self,
487 kind: PayloadKind,
488 ) -> (Self::ResolvePayloadFuture, KeepPayloadJobAlive) {
489 let best_payload = self.best_payload.payload().cloned();
490 if best_payload.is_none() && self.pending_block.is_none() {
491 self.spawn_build_job();
493 }
494
495 let maybe_better = self.pending_block.take();
496 let mut empty_payload = None;
497
498 if best_payload.is_none() {
499 debug!(target: "payload_builder", id=%self.config.payload_id(), "no best payload yet to resolve, building empty payload");
500
501 let args = BuildArguments {
502 client: self.client.clone(),
503 pool: self.pool.clone(),
504 cached_reads: self.cached_reads.take().unwrap_or_default(),
505 config: self.config.clone(),
506 cancel: Cancelled::default(),
507 best_payload: None,
508 };
509
510 match self.builder.on_missing_payload(args) {
511 MissingPayloadBehaviour::AwaitInProgress => {
512 debug!(target: "payload_builder", id=%self.config.payload_id(), "awaiting in progress payload build job");
513 }
514 MissingPayloadBehaviour::RaceEmptyPayload => {
515 debug!(target: "payload_builder", id=%self.config.payload_id(), "racing empty payload");
516
517 self.metrics.inc_requested_empty_payload();
519 let (tx, rx) = oneshot::channel();
521 let client = self.client.clone();
522 let config = self.config.clone();
523 let builder = self.builder.clone();
524 self.executor.spawn_blocking(Box::pin(async move {
525 let res = builder.build_empty_payload(&client, config);
526 let _ = tx.send(res);
527 }));
528
529 empty_payload = Some(rx);
530 }
531 MissingPayloadBehaviour::RacePayload(job) => {
532 debug!(target: "payload_builder", id=%self.config.payload_id(), "racing fallback payload");
533 let (tx, rx) = oneshot::channel();
535 self.executor.spawn_blocking(Box::pin(async move {
536 let _ = tx.send(job());
537 }));
538 empty_payload = Some(rx);
539 }
540 };
541 }
542
543 let fut = ResolveBestPayload {
544 best_payload,
545 maybe_better,
546 empty_payload: empty_payload.filter(|_| kind != PayloadKind::WaitForPending),
547 };
548
549 (fut, KeepPayloadJobAlive::No)
550 }
551}
552
553#[derive(Debug, Clone)]
555pub enum PayloadState<P> {
556 Missing,
558 Best(P),
560 Frozen(P),
564}
565
566impl<P> PayloadState<P> {
567 pub const fn is_frozen(&self) -> bool {
569 matches!(self, Self::Frozen(_))
570 }
571
572 pub const fn payload(&self) -> Option<&P> {
574 match self {
575 Self::Missing => None,
576 Self::Best(p) | Self::Frozen(p) => Some(p),
577 }
578 }
579}
580
581#[derive(Debug)]
591pub struct ResolveBestPayload<Payload> {
592 pub best_payload: Option<Payload>,
594 pub maybe_better: Option<PendingPayload<Payload>>,
596 pub empty_payload: Option<oneshot::Receiver<Result<Payload, PayloadBuilderError>>>,
598}
599
600impl<Payload> ResolveBestPayload<Payload> {
601 const fn is_empty(&self) -> bool {
602 self.best_payload.is_none() && self.maybe_better.is_none() && self.empty_payload.is_none()
603 }
604}
605
606impl<Payload> Future for ResolveBestPayload<Payload>
607where
608 Payload: Unpin,
609{
610 type Output = Result<Payload, PayloadBuilderError>;
611
612 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
613 let this = self.get_mut();
614
615 if let Some(fut) = Pin::new(&mut this.maybe_better).as_pin_mut() {
617 if let Poll::Ready(res) = fut.poll(cx) {
618 this.maybe_better = None;
619 if let Ok(Some(payload)) = res.map(|out| out.into_payload())
620 .inspect_err(|err| warn!(target: "payload_builder", %err, "failed to resolve pending payload"))
621 {
622 debug!(target: "payload_builder", "resolving better payload");
623 return Poll::Ready(Ok(payload))
624 }
625 }
626 }
627
628 if let Some(best) = this.best_payload.take() {
629 debug!(target: "payload_builder", "resolving best payload");
630 return Poll::Ready(Ok(best))
631 }
632
633 if let Some(fut) = Pin::new(&mut this.empty_payload).as_pin_mut() {
634 if let Poll::Ready(res) = fut.poll(cx) {
635 this.empty_payload = None;
636 return match res {
637 Ok(res) => {
638 if let Err(err) = &res {
639 warn!(target: "payload_builder", %err, "failed to resolve empty payload");
640 } else {
641 debug!(target: "payload_builder", "resolving empty payload");
642 }
643 Poll::Ready(res)
644 }
645 Err(err) => Poll::Ready(Err(err.into())),
646 }
647 }
648 }
649
650 if this.is_empty() {
651 return Poll::Ready(Err(PayloadBuilderError::MissingPayload))
652 }
653
654 Poll::Pending
655 }
656}
657
658#[derive(Debug)]
660pub struct PendingPayload<P> {
661 _cancel: Cancelled,
663 payload: oneshot::Receiver<Result<BuildOutcome<P>, PayloadBuilderError>>,
665}
666
667impl<P> PendingPayload<P> {
668 pub const fn new(
670 cancel: Cancelled,
671 payload: oneshot::Receiver<Result<BuildOutcome<P>, PayloadBuilderError>>,
672 ) -> Self {
673 Self { _cancel: cancel, payload }
674 }
675}
676
677impl<P> Future for PendingPayload<P> {
678 type Output = Result<BuildOutcome<P>, PayloadBuilderError>;
679
680 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
681 let res = ready!(self.payload.poll_unpin(cx));
682 Poll::Ready(res.map_err(Into::into).and_then(|res| res))
683 }
684}
685
686#[derive(Default, Clone, Debug)]
690pub struct Cancelled(Arc<AtomicBool>);
691
692impl Cancelled {
695 pub fn is_cancelled(&self) -> bool {
697 self.0.load(std::sync::atomic::Ordering::Relaxed)
698 }
699}
700
701impl Drop for Cancelled {
702 fn drop(&mut self) {
703 self.0.store(true, std::sync::atomic::Ordering::Relaxed);
704 }
705}
706
707#[derive(Clone, Debug)]
709pub struct PayloadConfig<Attributes> {
710 pub parent_header: Arc<SealedHeader>,
712 pub attributes: Attributes,
714}
715
716impl<Attributes> PayloadConfig<Attributes>
717where
718 Attributes: PayloadBuilderAttributes,
719{
720 pub const fn new(parent_header: Arc<SealedHeader>, attributes: Attributes) -> Self {
722 Self { parent_header, attributes }
723 }
724
725 pub fn payload_id(&self) -> PayloadId {
727 self.attributes.payload_id()
728 }
729}
730
731#[derive(Debug)]
733pub enum BuildOutcome<Payload> {
734 Better {
736 payload: Payload,
738 cached_reads: CachedReads,
740 },
741 Aborted {
743 fees: U256,
745 cached_reads: CachedReads,
747 },
748 Cancelled,
750
751 Freeze(Payload),
753}
754
755impl<Payload> BuildOutcome<Payload> {
756 pub fn into_payload(self) -> Option<Payload> {
758 match self {
759 Self::Better { payload, .. } | Self::Freeze(payload) => Some(payload),
760 _ => None,
761 }
762 }
763
764 pub const fn is_better(&self) -> bool {
766 matches!(self, Self::Better { .. })
767 }
768
769 pub const fn is_aborted(&self) -> bool {
771 matches!(self, Self::Aborted { .. })
772 }
773
774 pub const fn is_cancelled(&self) -> bool {
776 matches!(self, Self::Cancelled)
777 }
778
779 pub(crate) fn map_payload<F, P>(self, f: F) -> BuildOutcome<P>
781 where
782 F: FnOnce(Payload) -> P,
783 {
784 match self {
785 Self::Better { payload, cached_reads } => {
786 BuildOutcome::Better { payload: f(payload), cached_reads }
787 }
788 Self::Aborted { fees, cached_reads } => BuildOutcome::Aborted { fees, cached_reads },
789 Self::Cancelled => BuildOutcome::Cancelled,
790 Self::Freeze(payload) => BuildOutcome::Freeze(f(payload)),
791 }
792 }
793}
794
795#[derive(Debug)]
797pub enum BuildOutcomeKind<Payload> {
798 Better {
800 payload: Payload,
802 },
803 Aborted {
805 fees: U256,
807 },
808 Cancelled,
810 Freeze(Payload),
812}
813
814impl<Payload> BuildOutcomeKind<Payload> {
815 pub fn with_cached_reads(self, cached_reads: CachedReads) -> BuildOutcome<Payload> {
817 match self {
818 Self::Better { payload } => BuildOutcome::Better { payload, cached_reads },
819 Self::Aborted { fees } => BuildOutcome::Aborted { fees, cached_reads },
820 Self::Cancelled => BuildOutcome::Cancelled,
821 Self::Freeze(payload) => BuildOutcome::Freeze(payload),
822 }
823 }
824}
825
826#[derive(Debug)]
832pub struct BuildArguments<Pool, Client, Attributes, Payload> {
833 pub client: Client,
835 pub pool: Pool,
839 pub cached_reads: CachedReads,
841 pub config: PayloadConfig<Attributes>,
843 pub cancel: Cancelled,
845 pub best_payload: Option<Payload>,
847}
848
849impl<Pool, Client, Attributes, Payload> BuildArguments<Pool, Client, Attributes, Payload> {
850 pub const fn new(
852 client: Client,
853 pool: Pool,
854 cached_reads: CachedReads,
855 config: PayloadConfig<Attributes>,
856 cancel: Cancelled,
857 best_payload: Option<Payload>,
858 ) -> Self {
859 Self { client, pool, cached_reads, config, cancel, best_payload }
860 }
861
862 pub fn with_pool<P>(self, pool: P) -> BuildArguments<P, Client, Attributes, Payload> {
864 BuildArguments {
865 client: self.client,
866 pool,
867 cached_reads: self.cached_reads,
868 config: self.config,
869 cancel: self.cancel,
870 best_payload: self.best_payload,
871 }
872 }
873
874 pub fn map_pool<F, P>(self, f: F) -> BuildArguments<P, Client, Attributes, Payload>
876 where
877 F: FnOnce(Pool) -> P,
878 {
879 BuildArguments {
880 client: self.client,
881 pool: f(self.pool),
882 cached_reads: self.cached_reads,
883 config: self.config,
884 cancel: self.cancel,
885 best_payload: self.best_payload,
886 }
887 }
888}
889
890pub trait PayloadBuilder<Pool, Client>: Send + Sync + Clone {
899 type Attributes: PayloadBuilderAttributes;
901 type BuiltPayload: BuiltPayload;
903
904 fn try_build(
917 &self,
918 args: BuildArguments<Pool, Client, Self::Attributes, Self::BuiltPayload>,
919 ) -> Result<BuildOutcome<Self::BuiltPayload>, PayloadBuilderError>;
920
921 fn on_missing_payload(
925 &self,
926 _args: BuildArguments<Pool, Client, Self::Attributes, Self::BuiltPayload>,
927 ) -> MissingPayloadBehaviour<Self::BuiltPayload> {
928 MissingPayloadBehaviour::RaceEmptyPayload
929 }
930
931 fn build_empty_payload(
933 &self,
934 client: &Client,
935 config: PayloadConfig<Self::Attributes>,
936 ) -> Result<Self::BuiltPayload, PayloadBuilderError>;
937}
938
939pub enum MissingPayloadBehaviour<Payload> {
943 AwaitInProgress,
945 RaceEmptyPayload,
947 RacePayload(Box<dyn FnOnce() -> Result<Payload, PayloadBuilderError> + Send>),
949}
950
951impl<Payload> fmt::Debug for MissingPayloadBehaviour<Payload> {
952 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
953 match self {
954 Self::AwaitInProgress => write!(f, "AwaitInProgress"),
955 Self::RaceEmptyPayload => {
956 write!(f, "RaceEmptyPayload")
957 }
958 Self::RacePayload(_) => write!(f, "RacePayload"),
959 }
960 }
961}
962
963impl<Payload> Default for MissingPayloadBehaviour<Payload> {
964 fn default() -> Self {
965 Self::RaceEmptyPayload
966 }
967}
968
969pub fn commit_withdrawals<DB, ChainSpec>(
975 db: &mut State<DB>,
976 chain_spec: &ChainSpec,
977 timestamp: u64,
978 withdrawals: &Withdrawals,
979) -> Result<Option<B256>, DB::Error>
980where
981 DB: Database,
982 ChainSpec: EthereumHardforks,
983{
984 if !chain_spec.is_shanghai_active_at_timestamp(timestamp) {
985 return Ok(None)
986 }
987
988 if withdrawals.is_empty() {
989 return Ok(Some(EMPTY_WITHDRAWALS))
990 }
991
992 let balance_increments =
993 post_block_withdrawals_balance_increments(chain_spec, timestamp, withdrawals);
994
995 db.increment_balances(balance_increments)?;
996
997 Ok(Some(proofs::calculate_withdrawals_root(withdrawals)))
998}
999
1000#[inline(always)]
1004pub fn is_better_payload<T: BuiltPayload>(best_payload: Option<&T>, new_fees: U256) -> bool {
1005 if let Some(best_payload) = best_payload {
1006 new_fees > best_payload.fees()
1007 } else {
1008 true
1009 }
1010}
1011
1012fn duration_until(unix_timestamp_secs: u64) -> Duration {
1016 let unix_now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default();
1017 let timestamp = Duration::from_secs(unix_timestamp_secs);
1018 timestamp.saturating_sub(unix_now)
1019}