From cdba22a2cbf80dce48df84a5a9650a197420de60 Mon Sep 17 00:00:00 2001 From: Seun LanLege Date: Wed, 16 Jan 2019 19:37:26 +0400 Subject: [PATCH] Adds cli interface to allow reseting chain to a particular block (#9782) * added BlockChainReset trait, client impl, and cli interface * show block hashes to be deleted and new best block, update best block in db, better cli interface * delete BlockNumber from COL_EXTRA * add TODO comment * add BlockReciepts to imports * refactor block_headers_from_best_block, better cli documentation * exit gracefully if reset arg isn't supplied * fix cli usage macro * removed stray int literals * use Vec::with_capacity Co-Authored-By: seunlanlege * cast n to usize * correct imports * make db reset arg required --- ethcore/blockchain/src/blockchain.rs | 15 +++++++++ ethcore/blockchain/src/lib.rs | 2 +- ethcore/src/client/client.rs | 49 ++++++++++++++++++++++++++-- ethcore/src/client/mod.rs | 3 +- ethcore/src/client/traits.rs | 6 ++++ parity/blockchain.rs | 42 +++++++++++++++++++++++- parity/cli/mod.rs | 11 +++++++ parity/configuration.rs | 15 ++++++++- 8 files changed, 137 insertions(+), 6 deletions(-) diff --git a/ethcore/blockchain/src/blockchain.rs b/ethcore/blockchain/src/blockchain.rs index 88f150388..de6e8c134 100644 --- a/ethcore/blockchain/src/blockchain.rs +++ b/ethcore/blockchain/src/blockchain.rs @@ -668,6 +668,21 @@ impl BlockChain { self.db.key_value().read_with_cache(db::COL_EXTRA, &self.block_details, parent).map_or(false, |d| d.children.contains(hash)) } + /// fetches the list of blocks from best block to n, and n's parent hash + /// where n > 0 + pub fn block_headers_from_best_block(&self, n: u32) -> Option<(Vec, H256)> { + let mut blocks = Vec::with_capacity(n as usize); + let mut hash = self.best_block_hash(); + + for _ in 0..n { + let current_hash = self.block_header_data(&hash)?; + hash = current_hash.parent_hash(); + blocks.push(current_hash); + } + + Some((blocks, hash)) + } + /// Returns a tree route between `from` and `to`, which is a tuple of: /// /// - a vector of hashes of all blocks, ordered from `from` to `to`. diff --git a/ethcore/blockchain/src/lib.rs b/ethcore/blockchain/src/lib.rs index 0ee7a5c1c..3f07a6d80 100644 --- a/ethcore/blockchain/src/lib.rs +++ b/ethcore/blockchain/src/lib.rs @@ -33,5 +33,5 @@ pub use self::cache::CacheSize; pub use self::config::Config; pub use self::import_route::ImportRoute; pub use self::update::ExtrasInsert; -pub use ethcore_db::keys::{BlockReceipts, BlockDetails, TransactionAddress}; +pub use ethcore_db::keys::{BlockReceipts, BlockDetails, TransactionAddress, BlockNumberKey}; pub use common_types::tree_route::TreeRoute; diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 995b11520..52aedc37c 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -21,7 +21,7 @@ use std::sync::atomic::{AtomicUsize, AtomicBool, Ordering as AtomicOrdering}; use std::sync::{Arc, Weak}; use std::time::{Instant, Duration}; -use blockchain::{BlockReceipts, BlockChain, BlockChainDB, BlockProvider, TreeRoute, ImportRoute, TransactionAddress, ExtrasInsert}; +use blockchain::{BlockReceipts, BlockChain, BlockChainDB, BlockProvider, TreeRoute, ImportRoute, TransactionAddress, ExtrasInsert, BlockNumberKey}; use bytes::Bytes; use ethcore_miner::pool::VerifiedTransaction; use ethereum_types::{H256, Address, U256}; @@ -50,7 +50,7 @@ use client::{ RegistryInfo, ReopenBlock, PrepareOpenBlock, ScheduleInfo, ImportSealedBlock, BroadcastProposalBlock, ImportBlock, StateOrBlock, StateInfo, StateClient, Call, AccountData, BlockChain as BlockChainTrait, BlockProducer, SealedBlockImporter, - ClientIoMessage, + ClientIoMessage, BlockChainReset }; use client::{ BlockId, TransactionId, UncleId, TraceId, ClientConfig, BlockChainClient, @@ -77,12 +77,14 @@ use verification::queue::kind::BlockLike; use verification::queue::kind::blocks::Unverified; use verification::{PreverifiedBlock, Verifier, BlockQueue}; use verification; +use ansi_term::Colour; // re-export pub use types::blockchain_info::BlockChainInfo; pub use types::block_status::BlockStatus; pub use blockchain::CacheSize as BlockChainCacheSize; pub use verification::QueueInfo as BlockQueueInfo; +use db::Writable; use_contract!(registry, "res/contracts/registrar.json"); @@ -469,6 +471,7 @@ impl Importer { // it is for reconstructing the state transition. // // The header passed is from the original block data and is sealed. + // TODO: should return an error if ImportRoute is none, issue #9910 fn commit_block(&self, block: B, header: &Header, block_data: encoded::Block, client: &Client) -> ImportRoute where B: Drain { let hash = &header.hash(); let number = header.number(); @@ -1328,6 +1331,48 @@ impl snapshot::DatabaseRestore for Client { } } +impl BlockChainReset for Client { + fn reset(&self, num: u32) -> Result<(), String> { + if num as u64 > self.pruning_history() { + return Err("Attempting to reset to block with pruned state".into()) + } + + let (blocks_to_delete, best_block_hash) = self.chain.read() + .block_headers_from_best_block(num) + .ok_or("Attempted to reset past genesis block")?; + + let mut db_transaction = DBTransaction::with_capacity((num + 1) as usize); + + for hash in &blocks_to_delete { + db_transaction.delete(::db::COL_HEADERS, &hash.hash()); + db_transaction.delete(::db::COL_BODIES, &hash.hash()); + db_transaction.delete(::db::COL_EXTRA, &hash.hash()); + Writable::delete:: + (&mut db_transaction, ::db::COL_EXTRA, &hash.number()); + } + + // update the new best block hash + db_transaction.put(::db::COL_EXTRA, b"best", &*best_block_hash); + + self.db.read() + .key_value() + .write(db_transaction) + .map_err(|err| format!("could not complete reset operation; io error occured: {}", err))?; + + let hashes = blocks_to_delete.iter().map(|b| b.hash()).collect::>(); + + info!("Deleting block hashes {}", + Colour::Red + .bold() + .paint(format!("{:#?}", hashes)) + ); + + info!("New best block hash {}", Colour::Green.bold().paint(format!("{:?}", best_block_hash))); + + Ok(()) + } +} + impl Nonce for Client { fn nonce(&self, address: &Address, id: BlockId) -> Option { self.state_at(id).and_then(|s| s.nonce(address).ok()) diff --git a/ethcore/src/client/mod.rs b/ethcore/src/client/mod.rs index 9c534d470..2d042b874 100644 --- a/ethcore/src/client/mod.rs +++ b/ethcore/src/client/mod.rs @@ -37,7 +37,8 @@ pub use self::test_client::{TestBlockChainClient, EachBlockWith}; pub use self::chain_notify::{ChainNotify, NewBlocks, ChainRoute, ChainRouteType, ChainMessageType}; pub use self::traits::{ Nonce, Balance, ChainInfo, BlockInfo, ReopenBlock, PrepareOpenBlock, CallContract, TransactionInfo, RegistryInfo, ScheduleInfo, ImportSealedBlock, BroadcastProposalBlock, ImportBlock, - StateOrBlock, StateClient, Call, EngineInfo, AccountData, BlockChain, BlockProducer, SealedBlockImporter, BadBlocks, + StateOrBlock, StateClient, Call, EngineInfo, AccountData, BlockChain, BlockProducer, SealedBlockImporter, + BadBlocks, BlockChainReset }; pub use state::StateInfo; pub use self::traits::{BlockChainClient, EngineClient, ProvingBlockChainClient, IoClient}; diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index ff0148bee..03f3b0969 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -482,3 +482,9 @@ pub trait ProvingBlockChainClient: BlockChainClient { /// Get an epoch change signal by block hash. fn epoch_signal(&self, hash: H256) -> Option>; } + +/// resets the blockchain +pub trait BlockChainReset { + /// reset to best_block - n + fn reset(&self, num: u32) -> Result<(), String>; +} diff --git a/parity/blockchain.rs b/parity/blockchain.rs index 959de3a30..0f9d081f6 100644 --- a/parity/blockchain.rs +++ b/parity/blockchain.rs @@ -26,7 +26,8 @@ use ethereum_types::{U256, H256, Address}; use bytes::ToPretty; use rlp::PayloadInfo; use ethcore::account_provider::AccountProvider; -use ethcore::client::{Mode, DatabaseCompactionProfile, VMType, Nonce, Balance, BlockChainClient, BlockId, BlockInfo, ImportBlock}; +use ethcore::client::{Mode, DatabaseCompactionProfile, VMType, Nonce, Balance, BlockChainClient, BlockId, BlockInfo, + ImportBlock, BlockChainReset}; use ethcore::error::{ImportErrorKind, ErrorKind as EthcoreErrorKind, Error as EthcoreError}; use ethcore::miner::Miner; use ethcore::verification::queue::VerifierSettings; @@ -40,6 +41,7 @@ use dir::Directories; use user_defaults::UserDefaults; use ethcore_private_tx; use db; +use ansi_term::Colour; #[derive(Debug, PartialEq)] pub enum DataFormat { @@ -71,6 +73,21 @@ pub enum BlockchainCmd { Import(ImportBlockchain), Export(ExportBlockchain), ExportState(ExportState), + Reset(ResetBlockchain) +} + +#[derive(Debug, PartialEq)] +pub struct ResetBlockchain { + pub dirs: Directories, + pub spec: SpecType, + pub pruning: Pruning, + pub pruning_history: u64, + pub pruning_memory: usize, + pub tracing: Switch, + pub fat_db: Switch, + pub compaction: DatabaseCompactionProfile, + pub cache_config: CacheConfig, + pub num: u32, } #[derive(Debug, PartialEq)] @@ -153,6 +170,7 @@ pub fn execute(cmd: BlockchainCmd) -> Result<(), String> { } BlockchainCmd::Export(export_cmd) => execute_export(export_cmd), BlockchainCmd::ExportState(export_cmd) => execute_export_state(export_cmd), + BlockchainCmd::Reset(reset_cmd) => execute_reset(reset_cmd), } } @@ -709,6 +727,28 @@ fn execute_export_state(cmd: ExportState) -> Result<(), String> { Ok(()) } +fn execute_reset(cmd: ResetBlockchain) -> Result<(), String> { + let service = start_client( + cmd.dirs, + cmd.spec, + cmd.pruning, + cmd.pruning_history, + cmd.pruning_memory, + cmd.tracing, + cmd.fat_db, + cmd.compaction, + cmd.cache_config, + false, + 0, + )?; + + let client = service.client(); + client.reset(cmd.num)?; + info!("{}", Colour::Green.bold().paint("Successfully reset db!")); + + Ok(()) +} + pub fn kill_db(cmd: KillBlockchain) -> Result<(), String> { let spec = cmd.spec.spec(&cmd.dirs.cache)?; let genesis_hash = spec.genesis_header().hash(); diff --git a/parity/cli/mod.rs b/parity/cli/mod.rs index 77c1aac2e..9ed6ed957 100644 --- a/parity/cli/mod.rs +++ b/parity/cli/mod.rs @@ -217,6 +217,15 @@ usage! { CMD cmd_db_kill { "Clean the database of the given --chain (default: mainnet)", } + + CMD cmd_db_reset { + "Removes NUM latests blocks from the db", + + ARG arg_db_reset_num: (u32) = 10u32, + "", + "Number of blocks to revert", + } + } CMD cmd_export_hardcoded_sync @@ -1612,6 +1621,7 @@ mod tests { cmd_tools_hash: false, cmd_db: false, cmd_db_kill: false, + cmd_db_reset: false, cmd_export_hardcoded_sync: false, // Arguments @@ -1631,6 +1641,7 @@ mod tests { arg_dapp_path: None, arg_account_import_path: None, arg_wallet_import_path: None, + arg_db_reset_num: 10, // -- Operating Options arg_mode: "last".into(), diff --git a/parity/configuration.rs b/parity/configuration.rs index c0f9c35eb..48207683e 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -48,7 +48,7 @@ use ethcore_private_tx::{ProviderConfig, EncryptorConfig}; use secretstore::{NodeSecretKey, Configuration as SecretStoreConfiguration, ContractAddress as SecretStoreContractAddress}; use updater::{UpdatePolicy, UpdateFilter, ReleaseTrack}; use run::RunCmd; -use blockchain::{BlockchainCmd, ImportBlockchain, ExportBlockchain, KillBlockchain, ExportState, DataFormat}; +use blockchain::{BlockchainCmd, ImportBlockchain, ExportBlockchain, KillBlockchain, ExportState, DataFormat, ResetBlockchain}; use export_hardcoded_sync::ExportHsyncCmd; use presale::ImportWallet; use account::{AccountCmd, NewAccount, ListAccounts, ImportAccounts, ImportFromGethAccounts}; @@ -176,6 +176,19 @@ impl Configuration { } } else if self.args.cmd_tools && self.args.cmd_tools_hash { Cmd::Hash(self.args.arg_tools_hash_file) + } else if self.args.cmd_db && self.args.cmd_db_reset { + Cmd::Blockchain(BlockchainCmd::Reset(ResetBlockchain { + dirs, + spec, + pruning, + pruning_history, + pruning_memory: self.args.arg_pruning_memory, + tracing, + fat_db, + compaction, + cache_config, + num: self.args.arg_db_reset_num, + })) } else if self.args.cmd_db && self.args.cmd_db_kill { Cmd::Blockchain(BlockchainCmd::Kill(KillBlockchain { spec: spec,