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
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions crates/cli/commands/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ reth-static-file-types = { workspace = true, features = ["clap"] }
reth-static-file.workspace = true
reth-trie = { workspace = true, features = ["metrics"] }
reth-trie-db = { workspace = true, features = ["metrics"] }
reth-trie-common = { workspace = true, optional = true }
reth-trie-common.workspace = true
reth-primitives-traits.workspace = true
reth-discv4.workspace = true
reth-discv5.workspace = true
Expand All @@ -68,6 +68,7 @@ futures.workspace = true
tokio.workspace = true

# misc
humantime.workspace = true
human_bytes.workspace = true
eyre.workspace = true
clap = { workspace = true, features = ["derive", "env"] }
Expand Down Expand Up @@ -118,7 +119,7 @@ arbitrary = [
"reth-codecs/arbitrary",
"reth-prune-types?/arbitrary",
"reth-stages-types?/arbitrary",
"reth-trie-common?/arbitrary",
"reth-trie-common/arbitrary",
"alloy-consensus/arbitrary",
"reth-primitives-traits/arbitrary",
"reth-ethereum-primitives/arbitrary",
Expand Down
7 changes: 7 additions & 0 deletions crates/cli/commands/src/db/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ mod clear;
mod diff;
mod get;
mod list;
mod repair_trie;
mod stats;
/// DB List TUI
mod tui;
Expand Down Expand Up @@ -48,6 +49,8 @@ pub enum Subcommands {
},
/// Deletes all table entries
Clear(clear::Command),
/// Verifies trie consistency and outputs any inconsistencies
RepairTrie(repair_trie::Command),
/// Lists current and local database versions
Version,
/// Returns the full database path
Expand Down Expand Up @@ -135,6 +138,10 @@ impl<C: ChainSpecParser<ChainSpec: EthChainSpec + EthereumHardforks>> Command<C>
let Environment { provider_factory, .. } = self.env.init::<N>(AccessRights::RW)?;
command.execute(provider_factory)?;
}
Subcommands::RepairTrie(command) => {
let Environment { provider_factory, .. } = self.env.init::<N>(AccessRights::RW)?;
command.execute(provider_factory)?;
Comment on lines +142 to +143
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

we could add a --dry-run arg and init this with readonly, but could do this as a followup

}
Subcommands::Version => {
let local_db_version = match get_db_version(&db_path) {
Ok(version) => Some(version),
Expand Down
163 changes: 163 additions & 0 deletions crates/cli/commands/src/db/repair_trie.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
use clap::Parser;
use reth_db_api::{
cursor::{DbCursorRO, DbCursorRW, DbDupCursorRO},
database::Database,
tables,
transaction::{DbTx, DbTxMut},
};
use reth_node_builder::NodeTypesWithDB;
use reth_provider::ProviderFactory;
use reth_trie::{
verify::{Output, Verifier},
Nibbles,
};
use reth_trie_common::{StorageTrieEntry, StoredNibbles, StoredNibblesSubKey};
use reth_trie_db::{DatabaseHashedCursorFactory, DatabaseTrieCursorFactory};
use std::time::{Duration, Instant};
use tracing::{info, warn};

/// 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,
}

impl Command {
/// Execute `db repair-trie` command
pub fn execute<N: NodeTypesWithDB>(
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();

// Create the hashed cursor factory
let hashed_cursor_factory = DatabaseHashedCursorFactory::new(&tx);

// 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 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
};

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)?;
}
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!()
}
}
}

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");
}
} else {
info!("No inconsistencies found");
}

Ok(())
}
}

/// Output progress information based on the last seen account path.
fn output_progress(last_account: Nibbles, start_time: Instant, inconsistent_nodes: u64) {
// Calculate percentage based on position in the trie path space
// For progress estimation, we'll use the first few nibbles as an approximation

// Convert the first 16 nibbles (8 bytes) to a u64 for progress calculation
let mut current_value: u64 = 0;
let nibbles_to_use = last_account.len().min(16);

for i in 0..nibbles_to_use {
current_value = (current_value << 4) | (last_account.get(i).unwrap_or(0) as u64);
}
// Shift left to fill remaining bits if we have fewer than 16 nibbles
if nibbles_to_use < 16 {
current_value <<= (16 - nibbles_to_use) * 4;
}

let progress_percent = current_value as f64 / u64::MAX as f64 * 100.0;
let progress_percent_str = format!("{progress_percent:.2}");

// Calculate ETA based on current speed
let elapsed = start_time.elapsed();
let elapsed_secs = elapsed.as_secs_f64();

let estimated_total_time =
if progress_percent > 0.0 { elapsed_secs / (progress_percent / 100.0) } else { 0.0 };
let remaining_time = estimated_total_time - elapsed_secs;
let eta_duration = Duration::from_secs(remaining_time as u64);

info!(
progress_percent = progress_percent_str,
eta = %humantime::format_duration(eta_duration),
inconsistent_nodes,
"Repairing trie tables",
);
}
1 change: 1 addition & 0 deletions crates/trie/trie/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ revm-state.workspace = true
triehash.workspace = true

# misc
assert_matches.workspace = true
criterion.workspace = true
parking_lot.workspace = true
pretty_assertions.workspace = true
Expand Down
3 changes: 3 additions & 0 deletions crates/trie/trie/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,6 @@ pub mod test_utils;
/// Collection of mock types for testing.
#[cfg(test)]
pub mod mock;

/// Verification of existing stored trie nodes against state data.
pub mod verify;
Loading
Loading