Skip to content
Open
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
6 changes: 3 additions & 3 deletions .github/config/hive/amsterdam.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Amsterdam (BAL) hive test configuration
# Pinned to tests-glamsterdam-devnet@v7.2.0 (execution-specs `devnets/glamsterdam/7`)
fixtures: https://github.com/ethereum/execution-specs/releases/download/tests-glamsterdam-devnet%40v7.2.0/fixtures_glamsterdam-devnet.tar.gz
eels_commit: 50a25a54997581820640a1820e9ebb4bae93486b
# Pinned to tests-glamsterdam-devnet@v8.0.0 (execution-specs `devnets/glamsterdam/8`)
fixtures: https://github.com/ethereum/execution-specs/releases/download/tests-glamsterdam-devnet%40v8.0.0/fixtures_glamsterdam-devnet.tar.gz
eels_commit: d681ca4fd019ee80099dd1899bdbee419cab8e0b
14 changes: 10 additions & 4 deletions .github/workflows/daily_hive_report.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ jobs:
name: "Consume Engine tests (Amsterdam)",
file_name: consume-engine-amsterdam,
simulation: ethereum/eels/consume-engine,
limit: ".*fork_Amsterdam.*",
limit: ".*fork_.*Amsterdam.*",
amsterdam: true,
}
- {
name: "Consume RLP tests (Rest)",
Expand All @@ -88,7 +89,8 @@ jobs:
name: "Consume RLP tests (Amsterdam)",
file_name: consume-rlp-amsterdam,
simulation: ethereum/eels/consume-rlp,
limit: ".*fork_Amsterdam.*",
limit: ".*fork_.*Amsterdam.*",
amsterdam: true,
parallelism: 2,
}
- {
Expand Down Expand Up @@ -130,15 +132,19 @@ jobs:
env:
SIMULATION: ${{ matrix.test.simulation }}
SIM_LIMIT: ${{ matrix.test.limit || '' }}
AMSTERDAM: ${{ matrix.test.amsterdam || 'false' }}
AMSTERDAM_FIXTURES: ${{ steps.amsterdam-config.outputs.fixtures }}
AMSTERDAM_EELS_COMMIT: ${{ steps.amsterdam-config.outputs.eels_commit }}
MAINNET_FIXTURES: ${{ steps.mainnet-config.outputs.fixtures }}
MAINNET_EELS_COMMIT: ${{ steps.mainnet-config.outputs.eels_commit }}
run: |
FLAGS="--sim.parallelism ${{ matrix.test.parallelism || 4 }} --sim.loglevel 1"
if [[ "$SIMULATION" == "ethereum/eels/consume-engine" || "$SIMULATION" == "ethereum/eels/consume-rlp" ]]; then
# Use fork-specific fixtures to ensure comprehensive test coverage
if [[ "$SIM_LIMIT" == *"fork_Amsterdam"* ]]; then
# Use fork-specific fixtures to ensure comprehensive test coverage. Keyed off an
# explicit flag rather than a substring of the limit, which no longer contains a
# literal fork name; matching on the limit would fall through to the mainnet
# bundle, which carries no Amsterdam fixtures at all.
if [[ "$AMSTERDAM" == "true" ]]; then
# Amsterdam config loaded from .github/config/hive/amsterdam.yaml
FLAGS+=" --sim.buildarg fixtures=$AMSTERDAM_FIXTURES"
FLAGS+=" --sim.buildarg branch=$AMSTERDAM_EELS_COMMIT"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pr-main_l1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,7 @@ jobs:
# Amsterdam config is loaded from .github/config/hive/amsterdam.yaml
- name: "Consume Engine Amsterdam"
simulation: ethereum/eels/consume-engine
limit: ".*(8024|7708|7778|7843|7928|7954|8037).*"
limit: ".*(2780|7708|7732|7778|7843|7928|7954|7975|7976|7981|7997|8024|8037|8038|8045|8061|8070|8159|8246|8282).*"
amsterdam: true
artifact_prefix: consume_engine_amsterdam
# Investigate this test
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ jwt.hex
/tmp

*tests*.tar.gz
tooling/ef_tests/state/test.tar.gz

.env

Expand Down
17 changes: 10 additions & 7 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -162,20 +162,23 @@ run-hive-eels-blobs: ## Run hive EELS Blobs tests
$(MAKE) run-hive-eels EELS_SIM=ethereum/eels/execute-blobs

AMSTERDAM_FIXTURES_URL ?= $(shell cat tooling/ef_tests/.fixtures_url_amsterdam)
AMSTERDAM_FIXTURES_BRANCH ?= devnets/glamsterdam/7
AMSTERDAM_FIXTURES_BRANCH ?= devnets/glamsterdam/8
# `fork_.*Amsterdam` rather than `fork_Amsterdam` so the BPO2->Amsterdam activation
# fixtures (`fork_BPO2ToAmsterdamAtTime15k`) are swept alongside the Amsterdam ones.
AMSTERDAM_FORK_PATTERN ?= .*fork_.*Amsterdam.*
run-hive-eels-amsterdam: build-image setup-hive ## 🧪 Run hive EELS Amsterdam Engine tests
- cd hive && ./hive --client-file $(HIVE_CLIENT_FILE) --client ethrex --sim ethereum/eels/consume-engine --sim.limit ".*fork_Amsterdam.*" --sim.parallelism $(SIM_PARALLELISM) --sim.loglevel $(SIM_LOG_LEVEL) --sim.buildarg fixtures=$(AMSTERDAM_FIXTURES_URL) --sim.buildarg branch=$(AMSTERDAM_FIXTURES_BRANCH)
- cd hive && ./hive --client-file $(HIVE_CLIENT_FILE) --client ethrex --sim ethereum/eels/consume-engine --sim.limit "$(AMSTERDAM_FORK_PATTERN)" --sim.parallelism $(SIM_PARALLELISM) --sim.loglevel $(SIM_LOG_LEVEL) --sim.buildarg fixtures=$(AMSTERDAM_FIXTURES_URL) --sim.buildarg branch=$(AMSTERDAM_FIXTURES_BRANCH)

run-hive-eels-bal-quick: build-image setup-hive ## 🧪 Run hive EELS quick tests for the glam-7 EIPs
- cd hive && ./hive --client-file $(HIVE_CLIENT_FILE) --client ethrex --sim ethereum/eels/consume-engine --sim.limit ".*(8024|7708|7778|7843|7928|7954|8037|8038|2780|7997|7610|8246|8282).*" --sim.parallelism $(SIM_PARALLELISM) --sim.loglevel $(SIM_LOG_LEVEL) --sim.buildarg fixtures=$(AMSTERDAM_FIXTURES_URL) --sim.buildarg branch=$(AMSTERDAM_FIXTURES_BRANCH)
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)

