mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-27 03:27:58 +00:00
integrate dispute finality (#3484)
* finality_target adjustments * fn finality_target * partially address review comments * fixins * more rustic if condition * fix tests * fixins * Update node/core/approval-voting/src/lib.rs Co-authored-by: Andronik Ordian <write@reusable.software> * Update node/core/approval-voting/src/lib.rs Co-authored-by: Robert Habermeier <rphmeier@gmail.com> * review comments part one * rename candidates -> block_descriptions * testing outline (incomplete, WIP) * test foo * split RelayChainSelection into RelayChainSelection{,WithFallback}, introduce HeaderProvider{,Provider} * make some stuff public (revert this soon™) * some test improvements * slips of pens * test fixins * add another trait abstraction * pending edge case tests + warnings fixes * more test cases * fin * chore fmt * fix cargo.lock * Undo obsolete changes * // comments * make mod pub(crate) * fix * minimize static bounds * resolve number() as before * fmt * post merge fix * address some nits Co-authored-by: Andronik Ordian <write@reusable.software> Co-authored-by: Robert Habermeier <rphmeier@gmail.com>
This commit is contained in:
committed by
GitHub
parent
bd9b743872
commit
6519ba987c
@@ -0,0 +1,715 @@
|
||||
// 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/>.
|
||||
|
||||
use super::relay_chain_selection::*;
|
||||
use super::*;
|
||||
|
||||
use futures::channel::oneshot::Receiver;
|
||||
use polkadot_node_primitives::approval::{VRFOutput, VRFProof};
|
||||
use polkadot_node_subsystem_test_helpers as test_helpers;
|
||||
use polkadot_node_subsystem_util::TimeoutExt;
|
||||
use polkadot_subsystem::messages::AllMessages;
|
||||
use polkadot_subsystem::messages::BlockDescription;
|
||||
use polkadot_test_client::Sr25519Keyring;
|
||||
use sp_consensus_babe::digests::CompatibleDigestItem;
|
||||
use sp_consensus_babe::digests::{PreDigest, SecondaryVRFPreDigest};
|
||||
use sp_consensus_babe::Transcript;
|
||||
use sp_runtime::testing::*;
|
||||
use sp_runtime::DigestItem;
|
||||
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||
use std::iter::IntoIterator;
|
||||
|
||||
use assert_matches::assert_matches;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use consensus_common::SelectChain;
|
||||
use futures::channel::oneshot;
|
||||
use futures::prelude::*;
|
||||
use polkadot_primitives::v1::{Block, BlockNumber, Hash, Header};
|
||||
use polkadot_subsystem::messages::{
|
||||
ApprovalVotingMessage, ChainSelectionMessage, DisputeCoordinatorMessage, HighestApprovedAncestorBlock,
|
||||
};
|
||||
|
||||
use polkadot_node_subsystem_test_helpers::TestSubsystemSender;
|
||||
use polkadot_overseer::{SubsystemContext, SubsystemSender};
|
||||
|
||||
type VirtualOverseer = test_helpers::TestSubsystemContextHandle<ApprovalVotingMessage>;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl OverseerHandleT for TestSubsystemSender {
|
||||
async fn send_msg<M: Send + Into<AllMessages>>(&mut self, msg: M, _origin: &'static str) {
|
||||
TestSubsystemSender::send_message(self, msg.into()).await;
|
||||
}
|
||||
}
|
||||
|
||||
struct TestHarness {
|
||||
virtual_overseer: VirtualOverseer,
|
||||
case_vars: CaseVars,
|
||||
/// The result of `fn finality_target` will be injected into the
|
||||
/// harness scope via this channel.
|
||||
finality_target_rx: Receiver<Option<Hash>>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct HarnessConfig;
|
||||
|
||||
fn test_harness<T: Future<Output = VirtualOverseer>>(case_vars: CaseVars, test: impl FnOnce(TestHarness) -> T) {
|
||||
let _ = env_logger::builder().is_test(true).filter_level(log::LevelFilter::Trace).try_init();
|
||||
|
||||
let pool = sp_core::testing::TaskExecutor::new();
|
||||
let (mut context, virtual_overseer) = test_helpers::make_subsystem_context(pool);
|
||||
|
||||
let (finality_target_tx, finality_target_rx) = oneshot::channel::<Option<Hash>>();
|
||||
|
||||
let select_relay_chain = SelectRelayChain::<TestChainStorage, TestSubsystemSender>::new(
|
||||
Arc::new(case_vars.chain.clone()),
|
||||
context.sender().clone(),
|
||||
Default::default(),
|
||||
);
|
||||
|
||||
let target_hash = case_vars.target_block.clone();
|
||||
let selection_process = async move {
|
||||
let best = select_relay_chain.finality_target(target_hash, None).await.unwrap();
|
||||
finality_target_tx.send(best).unwrap();
|
||||
()
|
||||
};
|
||||
|
||||
let test_fut = test(TestHarness { virtual_overseer, case_vars, finality_target_rx });
|
||||
|
||||
futures::pin_mut!(test_fut);
|
||||
futures::pin_mut!(selection_process);
|
||||
|
||||
futures::executor::block_on(future::join(
|
||||
async move {
|
||||
let _overseer = test_fut.await;
|
||||
},
|
||||
selection_process,
|
||||
))
|
||||
.1;
|
||||
}
|
||||
|
||||
async fn overseer_recv(overseer: &mut VirtualOverseer) -> AllMessages {
|
||||
let msg = overseer_recv_with_timeout(overseer, TIMEOUT)
|
||||
.await
|
||||
.expect(&format!("{:?} is enough to receive messages.", TIMEOUT));
|
||||
|
||||
tracing::trace!("Received message:\n{:?}", &msg);
|
||||
|
||||
msg
|
||||
}
|
||||
async fn overseer_recv_with_timeout(overseer: &mut VirtualOverseer, timeout: Duration) -> Option<AllMessages> {
|
||||
tracing::trace!("Waiting for message...");
|
||||
overseer.recv().timeout(timeout).await
|
||||
}
|
||||
|
||||
const TIMEOUT: Duration = Duration::from_millis(2000);
|
||||
|
||||
// used for generating assignments where the validity of the VRF doesn't matter.
|
||||
fn garbage_vrf() -> (VRFOutput, VRFProof) {
|
||||
let key = Sr25519Keyring::Alice.pair();
|
||||
let key = key.as_ref();
|
||||
|
||||
let (o, p, _) = key.vrf_sign(Transcript::new(b"test-garbage"));
|
||||
(VRFOutput(o.to_output()), VRFProof(p))
|
||||
}
|
||||
|
||||
// nice to have
|
||||
const A1: Hash = Hash::repeat_byte(0xA1);
|
||||
const A2: Hash = Hash::repeat_byte(0xA2);
|
||||
const A3: Hash = Hash::repeat_byte(0xA3);
|
||||
const A5: Hash = Hash::repeat_byte(0xA5);
|
||||
|
||||
const B2: Hash = Hash::repeat_byte(0xB2);
|
||||
const B3: Hash = Hash::repeat_byte(0xB3);
|
||||
|
||||
/// Representation of a local representation
|
||||
/// to extract information for finalization target
|
||||
/// extraction.
|
||||
#[derive(Debug, Default, Clone)]
|
||||
struct TestChainStorage {
|
||||
blocks_by_hash: HashMap<Hash, Header>,
|
||||
blocks_at_height: BTreeMap<u32, Vec<Hash>>,
|
||||
disputed_blocks: HashSet<Hash>,
|
||||
approved_blocks: HashSet<Hash>,
|
||||
heads: HashSet<Hash>,
|
||||
}
|
||||
|
||||
impl TestChainStorage {
|
||||
/// Fill the [`HighestApprovedAncestor`] structure with mostly
|
||||
/// correct data.
|
||||
pub fn highest_approved_ancestors(
|
||||
&self,
|
||||
minimum_block_number: BlockNumber,
|
||||
leaf: Hash,
|
||||
) -> Option<HighestApprovedAncestorBlock> {
|
||||
let hash = leaf;
|
||||
let number = self.blocks_by_hash.get(&leaf)?.number;
|
||||
|
||||
let mut descriptions = Vec::new();
|
||||
let mut block_hash = leaf;
|
||||
let mut highest_approved_ancestor = None;
|
||||
|
||||
while let Some(block) = self.blocks_by_hash.get(&block_hash) {
|
||||
if minimum_block_number >= block.number {
|
||||
break;
|
||||
}
|
||||
descriptions.push(BlockDescription {
|
||||
session: 1 as _, // dummy, not checked
|
||||
block_hash,
|
||||
candidates: vec![], // not relevant for any test cases
|
||||
});
|
||||
if !self.approved_blocks.contains(&block_hash) {
|
||||
highest_approved_ancestor = None;
|
||||
descriptions.clear();
|
||||
} else if highest_approved_ancestor.is_none() {
|
||||
descriptions.clear();
|
||||
highest_approved_ancestor = Some(block_hash);
|
||||
}
|
||||
let next = block.parent_hash();
|
||||
if &leaf != next {
|
||||
block_hash = *next;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if highest_approved_ancestor.is_none() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(HighestApprovedAncestorBlock {
|
||||
hash,
|
||||
number,
|
||||
descriptions: descriptions.into_iter().rev().collect(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Traverse backwards from leave down to block number.
|
||||
fn undisputed_chain(&self, base_blocknumber: BlockNumber, highest_approved_block_hash: Hash) -> Option<Hash> {
|
||||
if self.disputed_blocks.is_empty() {
|
||||
return Some(highest_approved_block_hash);
|
||||
}
|
||||
|
||||
let mut undisputed_chain = Some(highest_approved_block_hash);
|
||||
let mut block_hash = highest_approved_block_hash;
|
||||
while let Some(block) = self.blocks_by_hash.get(&block_hash) {
|
||||
block_hash = block.hash();
|
||||
if ChainBuilder::GENESIS_HASH == block_hash {
|
||||
return None;
|
||||
}
|
||||
let next = block.parent_hash();
|
||||
if self.disputed_blocks.contains(&block_hash) {
|
||||
undisputed_chain = Some(*next);
|
||||
}
|
||||
if block.number() == &base_blocknumber {
|
||||
return None;
|
||||
}
|
||||
block_hash = *next;
|
||||
}
|
||||
undisputed_chain
|
||||
}
|
||||
}
|
||||
|
||||
impl HeaderProvider<Block> for TestChainStorage {
|
||||
fn header(&self, hash: Hash) -> sp_blockchain::Result<Option<Header>> {
|
||||
Ok(self.blocks_by_hash.get(&hash).cloned())
|
||||
}
|
||||
fn number(&self, hash: Hash) -> sp_blockchain::Result<Option<BlockNumber>> {
|
||||
self.header(hash).map(|opt| opt.map(|h| h.number))
|
||||
}
|
||||
}
|
||||
|
||||
impl HeaderProviderProvider<Block> for TestChainStorage {
|
||||
type Provider = Self;
|
||||
fn header_provider(&self) -> &Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct ChainBuilder(pub TestChainStorage);
|
||||
|
||||
impl ChainBuilder {
|
||||
const GENESIS_HASH: Hash = Hash::repeat_byte(0xff);
|
||||
const GENESIS_PARENT_HASH: Hash = Hash::repeat_byte(0x00);
|
||||
|
||||
pub fn new() -> Self {
|
||||
let mut builder = Self(TestChainStorage::default());
|
||||
let _ = builder.add_block_inner(Self::GENESIS_HASH, Self::GENESIS_PARENT_HASH, 0);
|
||||
builder
|
||||
}
|
||||
|
||||
pub fn add_block<'a>(&'a mut self, hash: Hash, parent_hash: Hash, number: u32) -> &'a mut Self {
|
||||
assert!(number != 0, "cannot add duplicate genesis block");
|
||||
assert!(hash != Self::GENESIS_HASH, "cannot add block with genesis hash");
|
||||
assert!(parent_hash != Self::GENESIS_PARENT_HASH, "cannot add block with genesis parent hash");
|
||||
assert!(self.0.blocks_by_hash.len() < u8::MAX.into());
|
||||
self.add_block_inner(hash, parent_hash, number)
|
||||
}
|
||||
|
||||
fn add_block_inner<'a>(&'a mut self, hash: Hash, parent_hash: Hash, number: u32) -> &'a mut Self {
|
||||
let header = ChainBuilder::make_header(parent_hash, number);
|
||||
assert!(self.0.blocks_by_hash.insert(hash, header).is_none(), "block with hash {:?} already exists", hash,);
|
||||
self.0.blocks_at_height.entry(number).or_insert_with(Vec::new).push(hash);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn fast_forward_approved(
|
||||
&mut self,
|
||||
branch_tag: u8,
|
||||
parent: Hash,
|
||||
block_number: BlockNumber,
|
||||
) -> Hash {
|
||||
let block = self.fast_forward(branch_tag, parent, block_number);
|
||||
let _ = self.0.approved_blocks.insert(block);
|
||||
block
|
||||
}
|
||||
|
||||
/// Add a relay chain block that contains a disputed parachain block.
|
||||
/// For simplicity this is not modeled explicitly.
|
||||
pub fn fast_forward_disputed(
|
||||
&mut self,
|
||||
branch_tag: u8,
|
||||
parent: Hash,
|
||||
block_number: BlockNumber,
|
||||
) -> Hash {
|
||||
let block = self.fast_forward_approved(branch_tag, parent, block_number);
|
||||
let _ = self.0.disputed_blocks.insert(block);
|
||||
block
|
||||
}
|
||||
|
||||
pub fn fast_forward(&mut self, branch_tag: u8, parent: Hash, block_number: BlockNumber) -> Hash {
|
||||
let hash = Hash::repeat_byte((block_number as u8 | branch_tag) as u8);
|
||||
let _ = self.add_block(hash, parent, block_number);
|
||||
hash
|
||||
}
|
||||
|
||||
pub fn set_heads(&mut self, heads: impl IntoIterator<Item = Hash>) {
|
||||
self.0.heads = heads.into_iter().collect();
|
||||
}
|
||||
|
||||
pub fn init(self) -> TestChainStorage {
|
||||
self.0
|
||||
}
|
||||
|
||||
fn make_header(parent_hash: Hash, number: u32) -> Header {
|
||||
let digest = {
|
||||
let mut digest = Digest::default();
|
||||
let (vrf_output, vrf_proof) = garbage_vrf();
|
||||
digest.push(DigestItem::babe_pre_digest(PreDigest::SecondaryVRF(SecondaryVRFPreDigest {
|
||||
authority_index: 0,
|
||||
slot: 1.into(), // slot, unused
|
||||
vrf_output,
|
||||
vrf_proof,
|
||||
})));
|
||||
digest
|
||||
};
|
||||
|
||||
Header { digest, extrinsics_root: Default::default(), number, state_root: Default::default(), parent_hash }
|
||||
}
|
||||
}
|
||||
|
||||
/// Generalized sequence of the test, based on
|
||||
/// the messages being sent out by the `fn finality_target`
|
||||
/// Depends on a particular `target_hash`
|
||||
/// that is passed to `finality_target` block number.
|
||||
async fn test_skeleton(
|
||||
chain: &TestChainStorage,
|
||||
virtual_overseer: &mut VirtualOverseer,
|
||||
target_block_hash: Hash,
|
||||
best_chain_containing_block: Option<Hash>,
|
||||
highest_approved_ancestor_block: Option<HighestApprovedAncestorBlock>,
|
||||
undisputed_chain: Option<Hash>,
|
||||
) {
|
||||
let undisputed_chain = undisputed_chain.map(|x| (chain.number(x).unwrap().unwrap(), x));
|
||||
|
||||
tracing::trace!("best leaf response: {:?}", undisputed_chain);
|
||||
assert_matches!(
|
||||
overseer_recv(
|
||||
virtual_overseer
|
||||
).await,
|
||||
AllMessages::ChainSelection(ChainSelectionMessage::BestLeafContaining(
|
||||
target_hash,
|
||||
tx,
|
||||
))
|
||||
=> {
|
||||
assert_eq!(target_block_hash, target_hash, "TestIntegrity: target hashes always match. qed");
|
||||
tx.send(best_chain_containing_block.clone()).unwrap();
|
||||
}
|
||||
);
|
||||
|
||||
if best_chain_containing_block.is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
tracing::trace!("approved ancestor response: {:?}", undisputed_chain);
|
||||
assert_matches!(
|
||||
overseer_recv(
|
||||
virtual_overseer
|
||||
).await,
|
||||
AllMessages::ApprovalVoting(ApprovalVotingMessage::ApprovedAncestor(_block_hash, _block_number, tx))
|
||||
=> {
|
||||
tx.send(highest_approved_ancestor_block.clone()).unwrap();
|
||||
}
|
||||
);
|
||||
|
||||
tracing::trace!("determine undisputed chain response: {:?}", undisputed_chain);
|
||||
assert_matches!(
|
||||
overseer_recv(
|
||||
virtual_overseer
|
||||
).await,
|
||||
AllMessages::DisputeCoordinator(
|
||||
DisputeCoordinatorMessage::DetermineUndisputedChain {
|
||||
base_number: _,
|
||||
block_descriptions: _,
|
||||
tx,
|
||||
}
|
||||
) => {
|
||||
tx.send(undisputed_chain).unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
/// Straight forward test case, where the test is not
|
||||
/// for integrity, but for different block relation structures.
|
||||
fn run_specialized_test_w_harness<F: FnOnce() -> CaseVars>(case_var_provider: F) {
|
||||
test_harness(case_var_provider(), |test_harness| async move {
|
||||
let TestHarness {
|
||||
mut virtual_overseer,
|
||||
finality_target_rx,
|
||||
case_vars:
|
||||
CaseVars {
|
||||
chain,
|
||||
target_block,
|
||||
best_chain_containing_block,
|
||||
highest_approved_ancestor_block,
|
||||
undisputed_chain,
|
||||
expected_finality_target_result,
|
||||
},
|
||||
..
|
||||
} = test_harness;
|
||||
|
||||
// Verify test integrity: the provided highest approved
|
||||
// ancestor must match the chain derived one.
|
||||
let highest_approved_ancestor_w_desc = best_chain_containing_block.and_then(|best_chain_containing_block| {
|
||||
let min_blocknumber =
|
||||
chain.blocks_by_hash.get(&best_chain_containing_block).map(|x| x.number).unwrap_or_default();
|
||||
let highest_approved_ancestor_w_desc =
|
||||
chain.highest_approved_ancestors(min_blocknumber, best_chain_containing_block);
|
||||
if let (Some(highest_approved_ancestor_w_desc), Some(highest_approved_ancestor_block)) =
|
||||
(&highest_approved_ancestor_w_desc, highest_approved_ancestor_block)
|
||||
{
|
||||
assert_eq!(
|
||||
highest_approved_ancestor_block, highest_approved_ancestor_w_desc.hash,
|
||||
"TestCaseIntegrity: Provided and expected approved ancestor hash mismatch: {:?} vs {:?}",
|
||||
highest_approved_ancestor_block, highest_approved_ancestor_w_desc.hash,
|
||||
);
|
||||
}
|
||||
highest_approved_ancestor_w_desc
|
||||
});
|
||||
if let Some(haacwd) = &highest_approved_ancestor_w_desc {
|
||||
let expected = chain.undisputed_chain(haacwd.number, haacwd.hash);
|
||||
assert_eq!(
|
||||
expected, undisputed_chain,
|
||||
"TestCaseIntegrity: Provided and anticipated undisputed chain mismatch: {:?} vs {:?}",
|
||||
undisputed_chain, expected,
|
||||
)
|
||||
}
|
||||
|
||||
test_skeleton(
|
||||
&chain,
|
||||
&mut virtual_overseer,
|
||||
target_block,
|
||||
best_chain_containing_block,
|
||||
highest_approved_ancestor_w_desc,
|
||||
undisputed_chain,
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_matches!(finality_target_rx.await,
|
||||
Ok(
|
||||
finality_target_val,
|
||||
) => assert_eq!(expected_finality_target_result, finality_target_val));
|
||||
|
||||
virtual_overseer
|
||||
});
|
||||
}
|
||||
|
||||
/// All variables relevant for a test case.
|
||||
#[derive(Clone, Debug)]
|
||||
struct CaseVars {
|
||||
/// Chain test _case_ definition.
|
||||
chain: TestChainStorage,
|
||||
|
||||
/// The target block to be finalized.
|
||||
target_block: Hash,
|
||||
|
||||
/// Response to the `target_block` request, must be a chain-head.
|
||||
/// `None` if no such chain exists.
|
||||
best_chain_containing_block: Option<Hash>,
|
||||
|
||||
/// Resulting best estimate, before considering
|
||||
/// the disputed state of blocks.
|
||||
highest_approved_ancestor_block: Option<Hash>,
|
||||
|
||||
/// Equal to the previous, unless there are disputes.
|
||||
/// The backtracked version of this must _never_
|
||||
/// contain a disputed block.
|
||||
undisputed_chain: Option<Hash>,
|
||||
|
||||
/// The returned value by `fn finality_target`.
|
||||
expected_finality_target_result: Option<Hash>,
|
||||
}
|
||||
|
||||
/// ```raw
|
||||
/// genesis -- 0xA1 --- 0xA2 --- 0xA3 --- 0xA4(!avail) --- 0xA5(!avail)
|
||||
/// \
|
||||
/// `- 0xB2
|
||||
/// ```
|
||||
fn chain_0() -> CaseVars {
|
||||
let head: Hash = ChainBuilder::GENESIS_HASH;
|
||||
let mut builder = ChainBuilder::new();
|
||||
|
||||
let a1 = builder.fast_forward_approved(0xA0, head, 1);
|
||||
let a2 = builder.fast_forward_approved(0xA0, a1, 2);
|
||||
let a3 = builder.fast_forward_approved(0xA0, a2, 3);
|
||||
let a4 = builder.fast_forward(0xA0, a3, 4);
|
||||
let a5 = builder.fast_forward(0xA0, a4, 5);
|
||||
|
||||
let b1 = builder.fast_forward_approved(0xB0, a1, 2);
|
||||
let b2 = builder.fast_forward_approved(0xB0, b1, 3);
|
||||
let b3 = builder.fast_forward_approved(0xB0, b2, 4);
|
||||
|
||||
builder.set_heads(vec![a5, b3]);
|
||||
|
||||
CaseVars {
|
||||
chain: builder.init(),
|
||||
target_block: A1,
|
||||
best_chain_containing_block: Some(A5),
|
||||
highest_approved_ancestor_block: Some(A3),
|
||||
undisputed_chain: Some(A2),
|
||||
expected_finality_target_result: Some(A2),
|
||||
}
|
||||
}
|
||||
|
||||
/// ```raw
|
||||
/// genesis -- 0xA1 --- 0xA2(disputed) --- 0xA3
|
||||
/// \
|
||||
/// `- 0xB2 --- 0xB3(!available)
|
||||
/// ```
|
||||
fn chain_1() -> CaseVars {
|
||||
let head: Hash = ChainBuilder::GENESIS_HASH;
|
||||
let mut builder = ChainBuilder::new();
|
||||
|
||||
let a1 = builder.fast_forward_approved(0xA0, head, 1);
|
||||
let a2 = builder.fast_forward_disputed(0xA0, a1, 2);
|
||||
let a3 = builder.fast_forward_approved(0xA0, a2, 3);
|
||||
|
||||
let b2 = builder.fast_forward_approved(0xB0, a1, 2);
|
||||
let b3 = builder.fast_forward_approved(0xB0, b2, 3);
|
||||
|
||||
builder.set_heads(vec![a3, b3]);
|
||||
|
||||
CaseVars {
|
||||
chain: builder.init(),
|
||||
target_block: A1,
|
||||
best_chain_containing_block: Some(B3),
|
||||
highest_approved_ancestor_block: Some(B2),
|
||||
undisputed_chain: Some(B2),
|
||||
expected_finality_target_result: Some(B2),
|
||||
}
|
||||
}
|
||||
|
||||
/// ```raw
|
||||
/// genesis -- 0xA1 --- 0xA2(disputed) --- 0xA3
|
||||
/// \
|
||||
/// `- 0xB2 --- 0xB3
|
||||
/// ```
|
||||
fn chain_2() -> CaseVars {
|
||||
let head: Hash = ChainBuilder::GENESIS_HASH;
|
||||
let mut builder = ChainBuilder::new();
|
||||
|
||||
let a1 = builder.fast_forward_approved(0xA0, head, 1);
|
||||
let a2 = builder.fast_forward_disputed(0xA0, a1, 2);
|
||||
let a3 = builder.fast_forward_approved(0xA0, a2, 3);
|
||||
|
||||
let b2 = builder.fast_forward_approved(0xB0, a1, 2);
|
||||
let b3 = builder.fast_forward_approved(0xB0, b2, 3);
|
||||
|
||||
builder.set_heads(vec![a3, b3]);
|
||||
|
||||
CaseVars {
|
||||
chain: builder.init(),
|
||||
target_block: A3,
|
||||
best_chain_containing_block: Some(A3),
|
||||
highest_approved_ancestor_block: Some(A3),
|
||||
undisputed_chain: Some(A1),
|
||||
expected_finality_target_result: Some(A1),
|
||||
}
|
||||
}
|
||||
|
||||
/// ```raw
|
||||
/// genesis -- 0xA1 --- 0xA2 --- 0xA3(disputed)
|
||||
/// \
|
||||
/// `- 0xB2 --- 0xB3
|
||||
/// ```
|
||||
fn chain_3() -> CaseVars {
|
||||
let head: Hash = ChainBuilder::GENESIS_HASH;
|
||||
let mut builder = ChainBuilder::new();
|
||||
|
||||
let a1 = builder.fast_forward_approved(0xA0, head, 1);
|
||||
let a2 = builder.fast_forward_approved(0xA0, a1, 2);
|
||||
let a3 = builder.fast_forward_disputed(0xA0, a2, 3);
|
||||
|
||||
let b2 = builder.fast_forward_approved(0xB0, a1, 2);
|
||||
let b3 = builder.fast_forward_approved(0xB0, b2, 3);
|
||||
|
||||
builder.set_heads(vec![a3, b3]);
|
||||
|
||||
CaseVars {
|
||||
chain: builder.init(),
|
||||
target_block: A2,
|
||||
best_chain_containing_block: Some(A3),
|
||||
highest_approved_ancestor_block: Some(A3),
|
||||
undisputed_chain: Some(A2),
|
||||
expected_finality_target_result: Some(A2),
|
||||
}
|
||||
}
|
||||
|
||||
/// ```raw
|
||||
/// genesis -- 0xA1 --- 0xA2 --- 0xA3(disputed)
|
||||
/// \
|
||||
/// `- 0xB2 --- 0xB3
|
||||
///
|
||||
/// ? --- NEX(does_not_exist)
|
||||
/// ```
|
||||
fn chain_4() -> CaseVars {
|
||||
let head: Hash = ChainBuilder::GENESIS_HASH;
|
||||
let mut builder = ChainBuilder::new();
|
||||
|
||||
let a1 = builder.fast_forward_approved(0xA0, head, 1);
|
||||
let a2 = builder.fast_forward_approved(0xA0, a1, 2);
|
||||
let a3 = builder.fast_forward_disputed(0xA0, a2, 3);
|
||||
|
||||
let b2 = builder.fast_forward_approved(0xB0, a1, 2);
|
||||
let b3 = builder.fast_forward_approved(0xB0, b2, 3);
|
||||
|
||||
builder.set_heads(vec![a3, b3]);
|
||||
|
||||
let does_not_exist = Hash::repeat_byte(0xCC);
|
||||
CaseVars {
|
||||
chain: builder.init(),
|
||||
target_block: does_not_exist,
|
||||
best_chain_containing_block: None,
|
||||
highest_approved_ancestor_block: None,
|
||||
undisputed_chain: None,
|
||||
expected_finality_target_result: Some(does_not_exist),
|
||||
}
|
||||
}
|
||||
|
||||
/// ```raw
|
||||
/// genesis -- 0xA1 --- 0xA2
|
||||
/// ```
|
||||
fn chain_5() -> CaseVars {
|
||||
let head: Hash = ChainBuilder::GENESIS_HASH;
|
||||
let mut builder = ChainBuilder::new();
|
||||
|
||||
let a1 = builder.fast_forward_approved(0xA0, head, 1);
|
||||
let a2 = builder.fast_forward_approved(0xA0, a1, 2);
|
||||
|
||||
builder.set_heads(vec![a2]);
|
||||
|
||||
CaseVars {
|
||||
chain: builder.init(),
|
||||
target_block: A2,
|
||||
best_chain_containing_block: Some(A2),
|
||||
highest_approved_ancestor_block: Some(A2),
|
||||
undisputed_chain: Some(A2),
|
||||
expected_finality_target_result: Some(A2),
|
||||
}
|
||||
}
|
||||
|
||||
/// ```raw
|
||||
/// genesis -- 0xB2 -- 0xD2 -- .. -- 0xD8 -- 0xC8(unapproved) -- .. -- 0xCF(unapproved)
|
||||
/// ```
|
||||
fn chain_6() -> CaseVars {
|
||||
let head: Hash = ChainBuilder::GENESIS_HASH;
|
||||
let mut builder = ChainBuilder::new();
|
||||
|
||||
let b1 = builder.fast_forward_approved(0xB0, head, 1);
|
||||
|
||||
let mut previous = b1;
|
||||
let mut approved = b1;
|
||||
for block_number in 2_u32..16 {
|
||||
if block_number <= 8 {
|
||||
previous = builder.fast_forward_approved(0xD0, previous, block_number as _);
|
||||
approved = previous;
|
||||
} else {
|
||||
previous = builder.fast_forward(0xA0, previous, block_number as _);
|
||||
}
|
||||
}
|
||||
let leaf = previous;
|
||||
|
||||
builder.set_heads(vec![leaf]);
|
||||
|
||||
let chain = builder.init();
|
||||
|
||||
tracing::trace!(highest_approved = ?chain.highest_approved_ancestors(1, leaf));
|
||||
tracing::trace!(undisputed = ?chain.undisputed_chain(1, approved));
|
||||
CaseVars {
|
||||
chain,
|
||||
target_block: b1,
|
||||
best_chain_containing_block: Some(leaf),
|
||||
highest_approved_ancestor_block: Some(approved),
|
||||
undisputed_chain: Some(approved),
|
||||
expected_finality_target_result: Some(approved),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn chain_sel_0() {
|
||||
run_specialized_test_w_harness(chain_0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn chain_sel_1() {
|
||||
run_specialized_test_w_harness(chain_1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn chain_sel_2() {
|
||||
run_specialized_test_w_harness(chain_2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn chain_sel_3() {
|
||||
run_specialized_test_w_harness(chain_3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn chain_sel_4_target_hash_value_not_contained() {
|
||||
run_specialized_test_w_harness(chain_4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn chain_sel_5_best_is_target_hash() {
|
||||
run_specialized_test_w_harness(chain_5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn chain_sel_6_approval_lag() {
|
||||
run_specialized_test_w_harness(chain_6);
|
||||
}
|
||||
Reference in New Issue
Block a user