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