From d4dd2aa62f4f85b232fdcfa9d5f85c88e66d912c Mon Sep 17 00:00:00 2001 From: Robin Salen Date: Wed, 6 Nov 2024 17:49:41 -0500 Subject: [PATCH] Move world into evm_arithmetization --- Cargo.lock | 7 + Cargo.toml | 4 +- evm_arithmetization/Cargo.toml | 11 +- evm_arithmetization/src/lib.rs | 2 + trace_decoder/Cargo.toml | 6 +- trace_decoder/src/core.rs | 10 +- trace_decoder/src/lib.rs | 25 -- trace_decoder/src/observer.rs | 2 +- trace_decoder/src/tries.rs | 452 --------------------------------- trace_decoder/src/type1.rs | 5 +- trace_decoder/src/type2.rs | 12 +- trace_decoder/src/world.rs | 434 ------------------------------- 12 files changed, 36 insertions(+), 934 deletions(-) delete mode 100644 trace_decoder/src/tries.rs delete mode 100644 trace_decoder/src/world.rs diff --git a/Cargo.lock b/Cargo.lock index 7fb230d98..69c75887b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2030,9 +2030,14 @@ dependencies = [ name = "evm_arithmetization" version = "0.4.0" dependencies = [ + "alloy", + "alloy-compat", "anyhow", + "bitvec", "bytes", + "copyvec", "criterion", + "either", "env_logger 0.11.5", "ethereum-types", "hashbrown", @@ -2051,6 +2056,7 @@ dependencies = [ "plonky2", "plonky2_maybe_rayon 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "plonky2_util", + "primitive-types 0.12.2", "rand", "rand_chacha", "ripemd", @@ -2068,6 +2074,7 @@ dependencies = [ "tokio", "tower-lsp", "tracing", + "u4", "url", "zk_evm_common", "zk_evm_proc_macro", diff --git a/Cargo.toml b/Cargo.toml index c56be95a0..f8c6be0f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,7 +45,8 @@ bytes = "1.6.0" ciborium = "0.2.2" ciborium-io = "0.2.2" clap = { version = "4.5.7", features = ["derive", "env"] } -compat = { path = "compat" } +alloy-compat = "0.1.0" +copyvec = "0.2.0" criterion = "0.5.1" dotenvy = "0.15.7" either = "1.12.0" @@ -104,6 +105,7 @@ url = "2.5.2" winnow = "0.6.13" # local dependencies +compat = { path = "compat" } evm_arithmetization = { path = "evm_arithmetization", version = "0.4.0", default-features = false } mpt_trie = { path = "mpt_trie", version = "0.4.1" } smt_trie = { path = "smt_trie", version = "0.1.1" } diff --git a/evm_arithmetization/Cargo.toml b/evm_arithmetization/Cargo.toml index f5dfec2f2..b7654d22d 100644 --- a/evm_arithmetization/Cargo.toml +++ b/evm_arithmetization/Cargo.toml @@ -15,8 +15,14 @@ homepage.workspace = true keywords.workspace = true [dependencies] +__compat_primitive_types.workspace = true # TODO(#229) +alloy.workspace = true +alloy-compat.workspace = true anyhow.workspace = true +bitvec.workspace = true bytes.workspace = true +copyvec.workspace = true +either.workspace = true env_logger.workspace = true ethereum-types.workspace = true hashbrown.workspace = true @@ -43,7 +49,7 @@ serde = { workspace = true, features = ["derive"] } serde-big-array.workspace = true serde_json.workspace = true sha2.workspace = true -smt_trie = { workspace = true, optional = true } +smt_trie = { workspace = true } starky = { workspace = true, features = ["parallel"] } static_assertions.workspace = true thiserror.workspace = true @@ -51,6 +57,7 @@ tiny-keccak.workspace = true tokio.workspace = true tower-lsp = "0.20.0" tracing.workspace = true +u4.workspace = true url.workspace = true zk_evm_common.workspace = true zk_evm_proc_macro.workspace = true @@ -64,7 +71,7 @@ ripemd.workspace = true default = ["eth_mainnet"] asmtools = ["hex"] polygon_pos = [] -cdk_erigon = ["smt_trie"] +cdk_erigon = [] eth_mainnet = [] [[bin]] diff --git a/evm_arithmetization/src/lib.rs b/evm_arithmetization/src/lib.rs index 41b7f093a..9bc6021e2 100644 --- a/evm_arithmetization/src/lib.rs +++ b/evm_arithmetization/src/lib.rs @@ -284,7 +284,9 @@ pub mod witness; pub mod curve_pairings; pub mod extension_tower; pub mod testing_utils; +pub mod tries; pub mod util; +pub mod world; // Public definitions and re-exports mod public_types; diff --git a/trace_decoder/Cargo.toml b/trace_decoder/Cargo.toml index 4b8d8e7bc..da9394823 100644 --- a/trace_decoder/Cargo.toml +++ b/trace_decoder/Cargo.toml @@ -11,14 +11,14 @@ keywords.workspace = true [dependencies] alloy.workspace = true -alloy-compat = "0.1.0" +alloy-compat.workspace = true anyhow.workspace = true bitflags.workspace = true bitvec.workspace = true bytes.workspace = true ciborium.workspace = true ciborium-io.workspace = true -copyvec = "0.2.0" +copyvec.workspace = true either.workspace = true enum-as-inner.workspace = true ethereum-types.workspace = true @@ -43,7 +43,7 @@ zk_evm_common.workspace = true [dev-dependencies] alloy.workspace = true -alloy-compat = "0.1.0" +alloy-compat.workspace = true assert2 = "0.3.15" camino = "1.1.9" clap.workspace = true diff --git a/trace_decoder/src/core.rs b/trace_decoder/src/core.rs index b34117911..71ac3599a 100644 --- a/trace_decoder/src/core.rs +++ b/trace_decoder/src/core.rs @@ -11,6 +11,8 @@ use ethereum_types::{Address, BigEndianHash as _, U256}; use evm_arithmetization::{ generation::TrieInputs, proof::{BlockMetadata, TrieRoots}, + tries::{MptKey, ReceiptTrie, StateMpt, StorageTrie, TransactionTrie}, + world::{Hasher, KeccakHash, PoseidonHash, Type1World, Type2World, World}, GenerationInputs, }; use itertools::Itertools as _; @@ -19,14 +21,8 @@ use mpt_trie::partial_trie::PartialTrie as _; use nunny::NonEmpty; use zk_evm_common::gwei_to_wei; +use crate::observer::{DummyObserver, Observer}; use crate::{ - observer::{DummyObserver, Observer}, - world::Type2World, - Hasher, KeccakHash, PoseidonHash, -}; -use crate::{ - tries::{MptKey, ReceiptTrie, StateMpt, StorageTrie, TransactionTrie}, - world::{Type1World, World}, BlockLevelData, BlockTrace, BlockTraceTriePreImages, CombinedPreImages, ContractCodeUsage, OtherBlockData, SeparateStorageTriesPreImage, SeparateTriePreImage, SeparateTriePreImages, TxnInfo, TxnMeta, TxnTrace, diff --git a/trace_decoder/src/lib.rs b/trace_decoder/src/lib.rs index b89332e5f..b0221f24d 100644 --- a/trace_decoder/src/lib.rs +++ b/trace_decoder/src/lib.rs @@ -62,14 +62,10 @@ const _DEVELOPER_DOCS: () = (); mod interface; pub use interface::*; -use keccak_hash::H256; -use smt_trie::code::hash_bytecode_h256; -mod tries; mod type1; mod type2; mod wire; -mod world; pub use core::{entrypoint, WireDisposition}; @@ -103,27 +99,6 @@ mod hex { } } -/// Utility trait to leverage a specific hash function across Type1 and Type2 -/// zkEVM variants. -pub(crate) trait Hasher { - fn hash(bytes: &[u8]) -> H256; -} - -pub(crate) struct PoseidonHash; -pub(crate) struct KeccakHash; - -impl Hasher for PoseidonHash { - fn hash(bytes: &[u8]) -> H256 { - hash_bytecode_h256(bytes) - } -} - -impl Hasher for KeccakHash { - fn hash(bytes: &[u8]) -> H256 { - keccak_hash::keccak(bytes) - } -} - #[cfg(test)] #[derive(serde::Deserialize)] struct Case { diff --git a/trace_decoder/src/observer.rs b/trace_decoder/src/observer.rs index 428cac086..3d19c700d 100644 --- a/trace_decoder/src/observer.rs +++ b/trace_decoder/src/observer.rs @@ -1,9 +1,9 @@ use std::marker::PhantomData; use ethereum_types::U256; +use evm_arithmetization::tries::{ReceiptTrie, TransactionTrie}; use crate::core::IntraBlockTries; -use crate::tries::{ReceiptTrie, TransactionTrie}; /// Observer API for the trace decoder. /// Observer is used to collect various debugging and metadata info diff --git a/trace_decoder/src/tries.rs b/trace_decoder/src/tries.rs deleted file mode 100644 index 7da8d2cfa..000000000 --- a/trace_decoder/src/tries.rs +++ /dev/null @@ -1,452 +0,0 @@ -//! Principled trie types used in this library. - -use core::fmt; -use std::cmp; - -use anyhow::ensure; -use bitvec::{array::BitArray, slice::BitSlice}; -use copyvec::CopyVec; -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}; - -/// Bounded sequence of [`U4`], -/// used as a key for [MPT](HashedPartialTrie) types in this module. -/// -/// Semantically equivalent to [`mpt_trie::nibbles::Nibbles`]. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] -pub struct MptKey(CopyVec); - -impl fmt::Display for MptKey { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - for u in self.0 { - f.write_fmt(format_args!("{:x}", u))? - } - Ok(()) - } -} - -impl MptKey { - pub fn new(components: impl IntoIterator) -> anyhow::Result { - Ok(MptKey(CopyVec::try_from_iter(components)?)) - } - 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") - } - - pub fn from_txn_ix(txn_ix: usize) -> Self { - MptKey::new(AsNibbles(rlp::encode(&txn_ix))).expect( - "\ - rlp of an usize goes through a u64, which is 8 bytes, - which will be 9 bytes RLP'ed. - 9 < 32", - ) - } - pub fn into_nibbles(self) -> mpt_trie::nibbles::Nibbles { - let mut theirs = mpt_trie::nibbles::Nibbles::default(); - for component in self.0 { - theirs.push_nibble_back(component as u8) - } - theirs - } - pub fn from_nibbles(mut theirs: mpt_trie::nibbles::Nibbles) -> Self { - let mut ours = CopyVec::new(); - while !theirs.is_empty() { - ours.try_push( - U4::new(theirs.pop_next_nibble_front()) - .expect("mpt_trie returned an invalid nibble"), - ) - .expect("mpt_trie should not have more than 64 nibbles") - } - Self(ours) - } - - pub fn into_hash(self) -> Option { - let Self(nibbles) = self; - let mut bytes = [0; 32]; - AsNibbles(&mut bytes).pack_from_slice(&nibbles.into_array()?); - Some(H256(bytes)) - } -} - -impl From
for MptKey { - fn from(value: Address) -> Self { - Self::from_hash(keccak_hash::keccak(value)) - } -} - -#[test] -fn mpt_key_into_hash() { - assert_eq!(MptKey::new([]).unwrap().into_hash(), None); - assert_eq!( - MptKey::new(itertools::repeat_n(u4::u4!(0), 64)) - .unwrap() - .into_hash(), - Some(H256::zero()) - ) -} - -/// Bounded sequence of bits, -/// used as a key for SMT tries. -/// -/// Semantically equivalent to [`smt_trie::bits::Bits`]. -#[derive(Clone, Copy)] -pub struct SmtKey { - bits: bitvec::array::BitArray<[u8; 32]>, - len: usize, -} - -impl SmtKey { - fn as_bitslice(&self) -> &BitSlice { - self.bits.as_bitslice().get(..self.len).unwrap() - } -} - -impl fmt::Debug for SmtKey { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_list() - .entries(self.as_bitslice().iter().map(|it| match *it { - true => 1, - false => 0, - })) - .finish() - } -} - -impl fmt::Display for SmtKey { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - for bit in self.as_bitslice() { - f.write_str(match *bit { - true => "1", - false => "0", - })? - } - Ok(()) - } -} - -impl SmtKey { - pub fn new(components: impl IntoIterator) -> anyhow::Result { - let mut bits = bitvec::array::BitArray::default(); - let mut len = 0; - for (ix, bit) in components.into_iter().enumerate() { - ensure!( - bits.get(ix).is_some(), - "expected at most {} components", - bits.len() - ); - bits.set(ix, bit); - len += 1 - } - Ok(Self { bits, len }) - } - - pub fn into_smt_bits(self) -> smt_trie::bits::Bits { - let mut bits = smt_trie::bits::Bits::default(); - for bit in self.as_bitslice() { - bits.push_bit(*bit) - } - bits - } -} - -impl From
for SmtKey { - fn from(addr: Address) -> Self { - let H256(bytes) = keccak_hash::keccak(addr); - Self::new(BitArray::<_>::new(bytes)).expect("SmtKey has room for 256 bits") - } -} - -impl Ord for SmtKey { - fn cmp(&self, other: &Self) -> cmp::Ordering { - self.as_bitslice().cmp(other.as_bitslice()) - } -} -impl PartialOrd for SmtKey { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} -impl Eq for SmtKey {} -impl PartialEq for SmtKey { - fn eq(&self, other: &Self) -> bool { - self.as_bitslice().eq(other.as_bitslice()) - } -} - -/// Per-block, `txn_ix -> [u8]`. -/// -/// See -#[derive(Debug, Clone, Default)] -pub struct TransactionTrie { - untyped: HashedPartialTrie, -} - -impl TransactionTrie { - pub fn new() -> Self { - Self::default() - } - pub fn insert(&mut self, txn_ix: usize, val: Vec) -> anyhow::Result>> { - let prev = self - .untyped - .get(MptKey::from_txn_ix(txn_ix).into_nibbles()) - .map(Vec::from); - self.untyped - .insert(MptKey::from_txn_ix(txn_ix).into_nibbles(), val)?; - Ok(prev) - } - pub fn root(&self) -> H256 { - self.untyped.hash() - } - pub const fn as_hashed_partial_trie(&self) -> &mpt_trie::partial_trie::HashedPartialTrie { - &self.untyped - } - /// _Hash out_ parts of the trie that aren't in `txn_ixs`. - pub fn mask(&mut self, txn_ixs: impl IntoIterator) -> anyhow::Result<()> { - self.untyped = mpt_trie::trie_subsets::create_trie_subset( - &self.untyped, - txn_ixs - .into_iter() - .map(|it| MptKey::from_txn_ix(it).into_nibbles()), - )?; - Ok(()) - } -} - -impl From for HashedPartialTrie { - fn from(value: TransactionTrie) -> Self { - value.untyped - } -} - -/// Per-block, `txn_ix -> [u8]`. -/// -/// See -#[derive(Debug, Clone, Default)] -pub struct ReceiptTrie { - untyped: HashedPartialTrie, -} - -impl ReceiptTrie { - pub fn new() -> Self { - Self::default() - } - pub fn insert(&mut self, txn_ix: usize, val: Vec) -> anyhow::Result>> { - let prev = self - .untyped - .get(MptKey::from_txn_ix(txn_ix).into_nibbles()) - .map(Vec::from); - self.untyped - .insert(MptKey::from_txn_ix(txn_ix).into_nibbles(), val)?; - Ok(prev) - } - pub fn root(&self) -> H256 { - self.untyped.hash() - } - pub const fn as_hashed_partial_trie(&self) -> &mpt_trie::partial_trie::HashedPartialTrie { - &self.untyped - } - /// _Hash out_ parts of the trie that aren't in `txn_ixs`. - pub fn mask(&mut self, txn_ixs: impl IntoIterator) -> anyhow::Result<()> { - self.untyped = mpt_trie::trie_subsets::create_trie_subset( - &self.untyped, - txn_ixs - .into_iter() - .map(|it| MptKey::from_txn_ix(it).into_nibbles()), - )?; - Ok(()) - } -} - -impl From for HashedPartialTrie { - fn from(value: ReceiptTrie) -> Self { - value.untyped - } -} - -/// Global, [`Address`] `->` [`AccountRlp`]. -/// -/// See -#[derive(Debug, Clone)] -pub struct StateMpt { - /// Values are always [`rlp`]-encoded [`AccountRlp`], - /// inserted at [256 bits](MptKey::from_hash). - inner: HashedPartialTrie, -} - -impl Default for StateMpt { - fn default() -> Self { - Self::new() - } -} - -#[track_caller] -fn assert_rlp_account(bytes: impl AsRef<[u8]>) -> AccountRlp { - rlp::decode(bytes.as_ref()).expect("invalid RLP in StateMPT") -} - -impl StateMpt { - pub fn new() -> Self { - Self { - inner: HashedPartialTrie::new_with_strategy( - Node::Empty, - // This frontend is intended to be used with our custom `zeroTracer`, - // which covers branch-to-extension collapse edge cases. - OnOrphanedHashNode::CollapseToExtension, - ), - } - } - pub fn as_hashed_partial_trie(&self) -> &HashedPartialTrie { - &self.inner - } - /// Insert a _hashed out_ part of the trie - pub fn insert_hash(&mut self, key: MptKey, hash: H256) -> anyhow::Result<()> { - Ok(self.inner.insert(key.into_nibbles(), hash)?) - } - pub fn insert(&mut self, key: H256, account: AccountRlp) -> anyhow::Result<()> { - Ok(self.inner.insert( - MptKey::from_hash(key).into_nibbles(), - rlp::encode(&account).to_vec(), - )?) - } - pub fn get(&self, key: H256) -> Option { - self.inner - .get(MptKey::from_hash(key).into_nibbles()) - .map(assert_rlp_account) - } - pub fn root(&self) -> H256 { - self.inner.hash() - } - pub fn reporting_remove(&mut self, address: Address) -> anyhow::Result> { - delete_node_and_report_remaining_key_if_branch_collapsed( - &mut self.inner, - MptKey::from_hash(keccak_hash::keccak(address)), - ) - } - pub fn mask(&mut self, addresses: impl IntoIterator) -> anyhow::Result<()> { - let new = mpt_trie::trie_subsets::create_trie_subset( - &self.inner, - addresses.into_iter().map(MptKey::into_nibbles), - )?; - self.inner = new; - Ok(()) - } - pub fn iter(&self) -> impl Iterator + '_ { - self.inner.items().filter_map(|(key, rlp)| match rlp { - mpt_trie::trie_ops::ValOrHash::Val(vec) => Some(( - MptKey::from_nibbles(key).into_hash().expect("bad depth"), - assert_rlp_account(vec), - )), - mpt_trie::trie_ops::ValOrHash::Hash(_) => None, - }) - } -} - -impl From for HashedPartialTrie { - fn from(StateMpt { inner }: StateMpt) -> Self { - inner - } -} - -/// Global, per-account. -/// -/// See -#[derive(Debug, Clone, Default)] -pub struct StorageTrie { - untyped: HashedPartialTrie, -} -impl StorageTrie { - pub fn new(strategy: OnOrphanedHashNode) -> Self { - Self { - untyped: HashedPartialTrie::new_with_strategy(Node::Empty, strategy), - } - } - pub fn get(&mut self, key: &MptKey) -> Option<&[u8]> { - self.untyped.get(key.into_nibbles()) - } - pub fn insert(&mut self, key: MptKey, value: Vec) -> anyhow::Result<()> { - self.untyped.insert(key.into_nibbles(), value)?; - Ok(()) - } - pub fn insert_hash(&mut self, key: MptKey, hash: H256) -> anyhow::Result<()> { - self.untyped.insert(key.into_nibbles(), hash)?; - Ok(()) - } - pub fn root(&self) -> H256 { - self.untyped.hash() - } - pub const fn as_hashed_partial_trie(&self) -> &HashedPartialTrie { - &self.untyped - } - pub fn reporting_remove(&mut self, key: MptKey) -> anyhow::Result> { - delete_node_and_report_remaining_key_if_branch_collapsed(&mut self.untyped, key) - } - pub fn as_mut_hashed_partial_trie_unchecked(&mut self) -> &mut HashedPartialTrie { - &mut self.untyped - } - /// _Hash out_ the parts of the trie that aren't in `paths`. - pub fn mask(&mut self, paths: impl IntoIterator) -> anyhow::Result<()> { - self.untyped = mpt_trie::trie_subsets::create_trie_subset( - &self.untyped, - paths.into_iter().map(MptKey::into_nibbles), - )?; - Ok(()) - } -} - -impl From for HashedPartialTrie { - fn from(value: StorageTrie) -> Self { - value.untyped - } -} - -/// If a branch collapse occurred after a delete, then we must ensure that -/// the other single child that remains also is not hashed when passed into -/// plonky2. Returns the key to the remaining child if a collapse occurred. -fn delete_node_and_report_remaining_key_if_branch_collapsed( - trie: &mut HashedPartialTrie, - key: MptKey, -) -> anyhow::Result> { - let old_trace = get_trie_trace(trie, key); - trie.delete(key.into_nibbles())?; - let new_trace = get_trie_trace(trie, key); - Ok( - node_deletion_resulted_in_a_branch_collapse(&old_trace, &new_trace) - .map(MptKey::from_nibbles), - ) -} - -fn get_trie_trace(trie: &HashedPartialTrie, k: MptKey) -> mpt_trie::utils::TriePath { - mpt_trie::special_query::path_for_query(trie, k.into_nibbles(), true).collect() -} - -/// Comparing the path of the deleted key before and after the deletion, -/// determine if the deletion resulted in a branch collapsing into a leaf or -/// extension node, and return the path to the remaining child if this -/// occurred. -fn node_deletion_resulted_in_a_branch_collapse( - old_path: &mpt_trie::utils::TriePath, - new_path: &mpt_trie::utils::TriePath, -) -> Option { - // Collapse requires at least 2 nodes. - if old_path.0.len() < 2 { - return None; - } - - // If the node path length decreased after the delete, then a collapse occurred. - // As an aside, note that while it's true that the branch could have collapsed - // into an extension node with multiple nodes below it, the query logic will - // always stop at most one node after the keys diverge, which guarantees that - // the new trie path will always be shorter if a collapse occurred. - let branch_collapse_occurred = old_path.0.len() > new_path.0.len(); - - // Now we need to determine the key of the only remaining node after the - // collapse. - branch_collapse_occurred.then(|| mpt_trie::utils::IntoTrieKey::into_key(new_path.iter())) -} diff --git a/trace_decoder/src/type1.rs b/trace_decoder/src/type1.rs index 072bdbe3c..2d9e293d5 100644 --- a/trace_decoder/src/type1.rs +++ b/trace_decoder/src/type1.rs @@ -7,15 +7,14 @@ use std::collections::{BTreeMap, BTreeSet}; use anyhow::{bail, ensure, Context as _}; use either::Either; use evm_arithmetization::generation::mpt::AccountRlp; +use evm_arithmetization::tries::{MptKey, StateMpt, StorageTrie}; +use evm_arithmetization::world::{Hasher as _, Type1World, World}; use keccak_hash::H256; use mpt_trie::partial_trie::OnOrphanedHashNode; use nunny::NonEmpty; use u4::U4; -use crate::tries::{MptKey, StateMpt, StorageTrie}; use crate::wire::{Instruction, SmtLeaf}; -use crate::world::{Type1World, World}; -use crate::Hasher as _; #[derive(Debug, Clone, Default)] pub struct Frontend { diff --git a/trace_decoder/src/type2.rs b/trace_decoder/src/type2.rs index 4a380bc70..87af8a464 100644 --- a/trace_decoder/src/type2.rs +++ b/trace_decoder/src/type2.rs @@ -5,16 +5,16 @@ use std::collections::{BTreeMap, HashSet}; use anyhow::{bail, ensure, Context as _}; use ethereum_types::{Address, U256}; +use evm_arithmetization::{ + tries::SmtKey, + world::{Type2Entry, Type2World}, +}; use itertools::EitherOrBoth; use keccak_hash::H256; use nunny::NonEmpty; use stackstack::Stack; -use crate::{ - tries::SmtKey, - wire::{Instruction, SmtLeaf, SmtLeafType}, - world::{Type2Entry, Type2World}, -}; +use crate::wire::{Instruction, SmtLeaf, SmtLeafType}; pub struct Frontend { pub world: Type2World, @@ -175,7 +175,7 @@ fn visit( #[test] fn test_tries() { - use crate::world::World as _; + use evm_arithmetization::world::World as _; for (ix, case) in serde_json::from_str::>(include_str!("cases/hermez_cdk_erigon.json")) .unwrap() diff --git a/trace_decoder/src/world.rs b/trace_decoder/src/world.rs deleted file mode 100644 index 463914c53..000000000 --- a/trace_decoder/src/world.rs +++ /dev/null @@ -1,434 +0,0 @@ -use std::collections::{BTreeMap, BTreeSet}; - -use alloy_compat::Compat as _; -use anyhow::{ensure, Context as _}; -use either::Either; -use ethereum_types::{Address, BigEndianHash as _, U256}; -use keccak_hash::H256; - -use crate::{ - tries::{MptKey, SmtKey, StateMpt, StorageTrie}, - Hasher, KeccakHash, PoseidonHash, -}; - -/// The [core](crate::core) of this crate is agnostic over state and storage -/// representations. -/// -/// This is the common interface to those data structures. -/// See also [crate::_DEVELOPER_DOCS]. -pub(crate) trait World { - /// (State) subtries may be _hashed out. - /// This type is a key which may identify a subtrie. - type SubtriePath; - - /// Hasher to use for contract bytecode. - type CodeHasher: Hasher; - - ////////////////////// - /// Account operations - ////////////////////// - - /// Whether the state contains an account at the given address. - /// - /// `false` is not necessarily definitive - the address may belong to a - /// _hashed out_ subtrie. - fn contains(&mut self, address: Address) -> anyhow::Result; - - /// Update the balance for the account at the given address. - /// - /// Creates a new account at `address` if it does not exist. - fn update_balance(&mut self, address: Address, f: impl FnOnce(&mut U256)) - -> anyhow::Result<()>; - - /// Update the nonce for the account at the given address. - /// - /// Creates a new account at `address` if it does not exist. - fn update_nonce(&mut self, address: Address, f: impl FnOnce(&mut U256)) -> anyhow::Result<()>; - - /// Update the code for the account at the given address. - /// - /// Creates a new account at `address` if it does not exist. - fn set_code(&mut self, address: Address, code: Either<&[u8], H256>) -> anyhow::Result<()>; - - /// The [core](crate::core) of this crate tracks required subtries for - /// proving. - /// - /// In case of a state delete, it may be that certain parts of the subtrie - /// must be retained. If so, it will be returned as [`Some`]. - fn reporting_destroy(&mut self, address: Address) -> anyhow::Result>; - - ////////////////////// - /// Storage operations - ////////////////////// - - /// Create an account at the given address. - /// - /// It may not be an error if the address already exists. - fn create_storage(&mut self, address: Address) -> anyhow::Result<()>; - - /// Destroy storage for the given address' account. - fn destroy_storage(&mut self, address: Address) -> anyhow::Result<()>; - - /// Store an integer for the given account at the given `slot`. - fn store_int(&mut self, address: Address, slot: U256, value: U256) -> anyhow::Result<()>; - fn store_hash(&mut self, address: Address, hash: H256, value: H256) -> anyhow::Result<()>; - - /// Load an integer from the given account at the given `slot`. - fn load_int(&mut self, address: Address, slot: U256) -> anyhow::Result; - - /// Delete the given slot from the given account's storage. - /// - /// In case of a delete, it may be that certain parts of the subtrie - /// must be retained. If so, it will be returned as [`Some`]. - fn reporting_destroy_slot( - &mut self, - address: Address, - slot: U256, - ) -> anyhow::Result>; - fn mask_storage(&mut self, masks: BTreeMap>) -> anyhow::Result<()>; - - //////////////////// - /// Other operations - //////////////////// - - /// _Hash out_ parts of the (state) trie that aren't in `paths`. - fn mask(&mut self, paths: impl IntoIterator) -> anyhow::Result<()>; - - /// Return an identifier for the world. - fn root(&mut self) -> H256; -} - -#[derive(Clone, Debug)] -pub struct Type1World { - state: StateMpt, - /// Writes to storage should be reconciled with - /// [`storage_root`](evm_arithmetization::generation::mpt::AccountRlp)s. - storage: BTreeMap, -} - -impl Type1World { - pub fn new(state: StateMpt, mut storage: BTreeMap) -> anyhow::Result { - // Initialise the storage tries. - for (haddr, acct) in state.iter() { - let storage = storage.entry(haddr).or_insert_with(|| { - let mut it = StorageTrie::default(); - it.insert_hash(MptKey::default(), acct.storage_root) - .expect("empty trie insert cannot fail"); - it - }); - ensure!( - storage.root() == acct.storage_root, - "inconsistent initial storage for hashed address {haddr}" - ) - } - Ok(Self { state, storage }) - } - pub fn state_trie(&self) -> &mpt_trie::partial_trie::HashedPartialTrie { - self.state.as_hashed_partial_trie() - } - pub fn into_state_and_storage(self) -> (StateMpt, BTreeMap) { - let Self { state, storage } = self; - (state, storage) - } - fn get_storage_mut(&mut self, address: Address) -> anyhow::Result<&mut StorageTrie> { - self.storage - .get_mut(&keccak_hash::keccak(address)) - .context("no such storage") - } - fn on_storage( - &mut self, - address: Address, - f: impl FnOnce(&mut StorageTrie) -> anyhow::Result, - ) -> anyhow::Result { - let mut acct = self - .state - .get(keccak_hash::keccak(address)) - .context("no such account")?; - let storage = self.get_storage_mut(address)?; - let ret = f(storage)?; - acct.storage_root = storage.root(); - self.state.insert(keccak_hash::keccak(address), acct)?; - Ok(ret) - } -} - -impl World for Type1World { - type SubtriePath = MptKey; - type CodeHasher = KeccakHash; - - fn contains(&mut self, address: Address) -> anyhow::Result { - Ok(self.state.get(keccak_hash::keccak(address)).is_some()) - } - fn update_balance( - &mut self, - address: Address, - f: impl FnOnce(&mut U256), - ) -> anyhow::Result<()> { - let key = keccak_hash::keccak(address); - let mut acct = self.state.get(key).unwrap_or_default(); - f(&mut acct.balance); - self.state.insert(key, acct) - } - fn update_nonce(&mut self, address: Address, f: impl FnOnce(&mut U256)) -> anyhow::Result<()> { - let key = keccak_hash::keccak(address); - let mut acct = self.state.get(key).unwrap_or_default(); - f(&mut acct.nonce); - self.state.insert(key, acct) - } - fn set_code(&mut self, address: Address, code: Either<&[u8], H256>) -> anyhow::Result<()> { - let key = keccak_hash::keccak(address); - let mut acct = self.state.get(key).unwrap_or_default(); - acct.code_hash = code.right_or_else(Self::CodeHasher::hash); - self.state.insert(key, acct) - } - fn reporting_destroy(&mut self, address: Address) -> anyhow::Result> { - self.state.reporting_remove(address) - } - fn mask( - &mut self, - addresses: impl IntoIterator, - ) -> anyhow::Result<()> { - self.state.mask(addresses) - } - fn root(&mut self) -> H256 { - self.state.root() - } - fn create_storage(&mut self, address: Address) -> anyhow::Result<()> { - let _clobbered = self - .storage - .insert(keccak_hash::keccak(address), StorageTrie::default()); - // ensure!(_clobbered.is_none()); // TODO(0xaatif): fails our tests - Ok(()) - } - fn destroy_storage(&mut self, address: Address) -> anyhow::Result<()> { - let removed = self.storage.remove(&keccak_hash::keccak(address)); - ensure!(removed.is_some()); - Ok(()) - } - - fn store_int(&mut self, address: Address, slot: U256, value: U256) -> anyhow::Result<()> { - self.on_storage(address, |it| { - it.insert( - MptKey::from_slot_position(slot), - alloy::rlp::encode(value.compat()), - ) - }) - } - - fn store_hash(&mut self, address: Address, hash: H256, value: H256) -> anyhow::Result<()> { - self.on_storage(address, |it| { - it.insert(MptKey::from_hash(hash), alloy::rlp::encode(value.compat())) - }) - } - - fn load_int(&mut self, address: Address, slot: U256) -> anyhow::Result { - let bytes = self - .get_storage_mut(address)? - .get(&MptKey::from_slot_position(slot)) - .context(format!("no storage at slot {slot} for address {address:x}"))?; - Ok(rlp::decode(bytes)?) - } - - fn reporting_destroy_slot( - &mut self, - address: Address, - slot: U256, - ) -> anyhow::Result> { - self.on_storage(address, |it| { - it.reporting_remove(MptKey::from_slot_position(slot)) - }) - } - - fn mask_storage(&mut self, masks: BTreeMap>) -> anyhow::Result<()> { - let keep = masks - .keys() - .map(keccak_hash::keccak) - .collect::>(); - self.storage.retain(|haddr, _| keep.contains(haddr)); - for (addr, mask) in masks { - if let Some(it) = self.storage.get_mut(&keccak_hash::keccak(addr)) { - it.mask(mask)? - } - } - Ok(()) - } -} - -impl World for Type2World { - type SubtriePath = SmtKey; - type CodeHasher = PoseidonHash; - - fn contains(&mut self, address: Address) -> anyhow::Result { - Ok(self.accounts.contains_key(&address)) - } - fn update_balance( - &mut self, - address: Address, - f: impl FnOnce(&mut U256), - ) -> anyhow::Result<()> { - let acct = self.accounts.entry(address).or_default(); - f(acct.balance.get_or_insert(Default::default())); - Ok(()) - } - fn update_nonce(&mut self, address: Address, f: impl FnOnce(&mut U256)) -> anyhow::Result<()> { - let acct = self.accounts.entry(address).or_default(); - f(acct.nonce.get_or_insert(Default::default())); - Ok(()) - } - fn set_code(&mut self, address: Address, code: Either<&[u8], H256>) -> anyhow::Result<()> { - let acct = self.accounts.entry(address).or_default(); - match code { - Either::Left(bytes) => { - acct.code_length = Some(U256::from(bytes.len())); - if bytes.is_empty() { - acct.code_hash = None; - } else { - acct.code_hash = Some(Self::CodeHasher::hash(bytes).into_uint()); - } - } - Either::Right(hash) => acct.code_hash = Some(hash.into_uint()), - }; - Ok(()) - } - fn reporting_destroy(&mut self, address: Address) -> anyhow::Result> { - self.accounts.remove(&address); - Ok(None) - } - fn create_storage(&mut self, address: Address) -> anyhow::Result<()> { - let _ = address; - Ok(()) - } - fn destroy_storage(&mut self, address: Address) -> anyhow::Result<()> { - self.accounts - .entry(address) - .and_modify(|it| it.storage.clear()); - Ok(()) - } - fn store_int(&mut self, address: Address, slot: U256, value: U256) -> anyhow::Result<()> { - self.accounts - .entry(address) - .or_default() - .storage - .insert(slot, value); - Ok(()) - } - fn store_hash(&mut self, address: Address, hash: H256, value: H256) -> anyhow::Result<()> { - self.accounts - .entry(address) - .or_default() - .storage - .insert(hash.into_uint(), value.into_uint()); - Ok(()) - } - fn load_int(&mut self, address: Address, slot: U256) -> anyhow::Result { - Ok(self - .accounts - .get(&address) - .context("no account")? - .storage - .get(&slot) - .copied() - .unwrap_or_default()) - } - fn reporting_destroy_slot( - &mut self, - address: Address, - slot: U256, - ) -> anyhow::Result> { - self.accounts.entry(address).and_modify(|it| { - it.storage.remove(&slot); - }); - Ok(None) - } - fn mask_storage(&mut self, masks: BTreeMap>) -> anyhow::Result<()> { - let _ = masks; - Ok(()) - } - fn mask(&mut self, paths: impl IntoIterator) -> anyhow::Result<()> { - let _ = paths; - Ok(()) - } - fn root(&mut self) -> H256 { - let mut it = [0; 32]; - smt_trie::utils::hashout2u(self.as_smt().root).to_big_endian(&mut it); - H256(it) - } -} - -// Having optional fields here is an odd decision, -// but without the distinction, -// the wire tests fail. -// This may be a bug in the SMT library. -#[derive(Default, Clone, Debug)] -pub struct Type2Entry { - pub balance: Option, - pub nonce: Option, - pub code_hash: Option, - pub code_length: Option, - pub storage: BTreeMap, -} - -// This is a buffered version -#[derive(Clone, Debug)] -pub struct Type2World { - accounts: BTreeMap, - hashed_out: BTreeMap, -} - -impl Type2World { - /// # Panics - /// - On untrusted inputs: . - pub fn as_smt(&self) -> smt_trie::smt::Smt { - let mut smt = smt_trie::smt::Smt::::default(); - - for (key, hash) in &self.hashed_out { - smt.set_hash( - key.into_smt_bits(), - smt_trie::smt::HashOut { - elements: { - let ethereum_types::U256(arr) = hash.into_uint(); - arr.map(plonky2::field::goldilocks_field::GoldilocksField) - }, - }, - ); - } - for ( - addr, - Type2Entry { - balance, - nonce, - code_hash, - code_length, - storage, - }, - ) in self.accounts.iter() - { - use smt_trie::keys::{key_balance, key_code, key_code_length, key_nonce, key_storage}; - - for (value, key_fn) in [ - (balance, key_balance as fn(_) -> _), - (nonce, key_nonce), - (code_hash, key_code), - (code_length, key_code_length), - ] { - if let Some(value) = value { - smt.set(key_fn(*addr), *value); - } - } - for (slot, value) in storage { - smt.set(key_storage(*addr, *slot), *value); - } - } - smt - } - - pub fn new_unchecked( - accounts: BTreeMap, - hashed_out: BTreeMap, - ) -> Self { - Self { - accounts, - hashed_out, - } - } -}