Skip to main content

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