alloy_eips/eip4844/
mod.rs

1//! [EIP-4844] constants and helpers.
2//!
3//! [EIP-4844]: https://eips.ethereum.org/EIPS/eip-4844
4
5/// Re-export the `c_kzg` crate for downstream consumers.
6#[cfg(feature = "kzg")]
7pub use c_kzg;
8
9/// Module houses the KZG settings, enabling Custom and Default
10#[cfg(feature = "kzg")]
11pub mod env_settings;
12/// This module contains functions and types used for parsing and utilizing the [Trusted Setup]( https://ceremony.ethereum.org/) for the `KzgSettings`.
13#[cfg(feature = "kzg")]
14pub mod trusted_setup_points;
15
16/// Builder and utils for the [EIP-4844 Blob Transaction](https://eips.ethereum.org/EIPS/eip-4844#blob-transaction)
17pub mod builder;
18pub mod utils;
19
20mod engine;
21pub use engine::*;
22
23/// Contains sidecar related types
24#[cfg(feature = "kzg-sidecar")]
25mod sidecar;
26#[cfg(feature = "kzg-sidecar")]
27pub use sidecar::*;
28
29use alloy_primitives::{b256, Bytes, FixedBytes, B256, U256};
30
31use crate::eip7840;
32
33/// The modulus of the BLS group used in the KZG commitment scheme. All field
34/// elements contained in a blob MUST be STRICTLY LESS than this value.
35pub const BLS_MODULUS_BYTES: B256 =
36    b256!("73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001");
37
38/// The modulus of the BLS group used in the KZG commitment scheme. All field
39/// elements contained in a blob MUST be STRICTLY LESS than this value.
40pub const BLS_MODULUS: U256 = U256::from_be_bytes(BLS_MODULUS_BYTES.0);
41
42/// Size a single field element in bytes.
43pub const FIELD_ELEMENT_BYTES: u64 = 32;
44
45/// Size a single field element in bytes.
46pub const FIELD_ELEMENT_BYTES_USIZE: usize = FIELD_ELEMENT_BYTES as usize;
47
48/// How many field elements are stored in a single data blob.
49pub const FIELD_ELEMENTS_PER_BLOB: u64 = 4096;
50
51/// Number of usable bits in a field element. The top two bits are always zero.
52pub const USABLE_BITS_PER_FIELD_ELEMENT: usize = 254;
53
54/// The number of usable bytes in a single data blob. This is the number of
55/// bytes you can encode in a blob without any field element being >=
56/// [`BLS_MODULUS`].
57pub const USABLE_BYTES_PER_BLOB: usize =
58    USABLE_BITS_PER_FIELD_ELEMENT * FIELD_ELEMENTS_PER_BLOB as usize / 8;
59
60/// Gas consumption of a single data blob.
61pub const DATA_GAS_PER_BLOB: u64 = 131_072u64; // 32*4096 = 131072 == 2^17 == 0x20000
62
63/// How many bytes are in a blob
64/// Same as [DATA_GAS_PER_BLOB], but as an usize
65pub const BYTES_PER_BLOB: usize = 131_072;
66
67/// Maximum data gas for data blobs in a single block.
68pub const MAX_DATA_GAS_PER_BLOCK_DENCUN: u64 = 786_432u64; // 0xC0000 = 6 * 0x20000
69
70/// Target data gas for data blobs in a single block.
71pub const TARGET_DATA_GAS_PER_BLOCK_DENCUN: u64 = 393_216u64; // 0x60000 = 3 * 0x20000
72
73/// Maximum number of data blobs in a single block.
74pub const MAX_BLOBS_PER_BLOCK_DENCUN: usize =
75    (MAX_DATA_GAS_PER_BLOCK_DENCUN / DATA_GAS_PER_BLOB) as usize; // 786432 / 131072  = 6
76
77/// Target number of data blobs in a single block.
78pub const TARGET_BLOBS_PER_BLOCK_DENCUN: u64 = TARGET_DATA_GAS_PER_BLOCK_DENCUN / DATA_GAS_PER_BLOB; // 393216 / 131072 = 3
79
80/// Determines the maximum rate of change for blob fee
81pub const BLOB_GASPRICE_UPDATE_FRACTION: u128 = 3_338_477u128; // 3338477
82
83/// Minimum gas price for a data blob
84pub const BLOB_TX_MIN_BLOB_GASPRICE: u128 = 1u128;
85
86/// Commitment version of a KZG commitment
87pub const VERSIONED_HASH_VERSION_KZG: u8 = 0x01;
88
89/// How many bytes are in a commitment
90pub const BYTES_PER_COMMITMENT: usize = 48;
91
92/// How many bytes are in a proof
93pub const BYTES_PER_PROOF: usize = 48;
94
95/// A Blob serialized as 0x-prefixed hex string
96pub type Blob = FixedBytes<BYTES_PER_BLOB>;
97
98/// Helper function to deserialize boxed blobs.
99#[cfg(feature = "serde")]
100pub fn deserialize_blob<'de, D>(deserializer: D) -> Result<alloc::boxed::Box<Blob>, D::Error>
101where
102    D: serde::de::Deserializer<'de>,
103{
104    use serde::Deserialize;
105    let raw_blob = <alloy_primitives::Bytes>::deserialize(deserializer)?;
106    let blob = alloc::boxed::Box::new(
107        Blob::try_from(raw_blob.as_ref()).map_err(serde::de::Error::custom)?,
108    );
109    Ok(blob)
110}
111
112/// Helper function to deserialize boxed blobs from a serde deserializer.
113#[cfg(all(debug_assertions, feature = "serde"))]
114pub fn deserialize_blobs<'de, D>(deserializer: D) -> Result<alloc::vec::Vec<Blob>, D::Error>
115where
116    D: serde::de::Deserializer<'de>,
117{
118    use alloc::vec::Vec;
119    use serde::Deserialize;
120
121    let raw_blobs = Vec::<alloy_primitives::Bytes>::deserialize(deserializer)?;
122    let mut blobs = Vec::with_capacity(raw_blobs.len());
123    for blob in raw_blobs {
124        blobs.push(Blob::try_from(blob.as_ref()).map_err(serde::de::Error::custom)?);
125    }
126    Ok(blobs)
127}
128
129#[cfg(all(not(debug_assertions), feature = "serde"))]
130#[inline(always)]
131pub fn deserialize_blobs<'de, D>(deserializer: D) -> Result<alloc::vec::Vec<Blob>, D::Error>
132where
133    D: serde::de::Deserializer<'de>,
134{
135    serde::Deserialize::deserialize(deserializer)
136}
137
138/// A heap allocated blob that serializes as 0x-prefixed hex string
139#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, alloy_rlp::RlpEncodableWrapper)]
140pub struct HeapBlob(Bytes);
141
142impl HeapBlob {
143    /// Create a new heap blob from a byte slice.
144    pub fn new(blob: &[u8]) -> Result<Self, InvalidBlobLength> {
145        if blob.len() != BYTES_PER_BLOB {
146            return Err(InvalidBlobLength(blob.len()));
147        }
148
149        Ok(Self(Bytes::copy_from_slice(blob)))
150    }
151
152    /// Create a new heap blob from an array.
153    pub fn from_array(blob: [u8; BYTES_PER_BLOB]) -> Self {
154        Self(Bytes::from(blob))
155    }
156
157    /// Create a new heap blob from [`Bytes`].
158    pub fn from_bytes(bytes: Bytes) -> Result<Self, InvalidBlobLength> {
159        if bytes.len() != BYTES_PER_BLOB {
160            return Err(InvalidBlobLength(bytes.len()));
161        }
162
163        Ok(Self(bytes))
164    }
165
166    /// Generate a new heap blob with all bytes set to `byte`.
167    pub fn repeat_byte(byte: u8) -> Self {
168        Self(Bytes::from(vec![byte; BYTES_PER_BLOB]))
169    }
170
171    /// Get the inner
172    pub const fn inner(&self) -> &Bytes {
173        &self.0
174    }
175}
176
177impl Default for HeapBlob {
178    fn default() -> Self {
179        Self::repeat_byte(0)
180    }
181}
182
183/// Error indicating that the blob length is invalid.
184#[derive(Debug, Clone)]
185pub struct InvalidBlobLength(usize);
186impl core::fmt::Display for InvalidBlobLength {
187    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
188        write!(f, "Invalid blob length: {}, expected: {BYTES_PER_BLOB}", self.0)
189    }
190}
191impl core::error::Error for InvalidBlobLength {}
192
193#[cfg(feature = "serde")]
194impl serde::Serialize for HeapBlob {
195    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
196    where
197        S: serde::Serializer,
198    {
199        self.inner().serialize(serializer)
200    }
201}
202
203#[cfg(any(test, feature = "arbitrary"))]
204impl<'a> arbitrary::Arbitrary<'a> for HeapBlob {
205    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
206        let mut blob = vec![0u8; BYTES_PER_BLOB];
207        u.fill_buffer(&mut blob)?;
208        Ok(Self(Bytes::from(blob)))
209    }
210}
211
212#[cfg(feature = "serde")]
213impl<'de> serde::Deserialize<'de> for HeapBlob {
214    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
215    where
216        D: serde::de::Deserializer<'de>,
217    {
218        let inner = <Bytes>::deserialize(deserializer)?;
219
220        Self::from_bytes(inner).map_err(serde::de::Error::custom)
221    }
222}
223
224impl alloy_rlp::Decodable for HeapBlob {
225    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
226        let bytes = <Bytes>::decode(buf)?;
227
228        Self::from_bytes(bytes).map_err(|_| alloy_rlp::Error::Custom("invalid blob length"))
229    }
230}
231
232/// A commitment/proof serialized as 0x-prefixed hex string
233pub type Bytes48 = FixedBytes<48>;
234
235/// Calculates the versioned hash for a KzgCommitment of 48 bytes.
236///
237/// Specified in [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844#header-extension)
238///
239/// # Panics
240///
241/// If the given commitment is not 48 bytes long.
242#[cfg(feature = "sha2")]
243pub fn kzg_to_versioned_hash(commitment: &[u8]) -> B256 {
244    use sha2::Digest;
245
246    debug_assert_eq!(commitment.len(), 48, "commitment length is not 48");
247    let mut res = sha2::Sha256::digest(commitment);
248    res[0] = VERSIONED_HASH_VERSION_KZG;
249    B256::new(res.into())
250}
251
252/// Calculates the `excess_blob_gas` from the parent header's `blob_gas_used` and `excess_blob_gas`.
253///
254/// See also [the EIP-4844 helpers](https://eips.ethereum.org/EIPS/eip-4844#helpers)
255/// (`calc_excess_blob_gas`).
256#[inline]
257pub const fn calc_excess_blob_gas(parent_excess_blob_gas: u64, parent_blob_gas_used: u64) -> u64 {
258    eip7840::BlobParams::cancun().next_block_excess_blob_gas_osaka(
259        parent_excess_blob_gas,
260        parent_blob_gas_used,
261        // base fee is not used in EIP-4844 excess blob gas calculation
262        0,
263    )
264}
265
266/// Calculates the blob gas price from the header's excess blob gas field.
267///
268/// See also [the EIP-4844 helpers](https://eips.ethereum.org/EIPS/eip-4844#helpers)
269/// (`get_blob_gasprice`).
270#[inline]
271pub const fn calc_blob_gasprice(excess_blob_gas: u64) -> u128 {
272    eip7840::BlobParams::cancun().calc_blob_fee(excess_blob_gas)
273}
274
275/// Approximates `factor * e ** (numerator / denominator)` using Taylor expansion.
276///
277/// This is used to calculate the blob price.
278///
279/// See also [the EIP-4844 helpers](https://eips.ethereum.org/EIPS/eip-4844#helpers)
280/// (`fake_exponential`).
281///
282/// # Panics
283///
284/// This function panics if `denominator` is zero.
285#[inline]
286pub const fn fake_exponential(factor: u128, numerator: u128, denominator: u128) -> u128 {
287    assert!(denominator != 0, "attempt to divide by zero");
288
289    let mut i = 1;
290    let mut output = 0;
291    let mut numerator_accum = factor * denominator;
292    while numerator_accum > 0 {
293        output += numerator_accum;
294
295        // Use checked multiplication to prevent overflow
296        let Some(val) = numerator_accum.checked_mul(numerator) else {
297            break;
298        };
299
300        // Denominator is asserted as not zero at the start of the function.
301        numerator_accum = val / (denominator * i);
302        i += 1;
303    }
304    output / denominator
305}
306
307#[cfg(test)]
308mod tests {
309    use super::*;
310
311    // https://github.com/ethereum/go-ethereum/blob/28857080d732857030eda80c69b9ba2c8926f221/consensus/misc/eip4844/eip4844_test.go#L27
312    #[test]
313    fn test_calc_excess_blob_gas() {
314        for t @ &(excess, blobs, expected) in &[
315            // The excess blob gas should not increase from zero if the used blob
316            // slots are below - or equal - to the target.
317            (0, 0, 0),
318            (0, 1, 0),
319            (0, TARGET_DATA_GAS_PER_BLOCK_DENCUN / DATA_GAS_PER_BLOB, 0),
320            // If the target blob gas is exceeded, the excessBlobGas should increase
321            // by however much it was overshot
322            (0, (TARGET_DATA_GAS_PER_BLOCK_DENCUN / DATA_GAS_PER_BLOB) + 1, DATA_GAS_PER_BLOB),
323            (1, (TARGET_DATA_GAS_PER_BLOCK_DENCUN / DATA_GAS_PER_BLOB) + 1, DATA_GAS_PER_BLOB + 1),
324            (
325                1,
326                (TARGET_DATA_GAS_PER_BLOCK_DENCUN / DATA_GAS_PER_BLOB) + 2,
327                2 * DATA_GAS_PER_BLOB + 1,
328            ),
329            // The excess blob gas should decrease by however much the target was
330            // under-shot, capped at zero.
331            (
332                TARGET_DATA_GAS_PER_BLOCK_DENCUN,
333                TARGET_DATA_GAS_PER_BLOCK_DENCUN / DATA_GAS_PER_BLOB,
334                TARGET_DATA_GAS_PER_BLOCK_DENCUN,
335            ),
336            (
337                TARGET_DATA_GAS_PER_BLOCK_DENCUN,
338                (TARGET_DATA_GAS_PER_BLOCK_DENCUN / DATA_GAS_PER_BLOB) - 1,
339                TARGET_DATA_GAS_PER_BLOCK_DENCUN - DATA_GAS_PER_BLOB,
340            ),
341            (
342                TARGET_DATA_GAS_PER_BLOCK_DENCUN,
343                (TARGET_DATA_GAS_PER_BLOCK_DENCUN / DATA_GAS_PER_BLOB) - 2,
344                TARGET_DATA_GAS_PER_BLOCK_DENCUN - (2 * DATA_GAS_PER_BLOB),
345            ),
346            (DATA_GAS_PER_BLOB - 1, (TARGET_DATA_GAS_PER_BLOCK_DENCUN / DATA_GAS_PER_BLOB) - 1, 0),
347        ] {
348            let actual = calc_excess_blob_gas(excess, blobs * DATA_GAS_PER_BLOB);
349            assert_eq!(actual, expected, "test: {t:?}");
350        }
351    }
352
353    // https://github.com/ethereum/go-ethereum/blob/28857080d732857030eda80c69b9ba2c8926f221/consensus/misc/eip4844/eip4844_test.go#L60
354    #[test]
355    fn test_calc_blob_fee() {
356        let blob_fee_vectors = &[
357            (0, 1),
358            (2314057, 1),
359            (2314058, 2),
360            (10 * 1024 * 1024, 23),
361            // calc_blob_gasprice approximates `e ** (excess_blob_gas /
362            // BLOB_GASPRICE_UPDATE_FRACTION)` using Taylor expansion
363            //
364            // to roughly find where boundaries will be hit:
365            // 2 ** bits = e ** (excess_blob_gas / BLOB_GASPRICE_UPDATE_FRACTION)
366            // excess_blob_gas = ln(2 ** bits) * BLOB_GASPRICE_UPDATE_FRACTION
367            (148099578, 18446739238971471609), // output is just below the overflow
368            (148099579, 18446744762204311910), // output is just after the overflow
369            (161087488, 902580055246494526580),
370        ];
371
372        for &(excess, expected) in blob_fee_vectors {
373            let actual = calc_blob_gasprice(excess);
374            assert_eq!(actual, expected, "test: {excess}");
375        }
376    }
377
378    // https://github.com/ethereum/go-ethereum/blob/28857080d732857030eda80c69b9ba2c8926f221/consensus/misc/eip4844/eip4844_test.go#L78
379    #[test]
380    fn fake_exp() {
381        for t @ &(factor, numerator, denominator, expected) in &[
382            (1u64, 0u64, 1u64, 1u128),
383            (38493, 0, 1000, 38493),
384            (0, 1234, 2345, 0),
385            (1, 2, 1, 6), // approximate 7.389
386            (1, 4, 2, 6),
387            (1, 3, 1, 16), // approximate 20.09
388            (1, 6, 2, 18),
389            (1, 4, 1, 49), // approximate 54.60
390            (1, 8, 2, 50),
391            (10, 8, 2, 542), // approximate 540.598
392            (11, 8, 2, 596), // approximate 600.58
393            (1, 5, 1, 136),  // approximate 148.4
394            (1, 5, 2, 11),   // approximate 12.18
395            (2, 5, 2, 23),   // approximate 24.36
396            (1, 50000000, 2225652, 5709098764),
397            (1, 380928, BLOB_GASPRICE_UPDATE_FRACTION.try_into().unwrap(), 1),
398        ] {
399            let actual = fake_exponential(factor as u128, numerator as u128, denominator as u128);
400            assert_eq!(actual, expected, "test: {t:?}");
401        }
402    }
403
404    #[test]
405    #[cfg(feature = "serde")]
406    fn serde_heap_blob() {
407        let blob = HeapBlob::repeat_byte(0x42);
408        let serialized = serde_json::to_string(&blob).unwrap();
409
410        let deserialized: HeapBlob = serde_json::from_str(&serialized).unwrap();
411        assert_eq!(blob, deserialized);
412    }
413
414    #[test]
415    fn fake_exp_handles_overflow() {
416        // Test with very large excess blob gas values that would cause overflow
417        let factor = 1u128; // BLOB_TX_MIN_BLOB_GASPRICE
418        let numerator = u64::MAX as u128; // Very large excess blob gas
419        let denominator = 5007716u128; // BLOB_GASPRICE_UPDATE_FRACTION_PECTRA
420
421        // This should not panic even with very large inputs
422        let result = fake_exponential(factor, numerator, denominator);
423
424        // The result should be a valid value (not panic)
425        assert!(result > 0);
426
427        // Test with Prague parameters
428        let prague_params = crate::eip7840::BlobParams::prague();
429        // This should also not panic when excess_blob_gas is very large
430        let _blob_fee = prague_params.calc_blob_fee(u64::MAX);
431    }
432}