reth_era/
consensus_types.rs

1//! Consensus types for Era post-merge history files
2
3use crate::{
4    e2s_types::{E2sError, Entry},
5    DecodeCompressedSsz,
6};
7use snap::{read::FrameDecoder, write::FrameEncoder};
8use ssz::Decode;
9use std::io::{Read, Write};
10
11/// `CompressedSignedBeaconBlock` record type: [0x01, 0x00]
12pub const COMPRESSED_SIGNED_BEACON_BLOCK: [u8; 2] = [0x01, 0x00];
13
14/// `CompressedBeaconState` record type: [0x02, 0x00]
15pub const COMPRESSED_BEACON_STATE: [u8; 2] = [0x02, 0x00];
16
17/// Compressed signed beacon block
18///
19/// See also <https://github.com/status-im/nimbus-eth2/blob/stable/docs/e2store.md#compressedsignedbeaconblock>.
20#[derive(Debug, Clone)]
21pub struct CompressedSignedBeaconBlock {
22    /// Snappy-compressed ssz-encoded `SignedBeaconBlock`
23    pub data: Vec<u8>,
24}
25
26impl CompressedSignedBeaconBlock {
27    /// Create a new [`CompressedSignedBeaconBlock`] from compressed data
28    pub const fn new(data: Vec<u8>) -> Self {
29        Self { data }
30    }
31
32    /// Create from ssz-encoded block by compressing it with snappy
33    pub fn from_ssz(ssz_data: &[u8]) -> Result<Self, E2sError> {
34        let mut compressed = Vec::new();
35        {
36            let mut encoder = FrameEncoder::new(&mut compressed);
37
38            Write::write_all(&mut encoder, ssz_data).map_err(|e| {
39                E2sError::SnappyCompression(format!("Failed to compress signed beacon block: {e}"))
40            })?;
41
42            encoder.flush().map_err(|e| {
43                E2sError::SnappyCompression(format!("Failed to flush encoder: {e}"))
44            })?;
45        }
46        Ok(Self { data: compressed })
47    }
48
49    /// Decompress to get the original ssz-encoded signed beacon block
50    pub fn decompress(&self) -> Result<Vec<u8>, E2sError> {
51        let mut decoder = FrameDecoder::new(self.data.as_slice());
52        let mut decompressed = Vec::new();
53        Read::read_to_end(&mut decoder, &mut decompressed).map_err(|e| {
54            E2sError::SnappyDecompression(format!("Failed to decompress signed beacon block: {e}"))
55        })?;
56
57        Ok(decompressed)
58    }
59
60    /// Convert to an [`Entry`]
61    pub fn to_entry(&self) -> Entry {
62        Entry::new(COMPRESSED_SIGNED_BEACON_BLOCK, self.data.clone())
63    }
64
65    /// Create from an [`Entry`]
66    pub fn from_entry(entry: &Entry) -> Result<Self, E2sError> {
67        if entry.entry_type != COMPRESSED_SIGNED_BEACON_BLOCK {
68            return Err(E2sError::Ssz(format!(
69                "Invalid entry type for CompressedSignedBeaconBlock: expected {:02x}{:02x}, got {:02x}{:02x}",
70                COMPRESSED_SIGNED_BEACON_BLOCK[0],
71                COMPRESSED_SIGNED_BEACON_BLOCK[1],
72                entry.entry_type[0],
73                entry.entry_type[1]
74            )));
75        }
76
77        Ok(Self { data: entry.data.clone() })
78    }
79
80    /// Decode the compressed signed beacon block into ssz bytes
81    pub fn decode_to_ssz(&self) -> Result<Vec<u8>, E2sError> {
82        self.decompress()
83    }
84}
85
86impl DecodeCompressedSsz for CompressedSignedBeaconBlock {
87    fn decode<T: Decode>(&self) -> Result<T, E2sError> {
88        let ssz_bytes = self.decompress()?;
89        T::from_ssz_bytes(&ssz_bytes).map_err(|e| {
90            E2sError::Ssz(format!("Failed to decode SSZ data into target type: {e:?}"))
91        })
92    }
93}
94
95/// Compressed beacon state
96///
97/// See also <https://github.com/status-im/nimbus-eth2/blob/stable/docs/e2store.md#compressedbeaconstate>.
98#[derive(Debug, Clone)]
99pub struct CompressedBeaconState {
100    /// Snappy-compressed ssz-encoded `BeaconState`
101    pub data: Vec<u8>,
102}
103
104impl CompressedBeaconState {
105    /// Create a new [`CompressedBeaconState`] from compressed data
106    pub const fn new(data: Vec<u8>) -> Self {
107        Self { data }
108    }
109
110    /// Compress with snappy from ssz-encoded state
111    pub fn from_ssz(ssz_data: &[u8]) -> Result<Self, E2sError> {
112        let mut compressed = Vec::new();
113        {
114            let mut encoder = FrameEncoder::new(&mut compressed);
115
116            Write::write_all(&mut encoder, ssz_data).map_err(|e| {
117                E2sError::SnappyCompression(format!("Failed to compress beacon state: {e}"))
118            })?;
119
120            encoder.flush().map_err(|e| {
121                E2sError::SnappyCompression(format!("Failed to flush encoder: {e}"))
122            })?;
123        }
124        Ok(Self { data: compressed })
125    }
126
127    /// Decompress to get the original ssz-encoded beacon state
128    pub fn decompress(&self) -> Result<Vec<u8>, E2sError> {
129        let mut decoder = FrameDecoder::new(self.data.as_slice());
130        let mut decompressed = Vec::new();
131        Read::read_to_end(&mut decoder, &mut decompressed).map_err(|e| {
132            E2sError::SnappyDecompression(format!("Failed to decompress beacon state: {e}"))
133        })?;
134
135        Ok(decompressed)
136    }
137
138    /// Convert to an [`Entry`]
139    pub fn to_entry(&self) -> Entry {
140        Entry::new(COMPRESSED_BEACON_STATE, self.data.clone())
141    }
142
143    /// Create from an [`Entry`]
144    pub fn from_entry(entry: &Entry) -> Result<Self, E2sError> {
145        if entry.entry_type != COMPRESSED_BEACON_STATE {
146            return Err(E2sError::Ssz(format!(
147                "Invalid entry type for CompressedBeaconState: expected {:02x}{:02x}, got {:02x}{:02x}",
148                COMPRESSED_BEACON_STATE[0],
149                COMPRESSED_BEACON_STATE[1],
150                entry.entry_type[0],
151                entry.entry_type[1]
152            )));
153        }
154
155        Ok(Self { data: entry.data.clone() })
156    }
157
158    /// Decode the compressed beacon state into ssz bytes
159    pub fn decode_to_ssz(&self) -> Result<Vec<u8>, E2sError> {
160        self.decompress()
161    }
162}
163
164impl DecodeCompressedSsz for CompressedBeaconState {
165    fn decode<T: Decode>(&self) -> Result<T, E2sError> {
166        let ssz_bytes = self.decompress()?;
167        T::from_ssz_bytes(&ssz_bytes).map_err(|e| {
168            E2sError::Ssz(format!("Failed to decode SSZ data into target type: {e:?}"))
169        })
170    }
171}
172
173#[cfg(test)]
174mod tests {
175    use super::*;
176
177    #[test]
178    fn test_signed_beacon_block_compression_roundtrip() {
179        let ssz_data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
180
181        let compressed_block = CompressedSignedBeaconBlock::from_ssz(&ssz_data).unwrap();
182        let decompressed = compressed_block.decompress().unwrap();
183
184        assert_eq!(decompressed, ssz_data);
185    }
186
187    #[test]
188    fn test_beacon_state_compression_roundtrip() {
189        let ssz_data = vec![10, 9, 8, 7, 6, 5, 4, 3, 2, 1];
190
191        let compressed_state = CompressedBeaconState::from_ssz(&ssz_data).unwrap();
192        let decompressed = compressed_state.decompress().unwrap();
193
194        assert_eq!(decompressed, ssz_data);
195    }
196
197    #[test]
198    fn test_entry_conversion_signed_beacon_block() {
199        let ssz_data = vec![1, 2, 3, 4, 5];
200        let compressed_block = CompressedSignedBeaconBlock::from_ssz(&ssz_data).unwrap();
201
202        let entry = compressed_block.to_entry();
203        assert_eq!(entry.entry_type, COMPRESSED_SIGNED_BEACON_BLOCK);
204
205        let recovered = CompressedSignedBeaconBlock::from_entry(&entry).unwrap();
206        let recovered_ssz = recovered.decode_to_ssz().unwrap();
207
208        assert_eq!(recovered_ssz, ssz_data);
209    }
210
211    #[test]
212    fn test_entry_conversion_beacon_state() {
213        let ssz_data = vec![5, 4, 3, 2, 1];
214        let compressed_state = CompressedBeaconState::from_ssz(&ssz_data).unwrap();
215
216        let entry = compressed_state.to_entry();
217        assert_eq!(entry.entry_type, COMPRESSED_BEACON_STATE);
218
219        let recovered = CompressedBeaconState::from_entry(&entry).unwrap();
220        let recovered_ssz = recovered.decode_to_ssz().unwrap();
221
222        assert_eq!(recovered_ssz, ssz_data);
223    }
224
225    #[test]
226    fn test_invalid_entry_type() {
227        let invalid_entry = Entry::new([0xFF, 0xFF], vec![1, 2, 3]);
228
229        let result = CompressedSignedBeaconBlock::from_entry(&invalid_entry);
230        assert!(result.is_err());
231
232        let result = CompressedBeaconState::from_entry(&invalid_entry);
233        assert!(result.is_err());
234    }
235}