1use crate::{
2 eip4844::{
3 kzg_to_versioned_hash, Blob, BlobAndProofV2, BlobTransactionSidecar, Bytes48,
4 BYTES_PER_BLOB, 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
12#[cfg(feature = "kzg")]
13use crate::eip4844::BlobTransactionValidationError;
14
15use super::{Decodable7594, Encodable7594};
16
17#[derive(Clone, PartialEq, Eq, Hash, Debug, derive_more::From)]
22#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
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 as_eip4844(&self) -> Option<&BlobTransactionSidecar> {
35 match self {
36 Self::Eip4844(sidecar) => Some(sidecar),
37 _ => None,
38 }
39 }
40
41 pub const fn as_eip7594(&self) -> Option<&BlobTransactionSidecarEip7594> {
43 match self {
44 Self::Eip7594(sidecar) => Some(sidecar),
45 _ => None,
46 }
47 }
48
49 #[inline]
51 pub fn size(&self) -> usize {
52 match self {
53 Self::Eip4844(sidecar) => sidecar.size(),
54 Self::Eip7594(sidecar) => sidecar.size(),
55 }
56 }
57
58 #[cfg(feature = "kzg")]
60 pub fn validate(
61 &self,
62 blob_versioned_hashes: &[B256],
63 proof_settings: &c_kzg::KzgSettings,
64 ) -> Result<(), BlobTransactionValidationError> {
65 match self {
66 Self::Eip4844(sidecar) => sidecar.validate(blob_versioned_hashes, proof_settings),
67 Self::Eip7594(sidecar) => sidecar.validate(blob_versioned_hashes, proof_settings),
68 }
69 }
70
71 pub fn versioned_hashes(&self) -> Vec<B256> {
73 match self {
74 Self::Eip4844(sidecar) => sidecar.versioned_hashes().collect(),
75 Self::Eip7594(sidecar) => sidecar.versioned_hashes().collect(),
76 }
77 }
78
79 #[doc(hidden)]
81 pub fn rlp_encoded_fields_length(&self) -> usize {
82 match self {
83 Self::Eip4844(sidecar) => sidecar.rlp_encoded_fields_length(),
84 Self::Eip7594(sidecar) => sidecar.rlp_encoded_fields_length(),
85 }
86 }
87
88 #[inline]
91 #[doc(hidden)]
92 pub fn rlp_encode_fields(&self, out: &mut dyn BufMut) {
93 match self {
94 Self::Eip4844(sidecar) => sidecar.rlp_encode_fields(out),
95 Self::Eip7594(sidecar) => sidecar.rlp_encode_fields(out),
96 }
97 }
98
99 #[doc(hidden)]
101 pub fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
102 Self::decode_7594(buf)
103 }
104}
105
106impl Encodable for BlobTransactionSidecarVariant {
107 fn encode(&self, out: &mut dyn BufMut) {
109 match self {
110 Self::Eip4844(sidecar) => sidecar.encode(out),
111 Self::Eip7594(sidecar) => sidecar.encode(out),
112 }
113 }
114
115 fn length(&self) -> usize {
116 match self {
117 Self::Eip4844(sidecar) => sidecar.rlp_encoded_length(),
118 Self::Eip7594(sidecar) => sidecar.rlp_encoded_length(),
119 }
120 }
121}
122
123impl Decodable for BlobTransactionSidecarVariant {
124 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
126 let header = Header::decode(buf)?;
127 if !header.list {
128 return Err(alloy_rlp::Error::UnexpectedString);
129 }
130 if buf.len() < header.payload_length {
131 return Err(alloy_rlp::Error::InputTooShort);
132 }
133 let remaining = buf.len();
134 let this = Self::rlp_decode_fields(buf)?;
135 if buf.len() + header.payload_length != remaining {
136 return Err(alloy_rlp::Error::UnexpectedLength);
137 }
138
139 Ok(this)
140 }
141}
142
143impl Encodable7594 for BlobTransactionSidecarVariant {
144 fn encode_7594_len(&self) -> usize {
145 self.rlp_encoded_fields_length()
146 }
147
148 fn encode_7594(&self, out: &mut dyn BufMut) {
149 self.rlp_encode_fields(out);
150 }
151}
152
153impl Decodable7594 for BlobTransactionSidecarVariant {
154 fn decode_7594(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
155 if buf.first() == Some(&EIP_7594_WRAPPER_VERSION) {
156 Ok(Self::Eip7594(Decodable7594::decode_7594(buf)?))
157 } else {
158 Ok(Self::Eip4844(Decodable7594::decode_7594(buf)?))
159 }
160 }
161}
162
163#[derive(Clone, Default, PartialEq, Eq, Hash)]
167#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
168#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
169pub struct BlobTransactionSidecarEip7594 {
170 #[cfg_attr(
172 all(debug_assertions, feature = "serde"),
173 serde(deserialize_with = "crate::eip4844::deserialize_blobs")
174 )]
175 pub blobs: Vec<Blob>,
176 pub commitments: Vec<Bytes48>,
178 pub cell_proofs: Vec<Bytes48>,
183}
184
185impl core::fmt::Debug for BlobTransactionSidecarEip7594 {
186 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
187 f.debug_struct("BlobTransactionSidecarEip7594")
188 .field("blobs", &self.blobs.len())
189 .field("commitments", &self.commitments)
190 .field("cell_proofs", &self.cell_proofs)
191 .finish()
192 }
193}
194
195impl BlobTransactionSidecarEip7594 {
196 pub const fn new(
199 blobs: Vec<Blob>,
200 commitments: Vec<Bytes48>,
201 cell_proofs: Vec<Bytes48>,
202 ) -> Self {
203 Self { blobs, commitments, cell_proofs }
204 }
205
206 #[inline]
208 pub fn size(&self) -> usize {
209 self.blobs.len() * BYTES_PER_BLOB + self.commitments.len() * BYTES_PER_COMMITMENT + self.cell_proofs.len() * BYTES_PER_PROOF }
213
214 #[cfg(feature = "kzg")]
228 pub fn validate(
229 &self,
230 blob_versioned_hashes: &[B256],
231 proof_settings: &c_kzg::KzgSettings,
232 ) -> Result<(), BlobTransactionValidationError> {
233 if blob_versioned_hashes.len() != self.commitments.len() {
235 return Err(c_kzg::Error::MismatchLength(format!(
236 "There are {} versioned commitment hashes and {} commitments",
237 blob_versioned_hashes.len(),
238 self.commitments.len()
239 ))
240 .into());
241 }
242
243 let blobs_len = self.blobs.len();
244 let expected_cell_proofs_len = blobs_len * CELLS_PER_EXT_BLOB;
245 if self.cell_proofs.len() != expected_cell_proofs_len {
246 return Err(c_kzg::Error::MismatchLength(format!(
247 "There are {} cell proofs and {} blobs. Expected {} cell proofs.",
248 self.cell_proofs.len(),
249 blobs_len,
250 expected_cell_proofs_len
251 ))
252 .into());
253 }
254
255 for (versioned_hash, commitment) in
257 blob_versioned_hashes.iter().zip(self.commitments.iter())
258 {
259 let commitment = c_kzg::KzgCommitment::from(commitment.0);
260
261 let calculated_versioned_hash = kzg_to_versioned_hash(commitment.as_slice());
263 if *versioned_hash != calculated_versioned_hash {
264 return Err(BlobTransactionValidationError::WrongVersionedHash {
265 have: *versioned_hash,
266 expected: calculated_versioned_hash,
267 });
268 }
269 }
270
271 let cell_indices =
273 Vec::from_iter((0..blobs_len).flat_map(|_| 0..CELLS_PER_EXT_BLOB as u64));
274
275 let mut commitments = Vec::with_capacity(blobs_len * CELLS_PER_EXT_BLOB);
277 for commitment in &self.commitments {
278 commitments.extend(core::iter::repeat_n(*commitment, CELLS_PER_EXT_BLOB));
279 }
280
281 let res = unsafe {
283 let mut cells = Vec::with_capacity(blobs_len * CELLS_PER_EXT_BLOB);
284 for blob in &self.blobs {
285 let blob = core::mem::transmute::<&Blob, &c_kzg::Blob>(blob);
286 cells.extend(proof_settings.compute_cells(blob)?.into_iter());
287 }
288
289 proof_settings.verify_cell_kzg_proof_batch(
290 core::mem::transmute::<&[Bytes48], &[c_kzg::Bytes48]>(&commitments),
292 &cell_indices,
294 &cells,
296 core::mem::transmute::<&[Bytes48], &[c_kzg::Bytes48]>(self.cell_proofs.as_slice()),
298 )?
299 };
300
301 res.then_some(()).ok_or(BlobTransactionValidationError::InvalidProof)
302 }
303
304 pub fn versioned_hashes(&self) -> impl Iterator<Item = B256> + '_ {
306 self.commitments.iter().map(|c| kzg_to_versioned_hash(c.as_slice()))
307 }
308
309 pub fn match_versioned_hashes<'a>(
315 &'a self,
316 versioned_hashes: &'a [B256],
317 ) -> impl Iterator<Item = (usize, BlobAndProofV2)> + 'a {
318 self.versioned_hashes().enumerate().flat_map(move |(i, blob_versioned_hash)| {
319 versioned_hashes.iter().enumerate().filter_map(move |(j, target_hash)| {
320 if blob_versioned_hash == *target_hash {
321 let maybe_blob = self.blobs.get(i);
322 let proof_range = i * CELLS_PER_EXT_BLOB..(i + 1) * CELLS_PER_EXT_BLOB;
323 let maybe_proofs = Some(&self.cell_proofs[proof_range])
324 .filter(|proofs| proofs.len() == CELLS_PER_EXT_BLOB);
325 if let Some((blob, proofs)) = maybe_blob.copied().zip(maybe_proofs) {
326 return Some((
327 j,
328 BlobAndProofV2 { blob: Box::new(blob), proofs: proofs.to_vec() },
329 ));
330 }
331 }
332 None
333 })
334 })
335 }
336
337 #[doc(hidden)]
339 pub fn rlp_encoded_fields_length(&self) -> usize {
340 1 + self.blobs.length() + self.commitments.length() + self.cell_proofs.length()
342 }
343
344 #[inline]
353 #[doc(hidden)]
354 pub fn rlp_encode_fields(&self, out: &mut dyn BufMut) {
355 out.put_u8(EIP_7594_WRAPPER_VERSION);
357 self.blobs.encode(out);
359 self.commitments.encode(out);
360 self.cell_proofs.encode(out);
361 }
362
363 fn rlp_header(&self) -> Header {
365 Header { list: true, payload_length: self.rlp_encoded_fields_length() }
366 }
367
368 pub fn rlp_encoded_length(&self) -> usize {
371 self.rlp_header().length() + self.rlp_encoded_fields_length()
372 }
373
374 pub fn rlp_encode(&self, out: &mut dyn BufMut) {
376 self.rlp_header().encode(out);
377 self.rlp_encode_fields(out);
378 }
379
380 #[doc(hidden)]
382 pub fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
383 Ok(Self {
384 blobs: Decodable::decode(buf)?,
385 commitments: Decodable::decode(buf)?,
386 cell_proofs: Decodable::decode(buf)?,
387 })
388 }
389
390 pub fn rlp_decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
392 let header = Header::decode(buf)?;
393 if !header.list {
394 return Err(alloy_rlp::Error::UnexpectedString);
395 }
396 if buf.len() < header.payload_length {
397 return Err(alloy_rlp::Error::InputTooShort);
398 }
399 let remaining = buf.len();
400
401 let this = Self::decode_7594(buf)?;
402 if buf.len() + header.payload_length != remaining {
403 return Err(alloy_rlp::Error::UnexpectedLength);
404 }
405
406 Ok(this)
407 }
408}
409
410impl Encodable for BlobTransactionSidecarEip7594 {
411 fn encode(&self, out: &mut dyn BufMut) {
413 self.rlp_encode(out);
414 }
415
416 fn length(&self) -> usize {
417 self.rlp_encoded_length()
418 }
419}
420
421impl Decodable for BlobTransactionSidecarEip7594 {
422 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
425 Self::rlp_decode(buf)
426 }
427}
428
429impl Encodable7594 for BlobTransactionSidecarEip7594 {
430 fn encode_7594_len(&self) -> usize {
431 self.rlp_encoded_fields_length()
432 }
433
434 fn encode_7594(&self, out: &mut dyn BufMut) {
435 self.rlp_encode_fields(out);
436 }
437}
438
439impl Decodable7594 for BlobTransactionSidecarEip7594 {
440 fn decode_7594(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
441 let wrapper_version: u8 = Decodable::decode(buf)?;
442 if wrapper_version != EIP_7594_WRAPPER_VERSION {
443 return Err(alloy_rlp::Error::Custom("invalid wrapper version"));
444 }
445 Self::rlp_decode_fields(buf)
446 }
447}
448
449#[cfg(test)]
450mod tests {
451 use super::*;
452
453 #[test]
454 fn sidecar_variant_rlp_roundtrip() {
455 let mut encoded = Vec::new();
456
457 let empty_sidecar_4844 =
459 BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar::default());
460 empty_sidecar_4844.encode(&mut encoded);
461 assert_eq!(
462 empty_sidecar_4844,
463 BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap()
464 );
465
466 let sidecar_4844 = BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar::new(
467 vec![Blob::default()],
468 vec![Bytes48::ZERO],
469 vec![Bytes48::ZERO],
470 ));
471 encoded.clear();
472 sidecar_4844.encode(&mut encoded);
473 assert_eq!(sidecar_4844, BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap());
474
475 let empty_sidecar_7594 =
477 BlobTransactionSidecarVariant::Eip7594(BlobTransactionSidecarEip7594::default());
478 encoded.clear();
479 empty_sidecar_7594.encode(&mut encoded);
480 assert_eq!(
481 empty_sidecar_7594,
482 BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap()
483 );
484
485 let sidecar_7594 =
486 BlobTransactionSidecarVariant::Eip7594(BlobTransactionSidecarEip7594::new(
487 vec![Blob::default()],
488 vec![Bytes48::ZERO],
489 core::iter::repeat_n(Bytes48::ZERO, CELLS_PER_EXT_BLOB).collect(),
490 ));
491 encoded.clear();
492 sidecar_7594.encode(&mut encoded);
493 assert_eq!(sidecar_7594, BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap());
494 }
495
496 #[test]
497 fn rlp_7594_roundtrip() {
498 let mut encoded = Vec::new();
499
500 let sidecar_4844 = BlobTransactionSidecar::default();
501 sidecar_4844.encode_7594(&mut encoded);
502 assert_eq!(sidecar_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
503
504 let sidecar_variant_4844 = BlobTransactionSidecarVariant::Eip4844(sidecar_4844);
505 assert_eq!(sidecar_variant_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
506 encoded.clear();
507 sidecar_variant_4844.encode_7594(&mut encoded);
508 assert_eq!(sidecar_variant_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
509
510 let sidecar_7594 = BlobTransactionSidecarEip7594::default();
511 encoded.clear();
512 sidecar_7594.encode_7594(&mut encoded);
513 assert_eq!(sidecar_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
514
515 let sidecar_variant_7594 = BlobTransactionSidecarVariant::Eip7594(sidecar_7594);
516 assert_eq!(sidecar_variant_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
517 encoded.clear();
518 sidecar_variant_7594.encode_7594(&mut encoded);
519 assert_eq!(sidecar_variant_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
520 }
521}