Skip to main content

alloy_eips/eip4844/
engine.rs

1//! Misc types related to the 4844
2
3use crate::{
4    eip4844::{Blob, Bytes48},
5    eip7594::{Cell, CELLS_PER_EXT_BLOB},
6};
7use alloc::{boxed::Box, vec::Vec};
8
9/// Blob type returned in responses to `engine_getBlobsV1`: <https://github.com/ethereum/execution-apis/pull/559>
10#[derive(Debug, Clone, PartialEq, Eq)]
11#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
12pub struct BlobAndProofV1 {
13    /// The blob data.
14    pub blob: Box<Blob>,
15    /// The KZG proof for the blob.
16    pub proof: Bytes48,
17}
18
19#[cfg(feature = "ssz")]
20impl ssz::Encode for BlobAndProofV1 {
21    fn is_ssz_fixed_len() -> bool {
22        true
23    }
24
25    fn ssz_fixed_len() -> usize {
26        <Blob as ssz::Encode>::ssz_fixed_len() + <Bytes48 as ssz::Encode>::ssz_fixed_len()
27    }
28
29    fn ssz_bytes_len(&self) -> usize {
30        Self::ssz_fixed_len()
31    }
32
33    fn ssz_append(&self, buf: &mut Vec<u8>) {
34        ssz::Encode::ssz_append(self.blob.as_ref(), buf);
35        ssz::Encode::ssz_append(&self.proof, buf);
36    }
37}
38
39#[cfg(feature = "ssz")]
40impl ssz::Decode for BlobAndProofV1 {
41    fn is_ssz_fixed_len() -> bool {
42        true
43    }
44
45    fn ssz_fixed_len() -> usize {
46        <Blob as ssz::Decode>::ssz_fixed_len() + <Bytes48 as ssz::Decode>::ssz_fixed_len()
47    }
48
49    fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, ssz::DecodeError> {
50        let mut builder = ssz::SszDecoderBuilder::new(bytes);
51        builder.register_type::<Blob>()?;
52        builder.register_type::<Bytes48>()?;
53
54        let mut decoder = builder.build()?;
55        Ok(Self { blob: Box::new(decoder.decode_next()?), proof: decoder.decode_next()? })
56    }
57}
58
59/// Blob type returned in responses to `engine_getBlobsV2`: <https://github.com/ethereum/execution-apis/pull/630>
60#[derive(Debug, Clone, PartialEq, Eq)]
61#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
62#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
63pub struct BlobAndProofV2 {
64    /// The blob data.
65    pub blob: Box<Blob>,
66    /// The cell proofs for the blob.
67    pub proofs: Vec<Bytes48>,
68}
69
70#[cfg(feature = "ssz")]
71impl ssz::Encode for BlobAndProofV2 {
72    fn is_ssz_fixed_len() -> bool {
73        false
74    }
75
76    fn ssz_bytes_len(&self) -> usize {
77        <Blob as ssz::Encode>::ssz_fixed_len()
78            + <Vec<Bytes48> as ssz::Encode>::ssz_fixed_len()
79            + ssz::Encode::ssz_bytes_len(&self.proofs)
80    }
81
82    fn ssz_append(&self, buf: &mut Vec<u8>) {
83        let offset =
84            <Blob as ssz::Encode>::ssz_fixed_len() + <Vec<Bytes48> as ssz::Encode>::ssz_fixed_len();
85        let mut encoder = ssz::SszEncoder::container(buf, offset);
86        encoder.append(self.blob.as_ref());
87        encoder.append(&self.proofs);
88        encoder.finalize();
89    }
90}
91
92#[cfg(feature = "ssz")]
93impl ssz::Decode for BlobAndProofV2 {
94    fn is_ssz_fixed_len() -> bool {
95        false
96    }
97
98    fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, ssz::DecodeError> {
99        let mut builder = ssz::SszDecoderBuilder::new(bytes);
100        builder.register_type::<Blob>()?;
101        builder.register_type::<Vec<Bytes48>>()?;
102
103        let mut decoder = builder.build()?;
104        let blob = decoder.decode_next()?;
105        let proofs: Vec<Bytes48> = decoder.decode_next()?;
106        if proofs.len() > crate::eip7594::CELLS_PER_EXT_BLOB {
107            return Err(ssz::DecodeError::BytesInvalid(format!(
108                "Invalid BlobAndProofV2: expected at most {} proofs, got {}",
109                crate::eip7594::CELLS_PER_EXT_BLOB,
110                proofs.len()
111            )));
112        }
113
114        Ok(Self { blob: Box::new(blob), proofs })
115    }
116}
117
118/// Blob cells type returned in responses to `engine_getBlobsV4`:
119/// <https://github.com/ethereum/execution-apis/pull/774>
120#[derive(Debug, Clone, Default, PartialEq, Eq)]
121#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
122pub struct BlobCellsAndProofsV1 {
123    /// The requested blob cells.
124    pub blob_cells: Vec<Option<Cell>>,
125    /// The KZG proofs for the requested blob cells.
126    pub proofs: Vec<Option<Bytes48>>,
127}
128
129#[cfg(feature = "ssz")]
130impl ssz::Encode for BlobCellsAndProofsV1 {
131    fn is_ssz_fixed_len() -> bool {
132        false
133    }
134
135    fn ssz_bytes_len(&self) -> usize {
136        <Vec<Option<Cell>> as ssz::Encode>::ssz_fixed_len()
137            + <Vec<Option<Bytes48>> as ssz::Encode>::ssz_fixed_len()
138            + ssz::Encode::ssz_bytes_len(&self.blob_cells)
139            + ssz::Encode::ssz_bytes_len(&self.proofs)
140    }
141
142    fn ssz_append(&self, buf: &mut Vec<u8>) {
143        let offset = <Vec<Option<Cell>> as ssz::Encode>::ssz_fixed_len()
144            + <Vec<Option<Bytes48>> as ssz::Encode>::ssz_fixed_len();
145        let mut encoder = ssz::SszEncoder::container(buf, offset);
146        encoder.append(&self.blob_cells);
147        encoder.append(&self.proofs);
148        encoder.finalize();
149    }
150}
151
152#[cfg(feature = "ssz")]
153impl ssz::Decode for BlobCellsAndProofsV1 {
154    fn is_ssz_fixed_len() -> bool {
155        false
156    }
157
158    fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, ssz::DecodeError> {
159        let mut builder = ssz::SszDecoderBuilder::new(bytes);
160        builder.register_type::<Vec<Option<Cell>>>()?;
161        builder.register_type::<Vec<Option<Bytes48>>>()?;
162
163        let mut decoder = builder.build()?;
164        let blob_cells: Vec<Option<Cell>> = decoder.decode_next()?;
165        let proofs: Vec<Option<Bytes48>> = decoder.decode_next()?;
166
167        if blob_cells.len() > CELLS_PER_EXT_BLOB {
168            return Err(ssz::DecodeError::BytesInvalid(format!(
169                "Invalid BlobCellsAndProofsV1: expected at most {} blob cells, got {}",
170                CELLS_PER_EXT_BLOB,
171                blob_cells.len()
172            )));
173        }
174
175        if blob_cells.len() != proofs.len() {
176            return Err(ssz::DecodeError::BytesInvalid(format!(
177                "Invalid BlobCellsAndProofsV1: blob_cells length {} does not match proofs length {}",
178                blob_cells.len(),
179                proofs.len()
180            )));
181        }
182
183        if blob_cells.iter().zip(&proofs).any(|(cell, proof)| cell.is_some() != proof.is_some()) {
184            return Err(ssz::DecodeError::BytesInvalid(
185                "Invalid BlobCellsAndProofsV1: blob_cells and proofs must have matching null positions".into(),
186            ));
187        }
188
189        Ok(Self { blob_cells, proofs })
190    }
191}
192
193#[cfg(all(test, feature = "ssz"))]
194mod tests {
195    use super::*;
196    use crate::eip4844::BYTES_PER_BLOB;
197
198    #[test]
199    fn ssz_blob_and_proof_v1_roundtrip() {
200        let blob_and_proof = BlobAndProofV1 {
201            blob: Box::new(Blob::repeat_byte(0x42)),
202            proof: Bytes48::repeat_byte(0x24),
203        };
204
205        let encoded = ssz::Encode::as_ssz_bytes(&blob_and_proof);
206        assert_eq!(encoded.len(), BYTES_PER_BLOB + 48);
207        assert_eq!(&encoded[..BYTES_PER_BLOB], blob_and_proof.blob.as_slice());
208        assert_eq!(&encoded[BYTES_PER_BLOB..], blob_and_proof.proof.as_slice());
209
210        let decoded = <BlobAndProofV1 as ssz::Decode>::from_ssz_bytes(&encoded).unwrap();
211        assert_eq!(decoded, blob_and_proof);
212    }
213
214    #[test]
215    fn ssz_blob_and_proof_v2_roundtrip() {
216        let proofs =
217            (0..CELLS_PER_EXT_BLOB).map(|i| Bytes48::repeat_byte(i as u8)).collect::<Vec<_>>();
218        let blob_and_proof = BlobAndProofV2 { blob: Box::new(Blob::repeat_byte(0x42)), proofs };
219
220        let encoded = ssz::Encode::as_ssz_bytes(&blob_and_proof);
221        let expected_offset = BYTES_PER_BLOB + ssz::BYTES_PER_LENGTH_OFFSET;
222        assert_eq!(encoded.len(), expected_offset + CELLS_PER_EXT_BLOB * 48);
223        assert_eq!(
224            u32::from_le_bytes(encoded[BYTES_PER_BLOB..expected_offset].try_into().unwrap())
225                as usize,
226            expected_offset
227        );
228        assert_eq!(&encoded[..BYTES_PER_BLOB], blob_and_proof.blob.as_slice());
229
230        let mut proof_chunks = encoded[expected_offset..].chunks_exact(48);
231        for (proof, chunk) in blob_and_proof.proofs.iter().zip(&mut proof_chunks) {
232            assert_eq!(chunk, proof.as_slice());
233        }
234        assert!(proof_chunks.remainder().is_empty());
235
236        let decoded = <BlobAndProofV2 as ssz::Decode>::from_ssz_bytes(&encoded).unwrap();
237        assert_eq!(decoded, blob_and_proof);
238    }
239
240    #[test]
241    fn ssz_blob_and_proof_v2_rejects_too_many_proofs() {
242        let blob_and_proof = BlobAndProofV2 {
243            blob: Box::new(Blob::repeat_byte(0x42)),
244            proofs: vec![Bytes48::ZERO; CELLS_PER_EXT_BLOB + 1],
245        };
246        let encoded = ssz::Encode::as_ssz_bytes(&blob_and_proof);
247
248        let err = <BlobAndProofV2 as ssz::Decode>::from_ssz_bytes(&encoded).unwrap_err();
249        assert!(
250            matches!(err, ssz::DecodeError::BytesInvalid(message) if message.contains("BlobAndProofV2"))
251        );
252    }
253
254    #[test]
255    fn ssz_blob_cells_and_proofs_v1_roundtrip() {
256        let blob_cells = vec![
257            Some(Cell::repeat_byte(0x01)),
258            None,
259            Some(Cell::repeat_byte(0x03)),
260            Some(Cell::repeat_byte(0x04)),
261        ];
262        let proofs = vec![
263            Some(Bytes48::repeat_byte(0x11)),
264            None,
265            Some(Bytes48::repeat_byte(0x33)),
266            Some(Bytes48::repeat_byte(0x44)),
267        ];
268        let blob_cells_and_proofs = BlobCellsAndProofsV1 { blob_cells, proofs };
269
270        let encoded = ssz::Encode::as_ssz_bytes(&blob_cells_and_proofs);
271        let decoded = <BlobCellsAndProofsV1 as ssz::Decode>::from_ssz_bytes(&encoded).unwrap();
272        assert_eq!(decoded, blob_cells_and_proofs);
273    }
274
275    #[test]
276    fn ssz_blob_cells_and_proofs_v1_rejects_too_many_cells() {
277        let blob_cells_and_proofs = BlobCellsAndProofsV1 {
278            blob_cells: vec![Some(Cell::ZERO); CELLS_PER_EXT_BLOB + 1],
279            proofs: vec![Some(Bytes48::ZERO); CELLS_PER_EXT_BLOB + 1],
280        };
281        let encoded = ssz::Encode::as_ssz_bytes(&blob_cells_and_proofs);
282
283        let err = <BlobCellsAndProofsV1 as ssz::Decode>::from_ssz_bytes(&encoded).unwrap_err();
284        assert!(
285            matches!(err, ssz::DecodeError::BytesInvalid(message) if message.contains("expected at most"))
286        );
287    }
288
289    #[test]
290    fn ssz_blob_cells_and_proofs_v1_rejects_mismatched_lengths() {
291        let blob_cells_and_proofs = BlobCellsAndProofsV1 {
292            blob_cells: vec![Some(Cell::ZERO)],
293            proofs: vec![Some(Bytes48::ZERO), Some(Bytes48::ZERO)],
294        };
295        let encoded = ssz::Encode::as_ssz_bytes(&blob_cells_and_proofs);
296
297        let err = <BlobCellsAndProofsV1 as ssz::Decode>::from_ssz_bytes(&encoded).unwrap_err();
298        assert!(
299            matches!(err, ssz::DecodeError::BytesInvalid(message) if message.contains("does not match"))
300        );
301    }
302
303    #[test]
304    fn ssz_blob_cells_and_proofs_v1_rejects_mismatched_null_positions() {
305        let blob_cells_and_proofs = BlobCellsAndProofsV1 {
306            blob_cells: vec![Some(Cell::ZERO), None],
307            proofs: vec![None, Some(Bytes48::ZERO)],
308        };
309        let encoded = ssz::Encode::as_ssz_bytes(&blob_cells_and_proofs);
310
311        let err = <BlobCellsAndProofsV1 as ssz::Decode>::from_ssz_bytes(&encoded).unwrap_err();
312        assert!(
313            matches!(err, ssz::DecodeError::BytesInvalid(message) if message.contains("matching null positions"))
314        );
315    }
316}
317
318#[cfg(all(test, feature = "serde"))]
319mod serde_tests {
320    use super::*;
321
322    #[test]
323    fn blob_cells_and_proofs_v1_uses_spec_field_name() {
324        let blob_cells_and_proofs = BlobCellsAndProofsV1 {
325            blob_cells: vec![Some(Cell::ZERO), None],
326            proofs: vec![Some(Bytes48::ZERO), None],
327        };
328
329        let json = serde_json::to_string(&blob_cells_and_proofs).unwrap();
330        assert!(json.contains("\"blob_cells\""));
331        assert!(!json.contains("\"blobCells\""));
332    }
333}