# 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
# lives on the public HTTP port). Defaults to the Amsterdam/BAL fixtures.
# Defaults to the BAL EIP set (mirrors run-hive-eels-bal-quick) rather than all
# .*fork_Amsterdam.* fixtures, which pull in ~21k cross-fork cases. Override with
# BUILD_BLOCK_TEST_PATTERN=.*fork_Amsterdam.* for the full sweep.
# Defaults to the EIPs whose block-building behaviour this simulator exercises, rather
# than every Amsterdam fixture, which pulls in ~21k cross-fork cases. Override with
# BUILD_BLOCK_TEST_PATTERN=$(AMSTERDAM_FORK_PATTERN) for the full sweep.
BUILD_BLOCK_TEST_PATTERN ?= .*(7708|7778|7843|7928|7954|7976|7981|8024|8037).*
run-hive-build-block: build-image setup-hive ## 🧱 Run hive build-block simulator (testing_buildBlockV1)
mkdir -p hive/simulators/ethereum/eels/build-block
Expand Down
3 changes: 2 additions & 1 deletion crates/common/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,5 +80,6 @@ pub const POST_OSAKA_GAS_LIMIT_CAP: u64 = 16777216;
pub const TX_MAX_GAS_LIMIT_AMSTERDAM: u64 = 1 << 24; // 16,777,216

