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_payload_builder::{KeepPayloadJobAlive, PayloadId, PayloadJob, PayloadJobGenerator};
18use reth_payload_builder_primitives::PayloadBuilderError;
19use reth_payload_primitives::{BuiltPayload, PayloadBuilderAttributes, PayloadKind};
20use reth_primitives_traits::{HeaderTy, NodePrimitives, SealedHeader};
21use reth_revm::{cached::CachedReads, cancelled::CancelOnDrop};
22use reth_storage_api::{BlockReaderIdExt, StateProviderFactory};
23use reth_tasks::Runtime;
24use std::{
25 fmt,
26 future::Future,
27 ops::Deref,
28 pin::Pin,
29 sync::Arc,
30 task::{Context, Poll},
31 time::{Duration, SystemTime, UNIX_EPOCH},
32};
33use tokio::{
34 sync::{oneshot, Semaphore},
35 time::{Interval, Sleep},
36};
37use tracing::{debug, trace, warn};
38
39mod better_payload_emitter;
40mod metrics;
41mod stack;
42
43pub use better_payload_emitter::BetterPayloadEmitter;
44pub use stack::PayloadBuilderStack;
45
46pub type HeaderForPayload<P> = <<P as BuiltPayload>::Primitives as NodePrimitives>::BlockHeader;
48
49#[derive(Debug)]
51pub struct BasicPayloadJobGenerator<Client, Builder> {
52 client: Client,
54 executor: Runtime,
56 config: BasicPayloadJobGeneratorConfig,
58 payload_task_guard: PayloadTaskGuard,
60 builder: Builder,
64 pre_cached: Option<PrecachedState>,
66}
67
68impl<Client, Builder> BasicPayloadJobGenerator<Client, Builder> {
71 pub fn with_builder(
74 client: Client,
75 executor: Runtime,
76 config: BasicPayloadJobGeneratorConfig,
77 builder: Builder,
78 ) -> Self {
79 Self {
80 client,
81 executor,
82 payload_task_guard: PayloadTaskGuard::new(config.max_payload_tasks),
83 config,
84 builder,
85 pre_cached: None,
86 }
87 }
88
89 #[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) -> &Runtime {
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, Builder> PayloadJobGenerator for BasicPayloadJobGenerator<Client, Builder>
129where
130 Client: StateProviderFactory
131 + BlockReaderIdExt<Header = HeaderForPayload<Builder::BuiltPayload>>
132 + Clone
133 + Unpin
134 + 'static,
135 Builder: PayloadBuilder + Unpin + 'static,
136 Builder::Attributes: Unpin + Clone,
137 Builder::BuiltPayload: Unpin + Clone,
138{
139 type Job = BasicPayloadJob<Builder>;
140
141 fn new_payload_job(
142 &self,
143 attributes: <Self::Job as PayloadJob>::PayloadAttributes,
144 ) -> Result<Self::Job, PayloadBuilderError> {
145 let parent_header = if attributes.parent().is_zero() {
146 self.client
148 .latest_header()
149 .map_err(PayloadBuilderError::from)?
150 .ok_or_else(|| PayloadBuilderError::MissingParentHeader(B256::ZERO))?
151 } else {
152 self.client
154 .sealed_header_by_hash(attributes.parent())
155 .map_err(PayloadBuilderError::from)?
156 .ok_or_else(|| PayloadBuilderError::MissingParentHeader(attributes.parent()))?
157 };
158
159 let cached_reads = self.maybe_pre_cached(parent_header.hash());
160
161 let config = PayloadConfig::new(Arc::new(parent_header), attributes);
162
163 let until = self.job_deadline(config.attributes.timestamp());
164 let deadline = Box::pin(tokio::time::sleep_until(until));
165
166 let mut job = BasicPayloadJob {
167 config,
168 executor: self.executor.clone(),
169 deadline,
170 interval: tokio::time::interval(self.config.interval),
172 best_payload: PayloadState::Missing,
173 pending_block: None,
174 cached_reads,
175 payload_task_guard: self.payload_task_guard.clone(),
176 metrics: Default::default(),
177 builder: self.builder.clone(),
178 };
179
180 job.spawn_build_job();
182
183 Ok(job)
184 }
185
186 fn on_new_state<N: NodePrimitives>(&mut self, new_state: CanonStateNotification<N>) {
187 let mut cached = CachedReads::default();
188
189 let committed = new_state.committed();
191 let new_execution_outcome = committed.execution_outcome();
192 for (addr, acc) in new_execution_outcome.bundle_accounts_iter() {
193 if let Some(info) = acc.info.clone() {
194 let storage =
197 acc.storage.iter().map(|(key, slot)| (*key, slot.present_value)).collect();
198 cached.insert_account(addr, info, storage);
199 }
200 }
201
202 self.pre_cached = Some(PrecachedState { block: committed.tip().hash(), cached });
203 }
204}
205
206#[derive(Debug, Clone)]
210pub struct PrecachedState {
211 pub block: B256,
213 pub cached: CachedReads,
215}
216
217#[derive(Debug, Clone)]
219pub struct PayloadTaskGuard(Arc<Semaphore>);
220
221impl Deref for PayloadTaskGuard {
222 type Target = Semaphore;
223
224 fn deref(&self) -> &Self::Target {
225 &self.0
226 }
227}
228
229impl PayloadTaskGuard {
232 pub fn new(max_payload_tasks: usize) -> Self {
234 Self(Arc::new(Semaphore::new(max_payload_tasks)))
235 }
236}
237
238#[derive(Debug, Clone)]
240pub struct BasicPayloadJobGeneratorConfig {
241 interval: Duration,
243 deadline: Duration,
247 max_payload_tasks: usize,
249}
250
251impl BasicPayloadJobGeneratorConfig {
254 pub const fn interval(mut self, interval: Duration) -> Self {
256 self.interval = interval;
257 self
258 }
259
260 pub const fn deadline(mut self, deadline: Duration) -> Self {
262 self.deadline = deadline;
263 self
264 }
265
266 pub fn max_payload_tasks(mut self, max_payload_tasks: usize) -> Self {
272 assert!(max_payload_tasks > 0, "max_payload_tasks must be greater than 0");
273 self.max_payload_tasks = max_payload_tasks;
274 self
275 }
276}
277
278impl Default for BasicPayloadJobGeneratorConfig {
279 fn default() -> Self {
280 Self {
281 interval: Duration::from_secs(1),
282 deadline: SLOT_DURATION,
284 max_payload_tasks: 3,
285 }
286 }
287}
288
289#[derive(Debug)]
300pub struct BasicPayloadJob<Builder>
301where
302 Builder: PayloadBuilder,
303{
304 config: PayloadConfig<Builder::Attributes, HeaderForPayload<Builder::BuiltPayload>>,
306 executor: Runtime,
308 deadline: Pin<Box<Sleep>>,
310 interval: Interval,
312 best_payload: PayloadState<Builder::BuiltPayload>,
314 pending_block: Option<PendingPayload<Builder::BuiltPayload>>,
316 payload_task_guard: PayloadTaskGuard,
318 cached_reads: Option<CachedReads>,
323 metrics: PayloadBuilderMetrics,
325 builder: Builder,
329}
330
331impl<Builder> BasicPayloadJob<Builder>
332where
333 Builder: PayloadBuilder + Unpin + 'static,
334 Builder::Attributes: Unpin + Clone,
335 Builder::BuiltPayload: Unpin + Clone,
336{
337 fn spawn_build_job(&mut self) {
339 trace!(target: "payload_builder", id = %self.config.payload_id(), "spawn new payload build task");
340 let (tx, rx) = oneshot::channel();
341 let cancel = CancelOnDrop::default();
342 let _cancel = cancel.clone();
343 let guard = self.payload_task_guard.clone();
344 let payload_config = self.config.clone();
345 let best_payload = self.best_payload.payload().cloned();
346 self.metrics.inc_initiated_payload_builds();
347 let cached_reads = self.cached_reads.take().unwrap_or_default();
348 let builder = self.builder.clone();
349 self.executor.spawn_blocking_task(async move {
350 let _permit = guard.acquire().await;
352 let args =
353 BuildArguments { cached_reads, config: payload_config, cancel, best_payload };
354 let result = builder.try_build(args);
355 let _ = tx.send(result);
356 });
357
358 self.pending_block = Some(PendingPayload { _cancel, payload: rx });
359 }
360}
361
362impl<Builder> Future for BasicPayloadJob<Builder>
363where
364 Builder: PayloadBuilder + Unpin + 'static,
365 Builder::Attributes: Unpin + Clone,
366 Builder::BuiltPayload: Unpin + Clone,
367{
368 type Output = Result<(), PayloadBuilderError>;
369
370 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
371 let this = self.get_mut();
372
373 if this.deadline.as_mut().poll(cx).is_ready() {
375 trace!(target: "payload_builder", "payload building deadline reached");
376 return Poll::Ready(Ok(()))
377 }
378
379 loop {
380 if let Some(mut fut) = this.pending_block.take() {
386 match fut.poll_unpin(cx) {
387 Poll::Ready(Ok(outcome)) => match outcome {
388 BuildOutcome::Better { payload, cached_reads } => {
389 this.cached_reads = Some(cached_reads);
390 debug!(target: "payload_builder", value = %payload.fees(), "built better payload");
391 this.best_payload = PayloadState::Best(payload);
392 }
393 BuildOutcome::Freeze(payload) => {
394 debug!(target: "payload_builder", "payload frozen, no further building will occur");
395 this.best_payload = PayloadState::Frozen(payload);
396 }
397 BuildOutcome::Aborted { fees, cached_reads } => {
398 this.cached_reads = Some(cached_reads);
399 trace!(target: "payload_builder", worse_fees = %fees, "skipped payload build of worse block");
400 }
401 BuildOutcome::Cancelled => {
402 unreachable!("the cancel signal never fired")
403 }
404 },
405 Poll::Ready(Err(error)) => {
406 debug!(target: "payload_builder", %error, "payload build attempt failed");
408 this.metrics.inc_failed_payload_builds();
409 }
410 Poll::Pending => {
411 this.pending_block = Some(fut);
412 return Poll::Pending
413 }
414 }
415 }
416
417 if this.best_payload.is_frozen() {
418 return Poll::Pending
419 }
420
421 ready!(this.interval.poll_tick(cx));
427 this.spawn_build_job()
428 }
429 }
430}
431
432impl<Builder> PayloadJob for BasicPayloadJob<Builder>
433where
434 Builder: PayloadBuilder + Unpin + 'static,
435 Builder::Attributes: Unpin + Clone,
436 Builder::BuiltPayload: Unpin + Clone,
437{
438 type PayloadAttributes = Builder::Attributes;
439 type ResolvePayloadFuture = ResolveBestPayload<Self::BuiltPayload>;
440 type BuiltPayload = Builder::BuiltPayload;
441
442 fn best_payload(&self) -> Result<Self::BuiltPayload, PayloadBuilderError> {
443 if let Some(payload) = self.best_payload.payload() {
444 Ok(payload.clone())
445 } else {
446 self.metrics.inc_requested_empty_payload();
453 self.builder.build_empty_payload(self.config.clone())
454 }
455 }
456
457 fn payload_attributes(&self) -> Result<Self::PayloadAttributes, PayloadBuilderError> {
458 Ok(self.config.attributes.clone())
459 }
460
461 fn payload_timestamp(&self) -> Result<u64, PayloadBuilderError> {
462 Ok(self.config.attributes.timestamp())
463 }
464
465 fn resolve_kind(
466 &mut self,
467 kind: PayloadKind,
468 ) -> (Self::ResolvePayloadFuture, KeepPayloadJobAlive) {
469 let best_payload = self.best_payload.payload().cloned();
470 if best_payload.is_none() && self.pending_block.is_none() {
471 self.spawn_build_job();
473 }
474
475 let maybe_better = self.pending_block.take();
476 let mut empty_payload = None;
477
478 if best_payload.is_none() {
479 debug!(target: "payload_builder", id=%self.config.payload_id(), "no best payload yet to resolve, building empty payload");
480
481 let args = BuildArguments {
482 cached_reads: self.cached_reads.take().unwrap_or_default(),
483 config: self.config.clone(),
484 cancel: CancelOnDrop::default(),
485 best_payload: None,
486 };
487
488 match self.builder.on_missing_payload(args) {
489 MissingPayloadBehaviour::AwaitInProgress => {
490 debug!(target: "payload_builder", id=%self.config.payload_id(), "awaiting in progress payload build job");
491 }
492 MissingPayloadBehaviour::RaceEmptyPayload => {
493 debug!(target: "payload_builder", id=%self.config.payload_id(), "racing empty payload");
494
495 self.metrics.inc_requested_empty_payload();
497 let (tx, rx) = oneshot::channel();
499 let config = self.config.clone();
500 let builder = self.builder.clone();
501 self.executor.spawn_blocking_task(async move {
502 let res = builder.build_empty_payload(config);
503 let _ = tx.send(res);
504 });
505
506 empty_payload = Some(rx);
507 }
508 MissingPayloadBehaviour::RacePayload(job) => {
509 debug!(target: "payload_builder", id=%self.config.payload_id(), "racing fallback payload");
510 let (tx, rx) = oneshot::channel();
512 self.executor.spawn_blocking_task(async move {
513 let _ = tx.send(job());
514 });
515 empty_payload = Some(rx);
516 }
517 };
518 }
519
520 let fut = ResolveBestPayload {
521 best_payload,
522 maybe_better,
523 empty_payload: empty_payload.filter(|_| kind != PayloadKind::WaitForPending),
524 };
525
526 (fut, KeepPayloadJobAlive::No)
527 }
528}
529
530#[derive(Debug, Clone)]
532pub enum PayloadState<P> {
533 Missing,
535 Best(P),
537 Frozen(P),
541}
542
543impl<P> PayloadState<P> {
544 pub const fn is_frozen(&self) -> bool {
546 matches!(self, Self::Frozen(_))
547 }
548
549 pub const fn payload(&self) -> Option<&P> {
551 match self {
552 Self::Missing => None,
553 Self::Best(p) | Self::Frozen(p) => Some(p),
554 }
555 }
556}
557
558#[derive(Debug)]
568pub struct ResolveBestPayload<Payload> {
569 pub best_payload: Option<Payload>,
571 pub maybe_better: Option<PendingPayload<Payload>>,
573 pub empty_payload: Option<oneshot::Receiver<Result<Payload, PayloadBuilderError>>>,
575}
576
577impl<Payload> ResolveBestPayload<Payload> {
578 const fn is_empty(&self) -> bool {
579 self.best_payload.is_none() && self.maybe_better.is_none() && self.empty_payload.is_none()
580 }
581}
582
583impl<Payload> Future for ResolveBestPayload<Payload>
584where
585 Payload: Unpin,
586{
587 type Output = Result<Payload, PayloadBuilderError>;
588
589 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
590 let this = self.get_mut();
591
592 if let Some(fut) = Pin::new(&mut this.maybe_better).as_pin_mut() &&
594 let Poll::Ready(res) = fut.poll(cx)
595 {
596 this.maybe_better = None;
597 if let Ok(Some(payload)) = res.map(|out| out.into_payload()).inspect_err(
598 |err| warn!(target: "payload_builder", %err, "failed to resolve pending payload"),
599 ) {
600 debug!(target: "payload_builder", "resolving better payload");
601 return Poll::Ready(Ok(payload))
602 }
603 }
604
605 if let Some(best) = this.best_payload.take() {
606 debug!(target: "payload_builder", "resolving best payload");
607 return Poll::Ready(Ok(best))
608 }
609
610 if let Some(fut) = Pin::new(&mut this.empty_payload).as_pin_mut() &&
611 let Poll::Ready(res) = fut.poll(cx)
612 {
613 this.empty_payload = None;
614 return match res {
615 Ok(res) => {
616 if let Err(err) = &res {
617 warn!(target: "payload_builder", %err, "failed to resolve empty payload");
618 } else {
619 debug!(target: "payload_builder", "resolving empty payload");
620 }
621 Poll::Ready(res)
622 }
623 Err(err) => Poll::Ready(Err(err.into())),
624 }
625 }
626
627 if this.is_empty() {
628 return Poll::Ready(Err(PayloadBuilderError::MissingPayload))
629 }
630
631 Poll::Pending
632 }
633}
634
635#[derive(Debug)]
637pub struct PendingPayload<P> {
638 _cancel: CancelOnDrop,
640 payload: oneshot::Receiver<Result<BuildOutcome<P>, PayloadBuilderError>>,
642}
643
644impl<P> PendingPayload<P> {
645 pub const fn new(
647 cancel: CancelOnDrop,
648 payload: oneshot::Receiver<Result<BuildOutcome<P>, PayloadBuilderError>>,
649 ) -> Self {
650 Self { _cancel: cancel, payload }
651 }
652}
653
654impl<P> Future for PendingPayload<P> {
655 type Output = Result<BuildOutcome<P>, PayloadBuilderError>;
656
657 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
658 let res = ready!(self.payload.poll_unpin(cx));
659 Poll::Ready(res.map_err(Into::into).and_then(|res| res))
660 }
661}
662
663#[derive(Clone, Debug)]
665pub struct PayloadConfig<Attributes, Header = alloy_consensus::Header> {
666 pub parent_header: Arc<SealedHeader<Header>>,
668 pub attributes: Attributes,
670}
671
672impl<Attributes, Header> PayloadConfig<Attributes, Header>
673where
674 Attributes: PayloadBuilderAttributes,
675{
676 pub const fn new(parent_header: Arc<SealedHeader<Header>>, attributes: Attributes) -> Self {
678 Self { parent_header, attributes }
679 }
680
681 pub fn payload_id(&self) -> PayloadId {
683 self.attributes.payload_id()
684 }
685}
686
687#[derive(Debug)]
689pub enum BuildOutcome<Payload> {
690 Better {
692 payload: Payload,
694 cached_reads: CachedReads,
696 },
697 Aborted {
699 fees: U256,
701 cached_reads: CachedReads,
703 },
704 Cancelled,
706
707 Freeze(Payload),
709}
710
711impl<Payload> BuildOutcome<Payload> {
712 pub fn into_payload(self) -> Option<Payload> {
714 match self {
715 Self::Better { payload, .. } | Self::Freeze(payload) => Some(payload),
716 _ => None,
717 }
718 }
719
720 pub const fn payload(&self) -> Option<&Payload> {
722 match self {
723 Self::Better { payload, .. } | Self::Freeze(payload) => Some(payload),
724 _ => None,
725 }
726 }
727
728 pub const fn is_better(&self) -> bool {
730 matches!(self, Self::Better { .. })
731 }
732
733 pub const fn is_frozen(&self) -> bool {
735 matches!(self, Self::Freeze { .. })
736 }
737
738 pub const fn is_aborted(&self) -> bool {
740 matches!(self, Self::Aborted { .. })
741 }
742
743 pub const fn is_cancelled(&self) -> bool {
745 matches!(self, Self::Cancelled)
746 }
747
748 pub fn map_payload<F, P>(self, f: F) -> BuildOutcome<P>
750 where
751 F: FnOnce(Payload) -> P,
752 {
753 match self {
754 Self::Better { payload, cached_reads } => {
755 BuildOutcome::Better { payload: f(payload), cached_reads }
756 }
757 Self::Aborted { fees, cached_reads } => BuildOutcome::Aborted { fees, cached_reads },
758 Self::Cancelled => BuildOutcome::Cancelled,
759 Self::Freeze(payload) => BuildOutcome::Freeze(f(payload)),
760 }
761 }
762}
763
764#[derive(Debug)]
766pub enum BuildOutcomeKind<Payload> {
767 Better {
769 payload: Payload,
771 },
772 Aborted {
774 fees: U256,
776 },
777 Cancelled,
779 Freeze(Payload),
781}
782
783impl<Payload> BuildOutcomeKind<Payload> {
784 pub fn with_cached_reads(self, cached_reads: CachedReads) -> BuildOutcome<Payload> {
786 match self {
787 Self::Better { payload } => BuildOutcome::Better { payload, cached_reads },
788 Self::Aborted { fees } => BuildOutcome::Aborted { fees, cached_reads },
789 Self::Cancelled => BuildOutcome::Cancelled,
790 Self::Freeze(payload) => BuildOutcome::Freeze(payload),
791 }
792 }
793}
794
795#[derive(Debug)]
801pub struct BuildArguments<Attributes, Payload: BuiltPayload> {
802 pub cached_reads: CachedReads,
804 pub config: PayloadConfig<Attributes, HeaderTy<Payload::Primitives>>,
806 pub cancel: CancelOnDrop,
808 pub best_payload: Option<Payload>,
810}
811
812impl<Attributes, Payload: BuiltPayload> BuildArguments<Attributes, Payload> {
813 pub const fn new(
815 cached_reads: CachedReads,
816 config: PayloadConfig<Attributes, HeaderTy<Payload::Primitives>>,
817 cancel: CancelOnDrop,
818 best_payload: Option<Payload>,
819 ) -> Self {
820 Self { cached_reads, config, cancel, best_payload }
821 }
822}
823
824pub trait PayloadBuilder: Send + Sync + Clone {
833 type Attributes: PayloadBuilderAttributes;
835 type BuiltPayload: BuiltPayload;
837
838 fn try_build(
851 &self,
852 args: BuildArguments<Self::Attributes, Self::BuiltPayload>,
853 ) -> Result<BuildOutcome<Self::BuiltPayload>, PayloadBuilderError>;
854
855 fn on_missing_payload(
859 &self,
860 _args: BuildArguments<Self::Attributes, Self::BuiltPayload>,
861 ) -> MissingPayloadBehaviour<Self::BuiltPayload> {
862 MissingPayloadBehaviour::RaceEmptyPayload
863 }
864
865 fn build_empty_payload(
867 &self,
868 config: PayloadConfig<Self::Attributes, HeaderForPayload<Self::BuiltPayload>>,
869 ) -> Result<Self::BuiltPayload, PayloadBuilderError>;
870}
871
872#[derive(Default)]
876pub enum MissingPayloadBehaviour<Payload> {
877 AwaitInProgress,
879 #[default]
881 RaceEmptyPayload,
882 RacePayload(Box<dyn FnOnce() -> Result<Payload, PayloadBuilderError> + Send>),
884}
885
886impl<Payload> fmt::Debug for MissingPayloadBehaviour<Payload> {
887 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
888 match self {
889 Self::AwaitInProgress => write!(f, "AwaitInProgress"),
890 Self::RaceEmptyPayload => {
891 write!(f, "RaceEmptyPayload")
892 }
893 Self::RacePayload(_) => write!(f, "RacePayload"),
894 }
895 }
896}
897
898#[inline(always)]
902pub fn is_better_payload<T: BuiltPayload>(best_payload: Option<&T>, new_fees: U256) -> bool {
903 if let Some(best_payload) = best_payload {
904 new_fees > best_payload.fees()
905 } else {
906 true
907 }
908}
909
910fn duration_until(unix_timestamp_secs: u64) -> Duration {
914 let unix_now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default();
915 let timestamp = Duration::from_secs(unix_timestamp_secs);
916 timestamp.saturating_sub(unix_now)
917}