mina_producer_dashboard/evaluator/
epoch.rs1use std::ops::AddAssign;
2
3use serde::{Deserialize, Serialize};
4
5use crate::{
6 archive::{postgres_types::ChainStatus, Block},
7 node::epoch_ledgers::{Balances, NanoMina},
8};
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct EpochSlots {
12 inner: Vec<SlotData>,
13}
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct MergedSummary {
17 epoch_number: u32,
18 summary: Option<EpochSummary>,
19 sub_windows: Vec<EpochSummary>,
20 #[serde(flatten)]
21 balances: Balances,
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct AllTimeSummary {
26 #[serde(flatten)]
27 slot_summary: SlotSummary,
28}
29
30impl EpochSlots {
31 pub fn new(inner: Vec<SlotData>) -> Self {
32 Self { inner }
33 }
34
35 pub fn merged_summary(&self, epoch_number: u32, balances: Balances) -> MergedSummary {
36 if self.inner.is_empty() {
37 MergedSummary {
38 epoch_number,
39 summary: None,
40 sub_windows: vec![],
41 balances: Balances::default(),
42 }
43 } else {
44 let summary = self.summary();
45 MergedSummary {
46 epoch_number,
47 summary: Some(summary),
48 sub_windows: self.sub_windows(),
49 balances,
50 }
51 }
52 }
53
54 pub fn sub_windows(&self) -> Vec<EpochSummary> {
55 let chunk_size = self.inner.len() / 15;
56
57 self.inner
58 .chunks_exact(chunk_size)
59 .map(|window| EpochSlots::new(window.to_vec()).summary())
60 .collect()
61 }
62
63 pub fn slot_summary(&self) -> (SlotSummary, bool) {
64 let mut slot_summary = SlotSummary::default();
65 let mut is_current = false;
66 for slot in self.inner.iter() {
67 if slot.is_current_slot {
68 is_current = true;
69 }
70
71 match slot.block_status {
72 SlotStatus::Canonical | SlotStatus::CanonicalPending => {
73 slot_summary.won_slots += 1;
74 slot_summary.canonical += 1;
75 slot_summary.earned_rewards += NanoMina::new(720.into());
76 }
77 SlotStatus::Missed => {
78 slot_summary.won_slots += 1;
79 slot_summary.missed += 1;
80 }
81 SlotStatus::Orphaned | SlotStatus::OrphanedPending => {
82 slot_summary.won_slots += 1;
83 slot_summary.orphaned += 1;
84 }
85 SlotStatus::ToBeProduced => {
86 slot_summary.won_slots += 1;
87 slot_summary.future_rights += 1;
88 }
89 SlotStatus::Pending
90 | SlotStatus::Lost
91 | SlotStatus::Empty
92 | SlotStatus::Foreign
93 | SlotStatus::ForeignToBeProduced => {
94 }
96 }
97 }
98 slot_summary.expected_rewards = NanoMina::new((slot_summary.won_slots * 720).into());
99 (slot_summary, is_current)
100 }
101
102 fn summary(&self) -> EpochSummary {
103 let (slot_summary, is_current) = self.slot_summary();
104
105 let slot_start = self.inner.first().unwrap().global_slot.to_u32();
106 let slot_end = self.inner.last().unwrap().global_slot.to_u32();
107
108 EpochSummary {
109 max: slot_summary.won_slots,
110 slot_summary,
111 slot_start,
112 slot_end,
113 is_current,
114 }
115 }
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct EpochSummary {
120 max: usize,
121 #[serde(flatten)]
122 slot_summary: SlotSummary,
123 slot_start: u32,
124 slot_end: u32,
125 is_current: bool,
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
129pub struct SlotSummary {
130 won_slots: usize,
131 canonical: usize,
132 orphaned: usize,
133 missed: usize,
134 future_rights: usize,
135 expected_rewards: NanoMina,
136 earned_rewards: NanoMina,
137}
138
139impl AddAssign for SlotSummary {
140 fn add_assign(&mut self, rhs: Self) {
141 *self = Self {
142 won_slots: self.won_slots + rhs.won_slots,
143 canonical: self.canonical + rhs.canonical,
144 orphaned: self.orphaned + rhs.orphaned,
145 missed: self.missed + rhs.missed,
146 future_rights: self.future_rights + rhs.future_rights,
147 expected_rewards: self.expected_rewards.clone() + rhs.expected_rewards,
148 earned_rewards: self.earned_rewards.clone() + rhs.earned_rewards,
149 }
150 }
151}
152
153#[derive(Debug, Clone, Serialize, Deserialize)]
154pub enum SlotStatus {
155 Canonical,
156 CanonicalPending,
157 Missed,
158 Orphaned,
159 OrphanedPending,
160 Pending,
161 ToBeProduced,
162 Lost,
163 Empty,
164 Foreign,
165 ForeignToBeProduced,
166}
167
168impl From<ChainStatus> for SlotStatus {
169 fn from(value: ChainStatus) -> Self {
170 match value {
171 ChainStatus::Canonical => SlotStatus::Canonical,
172 ChainStatus::Orphaned => SlotStatus::Orphaned,
173 ChainStatus::Pending => SlotStatus::Pending,
174 }
175 }
176}
177
178impl SlotStatus {
179 pub fn in_transition_frontier(&self) -> bool {
180 matches!(
181 self,
182 SlotStatus::CanonicalPending | SlotStatus::OrphanedPending
183 )
184 }
185}
186
187#[derive(Debug, Clone, Serialize, Deserialize)]
189pub struct RawSlot(u32);
190
191#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, PartialOrd)]
192pub struct RawGlobalSlot(u32);
193
194impl From<RawGlobalSlot> for RawSlot {
195 fn from(value: RawGlobalSlot) -> Self {
196 RawSlot(value.0 % 7140)
197 }
198}
199
200impl From<u32> for RawSlot {
201 fn from(value: u32) -> Self {
202 RawSlot(value)
203 }
204}
205
206impl From<u32> for RawGlobalSlot {
207 fn from(value: u32) -> Self {
208 RawGlobalSlot(value)
209 }
210}
211
212impl RawGlobalSlot {
213 pub fn to_u32(&self) -> u32 {
214 self.0
215 }
216
217 pub fn epoch(&self) -> u32 {
218 self.0 / 7140
219 }
220}
221
222#[derive(Debug, Clone, Serialize, Deserialize)]
224pub struct SlotData {
225 slot: RawSlot,
226 global_slot: RawGlobalSlot,
227 block_status: SlotStatus,
228 timestamp: i64,
229 state_hash: Option<String>,
230 height: Option<u32>,
231 is_current_slot: bool,
232}
233
234#[derive(Debug, Clone, Serialize, Deserialize)]
235pub struct SlotBlockUpdate {
236 height: u32,
237 state_hash: String,
238 block_status: SlotStatus,
239}
240
241impl SlotBlockUpdate {
242 pub fn new(height: u32, state_hash: String, block_status: SlotStatus) -> Self {
243 Self {
244 height,
245 state_hash,
246 block_status,
247 }
248 }
249}
250
251impl From<Block> for SlotBlockUpdate {
252 fn from(value: Block) -> Self {
253 Self {
254 height: value.height as u32,
255 state_hash: value.state_hash,
256 block_status: value.chain_status.into(),
257 }
258 }
259}
260
261impl From<&Block> for SlotBlockUpdate {
262 fn from(value: &Block) -> Self {
263 value.clone().into()
264 }
265}
266
267impl SlotData {
268 pub fn new(global_slot: u32, timestamp: i64, block: Option<SlotBlockUpdate>) -> Self {
269 let block_status = block
270 .clone()
271 .map_or(SlotStatus::ToBeProduced, |block| block.block_status);
272 let state_hash = block.clone().map(|block| block.state_hash);
273 let height = block.map(|block| block.height);
274 let global_slot: RawGlobalSlot = global_slot.into();
275
276 Self {
277 slot: global_slot.clone().into(),
278 global_slot,
279 block_status,
280 state_hash,
281 height,
282 timestamp,
283 is_current_slot: false,
284 }
285 }
286
287 pub fn global_slot(&self) -> RawGlobalSlot {
288 self.global_slot.clone()
289 }
290
291 pub fn has_block(&self) -> bool {
292 self.state_hash.is_some()
293 }
294
295 pub fn block_status(&self) -> SlotStatus {
296 self.block_status.clone()
297 }
298
299 pub fn new_lost(global_slot: u32, timestamp: i64) -> Self {
300 let global_slot: RawGlobalSlot = global_slot.into();
301 Self {
302 slot: global_slot.clone().into(),
303 global_slot,
304 block_status: SlotStatus::Empty,
305 timestamp,
306 state_hash: None,
307 height: None,
308 is_current_slot: false,
309 }
310 }
311
312 pub fn add_block(&mut self, block: SlotBlockUpdate) {
313 self.state_hash = Some(block.state_hash);
314 self.height = Some(block.height);
315 self.block_status = block.block_status;
316 }
317
318 pub fn update_block_status(&mut self, block_status: SlotStatus) {
319 self.block_status = block_status;
320 }
321
322 pub fn set_as_current(&mut self) {
323 self.is_current_slot = true;
324 }
325
326 pub fn unset_as_current(&mut self) {
327 self.is_current_slot = false;
328 }
329}