From a7089d19751854197ed7a577c9d8f45fc21ddb56 Mon Sep 17 00:00:00 2001 From: Ignotus Peverell Date: Thu, 27 Apr 2017 21:59:53 -0700 Subject: [PATCH] Fixes for chain processing during bootstrap Adds checks to ignore a block that's already known (can happen if the block was saved but the chain update failed). Also saves saves the chain slightly differently during bootstrap as the header chain should only be updated when a header is received (and not when we got a full block). Finally, do not broadcast during bootstrap. --- chain/src/pipe.rs | 44 ++++++++++++++++++++++++++++++++++---------- chain/src/store.rs | 8 ++++++++ chain/src/types.rs | 7 +++++++ 3 files changed, 49 insertions(+), 10 deletions(-) diff --git a/chain/src/pipe.rs b/chain/src/pipe.rs index ed880628b..b6664c77a 100644 --- a/chain/src/pipe.rs +++ b/chain/src/pipe.rs @@ -115,9 +115,9 @@ pub fn process_block(b: &Block, try!(validate_header(&b.header, &mut ctx)); } try!(validate_block(b, &mut ctx)); - info!("Block at {} with hash {} is valid, going to save and append.", - b.header.height, - b.hash()); + debug!("Block at {} with hash {} is valid, going to save and append.", + b.header.height, + b.hash()); try!(add_block(b, &mut ctx)); // TODO a global lock should be set before that step or even earlier update_head(b, &mut ctx) @@ -155,6 +155,13 @@ fn check_known(bh: Hash, ctx: &mut BlockContext) -> Result<(), Error> { if bh == ctx.head.last_block_h || bh == ctx.head.prev_block_h { return Err(Error::Unfit("already known".to_string())); } + if let Ok(b) = ctx.store.get_block(&bh) { + // there is a window where a block can be saved but the chain head not + // updated yet, we plug that window here by re-accepting the block + if b.header.total_difficulty <= ctx.head.total_difficulty { + return Err(Error::Unfit("already in store".to_string())); + } + } Ok(()) } @@ -212,12 +219,18 @@ fn validate_header(header: &BlockHeader, ctx: &mut BlockContext) -> Result<(), E /// Fully validate the block content. fn validate_block(b: &Block, ctx: &mut BlockContext) -> Result<(), Error> { + if b.header.height > ctx.head.height + 1 { + // check orphan again, an orphan coming out of order from sync will have + // bypassed header checks + // TODO actually handle orphans and add them to a size-limited set + return Err(Error::Unfit("orphan".to_string())); + } + let curve = secp::Secp256k1::with_caps(secp::ContextFlag::Commit); try!(b.validate(&curve).map_err(&Error::InvalidBlockProof)); - if !ctx.opts.intersects(SYNC) { - // TODO check every input exists as a UTXO using the UXTO index - } + // TODO check every input exists as a UTXO using the UTXO index + Ok(()) } @@ -225,9 +238,11 @@ fn validate_block(b: &Block, ctx: &mut BlockContext) -> Result<(), Error> { fn add_block(b: &Block, ctx: &mut BlockContext) -> Result<(), Error> { ctx.store.save_block(b).map_err(&Error::StoreErr)?; - // broadcast the block - let adapter = ctx.adapter.clone(); - adapter.block_accepted(b); + if !ctx.opts.intersects(SYNC) { + // broadcast the block + let adapter = ctx.adapter.clone(); + adapter.block_accepted(b); + } Ok(()) } @@ -244,8 +259,17 @@ fn update_head(b: &Block, ctx: &mut BlockContext) -> Result, Error> // when extending the head), update it let tip = Tip::from_block(&b.header); if tip.total_difficulty > ctx.head.total_difficulty { + + // update the block height index ctx.store.setup_height(&b.header).map_err(&Error::StoreErr)?; - ctx.store.save_head(&tip).map_err(&Error::StoreErr)?; + + // in sync mode, only update the "body chain", otherwise update both the + // "header chain" and "body chain" + if ctx.opts.intersects(SYNC) { + ctx.store.save_body_head(&tip).map_err(&Error::StoreErr)?; + } else { + ctx.store.save_head(&tip).map_err(&Error::StoreErr)?; + } ctx.head = tip.clone(); info!("Updated head to {} at {}.", b.hash(), b.header.height); diff --git a/chain/src/store.rs b/chain/src/store.rs index 1b17d9ee9..70ab4bc25 100644 --- a/chain/src/store.rs +++ b/chain/src/store.rs @@ -62,6 +62,10 @@ impl ChainStore for ChainKVStore { .write() } + fn save_body_head(&self, t: &Tip) -> Result<(), Error> { + self.db.put_ser(&vec![HEAD_PREFIX], t) + } + fn get_header_head(&self) -> Result { option_to_not_found(self.db.get_ser(&vec![HEADER_HEAD_PREFIX])) } @@ -78,6 +82,10 @@ impl ChainStore for ChainKVStore { option_to_not_found(self.db.get_ser(&to_key(BLOCK_HEADER_PREFIX, &mut h.to_vec()))) } + fn check_block_exists(&self, h: &Hash) -> Result { + self.db.exists(&to_key(BLOCK_PREFIX, &mut h.to_vec())) + } + fn save_block(&self, b: &Block) -> Result<(), Error> { // saving the block and its header let mut batch = self.db diff --git a/chain/src/types.rs b/chain/src/types.rs index a077a662b..59f89546a 100644 --- a/chain/src/types.rs +++ b/chain/src/types.rs @@ -97,12 +97,19 @@ pub trait ChainStore: Send + Sync { /// Save the provided tip as the current head of our chain fn save_head(&self, t: &Tip) -> Result<(), Error>; + /// Save the provided tip as the current head of the body chain, leaving the + /// header chain alone. + fn save_body_head(&self, t: &Tip) -> Result<(), Error>; + /// Gets a block header by hash fn get_block(&self, h: &Hash) -> Result; /// Gets a block header by hash fn get_block_header(&self, h: &Hash) -> Result; + /// Checks whether a block has been been processed and saved + fn check_block_exists(&self, h: &Hash) -> Result; + /// Save the provided block in store fn save_block(&self, b: &Block) -> Result<(), Error>;