Skip to content
Draft
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ jwt.hex
/tmp

*tests*.tar.gz
# Partial downloads of the fixture bundles; the Makefile fetches to `.part`
# first so a failed download cannot truncate the previous bundle.
*.tar.gz.part
tooling/ef_tests/state/test.tar.gz

.env
Expand Down
31 changes: 30 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
.PHONY: build lint test clean run-image build-image clean-vectors \
setup-hive test-pattern-default run-hive run-hive-debug clean-hive-logs \
load-test-fibonacci load-test-io run-hive-eels-blobs run-hive-eels-amsterdam \
run-hive-eels-bal-quick run-hive-build-block bench-rlp zkevm-bench-setup
run-hive-eels-bal-quick run-hive-build-block bench-rlp zkevm-bench-setup \
patch-hive-frames-fork run-hive-eels-frames run-hive-eels-frames-rlp \
run-hive-eels-frames-quick

help: ## 📚 Show help for each of the Makefile recipes
@grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
Expand Down Expand Up @@ -172,6 +174,33 @@ run-hive-eels-amsterdam: build-image setup-hive ## 🧪 Run hive EELS Amsterdam
run-hive-eels-bal-quick: build-image setup-hive ## 🧪 Run hive EELS quick tests for the Amsterdam EIPs
- cd hive && ./hive --client-file $(HIVE_CLIENT_FILE) --client ethrex --sim ethereum/eels/consume-engine --sim.limit ".*(2780|7708|7732|7778|7843|7928|7954|7975|7976|7981|7997|8024|8037|8038|8045|8061|8070|8159|8246|8282).*" --sim.parallelism $(SIM_PARALLELISM) --sim.loglevel $(SIM_LOG_LEVEL) --sim.buildarg fixtures=$(AMSTERDAM_FIXTURES_URL) --sim.buildarg branch=$(AMSTERDAM_FIXTURES_BRANCH)

FRAMES_FIXTURES_URL ?= $(shell cat tooling/ef_tests/.fixtures_url_frames)
FRAMES_FIXTURES_BRANCH ?= devnets/frames/0
# The frames release refills the WHOLE suite at Bogota, so the default sweep is
# every Bogota fixture; `run-hive-eels-frames-quick` narrows it to EIP-8141.
FRAMES_FORK_PATTERN ?= .*fork_Bogota.*
FRAMES_QUICK_PATTERN ?= .*8141.*

# Hive's ethrex client definition maps HIVE_<FORK>_TIMESTAMP env vars onto genesis
# fields, and it stops at Amsterdam -- so the Bogota timestamp EEST sets for these
# fixtures reaches the client as nothing at all, frame transactions stay pre-fork,
# and every EIP-8141 test fails while the rest of the Bogota suite passes. Patch
# the mapper in the clone until it carries the field upstream. `git checkout` first
# so repeated runs do not stack the same line, mirroring run-hive-build-block.
patch-hive-frames-fork:
cd hive && git checkout -- clients/ethrex/mapper.jq
cd hive && sed -i 's/\( *\)"bpo1Time": env.HIVE_BPO1_TIMESTAMP|to_int,/\1"bogotaTime": env.HIVE_BOGOTA_TIMESTAMP|to_int,\n\1"bpo1Time": env.HIVE_BPO1_TIMESTAMP|to_int,/' clients/ethrex/mapper.jq
@grep -q bogotaTime hive/clients/ethrex/mapper.jq || { echo "failed to patch hive mapper for the Bogota fork"; exit 1; }

run-hive-eels-frames: build-image setup-hive patch-hive-frames-fork ## 🧪 Run hive EELS frames-devnet Engine tests
- cd hive && ./hive --client-file $(HIVE_CLIENT_FILE) --client ethrex --sim ethereum/eels/consume-engine --sim.limit "$(FRAMES_FORK_PATTERN)" --sim.parallelism $(SIM_PARALLELISM) --sim.loglevel $(SIM_LOG_LEVEL) --sim.buildarg fixtures=$(FRAMES_FIXTURES_URL) --sim.buildarg branch=$(FRAMES_FIXTURES_BRANCH)

