reth_ethereum_cli/debug_cmd/
in_memory_merkle.rs

1//! Command for debugging in-memory merkle trie calculation.
2
3use 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/// `reth debug in-memory-merkle` command
39/// This debug routine requires that the node is positioned at the block before the target.
40/// The script will then download the block from p2p network and attempt to calculate and verify
41/// merkle root for it.
42#[derive(Debug, Parser)]
43pub struct Command<C: ChainSpecParser> {
44    #[command(flatten)]
45    env: EnvironmentArgs<C>,
46
47    #[command(flatten)]
48    network: NetworkArgs,
49
50    /// The number of retries per request
51    #[arg(long, default_value = "5")]
52    retries: usize,
53
54    /// The depth after which we should start comparing branch nodes
55    #[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    /// Execute `debug in-memory-merkle` command
91    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        // Look up merkle checkpoint
101        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        // Configure and build network
108        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        // Unpacked `BundleState::state_root_slow` function
155        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        // Insert block, state and hashes
168        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        // Compare updates
196        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 without committing.
232        drop(provider_rw);
233
234        Ok(())
235    }
236}
237
238impl<C: ChainSpecParser> Command<C> {
239    /// Returns the underlying chain being used to run this command
240    pub const fn chain_spec(&self) -> Option<&Arc<C::ChainSpec>> {
241        Some(&self.env.chain)
242    }
243}