diff --git a/crates/blockchain/payload.rs b/crates/blockchain/payload.rs index f054584e13c..40d0b3f7213 100644 --- a/crates/blockchain/payload.rs +++ b/crates/blockchain/payload.rs @@ -250,7 +250,7 @@ impl PayloadBuildContext { .is_prague_activated(payload.header.timestamp) .then_some(Vec::new()), block_value: U256::zero(), - base_fee_per_blob_gas: U256::from(base_fee_per_blob_gas), + base_fee_per_blob_gas, payload, blobs_bundle: BlobsBundle::default(), store: storage.clone(), diff --git a/crates/common/serde_utils.rs b/crates/common/serde_utils.rs index 5d024cc865b..c7ff3dafaa9 100644 --- a/crates/common/serde_utils.rs +++ b/crates/common/serde_utils.rs @@ -137,6 +137,31 @@ pub mod u256 { .collect() } } + pub mod hex_str_opt { + use serde::Serialize; + + use super::*; + + pub fn serialize(value: &Option, serializer: S) -> Result + where + S: Serializer, + { + Option::::serialize(&value.map(|v| format!("{v:#x}")), serializer) + } + + pub fn deserialize<'de, D>(d: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + let value = Option::::deserialize(d)?; + match value { + Some(s) if !s.is_empty() => U256::from_str_radix(s.trim_start_matches("0x"), 16) + .map_err(|_| D::Error::custom("Failed to deserialize U256 value")) + .map(Some), + _ => Ok(None), + } + } + } } pub mod u32 { diff --git a/crates/common/types/block.rs b/crates/common/types/block.rs index 2348d13f028..0634a3095e7 100644 --- a/crates/common/types/block.rs +++ b/crates/common/types/block.rs @@ -384,74 +384,82 @@ fn check_gas_limit(gas_limit: u64, parent_gas_limit: u64) -> bool { /// Calculates the base fee per blob gas for the current block based on /// it's parent excess blob gas and the update fraction, which depends on the fork. -pub fn calculate_base_fee_per_blob_gas(parent_excess_blob_gas: u64, update_fraction: u64) -> u64 { +pub fn calculate_base_fee_per_blob_gas(parent_excess_blob_gas: u64, update_fraction: u64) -> U256 { if update_fraction == 0 { - return 0; + return U256::zero(); } fake_exponential( - MIN_BASE_FEE_PER_BLOB_GAS, - parent_excess_blob_gas, + U256::from(MIN_BASE_FEE_PER_BLOB_GAS), + U256::from(parent_excess_blob_gas), update_fraction, ) + .unwrap_or_default() } -// Defined in [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844) -pub fn fake_exponential(factor: u64, numerator: u64, denominator: u64) -> u64 { - let mut i = 1; - let mut output = U256::zero(); - let mut numerator_accum = U256::from(factor) * denominator; - while !numerator_accum.is_zero() { - output += numerator_accum; - numerator_accum = numerator_accum * numerator / (denominator * i); - i += 1; - } - if (output / denominator) > U256::from(u64::MAX) { - u64::MAX - } else { - (output / denominator).as_u64() - } -} - -#[derive(Debug, thiserror::Error)] -pub enum FakeExponentialError { - #[error("Denominator cannot be zero.")] - DenominatorIsZero, - #[error("Checked div failed is None.")] - CheckedDiv, - #[error("Checked mul failed is None.")] - CheckedMul, -} - -pub fn fake_exponential_checked( - factor: u64, - numerator: u64, +/// Approximates factor * e ** (numerator / denominator) using Taylor expansion +/// https://eips.ethereum.org/EIPS/eip-4844#helpers +/// 400_000_000 numerator is the limit for this operation to work with U256, +/// it will overflow with a larger numerator +pub fn fake_exponential( + factor: U256, + numerator: U256, denominator: u64, -) -> Result { - let mut i = 1_u64; - let mut output = 0_u64; - let mut numerator_accum = factor * denominator; +) -> Result { if denominator == 0 { return Err(FakeExponentialError::DenominatorIsZero); } - while numerator_accum > 0 { - output = output.saturating_add(numerator_accum); + if numerator.is_zero() { + return Ok(factor); + } - let denominator_i = denominator - .checked_mul(i) - .ok_or(FakeExponentialError::CheckedMul)?; + let mut output: U256 = U256::zero(); + let denominator_u256: U256 = denominator.into(); - if denominator_i == 0 { - return Err(FakeExponentialError::DenominatorIsZero); + // Initial multiplication: factor * denominator + let mut numerator_accum = factor + .checked_mul(denominator_u256) + .ok_or(FakeExponentialError::CheckedMul)?; + + let mut denominator_by_i = denominator_u256; + + #[expect( + clippy::arithmetic_side_effects, + reason = "division can't overflow since denominator is not 0" + )] + { + while !numerator_accum.is_zero() { + // Safe addition to output + output = output + .checked_add(numerator_accum) + .ok_or(FakeExponentialError::CheckedAdd)?; + + // Safe multiplication and division within loop + numerator_accum = numerator_accum + .checked_mul(numerator) + .ok_or(FakeExponentialError::CheckedMul)? + / denominator_by_i; + + // denominator comes from a u64 value, will never overflow before other variables. + denominator_by_i += denominator_u256; } - numerator_accum = numerator_accum * numerator / denominator_i; - i += 1; + output + .checked_div(denominator.into()) + .ok_or(FakeExponentialError::CheckedDiv) } +} - output - .checked_div(denominator) - .ok_or(FakeExponentialError::CheckedDiv) +#[derive(Debug, thiserror::Error, Serialize, Clone, PartialEq, Deserialize, Eq)] +pub enum FakeExponentialError { + #[error("FakeExponentialError: Denominator cannot be zero.")] + DenominatorIsZero, + #[error("FakeExponentialError: Checked div failed is None.")] + CheckedDiv, + #[error("FakeExponentialError: Checked mul failed is None.")] + CheckedMul, + #[error("FakeExponentialError: Checked add failed is None.")] + CheckedAdd, } // 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 } if fork >= Fork::Osaka - && BLOB_BASE_COST * parent_base_fee_per_gas - > (GAS_PER_BLOB as u64) + && U256::from(BLOB_BASE_COST * parent_base_fee_per_gas) + > (U256::from(GAS_PER_BLOB)) * calculate_base_fee_per_blob_gas( parent_excess_blob_gas, schedule.base_fee_update_fraction, @@ -767,7 +775,7 @@ pub fn calc_excess_blob_gas(parent: &BlockHeader, schedule: ForkBlobSchedule, fo mod test { use super::*; use crate::constants::EMPTY_KECCACK_HASH; - use crate::types::ELASTICITY_MULTIPLIER; + use crate::types::{BLOB_BASE_FEE_UPDATE_FRACTION, ELASTICITY_MULTIPLIER}; use ethereum_types::H160; use hex_literal::hex; use std::str::FromStr; @@ -991,6 +999,18 @@ mod test { #[test] fn test_fake_exponential_overflow() { // With u64 this overflows - fake_exponential(57532635, 3145728, 3338477); + assert!(fake_exponential(U256::from(57532635), U256::from(3145728), 3338477).is_ok()); + } + + #[test] + fn test_fake_exponential_bounds_overflow() { + // Making sure the limit we state in the documentation of 400_000_000 works + let thing = fake_exponential( + MIN_BASE_FEE_PER_BLOB_GAS.into(), + 400_000_000.into(), + BLOB_BASE_FEE_UPDATE_FRACTION, + ); + // With u64 this overflows + assert!(thing.is_ok()); } } diff --git a/crates/l2/sequencer/l1_committer.rs b/crates/l2/sequencer/l1_committer.rs index 0cdc5bfe9e7..5ff608f6296 100644 --- a/crates/l2/sequencer/l1_committer.rs +++ b/crates/l2/sequencer/l1_committer.rs @@ -17,8 +17,7 @@ use ethrex_common::{ Address, H256, U256, types::{ AccountUpdate, BLOB_BASE_FEE_UPDATE_FRACTION, BlobsBundle, Block, BlockNumber, Fork, - Genesis, MIN_BASE_FEE_PER_BLOB_GAS, TxType, batch::Batch, blobs_bundle, - fake_exponential_checked, + Genesis, MIN_BASE_FEE_PER_BLOB_GAS, TxType, batch::Batch, blobs_bundle, fake_exponential, }, }; use ethrex_l2_common::{ @@ -975,7 +974,7 @@ impl L1Committer { 20, // 20% of headroom ) .await? - .to_le_bytes(); + .to_little_endian(); let gas_price_per_blob = U256::from_little_endian(&le_bytes); @@ -1214,7 +1213,7 @@ async fn estimate_blob_gas( eth_client: &EthClient, arbitrary_base_blob_gas_price: u64, headroom: u64, -) -> Result { +) -> Result { let latest_block = eth_client .get_block_by_number(BlockIdentifier::Tag(BlockTag::Latest), false) .await?; @@ -1241,9 +1240,9 @@ async fn estimate_blob_gas( // If the blob's market is in high demand, the equation may give a really big number. // This function doesn't panic, it performs checked/saturating operations. - let blob_gas = fake_exponential_checked( - MIN_BASE_FEE_PER_BLOB_GAS, - total_blob_gas, + let blob_gas = fake_exponential( + U256::from(MIN_BASE_FEE_PER_BLOB_GAS), + U256::from(total_blob_gas), BLOB_BASE_FEE_UPDATE_FRACTION, ) .map_err(BlobEstimationError::FakeExponentialError)?; @@ -1251,7 +1250,7 @@ async fn estimate_blob_gas( let gas_with_headroom = (blob_gas * (100 + headroom)) / 100; // Check if we have an overflow when we take the headroom into account. - let blob_gas = arbitrary_base_blob_gas_price + let blob_gas = U256::from(arbitrary_base_blob_gas_price) .checked_add(gas_with_headroom) .ok_or(BlobEstimationError::OverflowError)?; diff --git a/crates/networking/rpc/eth/fee_market.rs b/crates/networking/rpc/eth/fee_market.rs index ad32396462c..0164b59f1e3 100644 --- a/crates/networking/rpc/eth/fee_market.rs +++ b/crates/networking/rpc/eth/fee_market.rs @@ -1,5 +1,6 @@ use ethrex_blockchain::payload::calc_gas_limit; use ethrex_common::{ + U256, constants::GAS_PER_BLOB, types::{ Block, BlockHeader, ELASTICITY_MULTIPLIER, Fork, ForkBlobSchedule, Transaction, @@ -102,7 +103,7 @@ impl RpcHandler for FeeHistoryRequest { let oldest_block = start_block; let block_count = (end_block - start_block + 1) as usize; let mut base_fee_per_gas = vec![0_u64; block_count + 1]; - let mut base_fee_per_blob_gas = vec![0_u64; block_count + 1]; + let mut base_fee_per_blob_gas = vec![U256::zero(); block_count + 1]; let mut gas_used_ratio = vec![0_f64; block_count]; let mut blob_gas_used_ratio = vec![0_f64; block_count]; let mut reward = Vec::>::with_capacity(block_count); @@ -165,12 +166,13 @@ impl RpcHandler for FeeHistoryRequest { } let u64_to_hex_str = |x: u64| format!("0x{x:x}"); + let u256_to_hex_str = |x: U256| format!("0x{x:x}"); let response = FeeHistoryResponse { oldest_block: u64_to_hex_str(oldest_block), base_fee_per_gas: base_fee_per_gas.into_iter().map(u64_to_hex_str).collect(), base_fee_per_blob_gas: base_fee_per_blob_gas .into_iter() - .map(u64_to_hex_str) + .map(u256_to_hex_str) .collect(), gas_used_ratio, blob_gas_used_ratio, @@ -189,7 +191,7 @@ fn project_next_block_base_fee_values( schedule: ForkBlobSchedule, fork: Fork, gas_ceil: u64, -) -> (u64, u64) { +) -> (u64, U256) { // NOTE: Given that this client supports the Paris fork and later versions, we are sure that the next block // will have the London update active, so the base fee calculation makes sense // Geth performs a validation for this case: diff --git a/crates/networking/rpc/types/receipt.rs b/crates/networking/rpc/types/receipt.rs index bdf4f2ddc45..716bf21da4a 100644 --- a/crates/networking/rpc/types/receipt.rs +++ b/crates/networking/rpc/types/receipt.rs @@ -1,5 +1,5 @@ use ethrex_common::{ - Address, Bloom, Bytes, H256, + Address, Bloom, Bytes, H256, U256, constants::GAS_PER_BLOB, evm::calculate_create_address, serde_utils, @@ -154,10 +154,10 @@ pub struct RpcReceiptTxInfo { pub effective_gas_price: u64, #[serde( skip_serializing_if = "Option::is_none", - with = "ethrex_common::serde_utils::u64::hex_str_opt", + with = "serde_utils::u256::hex_str_opt", default = "Option::default" )] - pub blob_gas_price: Option, + pub blob_gas_price: Option, #[serde( skip_serializing_if = "Option::is_none", with = "serde_utils::u64::hex_str_opt", @@ -171,7 +171,7 @@ impl RpcReceiptTxInfo { transaction: Transaction, index: u64, gas_used: u64, - block_blob_gas_price: u64, + block_blob_gas_price: U256, base_fee_per_gas: Option, ) -> Result { let nonce = transaction.nonce(); diff --git a/crates/vm/levm/src/errors.rs b/crates/vm/levm/src/errors.rs index c69e1ce82cb..b5fca88ef17 100644 --- a/crates/vm/levm/src/errors.rs +++ b/crates/vm/levm/src/errors.rs @@ -1,6 +1,9 @@ use bytes::Bytes; use derive_more::derive::Display; -use ethrex_common::{Address, H256, U256, types::Log}; +use ethrex_common::{ + Address, H256, U256, + types::{FakeExponentialError, Log}, +}; use serde::{Deserialize, Serialize}; use thiserror; @@ -176,6 +179,8 @@ pub enum InternalError { /// Unexpected error when accessing the database, used in trait `Database`. #[error("Database access error: {0}")] Database(#[from] DatabaseError), + #[error("{0}")] + FakeExponentialError(#[from] FakeExponentialError), } impl InternalError { diff --git a/crates/vm/levm/src/gas_cost.rs b/crates/vm/levm/src/gas_cost.rs index 031ab096657..9e745b27b37 100644 --- a/crates/vm/levm/src/gas_cost.rs +++ b/crates/vm/levm/src/gas_cost.rs @@ -781,52 +781,6 @@ pub fn staticcall( calculate_cost_and_gas_limit_call(true, gas_from_stack, gas_left, call_gas_costs, 0) } -/// Approximates factor * e ** (numerator / denominator) using Taylor expansion -/// https://eips.ethereum.org/EIPS/eip-4844#helpers -pub fn fake_exponential(factor: U256, numerator: U256, denominator: u64) -> Result { - if denominator == 0 { - return Err(InternalError::DivisionByZero.into()); - } - - if numerator.is_zero() { - return Ok(factor); - } - - let mut output: U256 = U256::zero(); - let denominator_u256: U256 = denominator.into(); - - // Initial multiplication: factor * denominator - let mut numerator_accum = factor - .checked_mul(denominator_u256) - .ok_or(InternalError::Overflow)?; - - let mut denominator_by_i = denominator_u256; - - #[expect( - clippy::arithmetic_side_effects, - reason = "division can't overflow since denominator is not 0" - )] - { - while !numerator_accum.is_zero() { - // Safe addition to output - output = output - .checked_add(numerator_accum) - .ok_or(InternalError::Overflow)?; - - // Safe multiplication and division within loop - numerator_accum = numerator_accum - .checked_mul(numerator) - .ok_or(InternalError::Overflow)? - / denominator_by_i; - - // denominator comes from a u64 value, will never overflow before other variables. - denominator_by_i += denominator_u256; - } - - Ok(output / denominator) - } -} - pub fn sha2_256(data_size: usize) -> Result { precompile(data_size, SHA2_256_STATIC_COST, SHA2_256_DYNAMIC_BASE) } diff --git a/crates/vm/levm/src/utils.rs b/crates/vm/levm/src/utils.rs index c6709341e2c..2b5d305da58 100644 --- a/crates/vm/levm/src/utils.rs +++ b/crates/vm/levm/src/utils.rs @@ -8,7 +8,7 @@ use crate::{ gas_cost::{ self, ACCESS_LIST_ADDRESS_COST, ACCESS_LIST_STORAGE_KEY_COST, BLOB_GAS_PER_BLOB, COLD_ADDRESS_ACCESS_COST, CREATE_BASE_COST, STANDARD_TOKEN_COST, - TOTAL_COST_FLOOR_PER_TOKEN, WARM_ADDRESS_ACCESS_COST, fake_exponential, + TOTAL_COST_FLOOR_PER_TOKEN, WARM_ADDRESS_ACCESS_COST, }, opcodes::Opcode, vm::{Substate, VM}, @@ -19,7 +19,10 @@ use bytes::Bytes; use ethrex_common::{ Address, H256, U256, evm::calculate_create_address, - types::{Account, Code, Fork, Transaction, account_diff::AccountStateDiff, tx_fields::*}, + types::{ + Account, Code, Fork, Transaction, account_diff::AccountStateDiff, fake_exponential, + tx_fields::*, + }, utils::{keccak, u256_to_big_endian}, }; use ethrex_common::{types::TxKind, utils::u256_from_big_endian_const}; @@ -268,6 +271,7 @@ pub fn get_base_fee_per_blob_gas( block_excess_blob_gas.unwrap_or_default(), base_fee_update_fraction, ) + .map_err(|err| VMError::Internal(InternalError::FakeExponentialError(err))) } /// Gets the max blob gas cost for a transaction that a user is