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