1use crate::{
6 consensus_types::{CompressedBeaconState, CompressedSignedBeaconBlock},
7 e2s_types::{Entry, IndexEntry, SLOT_INDEX},
8};
9
10#[derive(Debug)]
15pub struct EraGroup {
16 pub blocks: Vec<CompressedSignedBeaconBlock>,
18
19 pub era_state: CompressedBeaconState,
21
22 pub other_entries: Vec<Entry>,
24
25 pub slot_index: Option<SlotIndex>,
27
28 pub state_slot_index: SlotIndex,
30}
31
32impl EraGroup {
33 pub const fn new(
35 blocks: Vec<CompressedSignedBeaconBlock>,
36 era_state: CompressedBeaconState,
37 state_slot_index: SlotIndex,
38 ) -> Self {
39 Self { blocks, era_state, other_entries: Vec::new(), slot_index: None, state_slot_index }
40 }
41
42 pub const fn with_block_index(
44 blocks: Vec<CompressedSignedBeaconBlock>,
45 era_state: CompressedBeaconState,
46 slot_index: SlotIndex,
47 state_slot_index: SlotIndex,
48 ) -> Self {
49 Self {
50 blocks,
51 era_state,
52 other_entries: Vec::new(),
53 slot_index: Some(slot_index),
54 state_slot_index,
55 }
56 }
57
58 pub const fn is_genesis(&self) -> bool {
60 self.blocks.is_empty() && self.slot_index.is_none()
61 }
62
63 pub fn add_entry(&mut self, entry: Entry) {
65 self.other_entries.push(entry);
66 }
67}
68
69#[derive(Debug, Clone, PartialEq, Eq)]
76pub struct SlotIndex {
77 pub starting_slot: u64,
79
80 pub offsets: Vec<i64>,
83}
84
85impl SlotIndex {
86 pub const fn new(starting_slot: u64, offsets: Vec<i64>) -> Self {
88 Self { starting_slot, offsets }
89 }
90
91 pub const fn slot_count(&self) -> usize {
93 self.offsets.len()
94 }
95
96 pub fn get_offset(&self, slot_index: usize) -> Option<i64> {
98 self.offsets.get(slot_index).copied()
99 }
100
101 pub fn has_data_at_slot(&self, slot_index: usize) -> bool {
103 self.get_offset(slot_index).is_some_and(|offset| offset != 0)
104 }
105}
106
107impl IndexEntry for SlotIndex {
108 fn new(starting_number: u64, offsets: Vec<i64>) -> Self {
109 Self { starting_slot: starting_number, offsets }
110 }
111
112 fn entry_type() -> [u8; 2] {
113 SLOT_INDEX
114 }
115
116 fn starting_number(&self) -> u64 {
117 self.starting_slot
118 }
119
120 fn offsets(&self) -> &[i64] {
121 &self.offsets
122 }
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128 use crate::{
129 e2s_types::{Entry, IndexEntry},
130 test_utils::{create_beacon_block, create_beacon_state},
131 };
132
133 #[test]
134 fn test_slot_index_roundtrip() {
135 let starting_slot = 1000;
136 let offsets = vec![100, 200, 300, 400, 500];
137
138 let slot_index = SlotIndex::new(starting_slot, offsets.clone());
139
140 let entry = slot_index.to_entry();
141
142 assert_eq!(entry.entry_type, SLOT_INDEX);
144
145 let recovered = SlotIndex::from_entry(&entry).unwrap();
147
148 assert_eq!(recovered.starting_slot, starting_slot);
150 assert_eq!(recovered.offsets, offsets);
151 }
152 #[test]
153 fn test_slot_index_basic_operations() {
154 let starting_slot = 2000;
155 let offsets = vec![100, 200, 300];
156
157 let slot_index = SlotIndex::new(starting_slot, offsets);
158
159 assert_eq!(slot_index.slot_count(), 3);
160 assert_eq!(slot_index.starting_slot, 2000);
161 }
162
163 #[test]
164 fn test_slot_index_empty_slots() {
165 let starting_slot = 1000;
166 let offsets = vec![100, 0, 300, 0, 500];
167
168 let slot_index = SlotIndex::new(starting_slot, offsets);
169
170 assert!(slot_index.has_data_at_slot(0));
173 assert!(!slot_index.has_data_at_slot(1));
175 assert!(slot_index.has_data_at_slot(2));
177 assert!(!slot_index.has_data_at_slot(3));
179 assert!(slot_index.has_data_at_slot(4));
181 }
182
183 #[test]
184 fn test_era_group_basic_construction() {
185 let blocks =
186 vec![create_beacon_block(10), create_beacon_block(15), create_beacon_block(20)];
187 let era_state = create_beacon_state(50);
188 let state_slot_index = SlotIndex::new(1000, vec![100, 200, 300]);
189
190 let era_group = EraGroup::new(blocks, era_state, state_slot_index);
191
192 assert_eq!(era_group.blocks.len(), 3);
194 assert_eq!(era_group.other_entries.len(), 0);
195 assert_eq!(era_group.slot_index, None);
196 assert_eq!(era_group.state_slot_index.starting_slot, 1000);
197 assert_eq!(era_group.state_slot_index.offsets, vec![100, 200, 300]);
198 }
199
200 #[test]
201 fn test_era_group_with_block_index() {
202 let blocks = vec![create_beacon_block(10), create_beacon_block(15)];
203 let era_state = create_beacon_state(50);
204 let block_slot_index = SlotIndex::new(500, vec![50, 100]);
205 let state_slot_index = SlotIndex::new(1000, vec![200, 300]);
206
207 let era_group =
208 EraGroup::with_block_index(blocks, era_state, block_slot_index, state_slot_index);
209
210 assert_eq!(era_group.blocks.len(), 2);
212 assert_eq!(era_group.other_entries.len(), 0);
213 assert!(era_group.slot_index.is_some());
214
215 let block_index = era_group.slot_index.as_ref().unwrap();
216 assert_eq!(block_index.starting_slot, 500);
217 assert_eq!(block_index.offsets, vec![50, 100]);
218
219 assert_eq!(era_group.state_slot_index.starting_slot, 1000);
220 assert_eq!(era_group.state_slot_index.offsets, vec![200, 300]);
221 }
222
223 #[test]
224 fn test_era_group_genesis_check() {
225 let era_state = create_beacon_state(50);
227 let state_slot_index = SlotIndex::new(0, vec![100]);
228
229 let genesis_era = EraGroup::new(vec![], era_state, state_slot_index);
230 assert!(genesis_era.is_genesis());
231
232 let blocks = vec![create_beacon_block(10)];
234 let era_state = create_beacon_state(50);
235 let state_slot_index = SlotIndex::new(1000, vec![100]);
236
237 let normal_era = EraGroup::new(blocks, era_state, state_slot_index);
238 assert!(!normal_era.is_genesis());
239
240 let era_state = create_beacon_state(50);
242 let block_slot_index = SlotIndex::new(500, vec![50]);
243 let state_slot_index = SlotIndex::new(1000, vec![100]);
244
245 let era_with_index =
246 EraGroup::with_block_index(vec![], era_state, block_slot_index, state_slot_index);
247 assert!(!era_with_index.is_genesis());
248 }
249
250 #[test]
251 fn test_era_group_add_entries() {
252 let blocks = vec![create_beacon_block(10)];
253 let era_state = create_beacon_state(50);
254 let state_slot_index = SlotIndex::new(1000, vec![100]);
255
256 let mut era_group = EraGroup::new(blocks, era_state, state_slot_index);
258 assert_eq!(era_group.other_entries.len(), 0);
259
260 let entry1 = Entry::new([0x01, 0x01], vec![1, 2, 3, 4]);
262 let entry2 = Entry::new([0x02, 0x02], vec![5, 6, 7, 8]);
263
264 era_group.add_entry(entry1);
266 era_group.add_entry(entry2);
267
268 assert_eq!(era_group.other_entries.len(), 2);
270 assert_eq!(era_group.other_entries[0].entry_type, [0x01, 0x01]);
271 assert_eq!(era_group.other_entries[0].data, vec![1, 2, 3, 4]);
272 assert_eq!(era_group.other_entries[1].entry_type, [0x02, 0x02]);
273 assert_eq!(era_group.other_entries[1].data, vec![5, 6, 7, 8]);
274 }
275
276 #[test]
277 fn test_index_with_negative_offset() {
278 let mut data = Vec::new();
279 data.extend_from_slice(&0u64.to_le_bytes());
280 data.extend_from_slice(&(-1024i64).to_le_bytes());
281 data.extend_from_slice(&0i64.to_le_bytes());
282 data.extend_from_slice(&2i64.to_le_bytes());
283
284 let entry = Entry::new(SLOT_INDEX, data);
285 let index = SlotIndex::from_entry(&entry).unwrap();
286 let parsed_offset = index.offsets[0];
287 assert_eq!(parsed_offset, -1024);
288 }
289}