Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(client): StatelessL2BlockExecutor #210

Merged
merged 7 commits into from
Jun 10, 2024
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
177 changes: 75 additions & 102 deletions Cargo.lock

Large diffs are not rendered by default.

9 changes: 7 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,17 @@ exclude = ["**/target", "benches/", "tests"]
anyhow = { version = "1.0.79", default-features = false }
tracing = { version = "0.1.40", default-features = false }
cfg-if = "1.0.0"
spin = { version = "0.9", features = ["mutex"] }
lru = "0.12.3"
async-trait = "0.1"

# Ethereum
alloy-primitives = { version = "0.7.1", default-features = false }
alloy-rlp = { version = "0.3.4", default-features = false }
alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "e3f2f07", default-features = false }
revm = { version = "9.0.0", default-features = false }
alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "cb95183", default-features = false }
op-alloy-consensus = { git = "https://github.com/clabby/op-alloy", branch = "refcell/consensus-port", default-features = false }
alloy-eips = { git = "https://github.com/alloy-rs/alloy", rev = "cb95183", default-features = false }
revm = { git = "https://github.com/bluealloy/revm", rev = "a832a4e", default-features = false }

[profile.dev]
opt-level = 1
Expand Down
12 changes: 6 additions & 6 deletions bin/host/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ anyhow.workspace = true
tracing.workspace = true
alloy-primitives = { workspace = true, features = ["serde"] }
revm = { workspace = true, features = ["std", "c-kzg", "secp256k1", "portable", "blst"] }
alloy-eips.workspace = true
alloy-consensus.workspace = true

# local
kona-client = { path = "../programs/client", version = "0.1.0" }
Expand All @@ -21,12 +23,10 @@ kona-preimage = { path = "../../crates/preimage", version = "0.0.1" }
kona-mpt = { path = "../../crates/mpt", version = "0.0.1" }

# external
alloy-provider = { git = "https://github.com/alloy-rs/alloy", rev = "e3f2f07" }
alloy-transport-http = { git = "https://github.com/alloy-rs/alloy", rev = "e3f2f07" }
alloy-rpc-client = { git = "https://github.com/alloy-rs/alloy", rev = "e3f2f07" }
alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy", rev = "e3f2f07" }
alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "e3f2f07" }
alloy-eips = { git = "https://github.com/alloy-rs/alloy", rev = "e3f2f07" }
alloy-provider = { git = "https://github.com/alloy-rs/alloy", rev = "cb95183" }
alloy-transport-http = { git = "https://github.com/alloy-rs/alloy", rev = "cb95183" }
alloy-rpc-client = { git = "https://github.com/alloy-rs/alloy", rev = "cb95183" }
alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy", rev = "cb95183" }
reqwest = "0.12"
tokio = { version = "1.37.0", features = ["full"] }
futures = "0.3"
Expand Down
23 changes: 16 additions & 7 deletions bin/programs/client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,23 @@ homepage.workspace = true
# workspace
cfg-if.workspace = true
alloy-primitives.workspace = true
alloy-consensus.workspace = true
alloy-rlp.workspace = true
alloy-eips.workspace = true
op-alloy-consensus.workspace = true
anyhow.workspace = true
revm = { workspace = true, features = ["optimism"] }
lru.workspace = true
spin.workspace = true
async-trait.workspace = true

# local
kona-common = { path = "../../../crates/common" }
kona-common-proc = { path = "../../../crates/common-proc" }
kona-preimage = { path = "../../../crates/preimage" }
kona-common = { path = "../../../crates/common", version = "0.0.1" }
kona-common-proc = { path = "../../../crates/common-proc", version = "0.0.1" }
kona-preimage = { path = "../../../crates/preimage", version = "0.0.1" }
kona-mpt = { path = "../../../crates/mpt", version = "0.0.1" }
kona-derive = { path = "../../../crates/derive", version = "0.0.1" }

# external
lru = "0.12.3"
async-trait = "0.1.80"
spin = "0.9.8"
[dev-dependencies]
serde = { version = "1.0.197", features = ["derive"] }
serde_json = "1.0.117"
3 changes: 0 additions & 3 deletions bin/programs/client/src/executor/mod.rs

This file was deleted.

54 changes: 54 additions & 0 deletions bin/programs/client/src/l2/executor/canyon.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//! Contains logic specific to Canyon hardfork activation.

