refactor the state_sync to handle the long fork (#1902)

* split horizon into two explicit values for cut through and txhashset request

* let node which has 2-7 days of history be able to handle forks larger than 2 days

* add test simulate_long_fork

* add pause/resume feature on p2p for tests

* refactor the state_sync

* ignore the test case simulate_long_fork for normal Travis-CI

* refactor function check_txhashset_needed to be shared with body_sync

* fix: state TxHashsetDone should allow header sync
This commit is contained in:
Gary Yu
2018-11-10 11:27:52 +08:00
committed by GitHub
parent 408fcc386e
commit 9af9ca9518
17 changed files with 788 additions and 144 deletions
+92 -11
View File
@@ -25,7 +25,7 @@ use util::RwLock;
use lmdb;
use lru_cache::LruCache;
use core::core::hash::{Hash, Hashed};
use core::core::hash::{Hash, Hashed, ZERO_HASH};
use core::core::merkle_proof::MerkleProof;
use core::core::verifier_cache::VerifierCache;
use core::core::{Block, BlockHeader, BlockSums, Output, OutputIdentifier, Transaction, TxKernel};
@@ -700,6 +700,73 @@ impl Chain {
Ok(())
}
/// Check chain status whether a txhashset downloading is needed
pub fn check_txhashset_needed(&self, caller: String, hashes: &mut Option<Vec<Hash>>) -> bool {
let horizon = global::cut_through_horizon() as u64;
let body_head = self.head().unwrap();
let header_head = self.header_head().unwrap();
let sync_head = self.get_sync_head().unwrap();
debug!(
"{}: body_head - {}, {}, header_head - {}, {}, sync_head - {}, {}",
caller,
body_head.last_block_h,
body_head.height,
header_head.last_block_h,
header_head.height,
sync_head.last_block_h,
sync_head.height,
);
if body_head.total_difficulty >= header_head.total_difficulty {
debug!(
"{}: no need. header_head.total_difficulty: {} <= body_head.total_difficulty: {}",
caller, header_head.total_difficulty, body_head.total_difficulty,
);
return false;
}
let mut oldest_height = 0;
let mut oldest_hash = ZERO_HASH;
let mut current = self.get_block_header(&header_head.last_block_h);
if current.is_err() {
error!(
"{}: header_head not found in chain db: {} at {}",
caller, header_head.last_block_h, header_head.height,
);
}
while let Ok(header) = current {
// break out of the while loop when we find a header common
// between the header chain and the current body chain
if let Ok(_) = self.is_on_current_chain(&header) {
break;
}
oldest_height = header.height;
oldest_hash = header.hash();
if let Some(hs) = hashes {
hs.push(oldest_hash);
}
current = self.get_previous_header(&header);
}
if oldest_height < header_head.height.saturating_sub(horizon) {
if oldest_height > 0 {
debug!(
"{}: oldest block which is not on local chain: {} at {}",
caller, oldest_hash, oldest_height,
);
return true;
} else {
error!("{}: something is wrong! oldest_height is 0", caller);
return false;
};
}
return false;
}
/// Writes a reading view on a txhashset state that's been provided to us.
/// If we're willing to accept that new state, the data stream will be
/// read as a zip file, unzipped and the resulting state files should be
@@ -712,13 +779,11 @@ impl Chain {
) -> Result<(), Error> {
status.on_setup();
// Initial check based on relative heights of current head and header_head.
{
let head = self.head().unwrap();
let header_head = self.header_head().unwrap();
if header_head.height - head.height < global::cut_through_horizon() as u64 {
return Err(ErrorKind::InvalidTxHashSet("not needed".to_owned()).into());
}
// Initial check whether this txhashset is needed or not
let mut hashes: Option<Vec<Hash>> = None;
if !self.check_txhashset_needed("txhashset_write".to_owned(), &mut hashes) {
warn!("txhashset_write: txhashset received but it's not needed! ignored.");
return Err(ErrorKind::InvalidTxHashSet("not needed".to_owned()).into());
}
let header = self.get_block_header(&h)?;
@@ -769,6 +834,9 @@ impl Chain {
batch.save_body_head(&tip)?;
batch.save_header_height(&header)?;
batch.build_by_height_index(&header, true)?;
// Reset the body tail to the body head after a txhashset write
batch.save_body_tail(&tip)?;
}
// Commit all the changes to the db.
@@ -819,12 +887,13 @@ impl Chain {
let horizon = global::cut_through_horizon() as u64;
let head = self.head()?;
let tail = self.tail()?;
let cutoff = head.height.saturating_sub(horizon);
debug!(
"compact_blocks_db: head height: {}, horizon: {}, cutoff: {}",
head.height, horizon, cutoff,
"compact_blocks_db: head height: {}, tail height: {}, horizon: {}, cutoff: {}",
head.height, tail.height, horizon, cutoff,
);
if cutoff == 0 {
@@ -859,8 +928,13 @@ impl Chain {
Err(e) => return Err(From::from(e)),
}
}
let tail = batch.get_header_by_height(head.height - horizon)?;
batch.save_body_tail(&Tip::from_header(&tail))?;
batch.commit()?;
debug!("compact_blocks_db: removed {} blocks.", count);
debug!(
"compact_blocks_db: removed {} blocks. tail height: {}",
count, tail.height
);
Ok(())
}
@@ -943,6 +1017,13 @@ impl Chain {
.map_err(|e| ErrorKind::StoreErr(e, "chain head".to_owned()).into())
}
/// Tail of the block chain in this node after compact (cross-block cut-through)
pub fn tail(&self) -> Result<Tip, Error> {
self.store
.tail()
.map_err(|e| ErrorKind::StoreErr(e, "chain tail".to_owned()).into())
}
/// Tip (head) of the header chain.
pub fn header_head(&self) -> Result<Tip, Error> {
self.store
+14
View File
@@ -199,6 +199,10 @@ pub fn process_block(b: &Block, ctx: &mut BlockContext) -> Result<Option<Tip>, E
// so we can maintain multiple (in progress) forks.
add_block(b, &ctx.batch)?;
if ctx.batch.tail().is_err() {
update_body_tail(&b.header, &ctx.batch)?;
}
// Update the chain head if total work is increased.
let res = update_head(b, ctx)?;
Ok(res)
@@ -552,6 +556,16 @@ fn add_block(b: &Block, batch: &store::Batch) -> Result<(), Error> {
Ok(())
}
/// Update the block chain tail so we can know the exact tail of full blocks in this node
fn update_body_tail(bh: &BlockHeader, batch: &store::Batch) -> Result<(), Error> {
let tip = Tip::from_header(bh);
batch
.save_body_tail(&tip)
.map_err(|e| ErrorKind::StoreErr(e, "pipe save body tail".to_owned()))?;
debug!("body tail {} @ {}", bh.hash(), bh.height);
Ok(())
}
/// Officially adds the block header to our header chain.
fn add_block_header(bh: &BlockHeader, batch: &store::Batch) -> Result<(), Error> {
batch
+13
View File
@@ -36,6 +36,7 @@ const STORE_SUBPATH: &'static str = "chain";
const BLOCK_HEADER_PREFIX: u8 = 'h' as u8;
const BLOCK_PREFIX: u8 = 'b' as u8;
const HEAD_PREFIX: u8 = 'H' as u8;
const TAIL_PREFIX: u8 = 'T' as u8;
const HEADER_HEAD_PREFIX: u8 = 'I' as u8;
const SYNC_HEAD_PREFIX: u8 = 's' as u8;
const HEADER_HEIGHT_PREFIX: u8 = '8' as u8;
@@ -70,6 +71,10 @@ impl ChainStore {
option_to_not_found(self.db.get_ser(&vec![HEAD_PREFIX]), "HEAD")
}
pub fn tail(&self) -> Result<Tip, Error> {
option_to_not_found(self.db.get_ser(&vec![TAIL_PREFIX]), "TAIL")
}
/// Header of the block at the head of the block chain (not the same thing as header_head).
pub fn head_header(&self) -> Result<BlockHeader, Error> {
self.get_block_header(&self.head()?.last_block_h)
@@ -225,6 +230,10 @@ impl<'a> Batch<'a> {
option_to_not_found(self.db.get_ser(&vec![HEAD_PREFIX]), "HEAD")
}
pub fn tail(&self) -> Result<Tip, Error> {
option_to_not_found(self.db.get_ser(&vec![TAIL_PREFIX]), "TAIL")
}
/// Header of the block at the head of the block chain (not the same thing as header_head).
pub fn head_header(&self) -> Result<BlockHeader, Error> {
self.get_block_header(&self.head()?.last_block_h)
@@ -248,6 +257,10 @@ impl<'a> Batch<'a> {
self.db.put_ser(&vec![HEAD_PREFIX], t)
}
pub fn save_body_tail(&self, t: &Tip) -> Result<(), Error> {
self.db.put_ser(&vec![TAIL_PREFIX], t)
}
pub fn save_header_head(&self, t: &Tip) -> Result<(), Error> {
self.db.put_ser(&vec![HEADER_HEAD_PREFIX], t)
}