From e153c87781d51b069936d7dfee03c7d274a1761d Mon Sep 17 00:00:00 2001 From: 0xaatif Date: Fri, 23 Aug 2024 13:49:07 +0100 Subject: [PATCH 01/13] mark: 0xaatif/typed-smt2 From 0c0ab2368a0bd5ea4ddedc907b9bc85e8bfcf3b4 Mon Sep 17 00:00:00 2001 From: 0xaatif Date: Fri, 23 Aug 2024 13:53:53 +0100 Subject: [PATCH 02/13] refactor: remove dead code --- trace_decoder/src/type1.rs | 2 +- trace_decoder/src/typed_mpt.rs | 23 ----------------------- 2 files changed, 1 insertion(+), 24 deletions(-) diff --git a/trace_decoder/src/type1.rs b/trace_decoder/src/type1.rs index 527fc2588..3353d0ea4 100644 --- a/trace_decoder/src/type1.rs +++ b/trace_decoder/src/type1.rs @@ -395,7 +395,7 @@ fn test_tries() { let frontend = frontend(instructions).unwrap(); assert_eq!(case.expected_state_root, frontend.state.root()); - for (path, acct) in &frontend.state { + for (path, acct) in frontend.state.iter() { if acct.storage_root != StateTrie::default().root() { assert!(frontend.storage.contains_key(&path)) } diff --git a/trace_decoder/src/typed_mpt.rs b/trace_decoder/src/typed_mpt.rs index f0346d746..b43aa5c72 100644 --- a/trace_decoder/src/typed_mpt.rs +++ b/trace_decoder/src/typed_mpt.rs @@ -63,16 +63,6 @@ impl TypedMpt { let bytes = self.inner.get(key.into_nibbles())?; Some(rlp::decode(bytes).expect(Self::PANIC_MSG)) } - fn remove(&mut self, key: TrieKey) -> Result, Error> - where - T: rlp::Decodable, - { - match self.inner.delete(key.into_nibbles()) { - Ok(Some(it)) => Ok(Some(rlp::decode(&it).expect(Self::PANIC_MSG))), - Ok(None) => Ok(None), - Err(source) => Err(Error { source }), - } - } fn as_hashed_partial_trie(&self) -> &HashedPartialTrie { &self.inner } @@ -305,9 +295,6 @@ impl StateTrie { pub fn as_mut_hashed_partial_trie_unchecked(&mut self) -> &mut HashedPartialTrie { self.typed.as_mut_hashed_partial_trie_unchecked() } - pub fn remove_address(&mut self, address: Address) -> Result, Error> { - self.typed.remove(TrieKey::from_address(address)) - } pub fn contains_address(&self, address: Address) -> bool { self.typed .as_hashed_partial_trie() @@ -328,16 +315,6 @@ impl StateTrie { } } -impl<'a> IntoIterator for &'a StateTrie { - type Item = (TrieKey, AccountRlp); - - type IntoIter = Box + 'a>; - - fn into_iter(self) -> Self::IntoIter { - self.typed.into_iter() - } -} - /// Global, per-account. /// /// See From b0a8ef3520ce4c7ff160aadc395fa26b15730c5d Mon Sep 17 00:00:00 2001 From: 0xaatif Date: Fri, 23 Aug 2024 15:34:01 +0100 Subject: [PATCH 03/13] refactor: StateTrie::reporting_remove --- trace_decoder/src/decoding.rs | 28 ++++++++++------------------ trace_decoder/src/typed_mpt.rs | 10 ++++++++-- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/trace_decoder/src/decoding.rs b/trace_decoder/src/decoding.rs index 8cab99b7c..9e2b740e1 100644 --- a/trace_decoder/src/decoding.rs +++ b/trace_decoder/src/decoding.rs @@ -16,6 +16,7 @@ use mpt_trie::{ nibbles::Nibbles, partial_trie::{HashedPartialTrie, PartialTrie as _}, special_query::path_for_query, + trie_ops::TrieOpError, utils::{IntoTrieKey as _, TriePath}, }; @@ -360,12 +361,7 @@ fn apply_deltas_to_trie_state( if !receipt.status { // The transaction failed, hence any created account should be removed. - if let Some(remaining_account_key) = - delete_node_and_report_remaining_key_if_branch_collapsed( - trie_state.state.as_mut_hashed_partial_trie_unchecked(), - &TrieKey::from_hash(hash(addr)), - )? - { + if let Some(remaining_account_key) = trie_state.state.reporting_remove(*addr)? { out.additional_state_trie_paths_to_not_hash .push(remaining_account_key); trie_state.storage.remove(&hash(addr)); @@ -379,12 +375,7 @@ fn apply_deltas_to_trie_state( for addr in deltas.self_destructed_accounts.iter() { trie_state.storage.remove(&hash(addr)); - if let Some(remaining_account_key) = - delete_node_and_report_remaining_key_if_branch_collapsed( - trie_state.state.as_mut_hashed_partial_trie_unchecked(), - &TrieKey::from_hash(hash(addr)), - )? - { + if let Some(remaining_account_key) = trie_state.state.reporting_remove(*addr)? { out.additional_state_trie_paths_to_not_hash .push(remaining_account_key); } @@ -400,13 +391,14 @@ fn get_trie_trace(trie: &HashedPartialTrie, k: &Nibbles) -> TriePath { /// 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( +pub fn delete_node_and_report_remaining_key_if_branch_collapsed( trie: &mut HashedPartialTrie, - delete_k: &TrieKey, -) -> anyhow::Result> { - let old_trace = get_trie_trace(trie, &delete_k.into_nibbles()); - trie.delete(delete_k.into_nibbles())?; - let new_trace = get_trie_trace(trie, &delete_k.into_nibbles()); + key: &TrieKey, +) -> Result, TrieOpError> { + let key = key.into_nibbles(); + let old_trace = get_trie_trace(trie, &key); + trie.delete(key)?; + let new_trace = get_trie_trace(trie, &key); Ok( node_deletion_resulted_in_a_branch_collapse(&old_trace, &new_trace) .map(TrieKey::from_nibbles), diff --git a/trace_decoder/src/typed_mpt.rs b/trace_decoder/src/typed_mpt.rs index b43aa5c72..c2fbf2c1c 100644 --- a/trace_decoder/src/typed_mpt.rs +++ b/trace_decoder/src/typed_mpt.rs @@ -292,8 +292,14 @@ impl StateTrie { pub fn as_hashed_partial_trie(&self) -> &mpt_trie::partial_trie::HashedPartialTrie { self.typed.as_hashed_partial_trie() } - pub fn as_mut_hashed_partial_trie_unchecked(&mut self) -> &mut HashedPartialTrie { - self.typed.as_mut_hashed_partial_trie_unchecked() + /// Delete the account at `address`, returning any remaining branch on + /// collapse + pub fn reporting_remove(&mut self, address: Address) -> Result, Error> { + crate::decoding::delete_node_and_report_remaining_key_if_branch_collapsed( + self.typed.as_mut_hashed_partial_trie_unchecked(), + &TrieKey::from_address(address), + ) + .map_err(|source| Error { source }) } pub fn contains_address(&self, address: Address) -> bool { self.typed From 121dc15a4cb0e5fe38070264cc7201c917e08c36 Mon Sep 17 00:00:00 2001 From: 0xaatif Date: Fri, 23 Aug 2024 15:55:21 +0100 Subject: [PATCH 04/13] refactor: StateTrie::trim_to --- trace_decoder/src/decoding.rs | 72 ++++++++++++---------------------- trace_decoder/src/typed_mpt.rs | 24 ++++++------ 2 files changed, 36 insertions(+), 60 deletions(-) diff --git a/trace_decoder/src/decoding.rs b/trace_decoder/src/decoding.rs index 9e2b740e1..144dfe1b3 100644 --- a/trace_decoder/src/decoding.rs +++ b/trace_decoder/src/decoding.rs @@ -252,15 +252,14 @@ fn create_minimal_partial_tries_needed_by_txn( txn_range: Range, delta_application_out: TrieDeltaApplicationOutput, ) -> anyhow::Result { - let state_trie = create_minimal_state_partial_trie( - &curr_block_tries.state, - nodes_used_by_txn.state_accesses.iter().map(hash), - delta_application_out - .additional_state_trie_paths_to_not_hash - .into_iter(), - )? - .as_hashed_partial_trie() - .clone(); + let mut state_trie = curr_block_tries.state.clone(); + state_trie.trim_to( + nodes_used_by_txn + .state_accesses + .iter() + .map(|it| TrieKey::from_address(*it)) + .chain(delta_application_out.additional_state_trie_paths_to_not_hash), + )?; let txn_keys = txn_range.map(TrieKey::from_txn_ix); @@ -283,7 +282,7 @@ fn create_minimal_partial_tries_needed_by_txn( )?; Ok(TrieInputs { - state_trie, + state_trie: state_trie.as_hashed_partial_trie().clone(), transactions_trie, receipts_trie, storage_tries, @@ -452,25 +451,22 @@ fn add_withdrawals_to_txns( .expect("We cannot have an empty list of payloads."); if last_inputs.signed_txns.is_empty() { - // This is a dummy payload, hence it does not contain yet - // state accesses to the withdrawal addresses. - let withdrawal_addrs = withdrawals_with_hashed_addrs_iter().map(|(_, h_addr, _)| h_addr); - - let additional_paths = if last_inputs.txn_number_before == 0.into() { - // We need to include the beacon roots contract as this payload is at the - // start of the block execution. - vec![TrieKey::from_hash(BEACON_ROOTS_CONTRACT_ADDRESS_HASHED)] - } else { - vec![] - }; - - last_inputs.tries.state_trie = create_minimal_state_partial_trie( - &final_trie_state.state, - withdrawal_addrs, - additional_paths, - )? - .as_hashed_partial_trie() - .clone(); + let mut state_trie = final_trie_state.state.clone(); + state_trie.trim_to( + // This is a dummy payload, hence it does not contain yet + // state accesses to the withdrawal addresses. + withdrawals + .iter() + .map(|(addr, _)| *addr) + .chain(match last_inputs.txn_number_before == 0.into() { + // We need to include the beacon roots contract as this payload is at the + // start of the block execution. + true => Some(BEACON_ROOTS_CONTRACT_ADDRESS), + false => None, + }) + .map(TrieKey::from_address), + )?; + last_inputs.tries.state_trie = state_trie.as_hashed_partial_trie().clone(); } update_trie_state_from_withdrawals( @@ -637,22 +633,6 @@ impl StateWrite { } } -fn create_minimal_state_partial_trie( - state_trie: &StateTrie, - state_accesses: impl IntoIterator, - additional_state_trie_paths_to_not_hash: impl IntoIterator, -) -> anyhow::Result { - create_trie_subset_wrapped( - state_trie.as_hashed_partial_trie(), - state_accesses - .into_iter() - .map(TrieKey::from_hash) - .chain(additional_state_trie_paths_to_not_hash), - TrieType::State, - ) - .map(StateTrie::from_hashed_partial_trie_unchecked) -} - // TODO!!!: We really need to be appending the empty storage tries to the base // trie somewhere else! This is a big hack! fn create_minimal_storage_partial_tries<'a>( @@ -706,11 +686,9 @@ fn eth_to_gwei(eth: U256) -> U256 { const ZERO_STORAGE_SLOT_VAL_RLPED: [u8; 1] = [128]; /// Aid for error context. -/// Covers all Ethereum trie types (see for details). #[derive(Debug, strum::Display)] #[allow(missing_docs)] enum TrieType { - State, Storage, Receipt, Txn, diff --git a/trace_decoder/src/typed_mpt.rs b/trace_decoder/src/typed_mpt.rs index c2fbf2c1c..d73a55e31 100644 --- a/trace_decoder/src/typed_mpt.rs +++ b/trace_decoder/src/typed_mpt.rs @@ -135,7 +135,7 @@ impl TrieKey { AsNibbles(&mut packed).pack_from_slice(&self.0); H256::from_slice(&packed) } - fn from_address(address: Address) -> Self { + pub fn from_address(address: Address) -> Self { Self::from_hash(keccak_hash::keccak(address)) } pub fn from_hash(H256(bytes): H256) -> Self { @@ -306,18 +306,16 @@ impl StateTrie { .as_hashed_partial_trie() .contains(TrieKey::from_address(address).into_nibbles()) } - /// This allows users to break the [`TypedMpt`] invariant. - /// If data that isn't a [`rlp::encode`]-ed [`AccountRlp`] is inserted, - /// subsequent API calls may panic. - pub fn from_hashed_partial_trie_unchecked( - src: mpt_trie::partial_trie::HashedPartialTrie, - ) -> Self { - Self { - typed: TypedMpt { - inner: src, - _ty: PhantomData, - }, - } + pub fn trim_to(&mut self, addresses: impl IntoIterator) -> anyhow::Result<()> { + let inner = mpt_trie::trie_subsets::create_trie_subset( + self.typed.as_hashed_partial_trie(), + addresses.into_iter().map(TrieKey::into_nibbles), + )?; + self.typed = TypedMpt { + inner, + _ty: PhantomData, + }; + Ok(()) } } From 22327fad7fb6f7376f7047b8d5e78d545099a810 Mon Sep 17 00:00:00 2001 From: 0xaatif Date: Fri, 23 Aug 2024 16:04:33 +0100 Subject: [PATCH 05/13] refactor: typed_mpt::Error -> anyhow::Error --- trace_decoder/src/typed_mpt.rs | 65 +++++++++++++--------------------- 1 file changed, 25 insertions(+), 40 deletions(-) diff --git a/trace_decoder/src/typed_mpt.rs b/trace_decoder/src/typed_mpt.rs index d73a55e31..5ac08962c 100644 --- a/trace_decoder/src/typed_mpt.rs +++ b/trace_decoder/src/typed_mpt.rs @@ -6,10 +6,7 @@ use std::marker::PhantomData; use copyvec::CopyVec; use ethereum_types::{Address, H256}; use evm_arithmetization::generation::mpt::AccountRlp; -use mpt_trie::{ - partial_trie::{HashedPartialTrie, Node, OnOrphanedHashNode, PartialTrie as _}, - trie_ops::TrieOpError, -}; +use mpt_trie::partial_trie::{HashedPartialTrie, Node, OnOrphanedHashNode, PartialTrie as _}; use u4::{AsNibbles, U4}; /// Map where keys are [up to 64 nibbles](TrieKey), @@ -34,22 +31,20 @@ impl TypedMpt { } } /// Insert a node which represents an out-of-band sub-trie. - fn insert_hash(&mut self, key: TrieKey, hash: H256) -> Result<(), Error> { - self.inner - .insert(key.into_nibbles(), hash) - .map_err(|source| Error { source }) + fn insert_hash(&mut self, key: TrieKey, hash: H256) -> anyhow::Result<()> { + self.inner.insert(key.into_nibbles(), hash)?; + Ok(()) } /// Returns an [`Error`] if the `key` crosses into a part of the trie that /// isn't hydrated. - fn insert(&mut self, key: TrieKey, value: T) -> Result, Error> + fn insert(&mut self, key: TrieKey, value: T) -> anyhow::Result> where T: rlp::Encodable + rlp::Decodable, { let prev = self.get(key); self.inner - .insert(key.into_nibbles(), rlp::encode(&value).to_vec()) - .map_err(|source| Error { source }) - .map(|_| prev) + .insert(key.into_nibbles(), rlp::encode(&value).to_vec())?; + Ok(prev) } /// Note that this returns [`None`] if `key` crosses into a part of the /// trie that isn't hydrated. @@ -101,12 +96,6 @@ where } } -#[derive(thiserror::Error, Debug)] -#[error(transparent)] -pub struct Error { - source: TrieOpError, -} - /// Bounded sequence of [`U4`], /// used as a key for [`TypedMpt`]. /// @@ -197,14 +186,13 @@ pub struct TransactionTrie { } impl TransactionTrie { - pub fn insert(&mut self, txn_ix: usize, val: Vec) -> Result>, Error> { + pub fn insert(&mut self, txn_ix: usize, val: Vec) -> anyhow::Result>> { let prev = self .untyped .get(TrieKey::from_txn_ix(txn_ix).into_nibbles()) .map(Vec::from); self.untyped - .insert(TrieKey::from_txn_ix(txn_ix).into_nibbles(), val) - .map_err(|source| Error { source })?; + .insert(TrieKey::from_txn_ix(txn_ix).into_nibbles(), val)?; Ok(prev) } pub fn root(&self) -> H256 { @@ -224,14 +212,13 @@ pub struct ReceiptTrie { } impl ReceiptTrie { - pub fn insert(&mut self, txn_ix: usize, val: Vec) -> Result>, Error> { + pub fn insert(&mut self, txn_ix: usize, val: Vec) -> anyhow::Result>> { let prev = self .untyped .get(TrieKey::from_txn_ix(txn_ix).into_nibbles()) .map(Vec::from); self.untyped - .insert(TrieKey::from_txn_ix(txn_ix).into_nibbles(), val) - .map_err(|source| Error { source })?; + .insert(TrieKey::from_txn_ix(txn_ix).into_nibbles(), val)?; Ok(prev) } pub fn root(&self) -> H256 { @@ -263,7 +250,7 @@ impl StateTrie { &mut self, address: Address, account: AccountRlp, - ) -> Result, Error> { + ) -> anyhow::Result> { #[expect(deprecated)] self.insert_by_hashed_address(crate::hash(address), account) } @@ -272,11 +259,11 @@ impl StateTrie { &mut self, key: H256, account: AccountRlp, - ) -> Result, Error> { + ) -> anyhow::Result> { self.typed.insert(TrieKey::from_hash(key), account) } /// Insert a deferred part of the trie - pub fn insert_hash_by_key(&mut self, key: TrieKey, hash: H256) -> Result<(), Error> { + pub fn insert_hash_by_key(&mut self, key: TrieKey, hash: H256) -> anyhow::Result<()> { self.typed.insert_hash(key, hash) } pub fn get_by_address(&self, address: Address) -> Option { @@ -294,12 +281,13 @@ impl StateTrie { } /// Delete the account at `address`, returning any remaining branch on /// collapse - pub fn reporting_remove(&mut self, address: Address) -> Result, Error> { - crate::decoding::delete_node_and_report_remaining_key_if_branch_collapsed( - self.typed.as_mut_hashed_partial_trie_unchecked(), - &TrieKey::from_address(address), + pub fn reporting_remove(&mut self, address: Address) -> anyhow::Result> { + Ok( + crate::decoding::delete_node_and_report_remaining_key_if_branch_collapsed( + self.typed.as_mut_hashed_partial_trie_unchecked(), + &TrieKey::from_address(address), + )?, ) - .map_err(|source| Error { source }) } pub fn contains_address(&self, address: Address) -> bool { self.typed @@ -332,17 +320,14 @@ impl StorageTrie { untyped: HashedPartialTrie::new_with_strategy(Node::Empty, strategy), } } - pub fn insert(&mut self, key: TrieKey, value: Vec) -> Result>, Error> { + pub fn insert(&mut self, key: TrieKey, value: Vec) -> anyhow::Result>> { let prev = self.untyped.get(key.into_nibbles()).map(Vec::from); - self.untyped - .insert(key.into_nibbles(), value) - .map_err(|source| Error { source })?; + self.untyped.insert(key.into_nibbles(), value)?; Ok(prev) } - pub fn insert_hash(&mut self, key: TrieKey, hash: H256) -> Result<(), Error> { - self.untyped - .insert(key.into_nibbles(), hash) - .map_err(|source| Error { source }) + pub fn insert_hash(&mut self, key: TrieKey, hash: H256) -> anyhow::Result<()> { + self.untyped.insert(key.into_nibbles(), hash)?; + Ok(()) } pub fn root(&self) -> H256 { self.untyped.hash() From 01ee0a08dabcf8520dc1f5270d333e15b7a582d5 Mon Sep 17 00:00:00 2001 From: 0xaatif Date: Fri, 23 Aug 2024 16:05:23 +0100 Subject: [PATCH 06/13] refactor: StateTrie -> StateMpt --- trace_decoder/src/decoding.rs | 6 +++--- trace_decoder/src/lib.rs | 6 +++--- trace_decoder/src/type1.rs | 8 ++++---- trace_decoder/src/typed_mpt.rs | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/trace_decoder/src/decoding.rs b/trace_decoder/src/decoding.rs index 144dfe1b3..1f3260a8b 100644 --- a/trace_decoder/src/decoding.rs +++ b/trace_decoder/src/decoding.rs @@ -25,7 +25,7 @@ use crate::{ processed_block_trace::{ NodesUsedByTxn, ProcessedBlockTrace, ProcessedTxnInfo, StateWrite, TxnMetaState, }, - typed_mpt::{ReceiptTrie, StateTrie, StorageTrie, TransactionTrie, TrieKey}, + typed_mpt::{ReceiptTrie, StateMpt, StorageTrie, TransactionTrie, TrieKey}, OtherBlockData, PartialTriePreImages, }; @@ -33,7 +33,7 @@ use crate::{ /// after every txn we process in the trace. #[derive(Clone, Debug, Default)] struct PartialTrieState { - state: StateTrie, + state: StateMpt, storage: HashMap, txn: TransactionTrie, receipt: ReceiptTrie, @@ -484,7 +484,7 @@ fn add_withdrawals_to_txns( /// our local trie state. fn update_trie_state_from_withdrawals<'a>( withdrawals: impl IntoIterator + 'a, - state: &mut StateTrie, + state: &mut StateMpt, ) -> anyhow::Result<()> { for (addr, h_addr, amt) in withdrawals { let mut acc_data = state.get_by_address(addr).context(format!( diff --git a/trace_decoder/src/lib.rs b/trace_decoder/src/lib.rs index 14a94d29b..1c4079877 100644 --- a/trace_decoder/src/lib.rs +++ b/trace_decoder/src/lib.rs @@ -106,7 +106,7 @@ use keccak_hash::H256; use mpt_trie::partial_trie::{HashedPartialTrie, OnOrphanedHashNode}; use processed_block_trace::ProcessedTxnInfo; use serde::{Deserialize, Serialize}; -use typed_mpt::{StateTrie, StorageTrie, TrieKey}; +use typed_mpt::{StateMpt, StorageTrie, TrieKey}; /// Core payload needed to generate proof for a block. /// Additional data retrievable from the blockchain node (using standard ETH RPC @@ -311,7 +311,7 @@ pub fn entrypoint( }) => ProcessedBlockTracePreImages { tries: PartialTriePreImages { state: state.items().try_fold( - StateTrie::new(OnOrphanedHashNode::Reject), + StateMpt::new(OnOrphanedHashNode::Reject), |mut acc, (nibbles, hash_or_val)| { let path = TrieKey::from_nibbles(nibbles); match hash_or_val { @@ -449,7 +449,7 @@ pub fn entrypoint( #[derive(Debug, Default)] struct PartialTriePreImages { - pub state: StateTrie, + pub state: StateMpt, pub storage: HashMap, } diff --git a/trace_decoder/src/type1.rs b/trace_decoder/src/type1.rs index 3353d0ea4..7b785fada 100644 --- a/trace_decoder/src/type1.rs +++ b/trace_decoder/src/type1.rs @@ -11,12 +11,12 @@ use mpt_trie::partial_trie::OnOrphanedHashNode; use nunny::NonEmpty; use u4::U4; -use crate::typed_mpt::{StateTrie, StorageTrie, TrieKey}; +use crate::typed_mpt::{StateMpt, StorageTrie, TrieKey}; use crate::wire::{Instruction, SmtLeaf}; #[derive(Debug, Clone)] pub struct Frontend { - pub state: StateTrie, + pub state: StateMpt, pub code: BTreeSet>>, /// The key here matches the [`TriePath`] inside [`Self::state`] for /// accounts which had inline storage. @@ -28,7 +28,7 @@ impl Default for Frontend { // which covers branch-to-extension collapse edge cases. fn default() -> Self { Self { - state: StateTrie::new(OnOrphanedHashNode::CollapseToExtension), + state: StateMpt::new(OnOrphanedHashNode::CollapseToExtension), code: BTreeSet::new(), storage: BTreeMap::new(), } @@ -396,7 +396,7 @@ fn test_tries() { assert_eq!(case.expected_state_root, frontend.state.root()); for (path, acct) in frontend.state.iter() { - if acct.storage_root != StateTrie::default().root() { + if acct.storage_root != StateMpt::default().root() { assert!(frontend.storage.contains_key(&path)) } } diff --git a/trace_decoder/src/typed_mpt.rs b/trace_decoder/src/typed_mpt.rs index 5ac08962c..5a654d203 100644 --- a/trace_decoder/src/typed_mpt.rs +++ b/trace_decoder/src/typed_mpt.rs @@ -233,11 +233,11 @@ impl ReceiptTrie { /// /// See #[derive(Debug, Clone, Default)] -pub struct StateTrie { +pub struct StateMpt { typed: TypedMpt, } -impl StateTrie { +impl StateMpt { pub fn new(strategy: OnOrphanedHashNode) -> Self { Self { typed: TypedMpt { From ea30b6d95909627eb8c31cf7f8cd501bbe649b26 Mon Sep 17 00:00:00 2001 From: 0xaatif Date: Fri, 23 Aug 2024 16:23:19 +0100 Subject: [PATCH 07/13] wip --- trace_decoder/src/decoding.rs | 2 +- trace_decoder/src/lib.rs | 2 +- trace_decoder/src/processed_block_trace.rs | 2 +- trace_decoder/src/type1.rs | 2 +- trace_decoder/src/typed_mpt.rs | 93 +++++++++++++++++----- 5 files changed, 75 insertions(+), 26 deletions(-) diff --git a/trace_decoder/src/decoding.rs b/trace_decoder/src/decoding.rs index 1f3260a8b..c78fe451b 100644 --- a/trace_decoder/src/decoding.rs +++ b/trace_decoder/src/decoding.rs @@ -25,7 +25,7 @@ use crate::{ processed_block_trace::{ NodesUsedByTxn, ProcessedBlockTrace, ProcessedTxnInfo, StateWrite, TxnMetaState, }, - typed_mpt::{ReceiptTrie, StateMpt, StorageTrie, TransactionTrie, TrieKey}, + typed_mpt::{ReceiptTrie, StateMpt, StateTrie as _, StorageTrie, TransactionTrie, TrieKey}, OtherBlockData, PartialTriePreImages, }; diff --git a/trace_decoder/src/lib.rs b/trace_decoder/src/lib.rs index 1c4079877..ab5eb22b3 100644 --- a/trace_decoder/src/lib.rs +++ b/trace_decoder/src/lib.rs @@ -106,7 +106,7 @@ use keccak_hash::H256; use mpt_trie::partial_trie::{HashedPartialTrie, OnOrphanedHashNode}; use processed_block_trace::ProcessedTxnInfo; use serde::{Deserialize, Serialize}; -use typed_mpt::{StateMpt, StorageTrie, TrieKey}; +use typed_mpt::{StateMpt, StateTrie as _, StorageTrie, TrieKey}; /// Core payload needed to generate proof for a block. /// Additional data retrievable from the blockchain node (using standard ETH RPC diff --git a/trace_decoder/src/processed_block_trace.rs b/trace_decoder/src/processed_block_trace.rs index 480928444..842fbbc9a 100644 --- a/trace_decoder/src/processed_block_trace.rs +++ b/trace_decoder/src/processed_block_trace.rs @@ -6,7 +6,7 @@ use evm_arithmetization::generation::mpt::{AccountRlp, LegacyReceiptRlp}; use itertools::Itertools; use zk_evm_common::EMPTY_TRIE_HASH; -use crate::typed_mpt::TrieKey; +use crate::typed_mpt::{StateTrie as _, TrieKey}; use crate::PartialTriePreImages; use crate::{hash, TxnTrace}; use crate::{ContractCodeUsage, TxnInfo}; diff --git a/trace_decoder/src/type1.rs b/trace_decoder/src/type1.rs index 7b785fada..6221573ed 100644 --- a/trace_decoder/src/type1.rs +++ b/trace_decoder/src/type1.rs @@ -11,7 +11,7 @@ use mpt_trie::partial_trie::OnOrphanedHashNode; use nunny::NonEmpty; use u4::U4; -use crate::typed_mpt::{StateMpt, StorageTrie, TrieKey}; +use crate::typed_mpt::{StateMpt, StateTrie as _, StorageTrie, TrieKey}; use crate::wire::{Instruction, SmtLeaf}; #[derive(Debug, Clone)] diff --git a/trace_decoder/src/typed_mpt.rs b/trace_decoder/src/typed_mpt.rs index 5a654d203..8507b09ba 100644 --- a/trace_decoder/src/typed_mpt.rs +++ b/trace_decoder/src/typed_mpt.rs @@ -1,7 +1,7 @@ //! Principled MPT types used in this library. use core::fmt; -use std::marker::PhantomData; +use std::{collections::BTreeMap, marker::PhantomData}; use copyvec::CopyVec; use ethereum_types::{Address, H256}; @@ -246,14 +246,6 @@ impl StateMpt { }, } } - pub fn insert_by_address( - &mut self, - address: Address, - account: AccountRlp, - ) -> anyhow::Result> { - #[expect(deprecated)] - self.insert_by_hashed_address(crate::hash(address), account) - } #[deprecated = "prefer operations on `Address` where possible, as SMT support requires this"] pub fn insert_by_hashed_address( &mut self, @@ -262,26 +254,37 @@ impl StateMpt { ) -> anyhow::Result> { self.typed.insert(TrieKey::from_hash(key), account) } - /// Insert a deferred part of the trie - pub fn insert_hash_by_key(&mut self, key: TrieKey, hash: H256) -> anyhow::Result<()> { - self.typed.insert_hash(key, hash) + pub fn iter(&self) -> impl Iterator + '_ { + self.typed.iter() } - pub fn get_by_address(&self, address: Address) -> Option { - self.typed - .get(TrieKey::from_hash(keccak_hash::keccak(address))) + pub fn as_hashed_partial_trie(&self) -> &mpt_trie::partial_trie::HashedPartialTrie { + self.typed.as_hashed_partial_trie() } pub fn root(&self) -> H256 { self.typed.root() } - pub fn iter(&self) -> impl Iterator + '_ { - self.typed.iter() +} + +impl StateTrie for StateMpt { + fn insert_by_address( + &mut self, + address: Address, + account: AccountRlp, + ) -> anyhow::Result> { + #[expect(deprecated)] + self.insert_by_hashed_address(crate::hash(address), account) } - pub fn as_hashed_partial_trie(&self) -> &mpt_trie::partial_trie::HashedPartialTrie { - self.typed.as_hashed_partial_trie() + /// Insert a deferred part of the trie + fn insert_hash_by_key(&mut self, key: TrieKey, hash: H256) -> anyhow::Result<()> { + self.typed.insert_hash(key, hash) + } + fn get_by_address(&self, address: Address) -> Option { + self.typed + .get(TrieKey::from_hash(keccak_hash::keccak(address))) } /// Delete the account at `address`, returning any remaining branch on /// collapse - pub fn reporting_remove(&mut self, address: Address) -> anyhow::Result> { + fn reporting_remove(&mut self, address: Address) -> anyhow::Result> { Ok( crate::decoding::delete_node_and_report_remaining_key_if_branch_collapsed( self.typed.as_mut_hashed_partial_trie_unchecked(), @@ -289,12 +292,12 @@ impl StateMpt { )?, ) } - pub fn contains_address(&self, address: Address) -> bool { + fn contains_address(&self, address: Address) -> bool { self.typed .as_hashed_partial_trie() .contains(TrieKey::from_address(address).into_nibbles()) } - pub fn trim_to(&mut self, addresses: impl IntoIterator) -> anyhow::Result<()> { + fn trim_to(&mut self, addresses: impl IntoIterator) -> anyhow::Result<()> { let inner = mpt_trie::trie_subsets::create_trie_subset( self.typed.as_hashed_partial_trie(), addresses.into_iter().map(TrieKey::into_nibbles), @@ -307,6 +310,52 @@ impl StateMpt { } } +pub struct StateSmt { + address2state: BTreeMap, + deferred: BTreeMap, +} + +pub trait StateTrie { + fn insert_by_address( + &mut self, + address: Address, + account: AccountRlp, + ) -> anyhow::Result>; + fn insert_hash_by_key(&mut self, key: TrieKey, hash: H256) -> anyhow::Result<()>; + fn get_by_address(&self, address: Address) -> Option; + fn reporting_remove(&mut self, address: Address) -> anyhow::Result>; + fn contains_address(&self, address: Address) -> bool; + fn trim_to(&mut self, address: impl IntoIterator) -> anyhow::Result<()>; +} + +impl StateTrie for StateSmt { + fn insert_by_address( + &mut self, + address: Address, + account: AccountRlp, + ) -> anyhow::Result> { + Ok(self.address2state.insert(address, account)) + } + fn insert_hash_by_key(&mut self, key: TrieKey, hash: H256) -> anyhow::Result<()> { + self.deferred.insert(key, hash); + Ok(()) + } + fn get_by_address(&self, address: Address) -> Option { + self.address2state.get(&address).copied() + } + fn reporting_remove(&mut self, address: Address) -> anyhow::Result> { + self.address2state.remove(&address); + Ok(None) + } + fn contains_address(&self, address: Address) -> bool { + self.address2state.contains_key(&address) + } + fn trim_to(&mut self, address: impl IntoIterator) -> anyhow::Result<()> { + let _ = address; + Ok(()) + } +} + /// Global, per-account. /// /// See From 8769038d79108eead2c3ba4ccc55157e497e18af Mon Sep 17 00:00:00 2001 From: 0xaatif Date: Fri, 23 Aug 2024 23:10:45 +0100 Subject: [PATCH 08/13] refactor: StateMpt::iter -> (H256, AccountRlp) --- trace_decoder/src/lib.rs | 12 ++---------- trace_decoder/src/type1.rs | 19 ++++++++----------- trace_decoder/src/typed_mpt.rs | 6 ++++-- 3 files changed, 14 insertions(+), 23 deletions(-) diff --git a/trace_decoder/src/lib.rs b/trace_decoder/src/lib.rs index ab5eb22b3..424ea9283 100644 --- a/trace_decoder/src/lib.rs +++ b/trace_decoder/src/lib.rs @@ -367,10 +367,7 @@ pub fn entrypoint( ProcessedBlockTracePreImages { tries: PartialTriePreImages { state, - storage: storage - .into_iter() - .map(|(path, trie)| (path.into_hash_left_padded(), trie)) - .collect(), + storage: storage.into_iter().collect(), }, extra_code_hash_mappings: match code.is_empty() { true => None, @@ -384,12 +381,7 @@ pub fn entrypoint( } }; - let all_accounts_in_pre_images = pre_images - .tries - .state - .iter() - .map(|(addr, data)| (addr.into_hash_left_padded(), data)) - .collect::>(); + let all_accounts_in_pre_images = pre_images.tries.state.iter().collect::>(); // Note we discard any user-provided hashes. let mut hash2code = code_db diff --git a/trace_decoder/src/type1.rs b/trace_decoder/src/type1.rs index 6221573ed..c073c2a13 100644 --- a/trace_decoder/src/type1.rs +++ b/trace_decoder/src/type1.rs @@ -7,6 +7,7 @@ use std::collections::{BTreeMap, BTreeSet}; use anyhow::{bail, ensure, Context as _}; use either::Either; use evm_arithmetization::generation::mpt::AccountRlp; +use keccak_hash::H256; use mpt_trie::partial_trie::OnOrphanedHashNode; use nunny::NonEmpty; use u4::U4; @@ -18,9 +19,7 @@ use crate::wire::{Instruction, SmtLeaf}; pub struct Frontend { pub state: StateMpt, pub code: BTreeSet>>, - /// The key here matches the [`TriePath`] inside [`Self::state`] for - /// accounts which had inline storage. - pub storage: BTreeMap, + pub storage: BTreeMap, } impl Default for Frontend { @@ -70,7 +69,9 @@ fn visit( .insert_hash_by_key(TrieKey::new(path.iter().copied())?, raw_hash.into())?; } Node::Leaf(Leaf { key, value }) => { - let path = TrieKey::new(path.iter().copied().chain(key))?; + let path = TrieKey::new(path.iter().copied().chain(key))? + .into_hash() + .context("invalid depth for leaf of state trie")?; match value { Either::Left(Value { .. }) => bail!("unsupported value node at top level"), Either::Right(Account { @@ -105,11 +106,7 @@ fn visit( }, }; #[expect(deprecated)] // this is MPT-specific code - let clobbered = frontend.state.insert_by_hashed_address( - path.into_hash() - .context("invalid path length for leaf of StateTrie")?, - account, - )?; + let clobbered = frontend.state.insert_by_hashed_address(path, account)?; ensure!(clobbered.is_none(), "duplicate account"); } } @@ -395,9 +392,9 @@ fn test_tries() { let frontend = frontend(instructions).unwrap(); assert_eq!(case.expected_state_root, frontend.state.root()); - for (path, acct) in frontend.state.iter() { + for (haddr, acct) in frontend.state.iter() { if acct.storage_root != StateMpt::default().root() { - assert!(frontend.storage.contains_key(&path)) + assert!(frontend.storage.contains_key(&haddr)) } } } diff --git a/trace_decoder/src/typed_mpt.rs b/trace_decoder/src/typed_mpt.rs index 8507b09ba..114c4dd11 100644 --- a/trace_decoder/src/typed_mpt.rs +++ b/trace_decoder/src/typed_mpt.rs @@ -254,8 +254,10 @@ impl StateMpt { ) -> anyhow::Result> { self.typed.insert(TrieKey::from_hash(key), account) } - pub fn iter(&self) -> impl Iterator + '_ { - self.typed.iter() + pub fn iter(&self) -> impl Iterator + '_ { + self.typed + .iter() + .map(|(key, rlp)| (key.into_hash().expect("key is always H256"), rlp)) } pub fn as_hashed_partial_trie(&self) -> &mpt_trie::partial_trie::HashedPartialTrie { self.typed.as_hashed_partial_trie() From 429800a40381b4ffaa72940f329c343286c40a84 Mon Sep 17 00:00:00 2001 From: 0xaatif Date: Sat, 24 Aug 2024 01:39:07 +0100 Subject: [PATCH 09/13] wip --- Cargo.lock | 6 +- trace_decoder/Cargo.toml | 2 + trace_decoder/examples/investigate.rs | 247 ++++++++++++++++++++++++++ trace_decoder/src/lib.rs | 4 +- 4 files changed, 255 insertions(+), 4 deletions(-) create mode 100644 trace_decoder/examples/investigate.rs diff --git a/Cargo.lock b/Cargo.lock index 140ad3196..597346248 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1148,9 +1148,9 @@ dependencies = [ [[package]] name = "camino" -version = "1.1.7" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0ec6b951b160caa93cc0c7b209e5a3bff7aae9062213451ac99493cd844c239" +checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" dependencies = [ "serde", ] @@ -5174,6 +5174,7 @@ dependencies = [ "bitflags 2.6.0", "bitvec", "bytes", + "camino", "ciborium", "ciborium-io", "copyvec", @@ -5182,6 +5183,7 @@ dependencies = [ "enum-as-inner", "ethereum-types", "evm_arithmetization", + "glob", "hex", "hex-literal", "itertools 0.13.0", diff --git a/trace_decoder/Cargo.toml b/trace_decoder/Cargo.toml index cac9547c0..87fd19889 100644 --- a/trace_decoder/Cargo.toml +++ b/trace_decoder/Cargo.toml @@ -48,6 +48,8 @@ serde_path_to_error = { workspace = true } plonky2_maybe_rayon = { workspace = true } alloy = { workspace = true } rstest = "0.21.0" +glob = "0.3.1" +camino = "1.1.9" [[bench]] diff --git a/trace_decoder/examples/investigate.rs b/trace_decoder/examples/investigate.rs new file mode 100644 index 000000000..0cb3dd1de --- /dev/null +++ b/trace_decoder/examples/investigate.rs @@ -0,0 +1,247 @@ +use std::{ + collections::{BTreeMap, HashSet}, + fs::File, + iter, + path::Path, +}; + +use alloy::rpc::types::Header; +use anyhow::{ensure, Context as _}; +use camino::Utf8Path; +use copyvec::CopyVec; +use either::Either; +use ethereum_types::Address; +use evm_arithmetization::generation::mpt::AccountRlp; +use glob::glob; +use keccak_hash::{keccak, H256}; +use mpt_trie::partial_trie::PartialTrie as _; +use mpt_trie::partial_trie::{HashedPartialTrie, Node}; +use prover::BlockProverInput; +use serde::de::DeserializeOwned; +use trace_decoder::{ + BlockTraceTriePreImages, CombinedPreImages, SeparateStorageTriesPreImage, SeparateTriePreImage, + SeparateTriePreImages, +}; +use u4::U4; + +/// This is the dream StateTrie representation +struct StateTrie { + /// items actually in the trie + full: BTreeMap, + deferred_subtries: BTreeMap, H256>, + deferred_accounts: BTreeMap, +} + +impl StateTrie { + /// Defer accounts in `locations`. + /// Absent values are not an error. + fn trim_to(&mut self, locations: impl IntoIterator) { + let want = locations.into_iter().collect::>(); + let have = self.full.keys().copied().collect(); + for hash in HashSet::difference(&have, &want) { + let (k, v) = self.full.remove_entry(hash).expect("key is in `have`"); + self.deferred_accounts.insert(k, v); + } + } + fn insert_by_address(&mut self, address: Address, account: AccountRlp) { + self.full.insert(keccak(address), account); + } + fn insert_hash_by_key(&mut self, key: CopyVec, hash: H256) { + self.deferred_subtries.insert(key, hash); + } + fn get_by_address(&self, address: Address) -> Option { + self.full.get(&keccak(address)).copied() + } +} +impl StateTrie { + fn from_mpt(_: HashedPartialTrie) -> anyhow::Result { + todo!() + } + fn to_mpt(&self) -> anyhow::Result { + todo!() + } + fn to_smt(&self) -> smt_trie::smt::Smt { + todo!() + } +} + +fn _discuss(src: HashedPartialTrie) -> anyhow::Result<()> { + // the goal is, of course, for the following to hold + assert_eq!(src.hash(), StateTrie::from_mpt(src)?.to_mpt()?.hash()); + + Ok(()) +} + +/// Test cases come in pairs of files, `foo_header.json` and `foo.json`. +struct FilePair { + /// `foo`, in the above example. + pub name: String, + pub cases: Vec<(Header, BlockProverInput)>, +} + +impl FilePair { + fn load(header_path: &Path) -> anyhow::Result { + let header_path = Utf8Path::from_path(header_path).context("non-UTF-8 path")?; + let base = Utf8Path::new( + header_path + .as_str() + .strip_suffix("_header.json") + .context("inconsistent header name")?, // sync with glob call + ); + let headers = json::>(header_path)?; + let bodies = json::>(base.with_extension("json"))?; + ensure!(headers.len() == bodies.len(), "inconsistent file pair"); + anyhow::Ok(FilePair { + name: base.file_name().context("inconsistent base name")?.into(), + cases: headers.into_iter().zip(bodies).collect(), + }) + } +} + +fn json(path: impl AsRef) -> anyhow::Result { + fn _imp(path: impl AsRef) -> anyhow::Result { + let file = File::open(path)?; + Ok(serde_path_to_error::deserialize( + &mut serde_json::Deserializer::from_reader(file), + )?) + } + + _imp(&path).context(format!("couldn't load {}", path.as_ref().display())) +} + +fn main() -> anyhow::Result<()> { + eprint!("loading test cases..."); + let file_pairs = glob(concat!( + env!("CARGO_MANIFEST_DIR"), + "/tests/data/witnesses/zero_jerigon/*_header.json" + )) + .expect("valid glob pattern") + .map(|res| { + let header_path = res.expect("filesystem error discovering test vectors"); + FilePair::load(&header_path).context(format!( + "couldn't load case for header {}", + header_path.display() + )) + }) + .collect::, _>>()?; + eprintln!("done."); + + let mut total = 0; + + for FilePair { name, cases } in file_pairs { + for (case_ix, (_header, bpi)) in cases.into_iter().enumerate() { + for (hpt_ix, hpt) in mpts(bpi)?.enumerate() { + total += 1; + let count = count_non_minimal(&hpt); + if count != 0 { + println!("{name}/{case_ix}/{hpt_ix}\t{}", count) + } + } + } + } + eprintln!("tested {total} tries"); + Ok(()) +} + +/// Iterate the state and storage tries in `bpi` +fn mpts(bpi: BlockProverInput) -> anyhow::Result> { + Ok(match bpi.block_trace.trie_pre_images { + BlockTraceTriePreImages::Separate(SeparateTriePreImages { + state: SeparateTriePreImage::Direct(state), + storage: SeparateStorageTriesPreImage::MultipleTries(hash2trie), + }) => Either::Left( + iter::once(state).chain( + hash2trie + .into_values() + .map(|SeparateTriePreImage::Direct(it)| it), + ), + ), + BlockTraceTriePreImages::Combined(CombinedPreImages { compact }) => { + let fe = + trace_decoder::wire::parse(&compact).and_then(trace_decoder::type1::frontend)?; + Either::Right( + iter::once(fe.state.as_hashed_partial_trie().clone()).chain( + fe.storage + .into_values() + .map(|it| it.as_hashed_partial_trie().clone()), + ), + ) + } + }) +} + +/// Count cases like `branch -> branch` or `branch -> extension` +fn count_non_minimal(node: &Node) -> usize { + let mut count = 0; + match node { + Node::Empty | Node::Hash(_) | Node::Leaf { .. } => {} + Node::Branch { children, .. } => { + let mut nonempty_children = + children.iter().filter(|it| !matches!(*****it, Node::Empty)); + + if let (1, Some(Node::Branch { .. } | Node::Extension { .. })) = ( + nonempty_children.clone().count(), + nonempty_children.next().map(|it| &****it), + ) { + count += 1 + } + + count += children + .iter() + .map(|it| count_non_minimal(it)) + .sum::() + } + Node::Extension { child, .. } => count += count_non_minimal(child), + }; + count +} + +/// Create [`Node::Branch`] with a single child, `child`. +#[cfg(test)] +fn branch0(child: HashedPartialTrie) -> HashedPartialTrie { + let mut child = Some(child); + + HashedPartialTrie::new(Node::Branch { + children: std::array::from_fn(|ix| { + std::sync::Arc::new(Box::new(match ix { + 0 => child.take().unwrap(), + _ => HashedPartialTrie::new(Node::Empty), + })) + }), + value: vec![], + }) +} + +#[test] +fn test_count_non_minimal() { + // root -> branch -> branch -> leaf + let subject = branch0(branch0(HashedPartialTrie::new(Node::Leaf { + nibbles: Default::default(), + value: vec![], + }))); + assert_eq!(count_non_minimal(&subject), 1); +} + +#[test] +fn test_badhash() { + let leaf = HashedPartialTrie::new(Node::Leaf { + nibbles: Default::default(), + value: b"hello".into(), + }); + let ext = HashedPartialTrie::new(Node::Extension { + nibbles: { + let mut nibbles = mpt_trie::nibbles::Nibbles::new(); + nibbles.push_nibble_back(0); + nibbles.push_nibble_back(0); + nibbles + }, + child: std::sync::Arc::new(Box::new(leaf.clone())), + }); + let branchy = branch0(branch0(leaf)); + + // two above representations are semantically equivalent + itertools::assert_equal(ext.items(), branchy.items()); + + // but have different hashes + assert_ne!(ext.hash(), branchy.hash()); +} diff --git a/trace_decoder/src/lib.rs b/trace_decoder/src/lib.rs index 424ea9283..76189efb1 100644 --- a/trace_decoder/src/lib.rs +++ b/trace_decoder/src/lib.rs @@ -87,14 +87,14 @@ mod decoding; /// Defines functions that processes a [BlockTrace] so that it is easier to turn /// the block transactions into IRs. mod processed_block_trace; -mod type1; +pub mod type1; // TODO(0xaatif): https://github.com/0xPolygonZero/zk_evm/issues/275 // add backend/prod support for type 2 #[cfg(test)] #[allow(dead_code)] mod type2; mod typed_mpt; -mod wire; +pub mod wire; use std::collections::HashMap; From 31795c692fea915cea9c3e611ce2ac330367a37b Mon Sep 17 00:00:00 2001 From: 0xaatif Date: Sat, 24 Aug 2024 03:32:55 +0100 Subject: [PATCH 10/13] wip --- trace_decoder/examples/investigate.rs | 94 +++++++++++++++++++++++---- 1 file changed, 81 insertions(+), 13 deletions(-) diff --git a/trace_decoder/examples/investigate.rs b/trace_decoder/examples/investigate.rs index 0cb3dd1de..ba7768021 100644 --- a/trace_decoder/examples/investigate.rs +++ b/trace_decoder/examples/investigate.rs @@ -1,3 +1,4 @@ +use core::fmt; use std::{ collections::{BTreeMap, HashSet}, fs::File, @@ -10,25 +11,45 @@ use anyhow::{ensure, Context as _}; use camino::Utf8Path; use copyvec::CopyVec; use either::Either; -use ethereum_types::Address; +use ethereum_types::{Address, BigEndianHash as _}; use evm_arithmetization::generation::mpt::AccountRlp; use glob::glob; use keccak_hash::{keccak, H256}; -use mpt_trie::partial_trie::PartialTrie as _; -use mpt_trie::partial_trie::{HashedPartialTrie, Node}; +use mpt_trie::{ + nibbles::Nibbles, + partial_trie::{HashedPartialTrie, Node}, +}; +use mpt_trie::{partial_trie::PartialTrie as _, trie_ops::ValOrHash}; use prover::BlockProverInput; use serde::de::DeserializeOwned; use trace_decoder::{ BlockTraceTriePreImages, CombinedPreImages, SeparateStorageTriesPreImage, SeparateTriePreImage, SeparateTriePreImages, }; -use u4::U4; +use u4::{AsNibbles, U4}; + +fn nibbles2hash(mut nibbles: Nibbles) -> Option { + let mut bytes = [0; 32]; + + let mut nibbles = iter::from_fn(|| match nibbles.count { + 0 => None, + _ => Some(nibbles.pop_next_nibble_front()), + }); + for (ix, nibble) in nibbles.by_ref().enumerate() { + AsNibbles(&mut bytes).set(ix, U4::new(nibble)?) + } + match nibbles.next() { + Some(_) => None, // too many + None => Some(H256(bytes)), + } +} /// This is the dream StateTrie representation +#[derive(Debug, Default)] struct StateTrie { /// items actually in the trie full: BTreeMap, - deferred_subtries: BTreeMap, H256>, + deferred_subtries: BTreeMap, deferred_accounts: BTreeMap, } @@ -43,10 +64,13 @@ impl StateTrie { self.deferred_accounts.insert(k, v); } } + fn insert_by_hashed_address(&mut self, hashed_address: H256, account: AccountRlp) { + self.full.insert(hashed_address, account); + } fn insert_by_address(&mut self, address: Address, account: AccountRlp) { - self.full.insert(keccak(address), account); + self.insert_by_hashed_address(keccak(address), account) } - fn insert_hash_by_key(&mut self, key: CopyVec, hash: H256) { + fn insert_hash_by_key(&mut self, key: Nibbles, hash: H256) { self.deferred_subtries.insert(key, hash); } fn get_by_address(&self, address: Address) -> Option { @@ -54,11 +78,37 @@ impl StateTrie { } } impl StateTrie { - fn from_mpt(_: HashedPartialTrie) -> anyhow::Result { - todo!() + fn from_mpt(src: &HashedPartialTrie) -> anyhow::Result { + let mut this = Self::default(); + for (path, voh) in src.items() { + match voh { + ValOrHash::Val(it) => this.insert_by_hashed_address( + nibbles2hash(path).context("invalid depth")?, + rlp::decode(&it)?, + ), + ValOrHash::Hash(hash) => this.insert_hash_by_key(path, hash), + }; + } + Ok(this) } fn to_mpt(&self) -> anyhow::Result { - todo!() + let Self { + full, + deferred_subtries, + deferred_accounts, + } = self; + + let mut theirs = HashedPartialTrie::default(); + for (path, hash) in deferred_subtries { + theirs.insert(*path, *hash)? + } + for (haddr, acct) in full.iter().chain(deferred_accounts) { + theirs.insert(Nibbles::from_h256_be(*haddr), rlp::encode(acct).to_vec())?; + } + Ok(mpt_trie::trie_subsets::create_trie_subset( + &theirs, + self.full.keys().map(|it| Nibbles::from_h256_be(*it)), + )?) } fn to_smt(&self) -> smt_trie::smt::Smt { todo!() @@ -67,7 +117,7 @@ impl StateTrie { fn _discuss(src: HashedPartialTrie) -> anyhow::Result<()> { // the goal is, of course, for the following to hold - assert_eq!(src.hash(), StateTrie::from_mpt(src)?.to_mpt()?.hash()); + assert_eq!(src.hash(), StateTrie::from_mpt(&src)?.to_mpt()?.hash()); Ok(()) } @@ -127,19 +177,37 @@ fn main() -> anyhow::Result<()> { eprintln!("done."); let mut total = 0; + let mut n_state = 0; for FilePair { name, cases } in file_pairs { for (case_ix, (_header, bpi)) in cases.into_iter().enumerate() { for (hpt_ix, hpt) in mpts(bpi)?.enumerate() { total += 1; + let count = count_non_minimal(&hpt); if count != 0 { println!("{name}/{case_ix}/{hpt_ix}\t{}", count) } + if hpt_ix == 0 { + n_state += 1; + match StateTrie::from_mpt(&hpt) + .context("failed to load") + .and_then(|it| it.to_mpt().context("failed to dump")) + { + Ok(ours) => match ours.hash() == hpt.hash() { + true => {} + false => println!("{name}/{case_ix}/{hpt_ix}\thash mismatch"), + }, + Err(e) => { + println!("{name}/{case_ix}/{hpt_ix}\tfailed conversion"); + eprintln!("{e:?}") + } + } + } } } } - eprintln!("tested {total} tries"); + eprintln!("tested {total} tries ({n_state} state tries)"); Ok(()) } @@ -230,7 +298,7 @@ fn test_badhash() { }); let ext = HashedPartialTrie::new(Node::Extension { nibbles: { - let mut nibbles = mpt_trie::nibbles::Nibbles::new(); + let mut nibbles = Nibbles::new(); nibbles.push_nibble_back(0); nibbles.push_nibble_back(0); nibbles From d2303e68c3a63a3bcd91ae933a419495a54c2632 Mon Sep 17 00:00:00 2001 From: 0xaatif Date: Sat, 24 Aug 2024 05:29:13 +0100 Subject: [PATCH 11/13] wip --- trace_decoder/src/decoding.rs | 34 +++++++++++++++++++--------------- trace_decoder/src/lib.rs | 17 +++++++++++++++++ trace_decoder/src/typed_mpt.rs | 9 +++++++++ 3 files changed, 45 insertions(+), 15 deletions(-) diff --git a/trace_decoder/src/decoding.rs b/trace_decoder/src/decoding.rs index c78fe451b..28de10e61 100644 --- a/trace_decoder/src/decoding.rs +++ b/trace_decoder/src/decoding.rs @@ -25,15 +25,15 @@ use crate::{ processed_block_trace::{ NodesUsedByTxn, ProcessedBlockTrace, ProcessedTxnInfo, StateWrite, TxnMetaState, }, - typed_mpt::{ReceiptTrie, StateMpt, StateTrie as _, StorageTrie, TransactionTrie, TrieKey}, - OtherBlockData, PartialTriePreImages, + typed_mpt::{ReceiptTrie, StateTrie, StorageTrie, TransactionTrie, TrieKey}, + OtherBlockData, PartialTriePreImages, TryIntoExt as TryIntoBounds, }; /// The current state of all tries as we process txn deltas. These are mutated /// after every txn we process in the trace. #[derive(Clone, Debug, Default)] -struct PartialTrieState { - state: StateMpt, +struct PartialTrieState { + state: StateTrieT, storage: HashMap, txn: TransactionTrie, receipt: ReceiptTrie, @@ -114,7 +114,7 @@ pub fn into_txn_proof_gen_ir( /// need to update the storage of the beacon block root contract. // See . fn update_beacon_block_root_contract_storage( - trie_state: &mut PartialTrieState, + trie_state: &mut PartialTrieState, delta_out: &mut TrieDeltaApplicationOutput, nodes_used: &mut NodesUsedByTxn, block_data: &BlockMetadata, @@ -208,7 +208,7 @@ fn update_beacon_block_root_contract_storage( } fn update_txn_and_receipt_tries( - trie_state: &mut PartialTrieState, + trie_state: &mut PartialTrieState, meta: &TxnMetaState, txn_idx: usize, ) -> anyhow::Result<()> { @@ -247,7 +247,7 @@ fn init_any_needed_empty_storage_tries<'a>( } fn create_minimal_partial_tries_needed_by_txn( - curr_block_tries: &PartialTrieState, + curr_block_tries: &PartialTrieState>, nodes_used_by_txn: &NodesUsedByTxn, txn_range: Range, delta_application_out: TrieDeltaApplicationOutput, @@ -282,7 +282,7 @@ fn create_minimal_partial_tries_needed_by_txn( )?; Ok(TrieInputs { - state_trie: state_trie.as_hashed_partial_trie().clone(), + state_trie: state_trie.try_into()?, transactions_trie, receipts_trie, storage_tries, @@ -290,7 +290,7 @@ fn create_minimal_partial_tries_needed_by_txn( } fn apply_deltas_to_trie_state( - trie_state: &mut PartialTrieState, + trie_state: &mut PartialTrieState, deltas: &NodesUsedByTxn, meta: &[TxnMetaState], ) -> anyhow::Result { @@ -432,7 +432,9 @@ fn node_deletion_resulted_in_a_branch_collapse( /// The withdrawals are always in the final ir payload. fn add_withdrawals_to_txns( txn_ir: &mut [GenerationInputs], - final_trie_state: &mut PartialTrieState, + final_trie_state: &mut PartialTrieState< + impl StateTrie + Clone + TryIntoBounds, + >, mut withdrawals: Vec<(Address, U256)>, ) -> anyhow::Result<()> { // Scale withdrawals amounts. @@ -466,7 +468,7 @@ fn add_withdrawals_to_txns( }) .map(TrieKey::from_address), )?; - last_inputs.tries.state_trie = state_trie.as_hashed_partial_trie().clone(); + last_inputs.tries.state_trie = state_trie.try_into()?; } update_trie_state_from_withdrawals( @@ -475,7 +477,7 @@ fn add_withdrawals_to_txns( )?; last_inputs.withdrawals = withdrawals; - last_inputs.trie_roots_after.state_root = final_trie_state.state.root(); + last_inputs.trie_roots_after.state_root = final_trie_state.state.clone().try_into()?.hash(); Ok(()) } @@ -484,7 +486,7 @@ fn add_withdrawals_to_txns( /// our local trie state. fn update_trie_state_from_withdrawals<'a>( withdrawals: impl IntoIterator + 'a, - state: &mut StateMpt, + state: &mut impl StateTrie, ) -> anyhow::Result<()> { for (addr, h_addr, amt) in withdrawals { let mut acc_data = state.get_by_address(addr).context(format!( @@ -508,7 +510,9 @@ fn process_txn_info( txn_range: Range, is_initial_payload: bool, txn_info: ProcessedTxnInfo, - curr_block_tries: &mut PartialTrieState, + curr_block_tries: &mut PartialTrieState< + impl StateTrie + Clone + TryIntoBounds, + >, extra_data: &mut ExtraBlockData, other_data: &OtherBlockData, ) -> anyhow::Result { @@ -583,7 +587,7 @@ fn process_txn_info( * for more info). */ tries, trie_roots_after: TrieRoots { - state_root: curr_block_tries.state.root(), + state_root: curr_block_tries.state.clone().try_into()?.hash(), transactions_root: curr_block_tries.txn.root(), receipts_root: curr_block_tries.receipt.root(), }, diff --git a/trace_decoder/src/lib.rs b/trace_decoder/src/lib.rs index 76189efb1..47a51614e 100644 --- a/trace_decoder/src/lib.rs +++ b/trace_decoder/src/lib.rs @@ -471,6 +471,23 @@ mod hex { } } +trait TryIntoExt { + type Error: std::error::Error + Send + Sync + 'static; + fn try_into(self) -> Result; +} + +impl TryIntoExt for ThisT +where + ThisT: TryInto, + E: std::error::Error + Send + Sync + 'static, +{ + type Error = ThisT::Error; + + fn try_into(self) -> Result { + TryInto::try_into(self) + } +} + #[cfg(test)] #[derive(serde::Deserialize)] struct Case { diff --git a/trace_decoder/src/typed_mpt.rs b/trace_decoder/src/typed_mpt.rs index 114c4dd11..8409e74c0 100644 --- a/trace_decoder/src/typed_mpt.rs +++ b/trace_decoder/src/typed_mpt.rs @@ -312,6 +312,15 @@ impl StateTrie for StateMpt { } } +impl From for HashedPartialTrie { + fn from(value: StateMpt) -> Self { + let StateMpt { + typed: TypedMpt { inner, _ty }, + } = value; + inner + } +} + pub struct StateSmt { address2state: BTreeMap, deferred: BTreeMap, From e0939d834d809127d68027da372a027c1d5a0d1e Mon Sep 17 00:00:00 2001 From: 0xaatif Date: Sat, 24 Aug 2024 06:43:55 +0100 Subject: [PATCH 12/13] chore: remove investigate --- trace_decoder/examples/investigate.rs | 315 -------------------------- trace_decoder/src/lib.rs | 4 +- 2 files changed, 2 insertions(+), 317 deletions(-) delete mode 100644 trace_decoder/examples/investigate.rs diff --git a/trace_decoder/examples/investigate.rs b/trace_decoder/examples/investigate.rs deleted file mode 100644 index ba7768021..000000000 --- a/trace_decoder/examples/investigate.rs +++ /dev/null @@ -1,315 +0,0 @@ -use core::fmt; -use std::{ - collections::{BTreeMap, HashSet}, - fs::File, - iter, - path::Path, -}; - -use alloy::rpc::types::Header; -use anyhow::{ensure, Context as _}; -use camino::Utf8Path; -use copyvec::CopyVec; -use either::Either; -use ethereum_types::{Address, BigEndianHash as _}; -use evm_arithmetization::generation::mpt::AccountRlp; -use glob::glob; -use keccak_hash::{keccak, H256}; -use mpt_trie::{ - nibbles::Nibbles, - partial_trie::{HashedPartialTrie, Node}, -}; -use mpt_trie::{partial_trie::PartialTrie as _, trie_ops::ValOrHash}; -use prover::BlockProverInput; -use serde::de::DeserializeOwned; -use trace_decoder::{ - BlockTraceTriePreImages, CombinedPreImages, SeparateStorageTriesPreImage, SeparateTriePreImage, - SeparateTriePreImages, -}; -use u4::{AsNibbles, U4}; - -fn nibbles2hash(mut nibbles: Nibbles) -> Option { - let mut bytes = [0; 32]; - - let mut nibbles = iter::from_fn(|| match nibbles.count { - 0 => None, - _ => Some(nibbles.pop_next_nibble_front()), - }); - for (ix, nibble) in nibbles.by_ref().enumerate() { - AsNibbles(&mut bytes).set(ix, U4::new(nibble)?) - } - match nibbles.next() { - Some(_) => None, // too many - None => Some(H256(bytes)), - } -} - -/// This is the dream StateTrie representation -#[derive(Debug, Default)] -struct StateTrie { - /// items actually in the trie - full: BTreeMap, - deferred_subtries: BTreeMap, - deferred_accounts: BTreeMap, -} - -impl StateTrie { - /// Defer accounts in `locations`. - /// Absent values are not an error. - fn trim_to(&mut self, locations: impl IntoIterator) { - let want = locations.into_iter().collect::>(); - let have = self.full.keys().copied().collect(); - for hash in HashSet::difference(&have, &want) { - let (k, v) = self.full.remove_entry(hash).expect("key is in `have`"); - self.deferred_accounts.insert(k, v); - } - } - fn insert_by_hashed_address(&mut self, hashed_address: H256, account: AccountRlp) { - self.full.insert(hashed_address, account); - } - fn insert_by_address(&mut self, address: Address, account: AccountRlp) { - self.insert_by_hashed_address(keccak(address), account) - } - fn insert_hash_by_key(&mut self, key: Nibbles, hash: H256) { - self.deferred_subtries.insert(key, hash); - } - fn get_by_address(&self, address: Address) -> Option { - self.full.get(&keccak(address)).copied() - } -} -impl StateTrie { - fn from_mpt(src: &HashedPartialTrie) -> anyhow::Result { - let mut this = Self::default(); - for (path, voh) in src.items() { - match voh { - ValOrHash::Val(it) => this.insert_by_hashed_address( - nibbles2hash(path).context("invalid depth")?, - rlp::decode(&it)?, - ), - ValOrHash::Hash(hash) => this.insert_hash_by_key(path, hash), - }; - } - Ok(this) - } - fn to_mpt(&self) -> anyhow::Result { - let Self { - full, - deferred_subtries, - deferred_accounts, - } = self; - - let mut theirs = HashedPartialTrie::default(); - for (path, hash) in deferred_subtries { - theirs.insert(*path, *hash)? - } - for (haddr, acct) in full.iter().chain(deferred_accounts) { - theirs.insert(Nibbles::from_h256_be(*haddr), rlp::encode(acct).to_vec())?; - } - Ok(mpt_trie::trie_subsets::create_trie_subset( - &theirs, - self.full.keys().map(|it| Nibbles::from_h256_be(*it)), - )?) - } - fn to_smt(&self) -> smt_trie::smt::Smt { - todo!() - } -} - -fn _discuss(src: HashedPartialTrie) -> anyhow::Result<()> { - // the goal is, of course, for the following to hold - assert_eq!(src.hash(), StateTrie::from_mpt(&src)?.to_mpt()?.hash()); - - Ok(()) -} - -/// Test cases come in pairs of files, `foo_header.json` and `foo.json`. -struct FilePair { - /// `foo`, in the above example. - pub name: String, - pub cases: Vec<(Header, BlockProverInput)>, -} - -impl FilePair { - fn load(header_path: &Path) -> anyhow::Result { - let header_path = Utf8Path::from_path(header_path).context("non-UTF-8 path")?; - let base = Utf8Path::new( - header_path - .as_str() - .strip_suffix("_header.json") - .context("inconsistent header name")?, // sync with glob call - ); - let headers = json::>(header_path)?; - let bodies = json::>(base.with_extension("json"))?; - ensure!(headers.len() == bodies.len(), "inconsistent file pair"); - anyhow::Ok(FilePair { - name: base.file_name().context("inconsistent base name")?.into(), - cases: headers.into_iter().zip(bodies).collect(), - }) - } -} - -fn json(path: impl AsRef) -> anyhow::Result { - fn _imp(path: impl AsRef) -> anyhow::Result { - let file = File::open(path)?; - Ok(serde_path_to_error::deserialize( - &mut serde_json::Deserializer::from_reader(file), - )?) - } - - _imp(&path).context(format!("couldn't load {}", path.as_ref().display())) -} - -fn main() -> anyhow::Result<()> { - eprint!("loading test cases..."); - let file_pairs = glob(concat!( - env!("CARGO_MANIFEST_DIR"), - "/tests/data/witnesses/zero_jerigon/*_header.json" - )) - .expect("valid glob pattern") - .map(|res| { - let header_path = res.expect("filesystem error discovering test vectors"); - FilePair::load(&header_path).context(format!( - "couldn't load case for header {}", - header_path.display() - )) - }) - .collect::, _>>()?; - eprintln!("done."); - - let mut total = 0; - let mut n_state = 0; - - for FilePair { name, cases } in file_pairs { - for (case_ix, (_header, bpi)) in cases.into_iter().enumerate() { - for (hpt_ix, hpt) in mpts(bpi)?.enumerate() { - total += 1; - - let count = count_non_minimal(&hpt); - if count != 0 { - println!("{name}/{case_ix}/{hpt_ix}\t{}", count) - } - if hpt_ix == 0 { - n_state += 1; - match StateTrie::from_mpt(&hpt) - .context("failed to load") - .and_then(|it| it.to_mpt().context("failed to dump")) - { - Ok(ours) => match ours.hash() == hpt.hash() { - true => {} - false => println!("{name}/{case_ix}/{hpt_ix}\thash mismatch"), - }, - Err(e) => { - println!("{name}/{case_ix}/{hpt_ix}\tfailed conversion"); - eprintln!("{e:?}") - } - } - } - } - } - } - eprintln!("tested {total} tries ({n_state} state tries)"); - Ok(()) -} - -/// Iterate the state and storage tries in `bpi` -fn mpts(bpi: BlockProverInput) -> anyhow::Result> { - Ok(match bpi.block_trace.trie_pre_images { - BlockTraceTriePreImages::Separate(SeparateTriePreImages { - state: SeparateTriePreImage::Direct(state), - storage: SeparateStorageTriesPreImage::MultipleTries(hash2trie), - }) => Either::Left( - iter::once(state).chain( - hash2trie - .into_values() - .map(|SeparateTriePreImage::Direct(it)| it), - ), - ), - BlockTraceTriePreImages::Combined(CombinedPreImages { compact }) => { - let fe = - trace_decoder::wire::parse(&compact).and_then(trace_decoder::type1::frontend)?; - Either::Right( - iter::once(fe.state.as_hashed_partial_trie().clone()).chain( - fe.storage - .into_values() - .map(|it| it.as_hashed_partial_trie().clone()), - ), - ) - } - }) -} - -/// Count cases like `branch -> branch` or `branch -> extension` -fn count_non_minimal(node: &Node) -> usize { - let mut count = 0; - match node { - Node::Empty | Node::Hash(_) | Node::Leaf { .. } => {} - Node::Branch { children, .. } => { - let mut nonempty_children = - children.iter().filter(|it| !matches!(*****it, Node::Empty)); - - if let (1, Some(Node::Branch { .. } | Node::Extension { .. })) = ( - nonempty_children.clone().count(), - nonempty_children.next().map(|it| &****it), - ) { - count += 1 - } - - count += children - .iter() - .map(|it| count_non_minimal(it)) - .sum::() - } - Node::Extension { child, .. } => count += count_non_minimal(child), - }; - count -} - -/// Create [`Node::Branch`] with a single child, `child`. -#[cfg(test)] -fn branch0(child: HashedPartialTrie) -> HashedPartialTrie { - let mut child = Some(child); - - HashedPartialTrie::new(Node::Branch { - children: std::array::from_fn(|ix| { - std::sync::Arc::new(Box::new(match ix { - 0 => child.take().unwrap(), - _ => HashedPartialTrie::new(Node::Empty), - })) - }), - value: vec![], - }) -} - -#[test] -fn test_count_non_minimal() { - // root -> branch -> branch -> leaf - let subject = branch0(branch0(HashedPartialTrie::new(Node::Leaf { - nibbles: Default::default(), - value: vec![], - }))); - assert_eq!(count_non_minimal(&subject), 1); -} - -#[test] -fn test_badhash() { - let leaf = HashedPartialTrie::new(Node::Leaf { - nibbles: Default::default(), - value: b"hello".into(), - }); - let ext = HashedPartialTrie::new(Node::Extension { - nibbles: { - let mut nibbles = Nibbles::new(); - nibbles.push_nibble_back(0); - nibbles.push_nibble_back(0); - nibbles - }, - child: std::sync::Arc::new(Box::new(leaf.clone())), - }); - let branchy = branch0(branch0(leaf)); - - // two above representations are semantically equivalent - itertools::assert_equal(ext.items(), branchy.items()); - - // but have different hashes - assert_ne!(ext.hash(), branchy.hash()); -} diff --git a/trace_decoder/src/lib.rs b/trace_decoder/src/lib.rs index 47a51614e..8d833a149 100644 --- a/trace_decoder/src/lib.rs +++ b/trace_decoder/src/lib.rs @@ -87,14 +87,14 @@ mod decoding; /// Defines functions that processes a [BlockTrace] so that it is easier to turn /// the block transactions into IRs. mod processed_block_trace; -pub mod type1; +mod type1; // TODO(0xaatif): https://github.com/0xPolygonZero/zk_evm/issues/275 // add backend/prod support for type 2 #[cfg(test)] #[allow(dead_code)] mod type2; mod typed_mpt; -pub mod wire; +mod wire; use std::collections::HashMap; From 3a5f3a182af022584b673907e5f9a9b57f50e11a Mon Sep 17 00:00:00 2001 From: 0xaatif Date: Tue, 27 Aug 2024 10:24:56 +0100 Subject: [PATCH 13/13] chore: sort and prune deps --- Cargo.lock | 2 -- trace_decoder/Cargo.toml | 13 +++++-------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 597346248..2b3eaf1d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5174,7 +5174,6 @@ dependencies = [ "bitflags 2.6.0", "bitvec", "bytes", - "camino", "ciborium", "ciborium-io", "copyvec", @@ -5183,7 +5182,6 @@ dependencies = [ "enum-as-inner", "ethereum-types", "evm_arithmetization", - "glob", "hex", "hex-literal", "itertools 0.13.0", diff --git a/trace_decoder/Cargo.toml b/trace_decoder/Cargo.toml index 87fd19889..4308a0ebe 100644 --- a/trace_decoder/Cargo.toml +++ b/trace_decoder/Cargo.toml @@ -37,20 +37,17 @@ strum = { version = "0.26.3", features = ["derive"] } thiserror = { workspace = true } u4 = { workspace = true } winnow = { workspace = true } -zk_evm_common = {workspace = true} +zk_evm_common = { workspace = true } [dev-dependencies] +alloy = { workspace = true } criterion = { workspace = true } +plonky2_maybe_rayon = { workspace = true } pretty_env_logger = { workspace = true } -serde_json = { workspace = true } prover = { workspace = true } -serde_path_to_error = { workspace = true } -plonky2_maybe_rayon = { workspace = true } -alloy = { workspace = true } rstest = "0.21.0" -glob = "0.3.1" -camino = "1.1.9" - +serde_json = { workspace = true } +serde_path_to_error = { workspace = true } [[bench]] name = "block_processing"