From 32dd9af5317d0a3a213dc47710ab9f4588af2dd5 Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Tue, 8 Aug 2023 11:59:43 +0300 Subject: [PATCH] feat(cli): storage tries recovery (#4109) --- bin/reth/src/cli/mod.rs | 6 +- bin/reth/src/lib.rs | 1 + bin/reth/src/recover/mod.rs | 29 ++++++++ bin/reth/src/recover/storage_tries.rs | 96 +++++++++++++++++++++++++++ 4 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 bin/reth/src/recover/mod.rs create mode 100644 bin/reth/src/recover/storage_tries.rs diff --git a/bin/reth/src/cli/mod.rs b/bin/reth/src/cli/mod.rs index 50acdf8c05..979ffa5e60 100644 --- a/bin/reth/src/cli/mod.rs +++ b/bin/reth/src/cli/mod.rs @@ -5,7 +5,7 @@ use crate::{ cli::ext::RethCliExt, db, debug_cmd, dirs::{LogsDir, PlatformPath}, - node, p2p, + node, p2p, recover, runner::CliRunner, stage, test_vectors, version::{LONG_VERSION, SHORT_VERSION}, @@ -77,6 +77,7 @@ impl Cli { Commands::TestVectors(command) => runner.run_until_ctrl_c(command.execute()), Commands::Config(command) => runner.run_until_ctrl_c(command.execute()), Commands::Debug(command) => runner.run_command_until_exit(|ctx| command.execute(ctx)), + Commands::Recover(command) => runner.run_command_until_exit(|ctx| command.execute(ctx)), } } @@ -132,6 +133,9 @@ pub enum Commands { /// Various debug routines #[command(name = "debug")] Debug(debug_cmd::Command), + /// Scripts for node recovery + #[command(name = "recover")] + Recover(recover::Command), } /// The log configuration. diff --git a/bin/reth/src/lib.rs b/bin/reth/src/lib.rs index 8320b68083..dbaa2ce58a 100644 --- a/bin/reth/src/lib.rs +++ b/bin/reth/src/lib.rs @@ -34,6 +34,7 @@ pub mod init; pub mod node; pub mod p2p; pub mod prometheus_exporter; +pub mod recover; pub mod runner; pub mod stage; pub mod test_vectors; diff --git a/bin/reth/src/recover/mod.rs b/bin/reth/src/recover/mod.rs new file mode 100644 index 0000000000..dd649abacc --- /dev/null +++ b/bin/reth/src/recover/mod.rs @@ -0,0 +1,29 @@ +//! `reth recover` command. +use clap::{Parser, Subcommand}; + +use crate::runner::CliContext; + +mod storage_tries; + +/// `reth recover` command +#[derive(Debug, Parser)] +pub struct Command { + #[clap(subcommand)] + command: Subcommands, +} + +/// `reth recover` subcommands +#[derive(Subcommand, Debug)] +pub enum Subcommands { + /// Recover the node by deleting dangling storage tries. + StorageTries(storage_tries::Command), +} + +impl Command { + /// Execute `recover` command + pub async fn execute(self, ctx: CliContext) -> eyre::Result<()> { + match self.command { + Subcommands::StorageTries(command) => command.execute(ctx).await, + } + } +} diff --git a/bin/reth/src/recover/storage_tries.rs b/bin/reth/src/recover/storage_tries.rs new file mode 100644 index 0000000000..49e39011de --- /dev/null +++ b/bin/reth/src/recover/storage_tries.rs @@ -0,0 +1,96 @@ +use crate::{ + args::utils::genesis_value_parser, + dirs::{DataDirPath, MaybePlatformPath}, + init::init_genesis, + runner::CliContext, +}; +use clap::Parser; +use reth_db::{ + cursor::{DbCursorRO, DbDupCursorRW}, + init_db, tables, + transaction::DbTx, +}; +use reth_primitives::{keccak256, ChainSpec}; +use reth_provider::{AccountExtReader, BlockNumReader, ProviderFactory}; +use std::{fs, sync::Arc}; +use tracing::*; + +/// `reth recover storage-tries` command +#[derive(Debug, Parser)] +pub struct Command { + /// The path to the data dir for all reth files and subdirectories. + /// + /// Defaults to the OS-specific data directory: + /// + /// - Linux: `$XDG_DATA_HOME/reth/` or `$HOME/.local/share/reth/` + /// - Windows: `{FOLDERID_RoamingAppData}/reth/` + /// - macOS: `$HOME/Library/Application Support/reth/` + #[arg(long, value_name = "DATA_DIR", verbatim_doc_comment, default_value_t)] + datadir: MaybePlatformPath, + + /// 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, + + /// The number of blocks in the past to look through. + #[arg(long, default_value_t = 100)] + lookback: u64, +} + +impl Command { + /// Execute `storage-tries` recovery command + pub async fn execute(self, _ctx: CliContext) -> eyre::Result<()> { + let data_dir = self.datadir.unwrap_or_chain_default(self.chain.chain); + let db_path = data_dir.db_path(); + fs::create_dir_all(&db_path)?; + let db = Arc::new(init_db(db_path, None)?); + + debug!(target: "reth::cli", chain=%self.chain.chain, genesis=?self.chain.genesis_hash(), "Initializing genesis"); + init_genesis(db.clone(), self.chain.clone())?; + + let factory = ProviderFactory::new(&db, self.chain.clone()); + let mut provider = factory.provider_rw()?; + + let best_block = provider.best_block_number()?; + + let block_range = best_block.saturating_sub(self.lookback)..=best_block; + let changed_accounts = provider.changed_accounts_with_range(block_range)?; + let destroyed_accounts = provider + .basic_accounts(changed_accounts)? + .into_iter() + .filter_map(|(address, acc)| acc.is_none().then_some(address)) + .collect::>(); + + info!(target: "reth::cli", destroyed = destroyed_accounts.len(), "Starting recovery of storage tries"); + + let mut deleted_tries = 0; + let tx_mut = provider.tx_mut(); + let mut storage_trie_cursor = tx_mut.cursor_dup_read::()?; + for address in destroyed_accounts { + let hashed_address = keccak256(address); + if storage_trie_cursor.seek_exact(hashed_address)?.is_some() { + deleted_tries += 1; + trace!(target: "reth::cli", ?address, ?hashed_address, "Deleting storage trie"); + storage_trie_cursor.delete_current_duplicates()?; + } + } + + provider.commit()?; + info!(target: "reth::cli", deleted = deleted_tries, "Finished recovery"); + + Ok(()) + } +}