From 623b5b34df89eb3250b4da8bc1569f047d090273 Mon Sep 17 00:00:00 2001 From: ilitteri Date: Mon, 20 Apr 2026 18:12:28 -0300 Subject: [PATCH 1/2] fix(l1): include blob gas in mempool balance check for EIP-4844 transactions Transaction::cost_without_base_fee() was omitting the blob-gas component from the upfront cost it returns, so the mempool balance check at crates/blockchain/blockchain.rs:2488-2493 accepted EIP-4844 transactions whose sender could not afford the blob fees. The transactions then failed at block inclusion, wasting pool capacity and peer bandwidth. Every peer client (geth, reth, nethermind, erigon, besu) includes blob_gas * max_fee_per_blob_gas in the equivalent check; ethrex did not. Changes: - Extend cost_without_base_fee() so the EIP-4844 branch adds blob_versioned_hashes.len() * GAS_PER_BLOB * max_fee_per_blob_gas using saturating arithmetic, matching the function's existing style. - Add regression test test_cost_without_base_fee_eip4844_includes_blob_gas in crates/common/types/transaction.rs. - Document MIN_GAS_TIP in crates/common/types/constants.rs as an RPC gas-price estimator floor, not a mempool admission gate. - Document PendingTxFilter.min_tip in crates/blockchain/mempool.rs as a payload-builder query filter, not an admission gate. --- crates/blockchain/mempool.rs | 7 ++++ crates/common/types/constants.rs | 7 ++++ crates/common/types/transaction.rs | 51 ++++++++++++++++++++++++++++-- 3 files changed, 63 insertions(+), 2 deletions(-) diff --git a/crates/blockchain/mempool.rs b/crates/blockchain/mempool.rs index d8b67c8f9b3..bdc31f8aaac 100644 --- a/crates/blockchain/mempool.rs +++ b/crates/blockchain/mempool.rs @@ -498,8 +498,15 @@ impl Mempool { } } +/// Filter applied by the payload builder when querying pending transactions +/// from the pool. NOT a mempool admission gate — all fields here are +/// query-time filters used to pick block-includable transactions. Admission +/// rules are enforced in `Blockchain::validate_transaction`. #[derive(Debug, Default)] pub struct PendingTxFilter { + /// Minimum effective priority fee for a transaction to be surfaced to + /// the payload builder. This is a block-building filter, not an + /// admission check — see `crates/common/types/constants.rs::MIN_GAS_TIP`. pub min_tip: Option, pub base_fee: Option, pub blob_fee: Option, diff --git a/crates/common/types/constants.rs b/crates/common/types/constants.rs index 9a525f0a7ee..24892257078 100644 --- a/crates/common/types/constants.rs +++ b/crates/common/types/constants.rs @@ -10,6 +10,13 @@ pub const MIN_BASE_FEE_PER_BLOB_GAS: u64 = 1; // Defined in [EIP-4844](https://e pub const BLOB_BASE_FEE_UPDATE_FRACTION: u64 = 3338477; // Defined in [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844) pub const VERSIONED_HASH_VERSION_KZG: u8 = 0x01; // Defined in [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844) /// Minimum tip, obtained from geth's default miner config (https://github.com/ethereum/go-ethereum/blob/f750117ad19d623622cc4a46ea361a716ba7407e/miner/miner.go#L56) +/// +/// Scope: this constant is consumed only by the RPC gas-price estimators +/// (`eth_gasPrice`, `eth_maxPriorityFeePerGas`). It is NOT an admission +/// gate in the mempool — zero-tip transactions are currently admitted. +/// A dedicated admission-time min-tip floor is tracked in the umbrella +/// `mempool-hardening-roadmap` change (Tier 1, PR T1b). +/// /// TODO: This should be configurable along with the tip filter on https://github.com/lambdaclass/ethrex/issues/680 pub const MIN_GAS_TIP: u64 = 1000000; diff --git a/crates/common/types/transaction.rs b/crates/common/types/transaction.rs index 81cb66417b1..59c026c39b9 100644 --- a/crates/common/types/transaction.rs +++ b/crates/common/types/transaction.rs @@ -452,10 +452,22 @@ impl Transaction { TxType::Privileged => self.gas_price(), }; - Some(U256::saturating_add( + let base = U256::saturating_add( U256::saturating_mul(price, self.gas_limit().into()), self.value(), - )) + ); + + // EIP-4844 blob txs pay an additional `blob_gas * max_fee_per_blob_gas` + // upfront. Every peer client (geth, reth, nethermind, erigon, besu) + // includes this in the balance-sufficiency check. + if let Transaction::EIP4844Transaction(tx) = self { + let blob_gas = U256::from(crate::constants::GAS_PER_BLOB) + .saturating_mul(U256::from(tx.blob_versioned_hashes.len() as u64)); + let blob_cost = blob_gas.saturating_mul(tx.max_fee_per_blob_gas); + return Some(base.saturating_add(blob_cost)); + } + + Some(base) } pub fn fee_token(&self) -> Option
{ @@ -3717,4 +3729,39 @@ mod tests { let tx = Transaction::EIP1559Transaction(EIP1559Transaction::default()); assert_eq!(tx.encode_to_vec().len(), EIP1559_DEFAULT_SERIALIZED_LENGTH); } + + #[test] + fn test_cost_without_base_fee_eip4844_includes_blob_gas() { + // Regression test for mempool balance check: for EIP-4844 txs, + // cost_without_base_fee() MUST include blob_gas_used * max_fee_per_blob_gas. + // Every peer client (geth, reth, nethermind, erigon, besu) does this. + use crate::constants::GAS_PER_BLOB; + + let max_fee_per_gas: u64 = 100; + let gas: u64 = 21_000; + let value = U256::from(7u64); + let max_fee_per_blob_gas = U256::from(50u64); + let blob_count: usize = 1; + + let tx = Transaction::EIP4844Transaction(EIP4844Transaction { + max_fee_per_gas, + gas, + value, + max_fee_per_blob_gas, + blob_versioned_hashes: vec![H256::zero(); blob_count], + ..Default::default() + }); + + let got = tx.cost_without_base_fee().expect("cost is computable"); + + let gas_cost = U256::from(max_fee_per_gas) * U256::from(gas); + let blob_gas = U256::from(GAS_PER_BLOB) * U256::from(blob_count as u64); + let blob_cost = blob_gas * max_fee_per_blob_gas; + let expected = gas_cost + blob_cost + value; + + assert_eq!( + got, expected, + "blob-gas term missing from cost_without_base_fee() for EIP-4844" + ); + } } From 4919f35e01bbe96f3047f3f4ea12351008886855 Mon Sep 17 00:00:00 2001 From: ilitteri Date: Mon, 20 Apr 2026 18:20:58 -0300 Subject: [PATCH 2/2] remove roadmap reference from MIN_GAS_TIP doc comment --- crates/common/types/constants.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/common/types/constants.rs b/crates/common/types/constants.rs index 24892257078..76d912531a8 100644 --- a/crates/common/types/constants.rs +++ b/crates/common/types/constants.rs @@ -12,10 +12,8 @@ pub const VERSIONED_HASH_VERSION_KZG: u8 = 0x01; // Defined in [EIP-4844](https: /// Minimum tip, obtained from geth's default miner config (https://github.com/ethereum/go-ethereum/blob/f750117ad19d623622cc4a46ea361a716ba7407e/miner/miner.go#L56) /// /// Scope: this constant is consumed only by the RPC gas-price estimators -/// (`eth_gasPrice`, `eth_maxPriorityFeePerGas`). It is NOT an admission -/// gate in the mempool — zero-tip transactions are currently admitted. -/// A dedicated admission-time min-tip floor is tracked in the umbrella -/// `mempool-hardening-roadmap` change (Tier 1, PR T1b). +/// (`eth_gasPrice`, `eth_maxPriorityFeePerGas`). It is NOT a mempool +/// admission gate — zero-tip transactions are currently admitted. /// /// TODO: This should be configurable along with the tip filter on https://github.com/lambdaclass/ethrex/issues/680 pub const MIN_GAS_TIP: u64 = 1000000;