Skip to content
This repository was archived by the owner on Jan 16, 2026. It is now read-only.
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
13 changes: 13 additions & 0 deletions bin/client/src/fault/handler/bn128_pair.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ const PAIR_ELEMENT_LEN: usize = 64 + 128;
pub(crate) const FPVM_ECPAIRING: PrecompileWithAddress =
PrecompileWithAddress(ECPAIRING_ADDRESS, Precompile::Standard(fpvm_ecpairing));

pub(crate) const FPVM_ECPAIRING_GRANITE: PrecompileWithAddress =
PrecompileWithAddress(ECPAIRING_ADDRESS, Precompile::Standard(fpvm_ecpairing_granite));

/// Performs an FPVM-accelerated `ecpairing` precompile call.
fn fpvm_ecpairing(input: &Bytes, gas_limit: u64) -> PrecompileResult {
let gas_used =
Expand Down Expand Up @@ -59,3 +62,13 @@ fn fpvm_ecpairing(input: &Bytes, gas_limit: u64) -> PrecompileResult {

Ok(PrecompileOutput::new(gas_used, result_data.into()))
}

/// Performs an FPVM-accelerated `ecpairing` precompile call after the Granite hardfork.
fn fpvm_ecpairing_granite(input: &Bytes, gas_limit: u64) -> PrecompileResult {
const BN256_MAX_PAIRING_SIZE_GRANITE: usize = 112_687;
if input.len() > BN256_MAX_PAIRING_SIZE_GRANITE {
return Err(PrecompileError::Bn128PairLength.into());
}

fpvm_ecpairing(input, gas_limit)
}
5 changes: 5 additions & 0 deletions bin/client/src/fault/handler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ pub(crate) fn fpvm_handle_register<F, H>(
kzg_point_eval::FPVM_KZG_POINT_EVAL,
];
ctx_precompiles.extend(override_precompiles);

if spec_id.is_enabled_in(SpecId::GRANITE) {
ctx_precompiles.extend([bn128_pair::FPVM_ECPAIRING_GRANITE]);
}

ctx_precompiles
});
}
6 changes: 6 additions & 0 deletions crates/executor/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,18 @@ pub enum ExecutorError {
/// Missing gas limit in the payload attributes.
#[error("Gas limit not provided in payload attributes")]
MissingGasLimit,
/// Missing transactions in the payload attributes.
#[error("Transactions not provided in payload attributes")]
MissingTransactions,
/// Missing EIP-1559 parameters in execution payload post-Holocene.
#[error("Missing EIP-1559 parameters in execution payload post-Holocene")]
MissingEIP1559Params,
/// Missing parent beacon block root in the payload attributes.
#[error("Parent beacon block root not provided in payload attributes")]
MissingParentBeaconBlockRoot,
/// Invalid `extraData` field in the block header.
#[error("Invalid `extraData` field in the block header")]
InvalidExtraData,
/// Block gas limit exceeded.
#[error("Block gas limit exceeded")]
BlockGasLimitExceeded,
Expand Down
110 changes: 34 additions & 76 deletions crates/executor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use alloy_eips::{
eip1559::BaseFeeParams,
eip2718::{Decodable2718, Encodable2718},
};
use alloy_primitives::{address, keccak256, Address, Bytes, TxKind, B256, B64, U256};
use alloy_primitives::{address, keccak256, Address, Bytes, TxKind, B256, U256};
use kona_mpt::{ordered_trie_with_encoder, TrieDB, TrieDBError, TrieHinter, TrieProvider};
use op_alloy_consensus::{OpReceiptEnvelope, OpTxEnvelope};
use op_alloy_genesis::RollupConfig;
Expand Down Expand Up @@ -43,7 +43,10 @@ mod canyon;
use canyon::ensure_create2_deployer_canyon;

mod util;
use util::{logs_bloom, receipt_envelope_from_parts};
use util::{
decode_holocene_eip_1559_params, encode_holocene_eip_1559_params, logs_bloom,
receipt_envelope_from_parts,
};

/// The block executor for the L2 client program. Operates off of a [TrieDB] backed [State],
/// allowing for stateless block execution of OP Stack blocks.
Expand Down Expand Up @@ -108,13 +111,15 @@ where
let block_number = initialized_block_env.number.to::<u64>();
let base_fee = initialized_block_env.basefee.to::<u128>();
let gas_limit = payload.gas_limit.ok_or(ExecutorError::MissingGasLimit)?;
let transactions =
payload.transactions.as_ref().ok_or(ExecutorError::MissingTransactions)?;

info!(
target: "client_executor",
"Executing block # {block_number} | Gas limit: {gas_limit} | Tx count: {tx_len}",
block_number = block_number,
gas_limit = gas_limit,
tx_len = payload.transactions.as_ref().map(|txs| txs.len()).unwrap_or_default(),
tx_len = transactions.len(),
);

let mut state =
Expand All @@ -138,8 +143,7 @@ where
)?;

