Reset blockchain properly (#10669)

* delete BlockDetails from COL_EXTRA

* better proofs

* added tests

* PR suggestions
This commit is contained in:
Seun LanLege 2019-05-21 12:52:49 +01:00 committed by Andronik Ordian
parent 10c121a299
commit 21a27fee9f
3 changed files with 74 additions and 38 deletions

View File

@ -668,21 +668,6 @@ impl BlockChain {
self.db.key_value().read_with_cache(db::COL_EXTRA, &self.block_details, parent).map_or(false, |d| d.children.contains(hash)) 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<encoded::Header>, 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: /// 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`. /// - a vector of hashes of all blocks, ordered from `from` to `to`.
@ -869,6 +854,14 @@ impl BlockChain {
} }
} }
/// clears all caches for testing purposes
pub fn clear_cache(&self) {
self.block_bodies.write().clear();
self.block_details.write().clear();
self.block_hashes.write().clear();
self.block_headers.write().clear();
}
/// Update the best ancient block to the given hash, after checking that /// Update the best ancient block to the given hash, after checking that
/// it's directly linked to the currently known best ancient block /// it's directly linked to the currently known best ancient block
pub fn update_best_ancient_block(&self, hash: &H256) { pub fn update_best_ancient_block(&self, hash: &H256) {

View File

@ -25,7 +25,7 @@ use blockchain::{BlockReceipts, BlockChain, BlockChainDB, BlockProvider, TreeRou
use bytes::Bytes; use bytes::Bytes;
use call_contract::{CallContract, RegistryInfo}; use call_contract::{CallContract, RegistryInfo};
use ethcore_miner::pool::VerifiedTransaction; use ethcore_miner::pool::VerifiedTransaction;
use ethereum_types::{H256, Address, U256}; use ethereum_types::{H256, H264, Address, U256};
use evm::Schedule; use evm::Schedule;
use hash::keccak; use hash::keccak;
use io::IoChannel; use io::IoChannel;
@ -86,7 +86,7 @@ pub use types::blockchain_info::BlockChainInfo;
pub use types::block_status::BlockStatus; pub use types::block_status::BlockStatus;
pub use blockchain::CacheSize as BlockChainCacheSize; pub use blockchain::CacheSize as BlockChainCacheSize;
pub use verification::QueueInfo as BlockQueueInfo; pub use verification::QueueInfo as BlockQueueInfo;
use db::Writable; use db::{Writable, Readable, keys::BlockDetails};
use_contract!(registry, "res/contracts/registrar.json"); use_contract!(registry, "res/contracts/registrar.json");
@ -1327,37 +1327,60 @@ impl BlockChainReset for Client {
fn reset(&self, num: u32) -> Result<(), String> { fn reset(&self, num: u32) -> Result<(), String> {
if num as u64 > self.pruning_history() { if num as u64 > self.pruning_history() {
return Err("Attempting to reset to block with pruned state".into()) return Err("Attempting to reset to block with pruned state".into())
} else if num == 0 {
return Err("invalid number of blocks to reset".into())
} }
let (blocks_to_delete, best_block_hash) = self.chain.read() let mut blocks_to_delete = Vec::with_capacity(num as usize);
.block_headers_from_best_block(num) let mut best_block_hash = self.chain.read().best_block_hash();
.ok_or("Attempted to reset past genesis block")?; let mut batch = DBTransaction::with_capacity(blocks_to_delete.len());
let mut db_transaction = DBTransaction::with_capacity((num + 1) as usize); for _ in 0..num {
let current_header = self.chain.read().block_header_data(&best_block_hash)
.expect("best_block_hash was fetched from db; block_header_data should exist in db; qed");
best_block_hash = current_header.parent_hash();
for hash in &blocks_to_delete { let (number, hash) = (current_header.number(), current_header.hash());
db_transaction.delete(::db::COL_HEADERS, &hash.hash()); batch.delete(::db::COL_HEADERS, &hash);
db_transaction.delete(::db::COL_BODIES, &hash.hash()); batch.delete(::db::COL_BODIES, &hash);
db_transaction.delete(::db::COL_EXTRA, &hash.hash()); Writable::delete::<BlockDetails, H264>
(&mut batch, ::db::COL_EXTRA, &hash);
Writable::delete::<H256, BlockNumberKey> Writable::delete::<H256, BlockNumberKey>
(&mut db_transaction, ::db::COL_EXTRA, &hash.number()); (&mut batch, ::db::COL_EXTRA, &number);
blocks_to_delete.push((number, hash));
} }
let hashes = blocks_to_delete.iter().map(|(_, hash)| hash).collect::<Vec<_>>();
info!("Deleting block hashes {}",
Colour::Red
.bold()
.paint(format!("{:#?}", hashes))
);
let mut best_block_details = Readable::read::<BlockDetails, H264>(
&**self.db.read().key_value(),
::db::COL_EXTRA,
&best_block_hash
).expect("block was previously imported; best_block_details should exist; qed");
let (_, last_hash) = blocks_to_delete.last()
.expect("num is > 0; blocks_to_delete can't be empty; qed");
// remove the last block as a child so that it can be re-imported
// ethcore/blockchain/src/blockchain.rs/Blockchain::is_known_child()
best_block_details.children.retain(|h| *h != *last_hash);
batch.write(
::db::COL_EXTRA,
&best_block_hash,
&best_block_details
);
// update the new best block hash // update the new best block hash
db_transaction.put(::db::COL_EXTRA, b"best", &*best_block_hash); batch.put(::db::COL_EXTRA, b"best", &best_block_hash);
self.db.read() self.db.read()
.key_value() .key_value()
.write(db_transaction) .write(batch)
.map_err(|err| format!("could not complete reset operation; io error occured: {}", err))?; .map_err(|err| format!("could not delete blocks; io error occurred: {}", err))?;
let hashes = blocks_to_delete.iter().map(|b| b.hash()).collect::<Vec<_>>();
info!("Deleting block hashes {}",
Colour::Red
.bold()
.paint(format!("{:#?}", hashes))
);
info!("New best block hash {}", Colour::Green.bold().paint(format!("{:?}", best_block_hash))); info!("New best block hash {}", Colour::Green.bold().paint(format!("{:?}", best_block_hash)));

View File

@ -27,7 +27,7 @@ use types::filter::Filter;
use types::view; use types::view;
use types::views::BlockView; use types::views::BlockView;
use client::{BlockChainClient, Client, ClientConfig, BlockId, ChainInfo, BlockInfo, PrepareOpenBlock, ImportSealedBlock, ImportBlock}; use client::{BlockChainClient, BlockChainReset, Client, ClientConfig, BlockId, ChainInfo, BlockInfo, PrepareOpenBlock, ImportSealedBlock, ImportBlock};
use ethereum; use ethereum;
use executive::{Executive, TransactOptions}; use executive::{Executive, TransactOptions};
use miner::{Miner, PendingOrdering, MinerService}; use miner::{Miner, PendingOrdering, MinerService};
@ -366,3 +366,23 @@ fn transaction_proof() {
assert_eq!(state.balance(&Address::default()).unwrap(), 5.into()); assert_eq!(state.balance(&Address::default()).unwrap(), 5.into());
assert_eq!(state.balance(&address).unwrap(), 95.into()); assert_eq!(state.balance(&address).unwrap(), 95.into());
} }
#[test]
fn reset_blockchain() {
let client = get_test_client_with_blocks(get_good_dummy_block_seq(19));
// 19 + genesis block
assert!(client.block_header(BlockId::Number(20)).is_some());
assert_eq!(client.block_header(BlockId::Number(20)).unwrap().hash(), client.best_block_header().hash());
assert!(client.reset(5).is_ok());
client.chain().clear_cache();
assert!(client.block_header(BlockId::Number(20)).is_none());
assert!(client.block_header(BlockId::Number(19)).is_none());
assert!(client.block_header(BlockId::Number(18)).is_none());
assert!(client.block_header(BlockId::Number(17)).is_none());
assert!(client.block_header(BlockId::Number(16)).is_none());
assert!(client.block_header(BlockId::Number(15)).is_some());
}