alloy_eips/eip4844/
mod.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
//! [EIP-4844] constants and helpers.
//!
//! [EIP-4844]: https://eips.ethereum.org/EIPS/eip-4844

/// Module houses the KZG settings, enabling Custom and Default
#[cfg(feature = "kzg")]
pub mod env_settings;
/// This module contains functions and types used for parsing and utilizing the [Trusted Setup]( https://ceremony.ethereum.org/) for the `KzgSettings`.
#[cfg(feature = "kzg")]
pub mod trusted_setup_points;

/// Builder and utils for the [EIP-4844 Blob Transaction](https://eips.ethereum.org/EIPS/eip-4844#blob-transaction)
pub mod builder;
pub mod utils;

mod engine;
pub use engine::*;

/// Contains sidecar related types
#[cfg(feature = "kzg-sidecar")]
mod sidecar;
#[cfg(feature = "kzg-sidecar")]
pub use sidecar::*;

use alloy_primitives::{b256, FixedBytes, B256, U256};

/// The modulus of the BLS group used in the KZG commitment scheme. All field
/// elements contained in a blob MUST be STRICTLY LESS than this value.
pub const BLS_MODULUS_BYTES: B256 =
    b256!("73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001");

/// The modulus of the BLS group used in the KZG commitment scheme. All field
/// elements contained in a blob MUST be STRICTLY LESS than this value.
pub const BLS_MODULUS: U256 = U256::from_be_bytes(BLS_MODULUS_BYTES.0);

/// Size a single field element in bytes.
pub const FIELD_ELEMENT_BYTES: u64 = 32;

/// How many field elements are stored in a single data blob.
pub const FIELD_ELEMENTS_PER_BLOB: u64 = 4096;

/// Number of usable bits in a field element. The top two bits are always zero.
pub const USABLE_BITS_PER_FIELD_ELEMENT: usize = 254;

/// The number of usable bytes in a single data blob. This is the number of
/// bytes you can encode in a blob without any field element being >=
/// [`BLS_MODULUS`].
pub const USABLE_BYTES_PER_BLOB: usize =
    USABLE_BITS_PER_FIELD_ELEMENT * FIELD_ELEMENTS_PER_BLOB as usize / 8;

/// Gas consumption of a single data blob.
pub const DATA_GAS_PER_BLOB: u64 = 131_072u64; // 32*4096 = 131072 == 2^17 == 0x20000

/// How many bytes are in a blob
/// Same as [DATA_GAS_PER_BLOB], but as an usize
pub const BYTES_PER_BLOB: usize = 131_072;

/// Maximum data gas for data blobs in a single block.
pub const MAX_DATA_GAS_PER_BLOCK: u64 = 786_432u64; // 0xC0000 = 6 * 0x20000

/// Target data gas for data blobs in a single block.
pub const TARGET_DATA_GAS_PER_BLOCK: u64 = 393_216u64; // 0x60000 = 3 * 0x20000

/// Maximum number of data blobs in a single block.
pub const MAX_BLOBS_PER_BLOCK: usize = (MAX_DATA_GAS_PER_BLOCK / DATA_GAS_PER_BLOB) as usize; // 786432 / 131072  = 6

/// Target number of data blobs in a single block.
pub const TARGET_BLOBS_PER_BLOCK: u64 = TARGET_DATA_GAS_PER_BLOCK / DATA_GAS_PER_BLOB; // 393216 / 131072 = 3

/// Determines the maximum rate of change for blob fee
pub const BLOB_GASPRICE_UPDATE_FRACTION: u128 = 3_338_477u128; // 3338477

/// Minimum gas price for a data blob
pub const BLOB_TX_MIN_BLOB_GASPRICE: u128 = 1u128;

/// Commitment version of a KZG commitment
pub const VERSIONED_HASH_VERSION_KZG: u8 = 0x01;

/// How many bytes are in a commitment
pub const BYTES_PER_COMMITMENT: usize = 48;

/// How many bytes are in a proof
pub const BYTES_PER_PROOF: usize = 48;

/// A Blob serialized as 0x-prefixed hex string
pub type Blob = FixedBytes<BYTES_PER_BLOB>;

