diff --git a/Cargo.lock b/Cargo.lock index abe128a632ad..bb247fd80e1b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6958,7 +6958,6 @@ dependencies = [ "reth-primitives", "reth-primitives-traits", "reth-storage-api", - "revm-primitives", ] [[package]] @@ -7301,6 +7300,7 @@ name = "reth-engine-local" version = "1.1.4" dependencies = [ "alloy-consensus", + "alloy-eips", "alloy-primitives", "alloy-rpc-types-engine", "eyre", diff --git a/crates/chainspec/src/spec.rs b/crates/chainspec/src/spec.rs index 1f8ebd45f45d..c200ad8c5600 100644 --- a/crates/chainspec/src/spec.rs +++ b/crates/chainspec/src/spec.rs @@ -4,7 +4,7 @@ use alloc::{boxed::Box, sync::Arc, vec::Vec}; use alloy_chains::{Chain, NamedChain}; use alloy_consensus::constants::EMPTY_WITHDRAWALS; use alloy_eips::{ - eip1559::INITIAL_BASE_FEE, eip6110::MAINNET_DEPOSIT_CONTRACT_ADDRESS, + eip1559::INITIAL_BASE_FEE, eip4844, eip6110::MAINNET_DEPOSIT_CONTRACT_ADDRESS, eip7685::EMPTY_REQUESTS_HASH, }; use alloy_genesis::Genesis; @@ -290,6 +290,9 @@ impl ChainSpec { let requests_hash = self .is_prague_active_at_timestamp(self.genesis.timestamp) .then_some(EMPTY_REQUESTS_HASH); + let target_blobs_per_block = self + .is_prague_active_at_timestamp(self.genesis.timestamp) + .then_some(eip4844::TARGET_BLOBS_PER_BLOCK); Header { gas_limit: self.genesis.gas_limit, @@ -306,6 +309,7 @@ impl ChainSpec { blob_gas_used: blob_gas_used.map(Into::into), excess_blob_gas: excess_blob_gas.map(Into::into), requests_hash, + target_blobs_per_block, ..Default::default() } } diff --git a/crates/consensus/common/Cargo.toml b/crates/consensus/common/Cargo.toml index a9a0c69ae559..3ba7ef7bc025 100644 --- a/crates/consensus/common/Cargo.toml +++ b/crates/consensus/common/Cargo.toml @@ -13,13 +13,12 @@ workspace = true [dependencies] # reth reth-chainspec.workspace = true -reth-consensus.workspace = true reth-primitives.workspace = true +reth-primitives-traits.workspace = true +reth-consensus.workspace = true # ethereum alloy-primitives.workspace = true -revm-primitives.workspace = true -reth-primitives-traits.workspace = true alloy-consensus.workspace = true alloy-eips.workspace = true diff --git a/crates/consensus/common/src/validation.rs b/crates/consensus/common/src/validation.rs index 37b6138e5d46..0966ca72c92a 100644 --- a/crates/consensus/common/src/validation.rs +++ b/crates/consensus/common/src/validation.rs @@ -2,14 +2,14 @@ use alloy_consensus::{constants::MAXIMUM_EXTRA_DATA_SIZE, BlockHeader, EMPTY_OMMER_ROOT_HASH}; use alloy_eips::{ - calc_next_block_base_fee, + calc_next_block_base_fee, eip4844, eip4844::{DATA_GAS_PER_BLOB, MAX_DATA_GAS_PER_BLOCK}, + eip7742, }; use reth_chainspec::{EthChainSpec, EthereumHardfork, EthereumHardforks}; use reth_consensus::ConsensusError; use reth_primitives::SealedBlock; use reth_primitives_traits::{BlockBody, GotExpected, SealedHeader}; -use revm_primitives::calc_excess_blob_gas; /// Gas used needs to be less than gas limit. Gas used is going to be checked after execution. #[inline] @@ -310,10 +310,15 @@ pub fn validate_against_parent_timestamp( /// ensures that the `blob_gas_used` and `excess_blob_gas` fields exist in the child header, and /// that the `excess_blob_gas` field matches the expected `excess_blob_gas` calculated from the /// parent header fields. -pub fn validate_against_parent_4844( +pub fn validate_against_parent_blob_fields( header: &H, parent: &H, + chain_spec: impl EthereumHardforks, ) -> Result<(), ConsensusError> { + if !chain_spec.is_cancun_active_at_timestamp(header.timestamp()) { + return Ok(()) + } + // From [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844#header-extension): // // > For the first post-fork block, both parent.blob_gas_used and parent.excess_blob_gas @@ -328,8 +333,18 @@ pub fn validate_against_parent_4844( } let excess_blob_gas = header.excess_blob_gas().ok_or(ConsensusError::ExcessBlobGasMissing)?; - let expected_excess_blob_gas = - calc_excess_blob_gas(parent_excess_blob_gas, parent_blob_gas_used); + let expected_excess_blob_gas = if chain_spec.is_prague_active_at_timestamp(parent.timestamp()) { + let parent_target_blobs_per_block = + parent.target_blobs_per_block().ok_or(ConsensusError::TargetBlobsPerBlockMissing)?; + eip7742::calc_excess_blob_gas( + parent_excess_blob_gas, + parent_blob_gas_used, + parent_target_blobs_per_block, + ) + } else { + eip4844::calc_excess_blob_gas(parent_excess_blob_gas, parent_blob_gas_used) + }; + if expected_excess_blob_gas != excess_blob_gas { return Err(ConsensusError::ExcessBlobGasDiff { diff: GotExpected { got: excess_blob_gas, expected: expected_excess_blob_gas }, diff --git a/crates/consensus/consensus/src/lib.rs b/crates/consensus/consensus/src/lib.rs index ba1b1321e776..7d5769d11dff 100644 --- a/crates/consensus/consensus/src/lib.rs +++ b/crates/consensus/consensus/src/lib.rs @@ -315,6 +315,10 @@ pub enum ConsensusError { #[display("missing requests hash")] RequestsHashMissing, + /// Error when target blobs per block is missing. + #[display("missing target blobs per block")] + TargetBlobsPerBlockMissing, + /// Error when an unexpected withdrawals root is encountered. #[display("unexpected withdrawals root")] WithdrawalsRootUnexpected, @@ -323,6 +327,10 @@ pub enum ConsensusError { #[display("unexpected requests hash")] RequestsHashUnexpected, + /// Error when an unexpected target blobs per block is encountered. + #[display("unexpected target blobs per block")] + TargetBlobsPerBlockUnexpected, + /// Error when withdrawals are missing. #[display("missing withdrawals")] BodyWithdrawalsMissing, diff --git a/crates/e2e-test-utils/src/engine_api.rs b/crates/e2e-test-utils/src/engine_api.rs index 8c0f03bafd3a..b9a7823e6fbb 100644 --- a/crates/e2e-test-utils/src/engine_api.rs +++ b/crates/e2e-test-utils/src/engine_api.rs @@ -1,5 +1,5 @@ use crate::traits::PayloadEnvelopeExt; -use alloy_primitives::B256; +use alloy_primitives::{B256, U64}; use alloy_rpc_types_engine::{ForkchoiceState, PayloadStatusEnum}; use jsonrpsee::{ core::client::ClientT, @@ -41,6 +41,14 @@ impl EngineApiTestContext eyre::Result { + Ok(self.engine_api_client.request("engine_getPayloadV4", (payload_id,)).await?) + } + /// Submits a payload to the engine api pub async fn submit_payload( &self, @@ -67,6 +75,7 @@ impl EngineApiTestContext( num_nodes: usize, chain_spec: Arc, is_dev: bool, - attributes_generator: impl Fn(u64) -> <::Engine as PayloadTypes>::PayloadBuilderAttributes + Copy + 'static, + attributes_generator: impl Fn(Arc, u64) -> <::Engine as PayloadTypes>::PayloadBuilderAttributes + Copy + 'static, ) -> eyre::Result<(Vec>, TaskManager, Wallet)> where N: Default + Node> + NodeTypesForTree + NodeTypesWithEngine, @@ -113,7 +113,7 @@ pub async fn setup_engine( num_nodes: usize, chain_spec: Arc, is_dev: bool, - attributes_generator: impl Fn(u64) -> <::Engine as PayloadTypes>::PayloadBuilderAttributes + Copy + 'static, + attributes_generator: impl Fn(Arc, u64) -> <::Engine as PayloadTypes>::PayloadBuilderAttributes + Copy + 'static, ) -> eyre::Result<( Vec>>>, TaskManager, diff --git a/crates/e2e-test-utils/src/node.rs b/crates/e2e-test-utils/src/node.rs index b5dd44841dc7..0bf548db98d3 100644 --- a/crates/e2e-test-utils/src/node.rs +++ b/crates/e2e-test-utils/src/node.rs @@ -20,7 +20,7 @@ use reth_provider::{ }; use reth_rpc_eth_api::helpers::{EthApiSpec, EthTransactions, TraceExt}; use reth_stages_types::StageId; -use std::{marker::PhantomData, pin::Pin}; +use std::{marker::PhantomData, pin::Pin, sync::Arc}; use tokio_stream::StreamExt; use url::Url; @@ -61,10 +61,14 @@ where /// Creates a new test node pub async fn new( node: FullNode, - attributes_generator: impl Fn(u64) -> Engine::PayloadBuilderAttributes + 'static, + attributes_generator: impl Fn(Arc<::ChainSpec>, u64) -> Engine::PayloadBuilderAttributes + + 'static, ) -> eyre::Result { let builder = node.payload_builder.clone(); + let chain_spec = node.chain_spec(); + let attributes_generator = + move |timestamp| attributes_generator(chain_spec.clone(), timestamp); Ok(Self { inner: node.clone(), payload: PayloadTestContext::new(builder, attributes_generator).await?, @@ -132,7 +136,11 @@ where // wait for the payload builder to have finished building self.payload.wait_for_built_payload(eth_attr.payload_id()).await; // trigger resolve payload via engine api - self.engine_api.get_payload_v3_value(eth_attr.payload_id()).await?; + if self.inner.chain_spec().is_prague_active_at_timestamp(eth_attr.timestamp()) { + self.engine_api.get_payload_v4_value(eth_attr.payload_id()).await?; + } else { + self.engine_api.get_payload_v3_value(eth_attr.payload_id()).await?; + } // ensure we're also receiving the built payload as event Ok((self.payload.expect_built_payload().await?, eth_attr)) } diff --git a/crates/engine/local/Cargo.toml b/crates/engine/local/Cargo.toml index b3ad169e3189..d8148492a3b6 100644 --- a/crates/engine/local/Cargo.toml +++ b/crates/engine/local/Cargo.toml @@ -29,6 +29,7 @@ reth-transaction-pool.workspace = true reth-stages-api.workspace = true # alloy +alloy-eips.workspace = true alloy-consensus.workspace = true alloy-primitives.workspace = true alloy-rpc-types-engine.workspace = true diff --git a/crates/engine/local/src/payload.rs b/crates/engine/local/src/payload.rs index 6355a2a00af2..f5ae02ff2104 100644 --- a/crates/engine/local/src/payload.rs +++ b/crates/engine/local/src/payload.rs @@ -1,6 +1,7 @@ //! The implementation of the [`PayloadAttributesBuilder`] for the //! [`LocalEngineService`](super::service::LocalEngineService). +use alloy_eips::eip4844; use alloy_primitives::{Address, B256}; use reth_chainspec::EthereumHardforks; use reth_ethereum_engine_primitives::EthPayloadAttributes; @@ -39,8 +40,14 @@ where .chain_spec .is_cancun_active_at_timestamp(timestamp) .then(B256::random), - target_blobs_per_block: None, - max_blobs_per_block: None, + target_blobs_per_block: self + .chain_spec + .is_prague_active_at_timestamp(timestamp) + .then_some(eip4844::TARGET_BLOBS_PER_BLOCK), + max_blobs_per_block: self + .chain_spec + .is_prague_active_at_timestamp(timestamp) + .then_some(eip4844::MAX_BLOBS_PER_BLOCK as u64), } } } diff --git a/crates/engine/util/src/reorg.rs b/crates/engine/util/src/reorg.rs index 24e141622842..e4e6316ec654 100644 --- a/crates/engine/util/src/reorg.rs +++ b/crates/engine/util/src/reorg.rs @@ -29,7 +29,7 @@ use reth_revm::{ DatabaseCommit, }; use reth_rpc_types_compat::engine::payload::block_to_payload; -use revm_primitives::{calc_excess_blob_gas, EVMError, EnvWithHandlerCfg}; +use revm_primitives::{EVMError, EnvWithHandlerCfg}; use std::{ collections::VecDeque, future::Future, @@ -387,10 +387,7 @@ where if chain_spec.is_cancun_active_at_timestamp(reorg_target.timestamp) { ( Some(sum_blob_gas_used), - Some(calc_excess_blob_gas( - reorg_target_parent.excess_blob_gas.unwrap_or_default(), - reorg_target_parent.blob_gas_used.unwrap_or_default(), - )), + Some(reorg_target_parent.next_block_excess_blob_gas().unwrap_or_default()), ) } else { (None, None) @@ -421,8 +418,8 @@ where blob_gas_used: blob_gas_used.map(Into::into), excess_blob_gas: excess_blob_gas.map(Into::into), state_root: state_provider.state_root(hashed_state)?, - requests_hash: None, // TODO(prague) - target_blobs_per_block: None, // TODO(prague) + requests_hash: None, // TODO(prague) + target_blobs_per_block: reorg_target.header.target_blobs_per_block, }, body: BlockBody { transactions, diff --git a/crates/ethereum/consensus/src/lib.rs b/crates/ethereum/consensus/src/lib.rs index 2eef9188671f..3a2993efd9ed 100644 --- a/crates/ethereum/consensus/src/lib.rs +++ b/crates/ethereum/consensus/src/lib.rs @@ -16,7 +16,7 @@ use reth_consensus::{ Consensus, ConsensusError, FullConsensus, HeaderValidator, PostExecutionInput, }; use reth_consensus_common::validation::{ - validate_4844_header_standalone, validate_against_parent_4844, + validate_4844_header_standalone, validate_against_parent_blob_fields, validate_against_parent_eip1559_base_fee, validate_against_parent_hash_number, validate_against_parent_timestamp, validate_block_pre_execution, validate_body_against_header, validate_header_base_fee, validate_header_extradata, validate_header_gas, @@ -167,8 +167,16 @@ where if header.requests_hash().is_none() { return Err(ConsensusError::RequestsHashMissing) } - } else if header.requests_hash().is_some() { - return Err(ConsensusError::RequestsHashUnexpected) + if header.target_blobs_per_block().is_none() { + return Err(ConsensusError::TargetBlobsPerBlockMissing) + } + } else { + if header.requests_hash().is_some() { + return Err(ConsensusError::RequestsHashUnexpected) + } + if header.target_blobs_per_block().is_some() { + return Err(ConsensusError::TargetBlobsPerBlockUnexpected) + } } Ok(()) @@ -194,9 +202,7 @@ where )?; // ensure that the blob gas fields for this block - if self.chain_spec.is_cancun_active_at_timestamp(header.timestamp()) { - validate_against_parent_4844(header.header(), parent.header())?; - } + validate_against_parent_blob_fields(header.header(), parent.header(), &self.chain_spec)?; Ok(()) } diff --git a/crates/ethereum/engine-primitives/src/payload.rs b/crates/ethereum/engine-primitives/src/payload.rs index ff07856f1ca6..802c68956a17 100644 --- a/crates/ethereum/engine-primitives/src/payload.rs +++ b/crates/ethereum/engine-primitives/src/payload.rs @@ -205,6 +205,10 @@ pub struct EthPayloadBuilderAttributes { pub withdrawals: Withdrawals, /// Root of the parent beacon block pub parent_beacon_block_root: Option, + /// Target blobs per block for the generated payload + pub target_blobs_per_block: Option, + /// Max blobs per block for the generated payload + pub max_blobs_per_block: Option, } // === impl EthPayloadBuilderAttributes === @@ -229,6 +233,8 @@ impl EthPayloadBuilderAttributes { prev_randao: attributes.prev_randao, withdrawals: attributes.withdrawals.unwrap_or_default().into(), parent_beacon_block_root: attributes.parent_beacon_block_root, + target_blobs_per_block: attributes.target_blobs_per_block, + max_blobs_per_block: attributes.max_blobs_per_block, } } } @@ -275,6 +281,10 @@ impl PayloadBuilderAttributes for EthPayloadBuilderAttributes { fn withdrawals(&self) -> &Withdrawals { &self.withdrawals } + + fn target_blobs_per_block(&self) -> Option { + self.target_blobs_per_block + } } /// Generates the payload id for the configured payload from the [`PayloadAttributes`]. diff --git a/crates/ethereum/node/tests/e2e/utils.rs b/crates/ethereum/node/tests/e2e/utils.rs index 84741a46aa69..7c56768cc2d7 100644 --- a/crates/ethereum/node/tests/e2e/utils.rs +++ b/crates/ethereum/node/tests/e2e/utils.rs @@ -1,4 +1,4 @@ -use alloy_eips::{BlockId, BlockNumberOrTag}; +use alloy_eips::{eip4844, BlockId, BlockNumberOrTag}; use alloy_primitives::{bytes, Address, B256}; use alloy_provider::{ network::{ @@ -10,6 +10,7 @@ use alloy_rpc_types_engine::PayloadAttributes; use alloy_rpc_types_eth::TransactionRequest; use alloy_signer::SignerSync; use rand::{seq::SliceRandom, Rng}; +use reth_chainspec::EthereumHardforks; use reth_e2e_test_utils::{wallet::Wallet, NodeHelperType, TmpDB}; use reth_node_api::NodeTypesWithDBAdapter; use reth_node_ethereum::EthereumNode; @@ -19,15 +20,22 @@ use reth_provider::FullProvider; use revm::primitives::{AccessListItem, Authorization}; /// Helper function to create a new eth payload attributes -pub(crate) fn eth_payload_attributes(timestamp: u64) -> EthPayloadBuilderAttributes { +pub(crate) fn eth_payload_attributes( + chain_spec: impl EthereumHardforks, + timestamp: u64, +) -> EthPayloadBuilderAttributes { let attributes = PayloadAttributes { timestamp, prev_randao: B256::ZERO, suggested_fee_recipient: Address::ZERO, withdrawals: Some(vec![]), parent_beacon_block_root: Some(B256::ZERO), - target_blobs_per_block: None, - max_blobs_per_block: None, + target_blobs_per_block: chain_spec + .is_prague_active_at_timestamp(timestamp) + .then_some(eip4844::TARGET_BLOBS_PER_BLOCK), + max_blobs_per_block: chain_spec + .is_prague_active_at_timestamp(timestamp) + .then_some(eip4844::MAX_BLOBS_PER_BLOCK as u64), }; EthPayloadBuilderAttributes::new(B256::ZERO, attributes) } diff --git a/crates/ethereum/payload/src/lib.rs b/crates/ethereum/payload/src/lib.rs index f909d3840e22..702343970291 100644 --- a/crates/ethereum/payload/src/lib.rs +++ b/crates/ethereum/payload/src/lib.rs @@ -11,8 +11,8 @@ use alloy_consensus::{Header, EMPTY_OMMER_ROOT_HASH}; use alloy_eips::{ - eip4844::MAX_DATA_GAS_PER_BLOCK, eip7002::WITHDRAWAL_REQUEST_TYPE, - eip7251::CONSOLIDATION_REQUEST_TYPE, eip7685::Requests, merge::BEACON_NONCE, + eip4844, eip7002::WITHDRAWAL_REQUEST_TYPE, eip7251::CONSOLIDATION_REQUEST_TYPE, + eip7685::Requests, eip7742, merge::BEACON_NONCE, }; use alloy_primitives::U256; use reth_basic_payload_builder::{ @@ -42,8 +42,8 @@ use reth_transaction_pool::{ use revm::{ db::{states::bundle_state::BundleRetention, State}, primitives::{ - calc_excess_blob_gas, BlockEnv, CfgEnvWithHandlerCfg, EVMError, EnvWithHandlerCfg, - InvalidTransaction, ResultAndState, TxEnv, + BlockEnv, CfgEnvWithHandlerCfg, EVMError, EnvWithHandlerCfg, InvalidTransaction, + ResultAndState, TxEnv, }, DatabaseCommit, }; @@ -173,6 +173,9 @@ where let mut cumulative_gas_used = 0; let mut sum_blob_gas_used = 0; let block_gas_limit: u64 = initialized_block_env.gas_limit.to::(); + let blob_gas_limit = + attributes.max_blobs_per_block.unwrap_or(eip4844::MAX_BLOBS_PER_BLOCK as u64) * + eip4844::DATA_GAS_PER_BLOB; let base_fee = initialized_block_env.basefee.to::(); let mut executed_txs = Vec::new(); @@ -250,7 +253,7 @@ where // the EIP-4844 can still fit in the block if let Some(blob_tx) = tx.transaction.as_eip4844() { let tx_blob_gas = blob_tx.blob_gas(); - if sum_blob_gas_used + tx_blob_gas > MAX_DATA_GAS_PER_BLOCK { + if sum_blob_gas_used + tx_blob_gas > blob_gas_limit { // we can't fit this _blob_ transaction into the block, so we mark it as // invalid, which removes its dependent transactions from // the iterator. This is similar to the gas limit condition @@ -258,10 +261,7 @@ where trace!(target: "payload_builder", tx=?tx.hash, ?sum_blob_gas_used, ?tx_blob_gas, "skipping blob transaction because it would exceed the max data gas per block"); best_txs.mark_invalid( &pool_tx, - InvalidPoolTransactionError::ExceedsGasLimit( - tx_blob_gas, - MAX_DATA_GAS_PER_BLOCK, - ), + InvalidPoolTransactionError::ExceedsGasLimit(tx_blob_gas, blob_gas_limit), ); continue } @@ -309,7 +309,7 @@ where sum_blob_gas_used += tx_blob_gas; // if we've reached the max data gas per block, we can skip blob txs entirely - if sum_blob_gas_used == MAX_DATA_GAS_PER_BLOCK { + if sum_blob_gas_used == blob_gas_limit { best_txs.skip_blobs(); } } @@ -440,14 +440,21 @@ where ) .map_err(PayloadBuilderError::other)?; - excess_blob_gas = if chain_spec.is_cancun_active_at_timestamp(parent_header.timestamp) { - let parent_excess_blob_gas = parent_header.excess_blob_gas.unwrap_or_default(); - let parent_blob_gas_used = parent_header.blob_gas_used.unwrap_or_default(); - Some(calc_excess_blob_gas(parent_excess_blob_gas, parent_blob_gas_used)) + excess_blob_gas = if chain_spec.is_prague_active_at_timestamp(parent_header.timestamp) { + Some(eip7742::calc_excess_blob_gas( + parent_header.excess_blob_gas.unwrap_or_default(), + parent_header.blob_gas_used.unwrap_or_default(), + parent_header.target_blobs_per_block.unwrap_or_default(), + )) + } else if chain_spec.is_cancun_active_at_timestamp(parent_header.timestamp) { + Some(eip4844::calc_excess_blob_gas( + parent_header.excess_blob_gas.unwrap_or_default(), + parent_header.blob_gas_used.unwrap_or_default(), + )) } else { // for the first post-fork block, both parent.blob_gas_used and // parent.excess_blob_gas are evaluated as 0 - Some(calc_excess_blob_gas(0, 0)) + Some(eip4844::calc_excess_blob_gas(0, 0)) }; blob_gas_used = Some(sum_blob_gas_used); @@ -475,7 +482,7 @@ where blob_gas_used: blob_gas_used.map(Into::into), excess_blob_gas: excess_blob_gas.map(Into::into), requests_hash, - target_blobs_per_block: None, + target_blobs_per_block: attributes.target_blobs_per_block, }; let withdrawals = chain_spec diff --git a/crates/optimism/consensus/src/lib.rs b/crates/optimism/consensus/src/lib.rs index d05ff9c9bd76..f27e777548f3 100644 --- a/crates/optimism/consensus/src/lib.rs +++ b/crates/optimism/consensus/src/lib.rs @@ -16,7 +16,7 @@ use reth_consensus::{ Consensus, ConsensusError, FullConsensus, HeaderValidator, PostExecutionInput, }; use reth_consensus_common::validation::{ - validate_against_parent_4844, validate_against_parent_eip1559_base_fee, + validate_against_parent_blob_fields, validate_against_parent_eip1559_base_fee, validate_against_parent_hash_number, validate_against_parent_timestamp, validate_body_against_header, validate_cancun_gas, validate_header_base_fee, validate_header_extradata, validate_header_gas, validate_shanghai_withdrawals, @@ -138,9 +138,7 @@ impl HeaderValidator for OpBeaconConsensus { } // ensure that the blob gas fields for this block - if self.chain_spec.is_cancun_active_at_timestamp(header.timestamp) { - validate_against_parent_4844(header.header(), parent.header())?; - } + validate_against_parent_blob_fields(header.header(), parent.header(), &self.chain_spec)?; Ok(()) } diff --git a/crates/optimism/node/src/engine.rs b/crates/optimism/node/src/engine.rs index 1db50b72ee80..b811e6c72f04 100644 --- a/crates/optimism/node/src/engine.rs +++ b/crates/optimism/node/src/engine.rs @@ -12,7 +12,8 @@ use reth_node_api::{ EngineObjectValidationError, MessageValidationKind, PayloadOrAttributes, PayloadTypes, VersionSpecificValidationError, }, - validate_version_specific_fields, EngineTypes, EngineValidator, PayloadValidator, + validate_eip7742_fields_presence, validate_version_specific_fields, EngineTypes, + EngineValidator, PayloadValidator, }; use reth_optimism_chainspec::OpChainSpec; use reth_optimism_forks::{OpHardfork, OpHardforks}; @@ -111,6 +112,11 @@ where payload_or_attrs.message_validation_kind(), payload_or_attrs.timestamp(), payload_or_attrs.parent_beacon_block_root().is_some(), + )?; + validate_eip7742_fields_presence( + version, + payload_or_attrs.message_validation_kind(), + payload_or_attrs.target_blobs_per_block().is_some(), ) } diff --git a/crates/optimism/node/src/utils.rs b/crates/optimism/node/src/utils.rs index 147aaac59dcc..34b7cc576c1f 100644 --- a/crates/optimism/node/src/utils.rs +++ b/crates/optimism/node/src/utils.rs @@ -1,7 +1,9 @@ use crate::{OpBuiltPayload, OpNode as OtherOpNode, OpPayloadBuilderAttributes}; +use alloy_eips::eip4844; use alloy_genesis::Genesis; use alloy_primitives::{Address, B256}; use alloy_rpc_types_engine::PayloadAttributes; +use reth_chainspec::EthereumHardforks; use reth_e2e_test_utils::{transaction::TransactionTestContext, wallet::Wallet, NodeHelperType}; use reth_optimism_chainspec::OpChainSpecBuilder; use reth_payload_builder::EthPayloadBuilderAttributes; @@ -48,15 +50,22 @@ pub async fn advance_chain( } /// Helper function to create a new eth payload attributes -pub fn optimism_payload_attributes(timestamp: u64) -> OpPayloadBuilderAttributes { +pub fn optimism_payload_attributes( + chain_spec: impl EthereumHardforks, + timestamp: u64, +) -> OpPayloadBuilderAttributes { let attributes = PayloadAttributes { timestamp, prev_randao: B256::ZERO, suggested_fee_recipient: Address::ZERO, withdrawals: Some(vec![]), parent_beacon_block_root: Some(B256::ZERO), - target_blobs_per_block: None, - max_blobs_per_block: None, + target_blobs_per_block: chain_spec + .is_prague_active_at_timestamp(timestamp) + .then_some(eip4844::TARGET_BLOBS_PER_BLOCK), + max_blobs_per_block: chain_spec + .is_prague_active_at_timestamp(timestamp) + .then_some(eip4844::MAX_BLOBS_PER_BLOCK as u64), }; OpPayloadBuilderAttributes { diff --git a/crates/optimism/payload/src/payload.rs b/crates/optimism/payload/src/payload.rs index e243745cea68..49c36f132449 100644 --- a/crates/optimism/payload/src/payload.rs +++ b/crates/optimism/payload/src/payload.rs @@ -89,6 +89,8 @@ impl PayloadBuilderAttributes for OpPayloadBuilderAttributes { prev_randao: attributes.payload_attributes.prev_randao, withdrawals: attributes.payload_attributes.withdrawals.unwrap_or_default().into(), parent_beacon_block_root: attributes.payload_attributes.parent_beacon_block_root, + target_blobs_per_block: attributes.payload_attributes.target_blobs_per_block, + max_blobs_per_block: attributes.payload_attributes.max_blobs_per_block, }; Ok(Self { @@ -127,6 +129,10 @@ impl PayloadBuilderAttributes for OpPayloadBuilderAttributes { fn withdrawals(&self) -> &Withdrawals { &self.payload_attributes.withdrawals } + + fn target_blobs_per_block(&self) -> Option { + self.payload_attributes.target_blobs_per_block + } } /// Contains the built payload. diff --git a/crates/payload/basic/src/stack.rs b/crates/payload/basic/src/stack.rs index 45a3f3b42448..937c23f4289a 100644 --- a/crates/payload/basic/src/stack.rs +++ b/crates/payload/basic/src/stack.rs @@ -120,6 +120,13 @@ where Self::Right(r) => r.withdrawals(), } } + + fn target_blobs_per_block(&self) -> Option { + match self { + Self::Left(l) => l.target_blobs_per_block(), + Self::Right(r) => r.target_blobs_per_block(), + } + } } /// this structure enables the chaining of multiple `PayloadBuilder` implementations, diff --git a/crates/payload/primitives/src/error.rs b/crates/payload/primitives/src/error.rs index ffe4e027e966..ec9ea3e57453 100644 --- a/crates/payload/primitives/src/error.rs +++ b/crates/payload/primitives/src/error.rs @@ -105,6 +105,14 @@ pub enum VersionSpecificValidationError { /// root after Cancun #[error("no parent beacon block root post-cancun")] NoParentBeaconBlockRootPostCancun, + /// Thrown if the pre-V4 `PayloadAttributes` or `ExecutionPayload` contains a target blobs per + /// block field + #[error("target blobs per block not supported before V4")] + TargetBlobsPerBlockNotSupportedBeforeV4, + /// Thrown if the `PayloadAttributes` or `ExecutionPayload` contains no target blobs per block + /// field after Cancun + #[error("no target blobs per block post-Prague")] + NoTargetBlobsPerBlockPostPrague, } impl EngineObjectValidationError { diff --git a/crates/payload/primitives/src/lib.rs b/crates/payload/primitives/src/lib.rs index 523e6fb057a6..ee3e2aeaa36e 100644 --- a/crates/payload/primitives/src/lib.rs +++ b/crates/payload/primitives/src/lib.rs @@ -96,27 +96,14 @@ pub fn validate_payload_timestamp( } let is_prague = chain_spec.is_prague_active_at_timestamp(timestamp); - if version == EngineApiMessageVersion::V4 && !is_prague { - // From the Engine API spec: - // - // - // For `engine_getPayloadV4`: - // - // 1. Client software **MUST** return `-38005: Unsupported fork` error if the `timestamp` of - // the built payload does not fall within the time frame of the Prague fork. - // - // For `engine_forkchoiceUpdatedV4`: - // - // 2. Client software **MUST** return `-38005: Unsupported fork` error if the - // `payloadAttributes` is set and the `payloadAttributes.timestamp` does not fall within - // the time frame of the Prague fork. - // - // For `engine_newPayloadV4`: - // - // 2. Client software **MUST** return `-38005: Unsupported fork` error if the `timestamp` of - // the payload does not fall within the time frame of the Prague fork. + if version < EngineApiMessageVersion::V4 && is_prague { return Err(EngineObjectValidationError::UnsupportedFork) } + + if version >= EngineApiMessageVersion::V4 && !is_prague { + return Err(EngineObjectValidationError::UnsupportedFork) + } + Ok(()) } @@ -132,24 +119,20 @@ pub fn validate_withdrawals_presence( ) -> Result<(), EngineObjectValidationError> { let is_shanghai_active = chain_spec.is_shanghai_active_at_timestamp(timestamp); - match version { - EngineApiMessageVersion::V1 => { - if has_withdrawals { - return Err(message_validation_kind - .to_error(VersionSpecificValidationError::WithdrawalsNotSupportedInV1)) - } - } - EngineApiMessageVersion::V2 | EngineApiMessageVersion::V3 | EngineApiMessageVersion::V4 => { - if is_shanghai_active && !has_withdrawals { - return Err(message_validation_kind - .to_error(VersionSpecificValidationError::NoWithdrawalsPostShanghai)) - } - if !is_shanghai_active && has_withdrawals { - return Err(message_validation_kind - .to_error(VersionSpecificValidationError::HasWithdrawalsPreShanghai)) - } - } - }; + if has_withdrawals && version < EngineApiMessageVersion::V2 { + return Err(message_validation_kind + .to_error(VersionSpecificValidationError::WithdrawalsNotSupportedInV1)) + } + + if !is_shanghai_active && has_withdrawals { + return Err(message_validation_kind + .to_error(VersionSpecificValidationError::HasWithdrawalsPreShanghai)) + } + + if is_shanghai_active && !has_withdrawals { + return Err(message_validation_kind + .to_error(VersionSpecificValidationError::NoWithdrawalsPostShanghai)) + } Ok(()) } @@ -232,21 +215,15 @@ pub fn validate_parent_beacon_block_root_presence( // failure. // 4. If any of the above checks fails, the `forkchoiceState` update **MUST NOT** be rolled // back. - match version { - EngineApiMessageVersion::V1 | EngineApiMessageVersion::V2 => { - if has_parent_beacon_block_root { - return Err(validation_kind.to_error( - VersionSpecificValidationError::ParentBeaconBlockRootNotSupportedBeforeV3, - )) - } - } - EngineApiMessageVersion::V3 | EngineApiMessageVersion::V4 => { - if !has_parent_beacon_block_root { - return Err(validation_kind - .to_error(VersionSpecificValidationError::NoParentBeaconBlockRootPostCancun)) - } - } - }; + if has_parent_beacon_block_root && version < EngineApiMessageVersion::V3 { + return Err(validation_kind + .to_error(VersionSpecificValidationError::ParentBeaconBlockRootNotSupportedBeforeV3)) + } + + if !has_parent_beacon_block_root && version >= EngineApiMessageVersion::V3 { + return Err(validation_kind + .to_error(VersionSpecificValidationError::NoParentBeaconBlockRootPostCancun)) + } // For `engine_forkchoiceUpdatedV3`: // @@ -263,6 +240,25 @@ pub fn validate_parent_beacon_block_root_presence( Ok(()) } +/// Validates that `targetBlobsPerBlock` field is only present for post-V4 requests. +pub fn validate_eip7742_fields_presence( + version: EngineApiMessageVersion, + validation_kind: MessageValidationKind, + has_target_blobs_per_block: bool, +) -> Result<(), EngineObjectValidationError> { + if has_target_blobs_per_block && version < EngineApiMessageVersion::V4 { + return Err(validation_kind + .to_error(VersionSpecificValidationError::TargetBlobsPerBlockNotSupportedBeforeV4)) + } + + if version >= EngineApiMessageVersion::V4 && !has_target_blobs_per_block { + return Err(validation_kind + .to_error(VersionSpecificValidationError::NoTargetBlobsPerBlockPostPrague)) + } + + Ok(()) +} + /// A type that represents whether or not we are validating a payload or payload attributes. /// /// This is used to ensure that the correct error code is returned when validating the payload or @@ -318,6 +314,11 @@ where payload_or_attrs.message_validation_kind(), payload_or_attrs.timestamp(), payload_or_attrs.parent_beacon_block_root().is_some(), + )?; + validate_eip7742_fields_presence( + version, + payload_or_attrs.message_validation_kind(), + payload_or_attrs.target_blobs_per_block().is_some(), ) } diff --git a/crates/payload/primitives/src/payload.rs b/crates/payload/primitives/src/payload.rs index bcf48cea8343..841513d44085 100644 --- a/crates/payload/primitives/src/payload.rs +++ b/crates/payload/primitives/src/payload.rs @@ -1,7 +1,7 @@ use crate::{MessageValidationKind, PayloadAttributes}; use alloy_eips::eip4895::Withdrawal; use alloy_primitives::B256; -use alloy_rpc_types_engine::ExecutionPayload; +use alloy_rpc_types_engine::{ExecutionPayload, ExecutionPayloadSidecar}; /// Either an [`ExecutionPayload`] or a types that implements the [`PayloadAttributes`] trait. /// @@ -12,8 +12,8 @@ pub enum PayloadOrAttributes<'a, Attributes> { ExecutionPayload { /// The inner execution payload payload: &'a ExecutionPayload, - /// The parent beacon block root - parent_beacon_block_root: Option, + /// Additional fields passed into `newPayload` request. + sidecar: &'a ExecutionPayloadSidecar, }, /// A payload attributes type. PayloadAttributes(&'a Attributes), @@ -24,9 +24,9 @@ impl<'a, Attributes> PayloadOrAttributes<'a, Attributes> { /// block root. pub const fn from_execution_payload( payload: &'a ExecutionPayload, - parent_beacon_block_root: Option, + sidecar: &'a ExecutionPayloadSidecar, ) -> Self { - Self::ExecutionPayload { payload, parent_beacon_block_root } + Self::ExecutionPayload { payload, sidecar } } /// Construct a [`PayloadOrAttributes::PayloadAttributes`] variant @@ -58,11 +58,19 @@ where /// Return the parent beacon block root for the payload or attributes. pub fn parent_beacon_block_root(&self) -> Option { match self { - Self::ExecutionPayload { parent_beacon_block_root, .. } => *parent_beacon_block_root, + Self::ExecutionPayload { sidecar, .. } => sidecar.parent_beacon_block_root(), Self::PayloadAttributes(attributes) => attributes.parent_beacon_block_root(), } } + /// Returns the target blobs per block for the payload or attributes. + pub fn target_blobs_per_block(&self) -> Option { + match self { + Self::ExecutionPayload { sidecar, .. } => sidecar.target_blobs_per_block(), + Self::PayloadAttributes(attributes) => attributes.target_blobs_per_block(), + } + } + /// Return a [`MessageValidationKind`] for the payload or attributes. pub const fn message_validation_kind(&self) -> MessageValidationKind { match self { diff --git a/crates/payload/primitives/src/traits.rs b/crates/payload/primitives/src/traits.rs index 8d5c429e6c64..d7684de2cb82 100644 --- a/crates/payload/primitives/src/traits.rs +++ b/crates/payload/primitives/src/traits.rs @@ -67,6 +67,9 @@ pub trait PayloadBuilderAttributes: Send + Sync + std::fmt::Debug { /// Returns the withdrawals for the running payload job. fn withdrawals(&self) -> &Withdrawals; + + /// Returns the target blobs per block for the running payload job. + fn target_blobs_per_block(&self) -> Option; } /// The execution payload attribute type the CL node emits via the engine API. @@ -84,6 +87,12 @@ pub trait PayloadAttributes: /// Return the parent beacon block root for the payload attributes. fn parent_beacon_block_root(&self) -> Option; + + /// Return the target blobs count for this block. + fn target_blobs_per_block(&self) -> Option; + + /// Return the maximum blobs count for this block. + fn max_blobs_per_block(&self) -> Option; } impl PayloadAttributes for EthPayloadAttributes { @@ -98,6 +107,14 @@ impl PayloadAttributes for EthPayloadAttributes { fn parent_beacon_block_root(&self) -> Option { self.parent_beacon_block_root } + + fn target_blobs_per_block(&self) -> Option { + self.target_blobs_per_block + } + + fn max_blobs_per_block(&self) -> Option { + self.max_blobs_per_block + } } #[cfg(feature = "op")] @@ -113,6 +130,14 @@ impl PayloadAttributes for op_alloy_rpc_types_engine::OpPayloadAttributes { fn parent_beacon_block_root(&self) -> Option { self.payload_attributes.parent_beacon_block_root } + + fn target_blobs_per_block(&self) -> Option { + self.payload_attributes.target_blobs_per_block + } + + fn max_blobs_per_block(&self) -> Option { + self.payload_attributes.max_blobs_per_block + } } /// A builder that can return the current payload attribute. diff --git a/crates/rpc/rpc-api/src/engine.rs b/crates/rpc/rpc-api/src/engine.rs index f78b8349be86..f7de962cdad6 100644 --- a/crates/rpc/rpc-api/src/engine.rs +++ b/crates/rpc/rpc-api/src/engine.rs @@ -58,6 +58,8 @@ pub trait EngineApi { versioned_hashes: Vec, parent_beacon_block_root: B256, execution_requests: Requests, + // U64 to deserialize as quantity + target_blobs_per_block: U64, ) -> RpcResult; /// See also @@ -100,6 +102,20 @@ pub trait EngineApi { payload_attributes: Option, ) -> RpcResult; + /// Post Prague forkchoice update handler + /// + /// This is the same as `forkchoiceUpdatedV3`, but expects additional + /// `targetBlobsPerBlock` and `maxBlobsPerBlock` fields in the `payloadAttributes`, if payload + /// attributes are provided. + /// + /// See also + #[method(name = "forkchoiceUpdatedV4")] + async fn fork_choice_updated_v4( + &self, + fork_choice_state: ForkchoiceState, + payload_attributes: Option, + ) -> RpcResult; + /// See also /// /// Returns the most recent version of the payload that is available in the corresponding diff --git a/crates/rpc/rpc-engine-api/src/engine_api.rs b/crates/rpc/rpc-engine-api/src/engine_api.rs index 2e80c105e7e6..43ce59c44487 100644 --- a/crates/rpc/rpc-engine-api/src/engine_api.rs +++ b/crates/rpc/rpc-engine-api/src/engine_api.rs @@ -142,9 +142,10 @@ where payload: ExecutionPayloadV1, ) -> EngineApiResult { let payload = ExecutionPayload::from(payload); + let sidecar = ExecutionPayloadSidecar::none(); let payload_or_attrs = PayloadOrAttributes::<'_, EngineT::PayloadAttributes>::from_execution_payload( - &payload, None, + &payload, &sidecar, ); self.inner .validator @@ -178,9 +179,10 @@ where payload: ExecutionPayloadInputV2, ) -> EngineApiResult { let payload = convert_payload_input_v2_to_payload(payload); + let sidecar = ExecutionPayloadSidecar::none(); let payload_or_attrs = PayloadOrAttributes::<'_, EngineT::PayloadAttributes>::from_execution_payload( - &payload, None, + &payload, &sidecar, ); self.inner .validator @@ -215,10 +217,13 @@ where parent_beacon_block_root: B256, ) -> EngineApiResult { let payload = ExecutionPayload::from(payload); + let sidecar = ExecutionPayloadSidecar::v3(CancunPayloadFields { + versioned_hashes, + parent_beacon_block_root, + }); let payload_or_attrs = PayloadOrAttributes::<'_, EngineT::PayloadAttributes>::from_execution_payload( - &payload, - Some(parent_beacon_block_root), + &payload, &sidecar, ); self.inner .validator @@ -227,13 +232,7 @@ where Ok(self .inner .beacon_consensus - .new_payload( - payload, - ExecutionPayloadSidecar::v3(CancunPayloadFields { - versioned_hashes, - parent_beacon_block_root, - }), - ) + .new_payload(payload, sidecar) .await .inspect(|_| self.inner.on_new_payload_response())?) } @@ -262,12 +261,19 @@ where versioned_hashes: Vec, parent_beacon_block_root: B256, execution_requests: Requests, + target_blobs_per_block: u64, ) -> EngineApiResult { let payload = ExecutionPayload::from(payload); + let sidecar = ExecutionPayloadSidecar::v4( + CancunPayloadFields { versioned_hashes, parent_beacon_block_root }, + PraguePayloadFields { + requests: RequestsOrHash::Requests(execution_requests), + target_blobs_per_block, + }, + ); let payload_or_attrs = PayloadOrAttributes::<'_, EngineT::PayloadAttributes>::from_execution_payload( - &payload, - Some(parent_beacon_block_root), + &payload, &sidecar, ); self.inner .validator @@ -276,17 +282,7 @@ where Ok(self .inner .beacon_consensus - .new_payload( - payload, - ExecutionPayloadSidecar::v4( - CancunPayloadFields { versioned_hashes, parent_beacon_block_root }, - PraguePayloadFields { - requests: RequestsOrHash::Requests(execution_requests), - // TODO: add as an argument and handle in `try_into_block` - target_blobs_per_block: 0, - }, - ), - ) + .new_payload(payload, sidecar) .await .inspect(|_| self.inner.on_new_payload_response())?) } @@ -298,6 +294,7 @@ where versioned_hashes: Vec, parent_beacon_block_root: B256, execution_requests: Requests, + target_blobs_per_block: u64, ) -> RpcResult { let start = Instant::now(); let gas_used = payload.payload_inner.payload_inner.gas_used; @@ -307,6 +304,7 @@ where versioned_hashes, parent_beacon_block_root, execution_requests, + target_blobs_per_block, ) .await; let elapsed = start.elapsed(); @@ -356,6 +354,19 @@ where .await } + /// Sends a message to the beacon consensus engine to update the fork choice _with_ withdrawals + /// and EIP-7742 fields, but only _after_ prague. + /// + /// See also + pub async fn fork_choice_updated_v4( + &self, + state: ForkchoiceState, + payload_attrs: Option, + ) -> EngineApiResult { + self.validate_and_execute_forkchoice(EngineApiMessageVersion::V4, state, payload_attrs) + .await + } + /// Returns the most recent version of the payload that is available in the corresponding /// payload build process at the time of receiving this call. /// @@ -676,9 +687,10 @@ where /// * If the version is [`EngineApiMessageVersion::V2`], then the payload attributes will be /// validated according to the Shanghai rules, as well as the validity changes from cancun: /// - /// - /// * If the version above [`EngineApiMessageVersion::V3`], then the payload attributes will be + /// * If the version is [`EngineApiMessageVersion::V3`], then the payload attributes will be /// validated according to the Cancun rules. + /// * If the version is [`EngineApiMessageVersion::V4`], then the payload attributes will be + /// validated according to the Prague rules. async fn validate_and_execute_forkchoice( &self, version: EngineApiMessageVersion, @@ -785,6 +797,7 @@ where versioned_hashes: Vec, parent_beacon_block_root: B256, execution_requests: Requests, + target_blobs_per_block: U64, ) -> RpcResult { trace!(target: "rpc::engine", "Serving engine_newPayloadV4"); Ok(self @@ -793,6 +806,7 @@ where versioned_hashes, parent_beacon_block_root, execution_requests, + target_blobs_per_block.to(), ) .await?) } @@ -829,7 +843,7 @@ where Ok(res?) } - /// Handler for `engine_forkchoiceUpdatedV2` + /// Handler for `engine_forkchoiceUpdatedV3` /// /// See also async fn fork_choice_updated_v3( @@ -845,6 +859,22 @@ where Ok(res?) } + /// Handler for `engine_forkchoiceUpdatedV4` + /// + /// See also + async fn fork_choice_updated_v4( + &self, + fork_choice_state: ForkchoiceState, + payload_attributes: Option, + ) -> RpcResult { + trace!(target: "rpc::engine", "Serving engine_forkchoiceUpdatedV4"); + let start = Instant::now(); + let res = Self::fork_choice_updated_v4(self, fork_choice_state, payload_attributes).await; + self.inner.metrics.latency.fork_choice_updated_v4.record(start.elapsed()); + self.inner.metrics.fcu_response.update_response_metrics(&res); + Ok(res?) + } + /// Handler for `engine_getPayloadV1` /// /// Returns the most recent version of the payload that is available in the corresponding diff --git a/crates/rpc/rpc-engine-api/src/metrics.rs b/crates/rpc/rpc-engine-api/src/metrics.rs index 9325ce267784..eb7cff5aafa4 100644 --- a/crates/rpc/rpc-engine-api/src/metrics.rs +++ b/crates/rpc/rpc-engine-api/src/metrics.rs @@ -34,6 +34,8 @@ pub(crate) struct EngineApiLatencyMetrics { pub(crate) fork_choice_updated_v2: Histogram, /// Latency for `engine_forkchoiceUpdatedV3` pub(crate) fork_choice_updated_v3: Histogram, + /// Latency for `engine_forkchoiceUpdatedV4` + pub(crate) fork_choice_updated_v4: Histogram, /// Time diff between `engine_newPayloadV*` and the next FCU pub(crate) new_payload_forkchoice_updated_time_diff: Histogram, /// Latency for `engine_getPayloadV1` diff --git a/crates/rpc/rpc-eth-types/src/fee_history.rs b/crates/rpc/rpc-eth-types/src/fee_history.rs index 2c365ae90bff..a48dc443265b 100644 --- a/crates/rpc/rpc-eth-types/src/fee_history.rs +++ b/crates/rpc/rpc-eth-types/src/fee_history.rs @@ -7,7 +7,7 @@ use std::{ }; use alloy_consensus::{BlockHeader, Transaction, TxReceipt}; -use alloy_eips::eip1559::calc_next_block_base_fee; +use alloy_eips::{eip1559::calc_next_block_base_fee, eip4844, eip7742}; use alloy_primitives::B256; use alloy_rpc_types_eth::TxGasAndReward; use futures::{ @@ -21,7 +21,7 @@ use reth_primitives::{NodePrimitives, SealedBlock}; use reth_primitives_traits::BlockBody; use reth_rpc_server_types::constants::gas_oracle::MAX_HEADER_HISTORY; use reth_storage_api::BlockReaderIdExt; -use revm_primitives::{calc_blob_gasprice, calc_excess_blob_gas}; +use revm_primitives::calc_blob_gasprice; use serde::{Deserialize, Serialize}; use tracing::trace; @@ -358,6 +358,8 @@ pub struct FeeHistoryEntry { pub rewards: Vec, /// The timestamp of the block. pub timestamp: u64, + /// EIP-7742 target blobs per block. + pub target_blobs_per_block: Option, } impl FeeHistoryEntry { @@ -378,6 +380,7 @@ impl FeeHistoryEntry { gas_limit: block.gas_limit(), rewards: Vec::new(), timestamp: block.timestamp(), + target_blobs_per_block: block.target_blobs_per_block(), } } @@ -404,6 +407,14 @@ impl FeeHistoryEntry { /// /// Returns a `None` if no excess blob gas is set, no EIP-4844 support pub fn next_block_excess_blob_gas(&self) -> Option { - Some(calc_excess_blob_gas(self.excess_blob_gas?, self.blob_gas_used?)) + if let Some(target_blobs_per_block) = self.target_blobs_per_block { + Some(eip7742::calc_excess_blob_gas( + self.excess_blob_gas?, + self.blob_gas_used?, + target_blobs_per_block, + )) + } else { + Some(eip4844::calc_excess_blob_gas(self.excess_blob_gas?, self.blob_gas_used?)) + } } } diff --git a/crates/rpc/rpc-types-compat/src/engine/payload.rs b/crates/rpc/rpc-types-compat/src/engine/payload.rs index 3be7835a35ae..f8c5ed4cfcb8 100644 --- a/crates/rpc/rpc-types-compat/src/engine/payload.rs +++ b/crates/rpc/rpc-types-compat/src/engine/payload.rs @@ -272,6 +272,7 @@ pub fn try_into_block( base_payload.header.parent_beacon_block_root = sidecar.parent_beacon_block_root(); base_payload.header.requests_hash = sidecar.requests_hash(); + base_payload.header.target_blobs_per_block = sidecar.target_blobs_per_block(); Ok(base_payload) } diff --git a/examples/custom-engine-types/src/main.rs b/examples/custom-engine-types/src/main.rs index f30956d8f5cf..ab9aa1fff867 100644 --- a/examples/custom-engine-types/src/main.rs +++ b/examples/custom-engine-types/src/main.rs @@ -104,6 +104,14 @@ impl PayloadAttributes for CustomPayloadAttributes { fn parent_beacon_block_root(&self) -> Option { self.inner.parent_beacon_block_root() } + + fn target_blobs_per_block(&self) -> Option { + self.inner.target_blobs_per_block() + } + + fn max_blobs_per_block(&self) -> Option { + self.inner.max_blobs_per_block() + } } /// New type around the payload builder attributes type @@ -149,6 +157,10 @@ impl PayloadBuilderAttributes for CustomPayloadBuilderAttributes { fn withdrawals(&self) -> &Withdrawals { &self.0.withdrawals } + + fn target_blobs_per_block(&self) -> Option { + self.0.target_blobs_per_block + } } /// Custom engine types - uses a custom payload attributes RPC type, but uses the default