alloy_eips/eip4844/
builder.rs

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