mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-27 02:17: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
@@ -18,23 +18,24 @@
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use sp_runtime::traits::{Block as BlockT, NumberFor};
|
||||
use sp_runtime::generic::BlockId;
|
||||
use sp_runtime::traits::Header as _;
|
||||
use sp_runtime::traits::{Block as BlockT, NumberFor};
|
||||
|
||||
use crate::HeaderProvider;
|
||||
|
||||
#[cfg(feature = "full-node")]
|
||||
use polkadot_primitives::v1::Hash;
|
||||
|
||||
/// Returns the block hash of the block at the given `target_number` by walking
|
||||
/// backwards from the given `current_header`.
|
||||
pub(super) fn walk_backwards_to_target_block<Block, B>(
|
||||
backend: &B,
|
||||
pub(super) fn walk_backwards_to_target_block<Block, HP>(
|
||||
backend: &HP,
|
||||
target_number: NumberFor<Block>,
|
||||
current_header: &Block::Header,
|
||||
) -> Result<(Block::Hash, NumberFor<Block>), sp_blockchain::Error>
|
||||
where
|
||||
Block: BlockT,
|
||||
B: sp_blockchain::HeaderBackend<Block>,
|
||||
HP: HeaderProvider<Block>,
|
||||
{
|
||||
let mut target_hash = current_header.hash();
|
||||
let mut target_header = current_header.clone();
|
||||
@@ -54,7 +55,7 @@ where
|
||||
|
||||
target_hash = *target_header.parent_hash();
|
||||
target_header = backend
|
||||
.header(BlockId::Hash(target_hash))?
|
||||
.header(target_hash)?
|
||||
.expect("Header known to exist due to the existence of one of its descendants; qed");
|
||||
}
|
||||
}
|
||||
@@ -69,7 +70,7 @@ pub(crate) struct PauseAfterBlockFor<N>(pub(crate) N, pub(crate) N);
|
||||
impl<Block, B> grandpa::VotingRule<Block, B> for PauseAfterBlockFor<NumberFor<Block>>
|
||||
where
|
||||
Block: BlockT,
|
||||
B: sp_blockchain::HeaderBackend<Block>,
|
||||
B: sp_blockchain::HeaderBackend<Block> + 'static,
|
||||
{
|
||||
fn restrict_vote(
|
||||
&self,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2017-2020 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2017-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot is free software: you can redistribute it and/or modify
|
||||
@@ -34,6 +34,9 @@ pub use self::overseer::{
|
||||
create_default_subsystems,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
#[cfg(feature = "full-node")]
|
||||
use {
|
||||
tracing::info,
|
||||
@@ -51,7 +54,6 @@ use {
|
||||
sp_trie::PrefixedMemoryDB,
|
||||
sc_client_api::ExecutorProvider,
|
||||
grandpa::{self, FinalityProofProvider as GrandpaFinalityProofProvider},
|
||||
sp_runtime::traits::Header as HeaderT,
|
||||
};
|
||||
|
||||
#[cfg(feature = "full-node")]
|
||||
@@ -103,7 +105,18 @@ pub use service::{
|
||||
};
|
||||
pub use service::config::{DatabaseConfig, PrometheusConfig};
|
||||
pub use sp_api::{ApiRef, Core as CoreApi, ConstructRuntimeApi, ProvideRuntimeApi, StateBackend};
|
||||
pub use sp_runtime::traits::{DigestFor, HashFor, NumberFor, Block as BlockT, self as runtime_traits, BlakeTwo256};
|
||||
pub use sp_runtime::{
|
||||
generic,
|
||||
traits::{
|
||||
DigestFor,
|
||||
HashFor,
|
||||
NumberFor,
|
||||
Block as BlockT,
|
||||
Header as HeaderT,
|
||||
self as runtime_traits,
|
||||
BlakeTwo256,
|
||||
},
|
||||
};
|
||||
|
||||
#[cfg(feature = "kusama-native")]
|
||||
pub use kusama_runtime;
|
||||
@@ -114,9 +127,76 @@ pub use rococo_runtime;
|
||||
pub use westend_runtime;
|
||||
|
||||
/// The maximum number of active leaves we forward to the [`Overseer`] on startup.
|
||||
#[cfg(any(test,feature = "full-node"))]
|
||||
#[cfg(any(test, feature = "full-node"))]
|
||||
const MAX_ACTIVE_LEAVES: usize = 4;
|
||||
|
||||
/// Provides the header and block number for a hash.
|
||||
///
|
||||
/// Decouples `sc_client_api::Backend` and `sp_blockchain::HeaderBackend`.
|
||||
pub trait HeaderProvider<Block, Error = sp_blockchain::Error>: Send + Sync + 'static
|
||||
where
|
||||
Block: BlockT,
|
||||
Error: std::fmt::Debug + Send + Sync + 'static,
|
||||
{
|
||||
/// Obtain the header for a hash.
|
||||
fn header(
|
||||
&self,
|
||||
hash: <Block as BlockT>::Hash,
|
||||
) -> Result<Option<<Block as BlockT>::Header>, Error>;
|
||||
/// Obtain the block number for a hash.
|
||||
fn number(
|
||||
&self,
|
||||
hash: <Block as BlockT>::Hash,
|
||||
) -> Result<Option<<<Block as BlockT>::Header as HeaderT>::Number>, Error>;
|
||||
}
|
||||
|
||||
impl<Block, T> HeaderProvider<Block> for T
|
||||
where
|
||||
Block: BlockT,
|
||||
T: sp_blockchain::HeaderBackend<Block> + 'static,
|
||||
{
|
||||
fn header(
|
||||
&self,
|
||||
hash: Block::Hash,
|
||||
) -> sp_blockchain::Result<Option<<Block as BlockT>::Header>> {
|
||||
<Self as sp_blockchain::HeaderBackend<Block>>::header(
|
||||
self,
|
||||
generic::BlockId::<Block>::Hash(hash),
|
||||
)
|
||||
}
|
||||
fn number(
|
||||
&self,
|
||||
hash: Block::Hash,
|
||||
) -> sp_blockchain::Result<Option<<<Block as BlockT>::Header as HeaderT>::Number>> {
|
||||
<Self as sp_blockchain::HeaderBackend<Block>>::number(self, hash)
|
||||
}
|
||||
}
|
||||
|
||||
/// Decoupling the provider.
|
||||
///
|
||||
/// Mandated since `trait HeaderProvider` can only be
|
||||
/// implemented once for a generic `T`.
|
||||
pub trait HeaderProviderProvider<Block>: Send + Sync + 'static
|
||||
where
|
||||
Block: BlockT,
|
||||
{
|
||||
type Provider: HeaderProvider<Block> + 'static;
|
||||
|
||||
fn header_provider(&self) -> &Self::Provider;
|
||||
}
|
||||
|
||||
impl<Block, T> HeaderProviderProvider<Block> for T
|
||||
where
|
||||
Block: BlockT,
|
||||
T: sc_client_api::Backend<Block> + 'static,
|
||||
{
|
||||
type Provider = <T as sc_client_api::Backend<Block>>::Blockchain;
|
||||
|
||||
fn header_provider(&self) -> &Self::Provider {
|
||||
self.blockchain()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error(transparent)]
|
||||
@@ -205,8 +285,12 @@ fn set_prometheus_registry(config: &mut Configuration) -> Result<(), Error> {
|
||||
|
||||
/// Initialize the `Jeager` collector. The destination must listen
|
||||
/// on the given address and port for `UDP` packets.
|
||||
#[cfg(any(test,feature = "full-node"))]
|
||||
fn jaeger_launch_collector_with_agent(spawner: impl SpawnNamed, config: &Configuration, agent: Option<std::net::SocketAddr>) -> Result<(), Error> {
|
||||
#[cfg(any(test, feature = "full-node"))]
|
||||
fn jaeger_launch_collector_with_agent(
|
||||
spawner: impl SpawnNamed,
|
||||
config: &Configuration,
|
||||
agent: Option<std::net::SocketAddr>,
|
||||
) -> Result<(), Error> {
|
||||
if let Some(agent) = agent {
|
||||
let cfg = jaeger::JaegerConfig::builder()
|
||||
.agent(agent)
|
||||
@@ -219,7 +303,7 @@ fn jaeger_launch_collector_with_agent(spawner: impl SpawnNamed, config: &Configu
|
||||
}
|
||||
|
||||
#[cfg(feature = "full-node")]
|
||||
type FullSelectChain = relay_chain_selection::SelectRelayChain<FullBackend>;
|
||||
type FullSelectChain = relay_chain_selection::SelectRelayChainWithFallback<FullBackend>;
|
||||
#[cfg(feature = "full-node")]
|
||||
type FullGrandpaBlockImport<RuntimeApi, Executor> = grandpa::GrandpaBlockImport<
|
||||
FullBackend, Block, FullClient<RuntimeApi, Executor>, FullSelectChain
|
||||
@@ -303,7 +387,7 @@ fn new_partial<RuntimeApi, Executor>(
|
||||
|
||||
jaeger_launch_collector_with_agent(task_manager.spawn_handle(), &*config, jaeger_agent)?;
|
||||
|
||||
let select_chain = relay_chain_selection::SelectRelayChain::new(
|
||||
let select_chain = relay_chain_selection::SelectRelayChainWithFallback::new(
|
||||
backend.clone(),
|
||||
Handle::new_disconnected(),
|
||||
polkadot_node_subsystem_util::metrics::Metrics::register(config.prometheus_registry())?,
|
||||
@@ -506,7 +590,7 @@ where
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.filter_map(|hash| {
|
||||
let number = client.number(hash).ok()??;
|
||||
let number = HeaderBackend::number(client, hash).ok()??;
|
||||
|
||||
// Only consider leaves that are in maximum an uncle of the best block.
|
||||
if number < best_block.number().saturating_sub(1) {
|
||||
|
||||
@@ -35,19 +35,16 @@
|
||||
|
||||
#![cfg(feature = "full-node")]
|
||||
|
||||
use {
|
||||
polkadot_primitives::v1::{
|
||||
Hash, BlockNumber, Block as PolkadotBlock, Header as PolkadotHeader,
|
||||
},
|
||||
polkadot_subsystem::messages::{ApprovalVotingMessage, ChainSelectionMessage},
|
||||
polkadot_node_subsystem_util::metrics::{self, prometheus},
|
||||
polkadot_overseer::{Handle, OverseerHandle},
|
||||
futures::channel::oneshot,
|
||||
consensus_common::{Error as ConsensusError, SelectChain},
|
||||
sp_blockchain::HeaderBackend,
|
||||
sp_runtime::generic::BlockId,
|
||||
std::sync::Arc,
|
||||
use polkadot_primitives::v1::{
|
||||
Hash, BlockNumber, Block as PolkadotBlock, Header as PolkadotHeader,
|
||||
};
|
||||
use polkadot_subsystem::messages::{ApprovalVotingMessage, HighestApprovedAncestorBlock, ChainSelectionMessage, DisputeCoordinatorMessage};
|
||||
use polkadot_node_subsystem_util::metrics::{self, prometheus};
|
||||
use futures::channel::oneshot;
|
||||
use consensus_common::{Error as ConsensusError, SelectChain};
|
||||
use std::sync::Arc;
|
||||
use polkadot_overseer::{AllMessages, Handle, OverseerHandle};
|
||||
use super::{HeaderProvider, HeaderProviderProvider};
|
||||
|
||||
/// The maximum amount of unfinalized blocks we are willing to allow due to approval checking
|
||||
/// or disputes.
|
||||
@@ -109,25 +106,120 @@ impl Metrics {
|
||||
}
|
||||
|
||||
/// A chain-selection implementation which provides safety for relay chains.
|
||||
pub struct SelectRelayChain<B> {
|
||||
backend: Arc<B>,
|
||||
overseer: Handle,
|
||||
pub struct SelectRelayChainWithFallback<
|
||||
B: sc_client_api::Backend<PolkadotBlock>,
|
||||
> {
|
||||
// A fallback to use in case the overseer is disconnected.
|
||||
//
|
||||
// This is used on relay chains which have not yet enabled
|
||||
// parachains as well as situations where the node is offline.
|
||||
fallback: sc_consensus::LongestChain<B, PolkadotBlock>,
|
||||
selection: SelectRelayChain<
|
||||
B,
|
||||
Handle,
|
||||
>,
|
||||
}
|
||||
|
||||
impl<B> Clone for SelectRelayChainWithFallback<B>
|
||||
where
|
||||
B: sc_client_api::Backend<PolkadotBlock>,
|
||||
SelectRelayChain<
|
||||
B,
|
||||
Handle,
|
||||
>: Clone,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
fallback: self.fallback.clone(),
|
||||
selection: self.selection.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl<B> SelectRelayChainWithFallback<B>
|
||||
where
|
||||
B: sc_client_api::Backend<PolkadotBlock> + 'static,
|
||||
{
|
||||
/// Create a new [`SelectRelayChainWithFallback`] wrapping the given chain backend
|
||||
/// and a handle to the overseer.
|
||||
pub fn new(backend: Arc<B>, overseer: Handle, metrics: Metrics) -> Self {
|
||||
SelectRelayChainWithFallback {
|
||||
fallback: sc_consensus::LongestChain::new(backend.clone()),
|
||||
selection: SelectRelayChain::new(
|
||||
backend,
|
||||
overseer,
|
||||
metrics,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> SelectRelayChainWithFallback<B>
|
||||
where
|
||||
B: sc_client_api::Backend<PolkadotBlock> + 'static,
|
||||
{
|
||||
/// Given an overseer handle, this connects the [`SelectRelayChainWithFallback`]'s
|
||||
/// internal handle and its clones to the same overseer.
|
||||
pub fn connect_to_overseer(
|
||||
&mut self,
|
||||
handle: OverseerHandle,
|
||||
) {
|
||||
self.selection.overseer.connect_to_overseer(handle);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl<B> SelectChain<PolkadotBlock> for SelectRelayChainWithFallback<B>
|
||||
where
|
||||
B: sc_client_api::Backend<PolkadotBlock> + 'static,
|
||||
{
|
||||
async fn leaves(&self) -> Result<Vec<Hash>, ConsensusError> {
|
||||
if self.selection.overseer.is_disconnected() {
|
||||
return self.fallback.leaves().await
|
||||
}
|
||||
|
||||
self.selection.leaves().await
|
||||
}
|
||||
|
||||
async fn best_chain(&self) -> Result<PolkadotHeader, ConsensusError> {
|
||||
if self.selection.overseer.is_disconnected() {
|
||||
return self.fallback.best_chain().await
|
||||
}
|
||||
self.selection.best_chain().await
|
||||
}
|
||||
|
||||
async fn finality_target(
|
||||
&self,
|
||||
target_hash: Hash,
|
||||
maybe_max_number: Option<BlockNumber>,
|
||||
) -> Result<Option<Hash>, ConsensusError> {
|
||||
if self.selection.overseer.is_disconnected() {
|
||||
return self.fallback.finality_target(target_hash, maybe_max_number).await
|
||||
}
|
||||
self.selection.finality_target(target_hash, maybe_max_number).await
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// A chain-selection implementation which provides safety for relay chains
|
||||
/// but does not handle situations where the overseer is not yet connected.
|
||||
pub struct SelectRelayChain<B, OH> {
|
||||
backend: Arc<B>,
|
||||
overseer: OH,
|
||||
metrics: Metrics,
|
||||
}
|
||||
|
||||
impl<B> SelectRelayChain<B>
|
||||
where B: sc_client_api::backend::Backend<PolkadotBlock> + 'static
|
||||
impl<B, OH> SelectRelayChain<B, OH>
|
||||
where
|
||||
B: HeaderProviderProvider<PolkadotBlock>,
|
||||
OH: OverseerHandleT,
|
||||
{
|
||||
/// Create a new [`SelectRelayChain`] wrapping the given chain backend
|
||||
/// and a handle to the overseer.
|
||||
pub fn new(backend: Arc<B>, overseer: Handle, metrics: Metrics) -> Self {
|
||||
pub fn new(backend: Arc<B>, overseer: OH, metrics: Metrics) -> Self {
|
||||
SelectRelayChain {
|
||||
fallback: sc_consensus::LongestChain::new(backend.clone()),
|
||||
backend,
|
||||
overseer,
|
||||
metrics,
|
||||
@@ -135,7 +227,7 @@ impl<B> SelectRelayChain<B>
|
||||
}
|
||||
|
||||
fn block_header(&self, hash: Hash) -> Result<PolkadotHeader, ConsensusError> {
|
||||
match self.backend.blockchain().header(BlockId::Hash(hash)) {
|
||||
match HeaderProvider::header(self.backend.header_provider(), hash) {
|
||||
Ok(Some(header)) => Ok(header),
|
||||
Ok(None) => Err(ConsensusError::ChainLookup(format!(
|
||||
"Missing header with hash {:?}",
|
||||
@@ -150,7 +242,7 @@ impl<B> SelectRelayChain<B>
|
||||
}
|
||||
|
||||
fn block_number(&self, hash: Hash) -> Result<BlockNumber, ConsensusError> {
|
||||
match self.backend.blockchain().number(hash) {
|
||||
match HeaderProvider::number(self.backend.header_provider(), hash) {
|
||||
Ok(Some(number)) => Ok(number),
|
||||
Ok(None) => Err(ConsensusError::ChainLookup(format!(
|
||||
"Missing number with hash {:?}",
|
||||
@@ -165,25 +257,15 @@ impl<B> SelectRelayChain<B>
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> SelectRelayChain<B> {
|
||||
/// Given an overseer handle, connects the [`SelectRelayChain`]'s
|
||||
/// internal handle and its clones to the same overseer.
|
||||
pub fn connect_to_overseer(
|
||||
&mut self,
|
||||
handle: OverseerHandle,
|
||||
) {
|
||||
self.overseer.connect_to_overseer(handle);
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> Clone for SelectRelayChain<B>
|
||||
where B: sc_client_api::backend::Backend<PolkadotBlock> + 'static
|
||||
impl<B, OH> Clone for SelectRelayChain<B, OH>
|
||||
where
|
||||
B: HeaderProviderProvider<PolkadotBlock> + Send + Sync,
|
||||
OH: OverseerHandleT,
|
||||
{
|
||||
fn clone(&self) -> SelectRelayChain<B> {
|
||||
fn clone(&self) -> Self {
|
||||
SelectRelayChain {
|
||||
backend: self.backend.clone(),
|
||||
overseer: self.overseer.clone(),
|
||||
fallback: self.fallback.clone(),
|
||||
metrics: self.metrics.clone(),
|
||||
}
|
||||
}
|
||||
@@ -199,17 +281,32 @@ enum Error {
|
||||
EmptyLeaves,
|
||||
}
|
||||
|
||||
|
||||
/// Decoupling trait for the overseer handle.
|
||||
///
|
||||
/// Required for testing purposes.
|
||||
#[async_trait::async_trait]
|
||||
impl<B> SelectChain<PolkadotBlock> for SelectRelayChain<B>
|
||||
where B: sc_client_api::backend::Backend<PolkadotBlock> + 'static
|
||||
pub trait OverseerHandleT: Clone + Send + Sync {
|
||||
async fn send_msg<M: Send + Into<AllMessages>>(&mut self, msg: M, origin: &'static str);
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl OverseerHandleT for Handle {
|
||||
async fn send_msg<M: Send + Into<AllMessages>>(&mut self, msg: M, origin: &'static str) {
|
||||
Handle::send_msg(self, msg, origin).await
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl<B, OH> SelectChain<PolkadotBlock> for SelectRelayChain<B, OH>
|
||||
where
|
||||
B: HeaderProviderProvider<PolkadotBlock>,
|
||||
OH: OverseerHandleT,
|
||||
{
|
||||
/// Get all leaves of the chain, i.e. block hashes that are suitable to
|
||||
/// build upon and have no suitable children.
|
||||
async fn leaves(&self) -> Result<Vec<Hash>, ConsensusError> {
|
||||
if self.overseer.is_disconnected() {
|
||||
return self.fallback.leaves().await
|
||||
}
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
self.overseer
|
||||
@@ -226,10 +323,6 @@ impl<B> SelectChain<PolkadotBlock> for SelectRelayChain<B>
|
||||
|
||||
/// Among all leaves, pick the one which is the best chain to build upon.
|
||||
async fn best_chain(&self) -> Result<PolkadotHeader, ConsensusError> {
|
||||
if self.overseer.is_disconnected() {
|
||||
return self.fallback.best_chain().await
|
||||
}
|
||||
|
||||
// The Chain Selection subsystem is supposed to treat the finalized
|
||||
// block as the best leaf in the case that there are no viable
|
||||
// leaves, so this should not happen in practice.
|
||||
@@ -257,10 +350,6 @@ impl<B> SelectChain<PolkadotBlock> for SelectRelayChain<B>
|
||||
target_hash: Hash,
|
||||
maybe_max_number: Option<BlockNumber>,
|
||||
) -> Result<Option<Hash>, ConsensusError> {
|
||||
if self.overseer.is_disconnected() {
|
||||
return self.fallback.finality_target(target_hash, maybe_max_number).await
|
||||
}
|
||||
|
||||
let mut overseer = self.overseer.clone();
|
||||
|
||||
let subchain_head = {
|
||||
@@ -305,7 +394,7 @@ impl<B> SelectChain<PolkadotBlock> for SelectRelayChain<B>
|
||||
subchain_head
|
||||
} else {
|
||||
let (ancestor_hash, _) = crate::grandpa_support::walk_backwards_to_target_block(
|
||||
self.backend.blockchain(),
|
||||
self.backend.header_provider(),
|
||||
max,
|
||||
&subchain_header,
|
||||
).map_err(|e| ConsensusError::ChainLookup(format!("{:?}", e)))?;
|
||||
@@ -319,7 +408,7 @@ impl<B> SelectChain<PolkadotBlock> for SelectRelayChain<B>
|
||||
let initial_leaf_number = self.block_number(initial_leaf)?;
|
||||
|
||||
// 2. Constrain according to `ApprovedAncestor`.
|
||||
let (subchain_head, subchain_number) = {
|
||||
let (subchain_head, subchain_number, subchain_block_descriptions) = {
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
overseer.send_msg(
|
||||
@@ -336,17 +425,45 @@ impl<B> SelectChain<PolkadotBlock> for SelectRelayChain<B>
|
||||
.map_err(|e| ConsensusError::Other(Box::new(e)))?
|
||||
{
|
||||
// No approved ancestors means target hash is maximal vote.
|
||||
None => (target_hash, target_number),
|
||||
Some((s_h, s_n)) => (s_h, s_n),
|
||||
None => (target_hash, target_number, Vec::new()),
|
||||
Some(HighestApprovedAncestorBlock {
|
||||
number, hash, descriptions
|
||||
}) => (hash, number, descriptions),
|
||||
}
|
||||
};
|
||||
|
||||
// Prevent sending flawed data to the dispute-coordinator.
|
||||
if Some(subchain_block_descriptions.len() as _) != subchain_number.checked_sub(target_number) {
|
||||
tracing::error!(
|
||||
LOG_TARGET,
|
||||
present_block_descriptions = subchain_block_descriptions.len(),
|
||||
target_number,
|
||||
subchain_number,
|
||||
"Mismatch of anticipated block descriptions and block number difference.",
|
||||
);
|
||||
return Ok(Some(target_hash));
|
||||
}
|
||||
|
||||
let lag = initial_leaf_number.saturating_sub(subchain_number);
|
||||
self.metrics.note_approval_checking_finality_lag(lag);
|
||||
|
||||
// 3. Constrain according to disputes:
|
||||
// TODO: https://github.com/paritytech/polkadot/issues/3164
|
||||
self.metrics.note_disputes_finality_lag(0);
|
||||
let (tx, rx) = oneshot::channel();
|
||||
overseer.send_msg(DisputeCoordinatorMessage::DetermineUndisputedChain{
|
||||
base_number: target_number,
|
||||
block_descriptions: subchain_block_descriptions,
|
||||
tx,
|
||||
},
|
||||
std::any::type_name::<Self>(),
|
||||
).await;
|
||||
let (subchain_number, subchain_head) = rx.await
|
||||
.map_err(Error::OverseerDisconnected)
|
||||
.map_err(|e| ConsensusError::Other(Box::new(e)))?
|
||||
.unwrap_or_else(|| (subchain_number, subchain_head));
|
||||
|
||||
// The the total lag accounting for disputes.
|
||||
let lag_disputes = initial_leaf_number.saturating_sub(subchain_number);
|
||||
self.metrics.note_disputes_finality_lag(lag_disputes);
|
||||
|
||||
// 4. Apply the maximum safeguard to the finality lag.
|
||||
if lag > MAX_FINALITY_LAG {
|
||||
@@ -361,7 +478,7 @@ impl<B> SelectChain<PolkadotBlock> for SelectRelayChain<B>
|
||||
// Otherwise we're looking for a descendant.
|
||||
let initial_leaf_header = self.block_header(initial_leaf)?;
|
||||
let (forced_target, _) = crate::grandpa_support::walk_backwards_to_target_block(
|
||||
self.backend.blockchain(),
|
||||
self.backend.header_provider(),
|
||||
safe_target,
|
||||
&initial_leaf_header,
|
||||
).map_err(|e| ConsensusError::ChainLookup(format!("{:?}", e)))?;
|
||||
|
||||
@@ -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