/// Helper function to deserialize boxed blobs.
#[cfg(feature = "serde")]
pub fn deserialize_blob<'de, D>(deserializer: D) -> Result<alloc::boxed::Box<Blob>, D::Error>
where
    D: serde::de::Deserializer<'de>,
{
    use serde::Deserialize;
    let raw_blob = <alloy_primitives::Bytes>::deserialize(deserializer)?;
    let blob = alloc::boxed::Box::new(
        Blob::try_from(raw_blob.as_ref()).map_err(serde::de::Error::custom)?,
    );
    Ok(blob)
}

/// A commitment/proof serialized as 0x-prefixed hex string
pub type Bytes48 = FixedBytes<48>;

/// Calculates the versioned hash for a KzgCommitment of 48 bytes.
///
/// Specified in [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844#header-extension)
///
/// # Panics
///
/// If the given commitment is not 48 bytes long.
#[cfg(feature = "sha2")]
pub fn kzg_to_versioned_hash(commitment: &[u8]) -> B256 {
    use sha2::Digest;

    debug_assert_eq!(commitment.len(), 48, "commitment length is not 48");
    let mut res = sha2::Sha256::digest(commitment);
    res[0] = VERSIONED_HASH_VERSION_KZG;
    B256::new(res.into())
}

/// Calculates the `excess_blob_gas` from the parent header's `blob_gas_used` and `excess_blob_gas`.
///
/// See also [the EIP-4844 helpers](https://eips.ethereum.org/EIPS/eip-4844#helpers)
/// (`calc_excess_blob_gas`).
#[inline]
pub const fn calc_excess_blob_gas(parent_excess_blob_gas: u64, parent_blob_gas_used: u64) -> u64 {
    (parent_excess_blob_gas + parent_blob_gas_used).saturating_sub(TARGET_DATA_GAS_PER_BLOCK)
}

/// Calculates the blob gas price from the header's excess blob gas field.
///
/// See also [the EIP-4844 helpers](https://eips.ethereum.org/EIPS/eip-4844#helpers)
/// (`get_blob_gasprice`).
#[inline]
pub fn calc_blob_gasprice(excess_blob_gas: u64) -> u128 {
    fake_exponential(
        BLOB_TX_MIN_BLOB_GASPRICE,
        excess_blob_gas as u128,
        BLOB_GASPRICE_UPDATE_FRACTION,
    )
}

/// Approximates `factor * e ** (numerator / denominator)` using Taylor expansion.
///
/// This is used to calculate the blob price.
///
/// See also [the EIP-4844 helpers](https://eips.ethereum.org/EIPS/eip-4844#helpers)
/// (`fake_exponential`).
///
/// # Panics
///
/// This function panics if `denominator` is zero.
#[inline]
fn fake_exponential(factor: u128, numerator: u128, denominator: u128) -> u128 {
    assert_ne!(denominator, 0, "attempt to divide by zero");

    let mut i = 1;
    let mut output = 0;
    let mut numerator_accum = factor * denominator;
    while numerator_accum > 0 {
        output += numerator_accum;

        // Denominator is asserted as not zero at the start of the function.
        numerator_accum = (numerator_accum * numerator) / (denominator * i);
        i += 1;
    }
    output / denominator
}

#[cfg(test)]
mod tests {
    use super::*;

