Skip to content

Commit 5b1c859

Browse files
fedackingjrchatruc
andauthored
fix(l1): unifiy fake_exponential to a single U256 function (#5107)
**Motivation** There were multiple functions implementing the eip4844 helper function `fake_exponential`. These functions should be unified to a single U256 implementation that properly takes into account the possible overflow. **Description** - Removed `fake_exponential` function in LEVM crate - Removed `fake_exponential_checked` function in `block.rs` - Changed `fake_exponential` function signature to be U256 - Changed `base_fee_per_blob_gas` to be U256 where is it used - Added a test for the 400M limit where the function still operates --------- Co-authored-by: Javier Rodríguez Chatruc <[email protected]>
1 parent 271e68a commit 5b1c859

File tree

9 files changed

+128
-119
lines changed

9 files changed

+128
-119
lines changed

crates/blockchain/payload.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ impl PayloadBuildContext {
250250
.is_prague_activated(payload.header.timestamp)
251251
.then_some(Vec::new()),
252252
block_value: U256::zero(),
253-
base_fee_per_blob_gas: U256::from(base_fee_per_blob_gas),
253+
base_fee_per_blob_gas,
254254
payload,
255255
blobs_bundle: BlobsBundle::default(),
256256
store: storage.clone(),

crates/common/serde_utils.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,31 @@ pub mod u256 {
137137
.collect()
138138
}
139139
}
140+
pub mod hex_str_opt {
141+
use serde::Serialize;
142+
143+
use super::*;
144+
145+
pub fn serialize<S>(value: &Option<U256>, serializer: S) -> Result<S::Ok, S::Error>
146+
where
147+
S: Serializer,
148+
{
149+
Option::<String>::serialize(&value.map(|v| format!("{v:#x}")), serializer)
150+
}
151+
152+
pub fn deserialize<'de, D>(d: D) -> Result<Option<U256>, D::Error>
153+
where
154+
D: Deserializer<'de>,
155+
{
156+
let value = Option::<String>::deserialize(d)?;
157+
match value {
158+
Some(s) if !s.is_empty() => U256::from_str_radix(s.trim_start_matches("0x"), 16)
159+
.map_err(|_| D::Error::custom("Failed to deserialize U256 value"))
160+
.map(Some),
161+
_ => Ok(None),
162+
}
163+
}
164+
}
140165
}
141166

