Pass the finalized state to chain contextual validation (#2503)

This change is required to validate UTXOs against a forked chain.

Also abstract chain contextual validation into a new method.
This commit is contained in:
teor 2021-07-19 10:59:30 +10:00 committed by GitHub
parent 8a4add55f1
commit 684ce70f12
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 79 additions and 32 deletions

View File

@ -175,9 +175,9 @@ impl StateService {
let parent_hash = prepared.block.header.previous_block_hash;
if self.disk.finalized_tip_hash() == parent_hash {
self.mem.commit_new_chain(prepared)?;
self.mem.commit_new_chain(prepared, &self.disk)?;
} else {
self.mem.commit_block(prepared)?;
self.mem.commit_block(prepared, &self.disk)?;
}
Ok(())

View File

@ -23,6 +23,8 @@ use crate::{FinalizedBlock, HashOrHeight, PreparedBlock, ValidateContextError};
use self::chain::Chain;
use super::finalized_state::FinalizedState;
/// The state of the chains in memory, incuding queued blocks.
#[derive(Debug, Clone)]
pub struct NonFinalizedState {
@ -107,14 +109,22 @@ impl NonFinalizedState {
finalizing.into()
}
/// Commit block to the non-finalized state.
pub fn commit_block(&mut self, prepared: PreparedBlock) -> Result<(), ValidateContextError> {
/// Commit block to the non-finalized state, on top of:
/// - an existing chain's tip, or
/// - a newly forked chain.
pub fn commit_block(
&mut self,
prepared: PreparedBlock,
finalized_state: &FinalizedState,
) -> Result<(), ValidateContextError> {
let parent_hash = prepared.block.header.previous_block_hash;
let (height, hash) = (prepared.height, prepared.hash);
let parent_chain = self.parent_chain(parent_hash)?;
match parent_chain.clone().push(prepared) {
// We might have taken a chain, so all validation must happen within
// validate_and_commit, so that the chain is restored correctly.
match self.validate_and_commit(*parent_chain.clone(), prepared, finalized_state) {
Ok(child_chain) => {
// if the block is valid, keep the child chain, and drop the parent chain
self.chain_set.insert(Box::new(child_chain));
@ -124,6 +134,10 @@ impl NonFinalizedState {
Err(err) => {
// if the block is invalid, restore the unmodified parent chain
// (the child chain might have been modified before the error)
//
// If the chain was forked, this adds an extra chain to the
// chain set. This extra chain will eventually get deleted
// (or re-used for a valid fork).
self.chain_set.insert(parent_chain);
Err(err)
}
@ -135,17 +149,31 @@ impl NonFinalizedState {
pub fn commit_new_chain(
&mut self,
prepared: PreparedBlock,
finalized_state: &FinalizedState,
) -> Result<(), ValidateContextError> {
let chain = Chain::default();
let (height, hash) = (prepared.height, prepared.hash);
// if the block is invalid, drop the newly created chain fork
let chain = chain.push(prepared)?;
let chain = self.validate_and_commit(chain, prepared, finalized_state)?;
self.chain_set.insert(Box::new(chain));
self.update_metrics_for_committed_block(height, hash);
Ok(())
}
/// Contextually validate `prepared` using `finalized_state`.
/// If validation succeeds, push `prepared` onto `parent_chain`.
fn validate_and_commit(
&self,
parent_chain: Chain,
prepared: PreparedBlock,
_finalized_state: &FinalizedState,
) -> Result<Chain, ValidateContextError> {
// TODO: insert validation of `prepared` block and `parent_chain` here
parent_chain.push(prepared)
}
/// Returns the length of the non-finalized portion of the current best chain.
pub fn best_chain_len(&self) -> u32 {
self.best_chain()

View File

@ -7,9 +7,11 @@ use zebra_chain::{block::Block, fmt::DisplayToDebug, parameters::NetworkUpgrade:
use crate::{
service::{
arbitrary::PreparedChain,
finalized_state::FinalizedState,
non_finalized_state::{Chain, NonFinalizedState},
},
tests::Prepare,
Config,
};
const DEFAULT_PARTIAL_CHAIN_PROPTEST_CASES: u32 = 32;
@ -110,6 +112,7 @@ fn rejection_restores_internal_state() -> Result<()> {
}
))| {
let mut state = NonFinalizedState::new(network);
let finalized_state = FinalizedState::new(&Config::ephemeral(), network);
// use `valid_count` as the number of valid blocks before an invalid block
let valid_tip_height = chain[valid_count - 1].height;
@ -119,12 +122,12 @@ fn rejection_restores_internal_state() -> Result<()> {
prop_assert!(state.eq_internal_state(&state));
if let Some(first_block) = chain.next() {
state.commit_new_chain(first_block)?;
state.commit_new_chain(first_block, &finalized_state)?;
prop_assert!(state.eq_internal_state(&state));
}
for block in chain {
state.commit_block(block)?;
state.commit_block(block, &finalized_state)?;
prop_assert!(state.eq_internal_state(&state));
}
@ -137,7 +140,7 @@ fn rejection_restores_internal_state() -> Result<()> {
bad_block.header.previous_block_hash = valid_tip_hash;
let bad_block = Arc::new(bad_block.0).prepare();
let reject_result = reject_state.commit_block(bad_block);
let reject_result = reject_state.commit_block(bad_block, &finalized_state);
if reject_result.is_err() {
prop_assert_eq!(state.best_tip(), reject_state.best_tip());

View File

@ -4,8 +4,12 @@ use zebra_chain::{block::Block, parameters::Network, serialization::ZcashDeseria
use zebra_test::prelude::*;
use crate::{
service::non_finalized_state::{Chain, NonFinalizedState},
service::{
finalized_state::FinalizedState,
non_finalized_state::{Chain, NonFinalizedState},
},
tests::{FakeChainHelper, Prepare},
Config,
};
use self::assert_eq;
@ -100,8 +104,10 @@ fn best_chain_wins_for_network(network: Network) -> Result<()> {
let expected_hash = block2.hash();
let mut state = NonFinalizedState::new(network);
state.commit_new_chain(block2.prepare())?;
state.commit_new_chain(child.prepare())?;
let finalized_state = FinalizedState::new(&Config::ephemeral(), network);
state.commit_new_chain(block2.prepare(), &finalized_state)?;
state.commit_new_chain(child.prepare(), &finalized_state)?;
let best_chain = state.best_chain().unwrap();
assert!(best_chain.height_by_hash.contains_key(&expected_hash));
@ -133,9 +139,11 @@ fn finalize_pops_from_best_chain_for_network(network: Network) -> Result<()> {
let child = block1.make_fake_child().set_work(1);
let mut state = NonFinalizedState::new(network);
state.commit_new_chain(block1.clone().prepare())?;
state.commit_block(block2.clone().prepare())?;
state.commit_block(child.prepare())?;
let finalized_state = FinalizedState::new(&Config::ephemeral(), network);
state.commit_new_chain(block1.clone().prepare(), &finalized_state)?;
state.commit_block(block2.clone().prepare(), &finalized_state)?;
state.commit_block(child.prepare(), &finalized_state)?;
let finalized = state.finalize();
assert_eq!(block1, finalized.block);
@ -176,14 +184,16 @@ fn commit_block_extending_best_chain_doesnt_drop_worst_chains_for_network(
let child2 = block2.make_fake_child().set_work(1);
let mut state = NonFinalizedState::new(network);
let finalized_state = FinalizedState::new(&Config::ephemeral(), network);
assert_eq!(0, state.chain_set.len());
state.commit_new_chain(block1.prepare())?;
state.commit_new_chain(block1.prepare(), &finalized_state)?;
assert_eq!(1, state.chain_set.len());
state.commit_block(block2.prepare())?;
state.commit_block(block2.prepare(), &finalized_state)?;
assert_eq!(1, state.chain_set.len());
state.commit_block(child1.prepare())?;
state.commit_block(child1.prepare(), &finalized_state)?;
assert_eq!(2, state.chain_set.len());
state.commit_block(child2.prepare())?;
state.commit_block(child2.prepare(), &finalized_state)?;
assert_eq!(2, state.chain_set.len());
Ok(())
@ -215,10 +225,12 @@ fn shorter_chain_can_be_best_chain_for_network(network: Network) -> Result<()> {
let short_chain_block = block1.make_fake_child().set_work(3);
let mut state = NonFinalizedState::new(network);
state.commit_new_chain(block1.prepare())?;
state.commit_block(long_chain_block1.prepare())?;
state.commit_block(long_chain_block2.prepare())?;
state.commit_block(short_chain_block.prepare())?;
let finalized_state = FinalizedState::new(&Config::ephemeral(), network);
state.commit_new_chain(block1.prepare(), &finalized_state)?;
state.commit_block(long_chain_block1.prepare(), &finalized_state)?;
state.commit_block(long_chain_block2.prepare(), &finalized_state)?;
state.commit_block(short_chain_block.prepare(), &finalized_state)?;
assert_eq!(2, state.chain_set.len());
assert_eq!(2, state.best_chain_len());
@ -254,12 +266,14 @@ fn longer_chain_with_more_work_wins_for_network(network: Network) -> Result<()>
let short_chain_block = block1.make_fake_child().set_work(3);
let mut state = NonFinalizedState::new(network);
state.commit_new_chain(block1.prepare())?;
state.commit_block(long_chain_block1.prepare())?;
state.commit_block(long_chain_block2.prepare())?;
state.commit_block(long_chain_block3.prepare())?;
state.commit_block(long_chain_block4.prepare())?;
state.commit_block(short_chain_block.prepare())?;
let finalized_state = FinalizedState::new(&Config::ephemeral(), network);
state.commit_new_chain(block1.prepare(), &finalized_state)?;
state.commit_block(long_chain_block1.prepare(), &finalized_state)?;
state.commit_block(long_chain_block2.prepare(), &finalized_state)?;
state.commit_block(long_chain_block3.prepare(), &finalized_state)?;
state.commit_block(long_chain_block4.prepare(), &finalized_state)?;
state.commit_block(short_chain_block.prepare(), &finalized_state)?;
assert_eq!(2, state.chain_set.len());
assert_eq!(5, state.best_chain_len());
@ -291,9 +305,11 @@ fn equal_length_goes_to_more_work_for_network(network: Network) -> Result<()> {
let expected_hash = more_work_child.hash();
let mut state = NonFinalizedState::new(network);
state.commit_new_chain(block1.prepare())?;
state.commit_block(less_work_child.prepare())?;
state.commit_block(more_work_child.prepare())?;
let finalized_state = FinalizedState::new(&Config::ephemeral(), network);
state.commit_new_chain(block1.prepare(), &finalized_state)?;
state.commit_block(less_work_child.prepare(), &finalized_state)?;
state.commit_block(more_work_child.prepare(), &finalized_state)?;
assert_eq!(2, state.chain_set.len());
let tip_hash = state.best_tip().unwrap().1;