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#[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 Eip4844(BlobTransactionSidecar),
28 Eip7594(BlobTransactionSidecarEip7594),
30}
31
32impl Default for BlobTransactionSidecarVariant {
33 fn default() -> Self {
34 Self::Eip4844(BlobTransactionSidecar::default())
35 }
36}
37
38impl BlobTransactionSidecarVariant {
39 pub const fn is_eip4844(&self) -> bool {
41 matches!(self, Self::Eip4844(_))
42 }
43
44 pub const fn is_eip7594(&self) -> bool {
46 matches!(self, Self::Eip7594(_))
47 }
48
49 pub const fn as_eip4844(&self) -> Option<&BlobTransactionSidecar> {
51 match self {
52 Self::Eip4844(sidecar) => Some(sidecar),
53 _ => None,
54 }
55 }
56
57 pub const fn as_eip7594(&self) -> Option<&BlobTransactionSidecarEip7594> {
59 match self {
60 Self::Eip7594(sidecar) => Some(sidecar),
61 _ => None,
62 }
63 }
64
65 pub fn into_eip4844(self) -> Option<BlobTransactionSidecar> {
67 match self {
68 Self::Eip4844(sidecar) => Some(sidecar),
69 _ => None,
70 }
71 }
72
73 pub fn into_eip7594(self) -> Option<BlobTransactionSidecarEip7594> {
75 match self {
76 Self::Eip7594(sidecar) => Some(sidecar),
77 _ => None,
78 }
79 }
80
81 pub fn blobs(&self) -> &[Blob] {
83 match self {
84 Self::Eip4844(sidecar) => &sidecar.blobs,
85 Self::Eip7594(sidecar) => &sidecar.blobs,
86 }
87 }
88
89 pub fn into_blobs(self) -> Vec<Blob> {
91 match self {
92 Self::Eip4844(sidecar) => sidecar.blobs,
93 Self::Eip7594(sidecar) => sidecar.blobs,
94 }
95 }
96
97 #[inline]
99 pub const fn size(&self) -> usize {
100 match self {
101 Self::Eip4844(sidecar) => sidecar.size(),
102 Self::Eip7594(sidecar) => sidecar.size(),
103 }
104 }
105
106 #[cfg(feature = "kzg")]
134 pub fn try_convert_into_eip7594(self) -> Result<Self, c_kzg::Error> {
135 self.try_convert_into_eip7594_with_settings(
136 crate::eip4844::env_settings::EnvKzgSettings::Default.get(),
137 )
138 }
139
140 #[cfg(feature = "kzg")]
181 pub fn try_convert_into_eip7594_with_settings(
182 self,
183 settings: &c_kzg::KzgSettings,
184 ) -> Result<Self, c_kzg::Error> {
185 match self {
186 Self::Eip4844(legacy) => legacy.try_into_7594(settings).map(Self::Eip7594),
187 sidecar @ Self::Eip7594(_) => Ok(sidecar),
188 }
189 }
190
191 #[cfg(feature = "kzg")]
224 pub fn try_into_eip7594(self) -> Result<BlobTransactionSidecarEip7594, c_kzg::Error> {
225 self.try_into_eip7594_with_settings(
226 crate::eip4844::env_settings::EnvKzgSettings::Default.get(),
227 )
228 }
229
230 #[cfg(feature = "kzg")]
275 pub fn try_into_eip7594_with_settings(
276 self,
277 settings: &c_kzg::KzgSettings,
278 ) -> Result<BlobTransactionSidecarEip7594, c_kzg::Error> {
279 match self {
280 Self::Eip4844(legacy) => legacy.try_into_7594(settings),
281 Self::Eip7594(sidecar) => Ok(sidecar),
282 }
283 }
284
285 #[cfg(feature = "kzg")]
287 pub fn validate(
288 &self,
289 blob_versioned_hashes: &[B256],
290 proof_settings: &c_kzg::KzgSettings,
291 ) -> Result<(), BlobTransactionValidationError> {
292 match self {
293 Self::Eip4844(sidecar) => sidecar.validate(blob_versioned_hashes, proof_settings),
294 Self::Eip7594(sidecar) => sidecar.validate(blob_versioned_hashes, proof_settings),
295 }
296 }
297
298 pub fn commitments(&self) -> &[Bytes48] {
300 match self {
301 Self::Eip4844(sidecar) => &sidecar.commitments,
302 Self::Eip7594(sidecar) => &sidecar.commitments,
303 }
304 }
305
306 pub fn versioned_hashes(&self) -> VersionedHashIter<'_> {
308 VersionedHashIter::new(self.commitments())
309 }
310
311 pub fn versioned_hash_index(&self, hash: &B256) -> Option<usize> {
313 match self {
314 Self::Eip4844(s) => s.versioned_hash_index(hash),
315 Self::Eip7594(s) => s.versioned_hash_index(hash),
316 }
317 }
318
319 pub fn blob_by_versioned_hash(&self, hash: &B256) -> Option<&Blob> {
321 match self {
322 Self::Eip4844(s) => s.blob_by_versioned_hash(hash),
323 Self::Eip7594(s) => s.blob_by_versioned_hash(hash),
324 }
325 }
326
327 #[doc(hidden)]
329 pub fn rlp_encoded_fields_length(&self) -> usize {
330 match self {
331 Self::Eip4844(sidecar) => sidecar.rlp_encoded_fields_length(),
332 Self::Eip7594(sidecar) => sidecar.rlp_encoded_fields_length(),
333 }
334 }
335
336 #[inline]
338 #[doc(hidden)]
339 pub fn rlp_encoded_fields(&self) -> Vec<u8> {
340 let mut buf = Vec::with_capacity(self.rlp_encoded_fields_length());
341 self.rlp_encode_fields(&mut buf);
342 buf
343 }
344
345 #[inline]
348 #[doc(hidden)]
349 pub fn rlp_encode_fields(&self, out: &mut dyn BufMut) {
350 match self {
351 Self::Eip4844(sidecar) => sidecar.rlp_encode_fields(out),
352 Self::Eip7594(sidecar) => sidecar.rlp_encode_fields(out),
353 }
354 }
355
356 #[doc(hidden)]
358 pub fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
359 Self::decode_7594(buf)
360 }
361}
362
363impl Encodable for BlobTransactionSidecarVariant {
364 fn encode(&self, out: &mut dyn BufMut) {
366 match self {
367 Self::Eip4844(sidecar) => sidecar.encode(out),
368 Self::Eip7594(sidecar) => sidecar.encode(out),
369 }
370 }
371
372 fn length(&self) -> usize {
373 match self {
374 Self::Eip4844(sidecar) => sidecar.rlp_encoded_length(),
375 Self::Eip7594(sidecar) => sidecar.rlp_encoded_length(),
376 }
377 }
378}
379
380impl Decodable for BlobTransactionSidecarVariant {
381 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
383 let header = Header::decode(buf)?;
384 if !header.list {
385 return Err(alloy_rlp::Error::UnexpectedString);
386 }
387 if buf.len() < header.payload_length {
388 return Err(alloy_rlp::Error::InputTooShort);
389 }
390 let remaining = buf.len();
391 let this = Self::rlp_decode_fields(buf)?;
392 if buf.len() + header.payload_length != remaining {
393 return Err(alloy_rlp::Error::UnexpectedLength);
394 }
395
396 Ok(this)
397 }
398}
399
400impl Encodable7594 for BlobTransactionSidecarVariant {
401 fn encode_7594_len(&self) -> usize {
402 self.rlp_encoded_fields_length()
403 }
404
405 fn encode_7594(&self, out: &mut dyn BufMut) {
406 self.rlp_encode_fields(out);
407 }
408}
409
410impl Decodable7594 for BlobTransactionSidecarVariant {
411 fn decode_7594(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
412 if buf.first() == Some(&EIP_7594_WRAPPER_VERSION) {
413 Ok(Self::Eip7594(Decodable7594::decode_7594(buf)?))
414 } else {
415 Ok(Self::Eip4844(Decodable7594::decode_7594(buf)?))
416 }
417 }
418}
419
420#[cfg(feature = "kzg")]
421impl TryFrom<BlobTransactionSidecarVariant> for BlobTransactionSidecarEip7594 {
422 type Error = c_kzg::Error;
423
424 fn try_from(value: BlobTransactionSidecarVariant) -> Result<Self, Self::Error> {
425 value.try_into_eip7594()
426 }
427}
428
429#[cfg(feature = "serde")]
430impl<'de> serde::Deserialize<'de> for BlobTransactionSidecarVariant {
431 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
432 where
433 D: serde::Deserializer<'de>,
434 {
435 use core::fmt;
436
437 #[derive(serde::Deserialize, fmt::Debug)]
438 #[serde(field_identifier, rename_all = "camelCase")]
439 enum Field {
440 Blobs,
441 Commitments,
442 Proofs,
443 CellProofs,
444 }
445
446 struct VariantVisitor;
447
448 impl<'de> serde::de::Visitor<'de> for VariantVisitor {
449 type Value = BlobTransactionSidecarVariant;
450
451 fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
452 formatter
453 .write_str("a valid blob transaction sidecar (EIP-4844 or EIP-7594 variant)")
454 }
455
456 fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
457 where
458 M: serde::de::MapAccess<'de>,
459 {
460 let mut blobs = None;
461 let mut commitments = None;
462 let mut proofs = None;
463 let mut cell_proofs = None;
464
465 while let Some(key) = map.next_key()? {
466 match key {
467 Field::Blobs => {
468 blobs = Some(crate::eip4844::deserialize_blobs_map(&mut map)?);
469 }
470 Field::Commitments => commitments = Some(map.next_value()?),
471 Field::Proofs => proofs = Some(map.next_value()?),
472 Field::CellProofs => cell_proofs = Some(map.next_value()?),
473 }
474 }
475
476 let blobs = blobs.ok_or_else(|| serde::de::Error::missing_field("blobs"))?;
477 let commitments =
478 commitments.ok_or_else(|| serde::de::Error::missing_field("commitments"))?;
479
480 match (cell_proofs, proofs) {
481 (Some(cp), None) => {
482 Ok(BlobTransactionSidecarVariant::Eip7594(BlobTransactionSidecarEip7594 {
483 blobs,
484 commitments,
485 cell_proofs: cp,
486 }))
487 }
488 (None, Some(pf)) => {
489 Ok(BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar {
490 blobs,
491 commitments,
492 proofs: pf,
493 }))
494 }
495 (None, None) => {
496 Err(serde::de::Error::custom("Missing 'cellProofs' or 'proofs'"))
497 }
498 (Some(_), Some(_)) => Err(serde::de::Error::custom(
499 "Both 'cellProofs' and 'proofs' cannot be present",
500 )),
501 }
502 }
503 }
504
505 const FIELDS: &[&str] = &["blobs", "commitments", "proofs", "cellProofs"];
506 deserializer.deserialize_struct("BlobTransactionSidecarVariant", FIELDS, VariantVisitor)
507 }
508}
509
510#[derive(Clone, Default, PartialEq, Eq, Hash)]
514#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
515#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
516#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
517pub struct BlobTransactionSidecarEip7594 {
518 #[cfg_attr(feature = "serde", serde(deserialize_with = "crate::eip4844::deserialize_blobs"))]
520 pub blobs: Vec<Blob>,
521 pub commitments: Vec<Bytes48>,
523 pub cell_proofs: Vec<Bytes48>,
528}
529
530impl core::fmt::Debug for BlobTransactionSidecarEip7594 {
531 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
532 f.debug_struct("BlobTransactionSidecarEip7594")
533 .field("blobs", &self.blobs.len())
534 .field("commitments", &self.commitments)
535 .field("cell_proofs", &self.cell_proofs)
536 .finish()
537 }
538}
539
540impl BlobTransactionSidecarEip7594 {
541 pub const fn new(
544 blobs: Vec<Blob>,
545 commitments: Vec<Bytes48>,
546 cell_proofs: Vec<Bytes48>,
547 ) -> Self {
548 Self { blobs, commitments, cell_proofs }
549 }
550
551 #[inline]
553 pub const fn size(&self) -> usize {
554 self.blobs.capacity() * BYTES_PER_BLOB
555 + self.commitments.capacity() * BYTES_PER_COMMITMENT
556 + self.cell_proofs.capacity() * BYTES_PER_PROOF
557 }
558
559 #[cfg(all(feature = "kzg", any(test, feature = "arbitrary")))]
563 pub fn try_from_blobs_hex<I, B>(blobs: I) -> Result<Self, c_kzg::Error>
564 where
565 I: IntoIterator<Item = B>,
566 B: AsRef<str>,
567 {
568 let mut converted = Vec::new();
569 for blob in blobs {
570 converted.push(crate::eip4844::utils::hex_to_blob(blob)?);
571 }
572 Self::try_from_blobs(converted)
573 }
574
575 #[cfg(all(feature = "kzg", any(test, feature = "arbitrary")))]
580 pub fn try_from_blobs_bytes<I, B>(blobs: I) -> Result<Self, c_kzg::Error>
581 where
582 I: IntoIterator<Item = B>,
583 B: AsRef<[u8]>,
584 {
585 let mut converted = Vec::new();
586 for blob in blobs {
587 converted.push(crate::eip4844::utils::bytes_to_blob(blob)?);
588 }
589 Self::try_from_blobs(converted)
590 }
591
592 #[cfg(feature = "kzg")]
595 pub fn try_from_blobs_with_settings(
596 blobs: Vec<Blob>,
597 settings: &c_kzg::KzgSettings,
598 ) -> Result<Self, c_kzg::Error> {
599 let mut commitments = Vec::with_capacity(blobs.len());
600 let mut proofs = Vec::with_capacity(blobs.len());
601 for blob in &blobs {
602 let blob = unsafe { core::mem::transmute::<&Blob, &c_kzg::Blob>(blob) };
604 let commitment = settings.blob_to_kzg_commitment(blob)?;
605 let (_cells, kzg_proofs) = settings.compute_cells_and_kzg_proofs(blob)?;
606
607 unsafe {
609 commitments
610 .push(core::mem::transmute::<c_kzg::Bytes48, Bytes48>(commitment.to_bytes()));
611 for kzg_proof in kzg_proofs.iter() {
612 proofs.push(core::mem::transmute::<c_kzg::Bytes48, Bytes48>(
613 kzg_proof.to_bytes(),
614 ));
615 }
616 }
617 }
618
619 Ok(Self::new(blobs, commitments, proofs))
620 }
621
622 #[cfg(feature = "kzg")]
628 pub fn try_from_blobs(blobs: Vec<Blob>) -> Result<Self, c_kzg::Error> {
629 use crate::eip4844::env_settings::EnvKzgSettings;
630
631 Self::try_from_blobs_with_settings(blobs, EnvKzgSettings::Default.get())
632 }
633
634 #[cfg(feature = "kzg")]
648 pub fn validate(
649 &self,
650 blob_versioned_hashes: &[B256],
651 proof_settings: &c_kzg::KzgSettings,
652 ) -> Result<(), BlobTransactionValidationError> {
653 if blob_versioned_hashes.len() != self.commitments.len() {
655 return Err(c_kzg::Error::MismatchLength(format!(
656 "There are {} versioned commitment hashes and {} commitments",
657 blob_versioned_hashes.len(),
658 self.commitments.len()
659 ))
660 .into());
661 }
662
663 let blobs_len = self.blobs.len();
664 let expected_cell_proofs_len = blobs_len * CELLS_PER_EXT_BLOB;
665 if self.cell_proofs.len() != expected_cell_proofs_len {
666 return Err(c_kzg::Error::MismatchLength(format!(
667 "There are {} cell proofs and {} blobs. Expected {} cell proofs.",
668 self.cell_proofs.len(),
669 blobs_len,
670 expected_cell_proofs_len
671 ))
672 .into());
673 }
674
675 for (versioned_hash, commitment) in
677 blob_versioned_hashes.iter().zip(self.commitments.iter())
678 {
679 let calculated_versioned_hash =
681 crate::eip4844::kzg_to_versioned_hash(commitment.as_slice());
682 if *versioned_hash != calculated_versioned_hash {
683 return Err(BlobTransactionValidationError::WrongVersionedHash {
684 have: *versioned_hash,
685 expected: calculated_versioned_hash,
686 });
687 }
688 }
689
690 let cell_indices =
692 Vec::from_iter((0..blobs_len).flat_map(|_| 0..CELLS_PER_EXT_BLOB as u64));
693
694 let mut commitments = Vec::with_capacity(blobs_len * CELLS_PER_EXT_BLOB);
696 for commitment in &self.commitments {
697 commitments.extend(core::iter::repeat_n(*commitment, CELLS_PER_EXT_BLOB));
698 }
699
700 let res = unsafe {
702 let mut cells = Vec::with_capacity(blobs_len * CELLS_PER_EXT_BLOB);
703 for blob in &self.blobs {
704 let blob = core::mem::transmute::<&Blob, &c_kzg::Blob>(blob);
705 let blob_cells = proof_settings.compute_cells(blob)?;
706 cells.extend_from_slice(blob_cells.as_ref());
707 }
708
709 proof_settings.verify_cell_kzg_proof_batch(
710 core::mem::transmute::<&[Bytes48], &[c_kzg::Bytes48]>(&commitments),
712 &cell_indices,
714 &cells,
716 core::mem::transmute::<&[Bytes48], &[c_kzg::Bytes48]>(self.cell_proofs.as_slice()),
718 )?
719 };
720
721 res.then_some(()).ok_or(BlobTransactionValidationError::InvalidProof)
722 }
723
724 pub fn versioned_hashes(&self) -> VersionedHashIter<'_> {
726 VersionedHashIter::new(&self.commitments)
727 }
728
729 pub fn versioned_hash_index(&self, hash: &B256) -> Option<usize> {
731 self.commitments.iter().position(|commitment| {
732 crate::eip4844::kzg_to_versioned_hash(commitment.as_slice()) == *hash
733 })
734 }
735
736 pub fn blob_by_versioned_hash(&self, hash: &B256) -> Option<&Blob> {
738 self.versioned_hash_index(hash).and_then(|index| self.blobs.get(index))
739 }
740
741 pub fn match_versioned_hashes<'a>(
747 &'a self,
748 versioned_hashes: &'a [B256],
749 ) -> impl Iterator<Item = (usize, BlobAndProofV2)> + 'a {
750 self.versioned_hashes().enumerate().flat_map(move |(i, blob_versioned_hash)| {
751 versioned_hashes.iter().enumerate().filter_map(move |(j, target_hash)| {
752 if blob_versioned_hash == *target_hash {
753 let maybe_blob = self.blobs.get(i);
754 let proof_range = i * CELLS_PER_EXT_BLOB..(i + 1) * CELLS_PER_EXT_BLOB;
755 let maybe_proofs = Some(&self.cell_proofs[proof_range])
756 .filter(|proofs| proofs.len() == CELLS_PER_EXT_BLOB);
757 if let Some((blob, proofs)) = maybe_blob.copied().zip(maybe_proofs) {
758 return Some((
759 j,
760 BlobAndProofV2 { blob: Box::new(blob), proofs: proofs.to_vec() },
761 ));
762 }
763 }
764 None
765 })
766 })
767 }
768
769 #[doc(hidden)]
771 pub fn rlp_encoded_fields_length(&self) -> usize {
772 1 + self.blobs.length() + self.commitments.length() + self.cell_proofs.length()
774 }
775
776 #[inline]
785 #[doc(hidden)]
786 pub fn rlp_encode_fields(&self, out: &mut dyn BufMut) {
787 out.put_u8(EIP_7594_WRAPPER_VERSION);
789 self.blobs.encode(out);
791 self.commitments.encode(out);
792 self.cell_proofs.encode(out);
793 }
794
795 fn rlp_header(&self) -> Header {
797 Header { list: true, payload_length: self.rlp_encoded_fields_length() }
798 }
799
800 pub fn rlp_encoded_length(&self) -> usize {
803 self.rlp_header().length() + self.rlp_encoded_fields_length()
804 }
805
806 pub fn rlp_encode(&self, out: &mut dyn BufMut) {
808 self.rlp_header().encode(out);
809 self.rlp_encode_fields(out);
810 }
811
812 #[doc(hidden)]
814 pub fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
815 Ok(Self {
816 blobs: Decodable::decode(buf)?,
817 commitments: Decodable::decode(buf)?,
818 cell_proofs: Decodable::decode(buf)?,
819 })
820 }
821
822 pub fn rlp_decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
824 let header = Header::decode(buf)?;
825 if !header.list {
826 return Err(alloy_rlp::Error::UnexpectedString);
827 }
828 if buf.len() < header.payload_length {
829 return Err(alloy_rlp::Error::InputTooShort);
830 }
831 let remaining = buf.len();
832
833 let this = Self::decode_7594(buf)?;
834 if buf.len() + header.payload_length != remaining {
835 return Err(alloy_rlp::Error::UnexpectedLength);
836 }
837
838 Ok(this)
839 }
840}
841
842impl Encodable for BlobTransactionSidecarEip7594 {
843 fn encode(&self, out: &mut dyn BufMut) {
845 self.rlp_encode(out);
846 }
847
848 fn length(&self) -> usize {
849 self.rlp_encoded_length()
850 }
851}
852
853impl Decodable for BlobTransactionSidecarEip7594 {
854 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
857 Self::rlp_decode(buf)
858 }
859}
860
861impl Encodable7594 for BlobTransactionSidecarEip7594 {
862 fn encode_7594_len(&self) -> usize {
863 self.rlp_encoded_fields_length()
864 }
865
866 fn encode_7594(&self, out: &mut dyn BufMut) {
867 self.rlp_encode_fields(out);
868 }
869}
870
871impl Decodable7594 for BlobTransactionSidecarEip7594 {
872 fn decode_7594(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
873 let wrapper_version: u8 = Decodable::decode(buf)?;
874 if wrapper_version != EIP_7594_WRAPPER_VERSION {
875 return Err(alloy_rlp::Error::Custom("invalid wrapper version"));
876 }
877 Self::rlp_decode_fields(buf)
878 }
879}
880
881#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
883pub mod serde_bincode_compat {
884 use crate::eip4844::{Blob, Bytes48};
885 use alloc::{borrow::Cow, vec::Vec};
886 use serde::{Deserialize, Deserializer, Serialize, Serializer};
887 use serde_with::{DeserializeAs, SerializeAs};
888
889 #[derive(Debug, Serialize, Deserialize)]
905 pub struct BlobTransactionSidecarVariant<'a> {
906 pub blobs: Cow<'a, Vec<Blob>>,
908 pub commitments: Cow<'a, Vec<Bytes48>>,
910 pub proofs: Option<Cow<'a, Vec<Bytes48>>>,
912 pub cell_proofs: Option<Cow<'a, Vec<Bytes48>>>,
914 }
915
916 impl<'a> From<&'a super::BlobTransactionSidecarVariant> for BlobTransactionSidecarVariant<'a> {
917 fn from(value: &'a super::BlobTransactionSidecarVariant) -> Self {
918 match value {
919 super::BlobTransactionSidecarVariant::Eip4844(sidecar) => Self {
920 blobs: Cow::Borrowed(&sidecar.blobs),
921 commitments: Cow::Borrowed(&sidecar.commitments),
922 proofs: Some(Cow::Borrowed(&sidecar.proofs)),
923 cell_proofs: None,
924 },
925 super::BlobTransactionSidecarVariant::Eip7594(sidecar) => Self {
926 blobs: Cow::Borrowed(&sidecar.blobs),
927 commitments: Cow::Borrowed(&sidecar.commitments),
928 proofs: None,
929 cell_proofs: Some(Cow::Borrowed(&sidecar.cell_proofs)),
930 },
931 }
932 }
933 }
934
935 impl<'a> BlobTransactionSidecarVariant<'a> {
936 fn try_into_inner(self) -> Result<super::BlobTransactionSidecarVariant, &'static str> {
937 match (self.proofs, self.cell_proofs) {
938 (Some(proofs), None) => Ok(super::BlobTransactionSidecarVariant::Eip4844(
939 crate::eip4844::BlobTransactionSidecar {
940 blobs: self.blobs.into_owned(),
941 commitments: self.commitments.into_owned(),
942 proofs: proofs.into_owned(),
943 },
944 )),
945 (None, Some(cell_proofs)) => Ok(super::BlobTransactionSidecarVariant::Eip7594(
946 super::BlobTransactionSidecarEip7594 {
947 blobs: self.blobs.into_owned(),
948 commitments: self.commitments.into_owned(),
949 cell_proofs: cell_proofs.into_owned(),
950 },
951 )),
952 (None, None) => Err("Missing both 'proofs' and 'cell_proofs'"),
953 (Some(_), Some(_)) => Err("Both 'proofs' and 'cell_proofs' cannot be present"),
954 }
955 }
956 }
957
958 impl<'a> From<BlobTransactionSidecarVariant<'a>> for super::BlobTransactionSidecarVariant {
959 fn from(value: BlobTransactionSidecarVariant<'a>) -> Self {
960 value.try_into_inner().expect("Invalid BlobTransactionSidecarVariant")
961 }
962 }
963
964 impl SerializeAs<super::BlobTransactionSidecarVariant> for BlobTransactionSidecarVariant<'_> {
965 fn serialize_as<S>(
966 source: &super::BlobTransactionSidecarVariant,
967 serializer: S,
968 ) -> Result<S::Ok, S::Error>
969 where
970 S: Serializer,
971 {
972 BlobTransactionSidecarVariant::from(source).serialize(serializer)
973 }
974 }
975
976 impl<'de> DeserializeAs<'de, super::BlobTransactionSidecarVariant>
977 for BlobTransactionSidecarVariant<'de>
978 {
979 fn deserialize_as<D>(
980 deserializer: D,
981 ) -> Result<super::BlobTransactionSidecarVariant, D::Error>
982 where
983 D: Deserializer<'de>,
984 {
985 let value = BlobTransactionSidecarVariant::deserialize(deserializer)?;
986 value.try_into_inner().map_err(serde::de::Error::custom)
987 }
988 }
989}
990
991#[cfg(test)]
992mod tests {
993 use super::*;
994 #[cfg(feature = "kzg")]
995 use crate::eip4844::{
996 builder::{SidecarBuilder, SimpleCoder},
997 env_settings::EnvKzgSettings,
998 };
999
1000 #[test]
1001 fn sidecar_variant_rlp_roundtrip() {
1002 let mut encoded = Vec::new();
1003
1004 let empty_sidecar_4844 =
1006 BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar::default());
1007 empty_sidecar_4844.encode(&mut encoded);
1008 assert_eq!(
1009 empty_sidecar_4844,
1010 BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap()
1011 );
1012
1013 let sidecar_4844 = BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar::new(
1014 vec![Blob::default()],
1015 vec![Bytes48::ZERO],
1016 vec![Bytes48::ZERO],
1017 ));
1018 encoded.clear();
1019 sidecar_4844.encode(&mut encoded);
1020 assert_eq!(sidecar_4844, BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap());
1021
1022 let empty_sidecar_7594 =
1024 BlobTransactionSidecarVariant::Eip7594(BlobTransactionSidecarEip7594::default());
1025 encoded.clear();
1026 empty_sidecar_7594.encode(&mut encoded);
1027 assert_eq!(
1028 empty_sidecar_7594,
1029 BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap()
1030 );
1031
1032 let sidecar_7594 =
1033 BlobTransactionSidecarVariant::Eip7594(BlobTransactionSidecarEip7594::new(
1034 vec![Blob::default()],
1035 vec![Bytes48::ZERO],
1036 core::iter::repeat_n(Bytes48::ZERO, CELLS_PER_EXT_BLOB).collect(),
1037 ));
1038 encoded.clear();
1039 sidecar_7594.encode(&mut encoded);
1040 assert_eq!(sidecar_7594, BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap());
1041 }
1042
1043 #[test]
1044 #[cfg(feature = "serde")]
1045 fn sidecar_variant_json_deserialize_sanity() {
1046 let mut eip4844 = BlobTransactionSidecar::default();
1047 eip4844.blobs.push(Blob::repeat_byte(0x2));
1048
1049 let json = serde_json::to_string(&eip4844).unwrap();
1050 let variant: BlobTransactionSidecarVariant = serde_json::from_str(&json).unwrap();
1051 assert!(variant.is_eip4844());
1052 let jsonvariant = serde_json::to_string(&variant).unwrap();
1053 assert_eq!(json, jsonvariant);
1054
1055 let mut eip7594 = BlobTransactionSidecarEip7594::default();
1056 eip7594.blobs.push(Blob::repeat_byte(0x4));
1057 let json = serde_json::to_string(&eip7594).unwrap();
1058 let variant: BlobTransactionSidecarVariant = serde_json::from_str(&json).unwrap();
1059 assert!(variant.is_eip7594());
1060 let jsonvariant = serde_json::to_string(&variant).unwrap();
1061 assert_eq!(json, jsonvariant);
1062 }
1063
1064 #[test]
1065 fn rlp_7594_roundtrip() {
1066 let mut encoded = Vec::new();
1067
1068 let sidecar_4844 = BlobTransactionSidecar::default();
1069 sidecar_4844.encode_7594(&mut encoded);
1070 assert_eq!(sidecar_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1071
1072 let sidecar_variant_4844 = BlobTransactionSidecarVariant::Eip4844(sidecar_4844);
1073 assert_eq!(sidecar_variant_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1074 encoded.clear();
1075 sidecar_variant_4844.encode_7594(&mut encoded);
1076 assert_eq!(sidecar_variant_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1077
1078 let sidecar_7594 = BlobTransactionSidecarEip7594::default();
1079 encoded.clear();
1080 sidecar_7594.encode_7594(&mut encoded);
1081 assert_eq!(sidecar_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1082
1083 let sidecar_variant_7594 = BlobTransactionSidecarVariant::Eip7594(sidecar_7594);
1084 assert_eq!(sidecar_variant_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1085 encoded.clear();
1086 sidecar_variant_7594.encode_7594(&mut encoded);
1087 assert_eq!(sidecar_variant_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1088 }
1089
1090 #[test]
1091 #[cfg(feature = "kzg")]
1092 fn validate_7594_sidecar() {
1093 let sidecar =
1094 SidecarBuilder::<SimpleCoder>::from_slice(b"Blobs are fun!").build_7594().unwrap();
1095 let versioned_hashes = sidecar.versioned_hashes().collect::<Vec<_>>();
1096
1097 sidecar.validate(&versioned_hashes, EnvKzgSettings::Default.get()).unwrap();
1098 }
1099}