use alloy_consensus::Header;
use alloy_primitives::{address, b256, hex, Address, Bytes, B256};
use kona_derive::types::RollupConfig;
use revm::{
primitives::{Account, Bytecode, HashMap},
DatabaseCommit, State,
};

/// The address of the create2 deployer
const CREATE_2_DEPLOYER_ADDR: Address = address!("13b0D85CcB8bf860b6b79AF3029fCA081AE9beF2");

/// The codehash of the create2 deployer contract.
const CREATE_2_DEPLOYER_CODEHASH: B256 =
b256!("b0550b5b431e30d38000efb7107aaa0ade03d48a7198a140edda9d27134468b2");

/// The raw bytecode of the create2 deployer contract.
const CREATE_2_DEPLOYER_BYTECODE: [u8; 1584] = hex!("6080604052600436106100435760003560e01c8063076c37b21461004f578063481286e61461007157806356299481146100ba57806366cfa057146100da57600080fd5b3661004a57005b600080fd5b34801561005b57600080fd5b5061006f61006a366004610327565b6100fa565b005b34801561007d57600080fd5b5061009161008c366004610327565b61014a565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200160405180910390f35b3480156100c657600080fd5b506100916100d5366004610349565b61015d565b3480156100e657600080fd5b5061006f6100f53660046103ca565b610172565b61014582826040518060200161010f9061031a565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe082820381018352601f90910116604052610183565b505050565b600061015683836102e7565b9392505050565b600061016a8484846102f0565b949350505050565b61017d838383610183565b50505050565b6000834710156101f4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f437265617465323a20696e73756666696369656e742062616c616e636500000060448201526064015b60405180910390fd5b815160000361025f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f437265617465323a2062797465636f6465206c656e677468206973207a65726f60448201526064016101eb565b8282516020840186f5905073ffffffffffffffffffffffffffffffffffffffff8116610156576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601960248201527f437265617465323a204661696c6564206f6e206465706c6f790000000000000060448201526064016101eb565b60006101568383305b6000604051836040820152846020820152828152600b8101905060ff815360559020949350505050565b61014e806104ad83390190565b6000806040838503121561033a57600080fd5b50508035926020909101359150565b60008060006060848603121561035e57600080fd5b8335925060208401359150604084013573ffffffffffffffffffffffffffffffffffffffff8116811461039057600080fd5b809150509250925092565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000806000606084860312156103df57600080fd5b8335925060208401359150604084013567ffffffffffffffff8082111561040557600080fd5b818601915086601f83011261041957600080fd5b81358181111561042b5761042b61039b565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f011681019083821181831017156104715761047161039b565b8160405282815289602084870101111561048a57600080fd5b826020860160208301376000602084830101528095505050505050925092509256fe608060405234801561001057600080fd5b5061012e806100206000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063249cb3fa14602d575b600080fd5b603c603836600460b1565b604e565b60405190815260200160405180910390f35b60008281526020818152604080832073ffffffffffffffffffffffffffffffffffffffff8516845290915281205460ff16608857600060aa565b7fa2ef4600d742022d532d4747cb3547474667d6f13804902513b2ec01c848f4b45b9392505050565b6000806040838503121560c357600080fd5b82359150602083013573ffffffffffffffffffffffffffffffffffffffff8116811460ed57600080fd5b80915050925092905056fea26469706673582212205ffd4e6cede7d06a5daf93d48d0541fc68189eeb16608c1999a82063b666eb1164736f6c63430008130033a2646970667358221220fdc4a0fe96e3b21c108ca155438d37c9143fb01278a3c1d274948bad89c564ba64736f6c63430008130033");

/// The Canyon hardfork issues an irregular state transition that force-deploys the create2
/// deployer contract. This is done by directly setting the code of the create2 deployer account
/// prior to executing any transactions on the timestamp activation of the fork.
pub(crate) fn ensure_create2_deployer_canyon<DB>(
db: &mut State<DB>,
config: &RollupConfig,
timestamp: u64,
parent_header: &Header,
) -> Result<(), DB::Error>
where
DB: revm::Database,
{
// If the canyon hardfork is active at the current timestamp, and it was not active at the
// previous block timestamp, then we need to force-deploy the create2 deployer contract.
if config.is_canyon_active(timestamp) && !config.is_canyon_active(parent_header.timestamp) {
// Load the create2 deployer account from the cache.
let acc = db.load_cache_account(CREATE_2_DEPLOYER_ADDR)?;

// Update the account info with the create2 deployer codehash and bytecode.
let mut acc_info = acc.account_info().unwrap_or_default();
acc_info.code_hash = CREATE_2_DEPLOYER_CODEHASH;
acc_info.code = Some(Bytecode::new_raw(Bytes::from_static(&CREATE_2_DEPLOYER_BYTECODE)));

// Convert the cache account back into a revm account and mark it as touched.
let mut revm_acc: Account = acc_info.into();
revm_acc.mark_touch();

// Commit the create2 deployer account to the database.
db.commit(HashMap::from([(CREATE_2_DEPLOYER_ADDR, revm_acc)]));
return Ok(());
}

Ok(())
}
168 changes: 168 additions & 0 deletions bin/programs/client/src/l2/executor/eip4788.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
//! Contains the logic for executing the pre-block beacon root call.

