From 065ea15c9f3267094ec9a57e395f8f08f4722929 Mon Sep 17 00:00:00 2001 From: ethDreamer <37123614+ethDreamer@users.noreply.github.com> Date: Mon, 13 Apr 2020 20:45:02 -0400 Subject: [PATCH] Added purge subcommand to purge beacon chain db (#971) --- beacon_node/beacon_chain/src/builder.rs | 2 +- beacon_node/client/src/config.rs | 66 +++++++++++++++++++++++++ beacon_node/src/cli.rs | 8 +++ beacon_node/src/config.rs | 22 +++++++-- 4 files changed, 94 insertions(+), 4 deletions(-) diff --git a/beacon_node/beacon_chain/src/builder.rs b/beacon_node/beacon_chain/src/builder.rs index c313b797d..1339702a4 100644 --- a/beacon_node/beacon_chain/src/builder.rs +++ b/beacon_node/beacon_chain/src/builder.rs @@ -221,7 +221,7 @@ where .get::(&Hash256::from_slice(&BEACON_CHAIN_DB_KEY)) .map_err(|e| format!("DB error when reading persisted beacon chain: {:?}", e))? .ok_or_else(|| { - "No persisted beacon chain found in store. Try deleting the .lighthouse/beacon dir." + "No persisted beacon chain found in store. Try purging the beacon chain database." .to_string() })?; diff --git a/beacon_node/client/src/config.rs b/beacon_node/client/src/config.rs index 8ce129ecb..d779eb792 100644 --- a/beacon_node/client/src/config.rs +++ b/beacon_node/client/src/config.rs @@ -1,3 +1,4 @@ +use beacon_chain::builder::PUBKEY_CACHE_FILENAME; use network::NetworkConfig; use serde_derive::{Deserialize, Serialize}; use std::fs; @@ -11,6 +12,9 @@ const TESTNET_SPEC_CONSTANTS: &str = "minimal"; /// Default directory name for the freezer database under the top-level data dir. const DEFAULT_FREEZER_DB_DIR: &str = "freezer_db"; +/// Trap file indicating if chain_db was purged +const CHAIN_DB_PURGED_TRAP_FILE: &str = ".db_purged"; + /// Defines how the client should initialize the `BeaconChain` and other components. #[derive(PartialEq, Debug, Clone, Serialize, Deserialize)] pub enum ClientGenesis { @@ -97,6 +101,68 @@ impl Config { .map(|data_dir| data_dir.join(&self.db_name)) } + /// Get the path of the chain db purged trap file + pub fn get_db_purged_trap_file_path(&self) -> Option { + self.get_data_dir() + .map(|data_dir| data_dir.join(CHAIN_DB_PURGED_TRAP_FILE)) + } + + /// returns whether chain_db was recently purged + pub fn chain_db_was_purged(&self) -> bool { + self.get_db_purged_trap_file_path() + .map_or(false, |trap_file| trap_file.exists()) + } + + /// purges the chain_db and creates trap file + pub fn purge_chain_db(&self) -> Result<(), String> { + // create the trap file + let trap_file = self + .get_db_purged_trap_file_path() + .ok_or("Failed to get trap file path".to_string())?; + fs::File::create(trap_file) + .map_err(|err| format!("Failed to create trap file: {}", err))?; + + // remove the chain_db + fs::remove_dir_all( + self.get_db_path() + .ok_or("Failed to get db_path".to_string())?, + ) + .map_err(|err| format!("Failed to remove chain_db: {}", err))?; + + // remove the freezer db + fs::remove_dir_all( + self.get_freezer_db_path() + .ok_or("Failed to get freezer db path".to_string())?, + ) + .map_err(|err| format!("Failed to remove chain_db: {}", err))?; + + // also need to remove pubkey cache file if it exists + let pubkey_cache_file = self + .get_data_dir() + .map(|data_dir| data_dir.join(PUBKEY_CACHE_FILENAME)) + .ok_or("Failed to get pubkey cache file path".to_string())?; + if !pubkey_cache_file.exists() { + return Ok(()); + } + fs::remove_file(pubkey_cache_file) + .map_err(|err| format!("Failed to remove pubkey cache: {}", err))?; + + Ok(()) + } + + /// cleans up purge_db trap file + pub fn cleanup_after_purge_db(&self) -> Result<(), String> { + let trap_file = self + .get_db_purged_trap_file_path() + .ok_or("Failed to get trap file path".to_string())?; + if !trap_file.exists() { + return Ok(()); + } + fs::remove_file(trap_file).map_err(|err| format!("Failed to remove trap file: {}", err))?; + + Ok(()) + } + /// Get the database path, creating it if necessary. pub fn create_db_path(&self) -> Result { let db_path = self diff --git a/beacon_node/src/cli.rs b/beacon_node/src/cli.rs index 783e94b0a..23b6b939d 100644 --- a/beacon_node/src/cli.rs +++ b/beacon_node/src/cli.rs @@ -309,4 +309,12 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { .help("A file from which to read the state")) ) ) + /* + * The "purge" sub-command. + * + * Allows user to purge beacon database + */ + .subcommand(SubCommand::with_name("purge") + .about("Purge the beacon chain database.") + ) } diff --git a/beacon_node/src/config.rs b/beacon_node/src/config.rs index fd4fc7bad..62bf55af0 100644 --- a/beacon_node/src/config.rs +++ b/beacon_node/src/config.rs @@ -189,15 +189,27 @@ pub fn get_config( ("testnet", Some(sub_cmd_args)) => { process_testnet_subcommand(&mut client_config, ð2_config, sub_cmd_args)? } + ("purge", _) => { + client_config.purge_chain_db()?; + println!("Successfully purged chain db"); + std::process::exit(0); + } // No sub-command assumes a resume operation. _ => { // If no primary subcommand was given, start the beacon chain from an existing // database. client_config.genesis = ClientGenesis::Resume; + let db_path_exists: bool = match client_config.get_db_path() { + Some(path) => path.exists(), + None => false, + }; + // Whilst there is no large testnet or mainnet force the user to specify how they want // to start a new chain (e.g., from a genesis YAML file, another node, etc). - if !client_config.data_dir.exists() { + if !client_config.data_dir.exists() + || (!db_path_exists && client_config.chain_db_was_purged()) + { info!( log, "Starting from an empty database"; @@ -392,7 +404,8 @@ fn init_new_client( /// /// Returns an error if `self.data_dir` already exists. pub fn create_new_datadir(client_config: &ClientConfig, eth2_config: &Eth2Config) -> Result<()> { - if client_config.data_dir.exists() { + let rebuild_db = client_config.chain_db_was_purged(); + if client_config.data_dir.exists() && !rebuild_db { return Err(format!( "Data dir already exists at {:?}", client_config.data_dir @@ -407,7 +420,9 @@ pub fn create_new_datadir(client_config: &ClientConfig, eth2_config: &Eth2Config ($file: ident, $variable: ident) => { let file = client_config.data_dir.join($file); if file.exists() { - return Err(format!("Datadir is not clean, {} exists.", $file)); + if !rebuild_db { + return Err(format!("Datadir is not clean, {} exists.", $file)); + } } else { // Write the onfig to a TOML file in the datadir. write_to_file(client_config.data_dir.join($file), $variable) @@ -418,6 +433,7 @@ pub fn create_new_datadir(client_config: &ClientConfig, eth2_config: &Eth2Config write_to_file!(CLIENT_CONFIG_FILENAME, client_config); write_to_file!(ETH2_CONFIG_FILENAME, eth2_config); + client_config.cleanup_after_purge_db()?; Ok(()) }