Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(l1): modify the RPC eth_feeHistory controller implementation #1797

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
13 changes: 7 additions & 6 deletions crates/blockchain/payload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ use crate::{

use tracing::debug;

// TODO: check where we should get builder values from
const DEFAULT_BUILDER_GAS_CEIL: u64 = 30_000_000;

pub struct BuildPayloadArgs {
pub parent: BlockHash,
pub timestamp: u64,
Expand Down Expand Up @@ -79,13 +82,11 @@ impl BuildPayloadArgs {
/// Creates a new payload based on the payload arguments
// Basic payload block building, can and should be improved
pub fn create_payload(args: &BuildPayloadArgs, storage: &Store) -> Result<Block, ChainError> {
// TODO: check where we should get builder values from
const DEFAULT_BUILDER_GAS_CEIL: u64 = 30_000_000;
let parent_block = storage
.get_block_header_by_hash(args.parent)?
.ok_or_else(|| ChainError::ParentNotFound)?;
let chain_config = storage.get_chain_config()?;
let gas_limit = calc_gas_limit(parent_block.gas_limit, DEFAULT_BUILDER_GAS_CEIL);
let gas_limit = calc_gas_limit(parent_block.gas_limit);

let header = BlockHeader {
parent_hash: args.parent,
Expand Down Expand Up @@ -137,10 +138,10 @@ pub fn create_payload(args: &BuildPayloadArgs, storage: &Store) -> Result<Block,
Ok(Block::new(header, body))
}

fn calc_gas_limit(parent_gas_limit: u64, desired_limit: u64) -> u64 {
pub fn calc_gas_limit(parent_gas_limit: u64) -> u64 {
let delta = parent_gas_limit / GAS_LIMIT_BOUND_DIVISOR - 1;
let mut limit = parent_gas_limit;
let desired_limit = max(desired_limit, MIN_GAS_LIMIT);
let desired_limit = max(DEFAULT_BUILDER_GAS_CEIL, MIN_GAS_LIMIT);
if limit < desired_limit {
limit = parent_gas_limit + delta;
if limit > desired_limit {
Expand All @@ -157,7 +158,7 @@ fn calc_gas_limit(parent_gas_limit: u64, desired_limit: u64) -> u64 {
limit
}

fn calc_excess_blob_gas(parent_excess_blob_gas: u64, parent_blob_gas_used: u64) -> u64 {
pub fn calc_excess_blob_gas(parent_excess_blob_gas: u64, parent_blob_gas_used: u64) -> u64 {
let excess_blob_gas = parent_excess_blob_gas + parent_blob_gas_used;
if excess_blob_gas < TARGET_BLOB_GAS_PER_BLOCK {
0
Expand Down
258 changes: 139 additions & 119 deletions crates/networking/rpc/eth/fee_market.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,29 @@
use ethrex_blockchain::constants::MAX_BLOB_GAS_PER_BLOCK;
use ethrex_core::types::{Block, Transaction};
use ethrex_blockchain::{
constants::MAX_BLOB_GAS_PER_BLOCK,
payload::{calc_excess_blob_gas, calc_gas_limit},
};
use ethrex_core::types::{calculate_base_fee_per_gas, Block, BlockHeader, Transaction};
use serde::Serialize;
use serde_json::Value;
use tracing::info;

use crate::{types::block_identifier::BlockIdentifier, utils::RpcErr, RpcApiContext, RpcHandler};
use crate::{
types::block_identifier::BlockIdentifier,
utils::{parse_json_hex, RpcErr},
RpcApiContext, RpcHandler,
};
use ethrex_core::types::calculate_base_fee_per_blob_gas;
use ethrex_storage::Store;

// Those are some offspec constants
const MAX_PERCENTILE_ARRAY_LEN: usize = 128;
const MAX_BLOCK_COUNT: u64 = 1024;

#[derive(Clone, Debug)]
pub struct FeeHistoryRequest {
pub block_count: u64,
pub newest_block: BlockIdentifier,
pub reward_percentiles: Option<Vec<f32>>,
pub reward_percentiles: Vec<f32>,
}

#[derive(Serialize, Default, Clone, Debug)]
Expand All @@ -26,44 +37,49 @@ pub struct FeeHistoryResponse {
pub reward: Vec<Vec<String>>,
}

// Implemented by reading:
// - https://github.com/ethereum/EIPs/blob/master/EIPS/eip-4844.md
// - https://ethereum.github.io/execution-apis/api-documentation/
// - https://github.com/ethereum/go-ethereum/blob/master/eth/gasprice/feehistory.go
impl RpcHandler for FeeHistoryRequest {
fn parse(params: &Option<Vec<Value>>) -> Result<FeeHistoryRequest, RpcErr> {
let params = params
.as_ref()
.ok_or(RpcErr::BadParams("No params provided".to_owned()))?;
if params.len() < 2 || params.len() > 3 {
if params.len() != 3 {
return Err(RpcErr::BadParams(format!(
"Expected 2 or 3 params, got {}",
"Expected 3 params, got {}",
params.len()
)));
};

let reward_percentiles = match params.get(2).cloned() {
Some(rp) => {
let rp: Vec<f32> = serde_json::from_value(rp)?;
let all_ok = rp
.windows(2)
.all(|w| w[0] <= w[1] || w[0] >= 0.0 && w[0] <= 100.0);
// We want to return None if any value is wrong
Some(
all_ok
.then_some(rp)
.ok_or(RpcErr::BadParams("Some of the params are wrong".to_owned()))?,
)
}
None => None,
};

let block_count_str: String = serde_json::from_value(params[0].clone())?;
let block_count_str = block_count_str.strip_prefix("0x").ok_or(RpcErr::BadParams(
"Expected param to be 0x prefixed".to_owned(),
))?;
let block_count: u64 =
parse_json_hex(&params[0]).map_err(|error| RpcErr::BadParams(error))?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit.

Suggested change
parse_json_hex(&params[0]).map_err(|error| RpcErr::BadParams(error))?;
parse_json_hex(&params[0]).map_err(RpcErr::BadParams)?;

// NOTE: This check is offspec
if block_count > MAX_BLOCK_COUNT {
return Err(RpcErr::BadParams(
"Too large block_count parameter".to_owned(),
));
}
let rp: Vec<f32> = serde_json::from_value(params[2].clone())?;
// NOTE: This check is offspec
if rp.len() > MAX_PERCENTILE_ARRAY_LEN {
return Err(RpcErr::BadParams(
format!("Wrong size reward_percentiles parameter, must be {MAX_PERCENTILE_ARRAY_LEN} at max"),
));
}
// Restric them to be monotnically increasing and in the range [0.0; 100.0]
let mut ok = rp.iter().all(|a| *a >= 0.0 && *a <= 100.0);
ok &= rp.windows(2).all(|w| w[0] <= w[1]);
if !ok {
return Err(RpcErr::BadParams(
"Wrong reward_percentiles parameter".to_owned(),
));
}

Ok(FeeHistoryRequest {
block_count: u64::from_str_radix(block_count_str, 16)
.map_err(|error| RpcErr::BadParams(error.to_string()))?,
newest_block: BlockIdentifier::parse(params[0].clone(), 0)?,
reward_percentiles,
block_count,
newest_block: BlockIdentifier::parse(params[1].clone(), 0)?,
reward_percentiles: rp,
})
}

Expand All @@ -79,17 +95,17 @@ impl RpcHandler for FeeHistoryRequest {
.map_err(|error| RpcErr::Internal(error.to_string()));
}

let (start_block, end_block) =
Self::get_range(storage, self.block_count, &self.newest_block)?;
let (start_block, end_block) = get_range(storage, self.block_count, &self.newest_block)?;
let oldest_block = start_block;
let block_count = (end_block - start_block) as usize;
let mut base_fee_per_gas = Vec::<u64>::with_capacity(block_count + 1);
let mut base_fee_per_blob_gas = Vec::<u64>::with_capacity(block_count + 1);
let mut gas_used_ratio = Vec::<f64>::with_capacity(block_count);
let mut blob_gas_used_ratio = Vec::<f64>::with_capacity(block_count);
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 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);

for block_number in start_block..end_block {
for block_number in start_block..=end_block {
let idx: usize = (block_number - start_block) as usize;
let header = storage
.get_block_header(block_number)?
.ok_or(RpcErr::Internal(format!(
Expand All @@ -100,34 +116,25 @@ impl RpcHandler for FeeHistoryRequest {
.ok_or(RpcErr::Internal(format!(
"Could not get body for block {block_number}"
)))?;
let blob_base_fee =
calculate_base_fee_per_blob_gas(header.excess_blob_gas.unwrap_or_default());

base_fee_per_gas.push(header.base_fee_per_gas.unwrap_or_default());
base_fee_per_blob_gas.push(blob_base_fee);
gas_used_ratio.push(header.gas_used as f64 / header.gas_limit as f64);
blob_gas_used_ratio.push(
header.blob_gas_used.unwrap_or_default() as f64 / MAX_BLOB_GAS_PER_BLOCK as f64,
);

if let Some(percentiles) = &self.reward_percentiles {
let block = Block::new(header, body);
reward.push(Self::calculate_percentiles_for_block(block, percentiles));
base_fee_per_gas[idx] = header.base_fee_per_gas.unwrap_or_default();
base_fee_per_blob_gas[idx] =
calculate_base_fee_per_blob_gas(header.excess_blob_gas.unwrap_or_default());
gas_used_ratio[idx] = header.gas_used as f64 / header.gas_limit as f64;
blob_gas_used_ratio[idx] =
header.blob_gas_used.unwrap_or_default() as f64 / MAX_BLOB_GAS_PER_BLOCK as f64;
if block_number == end_block {
(base_fee_per_gas[idx + 1], base_fee_per_blob_gas[idx + 1]) =
project_next_block_base_fee_values(&header);
}
if self.reward_percentiles.len() > 0 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit.

Suggested change
if self.reward_percentiles.len() > 0 {
if !self.reward_percentiles.len().is_empty() {

reward.push(calculate_percentiles_for_block(
Block::new(header, body),
&self.reward_percentiles,
));
}
}

// Now we project base_fee_per_gas and base_fee_per_blob_gas from last block
let header = storage
.get_block_header(end_block)?
.ok_or(RpcErr::Internal(format!(
"Could not get header for block {end_block}"
)))?;

let blob_base_fee =
calculate_base_fee_per_blob_gas(header.excess_blob_gas.unwrap_or_default());
base_fee_per_gas.push(header.base_fee_per_gas.unwrap_or_default());
base_fee_per_blob_gas.push(blob_base_fee);

let u64_to_hex_str = |x: u64| format!("0x{:x}", x);
let response = FeeHistoryResponse {
oldest_block: u64_to_hex_str(oldest_block),
Expand All @@ -143,73 +150,86 @@ impl RpcHandler for FeeHistoryRequest {
.map(|v| v.into_iter().map(u64_to_hex_str).collect())
.collect(),
};

serde_json::to_value(response).map_err(|error| RpcErr::Internal(error.to_string()))
}
}

impl FeeHistoryRequest {
fn get_range(
storage: &Store,
block_num: u64,
finish_block: &BlockIdentifier,
) -> Result<(u64, u64), RpcErr> {
// TODO: We should probably restrict how many blocks we are fetching to a certain limit

// Get earliest block
let earliest_block_num = storage.get_earliest_block_number()?;

// Get latest block
let latest_block_num = storage.get_latest_block_number()?;
// Project base_fee_per_gas and base_fee_per_blob_gas of next block, from provided block
fn project_next_block_base_fee_values(header: &BlockHeader) -> (u64, u64) {
// 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:
// -> https://github.com/ethereum/go-ethereum/blob/master/eth/gasprice/feehistory.go#L93
let next_gas_limit = calc_gas_limit(header.gas_limit);
let base_fee_per_gas = calculate_base_fee_per_gas(
next_gas_limit,
header.gas_limit,
header.gas_used,
header.base_fee_per_gas.unwrap_or_default(),
)
.unwrap_or_default();
let next_excess_blob_gas = calc_excess_blob_gas(
header.excess_blob_gas.unwrap_or_default(),
header.blob_gas_used.unwrap_or_default(),
);
let base_fee_per_blob = calculate_base_fee_per_blob_gas(next_excess_blob_gas);
(base_fee_per_gas, base_fee_per_blob)
}

// Get finish_block number
let finish_block = finish_block
fn get_range(
storage: &Store,
block_count: u64,
expected_finish_block: &BlockIdentifier,
) -> Result<(u64, u64), RpcErr> {
// NOTE: The amount of blocks to retrieve is capped by MAX_BLOCK_COUNT

// Get earliest block
let earliest_block_num = storage.get_earliest_block_number()?;
// Get latest block
let latest_block_num = storage.get_latest_block_number()?;
// Get the expected finish block number from the parameter
let expected_finish_block_num =
expected_finish_block
.resolve_block_number(storage)?
.ok_or(RpcErr::Internal(
"Could not resolve block number".to_owned(),
))?;
// Calculate start and finish block numbers, considering finish block inclusion
let finish_block_num = expected_finish_block_num.min(latest_block_num);
let expected_start_block_num = (finish_block_num + 1).saturating_sub(block_count);
let start_block_num = earliest_block_num.max(expected_start_block_num);

// finish block has to be included in the range
let finish_block = finish_block + 1;

// Acomodate finish_block to be <= latest_block
let finish_block = finish_block.min(latest_block_num);
Ok((start_block_num, finish_block_num))
}

// Acomodate start_block to be >= earliest_block
let start_block = earliest_block_num.max(finish_block.saturating_sub(block_num));
fn calculate_percentiles_for_block(block: Block, percentiles: &[f32]) -> Vec<u64> {
let base_fee_per_gas = block.header.base_fee_per_gas.unwrap_or_default();
let mut effective_priority_fees: Vec<u64> = block
.body
.transactions
.into_iter()
.map(|t: Transaction| match t {
Transaction::LegacyTransaction(_) | Transaction::EIP2930Transaction(_) => 0,
Transaction::EIP1559Transaction(t) => t
.max_priority_fee_per_gas
.min(t.max_fee_per_gas.saturating_sub(base_fee_per_gas)),
Transaction::EIP4844Transaction(t) => t
.max_priority_fee_per_gas
.min(t.max_fee_per_gas.saturating_sub(base_fee_per_gas)),
Transaction::PrivilegedL2Transaction(t) => t
.max_priority_fee_per_gas
.min(t.max_fee_per_gas.saturating_sub(base_fee_per_gas)),
})
.collect();

Ok((start_block, finish_block))
}
effective_priority_fees.sort();
let t_len = effective_priority_fees.len() as f32;

fn calculate_percentiles_for_block(block: Block, percentiles: &[f32]) -> Vec<u64> {
let base_fee_per_gas = block.header.base_fee_per_gas.unwrap_or_default();
let mut effective_priority_fees: Vec<u64> = block
.body
.transactions
.into_iter()
.map(|t: Transaction| match t {
Transaction::LegacyTransaction(_) | Transaction::EIP2930Transaction(_) => 0,
Transaction::EIP1559Transaction(t) => t
.max_priority_fee_per_gas
.min(t.max_fee_per_gas.saturating_sub(base_fee_per_gas)),
Transaction::EIP4844Transaction(t) => t
.max_priority_fee_per_gas
.min(t.max_fee_per_gas.saturating_sub(base_fee_per_gas)),
Transaction::PrivilegedL2Transaction(t) => t
.max_priority_fee_per_gas
.min(t.max_fee_per_gas.saturating_sub(base_fee_per_gas)),
})
.collect();

effective_priority_fees.sort();
let t_len = effective_priority_fees.len() as f32;

percentiles
.iter()
.map(|x: &f32| {
let i = (x * t_len / 100_f32) as usize;
effective_priority_fees.get(i).cloned().unwrap_or_default()
})
.collect()
}
percentiles
.iter()
.map(|x: &f32| {
let i = (x * t_len / 100_f32) as usize;
effective_priority_fees.get(i).cloned().unwrap_or_default()
})
.collect()
}
Loading