Skip to content

Commit

Permalink
Pass the finalized state to chain contextual validation
Browse files Browse the repository at this point in the history
This change is required to validate UTXOs against a forked chain.

Also abstract chain contextual validation into a new method.
  • Loading branch information
teor2345 committed Jul 16, 2021
1 parent cd78241 commit 86ff25c
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 32 deletions.
4 changes: 2 additions & 2 deletions zebra-state/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
Expand Down
36 changes: 32 additions & 4 deletions zebra-state/src/service/non_finalized_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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));
Expand All @@ -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)
}
Expand All @@ -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()
Expand Down
9 changes: 6 additions & 3 deletions zebra-state/src/service/non_finalized_state/tests/prop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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));
}

Expand All @@ -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());
Expand Down
62 changes: 39 additions & 23 deletions zebra-state/src/service/non_finalized_state/tests/vectors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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(())
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit 86ff25c

Please sign in to comment.