diff --git a/crates/cli/commands/src/db/mod.rs b/crates/cli/commands/src/db/mod.rs index fd7e577c44c..6c66e7159a9 100644 --- a/crates/cli/commands/src/db/mod.rs +++ b/crates/cli/commands/src/db/mod.rs @@ -139,7 +139,9 @@ impl> Command command.execute(provider_factory)?; } Subcommands::RepairTrie(command) => { - let Environment { provider_factory, .. } = self.env.init::(AccessRights::RW)?; + let access_rights = + if command.dry_run { AccessRights::RO } else { AccessRights::RW }; + let Environment { provider_factory, .. } = self.env.init::(access_rights)?; command.execute(provider_factory)?; } Subcommands::Version => { diff --git a/crates/cli/commands/src/db/repair_trie.rs b/crates/cli/commands/src/db/repair_trie.rs index fcfa679b4ac..b0ec3eebd17 100644 --- a/crates/cli/commands/src/db/repair_trie.rs +++ b/crates/cli/commands/src/db/repair_trie.rs @@ -16,12 +16,14 @@ use reth_trie_db::{DatabaseHashedCursorFactory, DatabaseTrieCursorFactory}; use std::time::{Duration, Instant}; use tracing::{info, warn}; +const PROGRESS_PERIOD: Duration = Duration::from_secs(5); + /// The arguments for the `reth db repair-trie` command #[derive(Parser, Debug)] pub struct Command { /// Only show inconsistencies without making any repairs #[arg(long)] - dry_run: bool, + pub(crate) dry_run: bool, } impl Command { @@ -30,99 +32,132 @@ impl Command { self, provider_factory: ProviderFactory, ) -> eyre::Result<()> { - // Get a database transaction directly from the database - let db = provider_factory.db_ref(); - let mut tx = db.tx_mut()?; - tx.disable_long_read_transaction_safety(); + if self.dry_run { + verify_only(provider_factory)? + } else { + verify_and_repair(provider_factory)? + } - // Create the hashed cursor factory - let hashed_cursor_factory = DatabaseHashedCursorFactory::new(&tx); + Ok(()) + } +} - // Create the trie cursor factory - let trie_cursor_factory = DatabaseTrieCursorFactory::new(&tx); +fn verify_only(provider_factory: ProviderFactory) -> eyre::Result<()> { + // Get a database transaction directly from the database + let db = provider_factory.db_ref(); + let mut tx = db.tx()?; + tx.disable_long_read_transaction_safety(); + + // Create the verifier + let hashed_cursor_factory = DatabaseHashedCursorFactory::new(&tx); + let trie_cursor_factory = DatabaseTrieCursorFactory::new(&tx); + let verifier = Verifier::new(trie_cursor_factory, hashed_cursor_factory)?; + + let mut inconsistent_nodes = 0; + let start_time = Instant::now(); + let mut last_progress_time = Instant::now(); + + // Iterate over the verifier and repair inconsistencies + for output_result in verifier { + let output = output_result?; + + if let Output::Progress(path) = output { + if last_progress_time.elapsed() > PROGRESS_PERIOD { + output_progress(path, start_time, inconsistent_nodes); + last_progress_time = Instant::now(); + } + } else { + warn!("Inconsistency found: {output:?}"); + inconsistent_nodes += 1; + } + } - // Create the verifier - let verifier = Verifier::new(trie_cursor_factory, hashed_cursor_factory)?; + info!("Found {} inconsistencies (dry run - no changes made)", inconsistent_nodes); - let mut account_trie_cursor = tx.cursor_write::()?; - let mut storage_trie_cursor = tx.cursor_dup_write::()?; + Ok(()) +} - let mut inconsistent_nodes = 0; - let start_time = Instant::now(); - let mut last_progress_time = Instant::now(); +fn verify_and_repair(provider_factory: ProviderFactory) -> eyre::Result<()> { + // Get a database transaction directly from the database + let db = provider_factory.db_ref(); + let mut tx = db.tx_mut()?; + tx.disable_long_read_transaction_safety(); - // Iterate over the verifier and repair inconsistencies - for output_result in verifier { - let output = output_result?; + // Create the hashed cursor factory + let hashed_cursor_factory = DatabaseHashedCursorFactory::new(&tx); - if let Output::Progress(path) = output { - // Output progress every 5 seconds - if last_progress_time.elapsed() > Duration::from_secs(5) { - output_progress(path, start_time, inconsistent_nodes); - last_progress_time = Instant::now(); - } - continue - }; + // Create the trie cursor factory + let trie_cursor_factory = DatabaseTrieCursorFactory::new(&tx); + + // Create the verifier + let verifier = Verifier::new(trie_cursor_factory, hashed_cursor_factory)?; + + let mut account_trie_cursor = tx.cursor_write::()?; + let mut storage_trie_cursor = tx.cursor_dup_write::()?; + + let mut inconsistent_nodes = 0; + let start_time = Instant::now(); + let mut last_progress_time = Instant::now(); + + // Iterate over the verifier and repair inconsistencies + for output_result in verifier { + let output = output_result?; + if !matches!(output, Output::Progress(_)) { warn!("Inconsistency found, will repair: {output:?}"); inconsistent_nodes += 1; + } - if self.dry_run { - continue; - } - - match output { - Output::AccountExtra(path, _node) => { - // Extra account node in trie, remove it - let nibbles = StoredNibbles(path); - if account_trie_cursor.seek_exact(nibbles)?.is_some() { - account_trie_cursor.delete_current()?; - } - } - Output::StorageExtra(account, path, _node) => { - // Extra storage node in trie, remove it - let nibbles = StoredNibblesSubKey(path); - if storage_trie_cursor - .seek_by_key_subkey(account, nibbles.clone())? - .filter(|e| e.nibbles == nibbles) - .is_some() - { - storage_trie_cursor.delete_current()?; - } - } - Output::AccountWrong { path, expected: node, .. } | - Output::AccountMissing(path, node) => { - // Wrong/missing account node value, upsert it - let nibbles = StoredNibbles(path); - account_trie_cursor.upsert(nibbles, &node)?; + match output { + Output::AccountExtra(path, _node) => { + // Extra account node in trie, remove it + let nibbles = StoredNibbles(path); + if account_trie_cursor.seek_exact(nibbles)?.is_some() { + account_trie_cursor.delete_current()?; } - Output::StorageWrong { account, path, expected: node, .. } | - Output::StorageMissing(account, path, node) => { - // Wrong/missing storage node value, upsert it - let nibbles = StoredNibblesSubKey(path); - let entry = StorageTrieEntry { nibbles, node }; - storage_trie_cursor.upsert(account, &entry)?; - } - Output::Progress(_) => { - unreachable!() + } + Output::StorageExtra(account, path, _node) => { + // Extra storage node in trie, remove it + let nibbles = StoredNibblesSubKey(path); + if storage_trie_cursor + .seek_by_key_subkey(account, nibbles.clone())? + .filter(|e| e.nibbles == nibbles) + .is_some() + { + storage_trie_cursor.delete_current()?; } } - } - - if inconsistent_nodes > 0 { - if self.dry_run { - info!("Found {} inconsistencies (dry run - no changes made)", inconsistent_nodes); - } else { - info!("Repaired {} inconsistencies", inconsistent_nodes); - tx.commit()?; - info!("Changes committed to database"); + Output::AccountWrong { path, expected: node, .. } | + Output::AccountMissing(path, node) => { + // Wrong/missing account node value, upsert it + let nibbles = StoredNibbles(path); + account_trie_cursor.upsert(nibbles, &node)?; + } + Output::StorageWrong { account, path, expected: node, .. } | + Output::StorageMissing(account, path, node) => { + // Wrong/missing storage node value, upsert it + let nibbles = StoredNibblesSubKey(path); + let entry = StorageTrieEntry { nibbles, node }; + storage_trie_cursor.upsert(account, &entry)?; + } + Output::Progress(path) => { + if last_progress_time.elapsed() > PROGRESS_PERIOD { + output_progress(path, start_time, inconsistent_nodes); + last_progress_time = Instant::now(); + } } - } else { - info!("No inconsistencies found"); } + } - Ok(()) + if inconsistent_nodes == 0 { + info!("No inconsistencies found"); + } else { + info!("Repaired {} inconsistencies", inconsistent_nodes); + tx.commit()?; + info!("Changes committed to database"); } + + Ok(()) } /// Output progress information based on the last seen account path.