4666047395
Updated 4763 files with dual copyright: - Parity Technologies (UK) Ltd. - Dijital Kurdistan Tech Institute
2794 lines
73 KiB
Rust
2794 lines
73 KiB
Rust
// Copyright (C) Parity Technologies (UK) Ltd. and Dijital Kurdistan Tech Institute
|
|
// This file is part of Pezkuwi.
|
|
|
|
// Pezkuwi 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.
|
|
|
|
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
use super::*;
|
|
use assert_matches::assert_matches;
|
|
use pezkuwi_node_subsystem::{
|
|
messages::{
|
|
AllMessages, HypotheticalMembershipRequest, ParentHeadData, ProspectiveTeyrchainsMessage,
|
|
ProspectiveValidationDataRequest,
|
|
},
|
|
RuntimeApiError,
|
|
};
|
|
use pezkuwi_node_subsystem_test_helpers as test_helpers;
|
|
use pezkuwi_primitives::{
|
|
async_backing::{
|
|
BackingState, CandidatePendingAvailability, Constraints, InboundHrmpLimitations,
|
|
},
|
|
CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreIndex, HeadData, Header,
|
|
MutateDescriptorV2, PersistedValidationData, ValidationCodeHash, DEFAULT_SCHEDULING_LOOKAHEAD,
|
|
};
|
|
use pezkuwi_primitives_test_helpers::make_candidate;
|
|
use rstest::rstest;
|
|
use std::{
|
|
collections::{BTreeMap, VecDeque},
|
|
sync::Arc,
|
|
};
|
|
use test_helpers::mock::new_leaf;
|
|
|
|
const RUNTIME_API_NOT_SUPPORTED: RuntimeApiError =
|
|
RuntimeApiError::NotSupported { runtime_api_name: "test-runtime" };
|
|
|
|
const MAX_POV_SIZE: u32 = 1_000_000;
|
|
|
|
type VirtualOverseer =
|
|
pezkuwi_node_subsystem_test_helpers::TestSubsystemContextHandle<ProspectiveTeyrchainsMessage>;
|
|
|
|
fn dummy_constraints(
|
|
min_relay_parent_number: BlockNumber,
|
|
valid_watermarks: Vec<BlockNumber>,
|
|
required_parent: HeadData,
|
|
validation_code_hash: ValidationCodeHash,
|
|
) -> Constraints {
|
|
Constraints {
|
|
min_relay_parent_number,
|
|
max_pov_size: MAX_POV_SIZE,
|
|
max_head_data_size: 20480,
|
|
max_code_size: 1_000_000,
|
|
ump_remaining: 10,
|
|
ump_remaining_bytes: 1_000,
|
|
max_ump_num_per_candidate: 10,
|
|
dmp_remaining_messages: vec![],
|
|
hrmp_inbound: InboundHrmpLimitations { valid_watermarks },
|
|
hrmp_channels_out: vec![],
|
|
max_hrmp_num_per_candidate: 0,
|
|
required_parent,
|
|
validation_code_hash,
|
|
upgrade_restriction: None,
|
|
future_validation_code: None,
|
|
}
|
|
}
|
|
|
|
struct TestState {
|
|
claim_queue: BTreeMap<CoreIndex, VecDeque<ParaId>>,
|
|
runtime_api_version: u32,
|
|
validation_code_hash: ValidationCodeHash,
|
|
}
|
|
|
|
impl Default for TestState {
|
|
fn default() -> Self {
|
|
let chain_a = ParaId::from(1);
|
|
let chain_b = ParaId::from(2);
|
|
|
|
let mut claim_queue = BTreeMap::new();
|
|
claim_queue.insert(
|
|
CoreIndex(0),
|
|
std::iter::repeat(chain_a).take(DEFAULT_SCHEDULING_LOOKAHEAD as _).collect(),
|
|
);
|
|
claim_queue.insert(
|
|
CoreIndex(1),
|
|
std::iter::repeat(chain_b).take(DEFAULT_SCHEDULING_LOOKAHEAD as _).collect(),
|
|
);
|
|
|
|
let validation_code_hash = Hash::repeat_byte(42).into();
|
|
|
|
Self {
|
|
validation_code_hash,
|
|
claim_queue,
|
|
runtime_api_version: RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TestState {
|
|
fn set_runtime_api_version(&mut self, version: u32) {
|
|
self.runtime_api_version = version;
|
|
}
|
|
}
|
|
|
|
fn get_parent_hash(hash: Hash) -> Hash {
|
|
Hash::from_low_u64_be(hash.to_low_u64_be() + 1)
|
|
}
|
|
|
|
fn test_harness<T: Future<Output = VirtualOverseer>>(
|
|
test: impl FnOnce(VirtualOverseer) -> T,
|
|
) -> View {
|
|
pezsp_tracing::init_for_tests();
|
|
|
|
let pool = pezsp_core::testing::TaskExecutor::new();
|
|
|
|
let (mut context, virtual_overseer) =
|
|
pezkuwi_node_subsystem_test_helpers::make_subsystem_context(pool.clone());
|
|
|
|
let mut view = View::new();
|
|
let subsystem = async move {
|
|
if let Err(e) = run_iteration(&mut context, &mut view, &Metrics(None)).await {
|
|
panic!("{:?}", e);
|
|
}
|
|
|
|
view
|
|
};
|
|
|
|
let test_fut = test(virtual_overseer);
|
|
|
|
futures::pin_mut!(test_fut);
|
|
futures::pin_mut!(subsystem);
|
|
let (_, view) = futures::executor::block_on(future::join(
|
|
async move {
|
|
let mut virtual_overseer = test_fut.await;
|
|
virtual_overseer.send(FromOrchestra::Signal(OverseerSignal::Conclude)).await;
|
|
},
|
|
subsystem,
|
|
));
|
|
|
|
view
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
struct PerParaData {
|
|
min_relay_parent: BlockNumber,
|
|
head_data: HeadData,
|
|
pending_availability: Vec<CandidatePendingAvailability>,
|
|
}
|
|
|
|
impl PerParaData {
|
|
pub fn new(min_relay_parent: BlockNumber, head_data: HeadData) -> Self {
|
|
Self { min_relay_parent, head_data, pending_availability: Vec::new() }
|
|
}
|
|
|
|
pub fn new_with_pending(
|
|
min_relay_parent: BlockNumber,
|
|
head_data: HeadData,
|
|
pending: Vec<CandidatePendingAvailability>,
|
|
) -> Self {
|
|
Self { min_relay_parent, head_data, pending_availability: pending }
|
|
}
|
|
}
|
|
|
|
struct TestLeaf {
|
|
number: BlockNumber,
|
|
hash: Hash,
|
|
para_data: Vec<(ParaId, PerParaData)>,
|
|
}
|
|
|
|
impl TestLeaf {
|
|
pub fn para_data(&self, para_id: ParaId) -> &PerParaData {
|
|
self.para_data
|
|
.iter()
|
|
.find_map(|(p_id, data)| if *p_id == para_id { Some(data) } else { None })
|
|
.unwrap()
|
|
}
|
|
}
|
|
|
|
async fn send_block_header(virtual_overseer: &mut VirtualOverseer, hash: Hash, number: u32) {
|
|
let header = Header {
|
|
parent_hash: get_parent_hash(hash),
|
|
number,
|
|
state_root: Hash::zero(),
|
|
extrinsics_root: Hash::zero(),
|
|
digest: Default::default(),
|
|
};
|
|
|
|
assert_matches!(
|
|
virtual_overseer.recv().await,
|
|
AllMessages::ChainApi(
|
|
ChainApiMessage::BlockHeader(parent, tx)
|
|
) if parent == hash => {
|
|
tx.send(Ok(Some(header))).unwrap();
|
|
}
|
|
);
|
|
}
|
|
|
|
async fn activate_leaf(
|
|
virtual_overseer: &mut VirtualOverseer,
|
|
leaf: &TestLeaf,
|
|
test_state: &TestState,
|
|
) {
|
|
let TestLeaf { number, hash, .. } = leaf;
|
|
|
|
let activated = new_leaf(*hash, *number);
|
|
|
|
virtual_overseer
|
|
.send(FromOrchestra::Signal(OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(
|
|
activated,
|
|
))))
|
|
.await;
|
|
|
|
handle_leaf_activation(virtual_overseer, leaf, test_state, get_parent_hash).await;
|
|
}
|
|
|
|
async fn activate_leaf_with_parent_hash_fn(
|
|
virtual_overseer: &mut VirtualOverseer,
|
|
leaf: &TestLeaf,
|
|
test_state: &TestState,
|
|
parent_hash_fn: impl Fn(Hash) -> Hash,
|
|
) {
|
|
let TestLeaf { number, hash, .. } = leaf;
|
|
|
|
let activated = new_leaf(*hash, *number);
|
|
|
|
virtual_overseer
|
|
.send(FromOrchestra::Signal(OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(
|
|
activated,
|
|
))))
|
|
.await;
|
|
|
|
handle_leaf_activation(virtual_overseer, leaf, test_state, parent_hash_fn).await;
|
|
}
|
|
|
|
async fn handle_leaf_activation(
|
|
virtual_overseer: &mut VirtualOverseer,
|
|
leaf: &TestLeaf,
|
|
test_state: &TestState,
|
|
parent_hash_fn: impl Fn(Hash) -> Hash,
|
|
) {
|
|
let TestLeaf { number, hash, para_data } = leaf;
|
|
|
|
assert_matches!(
|
|
virtual_overseer.recv().await,
|
|
AllMessages::RuntimeApi(
|
|
RuntimeApiMessage::Request(parent, RuntimeApiRequest::ClaimQueue(tx))
|
|
) if parent == *hash => {
|
|
tx.send(Ok(test_state.claim_queue.clone())).unwrap();
|
|
}
|
|
);
|
|
|
|
send_block_header(virtual_overseer, *hash, *number).await;
|
|
|
|
assert_matches!(
|
|
virtual_overseer.recv().await,
|
|
AllMessages::RuntimeApi(
|
|
RuntimeApiMessage::Request(parent, RuntimeApiRequest::SessionIndexForChild(tx))
|
|
) if parent == *hash => {
|
|
tx.send(Ok(1)).unwrap();
|
|
}
|
|
);
|
|
|
|
assert_matches!(
|
|
virtual_overseer.recv().await,
|
|
AllMessages::RuntimeApi(
|
|
RuntimeApiMessage::Request(parent, RuntimeApiRequest::SchedulingLookahead(session_index, tx))
|
|
) if parent == *hash && session_index == 1 => {
|
|
tx.send(Ok(DEFAULT_SCHEDULING_LOOKAHEAD)).unwrap();
|
|
}
|
|
);
|
|
|
|
// Check that subsystem job issues a request for ancestors.
|
|
let min_min = para_data.iter().map(|(_, data)| data.min_relay_parent).min().unwrap_or(*number);
|
|
let ancestry_len = number - min_min;
|
|
let ancestry_hashes: Vec<Hash> =
|
|
std::iter::successors(Some(*hash), |h| Some(parent_hash_fn(*h)))
|
|
.skip(1)
|
|
.take(ancestry_len as usize)
|
|
.collect();
|
|
let ancestry_numbers = (min_min..*number).rev();
|
|
let ancestry_iter = ancestry_hashes.clone().into_iter().zip(ancestry_numbers).peekable();
|
|
if ancestry_len > 0 {
|
|
assert_matches!(
|
|
virtual_overseer.recv().await,
|
|
AllMessages::ChainApi(
|
|
ChainApiMessage::Ancestors{hash: block_hash, k, response_channel: tx}
|
|
) if block_hash == *hash && k == (DEFAULT_SCHEDULING_LOOKAHEAD - 1) as usize => {
|
|
tx.send(Ok(ancestry_hashes.clone())).unwrap();
|
|
}
|
|
);
|
|
}
|
|
|
|
let mut used_relay_parents = HashSet::new();
|
|
for (hash, number) in ancestry_iter {
|
|
if !used_relay_parents.contains(&hash) {
|
|
send_block_header(virtual_overseer, hash, number).await;
|
|
assert_matches!(
|
|
virtual_overseer.recv().await,
|
|
AllMessages::RuntimeApi(
|
|
RuntimeApiMessage::Request(parent, RuntimeApiRequest::SessionIndexForChild(tx))
|
|
) if parent == hash => {
|
|
tx.send(Ok(1)).unwrap();
|
|
}
|
|
);
|
|
used_relay_parents.insert(hash);
|
|
}
|
|
}
|
|
|
|
let paras: HashSet<_> = test_state.claim_queue.values().flatten().collect();
|
|
|
|
// We expect two messages per teyrchain block.
|
|
for _ in 0..paras.len() * 2 {
|
|
let message = virtual_overseer.recv().await;
|
|
let para_id = match message {
|
|
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
|
parent,
|
|
RuntimeApiRequest::ParaBackingState(p_id, tx),
|
|
)) if parent == *hash => {
|
|
let PerParaData { min_relay_parent, head_data, pending_availability } =
|
|
leaf.para_data(p_id);
|
|
|
|
let constraints = dummy_constraints(
|
|
*min_relay_parent,
|
|
vec![*number],
|
|
head_data.clone(),
|
|
test_state.validation_code_hash,
|
|
);
|
|
|
|
tx.send(Ok(Some(BackingState {
|
|
constraints,
|
|
pending_availability: pending_availability.clone(),
|
|
})))
|
|
.unwrap();
|
|
Some(p_id)
|
|
},
|
|
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
|
parent,
|
|
RuntimeApiRequest::BackingConstraints(p_id, tx),
|
|
)) if parent == *hash
|
|
&& test_state.runtime_api_version
|
|
>= RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT =>
|
|
{
|
|
let PerParaData { min_relay_parent, head_data, pending_availability: _ } =
|
|
leaf.para_data(p_id);
|
|
let constraints = dummy_constraints(
|
|
*min_relay_parent,
|
|
vec![*number],
|
|
head_data.clone(),
|
|
test_state.validation_code_hash,
|
|
);
|
|
|
|
tx.send(Ok(Some(constraints))).unwrap();
|
|
None
|
|
},
|
|
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
|
parent,
|
|
RuntimeApiRequest::BackingConstraints(_p_id, tx),
|
|
)) if parent == *hash
|
|
&& test_state.runtime_api_version
|
|
< RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT =>
|
|
{
|
|
tx.send(Err(RUNTIME_API_NOT_SUPPORTED)).unwrap();
|
|
None
|
|
},
|
|
|
|
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
|
parent,
|
|
RuntimeApiRequest::CandidatesPendingAvailability(p_id, tx),
|
|
)) if parent == *hash => {
|
|
tx.send(Ok(leaf
|
|
.para_data(p_id)
|
|
.pending_availability
|
|
.clone()
|
|
.into_iter()
|
|
.map(|c| CommittedCandidateReceipt {
|
|
descriptor: c.descriptor,
|
|
commitments: c.commitments,
|
|
})
|
|
.collect()))
|
|
.unwrap();
|
|
Some(p_id)
|
|
},
|
|
_ => panic!("received unexpected message {:?}", message),
|
|
};
|
|
|
|
if let Some(para_id) = para_id {
|
|
for pending in leaf.para_data(para_id).pending_availability.clone() {
|
|
if !used_relay_parents.contains(&pending.descriptor.relay_parent()) {
|
|
send_block_header(
|
|
virtual_overseer,
|
|
pending.descriptor.relay_parent(),
|
|
pending.relay_parent_number,
|
|
)
|
|
.await;
|
|
|
|
used_relay_parents.insert(pending.descriptor.relay_parent());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get minimum relay parents.
|
|
let (tx, rx) = oneshot::channel();
|
|
virtual_overseer
|
|
.send(overseer::FromOrchestra::Communication {
|
|
msg: ProspectiveTeyrchainsMessage::GetMinimumRelayParents(*hash, tx),
|
|
})
|
|
.await;
|
|
|
|
let mut resp = rx.await.unwrap();
|
|
|
|
resp.sort();
|
|
let mrp_response: Vec<(ParaId, BlockNumber)> = para_data
|
|
.iter()
|
|
.map(|(para_id, data)| (*para_id, data.min_relay_parent))
|
|
.collect();
|
|
assert_eq!(resp, mrp_response);
|
|
}
|
|
|
|
async fn deactivate_leaf(virtual_overseer: &mut VirtualOverseer, hash: Hash) {
|
|
virtual_overseer
|
|
.send(FromOrchestra::Signal(OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::stop_work(
|
|
hash,
|
|
))))
|
|
.await;
|
|
}
|
|
|
|
async fn introduce_seconded_candidate(
|
|
virtual_overseer: &mut VirtualOverseer,
|
|
candidate: CommittedCandidateReceipt,
|
|
pvd: PersistedValidationData,
|
|
) {
|
|
let req = IntroduceSecondedCandidateRequest {
|
|
candidate_para: candidate.descriptor.para_id(),
|
|
candidate_receipt: candidate,
|
|
persisted_validation_data: pvd,
|
|
};
|
|
let (tx, rx) = oneshot::channel();
|
|
virtual_overseer
|
|
.send(overseer::FromOrchestra::Communication {
|
|
msg: ProspectiveTeyrchainsMessage::IntroduceSecondedCandidate(req, tx),
|
|
})
|
|
.await;
|
|
assert!(rx.await.unwrap());
|
|
}
|
|
|
|
async fn introduce_seconded_candidate_failed(
|
|
virtual_overseer: &mut VirtualOverseer,
|
|
candidate: CommittedCandidateReceipt,
|
|
pvd: PersistedValidationData,
|
|
) {
|
|
let req = IntroduceSecondedCandidateRequest {
|
|
candidate_para: candidate.descriptor.para_id(),
|
|
candidate_receipt: candidate,
|
|
persisted_validation_data: pvd,
|
|
};
|
|
let (tx, rx) = oneshot::channel();
|
|
virtual_overseer
|
|
.send(overseer::FromOrchestra::Communication {
|
|
msg: ProspectiveTeyrchainsMessage::IntroduceSecondedCandidate(req, tx),
|
|
})
|
|
.await;
|
|
assert!(!rx.await.unwrap());
|
|
}
|
|
|
|
async fn back_candidate(
|
|
virtual_overseer: &mut VirtualOverseer,
|
|
candidate: &CommittedCandidateReceipt,
|
|
candidate_hash: CandidateHash,
|
|
) {
|
|
virtual_overseer
|
|
.send(overseer::FromOrchestra::Communication {
|
|
msg: ProspectiveTeyrchainsMessage::CandidateBacked(
|
|
candidate.descriptor.para_id(),
|
|
candidate_hash,
|
|
),
|
|
})
|
|
.await;
|
|
}
|
|
|
|
async fn get_backable_candidates(
|
|
virtual_overseer: &mut VirtualOverseer,
|
|
leaf: &TestLeaf,
|
|
para_id: ParaId,
|
|
ancestors: Ancestors,
|
|
count: u32,
|
|
expected_result: Vec<(CandidateHash, Hash)>,
|
|
) {
|
|
let (tx, rx) = oneshot::channel();
|
|
virtual_overseer
|
|
.send(overseer::FromOrchestra::Communication {
|
|
msg: ProspectiveTeyrchainsMessage::GetBackableCandidates(
|
|
leaf.hash, para_id, count, ancestors, tx,
|
|
),
|
|
})
|
|
.await;
|
|
let resp = rx.await.unwrap();
|
|
assert_eq!(resp, expected_result);
|
|
}
|
|
|
|
async fn get_hypothetical_membership(
|
|
virtual_overseer: &mut VirtualOverseer,
|
|
candidate_hash: CandidateHash,
|
|
receipt: CommittedCandidateReceipt,
|
|
persisted_validation_data: PersistedValidationData,
|
|
expected_membership: Vec<Hash>,
|
|
) {
|
|
let hypothetical_candidate = HypotheticalCandidate::Complete {
|
|
candidate_hash,
|
|
receipt: Arc::new(receipt),
|
|
persisted_validation_data,
|
|
};
|
|
let request = HypotheticalMembershipRequest {
|
|
candidates: vec![hypothetical_candidate.clone()],
|
|
fragment_chain_relay_parent: None,
|
|
};
|
|
let (tx, rx) = oneshot::channel();
|
|
virtual_overseer
|
|
.send(overseer::FromOrchestra::Communication {
|
|
msg: ProspectiveTeyrchainsMessage::GetHypotheticalMembership(request, tx),
|
|
})
|
|
.await;
|
|
let mut resp = rx.await.unwrap();
|
|
assert_eq!(resp.len(), 1);
|
|
let (candidate, membership) = resp.remove(0);
|
|
assert_eq!(candidate, hypothetical_candidate);
|
|
assert_eq!(
|
|
membership.into_iter().collect::<HashSet<_>>(),
|
|
expected_membership.into_iter().collect::<HashSet<_>>()
|
|
);
|
|
}
|
|
|
|
async fn get_pvd(
|
|
virtual_overseer: &mut VirtualOverseer,
|
|
para_id: ParaId,
|
|
candidate_relay_parent: Hash,
|
|
parent_head_data: HeadData,
|
|
expected_pvd: Option<PersistedValidationData>,
|
|
) {
|
|
let request = ProspectiveValidationDataRequest {
|
|
para_id,
|
|
candidate_relay_parent,
|
|
parent_head_data: ParentHeadData::OnlyHash(parent_head_data.hash()),
|
|
};
|
|
let (tx, rx) = oneshot::channel();
|
|
virtual_overseer
|
|
.send(overseer::FromOrchestra::Communication {
|
|
msg: ProspectiveTeyrchainsMessage::GetProspectiveValidationData(request, tx),
|
|
})
|
|
.await;
|
|
let resp = rx.await.unwrap();
|
|
assert_eq!(resp, expected_pvd);
|
|
}
|
|
|
|
macro_rules! make_and_back_candidate {
|
|
($test_state:ident, $virtual_overseer:ident, $leaf:ident, $parent:expr, $index:expr) => {{
|
|
let (mut candidate, pvd) = make_candidate(
|
|
$leaf.hash,
|
|
$leaf.number,
|
|
1.into(),
|
|
$parent.commitments.head_data.clone(),
|
|
HeadData(vec![$index]),
|
|
$test_state.validation_code_hash,
|
|
);
|
|
// Set a field to make this candidate unique.
|
|
candidate.descriptor.set_para_head(Hash::from_low_u64_le($index));
|
|
let candidate_hash = candidate.hash();
|
|
introduce_seconded_candidate(&mut $virtual_overseer, candidate.clone(), pvd).await;
|
|
back_candidate(&mut $virtual_overseer, &candidate, candidate_hash).await;
|
|
|
|
(candidate, candidate_hash)
|
|
}};
|
|
}
|
|
|
|
// Send some candidates and make sure all are found:
|
|
// - Two for the same leaf A (one for teyrchain 1 and one for teyrchain 2)
|
|
// - One for leaf B on teyrchain 1
|
|
// - One for leaf C on teyrchain 2
|
|
// Also tests a claim queue size larger than 1.
|
|
#[rstest]
|
|
#[case(RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT)]
|
|
#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)]
|
|
fn introduce_candidates_basic(#[case] runtime_api_version: u32) {
|
|
let mut test_state = TestState::default();
|
|
test_state.set_runtime_api_version(runtime_api_version);
|
|
|
|
let chain_a = ParaId::from(1);
|
|
let chain_b = ParaId::from(2);
|
|
let mut claim_queue = BTreeMap::new();
|
|
claim_queue.insert(CoreIndex(0), [chain_a, chain_b].into_iter().collect());
|
|
|
|
test_state.claim_queue = claim_queue;
|
|
|
|
let view = test_harness(|mut virtual_overseer| async move {
|
|
// Leaf A
|
|
let leaf_a = TestLeaf {
|
|
number: 100,
|
|
hash: Hash::from_low_u64_be(130),
|
|
para_data: vec![
|
|
(1.into(), PerParaData::new(97, HeadData(vec![1, 2, 3]))),
|
|
(2.into(), PerParaData::new(100, HeadData(vec![2, 3, 4]))),
|
|
],
|
|
};
|
|
// Leaf B
|
|
let leaf_b = TestLeaf {
|
|
number: 101,
|
|
hash: Hash::from_low_u64_be(131),
|
|
para_data: vec![
|
|
(1.into(), PerParaData::new(99, HeadData(vec![3, 4, 5]))),
|
|
(2.into(), PerParaData::new(101, HeadData(vec![4, 5, 6]))),
|
|
],
|
|
};
|
|
// Leaf C
|
|
let leaf_c = TestLeaf {
|
|
number: 102,
|
|
hash: Hash::from_low_u64_be(132),
|
|
para_data: vec![
|
|
(1.into(), PerParaData::new(102, HeadData(vec![5, 6, 7]))),
|
|
(2.into(), PerParaData::new(98, HeadData(vec![6, 7, 8]))),
|
|
],
|
|
};
|
|
|
|
// Activate leaves.
|
|
activate_leaf(&mut virtual_overseer, &leaf_a, &test_state).await;
|
|
activate_leaf(&mut virtual_overseer, &leaf_b, &test_state).await;
|
|
activate_leaf(&mut virtual_overseer, &leaf_c, &test_state).await;
|
|
|
|
// Candidate A1
|
|
let (candidate_a1, pvd_a1) = make_candidate(
|
|
leaf_a.hash,
|
|
leaf_a.number,
|
|
1.into(),
|
|
HeadData(vec![1, 2, 3]),
|
|
HeadData(vec![1]),
|
|
test_state.validation_code_hash,
|
|
);
|
|
let candidate_hash_a1 = candidate_a1.hash();
|
|
let response_a1 = vec![(candidate_hash_a1, leaf_a.hash)];
|
|
|
|
// Candidate A2
|
|
let (candidate_a2, pvd_a2) = make_candidate(
|
|
leaf_a.hash,
|
|
leaf_a.number,
|
|
2.into(),
|
|
HeadData(vec![2, 3, 4]),
|
|
HeadData(vec![2]),
|
|
test_state.validation_code_hash,
|
|
);
|
|
let candidate_hash_a2 = candidate_a2.hash();
|
|
let response_a2 = vec![(candidate_hash_a2, leaf_a.hash)];
|
|
|
|
// Candidate B
|
|
let (candidate_b, pvd_b) = make_candidate(
|
|
leaf_b.hash,
|
|
leaf_b.number,
|
|
1.into(),
|
|
HeadData(vec![3, 4, 5]),
|
|
HeadData(vec![3]),
|
|
test_state.validation_code_hash,
|
|
);
|
|
let candidate_hash_b = candidate_b.hash();
|
|
let response_b = vec![(candidate_hash_b, leaf_b.hash)];
|
|
|
|
// Candidate C
|
|
let (candidate_c, pvd_c) = make_candidate(
|
|
leaf_c.hash,
|
|
leaf_c.number,
|
|
2.into(),
|
|
HeadData(vec![6, 7, 8]),
|
|
HeadData(vec![4]),
|
|
test_state.validation_code_hash,
|
|
);
|
|
let candidate_hash_c = candidate_c.hash();
|
|
let response_c = vec![(candidate_hash_c, leaf_c.hash)];
|
|
|
|
// Introduce candidates.
|
|
introduce_seconded_candidate(&mut virtual_overseer, candidate_a1.clone(), pvd_a1).await;
|
|
introduce_seconded_candidate(&mut virtual_overseer, candidate_a2.clone(), pvd_a2).await;
|
|
introduce_seconded_candidate(&mut virtual_overseer, candidate_b.clone(), pvd_b).await;
|
|
introduce_seconded_candidate(&mut virtual_overseer, candidate_c.clone(), pvd_c).await;
|
|
|
|
// Back candidates. Otherwise, we cannot check membership with GetBackableCandidates.
|
|
back_candidate(&mut virtual_overseer, &candidate_a1, candidate_hash_a1).await;
|
|
back_candidate(&mut virtual_overseer, &candidate_a2, candidate_hash_a2).await;
|
|
back_candidate(&mut virtual_overseer, &candidate_b, candidate_hash_b).await;
|
|
back_candidate(&mut virtual_overseer, &candidate_c, candidate_hash_c).await;
|
|
|
|
// Check candidate tree membership.
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
1.into(),
|
|
Ancestors::default(),
|
|
5,
|
|
response_a1,
|
|
)
|
|
.await;
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
2.into(),
|
|
Ancestors::default(),
|
|
5,
|
|
response_a2,
|
|
)
|
|
.await;
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_b,
|
|
1.into(),
|
|
Ancestors::default(),
|
|
5,
|
|
response_b,
|
|
)
|
|
.await;
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_c,
|
|
2.into(),
|
|
Ancestors::default(),
|
|
5,
|
|
response_c,
|
|
)
|
|
.await;
|
|
|
|
// Check membership on other leaves.
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_b,
|
|
2.into(),
|
|
Ancestors::default(),
|
|
5,
|
|
vec![],
|
|
)
|
|
.await;
|
|
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_c,
|
|
1.into(),
|
|
Ancestors::default(),
|
|
5,
|
|
vec![],
|
|
)
|
|
.await;
|
|
|
|
virtual_overseer
|
|
});
|
|
|
|
assert_eq!(view.active_leaves.len(), 3);
|
|
}
|
|
|
|
// Check if candidates are not backed if they fail constraint checks
|
|
#[rstest]
|
|
#[case(RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT)]
|
|
#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)]
|
|
fn introduce_candidates_error(#[case] runtime_api_version: u32) {
|
|
let mut test_state = TestState::default();
|
|
test_state.set_runtime_api_version(runtime_api_version);
|
|
test_state.claim_queue.insert(
|
|
CoreIndex(2),
|
|
std::iter::repeat(1.into()).take(DEFAULT_SCHEDULING_LOOKAHEAD as _).collect(),
|
|
);
|
|
|
|
let view = test_harness(|mut virtual_overseer| async move {
|
|
// Leaf A
|
|
let leaf_a = TestLeaf {
|
|
number: 100,
|
|
hash: Default::default(),
|
|
para_data: vec![
|
|
(1.into(), PerParaData::new(98, HeadData(vec![1, 2, 3]))),
|
|
(2.into(), PerParaData::new(100, HeadData(vec![2, 3, 4]))),
|
|
],
|
|
};
|
|
|
|
// Activate leaves.
|
|
activate_leaf(&mut virtual_overseer, &leaf_a, &test_state).await;
|
|
|
|
// Candidate A.
|
|
let (candidate_a, pvd_a) = make_candidate(
|
|
leaf_a.hash,
|
|
leaf_a.number,
|
|
1.into(),
|
|
HeadData(vec![1, 2, 3]),
|
|
HeadData(vec![1]),
|
|
test_state.validation_code_hash,
|
|
);
|
|
|
|
// Candidate B.
|
|
let (candidate_b, pvd_b) = make_candidate(
|
|
leaf_a.hash,
|
|
leaf_a.number,
|
|
1.into(),
|
|
HeadData(vec![1]),
|
|
HeadData(vec![1; 20480]),
|
|
test_state.validation_code_hash,
|
|
);
|
|
|
|
// Candidate C commits to oversized head data.
|
|
let (candidate_c, pvd_c) = make_candidate(
|
|
leaf_a.hash,
|
|
leaf_a.number,
|
|
1.into(),
|
|
HeadData(vec![1; 20480]),
|
|
HeadData(vec![0; 20485]),
|
|
test_state.validation_code_hash,
|
|
);
|
|
|
|
// Get hypothetical membership of candidates before adding candidate A.
|
|
// Candidate A can be added directly, candidates B and C are potential candidates.
|
|
for (candidate, pvd) in
|
|
[(candidate_a.clone(), pvd_a.clone()), (candidate_b.clone(), pvd_b.clone())]
|
|
{
|
|
get_hypothetical_membership(
|
|
&mut virtual_overseer,
|
|
candidate.hash(),
|
|
candidate,
|
|
pvd,
|
|
vec![leaf_a.hash],
|
|
)
|
|
.await;
|
|
}
|
|
|
|
// Fails constraints check
|
|
get_hypothetical_membership(
|
|
&mut virtual_overseer,
|
|
candidate_c.hash(),
|
|
candidate_c.clone(),
|
|
pvd_c.clone(),
|
|
Vec::new(),
|
|
)
|
|
.await;
|
|
|
|
// Add candidates
|
|
introduce_seconded_candidate(&mut virtual_overseer, candidate_a.clone(), pvd_a.clone())
|
|
.await;
|
|
introduce_seconded_candidate(&mut virtual_overseer, candidate_b.clone(), pvd_b.clone())
|
|
.await;
|
|
// Fails constraints check
|
|
introduce_seconded_candidate_failed(
|
|
&mut virtual_overseer,
|
|
candidate_c.clone(),
|
|
pvd_c.clone(),
|
|
)
|
|
.await;
|
|
|
|
back_candidate(&mut virtual_overseer, &candidate_a, candidate_a.hash()).await;
|
|
back_candidate(&mut virtual_overseer, &candidate_b, candidate_b.hash()).await;
|
|
// This one will not be backed.
|
|
back_candidate(&mut virtual_overseer, &candidate_c, candidate_c.hash()).await;
|
|
|
|
// Expect only A and B to be backable
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
1.into(),
|
|
Ancestors::default(),
|
|
5,
|
|
vec![(candidate_a.hash(), leaf_a.hash), (candidate_b.hash(), leaf_a.hash)],
|
|
)
|
|
.await;
|
|
virtual_overseer
|
|
});
|
|
|
|
assert_eq!(view.active_leaves.len(), 1);
|
|
}
|
|
|
|
#[rstest]
|
|
#[case(RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT)]
|
|
#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)]
|
|
fn introduce_candidate_multiple_times(#[case] runtime_api_version: u32) {
|
|
let mut test_state = TestState::default();
|
|
test_state.set_runtime_api_version(runtime_api_version);
|
|
let view = test_harness(|mut virtual_overseer| async move {
|
|
// Leaf A
|
|
let leaf_a = TestLeaf {
|
|
number: 100,
|
|
hash: Hash::from_low_u64_be(130),
|
|
para_data: vec![
|
|
(1.into(), PerParaData::new(97, HeadData(vec![1, 2, 3]))),
|
|
(2.into(), PerParaData::new(100, HeadData(vec![2, 3, 4]))),
|
|
],
|
|
};
|
|
// Activate leaves.
|
|
activate_leaf(&mut virtual_overseer, &leaf_a, &test_state).await;
|
|
|
|
// Candidate A.
|
|
let (candidate_a, pvd_a) = make_candidate(
|
|
leaf_a.hash,
|
|
leaf_a.number,
|
|
1.into(),
|
|
HeadData(vec![1, 2, 3]),
|
|
HeadData(vec![1]),
|
|
test_state.validation_code_hash,
|
|
);
|
|
let candidate_hash_a = candidate_a.hash();
|
|
let response_a = vec![(candidate_hash_a, leaf_a.hash)];
|
|
|
|
// Introduce candidates.
|
|
introduce_seconded_candidate(&mut virtual_overseer, candidate_a.clone(), pvd_a.clone())
|
|
.await;
|
|
|
|
// Back candidates. Otherwise, we cannot check membership with GetBackableCandidates.
|
|
back_candidate(&mut virtual_overseer, &candidate_a, candidate_hash_a).await;
|
|
|
|
// Check candidate tree membership.
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
1.into(),
|
|
Ancestors::default(),
|
|
5,
|
|
response_a.clone(),
|
|
)
|
|
.await;
|
|
|
|
// Introduce the same candidate multiple times. It'll return true but it will only be added
|
|
// once.
|
|
for _ in 0..5 {
|
|
introduce_seconded_candidate(&mut virtual_overseer, candidate_a.clone(), pvd_a.clone())
|
|
.await;
|
|
}
|
|
|
|
// Check candidate tree membership.
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
1.into(),
|
|
Ancestors::default(),
|
|
5,
|
|
response_a,
|
|
)
|
|
.await;
|
|
|
|
virtual_overseer
|
|
});
|
|
|
|
assert_eq!(view.active_leaves.len(), 1);
|
|
}
|
|
|
|
#[test]
|
|
fn fragment_chain_best_chain_length_is_bounded() {
|
|
let mut test_state = TestState::default();
|
|
test_state.claim_queue.insert(
|
|
CoreIndex(2),
|
|
std::iter::repeat(1.into()).take(DEFAULT_SCHEDULING_LOOKAHEAD as _).collect(),
|
|
);
|
|
let view = test_harness(|mut virtual_overseer| async move {
|
|
// Leaf A
|
|
let leaf_a = TestLeaf {
|
|
number: 100,
|
|
hash: Hash::from_low_u64_be(130),
|
|
para_data: vec![
|
|
(1.into(), PerParaData::new(97, HeadData(vec![1, 2, 3]))),
|
|
(2.into(), PerParaData::new(100, HeadData(vec![2, 3, 4]))),
|
|
],
|
|
};
|
|
// Activate leaves.
|
|
activate_leaf(&mut virtual_overseer, &leaf_a, &test_state).await;
|
|
|
|
// Candidates A, B and C form a chain.
|
|
let (candidate_a, pvd_a) = make_candidate(
|
|
leaf_a.hash,
|
|
leaf_a.number,
|
|
1.into(),
|
|
HeadData(vec![1, 2, 3]),
|
|
HeadData(vec![1]),
|
|
test_state.validation_code_hash,
|
|
);
|
|
let (candidate_b, pvd_b) = make_candidate(
|
|
leaf_a.hash,
|
|
leaf_a.number,
|
|
1.into(),
|
|
HeadData(vec![1]),
|
|
HeadData(vec![2]),
|
|
test_state.validation_code_hash,
|
|
);
|
|
let (candidate_c, pvd_c) = make_candidate(
|
|
leaf_a.hash,
|
|
leaf_a.number,
|
|
1.into(),
|
|
HeadData(vec![2]),
|
|
HeadData(vec![3]),
|
|
test_state.validation_code_hash,
|
|
);
|
|
|
|
// Introduce candidates A and B. Since max depth is 2, only these two will be allowed.
|
|
introduce_seconded_candidate(&mut virtual_overseer, candidate_a.clone(), pvd_a).await;
|
|
introduce_seconded_candidate(&mut virtual_overseer, candidate_b.clone(), pvd_b).await;
|
|
|
|
// Back candidates. Otherwise, we cannot check membership with GetBackableCandidates and
|
|
// they won't be part of the best chain.
|
|
back_candidate(&mut virtual_overseer, &candidate_a, candidate_a.hash()).await;
|
|
back_candidate(&mut virtual_overseer, &candidate_b, candidate_b.hash()).await;
|
|
|
|
// Check candidate tree membership.
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
1.into(),
|
|
Ancestors::default(),
|
|
5,
|
|
vec![(candidate_a.hash(), leaf_a.hash), (candidate_b.hash(), leaf_a.hash)],
|
|
)
|
|
.await;
|
|
|
|
// Introducing C will not fail. It will be kept as unconnected storage.
|
|
introduce_seconded_candidate(&mut virtual_overseer, candidate_c.clone(), pvd_c).await;
|
|
// When being backed, candidate C will be dropped.
|
|
back_candidate(&mut virtual_overseer, &candidate_c, candidate_c.hash()).await;
|
|
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
1.into(),
|
|
Ancestors::default(),
|
|
5,
|
|
vec![(candidate_a.hash(), leaf_a.hash), (candidate_b.hash(), leaf_a.hash)],
|
|
)
|
|
.await;
|
|
|
|
virtual_overseer
|
|
});
|
|
|
|
assert_eq!(view.active_leaves.len(), 1);
|
|
}
|
|
|
|
// Send some candidates, check if the candidate won't be found once its relay parent leaves the
|
|
// view.
|
|
#[test]
|
|
fn introduce_candidate_parent_leaving_view() {
|
|
let test_state = TestState::default();
|
|
let view = test_harness(|mut virtual_overseer| async move {
|
|
// Leaf A
|
|
let leaf_a = TestLeaf {
|
|
number: 100,
|
|
hash: Hash::from_low_u64_be(130),
|
|
para_data: vec![
|
|
(1.into(), PerParaData::new(97, HeadData(vec![1, 2, 3]))),
|
|
(2.into(), PerParaData::new(100, HeadData(vec![2, 3, 4]))),
|
|
],
|
|
};
|
|
// Leaf B
|
|
let leaf_b = TestLeaf {
|
|
number: 101,
|
|
hash: Hash::from_low_u64_be(131),
|
|
para_data: vec![
|
|
(1.into(), PerParaData::new(99, HeadData(vec![3, 4, 5]))),
|
|
(2.into(), PerParaData::new(101, HeadData(vec![4, 5, 6]))),
|
|
],
|
|
};
|
|
// Leaf C
|
|
let leaf_c = TestLeaf {
|
|
number: 102,
|
|
hash: Hash::from_low_u64_be(132),
|
|
para_data: vec![
|
|
(1.into(), PerParaData::new(102, HeadData(vec![5, 6, 7]))),
|
|
(2.into(), PerParaData::new(98, HeadData(vec![6, 7, 8]))),
|
|
],
|
|
};
|
|
|
|
// Activate leaves.
|
|
activate_leaf(&mut virtual_overseer, &leaf_a, &test_state).await;
|
|
activate_leaf(&mut virtual_overseer, &leaf_b, &test_state).await;
|
|
activate_leaf(&mut virtual_overseer, &leaf_c, &test_state).await;
|
|
|
|
// Candidate A1
|
|
let (candidate_a1, pvd_a1) = make_candidate(
|
|
leaf_a.hash,
|
|
leaf_a.number,
|
|
1.into(),
|
|
HeadData(vec![1, 2, 3]),
|
|
HeadData(vec![1]),
|
|
test_state.validation_code_hash,
|
|
);
|
|
let candidate_hash_a1 = candidate_a1.hash();
|
|
|
|
// Candidate A2
|
|
let (candidate_a2, pvd_a2) = make_candidate(
|
|
leaf_a.hash,
|
|
leaf_a.number,
|
|
2.into(),
|
|
HeadData(vec![2, 3, 4]),
|
|
HeadData(vec![2]),
|
|
test_state.validation_code_hash,
|
|
);
|
|
let candidate_hash_a2 = candidate_a2.hash();
|
|
|
|
// Candidate B
|
|
let (candidate_b, pvd_b) = make_candidate(
|
|
leaf_b.hash,
|
|
leaf_b.number,
|
|
1.into(),
|
|
HeadData(vec![3, 4, 5]),
|
|
HeadData(vec![3]),
|
|
test_state.validation_code_hash,
|
|
);
|
|
let candidate_hash_b = candidate_b.hash();
|
|
let response_b = vec![(candidate_hash_b, leaf_b.hash)];
|
|
|
|
// Candidate C
|
|
let (candidate_c, pvd_c) = make_candidate(
|
|
leaf_c.hash,
|
|
leaf_c.number,
|
|
2.into(),
|
|
HeadData(vec![6, 7, 8]),
|
|
HeadData(vec![4]),
|
|
test_state.validation_code_hash,
|
|
);
|
|
let candidate_hash_c = candidate_c.hash();
|
|
let response_c = vec![(candidate_hash_c, leaf_c.hash)];
|
|
|
|
// Introduce candidates.
|
|
introduce_seconded_candidate(&mut virtual_overseer, candidate_a1.clone(), pvd_a1).await;
|
|
introduce_seconded_candidate(&mut virtual_overseer, candidate_a2.clone(), pvd_a2).await;
|
|
introduce_seconded_candidate(&mut virtual_overseer, candidate_b.clone(), pvd_b).await;
|
|
introduce_seconded_candidate(&mut virtual_overseer, candidate_c.clone(), pvd_c).await;
|
|
|
|
// Back candidates. Otherwise, we cannot check membership with GetBackableCandidates.
|
|
back_candidate(&mut virtual_overseer, &candidate_a1, candidate_hash_a1).await;
|
|
back_candidate(&mut virtual_overseer, &candidate_a2, candidate_hash_a2).await;
|
|
back_candidate(&mut virtual_overseer, &candidate_b, candidate_hash_b).await;
|
|
back_candidate(&mut virtual_overseer, &candidate_c, candidate_hash_c).await;
|
|
|
|
// Deactivate leaf A.
|
|
deactivate_leaf(&mut virtual_overseer, leaf_a.hash).await;
|
|
|
|
// Candidates A1 and A2 should be gone. Candidates B and C should remain.
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
1.into(),
|
|
Ancestors::default(),
|
|
5,
|
|
vec![],
|
|
)
|
|
.await;
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
2.into(),
|
|
Ancestors::default(),
|
|
5,
|
|
vec![],
|
|
)
|
|
.await;
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_b,
|
|
1.into(),
|
|
Ancestors::default(),
|
|
5,
|
|
response_b,
|
|
)
|
|
.await;
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_c,
|
|
2.into(),
|
|
Ancestors::default(),
|
|
5,
|
|
response_c.clone(),
|
|
)
|
|
.await;
|
|
|
|
// Deactivate leaf B.
|
|
deactivate_leaf(&mut virtual_overseer, leaf_b.hash).await;
|
|
|
|
// Candidate B should be gone, C should remain.
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
1.into(),
|
|
Ancestors::default(),
|
|
5,
|
|
vec![],
|
|
)
|
|
.await;
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
2.into(),
|
|
Ancestors::default(),
|
|
5,
|
|
vec![],
|
|
)
|
|
.await;
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_b,
|
|
1.into(),
|
|
Ancestors::default(),
|
|
5,
|
|
vec![],
|
|
)
|
|
.await;
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_c,
|
|
2.into(),
|
|
Ancestors::default(),
|
|
5,
|
|
response_c,
|
|
)
|
|
.await;
|
|
|
|
// Deactivate leaf C.
|
|
deactivate_leaf(&mut virtual_overseer, leaf_c.hash).await;
|
|
|
|
// Candidate C should be gone.
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
1.into(),
|
|
Ancestors::default(),
|
|
5,
|
|
vec![],
|
|
)
|
|
.await;
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
2.into(),
|
|
Ancestors::default(),
|
|
5,
|
|
vec![],
|
|
)
|
|
.await;
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_b,
|
|
1.into(),
|
|
Ancestors::default(),
|
|
5,
|
|
vec![],
|
|
)
|
|
.await;
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_c,
|
|
2.into(),
|
|
Ancestors::default(),
|
|
5,
|
|
vec![],
|
|
)
|
|
.await;
|
|
|
|
virtual_overseer
|
|
});
|
|
|
|
assert_eq!(view.active_leaves.len(), 0);
|
|
}
|
|
|
|
// Introduce a candidate to multiple forks, see how the membership is returned.
|
|
#[rstest]
|
|
#[case(RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT)]
|
|
#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)]
|
|
fn introduce_candidate_on_multiple_forks(#[case] runtime_api_version: u32) {
|
|
let mut test_state = TestState::default();
|
|
test_state.set_runtime_api_version(runtime_api_version);
|
|
let view = test_harness(|mut virtual_overseer| async move {
|
|
// Leaf B
|
|
let leaf_b = TestLeaf {
|
|
number: 101,
|
|
hash: Hash::from_low_u64_be(131),
|
|
para_data: vec![
|
|
(1.into(), PerParaData::new(99, HeadData(vec![1, 2, 3]))),
|
|
(2.into(), PerParaData::new(101, HeadData(vec![4, 5, 6]))),
|
|
],
|
|
};
|
|
// Leaf A
|
|
let leaf_a = TestLeaf {
|
|
number: 100,
|
|
hash: get_parent_hash(leaf_b.hash),
|
|
para_data: vec![
|
|
(1.into(), PerParaData::new(97, HeadData(vec![1, 2, 3]))),
|
|
(2.into(), PerParaData::new(100, HeadData(vec![2, 3, 4]))),
|
|
],
|
|
};
|
|
|
|
// Activate leaves.
|
|
activate_leaf(&mut virtual_overseer, &leaf_a, &test_state).await;
|
|
activate_leaf(&mut virtual_overseer, &leaf_b, &test_state).await;
|
|
|
|
// Candidate built on leaf A.
|
|
let (candidate_a, pvd_a) = make_candidate(
|
|
leaf_a.hash,
|
|
leaf_a.number,
|
|
1.into(),
|
|
HeadData(vec![1, 2, 3]),
|
|
HeadData(vec![1]),
|
|
test_state.validation_code_hash,
|
|
);
|
|
let candidate_hash_a = candidate_a.hash();
|
|
let response_a = vec![(candidate_hash_a, leaf_a.hash)];
|
|
|
|
// Introduce candidate. Should be present on leaves B and C.
|
|
introduce_seconded_candidate(&mut virtual_overseer, candidate_a.clone(), pvd_a).await;
|
|
back_candidate(&mut virtual_overseer, &candidate_a, candidate_hash_a).await;
|
|
|
|
// Check candidate tree membership.
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
1.into(),
|
|
Ancestors::default(),
|
|
5,
|
|
response_a.clone(),
|
|
)
|
|
.await;
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_b,
|
|
1.into(),
|
|
Ancestors::default(),
|
|
5,
|
|
response_a.clone(),
|
|
)
|
|
.await;
|
|
|
|
virtual_overseer
|
|
});
|
|
|
|
assert_eq!(view.active_leaves.len(), 2);
|
|
}
|
|
|
|
#[rstest]
|
|
#[case(RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT)]
|
|
#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)]
|
|
fn unconnected_candidates_become_connected(#[case] runtime_api_version: u32) {
|
|
// This doesn't test all the complicated cases with many unconnected candidates, as it's more
|
|
// extensively tested in the `fragment_chain::tests` module.
|
|
let mut test_state = TestState::default();
|
|
for i in 2..=4 {
|
|
test_state.claim_queue.insert(
|
|
CoreIndex(i),
|
|
std::iter::repeat(1.into()).take(DEFAULT_SCHEDULING_LOOKAHEAD as _).collect(),
|
|
);
|
|
}
|
|
|
|
test_state.set_runtime_api_version(runtime_api_version);
|
|
let view = test_harness(|mut virtual_overseer| async move {
|
|
// Leaf A
|
|
let leaf_a = TestLeaf {
|
|
number: 100,
|
|
hash: Hash::from_low_u64_be(130),
|
|
para_data: vec![
|
|
(1.into(), PerParaData::new(97, HeadData(vec![1, 2, 3]))),
|
|
(2.into(), PerParaData::new(100, HeadData(vec![2, 3, 4]))),
|
|
],
|
|
};
|
|
// Activate leaves.
|
|
activate_leaf(&mut virtual_overseer, &leaf_a, &test_state).await;
|
|
|
|
// Candidates A, B, C and D all form a chain, but we'll first introduce A, C and D.
|
|
let (candidate_a, pvd_a) = make_candidate(
|
|
leaf_a.hash,
|
|
leaf_a.number,
|
|
1.into(),
|
|
HeadData(vec![1, 2, 3]),
|
|
HeadData(vec![1]),
|
|
test_state.validation_code_hash,
|
|
);
|
|
let (candidate_b, pvd_b) = make_candidate(
|
|
leaf_a.hash,
|
|
leaf_a.number,
|
|
1.into(),
|
|
HeadData(vec![1]),
|
|
HeadData(vec![2]),
|
|
test_state.validation_code_hash,
|
|
);
|
|
let (candidate_c, pvd_c) = make_candidate(
|
|
leaf_a.hash,
|
|
leaf_a.number,
|
|
1.into(),
|
|
HeadData(vec![2]),
|
|
HeadData(vec![3]),
|
|
test_state.validation_code_hash,
|
|
);
|
|
let (candidate_d, pvd_d) = make_candidate(
|
|
leaf_a.hash,
|
|
leaf_a.number,
|
|
1.into(),
|
|
HeadData(vec![3]),
|
|
HeadData(vec![4]),
|
|
test_state.validation_code_hash,
|
|
);
|
|
|
|
// Introduce candidates A, C and D.
|
|
introduce_seconded_candidate(&mut virtual_overseer, candidate_a.clone(), pvd_a.clone())
|
|
.await;
|
|
introduce_seconded_candidate(&mut virtual_overseer, candidate_c.clone(), pvd_c.clone())
|
|
.await;
|
|
introduce_seconded_candidate(&mut virtual_overseer, candidate_d.clone(), pvd_d.clone())
|
|
.await;
|
|
|
|
// Back candidates. Otherwise, we cannot check membership with GetBackableCandidates.
|
|
back_candidate(&mut virtual_overseer, &candidate_a, candidate_a.hash()).await;
|
|
back_candidate(&mut virtual_overseer, &candidate_c, candidate_c.hash()).await;
|
|
back_candidate(&mut virtual_overseer, &candidate_d, candidate_d.hash()).await;
|
|
|
|
// Check candidate tree membership. Only A should be returned.
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
1.into(),
|
|
Ancestors::default(),
|
|
5,
|
|
vec![(candidate_a.hash(), leaf_a.hash)],
|
|
)
|
|
.await;
|
|
|
|
// Introduce C and check membership. Full chain should be returned.
|
|
introduce_seconded_candidate(&mut virtual_overseer, candidate_b.clone(), pvd_b.clone())
|
|
.await;
|
|
back_candidate(&mut virtual_overseer, &candidate_b, candidate_b.hash()).await;
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
1.into(),
|
|
Ancestors::default(),
|
|
5,
|
|
vec![
|
|
(candidate_a.hash(), leaf_a.hash),
|
|
(candidate_b.hash(), leaf_a.hash),
|
|
(candidate_c.hash(), leaf_a.hash),
|
|
(candidate_d.hash(), leaf_a.hash),
|
|
],
|
|
)
|
|
.await;
|
|
|
|
virtual_overseer
|
|
});
|
|
|
|
assert_eq!(view.active_leaves.len(), 1);
|
|
}
|
|
|
|
// Backs some candidates and tests `GetBackableCandidates` when requesting a single candidate.
|
|
#[test]
|
|
fn check_backable_query_single_candidate() {
|
|
let mut test_state = TestState::default();
|
|
test_state.claim_queue.insert(
|
|
CoreIndex(2),
|
|
std::iter::repeat(1.into()).take(DEFAULT_SCHEDULING_LOOKAHEAD as _).collect(),
|
|
);
|
|
let view = test_harness(|mut virtual_overseer| async move {
|
|
// Leaf A
|
|
let leaf_a = TestLeaf {
|
|
number: 100,
|
|
hash: Hash::from_low_u64_be(130),
|
|
para_data: vec![
|
|
(1.into(), PerParaData::new(97, HeadData(vec![1, 2, 3]))),
|
|
(2.into(), PerParaData::new(100, HeadData(vec![2, 3, 4]))),
|
|
],
|
|
};
|
|
|
|
// Activate leaves.
|
|
activate_leaf(&mut virtual_overseer, &leaf_a, &test_state).await;
|
|
|
|
// Candidate A
|
|
let (candidate_a, pvd_a) = make_candidate(
|
|
leaf_a.hash,
|
|
leaf_a.number,
|
|
1.into(),
|
|
HeadData(vec![1, 2, 3]),
|
|
HeadData(vec![1]),
|
|
test_state.validation_code_hash,
|
|
);
|
|
let candidate_hash_a = candidate_a.hash();
|
|
|
|
// Candidate B
|
|
let (mut candidate_b, pvd_b) = make_candidate(
|
|
leaf_a.hash,
|
|
leaf_a.number,
|
|
1.into(),
|
|
HeadData(vec![1]),
|
|
HeadData(vec![2]),
|
|
test_state.validation_code_hash,
|
|
);
|
|
// Set a field to make this candidate unique.
|
|
candidate_b.descriptor.set_para_head(Hash::from_low_u64_le(1000));
|
|
let candidate_hash_b = candidate_b.hash();
|
|
|
|
// Introduce candidates.
|
|
introduce_seconded_candidate(&mut virtual_overseer, candidate_a.clone(), pvd_a).await;
|
|
introduce_seconded_candidate(&mut virtual_overseer, candidate_b.clone(), pvd_b).await;
|
|
|
|
// Should not get any backable candidates.
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
1.into(),
|
|
vec![candidate_hash_a].into_iter().collect(),
|
|
1,
|
|
vec![],
|
|
)
|
|
.await;
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
1.into(),
|
|
vec![candidate_hash_a].into_iter().collect(),
|
|
0,
|
|
vec![],
|
|
)
|
|
.await;
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
1.into(),
|
|
Ancestors::new(),
|
|
0,
|
|
vec![],
|
|
)
|
|
.await;
|
|
|
|
// Back candidates.
|
|
back_candidate(&mut virtual_overseer, &candidate_a, candidate_hash_a).await;
|
|
back_candidate(&mut virtual_overseer, &candidate_b, candidate_hash_b).await;
|
|
|
|
// Back an unknown candidate. It doesn't return anything but it's ignored. Will not have any
|
|
// effect on the backable candidates.
|
|
back_candidate(&mut virtual_overseer, &candidate_b, CandidateHash(Hash::random())).await;
|
|
|
|
// Should not get any backable candidates for the other para.
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
2.into(),
|
|
Ancestors::new(),
|
|
1,
|
|
vec![],
|
|
)
|
|
.await;
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
2.into(),
|
|
vec![candidate_hash_a].into_iter().collect(),
|
|
1,
|
|
vec![],
|
|
)
|
|
.await;
|
|
|
|
// Get backable candidate.
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
1.into(),
|
|
Ancestors::new(),
|
|
1,
|
|
vec![(candidate_hash_a, leaf_a.hash)],
|
|
)
|
|
.await;
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
1.into(),
|
|
vec![candidate_hash_a].into_iter().collect(),
|
|
1,
|
|
vec![(candidate_hash_b, leaf_a.hash)],
|
|
)
|
|
.await;
|
|
|
|
// Wrong path
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
1.into(),
|
|
vec![candidate_hash_b].into_iter().collect(),
|
|
1,
|
|
vec![(candidate_hash_a, leaf_a.hash)],
|
|
)
|
|
.await;
|
|
|
|
virtual_overseer
|
|
});
|
|
|
|
assert_eq!(view.active_leaves.len(), 1);
|
|
}
|
|
|
|
// Backs some candidates and tests `GetBackableCandidates` when requesting a multiple candidates.
|
|
#[rstest]
|
|
#[case(RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT)]
|
|
#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)]
|
|
|
|
fn check_backable_query_multiple_candidates(#[case] runtime_api_version: u32) {
|
|
// This doesn't test all the complicated cases with many unconnected candidates, as it's more
|
|
// extensively tested in the `fragment_chain::tests` module.
|
|
let mut test_state = TestState::default();
|
|
test_state.set_runtime_api_version(runtime_api_version);
|
|
// Add three more cores for para A, so that we can get a chain of max length 4
|
|
for i in 2..=4 {
|
|
test_state.claim_queue.insert(
|
|
CoreIndex(i),
|
|
std::iter::repeat(1.into()).take(DEFAULT_SCHEDULING_LOOKAHEAD as _).collect(),
|
|
);
|
|
}
|
|
|
|
let view = test_harness(|mut virtual_overseer| async move {
|
|
// Leaf A
|
|
let leaf_a = TestLeaf {
|
|
number: 100,
|
|
hash: Hash::from_low_u64_be(130),
|
|
para_data: vec![
|
|
(1.into(), PerParaData::new(97, HeadData(vec![1, 2, 3]))),
|
|
(2.into(), PerParaData::new(100, HeadData(vec![2, 3, 4]))),
|
|
],
|
|
};
|
|
|
|
// Activate leaves.
|
|
activate_leaf(&mut virtual_overseer, &leaf_a, &test_state).await;
|
|
|
|
// Candidate A
|
|
let (candidate_a, pvd_a) = make_candidate(
|
|
leaf_a.hash,
|
|
leaf_a.number,
|
|
1.into(),
|
|
HeadData(vec![1, 2, 3]),
|
|
HeadData(vec![1]),
|
|
test_state.validation_code_hash,
|
|
);
|
|
let candidate_hash_a = candidate_a.hash();
|
|
introduce_seconded_candidate(&mut virtual_overseer, candidate_a.clone(), pvd_a).await;
|
|
back_candidate(&mut virtual_overseer, &candidate_a, candidate_hash_a).await;
|
|
|
|
let (candidate_b, candidate_hash_b) =
|
|
make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_a, 2);
|
|
let (candidate_c, candidate_hash_c) =
|
|
make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_b, 3);
|
|
let (_candidate_d, candidate_hash_d) =
|
|
make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_c, 4);
|
|
|
|
// Should not get any backable candidates for the other para.
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
2.into(),
|
|
Ancestors::new(),
|
|
1,
|
|
vec![],
|
|
)
|
|
.await;
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
2.into(),
|
|
Ancestors::new(),
|
|
5,
|
|
vec![],
|
|
)
|
|
.await;
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
2.into(),
|
|
vec![candidate_hash_a].into_iter().collect(),
|
|
1,
|
|
vec![],
|
|
)
|
|
.await;
|
|
|
|
// Test various scenarios with various counts.
|
|
|
|
// empty ancestors
|
|
{
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
1.into(),
|
|
Ancestors::new(),
|
|
1,
|
|
vec![(candidate_hash_a, leaf_a.hash)],
|
|
)
|
|
.await;
|
|
for count in 4..10 {
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
1.into(),
|
|
Ancestors::new(),
|
|
count,
|
|
vec![
|
|
(candidate_hash_a, leaf_a.hash),
|
|
(candidate_hash_b, leaf_a.hash),
|
|
(candidate_hash_c, leaf_a.hash),
|
|
(candidate_hash_d, leaf_a.hash),
|
|
],
|
|
)
|
|
.await;
|
|
}
|
|
}
|
|
|
|
// ancestors of size 1
|
|
{
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
1.into(),
|
|
vec![candidate_hash_a].into_iter().collect(),
|
|
1,
|
|
vec![(candidate_hash_b, leaf_a.hash)],
|
|
)
|
|
.await;
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
1.into(),
|
|
vec![candidate_hash_a].into_iter().collect(),
|
|
2,
|
|
vec![(candidate_hash_b, leaf_a.hash), (candidate_hash_c, leaf_a.hash)],
|
|
)
|
|
.await;
|
|
|
|
// If the requested count exceeds the largest chain, return the longest
|
|
// chain we can get.
|
|
for count in 3..10 {
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
1.into(),
|
|
vec![candidate_hash_a].into_iter().collect(),
|
|
count,
|
|
vec![
|
|
(candidate_hash_b, leaf_a.hash),
|
|
(candidate_hash_c, leaf_a.hash),
|
|
(candidate_hash_d, leaf_a.hash),
|
|
],
|
|
)
|
|
.await;
|
|
}
|
|
}
|
|
|
|
// ancestor count 2 and higher
|
|
{
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
1.into(),
|
|
vec![candidate_hash_a, candidate_hash_b, candidate_hash_c].into_iter().collect(),
|
|
1,
|
|
vec![(candidate_hash_d, leaf_a.hash)],
|
|
)
|
|
.await;
|
|
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
1.into(),
|
|
vec![candidate_hash_a, candidate_hash_b].into_iter().collect(),
|
|
1,
|
|
vec![(candidate_hash_c, leaf_a.hash)],
|
|
)
|
|
.await;
|
|
|
|
// If the requested count exceeds the largest chain, return the longest
|
|
// chain we can get.
|
|
for count in 3..10 {
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
1.into(),
|
|
vec![candidate_hash_a, candidate_hash_b].into_iter().collect(),
|
|
count,
|
|
vec![(candidate_hash_c, leaf_a.hash), (candidate_hash_d, leaf_a.hash)],
|
|
)
|
|
.await;
|
|
}
|
|
}
|
|
|
|
// No more candidates in the chain.
|
|
for count in 1..4 {
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
1.into(),
|
|
vec![candidate_hash_a, candidate_hash_b, candidate_hash_c, candidate_hash_d]
|
|
.into_iter()
|
|
.collect(),
|
|
count,
|
|
vec![],
|
|
)
|
|
.await;
|
|
}
|
|
|
|
// Wrong paths.
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
1.into(),
|
|
vec![candidate_hash_b].into_iter().collect(),
|
|
1,
|
|
vec![(candidate_hash_a, leaf_a.hash)],
|
|
)
|
|
.await;
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
1.into(),
|
|
vec![candidate_hash_b, candidate_hash_c].into_iter().collect(),
|
|
3,
|
|
vec![
|
|
(candidate_hash_a, leaf_a.hash),
|
|
(candidate_hash_b, leaf_a.hash),
|
|
(candidate_hash_c, leaf_a.hash),
|
|
],
|
|
)
|
|
.await;
|
|
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
1.into(),
|
|
vec![candidate_hash_a, candidate_hash_c, candidate_hash_d].into_iter().collect(),
|
|
2,
|
|
vec![(candidate_hash_b, leaf_a.hash), (candidate_hash_c, leaf_a.hash)],
|
|
)
|
|
.await;
|
|
|
|
// Non-existent candidate.
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
1.into(),
|
|
vec![candidate_hash_a, CandidateHash(Hash::from_low_u64_be(100))]
|
|
.into_iter()
|
|
.collect(),
|
|
2,
|
|
vec![(candidate_hash_b, leaf_a.hash), (candidate_hash_c, leaf_a.hash)],
|
|
)
|
|
.await;
|
|
|
|
// Requested count is zero.
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
1.into(),
|
|
Ancestors::new(),
|
|
0,
|
|
vec![],
|
|
)
|
|
.await;
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
1.into(),
|
|
vec![candidate_hash_a].into_iter().collect(),
|
|
0,
|
|
vec![],
|
|
)
|
|
.await;
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
1.into(),
|
|
vec![candidate_hash_a, candidate_hash_b].into_iter().collect(),
|
|
0,
|
|
vec![],
|
|
)
|
|
.await;
|
|
|
|
virtual_overseer
|
|
});
|
|
|
|
assert_eq!(view.active_leaves.len(), 1);
|
|
}
|
|
|
|
// Test hypothetical membership query.
|
|
#[rstest]
|
|
#[case(RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT)]
|
|
#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)]
|
|
fn check_hypothetical_membership_query(#[case] runtime_api_version: u32) {
|
|
let mut test_state = TestState::default();
|
|
test_state.set_runtime_api_version(runtime_api_version);
|
|
|
|
let view = test_harness(|mut virtual_overseer| async move {
|
|
// Leaf B
|
|
let leaf_b = TestLeaf {
|
|
number: 101,
|
|
hash: Hash::from_low_u64_be(131),
|
|
para_data: vec![
|
|
(1.into(), PerParaData::new(97, HeadData(vec![1, 2, 3]))),
|
|
(2.into(), PerParaData::new(100, HeadData(vec![2, 3, 4]))),
|
|
],
|
|
};
|
|
// Leaf A
|
|
let leaf_a = TestLeaf {
|
|
number: 100,
|
|
hash: get_parent_hash(leaf_b.hash),
|
|
para_data: vec![
|
|
(1.into(), PerParaData::new(98, HeadData(vec![1, 2, 3]))),
|
|
(2.into(), PerParaData::new(100, HeadData(vec![2, 3, 4]))),
|
|
],
|
|
};
|
|
|
|
// Activate leaves.
|
|
activate_leaf(&mut virtual_overseer, &leaf_a, &test_state).await;
|
|
activate_leaf(&mut virtual_overseer, &leaf_b, &test_state).await;
|
|
|
|
// Candidates will be valid on both leaves.
|
|
|
|
// Candidate A.
|
|
let (candidate_a, pvd_a) = make_candidate(
|
|
leaf_a.hash,
|
|
leaf_a.number,
|
|
1.into(),
|
|
HeadData(vec![1, 2, 3]),
|
|
HeadData(vec![1]),
|
|
test_state.validation_code_hash,
|
|
);
|
|
|
|
// Candidate B.
|
|
let (candidate_b, pvd_b) = make_candidate(
|
|
leaf_a.hash,
|
|
leaf_a.number,
|
|
1.into(),
|
|
HeadData(vec![1]),
|
|
HeadData(vec![2]),
|
|
test_state.validation_code_hash,
|
|
);
|
|
|
|
// Candidate C.
|
|
let (candidate_c, pvd_c) = make_candidate(
|
|
leaf_a.hash,
|
|
leaf_a.number,
|
|
1.into(),
|
|
HeadData(vec![2]),
|
|
HeadData(vec![3]),
|
|
test_state.validation_code_hash,
|
|
);
|
|
|
|
// Get hypothetical membership of candidates before adding candidate A.
|
|
// Candidate A can be added directly, candidates B and C are potential candidates.
|
|
for (candidate, pvd) in [
|
|
(candidate_a.clone(), pvd_a.clone()),
|
|
(candidate_b.clone(), pvd_b.clone()),
|
|
(candidate_c.clone(), pvd_c.clone()),
|
|
] {
|
|
get_hypothetical_membership(
|
|
&mut virtual_overseer,
|
|
candidate.hash(),
|
|
candidate,
|
|
pvd,
|
|
vec![leaf_a.hash, leaf_b.hash],
|
|
)
|
|
.await;
|
|
}
|
|
|
|
// Add candidate A.
|
|
introduce_seconded_candidate(&mut virtual_overseer, candidate_a.clone(), pvd_a.clone())
|
|
.await;
|
|
|
|
// Get membership of candidates after adding A. They all are still unconnected candidates
|
|
// (not part of the best backable chain).
|
|
for (candidate, pvd) in [
|
|
(candidate_a.clone(), pvd_a.clone()),
|
|
(candidate_b.clone(), pvd_b.clone()),
|
|
(candidate_c.clone(), pvd_c.clone()),
|
|
] {
|
|
get_hypothetical_membership(
|
|
&mut virtual_overseer,
|
|
candidate.hash(),
|
|
candidate,
|
|
pvd,
|
|
vec![leaf_a.hash, leaf_b.hash],
|
|
)
|
|
.await;
|
|
}
|
|
|
|
// Back A. Now A is part of the best chain the rest can be added as unconnected.
|
|
|
|
back_candidate(&mut virtual_overseer, &candidate_a, candidate_a.hash()).await;
|
|
|
|
for (candidate, pvd) in [
|
|
(candidate_a.clone(), pvd_a.clone()),
|
|
(candidate_b.clone(), pvd_b.clone()),
|
|
(candidate_c.clone(), pvd_c.clone()),
|
|
] {
|
|
get_hypothetical_membership(
|
|
&mut virtual_overseer,
|
|
candidate.hash(),
|
|
candidate,
|
|
pvd,
|
|
vec![leaf_a.hash, leaf_b.hash],
|
|
)
|
|
.await;
|
|
}
|
|
|
|
// Candidate D has invalid relay parent.
|
|
let (candidate_d, pvd_d) = make_candidate(
|
|
Hash::from_low_u64_be(200),
|
|
leaf_a.number,
|
|
1.into(),
|
|
HeadData(vec![1]),
|
|
HeadData(vec![2]),
|
|
test_state.validation_code_hash,
|
|
);
|
|
introduce_seconded_candidate_failed(&mut virtual_overseer, candidate_d, pvd_d).await;
|
|
|
|
// Candidate E has invalid head data.
|
|
let (candidate_e, pvd_e) = make_candidate(
|
|
leaf_a.hash,
|
|
leaf_a.number,
|
|
1.into(),
|
|
HeadData(vec![2]),
|
|
HeadData(vec![0; 20481]),
|
|
test_state.validation_code_hash,
|
|
);
|
|
introduce_seconded_candidate_failed(&mut virtual_overseer, candidate_e, pvd_e).await;
|
|
|
|
// Add candidate B and back it.
|
|
introduce_seconded_candidate(&mut virtual_overseer, candidate_b.clone(), pvd_b.clone())
|
|
.await;
|
|
back_candidate(&mut virtual_overseer, &candidate_b, candidate_b.hash()).await;
|
|
|
|
// Get membership of candidates after adding B.
|
|
for (candidate, pvd) in [
|
|
(candidate_a.clone(), pvd_a.clone()),
|
|
(candidate_b.clone(), pvd_b.clone()),
|
|
(candidate_c.clone(), pvd_c.clone()),
|
|
] {
|
|
get_hypothetical_membership(
|
|
&mut virtual_overseer,
|
|
candidate.hash(),
|
|
candidate,
|
|
pvd,
|
|
vec![leaf_a.hash, leaf_b.hash],
|
|
)
|
|
.await;
|
|
}
|
|
|
|
virtual_overseer
|
|
});
|
|
|
|
assert_eq!(view.active_leaves.len(), 2);
|
|
}
|
|
|
|
#[rstest]
|
|
#[case(RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT)]
|
|
#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)]
|
|
fn check_pvd_query(#[case] runtime_api_version: u32) {
|
|
// This doesn't test all the complicated cases with many unconnected candidates, as it's more
|
|
// extensively tested in the `fragment_chain::tests` module.
|
|
let mut test_state = TestState::default();
|
|
test_state.set_runtime_api_version(runtime_api_version);
|
|
let view = test_harness(|mut virtual_overseer| async move {
|
|
// Leaf A
|
|
let leaf_a = TestLeaf {
|
|
number: 100,
|
|
hash: Hash::from_low_u64_be(130),
|
|
para_data: vec![
|
|
(1.into(), PerParaData::new(97, HeadData(vec![1, 2, 3]))),
|
|
(2.into(), PerParaData::new(100, HeadData(vec![2, 3, 4]))),
|
|
],
|
|
};
|
|
|
|
// Activate leaves.
|
|
activate_leaf(&mut virtual_overseer, &leaf_a, &test_state).await;
|
|
|
|
// Candidate A.
|
|
let (candidate_a, pvd_a) = make_candidate(
|
|
leaf_a.hash,
|
|
leaf_a.number,
|
|
1.into(),
|
|
HeadData(vec![1, 2, 3]),
|
|
HeadData(vec![1]),
|
|
test_state.validation_code_hash,
|
|
);
|
|
|
|
// Candidate B.
|
|
let (candidate_b, pvd_b) = make_candidate(
|
|
leaf_a.hash,
|
|
leaf_a.number,
|
|
1.into(),
|
|
HeadData(vec![1]),
|
|
HeadData(vec![2]),
|
|
test_state.validation_code_hash,
|
|
);
|
|
|
|
// Candidate C.
|
|
let (candidate_c, pvd_c) = make_candidate(
|
|
leaf_a.hash,
|
|
leaf_a.number,
|
|
1.into(),
|
|
HeadData(vec![2]),
|
|
HeadData(vec![3]),
|
|
test_state.validation_code_hash,
|
|
);
|
|
|
|
// Candidate E.
|
|
let (candidate_e, pvd_e) = make_candidate(
|
|
leaf_a.hash,
|
|
leaf_a.number,
|
|
1.into(),
|
|
HeadData(vec![5]),
|
|
HeadData(vec![6]),
|
|
test_state.validation_code_hash,
|
|
);
|
|
|
|
// Get pvd of candidate A before adding it.
|
|
get_pvd(
|
|
&mut virtual_overseer,
|
|
1.into(),
|
|
leaf_a.hash,
|
|
HeadData(vec![1, 2, 3]),
|
|
Some(pvd_a.clone()),
|
|
)
|
|
.await;
|
|
|
|
// Add candidate A.
|
|
introduce_seconded_candidate(&mut virtual_overseer, candidate_a.clone(), pvd_a.clone())
|
|
.await;
|
|
back_candidate(&mut virtual_overseer, &candidate_a, candidate_a.hash()).await;
|
|
|
|
// Get pvd of candidate A after adding it.
|
|
get_pvd(
|
|
&mut virtual_overseer,
|
|
1.into(),
|
|
leaf_a.hash,
|
|
HeadData(vec![1, 2, 3]),
|
|
Some(pvd_a.clone()),
|
|
)
|
|
.await;
|
|
|
|
// Get pvd of candidate B before adding it.
|
|
get_pvd(
|
|
&mut virtual_overseer,
|
|
1.into(),
|
|
leaf_a.hash,
|
|
HeadData(vec![1]),
|
|
Some(pvd_b.clone()),
|
|
)
|
|
.await;
|
|
|
|
// Add candidate B.
|
|
introduce_seconded_candidate(&mut virtual_overseer, candidate_b, pvd_b.clone()).await;
|
|
|
|
// Get pvd of candidate B after adding it.
|
|
get_pvd(
|
|
&mut virtual_overseer,
|
|
1.into(),
|
|
leaf_a.hash,
|
|
HeadData(vec![1]),
|
|
Some(pvd_b.clone()),
|
|
)
|
|
.await;
|
|
|
|
// Get pvd of candidate C before adding it.
|
|
get_pvd(
|
|
&mut virtual_overseer,
|
|
1.into(),
|
|
leaf_a.hash,
|
|
HeadData(vec![2]),
|
|
Some(pvd_c.clone()),
|
|
)
|
|
.await;
|
|
|
|
// Add candidate C.
|
|
introduce_seconded_candidate(&mut virtual_overseer, candidate_c, pvd_c.clone()).await;
|
|
|
|
// Get pvd of candidate C after adding it.
|
|
get_pvd(&mut virtual_overseer, 1.into(), leaf_a.hash, HeadData(vec![2]), Some(pvd_c)).await;
|
|
|
|
// Get pvd of candidate E before adding it. It won't be found, as we don't have its parent.
|
|
get_pvd(&mut virtual_overseer, 1.into(), leaf_a.hash, HeadData(vec![5]), None).await;
|
|
|
|
// Add candidate E and check again. Should succeed this time.
|
|
introduce_seconded_candidate(&mut virtual_overseer, candidate_e, pvd_e.clone()).await;
|
|
|
|
get_pvd(&mut virtual_overseer, 1.into(), leaf_a.hash, HeadData(vec![5]), Some(pvd_e)).await;
|
|
|
|
virtual_overseer
|
|
});
|
|
|
|
assert_eq!(view.active_leaves.len(), 1);
|
|
}
|
|
|
|
// Test simultaneously activating and deactivating leaves, and simultaneously deactivating
|
|
// multiple leaves.
|
|
#[test]
|
|
fn correctly_updates_leaves() {
|
|
let test_state = TestState::default();
|
|
|
|
let view = test_harness(|mut virtual_overseer| async move {
|
|
// Leaf A
|
|
let leaf_a = TestLeaf {
|
|
number: 100,
|
|
hash: Hash::from_low_u64_be(130),
|
|
para_data: vec![
|
|
(1.into(), PerParaData::new(97, HeadData(vec![1, 2, 3]))),
|
|
(2.into(), PerParaData::new(100, HeadData(vec![2, 3, 4]))),
|
|
],
|
|
};
|
|
// Leaf B
|
|
let leaf_b = TestLeaf {
|
|
number: 101,
|
|
hash: Hash::from_low_u64_be(131),
|
|
para_data: vec![
|
|
(1.into(), PerParaData::new(99, HeadData(vec![3, 4, 5]))),
|
|
(2.into(), PerParaData::new(101, HeadData(vec![4, 5, 6]))),
|
|
],
|
|
};
|
|
// Leaf C
|
|
let leaf_c = TestLeaf {
|
|
number: 102,
|
|
hash: Hash::from_low_u64_be(132),
|
|
para_data: vec![
|
|
(1.into(), PerParaData::new(102, HeadData(vec![5, 6, 7]))),
|
|
(2.into(), PerParaData::new(98, HeadData(vec![6, 7, 8]))),
|
|
],
|
|
};
|
|
|
|
// Activate leaves.
|
|
activate_leaf(&mut virtual_overseer, &leaf_a, &test_state).await;
|
|
|
|
activate_leaf(&mut virtual_overseer, &leaf_b, &test_state).await;
|
|
|
|
// Try activating a duplicate leaf.
|
|
activate_leaf(&mut virtual_overseer, &leaf_b, &test_state).await;
|
|
|
|
// Pass in an empty update.
|
|
let update = ActiveLeavesUpdate::default();
|
|
virtual_overseer
|
|
.send(FromOrchestra::Signal(OverseerSignal::ActiveLeaves(update)))
|
|
.await;
|
|
|
|
// Activate a leaf and remove one at the same time.
|
|
let activated = new_leaf(leaf_c.hash, leaf_c.number);
|
|
let update = ActiveLeavesUpdate {
|
|
activated: Some(activated),
|
|
deactivated: [leaf_b.hash][..].into(),
|
|
};
|
|
virtual_overseer
|
|
.send(FromOrchestra::Signal(OverseerSignal::ActiveLeaves(update)))
|
|
.await;
|
|
handle_leaf_activation(&mut virtual_overseer, &leaf_c, &test_state, get_parent_hash).await;
|
|
|
|
// Remove all remaining leaves.
|
|
let update = ActiveLeavesUpdate {
|
|
deactivated: [leaf_a.hash, leaf_c.hash][..].into(),
|
|
..Default::default()
|
|
};
|
|
virtual_overseer
|
|
.send(FromOrchestra::Signal(OverseerSignal::ActiveLeaves(update)))
|
|
.await;
|
|
|
|
// Activate and deactivate the same leaf.
|
|
let activated = new_leaf(leaf_a.hash, leaf_a.number);
|
|
let update = ActiveLeavesUpdate {
|
|
activated: Some(activated),
|
|
deactivated: [leaf_a.hash][..].into(),
|
|
};
|
|
virtual_overseer
|
|
.send(FromOrchestra::Signal(OverseerSignal::ActiveLeaves(update)))
|
|
.await;
|
|
|
|
// Remove the leaf again. Send some unnecessary hashes.
|
|
let update = ActiveLeavesUpdate {
|
|
deactivated: [leaf_a.hash, leaf_b.hash, leaf_c.hash][..].into(),
|
|
..Default::default()
|
|
};
|
|
virtual_overseer
|
|
.send(FromOrchestra::Signal(OverseerSignal::ActiveLeaves(update)))
|
|
.await;
|
|
|
|
virtual_overseer
|
|
});
|
|
|
|
assert_eq!(view.active_leaves.len(), 0);
|
|
}
|
|
|
|
#[rstest]
|
|
#[case(RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT)]
|
|
#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)]
|
|
fn handle_active_leaves_update_gets_candidates_from_parent(#[case] runtime_api_version: u32) {
|
|
let para_id = ParaId::from(1);
|
|
|
|
// This doesn't test all the complicated cases with many unconnected candidates, as it's more
|
|
// extensively tested in the `fragment_chain::tests` module.
|
|
let mut test_state = TestState::default();
|
|
test_state.set_runtime_api_version(runtime_api_version);
|
|
|
|
test_state.claim_queue = BTreeMap::new();
|
|
for i in 0..=4 {
|
|
test_state.claim_queue.insert(
|
|
CoreIndex(i),
|
|
std::iter::repeat(para_id).take(DEFAULT_SCHEDULING_LOOKAHEAD as _).collect(),
|
|
);
|
|
}
|
|
|
|
let view = test_harness(|mut virtual_overseer| async move {
|
|
// Leaf A
|
|
let leaf_a = TestLeaf {
|
|
number: 100,
|
|
hash: Hash::from_low_u64_be(130),
|
|
para_data: vec![(para_id, PerParaData::new(97, HeadData(vec![1, 2, 3])))],
|
|
};
|
|
// Activate leaf A.
|
|
activate_leaf(&mut virtual_overseer, &leaf_a, &test_state).await;
|
|
|
|
// Candidates A, B, C and D all form a chain
|
|
let (candidate_a, pvd_a) = make_candidate(
|
|
leaf_a.hash,
|
|
leaf_a.number,
|
|
para_id,
|
|
HeadData(vec![1, 2, 3]),
|
|
HeadData(vec![1]),
|
|
test_state.validation_code_hash,
|
|
);
|
|
let candidate_hash_a = candidate_a.hash();
|
|
introduce_seconded_candidate(&mut virtual_overseer, candidate_a.clone(), pvd_a).await;
|
|
back_candidate(&mut virtual_overseer, &candidate_a, candidate_hash_a).await;
|
|
|
|
let (candidate_b, candidate_hash_b) =
|
|
make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_a, 2);
|
|
let (candidate_c, candidate_hash_c) =
|
|
make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_b, 3);
|
|
let (candidate_d, candidate_hash_d) =
|
|
make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_c, 4);
|
|
|
|
let mut all_candidates_resp = vec![
|
|
(candidate_hash_a, leaf_a.hash),
|
|
(candidate_hash_b, leaf_a.hash),
|
|
(candidate_hash_c, leaf_a.hash),
|
|
(candidate_hash_d, leaf_a.hash),
|
|
];
|
|
|
|
// Check candidate tree membership.
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
para_id,
|
|
Ancestors::default(),
|
|
5,
|
|
all_candidates_resp.clone(),
|
|
)
|
|
.await;
|
|
|
|
// Activate leaf B, which makes candidates A and B pending availability.
|
|
// Leaf B
|
|
let leaf_b = TestLeaf {
|
|
number: 101,
|
|
hash: Hash::from_low_u64_be(129),
|
|
para_data: vec![(
|
|
para_id,
|
|
PerParaData::new_with_pending(
|
|
98,
|
|
HeadData(vec![1, 2, 3]),
|
|
vec![
|
|
CandidatePendingAvailability {
|
|
candidate_hash: candidate_a.hash(),
|
|
descriptor: candidate_a.descriptor.clone(),
|
|
commitments: candidate_a.commitments.clone(),
|
|
relay_parent_number: leaf_a.number,
|
|
max_pov_size: MAX_POV_SIZE,
|
|
},
|
|
CandidatePendingAvailability {
|
|
candidate_hash: candidate_b.hash(),
|
|
descriptor: candidate_b.descriptor.clone(),
|
|
commitments: candidate_b.commitments.clone(),
|
|
relay_parent_number: leaf_a.number,
|
|
max_pov_size: MAX_POV_SIZE,
|
|
},
|
|
],
|
|
),
|
|
)],
|
|
};
|
|
// Activate leaf B.
|
|
activate_leaf(&mut virtual_overseer, &leaf_b, &test_state).await;
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_b,
|
|
para_id,
|
|
Ancestors::default(),
|
|
5,
|
|
vec![],
|
|
)
|
|
.await;
|
|
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_b,
|
|
para_id,
|
|
[candidate_a.hash(), candidate_b.hash()].into_iter().collect(),
|
|
5,
|
|
vec![(candidate_c.hash(), leaf_a.hash), (candidate_d.hash(), leaf_a.hash)],
|
|
)
|
|
.await;
|
|
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_b,
|
|
para_id,
|
|
Ancestors::default(),
|
|
5,
|
|
vec![],
|
|
)
|
|
.await;
|
|
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
para_id,
|
|
Ancestors::default(),
|
|
5,
|
|
all_candidates_resp.clone(),
|
|
)
|
|
.await;
|
|
|
|
// Now deactivate leaf A.
|
|
deactivate_leaf(&mut virtual_overseer, leaf_a.hash).await;
|
|
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_b,
|
|
para_id,
|
|
Ancestors::default(),
|
|
5,
|
|
vec![],
|
|
)
|
|
.await;
|
|
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_b,
|
|
para_id,
|
|
[candidate_a.hash(), candidate_b.hash()].into_iter().collect(),
|
|
5,
|
|
vec![(candidate_c.hash(), leaf_a.hash), (candidate_d.hash(), leaf_a.hash)],
|
|
)
|
|
.await;
|
|
|
|
// Now add leaf C, which will be a sibling (fork) of leaf B. It should also inherit the
|
|
// candidates of leaf A (their common parent).
|
|
let leaf_c = TestLeaf {
|
|
number: 101,
|
|
hash: Hash::from_low_u64_be(12),
|
|
para_data: vec![(
|
|
para_id,
|
|
PerParaData::new_with_pending(98, HeadData(vec![1, 2, 3]), vec![]),
|
|
)],
|
|
};
|
|
|
|
activate_leaf_with_parent_hash_fn(&mut virtual_overseer, &leaf_c, &test_state, |hash| {
|
|
if hash == leaf_c.hash {
|
|
leaf_a.hash
|
|
} else {
|
|
get_parent_hash(hash)
|
|
}
|
|
})
|
|
.await;
|
|
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_b,
|
|
para_id,
|
|
[candidate_a.hash(), candidate_b.hash()].into_iter().collect(),
|
|
5,
|
|
vec![(candidate_c.hash(), leaf_a.hash), (candidate_d.hash(), leaf_a.hash)],
|
|
)
|
|
.await;
|
|
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_c,
|
|
para_id,
|
|
Ancestors::new(),
|
|
5,
|
|
all_candidates_resp.clone(),
|
|
)
|
|
.await;
|
|
|
|
// Deactivate C and add another candidate that will be present on the deactivated parent A.
|
|
// When activating C again it should also get the new candidate. Deactivated leaves are
|
|
// still updated with new candidates.
|
|
deactivate_leaf(&mut virtual_overseer, leaf_c.hash).await;
|
|
|
|
let (candidate_e, _) =
|
|
make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_d, 5);
|
|
activate_leaf_with_parent_hash_fn(&mut virtual_overseer, &leaf_c, &test_state, |hash| {
|
|
if hash == leaf_c.hash {
|
|
leaf_a.hash
|
|
} else {
|
|
get_parent_hash(hash)
|
|
}
|
|
})
|
|
.await;
|
|
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_b,
|
|
para_id,
|
|
[candidate_a.hash(), candidate_b.hash()].into_iter().collect(),
|
|
5,
|
|
vec![
|
|
(candidate_c.hash(), leaf_a.hash),
|
|
(candidate_d.hash(), leaf_a.hash),
|
|
(candidate_e.hash(), leaf_a.hash),
|
|
],
|
|
)
|
|
.await;
|
|
|
|
all_candidates_resp.push((candidate_e.hash(), leaf_a.hash));
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_c,
|
|
para_id,
|
|
Ancestors::new(),
|
|
5,
|
|
all_candidates_resp,
|
|
)
|
|
.await;
|
|
|
|
// Querying the backable candidates for deactivated leaf won't work.
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
para_id,
|
|
Ancestors::new(),
|
|
5,
|
|
vec![],
|
|
)
|
|
.await;
|
|
|
|
virtual_overseer
|
|
});
|
|
|
|
assert_eq!(view.active_leaves.len(), 2);
|
|
assert_eq!(view.per_relay_parent.len(), 3);
|
|
}
|
|
|
|
#[test]
|
|
fn handle_active_leaves_update_bounded_implicit_view() {
|
|
let para_id = ParaId::from(1);
|
|
let mut test_state = TestState::default();
|
|
test_state.claim_queue = test_state
|
|
.claim_queue
|
|
.into_iter()
|
|
.filter(|(_, paras)| matches!(paras.front(), Some(para) if para == ¶_id))
|
|
.collect();
|
|
assert_eq!(test_state.claim_queue.len(), 1);
|
|
|
|
let scheduling_lookahead = DEFAULT_SCHEDULING_LOOKAHEAD;
|
|
|
|
let mut leaves = vec![TestLeaf {
|
|
number: 100,
|
|
hash: Hash::from_low_u64_be(130),
|
|
para_data: vec![(
|
|
para_id,
|
|
PerParaData::new(100 - (scheduling_lookahead - 1), HeadData(vec![1, 2, 3])),
|
|
)],
|
|
}];
|
|
|
|
for index in 1..10 {
|
|
let prev_leaf = &leaves[index - 1];
|
|
leaves.push(TestLeaf {
|
|
number: prev_leaf.number - 1,
|
|
hash: get_parent_hash(prev_leaf.hash),
|
|
para_data: vec![(
|
|
para_id,
|
|
PerParaData::new(
|
|
prev_leaf.number - 1 - (scheduling_lookahead - 1),
|
|
HeadData(vec![1, 2, 3]),
|
|
),
|
|
)],
|
|
});
|
|
}
|
|
leaves.reverse();
|
|
|
|
let view = test_harness(|mut virtual_overseer| async {
|
|
// Activate first 10 leaves.
|
|
for leaf in &leaves[0..10] {
|
|
activate_leaf(&mut virtual_overseer, leaf, &test_state).await;
|
|
}
|
|
|
|
// Now deactivate first 9 leaves.
|
|
for leaf in &leaves[0..9] {
|
|
deactivate_leaf(&mut virtual_overseer, leaf.hash).await;
|
|
}
|
|
|
|
virtual_overseer
|
|
});
|
|
|
|
// Only latest leaf is active.
|
|
assert_eq!(view.active_leaves.len(), 1);
|
|
// We keep scheduling_lookahead - 1 implicit leaves. The latest leaf is also present here.
|
|
assert_eq!(view.per_relay_parent.len() as u32, scheduling_lookahead);
|
|
|
|
assert_eq!(view.active_leaves, [leaves[9].hash].into_iter().collect());
|
|
assert_eq!(
|
|
view.per_relay_parent.into_keys().collect::<HashSet<_>>(),
|
|
leaves[7..].into_iter().map(|l| l.hash).collect::<HashSet<_>>()
|
|
);
|
|
}
|
|
|
|
#[rstest]
|
|
#[case(RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT)]
|
|
#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)]
|
|
fn persists_pending_availability_candidate(#[case] runtime_api_version: u32) {
|
|
// This doesn't test all the complicated cases with many unconnected candidates, as it's more
|
|
// extensively tested in the `fragment_chain::tests` module.
|
|
let mut test_state = TestState::default();
|
|
test_state.set_runtime_api_version(runtime_api_version);
|
|
let para_id = ParaId::from(1);
|
|
test_state.claim_queue = test_state
|
|
.claim_queue
|
|
.into_iter()
|
|
.filter(|(_, paras)| matches!(paras.front(), Some(para) if para == ¶_id))
|
|
.collect();
|
|
assert_eq!(test_state.claim_queue.len(), 1);
|
|
|
|
test_harness(|mut virtual_overseer| async move {
|
|
let para_head = HeadData(vec![1, 2, 3]);
|
|
|
|
// Min allowed relay parent for leaf `a` which goes out of scope in the test.
|
|
let candidate_relay_parent = Hash::from_low_u64_be(5);
|
|
let candidate_relay_parent_number = 97;
|
|
|
|
let leaf_a = TestLeaf {
|
|
number: candidate_relay_parent_number + DEFAULT_SCHEDULING_LOOKAHEAD,
|
|
hash: Hash::from_low_u64_be(2),
|
|
para_data: vec![(
|
|
para_id,
|
|
PerParaData::new(candidate_relay_parent_number, para_head.clone()),
|
|
)],
|
|
};
|
|
|
|
let leaf_b_hash = Hash::from_low_u64_be(1);
|
|
let leaf_b_number = leaf_a.number + 1;
|
|
|
|
// Activate leaf.
|
|
activate_leaf(&mut virtual_overseer, &leaf_a, &test_state).await;
|
|
|
|
// Candidate A
|
|
let (candidate_a, pvd_a) = make_candidate(
|
|
candidate_relay_parent,
|
|
candidate_relay_parent_number,
|
|
para_id,
|
|
para_head.clone(),
|
|
HeadData(vec![1]),
|
|
test_state.validation_code_hash,
|
|
);
|
|
let candidate_hash_a = candidate_a.hash();
|
|
|
|
// Candidate B, built on top of the candidate which is out of scope but pending
|
|
// availability.
|
|
let (candidate_b, pvd_b) = make_candidate(
|
|
leaf_b_hash,
|
|
leaf_b_number,
|
|
para_id,
|
|
HeadData(vec![1]),
|
|
HeadData(vec![2]),
|
|
test_state.validation_code_hash,
|
|
);
|
|
let candidate_hash_b = candidate_b.hash();
|
|
|
|
introduce_seconded_candidate(&mut virtual_overseer, candidate_a.clone(), pvd_a.clone())
|
|
.await;
|
|
back_candidate(&mut virtual_overseer, &candidate_a, candidate_hash_a).await;
|
|
|
|
let candidate_a_pending_av = CandidatePendingAvailability {
|
|
candidate_hash: candidate_hash_a,
|
|
descriptor: candidate_a.descriptor.clone(),
|
|
commitments: candidate_a.commitments.clone(),
|
|
relay_parent_number: candidate_relay_parent_number,
|
|
max_pov_size: MAX_POV_SIZE,
|
|
};
|
|
let leaf_b = TestLeaf {
|
|
number: leaf_b_number,
|
|
hash: leaf_b_hash,
|
|
para_data: vec![(
|
|
1.into(),
|
|
PerParaData::new_with_pending(
|
|
candidate_relay_parent_number + 1,
|
|
para_head.clone(),
|
|
vec![candidate_a_pending_av],
|
|
),
|
|
)],
|
|
};
|
|
activate_leaf(&mut virtual_overseer, &leaf_b, &test_state).await;
|
|
|
|
get_hypothetical_membership(
|
|
&mut virtual_overseer,
|
|
candidate_hash_a,
|
|
candidate_a,
|
|
pvd_a,
|
|
vec![leaf_a.hash, leaf_b.hash],
|
|
)
|
|
.await;
|
|
|
|
introduce_seconded_candidate(&mut virtual_overseer, candidate_b.clone(), pvd_b).await;
|
|
back_candidate(&mut virtual_overseer, &candidate_b, candidate_hash_b).await;
|
|
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_b,
|
|
para_id,
|
|
vec![candidate_hash_a].into_iter().collect(),
|
|
1,
|
|
vec![(candidate_hash_b, leaf_b_hash)],
|
|
)
|
|
.await;
|
|
|
|
virtual_overseer
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn uses_ancestry_only_within_session() {
|
|
test_harness(|mut virtual_overseer| async move {
|
|
let number = 5;
|
|
let hash = Hash::repeat_byte(5);
|
|
let scheduling_lookahead = DEFAULT_SCHEDULING_LOOKAHEAD;
|
|
let session = 2;
|
|
|
|
let ancestry_hashes =
|
|
vec![Hash::repeat_byte(4), Hash::repeat_byte(3), Hash::repeat_byte(2)];
|
|
let session_change_hash = Hash::repeat_byte(3);
|
|
|
|
let activated = new_leaf(hash, number);
|
|
|
|
virtual_overseer
|
|
.send(FromOrchestra::Signal(OverseerSignal::ActiveLeaves(
|
|
ActiveLeavesUpdate::start_work(activated),
|
|
)))
|
|
.await;
|
|
|
|
assert_matches!(
|
|
virtual_overseer.recv().await,
|
|
AllMessages::RuntimeApi(
|
|
RuntimeApiMessage::Request(parent, RuntimeApiRequest::ClaimQueue(tx))
|
|
) if parent == hash => {
|
|
tx.send(Ok(BTreeMap::new())).unwrap();
|
|
}
|
|
);
|
|
|
|
send_block_header(&mut virtual_overseer, hash, number).await;
|
|
|
|
assert_matches!(
|
|
virtual_overseer.recv().await,
|
|
AllMessages::RuntimeApi(
|
|
RuntimeApiMessage::Request(parent, RuntimeApiRequest::SessionIndexForChild(tx))
|
|
) if parent == hash => {
|
|
tx.send(Ok(session)).unwrap();
|
|
}
|
|
);
|
|
|
|
assert_matches!(
|
|
virtual_overseer.recv().await,
|
|
AllMessages::RuntimeApi(
|
|
RuntimeApiMessage::Request(parent, RuntimeApiRequest::SchedulingLookahead(session_index, tx))
|
|
) if parent == hash && session_index == session => {
|
|
tx.send(Ok(scheduling_lookahead)).unwrap();
|
|
}
|
|
);
|
|
|
|
assert_matches!(
|
|
virtual_overseer.recv().await,
|
|
AllMessages::ChainApi(
|
|
ChainApiMessage::Ancestors{hash: block_hash, k, response_channel: tx}
|
|
) if block_hash == hash && k == (scheduling_lookahead - 1) as usize => {
|
|
tx.send(Ok(ancestry_hashes.clone())).unwrap();
|
|
}
|
|
);
|
|
|
|
for (i, hash) in ancestry_hashes.into_iter().enumerate() {
|
|
let number = number - (i + 1) as BlockNumber;
|
|
send_block_header(&mut virtual_overseer, hash, number).await;
|
|
assert_matches!(
|
|
virtual_overseer.recv().await,
|
|
AllMessages::RuntimeApi(
|
|
RuntimeApiMessage::Request(parent, RuntimeApiRequest::SessionIndexForChild(tx))
|
|
) if parent == hash => {
|
|
if hash == session_change_hash {
|
|
tx.send(Ok(session - 1)).unwrap();
|
|
break
|
|
} else {
|
|
tx.send(Ok(session)).unwrap();
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
virtual_overseer
|
|
});
|
|
}
|