use crate::{
eip4844::{
Blob, BlobAndProofV2, BlobCellsAndProofsV1, BlobTransactionSidecar, Bytes48,
BYTES_PER_BLOB, BYTES_PER_COMMITMENT, BYTES_PER_PROOF,
},
eip7594::{Cell, CELLS_PER_EXT_BLOB, EIP_7594_WRAPPER_VERSION},
};
use alloc::{boxed::Box, vec::Vec};
use alloy_primitives::{B128, B256};
use alloy_rlp::{BufMut, Decodable, Encodable, Header};
use super::{Decodable7594, Encodable7594};
#[cfg(feature = "kzg")]
use crate::eip4844::BlobTransactionValidationError;
use crate::eip4844::VersionedHashIter;
#[derive(Clone, PartialEq, Eq, Hash, Debug, derive_more::From)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "serde", serde(untagged))]
#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
pub enum BlobTransactionSidecarVariant {
Eip4844(BlobTransactionSidecar),
Eip7594(BlobTransactionSidecarEip7594),
}
impl Default for BlobTransactionSidecarVariant {
fn default() -> Self {
Self::Eip4844(BlobTransactionSidecar::default())
}
}
impl BlobTransactionSidecarVariant {
pub const fn is_eip4844(&self) -> bool {
matches!(self, Self::Eip4844(_))
}
pub const fn is_eip7594(&self) -> bool {
matches!(self, Self::Eip7594(_))
}
pub const fn as_eip4844(&self) -> Option<&BlobTransactionSidecar> {
match self {
Self::Eip4844(sidecar) => Some(sidecar),
_ => None,
}
}
pub const fn as_eip7594(&self) -> Option<&BlobTransactionSidecarEip7594> {
match self {
Self::Eip7594(sidecar) => Some(sidecar),
_ => None,
}
}
pub fn into_eip4844(self) -> Option<BlobTransactionSidecar> {
match self {
Self::Eip4844(sidecar) => Some(sidecar),
_ => None,
}
}
pub fn into_eip7594(self) -> Option<BlobTransactionSidecarEip7594> {
match self {
Self::Eip7594(sidecar) => Some(sidecar),
_ => None,
}
}
pub fn blobs(&self) -> &[Blob] {
match self {
Self::Eip4844(sidecar) => &sidecar.blobs,
Self::Eip7594(sidecar) => &sidecar.blobs,
}
}
pub fn into_blobs(self) -> Vec<Blob> {
match self {
Self::Eip4844(sidecar) => sidecar.blobs,
Self::Eip7594(sidecar) => sidecar.blobs,
}
}
#[inline]
pub const fn size(&self) -> usize {
match self {
Self::Eip4844(sidecar) => sidecar.size(),
Self::Eip7594(sidecar) => sidecar.size(),
}
}
#[cfg(feature = "kzg")]
pub fn try_convert_into_eip7594(self) -> Result<Self, c_kzg::Error> {
self.try_convert_into_eip7594_with_settings(
crate::eip4844::env_settings::EnvKzgSettings::Default.get(),
)
}
#[cfg(feature = "kzg")]
pub fn try_convert_into_eip7594_with_settings(
self,
settings: &c_kzg::KzgSettings,
) -> Result<Self, c_kzg::Error> {
match self {
Self::Eip4844(legacy) => legacy.try_into_7594(settings).map(Self::Eip7594),
sidecar @ Self::Eip7594(_) => Ok(sidecar),
}
}
#[cfg(feature = "kzg")]
pub fn try_into_eip7594(self) -> Result<BlobTransactionSidecarEip7594, c_kzg::Error> {
self.try_into_eip7594_with_settings(
crate::eip4844::env_settings::EnvKzgSettings::Default.get(),
)
}
#[cfg(feature = "kzg")]
pub fn try_into_eip7594_with_settings(
self,
settings: &c_kzg::KzgSettings,
) -> Result<BlobTransactionSidecarEip7594, c_kzg::Error> {
match self {
Self::Eip4844(legacy) => legacy.try_into_7594(settings),
Self::Eip7594(sidecar) => Ok(sidecar),
}
}
#[cfg(feature = "kzg")]
pub fn validate(
&self,
blob_versioned_hashes: &[B256],
proof_settings: &c_kzg::KzgSettings,
) -> Result<(), BlobTransactionValidationError> {
match self {
Self::Eip4844(sidecar) => sidecar.validate(blob_versioned_hashes, proof_settings),
Self::Eip7594(sidecar) => sidecar.validate(blob_versioned_hashes, proof_settings),
}
}
pub fn commitments(&self) -> &[Bytes48] {
match self {
Self::Eip4844(sidecar) => &sidecar.commitments,
Self::Eip7594(sidecar) => &sidecar.commitments,
}
}
pub fn versioned_hashes(&self) -> VersionedHashIter<'_> {
VersionedHashIter::new(self.commitments())
}
pub fn versioned_hash_index(&self, hash: &B256) -> Option<usize> {
match self {
Self::Eip4844(s) => s.versioned_hash_index(hash),
Self::Eip7594(s) => s.versioned_hash_index(hash),
}
}
pub fn blob_by_versioned_hash(&self, hash: &B256) -> Option<&Blob> {
match self {
Self::Eip4844(s) => s.blob_by_versioned_hash(hash),
Self::Eip7594(s) => s.blob_by_versioned_hash(hash),
}
}
#[doc(hidden)]
pub fn rlp_encoded_fields_length(&self) -> usize {
match self {
Self::Eip4844(sidecar) => sidecar.rlp_encoded_fields_length(),
Self::Eip7594(sidecar) => sidecar.rlp_encoded_fields_length(),
}
}
#[inline]
#[doc(hidden)]
pub fn rlp_encoded_fields(&self) -> Vec<u8> {
let mut buf = Vec::with_capacity(self.rlp_encoded_fields_length());
self.rlp_encode_fields(&mut buf);
buf
}
#[inline]
#[doc(hidden)]
pub fn rlp_encode_fields(&self, out: &mut dyn BufMut) {
match self {
Self::Eip4844(sidecar) => sidecar.rlp_encode_fields(out),
Self::Eip7594(sidecar) => sidecar.rlp_encode_fields(out),
}
}
#[doc(hidden)]
pub fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
Self::decode_7594(buf)
}
}
impl Encodable for BlobTransactionSidecarVariant {
fn encode(&self, out: &mut dyn BufMut) {
match self {
Self::Eip4844(sidecar) => sidecar.encode(out),
Self::Eip7594(sidecar) => sidecar.encode(out),
}
}
fn length(&self) -> usize {
match self {
Self::Eip4844(sidecar) => sidecar.rlp_encoded_length(),
Self::Eip7594(sidecar) => sidecar.rlp_encoded_length(),
}
}
}
impl Decodable for BlobTransactionSidecarVariant {
fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
let header = Header::decode(buf)?;
if !header.list {
return Err(alloy_rlp::Error::UnexpectedString);
}
if buf.len() < header.payload_length {
return Err(alloy_rlp::Error::InputTooShort);
}
let remaining = buf.len();
let this = Self::rlp_decode_fields(buf)?;
if buf.len() + header.payload_length != remaining {
return Err(alloy_rlp::Error::UnexpectedLength);
}
Ok(this)
}
}
impl Encodable7594 for BlobTransactionSidecarVariant {
fn encode_7594_len(&self) -> usize {
self.rlp_encoded_fields_length()
}
fn encode_7594(&self, out: &mut dyn BufMut) {
self.rlp_encode_fields(out);
}
}
impl Decodable7594 for BlobTransactionSidecarVariant {
fn decode_7594(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
if buf.first() == Some(&EIP_7594_WRAPPER_VERSION) {
Ok(Self::Eip7594(Decodable7594::decode_7594(buf)?))
} else {
Ok(Self::Eip4844(Decodable7594::decode_7594(buf)?))
}
}
}
#[cfg(feature = "kzg")]
impl TryFrom<BlobTransactionSidecarVariant> for BlobTransactionSidecarEip7594 {
type Error = c_kzg::Error;
fn try_from(value: BlobTransactionSidecarVariant) -> Result<Self, Self::Error> {
value.try_into_eip7594()
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for BlobTransactionSidecarVariant {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use core::fmt;
#[derive(serde::Deserialize, fmt::Debug)]
#[serde(field_identifier, rename_all = "camelCase")]
enum Field {
Blobs,
Commitments,
Proofs,
CellProofs,
}
struct VariantVisitor;
impl<'de> serde::de::Visitor<'de> for VariantVisitor {
type Value = BlobTransactionSidecarVariant;
fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
formatter
.write_str("a valid blob transaction sidecar (EIP-4844 or EIP-7594 variant)")
}
fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
where
M: serde::de::MapAccess<'de>,
{
let mut blobs = None;
let mut commitments = None;
let mut proofs = None;
let mut cell_proofs = None;
while let Some(key) = map.next_key()? {
match key {
Field::Blobs => {
blobs = Some(crate::eip4844::deserialize_blobs_map(&mut map)?);
}
Field::Commitments => commitments = Some(map.next_value()?),
Field::Proofs => proofs = Some(map.next_value()?),
Field::CellProofs => cell_proofs = Some(map.next_value()?),
}
}
let blobs = blobs.ok_or_else(|| serde::de::Error::missing_field("blobs"))?;
let commitments =
commitments.ok_or_else(|| serde::de::Error::missing_field("commitments"))?;
match (cell_proofs, proofs) {
(Some(cp), None) => {
Ok(BlobTransactionSidecarVariant::Eip7594(BlobTransactionSidecarEip7594 {
blobs,
commitments,
cell_proofs: cp,
}))
}
(None, Some(pf)) => {
Ok(BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar {
blobs,
commitments,
proofs: pf,
}))
}
(None, None) => {
Err(serde::de::Error::custom("Missing 'cellProofs' or 'proofs'"))
}
(Some(_), Some(_)) => Err(serde::de::Error::custom(
"Both 'cellProofs' and 'proofs' cannot be present",
)),
}
}
}
const FIELDS: &[&str] = &["blobs", "commitments", "proofs", "cellProofs"];
deserializer.deserialize_struct("BlobTransactionSidecarVariant", FIELDS, VariantVisitor)
}
}
#[derive(Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
pub struct BlobTransactionSidecarEip7594 {
#[cfg_attr(feature = "serde", serde(deserialize_with = "crate::eip4844::deserialize_blobs"))]
pub blobs: Vec<Blob>,
pub commitments: Vec<Bytes48>,
pub cell_proofs: Vec<Bytes48>,
}
impl core::fmt::Debug for BlobTransactionSidecarEip7594 {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("BlobTransactionSidecarEip7594")
.field("blobs", &self.blobs.len())
.field("commitments", &self.commitments)
.field("cell_proofs", &self.cell_proofs)
.finish()
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct BlobCellMask {
value: u128,
}
impl BlobCellMask {
#[inline]
pub fn new(indices_bitarray: B128) -> Self {
Self { value: u128::from(indices_bitarray) }
}
#[inline]
pub const fn from_bits(value: u128) -> Self {
Self { value }
}
#[inline]
pub const fn bits(self) -> u128 {
self.value
}
#[inline]
pub const fn count(self) -> usize {
self.value.count_ones() as usize
}
#[inline]
pub const fn contains(self, index: usize) -> bool {
index < CELLS_PER_EXT_BLOB && self.value & (1u128 << index) != 0
}
#[inline]
pub fn selected_indices(self) -> impl Iterator<Item = usize> {
let mut bits = self.value;
core::iter::from_fn(move || {
if bits == 0 {
return None;
}
let index = bits.trailing_zeros() as usize;
bits &= bits - 1;
Some(index)
})
}
}
impl BlobTransactionSidecarEip7594 {
pub const fn new(
blobs: Vec<Blob>,
commitments: Vec<Bytes48>,
cell_proofs: Vec<Bytes48>,
) -> Self {
Self { blobs, commitments, cell_proofs }
}
#[inline]
pub const fn size(&self) -> usize {
self.blobs.capacity() * BYTES_PER_BLOB
+ self.commitments.capacity() * BYTES_PER_COMMITMENT
+ self.cell_proofs.capacity() * BYTES_PER_PROOF
}
#[inline]
pub fn shrink_to_fit(&mut self) {
self.blobs.shrink_to_fit();
self.commitments.shrink_to_fit();
self.cell_proofs.shrink_to_fit();
}
#[cfg(all(feature = "kzg", any(test, feature = "arbitrary")))]
pub fn try_from_blobs_hex<I, B>(blobs: I) -> Result<Self, c_kzg::Error>
where
I: IntoIterator<Item = B>,
B: AsRef<str>,
{
let mut converted = Vec::new();
for blob in blobs {
converted.push(crate::eip4844::utils::hex_to_blob(blob)?);
}
Self::try_from_blobs(converted)
}
#[cfg(all(feature = "kzg", any(test, feature = "arbitrary")))]
pub fn try_from_blobs_bytes<I, B>(blobs: I) -> Result<Self, c_kzg::Error>
where
I: IntoIterator<Item = B>,
B: AsRef<[u8]>,
{
let mut converted = Vec::new();
for blob in blobs {
converted.push(crate::eip4844::utils::bytes_to_blob(blob)?);
}
Self::try_from_blobs(converted)
}
#[cfg(feature = "kzg")]
pub fn try_from_blobs_with_settings(
blobs: Vec<Blob>,
settings: &c_kzg::KzgSettings,
) -> Result<Self, c_kzg::Error> {
let mut commitments = Vec::with_capacity(blobs.len());
let mut proofs = Vec::with_capacity(blobs.len());
for blob in &blobs {
let blob = unsafe { core::mem::transmute::<&Blob, &c_kzg::Blob>(blob) };
let commitment = settings.blob_to_kzg_commitment(blob)?;
let (_cells, kzg_proofs) = settings.compute_cells_and_kzg_proofs(blob)?;
unsafe {
commitments
.push(core::mem::transmute::<c_kzg::Bytes48, Bytes48>(commitment.to_bytes()));
for kzg_proof in kzg_proofs.iter() {
proofs.push(core::mem::transmute::<c_kzg::Bytes48, Bytes48>(
kzg_proof.to_bytes(),
));
}
}
}
Ok(Self::new(blobs, commitments, proofs))
}
#[cfg(feature = "kzg")]
pub fn try_from_blobs(blobs: Vec<Blob>) -> Result<Self, c_kzg::Error> {
use crate::eip4844::env_settings::EnvKzgSettings;
Self::try_from_blobs_with_settings(blobs, EnvKzgSettings::Default.get())
}
#[cfg(feature = "kzg")]
pub fn compute_cells(&self) -> Result<Vec<Cell>, c_kzg::Error> {
use crate::eip4844::env_settings::EnvKzgSettings;
self.compute_cells_with_settings(EnvKzgSettings::Default.get())
}
#[cfg(feature = "kzg")]
pub fn compute_cells_with_settings(
&self,
settings: &c_kzg::KzgSettings,
) -> Result<Vec<Cell>, c_kzg::Error> {
let mut cells = Vec::with_capacity(self.blobs.len() * CELLS_PER_EXT_BLOB);
for blob in &self.blobs {
let blob = unsafe { core::mem::transmute::<&Blob, &c_kzg::Blob>(blob) };
let blob_cells = settings.compute_cells(blob)?;
cells.extend(blob_cells.iter().map(|cell| Cell::new(cell.to_bytes())));
}
Ok(cells)
}
#[cfg(feature = "kzg")]
pub fn validate(
&self,
blob_versioned_hashes: &[B256],
proof_settings: &c_kzg::KzgSettings,
) -> Result<(), BlobTransactionValidationError> {
if blob_versioned_hashes.len() != self.commitments.len() {
return Err(c_kzg::Error::MismatchLength(format!(
"There are {} versioned commitment hashes and {} commitments",
blob_versioned_hashes.len(),
self.commitments.len()
))
.into());
}
let blobs_len = self.blobs.len();
let expected_cell_proofs_len = blobs_len * CELLS_PER_EXT_BLOB;
if self.cell_proofs.len() != expected_cell_proofs_len {
return Err(c_kzg::Error::MismatchLength(format!(
"There are {} cell proofs and {} blobs. Expected {} cell proofs.",
self.cell_proofs.len(),
blobs_len,
expected_cell_proofs_len
))
.into());
}
for (versioned_hash, commitment) in
blob_versioned_hashes.iter().zip(self.commitments.iter())
{
let calculated_versioned_hash =
crate::eip4844::kzg_to_versioned_hash(commitment.as_slice());
if *versioned_hash != calculated_versioned_hash {
return Err(BlobTransactionValidationError::WrongVersionedHash {
have: *versioned_hash,
expected: calculated_versioned_hash,
});
}
}
let cell_indices =
Vec::from_iter((0..blobs_len).flat_map(|_| 0..CELLS_PER_EXT_BLOB as u64));
let mut commitments = Vec::with_capacity(blobs_len * CELLS_PER_EXT_BLOB);
for commitment in &self.commitments {
commitments.extend(core::iter::repeat_n(*commitment, CELLS_PER_EXT_BLOB));
}
let res = unsafe {
let mut cells = Vec::with_capacity(blobs_len * CELLS_PER_EXT_BLOB);
for blob in &self.blobs {
let blob = core::mem::transmute::<&Blob, &c_kzg::Blob>(blob);
let blob_cells = proof_settings.compute_cells(blob)?;
cells.extend_from_slice(blob_cells.as_ref());
}
proof_settings.verify_cell_kzg_proof_batch(
core::mem::transmute::<&[Bytes48], &[c_kzg::Bytes48]>(&commitments),
&cell_indices,
&cells,
core::mem::transmute::<&[Bytes48], &[c_kzg::Bytes48]>(self.cell_proofs.as_slice()),
)?
};
res.then_some(()).ok_or(BlobTransactionValidationError::InvalidProof)
}
pub fn versioned_hashes(&self) -> VersionedHashIter<'_> {
VersionedHashIter::new(&self.commitments)
}
pub fn versioned_hash_index(&self, hash: &B256) -> Option<usize> {
self.commitments.iter().position(|commitment| {
crate::eip4844::kzg_to_versioned_hash(commitment.as_slice()) == *hash
})
}
pub fn blob_by_versioned_hash(&self, hash: &B256) -> Option<&Blob> {
self.versioned_hash_index(hash).and_then(|index| self.blobs.get(index))
}
#[cfg(feature = "kzg")]
pub fn blob_cells_and_proofs(
&self,
blob_index: usize,
cell_mask: BlobCellMask,
) -> Result<Option<BlobCellsAndProofsV1>, c_kzg::Error> {
use crate::eip4844::env_settings::EnvKzgSettings;
self.blob_cells_and_proofs_with_settings(
blob_index,
cell_mask,
EnvKzgSettings::Default.get(),
)
}
#[cfg(feature = "kzg")]
pub fn blob_cells_and_proofs_with_settings(
&self,
blob_index: usize,
cell_mask: BlobCellMask,
settings: &c_kzg::KzgSettings,
) -> Result<Option<BlobCellsAndProofsV1>, c_kzg::Error> {
let Some(blob) = self.blobs.get(blob_index) else { return Ok(None) };
let proof_start = blob_index * CELLS_PER_EXT_BLOB;
let Some(proofs) = self.cell_proofs.get(proof_start..proof_start + CELLS_PER_EXT_BLOB)
else {
return Ok(None);
};
if cell_mask.count() == 0 {
return Ok(Some(BlobCellsAndProofsV1::default()));
}
let blob = unsafe { core::mem::transmute::<&Blob, &c_kzg::Blob>(blob) };
let cells = settings.compute_cells(blob)?;
Ok(Some(Self::blob_cells_and_proofs_from_computed_cells(cell_mask, cells.as_ref(), proofs)))
}
#[cfg(feature = "kzg")]
fn blob_cells_and_proofs_from_computed_cells(
cell_mask: BlobCellMask,
cells: &[c_kzg::Cell],
proofs: &[Bytes48],
) -> BlobCellsAndProofsV1 {
let mut blob_cells = Vec::with_capacity(cell_mask.count());
let mut selected_proofs = Vec::with_capacity(cell_mask.count());
for cell_index in cell_mask.selected_indices() {
blob_cells.push(Some(Cell::new(cells[cell_index].to_bytes())));
selected_proofs.push(Some(proofs[cell_index]));
}
BlobCellsAndProofsV1 { blob_cells, proofs: selected_proofs }
}
pub fn match_versioned_hashes<'a>(
&'a self,
versioned_hashes: &'a [B256],
) -> impl Iterator<Item = (usize, BlobAndProofV2)> + 'a {
self.versioned_hashes().enumerate().flat_map(move |(i, blob_versioned_hash)| {
versioned_hashes.iter().enumerate().filter_map(move |(j, target_hash)| {
if blob_versioned_hash == *target_hash {
let maybe_blob = self.blobs.get(i);
let proof_range = i * CELLS_PER_EXT_BLOB..(i + 1) * CELLS_PER_EXT_BLOB;
let maybe_proofs = Some(&self.cell_proofs[proof_range])
.filter(|proofs| proofs.len() == CELLS_PER_EXT_BLOB);
if let Some((blob, proofs)) = maybe_blob.copied().zip(maybe_proofs) {
return Some((
j,
BlobAndProofV2 { blob: Box::new(blob), proofs: proofs.to_vec() },
));
}
}
None
})
})
}
#[cfg(feature = "kzg")]
pub fn match_versioned_hashes_cells<'a>(
&'a self,
versioned_hashes: &'a [B256],
cell_mask: BlobCellMask,
) -> Result<impl Iterator<Item = (usize, BlobCellsAndProofsV1)> + 'a, c_kzg::Error> {
use crate::eip4844::env_settings::EnvKzgSettings;
self.match_versioned_hashes_cells_with_settings(
versioned_hashes,
cell_mask,
EnvKzgSettings::Default.get(),
)
}
#[cfg(feature = "kzg")]
pub fn match_versioned_hashes_cells_with_settings<'a>(
&'a self,
versioned_hashes: &'a [B256],
cell_mask: BlobCellMask,
settings: &c_kzg::KzgSettings,
) -> Result<impl Iterator<Item = (usize, BlobCellsAndProofsV1)> + 'a, c_kzg::Error> {
let mut matches = Vec::new();
let mut cells_and_proofs_by_blob = Vec::<(usize, BlobCellsAndProofsV1)>::new();
for (blob_index, commitment) in self.commitments.iter().enumerate() {
let blob_versioned_hash = crate::eip4844::kzg_to_versioned_hash(commitment.as_slice());
for (matched_index, target_hash) in versioned_hashes.iter().enumerate() {
if blob_versioned_hash != *target_hash {
continue;
}
let Some(blob) = self.blobs.get(blob_index) else { continue };
let proof_start = blob_index * CELLS_PER_EXT_BLOB;
let Some(proofs) =
self.cell_proofs.get(proof_start..proof_start + CELLS_PER_EXT_BLOB)
else {
continue;
};
let cells_and_proofs = if cell_mask.count() == 0 {
BlobCellsAndProofsV1::default()
} else if let Some((_, cells_and_proofs)) =
cells_and_proofs_by_blob.iter().find(|(index, _)| *index == blob_index)
{
cells_and_proofs.clone()
} else {
let blob = unsafe { core::mem::transmute::<&Blob, &c_kzg::Blob>(blob) };
let cells = settings.compute_cells(blob)?;
let cells_and_proofs = Self::blob_cells_and_proofs_from_computed_cells(
cell_mask,
cells.as_ref(),
proofs,
);
cells_and_proofs_by_blob.push((blob_index, cells_and_proofs.clone()));
cells_and_proofs
};
matches.push((matched_index, cells_and_proofs));
}
}
Ok(matches.into_iter())
}
#[doc(hidden)]
pub fn rlp_encoded_fields_length(&self) -> usize {
1 + self.blobs.length() + self.commitments.length() + self.cell_proofs.length()
}
#[inline]
#[doc(hidden)]
pub fn rlp_encode_fields(&self, out: &mut dyn BufMut) {
out.put_u8(EIP_7594_WRAPPER_VERSION);
self.blobs.encode(out);
self.commitments.encode(out);
self.cell_proofs.encode(out);
}
fn rlp_header(&self) -> Header {
Header { list: true, payload_length: self.rlp_encoded_fields_length() }
}
pub fn rlp_encoded_length(&self) -> usize {
self.rlp_header().length() + self.rlp_encoded_fields_length()
}
pub fn rlp_encode(&self, out: &mut dyn BufMut) {
self.rlp_header().encode(out);
self.rlp_encode_fields(out);
}
#[doc(hidden)]
pub fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
Ok(Self {
blobs: Decodable::decode(buf)?,
commitments: Decodable::decode(buf)?,
cell_proofs: Decodable::decode(buf)?,
})
}
pub fn rlp_decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
let header = Header::decode(buf)?;
if !header.list {
return Err(alloy_rlp::Error::UnexpectedString);
}
if buf.len() < header.payload_length {
return Err(alloy_rlp::Error::InputTooShort);
}
let remaining = buf.len();
let this = Self::decode_7594(buf)?;
if buf.len() + header.payload_length != remaining {
return Err(alloy_rlp::Error::UnexpectedLength);
}
Ok(this)
}
}
impl Encodable for BlobTransactionSidecarEip7594 {
fn encode(&self, out: &mut dyn BufMut) {
self.rlp_encode(out);
}
fn length(&self) -> usize {
self.rlp_encoded_length()
}
}
impl Decodable for BlobTransactionSidecarEip7594 {
fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
Self::rlp_decode(buf)
}
}
impl Encodable7594 for BlobTransactionSidecarEip7594 {
fn encode_7594_len(&self) -> usize {
self.rlp_encoded_fields_length()
}
fn encode_7594(&self, out: &mut dyn BufMut) {
self.rlp_encode_fields(out);
}
}
impl Decodable7594 for BlobTransactionSidecarEip7594 {
fn decode_7594(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
let wrapper_version: u8 = Decodable::decode(buf)?;
if wrapper_version != EIP_7594_WRAPPER_VERSION {
return Err(alloy_rlp::Error::Custom("invalid wrapper version"));
}
Self::rlp_decode_fields(buf)
}
}
#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
pub mod serde_bincode_compat {
use crate::eip4844::{Blob, Bytes48};
use alloc::{borrow::Cow, vec::Vec};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_with::{DeserializeAs, SerializeAs};
#[derive(Debug, Serialize, Deserialize)]
pub struct BlobTransactionSidecarVariant<'a> {
pub blobs: Cow<'a, Vec<Blob>>,
pub commitments: Cow<'a, Vec<Bytes48>>,
pub proofs: Option<Cow<'a, Vec<Bytes48>>>,
pub cell_proofs: Option<Cow<'a, Vec<Bytes48>>>,
}
impl<'a> From<&'a super::BlobTransactionSidecarVariant> for BlobTransactionSidecarVariant<'a> {
fn from(value: &'a super::BlobTransactionSidecarVariant) -> Self {
match value {
super::BlobTransactionSidecarVariant::Eip4844(sidecar) => Self {
blobs: Cow::Borrowed(&sidecar.blobs),
commitments: Cow::Borrowed(&sidecar.commitments),
proofs: Some(Cow::Borrowed(&sidecar.proofs)),
cell_proofs: None,
},
super::BlobTransactionSidecarVariant::Eip7594(sidecar) => Self {
blobs: Cow::Borrowed(&sidecar.blobs),
commitments: Cow::Borrowed(&sidecar.commitments),
proofs: None,
cell_proofs: Some(Cow::Borrowed(&sidecar.cell_proofs)),
},
}
}
}
impl<'a> BlobTransactionSidecarVariant<'a> {
fn try_into_inner(self) -> Result<super::BlobTransactionSidecarVariant, &'static str> {
match (self.proofs, self.cell_proofs) {
(Some(proofs), None) => Ok(super::BlobTransactionSidecarVariant::Eip4844(
crate::eip4844::BlobTransactionSidecar {
blobs: self.blobs.into_owned(),
commitments: self.commitments.into_owned(),
proofs: proofs.into_owned(),
},
)),
(None, Some(cell_proofs)) => Ok(super::BlobTransactionSidecarVariant::Eip7594(
super::BlobTransactionSidecarEip7594 {
blobs: self.blobs.into_owned(),
commitments: self.commitments.into_owned(),
cell_proofs: cell_proofs.into_owned(),
},
)),
(None, None) => Err("Missing both 'proofs' and 'cell_proofs'"),
(Some(_), Some(_)) => Err("Both 'proofs' and 'cell_proofs' cannot be present"),
}
}
}
impl<'a> From<BlobTransactionSidecarVariant<'a>> for super::BlobTransactionSidecarVariant {
fn from(value: BlobTransactionSidecarVariant<'a>) -> Self {
value.try_into_inner().expect("Invalid BlobTransactionSidecarVariant")
}
}
impl SerializeAs<super::BlobTransactionSidecarVariant> for BlobTransactionSidecarVariant<'_> {
fn serialize_as<S>(
source: &super::BlobTransactionSidecarVariant,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
BlobTransactionSidecarVariant::from(source).serialize(serializer)
}
}
impl<'de> DeserializeAs<'de, super::BlobTransactionSidecarVariant>
for BlobTransactionSidecarVariant<'de>
{
fn deserialize_as<D>(
deserializer: D,
) -> Result<super::BlobTransactionSidecarVariant, D::Error>
where
D: Deserializer<'de>,
{
let value = BlobTransactionSidecarVariant::deserialize(deserializer)?;
value.try_into_inner().map_err(serde::de::Error::custom)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "kzg")]
use crate::eip4844::{
builder::{SidecarBuilder, SimpleCoder},
env_settings::EnvKzgSettings,
};
#[test]
fn sidecar_variant_rlp_roundtrip() {
let mut encoded = Vec::new();
let empty_sidecar_4844 =
BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar::default());
empty_sidecar_4844.encode(&mut encoded);
assert_eq!(
empty_sidecar_4844,
BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap()
);
let sidecar_4844 = BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar::new(
vec![Blob::default()],
vec![Bytes48::ZERO],
vec![Bytes48::ZERO],
));
encoded.clear();
sidecar_4844.encode(&mut encoded);
assert_eq!(sidecar_4844, BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap());
let empty_sidecar_7594 =
BlobTransactionSidecarVariant::Eip7594(BlobTransactionSidecarEip7594::default());
encoded.clear();
empty_sidecar_7594.encode(&mut encoded);
assert_eq!(
empty_sidecar_7594,
BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap()
);
let sidecar_7594 =
BlobTransactionSidecarVariant::Eip7594(BlobTransactionSidecarEip7594::new(
vec![Blob::default()],
vec![Bytes48::ZERO],
core::iter::repeat_n(Bytes48::ZERO, CELLS_PER_EXT_BLOB).collect(),
));
encoded.clear();
sidecar_7594.encode(&mut encoded);
assert_eq!(sidecar_7594, BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap());
}
#[test]
#[cfg(feature = "serde")]
fn sidecar_variant_json_deserialize_sanity() {
let mut eip4844 = BlobTransactionSidecar::default();
eip4844.blobs.push(Blob::repeat_byte(0x2));
let json = serde_json::to_string(&eip4844).unwrap();
let variant: BlobTransactionSidecarVariant = serde_json::from_str(&json).unwrap();
assert!(variant.is_eip4844());
let jsonvariant = serde_json::to_string(&variant).unwrap();
assert_eq!(json, jsonvariant);
let mut eip7594 = BlobTransactionSidecarEip7594::default();
eip7594.blobs.push(Blob::repeat_byte(0x4));
let json = serde_json::to_string(&eip7594).unwrap();
let variant: BlobTransactionSidecarVariant = serde_json::from_str(&json).unwrap();
assert!(variant.is_eip7594());
let jsonvariant = serde_json::to_string(&variant).unwrap();
assert_eq!(json, jsonvariant);
}
#[test]
fn rlp_7594_roundtrip() {
let mut encoded = Vec::new();
let sidecar_4844 = BlobTransactionSidecar::default();
sidecar_4844.encode_7594(&mut encoded);
assert_eq!(sidecar_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
let sidecar_variant_4844 = BlobTransactionSidecarVariant::Eip4844(sidecar_4844);
assert_eq!(sidecar_variant_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
encoded.clear();
sidecar_variant_4844.encode_7594(&mut encoded);
assert_eq!(sidecar_variant_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
let sidecar_7594 = BlobTransactionSidecarEip7594::default();
encoded.clear();
sidecar_7594.encode_7594(&mut encoded);
assert_eq!(sidecar_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
let sidecar_variant_7594 = BlobTransactionSidecarVariant::Eip7594(sidecar_7594);
assert_eq!(sidecar_variant_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
encoded.clear();
sidecar_variant_7594.encode_7594(&mut encoded);
assert_eq!(sidecar_variant_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
}
#[test]
#[cfg(feature = "kzg")]
fn validate_7594_sidecar() {
let sidecar =
SidecarBuilder::<SimpleCoder>::from_slice(b"Blobs are fun!").build_7594().unwrap();
let versioned_hashes = sidecar.versioned_hashes().collect::<Vec<_>>();
sidecar.validate(&versioned_hashes, EnvKzgSettings::Default.get()).unwrap();
}
#[test]
#[cfg(feature = "kzg")]
fn compute_cells_for_7594_sidecar() {
let settings = EnvKzgSettings::Default.get();
let sidecar = BlobTransactionSidecarEip7594::try_from_blobs_with_settings(
vec![Blob::repeat_byte(0x01), Blob::repeat_byte(0x02)],
settings,
)
.unwrap();
let cells = sidecar.compute_cells_with_settings(settings).unwrap();
assert_eq!(cells.len(), sidecar.blobs.len() * CELLS_PER_EXT_BLOB);
assert_eq!(sidecar.compute_cells().unwrap(), cells);
for (blob_index, blob) in sidecar.blobs.iter().enumerate() {
let blob = unsafe { core::mem::transmute::<&Blob, &c_kzg::Blob>(blob) };
let expected_cells = settings.compute_cells(blob).unwrap();
let start = blob_index * CELLS_PER_EXT_BLOB;
let end = start + CELLS_PER_EXT_BLOB;
for (cell, expected_cell) in cells[start..end].iter().zip(expected_cells.iter()) {
assert_eq!(*cell, Cell::new(expected_cell.to_bytes()));
}
}
}
#[test]
fn blob_cell_mask_selects_indices() {
let selected = (1u128 << 0) | (1u128 << 7);
let mask = BlobCellMask::new(B128::from(selected));
assert_eq!(mask.bits(), selected);
assert_eq!(mask.count(), 2);
assert!(mask.contains(0));
assert!(mask.contains(7));
assert!(!mask.contains(1));
assert_eq!(mask.selected_indices().collect::<Vec<_>>(), vec![0, 7]);
}
#[test]
#[cfg(feature = "kzg")]
fn match_versioned_hashes_cells_for_7594_sidecar() {
let settings = EnvKzgSettings::Default.get();
let sidecar = BlobTransactionSidecarEip7594::try_from_blobs_with_settings(
vec![Blob::repeat_byte(0x01), Blob::repeat_byte(0x02)],
settings,
)
.unwrap();
let versioned_hashes = sidecar.versioned_hashes().collect::<Vec<_>>();
let cell_mask = BlobCellMask::from_bits((1u128 << 0) | (1u128 << 7));
let cells_and_proofs =
sidecar.blob_cells_and_proofs_with_settings(0, cell_mask, settings).unwrap().unwrap();
assert_eq!(cells_and_proofs.blob_cells.len(), 2);
assert_eq!(cells_and_proofs.proofs.len(), 2);
assert_eq!(
cells_and_proofs.proofs,
vec![Some(sidecar.cell_proofs[0]), Some(sidecar.cell_proofs[7])]
);
let blob = unsafe { core::mem::transmute::<&Blob, &c_kzg::Blob>(&sidecar.blobs[0]) };
let expected_cells = settings.compute_cells(blob).unwrap();
assert_eq!(
cells_and_proofs.blob_cells,
vec![
Some(Cell::new(expected_cells[0].to_bytes())),
Some(Cell::new(expected_cells[7].to_bytes()))
]
);
let request = vec![versioned_hashes[0], B256::ZERO, versioned_hashes[0]];
let matches = sidecar
.match_versioned_hashes_cells_with_settings(&request, cell_mask, settings)
.unwrap()
.collect::<Vec<_>>();
assert_eq!(matches.len(), 2);
assert_eq!(matches[0], (0, cells_and_proofs.clone()));
assert_eq!(matches[1], (2, cells_and_proofs.clone()));
let default_matches = sidecar
.match_versioned_hashes_cells(&[versioned_hashes[0]], cell_mask)
.unwrap()
.collect::<Vec<_>>();
assert_eq!(default_matches, vec![(0, cells_and_proofs)]);
}
#[test]
#[cfg(feature = "kzg")]
fn match_versioned_hashes_cells_only_computes_matched_blobs() {
let settings = EnvKzgSettings::Default.get();
let mut sidecar = BlobTransactionSidecarEip7594::try_from_blobs_with_settings(
vec![Blob::repeat_byte(0x01)],
settings,
)
.unwrap();
let versioned_hash = sidecar.versioned_hashes().next().unwrap();
let cell_mask = BlobCellMask::from_bits(1);
let invalid_blob = Blob::repeat_byte(0xff);
let blob = unsafe { core::mem::transmute::<&Blob, &c_kzg::Blob>(&invalid_blob) };
assert!(settings.compute_cells(blob).is_err());
sidecar.blobs.push(invalid_blob);
sidecar.commitments.push(Bytes48::ZERO);
sidecar.cell_proofs.extend(core::iter::repeat_n(Bytes48::ZERO, CELLS_PER_EXT_BLOB));
let cells_and_proofs =
sidecar.blob_cells_and_proofs_with_settings(0, cell_mask, settings).unwrap().unwrap();
let matches = sidecar
.match_versioned_hashes_cells_with_settings(&[versioned_hash], cell_mask, settings)
.unwrap()
.collect::<Vec<_>>();
assert_eq!(matches, vec![(0, cells_and_proofs)]);
}
}