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, doc_auto_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::TaskSpawner;
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
46pub type HeaderForPayload<P> = <<P as BuiltPayload>::Primitives as NodePrimitives>::BlockHeader;
48
49#[derive(Debug)]
51pub struct BasicPayloadJobGenerator<Client, Tasks, Builder> {
52 client: Client,
54 executor: Tasks,
56 config: BasicPayloadJobGeneratorConfig,
58 payload_task_guard: PayloadTaskGuard,
60 builder: Builder,
64 pre_cached: Option<PrecachedState>,
66}
67
68impl<Client, Tasks, Builder> BasicPayloadJobGenerator<Client, Tasks, Builder> {
71 pub fn with_builder(
74 client: Client,
75 executor: Tasks,
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 #[inline]
98 fn max_job_duration(&self, unix_timestamp: u64) -> Duration {
99 let duration_until_timestamp = duration_until(unix_timestamp);
100
101 let duration_until_timestamp = duration_until_timestamp.min(self.config.deadline * 3);
103
104 self.config.deadline + duration_until_timestamp
105 }
106
107 #[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 pub const fn tasks(&self) -> &Tasks {
116 &self.executor
117 }
118
119 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
126impl<Client, Tasks, Builder> PayloadJobGenerator
129 for BasicPayloadJobGenerator<Client, Tasks, Builder>
130where
131 Client: StateProviderFactory
132 + BlockReaderIdExt<Header = HeaderForPayload<Builder::BuiltPayload>>
133 + Clone
134 + Unpin
135 + 'static,
136 Tasks: TaskSpawner + Clone + Unpin + 'static,
137 Builder: PayloadBuilder + Unpin + 'static,
138 Builder::Attributes: Unpin + Clone,
139 Builder::BuiltPayload: Unpin + Clone,
140{
141 type Job = BasicPayloadJob<Tasks, Builder>;
142
143 fn new_payload_job(
144 &self,
145 attributes: <Self::Job as PayloadJob>::PayloadAttributes,
146 ) -> Result<Self::Job, PayloadBuilderError> {
147 let parent_header = if attributes.parent().is_zero() {
148 self.client
150 .latest_header()
151 .map_err(PayloadBuilderError::from)?
152 .ok_or_else(|| PayloadBuilderError::MissingParentHeader(B256::ZERO))?
153 } else {
154 self.client
156 .sealed_header_by_hash(attributes.parent())
157 .map_err(PayloadBuilderError::from)?
158 .ok_or_else(|| PayloadBuilderError::MissingParentHeader(attributes.parent()))?
159 };
160
161 let config = PayloadConfig::new(Arc::new(parent_header.clone()), attributes);
162
163 let until = self.job_deadline(config.attributes.timestamp());
164 let deadline = Box::pin(tokio::time::sleep_until(until));
165
166 let cached_reads = self.maybe_pre_cached(parent_header.hash());
167
168 let mut job = BasicPayloadJob {
169 config,
170 executor: self.executor.clone(),
171 deadline,
172 interval: tokio::time::interval(self.config.interval),
174 best_payload: PayloadState::Missing,
175 pending_block: None,
176 cached_reads,
177 payload_task_guard: self.payload_task_guard.clone(),
178 metrics: Default::default(),
179 builder: self.builder.clone(),
180 };
181
182 job.spawn_build_job();
184
185 Ok(job)
186 }
187
188 fn on_new_state<N: NodePrimitives>(&mut self, new_state: CanonStateNotification<N>) {
189 let mut cached = CachedReads::default();
190
191 let committed = new_state.committed();
193 let new_execution_outcome = committed.execution_outcome();
194 for (addr, acc) in new_execution_outcome.bundle_accounts_iter() {
195 if let Some(info) = acc.info.clone() {
196 let storage =
199 acc.storage.iter().map(|(key, slot)| (*key, slot.present_value)).collect();
200 cached.insert_account(addr, info, storage);
201 }
202 }
203
204 self.pre_cached = Some(PrecachedState { block: committed.tip().hash(), cached });
205 }
206}
207
208#[derive(Debug, Clone)]
212pub struct PrecachedState {
213 pub block: B256,
215 pub cached: CachedReads,
217}
218
219#[derive(Debug, Clone)]
221pub struct PayloadTaskGuard(Arc<Semaphore>);
222
223impl Deref for PayloadTaskGuard {
224 type Target = Semaphore;
225
226 fn deref(&self) -> &Self::Target {
227 &self.0
228 }
229}
230
231impl PayloadTaskGuard {
234 pub fn new(max_payload_tasks: usize) -> Self {
236 Self(Arc::new(Semaphore::new(max_payload_tasks)))
237 }
238}
239
240#[derive(Debug, Clone)]
242pub struct BasicPayloadJobGeneratorConfig {
243 interval: Duration,
245 deadline: Duration,
249 max_payload_tasks: usize,
251}
252
253impl BasicPayloadJobGeneratorConfig {
256 pub const fn interval(mut self, interval: Duration) -> Self {
258 self.interval = interval;
259 self
260 }
261
262 pub const fn deadline(mut self, deadline: Duration) -> Self {
264 self.deadline = deadline;
265 self
266 }
267
268 pub fn max_payload_tasks(mut self, max_payload_tasks: usize) -> Self {
274 assert!(max_payload_tasks > 0, "max_payload_tasks must be greater than 0");
275 self.max_payload_tasks = max_payload_tasks;
276 self
277 }
278}
279
280impl Default for BasicPayloadJobGeneratorConfig {
281 fn default() -> Self {
282 Self {
283 interval: Duration::from_secs(1),
284 deadline: SLOT_DURATION,
286 max_payload_tasks: 3,
287 }
288 }
289}
290
291#[derive(Debug)]
302pub struct BasicPayloadJob<Tasks, Builder>
303where
304 Builder: PayloadBuilder,
305{
306 config: PayloadConfig<Builder::Attributes, HeaderForPayload<Builder::BuiltPayload>>,
308 executor: Tasks,
310 deadline: Pin<Box<Sleep>>,
312 interval: Interval,
314 best_payload: PayloadState<Builder::BuiltPayload>,
316 pending_block: Option<PendingPayload<Builder::BuiltPayload>>,
318 payload_task_guard: PayloadTaskGuard,
320 cached_reads: Option<CachedReads>,
325 metrics: PayloadBuilderMetrics,
327 builder: Builder,
331}
332
333impl<Tasks, Builder> BasicPayloadJob<Tasks, Builder>
334where
335 Tasks: TaskSpawner + Clone + 'static,
336 Builder: PayloadBuilder + Unpin + 'static,
337 Builder::Attributes: Unpin + Clone,
338 Builder::BuiltPayload: Unpin + Clone,
339{
340 fn spawn_build_job(&mut self) {
342 trace!(target: "payload_builder", id = %self.config.payload_id(), "spawn new payload build task");
343 let (tx, rx) = oneshot::channel();
344 let cancel = CancelOnDrop::default();
345 let _cancel = cancel.clone();
346 let guard = self.payload_task_guard.clone();
347 let payload_config = self.config.clone();
348 let best_payload = self.best_payload.payload().cloned();
349 self.metrics.inc_initiated_payload_builds();
350 let cached_reads = self.cached_reads.take().unwrap_or_default();
351 let builder = self.builder.clone();
352 self.executor.spawn_blocking(Box::pin(async move {
353 let _permit = guard.acquire().await;
355 let args =
356 BuildArguments { cached_reads, config: payload_config, cancel, best_payload };
357 let result = builder.try_build(args);
358 let _ = tx.send(result);
359 }));
360
361 self.pending_block = Some(PendingPayload { _cancel, payload: rx });
362 }
363}
364
365impl<Tasks, Builder> Future for BasicPayloadJob<Tasks, Builder>
366where
367 Tasks: TaskSpawner + Clone + 'static,
368 Builder: PayloadBuilder + Unpin + 'static,
369 Builder::Attributes: Unpin + Clone,
370 Builder::BuiltPayload: Unpin + Clone,
371{
372 type Output = Result<(), PayloadBuilderError>;
373
374 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
375 let this = self.get_mut();
376
377 if this.deadline.as_mut().poll(cx).is_ready() {
379 trace!(target: "payload_builder", "payload building deadline reached");
380 return Poll::Ready(Ok(()))
381 }
382
383 while this.interval.poll_tick(cx).is_ready() {
385 if this.pending_block.is_none() && !this.best_payload.is_frozen() {
388 this.spawn_build_job();
389 }
390 }
391
392 if let Some(mut fut) = this.pending_block.take() {
394 match fut.poll_unpin(cx) {
395 Poll::Ready(Ok(outcome)) => match outcome {
396 BuildOutcome::Better { payload, cached_reads } => {
397 this.cached_reads = Some(cached_reads);
398 debug!(target: "payload_builder", value = %payload.fees(), "built better payload");
399 this.best_payload = PayloadState::Best(payload);
400 }
401 BuildOutcome::Freeze(payload) => {
402 debug!(target: "payload_builder", "payload frozen, no further building will occur");
403 this.best_payload = PayloadState::Frozen(payload);
404 }
405 BuildOutcome::Aborted { fees, cached_reads } => {
406 this.cached_reads = Some(cached_reads);
407 trace!(target: "payload_builder", worse_fees = %fees, "skipped payload build of worse block");
408 }
409 BuildOutcome::Cancelled => {
410 unreachable!("the cancel signal never fired")
411 }
412 },
413 Poll::Ready(Err(error)) => {
414 debug!(target: "payload_builder", %error, "payload build attempt failed");
416 this.metrics.inc_failed_payload_builds();
417 }
418 Poll::Pending => {
419 this.pending_block = Some(fut);
420 }
421 }
422 }
423
424 Poll::Pending
425 }
426}
427
428impl<Tasks, Builder> PayloadJob for BasicPayloadJob<Tasks, Builder>
429where
430 Tasks: TaskSpawner + Clone + 'static,
431 Builder: PayloadBuilder + Unpin + 'static,
432 Builder::Attributes: Unpin + Clone,
433 Builder::BuiltPayload: Unpin + Clone,
434{
435 type PayloadAttributes = Builder::Attributes;
436 type ResolvePayloadFuture = ResolveBestPayload<Self::BuiltPayload>;
437 type BuiltPayload = Builder::BuiltPayload;
438
439 fn best_payload(&self) -> Result<Self::BuiltPayload, PayloadBuilderError> {
440 if let Some(payload) = self.best_payload.payload() {
441 Ok(payload.clone())
442 } else {
443 self.metrics.inc_requested_empty_payload();
450 self.builder.build_empty_payload(self.config.clone())
451 }
452 }
453
454 fn payload_attributes(&self) -> Result<Self::PayloadAttributes, PayloadBuilderError> {
455 Ok(self.config.attributes.clone())
456 }
457
458 fn payload_timestamp(&self) -> Result<u64, PayloadBuilderError> {
459 Ok(self.config.attributes.timestamp())
460 }
461
462 fn resolve_kind(
463 &mut self,
464 kind: PayloadKind,
465 ) -> (Self::ResolvePayloadFuture, KeepPayloadJobAlive) {
466 let best_payload = self.best_payload.payload().cloned();
467 if best_payload.is_none() && self.pending_block.is_none() {
468 self.spawn_build_job();
470 }
471
472 let maybe_better = self.pending_block.take();
473 let mut empty_payload = None;
474
475 if best_payload.is_none() {
476 debug!(target: "payload_builder", id=%self.config.payload_id(), "no best payload yet to resolve, building empty payload");
477
478 let args = BuildArguments {
479 cached_reads: self.cached_reads.take().unwrap_or_default(),
480 config: self.config.clone(),
481 cancel: CancelOnDrop::default(),
482 best_payload: None,
483 };
484
485 match self.builder.on_missing_payload(args) {
486 MissingPayloadBehaviour::AwaitInProgress => {
487 debug!(target: "payload_builder", id=%self.config.payload_id(), "awaiting in progress payload build job");
488 }
489 MissingPayloadBehaviour::RaceEmptyPayload => {
490 debug!(target: "payload_builder", id=%self.config.payload_id(), "racing empty payload");
491
492 self.metrics.inc_requested_empty_payload();
494 let (tx, rx) = oneshot::channel();
496 let config = self.config.clone();
497 let builder = self.builder.clone();
498 self.executor.spawn_blocking(Box::pin(async move {
499 let res = builder.build_empty_payload(config);
500 let _ = tx.send(res);
501 }));
502
503 empty_payload = Some(rx);
504 }
505 MissingPayloadBehaviour::RacePayload(job) => {
506 debug!(target: "payload_builder", id=%self.config.payload_id(), "racing fallback payload");
507 let (tx, rx) = oneshot::channel();
509 self.executor.spawn_blocking(Box::pin(async move {
510 let _ = tx.send(job());
511 }));
512 empty_payload = Some(rx);
513 }
514 };
515 }
516
517 let fut = ResolveBestPayload {
518 best_payload,
519 maybe_better,
520 empty_payload: empty_payload.filter(|_| kind != PayloadKind::WaitForPending),
521 };
522
523 (fut, KeepPayloadJobAlive::No)
524 }
525}
526
527#[derive(Debug, Clone)]
529pub enum PayloadState<P> {
530 Missing,
532 Best(P),
534 Frozen(P),
538}
539
540impl<P> PayloadState<P> {
541 pub const fn is_frozen(&self) -> bool {
543 matches!(self, Self::Frozen(_))
544 }
545
546 pub const fn payload(&self) -> Option<&P> {
548 match self {
549 Self::Missing => None,
550 Self::Best(p) | Self::Frozen(p) => Some(p),
551 }
552 }
553}
554
555#[derive(Debug)]
565pub struct ResolveBestPayload<Payload> {
566 pub best_payload: Option<Payload>,
568 pub maybe_better: Option<PendingPayload<Payload>>,
570 pub empty_payload: Option<oneshot::Receiver<Result<Payload, PayloadBuilderError>>>,
572}
573
574impl<Payload> ResolveBestPayload<Payload> {
575 const fn is_empty(&self) -> bool {
576 self.best_payload.is_none() && self.maybe_better.is_none() && self.empty_payload.is_none()
577 }
578}
579
580impl<Payload> Future for ResolveBestPayload<Payload>
581where
582 Payload: Unpin,
583{
584 type Output = Result<Payload, PayloadBuilderError>;
585
586 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
587 let this = self.get_mut();
588
589 if let Some(fut) = Pin::new(&mut this.maybe_better).as_pin_mut() {
591 if let Poll::Ready(res) = fut.poll(cx) {
592 this.maybe_better = None;
593 if let Ok(Some(payload)) = res.map(|out| out.into_payload())
594 .inspect_err(|err| warn!(target: "payload_builder", %err, "failed to resolve pending payload"))
595 {
596 debug!(target: "payload_builder", "resolving better payload");
597 return Poll::Ready(Ok(payload))
598 }
599 }
600 }
601
602 if let Some(best) = this.best_payload.take() {
603 debug!(target: "payload_builder", "resolving best payload");
604 return Poll::Ready(Ok(best))
605 }
606
607 if let Some(fut) = Pin::new(&mut this.empty_payload).as_pin_mut() {
608 if let Poll::Ready(res) = fut.poll(cx) {
609 this.empty_payload = None;
610 return match res {
611 Ok(res) => {
612 if let Err(err) = &res {
613 warn!(target: "payload_builder", %err, "failed to resolve empty payload");
614 } else {
615 debug!(target: "payload_builder", "resolving empty payload");
616 }
617 Poll::Ready(res)
618 }
619 Err(err) => Poll::Ready(Err(err.into())),
620 }
621 }
622 }
623
624 if this.is_empty() {
625 return Poll::Ready(Err(PayloadBuilderError::MissingPayload))
626 }
627
628 Poll::Pending
629 }
630}
631
632#[derive(Debug)]
634pub struct PendingPayload<P> {
635 _cancel: CancelOnDrop,
637 payload: oneshot::Receiver<Result<BuildOutcome<P>, PayloadBuilderError>>,
639}
640
641impl<P> PendingPayload<P> {
642 pub const fn new(
644 cancel: CancelOnDrop,
645 payload: oneshot::Receiver<Result<BuildOutcome<P>, PayloadBuilderError>>,
646 ) -> Self {
647 Self { _cancel: cancel, payload }
648 }
649}
650
651impl<P> Future for PendingPayload<P> {
652 type Output = Result<BuildOutcome<P>, PayloadBuilderError>;
653
654 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
655 let res = ready!(self.payload.poll_unpin(cx));
656 Poll::Ready(res.map_err(Into::into).and_then(|res| res))
657 }
658}
659
660#[derive(Clone, Debug)]
662pub struct PayloadConfig<Attributes, Header = alloy_consensus::Header> {
663 pub parent_header: Arc<SealedHeader<Header>>,
665 pub attributes: Attributes,
667}
668
669impl<Attributes, Header> PayloadConfig<Attributes, Header>
670where
671 Attributes: PayloadBuilderAttributes,
672{
673 pub const fn new(parent_header: Arc<SealedHeader<Header>>, attributes: Attributes) -> Self {
675 Self { parent_header, attributes }
676 }
677
678 pub fn payload_id(&self) -> PayloadId {
680 self.attributes.payload_id()
681 }
682}
683
684#[derive(Debug)]
686pub enum BuildOutcome<Payload> {
687 Better {
689 payload: Payload,
691 cached_reads: CachedReads,
693 },
694 Aborted {
696 fees: U256,
698 cached_reads: CachedReads,
700 },
701 Cancelled,
703
704 Freeze(Payload),
706}
707
708impl<Payload> BuildOutcome<Payload> {
709 pub fn into_payload(self) -> Option<Payload> {
711 match self {
712 Self::Better { payload, .. } | Self::Freeze(payload) => Some(payload),
713 _ => None,
714 }
715 }
716
717 pub const fn is_better(&self) -> bool {
719 matches!(self, Self::Better { .. })
720 }
721
722 pub const fn is_aborted(&self) -> bool {
724 matches!(self, Self::Aborted { .. })
725 }
726
727 pub const fn is_cancelled(&self) -> bool {
729 matches!(self, Self::Cancelled)
730 }
731
732 pub fn map_payload<F, P>(self, f: F) -> BuildOutcome<P>
734 where
735 F: FnOnce(Payload) -> P,
736 {
737 match self {
738 Self::Better { payload, cached_reads } => {
739 BuildOutcome::Better { payload: f(payload), cached_reads }
740 }
741 Self::Aborted { fees, cached_reads } => BuildOutcome::Aborted { fees, cached_reads },
742 Self::Cancelled => BuildOutcome::Cancelled,
743 Self::Freeze(payload) => BuildOutcome::Freeze(f(payload)),
744 }
745 }
746}
747
748#[derive(Debug)]
750pub enum BuildOutcomeKind<Payload> {
751 Better {
753 payload: Payload,
755 },
756 Aborted {
758 fees: U256,
760 },
761 Cancelled,
763 Freeze(Payload),
765}
766
767impl<Payload> BuildOutcomeKind<Payload> {
768 pub fn with_cached_reads(self, cached_reads: CachedReads) -> BuildOutcome<Payload> {
770 match self {
771 Self::Better { payload } => BuildOutcome::Better { payload, cached_reads },
772 Self::Aborted { fees } => BuildOutcome::Aborted { fees, cached_reads },
773 Self::Cancelled => BuildOutcome::Cancelled,
774 Self::Freeze(payload) => BuildOutcome::Freeze(payload),
775 }
776 }
777}
778
779#[derive(Debug)]
785pub struct BuildArguments<Attributes, Payload: BuiltPayload> {
786 pub cached_reads: CachedReads,
788 pub config: PayloadConfig<Attributes, HeaderTy<Payload::Primitives>>,
790 pub cancel: CancelOnDrop,
792 pub best_payload: Option<Payload>,
794}
795
796impl<Attributes, Payload: BuiltPayload> BuildArguments<Attributes, Payload> {
797 pub const fn new(
799 cached_reads: CachedReads,
800 config: PayloadConfig<Attributes, HeaderTy<Payload::Primitives>>,
801 cancel: CancelOnDrop,
802 best_payload: Option<Payload>,
803 ) -> Self {
804 Self { cached_reads, config, cancel, best_payload }
805 }
806}
807
808pub trait PayloadBuilder: Send + Sync + Clone {
817 type Attributes: PayloadBuilderAttributes;
819 type BuiltPayload: BuiltPayload;
821
822 fn try_build(
835 &self,
836 args: BuildArguments<Self::Attributes, Self::BuiltPayload>,
837 ) -> Result<BuildOutcome<Self::BuiltPayload>, PayloadBuilderError>;
838
839 fn on_missing_payload(
843 &self,
844 _args: BuildArguments<Self::Attributes, Self::BuiltPayload>,
845 ) -> MissingPayloadBehaviour<Self::BuiltPayload> {
846 MissingPayloadBehaviour::RaceEmptyPayload
847 }
848
849 fn build_empty_payload(
851 &self,
852 config: PayloadConfig<Self::Attributes, HeaderForPayload<Self::BuiltPayload>>,
853 ) -> Result<Self::BuiltPayload, PayloadBuilderError>;
854}
855
856pub enum MissingPayloadBehaviour<Payload> {
860 AwaitInProgress,
862 RaceEmptyPayload,
864 RacePayload(Box<dyn FnOnce() -> Result<Payload, PayloadBuilderError> + Send>),
866}
867
868impl<Payload> fmt::Debug for MissingPayloadBehaviour<Payload> {
869 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
870 match self {
871 Self::AwaitInProgress => write!(f, "AwaitInProgress"),
872 Self::RaceEmptyPayload => {
873 write!(f, "RaceEmptyPayload")
874 }
875 Self::RacePayload(_) => write!(f, "RacePayload"),
876 }
877 }
878}
879
880impl<Payload> Default for MissingPayloadBehaviour<Payload> {
881 fn default() -> Self {
882 Self::RaceEmptyPayload
883 }
884}
885
886#[inline(always)]
890pub fn is_better_payload<T: BuiltPayload>(best_payload: Option<&T>, new_fees: U256) -> bool {
891 if let Some(best_payload) = best_payload {
892 new_fees > best_payload.fees()
893 } else {
894 true
895 }
896}
897
898fn duration_until(unix_timestamp_secs: u64) -> Duration {
902 let unix_now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default();
903 let timestamp = Duration::from_secs(unix_timestamp_secs);
904 timestamp.saturating_sub(unix_now)
905}