Panic when state blocks are inserted out of order

This panic ensures our state is not corrupted. Corrupt states can lead
to future panics when querying the chain.
This commit is contained in:
teor 2020-09-04 19:34:40 +10:00 committed by Henry de Valence
parent 1285561c3f
commit 8463b705c8
3 changed files with 52 additions and 6 deletions

View File

@ -268,7 +268,7 @@ async fn continuous_blockchain(restart_height: Option<block::Height>) -> Result<
.cloned();
let state_service = zebra_state::init(zebra_state::Config::ephemeral(), Mainnet);
let mut checkpoint_verifier =
CheckpointVerifier::from_list(checkpoint_list, initial_tip, state_service)
CheckpointVerifier::from_list(checkpoint_list, initial_tip, state_service.clone())
.map_err(|e| eyre!(e))?;
// Setup checks
@ -305,7 +305,18 @@ async fn continuous_blockchain(restart_height: Option<block::Height>) -> Result<
for (block, height, _hash) in blockchain {
if let Some(restart_height) = restart_height {
if height <= restart_height {
continue;
let mut state_service = state_service.clone();
/// SPANDOC: Make sure the state service is ready for block {?height}
let ready_state_service =
state_service.ready_and().map_err(|e| eyre!(e)).await?;
/// SPANDOC: Add block to the state {?height}
ready_state_service
.call(zebra_state::Request::AddBlock {
block: block.clone(),
})
.await
.map_err(|e| eyre!(e))?;
}
}
if height > checkpoint_verifier.checkpoint_list.max_height() {

View File

@ -44,7 +44,42 @@ impl SledState {
let block = block.into();
let hash = block.hash();
let height = block.coinbase_height().unwrap();
let height = block
.coinbase_height()
.expect("missing height: valid blocks must have a height");
// Make sure blocks are inserted in order, as a defence in depth.
// See the state design RFC #0005 for details.
//
// TODO: handle multiple chains
match self.get_tip()? {
None => {
// This is a defence in depth - there is no need to check the
// genesis hash or previous block hash here.
assert_eq!(
height,
block::Height(0),
"out of order block: the first block must be at the genesis height"
);
}
Some(tip_hash) => {
assert_eq!(
block.header.previous_block_hash, tip_hash,
"out of order block: the next block must be a child of the current tip"
);
let tip_block = self
.get(tip_hash)?
.expect("missing tip block: tip hashes must have a corresponding block");
let tip_height = tip_block
.coinbase_height()
.expect("missing height: valid blocks must have a height");
assert_eq!(
height,
block::Height(tip_height.0 + 1),
"out of order block: the next height must be 1 greater than the tip height"
);
}
};
let height_map = self.storage.open_tree(b"height_map")?;
let by_hash = self.storage.open_tree(b"by_hash")?;

View File

@ -10,7 +10,7 @@ use zebra_state::*;
static ADD_BLOCK_TRANSCRIPT_MAINNET: Lazy<Vec<(Request, Result<Response, TransError>)>> =
Lazy::new(|| {
let block: Arc<_> =
Block::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_415000_BYTES[..])
Block::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_GENESIS_BYTES[..])
.unwrap()
.into();
let hash = block.as_ref().into();
@ -28,7 +28,7 @@ static ADD_BLOCK_TRANSCRIPT_MAINNET: Lazy<Vec<(Request, Result<Response, TransEr
static ADD_BLOCK_TRANSCRIPT_TESTNET: Lazy<Vec<(Request, Result<Response, TransError>)>> =
Lazy::new(|| {
let block: Arc<_> =
Block::zcash_deserialize(&zebra_test::vectors::BLOCK_TESTNET_10_BYTES[..])
Block::zcash_deserialize(&zebra_test::vectors::BLOCK_TESTNET_GENESIS_BYTES[..])
.unwrap()
.into();
let hash = block.as_ref().into();
@ -76,7 +76,7 @@ static GET_TIP_TRANSCRIPT_TESTNET: Lazy<Vec<(Request, Result<Response, TransErro
.unwrap()
.into();
let block1: Arc<_> =
Block::zcash_deserialize(&zebra_test::vectors::BLOCK_TESTNET_10_BYTES[..])
Block::zcash_deserialize(&zebra_test::vectors::BLOCK_TESTNET_1_BYTES[..])
.unwrap()
.into();
let hash0 = block0.as_ref().into();