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::{B128, B256};
10use alloy_rlp::{BufMut, Decodable, Encodable, Header};
11
12use super::{Decodable7594, Encodable7594};
13use crate::eip4844::VersionedHashIter;
14#[cfg(feature = "kzg")]
15use crate::eip4844::{AsAlloy, AsCkzg, BlobTransactionValidationError};
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 #[inline]
561 pub fn shrink_to_fit(&mut self) {
562 self.blobs.shrink_to_fit();
563 self.commitments.shrink_to_fit();
564 self.cell_proofs.shrink_to_fit();
565 }
566
567 #[cfg(all(feature = "kzg", any(test, feature = "arbitrary")))]
571 pub fn try_from_blobs_hex<I, B>(blobs: I) -> Result<Self, c_kzg::Error>
572 where
573 I: IntoIterator<Item = B>,
574 B: AsRef<str>,
575 {
576 let mut converted = Vec::new();
577 for blob in blobs {
578 converted.push(crate::eip4844::utils::hex_to_blob(blob)?);
579 }
580 Self::try_from_blobs(converted)
581 }
582
583 #[cfg(all(feature = "kzg", any(test, feature = "arbitrary")))]
588 pub fn try_from_blobs_bytes<I, B>(blobs: I) -> Result<Self, c_kzg::Error>
589 where
590 I: IntoIterator<Item = B>,
591 B: AsRef<[u8]>,
592 {
593 let mut converted = Vec::new();
594 for blob in blobs {
595 converted.push(crate::eip4844::utils::bytes_to_blob(blob)?);
596 }
597 Self::try_from_blobs(converted)
598 }
599
600 #[cfg(feature = "kzg")]
603 pub fn try_from_blobs_with_settings(
604 blobs: Vec<Blob>,
605 settings: &c_kzg::KzgSettings,
606 ) -> Result<Self, c_kzg::Error> {
607 if let [blob] = blobs.as_slice() {
608 let blob = blob.as_ckzg();
609 let commitment = settings.blob_to_kzg_commitment(blob)?;
610 let (_cells, kzg_proofs) = settings.compute_cells_and_kzg_proofs(blob)?;
611 let commitments = vec![Bytes48::from_ckzg(commitment.to_bytes())];
612 let proofs = c_kzg::KzgProof::boxed_slice_as_alloy(kzg_proofs).into();
613 return Ok(Self::new(blobs, commitments, proofs));
614 }
615
616 let mut commitments = Vec::with_capacity(blobs.len());
617 let mut proofs = Vec::with_capacity(blobs.len() * CELLS_PER_EXT_BLOB);
618 for blob in &blobs {
619 let blob = blob.as_ckzg();
620 let commitment = settings.blob_to_kzg_commitment(blob)?;
621 let (_cells, kzg_proofs) = settings.compute_cells_and_kzg_proofs(blob)?;
622
623 commitments.push(Bytes48::from_ckzg(commitment.to_bytes()));
624 proofs.extend_from_slice(c_kzg::KzgProof::slice_as_alloy(kzg_proofs.as_ref()));
625 }
626
627 Ok(Self::new(blobs, commitments, proofs))
628 }
629
630 #[cfg(feature = "kzg")]
636 pub fn try_from_blobs(blobs: Vec<Blob>) -> Result<Self, c_kzg::Error> {
637 use crate::eip4844::env_settings::EnvKzgSettings;
638
639 Self::try_from_blobs_with_settings(blobs, EnvKzgSettings::Default.get())
640 }
641
642 #[cfg(feature = "kzg")]
650 pub fn compute_cells(&self) -> Result<Vec<crate::eip7594::Cell>, c_kzg::Error> {
651 use crate::eip4844::env_settings::EnvKzgSettings;
652
653 self.compute_cells_with_settings(EnvKzgSettings::Default.get())
654 }
655
656 #[cfg(feature = "kzg")]
664 pub fn compute_cells_with_settings(
665 &self,
666 settings: &c_kzg::KzgSettings,
667 ) -> Result<Vec<crate::eip7594::Cell>, c_kzg::Error> {
668 if let [blob] = self.blobs.as_slice() {
669 let blob_cells = settings.compute_cells(blob.as_ckzg())?;
670 return Ok(c_kzg::Cell::boxed_slice_as_alloy(blob_cells).into());
671 }
672
673 let mut cells = Vec::with_capacity(self.blobs.len() * CELLS_PER_EXT_BLOB);
674 for blob in &self.blobs {
675 let blob_cells = settings.compute_cells(blob.as_ckzg())?;
676 cells.extend_from_slice(c_kzg::Cell::slice_as_alloy(blob_cells.as_ref()));
677 }
678 Ok(cells)
679 }
680
681 #[cfg(feature = "kzg")]
689 pub fn compute_matching_cells(
690 &self,
691 cell_mask: BlobCellMask,
692 ) -> Result<Vec<crate::eip7594::Cell>, c_kzg::Error> {
693 use crate::eip4844::env_settings::EnvKzgSettings;
694
695 self.compute_matching_cells_with_settings(cell_mask, EnvKzgSettings::Default.get())
696 }
697
698 #[cfg(feature = "kzg")]
704 pub fn compute_matching_cells_with_settings(
705 &self,
706 cell_mask: BlobCellMask,
707 settings: &c_kzg::KzgSettings,
708 ) -> Result<Vec<crate::eip7594::Cell>, c_kzg::Error> {
709 let cells = self.compute_cells_with_settings(settings)?;
710 Ok(cell_mask
711 .matching_cells_from_computed_cells(&cells)
712 .expect("computed cells must contain full extended blob cell chunks"))
713 }
714
715 #[cfg(feature = "kzg")]
729 pub fn validate(
730 &self,
731 blob_versioned_hashes: &[B256],
732 proof_settings: &c_kzg::KzgSettings,
733 ) -> Result<(), BlobTransactionValidationError> {
734 if blob_versioned_hashes.len() != self.commitments.len() {
736 return Err(c_kzg::Error::MismatchLength(format!(
737 "There are {} versioned commitment hashes and {} commitments",
738 blob_versioned_hashes.len(),
739 self.commitments.len()
740 ))
741 .into());
742 }
743
744 let blobs_len = self.blobs.len();
745 let expected_cell_proofs_len = blobs_len * CELLS_PER_EXT_BLOB;
746 if self.cell_proofs.len() != expected_cell_proofs_len {
747 return Err(c_kzg::Error::MismatchLength(format!(
748 "There are {} cell proofs and {} blobs. Expected {} cell proofs.",
749 self.cell_proofs.len(),
750 blobs_len,
751 expected_cell_proofs_len
752 ))
753 .into());
754 }
755
756 for (versioned_hash, commitment) in
758 blob_versioned_hashes.iter().zip(self.commitments.iter())
759 {
760 let calculated_versioned_hash =
762 crate::eip4844::kzg_to_versioned_hash(commitment.as_slice());
763 if *versioned_hash != calculated_versioned_hash {
764 return Err(BlobTransactionValidationError::WrongVersionedHash {
765 have: *versioned_hash,
766 expected: calculated_versioned_hash,
767 });
768 }
769 }
770
771 let cell_indices =
773 Vec::from_iter((0..blobs_len).flat_map(|_| 0..CELLS_PER_EXT_BLOB as u64));
774
775 let mut commitments = Vec::with_capacity(blobs_len * CELLS_PER_EXT_BLOB);
777 for commitment in &self.commitments {
778 commitments.extend(core::iter::repeat_n(*commitment, CELLS_PER_EXT_BLOB));
779 }
780
781 let cells = if let [blob] = self.blobs.as_slice() {
782 let cells: Box<[c_kzg::Cell]> = proof_settings.compute_cells(blob.as_ckzg())?;
783 cells.into()
784 } else {
785 let mut cells = Vec::with_capacity(blobs_len * CELLS_PER_EXT_BLOB);
786 for blob in &self.blobs {
787 let blob_cells = proof_settings.compute_cells(blob.as_ckzg())?;
788 cells.extend_from_slice(blob_cells.as_ref());
789 }
790 cells
791 };
792
793 let res = proof_settings.verify_cell_kzg_proof_batch(
794 Bytes48::slice_as_ckzg(&commitments),
795 &cell_indices,
796 &cells,
797 Bytes48::slice_as_ckzg(self.cell_proofs.as_slice()),
798 )?;
799
800 res.then_some(()).ok_or(BlobTransactionValidationError::InvalidProof)
801 }
802
803 pub fn versioned_hashes(&self) -> VersionedHashIter<'_> {
805 VersionedHashIter::new(&self.commitments)
806 }
807
808 pub fn versioned_hash_index(&self, hash: &B256) -> Option<usize> {
810 self.commitments.iter().position(|commitment| {
811 crate::eip4844::kzg_to_versioned_hash(commitment.as_slice()) == *hash
812 })
813 }
814
815 pub fn blob_by_versioned_hash(&self, hash: &B256) -> Option<&Blob> {
817 self.versioned_hash_index(hash).and_then(|index| self.blobs.get(index))
818 }
819
820 #[cfg(feature = "kzg")]
824 pub fn blob_cells_and_proofs(
825 &self,
826 blob_index: usize,
827 cell_mask: BlobCellMask,
828 ) -> Result<Option<crate::eip4844::BlobCellsAndProofsV1>, c_kzg::Error> {
829 use crate::eip4844::env_settings::EnvKzgSettings;
830
831 self.blob_cells_and_proofs_with_settings(
832 blob_index,
833 cell_mask,
834 EnvKzgSettings::Default.get(),
835 )
836 }
837
838 #[cfg(feature = "kzg")]
840 pub fn blob_cells_and_proofs_with_settings(
841 &self,
842 blob_index: usize,
843 cell_mask: BlobCellMask,
844 settings: &c_kzg::KzgSettings,
845 ) -> Result<Option<crate::eip4844::BlobCellsAndProofsV1>, c_kzg::Error> {
846 let Some(blob) = self.blobs.get(blob_index) else { return Ok(None) };
847
848 let proof_start = blob_index * CELLS_PER_EXT_BLOB;
849 let Some(proofs) = self.cell_proofs.get(proof_start..proof_start + CELLS_PER_EXT_BLOB)
850 else {
851 return Ok(None);
852 };
853
854 if cell_mask.count() == 0 {
855 return Ok(Some(crate::eip4844::BlobCellsAndProofsV1::default()));
856 }
857
858 let cells = settings.compute_cells(blob.as_ckzg())?;
859
860 Ok(Some(Self::blob_cells_and_proofs_from_computed_cells(cell_mask, cells.as_ref(), proofs)))
861 }
862
863 #[cfg(feature = "kzg")]
865 fn blob_cells_and_proofs_from_computed_cells(
866 cell_mask: BlobCellMask,
867 cells: &[c_kzg::Cell],
868 proofs: &[Bytes48],
869 ) -> crate::eip4844::BlobCellsAndProofsV1 {
870 let mut blob_cells = Vec::with_capacity(cell_mask.count());
873 let mut selected_proofs = Vec::with_capacity(cell_mask.count());
874 for cell_index in cell_mask.selected_indices() {
875 blob_cells
876 .push(cells.get(cell_index).map(|cell| crate::eip7594::Cell::new(cell.to_bytes())));
877 selected_proofs.push(proofs.get(cell_index).copied());
878 }
879
880 crate::eip4844::BlobCellsAndProofsV1 { blob_cells, proofs: selected_proofs }
881 }
882
883 pub fn match_versioned_hashes<'a>(
889 &'a self,
890 versioned_hashes: &'a [B256],
891 ) -> impl Iterator<Item = (usize, BlobAndProofV2)> + 'a {
892 self.versioned_hashes().enumerate().flat_map(move |(i, blob_versioned_hash)| {
893 versioned_hashes.iter().enumerate().filter_map(move |(j, target_hash)| {
894 if blob_versioned_hash == *target_hash {
895 let maybe_blob = self.blobs.get(i);
896 let proof_range = i * CELLS_PER_EXT_BLOB..(i + 1) * CELLS_PER_EXT_BLOB;
897 let maybe_proofs = self
898 .cell_proofs
899 .get(proof_range)
900 .filter(|proofs| proofs.len() == CELLS_PER_EXT_BLOB);
901 if let Some((blob, proofs)) = maybe_blob.copied().zip(maybe_proofs) {
902 return Some((
903 j,
904 BlobAndProofV2 { blob: Box::new(blob), proofs: proofs.to_vec() },
905 ));
906 }
907 }
908 None
909 })
910 })
911 }
912
913 #[cfg(feature = "kzg")]
921 pub fn match_versioned_hashes_cells<'a>(
922 &'a self,
923 versioned_hashes: &'a [B256],
924 cell_mask: BlobCellMask,
925 ) -> Result<
926 impl Iterator<Item = (usize, crate::eip4844::BlobCellsAndProofsV1)> + 'a,
927 c_kzg::Error,
928 > {
929 use crate::eip4844::env_settings::EnvKzgSettings;
930
931 self.match_versioned_hashes_cells_with_settings(
932 versioned_hashes,
933 cell_mask,
934 EnvKzgSettings::Default.get(),
935 )
936 }
937
938 #[cfg(feature = "kzg")]
942 pub fn match_versioned_hashes_cells_with_settings<'a>(
943 &'a self,
944 versioned_hashes: &'a [B256],
945 cell_mask: BlobCellMask,
946 settings: &c_kzg::KzgSettings,
947 ) -> Result<
948 impl Iterator<Item = (usize, crate::eip4844::BlobCellsAndProofsV1)> + 'a,
949 c_kzg::Error,
950 > {
951 let mut matches = Vec::new();
952 let mut cells_and_proofs_by_blob =
953 Vec::<(usize, crate::eip4844::BlobCellsAndProofsV1)>::new();
954
955 for (blob_index, commitment) in self.commitments.iter().enumerate() {
956 let blob_versioned_hash = crate::eip4844::kzg_to_versioned_hash(commitment.as_slice());
957 for (matched_index, target_hash) in versioned_hashes.iter().enumerate() {
958 if blob_versioned_hash != *target_hash {
959 continue;
960 }
961
962 let Some(blob) = self.blobs.get(blob_index) else { continue };
963 let proof_start = blob_index * CELLS_PER_EXT_BLOB;
964 let Some(proofs) =
965 self.cell_proofs.get(proof_start..proof_start + CELLS_PER_EXT_BLOB)
966 else {
967 continue;
968 };
969
970 let cells_and_proofs = if cell_mask.count() == 0 {
971 crate::eip4844::BlobCellsAndProofsV1::default()
972 } else if let Some((_, cells_and_proofs)) =
973 cells_and_proofs_by_blob.iter().find(|(index, _)| *index == blob_index)
974 {
975 cells_and_proofs.clone()
976 } else {
977 let cells = settings.compute_cells(blob.as_ckzg())?;
978 let cells_and_proofs = Self::blob_cells_and_proofs_from_computed_cells(
979 cell_mask,
980 cells.as_ref(),
981 proofs,
982 );
983 cells_and_proofs_by_blob.push((blob_index, cells_and_proofs.clone()));
984 cells_and_proofs
985 };
986
987 matches.push((matched_index, cells_and_proofs));
988 }
989 }
990
991 Ok(matches.into_iter())
992 }
993
994 #[doc(hidden)]
996 pub fn rlp_encoded_fields_length(&self) -> usize {
997 1 + self.blobs.length() + self.commitments.length() + self.cell_proofs.length()
999 }
1000
1001 #[inline]
1010 #[doc(hidden)]
1011 pub fn rlp_encode_fields(&self, out: &mut dyn BufMut) {
1012 out.put_u8(EIP_7594_WRAPPER_VERSION);
1014 self.blobs.encode(out);
1016 self.commitments.encode(out);
1017 self.cell_proofs.encode(out);
1018 }
1019
1020 fn rlp_header(&self) -> Header {
1022 Header { list: true, payload_length: self.rlp_encoded_fields_length() }
1023 }
1024
1025 pub fn rlp_encoded_length(&self) -> usize {
1028 self.rlp_header().length() + self.rlp_encoded_fields_length()
1029 }
1030
1031 pub fn rlp_encode(&self, out: &mut dyn BufMut) {
1033 self.rlp_header().encode(out);
1034 self.rlp_encode_fields(out);
1035 }
1036
1037 #[doc(hidden)]
1039 pub fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
1040 Ok(Self {
1041 blobs: Decodable::decode(buf)?,
1042 commitments: Decodable::decode(buf)?,
1043 cell_proofs: Decodable::decode(buf)?,
1044 })
1045 }
1046
1047 pub fn rlp_decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
1049 let header = Header::decode(buf)?;
1050 if !header.list {
1051 return Err(alloy_rlp::Error::UnexpectedString);
1052 }
1053 if buf.len() < header.payload_length {
1054 return Err(alloy_rlp::Error::InputTooShort);
1055 }
1056 let remaining = buf.len();
1057
1058 let this = Self::decode_7594(buf)?;
1059 if buf.len() + header.payload_length != remaining {
1060 return Err(alloy_rlp::Error::UnexpectedLength);
1061 }
1062
1063 Ok(this)
1064 }
1065}
1066
1067impl Encodable for BlobTransactionSidecarEip7594 {
1068 fn encode(&self, out: &mut dyn BufMut) {
1070 self.rlp_encode(out);
1071 }
1072
1073 fn length(&self) -> usize {
1074 self.rlp_encoded_length()
1075 }
1076}
1077
1078impl Decodable for BlobTransactionSidecarEip7594 {
1079 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
1082 Self::rlp_decode(buf)
1083 }
1084}
1085
1086impl Encodable7594 for BlobTransactionSidecarEip7594 {
1087 fn encode_7594_len(&self) -> usize {
1088 self.rlp_encoded_fields_length()
1089 }
1090
1091 fn encode_7594(&self, out: &mut dyn BufMut) {
1092 self.rlp_encode_fields(out);
1093 }
1094}
1095
1096impl Decodable7594 for BlobTransactionSidecarEip7594 {
1097 fn decode_7594(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
1098 let wrapper_version: u8 = Decodable::decode(buf)?;
1099 if wrapper_version != EIP_7594_WRAPPER_VERSION {
1100 return Err(alloy_rlp::Error::Custom("invalid wrapper version"));
1101 }
1102 Self::rlp_decode_fields(buf)
1103 }
1104}
1105
1106#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
1108pub struct BlobCellMask {
1109 value: u128,
1110}
1111
1112impl BlobCellMask {
1113 #[inline]
1115 pub fn new(indices_bitarray: B128) -> Self {
1116 Self { value: u128::from(indices_bitarray) }
1117 }
1118
1119 #[inline]
1121 pub const fn from_bits(value: u128) -> Self {
1122 Self { value }
1123 }
1124
1125 #[inline]
1127 pub const fn bits(self) -> u128 {
1128 self.value
1129 }
1130
1131 #[inline]
1133 pub const fn count(self) -> usize {
1134 self.value.count_ones() as usize
1135 }
1136
1137 #[inline]
1139 pub const fn contains(self, index: usize) -> bool {
1140 index < CELLS_PER_EXT_BLOB && self.value & (1u128 << index) != 0
1141 }
1142
1143 #[inline]
1145 pub fn selected_indices(self) -> impl Iterator<Item = usize> {
1146 let mut bits = self.value;
1147 core::iter::from_fn(move || {
1148 if bits == 0 {
1149 return None;
1150 }
1151
1152 let index = bits.trailing_zeros() as usize;
1153 bits &= bits - 1;
1154 Some(index)
1155 })
1156 }
1157
1158 pub fn matching_cells_from_computed_cells(
1168 self,
1169 cells: &[crate::eip7594::Cell],
1170 ) -> Option<Vec<crate::eip7594::Cell>> {
1171 let chunks = cells.chunks_exact(CELLS_PER_EXT_BLOB);
1172 if !chunks.remainder().is_empty() {
1173 return None;
1174 }
1175
1176 let mut matching_cells = Vec::with_capacity(chunks.len() * self.count());
1177 for blob_cells in chunks {
1178 for cell_index in self.selected_indices() {
1179 let cell = blob_cells
1180 .get(cell_index)
1181 .expect("cell mask index must be within extended blob cells");
1182 matching_cells.push(*cell);
1183 }
1184 }
1185
1186 Some(matching_cells)
1187 }
1188}
1189
1190#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
1192pub mod serde_bincode_compat {
1193 use crate::eip4844::{Blob, Bytes48};
1194 use alloc::{borrow::Cow, vec::Vec};
1195 use serde::{Deserialize, Deserializer, Serialize, Serializer};
1196 use serde_with::{DeserializeAs, SerializeAs};
1197
1198 #[derive(Debug, Serialize, Deserialize)]
1214 pub struct BlobTransactionSidecarVariant<'a> {
1215 pub blobs: Cow<'a, Vec<Blob>>,
1217 pub commitments: Cow<'a, Vec<Bytes48>>,
1219 pub proofs: Option<Cow<'a, Vec<Bytes48>>>,
1221 pub cell_proofs: Option<Cow<'a, Vec<Bytes48>>>,
1223 }
1224
1225 impl<'a> From<&'a super::BlobTransactionSidecarVariant> for BlobTransactionSidecarVariant<'a> {
1226 fn from(value: &'a super::BlobTransactionSidecarVariant) -> Self {
1227 match value {
1228 super::BlobTransactionSidecarVariant::Eip4844(sidecar) => Self {
1229 blobs: Cow::Borrowed(&sidecar.blobs),
1230 commitments: Cow::Borrowed(&sidecar.commitments),
1231 proofs: Some(Cow::Borrowed(&sidecar.proofs)),
1232 cell_proofs: None,
1233 },
1234 super::BlobTransactionSidecarVariant::Eip7594(sidecar) => Self {
1235 blobs: Cow::Borrowed(&sidecar.blobs),
1236 commitments: Cow::Borrowed(&sidecar.commitments),
1237 proofs: None,
1238 cell_proofs: Some(Cow::Borrowed(&sidecar.cell_proofs)),
1239 },
1240 }
1241 }
1242 }
1243
1244 impl<'a> BlobTransactionSidecarVariant<'a> {
1245 fn try_into_inner(self) -> Result<super::BlobTransactionSidecarVariant, &'static str> {
1246 match (self.proofs, self.cell_proofs) {
1247 (Some(proofs), None) => Ok(super::BlobTransactionSidecarVariant::Eip4844(
1248 crate::eip4844::BlobTransactionSidecar {
1249 blobs: self.blobs.into_owned(),
1250 commitments: self.commitments.into_owned(),
1251 proofs: proofs.into_owned(),
1252 },
1253 )),
1254 (None, Some(cell_proofs)) => Ok(super::BlobTransactionSidecarVariant::Eip7594(
1255 super::BlobTransactionSidecarEip7594 {
1256 blobs: self.blobs.into_owned(),
1257 commitments: self.commitments.into_owned(),
1258 cell_proofs: cell_proofs.into_owned(),
1259 },
1260 )),
1261 (None, None) => Err("Missing both 'proofs' and 'cell_proofs'"),
1262 (Some(_), Some(_)) => Err("Both 'proofs' and 'cell_proofs' cannot be present"),
1263 }
1264 }
1265 }
1266
1267 impl<'a> From<BlobTransactionSidecarVariant<'a>> for super::BlobTransactionSidecarVariant {
1268 fn from(value: BlobTransactionSidecarVariant<'a>) -> Self {
1269 value.try_into_inner().expect("Invalid BlobTransactionSidecarVariant")
1270 }
1271 }
1272
1273 impl SerializeAs<super::BlobTransactionSidecarVariant> for BlobTransactionSidecarVariant<'_> {
1274 fn serialize_as<S>(
1275 source: &super::BlobTransactionSidecarVariant,
1276 serializer: S,
1277 ) -> Result<S::Ok, S::Error>
1278 where
1279 S: Serializer,
1280 {
1281 BlobTransactionSidecarVariant::from(source).serialize(serializer)
1282 }
1283 }
1284
1285 impl<'de> DeserializeAs<'de, super::BlobTransactionSidecarVariant>
1286 for BlobTransactionSidecarVariant<'de>
1287 {
1288 fn deserialize_as<D>(
1289 deserializer: D,
1290 ) -> Result<super::BlobTransactionSidecarVariant, D::Error>
1291 where
1292 D: Deserializer<'de>,
1293 {
1294 let value = BlobTransactionSidecarVariant::deserialize(deserializer)?;
1295 value.try_into_inner().map_err(serde::de::Error::custom)
1296 }
1297 }
1298}
1299
1300#[cfg(test)]
1301mod tests {
1302 use super::*;
1303 #[cfg(feature = "kzg")]
1304 use crate::eip4844::{
1305 builder::{SidecarBuilder, SimpleCoder},
1306 env_settings::EnvKzgSettings,
1307 };
1308
1309 #[test]
1310 fn sidecar_variant_rlp_roundtrip() {
1311 let mut encoded = Vec::new();
1312
1313 let empty_sidecar_4844 =
1315 BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar::default());
1316 empty_sidecar_4844.encode(&mut encoded);
1317 assert_eq!(
1318 empty_sidecar_4844,
1319 BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap()
1320 );
1321
1322 let sidecar_4844 = BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar::new(
1323 vec![Blob::default()],
1324 vec![Bytes48::ZERO],
1325 vec![Bytes48::ZERO],
1326 ));
1327 encoded.clear();
1328 sidecar_4844.encode(&mut encoded);
1329 assert_eq!(sidecar_4844, BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap());
1330
1331 let empty_sidecar_7594 =
1333 BlobTransactionSidecarVariant::Eip7594(BlobTransactionSidecarEip7594::default());
1334 encoded.clear();
1335 empty_sidecar_7594.encode(&mut encoded);
1336 assert_eq!(
1337 empty_sidecar_7594,
1338 BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap()
1339 );
1340
1341 let sidecar_7594 =
1342 BlobTransactionSidecarVariant::Eip7594(BlobTransactionSidecarEip7594::new(
1343 vec![Blob::default()],
1344 vec![Bytes48::ZERO],
1345 core::iter::repeat_n(Bytes48::ZERO, CELLS_PER_EXT_BLOB).collect(),
1346 ));
1347 encoded.clear();
1348 sidecar_7594.encode(&mut encoded);
1349 assert_eq!(sidecar_7594, BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap());
1350 }
1351
1352 #[test]
1353 #[cfg(feature = "serde")]
1354 fn sidecar_variant_json_deserialize_sanity() {
1355 let mut eip4844 = BlobTransactionSidecar::default();
1356 eip4844.blobs.push(Blob::repeat_byte(0x2));
1357
1358 let json = serde_json::to_string(&eip4844).unwrap();
1359 let variant: BlobTransactionSidecarVariant = serde_json::from_str(&json).unwrap();
1360 assert!(variant.is_eip4844());
1361 let jsonvariant = serde_json::to_string(&variant).unwrap();
1362 assert_eq!(json, jsonvariant);
1363
1364 let mut eip7594 = BlobTransactionSidecarEip7594::default();
1365 eip7594.blobs.push(Blob::repeat_byte(0x4));
1366 let json = serde_json::to_string(&eip7594).unwrap();
1367 let variant: BlobTransactionSidecarVariant = serde_json::from_str(&json).unwrap();
1368 assert!(variant.is_eip7594());
1369 let jsonvariant = serde_json::to_string(&variant).unwrap();
1370 assert_eq!(json, jsonvariant);
1371 }
1372
1373 #[test]
1374 fn rlp_7594_roundtrip() {
1375 let mut encoded = Vec::new();
1376
1377 let sidecar_4844 = BlobTransactionSidecar::default();
1378 sidecar_4844.encode_7594(&mut encoded);
1379 assert_eq!(sidecar_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1380
1381 let sidecar_variant_4844 = BlobTransactionSidecarVariant::Eip4844(sidecar_4844);
1382 assert_eq!(sidecar_variant_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1383 encoded.clear();
1384 sidecar_variant_4844.encode_7594(&mut encoded);
1385 assert_eq!(sidecar_variant_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1386
1387 let sidecar_7594 = BlobTransactionSidecarEip7594::default();
1388 encoded.clear();
1389 sidecar_7594.encode_7594(&mut encoded);
1390 assert_eq!(sidecar_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1391
1392 let sidecar_variant_7594 = BlobTransactionSidecarVariant::Eip7594(sidecar_7594);
1393 assert_eq!(sidecar_variant_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1394 encoded.clear();
1395 sidecar_variant_7594.encode_7594(&mut encoded);
1396 assert_eq!(sidecar_variant_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1397 }
1398
1399 #[test]
1400 #[cfg(feature = "kzg")]
1401 fn validate_7594_sidecar() {
1402 let sidecar =
1403 SidecarBuilder::<SimpleCoder>::from_slice(b"Blobs are fun!").build_7594().unwrap();
1404 let versioned_hashes = sidecar.versioned_hashes().collect::<Vec<_>>();
1405
1406 sidecar.validate(&versioned_hashes, EnvKzgSettings::Default.get()).unwrap();
1407 }
1408
1409 #[test]
1410 #[cfg(feature = "kzg")]
1411 fn compute_cells_for_7594_sidecar() {
1412 let settings = EnvKzgSettings::Default.get();
1413 let sidecar = BlobTransactionSidecarEip7594::try_from_blobs_with_settings(
1414 vec![Blob::repeat_byte(0x01), Blob::repeat_byte(0x02)],
1415 settings,
1416 )
1417 .unwrap();
1418
1419 let cells = sidecar.compute_cells_with_settings(settings).unwrap();
1420 assert_eq!(cells.len(), sidecar.blobs.len() * CELLS_PER_EXT_BLOB);
1421 assert_eq!(sidecar.compute_cells().unwrap(), cells);
1422
1423 let cell_mask = BlobCellMask::from_bits((1u128 << 0) | (1u128 << 7));
1424 let matching_cells =
1425 sidecar.compute_matching_cells_with_settings(cell_mask, settings).unwrap();
1426 let expected_matching_cells = cells
1427 .chunks_exact(CELLS_PER_EXT_BLOB)
1428 .flat_map(|blob_cells| [blob_cells[0], blob_cells[7]])
1429 .collect::<Vec<_>>();
1430 assert_eq!(
1431 cell_mask.matching_cells_from_computed_cells(&cells),
1432 Some(expected_matching_cells.clone())
1433 );
1434 assert_eq!(matching_cells, expected_matching_cells);
1435 assert_eq!(sidecar.compute_matching_cells(cell_mask).unwrap(), expected_matching_cells);
1436 assert!(sidecar.compute_matching_cells(BlobCellMask::default()).unwrap().is_empty());
1437
1438 for (blob_index, blob) in sidecar.blobs.iter().enumerate() {
1439 let expected_cells = settings.compute_cells(blob.as_ckzg()).unwrap();
1440 let start = blob_index * CELLS_PER_EXT_BLOB;
1441 let end = start + CELLS_PER_EXT_BLOB;
1442
1443 for (cell, expected_cell) in cells[start..end].iter().zip(expected_cells.iter()) {
1444 assert_eq!(*cell, crate::eip7594::Cell::new(expected_cell.to_bytes()));
1445 }
1446 }
1447 }
1448
1449 #[test]
1450 fn blob_cell_mask_selects_indices() {
1451 let selected = (1u128 << 0) | (1u128 << 7);
1452 let mask = BlobCellMask::new(B128::from(selected));
1453
1454 assert_eq!(mask.bits(), selected);
1455 assert_eq!(mask.count(), 2);
1456 assert!(mask.contains(0));
1457 assert!(mask.contains(7));
1458 assert!(!mask.contains(1));
1459 assert_eq!(mask.selected_indices().collect::<Vec<_>>(), vec![0, 7]);
1460
1461 let cells = (0..CELLS_PER_EXT_BLOB * 2)
1462 .map(|i| crate::eip7594::Cell::repeat_byte(i as u8))
1463 .collect::<Vec<_>>();
1464 assert_eq!(
1465 mask.matching_cells_from_computed_cells(&cells),
1466 Some(vec![
1467 cells[0],
1468 cells[7],
1469 cells[CELLS_PER_EXT_BLOB],
1470 cells[CELLS_PER_EXT_BLOB + 7]
1471 ])
1472 );
1473 assert_eq!(mask.matching_cells_from_computed_cells(&cells[..cells.len() - 1]), None);
1474 }
1475
1476 #[test]
1477 fn match_versioned_hashes_skips_incomplete_proof_chunks() {
1478 let sidecar = BlobTransactionSidecarEip7594::new(
1479 vec![Blob::repeat_byte(0x01)],
1480 vec![Bytes48::repeat_byte(0x02)],
1481 vec![Bytes48::repeat_byte(0x03)],
1482 );
1483 let versioned_hash = sidecar.versioned_hashes().next().unwrap();
1484
1485 let matches = sidecar.match_versioned_hashes(&[versioned_hash]).collect::<Vec<_>>();
1486 assert!(matches.is_empty());
1487 }
1488
1489 #[test]
1490 #[cfg(feature = "kzg")]
1491 fn match_versioned_hashes_cells_for_7594_sidecar() {
1492 let settings = EnvKzgSettings::Default.get();
1493 let sidecar = BlobTransactionSidecarEip7594::try_from_blobs_with_settings(
1494 vec![Blob::repeat_byte(0x01), Blob::repeat_byte(0x02)],
1495 settings,
1496 )
1497 .unwrap();
1498 let versioned_hashes = sidecar.versioned_hashes().collect::<Vec<_>>();
1499 let cell_mask = BlobCellMask::from_bits((1u128 << 0) | (1u128 << 7));
1500
1501 let cells_and_proofs =
1502 sidecar.blob_cells_and_proofs_with_settings(0, cell_mask, settings).unwrap().unwrap();
1503 assert_eq!(cells_and_proofs.blob_cells.len(), 2);
1504 assert_eq!(cells_and_proofs.proofs.len(), 2);
1505 assert_eq!(
1506 cells_and_proofs.proofs,
1507 vec![Some(sidecar.cell_proofs[0]), Some(sidecar.cell_proofs[7])]
1508 );
1509
1510 let expected_cells = settings.compute_cells(sidecar.blobs[0].as_ckzg()).unwrap();
1511 assert_eq!(
1512 cells_and_proofs.blob_cells,
1513 vec![
1514 Some(crate::eip7594::Cell::new(expected_cells[0].to_bytes())),
1515 Some(crate::eip7594::Cell::new(expected_cells[7].to_bytes()))
1516 ]
1517 );
1518
1519 let request = vec![versioned_hashes[0], B256::ZERO, versioned_hashes[0]];
1520 let matches = sidecar
1521 .match_versioned_hashes_cells_with_settings(&request, cell_mask, settings)
1522 .unwrap()
1523 .collect::<Vec<_>>();
1524 assert_eq!(matches.len(), 2);
1525 assert_eq!(matches[0], (0, cells_and_proofs.clone()));
1526 assert_eq!(matches[1], (2, cells_and_proofs.clone()));
1527
1528 let default_matches = sidecar
1529 .match_versioned_hashes_cells(&[versioned_hashes[0]], cell_mask)
1530 .unwrap()
1531 .collect::<Vec<_>>();
1532 assert_eq!(default_matches, vec![(0, cells_and_proofs)]);
1533 }
1534
1535 #[test]
1536 #[cfg(feature = "kzg")]
1537 fn match_versioned_hashes_cells_only_computes_matched_blobs() {
1538 let settings = EnvKzgSettings::Default.get();
1539 let mut sidecar = BlobTransactionSidecarEip7594::try_from_blobs_with_settings(
1540 vec![Blob::repeat_byte(0x01)],
1541 settings,
1542 )
1543 .unwrap();
1544 let versioned_hash = sidecar.versioned_hashes().next().unwrap();
1545 let cell_mask = BlobCellMask::from_bits(1);
1546
1547 let invalid_blob = Blob::repeat_byte(0xff);
1548 assert!(settings.compute_cells(invalid_blob.as_ckzg()).is_err());
1549
1550 sidecar.blobs.push(invalid_blob);
1551 sidecar.commitments.push(Bytes48::ZERO);
1552 sidecar.cell_proofs.extend(core::iter::repeat_n(Bytes48::ZERO, CELLS_PER_EXT_BLOB));
1553
1554 let cells_and_proofs =
1555 sidecar.blob_cells_and_proofs_with_settings(0, cell_mask, settings).unwrap().unwrap();
1556 let matches = sidecar
1557 .match_versioned_hashes_cells_with_settings(&[versioned_hash], cell_mask, settings)
1558 .unwrap()
1559 .collect::<Vec<_>>();
1560 assert_eq!(matches, vec![(0, cells_and_proofs)]);
1561 }
1562}