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]
138 #[doc(hidden)]
139 pub fn rlp_encode_fields(&self, out: &mut dyn BufMut) {
140 match self {
141 Self::Eip4844(sidecar) => sidecar.rlp_encode_fields(out),
142 Self::Eip7594(sidecar) => sidecar.rlp_encode_fields(out),
143 }
144 }
145
146 #[doc(hidden)]
148 pub fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
149 Self::decode_7594(buf)
150 }
151}
152
153impl Encodable for BlobTransactionSidecarVariant {
154 fn encode(&self, out: &mut dyn BufMut) {
156 match self {
157 Self::Eip4844(sidecar) => sidecar.encode(out),
158 Self::Eip7594(sidecar) => sidecar.encode(out),
159 }
160 }
161
162 fn length(&self) -> usize {
163 match self {
164 Self::Eip4844(sidecar) => sidecar.rlp_encoded_length(),
165 Self::Eip7594(sidecar) => sidecar.rlp_encoded_length(),
166 }
167 }
168}
169
170impl Decodable for BlobTransactionSidecarVariant {
171 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
173 let header = Header::decode(buf)?;
174 if !header.list {
175 return Err(alloy_rlp::Error::UnexpectedString);
176 }
177 if buf.len() < header.payload_length {
178 return Err(alloy_rlp::Error::InputTooShort);
179 }
180 let remaining = buf.len();
181 let this = Self::rlp_decode_fields(buf)?;
182 if buf.len() + header.payload_length != remaining {
183 return Err(alloy_rlp::Error::UnexpectedLength);
184 }
185
186 Ok(this)
187 }
188}
189
190impl Encodable7594 for BlobTransactionSidecarVariant {
191 fn encode_7594_len(&self) -> usize {
192 self.rlp_encoded_fields_length()
193 }
194
195 fn encode_7594(&self, out: &mut dyn BufMut) {
196 self.rlp_encode_fields(out);
197 }
198}
199
200impl Decodable7594 for BlobTransactionSidecarVariant {
201 fn decode_7594(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
202 if buf.first() == Some(&EIP_7594_WRAPPER_VERSION) {
203 Ok(Self::Eip7594(Decodable7594::decode_7594(buf)?))
204 } else {
205 Ok(Self::Eip4844(Decodable7594::decode_7594(buf)?))
206 }
207 }
208}
209
210#[cfg(feature = "serde")]
211impl<'de> serde::Deserialize<'de> for BlobTransactionSidecarVariant {
212 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
213 where
214 D: serde::Deserializer<'de>,
215 {
216 #[derive(serde::Deserialize)]
217 #[serde(field_identifier, rename_all = "camelCase")]
218 enum Field {
219 Blobs,
220 Commitments,
221 Proofs,
222 CellProofs,
223 }
224
225 struct VariantVisitor;
226
227 impl<'de> serde::de::Visitor<'de> for VariantVisitor {
228 type Value = BlobTransactionSidecarVariant;
229
230 fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
231 formatter
232 .write_str("a valid blob transaction sidecar (EIP-4844 or EIP-7594 variant)")
233 }
234
235 fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
236 where
237 M: serde::de::MapAccess<'de>,
238 {
239 let mut blobs = None;
240 let mut commitments = None;
241 let mut proofs = None;
242 let mut cell_proofs = None;
243
244 while let Some(key) = map.next_key()? {
245 match key {
246 Field::Blobs => blobs = Some(map.next_value()?),
247 Field::Commitments => commitments = Some(map.next_value()?),
248 Field::Proofs => proofs = Some(map.next_value()?),
249 Field::CellProofs => cell_proofs = Some(map.next_value()?),
250 }
251 }
252
253 let blobs = blobs.ok_or_else(|| serde::de::Error::missing_field("blobs"))?;
254 let commitments =
255 commitments.ok_or_else(|| serde::de::Error::missing_field("commitments"))?;
256
257 match (cell_proofs, proofs) {
258 (Some(cp), None) => {
259 Ok(BlobTransactionSidecarVariant::Eip7594(BlobTransactionSidecarEip7594 {
260 blobs,
261 commitments,
262 cell_proofs: cp,
263 }))
264 }
265 (None, Some(pf)) => {
266 Ok(BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar {
267 blobs,
268 commitments,
269 proofs: pf,
270 }))
271 }
272 (None, None) => {
273 Err(serde::de::Error::custom("Missing 'cellProofs' or 'proofs'"))
274 }
275 (Some(_), Some(_)) => Err(serde::de::Error::custom(
276 "Both 'cellProofs' and 'proofs' cannot be present",
277 )),
278 }
279 }
280 }
281
282 const FIELDS: &[&str] = &["blobs", "commitments", "proofs", "cellProofs"];
283 deserializer.deserialize_struct("BlobTransactionSidecarVariant", FIELDS, VariantVisitor)
284 }
285}
286
287#[derive(Clone, Default, PartialEq, Eq, Hash)]
291#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
292#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
293#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
294pub struct BlobTransactionSidecarEip7594 {
295 #[cfg_attr(
297 all(debug_assertions, feature = "serde"),
298 serde(deserialize_with = "crate::eip4844::deserialize_blobs")
299 )]
300 pub blobs: Vec<Blob>,
301 pub commitments: Vec<Bytes48>,
303 pub cell_proofs: Vec<Bytes48>,
308}
309
310impl core::fmt::Debug for BlobTransactionSidecarEip7594 {
311 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
312 f.debug_struct("BlobTransactionSidecarEip7594")
313 .field("blobs", &self.blobs.len())
314 .field("commitments", &self.commitments)
315 .field("cell_proofs", &self.cell_proofs)
316 .finish()
317 }
318}
319
320impl BlobTransactionSidecarEip7594 {
321 pub const fn new(
324 blobs: Vec<Blob>,
325 commitments: Vec<Bytes48>,
326 cell_proofs: Vec<Bytes48>,
327 ) -> Self {
328 Self { blobs, commitments, cell_proofs }
329 }
330
331 #[inline]
333 pub fn size(&self) -> usize {
334 self.blobs.len() * BYTES_PER_BLOB + self.commitments.len() * BYTES_PER_COMMITMENT + self.cell_proofs.len() * BYTES_PER_PROOF }
338
339 #[cfg(feature = "kzg")]
353 pub fn validate(
354 &self,
355 blob_versioned_hashes: &[B256],
356 proof_settings: &c_kzg::KzgSettings,
357 ) -> Result<(), BlobTransactionValidationError> {
358 if blob_versioned_hashes.len() != self.commitments.len() {
360 return Err(c_kzg::Error::MismatchLength(format!(
361 "There are {} versioned commitment hashes and {} commitments",
362 blob_versioned_hashes.len(),
363 self.commitments.len()
364 ))
365 .into());
366 }
367
368 let blobs_len = self.blobs.len();
369 let expected_cell_proofs_len = blobs_len * CELLS_PER_EXT_BLOB;
370 if self.cell_proofs.len() != expected_cell_proofs_len {
371 return Err(c_kzg::Error::MismatchLength(format!(
372 "There are {} cell proofs and {} blobs. Expected {} cell proofs.",
373 self.cell_proofs.len(),
374 blobs_len,
375 expected_cell_proofs_len
376 ))
377 .into());
378 }
379
380 for (versioned_hash, commitment) in
382 blob_versioned_hashes.iter().zip(self.commitments.iter())
383 {
384 let calculated_versioned_hash =
386 crate::eip4844::kzg_to_versioned_hash(commitment.as_slice());
387 if *versioned_hash != calculated_versioned_hash {
388 return Err(BlobTransactionValidationError::WrongVersionedHash {
389 have: *versioned_hash,
390 expected: calculated_versioned_hash,
391 });
392 }
393 }
394
395 let cell_indices =
397 Vec::from_iter((0..blobs_len).flat_map(|_| 0..CELLS_PER_EXT_BLOB as u64));
398
399 let mut commitments = Vec::with_capacity(blobs_len * CELLS_PER_EXT_BLOB);
401 for commitment in &self.commitments {
402 commitments.extend(core::iter::repeat_n(*commitment, CELLS_PER_EXT_BLOB));
403 }
404
405 let res = unsafe {
407 let mut cells = Vec::with_capacity(blobs_len * CELLS_PER_EXT_BLOB);
408 for blob in &self.blobs {
409 let blob = core::mem::transmute::<&Blob, &c_kzg::Blob>(blob);
410 cells.extend(proof_settings.compute_cells(blob)?.into_iter());
411 }
412
413 proof_settings.verify_cell_kzg_proof_batch(
414 core::mem::transmute::<&[Bytes48], &[c_kzg::Bytes48]>(&commitments),
416 &cell_indices,
418 &cells,
420 core::mem::transmute::<&[Bytes48], &[c_kzg::Bytes48]>(self.cell_proofs.as_slice()),
422 )?
423 };
424
425 res.then_some(()).ok_or(BlobTransactionValidationError::InvalidProof)
426 }
427
428 pub fn versioned_hashes(&self) -> VersionedHashIter<'_> {
430 VersionedHashIter::new(&self.commitments)
431 }
432
433 pub fn versioned_hash_index(&self, hash: &B256) -> Option<usize> {
435 self.commitments.iter().position(|commitment| {
436 crate::eip4844::kzg_to_versioned_hash(commitment.as_slice()) == *hash
437 })
438 }
439
440 pub fn blob_by_versioned_hash(&self, hash: &B256) -> Option<&Blob> {
442 self.versioned_hash_index(hash).and_then(|index| self.blobs.get(index))
443 }
444
445 pub fn match_versioned_hashes<'a>(
451 &'a self,
452 versioned_hashes: &'a [B256],
453 ) -> impl Iterator<Item = (usize, BlobAndProofV2)> + 'a {
454 self.versioned_hashes().enumerate().flat_map(move |(i, blob_versioned_hash)| {
455 versioned_hashes.iter().enumerate().filter_map(move |(j, target_hash)| {
456 if blob_versioned_hash == *target_hash {
457 let maybe_blob = self.blobs.get(i);
458 let proof_range = i * CELLS_PER_EXT_BLOB..(i + 1) * CELLS_PER_EXT_BLOB;
459 let maybe_proofs = Some(&self.cell_proofs[proof_range])
460 .filter(|proofs| proofs.len() == CELLS_PER_EXT_BLOB);
461 if let Some((blob, proofs)) = maybe_blob.copied().zip(maybe_proofs) {
462 return Some((
463 j,
464 BlobAndProofV2 { blob: Box::new(blob), proofs: proofs.to_vec() },
465 ));
466 }
467 }
468 None
469 })
470 })
471 }
472
473 #[doc(hidden)]
475 pub fn rlp_encoded_fields_length(&self) -> usize {
476 1 + self.blobs.length() + self.commitments.length() + self.cell_proofs.length()
478 }
479
480 #[inline]
489 #[doc(hidden)]
490 pub fn rlp_encode_fields(&self, out: &mut dyn BufMut) {
491 out.put_u8(EIP_7594_WRAPPER_VERSION);
493 self.blobs.encode(out);
495 self.commitments.encode(out);
496 self.cell_proofs.encode(out);
497 }
498
499 fn rlp_header(&self) -> Header {
501 Header { list: true, payload_length: self.rlp_encoded_fields_length() }
502 }
503
504 pub fn rlp_encoded_length(&self) -> usize {
507 self.rlp_header().length() + self.rlp_encoded_fields_length()
508 }
509
510 pub fn rlp_encode(&self, out: &mut dyn BufMut) {
512 self.rlp_header().encode(out);
513 self.rlp_encode_fields(out);
514 }
515
516 #[doc(hidden)]
518 pub fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
519 Ok(Self {
520 blobs: Decodable::decode(buf)?,
521 commitments: Decodable::decode(buf)?,
522 cell_proofs: Decodable::decode(buf)?,
523 })
524 }
525
526 pub fn rlp_decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
528 let header = Header::decode(buf)?;
529 if !header.list {
530 return Err(alloy_rlp::Error::UnexpectedString);
531 }
532 if buf.len() < header.payload_length {
533 return Err(alloy_rlp::Error::InputTooShort);
534 }
535 let remaining = buf.len();
536
537 let this = Self::decode_7594(buf)?;
538 if buf.len() + header.payload_length != remaining {
539 return Err(alloy_rlp::Error::UnexpectedLength);
540 }
541
542 Ok(this)
543 }
544}
545
546impl Encodable for BlobTransactionSidecarEip7594 {
547 fn encode(&self, out: &mut dyn BufMut) {
549 self.rlp_encode(out);
550 }
551
552 fn length(&self) -> usize {
553 self.rlp_encoded_length()
554 }
555}
556
557impl Decodable for BlobTransactionSidecarEip7594 {
558 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
561 Self::rlp_decode(buf)
562 }
563}
564
565impl Encodable7594 for BlobTransactionSidecarEip7594 {
566 fn encode_7594_len(&self) -> usize {
567 self.rlp_encoded_fields_length()
568 }
569
570 fn encode_7594(&self, out: &mut dyn BufMut) {
571 self.rlp_encode_fields(out);
572 }
573}
574
575impl Decodable7594 for BlobTransactionSidecarEip7594 {
576 fn decode_7594(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
577 let wrapper_version: u8 = Decodable::decode(buf)?;
578 if wrapper_version != EIP_7594_WRAPPER_VERSION {
579 return Err(alloy_rlp::Error::Custom("invalid wrapper version"));
580 }
581 Self::rlp_decode_fields(buf)
582 }
583}
584
585#[cfg(test)]
586mod tests {
587 use super::*;
588
589 #[test]
590 fn sidecar_variant_rlp_roundtrip() {
591 let mut encoded = Vec::new();
592
593 let empty_sidecar_4844 =
595 BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar::default());
596 empty_sidecar_4844.encode(&mut encoded);
597 assert_eq!(
598 empty_sidecar_4844,
599 BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap()
600 );
601
602 let sidecar_4844 = BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar::new(
603 vec![Blob::default()],
604 vec![Bytes48::ZERO],
605 vec![Bytes48::ZERO],
606 ));
607 encoded.clear();
608 sidecar_4844.encode(&mut encoded);
609 assert_eq!(sidecar_4844, BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap());
610
611 let empty_sidecar_7594 =
613 BlobTransactionSidecarVariant::Eip7594(BlobTransactionSidecarEip7594::default());
614 encoded.clear();
615 empty_sidecar_7594.encode(&mut encoded);
616 assert_eq!(
617 empty_sidecar_7594,
618 BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap()
619 );
620
621 let sidecar_7594 =
622 BlobTransactionSidecarVariant::Eip7594(BlobTransactionSidecarEip7594::new(
623 vec![Blob::default()],
624 vec![Bytes48::ZERO],
625 core::iter::repeat_n(Bytes48::ZERO, CELLS_PER_EXT_BLOB).collect(),
626 ));
627 encoded.clear();
628 sidecar_7594.encode(&mut encoded);
629 assert_eq!(sidecar_7594, BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap());
630 }
631
632 #[test]
633 #[cfg(feature = "serde")]
634 fn sidecar_variant_json_deserialize_sanity() {
635 let eip4844 = BlobTransactionSidecar::default();
636 let json = serde_json::to_string(&eip4844).unwrap();
637 let variant: BlobTransactionSidecarVariant = serde_json::from_str(&json).unwrap();
638 assert!(variant.is_eip4844());
639 let jsonvariant = serde_json::to_string(&variant).unwrap();
640 assert_eq!(json, jsonvariant);
641
642 let eip7594 = BlobTransactionSidecarEip7594::default();
643 let json = serde_json::to_string(&eip7594).unwrap();
644 let variant: BlobTransactionSidecarVariant = serde_json::from_str(&json).unwrap();
645 assert!(variant.is_eip7594());
646 let jsonvariant = serde_json::to_string(&variant).unwrap();
647 assert_eq!(json, jsonvariant);
648 }
649
650 #[test]
651 fn rlp_7594_roundtrip() {
652 let mut encoded = Vec::new();
653
654 let sidecar_4844 = BlobTransactionSidecar::default();
655 sidecar_4844.encode_7594(&mut encoded);
656 assert_eq!(sidecar_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
657
658 let sidecar_variant_4844 = BlobTransactionSidecarVariant::Eip4844(sidecar_4844);
659 assert_eq!(sidecar_variant_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
660 encoded.clear();
661 sidecar_variant_4844.encode_7594(&mut encoded);
662 assert_eq!(sidecar_variant_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
663
664 let sidecar_7594 = BlobTransactionSidecarEip7594::default();
665 encoded.clear();
666 sidecar_7594.encode_7594(&mut encoded);
667 assert_eq!(sidecar_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
668
669 let sidecar_variant_7594 = BlobTransactionSidecarVariant::Eip7594(sidecar_7594);
670 assert_eq!(sidecar_variant_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
671 encoded.clear();
672 sidecar_variant_7594.encode_7594(&mut encoded);
673 assert_eq!(sidecar_variant_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
674 }
675}