use crate::eip4844::{
utils::WholeFe, Blob, BYTES_PER_BLOB, FIELD_ELEMENTS_PER_BLOB, FIELD_ELEMENT_BYTES_USIZE,
};
use alloc::vec::Vec;
use core::cmp;
#[cfg(any(feature = "kzg", feature = "arbitrary"))]
use crate::eip4844::BlobTransactionSidecar;
#[cfg(feature = "kzg")]
use crate::{eip4844::env_settings::EnvKzgSettings, eip7594::BlobTransactionSidecarEip7594};
#[cfg(feature = "kzg")]
pub trait BuildableSidecar {
fn build_with_settings(
blobs: Vec<Blob>,
settings: &c_kzg::KzgSettings,
) -> Result<Self, c_kzg::Error>
where
Self: Sized;
fn build(blobs: Vec<Blob>) -> Result<Self, c_kzg::Error>
where
Self: Sized,
{
Self::build_with_settings(blobs, EnvKzgSettings::Default.get())
}
}
#[cfg(feature = "kzg")]
impl BuildableSidecar for BlobTransactionSidecar {
fn build_with_settings(
blobs: Vec<Blob>,
settings: &c_kzg::KzgSettings,
) -> Result<Self, c_kzg::Error> {
Self::try_from_blobs_with_settings(blobs, settings)
}
}
#[cfg(feature = "kzg")]
impl BuildableSidecar for BlobTransactionSidecarEip7594 {
fn build_with_settings(
blobs: Vec<Blob>,
settings: &c_kzg::KzgSettings,
) -> Result<Self, c_kzg::Error> {
Self::try_from_blobs_with_settings(blobs, settings)
}
}
#[derive(Clone, Debug)]
pub struct PartialSidecar {
blobs: Vec<Blob>,
fe: usize,
}
impl Default for PartialSidecar {
fn default() -> Self {
Self::new()
}
}
impl PartialSidecar {
pub fn new() -> Self {
Self::with_capacity(2)
}
pub fn with_capacity(capacity: usize) -> Self {
let mut blobs = Vec::with_capacity(capacity);
blobs.push(Blob::new([0u8; BYTES_PER_BLOB]));
Self { blobs, fe: 0 }
}
#[allow(clippy::missing_const_for_fn)]
pub fn blobs(&self) -> &[Blob] {
&self.blobs
}
const fn free_fe(&self) -> usize {
self.blobs.len() * FIELD_ELEMENTS_PER_BLOB as usize - self.fe
}
pub const fn len(&self) -> usize {
self.fe * 32
}
pub const fn is_empty(&self) -> bool {
self.fe == 0
}
fn push_empty_blob(&mut self) {
self.blobs.push(Blob::new([0u8; BYTES_PER_BLOB]));
}
pub fn alloc_fes(&mut self, required_fe: usize) {
while self.free_fe() < required_fe {
self.push_empty_blob()
}
}
const fn fe_in_current_blob(&self) -> usize {
self.fe % FIELD_ELEMENTS_PER_BLOB as usize
}
const fn first_unused_fe_index_in_current_blob(&self) -> usize {
self.fe_in_current_blob()
}
fn current_blob_mut(&mut self) -> &mut Blob {
let last_unused_blob_index = self.fe / FIELD_ELEMENTS_PER_BLOB as usize;
self.blobs.get_mut(last_unused_blob_index).expect("never empty")
}
fn fe_at_mut(&mut self, index: usize) -> &mut [u8] {
&mut self.current_blob_mut()[index * 32..(index + 1) * 32]
}
fn next_unused_fe_mut(&mut self) -> &mut [u8] {
self.fe_at_mut(self.first_unused_fe_index_in_current_blob())
}
pub fn ingest_valid_fe(&mut self, data: WholeFe<'_>) {
self.alloc_fes(1);
self.next_unused_fe_mut().copy_from_slice(data.as_ref());
self.fe += 1;
}
pub fn ingest_partial_fe(&mut self, data: &[u8]) {
self.alloc_fes(1);
let fe = self.next_unused_fe_mut();
fe[1..1 + data.len()].copy_from_slice(data);
self.fe += 1;
}
}
pub trait SidecarCoder {
fn required_fe(&self, data: &[u8]) -> usize;
fn code(&mut self, builder: &mut PartialSidecar, data: &[u8]);
fn finish(self, builder: &mut PartialSidecar);
fn decode_all(&mut self, blobs: &[Blob]) -> Option<Vec<Vec<u8>>>;
}
#[derive(Clone, Copy, Debug, Default)]
#[non_exhaustive]
pub struct SimpleCoder;
impl SimpleCoder {
fn decode_one<'a>(mut fes: impl Iterator<Item = WholeFe<'a>>) -> Result<Option<Vec<u8>>, ()> {
let Some(first) = fes.next() else {
return Ok(None);
};
let mut num_bytes = u64::from_be_bytes(first.as_ref()[1..9].try_into().unwrap()) as usize;
if num_bytes == 0 {
return Ok(None);
}
const MAX_ALLOCATION_SIZE: usize = 2_097_152; if num_bytes > MAX_ALLOCATION_SIZE {
return Err(());
}
let mut res = Vec::with_capacity(num_bytes);
while num_bytes > 0 {
let to_copy = cmp::min(31, num_bytes);
let fe = fes.next().ok_or(())?;
res.extend_from_slice(&fe.as_ref()[1..1 + to_copy]);
num_bytes -= to_copy;
}
Ok(Some(res))
}
}
impl SidecarCoder for SimpleCoder {
fn required_fe(&self, data: &[u8]) -> usize {
data.len().div_ceil(31) + 1
}
fn code(&mut self, builder: &mut PartialSidecar, mut data: &[u8]) {
if data.is_empty() {
return;
}
builder.ingest_partial_fe(&(data.len() as u64).to_be_bytes());
while !data.is_empty() {
let (left, right) = data.split_at(cmp::min(31, data.len()));
builder.ingest_partial_fe(left);
data = right
}
}
fn finish(self, _builder: &mut PartialSidecar) {}
fn decode_all(&mut self, blobs: &[Blob]) -> Option<Vec<Vec<u8>>> {
if blobs.is_empty() {
return None;
}
if blobs
.iter()
.flat_map(|blob| blob.chunks(FIELD_ELEMENT_BYTES_USIZE).map(WholeFe::new))
.any(|fe| fe.is_none())
{
return None;
}
let mut fes = blobs
.iter()
.flat_map(|blob| blob.chunks(FIELD_ELEMENT_BYTES_USIZE).map(WholeFe::new_unchecked));
let mut res = Vec::new();
loop {
match Self::decode_one(&mut fes) {
Ok(Some(data)) => res.push(data),
Ok(None) => break,
Err(()) => return None,
}
}
Some(res)
}
}
#[derive(Clone, Debug)]
pub struct SidecarBuilder<T = SimpleCoder> {
inner: PartialSidecar,
coder: T,
}
impl<T> Default for SidecarBuilder<T>
where
T: Default + SidecarCoder,
{
fn default() -> Self {
Self::new()
}
}
#[cfg(feature = "arbitrary")]
impl<'a, T: arbitrary::Arbitrary<'a> + Clone> SidecarBuilder<T> {
pub fn build_arbitrary(&self) -> BlobTransactionSidecar {
<BlobTransactionSidecar as arbitrary::Arbitrary>::arbitrary(
&mut arbitrary::Unstructured::new(&[]),
)
.unwrap()
}
}
impl<T: SidecarCoder + Default> SidecarBuilder<T> {
pub fn new() -> Self {
T::default().into()
}
pub fn from_slice(data: &[u8]) -> Self {
Self::from_coder_and_data(T::default(), data)
}
pub fn with_capacity(capacity: usize) -> Self {
Self::from_coder_and_capacity(T::default(), capacity)
}
}
impl<T: SidecarCoder> SidecarBuilder<T> {
pub fn from_coder_and_capacity(coder: T, capacity: usize) -> Self {
Self { inner: PartialSidecar::with_capacity(capacity), coder }
}
pub const fn len(&self) -> usize {
self.inner.len()
}
pub const fn is_empty(&self) -> bool {
self.inner.is_empty()
}
pub fn from_coder_and_data(coder: T, data: &[u8]) -> Self {
let required_fe = coder.required_fe(data);
let mut this = Self::from_coder_and_capacity(
coder,
required_fe.div_ceil(FIELD_ELEMENTS_PER_BLOB as usize),
);
this.ingest(data);
this
}
pub fn ingest(&mut self, data: &[u8]) {
self.inner.alloc_fes(self.coder.required_fe(data));
self.coder.code(&mut self.inner, data);
}
#[cfg(feature = "kzg")]
pub fn build<U: BuildableSidecar>(self) -> Result<U, c_kzg::Error> {
self.build_with_settings(EnvKzgSettings::Default.get())
}
#[cfg(feature = "kzg")]
pub fn build_with_settings<U: BuildableSidecar>(
self,
settings: &c_kzg::KzgSettings,
) -> Result<U, c_kzg::Error> {
U::build_with_settings(self.inner.blobs, settings)
}
#[cfg(feature = "kzg")]
pub fn build_4844_with_settings(
self,
settings: &c_kzg::KzgSettings,
) -> Result<BlobTransactionSidecar, c_kzg::Error> {
BlobTransactionSidecar::build_with_settings(self.inner.blobs, settings)
}
#[cfg(feature = "kzg")]
pub fn build_4844(self) -> Result<BlobTransactionSidecar, c_kzg::Error> {
self.build_4844_with_settings(EnvKzgSettings::Default.get())
}
pub fn take(self) -> Vec<Blob> {
self.inner.blobs
}
#[cfg(feature = "kzg")]
pub fn build_7594(self) -> Result<BlobTransactionSidecarEip7594, c_kzg::Error> {
self.build_7594_with_settings(EnvKzgSettings::Default.get())
}
#[cfg(feature = "kzg")]
pub fn build_7594_with_settings(
self,
settings: &c_kzg::KzgSettings,
) -> Result<BlobTransactionSidecarEip7594, c_kzg::Error> {
BlobTransactionSidecarEip7594::build_with_settings(self.inner.blobs, settings)
}
}
impl<T: SidecarCoder> From<T> for SidecarBuilder<T> {
fn from(coder: T) -> Self {
Self::from_coder_and_capacity(coder, 1)
}
}
impl<T, R> FromIterator<R> for SidecarBuilder<T>
where
T: SidecarCoder + Default,
R: AsRef<[u8]>,
{
fn from_iter<I: IntoIterator<Item = R>>(iter: I) -> Self {
let mut this = Self::new();
for data in iter {
this.ingest(data.as_ref());
}
this
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::eip4844::USABLE_BYTES_PER_BLOB;
#[test]
fn ingestion_strategy() {
let mut builder = PartialSidecar::new();
let data = &[
vec![1u8; 32],
vec![2u8; 372],
vec![3u8; 17],
vec![4u8; 5],
vec![5u8; 126_945],
vec![6u8; 2 * 126_945],
];
data.iter().for_each(|data| SimpleCoder.code(&mut builder, data.as_slice()));
let decoded = SimpleCoder.decode_all(builder.blobs()).unwrap();
assert_eq!(decoded, data);
}
#[test]
fn big_ingestion_strategy() {
let data = vec![1u8; 126_945];
let builder = SidecarBuilder::<SimpleCoder>::from_slice(&data);
let blobs = builder.take();
let decoded = SimpleCoder.decode_all(&blobs).unwrap().concat();
assert_eq!(decoded, data);
}
#[test]
fn decode_all_rejects_invalid_data() {
assert_eq!(SimpleCoder.decode_all(&[]), None);
assert_eq!(SimpleCoder.decode_all(&[Blob::new([0xffu8; BYTES_PER_BLOB])]), None);
}
#[test]
fn it_ingests() {
let data = [
vec![1u8; 32],
vec![2u8; 372],
vec![3u8; 17],
vec![4u8; 5],
vec![5u8; USABLE_BYTES_PER_BLOB + 2],
];
let mut builder = data.iter().collect::<SidecarBuilder<SimpleCoder>>();
let expected_fe = data.iter().map(|d| SimpleCoder.required_fe(d)).sum::<usize>();
assert_eq!(builder.len(), expected_fe * 32);
builder.ingest(b"hello");
assert_eq!(builder.len(), expected_fe * 32 + 64);
}
}