Merge pull request #145 from ethcore/db

Return block inserted chain diag
This commit is contained in:
Svyatoslav Nikolsky 2016-11-17 20:32:58 +03:00 committed by GitHub
commit 056e623722
4 changed files with 112 additions and 18 deletions

View File

@ -34,7 +34,7 @@ pub enum BlockLocation {
}
pub use best_block::BestBlock;
pub use storage::{Storage, Store, Error};
pub use storage::{Storage, Store, Error, BlockInsertedChain};
pub use kvdb::Database;
#[cfg(feature="dev")]

View File

@ -70,7 +70,7 @@ pub trait Store : Send + Sync {
}
/// insert block in the storage
fn insert_block(&self, block: &chain::Block) -> Result<(), Error>;
fn insert_block(&self, block: &chain::Block) -> Result<BlockInsertedChain, Error>;
/// get transaction metadata
fn transaction_meta(&self, hash: &H256) -> Option<TransactionMeta>;
@ -206,6 +206,35 @@ impl UpdateContext {
}
}
#[derive(Debug)]
pub struct Reorganization {
height: u32,
canonized: Vec<H256>,
decanonized: Vec<H256>,
}
impl Reorganization {
fn new(height: u32) -> Reorganization {
Reorganization { height: height, canonized: Vec::new(), decanonized: Vec::new() }
}
fn push_canonized(&mut self, hash: &H256) {
self.canonized.push(hash.clone());
}
fn push_decanonized(&mut self, hash: &H256) {
self.decanonized.push(hash.clone());
}
}
#[derive(Debug)]
pub enum BlockInsertedChain {
Disconnected,
Main,
Side,
Reorganized(Reorganization),
}
impl Storage {
/// new storage at the selected path
@ -470,7 +499,7 @@ impl Storage {
// maybe reorganize to the _known_ block
// it will actually reorganize only when side chain is at least the same length as main
fn maybe_reorganize(&self, context: &mut UpdateContext, hash: &H256) -> Result<Option<(u32, H256)>, Error> {
fn maybe_reorganize(&self, context: &mut UpdateContext, hash: &H256) -> Result<Option<Reorganization>, Error> {
context.restore_point();
match self.maybe_reorganize_fallable(context, hash) {
@ -484,7 +513,7 @@ impl Storage {
}
}
fn maybe_reorganize_fallable(&self, context: &mut UpdateContext, hash: &H256) -> Result<Option<(u32, H256)>, Error> {
fn maybe_reorganize_fallable(&self, context: &mut UpdateContext, hash: &H256) -> Result<Option<Reorganization>, Error> {
if self.block_number(hash).is_some() {
return Ok(None); // cannot reorganize to canonical block
}
@ -498,12 +527,14 @@ impl Storage {
return Ok(None);
}
let mut reorganization = Reorganization::new(at_height);
let mut now_best = try!(self.best_number().ok_or(Error::Consistency(ConsistencyError::NoBestBlock)));
// decanonizing main chain to the split point
loop {
let next_decanonize = try!(self.block_hash(now_best).ok_or(Error::unknown_number(now_best)));
try!(self.decanonize_block(context, &next_decanonize));
reorganization.push_decanonized(&next_decanonize);
now_best -= 1;
@ -514,12 +545,15 @@ impl Storage {
for new_canonical_hash in &route {
now_best += 1;
try!(self.canonize_block(context, now_best, &new_canonical_hash));
reorganization.push_canonized(&new_canonical_hash);
}
// finaly canonizing the top block we are reorganizing to
try!(self.canonize_block(context, now_best + 1, hash));
reorganization.push_canonized(&hash);
reorganization.height = now_best + 1;
Ok(Some((now_best+1, hash.clone())))
Ok(Some(reorganization))
}
}
@ -575,7 +609,7 @@ impl Store for Storage {
)
}
fn insert_block(&self, block: &chain::Block) -> Result<(), Error> {
fn insert_block(&self, block: &chain::Block) -> Result<BlockInsertedChain, Error> {
// ! lock will be held during the entire insert routine
let mut best_block = self.best_block.write();
@ -617,13 +651,15 @@ impl Store for Storage {
);
// the block is continuing the main chain
if best_block.as_ref().map(|b| b.number) != Some(new_best_number) {
let result = if best_block.as_ref().map(|b| b.number) != Some(new_best_number) {
try!(self.update_transactions_meta(&mut context, new_best_number, block.transactions()));
context.db_transaction.write_u32(Some(COL_META), KEY_BEST_BLOCK_NUMBER, new_best_number);
// updating main chain height reference
context.db_transaction.put(Some(COL_BLOCK_HASHES), &u32_key(new_best_number), std::ops::Deref::deref(&block_hash));
context.db_transaction.write_u32(Some(COL_BLOCK_NUMBERS), std::ops::Deref::deref(&block_hash), new_best_number);
BlockInsertedChain::Main
}
// the block does not continue the main chain
@ -631,9 +667,9 @@ impl Store for Storage {
// this can canonize the block parent if block parent + this block is longer than the main chain
else {
match self.maybe_reorganize(&mut context, &block.header().previous_header_hash) {
Ok(Some((reorg_number, _))) => {
Ok(Some(mut reorg)) => {
// if so, we have new best main chain block
new_best_number = reorg_number + 1;
new_best_number = reorg.height + 1;
new_best_hash = block_hash;
// and we canonize it also by provisioning transactions
@ -641,6 +677,10 @@ impl Store for Storage {
context.db_transaction.write_u32(Some(COL_META), KEY_BEST_BLOCK_NUMBER, new_best_number);
context.db_transaction.put(Some(COL_BLOCK_HASHES), &u32_key(new_best_number), std::ops::Deref::deref(&new_best_hash));
context.db_transaction.write_u32(Some(COL_BLOCK_NUMBERS), std::ops::Deref::deref(&new_best_hash), new_best_number);
reorg.push_canonized(&new_best_hash);
BlockInsertedChain::Reorganized(reorg)
},
Err(Error::Consistency(consistency_error)) => {
match consistency_error {
@ -673,6 +713,8 @@ impl Store for Storage {
hash.to_reversed_str(),
block_hash.to_reversed_str()
);
BlockInsertedChain::Disconnected
},
_ => {
// we don't allow other errors on side chain/orphans
@ -685,9 +727,10 @@ impl Store for Storage {
},
Ok(None) => {
// reorganize didn't happen but the block is ok, no-op here
BlockInsertedChain::Side
}
}
}
};
// we always update best hash even if it is not changed
context.db_transaction.put(Some(COL_META), KEY_BEST_BLOCK_HASH, std::ops::Deref::deref(&new_best_hash));
@ -698,7 +741,7 @@ impl Store for Storage {
// updating locked best block
*best_block = Some(BestBlock { hash: new_best_hash, number: new_best_number });
Ok(())
Ok(result)
}
fn transaction(&self, hash: &H256) -> Option<chain::Transaction> {
@ -741,7 +784,7 @@ impl Store for Storage {
#[cfg(test)]
mod tests {
use super::{Storage, Store, UpdateContext, Error, ConsistencyError};
use super::{Storage, Store, UpdateContext, Error, ConsistencyError, BlockInsertedChain};
use devtools::RandomTempPath;
use chain::{Block, RepresentH256};
use super::super::{BlockRef, BlockLocation};
@ -1282,6 +1325,58 @@ mod tests {
assert_eq!(store.block_number(&block_hash), None);
}
#[test]
fn chain_for_genesis() {
let path = RandomTempPath::create_dir();
let store = Storage::new(path.as_path()).unwrap();
let inserted_chain = store.insert_block(&test_data::genesis()).unwrap();
if let BlockInsertedChain::Main = inserted_chain { }
else { panic!("Genesis should become main chain"); }
}
#[test]
fn chain_for_main() {
let path = RandomTempPath::create_dir();
let store = Storage::new(path.as_path()).unwrap();
store.insert_block(&test_data::genesis())
.expect("Genesis should be inserted with no issues");
let inserted_chain = store.insert_block(&test_data::block_h1()).unwrap();
if let BlockInsertedChain::Main = inserted_chain { }
else { panic!("h1 should become main chain"); }
}
#[test]
fn chain_for_side() {
let path = RandomTempPath::create_dir();
let store = Storage::new(path.as_path()).unwrap();
store.insert_block(&test_data::genesis())
.expect("Genesis should be inserted with no issues");
let block1 = test_data::block_h1();
let block1_hash = block1.hash();
store.insert_block(&block1)
.expect("Block 1 should be inserted with no issues");
store.insert_block(&test_data::block_h2())
.expect("Block 2 should be inserted with no issues");
let block2_side = test_data::block_builder()
.header().parent(block1_hash).build()
.build();
let inserted_chain = store.insert_block(&block2_side).unwrap();
if let BlockInsertedChain::Side = inserted_chain { }
else { panic!("h1 should become main chain"); }
}
#[test]
fn accepted_location_for_genesis() {
@ -1308,7 +1403,6 @@ mod tests {
assert_eq!(Some(BlockLocation::Main(1)), location);
}
#[test]
fn accepted_location_for_branch() {

View File

@ -1,6 +1,6 @@
//! Test storage
use super::{BlockRef, Store, Error, BestBlock, BlockLocation};
use super::{BlockRef, Store, Error, BestBlock, BlockLocation, BlockInsertedChain};
use chain::{self, Block, RepresentH256};
use primitives::hash::H256;
use serialization;
@ -113,13 +113,13 @@ impl Store for TestStorage {
.cloned()
}
fn insert_block(&self, block: &chain::Block) -> Result<(), Error> {
fn insert_block(&self, block: &chain::Block) -> Result<BlockInsertedChain, Error> {
let hash = block.hash();
let mut data = self.data.write();
match data.blocks.entry(hash.clone()) {
Entry::Occupied(mut entry) => {
replace(entry.get_mut(), block.clone());
return Ok(());
return Ok(BlockInsertedChain::Main);
},
Entry::Vacant(entry) => {
entry.insert(block.clone());
@ -144,7 +144,7 @@ impl Store for TestStorage {
},
}
Ok(())
Ok(BlockInsertedChain::Main)
}
// just spawns new meta so far, use real store for proper tests

View File

@ -25,7 +25,7 @@ impl BlocksWriter {
match self.verifier.verify(&block) {
Err(err) => Err(Error::Verification(err)),
Ok(_chain) => self.storage.insert_block(&block).map_err(Error::Database),
Ok(_chain) => { try!(self.storage.insert_block(&block).map_err(Error::Database)); Ok(()) }
}
}
}