use alloc::{boxed::Box, vec::Vec};
use alloy_consensus::constants::BEACON_ROOTS_ADDRESS;
use alloy_primitives::{Address, Bytes, B256, U256};
use anyhow::{anyhow, Result};
use kona_derive::types::{L2PayloadAttributes, RollupConfig};
use revm::{
primitives::{
BlockEnv, CfgEnvWithHandlerCfg, Env, EnvWithHandlerCfg, OptimismFields, TransactTo, TxEnv,
},
Database, DatabaseCommit, Evm,
};

/// Execute the EIP-4788 pre-block beacon root contract call.
pub(crate) fn pre_block_beacon_root_contract_call<DB: Database + DatabaseCommit>(
db: &mut DB,
config: &RollupConfig,
block_number: u64,
initialized_cfg: &CfgEnvWithHandlerCfg,
initialized_block_env: &BlockEnv,
payload: &L2PayloadAttributes,
) -> Result<()>
where
DB::Error: core::fmt::Display,
{
// apply pre-block EIP-4788 contract call
let mut evm_pre_block = Evm::builder()
.with_db(db)
.with_env_with_handler_cfg(EnvWithHandlerCfg::new_with_cfg_env(
initialized_cfg.clone(),
initialized_block_env.clone(),
Default::default(),
))
.build();

// initialize a block from the env, because the pre block call needs the block itself
apply_beacon_root_contract_call(
config,
payload.timestamp,
block_number,
payload.parent_beacon_block_root,
&mut evm_pre_block,
)
}

/// Apply the EIP-4788 pre-block beacon root contract call to a given EVM instance.
fn apply_beacon_root_contract_call<EXT, DB: Database + DatabaseCommit>(
config: &RollupConfig,
timestamp: u64,
block_number: u64,
parent_beacon_block_root: Option<B256>,
evm: &mut Evm<'_, EXT, DB>,
) -> Result<()>
where
DB::Error: core::fmt::Display,
{
if !config.is_ecotone_active(timestamp) {
return Ok(());
}

let parent_beacon_block_root =
parent_beacon_block_root.ok_or(anyhow!("missing parent beacon block root"))?;

// if the block number is zero (genesis block) then the parent beacon block root must
// be 0x0 and no system transaction may occur as per EIP-4788
if block_number == 0 {
if parent_beacon_block_root != B256::ZERO {
anyhow::bail!("Cancun genesis block parent beacon block root must be 0x0");
}
return Ok(());
}

// Get the previous environment
let previous_env = Box::new(evm.context.evm.env().clone());

// modify env for pre block call
fill_tx_env_with_beacon_root_contract_call(&mut evm.context.evm.env, parent_beacon_block_root);

let mut state = match evm.transact() {
Ok(res) => res.state,
Err(e) => {
evm.context.evm.env = previous_env;
anyhow::bail!("Failed to execute pre block call: {}", e);
}
};

state.remove(&alloy_eips::eip4788::SYSTEM_ADDRESS);
state.remove(&evm.block().coinbase);

evm.context.evm.db.commit(state);

// re-set the previous env
evm.context.evm.env = previous_env;

Ok(())
}

/// Fill transaction environment with the EIP-4788 system contract message data.
///
/// This requirements for the beacon root contract call defined by
/// [EIP-4788](https://eips.ethereum.org/EIPS/eip-4788) are:
///
/// At the start of processing any execution block where `block.timestamp >= FORK_TIMESTAMP` (i.e.
/// before processing any transactions), call [`BEACON_ROOTS_ADDRESS`] as
/// [`SYSTEM_ADDRESS`](alloy_eips::eip4788::SYSTEM_ADDRESS) with the 32-byte input of
/// `header.parent_beacon_block_root`. This will trigger the `set()` routine of the beacon roots
/// contract.
fn fill_tx_env_with_beacon_root_contract_call(env: &mut Env, parent_beacon_block_root: B256) {
fill_tx_env_with_system_contract_call(
env,
alloy_eips::eip4788::SYSTEM_ADDRESS,
BEACON_ROOTS_ADDRESS,
parent_beacon_block_root.0.into(),
);
}

