1use crate::chain_query::EraSummary;
4use crate::error::{Error, Result};
5use anyhow::anyhow;
6use chrono::{DateTime, Duration, Utc};
7use plutus_ledger_api::{
8 csl::lib as csl,
9 v3::{
10 interval::{Extended, LowerBound, PlutusInterval, UpperBound},
11 transaction::{POSIXTime, POSIXTimeConversionError, POSIXTimeRange},
12 },
13};
14
15pub fn posix_time_into_slot(
17 era_summaries: &[EraSummary],
18 sys_start: &DateTime<Utc>,
19 posix_time: POSIXTime,
20) -> Result<u64> {
21 let abs_time: DateTime<Utc> = posix_time
22 .try_into()
23 .map_err(|source: POSIXTimeConversionError| Error::ConversionError(anyhow!(source)))?;
24 time_into_slot(era_summaries, sys_start, abs_time)
25}
26
27pub fn time_into_slot(
28 era_summaries: &[EraSummary],
29 sys_start: &DateTime<Utc>,
30 time: DateTime<Utc>,
31) -> Result<u64> {
32 let rel_time = time - sys_start;
34
35 if era_summaries.is_empty() {
36 return Err(Error::InvalidConfiguration("era_summaries".to_string()));
37 }
38
39 let era_summary = era_summaries
40 .iter()
41 .filter(|era_summary| era_summary.start.time <= rel_time)
42 .last()
43 .ok_or(Error::InvalidPOSIXTime(format!(
44 "{} is before system start at {}, or era_summaries are invalid.",
45 time, sys_start
46 )))?;
47
48 let time_in_era = rel_time - era_summary.start.time;
49 let slots_in_era = time_in_era.num_milliseconds() as u64 / era_summary.parameters.slot_length;
50
51 Ok(era_summary.start.slot + slots_in_era)
52}
53
54pub fn slot_into_posix_time(
55 era_summaries: &[EraSummary],
56 sys_start: &DateTime<Utc>,
57 slot: u64,
58) -> Result<POSIXTime> {
59 Ok(slot_into_time(era_summaries, sys_start, slot)?.into())
60}
61
62pub fn slot_into_time(
63 era_summaries: &[EraSummary],
64 sys_start: &DateTime<Utc>,
65 slot: u64,
66) -> Result<DateTime<Utc>> {
67 let era_summary = era_summaries
68 .iter()
69 .filter(|era_summary| era_summary.start.slot <= slot)
70 .last()
71 .ok_or(Error::InvalidConfiguration("era_summaries".to_string()))?;
72
73 let time_in_era = Duration::try_milliseconds(
74 ((slot - era_summary.start.slot) * era_summary.parameters.slot_length) as i64,
75 )
76 .ok_or(Error::Internal(anyhow!(
77 "Couldn't convert era in time to milliseconds"
78 )))?;
79
80 Ok(*sys_start + era_summary.start.time + time_in_era)
81}
82
83pub fn time_range_into_slots(
84 era_summaries: &[EraSummary],
85 sys_start: &DateTime<Utc>,
86 interval: POSIXTimeRange,
87) -> Result<(Option<csl::BigNum>, Option<csl::BigNum>)> {
88 let PlutusInterval {
89 from:
90 LowerBound {
91 bound: lower_bound,
92 closed: lower_closed,
93 },
94 to: UpperBound {
95 bound: upper_bound,
96 closed: upper_closed,
97 },
98 } = interval;
99
100 let lb_slots = match lower_bound {
101 Extended::NegInf => None,
102 Extended::PosInf => Some(u64::MAX),
103 Extended::Finite(posix_time) => {
104 let slots = posix_time_into_slot(era_summaries, sys_start, posix_time)?;
105 if lower_closed {
106 Some(slots)
107 } else {
108 Some(slots + 1)
109 }
110 }
111 };
112
113 let ub_slots = match upper_bound {
114 Extended::NegInf => Some(0),
115 Extended::PosInf => None,
116 Extended::Finite(posix_time) => {
117 let slots = posix_time_into_slot(era_summaries, sys_start, posix_time)?;
118 if upper_closed {
119 Some(slots)
120 } else {
121 Some(slots - 1)
122 }
123 }
124 };
125
126 Ok((
127 lb_slots.map(csl::BigNum::from),
128 ub_slots.map(csl::BigNum::from),
129 ))
130}