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 use core::fmt;
217
218 #[derive(serde::Deserialize, fmt::Debug)]
219 #[serde(field_identifier, rename_all = "camelCase")]
220 enum Field {
221 Blobs,
222 Commitments,
223 Proofs,
224 CellProofs,
225 }
226
227 struct VariantVisitor;
228
229 impl<'de> serde::de::Visitor<'de> for VariantVisitor {
230 type Value = BlobTransactionSidecarVariant;
231
232 fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
233 formatter
234 .write_str("a valid blob transaction sidecar (EIP-4844 or EIP-7594 variant)")
235 }
236
237 fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
238 where
239 M: serde::de::MapAccess<'de>,
240 {
241 let mut blobs = None;
242 let mut commitments = None;
243 let mut proofs = None;
244 let mut cell_proofs = None;
245
246 while let Some(key) = map.next_key()? {
247 match key {
248 Field::Blobs => {
249 blobs = Some(crate::eip4844::deserialize_blobs_map(&mut map)?);
250 }
251 Field::Commitments => commitments = Some(map.next_value()?),
252 Field::Proofs => proofs = Some(map.next_value()?),
253 Field::CellProofs => cell_proofs = Some(map.next_value()?),
254 }
255 }
256
257 let blobs = blobs.ok_or_else(|| serde::de::Error::missing_field("blobs"))?;
258 let commitments =
259 commitments.ok_or_else(|| serde::de::Error::missing_field("commitments"))?;
260
261 match (cell_proofs, proofs) {
262 (Some(cp), None) => {
263 Ok(BlobTransactionSidecarVariant::Eip7594(BlobTransactionSidecarEip7594 {
264 blobs,
265 commitments,
266 cell_proofs: cp,
267 }))
268 }
269 (None, Some(pf)) => {
270 Ok(BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar {
271 blobs,
272 commitments,
273 proofs: pf,
274 }))
275 }
276 (None, None) => {
277 Err(serde::de::Error::custom("Missing 'cellProofs' or 'proofs'"))
278 }
279 (Some(_), Some(_)) => Err(serde::de::Error::custom(
280 "Both 'cellProofs' and 'proofs' cannot be present",
281 )),
282 }
283 }
284 }
285
286 const FIELDS: &[&str] = &["blobs", "commitments", "proofs", "cellProofs"];
287 deserializer.deserialize_struct("BlobTransactionSidecarVariant", FIELDS, VariantVisitor)
288 }
289}
290
291#[derive(Clone, Default, PartialEq, Eq, Hash)]
295#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
296#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
297#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
298pub struct BlobTransactionSidecarEip7594 {
299 #[cfg_attr(feature = "serde", serde(deserialize_with = "crate::eip4844::deserialize_blobs"))]
301 pub blobs: Vec<Blob>,
302 pub commitments: Vec<Bytes48>,
304 pub cell_proofs: Vec<Bytes48>,
309}
310
311impl core::fmt::Debug for BlobTransactionSidecarEip7594 {
312 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
313 f.debug_struct("BlobTransactionSidecarEip7594")
314 .field("blobs", &self.blobs.len())
315 .field("commitments", &self.commitments)
316 .field("cell_proofs", &self.cell_proofs)
317 .finish()
318 }
319}
320
321impl BlobTransactionSidecarEip7594 {
322 pub const fn new(
325 blobs: Vec<Blob>,
326 commitments: Vec<Bytes48>,
327 cell_proofs: Vec<Bytes48>,
328 ) -> Self {
329 Self { blobs, commitments, cell_proofs }
330 }
331
332 #[inline]
334 pub fn size(&self) -> usize {
335 self.blobs.len() * BYTES_PER_BLOB + self.commitments.len() * BYTES_PER_COMMITMENT + self.cell_proofs.len() * BYTES_PER_PROOF }
339
340 #[cfg(feature = "kzg")]
354 pub fn validate(
355 &self,
356 blob_versioned_hashes: &[B256],
357 proof_settings: &c_kzg::KzgSettings,
358 ) -> Result<(), BlobTransactionValidationError> {
359 if blob_versioned_hashes.len() != self.commitments.len() {
361 return Err(c_kzg::Error::MismatchLength(format!(
362 "There are {} versioned commitment hashes and {} commitments",
363 blob_versioned_hashes.len(),
364 self.commitments.len()
365 ))
366 .into());
367 }
368
369 let blobs_len = self.blobs.len();
370 let expected_cell_proofs_len = blobs_len * CELLS_PER_EXT_BLOB;
371 if self.cell_proofs.len() != expected_cell_proofs_len {
372 return Err(c_kzg::Error::MismatchLength(format!(
373 "There are {} cell proofs and {} blobs. Expected {} cell proofs.",
374 self.cell_proofs.len(),
375 blobs_len,
376 expected_cell_proofs_len
377 ))
378 .into());
379 }
380
381 for (versioned_hash, commitment) in
383 blob_versioned_hashes.iter().zip(self.commitments.iter())
384 {
385 let calculated_versioned_hash =
387 crate::eip4844::kzg_to_versioned_hash(commitment.as_slice());
388 if *versioned_hash != calculated_versioned_hash {
389 return Err(BlobTransactionValidationError::WrongVersionedHash {
390 have: *versioned_hash,
391 expected: calculated_versioned_hash,
392 });
393 }
394 }
395
396 let cell_indices =
398 Vec::from_iter((0..blobs_len).flat_map(|_| 0..CELLS_PER_EXT_BLOB as u64));
399
400 let mut commitments = Vec::with_capacity(blobs_len * CELLS_PER_EXT_BLOB);
402 for commitment in &self.commitments {
403 commitments.extend(core::iter::repeat_n(*commitment, CELLS_PER_EXT_BLOB));
404 }
405
406 let res = unsafe {
408 let mut cells = Vec::with_capacity(blobs_len * CELLS_PER_EXT_BLOB);
409 for blob in &self.blobs {
410 let blob = core::mem::transmute::<&Blob, &c_kzg::Blob>(blob);
411 cells.extend(proof_settings.compute_cells(blob)?.into_iter());
412 }
413
414 proof_settings.verify_cell_kzg_proof_batch(
415 core::mem::transmute::<&[Bytes48], &[c_kzg::Bytes48]>(&commitments),
417 &cell_indices,
419 &cells,
421 core::mem::transmute::<&[Bytes48], &[c_kzg::Bytes48]>(self.cell_proofs.as_slice()),
423 )?
424 };
425
426 res.then_some(()).ok_or(BlobTransactionValidationError::InvalidProof)
427 }
428
429 pub fn versioned_hashes(&self) -> VersionedHashIter<'_> {
431 VersionedHashIter::new(&self.commitments)
432 }
433
434 pub fn versioned_hash_index(&self, hash: &B256) -> Option<usize> {
436 self.commitments.iter().position(|commitment| {
437 crate::eip4844::kzg_to_versioned_hash(commitment.as_slice()) == *hash
438 })
439 }
440
441 pub fn blob_by_versioned_hash(&self, hash: &B256) -> Option<&Blob> {
443 self.versioned_hash_index(hash).and_then(|index| self.blobs.get(index))
444 }
445
446 pub fn match_versioned_hashes<'a>(
452 &'a self,
453 versioned_hashes: &'a [B256],
454 ) -> impl Iterator<Item = (usize, BlobAndProofV2)> + 'a {
455 self.versioned_hashes().enumerate().flat_map(move |(i, blob_versioned_hash)| {
456 versioned_hashes.iter().enumerate().filter_map(move |(j, target_hash)| {
457 if blob_versioned_hash == *target_hash {
458 let maybe_blob = self.blobs.get(i);
459 let proof_range = i * CELLS_PER_EXT_BLOB..(i + 1) * CELLS_PER_EXT_BLOB;
460 let maybe_proofs = Some(&self.cell_proofs[proof_range])
461 .filter(|proofs| proofs.len() == CELLS_PER_EXT_BLOB);
462 if let Some((blob, proofs)) = maybe_blob.copied().zip(maybe_proofs) {
463 return Some((
464 j,
465 BlobAndProofV2 { blob: Box::new(blob), proofs: proofs.to_vec() },
466 ));
467 }
468 }
469 None
470 })
471 })
472 }
473
474 #[doc(hidden)]
476 pub fn rlp_encoded_fields_length(&self) -> usize {
477 1 + self.blobs.length() + self.commitments.length() + self.cell_proofs.length()
479 }
480
481 #[inline]
490 #[doc(hidden)]
491 pub fn rlp_encode_fields(&self, out: &mut dyn BufMut) {
492 out.put_u8(EIP_7594_WRAPPER_VERSION);
494 self.blobs.encode(out);
496 self.commitments.encode(out);
497 self.cell_proofs.encode(out);
498 }
499
500 fn rlp_header(&self) -> Header {
502 Header { list: true, payload_length: self.rlp_encoded_fields_length() }
503 }
504
505 pub fn rlp_encoded_length(&self) -> usize {
508 self.rlp_header().length() + self.rlp_encoded_fields_length()
509 }
510
511 pub fn rlp_encode(&self, out: &mut dyn BufMut) {
513 self.rlp_header().encode(out);
514 self.rlp_encode_fields(out);
515 }
516
517 #[doc(hidden)]
519 pub fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
520 Ok(Self {
521 blobs: Decodable::decode(buf)?,
522 commitments: Decodable::decode(buf)?,
523 cell_proofs: Decodable::decode(buf)?,
524 })
525 }
526
527 pub fn rlp_decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
529 let header = Header::decode(buf)?;
530 if !header.list {
531 return Err(alloy_rlp::Error::UnexpectedString);
532 }
533 if buf.len() < header.payload_length {
534 return Err(alloy_rlp::Error::InputTooShort);
535 }
536 let remaining = buf.len();
537
538 let this = Self::decode_7594(buf)?;
539 if buf.len() + header.payload_length != remaining {
540 return Err(alloy_rlp::Error::UnexpectedLength);
541 }
542
543 Ok(this)
544 }
545}
546
547impl Encodable for BlobTransactionSidecarEip7594 {
548 fn encode(&self, out: &mut dyn BufMut) {
550 self.rlp_encode(out);
551 }
552
553 fn length(&self) -> usize {
554 self.rlp_encoded_length()
555 }
556}
557
558impl Decodable for BlobTransactionSidecarEip7594 {
559 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
562 Self::rlp_decode(buf)
563 }
564}
565
566impl Encodable7594 for BlobTransactionSidecarEip7594 {
567 fn encode_7594_len(&self) -> usize {
568 self.rlp_encoded_fields_length()
569 }
570
571 fn encode_7594(&self, out: &mut dyn BufMut) {
572 self.rlp_encode_fields(out);
573 }
574}
575
576impl Decodable7594 for BlobTransactionSidecarEip7594 {
577 fn decode_7594(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
578 let wrapper_version: u8 = Decodable::decode(buf)?;
579 if wrapper_version != EIP_7594_WRAPPER_VERSION {
580 return Err(alloy_rlp::Error::Custom("invalid wrapper version"));
581 }
582 Self::rlp_decode_fields(buf)
583 }
584}
585
586#[cfg(test)]
587mod tests {
588 use super::*;
589
590 #[test]
591 fn sidecar_variant_rlp_roundtrip() {
592 let mut encoded = Vec::new();
593
594 let empty_sidecar_4844 =
596 BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar::default());
597 empty_sidecar_4844.encode(&mut encoded);
598 assert_eq!(
599 empty_sidecar_4844,
600 BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap()
601 );
602
603 let sidecar_4844 = BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar::new(
604 vec![Blob::default()],
605 vec![Bytes48::ZERO],
606 vec![Bytes48::ZERO],
607 ));
608 encoded.clear();
609 sidecar_4844.encode(&mut encoded);
610 assert_eq!(sidecar_4844, BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap());
611
612 let empty_sidecar_7594 =
614 BlobTransactionSidecarVariant::Eip7594(BlobTransactionSidecarEip7594::default());
615 encoded.clear();
616 empty_sidecar_7594.encode(&mut encoded);
617 assert_eq!(
618 empty_sidecar_7594,
619 BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap()
620 );
621
622 let sidecar_7594 =
623 BlobTransactionSidecarVariant::Eip7594(BlobTransactionSidecarEip7594::new(
624 vec![Blob::default()],
625 vec![Bytes48::ZERO],
626 core::iter::repeat_n(Bytes48::ZERO, CELLS_PER_EXT_BLOB).collect(),
627 ));
628 encoded.clear();
629 sidecar_7594.encode(&mut encoded);
630 assert_eq!(sidecar_7594, BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap());
631 }
632
633 #[test]
634 #[cfg(feature = "serde")]
635 fn sidecar_variant_json_deserialize_sanity() {
636 let mut eip4844 = BlobTransactionSidecar::default();
637 eip4844.blobs.push(Blob::repeat_byte(0x2));
638
639 let json = serde_json::to_string(&eip4844).unwrap();
640 let variant: BlobTransactionSidecarVariant = serde_json::from_str(&json).unwrap();
641 assert!(variant.is_eip4844());
642 let jsonvariant = serde_json::to_string(&variant).unwrap();
643 assert_eq!(json, jsonvariant);
644
645 let mut eip7594 = BlobTransactionSidecarEip7594::default();
646 eip7594.blobs.push(Blob::repeat_byte(0x4));
647 let json = serde_json::to_string(&eip7594).unwrap();
648 let variant: BlobTransactionSidecarVariant = serde_json::from_str(&json).unwrap();
649 assert!(variant.is_eip7594());
650 let jsonvariant = serde_json::to_string(&variant).unwrap();
651 assert_eq!(json, jsonvariant);
652 }
653
654 #[test]
655 fn rlp_7594_roundtrip() {
656 let mut encoded = Vec::new();
657
658 let sidecar_4844 = BlobTransactionSidecar::default();
659 sidecar_4844.encode_7594(&mut encoded);
660 assert_eq!(sidecar_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
661
662 let sidecar_variant_4844 = BlobTransactionSidecarVariant::Eip4844(sidecar_4844);
663 assert_eq!(sidecar_variant_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
664 encoded.clear();
665 sidecar_variant_4844.encode_7594(&mut encoded);
666 assert_eq!(sidecar_variant_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
667
668 let sidecar_7594 = BlobTransactionSidecarEip7594::default();
669 encoded.clear();
670 sidecar_7594.encode_7594(&mut encoded);
671 assert_eq!(sidecar_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
672
673 let sidecar_variant_7594 = BlobTransactionSidecarVariant::Eip7594(sidecar_7594);
674 assert_eq!(sidecar_variant_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
675 encoded.clear();
676 sidecar_variant_7594.encode_7594(&mut encoded);
677 assert_eq!(sidecar_variant_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
678 }
679}