diff --git a/zebra-state/src/service.rs b/zebra-state/src/service.rs index f5a7133d856..e975a623eb1 100644 --- a/zebra-state/src/service.rs +++ b/zebra-state/src/service.rs @@ -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(()) diff --git a/zebra-state/src/service/non_finalized_state.rs b/zebra-state/src/service/non_finalized_state.rs index 0e2f25d0e46..eefa915a3bb 100644 --- a/zebra-state/src/service/non_finalized_state.rs +++ b/zebra-state/src/service/non_finalized_state.rs @@ -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 { + // 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() diff --git a/zebra-state/src/service/non_finalized_state/tests/prop.rs b/zebra-state/src/service/non_finalized_state/tests/prop.rs index 5d1f5b36d68..c7de35d2097 100644 --- a/zebra-state/src/service/non_finalized_state/tests/prop.rs +++ b/zebra-state/src/service/non_finalized_state/tests/prop.rs @@ -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()); diff --git a/zebra-state/src/service/non_finalized_state/tests/vectors.rs b/zebra-state/src/service/non_finalized_state/tests/vectors.rs index 95f33ba1aaa..45a67a5a3f3 100644 --- a/zebra-state/src/service/non_finalized_state/tests/vectors.rs +++ b/zebra-state/src/service/non_finalized_state/tests/vectors.rs @@ -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;