reth_ethereum_cli/debug_cmd/
in_memory_merkle.rs1use alloy_consensus::BlockHeader;
4use alloy_eips::BlockHashOrNumber;
5use backon::{ConstantBuilder, Retryable};
6use clap::Parser;
7use reth_chainspec::ChainSpec;
8use reth_cli::chainspec::ChainSpecParser;
9use reth_cli_commands::common::{AccessRights, CliNodeTypes, Environment, EnvironmentArgs};
10use reth_cli_runner::CliContext;
11use reth_cli_util::get_secret_key;
12use reth_config::Config;
13use reth_ethereum_primitives::EthPrimitives;
14use reth_evm::{execute::Executor, ConfigureEvm};
15use reth_execution_types::ExecutionOutcome;
16use reth_network::{BlockDownloaderProvider, NetworkHandle};
17use reth_network_api::NetworkInfo;
18use reth_node_api::{BlockTy, NodePrimitives};
19use reth_node_core::{
20 args::NetworkArgs,
21 utils::{get_single_body, get_single_header},
22};
23use reth_node_ethereum::{consensus::EthBeaconConsensus, EthEvmConfig};
24use reth_primitives_traits::SealedBlock;
25use reth_provider::{
26 providers::ProviderNodeTypes, AccountExtReader, ChainSpecProvider, DatabaseProviderFactory,
27 HashedPostStateProvider, HashingWriter, LatestStateProviderRef, OriginalValuesKnown,
28 ProviderFactory, StageCheckpointReader, StateWriter, StorageLocation, StorageReader,
29};
30use reth_revm::database::StateProviderDatabase;
31use reth_stages::StageId;
32use reth_tasks::TaskExecutor;
33use reth_trie::StateRoot;
34use reth_trie_db::DatabaseStateRoot;
35use std::{path::PathBuf, sync::Arc};
36use tracing::*;
37
38#[derive(Debug, Parser)]
43pub struct Command<C: ChainSpecParser> {
44 #[command(flatten)]
45 env: EnvironmentArgs<C>,
46
47 #[command(flatten)]
48 network: NetworkArgs,
49
50 #[arg(long, default_value = "5")]
52 retries: usize,
53
54 #[arg(long)]
56 skip_node_depth: Option<usize>,
57}
58
59impl<C: ChainSpecParser<ChainSpec = ChainSpec>> Command<C> {
60 async fn build_network<
61 N: ProviderNodeTypes<
62 ChainSpec = C::ChainSpec,
63 Primitives: NodePrimitives<
64 Block = reth_ethereum_primitives::Block,
65 Receipt = reth_ethereum_primitives::Receipt,
66 BlockHeader = alloy_consensus::Header,
67 >,
68 >,
69 >(
70 &self,
71 config: &Config,
72 task_executor: TaskExecutor,
73 provider_factory: ProviderFactory<N>,
74 network_secret_path: PathBuf,
75 default_peers_path: PathBuf,
76 ) -> eyre::Result<NetworkHandle> {
77 let secret_key = get_secret_key(&network_secret_path)?;
78 let network = self
79 .network
80 .network_config(config, provider_factory.chain_spec(), secret_key, default_peers_path)
81 .with_task_executor(Box::new(task_executor))
82 .build(provider_factory)
83 .start_network()
84 .await?;
85 info!(target: "reth::cli", peer_id = %network.peer_id(), local_addr = %network.local_addr(), "Connected to P2P network");
86 debug!(target: "reth::cli", peer_id = ?network.peer_id(), "Full peer ID");
87 Ok(network)
88 }
89
90 pub async fn execute<N: CliNodeTypes<ChainSpec = C::ChainSpec, Primitives = EthPrimitives>>(
92 self,
93 ctx: CliContext,
94 ) -> eyre::Result<()> {
95 let Environment { provider_factory, config, data_dir } =
96 self.env.init::<N>(AccessRights::RW)?;
97
98 let provider = provider_factory.provider()?;
99
100 let merkle_checkpoint = provider
102 .get_stage_checkpoint(StageId::MerkleExecute)?
103 .expect("merkle checkpoint exists");
104
105 let merkle_block_number = merkle_checkpoint.block_number;
106
107 let network_secret_path =
109 self.network.p2p_secret_key.clone().unwrap_or_else(|| data_dir.p2p_secret());
110 let network = self
111 .build_network(
112 &config,
113 ctx.task_executor.clone(),
114 provider_factory.clone(),
115 network_secret_path,
116 data_dir.known_peers(),
117 )
118 .await?;
119
120 let target_block_number = merkle_block_number + 1;
121
122 info!(target: "reth::cli", target_block_number, "Downloading full block");
123 let fetch_client = network.fetch_client().await?;
124
125 let retries = self.retries.max(1);
126 let backoff = ConstantBuilder::default().with_max_times(retries);
127
128 let client = fetch_client.clone();
129 let header = (move || {
130 get_single_header(client.clone(), BlockHashOrNumber::Number(target_block_number))
131 })
132 .retry(backoff)
133 .notify(|err, _| warn!(target: "reth::cli", "Error requesting header: {err}. Retrying..."))
134 .await?;
135
136 let client = fetch_client.clone();
137 let chain = provider_factory.chain_spec();
138 let consensus = Arc::new(EthBeaconConsensus::new(chain.clone()));
139 let block: SealedBlock<BlockTy<N>> = (move || {
140 get_single_body(client.clone(), header.clone(), consensus.clone())
141 })
142 .retry(backoff)
143 .notify(|err, _| warn!(target: "reth::cli", "Error requesting body: {err}. Retrying..."))
144 .await?;
145
146 let state_provider = LatestStateProviderRef::new(&provider);
147 let db = StateProviderDatabase::new(&state_provider);
148
149 let evm_config = EthEvmConfig::ethereum(provider_factory.chain_spec());
150 let executor = evm_config.batch_executor(db);
151 let block_execution_output = executor.execute(&block.clone().try_recover()?)?;
152 let execution_outcome = ExecutionOutcome::from((block_execution_output, block.number()));
153
154 let (in_memory_state_root, in_memory_updates) = StateRoot::overlay_root_with_updates(
156 provider.tx_ref(),
157 state_provider.hashed_post_state(execution_outcome.state()),
158 )?;
159
160 if in_memory_state_root == block.state_root() {
161 info!(target: "reth::cli", state_root = ?in_memory_state_root, "Computed in-memory state root matches");
162 return Ok(())
163 }
164
165 let provider_rw = provider_factory.database_provider_rw()?;
166
167 provider_rw.insert_historical_block(block.clone().try_recover()?)?;
169 provider_rw.write_state(
170 &execution_outcome,
171 OriginalValuesKnown::No,
172 StorageLocation::Database,
173 )?;
174 let storage_lists =
175 provider_rw.changed_storages_with_range(block.number..=block.number())?;
176 let storages = provider_rw.plain_state_storages(storage_lists)?;
177 provider_rw.insert_storage_for_hashing(storages)?;
178 let account_lists =
179 provider_rw.changed_accounts_with_range(block.number..=block.number())?;
180 let accounts = provider_rw.basic_accounts(account_lists)?;
181 provider_rw.insert_account_for_hashing(accounts)?;
182
183 let (state_root, incremental_trie_updates) = StateRoot::incremental_root_with_updates(
184 provider_rw.tx_ref(),
185 block.number..=block.number(),
186 )?;
187 if state_root != block.state_root() {
188 eyre::bail!(
189 "Computed incremental state root mismatch. Expected: {:?}. Got: {:?}",
190 block.state_root,
191 state_root
192 );
193 }
194
195 let mut in_mem_mismatched = Vec::new();
197 let mut incremental_mismatched = Vec::new();
198 let mut in_mem_updates_iter = in_memory_updates.account_nodes_ref().iter().peekable();
199 let mut incremental_updates_iter =
200 incremental_trie_updates.account_nodes_ref().iter().peekable();
201
202 while in_mem_updates_iter.peek().is_some() || incremental_updates_iter.peek().is_some() {
203 match (in_mem_updates_iter.next(), incremental_updates_iter.next()) {
204 (Some(in_mem), Some(incr)) => {
205 similar_asserts::assert_eq!(in_mem.0, incr.0, "Nibbles don't match");
206 if in_mem.1 != incr.1 &&
207 in_mem.0.len() > self.skip_node_depth.unwrap_or_default()
208 {
209 in_mem_mismatched.push(in_mem);
210 incremental_mismatched.push(incr);
211 }
212 }
213 (Some(in_mem), None) => {
214 warn!(target: "reth::cli", next = ?in_mem, "In-memory trie updates have more entries");
215 }
216 (None, Some(incr)) => {
217 tracing::warn!(target: "reth::cli", next = ?incr, "Incremental trie updates have more entries");
218 }
219 (None, None) => {
220 tracing::info!(target: "reth::cli", "Exhausted all trie updates entries");
221 }
222 }
223 }
224
225 similar_asserts::assert_eq!(
226 incremental_mismatched,
227 in_mem_mismatched,
228 "Mismatched trie updates"
229 );
230
231 drop(provider_rw);
233
234 Ok(())
235 }
236}
237
238impl<C: ChainSpecParser> Command<C> {
239 pub const fn chain_spec(&self) -> Option<&Arc<C::ChainSpec>> {
241 Some(&self.env.chain)
242 }
243}