run-hive-eels-frames-rlp: build-image setup-hive patch-hive-frames-fork ## 🧪 Run hive EELS frames-devnet RLP tests
- cd hive && ./hive --client-file $(HIVE_CLIENT_FILE) --client ethrex --sim ethereum/eels/consume-rlp --sim.limit "$(FRAMES_FORK_PATTERN)" --sim.parallelism $(SIM_PARALLELISM) --sim.loglevel $(SIM_LOG_LEVEL) --sim.buildarg fixtures=$(FRAMES_FIXTURES_URL) --sim.buildarg branch=$(FRAMES_FIXTURES_BRANCH)

run-hive-eels-frames-quick: build-image setup-hive patch-hive-frames-fork ## 🧪 Run hive EELS frames-devnet tests for EIP-8141 only
- cd hive && ./hive --client-file $(HIVE_CLIENT_FILE) --client ethrex --sim ethereum/eels/consume-engine --sim.limit "$(FRAMES_QUICK_PATTERN)" --sim.parallelism $(SIM_PARALLELISM) --sim.loglevel $(SIM_LOG_LEVEL) --sim.buildarg fixtures=$(FRAMES_FIXTURES_URL) --sim.buildarg branch=$(FRAMES_FIXTURES_BRANCH)

# Block-building simulator (execution-specs PR #2679). Not yet upstream in Hive,
# so we install the simulator Dockerfile into the hive clone and patch the
# ethrex hive client to expose the `testing` namespace (testing_buildBlockV1
Expand Down
2 changes: 1 addition & 1 deletion crates/blockchain/blockchain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3598,7 +3598,7 @@ impl Blockchain {
}

