1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
//! POSIX time to/from slot conversions

use crate::chain_query::EraSummary;
use crate::error::{Error, Result};
use anyhow::anyhow;
use cardano_serialization_lib as csl;
use chrono::{DateTime, Duration, Utc};
use plutus_ledger_api::v2::interval::{Extended, LowerBound, PlutusInterval, UpperBound};
use plutus_ledger_api::v2::transaction::{POSIXTime, POSIXTimeConversionError, POSIXTimeRange};

/// Convert a POSIX time into number of slots in the current system
pub fn posix_time_into_slot(
    era_summaries: &[EraSummary],
    sys_start: &DateTime<Utc>,
    posix_time: POSIXTime,
) -> Result<u64> {
    let abs_time: DateTime<Utc> = posix_time
        .try_into()
        .map_err(|source: POSIXTimeConversionError| Error::ConversionError(anyhow!(source)))?;
    time_into_slot(era_summaries, sys_start, abs_time)
}

pub fn time_into_slot(
    era_summaries: &[EraSummary],
    sys_start: &DateTime<Utc>,
    time: DateTime<Utc>,
) -> Result<u64> {
    // Time since system start
    let rel_time = time - sys_start;

    if era_summaries.is_empty() {
        return Err(Error::InvalidConfiguration("era_summaries".to_string()));
    }

    let era_summary = era_summaries
        .iter()
        .filter(|era_summary| era_summary.start.time <= rel_time)
        .last()
        .ok_or(Error::InvalidPOSIXTime(format!(
            "{} is before system start at {}, or era_summaries are invalid.",
            time, sys_start
        )))?;

    let time_in_era = rel_time - era_summary.start.time;
    let slots_in_era = time_in_era.num_milliseconds() as u64 / era_summary.parameters.slot_length;

    Ok(era_summary.start.slot + slots_in_era)
}

pub fn slot_into_posix_time(
    era_summaries: &[EraSummary],
    sys_start: &DateTime<Utc>,
    slot: u64,
) -> Result<POSIXTime> {
    Ok(slot_into_time(era_summaries, sys_start, slot)?.into())
}

pub fn slot_into_time(
    era_summaries: &[EraSummary],
    sys_start: &DateTime<Utc>,
    slot: u64,
) -> Result<DateTime<Utc>> {
    let era_summary = era_summaries
        .iter()
        .filter(|era_summary| era_summary.start.slot <= slot)
        .last()
        .ok_or(Error::InvalidConfiguration("era_summaries".to_string()))?;

    let time_in_era = Duration::try_milliseconds(
        ((slot - era_summary.start.slot) * era_summary.parameters.slot_length) as i64,
    )
    .ok_or(Error::Internal(anyhow!(
        "Couldn't convert era in time to milliseconds"
    )))?;

    Ok(*sys_start + era_summary.start.time + time_in_era)
}

pub fn time_range_into_slots(
    era_summaries: &[EraSummary],
    sys_start: &DateTime<Utc>,
    interval: POSIXTimeRange,
) -> Result<(Option<csl::BigNum>, Option<csl::BigNum>)> {
    let PlutusInterval {
        from:
            LowerBound {
                bound: lower_bound,
                closed: lower_closed,
            },
        to: UpperBound {
            bound: upper_bound,
            closed: upper_closed,
        },
    } = interval;

    let lb_slots = match lower_bound {
        Extended::NegInf => None,
        Extended::PosInf => Some(u64::MAX),
        Extended::Finite(posix_time) => {
            let slots = posix_time_into_slot(era_summaries, sys_start, posix_time)?;
            if lower_closed {
                Some(slots)
            } else {
                Some(slots + 1)
            }
        }
    };

    let ub_slots = match upper_bound {
        Extended::NegInf => Some(0),
        Extended::PosInf => None,
        Extended::Finite(posix_time) => {
            let slots = posix_time_into_slot(era_summaries, sys_start, posix_time)?;
            if upper_closed {
                Some(slots)
            } else {
                Some(slots - 1)
            }
        }
    };

    Ok((
        lb_slots.map(csl::BigNum::from),
        ub_slots.map(csl::BigNum::from),
    ))
}