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; let parent_hash = prepared.block.header.previous_block_hash;
if self.disk.finalized_tip_hash() == parent_hash { if self.disk.finalized_tip_hash() == parent_hash {
self.mem.commit_new_chain(prepared)?; self.mem.commit_new_chain(prepared, &self.disk)?;
} else { } else {
self.mem.commit_block(prepared)?; self.mem.commit_block(prepared, &self.disk)?;
} }
Ok(()) Ok(())

View File

@ -23,6 +23,8 @@ use crate::{FinalizedBlock, HashOrHeight, PreparedBlock, ValidateContextError};
use self::chain::Chain; use self::chain::Chain;
use super::finalized_state::FinalizedState;
/// The state of the chains in memory, incuding queued blocks. /// The state of the chains in memory, incuding queued blocks.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct NonFinalizedState { pub struct NonFinalizedState {
@ -107,14 +109,22 @@ impl NonFinalizedState {
finalizing.into() finalizing.into()
} }
/// Commit block to the non-finalized state. /// Commit block to the non-finalized state, on top of:
pub fn commit_block(&mut self, prepared: PreparedBlock) -> Result<(), ValidateContextError> { /// - 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 parent_hash = prepared.block.header.previous_block_hash;
let (height, hash) = (prepared.height, prepared.hash); let (height, hash) = (prepared.height, prepared.hash);
let parent_chain = self.parent_chain(parent_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) => { Ok(child_chain) => {
// if the block is valid, keep the child chain, and drop the parent chain // if the block is valid, keep the child chain, and drop the parent chain
self.chain_set.insert(Box::new(child_chain)); self.chain_set.insert(Box::new(child_chain));
@ -124,6 +134,10 @@ impl NonFinalizedState {
Err(err) => { Err(err) => {
// if the block is invalid, restore the unmodified parent chain // if the block is invalid, restore the unmodified parent chain
// (the child chain might have been modified before the error) // (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); self.chain_set.insert(parent_chain);
Err(err) Err(err)
} }
@ -135,17 +149,31 @@ impl NonFinalizedState {
pub fn commit_new_chain( pub fn commit_new_chain(
&mut self, &mut self,
prepared: PreparedBlock, prepared: PreparedBlock,
finalized_state: &FinalizedState,
) -> Result<(), ValidateContextError> { ) -> Result<(), ValidateContextError> {
let chain = Chain::default(); let chain = Chain::default();
let (height, hash) = (prepared.height, prepared.hash); let (height, hash) = (prepared.height, prepared.hash);
// if the block is invalid, drop the newly created chain fork // 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.chain_set.insert(Box::new(chain));
self.update_metrics_for_committed_block(height, hash); self.update_metrics_for_committed_block(height, hash);
Ok(()) 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. /// Returns the length of the non-finalized portion of the current best chain.
pub fn best_chain_len(&self) -> u32 { pub fn best_chain_len(&self) -> u32 {
self.best_chain() self.best_chain()

View File

@ -7,9 +7,11 @@ use zebra_chain::{block::Block, fmt::DisplayToDebug, parameters::NetworkUpgrade:
use crate::{ use crate::{
service::{ service::{
arbitrary::PreparedChain, arbitrary::PreparedChain,
finalized_state::FinalizedState,
non_finalized_state::{Chain, NonFinalizedState}, non_finalized_state::{Chain, NonFinalizedState},
}, },
tests::Prepare, tests::Prepare,
Config,
}; };
const DEFAULT_PARTIAL_CHAIN_PROPTEST_CASES: u32 = 32; 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 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 // use `valid_count` as the number of valid blocks before an invalid block
let valid_tip_height = chain[valid_count - 1].height; 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)); prop_assert!(state.eq_internal_state(&state));
if let Some(first_block) = chain.next() { 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)); prop_assert!(state.eq_internal_state(&state));
} }
for block in chain { for block in chain {
state.commit_block(block)?; state.commit_block(block, &finalized_state)?;
prop_assert!(state.eq_internal_state(&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; bad_block.header.previous_block_hash = valid_tip_hash;
let bad_block = Arc::new(bad_block.0).prepare(); 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() { if reject_result.is_err() {
prop_assert_eq!(state.best_tip(), reject_state.best_tip()); 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 zebra_test::prelude::*;
use crate::{ use crate::{
service::non_finalized_state::{Chain, NonFinalizedState}, service::{
finalized_state::FinalizedState,
non_finalized_state::{Chain, NonFinalizedState},
},
tests::{FakeChainHelper, Prepare}, tests::{FakeChainHelper, Prepare},
Config,
}; };
use self::assert_eq; use self::assert_eq;
@ -100,8 +104,10 @@ fn best_chain_wins_for_network(network: Network) -> Result<()> {
let expected_hash = block2.hash(); let expected_hash = block2.hash();
let mut state = NonFinalizedState::new(network); let mut state = NonFinalizedState::new(network);
state.commit_new_chain(block2.prepare())?; let finalized_state = FinalizedState::new(&Config::ephemeral(), network);
state.commit_new_chain(child.prepare())?;
state.commit_new_chain(block2.prepare(), &finalized_state)?;
state.commit_new_chain(child.prepare(), &finalized_state)?;
let best_chain = state.best_chain().unwrap(); let best_chain = state.best_chain().unwrap();
assert!(best_chain.height_by_hash.contains_key(&expected_hash)); 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 child = block1.make_fake_child().set_work(1);
let mut state = NonFinalizedState::new(network); let mut state = NonFinalizedState::new(network);
state.commit_new_chain(block1.clone().prepare())?; let finalized_state = FinalizedState::new(&Config::ephemeral(), network);
state.commit_block(block2.clone().prepare())?;
state.commit_block(child.prepare())?; 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(); let finalized = state.finalize();
assert_eq!(block1, finalized.block); 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 child2 = block2.make_fake_child().set_work(1);
let mut state = NonFinalizedState::new(network); let mut state = NonFinalizedState::new(network);
let finalized_state = FinalizedState::new(&Config::ephemeral(), network);
assert_eq!(0, state.chain_set.len()); 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()); 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()); 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()); 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()); assert_eq!(2, state.chain_set.len());
Ok(()) 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 short_chain_block = block1.make_fake_child().set_work(3);
let mut state = NonFinalizedState::new(network); let mut state = NonFinalizedState::new(network);
state.commit_new_chain(block1.prepare())?; let finalized_state = FinalizedState::new(&Config::ephemeral(), network);
state.commit_block(long_chain_block1.prepare())?;
state.commit_block(long_chain_block2.prepare())?; state.commit_new_chain(block1.prepare(), &finalized_state)?;
state.commit_block(short_chain_block.prepare())?; 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.chain_set.len());
assert_eq!(2, state.best_chain_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 short_chain_block = block1.make_fake_child().set_work(3);
let mut state = NonFinalizedState::new(network); let mut state = NonFinalizedState::new(network);
state.commit_new_chain(block1.prepare())?; let finalized_state = FinalizedState::new(&Config::ephemeral(), network);
state.commit_block(long_chain_block1.prepare())?;
state.commit_block(long_chain_block2.prepare())?; state.commit_new_chain(block1.prepare(), &finalized_state)?;
state.commit_block(long_chain_block3.prepare())?; state.commit_block(long_chain_block1.prepare(), &finalized_state)?;
state.commit_block(long_chain_block4.prepare())?; state.commit_block(long_chain_block2.prepare(), &finalized_state)?;
state.commit_block(short_chain_block.prepare())?; 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!(2, state.chain_set.len());
assert_eq!(5, state.best_chain_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 expected_hash = more_work_child.hash();
let mut state = NonFinalizedState::new(network); let mut state = NonFinalizedState::new(network);
state.commit_new_chain(block1.prepare())?; let finalized_state = FinalizedState::new(&Config::ephemeral(), network);
state.commit_block(less_work_child.prepare())?;
state.commit_block(more_work_child.prepare())?; 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()); assert_eq!(2, state.chain_set.len());
let tip_hash = state.best_tip().unwrap().1; let tip_hash = state.best_tip().unwrap().1;