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 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")]
102 pub fn validate(
103 &self,
104 blob_versioned_hashes: &[B256],
105 proof_settings: &c_kzg::KzgSettings,
106 ) -> Result<(), BlobTransactionValidationError> {
107 match self {
108 Self::Eip4844(sidecar) => sidecar.validate(blob_versioned_hashes, proof_settings),
109 Self::Eip7594(sidecar) => sidecar.validate(blob_versioned_hashes, proof_settings),
110 }
111 }
112
113 pub fn commitments(&self) -> &[Bytes48] {
115 match self {
116 Self::Eip4844(sidecar) => &sidecar.commitments,
117 Self::Eip7594(sidecar) => &sidecar.commitments,
118 }
119 }
120
121 pub fn versioned_hashes(&self) -> VersionedHashIter<'_> {
123 VersionedHashIter::new(self.commitments())
124 }
125
126 pub fn versioned_hash_index(&self, hash: &B256) -> Option<usize> {
128 match self {
129 Self::Eip4844(s) => s.versioned_hash_index(hash),
130 Self::Eip7594(s) => s.versioned_hash_index(hash),
131 }
132 }
133
134 pub fn blob_by_versioned_hash(&self, hash: &B256) -> Option<&Blob> {
136 match self {
137 Self::Eip4844(s) => s.blob_by_versioned_hash(hash),
138 Self::Eip7594(s) => s.blob_by_versioned_hash(hash),
139 }
140 }
141
142 #[doc(hidden)]
144 pub fn rlp_encoded_fields_length(&self) -> usize {
145 match self {
146 Self::Eip4844(sidecar) => sidecar.rlp_encoded_fields_length(),
147 Self::Eip7594(sidecar) => sidecar.rlp_encoded_fields_length(),
148 }
149 }
150
151 #[inline]
153 #[doc(hidden)]
154 pub fn rlp_encoded_fields(&self) -> Vec<u8> {
155 let mut buf = Vec::with_capacity(self.rlp_encoded_fields_length());
156 self.rlp_encode_fields(&mut buf);
157 buf
158 }
159
160 #[inline]
163 #[doc(hidden)]
164 pub fn rlp_encode_fields(&self, out: &mut dyn BufMut) {
165 match self {
166 Self::Eip4844(sidecar) => sidecar.rlp_encode_fields(out),
167 Self::Eip7594(sidecar) => sidecar.rlp_encode_fields(out),
168 }
169 }
170
171 #[doc(hidden)]
173 pub fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
174 Self::decode_7594(buf)
175 }
176}
177
178impl Encodable for BlobTransactionSidecarVariant {
179 fn encode(&self, out: &mut dyn BufMut) {
181 match self {
182 Self::Eip4844(sidecar) => sidecar.encode(out),
183 Self::Eip7594(sidecar) => sidecar.encode(out),
184 }
185 }
186
187 fn length(&self) -> usize {
188 match self {
189 Self::Eip4844(sidecar) => sidecar.rlp_encoded_length(),
190 Self::Eip7594(sidecar) => sidecar.rlp_encoded_length(),
191 }
192 }
193}
194
195impl Decodable for BlobTransactionSidecarVariant {
196 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
198 let header = Header::decode(buf)?;
199 if !header.list {
200 return Err(alloy_rlp::Error::UnexpectedString);
201 }
202 if buf.len() < header.payload_length {
203 return Err(alloy_rlp::Error::InputTooShort);
204 }
205 let remaining = buf.len();
206 let this = Self::rlp_decode_fields(buf)?;
207 if buf.len() + header.payload_length != remaining {
208 return Err(alloy_rlp::Error::UnexpectedLength);
209 }
210
211 Ok(this)
212 }
213}
214
215impl Encodable7594 for BlobTransactionSidecarVariant {
216 fn encode_7594_len(&self) -> usize {
217 self.rlp_encoded_fields_length()
218 }
219
220 fn encode_7594(&self, out: &mut dyn BufMut) {
221 self.rlp_encode_fields(out);
222 }
223}
224
225impl Decodable7594 for BlobTransactionSidecarVariant {
226 fn decode_7594(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
227 if buf.first() == Some(&EIP_7594_WRAPPER_VERSION) {
228 Ok(Self::Eip7594(Decodable7594::decode_7594(buf)?))
229 } else {
230 Ok(Self::Eip4844(Decodable7594::decode_7594(buf)?))
231 }
232 }
233}
234
235#[cfg(feature = "serde")]
236impl<'de> serde::Deserialize<'de> for BlobTransactionSidecarVariant {
237 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
238 where
239 D: serde::Deserializer<'de>,
240 {
241 use core::fmt;
242
243 #[derive(serde::Deserialize, fmt::Debug)]
244 #[serde(field_identifier, rename_all = "camelCase")]
245 enum Field {
246 Blobs,
247 Commitments,
248 Proofs,
249 CellProofs,
250 }
251
252 struct VariantVisitor;
253
254 impl<'de> serde::de::Visitor<'de> for VariantVisitor {
255 type Value = BlobTransactionSidecarVariant;
256
257 fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
258 formatter
259 .write_str("a valid blob transaction sidecar (EIP-4844 or EIP-7594 variant)")
260 }
261
262 fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
263 where
264 M: serde::de::MapAccess<'de>,
265 {
266 let mut blobs = None;
267 let mut commitments = None;
268 let mut proofs = None;
269 let mut cell_proofs = None;
270
271 while let Some(key) = map.next_key()? {
272 match key {
273 Field::Blobs => {
274 blobs = Some(crate::eip4844::deserialize_blobs_map(&mut map)?);
275 }
276 Field::Commitments => commitments = Some(map.next_value()?),
277 Field::Proofs => proofs = Some(map.next_value()?),
278 Field::CellProofs => cell_proofs = Some(map.next_value()?),
279 }
280 }
281
282 let blobs = blobs.ok_or_else(|| serde::de::Error::missing_field("blobs"))?;
283 let commitments =
284 commitments.ok_or_else(|| serde::de::Error::missing_field("commitments"))?;
285
286 match (cell_proofs, proofs) {
287 (Some(cp), None) => {
288 Ok(BlobTransactionSidecarVariant::Eip7594(BlobTransactionSidecarEip7594 {
289 blobs,
290 commitments,
291 cell_proofs: cp,
292 }))
293 }
294 (None, Some(pf)) => {
295 Ok(BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar {
296 blobs,
297 commitments,
298 proofs: pf,
299 }))
300 }
301 (None, None) => {
302 Err(serde::de::Error::custom("Missing 'cellProofs' or 'proofs'"))
303 }
304 (Some(_), Some(_)) => Err(serde::de::Error::custom(
305 "Both 'cellProofs' and 'proofs' cannot be present",
306 )),
307 }
308 }
309 }
310
311 const FIELDS: &[&str] = &["blobs", "commitments", "proofs", "cellProofs"];
312 deserializer.deserialize_struct("BlobTransactionSidecarVariant", FIELDS, VariantVisitor)
313 }
314}
315
316#[derive(Clone, Default, PartialEq, Eq, Hash)]
320#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
321#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
322#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
323pub struct BlobTransactionSidecarEip7594 {
324 #[cfg_attr(feature = "serde", serde(deserialize_with = "crate::eip4844::deserialize_blobs"))]
326 pub blobs: Vec<Blob>,
327 pub commitments: Vec<Bytes48>,
329 pub cell_proofs: Vec<Bytes48>,
334}
335
336impl core::fmt::Debug for BlobTransactionSidecarEip7594 {
337 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
338 f.debug_struct("BlobTransactionSidecarEip7594")
339 .field("blobs", &self.blobs.len())
340 .field("commitments", &self.commitments)
341 .field("cell_proofs", &self.cell_proofs)
342 .finish()
343 }
344}
345
346impl BlobTransactionSidecarEip7594 {
347 pub const fn new(
350 blobs: Vec<Blob>,
351 commitments: Vec<Bytes48>,
352 cell_proofs: Vec<Bytes48>,
353 ) -> Self {
354 Self { blobs, commitments, cell_proofs }
355 }
356
357 #[inline]
359 pub fn size(&self) -> usize {
360 self.blobs.len() * BYTES_PER_BLOB + self.commitments.len() * BYTES_PER_COMMITMENT + self.cell_proofs.len() * BYTES_PER_PROOF }
364
365 #[cfg(feature = "kzg")]
379 pub fn validate(
380 &self,
381 blob_versioned_hashes: &[B256],
382 proof_settings: &c_kzg::KzgSettings,
383 ) -> Result<(), BlobTransactionValidationError> {
384 if blob_versioned_hashes.len() != self.commitments.len() {
386 return Err(c_kzg::Error::MismatchLength(format!(
387 "There are {} versioned commitment hashes and {} commitments",
388 blob_versioned_hashes.len(),
389 self.commitments.len()
390 ))
391 .into());
392 }
393
394 let blobs_len = self.blobs.len();
395 let expected_cell_proofs_len = blobs_len * CELLS_PER_EXT_BLOB;
396 if self.cell_proofs.len() != expected_cell_proofs_len {
397 return Err(c_kzg::Error::MismatchLength(format!(
398 "There are {} cell proofs and {} blobs. Expected {} cell proofs.",
399 self.cell_proofs.len(),
400 blobs_len,
401 expected_cell_proofs_len
402 ))
403 .into());
404 }
405
406 for (versioned_hash, commitment) in
408 blob_versioned_hashes.iter().zip(self.commitments.iter())
409 {
410 let calculated_versioned_hash =
412 crate::eip4844::kzg_to_versioned_hash(commitment.as_slice());
413 if *versioned_hash != calculated_versioned_hash {
414 return Err(BlobTransactionValidationError::WrongVersionedHash {
415 have: *versioned_hash,
416 expected: calculated_versioned_hash,
417 });
418 }
419 }
420
421 let cell_indices =
423 Vec::from_iter((0..blobs_len).flat_map(|_| 0..CELLS_PER_EXT_BLOB as u64));
424
425 let mut commitments = Vec::with_capacity(blobs_len * CELLS_PER_EXT_BLOB);
427 for commitment in &self.commitments {
428 commitments.extend(core::iter::repeat_n(*commitment, CELLS_PER_EXT_BLOB));
429 }
430
431 let res = unsafe {
433 let mut cells = Vec::with_capacity(blobs_len * CELLS_PER_EXT_BLOB);
434 for blob in &self.blobs {
435 let blob = core::mem::transmute::<&Blob, &c_kzg::Blob>(blob);
436 cells.extend(proof_settings.compute_cells(blob)?.into_iter());
437 }
438
439 proof_settings.verify_cell_kzg_proof_batch(
440 core::mem::transmute::<&[Bytes48], &[c_kzg::Bytes48]>(&commitments),
442 &cell_indices,
444 &cells,
446 core::mem::transmute::<&[Bytes48], &[c_kzg::Bytes48]>(self.cell_proofs.as_slice()),
448 )?
449 };
450
451 res.then_some(()).ok_or(BlobTransactionValidationError::InvalidProof)
452 }
453
454 pub fn versioned_hashes(&self) -> VersionedHashIter<'_> {
456 VersionedHashIter::new(&self.commitments)
457 }
458
459 pub fn versioned_hash_index(&self, hash: &B256) -> Option<usize> {
461 self.commitments.iter().position(|commitment| {
462 crate::eip4844::kzg_to_versioned_hash(commitment.as_slice()) == *hash
463 })
464 }
465
466 pub fn blob_by_versioned_hash(&self, hash: &B256) -> Option<&Blob> {
468 self.versioned_hash_index(hash).and_then(|index| self.blobs.get(index))
469 }
470
471 pub fn match_versioned_hashes<'a>(
477 &'a self,
478 versioned_hashes: &'a [B256],
479 ) -> impl Iterator<Item = (usize, BlobAndProofV2)> + 'a {
480 self.versioned_hashes().enumerate().flat_map(move |(i, blob_versioned_hash)| {
481 versioned_hashes.iter().enumerate().filter_map(move |(j, target_hash)| {
482 if blob_versioned_hash == *target_hash {
483 let maybe_blob = self.blobs.get(i);
484 let proof_range = i * CELLS_PER_EXT_BLOB..(i + 1) * CELLS_PER_EXT_BLOB;
485 let maybe_proofs = Some(&self.cell_proofs[proof_range])
486 .filter(|proofs| proofs.len() == CELLS_PER_EXT_BLOB);
487 if let Some((blob, proofs)) = maybe_blob.copied().zip(maybe_proofs) {
488 return Some((
489 j,
490 BlobAndProofV2 { blob: Box::new(blob), proofs: proofs.to_vec() },
491 ));
492 }
493 }
494 None
495 })
496 })
497 }
498
499 #[doc(hidden)]
501 pub fn rlp_encoded_fields_length(&self) -> usize {
502 1 + self.blobs.length() + self.commitments.length() + self.cell_proofs.length()
504 }
505
506 #[inline]
515 #[doc(hidden)]
516 pub fn rlp_encode_fields(&self, out: &mut dyn BufMut) {
517 out.put_u8(EIP_7594_WRAPPER_VERSION);
519 self.blobs.encode(out);
521 self.commitments.encode(out);
522 self.cell_proofs.encode(out);
523 }
524
525 fn rlp_header(&self) -> Header {
527 Header { list: true, payload_length: self.rlp_encoded_fields_length() }
528 }
529
530 pub fn rlp_encoded_length(&self) -> usize {
533 self.rlp_header().length() + self.rlp_encoded_fields_length()
534 }
535
536 pub fn rlp_encode(&self, out: &mut dyn BufMut) {
538 self.rlp_header().encode(out);
539 self.rlp_encode_fields(out);
540 }
541
542 #[doc(hidden)]
544 pub fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
545 Ok(Self {
546 blobs: Decodable::decode(buf)?,
547 commitments: Decodable::decode(buf)?,
548 cell_proofs: Decodable::decode(buf)?,
549 })
550 }
551
552 pub fn rlp_decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
554 let header = Header::decode(buf)?;
555 if !header.list {
556 return Err(alloy_rlp::Error::UnexpectedString);
557 }
558 if buf.len() < header.payload_length {
559 return Err(alloy_rlp::Error::InputTooShort);
560 }
561 let remaining = buf.len();
562
563 let this = Self::decode_7594(buf)?;
564 if buf.len() + header.payload_length != remaining {
565 return Err(alloy_rlp::Error::UnexpectedLength);
566 }
567
568 Ok(this)
569 }
570}
571
572impl Encodable for BlobTransactionSidecarEip7594 {
573 fn encode(&self, out: &mut dyn BufMut) {
575 self.rlp_encode(out);
576 }
577
578 fn length(&self) -> usize {
579 self.rlp_encoded_length()
580 }
581}
582
583impl Decodable for BlobTransactionSidecarEip7594 {
584 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
587 Self::rlp_decode(buf)
588 }
589}
590
591impl Encodable7594 for BlobTransactionSidecarEip7594 {
592 fn encode_7594_len(&self) -> usize {
593 self.rlp_encoded_fields_length()
594 }
595
596 fn encode_7594(&self, out: &mut dyn BufMut) {
597 self.rlp_encode_fields(out);
598 }
599}
600
601impl Decodable7594 for BlobTransactionSidecarEip7594 {
602 fn decode_7594(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
603 let wrapper_version: u8 = Decodable::decode(buf)?;
604 if wrapper_version != EIP_7594_WRAPPER_VERSION {
605 return Err(alloy_rlp::Error::Custom("invalid wrapper version"));
606 }
607 Self::rlp_decode_fields(buf)
608 }
609}
610
611#[cfg(test)]
612mod tests {
613 use super::*;
614
615 #[test]
616 fn sidecar_variant_rlp_roundtrip() {
617 let mut encoded = Vec::new();
618
619 let empty_sidecar_4844 =
621 BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar::default());
622 empty_sidecar_4844.encode(&mut encoded);
623 assert_eq!(
624 empty_sidecar_4844,
625 BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap()
626 );
627
628 let sidecar_4844 = BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar::new(
629 vec![Blob::default()],
630 vec![Bytes48::ZERO],
631 vec![Bytes48::ZERO],
632 ));
633 encoded.clear();
634 sidecar_4844.encode(&mut encoded);
635 assert_eq!(sidecar_4844, BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap());
636
637 let empty_sidecar_7594 =
639 BlobTransactionSidecarVariant::Eip7594(BlobTransactionSidecarEip7594::default());
640 encoded.clear();
641 empty_sidecar_7594.encode(&mut encoded);
642 assert_eq!(
643 empty_sidecar_7594,
644 BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap()
645 );
646
647 let sidecar_7594 =
648 BlobTransactionSidecarVariant::Eip7594(BlobTransactionSidecarEip7594::new(
649 vec![Blob::default()],
650 vec![Bytes48::ZERO],
651 core::iter::repeat_n(Bytes48::ZERO, CELLS_PER_EXT_BLOB).collect(),
652 ));
653 encoded.clear();
654 sidecar_7594.encode(&mut encoded);
655 assert_eq!(sidecar_7594, BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap());
656 }
657
658 #[test]
659 #[cfg(feature = "serde")]
660 fn sidecar_variant_json_deserialize_sanity() {
661 let mut eip4844 = BlobTransactionSidecar::default();
662 eip4844.blobs.push(Blob::repeat_byte(0x2));
663
664 let json = serde_json::to_string(&eip4844).unwrap();
665 let variant: BlobTransactionSidecarVariant = serde_json::from_str(&json).unwrap();
666 assert!(variant.is_eip4844());
667 let jsonvariant = serde_json::to_string(&variant).unwrap();
668 assert_eq!(json, jsonvariant);
669
670 let mut eip7594 = BlobTransactionSidecarEip7594::default();
671 eip7594.blobs.push(Blob::repeat_byte(0x4));
672 let json = serde_json::to_string(&eip7594).unwrap();
673 let variant: BlobTransactionSidecarVariant = serde_json::from_str(&json).unwrap();
674 assert!(variant.is_eip7594());
675 let jsonvariant = serde_json::to_string(&variant).unwrap();
676 assert_eq!(json, jsonvariant);
677 }
678
679 #[test]
680 fn rlp_7594_roundtrip() {
681 let mut encoded = Vec::new();
682
683 let sidecar_4844 = BlobTransactionSidecar::default();
684 sidecar_4844.encode_7594(&mut encoded);
685 assert_eq!(sidecar_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
686
687 let sidecar_variant_4844 = BlobTransactionSidecarVariant::Eip4844(sidecar_4844);
688 assert_eq!(sidecar_variant_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
689 encoded.clear();
690 sidecar_variant_4844.encode_7594(&mut encoded);
691 assert_eq!(sidecar_variant_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
692
693 let sidecar_7594 = BlobTransactionSidecarEip7594::default();
694 encoded.clear();
695 sidecar_7594.encode_7594(&mut encoded);
696 assert_eq!(sidecar_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
697
698 let sidecar_variant_7594 = BlobTransactionSidecarVariant::Eip7594(sidecar_7594);
699 assert_eq!(sidecar_variant_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
700 encoded.clear();
701 sidecar_variant_7594.encode_7594(&mut encoded);
702 assert_eq!(sidecar_variant_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
703 }
704}