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;
|
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(())
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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());
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue