amaru_kernel/cardano/
era_summary.rs1use std::time::Duration;
16
17use crate::{Epoch, EraBound, Slot, cardano::era_params::EraParams, cbor};
18
19#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
22pub struct EraSummary {
23 pub start: EraBound,
24 pub end: Option<EraBound>,
25 pub params: EraParams,
26}
27
28impl EraSummary {
29 pub fn contains_slot(&self, slot: &Slot, tip: &Slot, stability_window: &Slot) -> bool {
33 &self.end.as_ref().map(|end| end.slot).unwrap_or_else(|| self.calculate_end_bound(tip, stability_window).slot)
34 >= slot
35 }
36
37 pub fn contains_slot_unchecked_horizon(&self, slot: &Slot) -> bool {
40 self.end.as_ref().map(|end| &end.slot >= slot).unwrap_or(true)
41 }
42
43 pub fn contains_epoch(&self, epoch: &Epoch, tip: &Slot, stability_window: &Slot) -> bool {
44 &self.end.as_ref().map(|end| end.epoch).unwrap_or_else(|| self.calculate_end_bound(tip, stability_window).epoch)
45 > epoch
46 }
47
48 pub fn contains_epoch_unchecked_horizon(&self, epoch: &Epoch) -> bool {
51 self.end.as_ref().map(|end| &end.epoch > epoch).unwrap_or(true)
52 }
53
54 fn calculate_end_bound(&self, tip: &Slot, stability_window: &Slot) -> EraBound {
58 let Self { start, params, end } = self;
59
60 debug_assert!(end.is_none());
61
62 let end_of_stable_window = start.slot.as_u64().max(tip.as_u64() + 1) + stability_window.as_u64();
66
67 let delta_slots = end_of_stable_window - start.slot.as_u64();
68
69 let delta_epochs = delta_slots / params.epoch_size_slots
70 + if delta_slots.is_multiple_of(params.epoch_size_slots) { 0 } else { 1 };
71
72 let max_foreseeable_epoch = start.epoch.as_u64() + delta_epochs;
73
74 let foreseeable_slots = delta_epochs * params.epoch_size_slots;
75
76 EraBound {
77 time: Duration::from_secs(start.time.as_secs() + params.slot_length.as_secs() * foreseeable_slots),
78 slot: Slot::new(start.slot.as_u64() + foreseeable_slots),
79 epoch: Epoch::new(max_foreseeable_epoch),
80 }
81 }
82}
83
84impl<C> cbor::Encode<C> for EraSummary {
85 fn encode<W: cbor::encode::Write>(
86 &self,
87 e: &mut cbor::Encoder<W>,
88 ctx: &mut C,
89 ) -> Result<(), cbor::encode::Error<W::Error>> {
90 e.begin_array()?;
91 self.start.encode(e, ctx)?;
92 self.end.encode(e, ctx)?;
93 self.params.encode(e, ctx)?;
94 e.end()?;
95 Ok(())
96 }
97}
98
99impl<'b, C> cbor::Decode<'b, C> for EraSummary {
100 fn decode(d: &mut cbor::Decoder<'b>, _ctx: &mut C) -> Result<Self, cbor::decode::Error> {
101 cbor::heterogeneous_array(d, |d, assert_len| {
102 assert_len(3)?;
103 let start = d.decode()?;
104 let end = d.decode()?;
105 let params = d.decode()?;
106 Ok(EraSummary { start, end, params })
107 })
108 }
109}
110
111#[cfg(any(test, feature = "test-utils"))]
112pub use tests::*;
113
114#[cfg(any(test, feature = "test-utils"))]
115mod tests {
116 use std::cmp::{max, min};
117
118 use proptest::prelude::*;
119
120 use super::*;
121 use crate::{Epoch, any_era_bound_for_epoch, any_era_params, prop_cbor_roundtrip};
122
123 prop_compose! {
124 pub fn any_era_summary()(
125 b1 in any::<u16>(),
126 b2 in any::<u16>(),
127 params in any_era_params(),
128 )(
129 first_epoch in Just(min(b1, b2) as u64),
130 last_epoch in Just(max(b1, b2) as u64),
131 params in Just(params),
132 start in any_era_bound_for_epoch(Epoch::from(max(b1, b2) as u64)),
133 ) -> EraSummary {
134 let epochs_elapsed = last_epoch - first_epoch;
135 let slots_elapsed = epochs_elapsed * params.epoch_size_slots;
136 let time_elapsed = params.slot_length * slots_elapsed as u32;
137 let end = Some(EraBound {
138 time: start.time + time_elapsed,
139 slot: start.slot.offset_by(slots_elapsed),
140 epoch: Epoch::from(last_epoch),
141 });
142 EraSummary { start, end, params }
143 }
144 }
145
146 prop_cbor_roundtrip!(EraSummary, any_era_summary());
147}