alloy_eips/eip4844/
mod.rs

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