Skip to content
Merged
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
89 changes: 52 additions & 37 deletions crates/l2/prover/src/guest_program/src/execution.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
use crate::input::ProgramInput;
use crate::output::ProgramOutput;
use crate::report_cycles;

use ethrex_blockchain::error::ChainError;
use ethrex_blockchain::{
validate_block, validate_gas_used, validate_receipts_root, validate_requests_hash,
validate_state_root,
};
use ethrex_common::types::AccountUpdate;
#[cfg(not(feature = "l2"))]
use ethrex_common::types::ELASTICITY_MULTIPLIER;
use ethrex_common::types::block_execution_witness::ExecutionWitness;
use ethrex_common::types::fee_config::FeeConfig;
use ethrex_common::types::{
Expand All @@ -19,7 +16,7 @@ use ethrex_common::{H256, types::Block};
use ethrex_l2_common::l1_messages::L1Message;
use ethrex_rlp::encode::RLPEncode;
use ethrex_vm::{Evm, EvmError, GuestProgramStateWrapper, VmDatabase};
use std::collections::{BTreeMap, HashMap};
use std::collections::HashMap;

#[cfg(feature = "l2")]
use ethrex_common::types::{
Expand Down Expand Up @@ -91,42 +88,52 @@ pub enum StatelessExecutionError {
TryIntoError(#[from] std::num::TryFromIntError),
}

#[cfg(feature = "l2")]
pub fn execution_program(input: ProgramInput) -> Result<ProgramOutput, StatelessExecutionError> {
let ProgramInput {
blocks,
execution_witness,
elasticity_multiplier,
fee_configs: _fee_configs,
#[cfg(feature = "l2")]
fee_configs,
blob_commitment,
#[cfg(feature = "l2")]
blob_proof,
} = input;

let chain_id = execution_witness.chain_config.chain_id;

if cfg!(feature = "l2") {
#[cfg(feature = "l2")]
return stateless_validation_l2(
&blocks,
execution_witness,
elasticity_multiplier,
_fee_configs,
blob_commitment,
blob_proof,
chain_id,
);
}
stateless_validation_l2(
&blocks,
execution_witness,
elasticity_multiplier,
fee_configs,
blob_commitment,
blob_proof,
chain_id,
)
}

#[cfg(not(feature = "l2"))]
pub fn execution_program(input: ProgramInput) -> Result<ProgramOutput, StatelessExecutionError> {
let ProgramInput {
block,
execution_witness,
} = input;

stateless_validation_l1(blocks, execution_witness, elasticity_multiplier, chain_id)
let chain_id = execution_witness.chain_config.chain_id;

stateless_validation_l1(&[block], execution_witness, chain_id)
}

#[cfg(not(feature = "l2"))]
pub fn stateless_validation_l1(
blocks: Vec<Block>,
execution_witness: ExecutionWitness,
elasticity_multiplier: u64,
chain_id: u64,
) -> Result<ProgramOutput, StatelessExecutionError> {
use std::collections::BTreeMap;

use crate::report_cycles;

let guest_program_state: GuestProgramState =
report_cycles("guest_program_state_initialization", || {
execution_witness
Expand Down Expand Up @@ -186,7 +193,7 @@ pub fn stateless_validation_l1(
for block in blocks.iter() {
// Validate the block
report_cycles("validate_block", || {
validate_block(
ethrex_blockchain::validate_block(
block,
parent_block_header,
&chain_config,
Expand Down Expand Up @@ -227,19 +234,23 @@ pub fn stateless_validation_l1(
}

report_cycles("validate_gas_and_receipts", || {
validate_gas_used(&result.receipts, &block.header)
ethrex_blockchain::validate_gas_used(&result.receipts, &block.header)
.map_err(StatelessExecutionError::GasValidationError)
})?;

report_cycles("validate_receipts_root", || {
validate_receipts_root(&block.header, &result.receipts)
ethrex_blockchain::validate_receipts_root(&block.header, &result.receipts)
.map_err(StatelessExecutionError::ReceiptsRootValidationError)
})?;

// validate_requests_hash doesn't do anything for l2 blocks as this verifies l1 requests (messages, privileged transactions and consolidations)
report_cycles("validate_requests_hash", || {
validate_requests_hash(&block.header, &chain_config, &result.requests)
.map_err(StatelessExecutionError::RequestsRootValidationError)
ethrex_blockchain::validate_requests_hash(
&block.header,
&chain_config,
&result.requests,
)
.map_err(StatelessExecutionError::RequestsRootValidationError)
})?;

non_privileged_count += block.body.transactions.len();
Expand All @@ -258,7 +269,7 @@ pub fn stateless_validation_l1(
.ok_or(StatelessExecutionError::EmptyBatchError)?;

report_cycles("validate_state_root", || {
validate_state_root(&last_block.header, final_state_root)
ethrex_blockchain::validate_state_root(&last_block.header, final_state_root)
.map_err(|_chain_err| StatelessExecutionError::InvalidFinalStateTrie)
})?;

Expand All @@ -282,7 +293,7 @@ pub fn stateless_validation_l2(
blocks: &[Block],
execution_witness: ExecutionWitness,
elasticity_multiplier: u64,
fee_configs: Option<Vec<FeeConfig>>,
fee_configs: Vec<FeeConfig>,
blob_commitment: Commitment,
blob_proof: Proof,
chain_id: u64,
Expand Down Expand Up @@ -314,7 +325,9 @@ pub fn stateless_validation_l2(

// Check blobs are valid
let blob_versioned_hash = if !validium {
let fee_configs = fee_configs.ok_or_else(|| StatelessExecutionError::FeeConfigNotFound)?;
if fee_configs.is_empty() {
return Err(StatelessExecutionError::FeeConfigNotFound);
}
verify_blob(blocks, &fee_configs, blob_commitment, blob_proof)?
} else {
H256::zero()
Expand Down Expand Up @@ -344,14 +357,16 @@ fn execute_stateless(
blocks: &[Block],
execution_witness: ExecutionWitness,
elasticity_multiplier: u64,
fee_configs: Option<Vec<FeeConfig>>,
fee_configs: Vec<FeeConfig>,
) -> Result<StatelessResult, StatelessExecutionError> {
let guest_program_state: GuestProgramState = execution_witness
.try_into()
.map_err(StatelessExecutionError::GuestProgramState)?;

#[cfg(feature = "l2")]
let fee_configs = fee_configs.ok_or_else(|| StatelessExecutionError::FeeConfigNotFound)?;
if fee_configs.is_empty() {
return Err(StatelessExecutionError::FeeConfigNotFound);
}

let mut wrapped_db = GuestProgramStateWrapper::new(guest_program_state);
let chain_config = wrapped_db.get_chain_config().map_err(|_| {
Expand Down Expand Up @@ -394,7 +409,7 @@ fn execute_stateless(

for (i, block) in blocks.iter().enumerate() {
// Validate the block
validate_block(
ethrex_blockchain::validate_block(
block,
parent_block_header,
&chain_config,
Expand Down Expand Up @@ -439,12 +454,12 @@ fn execute_stateless(
non_privileged_count += block.body.transactions.len()
- get_block_privileged_transactions(&block.body.transactions).len();

validate_gas_used(&receipts, &block.header)
ethrex_blockchain::validate_gas_used(&receipts, &block.header)
.map_err(StatelessExecutionError::GasValidationError)?;
validate_receipts_root(&block.header, &receipts)
ethrex_blockchain::validate_receipts_root(&block.header, &receipts)
.map_err(StatelessExecutionError::ReceiptsRootValidationError)?;
// validate_requests_hash doesn't do anything for l2 blocks as this verifies l1 requests (messages, privileged transactions and consolidations)
validate_requests_hash(&block.header, &chain_config, &result.requests)
ethrex_blockchain::validate_requests_hash(&block.header, &chain_config, &result.requests)
.map_err(StatelessExecutionError::RequestsRootValidationError)?;
acc_receipts.push(receipts);

Expand Down
44 changes: 25 additions & 19 deletions crates/l2/prover/src/guest_program/src/input.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,25 @@
use ethrex_common::types::{
Block, block_execution_witness::ExecutionWitness, fee_config::FeeConfig,
};
use rkyv::{Archive, Deserialize as RDeserialize, Serialize as RSerialize};
use serde::{Deserialize, Serialize};
use ethrex_common::types::{Block, block_execution_witness::ExecutionWitness};
use serde_with::serde_as;

#[cfg(feature = "l2")]
use ethrex_common::types::blobs_bundle;
/// Private input variables passed into the zkVM execution program.
#[cfg(not(feature = "l2"))]
#[serde_as]
#[derive(
serde::Serialize, serde::Deserialize, rkyv::Serialize, rkyv::Deserialize, rkyv::Archive, Default,
)]
pub struct ProgramInput {
/// Block to execute
pub block: Block,
/// database containing all the data necessary to execute
pub execution_witness: ExecutionWitness,
}

/// Private input variables passed into the zkVM execution program.
#[cfg(feature = "l2")]
#[serde_as]
#[derive(Serialize, Deserialize, RDeserialize, RSerialize, Archive)]
#[derive(
serde::Serialize, serde::Deserialize, rkyv::Serialize, rkyv::Deserialize, rkyv::Archive,
)]
pub struct ProgramInput {
/// blocks to execute
Copy link

Copilot AI Nov 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment starts with a lowercase letter "blocks to execute" while the corresponding L1 comment starts with an uppercase "Block to execute". For consistency, consider capitalizing this to "Blocks to execute".

Suggested change
/// blocks to execute
/// Blocks to execute

Copilot uses AI. Check for mistakes.
pub blocks: Vec<Block>,
Expand All @@ -19,28 +28,25 @@ pub struct ProgramInput {
/// value used to calculate base fee
pub elasticity_multiplier: u64,
/// Configuration for L2 fees used for each block
pub fee_configs: Option<Vec<FeeConfig>>,
#[cfg(feature = "l2")]
pub fee_configs: Vec<ethrex_common::types::fee_config::FeeConfig>,
/// KZG commitment to the blob data
#[serde_as(as = "[_; 48]")]
pub blob_commitment: blobs_bundle::Commitment,
#[cfg(feature = "l2")]
pub blob_commitment: ethrex_common::types::blobs_bundle::Commitment,
/// KZG opening for a challenge over the blob commitment
#[serde_as(as = "[_; 48]")]
pub blob_proof: blobs_bundle::Proof,
pub blob_proof: ethrex_common::types::blobs_bundle::Proof,
}

#[cfg(feature = "l2")]
impl Default for ProgramInput {
fn default() -> Self {
Self {
blocks: Default::default(),
blocks: Vec::default(),
execution_witness: ExecutionWitness::default(),
elasticity_multiplier: Default::default(),
fee_configs: None,
#[cfg(feature = "l2")]
elasticity_multiplier: u64::default(),
fee_configs: Vec::default(),
blob_commitment: [0; 48],
#[cfg(feature = "l2")]
blob_proof: [0u8; 48],
blob_proof: [0; 48],
}
}
}
39 changes: 33 additions & 6 deletions crates/l2/prover/src/guest_program/src/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,17 @@ use serde::{Deserialize, Serialize};

/// Public output variables exposed by the zkVM execution program. Some of these are part of
/// the program input.
#[cfg(feature = "l2")]
#[derive(Serialize, Deserialize)]
pub struct ProgramOutput {
/// initial state trie root hash
pub initial_state_hash: H256,
/// final state trie root hash
pub final_state_hash: H256,
#[cfg(feature = "l2")]
/// merkle root of all messages in a batch
pub l1messages_merkle_root: H256,
#[cfg(feature = "l2")]
/// hash of all the privileged transactions made in a batch
pub privileged_transactions_hash: H256,
#[cfg(feature = "l2")]
/// blob commitment versioned hash
pub blob_versioned_hash: H256,
/// hash of the last block in a batch
Expand All @@ -26,16 +24,14 @@ pub struct ProgramOutput {
pub non_privileged_count: U256,
}

#[cfg(feature = "l2")]
impl ProgramOutput {
pub fn encode(&self) -> Vec<u8> {
[
self.initial_state_hash.to_fixed_bytes(),
self.final_state_hash.to_fixed_bytes(),
#[cfg(feature = "l2")]
self.l1messages_merkle_root.to_fixed_bytes(),
#[cfg(feature = "l2")]
self.privileged_transactions_hash.to_fixed_bytes(),
#[cfg(feature = "l2")]
self.blob_versioned_hash.to_fixed_bytes(),
self.last_block_hash.to_fixed_bytes(),
self.chain_id.to_big_endian(),
Expand All @@ -44,3 +40,34 @@ impl ProgramOutput {
.concat()
}
}

/// Public output variables exposed by the zkVM execution program. Some of these are part of
/// the program input.
#[cfg(not(feature = "l2"))]
#[derive(Serialize, Deserialize)]
pub struct ProgramOutput {
/// initial state trie root hash
pub initial_state_hash: H256,
/// final state trie root hash
pub final_state_hash: H256,
/// hash of the last block in a batch
Copy link

Copilot AI Nov 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says "hash of the last block in a batch", but in the L1 context (non-l2 feature), only a single block is processed, not a batch. Consider updating the comment to reflect this, e.g., "hash of the block" or "hash of the last block".

Suggested change
/// hash of the last block in a batch
/// hash of the block

Copilot uses AI. Check for mistakes.
pub last_block_hash: H256,
/// chain_id of the network
pub chain_id: U256,
/// amount of non-privileged transactions
pub non_privileged_count: U256,
}

#[cfg(not(feature = "l2"))]
impl ProgramOutput {
pub fn encode(&self) -> Vec<u8> {
[
self.initial_state_hash.to_fixed_bytes(),
self.final_state_hash.to_fixed_bytes(),
self.last_block_hash.to_fixed_bytes(),
self.chain_id.to_big_endian(),
self.non_privileged_count.to_big_endian(),
]
.concat()
}
}
Comment on lines +43 to +73
Copy link

Copilot AI Nov 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The L1 ProgramOutput struct and its encode() implementation are duplicated from the L2 version. Consider using a macro or shared implementation to reduce code duplication and make maintenance easier. For example, you could define common fields in a macro and compose the different variants.

Copilot uses AI. Check for mistakes.
11 changes: 10 additions & 1 deletion crates/l2/prover/src/prover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,14 +123,23 @@ impl Prover {
Ok(Some(ProverData {
batch_number,
input: ProgramInput {
#[cfg(not(feature = "l2"))]
block: input
.blocks
.first()
.cloned()
.ok_or("Input blocks is empty")?,
#[cfg(feature = "l2")]
blocks: input.blocks,
execution_witness: input.execution_witness,
#[cfg(feature = "l2")]
elasticity_multiplier: input.elasticity_multiplier,
#[cfg(feature = "l2")]
blob_commitment: input.blob_commitment,
#[cfg(feature = "l2")]
blob_proof: input.blob_proof,
fee_configs: Some(input.fee_configs),
#[cfg(feature = "l2")]
fee_configs: input.fee_configs,
},
format,
}))
Expand Down
2 changes: 1 addition & 1 deletion crates/l2/tee/quote-gen/src/sender.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ pub async fn get_batch(commit_hash: String) -> Result<(u64, ProgramInput), Strin
blob_commitment: input.blob_commitment,
#[cfg(feature = "l2")]
blob_proof: input.blob_proof,
fee_configs: Some(input.fee_configs),
fee_configs: input.fee_configs,
},
)),
_ => Err("No blocks to prove.".to_owned()),
Expand Down
Loading