/// Fill transaction environment with the system caller and the system contract address and message
/// data.
///
/// This is a system operation and therefore:
/// * the call must execute to completion
/// * the call does not count against the block’s gas limit
/// * the call does not follow the EIP-1559 burn semantics - no value should be transferred as part
/// of the call
/// * if no code exists at the provided address, the call will fail silently
fn fill_tx_env_with_system_contract_call(
env: &mut Env,
caller: Address,
contract: Address,
data: Bytes,
) {
env.tx = TxEnv {
caller,
transact_to: TransactTo::Call(contract),
// Explicitly set nonce to None so revm does not do any nonce checks
nonce: None,
gas_limit: 30_000_000,
value: U256::ZERO,
data,
// Setting the gas price to zero enforces that no value is transferred as part of the call,
// and that the call will not count against the block's gas limit
gas_price: U256::ZERO,
// The chain ID check is not relevant here and is disabled if set to None
chain_id: None,
// Setting the gas priority fee to None ensures the effective gas price is derived from the
// `gas_price` field, which we need to be zero
gas_priority_fee: None,
access_list: Vec::new(),
// blob fields can be None for this tx
blob_hashes: Vec::new(),
max_fee_per_blob_gas: None,
optimism: OptimismFields {
source_hash: None,
mint: None,
is_system_transaction: Some(false),
// The L1 fee is not charged for the EIP-4788 transaction, submit zero bytes for the
// enveloped tx size.
enveloped_tx: Some(Bytes::default()),
},
};

// ensure the block gas limit is >= the tx
env.block.gas_limit = U256::from(env.tx.gas_limit);

// disable the base fee check for this call by setting the base fee to zero
env.block.basefee = U256::ZERO;
}
58 changes: 58 additions & 0 deletions bin/programs/client/src/l2/executor/fetcher.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//! Contains the fetcher construction functions for the block executor's [TrieDB].
//!
//! [TrieDB]: kona_mpt::TrieDB

use crate::CachingOracle;
use alloy_consensus::Header;
use alloy_primitives::{Bytes, B256};
use alloy_rlp::Decodable;
use anyhow::{anyhow, Result};
use kona_mpt::TrieDBFetcher;
use kona_preimage::{PreimageKey, PreimageKeyType, PreimageOracleClient};

/// The [TrieDBFetcher] implementation for the block executor's [TrieDB].
///
/// TODO: Move this into the higher-level L2 chain fetcher, and also implement the [TrieDBFetcher]
/// trait.
///
/// [TrieDB]: kona_mpt::TrieDB
#[derive(Debug)]
pub struct TrieDBProvider<'a, const N: usize> {
/// The inner caching oracle to fetch trie node preimages from.
caching_oracle: &'a CachingOracle<N>,
}

impl<'a, const N: usize> TrieDBProvider<'a, N> {
/// Constructs a new [TrieDBProvider] with the given [CachingOracle].
pub fn new(caching_oracle: &'a CachingOracle<N>) -> Self {
Self { caching_oracle }
}
}

impl<'a, const N: usize> TrieDBFetcher for TrieDBProvider<'a, N> {
fn trie_node_preimage(&self, key: B256) -> Result<Bytes> {
// Fetch the trie preimage from the caching oracle.
kona_common::block_on(async move {
self.caching_oracle
.get(PreimageKey::new(*key, PreimageKeyType::Keccak256))
.await
.map(Into::into)
})
}

fn bytecode_by_hash(&self, _: B256) -> Result<Bytes> {
todo!()
}

fn header_by_hash(&self, hash: B256) -> Result<Header> {
// Fetch the header from the caching oracle.
kona_common::block_on(async move {
let header_bytes = self
.caching_oracle
.get(PreimageKey::new(*hash, PreimageKeyType::Keccak256))
.await?;
Header::decode(&mut header_bytes.as_slice())
.map_err(|e| anyhow!("Failed to RLP decode Header: {e}"))
})
}
}
Loading
Loading