let mut cumulative_gas_used = 0u64;
let mut receipts: Vec<OpReceiptEnvelope> =
Vec::with_capacity(payload.transactions.as_ref().map(|t| t.len()).unwrap_or_default());
let mut receipts: Vec<OpReceiptEnvelope> = Vec::with_capacity(transactions.len());
let is_regolith = self.config.is_regolith_active(payload.payload_attributes.timestamp);

// Construct the block-scoped EVM with the given configuration.
Expand All @@ -162,18 +166,15 @@ where
};

// Execute the transactions in the payload.
let transactions = if let Some(ref txs) = payload.transactions {
txs.iter()
.map(|raw_tx| {
let tx = OpTxEnvelope::decode_2718(&mut raw_tx.as_ref())
.map_err(ExecutorError::RLPError)?;
Ok((tx, raw_tx.as_ref()))
})
.collect::<ExecutorResult<Vec<_>>>()?
} else {
Vec::new()
};
for (transaction, raw_transaction) in transactions {
let decoded_txs = transactions
.iter()
.map(|raw_tx| {
let tx = OpTxEnvelope::decode_2718(&mut raw_tx.as_ref())
.map_err(ExecutorError::RLPError)?;
Ok((tx, raw_tx.as_ref()))
})
.collect::<ExecutorResult<Vec<_>>>()?;
for (transaction, raw_transaction) in decoded_txs {
// The sum of the transaction’s gas limit, Tg, and the gas utilized in this block prior,
// must be no greater than the block’s gasLimit.
let block_available_gas = (gas_limit - cumulative_gas_used) as u128;
Expand Down Expand Up @@ -261,8 +262,7 @@ where
// Recompute the header roots.
let state_root = state.database.state_root(&bundle)?;

let transactions_root =
Self::compute_transactions_root(payload.transactions.unwrap_or_default().as_slice());
let transactions_root = Self::compute_transactions_root(transactions.as_slice());
let receipts_root = Self::compute_receipts_root(
&receipts,
self.config,
Expand Down Expand Up @@ -303,23 +303,14 @@ where
.unwrap_or_default();

// At holocene activation, the base fee parameters from the payload are placed
// into the Header's `nonce` field. Prior to Holocene, the `nonce` field should
// be set to 0.
// into the Header's `extra_data` field.
//
// If the payload's `eip_1559_params` are equal to `0`, then the header's `nonce`
// If the payload's `eip_1559_params` are equal to `0`, then the header's `extraData`
// field is set to the encoded canyon base fee parameters.
let encoded_base_fee_params = self
.config
.is_holocene_active(payload.payload_attributes.timestamp)
.then(|| {
let payload_params =
payload.eip_1559_params.ok_or(ExecutorError::MissingEIP1559Params)?;

let params = (payload_params == B64::ZERO)
.then(|| encode_canyon_base_fee_params(self.config))
.unwrap_or(payload_params);
Ok::<_, ExecutorError>(params)
})
.then(|| encode_holocene_eip_1559_params(self.config, &payload))
.transpose()?
.unwrap_or_default();

Expand All @@ -340,13 +331,12 @@ where
gas_used: cumulative_gas_used,
timestamp: payload.payload_attributes.timestamp,
mix_hash: payload.payload_attributes.prev_randao,
nonce: encoded_base_fee_params,
nonce: Default::default(),
base_fee_per_gas: base_fee.try_into().ok(),
blob_gas_used,
excess_blob_gas: excess_blob_gas.and_then(|x| x.try_into().ok()),
parent_beacon_block_root: payload.payload_attributes.parent_beacon_block_root,
// Provide no extra data on OP Stack chains
extra_data: Bytes::default(),
extra_data: encoded_base_fee_params,
}
.seal_slow();

Expand Down Expand Up @@ -432,7 +422,9 @@ where
/// ## Returns
/// The active [SpecId] for the executor.
fn revm_spec_id(&self, timestamp: u64) -> SpecId {
if self.config.is_fjord_active(timestamp) {
if self.config.is_holocene_active(timestamp) {
SpecId::HOLOCENE
} else if self.config.is_fjord_active(timestamp) {
SpecId::FJORD
} else if self.config.is_ecotone_active(timestamp) {
SpecId::ECOTONE
Expand Down Expand Up @@ -558,22 +550,12 @@ where
let base_fee_params =
if config.is_holocene_active(payload_attrs.payload_attributes.timestamp) {
// After Holocene activation, the base fee parameters are stored in the
// `nonce` field of the parent header. If the `nonce` field is not zero,
// then the base fee parameters are extracted from the `nonce` field.
// Otherwise, the canyon base fee parameters are used for the current block.
(parent_header.nonce != B64::ZERO)
.then(|| {
// SAFETY: The `nonce` field is always 8 bytes, and the 4-byte segments are
// guaranteed to be valid `u32` values.
let denominator: u32 =
u32::from_be_bytes(parent_header.nonce[0..4].try_into().unwrap());
let elasticity: u32 =
u32::from_be_bytes(parent_header.nonce[4..8].try_into().unwrap());
BaseFeeParams {
max_change_denominator: denominator as u128,
elasticity_multiplier: elasticity as u128,
}
})
// `extraData` field of the parent header. If Holocene wasn't active in the
// parent block, the default base fee parameters are used.
config
.is_holocene_active(parent_header.timestamp)
.then(|| decode_holocene_eip_1559_params(parent_header))
.transpose()?
.unwrap_or(config.canyon_base_fee_params)
} else if config.is_canyon_active(payload_attrs.payload_attributes.timestamp) {
// If the payload attribute timestamp is past canyon activation,
Expand Down Expand Up @@ -705,22 +687,10 @@ where
}
}

/// Encodes the canyon base fee parameters, per Holocene spec.
///
/// <https://specs.optimism.io/protocol/holocene/exec-engine.html#eip1559params-encoding>
fn encode_canyon_base_fee_params(config: &RollupConfig) -> B64 {
let params = config.canyon_base_fee_params;

let mut buf = B64::ZERO;
buf[0..4].copy_from_slice(&(params.max_change_denominator as u32).to_be_bytes());
buf[4..8].copy_from_slice(&(params.elasticity_multiplier as u32).to_be_bytes());
buf
}

#[cfg(test)]
mod test {
use super::*;
use alloy_primitives::{address, b256, b64, hex};
use alloy_primitives::{address, b256, hex};
use alloy_rlp::Decodable;
use alloy_rpc_types_engine::PayloadAttributes;
use anyhow::{anyhow, Result};
Expand Down Expand Up @@ -774,18 +744,6 @@ mod test {
}
}

#[test]
fn test_encode_canyon_1559_params() {
let cfg = RollupConfig {
canyon_base_fee_params: BaseFeeParams {
max_change_denominator: 32,
elasticity_multiplier: 64,
},
..Default::default()
};
assert_eq!(encode_canyon_base_fee_params(&cfg), b64!("0000002000000040"));
}

#[test]
fn test_l2_block_executor_small_block() {
// Static for the execution of block #120794432 on OP mainnet.
Expand Down
Loading