Merge pull request #145 from ethcore/db
Return block inserted chain diag
This commit is contained in:
commit
056e623722
|
@ -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")]
|
||||
|
|
|
@ -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() {
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue