From f1488f9529ec346ab43d2cc7f135d98ef95ff7f2 Mon Sep 17 00:00:00 2001 From: Ignotus Peverell Date: Sun, 22 Oct 2017 07:11:45 +0000 Subject: [PATCH] Lots of chain sync and block validation fixes * Fix for the chain pipeline partly relying on an outdated head, leading to not properly recognizing a fork and inconsistent sum tree state. * Do not drop block requests during sync that don't get satisfied, retry enough time to get them and avoid stall. * Always validate header, even in sync where we may have validated it already. We don't want a block coming from a peer that could squeeze through with an invalid header. * When syncing, do not mark blocks that were errored by the chain as received (typical case: orphan). Keep retrying. * Improved chain state dump for debugging. * Do not add to orphans blocks too far in the future. * Better error reporting on db errors. * Related sync test fixes. TODO figure out why syncing peers timeout so often, very useful to test but not that great for a fast sync experience. --- chain/src/chain.rs | 33 ++++++++-------- chain/src/pipe.rs | 45 +++++++++------------- chain/src/sumtree.rs | 29 +++++++++----- chain/src/types.rs | 4 +- core/src/core/block.rs | 2 +- core/src/core/pmmr.rs | 39 +++++++++++-------- grin/src/adapters.rs | 25 +++++++----- grin/src/sync.rs | 87 +++++++++++++++++++++++++++--------------- grin/src/types.rs | 2 + grin/tests/simulnet.rs | 28 +++++++------- src/bin/grin.rs | 32 ++++++++-------- 11 files changed, 183 insertions(+), 143 deletions(-) diff --git a/chain/src/chain.rs b/chain/src/chain.rs index 78a78232..61077be5 100644 --- a/chain/src/chain.rs +++ b/chain/src/chain.rs @@ -96,7 +96,7 @@ impl Chain { info!(LOGGER, "Saved genesis block with hash {}", gen.hash()); tip } - Err(e) => return Err(Error::StoreErr(e)), + Err(e) => return Err(Error::StoreErr(e, "chain init load head".to_owned())), }; let store = Arc::new(chain_store); @@ -116,7 +116,8 @@ impl Chain { /// has been added to the longest chain, None if it's added to an (as of /// now) orphan chain. pub fn process_block(&self, b: Block, opts: Options) -> Result, Error> { - let head = self.store.head().map_err(&Error::StoreErr)?; + let head = self.store.head().map_err(|e| Error::StoreErr(e, "chain load head".to_owned()))?; + let height = head.height; let ctx = self.ctx_from_head(head, opts); let res = pipe::process_block(&b, ctx); @@ -140,9 +141,11 @@ impl Chain { } Ok(None) => {} Err(Error::Orphan) => { - let mut orphans = self.orphans.lock().unwrap(); - orphans.push_front((opts, b)); - orphans.truncate(MAX_ORPHANS); + if b.header.height < height + (MAX_ORPHANS as u64) { + let mut orphans = self.orphans.lock().unwrap(); + orphans.push_front((opts, b)); + orphans.truncate(MAX_ORPHANS); + } } Err(ref e) => { info!( @@ -166,7 +169,7 @@ impl Chain { opts: Options, ) -> Result, Error> { - let head = self.store.get_header_head().map_err(&Error::StoreErr)?; + let head = self.store.get_header_head().map_err(|e| Error::StoreErr(e, "chain header head".to_owned()))?; let ctx = self.ctx_from_head(head, opts); pipe::process_block_header(bh, ctx) @@ -221,8 +224,8 @@ impl Chain { let sumtrees = self.sumtrees.read().unwrap(); let is_unspent = sumtrees.is_unspent(output_ref)?; if is_unspent { - self.store.get_output_by_commit(output_ref).map_err( - &Error::StoreErr, + self.store.get_output_by_commit(output_ref).map_err(|e| + Error::StoreErr(e, "chain get unspent".to_owned()) ) } else { Err(Error::OutputNotFound) @@ -259,23 +262,23 @@ impl Chain { /// Block header for the chain head pub fn head_header(&self) -> Result { - self.store.head_header().map_err(&Error::StoreErr) + self.store.head_header().map_err(|e| Error::StoreErr(e, "chain head header".to_owned())) } /// Gets a block header by hash pub fn get_block(&self, h: &Hash) -> Result { - self.store.get_block(h).map_err(&Error::StoreErr) + self.store.get_block(h).map_err(|e| Error::StoreErr(e, "chain get block".to_owned())) } /// Gets a block header by hash pub fn get_block_header(&self, h: &Hash) -> Result { - self.store.get_block_header(h).map_err(&Error::StoreErr) + self.store.get_block_header(h).map_err(|e| Error::StoreErr(e, "chain get header".to_owned())) } /// Gets the block header at the provided height pub fn get_header_by_height(&self, height: u64) -> Result { - self.store.get_header_by_height(height).map_err( - &Error::StoreErr, + self.store.get_header_by_height(height).map_err(|e| + Error::StoreErr(e, "chain get header by height".to_owned()), ) } @@ -286,12 +289,12 @@ impl Chain { ) -> Result { self.store .get_block_header_by_output_commit(commit) - .map_err(&Error::StoreErr) + .map_err(|e| Error::StoreErr(e, "chain get commitment".to_owned())) } /// Get the tip of the header chain pub fn get_header_head(&self) -> Result { - self.store.get_header_head().map_err(&Error::StoreErr) + self.store.get_header_head().map_err(|e |Error::StoreErr(e, "chain get header head".to_owned())) } /// Builds an iterator on blocks starting from the current chain head and diff --git a/chain/src/pipe.rs b/chain/src/pipe.rs index 8fd96044..965b0dea 100644 --- a/chain/src/pipe.rs +++ b/chain/src/pipe.rs @@ -63,10 +63,16 @@ pub fn process_block(b: &Block, mut ctx: BlockContext) -> Result, Er validate_header(&b.header, &mut ctx)?; - // take the lock on the sum trees and start a chain extension unit of work - // dependent on the success of the internal validation and saving operations + // valid header, time to take the lock on the sum trees let local_sumtrees = ctx.sumtrees.clone(); let mut sumtrees = local_sumtrees.write().unwrap(); + + // update head now that we're in the lock + ctx.head = ctx.store.head(). + map_err(|e| Error::StoreErr(e, "pipe reload head".to_owned()))?; + + // start a chain extension unit of work dependent on the success of the + // internal validation and saving operations sumtree::extending(&mut sumtrees, |mut extension| { validate_block(b, &mut ctx, &mut extension)?; @@ -162,8 +168,8 @@ fn validate_header(header: &BlockHeader, ctx: &mut BlockContext) -> Result<(), E } // first I/O cost, better as late as possible - let prev = try!(ctx.store.get_block_header(&header.previous).map_err( - &Error::StoreErr, + let prev = try!(ctx.store.get_block_header(&header.previous).map_err(|e| + Error::StoreErr(e, format!("previous block header {}", header.previous)), )); if header.height != prev.height + 1 { @@ -208,19 +214,6 @@ fn validate_block( let curve = secp::Secp256k1::with_caps(secp::ContextFlag::Commit); try!(b.validate(&curve).map_err(&Error::InvalidBlockProof)); - // check that all the outputs of the block are "new" - - // that they do not clobber any existing unspent outputs (by their commitment) - // - // TODO - do we need to do this here (and can we do this here if we need access - // to the chain) - // see check_duplicate_outputs in pool for the analogous operation on - // transaction outputs - // for output in &block.outputs { - // here we would check that the output is not a duplicate output based on the - // current chain - // }; - - // apply the new block to the MMR trees and check the new root hashes if b.header.previous == ctx.head.last_block_h { // standard head extension @@ -268,7 +261,7 @@ fn validate_block( kernel_root.hash != b.header.kernel_root { - ext.dump(); + ext.dump(false); return Err(Error::InvalidRoot); } @@ -297,14 +290,12 @@ fn validate_block( /// Officially adds the block to our chain. fn add_block(b: &Block, ctx: &mut BlockContext) -> Result<(), Error> { - ctx.store.save_block(b).map_err(&Error::StoreErr)?; - - Ok(()) + ctx.store.save_block(b).map_err(|e| Error::StoreErr(e, "pipe save block".to_owned())) } /// Officially adds the block header to our header chain. fn add_block_header(bh: &BlockHeader, ctx: &mut BlockContext) -> Result<(), Error> { - ctx.store.save_block_header(bh).map_err(&Error::StoreErr) + ctx.store.save_block_header(bh).map_err(|e| Error::StoreErr(e, "pipe save header".to_owned())) } /// Directly updates the head if we've just appended a new block to it or handle @@ -317,18 +308,16 @@ fn update_head(b: &Block, ctx: &mut BlockContext) -> Result, Error> 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.setup_height(&b.header).map_err(|e| Error::StoreErr(e, "pipe setup height".to_owned()))?; // in sync mode, only update the "body chain", otherwise update both the // "header chain" and "body chain", updating the header chain in sync resets // all additional "future" headers we've received if ctx.opts.intersects(SYNC) { - ctx.store.save_body_head(&tip).map_err(&Error::StoreErr)?; + ctx.store.save_body_head(&tip).map_err(|e| Error::StoreErr(e, "pipe save body".to_owned()))?; } else { - ctx.store.save_head(&tip).map_err(&Error::StoreErr)?; + ctx.store.save_head(&tip).map_err(|e| Error::StoreErr(e, "pipe save head".to_owned()))?; } - - ctx.store.save_head(&tip).map_err(&Error::StoreErr)?; ctx.head = tip.clone(); info!(LOGGER, "Updated head to {} at {}.", b.hash(), b.header.height); Ok(Some(tip)) @@ -345,7 +334,7 @@ fn update_header_head(bh: &BlockHeader, ctx: &mut BlockContext) -> Result