reth_era/e2s/
types.rs

1//! Types to build e2store files
2//! ie. with `.e2s` extension
3//!
4//! e2store file contains header and entry
5//!
6//! The [`Header`] is an 8-byte structure at the beginning of each record in the file
7//!
8//! An [`Entry`] is a complete record in the file, consisting of both a [`Header`] and its
9//! associated data
10
11use crate::e2s::error::E2sError;
12use ssz_derive::{Decode, Encode};
13use std::io::{self, Read, Write};
14
15/// [`Version`] record: ['e', '2']
16pub const VERSION: [u8; 2] = [0x65, 0x32];
17
18/// Empty record
19pub const EMPTY: [u8; 2] = [0x00, 0x00];
20
21/// `SlotIndex` record: ['i', '2']
22pub const SLOT_INDEX: [u8; 2] = [0x69, 0x32];
23
24/// Header for TLV records in e2store files
25#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)]
26pub struct Header {
27    /// Record type identifier
28    pub header_type: [u8; 2],
29
30    /// Length of data following the header
31    pub length: u32,
32
33    /// Reserved field, must be zero
34    pub reserved: u16,
35}
36
37impl Header {
38    /// Create a new header with the specified type and length
39    pub const fn new(header_type: [u8; 2], length: u32) -> Self {
40        Self { header_type, length, reserved: 0 }
41    }
42
43    /// Read header from a reader
44    pub fn read<R: Read>(reader: &mut R) -> Result<Option<Self>, E2sError> {
45        let mut header_bytes = [0u8; 8];
46        match reader.read_exact(&mut header_bytes) {
47            Ok(_) => {}
48            Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => return Ok(None),
49            Err(e) => return Err(e.into()),
50        }
51
52        let header: Self = match ssz::Decode::from_ssz_bytes(&header_bytes) {
53            Ok(h) => h,
54            Err(_) => return Err(E2sError::Ssz(String::from("Failed to decode SSZ header"))),
55        };
56
57        if header.reserved != 0 {
58            return Err(E2sError::ReservedNotZero);
59        }
60
61        Ok(Some(header))
62    }
63
64    /// Writes the header to the given writer.
65    pub fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
66        let encoded = ssz::Encode::as_ssz_bytes(self);
67        writer.write_all(&encoded)
68    }
69}
70
71/// The [`Version`] record must be the first record in an e2store file
72#[derive(Debug, Clone, PartialEq, Eq, Default)]
73pub struct Version;
74
75impl Version {
76    /// Encode this record to the given writer
77    pub fn encode<W: Write>(&self, writer: &mut W) -> io::Result<()> {
78        let header = Header::new(VERSION, 0);
79        header.write(writer)
80    }
81}
82
83/// Complete record in an e2store file, consisting of a type, length, and associated data
84#[derive(Debug, Clone)]
85pub struct Entry {
86    /// Record type identifier
87    pub entry_type: [u8; 2],
88
89    /// Data contained in the entry
90    pub data: Vec<u8>,
91}
92
93impl Entry {
94    /// Create a new entry
95    pub const fn new(entry_type: [u8; 2], data: Vec<u8>) -> Self {
96        Self { entry_type, data }
97    }
98
99    /// Read an entry from a reader
100    pub fn read<R: Read>(reader: &mut R) -> Result<Option<Self>, E2sError> {
101        // Read the header first
102        let header = match Header::read(reader)? {
103            Some(h) => h,
104            None => return Ok(None),
105        };
106
107        // Read the data
108        let mut data = vec![0u8; header.length as usize];
109        match reader.read_exact(&mut data) {
110            Ok(_) => {}
111            Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => {
112                return Err(E2sError::Io(io::Error::new(
113                    io::ErrorKind::UnexpectedEof,
114                    "Unexpected EOF while reading entry data",
115                )));
116            }
117            Err(e) => return Err(e.into()),
118        }
119
120        Ok(Some(Self { entry_type: header.header_type, data }))
121    }
122
123    /// Write the entry to [`Entry`] writer
124    pub fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
125        let header = Header::new(self.entry_type, self.data.len() as u32);
126        header.write(writer)?;
127        writer.write_all(&self.data)
128    }
129
130    /// Check if this is a [`Version`] entry
131    pub fn is_version(&self) -> bool {
132        self.entry_type == VERSION
133    }
134
135    /// Check if this is a `SlotIndex` entry
136    pub fn is_slot_index(&self) -> bool {
137        self.entry_type == SLOT_INDEX
138    }
139}
140
141/// Serialize and deserialize index entries with format:
142/// `starting-number | offsets... | count`
143pub trait IndexEntry: Sized {
144    /// Get the entry type identifier for this index
145    fn entry_type() -> [u8; 2];
146
147    /// Create a new instance with starting number and offsets
148    fn new(starting_number: u64, offsets: Vec<i64>) -> Self;
149
150    /// Get the starting number - can be starting slot or block number for example
151    fn starting_number(&self) -> u64;
152
153    /// Get the offsets vector
154    fn offsets(&self) -> &[i64];
155
156    /// Convert to an [`Entry`] for storage in an e2store file
157    /// Format: starting-number | offset1 | offset2 | ... | count
158    fn to_entry(&self) -> Entry {
159        let mut data = Vec::with_capacity(8 + self.offsets().len() * 8 + 8);
160
161        // Add starting number
162        data.extend_from_slice(&self.starting_number().to_le_bytes());
163
164        // Add all offsets
165        data.extend(self.offsets().iter().flat_map(|offset| offset.to_le_bytes()));
166
167        // Encode count - 8 bytes again
168        let count = self.offsets().len() as i64;
169        data.extend_from_slice(&count.to_le_bytes());
170
171        Entry::new(Self::entry_type(), data)
172    }
173
174    /// Create from an [`Entry`]
175    fn from_entry(entry: &Entry) -> Result<Self, E2sError> {
176        let expected_type = Self::entry_type();
177
178        if entry.entry_type != expected_type {
179            return Err(E2sError::Ssz(format!(
180                "Invalid entry type: expected {:02x}{:02x}, got {:02x}{:02x}",
181                expected_type[0], expected_type[1], entry.entry_type[0], entry.entry_type[1]
182            )));
183        }
184
185        if entry.data.len() < 16 {
186            return Err(E2sError::Ssz(
187                "Index entry too short: need at least 16 bytes for starting_number and count"
188                    .to_string(),
189            ));
190        }
191
192        // Extract count from last 8 bytes
193        let count_bytes = &entry.data[entry.data.len() - 8..];
194        let count = i64::from_le_bytes(
195            count_bytes
196                .try_into()
197                .map_err(|_| E2sError::Ssz("Failed to read count bytes".to_string()))?,
198        ) as usize;
199
200        // Verify entry has correct size
201        let expected_len = 8 + count * 8 + 8;
202        if entry.data.len() != expected_len {
203            return Err(E2sError::Ssz(format!(
204                "Index entry has incorrect length: expected {expected_len}, got {}",
205                entry.data.len()
206            )));
207        }
208
209        // Extract starting number from first 8 bytes
210        let starting_number = u64::from_le_bytes(
211            entry.data[0..8]
212                .try_into()
213                .map_err(|_| E2sError::Ssz("Failed to read starting_number bytes".to_string()))?,
214        );
215
216        // Extract all offsets
217        let mut offsets = Vec::with_capacity(count);
218        for i in 0..count {
219            let start = 8 + i * 8;
220            let end = start + 8;
221            let offset_bytes = &entry.data[start..end];
222            let offset = i64::from_le_bytes(
223                offset_bytes
224                    .try_into()
225                    .map_err(|_| E2sError::Ssz(format!("Failed to read offset {i} bytes")))?,
226            );
227            offsets.push(offset);
228        }
229
230        Ok(Self::new(starting_number, offsets))
231    }
232}