diff --git a/evm_arithmetization/src/cpu/kernel/asm/beacon_roots.asm b/evm_arithmetization/src/cpu/kernel/asm/beacon_roots.asm index 17ade9e86..265d61b90 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/beacon_roots.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/beacon_roots.asm @@ -18,7 +18,6 @@ global set_beacon_root: %slot_to_storage_key // stack: timestamp_slot_key, timestamp, retdest PUSH @BEACON_ROOTS_CONTRACT_STATE_KEY - %addr_to_state_key %parent_beacon_block_root // stack: calldata, state_key, timestamp_slot_key, timestamp, retdest PUSH @HISTORY_BUFFER_LENGTH diff --git a/evm_arithmetization/src/cpu/kernel/constants/mod.rs b/evm_arithmetization/src/cpu/kernel/constants/mod.rs index 348c5b2c6..c738bb582 100644 --- a/evm_arithmetization/src/cpu/kernel/constants/mod.rs +++ b/evm_arithmetization/src/cpu/kernel/constants/mod.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use ethereum_types::{H256, U256}; +use ethereum_types::{Address, H160, H256, U256}; use hex_literal::hex; use crate::cpu::kernel::constants::context_metadata::ContextMetadata; @@ -93,8 +93,8 @@ pub(crate) fn evm_constants() -> HashMap { U256::from(global_exit_root::GLOBAL_EXIT_ROOT_STORAGE_POS.1), ); c.insert( - global_exit_root::ADDRESS_SCALABLE_L2.0.into(), - U256::from_big_endian(&global_exit_root::ADDRESS_SCALABLE_L2.1), + "ADDRESS_SCALABLE_L2".into(), + U256::from_big_endian(&global_exit_root::ADDRESS_SCALABLE_L2.to_fixed_bytes()), ); c.insert( global_exit_root::ADDRESS_SCALABLE_L2_STATE_KEY.0.into(), @@ -403,7 +403,6 @@ const LINKED_LISTS_CONSTANTS: [(&str, u16); 5] = [ /// See and /// . pub mod cancun_constants { - use ethereum_types::{Address, H160}; use super::*; @@ -443,9 +442,9 @@ pub mod cancun_constants { "37d65eaa92c6bc4c13a5ec45527f0c18ea8932588728769ec7aecfe6d9f32e42" )); - pub const BEACON_ROOTS_CONTRACT_STATE_KEY: (&str, [u8; 20]) = ( + pub const BEACON_ROOTS_CONTRACT_STATE_KEY: (&str, [u8; 32]) = ( "BEACON_ROOTS_CONTRACT_STATE_KEY", - *BEACON_ROOTS_CONTRACT_ADDRESS.as_fixed_bytes(), + *BEACON_ROOTS_CONTRACT_ADDRESS_HASHED.as_fixed_bytes(), ); pub const BEACON_ROOTS_CONTRACT_CODE: [u8; 97] = hex!("3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500"); pub const BEACON_ROOTS_CONTRACT_CODE_HASH: [u8; 32] = @@ -476,8 +475,10 @@ pub mod global_exit_root { /// Taken from https://github.com/0xPolygonHermez/cdk-erigon/blob/61f0b6912055c73f6879ea7e9b5bac22ea5fc85c/zk/utils/global_exit_root.go#L16. pub const GLOBAL_EXIT_ROOT_MANAGER_L2: (&str, [u8; 20]) = ( "GLOBAL_EXIT_ROOT_MANAGER_L2", - hex!("a40D5f56745a118D0906a34E69aeC8C0Db1cB8fA"), + GLOBAL_EXIT_ROOT_ADDRESS.to_fixed_bytes(), ); + pub const GLOBAL_EXIT_ROOT_ADDRESS: Address = + H160(hex!("a40D5f56745a118D0906a34E69aeC8C0Db1cB8fA")); pub const GLOBAL_EXIT_ROOT_ADDRESS_HASHED: H256 = H256(hex!( "1d5e9c22b4b1a781d0ef63e9c1293c2a45fee966809019aa9804b5e7148b0ca9" )); @@ -486,13 +487,12 @@ pub mod global_exit_root { GLOBAL_EXIT_ROOT_ADDRESS_HASHED.to_fixed_bytes(), ); /// Taken from https://github.com/0xPolygonHermez/cdk-erigon/blob/dc3cbcc59a95769626056c7bc70aade501e7741d/core/state/intra_block_state_zkevm.go#L20. - pub const ADDRESS_SCALABLE_L2: (&str, [u8; 20]) = ( - "ADDRESS_SCALABLE_L2", - hex!("000000000000000000000000000000005ca1ab1e"), - ); + pub const ADDRESS_SCALABLE_L2: Address = H160(hex!("000000000000000000000000000000005ca1ab1e")); + pub const ADDRESS_SCALABLE_L2_ADDRESS_HASHED: H256 = H256(hex!( "4bff39c4f33dafcd90c45c9a0a1f2e72949b200788587b306eda5dd84aa87577" )); + pub const ADDRESS_SCALABLE_L2_STATE_KEY: (&str, [u8; 32]) = ( "ADDRESS_SCALABLE_L2_STATE_KEY", ADDRESS_SCALABLE_L2_ADDRESS_HASHED.to_fixed_bytes(), @@ -524,11 +524,11 @@ pub mod global_exit_root { #[test] fn hashed() { assert_eq!( - keccak_hash::keccak(GLOBAL_EXIT_ROOT_MANAGER_L2.1), + keccak_hash::keccak(GLOBAL_EXIT_ROOT_ADDRESS), GLOBAL_EXIT_ROOT_ADDRESS_HASHED ); assert_eq!( - keccak_hash::keccak(ADDRESS_SCALABLE_L2.1), + keccak_hash::keccak(ADDRESS_SCALABLE_L2), ADDRESS_SCALABLE_L2_ADDRESS_HASHED ); } diff --git a/trace_decoder/Cargo.toml b/trace_decoder/Cargo.toml index 7b9aa83e2..8f6a63159 100644 --- a/trace_decoder/Cargo.toml +++ b/trace_decoder/Cargo.toml @@ -54,16 +54,16 @@ glob = "0.3.1" libtest-mimic = "0.7.3" plonky2_maybe_rayon = { workspace = true } pretty_assertions = "1.4.0" -zero = { workspace = true, features = ["eth_mainnet"] } +zero = { workspace = true } pretty_env_logger = { workspace = true } serde_json = { workspace = true } serde_path_to_error = { workspace = true } [features] default = ["eth_mainnet"] -eth_mainnet = ["evm_arithmetization/eth_mainnet"] -cdk_erigon = ["evm_arithmetization/cdk_erigon"] -polygon_pos = ["evm_arithmetization/polygon_pos"] +eth_mainnet = ["evm_arithmetization/eth_mainnet", "zero/eth_mainnet"] +cdk_erigon = ["evm_arithmetization/cdk_erigon", "zero/cdk_erigon"] +polygon_pos = ["evm_arithmetization/polygon_pos", "zero/polygon_pos"] [[bench]] name = "block_processing" diff --git a/trace_decoder/src/core.rs b/trace_decoder/src/core.rs index 64c0dc2ee..0c13d724c 100644 --- a/trace_decoder/src/core.rs +++ b/trace_decoder/src/core.rs @@ -7,11 +7,10 @@ use std::{ use alloy::primitives::address; use alloy_compat::Compat as _; use anyhow::{anyhow, bail, ensure, Context as _}; -use ethereum_types::{Address, U256}; +use ethereum_types::{Address, H160, U256}; use evm_arithmetization::{ generation::{mpt::AccountRlp, TrieInputs}, - proof::TrieRoots, - testing_utils::{BEACON_ROOTS_CONTRACT_ADDRESS, HISTORY_BUFFER_LENGTH}, + proof::{BlockMetadata, TrieRoots}, GenerationInputs, }; use itertools::Itertools as _; @@ -32,6 +31,7 @@ pub fn entrypoint( trace: BlockTrace, other: OtherBlockData, batch_size_hint: usize, + pre_execution: Option, ) -> anyhow::Result>> { ensure!(batch_size_hint != 0); @@ -53,6 +53,7 @@ pub fn entrypoint( checkpoint_state_trie_root, checkpoint_consolidated_hash, burn_addr, + ger_data, } = other; for (_, amt) in &mut withdrawals { @@ -64,9 +65,10 @@ pub fn entrypoint( storage, batch(txn_info, batch_size_hint), &mut code, - b_meta.block_timestamp, - b_meta.parent_beacon_block_root, + &b_meta, + ger_data, withdrawals, + pre_execution, )?; let mut running_gas_used = 0; @@ -96,7 +98,7 @@ pub fn entrypoint( }, signed_txns: byte_code.into_iter().map(Into::into).collect(), withdrawals, - ger_data: None, + ger_data, tries: TrieInputs { state_trie: state.into(), transactions_trie: transaction.into(), @@ -269,6 +271,7 @@ struct IntraBlockTries { } /// Does the main work mentioned in the [module documentation](super). +#[allow(clippy::too_many_arguments)] // TODO(0xaatif): break this out into a `struct MiddleArgs` fn middle( // state at the beginning of the block mut state_trie: StateTrieT, @@ -278,10 +281,11 @@ fn middle( // all batches SHOULD not be empty batches: Vec>>, code: &mut Hash2Code, - block_timestamp: U256, - parent_beacon_block_root: H256, + block: &BlockMetadata, + ger_data: Option<(H256, H256)>, // added to final batch mut withdrawals: Vec<(Address, U256)>, + pre_execution: Option, ) -> anyhow::Result>> { // Initialise the storage tries. for (haddr, acct) in state_trie.iter() { @@ -326,14 +330,25 @@ fn middle( let mut state_mask = BTreeSet::new(); if txn_ix == 0 { - do_beacon_hook( - block_timestamp, - &mut storage_tries, - &mut storage_masks, - parent_beacon_block_root, - &mut state_mask, - &mut state_trie, - )?; + match pre_execution { + Some(PreExecution::Beacon) => beacon_pre_execution( + block.block_timestamp, + &mut storage_tries, + &mut storage_masks, + block.parent_beacon_block_root, + &mut state_mask, + &mut state_trie, + )?, + Some(PreExecution::Scalable) => scalable_pre_execution( + block, + ger_data, + &mut storage_tries, + &mut storage_masks, + &mut state_mask, + &mut state_trie, + )?, + None => {} + } } for txn in batch { @@ -462,10 +477,24 @@ fn middle( state_mask.extend(state_trie.reporting_remove(addr)?) } - let precompiled_addresses = address!("0000000000000000000000000000000000000001") - ..address!("000000000000000000000000000000000000000a"); + fn is_precompile(addr: H160) -> bool { + let precompiled_addresses = if cfg!(feature = "eth_mainnet") { + address!("0000000000000000000000000000000000000001") + ..address!("000000000000000000000000000000000000000a") + } else { + // Remove KZG Peval for non-Eth mainnet networks + address!("0000000000000000000000000000000000000001") + ..address!("0000000000000000000000000000000000000009") + }; - if !precompiled_addresses.contains(&addr.compat()) { + precompiled_addresses.contains(&addr.compat()) + || (cfg!(feature = "polygon_pos") + // Include P256Verify for Polygon PoS + && addr.compat() + == address!("0000000000000000000000000000000000000100")) + } + + if !is_precompile(addr) { // TODO(0xaatif): https://github.com/0xPolygonZero/zk_evm/pull/613 // masking like this SHOULD be a space-saving optimization, // BUT if it's omitted, we actually get state root mismatches @@ -533,12 +562,121 @@ fn middle( Ok(out) } +/// What pre-execution procedure to perform. +#[derive(Debug)] +pub enum PreExecution { + /// See [`beacon_pre_execution`]. + Beacon, + /// See [`scalable_pre_execution`]. + Scalable, +} + +/// Updates the storage of the Scalable and GER contracts, according to +/// . +/// +/// This is Polygon-CDK-specific, and runs at the start of the block, +/// before any transactions (as per the Etrog specification). +fn scalable_pre_execution( + block: &BlockMetadata, + ger_data: Option<(H256, H256)>, + storage: &mut BTreeMap, + trim_storage: &mut BTreeMap>, + trim_state: &mut BTreeSet, + state_trie: &mut StateTrieT, +) -> anyhow::Result<()> { + use evm_arithmetization::testing_utils::{ + ADDRESS_SCALABLE_L2, ADDRESS_SCALABLE_L2_ADDRESS_HASHED, GLOBAL_EXIT_ROOT_ADDRESS, + GLOBAL_EXIT_ROOT_ADDRESS_HASHED, GLOBAL_EXIT_ROOT_STORAGE_POS, LAST_BLOCK_STORAGE_POS, + STATE_ROOT_STORAGE_POS, TIMESTAMP_STORAGE_POS, + }; + + if block.block_number.is_zero() { + return Err(anyhow!("Attempted to prove the Genesis block!")); + } + let scalable_storage = storage + .get_mut(&ADDRESS_SCALABLE_L2_ADDRESS_HASHED) + .context("missing scalable contract storage trie")?; + let scalable_trim = trim_storage.entry(ADDRESS_SCALABLE_L2).or_default(); + + let timestamp_slot_key = TrieKey::from_slot_position(U256::from(TIMESTAMP_STORAGE_POS.1)); + + let timestamp = scalable_storage + .get(×tamp_slot_key) + .map(rlp::decode::) + .unwrap_or(Ok(0.into()))?; + let timestamp = core::cmp::max(timestamp, block.block_timestamp); + + // Store block number and largest timestamp + + for (ix, u) in [ + (U256::from(LAST_BLOCK_STORAGE_POS.1), block.block_number), + (U256::from(TIMESTAMP_STORAGE_POS.1), timestamp), + ] { + let slot = TrieKey::from_slot_position(ix); + + // These values are never 0. + scalable_storage.insert(slot, alloy::rlp::encode(u.compat()))?; + scalable_trim.insert(slot); + } + + // Store previous block root hash + + let prev_block_root_hash = state_trie.root(); + let mut arr = [0; 64]; + (block.block_number - 1).to_big_endian(&mut arr[0..32]); + U256::from(STATE_ROOT_STORAGE_POS.1).to_big_endian(&mut arr[32..64]); + let slot = TrieKey::from_hash(keccak_hash::keccak(arr)); + + scalable_storage.insert(slot, alloy::rlp::encode(prev_block_root_hash.compat()))?; + scalable_trim.insert(slot); + + trim_state.insert(TrieKey::from_address(ADDRESS_SCALABLE_L2)); + let mut scalable_acct = state_trie + .get_by_address(ADDRESS_SCALABLE_L2) + .context("missing scalable contract address")?; + scalable_acct.storage_root = scalable_storage.root(); + state_trie + .insert_by_address(ADDRESS_SCALABLE_L2, scalable_acct) + // TODO(0xaatif): https://github.com/0xPolygonZero/zk_evm/issues/275 + // Add an entry API + .expect("insert must succeed with the same key as a successful `get`"); + + // Update GER contract's storage if necessary + if let Some((root, l1blockhash)) = ger_data { + let ger_storage = storage + .get_mut(&GLOBAL_EXIT_ROOT_ADDRESS_HASHED) + .context("missing GER contract storage trie")?; + let ger_trim = trim_storage.entry(GLOBAL_EXIT_ROOT_ADDRESS).or_default(); + + let mut arr = [0; 64]; + arr[0..32].copy_from_slice(&root.0); + U256::from(GLOBAL_EXIT_ROOT_STORAGE_POS.1).to_big_endian(&mut arr[32..64]); + let slot = TrieKey::from_hash(keccak_hash::keccak(arr)); + + ger_storage.insert(slot, alloy::rlp::encode(l1blockhash.compat()))?; + ger_trim.insert(slot); + + trim_state.insert(TrieKey::from_address(GLOBAL_EXIT_ROOT_ADDRESS)); + let mut ger_acct = state_trie + .get_by_address(GLOBAL_EXIT_ROOT_ADDRESS) + .context("missing GER contract address")?; + ger_acct.storage_root = ger_storage.root(); + state_trie + .insert_by_address(GLOBAL_EXIT_ROOT_ADDRESS, ger_acct) + // TODO(0xaatif): https://github.com/0xPolygonZero/zk_evm/issues/275 + // Add an entry API + .expect("insert must succeed with the same key as a successful `get`"); + } + + Ok(()) +} + /// Updates the storage of the beacon block root contract, /// according to /// -/// This is cancun-specific, and runs at the start of the block, +/// This is Cancun-specific, and runs at the start of the block, /// before any transactions (as per the EIP). -fn do_beacon_hook( +fn beacon_pre_execution( block_timestamp: U256, storage: &mut BTreeMap, trim_storage: &mut BTreeMap>, @@ -546,24 +684,27 @@ fn do_beacon_hook( trim_state: &mut BTreeSet, state_trie: &mut StateTrieT, ) -> anyhow::Result<()> { - let history_timestamp = block_timestamp % HISTORY_BUFFER_LENGTH.value; - let history_timestamp_next = history_timestamp + HISTORY_BUFFER_LENGTH.value; + use evm_arithmetization::testing_utils::{ + BEACON_ROOTS_CONTRACT_ADDRESS, BEACON_ROOTS_CONTRACT_ADDRESS_HASHED, HISTORY_BUFFER_LENGTH, + }; + + let timestamp_idx = block_timestamp % HISTORY_BUFFER_LENGTH.value; + let root_idx = timestamp_idx + HISTORY_BUFFER_LENGTH.value; let beacon_storage = storage - .get_mut(&keccak_hash::keccak(BEACON_ROOTS_CONTRACT_ADDRESS)) + .get_mut(&BEACON_ROOTS_CONTRACT_ADDRESS_HASHED) .context("missing beacon contract storage trie")?; let beacon_trim = trim_storage .entry(BEACON_ROOTS_CONTRACT_ADDRESS) .or_default(); + for (ix, u) in [ - (history_timestamp, block_timestamp), + (timestamp_idx, block_timestamp), ( - history_timestamp_next, + root_idx, U256::from_big_endian(parent_beacon_block_root.as_bytes()), ), ] { - let mut h = [0; 32]; - ix.to_big_endian(&mut h); - let slot = TrieKey::from_hash(keccak_hash::keccak(H256::from_slice(&h))); + let slot = TrieKey::from_slot_position(ix); beacon_trim.insert(slot); match u.is_zero() { diff --git a/trace_decoder/src/interface.rs b/trace_decoder/src/interface.rs index 901fef89a..ef2e64486 100644 --- a/trace_decoder/src/interface.rs +++ b/trace_decoder/src/interface.rs @@ -178,6 +178,11 @@ pub struct OtherBlockData { /// /// Only used if the `cfg_erigon` feature is activated. pub burn_addr: Option
, + /// The global exit root along with the l1blockhash to write to the GER + /// manager. + /// + /// Only used if the `cfg_erigon` feature is activated. + pub ger_data: Option<(H256, H256)>, } /// Data that is specific to a block and is constant for all txns in a given diff --git a/trace_decoder/src/typed_mpt.rs b/trace_decoder/src/typed_mpt.rs index 28a03aa33..dc56d54a1 100644 --- a/trace_decoder/src/typed_mpt.rs +++ b/trace_decoder/src/typed_mpt.rs @@ -4,7 +4,7 @@ use core::fmt; use std::{collections::BTreeMap, marker::PhantomData}; use copyvec::CopyVec; -use ethereum_types::{Address, H256}; +use ethereum_types::{Address, H256, U256}; use evm_arithmetization::generation::mpt::AccountRlp; use mpt_trie::partial_trie::{HashedPartialTrie, Node, OnOrphanedHashNode, PartialTrie as _}; use u4::{AsNibbles, U4}; @@ -126,6 +126,11 @@ impl TrieKey { pub fn from_address(address: Address) -> Self { Self::from_hash(keccak_hash::keccak(address)) } + pub fn from_slot_position(pos: U256) -> Self { + let mut bytes = [0; 32]; + pos.to_big_endian(&mut bytes); + Self::from_hash(keccak_hash::keccak(H256::from_slice(&bytes))) + } pub fn from_hash(H256(bytes): H256) -> Self { Self::new(AsNibbles(bytes)).expect("32 bytes is 64 nibbles, which fits") } @@ -422,8 +427,11 @@ impl StorageTrie { untyped: HashedPartialTrie::new_with_strategy(Node::Empty, strategy), } } + pub fn get(&mut self, key: &TrieKey) -> Option<&[u8]> { + self.untyped.get(key.into_nibbles()) + } pub fn insert(&mut self, key: TrieKey, value: Vec) -> anyhow::Result>> { - let prev = self.untyped.get(key.into_nibbles()).map(Vec::from); + let prev = self.get(&key).map(Vec::from); self.untyped.insert(key.into_nibbles(), value)?; Ok(prev) } diff --git a/zero/Cargo.toml b/zero/Cargo.toml index 7086ecbd6..3f8023313 100644 --- a/zero/Cargo.toml +++ b/zero/Cargo.toml @@ -61,9 +61,9 @@ vergen-git2 = { version = "1.0.0", features = ["build"] } [features] default = ["eth_mainnet"] -eth_mainnet = ["evm_arithmetization/eth_mainnet", "proof_gen/eth_mainnet"] -cdk_erigon = ["evm_arithmetization/cdk_erigon", "proof_gen/cdk_erigon"] -polygon_pos = ["evm_arithmetization/polygon_pos", "proof_gen/polygon_pos"] +eth_mainnet = ["evm_arithmetization/eth_mainnet", "proof_gen/eth_mainnet", "trace_decoder/eth_mainnet"] +cdk_erigon = ["evm_arithmetization/cdk_erigon", "proof_gen/cdk_erigon", "trace_decoder/cdk_erigon"] +polygon_pos = ["evm_arithmetization/polygon_pos", "proof_gen/polygon_pos", "trace_decoder/polygon_pos"] [lints] workspace = true diff --git a/zero/src/rpc/mod.rs b/zero/src/rpc/mod.rs index cf8ec09cf..88be3e79e 100644 --- a/zero/src/rpc/mod.rs +++ b/zero/src/rpc/mod.rs @@ -312,6 +312,13 @@ where } else { None }, + ger_data: if cfg!(feature = "cdk_erigon") { + // TODO: https://github.com/0xPolygonZero/zk_evm/issues/565 + // Retrieve the actual GER data from `cdk-erigon`. + None + } else { + None + }, }; Ok(other_data) } diff --git a/zero/src/rpc/native/state.rs b/zero/src/rpc/native/state.rs index 579c3f5a6..833bb9070 100644 --- a/zero/src/rpc/native/state.rs +++ b/zero/src/rpc/native/state.rs @@ -97,7 +97,7 @@ fn insert_beacon_roots_update( ) -> anyhow::Result<()> { use alloy::primitives::U256; use evm_arithmetization::testing_utils::{ - BEACON_ROOTS_CONTRACT_STATE_KEY, HISTORY_BUFFER_LENGTH, + BEACON_ROOTS_CONTRACT_ADDRESS, HISTORY_BUFFER_LENGTH, }; let timestamp = U256::from(block.header.timestamp); @@ -107,7 +107,7 @@ fn insert_beacon_roots_update( (timestamp % chunk).into(), // timestamp_idx ((timestamp % chunk) + chunk).into(), // root_idx ]); - state_access.insert(BEACON_ROOTS_CONTRACT_STATE_KEY.1.into(), keys); + state_access.insert(BEACON_ROOTS_CONTRACT_ADDRESS.as_fixed_bytes().into(), keys); Ok(()) }