From cd5a6590f1a98245cd88525f21e23aab0f686ba8 Mon Sep 17 00:00:00 2001 From: rakita Date: Wed, 8 Jan 2025 14:00:45 +0100 Subject: [PATCH] feat(EIP-7840): Add blob schedule to execution client cfg (#1980) * feat(EIP-7840): Add blob schedule to execution client configuration files * fix test * no_std include vec * doc --- bins/revme/src/cmd/statetest/runner.rs | 37 +++++++----- crates/context/interface/src/block/blob.rs | 65 ++++++++++++++++------ crates/context/interface/src/cfg.rs | 7 ++- crates/context/interface/src/result.rs | 2 +- crates/context/src/block.rs | 7 ++- crates/context/src/cfg.rs | 40 +++++++++++++ crates/handler/src/validation.rs | 6 +- crates/specification/src/eip4844.rs | 38 +++++++++---- crates/statetest-types/src/env.rs | 1 + 9 files changed, 157 insertions(+), 46 deletions(-) diff --git a/bins/revme/src/cmd/statetest/runner.rs b/bins/revme/src/cmd/statetest/runner.rs index 26a2b0641e..7c046b3656 100644 --- a/bins/revme/src/cmd/statetest/runner.rs +++ b/bins/revme/src/cmd/statetest/runner.rs @@ -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; @@ -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 { @@ -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()); diff --git a/crates/context/interface/src/block/blob.rs b/crates/context/interface/src/block/blob.rs index da1aa40a1e..d94c23c99b 100644 --- a/crates/context/interface/src/block/blob.rs +++ b/crates/context/interface/src/block/blob.rs @@ -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 /// @@ -20,13 +18,33 @@ 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`. @@ -34,8 +52,12 @@ impl BlobExcessGasAndPrice { /// See also [the 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. @@ -43,11 +65,15 @@ pub fn calc_excess_blob_gas(parent_excess_blob_gas: u64, parent_blob_gas_used: u /// 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 + }, ) } @@ -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] @@ -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:?}"); } } @@ -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}"); } } @@ -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:?}"); diff --git a/crates/context/interface/src/cfg.rs b/crates/context/interface/src/cfg.rs index 5f2221d9c1..1872f2ace2 100644 --- a/crates/context/interface/src/cfg.rs +++ b/crates/context/interface/src/cfg.rs @@ -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; diff --git a/crates/context/interface/src/result.rs b/crates/context/interface/src/result.rs index 7a93da2a6a..29bd64d6d1 100644 --- a/crates/context/interface/src/result.rs +++ b/crates/context/interface/src/result.rs @@ -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, diff --git a/crates/context/src/block.rs b/crates/context/src/block.rs index 987cbbacab..48524a4cf9 100644 --- a/crates/context/src/block.rs +++ b/crates/context/src/block.rs @@ -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)); } } @@ -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)), } } } diff --git a/crates/context/src/cfg.rs b/crates/context/src/cfg.rs index e5695ca306..8e54dcaa65 100644 --- a/crates/context/src/cfg.rs +++ b/crates/context/src/cfg.rs @@ -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))] @@ -24,6 +25,10 @@ pub struct CfgEnv = SpecId> { pub limit_contract_code_size: Option, /// 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. /// @@ -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 + Copy> Cfg for CfgEnv { @@ -90,6 +101,20 @@ impl + Copy> Cfg for CfgEnv { 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) } @@ -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")] @@ -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)); + } +} diff --git a/crates/handler/src/validation.rs b/crates/handler/src/validation.rs index 00470d02f3..b672830c7e 100644 --- a/crates/handler/src/validation.rs +++ b/crates/handler/src/validation.rs @@ -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 { @@ -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(()) @@ -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 => { diff --git a/crates/specification/src/eip4844.rs b/crates/specification/src/eip4844.rs index 6ad46e67f3..146147ffff 100644 --- a/crates/specification/src/eip4844.rs +++ b/crates/specification/src/eip4844.rs @@ -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; diff --git a/crates/statetest-types/src/env.rs b/crates/statetest-types/src/env.rs index 6e39f77c8a..201d9c35c6 100644 --- a/crates/statetest-types/src/env.rs +++ b/crates/statetest-types/src/env.rs @@ -20,5 +20,6 @@ pub struct Env { pub parent_blob_gas_used: Option, pub parent_excess_blob_gas: Option, + pub parent_target_blobs_per_block: Option, pub current_excess_blob_gas: Option, }