diff --git a/chain/src/chain.rs b/chain/src/chain.rs index ec94c1bcf..b790e52eb 100644 --- a/chain/src/chain.rs +++ b/chain/src/chain.rs @@ -1449,9 +1449,15 @@ fn setup_head( } } - // Setup our header_head if we do not already have one. - // Migrating back to header_head in db and some nodes may note have one. - if batch.header_head().is_err() { + // Make sure our header PMMR is consistent with header_head from db if it exists. + // If header_head is missing in db then use head of header PMMR. + if let Ok(head) = batch.header_head() { + header_pmmr.init_head(&head)?; + txhashset::header_extending(header_pmmr, &mut batch, |ext, batch| { + let header = batch.get_block_header(&head.hash())?; + ext.rewind(&header) + })?; + } else { let hash = header_pmmr.head_hash()?; let header = batch.get_block_header(&hash)?; batch.save_header_head(&Tip::from_header(&header))?; diff --git a/chain/src/pipe.rs b/chain/src/pipe.rs index 2db6b1bf2..ae1b753f8 100644 --- a/chain/src/pipe.rs +++ b/chain/src/pipe.rs @@ -250,6 +250,11 @@ pub fn process_block_header(header: &BlockHeader, ctx: &mut BlockContext<'_>) -> } } + // We want to validate this individual header before applying it to our header PMMR. + validate_header(header, ctx)?; + + // Apply the header to the header PMMR, making sure we put the extension in the correct state + // based on previous header first. txhashset::header_extending(&mut ctx.header_pmmr, &mut ctx.batch, |ext, batch| { rewind_and_apply_header_fork(&prev_header, ext, batch)?; ext.validate_root(header)?; @@ -260,7 +265,7 @@ pub fn process_block_header(header: &BlockHeader, ctx: &mut BlockContext<'_>) -> Ok(()) })?; - validate_header(header, ctx)?; + // Add this new block header to the db. add_block_header(header, &ctx.batch)?; if has_more_work(header, &header_head) { diff --git a/chain/src/txhashset/txhashset.rs b/chain/src/txhashset/txhashset.rs index 548562822..f22946ba6 100644 --- a/chain/src/txhashset/txhashset.rs +++ b/chain/src/txhashset/txhashset.rs @@ -74,6 +74,41 @@ impl<T: PMMRable> PMMRHandle<T> { } impl PMMRHandle<BlockHeader> { + /// Used during chain init to ensure the header PMMR is consistent with header_head in the db. + pub fn init_head(&mut self, head: &Tip) -> Result<(), Error> { + let head_hash = self.head_hash()?; + let expected_hash = self.get_header_hash_by_height(head.height)?; + if head.hash() != expected_hash { + error!( + "header PMMR inconsistent: {} vs {} at {}", + expected_hash, + head.hash(), + head.height + ); + return Err(ErrorKind::Other("header PMMR inconsistent".to_string()).into()); + } + + // 1-indexed pos and we want to account for subsequent parent hash pos. + // so use next header pos to find our last_pos. + let next_height = head.height + 1; + let next_pos = pmmr::insertion_to_pmmr_index(next_height + 1); + let pos = next_pos.saturating_sub(1); + + debug!( + "init_head: header PMMR: current head {} at pos {}", + head_hash, self.last_pos + ); + debug!( + "init_head: header PMMR: resetting to {} at pos {} (height {})", + head.hash(), + pos, + head.height + ); + + self.last_pos = pos; + Ok(()) + } + /// Get the header hash at the specified height based on the current header MMR state. pub fn get_header_hash_by_height(&self, height: u64) -> Result<Hash, Error> { let pos = pmmr::insertion_to_pmmr_index(height + 1);