From 6540036c9bd8c65b23ac6323cbafb40f4684760d Mon Sep 17 00:00:00 2001 From: lakshya-sky Date: Mon, 22 Dec 2025 21:04:03 -0500 Subject: [PATCH 01/10] feat(l1): add amsterdam fork to chain config --- crates/common/types/genesis.rs | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/crates/common/types/genesis.rs b/crates/common/types/genesis.rs index eb0c33abc1d..6ed873ed05c 100644 --- a/crates/common/types/genesis.rs +++ b/crates/common/types/genesis.rs @@ -138,6 +138,8 @@ pub struct BlobSchedule { pub bpo4: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub bpo5: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub amsterdam: Option, } impl Default for BlobSchedule { @@ -151,6 +153,7 @@ impl Default for BlobSchedule { bpo3: None, bpo4: None, bpo5: None, + amsterdam: None, } } } @@ -251,6 +254,8 @@ pub struct ChainConfig { pub bpo4_time: Option, pub bpo5_time: Option, + pub amsterdam_time: Option, + /// Amount of total difficulty reached by the network that triggers the consensus upgrade. pub terminal_total_difficulty: Option, /// Network has already passed the terminal total difficult @@ -308,6 +313,7 @@ pub enum Fork { BPO3 = 22, BPO4 = 23, BPO5 = 24, + Amsterdam = 25, } impl From for &str { @@ -338,11 +344,17 @@ impl From for &str { Fork::BPO3 => "BPO3", Fork::BPO4 => "BPO4", Fork::BPO5 => "BPO5", + Fork::Amsterdam => "Amsterdam", } } } impl ChainConfig { + pub fn is_amsterdam_activated(&self, block_timestamp: u64) -> bool { + self.amsterdam_time + .is_some_and(|time| time <= block_timestamp) + } + pub fn is_bpo1_activated(&self, block_timestamp: u64) -> bool { self.bpo1_time.is_some_and(|time| time <= block_timestamp) } @@ -402,6 +414,7 @@ impl ChainConfig { ("Prague", self.prague_time), ("Verkle", self.verkle_time), ("Osaka", self.osaka_time), + ("Amsterdam", self.amsterdam_time), ]; let active_forks: Vec<_> = post_merge_forks @@ -421,7 +434,9 @@ impl ChainConfig { } pub fn get_fork(&self, block_timestamp: u64) -> Fork { - if self.is_bpo5_activated(block_timestamp) { + if self.is_amsterdam_activated(block_timestamp) { + Fork::Amsterdam + } else if self.is_bpo5_activated(block_timestamp) { Fork::BPO5 } else if self.is_bpo4_activated(block_timestamp) { Fork::BPO4 @@ -445,7 +460,9 @@ impl ChainConfig { } pub fn get_fork_blob_schedule(&self, block_timestamp: u64) -> Option { - if self.is_bpo5_activated(block_timestamp) { + if self.is_amsterdam_activated(block_timestamp) { + Some(self.blob_schedule.amsterdam.unwrap_or_default()) + } else if self.is_bpo5_activated(block_timestamp) { Some(self.blob_schedule.bpo5.unwrap_or_default()) } else if self.is_bpo4_activated(block_timestamp) { Some(self.blob_schedule.bpo4.unwrap_or_default()) @@ -471,8 +488,10 @@ impl ChainConfig { } pub fn next_fork(&self, block_timestamp: u64) -> Option { - let next = if self.is_bpo5_activated(block_timestamp) { + let next = if self.is_amsterdam_activated(block_timestamp) { None + } else if self.is_bpo5_activated(block_timestamp) && self.amsterdam_time.is_some() { + Some(Fork::Amsterdam) } else if self.is_bpo4_activated(block_timestamp) && self.bpo5_time.is_some() { Some(Fork::BPO5) } else if self.is_bpo3_activated(block_timestamp) && self.bpo4_time.is_some() { @@ -499,7 +518,9 @@ impl ChainConfig { } pub fn get_last_scheduled_fork(&self) -> Fork { - if self.bpo5_time.is_some() { + if self.amsterdam_time.is_some() { + Fork::Amsterdam + } else if self.bpo5_time.is_some() { Fork::BPO5 } else if self.bpo4_time.is_some() { Fork::BPO4 @@ -530,6 +551,7 @@ impl ChainConfig { Fork::BPO3 => self.bpo3_time, Fork::BPO4 => self.bpo4_time, Fork::BPO5 => self.bpo5_time, + Fork::Amsterdam => self.amsterdam_time, Fork::Homestead => self.homestead_block, Fork::DaoFork => self.dao_fork_block, Fork::Byzantium => self.byzantium_block, @@ -557,6 +579,7 @@ impl ChainConfig { Fork::BPO3 => self.blob_schedule.bpo3, Fork::BPO4 => self.blob_schedule.bpo4, Fork::BPO5 => self.blob_schedule.bpo5, + Fork::Amsterdam => self.blob_schedule.amsterdam, _ => None, } } @@ -601,6 +624,7 @@ impl ChainConfig { self.bpo3_time, self.bpo4_time, self.bpo5_time, + self.amsterdam_time, self.verkle_time, ] .into_iter() From 733f4bd37a6fe97852e01e5c365ac3ccfb4a4b3d Mon Sep 17 00:00:00 2001 From: lakshya-sky Date: Tue, 23 Dec 2025 12:07:15 -0500 Subject: [PATCH 02/10] update revm runner with todo and fix the check error --- tooling/ef_tests/state/runner/revm_runner.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tooling/ef_tests/state/runner/revm_runner.rs b/tooling/ef_tests/state/runner/revm_runner.rs index c04d74c19aa..2de3a783071 100644 --- a/tooling/ef_tests/state/runner/revm_runner.rs +++ b/tooling/ef_tests/state/runner/revm_runner.rs @@ -700,6 +700,7 @@ pub fn fork_to_spec_id(fork: Fork) -> SpecId { Fork::BPO3 => SpecId::OSAKA, Fork::BPO4 => SpecId::OSAKA, Fork::BPO5 => SpecId::OSAKA, + Fork::Amsterdam => todo!(), } } From c83826262d1d2094d61568fcf99f186444de932f Mon Sep 17 00:00:00 2001 From: lakshya-sky Date: Tue, 23 Dec 2025 12:29:43 -0500 Subject: [PATCH 03/10] fix tests --- crates/networking/rpc/rpc.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/networking/rpc/rpc.rs b/crates/networking/rpc/rpc.rs index 5529093de69..3e94563aed6 100644 --- a/crates/networking/rpc/rpc.rs +++ b/crates/networking/rpc/rpc.rs @@ -750,6 +750,7 @@ mod tests { "bpo3Time": null, "bpo4Time": null, "bpo5Time": null, + "amsterdamTime": null, "terminalTotalDifficulty": 0, "terminalTotalDifficultyPassed": true, "blobSchedule": blob_schedule, From b860d7b104d1af2a047bb3c8eb2676900e0b2fff Mon Sep 17 00:00:00 2001 From: Darshan Kathiriya <8559992+lakshya-sky@users.noreply.github.com> Date: Tue, 23 Dec 2025 08:32:33 -0500 Subject: [PATCH 04/10] fix(l1): use current header instead of parent to get excess blob gas (#5706) **Motivation** * `./hive --sim ethereum/eels/execute-blobs --client ethrex` fails with `execution_testing.rpc.rpc_types.JSONRPCError: JSONRPCError(code=-32603, message=Internal Error: Parent block not found)`. * GetBlobBaseFee is using parents header which causes to get blob base fee for genesis blocks. According to the specs `excess_blob_gas` is **Running total of blob gas consumed in excess of the target, prior to this block.**. That means we need to use latest block header only. **Description** - use latest block header and remove accessing parent header. Co-authored-by: lakshya-sky Co-authored-by: Edgar --- crates/networking/rpc/eth/block.rs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/crates/networking/rpc/eth/block.rs b/crates/networking/rpc/eth/block.rs index bf1c56d51d0..8fa1b750f36 100644 --- a/crates/networking/rpc/eth/block.rs +++ b/crates/networking/rpc/eth/block.rs @@ -1,4 +1,3 @@ -use ethrex_blockchain::find_parent_header; use ethrex_rlp::encode::RLPEncode; use serde_json::Value; use tracing::debug; @@ -311,14 +310,9 @@ impl RpcHandler for GetBlobBaseFee { Some(header) => header, _ => return Err(RpcErr::Internal("Could not get block header".to_owned())), }; - let parent_header = match find_parent_header(&header, &context.storage) { - Ok(option_header) => option_header, - Err(error) => return Err(RpcErr::Internal(error.to_string())), - }; - let config = context.storage.get_chain_config(); let blob_base_fee = calculate_base_fee_per_blob_gas( - parent_header.excess_blob_gas.unwrap_or_default(), + header.excess_blob_gas.unwrap_or_default(), config .get_fork_blob_schedule(header.timestamp) .map(|schedule| schedule.base_fee_update_fraction) @@ -341,8 +335,6 @@ pub async fn get_all_block_rpc_receipts( if header.parent_hash.is_zero() { return Ok(receipts); } - // TODO: Here we are calculating the base_fee_per_blob_gas with the current header. - // Check if we should be passing the parent header instead let config = storage.get_chain_config(); let blob_base_fee = calculate_base_fee_per_blob_gas( header.excess_blob_gas.unwrap_or_default(), From 282a56da2af569a091c21f9aec5123723c291f96 Mon Sep 17 00:00:00 2001 From: lakshya-sky Date: Tue, 23 Dec 2025 14:15:14 -0500 Subject: [PATCH 05/10] TODO: need to add bal in block header and body --- crates/networking/rpc/engine/payload.rs | 117 ++++++++++++++++++++++++ crates/networking/rpc/rpc.rs | 3 +- crates/networking/rpc/types/payload.rs | 5 + 3 files changed, 124 insertions(+), 1 deletion(-) diff --git a/crates/networking/rpc/engine/payload.rs b/crates/networking/rpc/engine/payload.rs index 6d64d06336f..11e66fd5474 100644 --- a/crates/networking/rpc/engine/payload.rs +++ b/crates/networking/rpc/engine/payload.rs @@ -144,6 +144,7 @@ impl RpcHandler for NewPayloadV3Request { serde_json::to_value(payload_status).map_err(|error| RpcErr::Internal(error.to_string())) } } + pub struct NewPayloadV4Request { pub payload: ExecutionPayload, pub expected_blob_versioned_hashes: Vec, @@ -225,6 +226,86 @@ impl RpcHandler for NewPayloadV4Request { } } +pub struct NewPayloadV5Request { + pub payload: ExecutionPayload, + pub expected_blob_versioned_hashes: Vec, + pub parent_beacon_block_root: H256, + pub execution_requests: Vec, +} + +impl From for RpcRequest { + fn from(val: NewPayloadV5Request) -> Self { + RpcRequest { + method: "engine_newPayloadV5".to_string(), + params: Some(vec![ + serde_json::json!(val.payload), + serde_json::json!(val.expected_blob_versioned_hashes), + serde_json::json!(val.parent_beacon_block_root), + serde_json::json!(val.execution_requests), + ]), + ..Default::default() + } + } +} + +impl RpcHandler for NewPayloadV5Request { + fn parse(params: &Option>) -> Result { + let params = params + .as_ref() + .ok_or(RpcErr::BadParams("No params provided".to_owned()))?; + if params.len() != 4 { + return Err(RpcErr::BadParams("Expected 4 params".to_owned())); + } + Ok(Self { + payload: serde_json::from_value(params[0].clone()) + .map_err(|_| RpcErr::WrongParam("payload".to_string()))?, + expected_blob_versioned_hashes: serde_json::from_value(params[1].clone()) + .map_err(|_| RpcErr::WrongParam("expected_blob_versioned_hashes".to_string()))?, + parent_beacon_block_root: serde_json::from_value(params[2].clone()) + .map_err(|_| RpcErr::WrongParam("parent_beacon_block_root".to_string()))?, + execution_requests: serde_json::from_value(params[3].clone()) + .map_err(|_| RpcErr::WrongParam("execution_requests".to_string()))?, + }) + } + + async fn handle(&self, context: RpcApiContext) -> Result { + // validate the received requests + validate_execution_requests(&self.execution_requests)?; + + let requests_hash = compute_requests_hash(&self.execution_requests); + let block = match get_block_from_payload( + &self.payload, + Some(self.parent_beacon_block_root), + Some(requests_hash), + ) { + Ok(block) => block, + Err(err) => { + return Ok(serde_json::to_value(PayloadStatus::invalid_with_err( + &err.to_string(), + ))?); + } + }; + + let chain_config = context.storage.get_chain_config(); + + if !chain_config.is_amsterdam_activated(block.header.timestamp) { + return Err(RpcErr::UnsuportedFork(format!( + "{:?}", + chain_config.get_fork(block.header.timestamp) + ))); + } + validate_execution_payload_v4(&self.payload)?; + let payload_status = handle_new_payload_v4( + &self.payload, + context, + block, + self.expected_blob_versioned_hashes.clone(), + ) + .await?; + serde_json::to_value(payload_status).map_err(|error| RpcErr::Internal(error.to_string())) + } +} + // GetPayload V1-V2-V3 implementations pub struct GetPayloadV1Request { pub payload_id: u64, @@ -541,6 +622,19 @@ fn validate_execution_payload_v3(payload: &ExecutionPayload) -> Result<(), RpcEr Ok(()) } +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 + + validate_execution_payload_v3(payload)?; + + if payload.block_access_list.is_none() { + return Err(RpcErr::WrongParam("block_access_list".to_string())); + } + + Ok(()) +} + fn validate_payload_v1_v2(block: &Block, context: &RpcApiContext) -> Result<(), RpcErr> { let chain_config = &context.storage.get_chain_config(); if chain_config.is_cancun_activated(block.header.timestamp) { @@ -644,6 +738,29 @@ async fn handle_new_payload_v3( handle_new_payload_v1_v2(payload, block, context).await } +async fn handle_new_payload_v4( + payload: &ExecutionPayload, + context: RpcApiContext, + block: Block, + expected_blob_versioned_hashes: Vec, +) -> Result { + // V4 specific: validate block access list + let blob_versioned_hashes: Vec = block + .body + .transactions + .iter() + .flat_map(|tx| tx.blob_versioned_hashes()) + .collect(); + + if expected_blob_versioned_hashes != blob_versioned_hashes { + return Ok(PayloadStatus::invalid_with_err( + "Invalid blob_versioned_hashes", + )); + } + + handle_new_payload_v3(payload, context, block, expected_blob_versioned_hashes).await +} + // Elements of the list MUST be ordered by request_type in ascending order. // Elements with empty request_data MUST be excluded from the list. fn validate_execution_requests(execution_requests: &[EncodedRequests]) -> Result<(), RpcErr> { diff --git a/crates/networking/rpc/rpc.rs b/crates/networking/rpc/rpc.rs index 3e94563aed6..5d46b8dbaa1 100644 --- a/crates/networking/rpc/rpc.rs +++ b/crates/networking/rpc/rpc.rs @@ -1,7 +1,7 @@ use crate::authentication::authenticate; use crate::debug::execution_witness::ExecutionWitnessRequest; use crate::engine::blobs::{BlobsV2Request, BlobsV3Request}; -use crate::engine::payload::GetPayloadV5Request; +use crate::engine::payload::{GetPayloadV5Request, NewPayloadV5Request}; use crate::engine::{ ExchangeCapabilitiesRequest, blobs::BlobsV1Request, @@ -585,6 +585,7 @@ pub async fn map_engine_requests( "engine_forkchoiceUpdatedV1" => ForkChoiceUpdatedV1::call(req, context).await, "engine_forkchoiceUpdatedV2" => ForkChoiceUpdatedV2::call(req, context).await, "engine_forkchoiceUpdatedV3" => ForkChoiceUpdatedV3::call(req, context).await, + "engine_newPayloadV5" => NewPayloadV5Request::call(req, context).await, "engine_newPayloadV4" => NewPayloadV4Request::call(req, context).await, "engine_newPayloadV3" => NewPayloadV3Request::call(req, context).await, "engine_newPayloadV2" => NewPayloadV2Request::call(req, context).await, diff --git a/crates/networking/rpc/types/payload.rs b/crates/networking/rpc/types/payload.rs index 526180c03a7..dcdb781f193 100644 --- a/crates/networking/rpc/types/payload.rs +++ b/crates/networking/rpc/types/payload.rs @@ -50,6 +50,9 @@ pub struct ExecutionPayload { default )] pub excess_blob_gas: Option, + #[serde(skip_serializing_if = "Option::is_none", default)] + // ExecutionPayloadV4 fields. Optional since we support previous versions. + pub block_access_list: Option>, } #[derive(Clone, Debug)] @@ -96,6 +99,7 @@ impl ExecutionPayload { self, parent_beacon_block_root: Option, requests_hash: Option, + block_access_list_hash: Option, ) -> Result { let body = BlockBody { transactions: self @@ -162,6 +166,7 @@ impl ExecutionPayload { withdrawals: block.body.withdrawals, blob_gas_used: block.header.blob_gas_used, excess_blob_gas: block.header.excess_blob_gas, + block_access_list: todo!(), } } } From d7bbe938469edbdd866205a412d5cc08b79e2d82 Mon Sep 17 00:00:00 2001 From: lakshya-sky Date: Thu, 25 Dec 2025 21:42:17 -0500 Subject: [PATCH 06/10] added bal types --- crates/common/constants.rs | 8 ++++++ crates/common/types/block.rs | 8 ++++++ crates/common/types/block_access_list.rs | 26 +++++++++++++++++++ crates/common/types/mod.rs | 1 + .../vm/levm/bench/revm_comparison/Cargo.lock | 19 +++++++------- 5 files changed, 53 insertions(+), 9 deletions(-) create mode 100644 crates/common/types/block_access_list.rs diff --git a/crates/common/constants.rs b/crates/common/constants.rs index a163aa07e3b..17ba6b58a5b 100644 --- a/crates/common/constants.rs +++ b/crates/common/constants.rs @@ -43,6 +43,14 @@ pub static DEPOSIT_TOPIC: LazyLock = LazyLock::new(|| { .expect("Failed to decode hex from string") }); +// = Keccak256(RLP([])) as of EIP-7928 +pub static EMPTY_BLOCK_ACCESS_LIST_HASH: LazyLock = LazyLock::new(|| { + H256::from_slice( + &hex::decode("1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347") + .expect("Failed to decode hex from string"), + ) +}); + // === EIP-4844 constants === /// Gas consumption of a single data blob (== blob byte size). diff --git a/crates/common/types/block.rs b/crates/common/types/block.rs index e1b36065a2b..961fefe3205 100644 --- a/crates/common/types/block.rs +++ b/crates/common/types/block.rs @@ -142,6 +142,9 @@ pub struct BlockHeader { #[serde(skip_serializing_if = "Option::is_none", default = "Option::default")] #[rkyv(with=crate::rkyv_utils::OptionH256Wrapper)] pub requests_hash: Option, + #[serde(skip_serializing_if = "Option::is_none", default = "Option::default")] + #[rkyv(with=crate::rkyv_utils::OptionH256Wrapper)] + pub block_access_list_hash: Option, } // Needs a explicit impl due to the hash OnceLock. @@ -170,6 +173,7 @@ impl PartialEq for BlockHeader { excess_blob_gas, parent_beacon_block_root, requests_hash, + block_access_list_hash, } = self; parent_hash == &other.parent_hash @@ -191,6 +195,7 @@ impl PartialEq for BlockHeader { && difficulty == &other.difficulty && ommers_hash == &other.ommers_hash && requests_hash == &other.requests_hash + && block_access_list_hash == &other.block_access_list_hash && logs_bloom == &other.logs_bloom && extra_data == &other.extra_data } @@ -220,6 +225,7 @@ impl RLPEncode for BlockHeader { .encode_optional_field(&self.excess_blob_gas) .encode_optional_field(&self.parent_beacon_block_root) .encode_optional_field(&self.requests_hash) + .encode_optional_field(&self.block_access_list_hash) .finish(); } } @@ -249,6 +255,7 @@ impl RLPDecode for BlockHeader { let (excess_blob_gas, decoder) = decoder.decode_optional_field(); let (parent_beacon_block_root, decoder) = decoder.decode_optional_field(); let (requests_hash, decoder) = decoder.decode_optional_field(); + let (block_access_list_hash, decoder) = decoder.decode_optional_field(); Ok(( BlockHeader { @@ -274,6 +281,7 @@ impl RLPDecode for BlockHeader { excess_blob_gas, parent_beacon_block_root, requests_hash, + block_access_list_hash, }, decoder.finish()?, )) diff --git a/crates/common/types/block_access_list.rs b/crates/common/types/block_access_list.rs new file mode 100644 index 00000000000..3fe9a005f4f --- /dev/null +++ b/crates/common/types/block_access_list.rs @@ -0,0 +1,26 @@ +#![allow(dead_code)] + +use bytes::Bytes; +use ethereum_types::{Address, U256}; +use serde::{Deserialize, Serialize}; + +#[derive(Default, Serialize, Deserialize, Clone)] +pub struct BlockAccessList { + inner: Vec, +} + +#[derive(Default, Serialize, Deserialize, Clone)] +pub struct AccountChanges { + address: Address, + slot_changes: Vec, + storage_reads: Vec, + balance_changes: Vec<(usize, U256)>, + nonce_changes: Vec<(usize, u64)>, + code_changes: Vec<(usize, Bytes)>, +} + +#[derive(Default, Serialize, Deserialize, Clone)] +pub struct SlotChange { + slot: U256, + storage_changes: Vec<(usize, U256)>, +} diff --git a/crates/common/types/mod.rs b/crates/common/types/mod.rs index 96487001e11..9250b7e5b0d 100644 --- a/crates/common/types/mod.rs +++ b/crates/common/types/mod.rs @@ -2,6 +2,7 @@ mod account; mod account_update; pub mod blobs_bundle; mod block; +pub mod block_access_list; pub mod block_execution_witness; mod constants; mod fork_id; diff --git a/crates/vm/levm/bench/revm_comparison/Cargo.lock b/crates/vm/levm/bench/revm_comparison/Cargo.lock index b191c8b1587..601daac2867 100644 --- a/crates/vm/levm/bench/revm_comparison/Cargo.lock +++ b/crates/vm/levm/bench/revm_comparison/Cargo.lock @@ -1084,7 +1084,7 @@ dependencies = [ [[package]] name = "ethrex-blockchain" -version = "7.0.0" +version = "8.0.0" dependencies = [ "bytes", "ethrex-common", @@ -1104,7 +1104,7 @@ dependencies = [ [[package]] name = "ethrex-common" -version = "7.0.0" +version = "8.0.0" dependencies = [ "bytes", "crc32fast", @@ -1113,6 +1113,7 @@ dependencies = [ "ethrex-rlp", "ethrex-trie", "hex", + "hex-literal", "k256", "kzg-rs", "lazy_static", @@ -1134,7 +1135,7 @@ dependencies = [ [[package]] name = "ethrex-crypto" -version = "7.0.0" +version = "8.0.0" dependencies = [ "kzg-rs", "thiserror", @@ -1143,7 +1144,7 @@ dependencies = [ [[package]] name = "ethrex-levm" -version = "7.0.0" +version = "8.0.0" dependencies = [ "ark-bn254", "ark-ec", @@ -1175,7 +1176,7 @@ dependencies = [ [[package]] name = "ethrex-metrics" -version = "7.0.0" +version = "8.0.0" dependencies = [ "ethrex-common", "serde", @@ -1186,7 +1187,7 @@ dependencies = [ [[package]] name = "ethrex-rlp" -version = "7.0.0" +version = "8.0.0" dependencies = [ "bytes", "ethereum-types", @@ -1199,7 +1200,7 @@ dependencies = [ [[package]] name = "ethrex-storage" -version = "7.0.0" +version = "8.0.0" dependencies = [ "anyhow", "async-trait", @@ -1230,7 +1231,7 @@ dependencies = [ [[package]] name = "ethrex-trie" -version = "7.0.0" +version = "8.0.0" dependencies = [ "anyhow", "bytes", @@ -1253,7 +1254,7 @@ dependencies = [ [[package]] name = "ethrex-vm" -version = "7.0.0" +version = "8.0.0" dependencies = [ "bincode", "bytes", From fe588a097c35258cb18f5303395955aaa9e450f4 Mon Sep 17 00:00:00 2001 From: lakshya-sky Date: Thu, 25 Dec 2025 22:07:47 -0500 Subject: [PATCH 07/10] derive debug --- crates/common/types/block_access_list.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/common/types/block_access_list.rs b/crates/common/types/block_access_list.rs index 3fe9a005f4f..161c07e6ae8 100644 --- a/crates/common/types/block_access_list.rs +++ b/crates/common/types/block_access_list.rs @@ -4,12 +4,12 @@ use bytes::Bytes; use ethereum_types::{Address, U256}; use serde::{Deserialize, Serialize}; -#[derive(Default, Serialize, Deserialize, Clone)] +#[derive(Default, Debug, Serialize, Deserialize, Clone)] pub struct BlockAccessList { inner: Vec, } -#[derive(Default, Serialize, Deserialize, Clone)] +#[derive(Default, Debug, Serialize, Deserialize, Clone)] pub struct AccountChanges { address: Address, slot_changes: Vec, @@ -19,7 +19,7 @@ pub struct AccountChanges { code_changes: Vec<(usize, Bytes)>, } -#[derive(Default, Serialize, Deserialize, Clone)] +#[derive(Default, Debug, Serialize, Deserialize, Clone)] pub struct SlotChange { slot: U256, storage_changes: Vec<(usize, U256)>, From fcf0063b3dd0a39552a59658da92572d126521d3 Mon Sep 17 00:00:00 2001 From: lakshya-sky Date: Fri, 26 Dec 2025 12:54:20 -0500 Subject: [PATCH 08/10] added rlp conversions --- crates/common/types/block_access_list.rs | 344 ++++++++++++++++++++++- 1 file changed, 334 insertions(+), 10 deletions(-) diff --git a/crates/common/types/block_access_list.rs b/crates/common/types/block_access_list.rs index 161c07e6ae8..d0e8fef726f 100644 --- a/crates/common/types/block_access_list.rs +++ b/crates/common/types/block_access_list.rs @@ -1,26 +1,350 @@ #![allow(dead_code)] use bytes::Bytes; -use ethereum_types::{Address, U256}; +use ethereum_types::{Address, H256, U256}; +use ethrex_rlp::encode::RLPEncode; +use ethrex_rlp::structs; use serde::{Deserialize, Serialize}; +use crate::constants::EMPTY_BLOCK_ACCESS_LIST_HASH; +use crate::utils::keccak; + #[derive(Default, Debug, Serialize, Deserialize, Clone)] -pub struct BlockAccessList { - inner: Vec, +pub struct StorageChange { + block_access_index: usize, + post_value: U256, +} + +impl RLPEncode for StorageChange { + fn encode(&self, buf: &mut dyn bytes::BufMut) { + structs::Encoder::new(buf) + .encode_field(&self.block_access_index) + .encode_field(&self.post_value) + .finish(); + } +} + +#[derive(Default, Debug, Serialize, Deserialize, Clone)] +pub struct SlotChange { + slot: U256, + slot_changes: Vec, +} + +impl RLPEncode for SlotChange { + fn encode(&self, buf: &mut dyn bytes::BufMut) { + structs::Encoder::new(buf) + .encode_field(&self.slot) + .encode_field(&self.slot_changes) + .finish(); + } +} + +#[derive(Default, Debug, Serialize, Deserialize, Clone)] +pub struct BalanceChange { + block_access_index: usize, + post_balance: U256, +} + +impl RLPEncode for BalanceChange { + fn encode(&self, buf: &mut dyn bytes::BufMut) { + structs::Encoder::new(buf) + .encode_field(&self.block_access_index) + .encode_field(&self.post_balance) + .finish(); + } +} + +#[derive(Default, Debug, Serialize, Deserialize, Clone)] +pub struct NonceChange { + block_access_index: usize, + post_nonce: u64, +} + +impl RLPEncode for NonceChange { + fn encode(&self, buf: &mut dyn bytes::BufMut) { + structs::Encoder::new(buf) + .encode_field(&self.block_access_index) + .encode_field(&self.post_nonce) + .finish(); + } +} + +#[derive(Default, Debug, Serialize, Deserialize, Clone)] +pub struct CodeChange { + block_access_index: usize, + new_code: Bytes, +} + +impl RLPEncode for CodeChange { + fn encode(&self, buf: &mut dyn bytes::BufMut) { + structs::Encoder::new(buf) + .encode_field(&self.block_access_index) + .encode_field(&self.new_code) + .finish(); + } } #[derive(Default, Debug, Serialize, Deserialize, Clone)] pub struct AccountChanges { address: Address, - slot_changes: Vec, + storage_changes: Vec, storage_reads: Vec, - balance_changes: Vec<(usize, U256)>, - nonce_changes: Vec<(usize, u64)>, - code_changes: Vec<(usize, Bytes)>, + balance_changes: Vec, + nonce_changes: Vec, + code_changes: Vec, +} + +impl RLPEncode for AccountChanges { + fn encode(&self, buf: &mut dyn bytes::BufMut) { + let mut sorted = self.clone(); + sorted.storage_changes.sort_by(|a, b| a.slot.cmp(&b.slot)); + sorted.storage_reads.sort(); + sorted + .balance_changes + .sort_by(|a, b| a.block_access_index.cmp(&b.block_access_index)); + sorted + .nonce_changes + .sort_by(|a, b| a.block_access_index.cmp(&b.block_access_index)); + sorted + .code_changes + .sort_by(|a, b| a.block_access_index.cmp(&b.block_access_index)); + + structs::Encoder::new(buf) + .encode_field(&sorted.address) + .encode_field(&sorted.storage_changes) + .encode_field(&sorted.storage_reads) + .encode_field(&sorted.balance_changes) + .encode_field(&sorted.nonce_changes) + .encode_field(&sorted.code_changes) + .finish(); + } } #[derive(Default, Debug, Serialize, Deserialize, Clone)] -pub struct SlotChange { - slot: U256, - storage_changes: Vec<(usize, U256)>, +pub struct BlockAccessList { + inner: Vec, +} + +impl BlockAccessList { + pub fn compute_hash(&self) -> H256 { + if self.inner.is_empty() { + return *EMPTY_BLOCK_ACCESS_LIST_HASH; + } + + let buf = self.encode_to_vec(); + keccak(buf) + } +} + +impl RLPEncode for BlockAccessList { + fn encode(&self, buf: &mut dyn bytes::BufMut) { + let mut sorted = self.inner.clone(); + sorted.sort_by(|a, b| a.address.cmp(&b.address)); + sorted.encode(buf); + } +} + +#[cfg(test)] +mod tests { + use ethereum_types::{H160, U256}; + use ethrex_rlp::encode::RLPEncode; + + use crate::types::block_access_list::{ + AccountChanges, BalanceChange, NonceChange, SlotChange, StorageChange, + }; + + use super::BlockAccessList; + + const ALICE_ADDR: H160 = H160([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10]); //0xA + const BOB_ADDR: H160 = H160([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11]); //0xB + const CHARLIE_ADDR: H160 = H160([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12]); //0xC + const CONTRACT_ADDR: H160 = H160([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12]); //0xC + + #[test] + fn test_empty_list_validation() { + let actual_bal = BlockAccessList { + inner: vec![AccountChanges { + address: ALICE_ADDR, + ..Default::default() + }], + }; + + let mut buf = Vec::new(); + actual_bal.encode(&mut buf); + + let encoded_rlp = hex::encode(buf); + assert_eq!( + &encoded_rlp, + "dbda94000000000000000000000000000000000000000ac0c0c0c0c0" + ); + } + + #[test] + fn test_partial_validation() { + let actual_bal = BlockAccessList { + inner: vec![AccountChanges { + address: ALICE_ADDR, + storage_reads: vec![U256::from(1), U256::from(2)], + balance_changes: vec![BalanceChange { + block_access_index: 1, + post_balance: U256::from(100), + }], + nonce_changes: vec![NonceChange { + block_access_index: 1, + post_nonce: 1, + }], + ..Default::default() + }], + }; + + let mut buf = Vec::new(); + actual_bal.encode(&mut buf); + + let encoded_rlp = hex::encode(buf); + assert_eq!( + &encoded_rlp, + "e3e294000000000000000000000000000000000000000ac0c20102c3c20164c3c20101c0" + ); + } + + #[test] + fn test_storage_changes_validation() { + let actual_bal = BlockAccessList { + inner: vec![AccountChanges { + address: CONTRACT_ADDR, + storage_changes: vec![SlotChange { + slot: U256::from(0x1), + slot_changes: vec![StorageChange { + block_access_index: 1, + post_value: U256::from(0x42), + }], + }], + ..Default::default() + }], + }; + + let mut buf = Vec::new(); + actual_bal.encode(&mut buf); + + let encoded_rlp = hex::encode(buf); + assert_eq!( + &encoded_rlp, + "e1e094000000000000000000000000000000000000000cc6c501c3c20142c0c0c0c0" + ); + } + + #[test] + fn test_expected_addresses_auto_sorted() { + let actual_bal = BlockAccessList { + inner: vec![ + AccountChanges { + address: CHARLIE_ADDR, + ..Default::default() + }, + AccountChanges { + address: ALICE_ADDR, + ..Default::default() + }, + AccountChanges { + address: BOB_ADDR, + ..Default::default() + }, + ], + }; + + let mut buf = Vec::new(); + actual_bal.encode(&mut buf); + + let encoded_rlp = hex::encode(buf); + assert_eq!( + &encoded_rlp, + "f851da94000000000000000000000000000000000000000ac0c0c0c0c0da94000000000000000000000000000000000000000bc0c0c0c0c0da94000000000000000000000000000000000000000cc0c0c0c0c0" + ); + } + + #[test] + fn test_expected_storage_slots_ordering_correct_order_should_pass() { + let actual_bal = BlockAccessList { + inner: vec![AccountChanges { + address: ALICE_ADDR, + storage_changes: vec![ + SlotChange { + slot: U256::from(0x02), + slot_changes: vec![], + }, + SlotChange { + slot: U256::from(0x01), + slot_changes: vec![], + }, + SlotChange { + slot: U256::from(0x03), + slot_changes: vec![], + }, + ], + ..Default::default() + }], + }; + + let mut buf = Vec::new(); + actual_bal.encode(&mut buf); + + let encoded_rlp = hex::encode(buf); + assert_eq!( + &encoded_rlp, + "e4e394000000000000000000000000000000000000000ac9c201c0c202c0c203c0c0c0c0c0" + ); + } + + #[test] + fn test_expected_storage_reads_ordering_correct_order_should_pass() { + let actual_bal = BlockAccessList { + inner: vec![AccountChanges { + address: ALICE_ADDR, + storage_reads: vec![U256::from(0x02), U256::from(0x01), U256::from(0x03)], + ..Default::default() + }], + }; + + let mut buf = Vec::new(); + actual_bal.encode(&mut buf); + + let encoded_rlp = hex::encode(buf); + assert_eq!( + &encoded_rlp, + "dedd94000000000000000000000000000000000000000ac0c3010203c0c0c0" + ); + } + + #[test] + fn test_expected_tx_indices_ordering_correct_order_should_pass() { + let actual_bal = BlockAccessList { + inner: vec![AccountChanges { + address: ALICE_ADDR, + nonce_changes: vec![ + NonceChange { + block_access_index: 2, + post_nonce: 2, + }, + NonceChange { + block_access_index: 3, + post_nonce: 3, + }, + NonceChange { + block_access_index: 1, + post_nonce: 1, + }, + ], + ..Default::default() + }], + }; + + let mut buf = Vec::new(); + actual_bal.encode(&mut buf); + + let encoded_rlp = hex::encode(buf); + assert_eq!( + &encoded_rlp, + "e4e394000000000000000000000000000000000000000ac0c0c0c9c20101c20202c20303c0" + ); + } } From 4ce3e76efbf69f11303adac207f6e023389a241b Mon Sep 17 00:00:00 2001 From: lakshya-sky Date: Fri, 26 Dec 2025 16:48:57 -0500 Subject: [PATCH 09/10] rlp decode --- crates/common/types/block_access_list.rs | 171 +++++++++++++++++++++-- 1 file changed, 157 insertions(+), 14 deletions(-) diff --git a/crates/common/types/block_access_list.rs b/crates/common/types/block_access_list.rs index d0e8fef726f..48718d0ebde 100644 --- a/crates/common/types/block_access_list.rs +++ b/crates/common/types/block_access_list.rs @@ -2,14 +2,13 @@ use bytes::Bytes; use ethereum_types::{Address, H256, U256}; -use ethrex_rlp::encode::RLPEncode; -use ethrex_rlp::structs; +use ethrex_rlp::{decode::RLPDecode, encode::RLPEncode, structs}; use serde::{Deserialize, Serialize}; use crate::constants::EMPTY_BLOCK_ACCESS_LIST_HASH; use crate::utils::keccak; -#[derive(Default, Debug, Serialize, Deserialize, Clone)] +#[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq)] pub struct StorageChange { block_access_index: usize, post_value: U256, @@ -24,7 +23,23 @@ impl RLPEncode for StorageChange { } } -#[derive(Default, Debug, Serialize, Deserialize, Clone)] +impl RLPDecode for StorageChange { + fn decode_unfinished(rlp: &[u8]) -> Result<(Self, &[u8]), ethrex_rlp::error::RLPDecodeError> { + let decoder = structs::Decoder::new(rlp)?; + let (block_access_index, decoder) = decoder.decode_field("block_access_index")?; + let (post_value, decoder) = decoder.decode_field("post_value")?; + let remaining = decoder.finish()?; + Ok(( + Self { + block_access_index, + post_value, + }, + remaining, + )) + } +} + +#[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq)] pub struct SlotChange { slot: U256, slot_changes: Vec, @@ -39,7 +54,17 @@ impl RLPEncode for SlotChange { } } -#[derive(Default, Debug, Serialize, Deserialize, Clone)] +impl RLPDecode for SlotChange { + fn decode_unfinished(rlp: &[u8]) -> Result<(Self, &[u8]), ethrex_rlp::error::RLPDecodeError> { + let decoder = structs::Decoder::new(rlp)?; + let (slot, decoder) = decoder.decode_field("slot")?; + let (slot_changes, decoder) = decoder.decode_field("slot_changes")?; + let remaining = decoder.finish()?; + Ok((Self { slot, slot_changes }, remaining)) + } +} + +#[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq)] pub struct BalanceChange { block_access_index: usize, post_balance: U256, @@ -54,7 +79,23 @@ impl RLPEncode for BalanceChange { } } -#[derive(Default, Debug, Serialize, Deserialize, Clone)] +impl RLPDecode for BalanceChange { + fn decode_unfinished(rlp: &[u8]) -> Result<(Self, &[u8]), ethrex_rlp::error::RLPDecodeError> { + let decoder = structs::Decoder::new(rlp)?; + let (block_access_index, decoder) = decoder.decode_field("block_access_index")?; + let (post_balance, decoder) = decoder.decode_field("post_balance")?; + let remaining = decoder.finish()?; + Ok(( + Self { + block_access_index, + post_balance, + }, + remaining, + )) + } +} + +#[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq)] pub struct NonceChange { block_access_index: usize, post_nonce: u64, @@ -69,7 +110,23 @@ impl RLPEncode for NonceChange { } } -#[derive(Default, Debug, Serialize, Deserialize, Clone)] +impl RLPDecode for NonceChange { + fn decode_unfinished(rlp: &[u8]) -> Result<(Self, &[u8]), ethrex_rlp::error::RLPDecodeError> { + let decoder = structs::Decoder::new(rlp)?; + let (block_access_index, decoder) = decoder.decode_field("block_access_index")?; + let (post_nonce, decoder) = decoder.decode_field("post_nonce")?; + let remaining = decoder.finish()?; + Ok(( + Self { + block_access_index, + post_nonce, + }, + remaining, + )) + } +} + +#[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq)] pub struct CodeChange { block_access_index: usize, new_code: Bytes, @@ -84,7 +141,23 @@ impl RLPEncode for CodeChange { } } -#[derive(Default, Debug, Serialize, Deserialize, Clone)] +impl RLPDecode for CodeChange { + fn decode_unfinished(rlp: &[u8]) -> Result<(Self, &[u8]), ethrex_rlp::error::RLPDecodeError> { + let decoder = structs::Decoder::new(rlp)?; + let (block_access_index, decoder) = decoder.decode_field("block_access_index")?; + let (new_code, decoder) = decoder.decode_field("new_code")?; + let remaining = decoder.finish()?; + Ok(( + Self { + block_access_index, + new_code, + }, + remaining, + )) + } +} + +#[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq)] pub struct AccountChanges { address: Address, storage_changes: Vec, @@ -120,7 +193,31 @@ impl RLPEncode for AccountChanges { } } -#[derive(Default, Debug, Serialize, Deserialize, Clone)] +impl RLPDecode for AccountChanges { + fn decode_unfinished(rlp: &[u8]) -> Result<(Self, &[u8]), ethrex_rlp::error::RLPDecodeError> { + let decoder = structs::Decoder::new(rlp)?; + let (address, decoder) = decoder.decode_field("address")?; + let (storage_changes, decoder) = decoder.decode_field("storage_changes")?; + let (storage_reads, decoder) = decoder.decode_field("storage_reads")?; + let (balance_changes, decoder) = decoder.decode_field("balance_changes")?; + let (nonce_changes, decoder) = decoder.decode_field("nonce_changes")?; + let (code_changes, decoder) = decoder.decode_field("code_changes")?; + let remaining = decoder.finish()?; + Ok(( + Self { + address, + storage_changes, + storage_reads, + balance_changes, + nonce_changes, + code_changes, + }, + remaining, + )) + } +} + +#[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq)] pub struct BlockAccessList { inner: Vec, } @@ -144,9 +241,17 @@ impl RLPEncode for BlockAccessList { } } +impl RLPDecode for BlockAccessList { + fn decode_unfinished(rlp: &[u8]) -> Result<(Self, &[u8]), ethrex_rlp::error::RLPDecodeError> { + let (inner, remaining) = RLPDecode::decode_unfinished(rlp)?; + Ok((Self { inner }, remaining)) + } +} + #[cfg(test)] mod tests { use ethereum_types::{H160, U256}; + use ethrex_rlp::decode::RLPDecode; use ethrex_rlp::encode::RLPEncode; use crate::types::block_access_list::{ @@ -161,7 +266,7 @@ mod tests { const CONTRACT_ADDR: H160 = H160([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12]); //0xC #[test] - fn test_empty_list_validation() { + fn test_encode_decode_empty_list_validation() { let actual_bal = BlockAccessList { inner: vec![AccountChanges { address: ALICE_ADDR, @@ -172,15 +277,18 @@ mod tests { let mut buf = Vec::new(); actual_bal.encode(&mut buf); - let encoded_rlp = hex::encode(buf); + let encoded_rlp = hex::encode(&buf); assert_eq!( &encoded_rlp, "dbda94000000000000000000000000000000000000000ac0c0c0c0c0" ); + + let decoded_bal = BlockAccessList::decode(&buf).unwrap(); + assert_eq!(decoded_bal, actual_bal); } #[test] - fn test_partial_validation() { + fn test_encode_decode_partial_validation() { let actual_bal = BlockAccessList { inner: vec![AccountChanges { address: ALICE_ADDR, @@ -200,11 +308,14 @@ mod tests { let mut buf = Vec::new(); actual_bal.encode(&mut buf); - let encoded_rlp = hex::encode(buf); + let encoded_rlp = hex::encode(&buf); assert_eq!( &encoded_rlp, "e3e294000000000000000000000000000000000000000ac0c20102c3c20164c3c20101c0" ); + + let decoded_bal = BlockAccessList::decode(&buf).unwrap(); + assert_eq!(decoded_bal, actual_bal); } #[test] @@ -288,7 +399,7 @@ mod tests { let mut buf = Vec::new(); actual_bal.encode(&mut buf); - let encoded_rlp = hex::encode(buf); + let encoded_rlp = hex::encode(&buf); assert_eq!( &encoded_rlp, "e4e394000000000000000000000000000000000000000ac9c201c0c202c0c203c0c0c0c0c0" @@ -347,4 +458,36 @@ mod tests { "e4e394000000000000000000000000000000000000000ac0c0c0c9c20101c20202c20303c0" ); } + + #[test] + fn test_decode_storage_slots_ordering_correct_order_should_pass() { + let actual_bal = BlockAccessList { + inner: vec![AccountChanges { + address: ALICE_ADDR, + storage_changes: vec![ + SlotChange { + slot: U256::from(0x01), + slot_changes: vec![], + }, + SlotChange { + slot: U256::from(0x02), + slot_changes: vec![], + }, + SlotChange { + slot: U256::from(0x03), + slot_changes: vec![], + }, + ], + ..Default::default() + }], + }; + + let encoded_rlp: Vec = hex::decode( + "e4e394000000000000000000000000000000000000000ac9c201c0c202c0c203c0c0c0c0c0", + ) + .unwrap(); + + let decoded_bal = BlockAccessList::decode(&encoded_rlp).unwrap(); + assert_eq!(decoded_bal, actual_bal); + } } From 5dcb02c42a47364d7d3f45aceb3eb77b26d0bd1a Mon Sep 17 00:00:00 2001 From: lakshya-sky Date: Fri, 26 Dec 2025 18:32:11 -0500 Subject: [PATCH 10/10] payload v5 method working --- crates/common/serde_utils.rs | 71 +++++++++++++++++++++++++ crates/common/types/genesis.rs | 12 ++++- crates/networking/rpc/engine/payload.rs | 40 +++++++------- crates/networking/rpc/types/payload.rs | 17 ++++-- 4 files changed, 114 insertions(+), 26 deletions(-) diff --git a/crates/common/serde_utils.rs b/crates/common/serde_utils.rs index 3d21f05680e..401c08df736 100644 --- a/crates/common/serde_utils.rs +++ b/crates/common/serde_utils.rs @@ -605,6 +605,77 @@ fn parse_duration(input: String) -> Option { Some(res) } +pub mod block_access_list { + + use super::*; + use ethrex_rlp::decode::RLPDecode; + use ethrex_rlp::encode::RLPEncode; + + pub mod rlp_str { + + use crate::types::block_access_list::BlockAccessList; + + use super::*; + pub fn deserialize<'de, D>(d: D) -> Result + where + D: Deserializer<'de>, + { + let value = String::deserialize(d)?; + let bytes = hex::decode(value.trim_start_matches("0x")) + .map_err(|e| D::Error::custom(e.to_string()))?; + BlockAccessList::decode(&bytes) + .map_err(|_| D::Error::custom("Failed to RLP decode BAL")) + } + + pub fn serialize(value: &BlockAccessList, serializer: S) -> Result + where + S: Serializer, + { + let buf = value.encode_to_vec(); + serializer.serialize_str(&hex::encode(buf)) + } + } + + pub mod rlp_str_opt { + + use serde::Serialize; + + use crate::types::block_access_list::BlockAccessList; + + use super::*; + pub fn deserialize<'de, D>(d: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + let value = Option::::deserialize(d)?; + match value { + Some(s) if !s.is_empty() => hex::decode(s.trim_start_matches("0x")) + .map_err(|e| D::Error::custom(e.to_string())) + .and_then(|b| { + BlockAccessList::decode(&b) + .map_err(|_| D::Error::custom("Failed to RLP decode BAL")) + }) + .map(Some), + _ => Ok(None), + } + } + + pub fn serialize( + value: &Option, + serializer: S, + ) -> Result + where + S: Serializer, + { + let bal = value + .as_ref() + .map(|bal| bal.encode_to_vec()) + .map(hex::encode); + Option::::serialize(&bal, serializer) + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/common/types/genesis.rs b/crates/common/types/genesis.rs index 6ed873ed05c..d3c5514a2d8 100644 --- a/crates/common/types/genesis.rs +++ b/crates/common/types/genesis.rs @@ -17,7 +17,7 @@ use super::{ compute_receipts_root, compute_transactions_root, compute_withdrawals_root, }; use crate::{ - constants::{DEFAULT_OMMERS_HASH, DEFAULT_REQUESTS_HASH}, + constants::{DEFAULT_OMMERS_HASH, DEFAULT_REQUESTS_HASH, EMPTY_BLOCK_ACCESS_LIST_HASH}, rkyv_utils, }; @@ -51,6 +51,7 @@ pub struct Genesis { #[serde(default, with = "crate::serde_utils::u64::hex_str_opt")] pub excess_blob_gas: Option, pub requests_hash: Option, + pub block_access_list_hash: Option, } #[derive(Debug, thiserror::Error)] @@ -692,6 +693,14 @@ impl Genesis { .is_prague_activated(self.timestamp) .then_some(self.requests_hash.unwrap_or(*DEFAULT_REQUESTS_HASH)); + let block_access_list_hash = self + .config + .is_amsterdam_activated(self.timestamp) + .then_some( + self.block_access_list_hash + .unwrap_or(*EMPTY_BLOCK_ACCESS_LIST_HASH), + ); + BlockHeader { parent_hash: H256::zero(), ommers_hash: *DEFAULT_OMMERS_HASH, @@ -714,6 +723,7 @@ impl Genesis { excess_blob_gas, parent_beacon_block_root, requests_hash, + block_access_list_hash, ..Default::default() } } diff --git a/crates/networking/rpc/engine/payload.rs b/crates/networking/rpc/engine/payload.rs index 11e66fd5474..34e5da5bd6e 100644 --- a/crates/networking/rpc/engine/payload.rs +++ b/crates/networking/rpc/engine/payload.rs @@ -36,7 +36,7 @@ impl RpcHandler for NewPayloadV1Request { async fn handle(&self, context: RpcApiContext) -> Result { validate_execution_payload_v1(&self.payload)?; - let block = match get_block_from_payload(&self.payload, None, None) { + let block = match get_block_from_payload(&self.payload, None, None, None) { Ok(block) => block, Err(err) => { return Ok(serde_json::to_value(PayloadStatus::invalid_with_err( @@ -68,7 +68,7 @@ impl RpcHandler for NewPayloadV2Request { // Behave as a v1 validate_execution_payload_v1(&self.payload)?; } - let block = match get_block_from_payload(&self.payload, None, None) { + let block = match get_block_from_payload(&self.payload, None, None, None) { Ok(block) => block, Err(err) => { return Ok(serde_json::to_value(PayloadStatus::invalid_with_err( @@ -124,6 +124,7 @@ impl RpcHandler for NewPayloadV3Request { &self.payload, Some(self.parent_beacon_block_root), None, + None, ) { Ok(block) => block, Err(err) => { @@ -196,6 +197,7 @@ impl RpcHandler for NewPayloadV4Request { &self.payload, Some(self.parent_beacon_block_root), Some(requests_hash), + None, ) { Ok(block) => block, Err(err) => { @@ -269,14 +271,23 @@ impl RpcHandler for NewPayloadV5Request { } async fn handle(&self, context: RpcApiContext) -> Result { + validate_execution_payload_v4(&self.payload)?; + // validate the received requests validate_execution_requests(&self.execution_requests)?; let requests_hash = compute_requests_hash(&self.execution_requests); + let block_access_list_hash = self + .payload + .block_access_list + .as_ref() + .map(|b| b.compute_hash()); + let block = match get_block_from_payload( &self.payload, Some(self.parent_beacon_block_root), Some(requests_hash), + block_access_list_hash, ) { Ok(block) => block, Err(err) => { @@ -294,7 +305,6 @@ impl RpcHandler for NewPayloadV5Request { chain_config.get_fork(block.header.timestamp) ))); } - validate_execution_payload_v4(&self.payload)?; let payload_status = handle_new_payload_v4( &self.payload, context, @@ -744,20 +754,7 @@ async fn handle_new_payload_v4( block: Block, expected_blob_versioned_hashes: Vec, ) -> Result { - // V4 specific: validate block access list - let blob_versioned_hashes: Vec = block - .body - .transactions - .iter() - .flat_map(|tx| tx.blob_versioned_hashes()) - .collect(); - - if expected_blob_versioned_hashes != blob_versioned_hashes { - return Ok(PayloadStatus::invalid_with_err( - "Invalid blob_versioned_hashes", - )); - } - + // TODO: V4 specific: validate block access list handle_new_payload_v3(payload, context, block, expected_blob_versioned_hashes).await } @@ -782,14 +779,17 @@ fn get_block_from_payload( payload: &ExecutionPayload, parent_beacon_block_root: Option, requests_hash: Option, + block_access_list_hash: Option, ) -> Result { let block_hash = payload.block_hash; let block_number = payload.block_number; info!(%block_hash, %block_number, "Received new payload"); - payload - .clone() - .into_block(parent_beacon_block_root, requests_hash) + payload.clone().into_block( + parent_beacon_block_root, + requests_hash, + block_access_list_hash, + ) } fn validate_block_hash(payload: &ExecutionPayload, block: &Block) -> Result<(), RpcErr> { diff --git a/crates/networking/rpc/types/payload.rs b/crates/networking/rpc/types/payload.rs index dcdb781f193..3f85c08d95b 100644 --- a/crates/networking/rpc/types/payload.rs +++ b/crates/networking/rpc/types/payload.rs @@ -8,7 +8,8 @@ use ethrex_common::{ serde_utils, types::{ BlobsBundle, Block, BlockBody, BlockHash, BlockHeader, Transaction, Withdrawal, - compute_transactions_root, compute_withdrawals_root, requests::EncodedRequests, + block_access_list::BlockAccessList, compute_transactions_root, compute_withdrawals_root, + requests::EncodedRequests, }, }; @@ -50,9 +51,13 @@ pub struct ExecutionPayload { default )] pub excess_blob_gas: Option, - #[serde(skip_serializing_if = "Option::is_none", default)] // ExecutionPayloadV4 fields. Optional since we support previous versions. - pub block_access_list: Option>, + #[serde( + skip_serializing_if = "Option::is_none", + with = "serde_utils::block_access_list::rlp_str_opt", + default + )] + pub block_access_list: Option, } #[derive(Clone, Debug)] @@ -136,6 +141,7 @@ impl ExecutionPayload { parent_beacon_block_root, // TODO: set the value properly requests_hash, + block_access_list_hash, ..Default::default() }; @@ -166,7 +172,8 @@ impl ExecutionPayload { withdrawals: block.body.withdrawals, blob_gas_used: block.header.blob_gas_used, excess_blob_gas: block.header.excess_blob_gas, - block_access_list: todo!(), + // TODO: need to finish this after we are able to get BAL from blocks + block_access_list: None, } } } @@ -292,6 +299,6 @@ mod test { // Payload extracted from running kurtosis, only some transactions are included to reduce it's size. let json = r#"{"baseFeePerGas":"0x342770c0","blobGasUsed":"0x0","blockHash":"0x4029a2342bb6d54db91457bc8e442be22b3481df8edea24cc721f9d0649f65be","blockNumber":"0x1","excessBlobGas":"0x0","extraData":"0xd883010e06846765746888676f312e32322e34856c696e7578","feeRecipient":"0x8943545177806ed17b9f23f0a21ee5948ecaa776","gasLimit":"0x17dd79d","gasUsed":"0x401640","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","parentHash":"0x2971eefd1f71f3548728cad87c16cc91b979ef035054828c59a02e49ae300a84","prevRandao":"0x2971eefd1f71f3548728cad87c16cc91b979ef035054828c59a02e49ae300a84","receiptsRoot":"0x0185e8473b81c3a504c4919249a94a94965a2f61c06367ee6ffb88cb7a3ef02b","stateRoot":"0x0eb8fd0af53174e65bb660d0904e5016425a713d8f11c767c26148b526fc05f3","timestamp":"0x66846fb2","transactions":["0xf86d80843baa0c4082f618946177843db3138ae69679a54b95cf345ed759450d870aa87bee538000808360306ba0151ccc02146b9b11adf516e6787b59acae3e76544fdcd75e77e67c6b598ce65da064c5dd5aae2fbb535830ebbdad0234975cd7ece3562013b63ea18cc0df6c97d4","0xf86d01843baa0c4082f61894687704db07e902e9a8b3754031d168d46e3d586e870aa87bee538000808360306ba0f6c479c3e9135a61d7cca17b7354ddc311cda2d8df265d0378f940bdefd62b54a077786891b0b6bcd438d8c24d00fa6628bc2f1caa554f9dec0a96daa4f40eb0d7","0xf86d02843baa0c4082f6189415e6a5a2e131dd5467fa1ff3acd104f45ee5940b870aa87bee538000808360306ca084469ec8ee41e9104cbe3ad7e7fe4225de86076dd2783749b099a4d155900305a07e64e8848c692f0fc251e78e6f3c388eb303349f3e247481366517c2a5ae2d89","0xf86d03843baa0c4082f6189480c4c7125967139acaa931ee984a9db4100e0f3b870aa87bee538000808360306ba021d2d8a35b8da03d7e0b494f71c9ed1c28a195b94c298407b81d65163a79fbdaa024a9bfcf5bbe75ba35130fa784ab88cd21c12c4e7daf3464de91bc1ed07d1bf6","0xf86d04843baa0c4082f61894d08a63244fcd28b0aec5075052cdce31ba04fead870aa87bee538000808360306ca07ee42fee5e426595056ad406aa65a3c7adb1d3d77279f56ebe2410bcf5118b2ca07b8a0e1d21578e9043a7331f60bafc71d15788d1a2d70d00b3c46e0856ff56d2","0xf86d05843baa0c4082f618940b06ef8be65fcda88f2dbae5813480f997ee8e35870aa87bee538000808360306ba0620669c8d6a781d3131bca874152bf833622af0edcd2247eab1b086875d5242ba01632353388f46946b5ce037130e92128e5837fe35d6c7de2b9e56a0f8cc1f5e6", "0x02f8ef83301824048413f157f8842daf517a830186a094000000000000000000000000000000000000000080b8807a0a600060a0553db8600060c855c77fb29ecd7661d8aefe101a0db652a728af0fded622ff55d019b545d03a7532932a60ad52604260cd5360bf60ce53609460cf53603e60d05360f560d153bc596000609e55600060c6556000601f556000609155535660556057536055605853606e60595360e7605a5360d0605b5360eb60c080a03acb03b1fc20507bc66210f7e18ff5af65038fb22c626ae488ad9513d9b6debca05d38459e9d2a221eb345b0c2761b719b313d062ff1ea3d10cf5b8762c44385a6"],"withdrawals":[]}"#; let payload: ExecutionPayload = serde_json::from_str(json).unwrap(); - assert!(payload.into_block(Some(H256::zero()), None).is_ok()); + assert!(payload.into_block(Some(H256::zero()), None, None).is_ok()); } }