mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-01 11:17:56 +00:00
74baed8b39
* crate skeleton and type definitions * add ChainSelectionMessage * add error type * run loop * fix overseer * simplify determine_new_blocks API * write an overlay struct and fetch new blocks * add new function to overlay * more flow * add leaves to overlay and add a strong type around leaves-set * add is_parent_viable * implement block import, ignoring reversions * add stagnant-at to overlay * add stagnant * add revert consensus log * flow for reversions * extract and import block reversions * recursively update viability * remove redundant parameter from WriteBlockEntry * do some removal of viable leaves * address grumbles * refactor * address grumbles * add comment about non-monotonicity * extract backend to submodule * begin the hunt for viable leaves * viability pivots for updating the active leaves * remove LeafSearchFrontier * partially -> explicitly viable and untwist some booleans * extract tree to submodule * implement block finality update * Implement block approval routine * implement stagnant detection * ensure blocks pruned on finality are removed from the active leaves set * write down some planned test cases * floww * leaf loading * implement best_leaf_containing * write down a few more tests to do * remove dependence of tree on header * guide: ChainApiMessage::BlockWeight * node: BlockWeight ChainAPI * fix compile issue * note a few TODOs for the future * fetch block weight using new BlockWeight ChainAPI * implement unimplemented * sort leaves by block number after weight * remove warnings and add more TODOs * create test module * storage for test backend * wrap inner in mutex * add write waker query to test backend * Add OverseerSignal -> FromOverseer conversion * add test harnes * add no-op test * add some more test helpers * the first test * more progress on tests * test two subtrees * determine-new-blocks: cleaner genesis avoidance and tighter ancestry requests * don't make ancestry requests when asking for one block * add a couple more tests * add to AllMessages in guide * remove bad spaces from bridge * compact iterator * test import with gaps * more reversion tests * test finalization pruning subtrees * fixups * test clobbering and fix bug in overlay * exhaustive backend state after finalizaiton tested * more finality tests * leaf tests * test approval * test ChainSelectionMessage::Leaves thoroughly * remove TODO * avoid Ordering::is_ne so CI can build * comment algorithmic complexity * Update node/core/chain-selection/src/lib.rs Co-authored-by: Bernhard Schuster <bernhard@ahoi.io> Co-authored-by: Bernhard Schuster <bernhard@ahoi.io>
1910 lines
45 KiB
Rust
1910 lines
45 KiB
Rust
// 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 <http://www.gnu.org/licenses/>.
|
|
|
|
//! 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::{HashMap, HashSet, BTreeMap};
|
|
use std::sync::Arc;
|
|
|
|
use futures::channel::oneshot;
|
|
use parity_scale_codec::Encode;
|
|
use parking_lot::Mutex;
|
|
use sp_core::testing::TaskExecutor;
|
|
use assert_matches::assert_matches;
|
|
|
|
use polkadot_primitives::v1::{BlakeTwo256, HashT, ConsensusLog};
|
|
use polkadot_subsystem::{jaeger, ActiveLeavesUpdate, ActivatedLeaf, LeafStatus};
|
|
use polkadot_subsystem::messages::AllMessages;
|
|
use polkadot_node_subsystem_test_helpers as test_helpers;
|
|
|
|
#[derive(Default)]
|
|
struct TestBackendInner {
|
|
leaves: LeafEntrySet,
|
|
block_entries: HashMap<Hash, BlockEntry>,
|
|
blocks_by_number: BTreeMap<BlockNumber, Vec<Hash>>,
|
|
stagnant_at: BTreeMap<Timestamp, Vec<Hash>>,
|
|
// earlier wakers at the back.
|
|
write_wakers: Vec<oneshot::Sender<()>>,
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
struct TestBackend {
|
|
inner: Arc<Mutex<TestBackendInner>>,
|
|
}
|
|
|
|
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));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Default for TestBackend {
|
|
fn default() -> Self {
|
|
TestBackend {
|
|
inner: Default::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Backend for TestBackend {
|
|
fn load_block_entry(&self, hash: &Hash) -> Result<Option<BlockEntry>, Error> {
|
|
Ok(self.inner.lock().block_entries.get(hash).map(|e| e.clone()))
|
|
}
|
|
fn load_leaves(&self) -> Result<LeafEntrySet, Error> {
|
|
Ok(self.inner.lock().leaves.clone())
|
|
}
|
|
fn load_stagnant_at(&self, timestamp: Timestamp) -> Result<Vec<Hash>, 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)
|
|
-> Result<Vec<(Timestamp, Vec<Hash>)>, Error>
|
|
{
|
|
Ok(self.inner.lock().stagnant_at.range(..=up_to).map(|(t, v)| (*t, v.clone())).collect())
|
|
}
|
|
fn load_first_block_number(&self) -> Result<Option<BlockNumber>, Error> {
|
|
Ok(self.inner.lock().blocks_by_number.range(..).map(|(k, _)| *k).next())
|
|
}
|
|
fn load_blocks_by_number(&self, number: BlockNumber) -> Result<Vec<Hash>, Error> {
|
|
Ok(self.inner.lock().blocks_by_number.get(&number).map_or(Vec::new(), |v| v.clone()))
|
|
}
|
|
|
|
fn write<I>(&mut self, ops: I) -> Result<(), Error>
|
|
where I: IntoIterator<Item = BackendWriteOp>
|
|
{
|
|
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(())
|
|
}
|
|
}
|
|
|
|
type VirtualOverseer = test_helpers::TestSubsystemContextHandle<ChainSelectionMessage>;
|
|
|
|
fn test_harness<T: Future<Output=VirtualOverseer>>(
|
|
test: impl FnOnce(TestBackend, VirtualOverseer) -> T
|
|
) {
|
|
let pool = TaskExecutor::new();
|
|
let (context, virtual_overseer) = test_helpers::make_subsystem_context(pool);
|
|
|
|
let backend = TestBackend::default();
|
|
let subsystem = crate::run(context, backend.clone());
|
|
|
|
let test_fut = test(backend, 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<Option<Header>>,
|
|
) {
|
|
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<Option<BlockWeight>>,
|
|
) {
|
|
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<Item=BlockNumber>,
|
|
) {
|
|
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<Item = BlockWeight>,
|
|
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<Vec<(Header, BlockWeight)>>,
|
|
) {
|
|
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<Header>,
|
|
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_tx) = backend.await_next_write();
|
|
|
|
virtual_overseer.send(
|
|
OverseerSignal::BlockFinalized(block_hash, block_number).into()
|
|
).await;
|
|
|
|
write_tx.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<Item = &'a Header>,
|
|
) {
|
|
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<Vec<(Header, BlockWeight)>>,
|
|
) {
|
|
for chain in chains {
|
|
assert_backend_contains(
|
|
backend,
|
|
chain.iter().map(|&(ref hdr, _)| hdr)
|
|
)
|
|
}
|
|
}
|
|
|
|
fn assert_leaves(
|
|
backend: &TestBackend,
|
|
leaves: Vec<Hash>,
|
|
) {
|
|
assert_eq!(
|
|
backend.load_leaves().unwrap().into_hashes_descending().into_iter().collect::<Vec<_>>(),
|
|
leaves,
|
|
);
|
|
}
|
|
|
|
async fn assert_leaves_query(
|
|
virtual_overseer: &mut VirtualOverseer,
|
|
leaves: Vec<Hash>,
|
|
) {
|
|
assert!(!leaves.is_empty(), "empty leaves impossible. answer finalized query");
|
|
|
|
let (tx, rx) = oneshot::channel();
|
|
virtual_overseer.send(FromOverseer::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(FromOverseer::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<Hash> {
|
|
let (tx, rx) = oneshot::channel();
|
|
virtual_overseer.send(FromOverseer::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(FromOverseer::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);
|
|
approve_block(&mut virtual_overseer, &backend, nonexistent).await;
|
|
|
|
// a3 is approved, but not a1 or a2.
|
|
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
|
|
})
|
|
}
|