reth_era/era/types/
consensus.rs1use crate::e2s::{error::E2sError, types::Entry};
63use snap::{read::FrameDecoder, write::FrameEncoder};
64use std::io::{Read, Write};
65
66const MAX_DECOMPRESSED_SIGNED_BEACON_BLOCK_BYTES: usize = 256 * 1024 * 1024; const MAX_DECOMPRESSED_BEACON_STATE_BYTES: usize = 2 * 1024 * 1024 * 1024; fn decompress_snappy_bounded(
73 compressed: &[u8],
74 max_decompressed_bytes: usize,
75 what: &str,
76) -> Result<Vec<u8>, E2sError> {
77 let mut decoder = FrameDecoder::new(compressed).take(max_decompressed_bytes as u64);
78 let mut decompressed = Vec::new();
79
80 Read::read_to_end(&mut decoder, &mut decompressed)
81 .map_err(|e| E2sError::SnappyDecompression(format!("Failed to decompress {what}: {e}")))?;
82
83 if decompressed.len() >= max_decompressed_bytes {
84 return Err(E2sError::SnappyDecompression(format!(
85 "Failed to decompress {what}: decompressed data exceeded limit of {max_decompressed_bytes} bytes"
86 )));
87 }
88
89 Ok(decompressed)
90}
91
92pub const COMPRESSED_SIGNED_BEACON_BLOCK: [u8; 2] = [0x01, 0x00];
94
95pub const COMPRESSED_BEACON_STATE: [u8; 2] = [0x02, 0x00];
97
98#[derive(Debug, Clone)]
102pub struct CompressedSignedBeaconBlock {
103 pub data: Vec<u8>,
105}
106
107impl CompressedSignedBeaconBlock {
108 pub const fn new(data: Vec<u8>) -> Self {
110 Self { data }
111 }
112
113 pub fn from_ssz(ssz_data: &[u8]) -> Result<Self, E2sError> {
115 let mut compressed = Vec::new();
116 {
117 let mut encoder = FrameEncoder::new(&mut compressed);
118
119 Write::write_all(&mut encoder, ssz_data).map_err(|e| {
120 E2sError::SnappyCompression(format!("Failed to compress signed beacon block: {e}"))
121 })?;
122
123 encoder.flush().map_err(|e| {
124 E2sError::SnappyCompression(format!("Failed to flush encoder: {e}"))
125 })?;
126 }
127 Ok(Self { data: compressed })
128 }
129
130 pub fn decompress(&self) -> Result<Vec<u8>, E2sError> {
132 decompress_snappy_bounded(
133 self.data.as_slice(),
134 MAX_DECOMPRESSED_SIGNED_BEACON_BLOCK_BYTES,
135 "signed beacon block",
136 )
137 }
138
139 pub fn to_entry(&self) -> Entry {
141 Entry::new(COMPRESSED_SIGNED_BEACON_BLOCK, self.data.clone())
142 }
143
144 pub fn from_entry(entry: &Entry) -> Result<Self, E2sError> {
146 if entry.entry_type != COMPRESSED_SIGNED_BEACON_BLOCK {
147 return Err(E2sError::Ssz(format!(
148 "Invalid entry type for CompressedSignedBeaconBlock: expected {:02x}{:02x}, got {:02x}{:02x}",
149 COMPRESSED_SIGNED_BEACON_BLOCK[0],
150 COMPRESSED_SIGNED_BEACON_BLOCK[1],
151 entry.entry_type[0],
152 entry.entry_type[1]
153 )));
154 }
155
156 Ok(Self { data: entry.data.clone() })
157 }
158}
159
160#[derive(Debug, Clone)]
164pub struct CompressedBeaconState {
165 pub data: Vec<u8>,
167}
168
169impl CompressedBeaconState {
170 pub const fn new(data: Vec<u8>) -> Self {
172 Self { data }
173 }
174
175 pub fn from_ssz(ssz_data: &[u8]) -> Result<Self, E2sError> {
177 let mut compressed = Vec::new();
178 {
179 let mut encoder = FrameEncoder::new(&mut compressed);
180
181 Write::write_all(&mut encoder, ssz_data).map_err(|e| {
182 E2sError::SnappyCompression(format!("Failed to compress beacon state: {e}"))
183 })?;
184
185 encoder.flush().map_err(|e| {
186 E2sError::SnappyCompression(format!("Failed to flush encoder: {e}"))
187 })?;
188 }
189 Ok(Self { data: compressed })
190 }
191
192 pub fn decompress(&self) -> Result<Vec<u8>, E2sError> {
194 decompress_snappy_bounded(
195 self.data.as_slice(),
196 MAX_DECOMPRESSED_BEACON_STATE_BYTES,
197 "beacon state",
198 )
199 }
200
201 pub fn to_entry(&self) -> Entry {
203 Entry::new(COMPRESSED_BEACON_STATE, self.data.clone())
204 }
205
206 pub fn from_entry(entry: &Entry) -> Result<Self, E2sError> {
208 if entry.entry_type != COMPRESSED_BEACON_STATE {
209 return Err(E2sError::Ssz(format!(
210 "Invalid entry type for CompressedBeaconState: expected {:02x}{:02x}, got {:02x}{:02x}",
211 COMPRESSED_BEACON_STATE[0],
212 COMPRESSED_BEACON_STATE[1],
213 entry.entry_type[0],
214 entry.entry_type[1]
215 )));
216 }
217
218 Ok(Self { data: entry.data.clone() })
219 }
220}
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225
226 #[test]
227 fn test_signed_beacon_block_compression_roundtrip() {
228 let ssz_data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
229
230 let compressed_block = CompressedSignedBeaconBlock::from_ssz(&ssz_data).unwrap();
231 let decompressed = compressed_block.decompress().unwrap();
232
233 assert_eq!(decompressed, ssz_data);
234 }
235
236 #[test]
237 fn test_beacon_state_compression_roundtrip() {
238 let ssz_data = vec![10, 9, 8, 7, 6, 5, 4, 3, 2, 1];
239
240 let compressed_state = CompressedBeaconState::from_ssz(&ssz_data).unwrap();
241 let decompressed = compressed_state.decompress().unwrap();
242
243 assert_eq!(decompressed, ssz_data);
244 }
245
246 #[test]
247 fn test_entry_conversion_signed_beacon_block() {
248 let ssz_data = vec![1, 2, 3, 4, 5];
249 let compressed_block = CompressedSignedBeaconBlock::from_ssz(&ssz_data).unwrap();
250
251 let entry = compressed_block.to_entry();
252 assert_eq!(entry.entry_type, COMPRESSED_SIGNED_BEACON_BLOCK);
253
254 let recovered = CompressedSignedBeaconBlock::from_entry(&entry).unwrap();
255 let recovered_ssz = recovered.decompress().unwrap();
256
257 assert_eq!(recovered_ssz, ssz_data);
258 }
259
260 #[test]
261 fn test_entry_conversion_beacon_state() {
262 let ssz_data = vec![5, 4, 3, 2, 1];
263 let compressed_state = CompressedBeaconState::from_ssz(&ssz_data).unwrap();
264
265 let entry = compressed_state.to_entry();
266 assert_eq!(entry.entry_type, COMPRESSED_BEACON_STATE);
267
268 let recovered = CompressedBeaconState::from_entry(&entry).unwrap();
269 let recovered_ssz = recovered.decompress().unwrap();
270
271 assert_eq!(recovered_ssz, ssz_data);
272 }
273
274 #[test]
275 fn test_invalid_entry_type() {
276 let invalid_entry = Entry::new([0xFF, 0xFF], vec![1, 2, 3]);
277
278 let result = CompressedSignedBeaconBlock::from_entry(&invalid_entry);
279 assert!(result.is_err());
280
281 let result = CompressedBeaconState::from_entry(&invalid_entry);
282 assert!(result.is_err());
283 }
284
285 #[test]
286 fn test_bounded_decompression_rejects_oversized_output() {
287 let ssz_data = vec![42u8; 1024];
288 let compressed = CompressedBeaconState::from_ssz(&ssz_data).unwrap();
289
290 let err =
291 decompress_snappy_bounded(compressed.data.as_slice(), 100, "beacon state").unwrap_err();
292
293 assert!(format!("{err:?}").contains("exceeded limit"));
294 }
295}