Skip to content

Commit

Permalink
feat(executor): Generic precompile overrides
Browse files Browse the repository at this point in the history
## Overview

Adds a generic route for overriding precompiles in the
`StatelessL2BlockExecutor`. This allows for different consumers to
override the various precompiles in a way that suits their own needs.
  • Loading branch information
clabby committed Jun 28, 2024
1 parent f900a1d commit e221133
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 24 deletions.
3 changes: 2 additions & 1 deletion bin/client/src/kona.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use kona_client::{
BootInfo, CachingOracle,
};
use kona_common_proc::client_entry;
use kona_executor::StatelessL2BlockExecutor;
use kona_executor::{NoPrecompileOverride, StatelessL2BlockExecutor};
use kona_primitives::L2AttributesWithParent;

extern crate alloc;
Expand Down Expand Up @@ -61,6 +61,7 @@ fn main() -> Result<()> {
.with_parent_header(driver.take_l2_safe_head_header())
.with_fetcher(l2_provider)
.with_hinter(TrieDBHintWriter)
.with_precompile_overrides(NoPrecompileOverride)
.build()?;
let Header { number, .. } = *executor.execute_payload(attributes)?;
let output_root = executor.compute_output_root()?;
Expand Down
34 changes: 27 additions & 7 deletions crates/executor/src/builder.rs
Original file line number Diff line number Diff line change
@@ -1,37 +1,47 @@
//! Contains the builder pattern for the [StatelessL2BlockExecutor].

use crate::StatelessL2BlockExecutor;
use crate::{PrecompileOverride, StatelessL2BlockExecutor};
use alloy_consensus::{Header, Sealable, Sealed};
use anyhow::Result;
use kona_derive::types::RollupConfig;
use kona_mpt::{NoopTrieDBFetcher, NoopTrieDBHinter, TrieDB, TrieDBFetcher, TrieDBHinter};
use kona_mpt::{TrieDB, TrieDBFetcher, TrieDBHinter};
use revm::StateBuilder;

/// The builder pattern for the [StatelessL2BlockExecutor].
#[derive(Debug)]
pub struct StatelessL2BlockExecutorBuilder<'a, F = NoopTrieDBFetcher, H = NoopTrieDBHinter>
pub struct StatelessL2BlockExecutorBuilder<'a, F, H, PO>
where
F: TrieDBFetcher,
H: TrieDBHinter,
PO: PrecompileOverride<F, H>,
{
/// The [RollupConfig].
config: &'a RollupConfig,
/// The parent [Header] to begin execution from.
parent_header: Option<Sealed<Header>>,
/// The precompile overrides to use during execution.
precompile_overrides: Option<PO>,
/// The [TrieDBFetcher] to fetch the state trie preimages.
fetcher: Option<F>,
/// The [TrieDBHinter] to hint the state trie preimages.
hinter: Option<H>,
}

impl<'a, F, H> StatelessL2BlockExecutorBuilder<'a, F, H>
impl<'a, F, H, PO> StatelessL2BlockExecutorBuilder<'a, F, H, PO>
where
F: TrieDBFetcher,
H: TrieDBHinter,
PO: PrecompileOverride<F, H>,
{
/// Instantiate a new builder with the given [RollupConfig].
pub fn with_config(config: &'a RollupConfig) -> Self {
Self { config, parent_header: None, fetcher: None, hinter: None }
Self {
config,
parent_header: None,
precompile_overrides: None,
fetcher: None,
hinter: None,
}
}

/// Set the [Header] to begin execution from.
Expand All @@ -52,8 +62,14 @@ where
self
}

/// Set the precompile overrides to use during execution.
pub fn with_precompile_overrides(mut self, precompile_overrides: PO) -> Self {
self.precompile_overrides = Some(precompile_overrides);
self
}

/// Build the [StatelessL2BlockExecutor] from the builder configuration.
pub fn build(self) -> Result<StatelessL2BlockExecutor<'a, F, H>> {
pub fn build(self) -> Result<StatelessL2BlockExecutor<'a, F, H, PO>> {
let fetcher = self.fetcher.ok_or(anyhow::anyhow!("Fetcher not set"))?;
let hinter = self.hinter.ok_or(anyhow::anyhow!("Hinter not set"))?;
let parent_header = self.parent_header.unwrap_or_else(|| {
Expand All @@ -64,6 +80,10 @@ where
let trie_db = TrieDB::new(parent_header.state_root, parent_header, fetcher, hinter);
let state = StateBuilder::new_with_database(trie_db).with_bundle_update().build();

Ok(StatelessL2BlockExecutor { config: self.config, state })
Ok(StatelessL2BlockExecutor {
config: self.config,
state,
_phantom: core::marker::PhantomData::<PO>,
})
}
}
52 changes: 36 additions & 16 deletions crates/executor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,7 @@ use alloy_eips::eip2718::{Decodable2718, Encodable2718};
use alloy_primitives::{address, keccak256, Address, Bytes, TxKind, B256, U256};
use anyhow::{anyhow, Result};
use kona_derive::types::{L2PayloadAttributes, RawTransaction, RollupConfig};
use kona_mpt::{
ordered_trie_with_encoder, NoopTrieDBFetcher, NoopTrieDBHinter, TrieDB, TrieDBFetcher,
TrieDBHinter,
};
use kona_mpt::{ordered_trie_with_encoder, TrieDB, TrieDBFetcher, TrieDBHinter};
use op_alloy_consensus::{OpReceiptEnvelope, OpTxEnvelope};
use revm::{
db::{states::bundle_state::BundleRetention, State},
Expand All @@ -30,6 +27,9 @@ use tracing::{debug, info};
mod builder;
pub use builder::StatelessL2BlockExecutorBuilder;

mod precompile;
pub use precompile::{NoPrecompileOverride, PrecompileOverride};

mod eip4788;
use eip4788::pre_block_beacon_root_contract_call;

Expand All @@ -42,24 +42,28 @@ use util::{extract_tx_gas_limit, is_system_transaction, logs_bloom, receipt_enve
/// The block executor for the L2 client program. Operates off of a [TrieDB] backed [State],
/// allowing for stateless block execution of OP Stack blocks.
#[derive(Debug)]
pub struct StatelessL2BlockExecutor<'a, F = NoopTrieDBFetcher, H = NoopTrieDBHinter>
pub struct StatelessL2BlockExecutor<'a, F, H, PO>
where
F: TrieDBFetcher,
H: TrieDBHinter,
PO: PrecompileOverride<F, H>,
{
/// The [RollupConfig].
config: &'a RollupConfig,
/// The inner state database component.
state: State<TrieDB<F, H>>,
/// Phantom data for the precompile overrides.
_phantom: core::marker::PhantomData<PO>,
}

impl<'a, F, H> StatelessL2BlockExecutor<'a, F, H>
impl<'a, F, H, PO> StatelessL2BlockExecutor<'a, F, H, PO>
where
F: TrieDBFetcher,
H: TrieDBHinter,
PO: PrecompileOverride<F, H>,
{
/// Constructs a new [StatelessL2BlockExecutorBuilder] with the given [RollupConfig].
pub fn builder(config: &'a RollupConfig) -> StatelessL2BlockExecutorBuilder<'a, F, H> {
pub fn builder(config: &'a RollupConfig) -> StatelessL2BlockExecutorBuilder<'a, F, H, PO> {
StatelessL2BlockExecutorBuilder::with_config(config)
}

Expand Down Expand Up @@ -114,12 +118,22 @@ where
// Ensure that the create2 contract is deployed upon transition to the Canyon hardfork.
ensure_create2_deployer_canyon(&mut self.state, self.config, payload.timestamp)?;

// Construct the EVM with the given configuration.
// TODO(clabby): Accelerate precompiles w/ custom precompile handler.
let mut cumulative_gas_used = 0u64;
let mut receipts: Vec<OpReceiptEnvelope> = Vec::with_capacity(payload.transactions.len());
let is_regolith = self.config.is_regolith_active(payload.timestamp);

// Construct the block-scoped EVM with the given configuration.
// The transaction environment is set within the loop for each transaction.
let mut evm = Evm::builder()
.with_db(&mut self.state)
.with_env_with_handler_cfg(EnvWithHandlerCfg::new_with_cfg_env(
initialized_cfg.clone(),
initialized_block_env.clone(),
Default::default(),
))
.append_handler_register(PO::set_precompiles)
.build();

// Execute the transactions in the payload.
let transactions = payload
.transactions
Expand All @@ -144,13 +158,10 @@ where
anyhow::bail!("EIP-4844 transactions are not supported");
}

let mut evm = Evm::builder()
.with_db(&mut self.state)
.with_env_with_handler_cfg(EnvWithHandlerCfg::new_with_cfg_env(
initialized_cfg.clone(),
initialized_block_env.clone(),
Self::prepare_tx_env(&transaction, raw_transaction)?,
))
// Modify the transaction environment with the current transaction.
evm = evm
.modify()
.with_tx_env(Self::prepare_tx_env(&transaction, raw_transaction)?)
.build();

// If the transaction is a deposit, cache the depositor account.
Expand Down Expand Up @@ -208,6 +219,9 @@ where
cumulative_gas_used = cumulative_gas_used
);

// Drop the EVM to rid the exclusive reference to the database.
drop(evm);

// Merge all state transitions into the cache state.
debug!(target: "client_executor", "Merging state transitions");
self.state.merge_transitions(BundleRetention::Reverts);
Expand Down Expand Up @@ -702,6 +716,7 @@ mod test {
.with_parent_header(header.seal_slow())
.with_fetcher(TestdataTrieDBFetcher::new("block_120794432_exec"))
.with_hinter(NoopTrieDBHinter)
.with_precompile_overrides(NoPrecompileOverride)
.build()
.unwrap();

Expand Down Expand Up @@ -755,6 +770,7 @@ mod test {
.with_parent_header(parent_header.seal_slow())
.with_fetcher(TestdataTrieDBFetcher::new("block_121049889_exec"))
.with_hinter(NoopTrieDBHinter)
.with_precompile_overrides(NoPrecompileOverride)
.build()
.unwrap();

Expand Down Expand Up @@ -812,6 +828,7 @@ mod test {
.with_parent_header(parent_header.seal_slow())
.with_fetcher(TestdataTrieDBFetcher::new("block_121003241_exec"))
.with_hinter(NoopTrieDBHinter)
.with_precompile_overrides(NoPrecompileOverride)
.build()
.unwrap();

Expand Down Expand Up @@ -876,6 +893,7 @@ mod test {
.with_parent_header(parent_header.seal_slow())
.with_fetcher(TestdataTrieDBFetcher::new("block_121057303_exec"))
.with_hinter(NoopTrieDBHinter)
.with_precompile_overrides(NoPrecompileOverride)
.build()
.unwrap();

Expand Down Expand Up @@ -934,6 +952,7 @@ mod test {
.with_parent_header(parent_header.seal_slow())
.with_fetcher(TestdataTrieDBFetcher::new("block_121065789_exec"))
.with_hinter(NoopTrieDBHinter)
.with_precompile_overrides(NoPrecompileOverride)
.build()
.unwrap();

Expand Down Expand Up @@ -1001,6 +1020,7 @@ mod test {
.with_parent_header(parent_header.seal_slow())
.with_fetcher(TestdataTrieDBFetcher::new("block_121135704_exec"))
.with_hinter(NoopTrieDBHinter)
.with_precompile_overrides(NoPrecompileOverride)
.build()
.unwrap();

Expand Down
28 changes: 28 additions & 0 deletions crates/executor/src/precompile.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//! Contains the [PrecompileOverride] trait.

use kona_mpt::{TrieDB, TrieDBFetcher, TrieDBHinter};
use revm::{db::State, handler::register::EvmHandler};

/// A trait for defining precompile overrides during execution.
pub trait PrecompileOverride<F, H>
where
F: TrieDBFetcher,
H: TrieDBHinter,
{
/// Set the precompiles to use during execution.
fn set_precompiles(handler: &mut EvmHandler<'_, (), &mut State<TrieDB<F, H>>>);
}

/// Default implementation of [PrecompileOverride], which does not override any precompiles.
#[derive(Debug, Default)]
pub struct NoPrecompileOverride;

impl<F, H> PrecompileOverride<F, H> for NoPrecompileOverride
where
F: TrieDBFetcher,
H: TrieDBHinter,
{
fn set_precompiles(_: &mut EvmHandler<'_, (), &mut State<TrieDB<F, H>>>) {
// Do nothing
}
}

0 comments on commit e221133

Please sign in to comment.