feat(cli): merkle debug cmd (#2314)

This commit is contained in:
Roman Krasiuk
2023-04-20 20:10:04 +03:00
committed by GitHub
parent 9a8d1022a6
commit 855a7d5541
5 changed files with 305 additions and 1 deletions

44
Cargo.lock generated
View File

@ -1056,6 +1056,16 @@ dependencies = [
"typenum",
]
[[package]]
name = "ctor"
version = "0.1.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096"
dependencies = [
"quote 1.0.26",
"syn 1.0.109",
]
[[package]]
name = "ctr"
version = "0.8.0"
@ -1308,6 +1318,12 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "diff"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
[[package]]
name = "difflib"
version = "0.4.0"
@ -3678,6 +3694,15 @@ version = "6.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee"
[[package]]
name = "output_vt100"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66"
dependencies = [
"winapi",
]
[[package]]
name = "overload"
version = "0.1.1"
@ -4025,6 +4050,18 @@ dependencies = [
"termtree",
]
[[package]]
name = "pretty_assertions"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a25e9bcb20aa780fd0bb16b72403a9064d6b3f22f026946029acb941a50af755"
dependencies = [
"ctor",
"diff",
"output_vt100",
"yansi",
]
[[package]]
name = "prettyplease"
version = "0.2.4"
@ -4462,6 +4499,7 @@ dependencies = [
"metrics-util",
"num_cpus",
"pin-project",
"pretty_assertions",
"proptest",
"reth-auto-seal-consensus",
"reth-basic-payload-builder",
@ -7576,6 +7614,12 @@ dependencies = [
"xml-rs",
]
[[package]]
name = "yansi"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
[[package]]
name = "zeroize"
version = "1.6.0"

View File

@ -84,3 +84,4 @@ tempfile = { version = "3.3.0" }
backon = "0.4"
hex = "0.4"
thiserror = "1.0"
pretty_assertions = "1.3.0"

View File

@ -2,7 +2,7 @@
use crate::{
chain, config, db,
dirs::{LogsDir, PlatformPath},
drop_stage, dump_stage, node, p2p,
drop_stage, dump_stage, merkle_debug, node, p2p,
runner::CliRunner,
stage, test_eth_chain, test_vectors,
};
@ -42,6 +42,7 @@ pub fn run() -> eyre::Result<()> {
Commands::TestVectors(command) => runner.run_until_ctrl_c(command.execute()),
Commands::TestEthChain(command) => runner.run_until_ctrl_c(command.execute()),
Commands::Config(command) => runner.run_until_ctrl_c(command.execute()),
Commands::MerkleDebug(command) => runner.run_until_ctrl_c(command.execute()),
}
}
@ -86,6 +87,9 @@ pub enum Commands {
/// Write config to stdout
#[command(name = "config")]
Config(config::Command),
/// Debug state root calculation
#[command(name = "merkle-debug")]
MerkleDebug(merkle_debug::Command),
}
#[derive(Debug, Parser)]

View File

@ -15,6 +15,7 @@ pub mod db;
pub mod dirs;
pub mod drop_stage;
pub mod dump_stage;
pub mod merkle_debug;
pub mod node;
pub mod p2p;
pub mod prometheus_exporter;

View File

@ -0,0 +1,254 @@
//! Command for debugging merkle trie calculation.
use crate::dirs::{DbPath, MaybePlatformPath};
use clap::Parser;
use reth_db::{cursor::DbCursorRO, tables, transaction::DbTx};
use reth_primitives::ChainSpec;
use reth_provider::Transaction;
use reth_staged_sync::utils::{chainspec::genesis_value_parser, init::init_db};
use reth_stages::{
stages::{
AccountHashingStage, ExecutionStage, MerkleStage, StorageHashingStage, ACCOUNT_HASHING,
EXECUTION, MERKLE_EXECUTION, SENDER_RECOVERY, STORAGE_HASHING,
},
ExecInput, Stage,
};
use std::{ops::Deref, sync::Arc};
/// `reth merkle-debug` command
#[derive(Debug, Parser)]
pub struct Command {
/// The path to the database folder.
///
/// Defaults to the OS-specific data directory:
///
/// - Linux: `$XDG_DATA_HOME/reth/db` or `$HOME/.local/share/reth/db`
/// - Windows: `{FOLDERID_RoamingAppData}/reth/db`
/// - macOS: `$HOME/Library/Application Support/reth/db`
#[arg(global = true, long, value_name = "PATH", verbatim_doc_comment, default_value_t)]
db: MaybePlatformPath<DbPath>,
/// The chain this node is running.
///
/// Possible values are either a built-in chain or the path to a chain specification file.
///
/// Built-in chains:
/// - mainnet
/// - goerli
/// - sepolia
#[arg(
long,
value_name = "CHAIN_OR_PATH",
verbatim_doc_comment,
default_value = "mainnet",
value_parser = genesis_value_parser
)]
chain: Arc<ChainSpec>,
/// The height to finish at
#[arg(long)]
to: u64,
/// The depth after which we should start comparing branch nodes
#[arg(long)]
skip_node_depth: Option<usize>,
}
impl Command {
/// Execute `merkle-debug` command
pub async fn execute(self) -> eyre::Result<()> {
// add network name to db directory
let db_path = self.db.unwrap_or_chain_default(self.chain.chain);
std::fs::create_dir_all(&db_path)?;
let db = Arc::new(init_db(db_path)?);
let mut tx = Transaction::new(db.as_ref())?;
let execution_progress = EXECUTION.get_progress(tx.deref())?.unwrap_or_default();
assert!(execution_progress < self.to, "Nothing to run");
let should_reset_stages = !(execution_progress ==
ACCOUNT_HASHING.get_progress(tx.deref())?.unwrap_or_default() &&
execution_progress == STORAGE_HASHING.get_progress(tx.deref())?.unwrap_or_default() &&
execution_progress ==
MERKLE_EXECUTION.get_progress(tx.deref())?.unwrap_or_default());
let factory = reth_revm::Factory::new(self.chain.clone());
let mut execution_stage = ExecutionStage::new(factory, 1);
let mut account_hashing_stage = AccountHashingStage::default();
let mut storage_hashing_stage = StorageHashingStage::default();
let mut merkle_stage = MerkleStage::default_execution();
for block in execution_progress + 1..=self.to {
tracing::trace!(target: "reth::cli", block, "Executing block");
let progress = if (!should_reset_stages || block > execution_progress + 1) && block > 0
{
Some(block - 1)
} else {
None
};
execution_stage
.execute(
&mut tx,
ExecInput {
previous_stage: Some((SENDER_RECOVERY, block)),
stage_progress: block.checked_sub(1),
},
)
.await?;
let mut account_hashing_done = false;
while !account_hashing_done {
let output = account_hashing_stage
.execute(
&mut tx,
ExecInput {
previous_stage: Some((EXECUTION, block)),
stage_progress: progress,
},
)
.await?;
account_hashing_done = output.done;
}
let mut storage_hashing_done = false;
while !storage_hashing_done {
let output = storage_hashing_stage
.execute(
&mut tx,
ExecInput {
previous_stage: Some((ACCOUNT_HASHING, block)),
stage_progress: progress,
},
)
.await?;
storage_hashing_done = output.done;
}
let incremental_result = merkle_stage
.execute(
&mut tx,
ExecInput {
previous_stage: Some((STORAGE_HASHING, block)),
stage_progress: progress,
},
)
.await;
if incremental_result.is_err() {
tracing::warn!(target: "reth::cli", block, "Incremental calculation failed, retrying from scratch");
let incremental_account_trie = tx
.cursor_read::<tables::AccountsTrie>()?
.walk_range(..)?
.collect::<Result<Vec<_>, _>>()?;
let incremental_storage_trie = tx
.cursor_dup_read::<tables::StoragesTrie>()?
.walk_range(..)?
.collect::<Result<Vec<_>, _>>()?;
let clean_result = merkle_stage
.execute(
&mut tx,
ExecInput {
previous_stage: Some((STORAGE_HASHING, block)),
stage_progress: None,
},
)
.await;
assert!(clean_result.is_ok(), "Clean state root calculation failed");
let clean_account_trie = tx
.cursor_read::<tables::AccountsTrie>()?
.walk_range(..)?
.collect::<Result<Vec<_>, _>>()?;
let clean_storage_trie = tx
.cursor_dup_read::<tables::StoragesTrie>()?
.walk_range(..)?
.collect::<Result<Vec<_>, _>>()?;
tracing::info!(target: "reth::cli", block, "Comparing incremental trie vs clean trie");
// Account trie
let mut incremental_account_mismatched = Vec::new();
let mut clean_account_mismatched = Vec::new();
let mut incremental_account_trie_iter =
incremental_account_trie.into_iter().peekable();
let mut clean_account_trie_iter = clean_account_trie.into_iter().peekable();
while incremental_account_trie_iter.peek().is_some() ||
clean_account_trie_iter.peek().is_some()
{
match (incremental_account_trie_iter.next(), clean_account_trie_iter.next()) {
(Some(incremental), Some(clean)) => {
pretty_assertions::assert_eq!(
incremental.0,
clean.0,
"Nibbles don't match"
);
if incremental.1 != clean.1 &&
clean.0.inner.len() > self.skip_node_depth.unwrap_or_default()
{
incremental_account_mismatched.push(incremental);
clean_account_mismatched.push(clean);
}
}
(Some(incremental), None) => {
tracing::warn!(target: "reth::cli", next = ?incremental, "Incremental account trie has more entries");
}
(None, Some(clean)) => {
tracing::warn!(target: "reth::cli", next = ?clean, "Clean account trie has more entries");
}
(None, None) => {
tracing::info!(target: "reth::cli", "Exhausted all account trie entries");
}
}
}
// Stoarge trie
let mut first_mismatched_storage = None;
let mut incremental_storage_trie_iter =
incremental_storage_trie.into_iter().peekable();
let mut clean_storage_trie_iter = clean_storage_trie.into_iter().peekable();
while incremental_storage_trie_iter.peek().is_some() ||
clean_storage_trie_iter.peek().is_some()
{
match (incremental_storage_trie_iter.next(), clean_storage_trie_iter.next()) {
(Some(incremental), Some(clean)) => {
if incremental != clean &&
clean.1.nibbles.inner.len() >
self.skip_node_depth.unwrap_or_default()
{
first_mismatched_storage = Some((incremental, clean));
break
}
}
(Some(incremental), None) => {
tracing::warn!(target: "reth::cli", next = ?incremental, "Incremental storage trie has more entries");
}
(None, Some(clean)) => {
tracing::warn!(target: "reth::cli", next = ?clean, "Clean storage trie has more entries")
}
(None, None) => {
tracing::info!(target: "reth::cli", "Exhausted all storage trie entries.")
}
}
}
pretty_assertions::assert_eq!(
(
incremental_account_mismatched,
first_mismatched_storage.as_ref().map(|(incremental, _)| incremental)
),
(
clean_account_mismatched,
first_mismatched_storage.as_ref().map(|(_, clean)| clean)
),
"Mismatched trie nodes"
);
}
}
Ok(())
}
}