// === EIP-7928 BAL size cap constants ===
/// GAS_BLOCK_ACCESS_LIST_ITEM = GAS_WARM_ACCESS (100) + TX_ACCESS_LIST_STORAGE_KEY_COST (1900)
/// GAS_BLOCK_ACCESS_LIST_ITEM: a flat per-item charge, independent of the
/// EIP-8038 access-list repricing.
pub const BAL_ITEM_COST: u64 = 2000;
33 changes: 32 additions & 1 deletion crates/common/types/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -920,7 +920,7 @@ pub fn calc_excess_blob_gas(parent: &BlockHeader, schedule: ForkBlobSchedule, fo
mod test {
use super::*;
use crate::constants::EMPTY_KECCAK_HASH;
use crate::types::{BLOB_BASE_FEE_UPDATE_FRACTION, ELASTICITY_MULTIPLIER};
use crate::types::{BLOB_BASE_FEE_UPDATE_FRACTION, ELASTICITY_MULTIPLIER, INITIAL_BASE_FEE};
use ethereum_types::H160;
use hex_literal::hex;
use std::str::FromStr;
Expand Down Expand Up @@ -1144,6 +1144,37 @@ mod test {
assert_eq!(res, 3538944)
}

#[test]
fn test_calc_excess_blob_gas_at_amsterdam_inherits_bpo1_target() {
// Amsterdam carries no blob params of its own, so a chain that scheduled BPO1
// and never scheduled BPO2 keeps BPO1's target of 10 and max of 15 across the
// Amsterdam boundary. A parent that spent 12 blobs is above that target, so the
// EIP-7918 reserve-price branch raises the excess by `used * (max - target) / max`.
// Resolving the schedule from an unscheduled BPO2 (target 14) instead would put
// the parent below target and wrongly return 0.
let parent = BlockHeader {
excess_blob_gas: Some(0),
blob_gas_used: Some(12 * GAS_PER_BLOB as u64),
base_fee_per_gas: Some(INITIAL_BASE_FEE),
..Default::default()
};
let config = ChainConfig {
osaka_time: Some(0),
bpo1_time: Some(0),
amsterdam_time: Some(100),
..Default::default()
};

let schedule = config
.get_fork_blob_schedule(100)
.expect("Amsterdam must resolve a blob schedule");
assert_eq!(schedule.target, 10);
assert_eq!(schedule.max, 15);

let res = calc_excess_blob_gas(&parent, schedule, config.fork(100));
assert_eq!(res, 4 * GAS_PER_BLOB as u64);
}

#[test]
fn test_fake_exponential_overflow() {
// With u64 this overflows
Expand Down
119 changes: 91 additions & 28 deletions crates/common/types/genesis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -495,22 +495,16 @@ impl ChainConfig {
}

pub fn get_fork_blob_schedule(&self, block_timestamp: u64) -> Option<ForkBlobSchedule> {
// Hegotá inherits Amsterdam's blob schedule unless an explicit Hegotá
// entry is added to BlobSchedule in a future change.
if self.is_hegota_activated(block_timestamp)
// EIP-7892: from Prague onward the blob schedule only changes at BPO forks.
// Named forks (Osaka, Amsterdam, Hegotá) carry no blob params of their own and
// inherit the highest activated BPO entry, so resolution falls through the BPO
// chain. A genesis that pins an entry for the named fork anyway takes precedence.
if (self.is_hegota_activated(block_timestamp)
|| self.is_amsterdam_activated(block_timestamp))
&& let Some(schedule) = self.blob_schedule.amsterdam
{
return Some(schedule);
}
// Amsterdam (and BPO3-5) don't independently define blob params in Hive;
// they inherit from the highest activated BPO fork. If the fork-specific
// entry is None, fall through to find the right BPO schedule.
if self.is_amsterdam_activated(block_timestamp)
&& let Some(schedule) = self.blob_schedule.amsterdam
{
return Some(schedule);
}
// Fall through to BPO chain
if self.is_bpo5_activated(block_timestamp)
&& let Some(schedule) = self.blob_schedule.bpo5
{
Expand All @@ -526,8 +520,7 @@ impl ChainConfig {
{
return Some(schedule);
}
// Amsterdam implies BPO2 blob params when no explicit schedule is set.
if self.is_bpo2_activated(block_timestamp) || self.is_amsterdam_activated(block_timestamp) {
if self.is_bpo2_activated(block_timestamp) {
Some(self.blob_schedule.bpo2)
} else if self.is_bpo1_activated(block_timestamp) {
Some(self.blob_schedule.bpo1)
Expand Down Expand Up @@ -635,21 +628,16 @@ impl ChainConfig {
}

pub fn get_blob_schedule_for_fork(&self, fork: Fork) -> Option<ForkBlobSchedule> {
match fork {
Fork::Cancun => Some(self.blob_schedule.cancun),
Fork::Prague => Some(self.blob_schedule.prague),
Fork::Osaka => Some(self.blob_schedule.osaka),
Fork::BPO1 => Some(self.blob_schedule.bpo1),
Fork::BPO2 => Some(self.blob_schedule.bpo2),
Fork::BPO3 => self.blob_schedule.bpo3,
Fork::BPO4 => self.blob_schedule.bpo4,
Fork::BPO5 => self.blob_schedule.bpo5,
Fork::Amsterdam => self.blob_schedule.amsterdam,
// Hegotá inherits Amsterdam's blob schedule unless an explicit
// Hegotá entry is added to BlobSchedule in a future change.
Fork::Hegota => self.blob_schedule.amsterdam,
_ => None,
// Blob params are timestamp-scheduled from Cancun onward; earlier forks are
// activated by block number and have no blob schedule. Resolving through the
// fork's activation timestamp keeps this in step with the inheritance rules in
// `get_fork_blob_schedule`, so a named fork that declares no entry of its own
// reports the entry actually in force at its activation.
if fork < Fork::Cancun {
return None;
}
self.get_activation_timestamp_for_fork(fork)
.and_then(|timestamp| self.get_fork_blob_schedule(timestamp))
}

pub fn gather_forks(&self, genesis_header: BlockHeader) -> (Vec<u64>, Vec<u64>) {
Expand Down Expand Up @@ -1390,4 +1378,79 @@ mod tests {
};
assert_eq!(config.next_fork(0), None);
}

#[test]
fn amsterdam_inherits_highest_activated_bpo_schedule() {
// EIP-7892: Amsterdam declares no blob params of its own, so a genesis that
// schedules BPO1 and then Amsterdam keeps BPO1's target and max across the
// Amsterdam boundary. An unscheduled BPO2 must never contribute its schedule.
let config: ChainConfig = serde_json::from_str(
r#"{
"chainId": 1,
"depositContractAddress": "0x00000000219ab540356cbb839cbe05303d7705fa",
"osakaTime": 0,
"bpo1Time": 0,
"amsterdamTime": 100,
"blobSchedule": {
"cancun": {"target": 3, "max": 6, "baseFeeUpdateFraction": 3338477},
"prague": {"target": 6, "max": 9, "baseFeeUpdateFraction": 5007716},
"bpo1": {"target": 10, "max": 15, "baseFeeUpdateFraction": 8346193}
}
}"#,
)
.expect("genesis should parse");

let bpo1 = ForkBlobSchedule {
target: 10,
max: 15,
base_fee_update_fraction: 8346193,
};
assert_eq!(config.get_fork_blob_schedule(99), Some(bpo1));
assert_eq!(config.get_fork_blob_schedule(100), Some(bpo1));
}

#[test]
fn blob_schedule_for_fork_reports_the_inherited_entry() {
// `eth_config` (EIP-7910) must report the blob params actually in force at a
// fork's activation, which for Amsterdam is the highest activated BPO entry.
let config = ChainConfig {
osaka_time: Some(0),
bpo1_time: Some(0),
amsterdam_time: Some(100),
..Default::default()
};

let schedule = config
.get_blob_schedule_for_fork(Fork::Amsterdam)
.expect("Amsterdam must report a blob schedule");
assert_eq!((schedule.target, schedule.max), (10, 15));

// Forks that were never scheduled have nothing to report, and pre-Cancun forks
// are activated by block number rather than timestamp.
assert_eq!(config.get_blob_schedule_for_fork(Fork::BPO2), None);
assert_eq!(config.get_blob_schedule_for_fork(Fork::London), None);
}

#[test]
fn amsterdam_prefers_explicitly_pinned_schedule() {
let mut config = ChainConfig {
osaka_time: Some(0),
bpo1_time: Some(0),
bpo2_time: Some(50),
amsterdam_time: Some(100),
..Default::default()
};
assert_eq!(
config.get_fork_blob_schedule(100),
Some(default_bpo2_schedule())
);

let pinned = ForkBlobSchedule {
target: 16,
max: 24,
base_fee_update_fraction: 13353910,
};
config.blob_schedule.amsterdam = Some(pinned);
assert_eq!(config.get_fork_blob_schedule(100), Some(pinned));
}
}
36 changes: 29 additions & 7 deletions crates/networking/rpc/engine/payload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -935,6 +935,19 @@ fn parse_execution_payload(params: &Option<Vec<Value>>) -> Result<ExecutionPaylo
serde_json::from_value(params[0].clone()).map_err(|_| RpcErr::WrongParam("payload".to_string()))
}

/// The Amsterdam payload fields (EIP-7928 block access list, EIP-7843 slot number) must be
/// absent from every pre-Amsterdam payload version.
fn reject_amsterdam_payload_fields(payload: &ExecutionPayload) -> Result<(), RpcErr> {
if payload.block_access_list.is_some() {
return Err(RpcErr::WrongParam("block_access_list".to_string()));
}
if payload.slot_number.is_some() {
return Err(RpcErr::WrongParam("slot_number".to_string()));
}

Ok(())
}

fn validate_execution_payload_v1(payload: &ExecutionPayload) -> Result<(), RpcErr> {
// Validate that only the required arguments are present
if payload.withdrawals.is_some() {
Expand All @@ -947,7 +960,7 @@ fn validate_execution_payload_v1(payload: &ExecutionPayload) -> Result<(), RpcEr
return Err(RpcErr::WrongParam("excess_blob_gas".to_string()));
}

Ok(())
reject_amsterdam_payload_fields(payload)
}

fn validate_execution_payload_v2(payload: &ExecutionPayload) -> Result<(), RpcErr> {
Expand All @@ -962,11 +975,11 @@ fn validate_execution_payload_v2(payload: &ExecutionPayload) -> Result<(), RpcEr
return Err(RpcErr::WrongParam("excess_blob_gas".to_string()));
}

Ok(())
reject_amsterdam_payload_fields(payload)
}

fn validate_execution_payload_v3(payload: &ExecutionPayload) -> Result<(), RpcErr> {
// Validate that only the required arguments are present
/// Fields shared by every payload version from Cancun onwards.
fn validate_execution_payload_cancun_fields(payload: &ExecutionPayload) -> Result<(), RpcErr> {
if payload.withdrawals.is_none() {
return Err(RpcErr::WrongParam("withdrawals".to_string()));
}
Expand All @@ -980,16 +993,25 @@ fn validate_execution_payload_v3(payload: &ExecutionPayload) -> Result<(), RpcEr
Ok(())
}

/// Shared by `engine_newPayloadV3` and `engine_newPayloadV4`, both of which predate Amsterdam.
fn validate_execution_payload_v3(payload: &ExecutionPayload) -> Result<(), RpcErr> {
// Validate that only the required arguments are present
validate_execution_payload_cancun_fields(payload)?;

reject_amsterdam_payload_fields(payload)
}

#[inline]
fn validate_execution_payload_v4(payload: &ExecutionPayload) -> Result<(), RpcErr> {
// This method follows the same specification as `engine_newPayloadV4` additionally
// rejects payload without block access list
// The Amsterdam payload shape: the Cancun fields plus a block access list. Reached only
// through `validate_execution_payload_v5`, so the Amsterdam fields are required here
// rather than rejected.

if payload.block_access_list.is_none() {
return Err(RpcErr::WrongParam("block_access_list".to_string()));
}

validate_execution_payload_v3(payload)?;
validate_execution_payload_cancun_fields(payload)?;

Ok(())
}
Expand Down
Loading