Skip to content

Commit

Permalink
feat(l1): implement EIPs 7685 and 6110 (#1921)
Browse files Browse the repository at this point in the history
**Motivation**

In order to support the Prague update.

**Description**

This PR implements
[EIP-7685](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-7685.md)
and
[EIP-6110](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-6110.md).

**Observation**

Two ef_tests from `vectors/prague/eip6110_deposits` where excluded since
we need the latest version of the evm to pass them.

Closes #1787
Closes #1789

---------

Co-authored-by: Julian Ventura <[email protected]>
Co-authored-by: fborello-lambda <[email protected]>
  • Loading branch information
3 people authored Feb 14, 2025
1 parent 092679d commit 89b1c5d
Show file tree
Hide file tree
Showing 12 changed files with 268 additions and 31 deletions.
13 changes: 11 additions & 2 deletions cmd/ef_tests/blockchain/tests/prague.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,18 @@ use ef_tests_blockchain::{
test_runner::{parse_test_file, run_ef_test},
};

// TODO: enable these tests once the evm is updated.
const SKIPPED_TEST: [&str; 2] = [
"tests/prague/eip6110_deposits/test_deposits.py::test_deposit[fork_Prague-blockchain_test-multiple_deposit_from_same_eoa_last_reverts]",
"tests/prague/eip6110_deposits/test_deposits.py::test_deposit[fork_Prague-blockchain_test-multiple_deposit_from_same_eoa_first_reverts]",
];

#[allow(dead_code)]
fn parse_and_execute(path: &Path) -> datatest_stable::Result<()> {
let tests = parse_test_file(path);

for (test_key, test) in tests {
if test.network < Network::Merge {
if test.network < Network::Merge || SKIPPED_TEST.contains(&test_key.as_str()) {
// Discard this test
continue;
}
Expand All @@ -23,5 +29,8 @@ fn parse_and_execute(path: &Path) -> datatest_stable::Result<()> {
datatest_stable::harness!(
parse_and_execute,
"vectors/prague/eip2935_historical_block_hashes_from_state",
r".*/.*\.json"
r".*/.*\.json",
parse_and_execute,
"vectors/prague/eip6110_deposits/deposits",
r".*/.*\.json",
);
2 changes: 2 additions & 0 deletions cmd/ethrex_l2/src/commands/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use std::{
io::{self, BufRead},
path::Path,
thread::sleep,
time::Duration,
};

#[derive(Subcommand)]
Expand Down Expand Up @@ -129,6 +130,7 @@ async fn transfer_from(
retries += 1;
sleep(std::time::Duration::from_secs(2));
}
sleep(Duration::from_millis(3));
}

retries
Expand Down
49 changes: 39 additions & 10 deletions crates/blockchain/blockchain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,16 @@ mod smoke_test;
use error::{ChainError, InvalidBlockError};
use ethrex_common::constants::GAS_PER_BLOB;
use ethrex_common::types::{
compute_receipts_root, validate_block_header, validate_post_cancun_header_fields,
calculate_requests_hash, compute_receipts_root, validate_block_header,
validate_cancun_header_fields, validate_prague_header_fields,
validate_pre_cancun_header_fields, Block, BlockHash, BlockHeader, BlockNumber, ChainConfig,
EIP4844Transaction, Receipt, Transaction,
};
use ethrex_common::H256;

use ethrex_storage::error::StoreError;
use ethrex_storage::{AccountUpdate, Store};
use ethrex_vm::db::{evm_state, EvmState};
use ethrex_vm::db::evm_state;

use ethrex_vm::EVM_BACKEND;
use ethrex_vm::{backends, backends::EVM};
Expand All @@ -39,9 +40,10 @@ pub fn add_block(block: &Block, storage: &Store) -> Result<(), ChainError> {
return Err(ChainError::ParentNotFound);
};
let mut state = evm_state(storage.clone(), block.header.parent_hash);
let chain_config = state.chain_config().map_err(ChainError::from)?;

// Validate the block pre-execution
validate_block(block, &parent_header, &state)?;
validate_block(block, &parent_header, &chain_config)?;
let (receipts, account_updates): (Vec<Receipt>, Vec<AccountUpdate>) = {
match EVM_BACKEND.get() {
Some(EVM::LEVM) => backends::levm::execute_block(block, &mut state)?,
Expand Down Expand Up @@ -69,12 +71,38 @@ pub fn add_block(block: &Block, storage: &Store) -> Result<(), ChainError> {
// Check receipts root matches the one in block header after execution
validate_receipts_root(&block.header, &receipts)?;

// Processes requests from receipts, computes the requests_hash and compares it against the header
validate_requests_hash(&block.header, &receipts, &chain_config)?;

store_block(storage, block.clone())?;
store_receipts(storage, receipts, block_hash)?;

Ok(())
}

pub fn validate_requests_hash(
header: &BlockHeader,
receipts: &[Receipt],
chain_config: &ChainConfig,
) -> Result<(), ChainError> {
if !chain_config.is_prague_activated(header.timestamp) {
return Ok(());
}
let computed_requests_hash = calculate_requests_hash(receipts);
let valid = header
.requests_hash
.map(|requests_hash| requests_hash == computed_requests_hash)
.unwrap_or(false);

if !valid {
return Err(ChainError::InvalidBlock(
InvalidBlockError::RequestsHashMismatch,
));
}

Ok(())
}

/// Stores block and header in the database
pub fn store_block(storage: &Store, block: Block) -> Result<(), ChainError> {
storage.add_block(block)?;
Expand Down Expand Up @@ -150,18 +178,19 @@ pub fn find_parent_header(
pub fn validate_block(
block: &Block,
parent_header: &BlockHeader,
state: &EvmState,
chain_config: &ChainConfig,
) -> Result<(), ChainError> {
let chain_config = state.chain_config().map_err(ChainError::from)?;

// Verify initial header validity against parent
validate_block_header(&block.header, parent_header).map_err(InvalidBlockError::from)?;

// TODO: Add Prague header validation here
if chain_config.is_cancun_activated(block.header.timestamp) {
validate_post_cancun_header_fields(&block.header, parent_header)
if chain_config.is_prague_activated(block.header.timestamp) {
validate_prague_header_fields(&block.header, parent_header)
.map_err(InvalidBlockError::from)?;
verify_blob_gas_usage(block, chain_config)?;
} else if chain_config.is_cancun_activated(block.header.timestamp) {
validate_cancun_header_fields(&block.header, parent_header)
.map_err(InvalidBlockError::from)?;
verify_blob_gas_usage(block, &chain_config)?;
verify_blob_gas_usage(block, chain_config)?;
} else {
validate_pre_cancun_header_fields(&block.header).map_err(InvalidBlockError::from)?
}
Expand Down
2 changes: 2 additions & 0 deletions crates/blockchain/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ pub enum ChainError {

#[derive(Debug, thiserror::Error)]
pub enum InvalidBlockError {
#[error("Requests hash does not match the one in the header after executing")]
RequestsHashMismatch,
#[error("World State Root does not match the one in the header after executing")]
StateRootMismatch,
#[error("Receipts Root does not match the one in the header after executing")]
Expand Down
18 changes: 12 additions & 6 deletions crates/blockchain/payload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,16 @@ use std::{
use ethrex_common::{
constants::GAS_PER_BLOB,
types::{
calculate_base_fee_per_blob_gas, calculate_base_fee_per_gas, compute_receipts_root,
compute_transactions_root, compute_withdrawals_root, BlobsBundle, Block, BlockBody,
BlockHash, BlockHeader, BlockNumber, ChainConfig, MempoolTransaction, Receipt, Transaction,
Withdrawal, DEFAULT_OMMERS_HASH, DEFAULT_REQUESTS_HASH,
calculate_base_fee_per_blob_gas, calculate_base_fee_per_gas, calculate_requests_hash,
compute_receipts_root, compute_transactions_root, compute_withdrawals_root, BlobsBundle,
Block, BlockBody, BlockHash, BlockHeader, BlockNumber, ChainConfig, Fork,
MempoolTransaction, Receipt, Transaction, Withdrawal, DEFAULT_OMMERS_HASH,
DEFAULT_REQUESTS_HASH,
},
Address, Bloom, Bytes, H256, U256,
};

use ethrex_common::types::{Fork, GWEI_TO_WEI};
use ethrex_common::types::GWEI_TO_WEI;
use ethrex_levm::{db::CacheDB, vm::EVMConfig, Account, AccountInfo};
use ethrex_vm::{
backends,
Expand Down Expand Up @@ -652,7 +653,10 @@ fn apply_plain_transaction(
}
}

fn finalize_payload(context: &mut PayloadBuildContext) -> Result<(), StoreError> {
fn finalize_payload(context: &mut PayloadBuildContext) -> Result<(), ChainError> {
let is_prague_activated = context
.chain_config()?
.is_prague_activated(context.payload.header.timestamp);
let account_updates = match EVM_BACKEND.get() {
Some(EVM::LEVM) => backends::levm::get_state_transitions_levm(
context.evm_state,
Expand All @@ -671,6 +675,8 @@ fn finalize_payload(context: &mut PayloadBuildContext) -> Result<(), StoreError>
context.payload.header.transactions_root =
compute_transactions_root(&context.payload.body.transactions);
context.payload.header.receipts_root = compute_receipts_root(&context.receipts);
context.payload.header.requests_hash =
is_prague_activated.then_some(calculate_requests_hash(&context.receipts));
context.payload.header.gas_used = context.payload.header.gas_limit - context.remaining_gas;
Ok(())
}
Expand Down
72 changes: 65 additions & 7 deletions crates/common/types/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use super::{
};
use crate::{
constants::MIN_BASE_FEE_PER_BLOB_GAS,
types::{Receipt, Transaction},
types::{requests::Requests, Receipt, Transaction},
Address, H256, U256,
};
use bytes::Bytes;
Expand All @@ -16,6 +16,7 @@ use ethrex_rlp::{
structs::{Decoder, Encoder},
};
use ethrex_trie::Trie;
use k256::sha2::{Digest, Sha256};
use keccak_hash::keccak;
use serde::{Deserialize, Serialize};

Expand Down Expand Up @@ -261,6 +262,24 @@ pub fn compute_withdrawals_root(withdrawals: &[Withdrawal]) -> H256 {
Trie::compute_hash_from_unsorted_iter(iter)
}

pub fn calculate_requests_hash(receipts: &[Receipt]) -> H256 {
let requests = Requests::from_deposit_receipts(receipts);
// TODO: implement other requests
compute_requests_hash(&[requests])
}

// See https://github.com/ethereum/EIPs/blob/2a6b6965e64787815f7fffb9a4c27660d9683846/EIPS/eip-7685.md?plain=1#L62.
fn compute_requests_hash(requests: &[Requests]) -> H256 {
let mut hasher = Sha256::new();
for request in requests {
let request_bytes = request.to_bytes();
if request_bytes.len() > 1 {
hasher.update(Sha256::digest(request_bytes));
}
}
H256::from_slice(&hasher.finalize())
}

impl RLPEncode for BlockBody {
fn encode(&self, buf: &mut dyn bytes::BufMut) {
Encoder::new(buf)
Expand Down Expand Up @@ -486,11 +505,17 @@ pub enum InvalidBlockHeaderError {
ExcessBlobGasIncorrect,
#[error("Parent beacon block root is not present")]
ParentBeaconBlockRootNotPresent,
#[error("Requests hash is not present")]
RequestsHashNotPresent,
// Other fork errors
#[error("Excess blob gas is present")]
ExcessBlobGasPresent,
#[error("Blob gas used is present")]
BlobGasUsedPresent,
#[error("Parent beacon block root is present")]
ParentBeaconBlockRootPresent,
#[error("Requests hash is present")]
RequestsHashPresent,
}

/// Validates that the header fields are correct in reference to the parent_header
Expand Down Expand Up @@ -547,10 +572,10 @@ pub fn validate_block_header(

Ok(())
}
/// Validates that excess_blob_gas and blob_gas_used are present in the header and
/// validates that excess_blob_gas value is correct on the block header
/// according to the values in the parent header.
pub fn validate_post_cancun_header_fields(

/// Validates that only the required field are present for a Prague block
/// Also validates excess_blob_gas value against parent's header
pub fn validate_prague_header_fields(
header: &BlockHeader,
parent_header: &BlockHeader,
) -> Result<(), InvalidBlockHeaderError> {
Expand All @@ -566,11 +591,38 @@ pub fn validate_post_cancun_header_fields(
if header.parent_beacon_block_root.is_none() {
return Err(InvalidBlockHeaderError::ParentBeaconBlockRootNotPresent);
}
if header.requests_hash.is_none() {
return Err(InvalidBlockHeaderError::RequestsHashNotPresent);
}
Ok(())
}

/// Validates that the excess blob gas value is correct on the block header
/// according to the values in the parent header.
/// Validates that only the required field are present for a Cancun block
/// Also validates excess_blob_gas value against parent's header
pub fn validate_cancun_header_fields(
header: &BlockHeader,
parent_header: &BlockHeader,
) -> Result<(), InvalidBlockHeaderError> {
if header.excess_blob_gas.is_none() {
return Err(InvalidBlockHeaderError::ExcessBlobGasNotPresent);
}
if header.blob_gas_used.is_none() {
return Err(InvalidBlockHeaderError::BlobGasUsedNotPresent);
}
if header.excess_blob_gas.unwrap() != calc_excess_blob_gas(parent_header) {
return Err(InvalidBlockHeaderError::ExcessBlobGasIncorrect);
}
if header.parent_beacon_block_root.is_none() {
return Err(InvalidBlockHeaderError::ParentBeaconBlockRootNotPresent);
}
if header.requests_hash.is_some() {
return Err(InvalidBlockHeaderError::RequestsHashPresent);
}
Ok(())
}

/// Validates that only the required field are present for a pre Cancun block
/// Also validates excess_blob_gas value against parent's header
pub fn validate_pre_cancun_header_fields(
header: &BlockHeader,
) -> Result<(), InvalidBlockHeaderError> {
Expand All @@ -580,6 +632,12 @@ pub fn validate_pre_cancun_header_fields(
if header.blob_gas_used.is_some() {
return Err(InvalidBlockHeaderError::BlobGasUsedPresent);
}
if header.parent_beacon_block_root.is_some() {
return Err(InvalidBlockHeaderError::ParentBeaconBlockRootPresent);
}
if header.requests_hash.is_some() {
return Err(InvalidBlockHeaderError::RequestsHashPresent);
}
Ok(())
}

Expand Down
1 change: 1 addition & 0 deletions crates/common/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ mod constants;
mod fork_id;
mod genesis;
mod receipt;
pub mod requests;
pub mod transaction;
pub mod tx_fields;

Expand Down
Loading

0 comments on commit 89b1c5d

Please sign in to comment.