// Check priority fee is less or equal than gas fee gap
if tx.max_priority_fee().unwrap_or(0) > tx.max_fee_per_gas().unwrap_or(0) {
if tx.max_priority_fee().unwrap_or_default() > tx.max_fee_per_gas().unwrap_or_default() {
return Err(MempoolError::TxTipAboveFeeCapError);
}

Expand Down
95 changes: 54 additions & 41 deletions crates/common/types/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -467,15 +467,15 @@ impl Transaction {
}

fn calc_effective_gas_price(&self, base_fee_per_gas: Option<u64>) -> Option<U256> {
let base_fee = base_fee_per_gas?;
let base_fee = U256::from(base_fee_per_gas?);
let max_fee = self.max_fee_per_gas()?;
if max_fee < base_fee {
// This is invalid, can't calculate
return None;
}

let priority_fee_per_gas = min(self.max_priority_fee()?, max_fee.saturating_sub(base_fee));
Some(U256::from(priority_fee_per_gas) + U256::from(base_fee))
Some(priority_fee_per_gas + base_fee)
}

pub fn effective_gas_price(&self, base_fee_per_gas: Option<u64>) -> Option<U256> {
Expand All @@ -495,11 +495,11 @@ impl Transaction {
let price = match self.tx_type() {
TxType::Legacy => self.gas_price(),
TxType::EIP2930 => self.gas_price(),
TxType::EIP1559 => U256::from(self.max_fee_per_gas()?),
TxType::EIP4844 => U256::from(self.max_fee_per_gas()?),
TxType::EIP7702 => U256::from(self.max_fee_per_gas()?),
TxType::Frame => U256::from(self.max_fee_per_gas()?),
TxType::FeeToken => U256::from(self.max_fee_per_gas()?),
TxType::EIP1559 => self.max_fee_per_gas()?,
TxType::EIP4844 => self.max_fee_per_gas()?,
TxType::EIP7702 => self.max_fee_per_gas()?,
TxType::Frame => self.max_fee_per_gas()?,
TxType::FeeToken => self.max_fee_per_gas()?,
TxType::Privileged => self.gas_price(),
};

Expand Down Expand Up @@ -1465,7 +1465,7 @@ impl Transaction {
Transaction::EIP4844Transaction(tx) => U256::from(tx.max_fee_per_gas),
Transaction::PrivilegedL2Transaction(tx) => U256::from(tx.max_fee_per_gas),
Transaction::FeeTokenTransaction(tx) => U256::from(tx.max_fee_per_gas),
Transaction::FrameTransaction(tx) => U256::from(tx.max_fee_per_gas),
Transaction::FrameTransaction(tx) => tx.max_fee_per_gas,
}
}

Expand Down Expand Up @@ -1495,15 +1495,18 @@ impl Transaction {
}
}

pub fn max_priority_fee(&self) -> Option<u64> {
/// Widened to `U256` for the same reason as [`Self::max_fee_per_gas`].
pub fn max_priority_fee(&self) -> Option<U256> {
match self {
Transaction::LegacyTransaction(_tx) => None,
Transaction::EIP2930Transaction(_tx) => None,
Transaction::EIP1559Transaction(tx) => Some(tx.max_priority_fee_per_gas),
Transaction::EIP4844Transaction(tx) => Some(tx.max_priority_fee_per_gas),
Transaction::EIP7702Transaction(tx) => Some(tx.max_priority_fee_per_gas),
Transaction::PrivilegedL2Transaction(tx) => Some(tx.max_priority_fee_per_gas),
Transaction::FeeTokenTransaction(tx) => Some(tx.max_priority_fee_per_gas),
Transaction::EIP1559Transaction(tx) => Some(U256::from(tx.max_priority_fee_per_gas)),
Transaction::EIP4844Transaction(tx) => Some(U256::from(tx.max_priority_fee_per_gas)),
Transaction::EIP7702Transaction(tx) => Some(U256::from(tx.max_priority_fee_per_gas)),
Transaction::PrivilegedL2Transaction(tx) => {
Some(U256::from(tx.max_priority_fee_per_gas))
}
Transaction::FeeTokenTransaction(tx) => Some(U256::from(tx.max_priority_fee_per_gas)),
Transaction::FrameTransaction(tx) => Some(tx.max_priority_fee_per_gas),
}
}
Expand Down Expand Up @@ -1635,15 +1638,18 @@ impl Transaction {
matches!(self, Transaction::PrivilegedL2Transaction(_))
}

pub fn max_fee_per_gas(&self) -> Option<u64> {
/// The transaction's `max_fee_per_gas`, widened to `U256` because EIP-8141
/// bounds a frame transaction's fee fields at 2**256 while every other type
/// keeps them within `u64`.
pub fn max_fee_per_gas(&self) -> Option<U256> {
match self {
Transaction::LegacyTransaction(_tx) => None,
Transaction::EIP2930Transaction(_tx) => None,
Transaction::EIP1559Transaction(tx) => Some(tx.max_fee_per_gas),
Transaction::EIP4844Transaction(tx) => Some(tx.max_fee_per_gas),
Transaction::EIP7702Transaction(tx) => Some(tx.max_fee_per_gas),
Transaction::PrivilegedL2Transaction(tx) => Some(tx.max_fee_per_gas),
Transaction::FeeTokenTransaction(tx) => Some(tx.max_fee_per_gas),
Transaction::EIP1559Transaction(tx) => Some(U256::from(tx.max_fee_per_gas)),
Transaction::EIP4844Transaction(tx) => Some(U256::from(tx.max_fee_per_gas)),
Transaction::EIP7702Transaction(tx) => Some(U256::from(tx.max_fee_per_gas)),
Transaction::PrivilegedL2Transaction(tx) => Some(U256::from(tx.max_fee_per_gas)),
Transaction::FeeTokenTransaction(tx) => Some(U256::from(tx.max_fee_per_gas)),
Transaction::FrameTransaction(tx) => Some(tx.max_fee_per_gas),
}
}
Expand Down Expand Up @@ -1671,15 +1677,11 @@ impl Transaction {
}

pub fn gas_tip_cap(&self) -> U256 {
self.max_priority_fee()
.map(U256::from)
.unwrap_or_else(|| self.gas_price())
self.max_priority_fee().unwrap_or_else(|| self.gas_price())
}

pub fn gas_fee_cap(&self) -> U256 {
self.max_fee_per_gas()
.map(U256::from)
.unwrap_or_else(|| self.gas_price())
self.max_fee_per_gas().unwrap_or_else(|| self.gas_price())
}

/// Returns the effective tip per gas for this transaction.
Expand Down Expand Up @@ -2028,8 +2030,13 @@ pub struct FrameTransaction {
/// EIP-8141 outer signature list. Validated
/// before any frame executes; referenced by VERIFY frames and SIGPARAM.
pub signatures: Vec<FrameSignature>,
pub max_priority_fee_per_gas: u64,
pub max_fee_per_gas: u64,
/// EIP-8141 bounds the fee fields at 2**256, not 2**64: a frame transaction
/// may legitimately name a fee no balance could pay, and a node still has to
/// decode it to reject it for the balance rather than for the field width.
#[rkyv(with=crate::rkyv_utils::U256Wrapper)]
pub max_priority_fee_per_gas: U256,
#[rkyv(with=crate::rkyv_utils::U256Wrapper)]
pub max_fee_per_gas: U256,
#[rkyv(with=crate::rkyv_utils::U256Wrapper)]
pub max_fee_per_blob_gas: U256,
#[rkyv(with=rkyv::with::Map<crate::rkyv_utils::H256Wrapper>)]
Expand Down Expand Up @@ -4269,9 +4276,15 @@ mod serde_impl {
from: value.sender,
gas: Some(value.max_gas()),
value: U256::zero(),
gas_price: value.max_fee_per_gas.into(),
max_priority_fee_per_gas: Some(value.max_priority_fee_per_gas),
max_fee_per_gas: Some(value.max_fee_per_gas),
gas_price: value.max_fee_per_gas,
// `GenericTransaction` keeps these as `u64`, and `U256::as_u64`
// panics rather than truncating, so saturate: this conversion feeds
// RPC and simulation shapes, and a fee this large is unaffordable at
// any balance, so the clamp cannot change an outcome.
max_priority_fee_per_gas: Some(
u64::try_from(value.max_priority_fee_per_gas).unwrap_or(u64::MAX),
),
max_fee_per_gas: Some(u64::try_from(value.max_fee_per_gas).unwrap_or(u64::MAX)),
max_fee_per_blob_gas: if value.blob_versioned_hashes.is_empty() {
None
} else {
Expand Down Expand Up @@ -5107,8 +5120,8 @@ mod tests {
msg: Bytes::new(),
signature: Bytes::from(vec![0u8; 65]),
}],
max_priority_fee_per_gas: 1_000_000_000,
max_fee_per_gas: 30_000_000_000,
max_priority_fee_per_gas: U256::from(1_000_000_000u64),
max_fee_per_gas: U256::from(30_000_000_000u64),
max_fee_per_blob_gas: U256::zero(),
blob_versioned_hashes: vec![],
inner_hash: OnceCell::new(),
Expand Down Expand Up @@ -5300,8 +5313,8 @@ mod tests {
assert_eq!(tx.data(), &Bytes::new());
assert!(tx.access_list().is_empty());
assert!(tx.authorization_list().is_none());
assert_eq!(tx.max_priority_fee(), Some(1_000_000_000));
assert_eq!(tx.max_fee_per_gas(), Some(30_000_000_000));
assert_eq!(tx.max_priority_fee(), Some(U256::from(1_000_000_000u64)));
assert_eq!(tx.max_fee_per_gas(), Some(U256::from(30_000_000_000u64)));
assert_eq!(tx.max_fee_per_blob_gas(), None); // no blobs
assert!(!tx.is_contract_creation());
// sender returns explicit sender, no ECDSA
Expand Down Expand Up @@ -5374,8 +5387,8 @@ mod tests {
sender: Address::from_low_u64_be(0xABCD),
frames,
signatures: vec![],
max_priority_fee_per_gas: 1_000_000_000,
max_fee_per_gas: 30_000_000_000,
max_priority_fee_per_gas: U256::from(1_000_000_000u64),
max_fee_per_gas: U256::from(30_000_000_000u64),
max_fee_per_blob_gas: U256::zero(),
blob_versioned_hashes: vec![],
inner_hash: OnceCell::new(),
Expand Down Expand Up @@ -5465,8 +5478,8 @@ mod tests {
data: Bytes::new(),
}],
signatures: vec![],
max_priority_fee_per_gas: 1_000_000_000,
max_fee_per_gas: 30_000_000_000,
max_priority_fee_per_gas: U256::from(1_000_000_000u64),
max_fee_per_gas: U256::from(30_000_000_000u64),
max_fee_per_blob_gas: U256::zero(),
blob_versioned_hashes: vec![],
inner_hash: OnceCell::new(),
Expand Down Expand Up @@ -5836,8 +5849,8 @@ mod tests {
msg: Bytes::new(),
signature: Bytes::from(vec![0x01u8; 65]),
}],
max_priority_fee_per_gas: 0x3b9aca00,
max_fee_per_gas: 0x6fc23ac00,
max_priority_fee_per_gas: U256::from(0x3b9aca00u64),
max_fee_per_gas: U256::from(0x6fc23ac00u64),
max_fee_per_blob_gas: U256::zero(),
blob_versioned_hashes: vec![],
inner_hash: OnceCell::new(),
Expand Down
14 changes: 11 additions & 3 deletions crates/networking/rpc/eth/fee_market.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,9 +258,17 @@ fn calculate_percentiles_for_block(block: Block, percentiles: &[f32]) -> Vec<u64
Transaction::FeeTokenTransaction(t) => t
.max_priority_fee_per_gas
.min(t.max_fee_per_gas.saturating_sub(base_fee_per_gas)),
Transaction::FrameTransaction(t) => t
.max_priority_fee_per_gas
.min(t.max_fee_per_gas.saturating_sub(base_fee_per_gas)),
// A frame transaction's fee fields are `U256` (EIP-8141 bounds them at
// 2**256). The reward reported here is an effective priority fee, which a
// payer must actually be able to cover, so clamping to `u64` matches every
// other arm and cannot understate a fee anyone paid.
Transaction::FrameTransaction(t) => u64::try_from(
t.max_priority_fee_per_gas.min(
t.max_fee_per_gas
.saturating_sub(U256::from(base_fee_per_gas)),
),
)
.unwrap_or(u64::MAX),
})
.collect();

Expand Down
13 changes: 7 additions & 6 deletions crates/vm/backends/levm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2988,8 +2988,8 @@ impl LEVM {
block_excess_blob_gas,
block_blob_gas_used: block_header.blob_gas_used,
tx_blob_hashes: tx.blob_versioned_hashes(),
tx_max_priority_fee_per_gas: tx.max_priority_fee().map(U256::from),
tx_max_fee_per_gas: tx.max_fee_per_gas().map(U256::from),
tx_max_priority_fee_per_gas: tx.max_priority_fee(),
tx_max_fee_per_gas: tx.max_fee_per_gas(),
tx_max_fee_per_blob_gas: tx.max_fee_per_blob_gas(),
tx_nonce: tx.nonce(),
block_gas_limit: block_header.gas_limit,
Expand Down Expand Up @@ -3285,8 +3285,9 @@ impl LEVM {
/// uses checked_mul/checked_add and halts on overflow. Saturating to
/// `U256::MAX` here only makes the reservation larger, never smaller.
fn frame_tx_reservation_ceiling(frame_tx: &ethrex_common::types::FrameTransaction) -> U256 {
let gas_cost =
U256::from(frame_tx.max_fee_per_gas).saturating_mul(U256::from(frame_tx.max_gas()));
let gas_cost = frame_tx
.max_fee_per_gas
.saturating_mul(U256::from(frame_tx.max_gas()));
let blob_cost = U256::from(frame_tx.blob_versioned_hashes.len())
.saturating_mul(U256::from(131072u64))
.saturating_mul(frame_tx.max_fee_per_blob_gas);
Expand Down Expand Up @@ -3809,13 +3810,13 @@ pub fn calculate_gas_price_for_tx(
fee_per_gas += operator_fee_config.operator_fee_per_gas;
}

if fee_per_gas > max_fee_per_gas {
if U256::from(fee_per_gas) > max_fee_per_gas {
return Err(VMError::TxValidation(
TxValidationError::InsufficientMaxFeePerGas,
));
}

Ok(min(max_priority_fee + fee_per_gas, max_fee_per_gas).into())
Ok(min(max_priority_fee + fee_per_gas, max_fee_per_gas))
}

/// When basefee tracking is disabled (ie. env.disable_base_fee = true; env.disable_block_gas_limit = true;)
Expand Down
10 changes: 6 additions & 4 deletions crates/vm/levm/src/opcode_handlers/frame_tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ pub fn u256_to_offset(value: U256) -> Option<usize> {
/// EIP-4844 blob burn (intrinsic gas is inside `total_gas_used`, so it stays
/// non-refundable).
pub(crate) fn compute_tx_max_cost(ctx: &crate::vm::FrameTxContext) -> Result<U256, VMError> {
let gas_cost = U256::from(ctx.tx.max_fee_per_gas)
let gas_cost = ctx
.tx
.max_fee_per_gas
.checked_mul(U256::from(ctx.max_gas))
.ok_or(ExceptionalHalt::InvalidOpcode)?;
let blob_cost = U256::from(ctx.tx.blob_versioned_hashes.len())
Expand Down Expand Up @@ -568,8 +570,8 @@ pub fn load_tx_param(ctx: &crate::vm::FrameTxContext, param_id: u64) -> Result<U
0x00 => Ok(U256::from(0x06u8)), // tx_type (EIP-8141 = type 6)
0x01 => Ok(U256::from(ctx.tx.nonce)),
0x02 => Ok(address_to_u256(ctx.tx.sender)),
0x03 => Ok(U256::from(ctx.tx.max_priority_fee_per_gas)),
0x04 => Ok(U256::from(ctx.tx.max_fee_per_gas)),
0x03 => Ok(ctx.tx.max_priority_fee_per_gas),
0x04 => Ok(ctx.tx.max_fee_per_gas),
0x05 => Ok(ctx.tx.max_fee_per_blob_gas),
0x06 => compute_tx_max_cost(ctx),
0x07 => Ok(U256::from(ctx.tx.blob_versioned_hashes.len())),
Expand Down Expand Up @@ -674,7 +676,7 @@ mod max_cost_tests {

fn ctx(max_fee: u64, blobs: usize, blob_base_fee: u64, max_gas: u64) -> FrameTxContext {
let tx = FrameTransaction {
max_fee_per_gas: max_fee,
max_fee_per_gas: U256::from(max_fee),
// Deliberately far above the base fee: `max_fee_per_blob_gas` bounds
// inclusion only and must not reach `max_cost`.
max_fee_per_blob_gas: U256::from(blob_base_fee).saturating_mul(U256::from(1_000u64)),
Expand Down
Loading
Loading