From 8e51391fb9e84be7d9756a56788e863a3a08ea8c Mon Sep 17 00:00:00 2001 From: clabby Date: Mon, 1 Jul 2024 14:18:22 -0400 Subject: [PATCH 1/9] feat(rpc): `debug_executionWitness` --- crates/rpc/rpc-api/src/debug.rs | 14 ++++++ crates/rpc/rpc/src/debug.rs | 76 +++++++++++++++++++++++++++++++-- 2 files changed, 87 insertions(+), 3 deletions(-) diff --git a/crates/rpc/rpc-api/src/debug.rs b/crates/rpc/rpc-api/src/debug.rs index 580245b1014c..f073cc434817 100644 --- a/crates/rpc/rpc-api/src/debug.rs +++ b/crates/rpc/rpc-api/src/debug.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use jsonrpsee::{core::RpcResult, proc_macros::rpc}; use reth_primitives::{Address, BlockId, BlockNumberOrTag, Bytes, B256}; use reth_rpc_types::{ @@ -132,6 +134,18 @@ pub trait DebugApi { opts: Option, ) -> RpcResult>>; + /// The `debug_executionWitness` method allows for re-execution of a block with the purpose of + /// generating an execution witness. The witness comprises of a map of all hashed trie nodes + /// to their preimages that were required during the execution of the block, including during + /// state root recomputation. + /// + /// The first and only argument is the block number or block hash. + #[method(name = "executionWitness")] + async fn debug_execution_witness( + &self, + block: BlockNumberOrTag, + ) -> RpcResult>; + /// Sets the logging backtrace location. When a backtrace location is set and a log message is /// emitted at that location, the stack of the goroutine executing the log statement will /// be printed to stderr. diff --git a/crates/rpc/rpc/src/debug.rs b/crates/rpc/rpc/src/debug.rs index cbf35a7d51c5..8638d0ab93b3 100644 --- a/crates/rpc/rpc/src/debug.rs +++ b/crates/rpc/rpc/src/debug.rs @@ -9,8 +9,8 @@ use reth_primitives::{ Address, Block, BlockId, BlockNumberOrTag, Bytes, TransactionSignedEcRecovered, B256, U256, }; use reth_provider::{ - BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, HeaderProvider, StateProviderFactory, - TransactionVariant, + BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, HeaderProvider, StateProvider, + StateProviderFactory, StateRootProvider, TransactionVariant, }; use reth_revm::database::StateProviderDatabase; use reth_rpc_api::DebugApiServer; @@ -30,13 +30,14 @@ use reth_rpc_types::{ }; use reth_tasks::pool::BlockingTaskGuard; use revm::{ - db::CacheDB, + db::{states::bundle_state::BundleRetention, CacheDB, State}, primitives::{db::DatabaseCommit, BlockEnv, CfgEnvWithHandlerCfg, Env, EnvWithHandlerCfg}, }; use revm_inspectors::tracing::{ js::{JsInspector, TransactionContext}, FourByteInspector, MuxInspector, TracingInspector, TracingInspectorConfig, }; +use revm_primitives::HashMap; use tokio::sync::{AcquireError, OwnedSemaphorePermit}; /// `debug` API implementation. @@ -550,6 +551,66 @@ where .await } + /// The `debug_executionWitness` method allows for re-execution of a block with the purpose of + /// generating an execution witness. The witness comprises of a map of all hashed trie nodes + /// to their preimages that were required during the execution of the block, including during + /// state root recomputation. + pub async fn debug_execution_witness( + &self, + block: BlockNumberOrTag, + ) -> EthResult> { + let block = match self.inner.eth_api.block(block.into()).await? { + None => return Err(EthApiError::UnknownBlockNumber), + Some(res) => res, + }; + let (cfg, block_env, _) = self.inner.eth_api.evm_env_at(block.hash().into()).await?; + let this = self.clone(); + + self.inner + .eth_api + .spawn_with_state_at_block(block.parent_hash.into(), move |state| { + let mut db = State::builder() + .with_database(StateProviderDatabase::new(state)) + .with_bundle_update() + .build(); + + // Re-execute all of the transactions in the block. + for tx in block.raw_transactions() { + let tx_envelope = TransactionSignedEcRecovered::decode(&mut tx.as_ref()) + .map_err(|_| EthApiError::FailedToDecodeSignedTransaction)?; + let env = EnvWithHandlerCfg { + env: Env::boxed( + cfg.cfg_env.clone(), + block_env.clone(), + Call::evm_config(this.eth_api()).tx_env(&tx_envelope), + ), + handler_cfg: cfg.handler_cfg, + }; + let (res, _) = this.inner.eth_api.transact(&mut db, env)?; + db.commit(res.state); + } + + // Merge all transitions, retaining both plain state and reverts. + db.merge_transitions(BundleRetention::Reverts); + + // Take the bundle state. + let bundle = db.take_bundle(); + + // Drop the DB and retrieve the original state provider. + let state_provider = db.database.into_inner(); + + // Recompute the state root, retrieving all trie updates. + // let proof = state_provider.proof(address, keys) + let (_, _trie_updates) = state_provider + .state_root_with_updates(&bundle) + .map_err(|_| EthApiError::InternalEthError)?; + + // TODO: Change + Ok(Default::default()) + }) + .await + } + /// Executes the configured transaction with the environment on the given database. /// /// Returns the trace frame and the state that got updated after executing the transaction. @@ -806,6 +867,15 @@ where .map_err(Into::into) } + /// Handler for `debug_executionWitness` + async fn debug_execution_witness( + &self, + block: BlockNumberOrTag, + ) -> RpcResult> { + let _permit = self.acquire_trace_permit().await; + Ok(Self::debug_execution_witness(self, block).await?) + } + /// Handler for `debug_traceCall` async fn debug_trace_call( &self, From 7283297426884df5661a5098f6bbcb487e56f3e5 Mon Sep 17 00:00:00 2001 From: clabby Date: Tue, 2 Jul 2024 14:45:13 -0400 Subject: [PATCH 2/9] adjust - execution witness (incomplete) --- Cargo.lock | 1 + crates/rpc/rpc/Cargo.toml | 1 + crates/rpc/rpc/src/debug.rs | 81 ++++++++++++++++++++++++++----------- 3 files changed, 60 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a52bf8e3917e..66e863237fe1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8278,6 +8278,7 @@ dependencies = [ "reth-tasks", "reth-testing-utils", "reth-transaction-pool", + "reth-trie", "revm", "revm-inspectors", "revm-primitives", diff --git a/crates/rpc/rpc/Cargo.toml b/crates/rpc/rpc/Cargo.toml index 1baf1f9d57e9..fea6c1705c25 100644 --- a/crates/rpc/rpc/Cargo.toml +++ b/crates/rpc/rpc/Cargo.toml @@ -34,6 +34,7 @@ reth-rpc-eth-types.workspace = true reth-rpc-server-types.workspace = true reth-node-api.workspace = true reth-network-types.workspace = true +reth-trie.workspace = true # eth alloy-dyn-abi.workspace = true diff --git a/crates/rpc/rpc/src/debug.rs b/crates/rpc/rpc/src/debug.rs index 8638d0ab93b3..b48a3c21273e 100644 --- a/crates/rpc/rpc/src/debug.rs +++ b/crates/rpc/rpc/src/debug.rs @@ -4,13 +4,15 @@ use alloy_rlp::{Decodable, Encodable}; use async_trait::async_trait; use jsonrpsee::core::RpcResult; use reth_chainspec::EthereumHardforks; +use reth_errors::ProviderError; use reth_evm::ConfigureEvmEnv; use reth_primitives::{ Address, Block, BlockId, BlockNumberOrTag, Bytes, TransactionSignedEcRecovered, B256, U256, }; use reth_provider::{ - BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, HeaderProvider, StateProvider, - StateProviderFactory, StateRootProvider, TransactionVariant, + BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, HeaderProvider, + HistoricalStateProviderRef, StateProofProvider, StateProvider, StateProviderFactory, + StateRootProvider, TransactionVariant, }; use reth_revm::database::StateProviderDatabase; use reth_rpc_api::DebugApiServer; @@ -29,15 +31,16 @@ use reth_rpc_types::{ BlockError, Bundle, RichBlock, StateContext, TransactionRequest, }; use reth_tasks::pool::BlockingTaskGuard; +use reth_trie::{proof::Proof, HashedPostState}; use revm::{ - db::{states::bundle_state::BundleRetention, CacheDB, State}, + db::{states::bundle_state::BundleRetention, BundleState, CacheDB, State}, primitives::{db::DatabaseCommit, BlockEnv, CfgEnvWithHandlerCfg, Env, EnvWithHandlerCfg}, }; use revm_inspectors::tracing::{ js::{JsInspector, TransactionContext}, FourByteInspector, MuxInspector, TracingInspector, TracingInspectorConfig, }; -use revm_primitives::HashMap; +use revm_primitives::{keccak256, HashMap}; use tokio::sync::{AcquireError, OwnedSemaphorePermit}; /// `debug` API implementation. @@ -569,12 +572,10 @@ where self.inner .eth_api .spawn_with_state_at_block(block.parent_hash.into(), move |state| { - let mut db = State::builder() - .with_database(StateProviderDatabase::new(state)) - .with_bundle_update() - .build(); + let mut db = CacheDB::new(StateProviderDatabase::new(state)); - // Re-execute all of the transactions in the block. + // Re-execute all of the transactions in the block to load all touched accounts into + // the cache DB. for tx in block.raw_transactions() { let tx_envelope = TransactionSignedEcRecovered::decode(&mut tx.as_ref()) .map_err(|_| EthApiError::FailedToDecodeSignedTransaction)?; @@ -586,27 +587,61 @@ where ), handler_cfg: cfg.handler_cfg, }; + let (res, _) = this.inner.eth_api.transact(&mut db, env)?; db.commit(res.state); } - // Merge all transitions, retaining both plain state and reverts. - db.merge_transitions(BundleRetention::Reverts); - - // Take the bundle state. - let bundle = db.take_bundle(); + // Destruct the cache database to retrieve the state provider. + let state = db.db.into_inner(); - // Drop the DB and retrieve the original state provider. - let state_provider = db.database.into_inner(); + // Grab all account proofs for the data accessed during block execution. + let account_proofs = db + .accounts + .into_iter() + .map(|(addr, db_acc)| { + let storage_keys = db_acc + .storage + .into_iter() + .map(|(storage_key, _)| storage_key.into()) + .collect::>(); + + let account_proof = + state.proof(&BundleState::default(), addr, &storage_keys)?; + Ok(account_proof) + }) + .collect::, ProviderError>>()?; + + // Compute the total number of trie nodes in the account proof witnesses. + let total_nodes = account_proofs.iter().fold(0, |acc, proof| { + let account_proof_size = proof.proof.len(); + let storage_proofs_size = + proof.storage_proofs.iter().map(|p| p.proof.len()).sum::(); + acc + account_proof_size + storage_proofs_size + }); + + // Generate the witness by re-hashing all intermediate nodes. + let mut witness = HashMap::with_capacity(total_nodes); + for proof in account_proofs { + // First, add all account proof nodes. + for node in proof.proof.into_iter() { + let hash = keccak256(node.as_ref()); + witness.insert(hash, node); + } - // Recompute the state root, retrieving all trie updates. - // let proof = state_provider.proof(address, keys) - let (_, _trie_updates) = state_provider - .state_root_with_updates(&bundle) - .map_err(|_| EthApiError::InternalEthError)?; + // Next, add all storage proof nodes. + for storage_proof in proof.storage_proofs.into_iter() { + for node in storage_proof.proof.into_iter() { + let hash = keccak256(node.as_ref()); + witness.insert(hash, node); + } + } + } - // TODO: Change - Ok(Default::default()) + // TODO: Also need blinded sibling nodes accessed during deletion, to allow for + // state root recomputation. As is, this is only sufficient for + // executing the block. + Ok(witness) }) .await } From c48453b4c2374f3f6252cc1f2b11a6d2867a06fa Mon Sep 17 00:00:00 2001 From: clabby Date: Tue, 2 Jul 2024 16:25:05 -0400 Subject: [PATCH 3/9] lint remove trailing semicolons --- crates/rpc/rpc-api/src/debug.rs | 3 +-- crates/rpc/rpc/src/debug.rs | 26 ++++++++++---------------- 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/crates/rpc/rpc-api/src/debug.rs b/crates/rpc/rpc-api/src/debug.rs index f073cc434817..0364b2c3e6cd 100644 --- a/crates/rpc/rpc-api/src/debug.rs +++ b/crates/rpc/rpc-api/src/debug.rs @@ -1,5 +1,3 @@ -use std::collections::HashMap; - use jsonrpsee::{core::RpcResult, proc_macros::rpc}; use reth_primitives::{Address, BlockId, BlockNumberOrTag, Bytes, B256}; use reth_rpc_types::{ @@ -9,6 +7,7 @@ use reth_rpc_types::{ }, Bundle, RichBlock, StateContext, TransactionRequest, }; +use std::collections::HashMap; /// Debug rpc interface. #[cfg_attr(not(feature = "client"), rpc(server, namespace = "debug"))] diff --git a/crates/rpc/rpc/src/debug.rs b/crates/rpc/rpc/src/debug.rs index b48a3c21273e..2d0bf002fb55 100644 --- a/crates/rpc/rpc/src/debug.rs +++ b/crates/rpc/rpc/src/debug.rs @@ -1,5 +1,3 @@ -use std::sync::Arc; - use alloy_rlp::{Decodable, Encodable}; use async_trait::async_trait; use jsonrpsee::core::RpcResult; @@ -10,9 +8,8 @@ use reth_primitives::{ Address, Block, BlockId, BlockNumberOrTag, Bytes, TransactionSignedEcRecovered, B256, U256, }; use reth_provider::{ - BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, HeaderProvider, - HistoricalStateProviderRef, StateProofProvider, StateProvider, StateProviderFactory, - StateRootProvider, TransactionVariant, + BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, HeaderProvider, StateProofProvider, + StateProviderFactory, TransactionVariant, }; use reth_revm::database::StateProviderDatabase; use reth_rpc_api::DebugApiServer; @@ -31,9 +28,8 @@ use reth_rpc_types::{ BlockError, Bundle, RichBlock, StateContext, TransactionRequest, }; use reth_tasks::pool::BlockingTaskGuard; -use reth_trie::{proof::Proof, HashedPostState}; use revm::{ - db::{states::bundle_state::BundleRetention, BundleState, CacheDB, State}, + db::{BundleState, CacheDB}, primitives::{db::DatabaseCommit, BlockEnv, CfgEnvWithHandlerCfg, Env, EnvWithHandlerCfg}, }; use revm_inspectors::tracing::{ @@ -41,6 +37,7 @@ use revm_inspectors::tracing::{ FourByteInspector, MuxInspector, TracingInspector, TracingInspectorConfig, }; use revm_primitives::{keccak256, HashMap}; +use std::sync::Arc; use tokio::sync::{AcquireError, OwnedSemaphorePermit}; /// `debug` API implementation. @@ -600,11 +597,8 @@ where .accounts .into_iter() .map(|(addr, db_acc)| { - let storage_keys = db_acc - .storage - .into_iter() - .map(|(storage_key, _)| storage_key.into()) - .collect::>(); + let storage_keys = + db_acc.storage.keys().copied().map(Into::into).collect::>(); let account_proof = state.proof(&BundleState::default(), addr, &storage_keys)?; @@ -624,14 +618,14 @@ where let mut witness = HashMap::with_capacity(total_nodes); for proof in account_proofs { // First, add all account proof nodes. - for node in proof.proof.into_iter() { + for node in proof.proof { let hash = keccak256(node.as_ref()); witness.insert(hash, node); } // Next, add all storage proof nodes. - for storage_proof in proof.storage_proofs.into_iter() { - for node in storage_proof.proof.into_iter() { + for storage_proof in proof.storage_proofs { + for node in storage_proof.proof { let hash = keccak256(node.as_ref()); witness.insert(hash, node); } @@ -668,7 +662,7 @@ where GethDebugBuiltInTracerType::FourByteTracer => { let mut inspector = FourByteInspector::default(); let (res, _) = self.eth_api().inspect(db, env, &mut inspector)?; - return Ok((FourByteFrame::from(inspector).into(), res.state)) + return Ok((FourByteFrame::from(inspector).into(), res.state)); } GethDebugBuiltInTracerType::CallTracer => { let call_config = tracer_config From 0a923a6e23712f11326dbd8ef6611e01d3e4fbb1 Mon Sep 17 00:00:00 2001 From: clabby Date: Wed, 3 Jul 2024 14:59:07 -0400 Subject: [PATCH 4/9] updates --- crates/rpc/rpc/src/debug.rs | 116 +++++++++++++++++++++++++++++++++--- 1 file changed, 109 insertions(+), 7 deletions(-) diff --git a/crates/rpc/rpc/src/debug.rs b/crates/rpc/rpc/src/debug.rs index 2d0bf002fb55..771a7ca53a7e 100644 --- a/crates/rpc/rpc/src/debug.rs +++ b/crates/rpc/rpc/src/debug.rs @@ -9,7 +9,7 @@ use reth_primitives::{ }; use reth_provider::{ BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, HeaderProvider, StateProofProvider, - StateProviderFactory, TransactionVariant, + StateProviderFactory, StateRootProvider, TransactionVariant, }; use reth_revm::database::StateProviderDatabase; use reth_rpc_api::DebugApiServer; @@ -29,8 +29,9 @@ use reth_rpc_types::{ }; use reth_tasks::pool::BlockingTaskGuard; use revm::{ - db::{BundleState, CacheDB}, + db::{states::bundle_state::BundleRetention, BundleState, CacheDB}, primitives::{db::DatabaseCommit, BlockEnv, CfgEnvWithHandlerCfg, Env, EnvWithHandlerCfg}, + StateBuilder, }; use revm_inspectors::tracing::{ js::{JsInspector, TransactionContext}, @@ -569,7 +570,10 @@ where self.inner .eth_api .spawn_with_state_at_block(block.parent_hash.into(), move |state| { - let mut db = CacheDB::new(StateProviderDatabase::new(state)); + let mut db = StateBuilder::new() + .with_database(StateProviderDatabase::new(state)) + .with_bundle_update() + .build(); // Re-execute all of the transactions in the block to load all touched accounts into // the cache DB. @@ -589,16 +593,32 @@ where db.commit(res.state); } + // Merge all state transitions + db.merge_transitions(BundleRetention::Reverts); + + // Take the bundle state + let bundle_state = db.take_bundle(); + // Destruct the cache database to retrieve the state provider. - let state = db.db.into_inner(); + let state = db.database.into_inner(); // Grab all account proofs for the data accessed during block execution. + // + // Note: We grab *all* accounts in the cache here, as the `BundleState` prunes + // referenced accounts + storage slots. let account_proofs = db + .cache .accounts .into_iter() .map(|(addr, db_acc)| { - let storage_keys = - db_acc.storage.keys().copied().map(Into::into).collect::>(); + let storage_keys = db_acc + .account + .ok_or(ProviderError::CacheServiceUnavailable)? + .storage + .keys() + .copied() + .map(Into::into) + .collect::>(); let account_proof = state.proof(&BundleState::default(), addr, &storage_keys)?; @@ -614,7 +634,7 @@ where acc + account_proof_size + storage_proofs_size }); - // Generate the witness by re-hashing all intermediate nodes. + // Generate the execution witness by re-hashing all intermediate nodes. let mut witness = HashMap::with_capacity(total_nodes); for proof in account_proofs { // First, add all account proof nodes. @@ -632,6 +652,88 @@ where } } + // Extend the witness with the information required to reproduce the state root + // after the block execution. + // + // The extra witness data appended within this operation consists of siblings within + // branch nodes that are required to unblind during state root recomputation. + let (_, trie_updates) = state.state_root_with_updates(&bundle_state)?; + for _path in trie_updates.removed_nodes_ref() { + // // Fetch parent `BranchNodeCompact` from the DB. It must be a branch node, as + // // extension nodes never point to single leaves. + // let (mut cursor, deleted_node_nibble, path): ( + // Box, + // u8, + // Nibbles, + // ) = match path { + // TrieKey::AccountNode(nibbles) => { + // let cursor = this + // .inner + // .provider + // .account_trie_cursor() + // .map_err(ProviderError::Database)?; + // + // (Box::new(cursor), nibbles[0], nibbles.slice(1..)) + // } + // TrieKey::StorageNode(hashed_address, nibbles) => { + // let cursor = this + // .inner + // .provider + // .storage_trie_cursor(hashed_address) + // .map_err(ProviderError::Database)?; + // + // (Box::new(cursor), nibbles[0], nibbles.slice(1..)) + // } + // TrieKey::StorageTrie(_) => { + // // Ignore storage trie root updates; These are not required for this + // portion of the // witness. + // continue; + // } + // }; + // + // // Fetch the parent branch node from the database. + // let (_, branch) = cursor + // .seek_exact(path.clone()) + // .map_err(ProviderError::Database)? + // .ok_or(ProviderError::Database(DatabaseError::Other( + // "Failed to seek to branch node".to_string(), + // )))?; + // + // // Check if there are only two children within the parent branch. If there + // are, one is the deleted // leaf, and the other is the + // sibling that we need to unblind. Otherwise, we can continue, as + // // the branch node will remain after the deletion. + // let sibling_nibble = if branch.state_mask.count_ones() != 2 { + // continue; + // } else { + // // Find the first set bit. + // let first_bit_index = branch.state_mask.trailing_zeros() as u8; + // + // // Create a new mask, clearing the first set bit. + // let mask = branch.state_mask.get() & (branch.state_mask.get() - 1); + // + // // Find the second set bit. + // let second_bit_index = mask.trailing_zeros() as u8; + // + // if first_bit_index == deleted_node_nibble { + // second_bit_index + // } else { + // first_bit_index + // } + // }; + // let _sibling_path = Nibbles::from_nibbles_unchecked( + // std::iter::once(sibling_nibble) + // .chain(path.iter().copied()) + // .collect::>(), + // ); + // + // // Rebuild the sub-trie rooted at the sibling node using `TrieWalker` + + // // `HashBuilder`. + // // TODO + // + // // Add the preimage of the sibling node to the witness. + } + // TODO: Also need blinded sibling nodes accessed during deletion, to allow for // state root recomputation. As is, this is only sufficient for // executing the block. From c86ac57393713f9f55cec621c4682be3bb391d24 Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Tue, 30 Jul 2024 23:19:19 +0200 Subject: [PATCH 5/9] rebase & clean up --- crates/rpc/rpc/src/debug.rs | 165 +++++++----------------------------- 1 file changed, 29 insertions(+), 136 deletions(-) diff --git a/crates/rpc/rpc/src/debug.rs b/crates/rpc/rpc/src/debug.rs index 771a7ca53a7e..6d5ce418a024 100644 --- a/crates/rpc/rpc/src/debug.rs +++ b/crates/rpc/rpc/src/debug.rs @@ -8,8 +8,8 @@ use reth_primitives::{ Address, Block, BlockId, BlockNumberOrTag, Bytes, TransactionSignedEcRecovered, B256, U256, }; use reth_provider::{ - BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, HeaderProvider, StateProofProvider, - StateProviderFactory, StateRootProvider, TransactionVariant, + BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, HeaderProvider, StateProviderFactory, + TransactionVariant, }; use reth_revm::database::StateProviderDatabase; use reth_rpc_api::DebugApiServer; @@ -28,8 +28,9 @@ use reth_rpc_types::{ BlockError, Bundle, RichBlock, StateContext, TransactionRequest, }; use reth_tasks::pool::BlockingTaskGuard; +use reth_trie::{HashedPostState, HashedStorage}; use revm::{ - db::{states::bundle_state::BundleRetention, BundleState, CacheDB}, + db::{states::bundle_state::BundleRetention, CacheDB}, primitives::{db::DatabaseCommit, BlockEnv, CfgEnvWithHandlerCfg, Env, EnvWithHandlerCfg}, StateBuilder, }; @@ -559,9 +560,9 @@ where pub async fn debug_execution_witness( &self, block: BlockNumberOrTag, - ) -> EthResult> { + ) -> Result, Eth::Error> { let block = match self.inner.eth_api.block(block.into()).await? { - None => return Err(EthApiError::UnknownBlockNumber), + None => return Err(EthApiError::UnknownBlockNumber.into()), Some(res) => res, }; let (cfg, block_env, _) = self.inner.eth_api.evm_env_at(block.hash().into()).await?; @@ -600,144 +601,36 @@ where let bundle_state = db.take_bundle(); // Destruct the cache database to retrieve the state provider. - let state = db.database.into_inner(); + let _state = db.database.into_inner(); // Grab all account proofs for the data accessed during block execution. // // Note: We grab *all* accounts in the cache here, as the `BundleState` prunes // referenced accounts + storage slots. - let account_proofs = db - .cache - .accounts - .into_iter() - .map(|(addr, db_acc)| { - let storage_keys = db_acc - .account - .ok_or(ProviderError::CacheServiceUnavailable)? - .storage - .keys() - .copied() - .map(Into::into) - .collect::>(); - - let account_proof = - state.proof(&BundleState::default(), addr, &storage_keys)?; - Ok(account_proof) - }) - .collect::, ProviderError>>()?; - - // Compute the total number of trie nodes in the account proof witnesses. - let total_nodes = account_proofs.iter().fold(0, |acc, proof| { - let account_proof_size = proof.proof.len(); - let storage_proofs_size = - proof.storage_proofs.iter().map(|p| p.proof.len()).sum::(); - acc + account_proof_size + storage_proofs_size - }); - - // Generate the execution witness by re-hashing all intermediate nodes. - let mut witness = HashMap::with_capacity(total_nodes); - for proof in account_proofs { - // First, add all account proof nodes. - for node in proof.proof { - let hash = keccak256(node.as_ref()); - witness.insert(hash, node); - } - - // Next, add all storage proof nodes. - for storage_proof in proof.storage_proofs { - for node in storage_proof.proof { - let hash = keccak256(node.as_ref()); - witness.insert(hash, node); - } + let mut hashed_state = HashedPostState::from_bundle_state(&bundle_state.state); + + for (address, account) in db.cache.accounts { + let hashed_address = keccak256(address); + hashed_state.accounts.insert( + hashed_address, + account.account.as_ref().map(|a| a.info.clone().into()), + ); + + let storage = hashed_state + .storages + .entry(hashed_address) + .or_insert_with(|| HashedStorage::new(account.status.was_destroyed())); + for (slot, value) in account + .account + .ok_or(ProviderError::CacheServiceUnavailable.into())? + .storage + { + let hashed_slot = keccak256(Into::::into(slot)); + storage.storage.insert(hashed_slot, value); } } - // Extend the witness with the information required to reproduce the state root - // after the block execution. - // - // The extra witness data appended within this operation consists of siblings within - // branch nodes that are required to unblind during state root recomputation. - let (_, trie_updates) = state.state_root_with_updates(&bundle_state)?; - for _path in trie_updates.removed_nodes_ref() { - // // Fetch parent `BranchNodeCompact` from the DB. It must be a branch node, as - // // extension nodes never point to single leaves. - // let (mut cursor, deleted_node_nibble, path): ( - // Box, - // u8, - // Nibbles, - // ) = match path { - // TrieKey::AccountNode(nibbles) => { - // let cursor = this - // .inner - // .provider - // .account_trie_cursor() - // .map_err(ProviderError::Database)?; - // - // (Box::new(cursor), nibbles[0], nibbles.slice(1..)) - // } - // TrieKey::StorageNode(hashed_address, nibbles) => { - // let cursor = this - // .inner - // .provider - // .storage_trie_cursor(hashed_address) - // .map_err(ProviderError::Database)?; - // - // (Box::new(cursor), nibbles[0], nibbles.slice(1..)) - // } - // TrieKey::StorageTrie(_) => { - // // Ignore storage trie root updates; These are not required for this - // portion of the // witness. - // continue; - // } - // }; - // - // // Fetch the parent branch node from the database. - // let (_, branch) = cursor - // .seek_exact(path.clone()) - // .map_err(ProviderError::Database)? - // .ok_or(ProviderError::Database(DatabaseError::Other( - // "Failed to seek to branch node".to_string(), - // )))?; - // - // // Check if there are only two children within the parent branch. If there - // are, one is the deleted // leaf, and the other is the - // sibling that we need to unblind. Otherwise, we can continue, as - // // the branch node will remain after the deletion. - // let sibling_nibble = if branch.state_mask.count_ones() != 2 { - // continue; - // } else { - // // Find the first set bit. - // let first_bit_index = branch.state_mask.trailing_zeros() as u8; - // - // // Create a new mask, clearing the first set bit. - // let mask = branch.state_mask.get() & (branch.state_mask.get() - 1); - // - // // Find the second set bit. - // let second_bit_index = mask.trailing_zeros() as u8; - // - // if first_bit_index == deleted_node_nibble { - // second_bit_index - // } else { - // first_bit_index - // } - // }; - // let _sibling_path = Nibbles::from_nibbles_unchecked( - // std::iter::once(sibling_nibble) - // .chain(path.iter().copied()) - // .collect::>(), - // ); - // - // // Rebuild the sub-trie rooted at the sibling node using `TrieWalker` + - // // `HashBuilder`. - // // TODO - // - // // Add the preimage of the sibling node to the witness. - } - - // TODO: Also need blinded sibling nodes accessed during deletion, to allow for - // state root recomputation. As is, this is only sufficient for - // executing the block. - Ok(witness) + todo!() }) .await } @@ -1004,7 +897,7 @@ where block: BlockNumberOrTag, ) -> RpcResult> { let _permit = self.acquire_trace_permit().await; - Ok(Self::debug_execution_witness(self, block).await?) + Self::debug_execution_witness(self, block).await.map_err(Into::into) } /// Handler for `debug_traceCall` From 3e2c7029bcb380b4fbcadf805f6e7457b677a91e Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Thu, 1 Aug 2024 17:29:34 +0200 Subject: [PATCH 6/9] generate witness --- crates/rpc/rpc/src/debug.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/rpc/rpc/src/debug.rs b/crates/rpc/rpc/src/debug.rs index 6d5ce418a024..174ca127c765 100644 --- a/crates/rpc/rpc/src/debug.rs +++ b/crates/rpc/rpc/src/debug.rs @@ -8,8 +8,8 @@ use reth_primitives::{ Address, Block, BlockId, BlockNumberOrTag, Bytes, TransactionSignedEcRecovered, B256, U256, }; use reth_provider::{ - BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, HeaderProvider, StateProviderFactory, - TransactionVariant, + BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, HeaderProvider, StateProofProvider, + StateProviderFactory, TransactionVariant, }; use reth_revm::database::StateProviderDatabase; use reth_rpc_api::DebugApiServer; @@ -600,15 +600,11 @@ where // Take the bundle state let bundle_state = db.take_bundle(); - // Destruct the cache database to retrieve the state provider. - let _state = db.database.into_inner(); - // Grab all account proofs for the data accessed during block execution. // // Note: We grab *all* accounts in the cache here, as the `BundleState` prunes // referenced accounts + storage slots. let mut hashed_state = HashedPostState::from_bundle_state(&bundle_state.state); - for (address, account) in db.cache.accounts { let hashed_address = keccak256(address); hashed_state.accounts.insert( @@ -630,7 +626,11 @@ where } } - todo!() + // Generate an execution witness for the aggregated state of accessed accounts. + // Destruct the cache database to retrieve the state provider. + let state_provider = db.database.into_inner(); + let witness = state_provider.witness(HashedPostState::default(), hashed_state)?; + Ok(witness) }) .await } From 3f6fa815f1ba0e1163712eeff943d1428852d168 Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Thu, 1 Aug 2024 17:33:50 +0200 Subject: [PATCH 7/9] fix error conversion --- crates/rpc/rpc/src/debug.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/rpc/rpc/src/debug.rs b/crates/rpc/rpc/src/debug.rs index 174ca127c765..8e9b7d2fbf4e 100644 --- a/crates/rpc/rpc/src/debug.rs +++ b/crates/rpc/rpc/src/debug.rs @@ -629,7 +629,9 @@ where // Generate an execution witness for the aggregated state of accessed accounts. // Destruct the cache database to retrieve the state provider. let state_provider = db.database.into_inner(); - let witness = state_provider.witness(HashedPostState::default(), hashed_state)?; + let witness = state_provider + .witness(HashedPostState::default(), hashed_state) + .map_err(Into::into)?; Ok(witness) }) .await From ccbfa3afd3d56ba85404d1fac2771e481bf0a456 Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Thu, 1 Aug 2024 21:43:49 +0200 Subject: [PATCH 8/9] address comments --- crates/rpc/rpc/src/debug.rs | 46 +++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/crates/rpc/rpc/src/debug.rs b/crates/rpc/rpc/src/debug.rs index 8e9b7d2fbf4e..c20e142a1f57 100644 --- a/crates/rpc/rpc/src/debug.rs +++ b/crates/rpc/rpc/src/debug.rs @@ -2,8 +2,7 @@ use alloy_rlp::{Decodable, Encodable}; use async_trait::async_trait; use jsonrpsee::core::RpcResult; use reth_chainspec::EthereumHardforks; -use reth_errors::ProviderError; -use reth_evm::ConfigureEvmEnv; +use reth_evm::{system_calls::pre_block_beacon_root_contract_call, ConfigureEvmEnv}; use reth_primitives::{ Address, Block, BlockId, BlockNumberOrTag, Bytes, TransactionSignedEcRecovered, B256, U256, }; @@ -559,23 +558,37 @@ where /// state root recomputation. pub async fn debug_execution_witness( &self, - block: BlockNumberOrTag, + block_id: BlockNumberOrTag, ) -> Result, Eth::Error> { - let block = match self.inner.eth_api.block(block.into()).await? { - None => return Err(EthApiError::UnknownBlockNumber.into()), - Some(res) => res, - }; - let (cfg, block_env, _) = self.inner.eth_api.evm_env_at(block.hash().into()).await?; + let ((cfg, block_env, _), maybe_block) = futures::try_join!( + self.inner.eth_api.evm_env_at(block_id.into()), + self.inner.eth_api.block_with_senders(block_id.into()), + )?; + let block = maybe_block.ok_or(EthApiError::UnknownBlockNumber.into())?; + let this = self.clone(); self.inner .eth_api .spawn_with_state_at_block(block.parent_hash.into(), move |state| { + let evm_config = Call::evm_config(this.eth_api()).clone(); let mut db = StateBuilder::new() .with_database(StateProviderDatabase::new(state)) .with_bundle_update() .build(); + pre_block_beacon_root_contract_call( + &mut db, + &evm_config, + &this.inner.provider.chain_spec(), + &cfg, + &block_env, + block.timestamp, + block.number, + block.parent_beacon_block_root, + ) + .map_err(|err| EthApiError::Internal(err.into()))?; + // Re-execute all of the transactions in the block to load all touched accounts into // the cache DB. for tx in block.raw_transactions() { @@ -585,7 +598,7 @@ where env: Env::boxed( cfg.cfg_env.clone(), block_env.clone(), - Call::evm_config(this.eth_api()).tx_env(&tx_envelope), + evm_config.tx_env(&tx_envelope), ), handler_cfg: cfg.handler_cfg, }; @@ -616,13 +629,12 @@ where .storages .entry(hashed_address) .or_insert_with(|| HashedStorage::new(account.status.was_destroyed())); - for (slot, value) in account - .account - .ok_or(ProviderError::CacheServiceUnavailable.into())? - .storage - { - let hashed_slot = keccak256(Into::::into(slot)); - storage.storage.insert(hashed_slot, value); + + if let Some(account) = account.account { + for (slot, value) in account.storage { + let hashed_slot = keccak256(B256::from(slot)); + storage.storage.insert(hashed_slot, value); + } } } @@ -659,7 +671,7 @@ where GethDebugBuiltInTracerType::FourByteTracer => { let mut inspector = FourByteInspector::default(); let (res, _) = self.eth_api().inspect(db, env, &mut inspector)?; - return Ok((FourByteFrame::from(inspector).into(), res.state)); + return Ok((FourByteFrame::from(inspector).into(), res.state)) } GethDebugBuiltInTracerType::CallTracer => { let call_config = tracer_config From 0ae07c8c14d60c5ed48b7359b4c80f9e2210cae0 Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Thu, 1 Aug 2024 21:51:16 +0200 Subject: [PATCH 9/9] rm into --- crates/rpc/rpc/src/debug.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/rpc/rpc/src/debug.rs b/crates/rpc/rpc/src/debug.rs index c20e142a1f57..b3f43f7267a2 100644 --- a/crates/rpc/rpc/src/debug.rs +++ b/crates/rpc/rpc/src/debug.rs @@ -564,7 +564,7 @@ where self.inner.eth_api.evm_env_at(block_id.into()), self.inner.eth_api.block_with_senders(block_id.into()), )?; - let block = maybe_block.ok_or(EthApiError::UnknownBlockNumber.into())?; + let block = maybe_block.ok_or(EthApiError::UnknownBlockNumber)?; let this = self.clone();