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:
parent
8a4add55f1
commit
684ce70f12
|
|
@ -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(())
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue