Skip to content

Commit

Permalink
rewind bad headers, ban on explicit bad headers (#3603)
Browse files Browse the repository at this point in the history
* rewind bad headers
validate against explicit list of bad headers

* separate passes when rewinding bad block and bad header

* rebuild sync mmr safely
  • Loading branch information
antiochp authored Mar 19, 2021
1 parent 62105b3 commit d0c852e
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 6 deletions.
39 changes: 35 additions & 4 deletions chain/src/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,9 @@ impl Chain {
// Suppress any errors here in case we cannot find
chain.rewind_bad_block()?;

let header_head = chain.header_head()?;
chain.rebuild_sync_mmr(&header_head)?;

chain.log_heads()?;

// Temporarily exercising the initialization process.
Expand Down Expand Up @@ -277,14 +280,18 @@ impl Chain {
header.hash(),
header.height
);

let prev_header = self.get_previous_header(&header)?;
let new_head = Tip::from_header(&prev_header);

// Fix the (full) block chain.
if let Ok(block) = self.get_block(&hash) {
debug!(
"rewind_bad_block: found block: {} at {}",
block.header.hash(),
block.header.height
);

let prev_header = self.get_previous_header(&header)?;
debug!(
"rewind_bad_block: rewinding to prev: {} at {}",
prev_header.hash(),
Expand All @@ -296,7 +303,6 @@ impl Chain {
let mut batch = self.store.batch()?;

let old_head = batch.head()?;
let mut new_head = old_head.clone();

txhashset::extending(
&mut header_pmmr,
Expand All @@ -306,14 +312,14 @@ impl Chain {
pipe::rewind_and_apply_fork(&prev_header, ext, batch)?;

// Reset chain head.
new_head = Tip::from_header(&prev_header);
batch.save_body_head(&new_head)?;
batch.save_header_head(&new_head)?;

Ok(())
},
)?;

// Now delete bad block and all subsequent blocks from local db.
// Cleanup all subsequent bad blocks (back from old head).
let mut current = batch.get_block_header(&old_head.hash())?;
while current.height > new_head.height {
let _ = batch.delete_block(&current.hash());
Expand All @@ -322,6 +328,31 @@ impl Chain {

batch.commit()?;
}

{
let mut header_pmmr = self.header_pmmr.write();
let mut batch = self.store.batch()?;

let old_header_head = batch.header_head()?;

txhashset::header_extending(&mut header_pmmr, &mut batch, |ext, batch| {
pipe::rewind_and_apply_header_fork(&prev_header, ext, batch)?;

// Reset chain head.
batch.save_header_head(&new_head)?;

Ok(())
})?;

// cleanup all subsequent bad headers (back from old header_head).
let mut current = batch.get_block_header(&old_header_head.hash())?;
while current.height > new_head.height {
let _ = batch.delete_block_header(&current.hash());
current = batch.get_previous_header(&current)?;
}

batch.commit()?;
}
}
}

Expand Down
16 changes: 16 additions & 0 deletions chain/src/pipe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

//! Implementation of the chain block acceptance (or refusal) pipeline.
use grin_core::core::hash::Hash;

use crate::core::consensus;
use crate::core::core::hash::Hashed;
use crate::core::core::verifier_cache::VerifierCache;
Expand Down Expand Up @@ -317,6 +319,17 @@ fn prev_header_store(
Ok(prev)
}

fn check_bad_header(header: &BlockHeader) -> Result<(), Error> {
let bad_hashes = [Hash::from_hex(
"0002897182d8cf7631e86d56ad546b7cf0893bda811592aa9312ae633ce04813",
)?];
if bad_hashes.contains(&header.hash()) {
Err(ErrorKind::InvalidBlockProof(block::Error::Other("explicit bad header".into())).into())
} else {
Ok(())
}
}

/// First level of block validation that only needs to act on the block header
/// to make it as cheap as possible. The different validations are also
/// arranged by order of cost to have as little DoS surface as possible.
Expand All @@ -340,6 +353,9 @@ fn validate_header(header: &BlockHeader, ctx: &mut BlockContext<'_>) -> Result<(
return Err(ErrorKind::InvalidBlockTime.into());
}

// Check the header hash against a list of known bad headers.
check_bad_header(header)?;

// We can determine output and kernel counts for this block based on mmr sizes from previous header.
// Assume 0 inputs and estimate a lower bound on the full block weight.
let num_outputs = header
Expand Down
5 changes: 5 additions & 0 deletions chain/src/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,11 @@ impl<'a> Batch<'a> {
Ok(())
}

/// Delete a block header.
pub fn delete_block_header(&self, h: &Hash) -> Result<(), Error> {
self.db.delete(&to_key(BLOCK_HEADER_PREFIX, h)[..])
}

/// Save block header to db.
pub fn save_block_header(&self, header: &BlockHeader) -> Result<(), Error> {
let hash = header.hash();
Expand Down
7 changes: 5 additions & 2 deletions chain/src/txhashset/txhashset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -850,8 +850,11 @@ where
// Use underlying MMR to determine the "head".
let head = match handle.head_hash() {
Ok(hash) => {
let header = child_batch.get_block_header(&hash)?;
Tip::from_header(&header)
if let Ok(header) = child_batch.get_block_header(&hash) {
Tip::from_header(&header)
} else {
Tip::default()
}
}
Err(_) => Tip::default(),
};
Expand Down

0 comments on commit d0c852e

Please sign in to comment.