142167
pub mod u32 {

crates/common/types/block.rs

Lines changed: 74 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -384,74 +384,82 @@ fn check_gas_limit(gas_limit: u64, parent_gas_limit: u64) -> bool {
384384

385385
/// Calculates the base fee per blob gas for the current block based on
386386
/// it's parent excess blob gas and the update fraction, which depends on the fork.
387-
pub fn calculate_base_fee_per_blob_gas(parent_excess_blob_gas: u64, update_fraction: u64) -> u64 {
387+
pub fn calculate_base_fee_per_blob_gas(parent_excess_blob_gas: u64, update_fraction: u64) -> U256 {
388388
if update_fraction == 0 {
389-
return 0;
389+
return U256::zero();
390390
}
391391
fake_exponential(
392-
MIN_BASE_FEE_PER_BLOB_GAS,
393-
parent_excess_blob_gas,
392+
U256::from(MIN_BASE_FEE_PER_BLOB_GAS),
393+
U256::from(parent_excess_blob_gas),
394394
update_fraction,
395395
)
396+
.unwrap_or_default()
396397
}
397398

398-
// Defined in [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844)
399-
pub fn fake_exponential(factor: u64, numerator: u64, denominator: u64) -> u64 {
400-
let mut i = 1;
401-
let mut output = U256::zero();
402-
let mut numerator_accum = U256::from(factor) * denominator;
403-
while !numerator_accum.is_zero() {
404-
output += numerator_accum;
405-
numerator_accum = numerator_accum * numerator / (denominator * i);
406-
i += 1;
407-
}
408-
if (output / denominator) > U256::from(u64::MAX) {
409-
u64::MAX
410-
} else {
411-
(output / denominator).as_u64()
412-
}
413-
}
414-
415-
#[derive(Debug, thiserror::Error)]
416-
pub enum FakeExponentialError {
417-
#[error("Denominator cannot be zero.")]
418-
DenominatorIsZero,
419-
#[error("Checked div failed is None.")]
420-
CheckedDiv,
421-
#[error("Checked mul failed is None.")]
422-
CheckedMul,
423-
}
424-
425-
pub fn fake_exponential_checked(
426-
factor: u64,
427-
numerator: u64,
399+
/// Approximates factor * e ** (numerator / denominator) using Taylor expansion
400+
/// https://eips.ethereum.org/EIPS/eip-4844#helpers
401+
/// 400_000_000 numerator is the limit for this operation to work with U256,
402+
/// it will overflow with a larger numerator
403+
pub fn fake_exponential(
404+
factor: U256,
405+
numerator: U256,
428406
denominator: u64,
429-
) -> Result<u64, FakeExponentialError> {
430-
let mut i = 1_u64;
431-
let mut output = 0_u64;
432-
let mut numerator_accum = factor * denominator;
407+
) -> Result<U256, FakeExponentialError> {
433408
if denominator == 0 {
434409
return Err(FakeExponentialError::DenominatorIsZero);
435410
}
436411

437-
while numerator_accum > 0 {
438-
output = output.saturating_add(numerator_accum);
412+
if numerator.is_zero() {
413+
return Ok(factor);
414+
}
439415

440-
let denominator_i = denominator
441-
.checked_mul(i)
442-
.ok_or(FakeExponentialError::CheckedMul)?;
416+
let mut output: U256 = U256::zero();
417+
let denominator_u256: U256 = denominator.into();
443418

444-
if denominator_i == 0 {
445-
return Err(FakeExponentialError::DenominatorIsZero);
419+
// Initial multiplication: factor * denominator
420+
let mut numerator_accum = factor
421+
.checked_mul(denominator_u256)
422+
.ok_or(FakeExponentialError::CheckedMul)?;
423+
424+
let mut denominator_by_i = denominator_u256;
425+
426+
#[expect(
427+
clippy::arithmetic_side_effects,
428+
reason = "division can't overflow since denominator is not 0"
429+
)]
430+
{
431+
while !numerator_accum.is_zero() {
432+
// Safe addition to output
433+
output = output
434+
.checked_add(numerator_accum)
435+
.ok_or(FakeExponentialError::CheckedAdd)?;
436+
437+
// Safe multiplication and division within loop
438+
numerator_accum = numerator_accum
439+
.checked_mul(numerator)
440+
.ok_or(FakeExponentialError::CheckedMul)?
441+
/ denominator_by_i;
442+
443+
// denominator comes from a u64 value, will never overflow before other variables.
444+
denominator_by_i += denominator_u256;
446445
}
447446

448-
numerator_accum = numerator_accum * numerator / denominator_i;
449-
i += 1;
447+
output
448+
.checked_div(denominator.into())
449+
.ok_or(FakeExponentialError::CheckedDiv)
450450
}
451+
}
451452

452-
output
453-
.checked_div(denominator)
454-
.ok_or(FakeExponentialError::CheckedDiv)
453+
#[derive(Debug, thiserror::Error, Serialize, Clone, PartialEq, Deserialize, Eq)]
454+
pub enum FakeExponentialError {
455+
#[error("FakeExponentialError: Denominator cannot be zero.")]
456+
DenominatorIsZero,
457+
#[error("FakeExponentialError: Checked div failed is None.")]
458+
CheckedDiv,
459+
#[error("FakeExponentialError: Checked mul failed is None.")]
460+
CheckedMul,
461+
#[error("FakeExponentialError: Checked add failed is None.")]
462+
CheckedAdd,
455463
}
456464

457465
// Calculates the base fee for the current block based on its gas_limit and parent's gas and fee
@@ -748,8 +756,8 @@ pub fn calc_excess_blob_gas(parent: &BlockHeader, schedule: ForkBlobSchedule, fo
748756
}
749757

750758
if fork >= Fork::Osaka
751-
&& BLOB_BASE_COST * parent_base_fee_per_gas
752-
> (GAS_PER_BLOB as u64)
759+
&& U256::from(BLOB_BASE_COST * parent_base_fee_per_gas)
760+
> (U256::from(GAS_PER_BLOB))
753761
* calculate_base_fee_per_blob_gas(
754762
parent_excess_blob_gas,
755763
schedule.base_fee_update_fraction,
@@ -767,7 +775,7 @@ pub fn calc_excess_blob_gas(parent: &BlockHeader, schedule: ForkBlobSchedule, fo
767775
mod test {
768776
use super::*;
769777
use crate::constants::EMPTY_KECCACK_HASH;
770-
use crate::types::ELASTICITY_MULTIPLIER;
778+
use crate::types::{BLOB_BASE_FEE_UPDATE_FRACTION, ELASTICITY_MULTIPLIER};
771779
use ethereum_types::H160;
772780
use hex_literal::hex;
773781
use std::str::FromStr;
@@ -991,6 +999,18 @@ mod test {
991999
#[test]
9921000
fn test_fake_exponential_overflow() {
9931001
// With u64 this overflows
994-
fake_exponential(57532635, 3145728, 3338477);
1002+
assert!(fake_exponential(U256::from(57532635), U256::from(3145728), 3338477).is_ok());
1003+
}
1004+
1005+
#[test]
1006+
fn test_fake_exponential_bounds_overflow() {
1007+
// Making sure the limit we state in the documentation of 400_000_000 works
1008+
let thing = fake_exponential(
1009+
MIN_BASE_FEE_PER_BLOB_GAS.into(),
1010+
400_000_000.into(),
1011+
BLOB_BASE_FEE_UPDATE_FRACTION,
1012+
);
1013+
// With u64 this overflows
1014+
assert!(thing.is_ok());
9951015
}
9961016
}

crates/l2/sequencer/l1_committer.rs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,7 @@ use ethrex_common::{
1717
Address, H256, U256,
1818
types::{
1919
AccountUpdate, BLOB_BASE_FEE_UPDATE_FRACTION, BlobsBundle, Block, BlockNumber, Fork,
20-
Genesis, MIN_BASE_FEE_PER_BLOB_GAS, TxType, batch::Batch, blobs_bundle,
21-
fake_exponential_checked,
20+
Genesis, MIN_BASE_FEE_PER_BLOB_GAS, TxType, batch::Batch, blobs_bundle, fake_exponential,
2221
},
2322
};
2423
use ethrex_l2_common::{
@@ -975,7 +974,7 @@ impl L1Committer {
975974
20, // 20% of headroom
976975
)
977976
.await?
978-
.to_le_bytes();
977+
.to_little_endian();
979978

980979
let gas_price_per_blob = U256::from_little_endian(&le_bytes);
981980

@@ -1214,7 +1213,7 @@ async fn estimate_blob_gas(
12141213
eth_client: &EthClient,
12151214
arbitrary_base_blob_gas_price: u64,
12161215
headroom: u64,
1217-
) -> Result<u64, CommitterError> {
1216+
) -> Result<U256, CommitterError> {
12181217
let latest_block = eth_client
12191218
.get_block_by_number(BlockIdentifier::Tag(BlockTag::Latest), false)
12201219
.await?;
@@ -1241,17 +1240,17 @@ async fn estimate_blob_gas(
12411240

12421241
// If the blob's market is in high demand, the equation may give a really big number.
12431242
// This function doesn't panic, it performs checked/saturating operations.
1244-
let blob_gas = fake_exponential_checked(
1245-
MIN_BASE_FEE_PER_BLOB_GAS,
1246-
total_blob_gas,
1243+
let blob_gas = fake_exponential(
1244+
U256::from(MIN_BASE_FEE_PER_BLOB_GAS),
1245+
U256::from(total_blob_gas),
12471246
BLOB_BASE_FEE_UPDATE_FRACTION,
12481247
)
12491248
.map_err(BlobEstimationError::FakeExponentialError)?;
12501249

12511250
let gas_with_headroom = (blob_gas * (100 + headroom)) / 100;
12521251

12531252
// Check if we have an overflow when we take the headroom into account.
1254-
let blob_gas = arbitrary_base_blob_gas_price
1253+
let blob_gas = U256::from(arbitrary_base_blob_gas_price)
12551254
.checked_add(gas_with_headroom)
12561255
.ok_or(BlobEstimationError::OverflowError)?;
12571256

crates/networking/rpc/eth/fee_market.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use ethrex_blockchain::payload::calc_gas_limit;
22
use ethrex_common::{
3+
U256,
34
constants::GAS_PER_BLOB,
45
types::{
56
Block, BlockHeader, ELASTICITY_MULTIPLIER, Fork, ForkBlobSchedule, Transaction,
@@ -102,7 +103,7 @@ impl RpcHandler for FeeHistoryRequest {
102103
let oldest_block = start_block;
103104
let block_count = (end_block - start_block + 1) as usize;
104105
let mut base_fee_per_gas = vec![0_u64; block_count + 1];
105-
let mut base_fee_per_blob_gas = vec![0_u64; block_count + 1];
106+
let mut base_fee_per_blob_gas = vec![U256::zero(); block_count + 1];
106107
let mut gas_used_ratio = vec![0_f64; block_count];
107108
let mut blob_gas_used_ratio = vec![0_f64; block_count];
108109
let mut reward = Vec::<Vec<u64>>::with_capacity(block_count);
@@ -165,12 +166,13 @@ impl RpcHandler for FeeHistoryRequest {
165166
}
166167

167168
let u64_to_hex_str = |x: u64| format!("0x{x:x}");
169+
let u256_to_hex_str = |x: U256| format!("0x{x:x}");
168170
let response = FeeHistoryResponse {
169171
oldest_block: u64_to_hex_str(oldest_block),
170172
base_fee_per_gas: base_fee_per_gas.into_iter().map(u64_to_hex_str).collect(),
171173
base_fee_per_blob_gas: base_fee_per_blob_gas
172174
.into_iter()
173-
.map(u64_to_hex_str)
175+
.map(u256_to_hex_str)
174176
.collect(),
175177
gas_used_ratio,
176178
blob_gas_used_ratio,
@@ -189,7 +191,7 @@ fn project_next_block_base_fee_values(
189191
schedule: ForkBlobSchedule,
190192
fork: Fork,
191193
gas_ceil: u64,
192-
) -> (u64, u64) {
194+
) -> (u64, U256) {
193195
// NOTE: Given that this client supports the Paris fork and later versions, we are sure that the next block
194196
// will have the London update active, so the base fee calculation makes sense
195197
// Geth performs a validation for this case:

crates/networking/rpc/types/receipt.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use ethrex_common::{
2-
Address, Bloom, Bytes, H256,
2+
Address, Bloom, Bytes, H256, U256,
33
constants::GAS_PER_BLOB,
44
evm::calculate_create_address,
55
serde_utils,
@@ -154,10 +154,10 @@ pub struct RpcReceiptTxInfo {
154154
pub effective_gas_price: u64,
155155
#[serde(
156156
skip_serializing_if = "Option::is_none",
157-
with = "ethrex_common::serde_utils::u64::hex_str_opt",
157+
with = "serde_utils::u256::hex_str_opt",
158158
default = "Option::default"
159159
)]
160-
pub blob_gas_price: Option<u64>,
160+
pub blob_gas_price: Option<U256>,
161161
#[serde(
162162
skip_serializing_if = "Option::is_none",
163163
with = "serde_utils::u64::hex_str_opt",
@@ -171,7 +171,7 @@ impl RpcReceiptTxInfo {
171171
transaction: Transaction,
172172
index: u64,
173173
gas_used: u64,
174-
block_blob_gas_price: u64,
174+
block_blob_gas_price: U256,
175175
base_fee_per_gas: Option<u64>,
176176
) -> Result<Self, RpcErr> {
177177
let nonce = transaction.nonce();

crates/vm/levm/src/errors.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
use bytes::Bytes;
22
use derive_more::derive::Display;
3-
use ethrex_common::{Address, H256, U256, types::Log};
3+
use ethrex_common::{
4+
Address, H256, U256,
5+
types::{FakeExponentialError, Log},
6+
};
47
use serde::{Deserialize, Serialize};
58
use thiserror;
69

@@ -176,6 +179,8 @@ pub enum InternalError {
176179
/// Unexpected error when accessing the database, used in trait `Database`.
177180
#[error("Database access error: {0}")]
178181
Database(#[from] DatabaseError),
182+
#[error("{0}")]
183+
FakeExponentialError(#[from] FakeExponentialError),
179184
}
180185

181186
impl InternalError {

0 commit comments

Comments
 (0)