alloy_eips/eip7594/
sidecar.rs

1use crate::{
2    eip4844::{
3        Blob, BlobAndProofV2, BlobTransactionSidecar, Bytes48, BYTES_PER_BLOB,
4        BYTES_PER_COMMITMENT, BYTES_PER_PROOF,
5    },
6    eip7594::{CELLS_PER_EXT_BLOB, EIP_7594_WRAPPER_VERSION},
7};
8use alloc::{boxed::Box, vec::Vec};
9use alloy_primitives::B256;
10use alloy_rlp::{BufMut, Decodable, Encodable, Header};
11
12use super::{Decodable7594, Encodable7594};
13#[cfg(feature = "kzg")]
14use crate::eip4844::BlobTransactionValidationError;
15use crate::eip4844::VersionedHashIter;
16
17/// This represents a set of blobs, and its corresponding commitments and proofs.
18/// Proof type depends on the sidecar variant.
19///
20/// This type encodes and decodes the fields without an rlp header.
21#[derive(Clone, PartialEq, Eq, Hash, Debug, derive_more::From)]
22#[cfg_attr(feature = "serde", derive(serde::Serialize))]
23#[cfg_attr(feature = "serde", serde(untagged))]
24#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
25pub enum BlobTransactionSidecarVariant {
26    /// EIP-4844 style blob transaction sidecar.
27    Eip4844(BlobTransactionSidecar),
28    /// EIP-7594 style blob transaction sidecar with cell proofs.
29    Eip7594(BlobTransactionSidecarEip7594),
30}
31
32impl BlobTransactionSidecarVariant {
33    /// Returns true if this is a [`BlobTransactionSidecarVariant::Eip4844`].
34    pub const fn is_eip4844(&self) -> bool {
35        matches!(self, Self::Eip4844(_))
36    }
37
38    /// Returns true if this is a [`BlobTransactionSidecarVariant::Eip7594`].
39    pub const fn is_eip7594(&self) -> bool {
40        matches!(self, Self::Eip7594(_))
41    }
42
43    /// Returns the EIP-4844 sidecar if it is [`Self::Eip4844`].
44    pub const fn as_eip4844(&self) -> Option<&BlobTransactionSidecar> {
45        match self {
46            Self::Eip4844(sidecar) => Some(sidecar),
47            _ => None,
48        }
49    }
50
51    /// Returns the EIP-7594 sidecar if it is [`Self::Eip7594`].
52    pub const fn as_eip7594(&self) -> Option<&BlobTransactionSidecarEip7594> {
53        match self {
54            Self::Eip7594(sidecar) => Some(sidecar),
55            _ => None,
56        }
57    }
58
59    /// Converts into EIP-4844 sidecar if it is [`Self::Eip4844`].
60    pub fn into_eip4844(self) -> Option<BlobTransactionSidecar> {
61        match self {
62            Self::Eip4844(sidecar) => Some(sidecar),
63            _ => None,
64        }
65    }
66
67    /// Converts the EIP-7594 sidecar if it is [`Self::Eip7594`].
68    pub fn into_eip7594(self) -> Option<BlobTransactionSidecarEip7594> {
69        match self {
70            Self::Eip7594(sidecar) => Some(sidecar),
71            _ => None,
72        }
73    }
74
75    /// Calculates a size heuristic for the in-memory size of the [BlobTransactionSidecarVariant].
76    #[inline]
77    pub fn size(&self) -> usize {
78        match self {
79            Self::Eip4844(sidecar) => sidecar.size(),
80            Self::Eip7594(sidecar) => sidecar.size(),
81        }
82    }
83
84    /// Verifies that the sidecar is valid. See relevant methods for each variant for more info.
85    #[cfg(feature = "kzg")]
86    pub fn validate(
87        &self,
88        blob_versioned_hashes: &[B256],
89        proof_settings: &c_kzg::KzgSettings,
90    ) -> Result<(), BlobTransactionValidationError> {
91        match self {
92            Self::Eip4844(sidecar) => sidecar.validate(blob_versioned_hashes, proof_settings),
93            Self::Eip7594(sidecar) => sidecar.validate(blob_versioned_hashes, proof_settings),
94        }
95    }
96
97    /// Returns the commitments of the sidecar.
98    pub fn commitments(&self) -> &[Bytes48] {
99        match self {
100            Self::Eip4844(sidecar) => &sidecar.commitments,
101            Self::Eip7594(sidecar) => &sidecar.commitments,
102        }
103    }
104
105    /// Returns an iterator over the versioned hashes of the commitments.
106    pub fn versioned_hashes(&self) -> VersionedHashIter<'_> {
107        VersionedHashIter::new(self.commitments())
108    }
109
110    /// Outputs the RLP length of the [BlobTransactionSidecarVariant] fields, without a RLP header.
111    #[doc(hidden)]
112    pub fn rlp_encoded_fields_length(&self) -> usize {
113        match self {
114            Self::Eip4844(sidecar) => sidecar.rlp_encoded_fields_length(),
115            Self::Eip7594(sidecar) => sidecar.rlp_encoded_fields_length(),
116        }
117    }
118
119    /// Encodes the inner [BlobTransactionSidecarVariant] fields as RLP bytes, __without__ a RLP
120    /// header.
121    #[inline]
122    #[doc(hidden)]
123    pub fn rlp_encode_fields(&self, out: &mut dyn BufMut) {
124        match self {
125            Self::Eip4844(sidecar) => sidecar.rlp_encode_fields(out),
126            Self::Eip7594(sidecar) => sidecar.rlp_encode_fields(out),
127        }
128    }
129
130    /// RLP decode the fields of a [BlobTransactionSidecarVariant] based on the wrapper version.
131    #[doc(hidden)]
132    pub fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
133        Self::decode_7594(buf)
134    }
135}
136
137impl Encodable for BlobTransactionSidecarVariant {
138    /// Encodes the [BlobTransactionSidecar] fields as RLP bytes, without a RLP header.
139    fn encode(&self, out: &mut dyn BufMut) {
140        match self {
141            Self::Eip4844(sidecar) => sidecar.encode(out),
142            Self::Eip7594(sidecar) => sidecar.encode(out),
143        }
144    }
145
146    fn length(&self) -> usize {
147        match self {
148            Self::Eip4844(sidecar) => sidecar.rlp_encoded_length(),
149            Self::Eip7594(sidecar) => sidecar.rlp_encoded_length(),
150        }
151    }
152}
153
154impl Decodable for BlobTransactionSidecarVariant {
155    /// Decodes the inner [BlobTransactionSidecar] fields from RLP bytes, without a RLP header.
156    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
157        let header = Header::decode(buf)?;
158        if !header.list {
159            return Err(alloy_rlp::Error::UnexpectedString);
160        }
161        if buf.len() < header.payload_length {
162            return Err(alloy_rlp::Error::InputTooShort);
163        }
164        let remaining = buf.len();
165        let this = Self::rlp_decode_fields(buf)?;
166        if buf.len() + header.payload_length != remaining {
167            return Err(alloy_rlp::Error::UnexpectedLength);
168        }
169
170        Ok(this)
171    }
172}
173
174impl Encodable7594 for BlobTransactionSidecarVariant {
175    fn encode_7594_len(&self) -> usize {
176        self.rlp_encoded_fields_length()
177    }
178
179    fn encode_7594(&self, out: &mut dyn BufMut) {
180        self.rlp_encode_fields(out);
181    }
182}
183
184impl Decodable7594 for BlobTransactionSidecarVariant {
185    fn decode_7594(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
186        if buf.first() == Some(&EIP_7594_WRAPPER_VERSION) {
187            Ok(Self::Eip7594(Decodable7594::decode_7594(buf)?))
188        } else {
189            Ok(Self::Eip4844(Decodable7594::decode_7594(buf)?))
190        }
191    }
192}
193
194#[cfg(feature = "serde")]
195impl<'de> serde::Deserialize<'de> for BlobTransactionSidecarVariant {
196    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
197    where
198        D: serde::Deserializer<'de>,
199    {
200        #[derive(serde::Deserialize)]
201        #[serde(field_identifier, rename_all = "camelCase")]
202        enum Field {
203            Blobs,
204            Commitments,
205            Proofs,
206            CellProofs,
207        }
208
209        struct VariantVisitor;
210
211        impl<'de> serde::de::Visitor<'de> for VariantVisitor {
212            type Value = BlobTransactionSidecarVariant;
213
214            fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
215                formatter
216                    .write_str("a valid blob transaction sidecar (EIP-4844 or EIP-7594 variant)")
217            }
218
219            fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
220            where
221                M: serde::de::MapAccess<'de>,
222            {
223                let mut blobs = None;
224                let mut commitments = None;
225                let mut proofs = None;
226                let mut cell_proofs = None;
227
228                while let Some(key) = map.next_key()? {
229                    match key {
230                        Field::Blobs => blobs = Some(map.next_value()?),
231                        Field::Commitments => commitments = Some(map.next_value()?),
232                        Field::Proofs => proofs = Some(map.next_value()?),
233                        Field::CellProofs => cell_proofs = Some(map.next_value()?),
234                    }
235                }
236
237                let blobs = blobs.ok_or_else(|| serde::de::Error::missing_field("blobs"))?;
238                let commitments =
239                    commitments.ok_or_else(|| serde::de::Error::missing_field("commitments"))?;
240
241                match (cell_proofs, proofs) {
242                    (Some(cp), None) => {
243                        Ok(BlobTransactionSidecarVariant::Eip7594(BlobTransactionSidecarEip7594 {
244                            blobs,
245                            commitments,
246                            cell_proofs: cp,
247                        }))
248                    }
249                    (None, Some(pf)) => {
250                        Ok(BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar {
251                            blobs,
252                            commitments,
253                            proofs: pf,
254                        }))
255                    }
256                    (None, None) => {
257                        Err(serde::de::Error::custom("Missing 'cellProofs' or 'proofs'"))
258                    }
259                    (Some(_), Some(_)) => Err(serde::de::Error::custom(
260                        "Both 'cellProofs' and 'proofs' cannot be present",
261                    )),
262                }
263            }
264        }
265
266        const FIELDS: &[&str] = &["blobs", "commitments", "proofs", "cellProofs"];
267        deserializer.deserialize_struct("BlobTransactionSidecarVariant", FIELDS, VariantVisitor)
268    }
269}
270
271/// This represents a set of blobs, and its corresponding commitments and cell proofs.
272///
273/// This type encodes and decodes the fields without an rlp header.
274#[derive(Clone, Default, PartialEq, Eq, Hash)]
275#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
276#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
277#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
278pub struct BlobTransactionSidecarEip7594 {
279    /// The blob data.
280    #[cfg_attr(
281        all(debug_assertions, feature = "serde"),
282        serde(deserialize_with = "crate::eip4844::deserialize_blobs")
283    )]
284    pub blobs: Vec<Blob>,
285    /// The blob commitments.
286    pub commitments: Vec<Bytes48>,
287    /// List of cell proofs for all blobs in the sidecar, including the proofs for the extension
288    /// indices, for a total of `CELLS_PER_EXT_BLOB` proofs per blob (`CELLS_PER_EXT_BLOB` is the
289    /// number of cells for an extended blob, defined in
290    /// [the consensus specs](https://github.com/ethereum/consensus-specs/tree/9d377fd53d029536e57cfda1a4d2c700c59f86bf/specs/fulu/polynomial-commitments-sampling.md#cells))
291    pub cell_proofs: Vec<Bytes48>,
292}
293
294impl core::fmt::Debug for BlobTransactionSidecarEip7594 {
295    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
296        f.debug_struct("BlobTransactionSidecarEip7594")
297            .field("blobs", &self.blobs.len())
298            .field("commitments", &self.commitments)
299            .field("cell_proofs", &self.cell_proofs)
300            .finish()
301    }
302}
303
304impl BlobTransactionSidecarEip7594 {
305    /// Constructs a new [BlobTransactionSidecarEip7594] from a set of blobs, commitments, and
306    /// cell proofs.
307    pub const fn new(
308        blobs: Vec<Blob>,
309        commitments: Vec<Bytes48>,
310        cell_proofs: Vec<Bytes48>,
311    ) -> Self {
312        Self { blobs, commitments, cell_proofs }
313    }
314
315    /// Calculates a size heuristic for the in-memory size of the [BlobTransactionSidecarEip7594].
316    #[inline]
317    pub fn size(&self) -> usize {
318        self.blobs.len() * BYTES_PER_BLOB + // blobs
319               self.commitments.len() * BYTES_PER_COMMITMENT + // commitments
320               self.cell_proofs.len() * BYTES_PER_PROOF // proofs
321    }
322
323    /// Verifies that the versioned hashes are valid for this sidecar's blob data, commitments, and
324    /// proofs.
325    ///
326    /// Takes as input the [KzgSettings](c_kzg::KzgSettings), which should contain the parameters
327    /// derived from the KZG trusted setup.
328    ///
329    /// This ensures that the blob transaction payload has the expected number of blob data
330    /// elements, commitments, and proofs. The cells are constructed from each blob and verified
331    /// against the commitments and proofs.
332    ///
333    /// Returns [BlobTransactionValidationError::InvalidProof] if any blob KZG proof in the response
334    /// fails to verify, or if the versioned hashes in the transaction do not match the actual
335    /// commitment versioned hashes.
336    #[cfg(feature = "kzg")]
337    pub fn validate(
338        &self,
339        blob_versioned_hashes: &[B256],
340        proof_settings: &c_kzg::KzgSettings,
341    ) -> Result<(), BlobTransactionValidationError> {
342        // Ensure the versioned hashes and commitments have the same length.
343        if blob_versioned_hashes.len() != self.commitments.len() {
344            return Err(c_kzg::Error::MismatchLength(format!(
345                "There are {} versioned commitment hashes and {} commitments",
346                blob_versioned_hashes.len(),
347                self.commitments.len()
348            ))
349            .into());
350        }
351
352        let blobs_len = self.blobs.len();
353        let expected_cell_proofs_len = blobs_len * CELLS_PER_EXT_BLOB;
354        if self.cell_proofs.len() != expected_cell_proofs_len {
355            return Err(c_kzg::Error::MismatchLength(format!(
356                "There are {} cell proofs and {} blobs. Expected {} cell proofs.",
357                self.cell_proofs.len(),
358                blobs_len,
359                expected_cell_proofs_len
360            ))
361            .into());
362        }
363
364        // calculate versioned hashes by zipping & iterating
365        for (versioned_hash, commitment) in
366            blob_versioned_hashes.iter().zip(self.commitments.iter())
367        {
368            // calculate & verify versioned hash
369            let calculated_versioned_hash =
370                crate::eip4844::kzg_to_versioned_hash(commitment.as_slice());
371            if *versioned_hash != calculated_versioned_hash {
372                return Err(BlobTransactionValidationError::WrongVersionedHash {
373                    have: *versioned_hash,
374                    expected: calculated_versioned_hash,
375                });
376            }
377        }
378
379        // Repeat cell ranges for each blob.
380        let cell_indices =
381            Vec::from_iter((0..blobs_len).flat_map(|_| 0..CELLS_PER_EXT_BLOB as u64));
382
383        // Repeat commitments for each cell.
384        let mut commitments = Vec::with_capacity(blobs_len * CELLS_PER_EXT_BLOB);
385        for commitment in &self.commitments {
386            commitments.extend(core::iter::repeat_n(*commitment, CELLS_PER_EXT_BLOB));
387        }
388
389        // SAFETY: ALL types have the same size
390        let res = unsafe {
391            let mut cells = Vec::with_capacity(blobs_len * CELLS_PER_EXT_BLOB);
392            for blob in &self.blobs {
393                let blob = core::mem::transmute::<&Blob, &c_kzg::Blob>(blob);
394                cells.extend(proof_settings.compute_cells(blob)?.into_iter());
395            }
396
397            proof_settings.verify_cell_kzg_proof_batch(
398                // commitments
399                core::mem::transmute::<&[Bytes48], &[c_kzg::Bytes48]>(&commitments),
400                // cell indices
401                &cell_indices,
402                // cells
403                &cells,
404                // proofs
405                core::mem::transmute::<&[Bytes48], &[c_kzg::Bytes48]>(self.cell_proofs.as_slice()),
406            )?
407        };
408
409        res.then_some(()).ok_or(BlobTransactionValidationError::InvalidProof)
410    }
411
412    /// Returns an iterator over the versioned hashes of the commitments.
413    pub fn versioned_hashes(&self) -> VersionedHashIter<'_> {
414        VersionedHashIter::new(&self.commitments)
415    }
416
417    /// Matches versioned hashes and returns an iterator of (index, [`BlobAndProofV2`]) pairs
418    /// where index is the position in `versioned_hashes` that matched the versioned hash in the
419    /// sidecar.
420    ///
421    /// This is used for the `engine_getBlobsV2` RPC endpoint of the engine API
422    pub fn match_versioned_hashes<'a>(
423        &'a self,
424        versioned_hashes: &'a [B256],
425    ) -> impl Iterator<Item = (usize, BlobAndProofV2)> + 'a {
426        self.versioned_hashes().enumerate().flat_map(move |(i, blob_versioned_hash)| {
427            versioned_hashes.iter().enumerate().filter_map(move |(j, target_hash)| {
428                if blob_versioned_hash == *target_hash {
429                    let maybe_blob = self.blobs.get(i);
430                    let proof_range = i * CELLS_PER_EXT_BLOB..(i + 1) * CELLS_PER_EXT_BLOB;
431                    let maybe_proofs = Some(&self.cell_proofs[proof_range])
432                        .filter(|proofs| proofs.len() == CELLS_PER_EXT_BLOB);
433                    if let Some((blob, proofs)) = maybe_blob.copied().zip(maybe_proofs) {
434                        return Some((
435                            j,
436                            BlobAndProofV2 { blob: Box::new(blob), proofs: proofs.to_vec() },
437                        ));
438                    }
439                }
440                None
441            })
442        })
443    }
444
445    /// Outputs the RLP length of [BlobTransactionSidecarEip7594] fields without a RLP header.
446    #[doc(hidden)]
447    pub fn rlp_encoded_fields_length(&self) -> usize {
448        // wrapper version + blobs + commitments + cell proofs
449        1 + self.blobs.length() + self.commitments.length() + self.cell_proofs.length()
450    }
451
452    /// Encodes the inner [BlobTransactionSidecarEip7594] fields as RLP bytes, __without__ a
453    /// RLP header.
454    ///
455    /// This encodes the fields in the following order:
456    /// - `wrapper_version`
457    /// - `blobs`
458    /// - `commitments`
459    /// - `cell_proofs`
460    #[inline]
461    #[doc(hidden)]
462    pub fn rlp_encode_fields(&self, out: &mut dyn BufMut) {
463        // Put version byte.
464        out.put_u8(EIP_7594_WRAPPER_VERSION);
465        // Encode the blobs, commitments, and cell proofs
466        self.blobs.encode(out);
467        self.commitments.encode(out);
468        self.cell_proofs.encode(out);
469    }
470
471    /// Creates an RLP header for the [BlobTransactionSidecarEip7594].
472    fn rlp_header(&self) -> Header {
473        Header { list: true, payload_length: self.rlp_encoded_fields_length() }
474    }
475
476    /// Calculates the length of the [BlobTransactionSidecarEip7594] when encoded as
477    /// RLP.
478    pub fn rlp_encoded_length(&self) -> usize {
479        self.rlp_header().length() + self.rlp_encoded_fields_length()
480    }
481
482    /// Encodes the [BlobTransactionSidecarEip7594] as RLP bytes.
483    pub fn rlp_encode(&self, out: &mut dyn BufMut) {
484        self.rlp_header().encode(out);
485        self.rlp_encode_fields(out);
486    }
487
488    /// RLP decode the fields of a [BlobTransactionSidecarEip7594].
489    #[doc(hidden)]
490    pub fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
491        Ok(Self {
492            blobs: Decodable::decode(buf)?,
493            commitments: Decodable::decode(buf)?,
494            cell_proofs: Decodable::decode(buf)?,
495        })
496    }
497
498    /// Decodes the [BlobTransactionSidecarEip7594] from RLP bytes.
499    pub fn rlp_decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
500        let header = Header::decode(buf)?;
501        if !header.list {
502            return Err(alloy_rlp::Error::UnexpectedString);
503        }
504        if buf.len() < header.payload_length {
505            return Err(alloy_rlp::Error::InputTooShort);
506        }
507        let remaining = buf.len();
508
509        let this = Self::decode_7594(buf)?;
510        if buf.len() + header.payload_length != remaining {
511            return Err(alloy_rlp::Error::UnexpectedLength);
512        }
513
514        Ok(this)
515    }
516}
517
518impl Encodable for BlobTransactionSidecarEip7594 {
519    /// Encodes the inner [BlobTransactionSidecarEip7594] fields as RLP bytes, without a RLP header.
520    fn encode(&self, out: &mut dyn BufMut) {
521        self.rlp_encode(out);
522    }
523
524    fn length(&self) -> usize {
525        self.rlp_encoded_length()
526    }
527}
528
529impl Decodable for BlobTransactionSidecarEip7594 {
530    /// Decodes the inner [BlobTransactionSidecarEip7594] fields from RLP bytes, without a RLP
531    /// header.
532    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
533        Self::rlp_decode(buf)
534    }
535}
536
537impl Encodable7594 for BlobTransactionSidecarEip7594 {
538    fn encode_7594_len(&self) -> usize {
539        self.rlp_encoded_fields_length()
540    }
541
542    fn encode_7594(&self, out: &mut dyn BufMut) {
543        self.rlp_encode_fields(out);
544    }
545}
546
547impl Decodable7594 for BlobTransactionSidecarEip7594 {
548    fn decode_7594(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
549        let wrapper_version: u8 = Decodable::decode(buf)?;
550        if wrapper_version != EIP_7594_WRAPPER_VERSION {
551            return Err(alloy_rlp::Error::Custom("invalid wrapper version"));
552        }
553        Self::rlp_decode_fields(buf)
554    }
555}
556
557#[cfg(test)]
558mod tests {
559    use super::*;
560
561    #[test]
562    fn sidecar_variant_rlp_roundtrip() {
563        let mut encoded = Vec::new();
564
565        // 4844
566        let empty_sidecar_4844 =
567            BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar::default());
568        empty_sidecar_4844.encode(&mut encoded);
569        assert_eq!(
570            empty_sidecar_4844,
571            BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap()
572        );
573
574        let sidecar_4844 = BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar::new(
575            vec![Blob::default()],
576            vec![Bytes48::ZERO],
577            vec![Bytes48::ZERO],
578        ));
579        encoded.clear();
580        sidecar_4844.encode(&mut encoded);
581        assert_eq!(sidecar_4844, BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap());
582
583        // 7594
584        let empty_sidecar_7594 =
585            BlobTransactionSidecarVariant::Eip7594(BlobTransactionSidecarEip7594::default());
586        encoded.clear();
587        empty_sidecar_7594.encode(&mut encoded);
588        assert_eq!(
589            empty_sidecar_7594,
590            BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap()
591        );
592
593        let sidecar_7594 =
594            BlobTransactionSidecarVariant::Eip7594(BlobTransactionSidecarEip7594::new(
595                vec![Blob::default()],
596                vec![Bytes48::ZERO],
597                core::iter::repeat_n(Bytes48::ZERO, CELLS_PER_EXT_BLOB).collect(),
598            ));
599        encoded.clear();
600        sidecar_7594.encode(&mut encoded);
601        assert_eq!(sidecar_7594, BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap());
602    }
603
604    #[test]
605    #[cfg(feature = "serde")]
606    fn sidecar_variant_json_deserialize_sanity() {
607        let eip4844 = BlobTransactionSidecar::default();
608        let json = serde_json::to_string(&eip4844).unwrap();
609        let variant: BlobTransactionSidecarVariant = serde_json::from_str(&json).unwrap();
610        assert!(variant.is_eip4844());
611        let jsonvariant = serde_json::to_string(&variant).unwrap();
612        assert_eq!(json, jsonvariant);
613
614        let eip7594 = BlobTransactionSidecarEip7594::default();
615        let json = serde_json::to_string(&eip7594).unwrap();
616        let variant: BlobTransactionSidecarVariant = serde_json::from_str(&json).unwrap();
617        assert!(variant.is_eip7594());
618        let jsonvariant = serde_json::to_string(&variant).unwrap();
619        assert_eq!(json, jsonvariant);
620    }
621
622    #[test]
623    fn rlp_7594_roundtrip() {
624        let mut encoded = Vec::new();
625
626        let sidecar_4844 = BlobTransactionSidecar::default();
627        sidecar_4844.encode_7594(&mut encoded);
628        assert_eq!(sidecar_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
629
630        let sidecar_variant_4844 = BlobTransactionSidecarVariant::Eip4844(sidecar_4844);
631        assert_eq!(sidecar_variant_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
632        encoded.clear();
633        sidecar_variant_4844.encode_7594(&mut encoded);
634        assert_eq!(sidecar_variant_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
635
636        let sidecar_7594 = BlobTransactionSidecarEip7594::default();
637        encoded.clear();
638        sidecar_7594.encode_7594(&mut encoded);
639        assert_eq!(sidecar_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
640
641        let sidecar_variant_7594 = BlobTransactionSidecarVariant::Eip7594(sidecar_7594);
642        assert_eq!(sidecar_variant_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
643        encoded.clear();
644        sidecar_variant_7594.encode_7594(&mut encoded);
645        assert_eq!(sidecar_variant_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
646    }
647}