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 BlobTransactionSidecarVariant {
33 pub const fn is_eip4844(&self) -> bool {
35 matches!(self, Self::Eip4844(_))
36 }
37
38 pub const fn is_eip7594(&self) -> bool {
40 matches!(self, Self::Eip7594(_))
41 }
42
43 pub const fn as_eip4844(&self) -> Option<&BlobTransactionSidecar> {
45 match self {
46 Self::Eip4844(sidecar) => Some(sidecar),
47 _ => None,
48 }
49 }
50
51 pub const fn as_eip7594(&self) -> Option<&BlobTransactionSidecarEip7594> {
53 match self {
54 Self::Eip7594(sidecar) => Some(sidecar),
55 _ => None,
56 }
57 }
58
59 pub fn into_eip4844(self) -> Option<BlobTransactionSidecar> {
61 match self {
62 Self::Eip4844(sidecar) => Some(sidecar),
63 _ => None,
64 }
65 }
66
67 pub fn into_eip7594(self) -> Option<BlobTransactionSidecarEip7594> {
69 match self {
70 Self::Eip7594(sidecar) => Some(sidecar),
71 _ => None,
72 }
73 }
74
75 pub fn blobs(&self) -> &[Blob] {
77 match self {
78 Self::Eip4844(sidecar) => &sidecar.blobs,
79 Self::Eip7594(sidecar) => &sidecar.blobs,
80 }
81 }
82
83 pub fn into_blobs(self) -> Vec<Blob> {
85 match self {
86 Self::Eip4844(sidecar) => sidecar.blobs,
87 Self::Eip7594(sidecar) => sidecar.blobs,
88 }
89 }
90
91 #[inline]
93 pub const fn size(&self) -> usize {
94 match self {
95 Self::Eip4844(sidecar) => sidecar.size(),
96 Self::Eip7594(sidecar) => sidecar.size(),
97 }
98 }
99
100 #[cfg(feature = "kzg")]
128 pub fn try_convert_into_eip7594(self) -> Result<Self, c_kzg::Error> {
129 self.try_convert_into_eip7594_with_settings(
130 crate::eip4844::env_settings::EnvKzgSettings::Default.get(),
131 )
132 }
133
134 #[cfg(feature = "kzg")]
175 pub fn try_convert_into_eip7594_with_settings(
176 self,
177 settings: &c_kzg::KzgSettings,
178 ) -> Result<Self, c_kzg::Error> {
179 match self {
180 Self::Eip4844(legacy) => legacy.try_into_7594(settings).map(Self::Eip7594),
181 sidecar @ Self::Eip7594(_) => Ok(sidecar),
182 }
183 }
184
185 #[cfg(feature = "kzg")]
218 pub fn try_into_eip7594(self) -> Result<BlobTransactionSidecarEip7594, c_kzg::Error> {
219 self.try_into_eip7594_with_settings(
220 crate::eip4844::env_settings::EnvKzgSettings::Default.get(),
221 )
222 }
223
224 #[cfg(feature = "kzg")]
269 pub fn try_into_eip7594_with_settings(
270 self,
271 settings: &c_kzg::KzgSettings,
272 ) -> Result<BlobTransactionSidecarEip7594, c_kzg::Error> {
273 match self {
274 Self::Eip4844(legacy) => legacy.try_into_7594(settings),
275 Self::Eip7594(sidecar) => Ok(sidecar),
276 }
277 }
278
279 #[cfg(feature = "kzg")]
281 pub fn validate(
282 &self,
283 blob_versioned_hashes: &[B256],
284 proof_settings: &c_kzg::KzgSettings,
285 ) -> Result<(), BlobTransactionValidationError> {
286 match self {
287 Self::Eip4844(sidecar) => sidecar.validate(blob_versioned_hashes, proof_settings),
288 Self::Eip7594(sidecar) => sidecar.validate(blob_versioned_hashes, proof_settings),
289 }
290 }
291
292 pub fn commitments(&self) -> &[Bytes48] {
294 match self {
295 Self::Eip4844(sidecar) => &sidecar.commitments,
296 Self::Eip7594(sidecar) => &sidecar.commitments,
297 }
298 }
299
300 pub fn versioned_hashes(&self) -> VersionedHashIter<'_> {
302 VersionedHashIter::new(self.commitments())
303 }
304
305 pub fn versioned_hash_index(&self, hash: &B256) -> Option<usize> {
307 match self {
308 Self::Eip4844(s) => s.versioned_hash_index(hash),
309 Self::Eip7594(s) => s.versioned_hash_index(hash),
310 }
311 }
312
313 pub fn blob_by_versioned_hash(&self, hash: &B256) -> Option<&Blob> {
315 match self {
316 Self::Eip4844(s) => s.blob_by_versioned_hash(hash),
317 Self::Eip7594(s) => s.blob_by_versioned_hash(hash),
318 }
319 }
320
321 #[doc(hidden)]
323 pub fn rlp_encoded_fields_length(&self) -> usize {
324 match self {
325 Self::Eip4844(sidecar) => sidecar.rlp_encoded_fields_length(),
326 Self::Eip7594(sidecar) => sidecar.rlp_encoded_fields_length(),
327 }
328 }
329
330 #[inline]
332 #[doc(hidden)]
333 pub fn rlp_encoded_fields(&self) -> Vec<u8> {
334 let mut buf = Vec::with_capacity(self.rlp_encoded_fields_length());
335 self.rlp_encode_fields(&mut buf);
336 buf
337 }
338
339 #[inline]
342 #[doc(hidden)]
343 pub fn rlp_encode_fields(&self, out: &mut dyn BufMut) {
344 match self {
345 Self::Eip4844(sidecar) => sidecar.rlp_encode_fields(out),
346 Self::Eip7594(sidecar) => sidecar.rlp_encode_fields(out),
347 }
348 }
349
350 #[doc(hidden)]
352 pub fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
353 Self::decode_7594(buf)
354 }
355}
356
357impl Encodable for BlobTransactionSidecarVariant {
358 fn encode(&self, out: &mut dyn BufMut) {
360 match self {
361 Self::Eip4844(sidecar) => sidecar.encode(out),
362 Self::Eip7594(sidecar) => sidecar.encode(out),
363 }
364 }
365
366 fn length(&self) -> usize {
367 match self {
368 Self::Eip4844(sidecar) => sidecar.rlp_encoded_length(),
369 Self::Eip7594(sidecar) => sidecar.rlp_encoded_length(),
370 }
371 }
372}
373
374impl Decodable for BlobTransactionSidecarVariant {
375 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
377 let header = Header::decode(buf)?;
378 if !header.list {
379 return Err(alloy_rlp::Error::UnexpectedString);
380 }
381 if buf.len() < header.payload_length {
382 return Err(alloy_rlp::Error::InputTooShort);
383 }
384 let remaining = buf.len();
385 let this = Self::rlp_decode_fields(buf)?;
386 if buf.len() + header.payload_length != remaining {
387 return Err(alloy_rlp::Error::UnexpectedLength);
388 }
389
390 Ok(this)
391 }
392}
393
394impl Encodable7594 for BlobTransactionSidecarVariant {
395 fn encode_7594_len(&self) -> usize {
396 self.rlp_encoded_fields_length()
397 }
398
399 fn encode_7594(&self, out: &mut dyn BufMut) {
400 self.rlp_encode_fields(out);
401 }
402}
403
404impl Decodable7594 for BlobTransactionSidecarVariant {
405 fn decode_7594(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
406 if buf.first() == Some(&EIP_7594_WRAPPER_VERSION) {
407 Ok(Self::Eip7594(Decodable7594::decode_7594(buf)?))
408 } else {
409 Ok(Self::Eip4844(Decodable7594::decode_7594(buf)?))
410 }
411 }
412}
413
414#[cfg(feature = "kzg")]
415impl TryFrom<BlobTransactionSidecarVariant> for BlobTransactionSidecarEip7594 {
416 type Error = c_kzg::Error;
417
418 fn try_from(value: BlobTransactionSidecarVariant) -> Result<Self, Self::Error> {
419 value.try_into_eip7594()
420 }
421}
422
423#[cfg(feature = "serde")]
424impl<'de> serde::Deserialize<'de> for BlobTransactionSidecarVariant {
425 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
426 where
427 D: serde::Deserializer<'de>,
428 {
429 use core::fmt;
430
431 #[derive(serde::Deserialize, fmt::Debug)]
432 #[serde(field_identifier, rename_all = "camelCase")]
433 enum Field {
434 Blobs,
435 Commitments,
436 Proofs,
437 CellProofs,
438 }
439
440 struct VariantVisitor;
441
442 impl<'de> serde::de::Visitor<'de> for VariantVisitor {
443 type Value = BlobTransactionSidecarVariant;
444
445 fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
446 formatter
447 .write_str("a valid blob transaction sidecar (EIP-4844 or EIP-7594 variant)")
448 }
449
450 fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
451 where
452 M: serde::de::MapAccess<'de>,
453 {
454 let mut blobs = None;
455 let mut commitments = None;
456 let mut proofs = None;
457 let mut cell_proofs = None;
458
459 while let Some(key) = map.next_key()? {
460 match key {
461 Field::Blobs => {
462 blobs = Some(crate::eip4844::deserialize_blobs_map(&mut map)?);
463 }
464 Field::Commitments => commitments = Some(map.next_value()?),
465 Field::Proofs => proofs = Some(map.next_value()?),
466 Field::CellProofs => cell_proofs = Some(map.next_value()?),
467 }
468 }
469
470 let blobs = blobs.ok_or_else(|| serde::de::Error::missing_field("blobs"))?;
471 let commitments =
472 commitments.ok_or_else(|| serde::de::Error::missing_field("commitments"))?;
473
474 match (cell_proofs, proofs) {
475 (Some(cp), None) => {
476 Ok(BlobTransactionSidecarVariant::Eip7594(BlobTransactionSidecarEip7594 {
477 blobs,
478 commitments,
479 cell_proofs: cp,
480 }))
481 }
482 (None, Some(pf)) => {
483 Ok(BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar {
484 blobs,
485 commitments,
486 proofs: pf,
487 }))
488 }
489 (None, None) => {
490 Err(serde::de::Error::custom("Missing 'cellProofs' or 'proofs'"))
491 }
492 (Some(_), Some(_)) => Err(serde::de::Error::custom(
493 "Both 'cellProofs' and 'proofs' cannot be present",
494 )),
495 }
496 }
497 }
498
499 const FIELDS: &[&str] = &["blobs", "commitments", "proofs", "cellProofs"];
500 deserializer.deserialize_struct("BlobTransactionSidecarVariant", FIELDS, VariantVisitor)
501 }
502}
503
504#[derive(Clone, Default, PartialEq, Eq, Hash)]
508#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
509#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
510#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
511pub struct BlobTransactionSidecarEip7594 {
512 #[cfg_attr(feature = "serde", serde(deserialize_with = "crate::eip4844::deserialize_blobs"))]
514 pub blobs: Vec<Blob>,
515 pub commitments: Vec<Bytes48>,
517 pub cell_proofs: Vec<Bytes48>,
522}
523
524impl core::fmt::Debug for BlobTransactionSidecarEip7594 {
525 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
526 f.debug_struct("BlobTransactionSidecarEip7594")
527 .field("blobs", &self.blobs.len())
528 .field("commitments", &self.commitments)
529 .field("cell_proofs", &self.cell_proofs)
530 .finish()
531 }
532}
533
534impl BlobTransactionSidecarEip7594 {
535 pub const fn new(
538 blobs: Vec<Blob>,
539 commitments: Vec<Bytes48>,
540 cell_proofs: Vec<Bytes48>,
541 ) -> Self {
542 Self { blobs, commitments, cell_proofs }
543 }
544
545 #[inline]
547 pub const fn size(&self) -> usize {
548 self.blobs.capacity() * BYTES_PER_BLOB
549 + self.commitments.capacity() * BYTES_PER_COMMITMENT
550 + self.cell_proofs.capacity() * BYTES_PER_PROOF
551 }
552
553 #[cfg(all(feature = "kzg", any(test, feature = "arbitrary")))]
557 pub fn try_from_blobs_hex<I, B>(blobs: I) -> Result<Self, c_kzg::Error>
558 where
559 I: IntoIterator<Item = B>,
560 B: AsRef<str>,
561 {
562 blobs
563 .into_iter()
564 .map(crate::eip4844::utils::hex_to_blob)
565 .collect::<Result<Vec<_>, _>>()
566 .and_then(Self::try_from_blobs)
567 }
568
569 #[cfg(all(feature = "kzg", any(test, feature = "arbitrary")))]
574 pub fn try_from_blobs_bytes<I, B>(blobs: I) -> Result<Self, c_kzg::Error>
575 where
576 I: IntoIterator<Item = B>,
577 B: AsRef<[u8]>,
578 {
579 blobs
580 .into_iter()
581 .map(crate::eip4844::utils::bytes_to_blob)
582 .collect::<Result<Vec<_>, _>>()
583 .and_then(Self::try_from_blobs)
584 }
585
586 #[cfg(feature = "kzg")]
589 pub fn try_from_blobs_with_settings(
590 blobs: Vec<Blob>,
591 settings: &c_kzg::KzgSettings,
592 ) -> Result<Self, c_kzg::Error> {
593 let mut commitments = Vec::with_capacity(blobs.len());
594 let mut proofs = Vec::with_capacity(blobs.len());
595 for blob in &blobs {
596 let blob = unsafe { core::mem::transmute::<&Blob, &c_kzg::Blob>(blob) };
598 let commitment = settings.blob_to_kzg_commitment(blob)?;
599 let (_cells, kzg_proofs) = settings.compute_cells_and_kzg_proofs(blob)?;
600
601 unsafe {
603 commitments
604 .push(core::mem::transmute::<c_kzg::Bytes48, Bytes48>(commitment.to_bytes()));
605 for kzg_proof in kzg_proofs.iter() {
606 proofs.push(core::mem::transmute::<c_kzg::Bytes48, Bytes48>(
607 kzg_proof.to_bytes(),
608 ));
609 }
610 }
611 }
612
613 Ok(Self::new(blobs, commitments, proofs))
614 }
615
616 #[cfg(feature = "kzg")]
622 pub fn try_from_blobs(blobs: Vec<Blob>) -> Result<Self, c_kzg::Error> {
623 use crate::eip4844::env_settings::EnvKzgSettings;
624
625 Self::try_from_blobs_with_settings(blobs, EnvKzgSettings::Default.get())
626 }
627
628 #[cfg(feature = "kzg")]
642 pub fn validate(
643 &self,
644 blob_versioned_hashes: &[B256],
645 proof_settings: &c_kzg::KzgSettings,
646 ) -> Result<(), BlobTransactionValidationError> {
647 if blob_versioned_hashes.len() != self.commitments.len() {
649 return Err(c_kzg::Error::MismatchLength(format!(
650 "There are {} versioned commitment hashes and {} commitments",
651 blob_versioned_hashes.len(),
652 self.commitments.len()
653 ))
654 .into());
655 }
656
657 let blobs_len = self.blobs.len();
658 let expected_cell_proofs_len = blobs_len * CELLS_PER_EXT_BLOB;
659 if self.cell_proofs.len() != expected_cell_proofs_len {
660 return Err(c_kzg::Error::MismatchLength(format!(
661 "There are {} cell proofs and {} blobs. Expected {} cell proofs.",
662 self.cell_proofs.len(),
663 blobs_len,
664 expected_cell_proofs_len
665 ))
666 .into());
667 }
668
669 for (versioned_hash, commitment) in
671 blob_versioned_hashes.iter().zip(self.commitments.iter())
672 {
673 let calculated_versioned_hash =
675 crate::eip4844::kzg_to_versioned_hash(commitment.as_slice());
676 if *versioned_hash != calculated_versioned_hash {
677 return Err(BlobTransactionValidationError::WrongVersionedHash {
678 have: *versioned_hash,
679 expected: calculated_versioned_hash,
680 });
681 }
682 }
683
684 let cell_indices =
686 Vec::from_iter((0..blobs_len).flat_map(|_| 0..CELLS_PER_EXT_BLOB as u64));
687
688 let mut commitments = Vec::with_capacity(blobs_len * CELLS_PER_EXT_BLOB);
690 for commitment in &self.commitments {
691 commitments.extend(core::iter::repeat_n(*commitment, CELLS_PER_EXT_BLOB));
692 }
693
694 let res = unsafe {
696 let mut cells = Vec::with_capacity(blobs_len * CELLS_PER_EXT_BLOB);
697 for blob in &self.blobs {
698 let blob = core::mem::transmute::<&Blob, &c_kzg::Blob>(blob);
699 cells.extend(proof_settings.compute_cells(blob)?.into_iter());
700 }
701
702 proof_settings.verify_cell_kzg_proof_batch(
703 core::mem::transmute::<&[Bytes48], &[c_kzg::Bytes48]>(&commitments),
705 &cell_indices,
707 &cells,
709 core::mem::transmute::<&[Bytes48], &[c_kzg::Bytes48]>(self.cell_proofs.as_slice()),
711 )?
712 };
713
714 res.then_some(()).ok_or(BlobTransactionValidationError::InvalidProof)
715 }
716
717 pub fn versioned_hashes(&self) -> VersionedHashIter<'_> {
719 VersionedHashIter::new(&self.commitments)
720 }
721
722 pub fn versioned_hash_index(&self, hash: &B256) -> Option<usize> {
724 self.commitments.iter().position(|commitment| {
725 crate::eip4844::kzg_to_versioned_hash(commitment.as_slice()) == *hash
726 })
727 }
728
729 pub fn blob_by_versioned_hash(&self, hash: &B256) -> Option<&Blob> {
731 self.versioned_hash_index(hash).and_then(|index| self.blobs.get(index))
732 }
733
734 pub fn match_versioned_hashes<'a>(
740 &'a self,
741 versioned_hashes: &'a [B256],
742 ) -> impl Iterator<Item = (usize, BlobAndProofV2)> + 'a {
743 self.versioned_hashes().enumerate().flat_map(move |(i, blob_versioned_hash)| {
744 versioned_hashes.iter().enumerate().filter_map(move |(j, target_hash)| {
745 if blob_versioned_hash == *target_hash {
746 let maybe_blob = self.blobs.get(i);
747 let proof_range = i * CELLS_PER_EXT_BLOB..(i + 1) * CELLS_PER_EXT_BLOB;
748 let maybe_proofs = Some(&self.cell_proofs[proof_range])
749 .filter(|proofs| proofs.len() == CELLS_PER_EXT_BLOB);
750 if let Some((blob, proofs)) = maybe_blob.copied().zip(maybe_proofs) {
751 return Some((
752 j,
753 BlobAndProofV2 { blob: Box::new(blob), proofs: proofs.to_vec() },
754 ));
755 }
756 }
757 None
758 })
759 })
760 }
761
762 #[doc(hidden)]
764 pub fn rlp_encoded_fields_length(&self) -> usize {
765 1 + self.blobs.length() + self.commitments.length() + self.cell_proofs.length()
767 }
768
769 #[inline]
778 #[doc(hidden)]
779 pub fn rlp_encode_fields(&self, out: &mut dyn BufMut) {
780 out.put_u8(EIP_7594_WRAPPER_VERSION);
782 self.blobs.encode(out);
784 self.commitments.encode(out);
785 self.cell_proofs.encode(out);
786 }
787
788 fn rlp_header(&self) -> Header {
790 Header { list: true, payload_length: self.rlp_encoded_fields_length() }
791 }
792
793 pub fn rlp_encoded_length(&self) -> usize {
796 self.rlp_header().length() + self.rlp_encoded_fields_length()
797 }
798
799 pub fn rlp_encode(&self, out: &mut dyn BufMut) {
801 self.rlp_header().encode(out);
802 self.rlp_encode_fields(out);
803 }
804
805 #[doc(hidden)]
807 pub fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
808 Ok(Self {
809 blobs: Decodable::decode(buf)?,
810 commitments: Decodable::decode(buf)?,
811 cell_proofs: Decodable::decode(buf)?,
812 })
813 }
814
815 pub fn rlp_decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
817 let header = Header::decode(buf)?;
818 if !header.list {
819 return Err(alloy_rlp::Error::UnexpectedString);
820 }
821 if buf.len() < header.payload_length {
822 return Err(alloy_rlp::Error::InputTooShort);
823 }
824 let remaining = buf.len();
825
826 let this = Self::decode_7594(buf)?;
827 if buf.len() + header.payload_length != remaining {
828 return Err(alloy_rlp::Error::UnexpectedLength);
829 }
830
831 Ok(this)
832 }
833}
834
835impl Encodable for BlobTransactionSidecarEip7594 {
836 fn encode(&self, out: &mut dyn BufMut) {
838 self.rlp_encode(out);
839 }
840
841 fn length(&self) -> usize {
842 self.rlp_encoded_length()
843 }
844}
845
846impl Decodable for BlobTransactionSidecarEip7594 {
847 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
850 Self::rlp_decode(buf)
851 }
852}
853
854impl Encodable7594 for BlobTransactionSidecarEip7594 {
855 fn encode_7594_len(&self) -> usize {
856 self.rlp_encoded_fields_length()
857 }
858
859 fn encode_7594(&self, out: &mut dyn BufMut) {
860 self.rlp_encode_fields(out);
861 }
862}
863
864impl Decodable7594 for BlobTransactionSidecarEip7594 {
865 fn decode_7594(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
866 let wrapper_version: u8 = Decodable::decode(buf)?;
867 if wrapper_version != EIP_7594_WRAPPER_VERSION {
868 return Err(alloy_rlp::Error::Custom("invalid wrapper version"));
869 }
870 Self::rlp_decode_fields(buf)
871 }
872}
873
874#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
876pub mod serde_bincode_compat {
877 use crate::eip4844::{Blob, Bytes48};
878 use alloc::{borrow::Cow, vec::Vec};
879 use serde::{Deserialize, Deserializer, Serialize, Serializer};
880 use serde_with::{DeserializeAs, SerializeAs};
881
882 #[derive(Debug, Serialize, Deserialize)]
898 pub struct BlobTransactionSidecarVariant<'a> {
899 pub blobs: Cow<'a, Vec<Blob>>,
901 pub commitments: Cow<'a, Vec<Bytes48>>,
903 pub proofs: Option<Cow<'a, Vec<Bytes48>>>,
905 pub cell_proofs: Option<Cow<'a, Vec<Bytes48>>>,
907 }
908
909 impl<'a> From<&'a super::BlobTransactionSidecarVariant> for BlobTransactionSidecarVariant<'a> {
910 fn from(value: &'a super::BlobTransactionSidecarVariant) -> Self {
911 match value {
912 super::BlobTransactionSidecarVariant::Eip4844(sidecar) => Self {
913 blobs: Cow::Borrowed(&sidecar.blobs),
914 commitments: Cow::Borrowed(&sidecar.commitments),
915 proofs: Some(Cow::Borrowed(&sidecar.proofs)),
916 cell_proofs: None,
917 },
918 super::BlobTransactionSidecarVariant::Eip7594(sidecar) => Self {
919 blobs: Cow::Borrowed(&sidecar.blobs),
920 commitments: Cow::Borrowed(&sidecar.commitments),
921 proofs: None,
922 cell_proofs: Some(Cow::Borrowed(&sidecar.cell_proofs)),
923 },
924 }
925 }
926 }
927
928 impl<'a> BlobTransactionSidecarVariant<'a> {
929 fn try_into_inner(self) -> Result<super::BlobTransactionSidecarVariant, &'static str> {
930 match (self.proofs, self.cell_proofs) {
931 (Some(proofs), None) => Ok(super::BlobTransactionSidecarVariant::Eip4844(
932 crate::eip4844::BlobTransactionSidecar {
933 blobs: self.blobs.into_owned(),
934 commitments: self.commitments.into_owned(),
935 proofs: proofs.into_owned(),
936 },
937 )),
938 (None, Some(cell_proofs)) => Ok(super::BlobTransactionSidecarVariant::Eip7594(
939 super::BlobTransactionSidecarEip7594 {
940 blobs: self.blobs.into_owned(),
941 commitments: self.commitments.into_owned(),
942 cell_proofs: cell_proofs.into_owned(),
943 },
944 )),
945 (None, None) => Err("Missing both 'proofs' and 'cell_proofs'"),
946 (Some(_), Some(_)) => Err("Both 'proofs' and 'cell_proofs' cannot be present"),
947 }
948 }
949 }
950
951 impl<'a> From<BlobTransactionSidecarVariant<'a>> for super::BlobTransactionSidecarVariant {
952 fn from(value: BlobTransactionSidecarVariant<'a>) -> Self {
953 value.try_into_inner().expect("Invalid BlobTransactionSidecarVariant")
954 }
955 }
956
957 impl SerializeAs<super::BlobTransactionSidecarVariant> for BlobTransactionSidecarVariant<'_> {
958 fn serialize_as<S>(
959 source: &super::BlobTransactionSidecarVariant,
960 serializer: S,
961 ) -> Result<S::Ok, S::Error>
962 where
963 S: Serializer,
964 {
965 BlobTransactionSidecarVariant::from(source).serialize(serializer)
966 }
967 }
968
969 impl<'de> DeserializeAs<'de, super::BlobTransactionSidecarVariant>
970 for BlobTransactionSidecarVariant<'de>
971 {
972 fn deserialize_as<D>(
973 deserializer: D,
974 ) -> Result<super::BlobTransactionSidecarVariant, D::Error>
975 where
976 D: Deserializer<'de>,
977 {
978 let value = BlobTransactionSidecarVariant::deserialize(deserializer)?;
979 value.try_into_inner().map_err(serde::de::Error::custom)
980 }
981 }
982}
983
984#[cfg(test)]
985mod tests {
986 use super::*;
987
988 #[test]
989 fn sidecar_variant_rlp_roundtrip() {
990 let mut encoded = Vec::new();
991
992 let empty_sidecar_4844 =
994 BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar::default());
995 empty_sidecar_4844.encode(&mut encoded);
996 assert_eq!(
997 empty_sidecar_4844,
998 BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap()
999 );
1000
1001 let sidecar_4844 = BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar::new(
1002 vec![Blob::default()],
1003 vec![Bytes48::ZERO],
1004 vec![Bytes48::ZERO],
1005 ));
1006 encoded.clear();
1007 sidecar_4844.encode(&mut encoded);
1008 assert_eq!(sidecar_4844, BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap());
1009
1010 let empty_sidecar_7594 =
1012 BlobTransactionSidecarVariant::Eip7594(BlobTransactionSidecarEip7594::default());
1013 encoded.clear();
1014 empty_sidecar_7594.encode(&mut encoded);
1015 assert_eq!(
1016 empty_sidecar_7594,
1017 BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap()
1018 );
1019
1020 let sidecar_7594 =
1021 BlobTransactionSidecarVariant::Eip7594(BlobTransactionSidecarEip7594::new(
1022 vec![Blob::default()],
1023 vec![Bytes48::ZERO],
1024 core::iter::repeat_n(Bytes48::ZERO, CELLS_PER_EXT_BLOB).collect(),
1025 ));
1026 encoded.clear();
1027 sidecar_7594.encode(&mut encoded);
1028 assert_eq!(sidecar_7594, BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap());
1029 }
1030
1031 #[test]
1032 #[cfg(feature = "serde")]
1033 fn sidecar_variant_json_deserialize_sanity() {
1034 let mut eip4844 = BlobTransactionSidecar::default();
1035 eip4844.blobs.push(Blob::repeat_byte(0x2));
1036
1037 let json = serde_json::to_string(&eip4844).unwrap();
1038 let variant: BlobTransactionSidecarVariant = serde_json::from_str(&json).unwrap();
1039 assert!(variant.is_eip4844());
1040 let jsonvariant = serde_json::to_string(&variant).unwrap();
1041 assert_eq!(json, jsonvariant);
1042
1043 let mut eip7594 = BlobTransactionSidecarEip7594::default();
1044 eip7594.blobs.push(Blob::repeat_byte(0x4));
1045 let json = serde_json::to_string(&eip7594).unwrap();
1046 let variant: BlobTransactionSidecarVariant = serde_json::from_str(&json).unwrap();
1047 assert!(variant.is_eip7594());
1048 let jsonvariant = serde_json::to_string(&variant).unwrap();
1049 assert_eq!(json, jsonvariant);
1050 }
1051
1052 #[test]
1053 fn rlp_7594_roundtrip() {
1054 let mut encoded = Vec::new();
1055
1056 let sidecar_4844 = BlobTransactionSidecar::default();
1057 sidecar_4844.encode_7594(&mut encoded);
1058 assert_eq!(sidecar_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1059
1060 let sidecar_variant_4844 = BlobTransactionSidecarVariant::Eip4844(sidecar_4844);
1061 assert_eq!(sidecar_variant_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1062 encoded.clear();
1063 sidecar_variant_4844.encode_7594(&mut encoded);
1064 assert_eq!(sidecar_variant_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1065
1066 let sidecar_7594 = BlobTransactionSidecarEip7594::default();
1067 encoded.clear();
1068 sidecar_7594.encode_7594(&mut encoded);
1069 assert_eq!(sidecar_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1070
1071 let sidecar_variant_7594 = BlobTransactionSidecarVariant::Eip7594(sidecar_7594);
1072 assert_eq!(sidecar_variant_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1073 encoded.clear();
1074 sidecar_variant_7594.encode_7594(&mut encoded);
1075 assert_eq!(sidecar_variant_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1076 }
1077}