Skip to content

Commit

Permalink
feat(EIP-7840): Add blob schedule to execution client cfg (#1980)
Browse files Browse the repository at this point in the history
* feat(EIP-7840): Add blob schedule to execution client configuration files

* fix test

* no_std include vec

* doc
  • Loading branch information
rakita authored Jan 8, 2025
1 parent 67200ca commit cd5a659
Show file tree
Hide file tree
Showing 9 changed files with 157 additions and 46 deletions.
37 changes: 24 additions & 13 deletions bins/revme/src/cmd/statetest/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use revm::{
database_interface::EmptyDB,
handler::EthHandler,
primitives::{keccak256, Bytes, TxKind, B256},
specification::hardfork::SpecId,
specification::{eip4844::TARGET_BLOB_GAS_PER_BLOCK_CANCUN, hardfork::SpecId},
Context, DatabaseCommit, EvmCommit, MainEvm,
};
use serde_json::json;
Expand Down Expand Up @@ -291,18 +291,6 @@ pub fn execute_test_suite(
block.difficulty = unit.env.current_difficulty;
// After the Merge prevrandao replaces mix_hash field in block and replaced difficulty opcode in EVM.
block.prevrandao = unit.env.current_random;
// EIP-4844
if let Some(current_excess_blob_gas) = unit.env.current_excess_blob_gas {
block.set_blob_excess_gas_and_price(current_excess_blob_gas.to());
} else if let (Some(parent_blob_gas_used), Some(parent_excess_blob_gas)) = (
unit.env.parent_blob_gas_used,
unit.env.parent_excess_blob_gas,
) {
block.set_blob_excess_gas_and_price(calc_excess_blob_gas(
parent_blob_gas_used.to(),
parent_excess_blob_gas.to(),
));
}

// Tx env
tx.caller = if let Some(address) = unit.transaction.sender {
Expand Down Expand Up @@ -344,6 +332,29 @@ pub fn execute_test_suite(

cfg.spec = spec_name.to_spec_id();

// EIP-4844
if let Some(current_excess_blob_gas) = unit.env.current_excess_blob_gas {
block.set_blob_excess_gas_and_price(
current_excess_blob_gas.to(),
cfg.spec.is_enabled_in(SpecId::PRAGUE),
);
} else if let (Some(parent_blob_gas_used), Some(parent_excess_blob_gas)) = (
unit.env.parent_blob_gas_used,
unit.env.parent_excess_blob_gas,
) {
block.set_blob_excess_gas_and_price(
calc_excess_blob_gas(
parent_blob_gas_used.to(),
parent_excess_blob_gas.to(),
unit.env
.parent_target_blobs_per_block
.map(|i| i.to())
.unwrap_or(TARGET_BLOB_GAS_PER_BLOCK_CANCUN),
),
cfg.spec.is_enabled_in(SpecId::PRAGUE),
);
}

if cfg.spec.is_enabled_in(SpecId::MERGE) && block.prevrandao.is_none() {
// If spec is merge and prevrandao is not set, set it to default
block.prevrandao = Some(B256::default());
Expand Down
65 changes: 49 additions & 16 deletions crates/context/interface/src/block/blob.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
use specification::eip4844::{
BLOB_GASPRICE_UPDATE_FRACTION, MIN_BLOB_GASPRICE, TARGET_BLOB_GAS_PER_BLOCK,
};
use specification::eip4844::{self, MIN_BLOB_GASPRICE};

/// Structure holding block blob excess gas and it calculates blob fee
///
Expand All @@ -20,34 +18,62 @@ pub struct BlobExcessGasAndPrice {

impl BlobExcessGasAndPrice {
/// Creates a new instance by calculating the blob gas price with [`calc_blob_gasprice`].
pub fn new(excess_blob_gas: u64) -> Self {
let blob_gasprice = calc_blob_gasprice(excess_blob_gas);
pub fn new(excess_blob_gas: u64, is_prague: bool) -> Self {
let blob_gasprice = calc_blob_gasprice(excess_blob_gas, is_prague);
Self {
excess_blob_gas,
blob_gasprice,
}
}

/// Calculate this block excess gas and price from the parent excess gas and gas used
/// and the target blob gas per block.
///
/// This fields will be used to calculate `excess_blob_gas` with [`calc_excess_blob_gas`] func.
pub fn from_parent_and_target(
parent_excess_blob_gas: u64,
parent_blob_gas_used: u64,
parent_target_blob_gas_per_block: u64,
is_prague: bool,
) -> Self {
Self::new(
calc_excess_blob_gas(
parent_excess_blob_gas,
parent_blob_gas_used,
parent_target_blob_gas_per_block,
),
is_prague,
)
}
}

/// 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 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_BLOB_GAS_PER_BLOCK)
pub fn calc_excess_blob_gas(
parent_excess_blob_gas: u64,
parent_blob_gas_used: u64,
parent_target_blob_gas_per_block: u64,
) -> u64 {
(parent_excess_blob_gas + parent_blob_gas_used).saturating_sub(parent_target_blob_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 {
pub fn calc_blob_gasprice(excess_blob_gas: u64, is_prague: bool) -> u128 {
fake_exponential(
MIN_BLOB_GASPRICE,
excess_blob_gas,
BLOB_GASPRICE_UPDATE_FRACTION,
if is_prague {
eip4844::BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE
} else {
eip4844::BLOB_BASE_FEE_UPDATE_FRACTION_CANCUN
},
)
}

Expand Down Expand Up @@ -84,7 +110,10 @@ pub fn fake_exponential(factor: u64, numerator: u64, denominator: u64) -> u128 {
#[cfg(test)]
mod tests {
use super::*;
use specification::eip4844::GAS_PER_BLOB;
use specification::eip4844::{
BLOB_BASE_FEE_UPDATE_FRACTION_CANCUN, GAS_PER_BLOB,
TARGET_BLOB_GAS_PER_BLOCK_CANCUN as TARGET_BLOB_GAS_PER_BLOCK,
};

// https://github.com/ethereum/go-ethereum/blob/28857080d732857030eda80c69b9ba2c8926f221/consensus/misc/eip4844/eip4844_test.go#L27
#[test]
Expand Down Expand Up @@ -135,7 +164,11 @@ mod tests {
0,
),
] {
let actual = calc_excess_blob_gas(excess, blobs * GAS_PER_BLOB);
let actual = calc_excess_blob_gas(
excess,
blobs * GAS_PER_BLOB,
eip4844::TARGET_BLOB_GAS_PER_BLOCK_CANCUN,
);
assert_eq!(actual, expected, "test: {t:?}");
}
}
Expand All @@ -148,18 +181,18 @@ mod tests {
(2314057, 1),
(2314058, 2),
(10 * 1024 * 1024, 23),
// `calc_blob_gasprice` approximates `e ** (excess_blob_gas / BLOB_GASPRICE_UPDATE_FRACTION)` using Taylor expansion
// `calc_blob_gasprice` approximates `e ** (excess_blob_gas / BLOB_BASE_FEE_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
// 2 ** bits = e ** (excess_blob_gas / BLOB_BASE_FEE_UPDATE_FRACTION)
// excess_blob_gas = ln(2 ** bits) * BLOB_BASE_FEE_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);
let actual = calc_blob_gasprice(excess, false);
assert_eq!(actual, expected, "test: {excess}");
}
}
Expand All @@ -183,7 +216,7 @@ mod tests {
(1, 5, 2, 11), // approximate 12.18
(2, 5, 2, 23), // approximate 24.36
(1, 50000000, 2225652, 5709098764),
(1, 380928, BLOB_GASPRICE_UPDATE_FRACTION, 1),
(1, 380928, BLOB_BASE_FEE_UPDATE_FRACTION_CANCUN, 1),
] {
let actual = fake_exponential(factor, numerator, denominator);
assert_eq!(actual, expected, "test: {t:?}");
Expand Down
7 changes: 6 additions & 1 deletion crates/context/interface/src/cfg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,14 @@ pub trait Cfg {

fn chain_id(&self) -> u64;

// TODO : Make SpecId a associated type but for faster development we use impl Into.
// Specification id that is set.
fn spec(&self) -> Self::Spec;

/// Returns the blob target and max count for the given spec id.
///
/// EIP-7840: Add blob schedule to execution client configuration files
fn blob_max_count(&self, spec_id: SpecId) -> u8;

fn max_code_size(&self) -> usize;

fn is_eip3607_disabled(&self) -> bool;
Expand Down
2 changes: 1 addition & 1 deletion crates/context/interface/src/result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ pub enum InvalidTransaction {
///
/// `to` must be present
BlobCreateTransaction,
/// Transaction has more then [`specification::eip4844::MAX_BLOB_NUMBER_PER_BLOCK`] blobs
/// Transaction has more then `max` blobs
TooManyBlobs {
max: usize,
have: usize,
Expand Down
7 changes: 4 additions & 3 deletions crates/context/src/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,9 @@ pub struct BlockEnv {
impl BlockEnv {
/// Takes `blob_excess_gas` saves it inside env
/// and calculates `blob_fee` with [`BlobExcessGasAndPrice`].
pub fn set_blob_excess_gas_and_price(&mut self, excess_blob_gas: u64) {
self.blob_excess_gas_and_price = Some(BlobExcessGasAndPrice::new(excess_blob_gas));
pub fn set_blob_excess_gas_and_price(&mut self, excess_blob_gas: u64, is_prague: bool) {
self.blob_excess_gas_and_price =
Some(BlobExcessGasAndPrice::new(excess_blob_gas, is_prague));
}
}

Expand Down Expand Up @@ -103,7 +104,7 @@ impl Default for BlockEnv {
basefee: 0,
difficulty: U256::ZERO,
prevrandao: Some(B256::ZERO),
blob_excess_gas_and_price: Some(BlobExcessGasAndPrice::new(0)),
blob_excess_gas_and_price: Some(BlobExcessGasAndPrice::new(0, false)),
}
}
}
40 changes: 40 additions & 0 deletions crates/context/src/cfg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ pub use context_interface::Cfg;

use interpreter::MAX_CODE_SIZE;
use specification::hardfork::SpecId;
use std::{vec, vec::Vec};

/// EVM configuration
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
Expand All @@ -24,6 +25,10 @@ pub struct CfgEnv<SPEC: Into<SpecId> = SpecId> {
pub limit_contract_code_size: Option<usize>,
/// Skips the nonce validation against the account's nonce
pub disable_nonce_check: bool,
/// Blob target count. EIP-7840 Add blob schedule to EL config files.
///
/// Note : Items must be sorted by `SpecId`.
pub blob_target_and_max_count: Vec<(SpecId, u8, u8)>,
/// A hard memory limit in bytes beyond which
/// [OutOfGasError::Memory][context_interface::result::OutOfGasError::Memory] cannot be resized.
///
Expand Down Expand Up @@ -77,6 +82,12 @@ impl CfgEnv {
self.chain_id = chain_id;
self
}

/// Sets the blob target and max count over hardforks.
pub fn set_blob_max_and_target_count(&mut self, mut vec: Vec<(SpecId, u8, u8)>) {
vec.sort_by_key(|(id, _, _)| *id);
self.blob_target_and_max_count = vec;
}
}

impl<SPEC: Into<SpecId> + Copy> Cfg for CfgEnv<SPEC> {
Expand All @@ -90,6 +101,20 @@ impl<SPEC: Into<SpecId> + Copy> Cfg for CfgEnv<SPEC> {
self.spec
}

#[inline]
fn blob_max_count(&self, spec_id: SpecId) -> u8 {
self.blob_target_and_max_count
.iter()
.rev()
.find_map(|(id, _, max)| {
if spec_id as u8 >= *id as u8 {
return Some(*max);
}
None
})
.unwrap_or(6)
}

fn max_code_size(&self) -> usize {
self.limit_contract_code_size.unwrap_or(MAX_CODE_SIZE)
}
Expand Down Expand Up @@ -156,6 +181,7 @@ impl Default for CfgEnv {
limit_contract_code_size: None,
spec: SpecId::PRAGUE,
disable_nonce_check: false,
blob_target_and_max_count: vec![(SpecId::CANCUN, 3, 6), (SpecId::PRAGUE, 6, 9)],
#[cfg(feature = "memory_limit")]
memory_limit: (1 << 32) - 1,
#[cfg(feature = "optional_balance_check")]
Expand All @@ -171,3 +197,17 @@ impl Default for CfgEnv {
}
}
}

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

#[test]
fn blob_max_and_target_count() {
let cfg = CfgEnv::default();
assert_eq!(cfg.blob_max_count(SpecId::BERLIN), (6));
assert_eq!(cfg.blob_max_count(SpecId::CANCUN), (6));
assert_eq!(cfg.blob_max_count(SpecId::PRAGUE), (9));
assert_eq!(cfg.blob_max_count(SpecId::OSAKA), (9));
}
}
6 changes: 4 additions & 2 deletions crates/handler/src/validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ pub fn validate_eip4844_tx(
blobs: &[B256],
max_blob_fee: u128,
block_blob_gas_price: u128,
max_blobs: u8,
) -> Result<(), InvalidTransaction> {
// Ensure that the user was willing to at least pay the current blob gasprice
if block_blob_gas_price > max_blob_fee {
Expand All @@ -126,10 +127,10 @@ pub fn validate_eip4844_tx(

// Ensure the total blob gas spent is at most equal to the limit
// assert blob_gas_used <= MAX_BLOB_GAS_PER_BLOCK
if blobs.len() > eip4844::MAX_BLOB_NUMBER_PER_BLOCK as usize {
if blobs.len() > max_blobs as usize {
return Err(InvalidTransaction::TooManyBlobs {
have: blobs.len(),
max: eip4844::MAX_BLOB_NUMBER_PER_BLOCK as usize,
max: max_blobs as usize,
});
}
Ok(())
Expand Down Expand Up @@ -220,6 +221,7 @@ where
tx.blob_versioned_hashes(),
tx.max_fee_per_blob_gas(),
context.block().blob_gasprice().unwrap_or_default(),
context.cfg().blob_max_count(spec_id),
)?;
}
TransactionType::Eip7702 => {
Expand Down
38 changes: 28 additions & 10 deletions crates/specification/src/eip4844.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,43 @@
//! EIP-4844 constants
//!
/// First version of the blob
pub const VERSIONED_HASH_VERSION_KZG: u8 = 0x01;

/// Gas consumption of a single data blob (== blob byte size)
pub const GAS_PER_BLOB: u64 = 1 << 17;

/// Min blob gas price
pub const MIN_BLOB_GASPRICE: u64 = 1;

/// Target number of the blob per block
pub const TARGET_BLOB_NUMBER_PER_BLOCK: u64 = 3;
pub const TARGET_BLOB_NUMBER_PER_BLOCK_CANCUN: u64 = 3;

/// Max number of blobs per block
pub const MAX_BLOB_NUMBER_PER_BLOCK: u64 = 2 * TARGET_BLOB_NUMBER_PER_BLOCK;
pub const MAX_BLOB_NUMBER_PER_BLOCK_CANCUN: u64 = 2 * TARGET_BLOB_NUMBER_PER_BLOCK_CANCUN;

/// Maximum consumable blob gas for data blobs per block
pub const MAX_BLOB_GAS_PER_BLOCK: u64 = MAX_BLOB_NUMBER_PER_BLOCK * GAS_PER_BLOB;
pub const MAX_BLOB_GAS_PER_BLOCK_CANCUN: u64 = MAX_BLOB_NUMBER_PER_BLOCK_CANCUN * GAS_PER_BLOB;

/// Target consumable blob gas for data blobs per block (for 1559-like pricing)
pub const TARGET_BLOB_GAS_PER_BLOCK: u64 = TARGET_BLOB_NUMBER_PER_BLOCK * GAS_PER_BLOB;

/// Minimum gas price for data blobs
pub const MIN_BLOB_GASPRICE: u64 = 1;
pub const TARGET_BLOB_GAS_PER_BLOCK_CANCUN: u64 =
TARGET_BLOB_NUMBER_PER_BLOCK_CANCUN * GAS_PER_BLOB;

/// Controls the maximum rate of change for blob gas price
pub const BLOB_GASPRICE_UPDATE_FRACTION: u64 = 3338477;
pub const BLOB_BASE_FEE_UPDATE_FRACTION_CANCUN: u64 = 3338477;

/// First version of the blob
pub const VERSIONED_HASH_VERSION_KZG: u8 = 0x01;
/// Target number of the blob per block
pub const TARGET_BLOB_NUMBER_PER_BLOCK_PRAGUE: u64 = 6;

/// Max number of blobs per block
pub const MAX_BLOB_NUMBER_PER_BLOCK_PRAGUE: u64 = 9;

/// Maximum consumable blob gas for data blobs per block
pub const MAX_BLOB_GAS_PER_BLOCK_PRAGUE: u64 = MAX_BLOB_NUMBER_PER_BLOCK_PRAGUE * GAS_PER_BLOB;

/// Target consumable blob gas for data blobs per block (for 1559-like pricing)
pub const TARGET_BLOB_GAS_PER_BLOCK_PRAGUE: u64 =
TARGET_BLOB_NUMBER_PER_BLOCK_PRAGUE * GAS_PER_BLOB;

/// Controls the maximum rate of change for blob gas price
pub const BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE: u64 = 5007716;
Loading

0 comments on commit cd5a659

Please sign in to comment.