    // https://github.com/ethereum/go-ethereum/blob/28857080d732857030eda80c69b9ba2c8926f221/consensus/misc/eip4844/eip4844_test.go#L27
    #[test]
    fn test_calc_excess_blob_gas() {
        for t @ &(excess, blobs, expected) in &[
            // The excess blob gas should not increase from zero if the used blob
            // slots are below - or equal - to the target.
            (0, 0, 0),
            (0, 1, 0),
            (0, TARGET_DATA_GAS_PER_BLOCK / DATA_GAS_PER_BLOB, 0),
            // If the target blob gas is exceeded, the excessBlobGas should increase
            // by however much it was overshot
            (0, (TARGET_DATA_GAS_PER_BLOCK / DATA_GAS_PER_BLOB) + 1, DATA_GAS_PER_BLOB),
            (1, (TARGET_DATA_GAS_PER_BLOCK / DATA_GAS_PER_BLOB) + 1, DATA_GAS_PER_BLOB + 1),
            (1, (TARGET_DATA_GAS_PER_BLOCK / DATA_GAS_PER_BLOB) + 2, 2 * DATA_GAS_PER_BLOB + 1),
            // The excess blob gas should decrease by however much the target was
            // under-shot, capped at zero.
            (
                TARGET_DATA_GAS_PER_BLOCK,
                TARGET_DATA_GAS_PER_BLOCK / DATA_GAS_PER_BLOB,
                TARGET_DATA_GAS_PER_BLOCK,
            ),
            (
                TARGET_DATA_GAS_PER_BLOCK,
                (TARGET_DATA_GAS_PER_BLOCK / DATA_GAS_PER_BLOB) - 1,
                TARGET_DATA_GAS_PER_BLOCK - DATA_GAS_PER_BLOB,
            ),
            (
                TARGET_DATA_GAS_PER_BLOCK,
                (TARGET_DATA_GAS_PER_BLOCK / DATA_GAS_PER_BLOB) - 2,
                TARGET_DATA_GAS_PER_BLOCK - (2 * DATA_GAS_PER_BLOB),
            ),
            (DATA_GAS_PER_BLOB - 1, (TARGET_DATA_GAS_PER_BLOCK / DATA_GAS_PER_BLOB) - 1, 0),
        ] {
            let actual = calc_excess_blob_gas(excess, blobs * DATA_GAS_PER_BLOB);
            assert_eq!(actual, expected, "test: {t:?}");
        }
    }

    // https://github.com/ethereum/go-ethereum/blob/28857080d732857030eda80c69b9ba2c8926f221/consensus/misc/eip4844/eip4844_test.go#L60
    #[test]
    fn test_calc_blob_fee() {
        let blob_fee_vectors = &[
            (0, 1),
            (2314057, 1),
            (2314058, 2),
            (10 * 1024 * 1024, 23),
            // calc_blob_gasprice approximates `e ** (excess_blob_gas /
            // BLOB_GASPRICE_UPDATE_FRACTION)` using Taylor expansion
            //
            // to roughly find where boundaries will be hit:
            // 2 ** bits = e ** (excess_blob_gas / BLOB_GASPRICE_UPDATE_FRACTION)
            // excess_blob_gas = ln(2 ** bits) * BLOB_GASPRICE_UPDATE_FRACTION
            (148099578, 18446739238971471609), // output is just below the overflow
            (148099579, 18446744762204311910), // output is just after the overflow
            (161087488, 902580055246494526580),
        ];

        for &(excess, expected) in blob_fee_vectors {
            let actual = calc_blob_gasprice(excess);
            assert_eq!(actual, expected, "test: {excess}");
        }
    }

    // https://github.com/ethereum/go-ethereum/blob/28857080d732857030eda80c69b9ba2c8926f221/consensus/misc/eip4844/eip4844_test.go#L78
    #[test]
    fn fake_exp() {
        for t @ &(factor, numerator, denominator, expected) in &[
            (1u64, 0u64, 1u64, 1u128),
            (38493, 0, 1000, 38493),
            (0, 1234, 2345, 0),
            (1, 2, 1, 6), // approximate 7.389
            (1, 4, 2, 6),
            (1, 3, 1, 16), // approximate 20.09
            (1, 6, 2, 18),
            (1, 4, 1, 49), // approximate 54.60
            (1, 8, 2, 50),
            (10, 8, 2, 542), // approximate 540.598
            (11, 8, 2, 596), // approximate 600.58
            (1, 5, 1, 136),  // approximate 148.4
            (1, 5, 2, 11),   // approximate 12.18
            (2, 5, 2, 23),   // approximate 24.36
            (1, 50000000, 2225652, 5709098764),
            (1, 380928, BLOB_GASPRICE_UPDATE_FRACTION.try_into().unwrap(), 1),
        ] {
            let actual = fake_exponential(factor as u128, numerator as u128, denominator as u128);
            assert_eq!(actual, expected, "test: {t:?}");
        }
    }
}