Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion crates/cli/commands/src/db/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,9 @@ impl<C: ChainSpecParser<ChainSpec: EthChainSpec + EthereumHardforks>> Command<C>
command.execute(provider_factory)?;
}
Subcommands::RepairTrie(command) => {
let Environment { provider_factory, .. } = self.env.init::<N>(AccessRights::RW)?;
let access_rights =
if command.dry_run { AccessRights::RO } else { AccessRights::RW };
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice, using read only

let Environment { provider_factory, .. } = self.env.init::<N>(access_rights)?;
command.execute(provider_factory)?;
}
Subcommands::Version => {
Expand Down
187 changes: 111 additions & 76 deletions crates/cli/commands/src/db/repair_trie.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -30,99 +32,132 @@ impl Command {
self,
provider_factory: ProviderFactory<N>,
) -> 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<N: NodeTypesWithDB>(provider_factory: ProviderFactory<N>) -> 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::<tables::AccountsTrie>()?;
let mut storage_trie_cursor = tx.cursor_dup_write::<tables::StoragesTrie>()?;
Ok(())
}

let mut inconsistent_nodes = 0;
let start_time = Instant::now();
let mut last_progress_time = Instant::now();
fn verify_and_repair<N: NodeTypesWithDB>(provider_factory: ProviderFactory<N>) -> 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::<tables::AccountsTrie>()?;
let mut storage_trie_cursor = tx.cursor_dup_write::<tables::StoragesTrie>()?;

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.
Expand Down