Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/blockchain/payload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
25 changes: 25 additions & 0 deletions crates/common/serde_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,31 @@ pub mod u256 {
.collect()
}
}
pub mod hex_str_opt {
use serde::Serialize;

use super::*;

pub fn serialize<S>(value: &Option<U256>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
Option::<String>::serialize(&value.map(|v| format!("{v:#x}")), serializer)
}

pub fn deserialize<'de, D>(d: D) -> Result<Option<U256>, D::Error>
where
D: Deserializer<'de>,
{
let value = Option::<String>::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 {
Expand Down
128 changes: 74 additions & 54 deletions crates/common/types/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u64, FakeExponentialError> {
let mut i = 1_u64;
let mut output = 0_u64;
let mut numerator_accum = factor * denominator;
) -> Result<U256, FakeExponentialError> {
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
Expand Down Expand Up @@ -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,
Expand All @@ -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;
Expand Down Expand Up @@ -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());
}
}
15 changes: 7 additions & 8 deletions crates/l2/sequencer/l1_committer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -1214,7 +1213,7 @@ async fn estimate_blob_gas(
eth_client: &EthClient,
arbitrary_base_blob_gas_price: u64,
headroom: u64,
) -> Result<u64, CommitterError> {
) -> Result<U256, CommitterError> {
let latest_block = eth_client
.get_block_by_number(BlockIdentifier::Tag(BlockTag::Latest), false)
.await?;
Expand All @@ -1241,17 +1240,17 @@ 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)?;

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)?;

Expand Down
8 changes: 5 additions & 3 deletions crates/networking/rpc/eth/fee_market.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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::<Vec<u64>>::with_capacity(block_count);
Expand Down Expand Up @@ -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,
Expand All @@ -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:
Expand Down
8 changes: 4 additions & 4 deletions crates/networking/rpc/types/receipt.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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<u64>,
pub blob_gas_price: Option<U256>,
#[serde(
skip_serializing_if = "Option::is_none",
with = "serde_utils::u64::hex_str_opt",
Expand All @@ -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<u64>,
) -> Result<Self, RpcErr> {
let nonce = transaction.nonce();
Expand Down
7 changes: 6 additions & 1 deletion crates/vm/levm/src/errors.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -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 {
Expand Down
Loading
Loading