alloy_eips/eip4844/
builder.rs

1use crate::eip4844::{
2    utils::WholeFe, Blob, BYTES_PER_BLOB, FIELD_ELEMENTS_PER_BLOB, FIELD_ELEMENT_BYTES_USIZE,
3};
4use alloc::vec::Vec;
5use core::cmp;
6
7#[cfg(any(feature = "kzg", feature = "arbitrary"))]
8use crate::eip4844::BlobTransactionSidecar;
9#[cfg(feature = "kzg")]
10use crate::{eip4844::env_settings::EnvKzgSettings, eip7594::BlobTransactionSidecarEip7594};
11
12/// Describes types that can be built from blobs and KZG settings.
13///
14/// This trait primarily exists to allow inference of the output type of
15/// [`SidecarBuilder::build`], and should generally not be implemented outside
16/// of this crate.
17#[cfg(feature = "kzg")]
18pub trait BuildableSidecar {
19    /// Build the sidecar from the blobs and KZG settings.
20    fn build_with_settings(
21        blobs: Vec<Blob>,
22        settings: &c_kzg::KzgSettings,
23    ) -> Result<Self, c_kzg::Error>
24    where
25        Self: Sized;
26
27    /// Build the sidecar from the blobs with default (Ethereum Mainnet) KZG
28    /// settings.
29    fn build(blobs: Vec<Blob>) -> Result<Self, c_kzg::Error>
30    where
31        Self: Sized,
32    {
33        Self::build_with_settings(blobs, EnvKzgSettings::Default.get())
34    }
35}
36
37#[cfg(feature = "kzg")]
38impl BuildableSidecar for BlobTransactionSidecar {
39    fn build_with_settings(
40        blobs: Vec<Blob>,
41        settings: &c_kzg::KzgSettings,
42    ) -> Result<Self, c_kzg::Error> {
43        Self::try_from_blobs_with_settings(blobs, settings)
44    }
45}
46
47#[cfg(feature = "kzg")]
48impl BuildableSidecar for BlobTransactionSidecarEip7594 {
49    fn build_with_settings(
50        blobs: Vec<Blob>,
51        settings: &c_kzg::KzgSettings,
52    ) -> Result<Self, c_kzg::Error> {
53        Self::try_from_blobs_with_settings(blobs, settings)
54    }
55}
56
57/// A builder for creating a [`BlobTransactionSidecar`].
58///
59/// [`BlobTransactionSidecar`]: crate::eip4844::BlobTransactionSidecar
60#[derive(Clone, Debug)]
61pub struct PartialSidecar {
62    /// The blobs in the sidecar.
63    blobs: Vec<Blob>,
64    /// The number of field elements that we have ingested, total.
65    fe: usize,
66}
67
68impl Default for PartialSidecar {
69    fn default() -> Self {
70        Self::new()
71    }
72}
73
74impl PartialSidecar {
75    /// Create a new builder, and push an empty blob to it. This is the default
76    /// constructor, and allocates space for 2 blobs (256 KiB). If you want to
77    /// preallocate a specific number of blobs, use
78    /// [`PartialSidecar::with_capacity`].
79    pub fn new() -> Self {
80        Self::with_capacity(2)
81    }
82
83    /// Create a new builder, preallocating room for `capacity` blobs, and push
84    /// an empty blob to it.
85    pub fn with_capacity(capacity: usize) -> Self {
86        let mut blobs = Vec::with_capacity(capacity);
87        blobs.push(Blob::new([0u8; BYTES_PER_BLOB]));
88        Self { blobs, fe: 0 }
89    }
90
91    /// Get a reference to the blobs currently in the builder.
92    #[allow(clippy::missing_const_for_fn)]
93    pub fn blobs(&self) -> &[Blob] {
94        &self.blobs
95    }
96
97    /// Get the number of unused field elements that have been allocated
98    const fn free_fe(&self) -> usize {
99        self.blobs.len() * FIELD_ELEMENTS_PER_BLOB as usize - self.fe
100    }
101
102    /// Calculate the length of used field elements IN BYTES in the builder.
103    ///
104    /// This is always strictly greater than the number of bytes that have been
105    /// ingested.
106    pub const fn len(&self) -> usize {
107        self.fe * 32
108    }
109
110    /// Check if the builder is empty.
111    pub const fn is_empty(&self) -> bool {
112        self.fe == 0
113    }
114
115    /// Push an empty blob to the builder.
116    fn push_empty_blob(&mut self) {
117        self.blobs.push(Blob::new([0u8; BYTES_PER_BLOB]));
118    }
119
120    /// Allocate enough space for the required number of new field elements.
121    pub fn alloc_fes(&mut self, required_fe: usize) {
122        while self.free_fe() < required_fe {
123            self.push_empty_blob()
124        }
125    }
126
127    /// Get the number of used field elements in the current blob.
128    const fn fe_in_current_blob(&self) -> usize {
129        self.fe % FIELD_ELEMENTS_PER_BLOB as usize
130    }
131
132    /// Get the index of the first unused field element in the current blob.
133    const fn first_unused_fe_index_in_current_blob(&self) -> usize {
134        self.fe_in_current_blob()
135    }
136
137    /// Get a mutable reference to the current blob.
138    fn current_blob_mut(&mut self) -> &mut Blob {
139        let last_unused_blob_index = self.fe / FIELD_ELEMENTS_PER_BLOB as usize;
140        self.blobs.get_mut(last_unused_blob_index).expect("never empty")
141    }
142
143    /// Get a mutable reference to the field element at the given index, in
144    /// the current blob.
145    fn fe_at_mut(&mut self, index: usize) -> &mut [u8] {
146        &mut self.current_blob_mut()[index * 32..(index + 1) * 32]
147    }
148
149    /// Get a mutable reference to the next unused field element.
150    fn next_unused_fe_mut(&mut self) -> &mut [u8] {
151        self.fe_at_mut(self.first_unused_fe_index_in_current_blob())
152    }
153
154    /// Ingest a field element into the current blobs.
155    pub fn ingest_valid_fe(&mut self, data: WholeFe<'_>) {
156        self.alloc_fes(1);
157        self.next_unused_fe_mut().copy_from_slice(data.as_ref());
158        self.fe += 1;
159    }
160
161    /// Ingest a partial FE into the current blobs.
162    ///
163    /// # Panics
164    ///
165    /// If the data is >=32 bytes. Or if there are not enough free FEs to
166    /// encode the data.
167    pub fn ingest_partial_fe(&mut self, data: &[u8]) {
168        self.alloc_fes(1);
169        let fe = self.next_unused_fe_mut();
170        fe[1..1 + data.len()].copy_from_slice(data);
171        self.fe += 1;
172    }
173}
174
175/// A strategy for coding and decoding data into sidecars.
176///
177/// Coder instances are responsible for encoding and decoding data into and from the sidecar. They
178/// are called by the [`SidecarBuilder`] during the [`ingest`], [`take`], and (if `c_kzg` feature
179/// enabled) `build` methods.
180///
181/// This trait allows different downstream users to use different bit-packing
182/// strategies. For example, a simple coder might only use the last 31 bytes of
183/// each blob, while a more complex coder might use a more sophisticated
184/// strategy to pack data into the low 6 bits of the top byte.
185///
186/// [`ingest`]: SidecarBuilder::ingest
187/// [`take`]: SidecarBuilder::take
188pub trait SidecarCoder {
189    /// Calculate the number of field elements required to store the given
190    /// data.
191    fn required_fe(&self, data: &[u8]) -> usize;
192
193    /// Code a slice of data into the builder.
194    fn code(&mut self, builder: &mut PartialSidecar, data: &[u8]);
195
196    /// Finish the sidecar, and commit to the data. This method should empty
197    /// any buffer or scratch space in the coder, and is called by
198    /// [`SidecarBuilder`]'s `take` and `build` methods.
199    fn finish(self, builder: &mut PartialSidecar);
200
201    /// Decode all slices of data from the blobs.
202    fn decode_all(&mut self, blobs: &[Blob]) -> Option<Vec<Vec<u8>>>;
203}
204
205/// Simple coder that only uses the last 31 bytes of each blob. This is the
206/// default coder for the [`SidecarBuilder`].
207///
208/// # Note
209///
210/// Because this coder sacrifices around 3% of total sidecar space, we do not
211/// recommend its use in production. It is provided for convenience and
212/// non-prod environments.
213///
214/// # Behavior
215///
216/// This coder encodes data as follows:
217/// - The first byte of every 32-byte word is empty.
218/// - Data is pre-pended with a 64-bit big-endian length prefix, which is right padded with zeros to
219///   form a complete word.
220/// - The rest of the data is packed into the remaining 31 bytes of each word.
221/// - If the data is not a multiple of 31 bytes, the last word is right-padded with zeros.
222///
223/// This means that the following regions cannot be used to store data, and are
224/// considered "wasted":
225///
226/// - The first byte of every 32-byte word.
227/// - The right padding on the header word containing the data length.
228/// - Any right padding on the last word for each piece of data.
229#[derive(Clone, Copy, Debug, Default)]
230#[non_exhaustive]
231pub struct SimpleCoder;
232
233impl SimpleCoder {
234    /// Decode an some bytes from an iterator of valid FEs.
235    ///
236    /// Returns `Ok(Some(data))` if there is some data.
237    /// Returns `Ok(None)` if there is no data (empty iterator, length prefix is 0).
238    /// Returns `Err(())` if there is an error.
239    fn decode_one<'a>(mut fes: impl Iterator<Item = WholeFe<'a>>) -> Result<Option<Vec<u8>>, ()> {
240        let Some(first) = fes.next() else {
241            return Ok(None);
242        };
243        let mut num_bytes = u64::from_be_bytes(first.as_ref()[1..9].try_into().unwrap()) as usize;
244
245        // if no more bytes is 0, we're done
246        if num_bytes == 0 {
247            return Ok(None);
248        }
249
250        // if there are too many bytes
251        const MAX_ALLOCATION_SIZE: usize = 2_097_152; //2 MiB
252        if num_bytes > MAX_ALLOCATION_SIZE {
253            return Err(());
254        }
255
256        let mut res = Vec::with_capacity(num_bytes);
257        while num_bytes > 0 {
258            let to_copy = cmp::min(31, num_bytes);
259            let fe = fes.next().ok_or(())?;
260            res.extend_from_slice(&fe.as_ref()[1..1 + to_copy]);
261            num_bytes -= to_copy;
262        }
263        Ok(Some(res))
264    }
265}
266
267impl SidecarCoder for SimpleCoder {
268    fn required_fe(&self, data: &[u8]) -> usize {
269        data.len().div_ceil(31) + 1
270    }
271
272    fn code(&mut self, builder: &mut PartialSidecar, mut data: &[u8]) {
273        if data.is_empty() {
274            return;
275        }
276
277        // first FE is the number of following bytes
278        builder.ingest_partial_fe(&(data.len() as u64).to_be_bytes());
279
280        // ingest the rest of the data
281        while !data.is_empty() {
282            let (left, right) = data.split_at(cmp::min(31, data.len()));
283            builder.ingest_partial_fe(left);
284            data = right
285        }
286    }
287
288    /// No-op
289    fn finish(self, _builder: &mut PartialSidecar) {}
290
291    fn decode_all(&mut self, blobs: &[Blob]) -> Option<Vec<Vec<u8>>> {
292        if blobs.is_empty() {
293            return None;
294        }
295
296        if blobs
297            .iter()
298            .flat_map(|blob| blob.chunks(FIELD_ELEMENT_BYTES_USIZE).map(WholeFe::new))
299            .any(|fe| fe.is_none())
300        {
301            return None;
302        }
303
304        let mut fes = blobs
305            .iter()
306            .flat_map(|blob| blob.chunks(FIELD_ELEMENT_BYTES_USIZE).map(WholeFe::new_unchecked));
307
308        let mut res = Vec::new();
309        loop {
310            match Self::decode_one(&mut fes) {
311                Ok(Some(data)) => res.push(data),
312                Ok(None) => break,
313                Err(()) => return None,
314            }
315        }
316        Some(res)
317    }
318}
319
320/// Build a [`BlobTransactionSidecar`] from an arbitrary amount of data.
321///
322/// This is useful for creating a sidecar from a large amount of data,
323/// which is then split into blobs. It delays KZG commitments and proofs
324/// until all data is ready.
325///
326/// [`BlobTransactionSidecar`]: crate::eip4844::BlobTransactionSidecar
327#[derive(Clone, Debug)]
328pub struct SidecarBuilder<T = SimpleCoder> {
329    /// The blob array we will code data into
330    inner: PartialSidecar,
331    /// The coder to use for ingesting and decoding data.
332    coder: T,
333}
334
335impl<T> Default for SidecarBuilder<T>
336where
337    T: Default + SidecarCoder,
338{
339    fn default() -> Self {
340        Self::new()
341    }
342}
343
344#[cfg(feature = "arbitrary")]
345impl<'a, T: arbitrary::Arbitrary<'a> + Clone> SidecarBuilder<T> {
346    /// Builds an arbitrary realization for BlobTransactionSidecar.
347    pub fn build_arbitrary(&self) -> BlobTransactionSidecar {
348        <BlobTransactionSidecar as arbitrary::Arbitrary>::arbitrary(
349            &mut arbitrary::Unstructured::new(&[]),
350        )
351        .unwrap()
352    }
353}
354
355impl<T: SidecarCoder + Default> SidecarBuilder<T> {
356    /// Instantiate a new builder and new coder instance.
357    ///
358    /// By default, this allocates space for 2 blobs (256 KiB). If you want to
359    /// preallocate a specific number of blobs, use
360    /// [`SidecarBuilder::with_capacity`].
361    pub fn new() -> Self {
362        T::default().into()
363    }
364
365    /// Create a new builder from a slice of data by calling
366    /// [`SidecarBuilder::from_coder_and_data`]
367    pub fn from_slice(data: &[u8]) -> Self {
368        Self::from_coder_and_data(T::default(), data)
369    }
370
371    /// Create a new builder with a pre-allocated capacity. This capacity is
372    /// measured in blobs, each of which is 128 KiB.
373    pub fn with_capacity(capacity: usize) -> Self {
374        Self::from_coder_and_capacity(T::default(), capacity)
375    }
376}
377
378impl<T: SidecarCoder> SidecarBuilder<T> {
379    /// Instantiate a new builder with the provided coder and capacity. This
380    /// capacity is measured in blobs, each of which is 128 KiB.
381    pub fn from_coder_and_capacity(coder: T, capacity: usize) -> Self {
382        Self { inner: PartialSidecar::with_capacity(capacity), coder }
383    }
384
385    /// Calculate the length of bytes used by field elements in the builder.
386    ///
387    /// This is always strictly greater than the number of bytes that have been
388    /// ingested.
389    pub const fn len(&self) -> usize {
390        self.inner.len()
391    }
392
393    /// Check if the builder is empty.
394    pub const fn is_empty(&self) -> bool {
395        self.inner.is_empty()
396    }
397
398    /// Create a new builder from a slice of data.
399    pub fn from_coder_and_data(coder: T, data: &[u8]) -> Self {
400        let required_fe = coder.required_fe(data);
401        let mut this = Self::from_coder_and_capacity(
402            coder,
403            required_fe.div_ceil(FIELD_ELEMENTS_PER_BLOB as usize),
404        );
405        this.ingest(data);
406        this
407    }
408
409    /// Ingest a slice of data into the builder.
410    pub fn ingest(&mut self, data: &[u8]) {
411        self.inner.alloc_fes(self.coder.required_fe(data));
412        self.coder.code(&mut self.inner, data);
413    }
414
415    /// Build the sidecar from the data, with default (Ethereum Mainnet)
416    /// settings.
417    ///
418    /// # Note
419    ///
420    /// The output type is inferred, and should generally be either
421    /// [`BlobTransactionSidecar`] or [`BlobTransactionSidecarEip7594`].
422    #[cfg(feature = "kzg")]
423    pub fn build<U: BuildableSidecar>(self) -> Result<U, c_kzg::Error> {
424        self.build_with_settings(EnvKzgSettings::Default.get())
425    }
426
427    /// Build the sidecar from the data with the provided settings.
428    ///
429    /// # Note
430    ///
431    /// The output type is inferred, and should generally be either
432    /// [`BlobTransactionSidecar`] or [`BlobTransactionSidecarEip7594`].
433    #[cfg(feature = "kzg")]
434    pub fn build_with_settings<U: BuildableSidecar>(
435        self,
436        settings: &c_kzg::KzgSettings,
437    ) -> Result<U, c_kzg::Error> {
438        U::build_with_settings(self.inner.blobs, settings)
439    }
440
441    /// Build the sidecar from the data with the provided settings.
442    #[cfg(feature = "kzg")]
443    pub fn build_4844_with_settings(
444        self,
445        settings: &c_kzg::KzgSettings,
446    ) -> Result<BlobTransactionSidecar, c_kzg::Error> {
447        BlobTransactionSidecar::build_with_settings(self.inner.blobs, settings)
448    }
449
450    /// Build the sidecar from the data, with default (Ethereum Mainnet)
451    /// settings.
452    #[cfg(feature = "kzg")]
453    pub fn build_4844(self) -> Result<BlobTransactionSidecar, c_kzg::Error> {
454        self.build_4844_with_settings(EnvKzgSettings::Default.get())
455    }
456
457    /// Take the blobs from the builder, without committing them to a KZG proof.
458    pub fn take(self) -> Vec<Blob> {
459        self.inner.blobs
460    }
461
462    /// Build the sidecar for eip-7594 from the data, with default (Ethereum Mainnet)
463    /// settings.
464    #[cfg(feature = "kzg")]
465    pub fn build_7594(self) -> Result<BlobTransactionSidecarEip7594, c_kzg::Error> {
466        self.build_7594_with_settings(EnvKzgSettings::Default.get())
467    }
468
469    /// Build the sidecar for eip-7594 from the data with the provided settings.
470    #[cfg(feature = "kzg")]
471    pub fn build_7594_with_settings(
472        self,
473        settings: &c_kzg::KzgSettings,
474    ) -> Result<BlobTransactionSidecarEip7594, c_kzg::Error> {
475        BlobTransactionSidecarEip7594::build_with_settings(self.inner.blobs, settings)
476    }
477}
478
479impl<T: SidecarCoder> From<T> for SidecarBuilder<T> {
480    /// Instantiate a new builder with the provided coder.
481    ///
482    /// This is equivalent to calling
483    /// [`SidecarBuilder::from_coder_and_capacity`] with a capacity of 1.
484    /// If you want to preallocate a specific number of blobs, use
485    /// [`SidecarBuilder::from_coder_and_capacity`].
486    fn from(coder: T) -> Self {
487        Self::from_coder_and_capacity(coder, 1)
488    }
489}
490
491impl<T, R> FromIterator<R> for SidecarBuilder<T>
492where
493    T: SidecarCoder + Default,
494    R: AsRef<[u8]>,
495{
496    fn from_iter<I: IntoIterator<Item = R>>(iter: I) -> Self {
497        let mut this = Self::new();
498        for data in iter {
499            this.ingest(data.as_ref());
500        }
501        this
502    }
503}
504
505#[cfg(test)]
506mod tests {
507    use super::*;
508    use crate::eip4844::USABLE_BYTES_PER_BLOB;
509
510    #[test]
511    fn ingestion_strategy() {
512        let mut builder = PartialSidecar::new();
513        let data = &[
514            vec![1u8; 32],
515            vec![2u8; 372],
516            vec![3u8; 17],
517            vec![4u8; 5],
518            vec![5u8; 126_945],
519            vec![6u8; 2 * 126_945],
520        ];
521
522        data.iter().for_each(|data| SimpleCoder.code(&mut builder, data.as_slice()));
523
524        let decoded = SimpleCoder.decode_all(builder.blobs()).unwrap();
525        assert_eq!(decoded, data);
526    }
527
528    #[test]
529    fn big_ingestion_strategy() {
530        let data = vec![1u8; 126_945];
531        let builder = SidecarBuilder::<SimpleCoder>::from_slice(&data);
532
533        let blobs = builder.take();
534        let decoded = SimpleCoder.decode_all(&blobs).unwrap().concat();
535
536        assert_eq!(decoded, data);
537    }
538
539    #[test]
540    fn decode_all_rejects_invalid_data() {
541        assert_eq!(SimpleCoder.decode_all(&[]), None);
542        assert_eq!(SimpleCoder.decode_all(&[Blob::new([0xffu8; BYTES_PER_BLOB])]), None);
543    }
544
545    #[test]
546    fn it_ingests() {
547        // test ingesting a lot of data.
548        let data = [
549            vec![1u8; 32],
550            vec![2u8; 372],
551            vec![3u8; 17],
552            vec![4u8; 5],
553            vec![5u8; USABLE_BYTES_PER_BLOB + 2],
554        ];
555
556        let mut builder = data.iter().collect::<SidecarBuilder<SimpleCoder>>();
557
558        let expected_fe = data.iter().map(|d| SimpleCoder.required_fe(d)).sum::<usize>();
559        assert_eq!(builder.len(), expected_fe * 32);
560
561        // consume 2 more
562        builder.ingest(b"hello");
563        assert_eq!(builder.len(), expected_fe * 32 + 64);
564    }
565}