diff --git a/Cargo.lock b/Cargo.lock index 8c677e018bfc3..a9c9d21f65aae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8140,6 +8140,7 @@ dependencies = [ "reth-tokio-util", "reth-tracing", "reth-transaction-pool", + "reth-trie", "serde", "serde_json", "thiserror", diff --git a/crates/rpc/rpc-builder/Cargo.toml b/crates/rpc/rpc-builder/Cargo.toml index 7b7c89c0fcff4..4f577a16fdf5b 100644 --- a/crates/rpc/rpc-builder/Cargo.toml +++ b/crates/rpc/rpc-builder/Cargo.toml @@ -26,6 +26,7 @@ reth-tasks = { workspace = true, features = ["rayon"] } reth-transaction-pool.workspace = true reth-evm.workspace = true reth-engine-primitives.workspace = true +reth-trie.workspace = true # rpc/net jsonrpsee = { workspace = true, features = ["server"] } diff --git a/crates/rpc/rpc-builder/src/lib.rs b/crates/rpc/rpc-builder/src/lib.rs index 2800e83d11546..79da01db8451f 100644 --- a/crates/rpc/rpc-builder/src/lib.rs +++ b/crates/rpc/rpc-builder/src/lib.rs @@ -187,6 +187,7 @@ use reth_rpc_eth_types::{EthStateCache, EthSubscriptionIdProvider}; use reth_rpc_layer::{AuthLayer, Claims, JwtAuthValidator, JwtSecret}; use reth_tasks::{pool::BlockingTaskGuard, TaskSpawner, TokioTaskExecutor}; use reth_transaction_pool::{noop::NoopTransactionPool, TransactionPool}; +use reth_trie::trie_cursor::TrieCursorFactory; use serde::{Deserialize, Serialize}; use std::{ collections::HashMap, @@ -244,6 +245,7 @@ where + EvmEnvProvider + ChainSpecProvider + ChangeSetReader + + TrieCursorFactory + Clone + Unpin + 'static, @@ -435,6 +437,7 @@ where + EvmEnvProvider + ChainSpecProvider + ChangeSetReader + + TrieCursorFactory + Clone + Unpin + 'static, @@ -760,6 +763,7 @@ where + EvmEnvProvider + ChainSpecProvider + ChangeSetReader + + TrieCursorFactory + Clone + Unpin + 'static, diff --git a/crates/rpc/rpc/src/debug.rs b/crates/rpc/rpc/src/debug.rs index 652ed8df8caff..70c1e990683f8 100644 --- a/crates/rpc/rpc/src/debug.rs +++ b/crates/rpc/rpc/src/debug.rs @@ -2,15 +2,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_errors::{DatabaseError, ProviderError}; use reth_evm::ConfigureEvmEnv; use reth_primitives::{ Address, Block, BlockId, BlockNumberOrTag, Bytes, TransactionSignedEcRecovered, Withdrawals, B256, U256, }; use reth_provider::{ - BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, HeaderProvider, StateProvider, - StateProviderFactory, StateRootProvider, TransactionVariant, + errors::db::DatabaseErrorInfo, BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, + HeaderProvider, StateProvider, StateProviderFactory, StateRootProvider, TransactionVariant, }; use reth_revm::database::StateProviderDatabase; use reth_rpc_api::DebugApiServer; @@ -26,7 +26,11 @@ use reth_rpc_types::{ BlockError, Bundle, RichBlock, StateContext, TransactionRequest, }; use reth_tasks::pool::BlockingTaskGuard; -use reth_trie::updates::TrieOp; +use reth_trie::{ + trie_cursor::{DatabaseAccountTrieCursor, TrieCursor, TrieCursorFactory}, + updates::{TrieKey, TrieOp}, + Nibbles, +}; use revm::{ db::{states::bundle_state::BundleRetention, CacheDB}, primitives::{db::DatabaseCommit, BlockEnv, CfgEnvWithHandlerCfg, Env, EnvWithHandlerCfg}, @@ -71,6 +75,7 @@ where + ChainSpecProvider + StateProviderFactory + EvmEnvProvider + + TrieCursorFactory + 'static, Eth: TraceExt + 'static, { @@ -90,7 +95,7 @@ where ) -> EthResult> { if transactions.is_empty() { // nothing to trace - return Ok(Vec::new()) + return Ok(Vec::new()); } // replay all transactions of the block @@ -302,7 +307,7 @@ where Ok(inspector) }) .await?; - return Ok(FourByteFrame::from(inspector).into()) + return Ok(FourByteFrame::from(inspector).into()); } GethDebugBuiltInTracerType::CallTracer => { let call_config = tracer_config @@ -324,7 +329,7 @@ where Ok(frame.into()) }) .await?; - return Ok(frame) + return Ok(frame); } GethDebugBuiltInTracerType::PreStateTracer => { let prestate_config = tracer_config @@ -350,7 +355,7 @@ where Ok(frame) }) .await?; - return Ok(frame.into()) + return Ok(frame.into()); } GethDebugBuiltInTracerType::NoopTracer => Ok(NoopFrame::default().into()), GethDebugBuiltInTracerType::MuxTracer => { @@ -374,7 +379,7 @@ where Ok(frame.into()) }) .await?; - return Ok(frame) + return Ok(frame); } }, GethDebugTracerType::JsTracer(code) => { @@ -399,7 +404,7 @@ where Ok(GethTrace::JS(res)) } - } + }; } // default structlog tracer @@ -432,7 +437,7 @@ where opts: Option, ) -> EthResult>> { if bundles.is_empty() { - return Err(EthApiError::InvalidParams(String::from("bundles are empty."))) + return Err(EthApiError::InvalidParams(String::from("bundles are empty."))); } let StateContext { transaction_index, block_number } = state_context.unwrap_or_default(); @@ -560,6 +565,8 @@ where .with_bundle_update() .build(); + // TODO: 4788 call + // Re-execute all of the transactions in the block to load all touched accounts into // the cache DB. for tx in block.raw_transactions() { @@ -647,16 +654,77 @@ where .filter(|(_, op)| matches!(op, TrieOp::Delete)) .map(|(path, _)| path) .collect::>(); - for _path in trie_deletions { + for path in trie_deletions { // 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; + } + }; - // Iterate through all of the hashes in the parent branch node. If there is only - // a single non-empty hash left, take the path to that - // sibling node. + // 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. } @@ -708,7 +776,7 @@ where .into_geth_builder() .geth_call_traces(call_config, res.result.gas_used()); - return Ok((frame.into(), res.state)) + return Ok((frame.into(), res.state)); } GethDebugBuiltInTracerType::PreStateTracer => { let prestate_config = tracer_config @@ -726,7 +794,7 @@ where db, )?; - return Ok((frame.into(), res.state)) + return Ok((frame.into(), res.state)); } GethDebugBuiltInTracerType::NoopTracer => { Ok((NoopFrame::default().into(), Default::default())) @@ -740,7 +808,7 @@ where let (res, _) = self.eth_api().inspect(&mut *db, env, &mut inspector)?; let frame = inspector.try_into_mux_frame(&res, db)?; - return Ok((frame.into(), res.state)) + return Ok((frame.into(), res.state)); } }, GethDebugTracerType::JsTracer(code) => { @@ -756,7 +824,7 @@ where let result = inspector.json_result(res, &env, db)?; Ok((GethTrace::JS(result), state)) } - } + }; } // default structlog tracer @@ -781,6 +849,7 @@ where + ChainSpecProvider + StateProviderFactory + EvmEnvProvider + + TrieCursorFactory + 'static, Eth: EthApiSpec + EthTransactions + TraceExt + 'static, { diff --git a/crates/storage/provider/src/test_utils/noop.rs b/crates/storage/provider/src/test_utils/noop.rs index d52da187f7dd9..9733d518b80e4 100644 --- a/crates/storage/provider/src/test_utils/noop.rs +++ b/crates/storage/provider/src/test_utils/noop.rs @@ -18,7 +18,7 @@ use reth_primitives::{ use reth_prune_types::{PruneCheckpoint, PruneSegment}; use reth_stages_types::{StageCheckpoint, StageId}; use reth_storage_errors::provider::ProviderResult; -use reth_trie::{updates::TrieUpdates, AccountProof}; +use reth_trie::{trie_cursor::{noop::{NoopAccountTrieCursor, NoopStorageTrieCursor}, TrieCursorFactory}, updates::TrieUpdates, AccountProof}; use revm::{ db::BundleState, primitives::{BlockEnv, CfgEnvWithHandlerCfg}, @@ -308,6 +308,22 @@ impl ChangeSetReader for NoopProvider { } } +impl TrieCursorFactory for NoopProvider { + type AccountTrieCursor = NoopAccountTrieCursor; + type StorageTrieCursor = NoopStorageTrieCursor; + + fn account_trie_cursor(&self) -> Result { + unimplemented!() + } + + fn storage_trie_cursor( + &self, + _hashed_address: B256, + ) -> Result { + unimplemented!() + } +} + impl StateRootProvider for NoopProvider { fn state_root(&self, _state: &BundleState) -> ProviderResult { Ok(B256::default())