// Copyright 2021 Parity Technologies (UK) Ltd. // This file is part of Polkadot. // Polkadot is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // Polkadot is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . //! Tests for the subsystem. //! //! These primarily revolve around having a backend which is shared between //! both the test code and the tested subsystem, and which also gives the //! test code the ability to wait for write operations to occur. use super::*; use std::{ collections::{BTreeMap, HashMap, HashSet}, sync::{ atomic::{AtomicU64, Ordering as AtomicOrdering}, Arc, }, }; use assert_matches::assert_matches; use futures::channel::oneshot; use parity_scale_codec::Encode; use parking_lot::Mutex; use sp_core::testing::TaskExecutor; use polkadot_node_subsystem::{ jaeger, messages::AllMessages, ActivatedLeaf, ActiveLeavesUpdate, LeafStatus, }; use polkadot_node_subsystem_test_helpers as test_helpers; use polkadot_primitives::v2::{BlakeTwo256, ConsensusLog, HashT}; #[derive(Default)] struct TestBackendInner { leaves: LeafEntrySet, block_entries: HashMap, blocks_by_number: BTreeMap>, stagnant_at: BTreeMap>, // earlier wakers at the back. write_wakers: Vec>, } #[derive(Clone)] struct TestBackend { inner: Arc>, } impl TestBackend { // Yields a receiver which will be woken up on some future write // to the backend along with its position (starting at 0) in the // queue. // // Our tests assume that there is only one task calling this function // and the index is useful to get a waker that will trigger after // some known amount of writes to the backend that happen internally // inside the subsystem. // // It's important to call this function at points where no writes // are pending to the backend. This requires knowing some details // about the internals of the subsystem, so the abstraction leaks // somewhat, but this is acceptable enough. fn await_next_write(&self) -> (usize, oneshot::Receiver<()>) { let (tx, rx) = oneshot::channel(); let mut inner = self.inner.lock(); let pos = inner.write_wakers.len(); inner.write_wakers.insert(0, tx); (pos, rx) } // Assert the backend contains only the given blocks and no others. // This does not check the stagnant_at mapping because that is // pruned lazily by the subsystem as opposed to eagerly. fn assert_contains_only(&self, blocks: Vec<(BlockNumber, Hash)>) { let hashes: Vec<_> = blocks.iter().map(|(_, h)| *h).collect(); let mut by_number: HashMap<_, HashSet<_>> = HashMap::new(); for (number, hash) in blocks { by_number.entry(number).or_default().insert(hash); } let inner = self.inner.lock(); assert_eq!(inner.block_entries.len(), hashes.len()); assert_eq!(inner.blocks_by_number.len(), by_number.len()); for leaf in inner.leaves.clone().into_hashes_descending() { assert!(hashes.contains(&leaf)); } for (number, hashes_at_number) in by_number { let at = inner.blocks_by_number.get(&number).unwrap(); for hash in at { assert!(hashes_at_number.contains(&hash)); } } } fn assert_stagnant_at_state(&self, stagnant_at: Vec<(Timestamp, Vec)>) { let inner = self.inner.lock(); assert_eq!(inner.stagnant_at.len(), stagnant_at.len()); for (at, hashes) in stagnant_at { let stored_hashes = inner.stagnant_at.get(&at).unwrap(); assert_eq!(hashes.len(), stored_hashes.len()); for hash in hashes { assert!(stored_hashes.contains(&hash)); } } } } impl Default for TestBackend { fn default() -> Self { TestBackend { inner: Default::default() } } } impl Backend for TestBackend { fn load_block_entry(&self, hash: &Hash) -> Result, Error> { Ok(self.inner.lock().block_entries.get(hash).map(|e| e.clone())) } fn load_leaves(&self) -> Result { Ok(self.inner.lock().leaves.clone()) } fn load_stagnant_at(&self, timestamp: Timestamp) -> Result, Error> { Ok(self.inner.lock().stagnant_at.get(×tamp).map_or(Vec::new(), |s| s.clone())) } fn load_stagnant_at_up_to( &self, up_to: Timestamp, max_elements: usize, ) -> Result)>, Error> { Ok(self .inner .lock() .stagnant_at .range(..=up_to) .enumerate() .take_while(|(idx, _)| *idx < max_elements) .map(|(_, (t, v))| (*t, v.clone())) .collect()) } fn load_first_block_number(&self) -> Result, Error> { Ok(self.inner.lock().blocks_by_number.range(..).map(|(k, _)| *k).next()) } fn load_blocks_by_number(&self, number: BlockNumber) -> Result, Error> { Ok(self .inner .lock() .blocks_by_number .get(&number) .map_or(Vec::new(), |v| v.clone())) } fn write(&mut self, ops: I) -> Result<(), Error> where I: IntoIterator, { let ops: Vec<_> = ops.into_iter().collect(); // Early return if empty because empty writes shouldn't // trigger wakeups (they happen on an interval) if ops.is_empty() { return Ok(()) } let mut inner = self.inner.lock(); for op in ops { match op { BackendWriteOp::WriteBlockEntry(entry) => { inner.block_entries.insert(entry.block_hash, entry); }, BackendWriteOp::WriteBlocksByNumber(number, hashes) => { inner.blocks_by_number.insert(number, hashes); }, BackendWriteOp::WriteViableLeaves(leaves) => { inner.leaves = leaves; }, BackendWriteOp::WriteStagnantAt(time, hashes) => { inner.stagnant_at.insert(time, hashes); }, BackendWriteOp::DeleteBlocksByNumber(number) => { inner.blocks_by_number.remove(&number); }, BackendWriteOp::DeleteBlockEntry(hash) => { inner.block_entries.remove(&hash); }, BackendWriteOp::DeleteStagnantAt(time) => { inner.stagnant_at.remove(&time); }, } } if let Some(waker) = inner.write_wakers.pop() { let _ = waker.send(()); } Ok(()) } } #[derive(Clone)] pub struct TestClock(Arc); impl TestClock { fn new(initial: u64) -> Self { TestClock(Arc::new(AtomicU64::new(initial))) } fn inc_by(&self, duration: u64) { self.0.fetch_add(duration, AtomicOrdering::Relaxed); } } impl Clock for TestClock { fn timestamp_now(&self) -> Timestamp { self.0.load(AtomicOrdering::Relaxed) } } const TEST_STAGNANT_INTERVAL: Duration = Duration::from_millis(20); type VirtualOverseer = test_helpers::TestSubsystemContextHandle; fn test_harness>( test: impl FnOnce(TestBackend, TestClock, VirtualOverseer) -> T, ) { let pool = TaskExecutor::new(); let (context, virtual_overseer) = test_helpers::make_subsystem_context(pool); let backend = TestBackend::default(); let clock = TestClock::new(0); let subsystem = crate::run( context, backend.clone(), StagnantCheckInterval::new(TEST_STAGNANT_INTERVAL), StagnantCheckMode::CheckAndPrune, Box::new(clock.clone()), ); let test_fut = test(backend, clock, virtual_overseer); let test_and_conclude = async move { let mut virtual_overseer = test_fut.await; virtual_overseer.send(OverseerSignal::Conclude.into()).await; // Ensure no messages are pending when the subsystem shuts down. assert!(virtual_overseer.try_recv().await.is_none()); }; futures::executor::block_on(futures::future::join(subsystem, test_and_conclude)); } // Answer requests from the subsystem about the finalized block. async fn answer_finalized_block_info( overseer: &mut VirtualOverseer, finalized_number: BlockNumber, finalized_hash: Hash, ) { assert_matches!( overseer.recv().await, AllMessages::ChainApi(ChainApiMessage::FinalizedBlockNumber(tx)) => { let _ = tx.send(Ok(finalized_number)); } ); assert_matches!( overseer.recv().await, AllMessages::ChainApi(ChainApiMessage::FinalizedBlockHash(n, tx)) => { assert_eq!(n, finalized_number); let _ = tx.send(Ok(Some(finalized_hash))); } ); } async fn answer_header_request( overseer: &mut VirtualOverseer, maybe_header: impl Into>, ) { assert_matches!( overseer.recv().await, AllMessages::ChainApi(ChainApiMessage::BlockHeader(hash, tx)) => { let maybe_header = maybe_header.into(); assert!(maybe_header.as_ref().map_or(true, |h| h.hash() == hash)); let _ = tx.send(Ok(maybe_header)); } ) } async fn answer_weight_request( overseer: &mut VirtualOverseer, hash: Hash, weight: impl Into>, ) { assert_matches!( overseer.recv().await, AllMessages::ChainApi(ChainApiMessage::BlockWeight(h, tx)) => { assert_eq!(h, hash); let _ = tx.send(Ok(weight.into())); } ) } fn child_header(parent_number: BlockNumber, parent_hash: Hash) -> Header { Header { parent_hash, number: parent_number + 1, state_root: Default::default(), extrinsics_root: Default::default(), digest: Default::default(), } } fn salt_header(header: &mut Header, salt: impl Encode) { header.state_root = BlakeTwo256::hash_of(&salt) } fn add_reversions(header: &mut Header, reversions: impl IntoIterator) { for log in reversions.into_iter().map(ConsensusLog::Revert) { header.digest.logs.push(log.into()) } } // Builds a chain on top of the given base, with one block for each // provided weight. fn construct_chain_on_base( weights: impl IntoIterator, base_number: BlockNumber, base_hash: Hash, mut mutate: impl FnMut(&mut Header), ) -> (Hash, Vec<(Header, BlockWeight)>) { let mut parent_number = base_number; let mut parent_hash = base_hash; let mut chain = Vec::new(); for weight in weights { let mut header = child_header(parent_number, parent_hash); mutate(&mut header); parent_number = header.number; parent_hash = header.hash(); chain.push((header, weight)); } (parent_hash, chain) } // import blocks 1-by-1. If `finalized_base` is supplied, // it will be answered before the first block in `answers. async fn import_blocks_into( virtual_overseer: &mut VirtualOverseer, backend: &TestBackend, mut finalized_base: Option<(BlockNumber, Hash)>, blocks: Vec<(Header, BlockWeight)>, ) { for (header, weight) in blocks { let (_, write_rx) = backend.await_next_write(); let hash = header.hash(); virtual_overseer .send( OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(ActivatedLeaf { hash, number: header.number, status: LeafStatus::Fresh, span: Arc::new(jaeger::Span::Disabled), })) .into(), ) .await; if let Some((f_n, f_h)) = finalized_base.take() { answer_finalized_block_info(virtual_overseer, f_n, f_h).await; } answer_header_request(virtual_overseer, header.clone()).await; answer_weight_request(virtual_overseer, hash, weight).await; write_rx.await.unwrap(); } } async fn import_chains_into_empty( virtual_overseer: &mut VirtualOverseer, backend: &TestBackend, finalized_number: BlockNumber, finalized_hash: Hash, chains: Vec>, ) { for (i, chain) in chains.into_iter().enumerate() { let finalized_base = Some((finalized_number, finalized_hash)).filter(|_| i == 0); import_blocks_into(virtual_overseer, backend, finalized_base, chain).await; } } // Import blocks all at once. This assumes that the ancestor is known/finalized // but none of the other blocks. // import blocks 1-by-1. If `finalized_base` is supplied, // it will be answered before the first block. // // some pre-blocks may need to be supplied to answer ancestry requests // that gather batches beyond the beginning of the new chain. // pre-blocks are those already known by the subsystem, however, // the subsystem has no way of knowin that until requesting ancestry. async fn import_all_blocks_into( virtual_overseer: &mut VirtualOverseer, backend: &TestBackend, finalized_base: Option<(BlockNumber, Hash)>, pre_blocks: Vec
, blocks: Vec<(Header, BlockWeight)>, ) { assert!(blocks.len() > 1, "gap only makes sense if importing multiple blocks"); let head = blocks.last().unwrap().0.clone(); let head_hash = head.hash(); let (_, write_rx) = backend.await_next_write(); virtual_overseer .send( OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(ActivatedLeaf { hash: head_hash, number: head.number, status: LeafStatus::Fresh, span: Arc::new(jaeger::Span::Disabled), })) .into(), ) .await; if let Some((f_n, f_h)) = finalized_base { answer_finalized_block_info(virtual_overseer, f_n, f_h).await; } // Head is always fetched first. answer_header_request(virtual_overseer, head).await; // Answer header and ancestry requests until the parent of head // is imported. { let find_block_header = |expected_hash| { pre_blocks .iter() .cloned() .chain(blocks.iter().map(|(h, _)| h.clone())) .find(|hdr| hdr.hash() == expected_hash) .unwrap() }; let mut behind_head = 0; loop { let nth_ancestor_of_head = |n: usize| { // blocks: [d, e, f, head] // pre: [a, b, c] // // [a, b, c, d, e, f, head] // [6, 5, 4, 3, 2, 1, 0] let new_ancestry_end = blocks.len() - 1; if n > new_ancestry_end { // [6, 5, 4] -> [2, 1, 0] let n_in_pre = n - blocks.len(); let pre_blocks_end = pre_blocks.len() - 1; pre_blocks[pre_blocks_end - n_in_pre].clone() } else { let blocks_end = blocks.len() - 1; blocks[blocks_end - n].0.clone() } }; match virtual_overseer.recv().await { AllMessages::ChainApi(ChainApiMessage::Ancestors { hash: h, k, response_channel: tx, }) => { let prev_response = nth_ancestor_of_head(behind_head); assert_eq!(h, prev_response.hash()); let _ = tx.send(Ok((0..k as usize) .map(|n| n + behind_head + 1) .map(nth_ancestor_of_head) .map(|h| h.hash()) .collect())); for _ in 0..k { assert_matches!( virtual_overseer.recv().await, AllMessages::ChainApi(ChainApiMessage::BlockHeader(h, tx)) => { let header = find_block_header(h); let _ = tx.send(Ok(Some(header))); } ) } behind_head = behind_head + k as usize; }, AllMessages::ChainApi(ChainApiMessage::BlockHeader(h, tx)) => { let header = find_block_header(h); let _ = tx.send(Ok(Some(header))); // Assuming that `determine_new_blocks` uses these // instead of ancestry: 1. behind_head += 1; }, AllMessages::ChainApi(ChainApiMessage::BlockWeight(h, tx)) => { let (_, weight) = blocks.iter().find(|(hdr, _)| hdr.hash() == h).unwrap(); let _ = tx.send(Ok(Some(*weight))); // Last weight has been returned. Time to go. if h == head_hash { break } }, _ => panic!("unexpected message"), } } } write_rx.await.unwrap(); } async fn finalize_block( virtual_overseer: &mut VirtualOverseer, backend: &TestBackend, block_number: BlockNumber, block_hash: Hash, ) { let (_, write_rx) = backend.await_next_write(); virtual_overseer .send(OverseerSignal::BlockFinalized(block_hash, block_number).into()) .await; write_rx.await.unwrap(); } fn extract_info_from_chain( i: usize, chain: &[(Header, BlockWeight)], ) -> (BlockNumber, Hash, BlockWeight) { let &(ref header, weight) = &chain[i]; (header.number, header.hash(), weight) } fn assert_backend_contains<'a>( backend: &TestBackend, headers: impl IntoIterator, ) { for header in headers { let hash = header.hash(); assert!( backend.load_blocks_by_number(header.number).unwrap().contains(&hash), "blocks at {} does not contain {}", header.number, hash, ); assert!(backend.load_block_entry(&hash).unwrap().is_some(), "no entry found for {}", hash); } } fn assert_backend_contains_chains(backend: &TestBackend, chains: Vec>) { for chain in chains { assert_backend_contains(backend, chain.iter().map(|&(ref hdr, _)| hdr)) } } fn assert_leaves(backend: &TestBackend, leaves: Vec) { assert_eq!( backend .load_leaves() .unwrap() .into_hashes_descending() .into_iter() .collect::>(), leaves, ); } async fn assert_leaves_query(virtual_overseer: &mut VirtualOverseer, leaves: Vec) { assert!(!leaves.is_empty(), "empty leaves impossible. answer finalized query"); let (tx, rx) = oneshot::channel(); virtual_overseer .send(FromOrchestra::Communication { msg: ChainSelectionMessage::Leaves(tx) }) .await; assert_eq!(rx.await.unwrap(), leaves); } async fn assert_finalized_leaves_query( virtual_overseer: &mut VirtualOverseer, finalized_number: BlockNumber, finalized_hash: Hash, ) { let (tx, rx) = oneshot::channel(); virtual_overseer .send(FromOrchestra::Communication { msg: ChainSelectionMessage::Leaves(tx) }) .await; answer_finalized_block_info(virtual_overseer, finalized_number, finalized_hash).await; assert_eq!(rx.await.unwrap(), vec![finalized_hash]); } async fn best_leaf_containing( virtual_overseer: &mut VirtualOverseer, required: Hash, ) -> Option { let (tx, rx) = oneshot::channel(); virtual_overseer .send(FromOrchestra::Communication { msg: ChainSelectionMessage::BestLeafContaining(required, tx), }) .await; rx.await.unwrap() } async fn approve_block( virtual_overseer: &mut VirtualOverseer, backend: &TestBackend, approved: Hash, ) { let (_, write_rx) = backend.await_next_write(); virtual_overseer .send(FromOrchestra::Communication { msg: ChainSelectionMessage::Approved(approved) }) .await; write_rx.await.unwrap() } #[test] fn no_op_subsystem_run() { test_harness(|_, _, virtual_overseer| async move { virtual_overseer }); } #[test] fn import_direct_child_of_finalized_on_empty() { test_harness(|backend, _, mut virtual_overseer| async move { let finalized_number = 0; let finalized_hash = Hash::repeat_byte(0); let child = child_header(finalized_number, finalized_hash); let child_hash = child.hash(); let child_weight = 1; let child_number = child.number; import_blocks_into( &mut virtual_overseer, &backend, Some((finalized_number, finalized_hash)), vec![(child.clone(), child_weight)], ) .await; assert_eq!(backend.load_first_block_number().unwrap().unwrap(), child_number); assert_backend_contains(&backend, &[child]); assert_leaves(&backend, vec![child_hash]); assert_leaves_query(&mut virtual_overseer, vec![child_hash]).await; virtual_overseer }) } #[test] fn import_chain_on_finalized_incrementally() { test_harness(|backend, _, mut virtual_overseer| async move { let finalized_number = 0; let finalized_hash = Hash::repeat_byte(0); let (head_hash, chain) = construct_chain_on_base(vec![1, 2, 3, 4, 5], finalized_number, finalized_hash, |_| {}); import_blocks_into( &mut virtual_overseer, &backend, Some((finalized_number, finalized_hash)), chain.clone(), ) .await; assert_eq!(backend.load_first_block_number().unwrap().unwrap(), 1); assert_backend_contains(&backend, chain.iter().map(|&(ref h, _)| h)); assert_leaves(&backend, vec![head_hash]); assert_leaves_query(&mut virtual_overseer, vec![head_hash]).await; virtual_overseer }) } #[test] fn import_two_subtrees_on_finalized() { test_harness(|backend, _, mut virtual_overseer| async move { let finalized_number = 0; let finalized_hash = Hash::repeat_byte(0); let (a_hash, chain_a) = construct_chain_on_base(vec![1], finalized_number, finalized_hash, |_| {}); let (b_hash, chain_b) = construct_chain_on_base(vec![2], finalized_number, finalized_hash, |h| { salt_header(h, b"b") }); import_blocks_into( &mut virtual_overseer, &backend, Some((finalized_number, finalized_hash)), chain_a.clone(), ) .await; import_blocks_into(&mut virtual_overseer, &backend, None, chain_b.clone()).await; assert_eq!(backend.load_first_block_number().unwrap().unwrap(), 1); assert_backend_contains(&backend, chain_a.iter().map(|&(ref h, _)| h)); assert_backend_contains(&backend, chain_b.iter().map(|&(ref h, _)| h)); assert_leaves(&backend, vec![b_hash, a_hash]); assert_leaves_query(&mut virtual_overseer, vec![b_hash, a_hash]).await; virtual_overseer }) } #[test] fn import_two_subtrees_on_nonzero_finalized() { test_harness(|backend, _, mut virtual_overseer| async move { let finalized_number = 100; let finalized_hash = Hash::repeat_byte(0); let (a_hash, chain_a) = construct_chain_on_base(vec![1], finalized_number, finalized_hash, |_| {}); let (b_hash, chain_b) = construct_chain_on_base(vec![2], finalized_number, finalized_hash, |h| { salt_header(h, b"b") }); import_blocks_into( &mut virtual_overseer, &backend, Some((finalized_number, finalized_hash)), chain_a.clone(), ) .await; import_blocks_into(&mut virtual_overseer, &backend, None, chain_b.clone()).await; assert_eq!(backend.load_first_block_number().unwrap().unwrap(), 101); assert_backend_contains(&backend, chain_a.iter().map(|&(ref h, _)| h)); assert_backend_contains(&backend, chain_b.iter().map(|&(ref h, _)| h)); assert_leaves(&backend, vec![b_hash, a_hash]); assert_leaves_query(&mut virtual_overseer, vec![b_hash, a_hash]).await; virtual_overseer }) } #[test] fn leaves_ordered_by_weight_and_then_number() { test_harness(|backend, _, mut virtual_overseer| async move { let finalized_number = 0; let finalized_hash = Hash::repeat_byte(0); // F <- A1 <- A2 <- A3 // A1 <- B2 // F <- C1 <- C2 // // expected_leaves: [(C2, 3), (A3, 2), (B2, 2)] let (a3_hash, chain_a) = construct_chain_on_base(vec![1, 1, 2], finalized_number, finalized_hash, |_| {}); let (_, a1_hash, _) = extract_info_from_chain(0, &chain_a); let (b2_hash, chain_b) = construct_chain_on_base(vec![2], 1, a1_hash, |h| salt_header(h, b"b")); let (c2_hash, chain_c) = construct_chain_on_base(vec![1, 3], finalized_number, finalized_hash, |h| { salt_header(h, b"c") }); import_chains_into_empty( &mut virtual_overseer, &backend, finalized_number, finalized_hash, vec![chain_a.clone(), chain_b.clone(), chain_c.clone()], ) .await; assert_eq!(backend.load_first_block_number().unwrap().unwrap(), 1); assert_backend_contains(&backend, chain_a.iter().map(|&(ref h, _)| h)); assert_backend_contains(&backend, chain_b.iter().map(|&(ref h, _)| h)); assert_backend_contains(&backend, chain_c.iter().map(|&(ref h, _)| h)); assert_leaves(&backend, vec![c2_hash, a3_hash, b2_hash]); assert_leaves_query(&mut virtual_overseer, vec![c2_hash, a3_hash, b2_hash]).await; virtual_overseer }); } #[test] fn subtrees_imported_even_with_gaps() { test_harness(|backend, _, mut virtual_overseer| async move { let finalized_number = 0; let finalized_hash = Hash::repeat_byte(0); // F <- A1 <- A2 <- A3 // A2 <- B3 <- B4 <- B5 let (a3_hash, chain_a) = construct_chain_on_base(vec![1, 2, 3], finalized_number, finalized_hash, |_| {}); let (_, a2_hash, _) = extract_info_from_chain(1, &chain_a); let (b5_hash, chain_b) = construct_chain_on_base(vec![4, 4, 5], 2, a2_hash, |h| salt_header(h, b"b")); import_all_blocks_into( &mut virtual_overseer, &backend, Some((finalized_number, finalized_hash)), Vec::new(), chain_a.clone(), ) .await; import_all_blocks_into( &mut virtual_overseer, &backend, None, vec![chain_a[0].0.clone(), chain_a[1].0.clone()], chain_b.clone(), ) .await; assert_eq!(backend.load_first_block_number().unwrap().unwrap(), 1); assert_backend_contains(&backend, chain_a.iter().map(|&(ref h, _)| h)); assert_backend_contains(&backend, chain_b.iter().map(|&(ref h, _)| h)); assert_leaves(&backend, vec![b5_hash, a3_hash]); assert_leaves_query(&mut virtual_overseer, vec![b5_hash, a3_hash]).await; virtual_overseer }); } #[test] fn reversion_removes_viability_of_chain() { test_harness(|backend, _, mut virtual_overseer| async move { let finalized_number = 0; let finalized_hash = Hash::repeat_byte(0); // F <- A1 <- A2 <- A3. // // A3 reverts A1 let (_a3_hash, chain_a) = construct_chain_on_base(vec![1, 2, 3], finalized_number, finalized_hash, |h| { if h.number == 3 { add_reversions(h, Some(1)) } }); import_blocks_into( &mut virtual_overseer, &backend, Some((finalized_number, finalized_hash)), chain_a.clone(), ) .await; assert_backend_contains(&backend, chain_a.iter().map(|&(ref h, _)| h)); assert_leaves(&backend, vec![]); assert_finalized_leaves_query(&mut virtual_overseer, finalized_number, finalized_hash) .await; virtual_overseer }); } #[test] fn reversion_removes_viability_and_finds_ancestor_as_leaf() { test_harness(|backend, _, mut virtual_overseer| async move { let finalized_number = 0; let finalized_hash = Hash::repeat_byte(0); // F <- A1 <- A2 <- A3. // // A3 reverts A2 let (_a3_hash, chain_a) = construct_chain_on_base(vec![1, 2, 3], finalized_number, finalized_hash, |h| { if h.number == 3 { add_reversions(h, Some(2)) } }); let (_, a1_hash, _) = extract_info_from_chain(0, &chain_a); import_blocks_into( &mut virtual_overseer, &backend, Some((finalized_number, finalized_hash)), chain_a.clone(), ) .await; assert_backend_contains(&backend, chain_a.iter().map(|&(ref h, _)| h)); assert_leaves(&backend, vec![a1_hash]); assert_leaves_query(&mut virtual_overseer, vec![a1_hash]).await; virtual_overseer }); } #[test] fn ancestor_of_unviable_is_not_leaf_if_has_children() { test_harness(|backend, _, mut virtual_overseer| async move { let finalized_number = 0; let finalized_hash = Hash::repeat_byte(0); // F <- A1 <- A2 <- A3. // A1 <- B2 // // A3 reverts A2 let (a2_hash, chain_a) = construct_chain_on_base(vec![1, 2], finalized_number, finalized_hash, |_| {}); let (_, a1_hash, _) = extract_info_from_chain(0, &chain_a); let (_a3_hash, chain_a_ext) = construct_chain_on_base(vec![3], 2, a2_hash, |h| add_reversions(h, Some(2))); let (b2_hash, chain_b) = construct_chain_on_base(vec![1], 1, a1_hash, |h| salt_header(h, b"b")); import_blocks_into( &mut virtual_overseer, &backend, Some((finalized_number, finalized_hash)), chain_a.clone(), ) .await; import_blocks_into(&mut virtual_overseer, &backend, None, chain_b.clone()).await; assert_backend_contains(&backend, chain_a.iter().map(|&(ref h, _)| h)); assert_backend_contains(&backend, chain_b.iter().map(|&(ref h, _)| h)); assert_leaves(&backend, vec![a2_hash, b2_hash]); import_blocks_into(&mut virtual_overseer, &backend, None, chain_a_ext.clone()).await; assert_backend_contains(&backend, chain_a.iter().map(|&(ref h, _)| h)); assert_backend_contains(&backend, chain_a_ext.iter().map(|&(ref h, _)| h)); assert_backend_contains(&backend, chain_b.iter().map(|&(ref h, _)| h)); assert_leaves(&backend, vec![b2_hash]); assert_leaves_query(&mut virtual_overseer, vec![b2_hash]).await; virtual_overseer }); } #[test] fn self_and_future_reversions_are_ignored() { test_harness(|backend, _, mut virtual_overseer| async move { let finalized_number = 0; let finalized_hash = Hash::repeat_byte(0); // F <- A1 <- A2 <- A3. // // A3 reverts itself and future blocks. ignored. let (a3_hash, chain_a) = construct_chain_on_base(vec![1, 2, 3], finalized_number, finalized_hash, |h| { if h.number == 3 { add_reversions(h, vec![3, 4, 100]) } }); import_blocks_into( &mut virtual_overseer, &backend, Some((finalized_number, finalized_hash)), chain_a.clone(), ) .await; assert_backend_contains(&backend, chain_a.iter().map(|&(ref h, _)| h)); assert_leaves(&backend, vec![a3_hash]); assert_leaves_query(&mut virtual_overseer, vec![a3_hash]).await; virtual_overseer }); } #[test] fn revert_finalized_is_ignored() { test_harness(|backend, _, mut virtual_overseer| async move { let finalized_number = 10; let finalized_hash = Hash::repeat_byte(0); // F <- A1 <- A2 <- A3. // // A3 reverts itself and future blocks. ignored. let (a3_hash, chain_a) = construct_chain_on_base(vec![1, 2, 3], finalized_number, finalized_hash, |h| { if h.number == 13 { add_reversions(h, vec![10, 9, 8, 0, 1]) } }); import_blocks_into( &mut virtual_overseer, &backend, Some((finalized_number, finalized_hash)), chain_a.clone(), ) .await; assert_backend_contains(&backend, chain_a.iter().map(|&(ref h, _)| h)); assert_leaves(&backend, vec![a3_hash]); assert_leaves_query(&mut virtual_overseer, vec![a3_hash]).await; virtual_overseer }); } #[test] fn reversion_affects_viability_of_all_subtrees() { test_harness(|backend, _, mut virtual_overseer| async move { let finalized_number = 0; let finalized_hash = Hash::repeat_byte(0); // F <- A1 <- A2 <- A3. // A2 <- B3 <- B4 // // B4 reverts A2. let (a3_hash, chain_a) = construct_chain_on_base(vec![1, 2, 3], finalized_number, finalized_hash, |_| {}); let (_, a1_hash, _) = extract_info_from_chain(0, &chain_a); let (_, a2_hash, _) = extract_info_from_chain(1, &chain_a); let (_b4_hash, chain_b) = construct_chain_on_base(vec![3, 4], 2, a2_hash, |h| { salt_header(h, b"b"); if h.number == 4 { add_reversions(h, Some(2)); } }); import_blocks_into( &mut virtual_overseer, &backend, Some((finalized_number, finalized_hash)), chain_a.clone(), ) .await; assert_leaves(&backend, vec![a3_hash]); import_blocks_into(&mut virtual_overseer, &backend, None, chain_b.clone()).await; assert_backend_contains(&backend, chain_a.iter().map(|&(ref h, _)| h)); assert_backend_contains(&backend, chain_b.iter().map(|&(ref h, _)| h)); assert_leaves(&backend, vec![a1_hash]); assert_leaves_query(&mut virtual_overseer, vec![a1_hash]).await; virtual_overseer }); } #[test] fn finalize_viable_prunes_subtrees() { test_harness(|backend, _, mut virtual_overseer| async move { let finalized_number = 0; let finalized_hash = Hash::repeat_byte(0); // A2 <- X3 // F <- A1 <- A2 <- A3. // A1 <- B2 // F <- C1 <- C2 <- C3 // C2 <- D3 // // Finalize A2. Only A2, A3, and X3 should remain. let (a3_hash, chain_a) = construct_chain_on_base(vec![1, 2, 10], finalized_number, finalized_hash, |h| { salt_header(h, b"a") }); let (_, a1_hash, _) = extract_info_from_chain(0, &chain_a); let (_, a2_hash, _) = extract_info_from_chain(1, &chain_a); let (x3_hash, chain_x) = construct_chain_on_base(vec![3], 2, a2_hash, |h| salt_header(h, b"x")); let (b2_hash, chain_b) = construct_chain_on_base(vec![6], 1, a1_hash, |h| salt_header(h, b"b")); let (c3_hash, chain_c) = construct_chain_on_base(vec![1, 2, 8], finalized_number, finalized_hash, |h| { salt_header(h, b"c") }); let (_, c2_hash, _) = extract_info_from_chain(1, &chain_c); let (d3_hash, chain_d) = construct_chain_on_base(vec![7], 2, c2_hash, |h| salt_header(h, b"d")); let all_chains = vec![ chain_a.clone(), chain_x.clone(), chain_b.clone(), chain_c.clone(), chain_d.clone(), ]; import_chains_into_empty( &mut virtual_overseer, &backend, finalized_number, finalized_hash, all_chains.clone(), ) .await; assert_backend_contains_chains(&backend, all_chains.clone()); assert_leaves(&backend, vec![a3_hash, c3_hash, d3_hash, b2_hash, x3_hash]); // Finalize block A2. Now lots of blocks should go missing. finalize_block(&mut virtual_overseer, &backend, 2, a2_hash).await; // A2 <- A3 // A2 <- X3 backend.assert_contains_only(vec![(3, a3_hash), (3, x3_hash)]); assert_leaves(&backend, vec![a3_hash, x3_hash]); assert_leaves_query(&mut virtual_overseer, vec![a3_hash, x3_hash]).await; assert_eq!(backend.load_first_block_number().unwrap().unwrap(), 3); assert_eq!(backend.load_blocks_by_number(3).unwrap(), vec![a3_hash, x3_hash]); virtual_overseer }); } #[test] fn finalization_does_not_clobber_unviability() { test_harness(|backend, _, mut virtual_overseer| async move { let finalized_number = 0; let finalized_hash = Hash::repeat_byte(0); // F <- A1 <- A2 <- A3 // A3 reverts A2. // Finalize A1. let (a3_hash, chain_a) = construct_chain_on_base(vec![1, 2, 10], finalized_number, finalized_hash, |h| { salt_header(h, b"a"); if h.number == 3 { add_reversions(h, Some(2)); } }); let (_, a1_hash, _) = extract_info_from_chain(0, &chain_a); let (_, a2_hash, _) = extract_info_from_chain(1, &chain_a); import_blocks_into( &mut virtual_overseer, &backend, Some((finalized_number, finalized_hash)), chain_a.clone(), ) .await; finalize_block(&mut virtual_overseer, &backend, 1, a1_hash).await; assert_leaves(&backend, vec![]); assert_finalized_leaves_query(&mut virtual_overseer, 1, a1_hash).await; backend.assert_contains_only(vec![(3, a3_hash), (2, a2_hash)]); virtual_overseer }); } #[test] fn finalization_erases_unviable() { test_harness(|backend, _, mut virtual_overseer| async move { let finalized_number = 0; let finalized_hash = Hash::repeat_byte(0); // F <- A1 <- A2 <- A3 // A1 <- B2 // // A2 reverts A1. // Finalize A1. let (a3_hash, chain_a) = construct_chain_on_base(vec![1, 2, 3], finalized_number, finalized_hash, |h| { salt_header(h, b"a"); if h.number == 2 { add_reversions(h, Some(1)); } }); let (_, a1_hash, _) = extract_info_from_chain(0, &chain_a); let (_, a2_hash, _) = extract_info_from_chain(1, &chain_a); let (b2_hash, chain_b) = construct_chain_on_base(vec![1], 1, a1_hash, |h| salt_header(h, b"b")); import_chains_into_empty( &mut virtual_overseer, &backend, finalized_number, finalized_hash, vec![chain_a.clone(), chain_b.clone()], ) .await; assert_leaves(&backend, vec![]); finalize_block(&mut virtual_overseer, &backend, 1, a1_hash).await; assert_leaves(&backend, vec![a3_hash, b2_hash]); assert_leaves_query(&mut virtual_overseer, vec![a3_hash, b2_hash]).await; backend.assert_contains_only(vec![(3, a3_hash), (2, a2_hash), (2, b2_hash)]); virtual_overseer }); } #[test] fn finalize_erases_unviable_but_keeps_later_unviability() { test_harness(|backend, _, mut virtual_overseer| async move { let finalized_number = 0; let finalized_hash = Hash::repeat_byte(0); // F <- A1 <- A2 <- A3 // A1 <- B2 // // A2 reverts A1. // A3 reverts A2. // Finalize A1. A2 is stil unviable, but B2 is viable. let (a3_hash, chain_a) = construct_chain_on_base(vec![1, 2, 3], finalized_number, finalized_hash, |h| { salt_header(h, b"a"); if h.number == 2 { add_reversions(h, Some(1)); } if h.number == 3 { add_reversions(h, Some(2)); } }); let (_, a1_hash, _) = extract_info_from_chain(0, &chain_a); let (_, a2_hash, _) = extract_info_from_chain(1, &chain_a); let (b2_hash, chain_b) = construct_chain_on_base(vec![1], 1, a1_hash, |h| salt_header(h, b"b")); import_chains_into_empty( &mut virtual_overseer, &backend, finalized_number, finalized_hash, vec![chain_a.clone(), chain_b.clone()], ) .await; assert_leaves(&backend, vec![]); finalize_block(&mut virtual_overseer, &backend, 1, a1_hash).await; assert_leaves(&backend, vec![b2_hash]); assert_leaves_query(&mut virtual_overseer, vec![b2_hash]).await; backend.assert_contains_only(vec![(3, a3_hash), (2, a2_hash), (2, b2_hash)]); virtual_overseer }); } #[test] fn finalize_erases_unviable_from_one_but_not_all_reverts() { test_harness(|backend, _, mut virtual_overseer| async move { let finalized_number = 0; let finalized_hash = Hash::repeat_byte(0); // F <- A1 <- A2 <- A3 // // A3 reverts A2 and A1. // Finalize A1. A2 is stil unviable. let (a3_hash, chain_a) = construct_chain_on_base(vec![1, 2, 3], finalized_number, finalized_hash, |h| { salt_header(h, b"a"); if h.number == 3 { add_reversions(h, Some(1)); add_reversions(h, Some(2)); } }); let (_, a1_hash, _) = extract_info_from_chain(0, &chain_a); let (_, a2_hash, _) = extract_info_from_chain(1, &chain_a); import_chains_into_empty( &mut virtual_overseer, &backend, finalized_number, finalized_hash, vec![chain_a.clone()], ) .await; assert_leaves(&backend, vec![]); finalize_block(&mut virtual_overseer, &backend, 1, a1_hash).await; assert_leaves(&backend, vec![]); assert_finalized_leaves_query(&mut virtual_overseer, 1, a1_hash).await; backend.assert_contains_only(vec![(3, a3_hash), (2, a2_hash)]); virtual_overseer }); } #[test] fn finalize_triggers_viability_search() { test_harness(|backend, _, mut virtual_overseer| async move { let finalized_number = 0; let finalized_hash = Hash::repeat_byte(0); // F <- A1 <- A2 <- A3 // A2 <- B3 // A2 <- C3 // A3 reverts A1. // Finalize A1. A3, B3, and C3 are all viable now. let (a3_hash, chain_a) = construct_chain_on_base(vec![1, 2, 3], finalized_number, finalized_hash, |h| { salt_header(h, b"a"); if h.number == 3 { add_reversions(h, Some(1)); } }); let (_, a1_hash, _) = extract_info_from_chain(0, &chain_a); let (_, a2_hash, _) = extract_info_from_chain(1, &chain_a); let (b3_hash, chain_b) = construct_chain_on_base(vec![4], 2, a2_hash, |h| salt_header(h, b"b")); let (c3_hash, chain_c) = construct_chain_on_base(vec![5], 2, a2_hash, |h| salt_header(h, b"c")); import_chains_into_empty( &mut virtual_overseer, &backend, finalized_number, finalized_hash, vec![chain_a.clone(), chain_b.clone(), chain_c.clone()], ) .await; assert_leaves(&backend, vec![]); finalize_block(&mut virtual_overseer, &backend, 1, a1_hash).await; assert_leaves(&backend, vec![c3_hash, b3_hash, a3_hash]); assert_leaves_query(&mut virtual_overseer, vec![c3_hash, b3_hash, a3_hash]).await; backend.assert_contains_only(vec![(3, a3_hash), (3, b3_hash), (3, c3_hash), (2, a2_hash)]); virtual_overseer }); } #[test] fn best_leaf_none_with_empty_db() { test_harness(|_backend, _, mut virtual_overseer| async move { let required = Hash::repeat_byte(1); let best_leaf = best_leaf_containing(&mut virtual_overseer, required).await; assert!(best_leaf.is_none()); virtual_overseer }) } #[test] fn best_leaf_none_with_no_viable_leaves() { test_harness(|backend, _, mut virtual_overseer| async move { let finalized_number = 0; let finalized_hash = Hash::repeat_byte(0); // F <- A1 <- A2 // // A2 reverts A1. let (a2_hash, chain_a) = construct_chain_on_base(vec![1, 2], finalized_number, finalized_hash, |h| { salt_header(h, b"a"); if h.number == 2 { add_reversions(h, Some(1)); } }); let (_, a1_hash, _) = extract_info_from_chain(0, &chain_a); import_chains_into_empty( &mut virtual_overseer, &backend, finalized_number, finalized_hash, vec![chain_a.clone()], ) .await; let best_leaf = best_leaf_containing(&mut virtual_overseer, a2_hash).await; assert!(best_leaf.is_none()); let best_leaf = best_leaf_containing(&mut virtual_overseer, a1_hash).await; assert!(best_leaf.is_none()); virtual_overseer }) } #[test] fn best_leaf_none_with_unknown_required() { test_harness(|backend, _, mut virtual_overseer| async move { let finalized_number = 0; let finalized_hash = Hash::repeat_byte(0); // F <- A1 <- A2 let (_a2_hash, chain_a) = construct_chain_on_base(vec![1, 2], finalized_number, finalized_hash, |h| { salt_header(h, b"a"); }); let unknown_hash = Hash::repeat_byte(0x69); import_chains_into_empty( &mut virtual_overseer, &backend, finalized_number, finalized_hash, vec![chain_a.clone()], ) .await; let best_leaf = best_leaf_containing(&mut virtual_overseer, unknown_hash).await; assert!(best_leaf.is_none()); virtual_overseer }) } #[test] fn best_leaf_none_with_unviable_required() { test_harness(|backend, _, mut virtual_overseer| async move { let finalized_number = 0; let finalized_hash = Hash::repeat_byte(0); // F <- A1 <- A2 // F <- B1 <- B2 // // A2 reverts A1. let (a2_hash, chain_a) = construct_chain_on_base(vec![1, 2], finalized_number, finalized_hash, |h| { salt_header(h, b"a"); if h.number == 2 { add_reversions(h, Some(1)); } }); let (_, a1_hash, _) = extract_info_from_chain(0, &chain_a); let (_b2_hash, chain_b) = construct_chain_on_base(vec![1, 2], finalized_number, finalized_hash, |h| { salt_header(h, b"b"); }); import_chains_into_empty( &mut virtual_overseer, &backend, finalized_number, finalized_hash, vec![chain_a.clone(), chain_b.clone()], ) .await; let best_leaf = best_leaf_containing(&mut virtual_overseer, a2_hash).await; assert!(best_leaf.is_none()); let best_leaf = best_leaf_containing(&mut virtual_overseer, a1_hash).await; assert!(best_leaf.is_none()); virtual_overseer }) } #[test] fn best_leaf_with_finalized_required() { test_harness(|backend, _, mut virtual_overseer| async move { let finalized_number = 0; let finalized_hash = Hash::repeat_byte(0); // F <- A1 <- A2 // F <- B1 <- B2 // // B2 > A2 let (_a2_hash, chain_a) = construct_chain_on_base(vec![1, 1], finalized_number, finalized_hash, |h| { salt_header(h, b"a"); }); let (b2_hash, chain_b) = construct_chain_on_base(vec![1, 2], finalized_number, finalized_hash, |h| { salt_header(h, b"b"); }); import_chains_into_empty( &mut virtual_overseer, &backend, finalized_number, finalized_hash, vec![chain_a.clone(), chain_b.clone()], ) .await; let best_leaf = best_leaf_containing(&mut virtual_overseer, finalized_hash).await; assert_eq!(best_leaf, Some(b2_hash)); virtual_overseer }) } #[test] fn best_leaf_with_unfinalized_required() { test_harness(|backend, _, mut virtual_overseer| async move { let finalized_number = 0; let finalized_hash = Hash::repeat_byte(0); // F <- A1 <- A2 // F <- B1 <- B2 // // B2 > A2 let (a2_hash, chain_a) = construct_chain_on_base(vec![1, 1], finalized_number, finalized_hash, |h| { salt_header(h, b"a"); }); let (_, a1_hash, _) = extract_info_from_chain(0, &chain_a); let (_b2_hash, chain_b) = construct_chain_on_base(vec![1, 2], finalized_number, finalized_hash, |h| { salt_header(h, b"b"); }); import_chains_into_empty( &mut virtual_overseer, &backend, finalized_number, finalized_hash, vec![chain_a.clone(), chain_b.clone()], ) .await; let best_leaf = best_leaf_containing(&mut virtual_overseer, a1_hash).await; assert_eq!(best_leaf, Some(a2_hash)); virtual_overseer }) } #[test] fn best_leaf_ancestor_of_all_leaves() { test_harness(|backend, _, mut virtual_overseer| async move { let finalized_number = 0; let finalized_hash = Hash::repeat_byte(0); // F <- A1 <- A2 <- A3 // A1 <- B2 <- B3 // B2 <- C3 // // C3 > B3 > A3 let (_a3_hash, chain_a) = construct_chain_on_base(vec![1, 1, 2], finalized_number, finalized_hash, |h| { salt_header(h, b"a"); }); let (_, a1_hash, _) = extract_info_from_chain(0, &chain_a); let (_b3_hash, chain_b) = construct_chain_on_base(vec![2, 3], 1, a1_hash, |h| { salt_header(h, b"b"); }); let (_, b2_hash, _) = extract_info_from_chain(0, &chain_b); let (c3_hash, chain_c) = construct_chain_on_base(vec![4], 2, b2_hash, |h| { salt_header(h, b"c"); }); import_chains_into_empty( &mut virtual_overseer, &backend, finalized_number, finalized_hash, vec![chain_a.clone(), chain_b.clone(), chain_c.clone()], ) .await; let best_leaf = best_leaf_containing(&mut virtual_overseer, a1_hash).await; assert_eq!(best_leaf, Some(c3_hash)); virtual_overseer }) } #[test] fn approve_message_approves_block_entry() { test_harness(|backend, _, mut virtual_overseer| async move { let finalized_number = 0; let finalized_hash = Hash::repeat_byte(0); // F <- A1 <- A2 <- A3 let (a3_hash, chain_a) = construct_chain_on_base(vec![1, 2, 3], finalized_number, finalized_hash, |h| { salt_header(h, b"a"); }); let (_, a1_hash, _) = extract_info_from_chain(0, &chain_a); let (_, a2_hash, _) = extract_info_from_chain(1, &chain_a); import_chains_into_empty( &mut virtual_overseer, &backend, finalized_number, finalized_hash, vec![chain_a.clone()], ) .await; approve_block(&mut virtual_overseer, &backend, a3_hash).await; // a3 is approved, but not a1 or a2. assert_matches!( backend.load_block_entry(&a3_hash).unwrap().unwrap().viability.approval, Approval::Approved ); assert_matches!( backend.load_block_entry(&a2_hash).unwrap().unwrap().viability.approval, Approval::Unapproved ); assert_matches!( backend.load_block_entry(&a1_hash).unwrap().unwrap().viability.approval, Approval::Unapproved ); virtual_overseer }) } #[test] fn approve_nonexistent_has_no_effect() { test_harness(|backend, _, mut virtual_overseer| async move { let finalized_number = 0; let finalized_hash = Hash::repeat_byte(0); // F <- A1 <- A2 <- A3 let (a3_hash, chain_a) = construct_chain_on_base(vec![1, 2, 3], finalized_number, finalized_hash, |h| { salt_header(h, b"a"); }); let (_, a1_hash, _) = extract_info_from_chain(0, &chain_a); let (_, a2_hash, _) = extract_info_from_chain(1, &chain_a); import_chains_into_empty( &mut virtual_overseer, &backend, finalized_number, finalized_hash, vec![chain_a.clone()], ) .await; let nonexistent = Hash::repeat_byte(1); virtual_overseer .send(FromOrchestra::Communication { msg: ChainSelectionMessage::Approved(nonexistent), }) .await; // None are approved. assert_matches!( backend.load_block_entry(&a3_hash).unwrap().unwrap().viability.approval, Approval::Unapproved ); assert_matches!( backend.load_block_entry(&a2_hash).unwrap().unwrap().viability.approval, Approval::Unapproved ); assert_matches!( backend.load_block_entry(&a1_hash).unwrap().unwrap().viability.approval, Approval::Unapproved ); virtual_overseer }) } #[test] fn block_has_correct_stagnant_at() { test_harness(|backend, clock, mut virtual_overseer| async move { let finalized_number = 0; let finalized_hash = Hash::repeat_byte(0); // F <- A1 <- A2 let (a1_hash, chain_a) = construct_chain_on_base(vec![1], finalized_number, finalized_hash, |h| { salt_header(h, b"a"); }); let (a2_hash, chain_a_ext) = construct_chain_on_base(vec![1], 1, a1_hash, |h| { salt_header(h, b"a"); }); import_chains_into_empty( &mut virtual_overseer, &backend, finalized_number, finalized_hash, vec![chain_a.clone()], ) .await; clock.inc_by(1); import_blocks_into(&mut virtual_overseer, &backend, None, chain_a_ext.clone()).await; backend.assert_stagnant_at_state(vec![ (STAGNANT_TIMEOUT, vec![a1_hash]), (STAGNANT_TIMEOUT + 1, vec![a2_hash]), ]); virtual_overseer }) } #[test] fn detects_stagnant() { test_harness(|backend, clock, mut virtual_overseer| async move { let finalized_number = 0; let finalized_hash = Hash::repeat_byte(0); // F <- A1 let (a1_hash, chain_a) = construct_chain_on_base(vec![1], finalized_number, finalized_hash, |h| { salt_header(h, b"a"); }); import_chains_into_empty( &mut virtual_overseer, &backend, finalized_number, finalized_hash, vec![chain_a.clone()], ) .await; { let (_, write_rx) = backend.await_next_write(); clock.inc_by(STAGNANT_TIMEOUT); write_rx.await.unwrap(); } backend.assert_stagnant_at_state(vec![]); assert_matches!( backend.load_block_entry(&a1_hash).unwrap().unwrap().viability.approval, Approval::Stagnant ); assert_leaves(&backend, vec![]); virtual_overseer }) } #[test] fn finalize_stagnant_unlocks_subtree() { test_harness(|backend, clock, mut virtual_overseer| async move { let finalized_number = 0; let finalized_hash = Hash::repeat_byte(0); // F <- A1 <- A2 let (a1_hash, chain_a) = construct_chain_on_base(vec![1], finalized_number, finalized_hash, |h| { salt_header(h, b"a"); }); let (a2_hash, chain_a_ext) = construct_chain_on_base(vec![1], 1, a1_hash, |h| { salt_header(h, b"a"); }); import_chains_into_empty( &mut virtual_overseer, &backend, finalized_number, finalized_hash, vec![chain_a.clone()], ) .await; clock.inc_by(1); import_blocks_into(&mut virtual_overseer, &backend, None, chain_a_ext.clone()).await; { let (_, write_rx) = backend.await_next_write(); clock.inc_by(STAGNANT_TIMEOUT - 1); write_rx.await.unwrap(); } backend.assert_stagnant_at_state(vec![(STAGNANT_TIMEOUT + 1, vec![a2_hash])]); assert_matches!( backend.load_block_entry(&a1_hash).unwrap().unwrap().viability.approval, Approval::Stagnant ); assert_leaves(&backend, vec![]); finalize_block(&mut virtual_overseer, &backend, 1, a1_hash).await; assert_leaves(&backend, vec![a2_hash]); virtual_overseer }) } #[test] fn approval_undoes_stagnant_unlocking_subtree() { test_harness(|backend, clock, mut virtual_overseer| async move { let finalized_number = 0; let finalized_hash = Hash::repeat_byte(0); // F <- A1 <- A2 let (a1_hash, chain_a) = construct_chain_on_base(vec![1], finalized_number, finalized_hash, |h| { salt_header(h, b"a"); }); let (a2_hash, chain_a_ext) = construct_chain_on_base(vec![1], 1, a1_hash, |h| { salt_header(h, b"a"); }); import_chains_into_empty( &mut virtual_overseer, &backend, finalized_number, finalized_hash, vec![chain_a.clone()], ) .await; clock.inc_by(1); import_blocks_into(&mut virtual_overseer, &backend, None, chain_a_ext.clone()).await; { let (_, write_rx) = backend.await_next_write(); clock.inc_by(STAGNANT_TIMEOUT - 1); write_rx.await.unwrap(); } backend.assert_stagnant_at_state(vec![(STAGNANT_TIMEOUT + 1, vec![a2_hash])]); approve_block(&mut virtual_overseer, &backend, a1_hash).await; assert_matches!( backend.load_block_entry(&a1_hash).unwrap().unwrap().viability.approval, Approval::Approved ); assert_leaves(&backend, vec![a2_hash]); virtual_overseer }) } #[test] fn stagnant_preserves_parents_children() { test_harness(|backend, clock, mut virtual_overseer| async move { let finalized_number = 0; let finalized_hash = Hash::repeat_byte(0); // F <- A1 <- A2 // A1 <- B2 let (a2_hash, chain_a) = construct_chain_on_base(vec![1, 2], finalized_number, finalized_hash, |h| { salt_header(h, b"a"); }); let (_, a1_hash, _) = extract_info_from_chain(0, &chain_a); let (b2_hash, chain_b) = construct_chain_on_base(vec![1], 1, a1_hash, |h| { salt_header(h, b"b"); }); import_chains_into_empty( &mut virtual_overseer, &backend, finalized_number, finalized_hash, vec![chain_a.clone(), chain_b.clone()], ) .await; approve_block(&mut virtual_overseer, &backend, a1_hash).await; approve_block(&mut virtual_overseer, &backend, b2_hash).await; assert_leaves(&backend, vec![a2_hash, b2_hash]); { let (_, write_rx) = backend.await_next_write(); clock.inc_by(STAGNANT_TIMEOUT); write_rx.await.unwrap(); } backend.assert_stagnant_at_state(vec![]); assert_leaves(&backend, vec![b2_hash]); virtual_overseer }) } #[test] fn stagnant_makes_childless_parent_leaf() { test_harness(|backend, clock, mut virtual_overseer| async move { let finalized_number = 0; let finalized_hash = Hash::repeat_byte(0); // F <- A1 <- A2 let (a2_hash, chain_a) = construct_chain_on_base(vec![1, 2], finalized_number, finalized_hash, |h| { salt_header(h, b"a"); }); let (_, a1_hash, _) = extract_info_from_chain(0, &chain_a); import_chains_into_empty( &mut virtual_overseer, &backend, finalized_number, finalized_hash, vec![chain_a.clone()], ) .await; approve_block(&mut virtual_overseer, &backend, a1_hash).await; assert_leaves(&backend, vec![a2_hash]); { let (_, write_rx) = backend.await_next_write(); clock.inc_by(STAGNANT_TIMEOUT); write_rx.await.unwrap(); } backend.assert_stagnant_at_state(vec![]); assert_leaves(&backend, vec![a1_hash]); virtual_overseer }) }