mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-19 05:21:03 +00:00
02e1a7f476
Fixes #3128. This introduces a new variant for the collation response from the collator that includes the parent head data. For now, collators won't send this new variant. We'll need to change the collator side of the collator protocol to detect all the cores assigned to a para and send the parent head data in the case when it's more than 1 core. - [x] validate approach - [x] check head data hash
2062 lines
55 KiB
Rust
2062 lines
55 KiB
Rust
// Copyright (C) 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::*;
|
|
use assert_matches::assert_matches;
|
|
use polkadot_node_subsystem::{
|
|
errors::RuntimeApiError,
|
|
messages::{
|
|
AllMessages, HypotheticalFrontierRequest, ParentHeadData, ProspectiveParachainsMessage,
|
|
ProspectiveValidationDataRequest,
|
|
},
|
|
};
|
|
use polkadot_node_subsystem_test_helpers as test_helpers;
|
|
use polkadot_primitives::{
|
|
async_backing::{AsyncBackingParams, BackingState, Constraints, InboundHrmpLimitations},
|
|
CommittedCandidateReceipt, HeadData, Header, PersistedValidationData, ScheduledCore,
|
|
ValidationCodeHash,
|
|
};
|
|
use polkadot_primitives_test_helpers::make_candidate;
|
|
use std::sync::Arc;
|
|
use test_helpers::mock::new_leaf;
|
|
|
|
const ALLOWED_ANCESTRY_LEN: u32 = 3;
|
|
const ASYNC_BACKING_PARAMETERS: AsyncBackingParams =
|
|
AsyncBackingParams { max_candidate_depth: 4, allowed_ancestry_len: ALLOWED_ANCESTRY_LEN };
|
|
|
|
const ASYNC_BACKING_DISABLED_ERROR: RuntimeApiError =
|
|
RuntimeApiError::NotSupported { runtime_api_name: "test-runtime" };
|
|
|
|
const MAX_POV_SIZE: u32 = 1_000_000;
|
|
|
|
type VirtualOverseer = test_helpers::TestSubsystemContextHandle<ProspectiveParachainsMessage>;
|
|
|
|
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_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 {
|
|
availability_cores: Vec<CoreState>,
|
|
validation_code_hash: ValidationCodeHash,
|
|
}
|
|
|
|
impl Default for TestState {
|
|
fn default() -> Self {
|
|
let chain_a = ParaId::from(1);
|
|
let chain_b = ParaId::from(2);
|
|
|
|
let availability_cores = vec![
|
|
CoreState::Scheduled(ScheduledCore { para_id: chain_a, collator: None }),
|
|
CoreState::Scheduled(ScheduledCore { para_id: chain_b, collator: None }),
|
|
];
|
|
let validation_code_hash = Hash::repeat_byte(42).into();
|
|
|
|
Self { availability_cores, validation_code_hash }
|
|
}
|
|
}
|
|
|
|
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 {
|
|
let pool = sp_core::testing::TaskExecutor::new();
|
|
|
|
let (mut context, virtual_overseer) = 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,
|
|
) {
|
|
activate_leaf_with_params(virtual_overseer, leaf, test_state, ASYNC_BACKING_PARAMETERS).await;
|
|
}
|
|
|
|
async fn activate_leaf_with_params(
|
|
virtual_overseer: &mut VirtualOverseer,
|
|
leaf: &TestLeaf,
|
|
test_state: &TestState,
|
|
async_backing_params: AsyncBackingParams,
|
|
) {
|
|
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, async_backing_params).await;
|
|
}
|
|
|
|
async fn handle_leaf_activation(
|
|
virtual_overseer: &mut VirtualOverseer,
|
|
leaf: &TestLeaf,
|
|
test_state: &TestState,
|
|
async_backing_params: AsyncBackingParams,
|
|
) {
|
|
let TestLeaf { number, hash, para_data } = leaf;
|
|
|
|
assert_matches!(
|
|
virtual_overseer.recv().await,
|
|
AllMessages::RuntimeApi(
|
|
RuntimeApiMessage::Request(parent, RuntimeApiRequest::AsyncBackingParams(tx))
|
|
) if parent == *hash => {
|
|
tx.send(Ok(async_backing_params)).unwrap();
|
|
}
|
|
);
|
|
|
|
assert_matches!(
|
|
virtual_overseer.recv().await,
|
|
AllMessages::RuntimeApi(
|
|
RuntimeApiMessage::Request(parent, RuntimeApiRequest::AvailabilityCores(tx))
|
|
) if parent == *hash => {
|
|
tx.send(Ok(test_state.availability_cores.clone())).unwrap();
|
|
}
|
|
);
|
|
|
|
send_block_header(virtual_overseer, *hash, *number).await;
|
|
|
|
// 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(get_parent_hash(*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 == ALLOWED_ANCESTRY_LEN as usize => {
|
|
tx.send(Ok(ancestry_hashes.clone())).unwrap();
|
|
}
|
|
);
|
|
|
|
assert_matches!(
|
|
virtual_overseer.recv().await,
|
|
AllMessages::RuntimeApi(
|
|
RuntimeApiMessage::Request(parent, RuntimeApiRequest::SessionIndexForChild(tx))
|
|
) if parent == *hash => {
|
|
tx.send(Ok(1)).unwrap();
|
|
}
|
|
);
|
|
}
|
|
|
|
for (hash, number) in ancestry_iter {
|
|
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();
|
|
}
|
|
);
|
|
}
|
|
|
|
for _ in 0..test_state.availability_cores.len() {
|
|
let message = virtual_overseer.recv().await;
|
|
// Get the para we are working with since the order is not deterministic.
|
|
let para_id = match message {
|
|
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
|
_,
|
|
RuntimeApiRequest::ParaBackingState(p_id, _),
|
|
)) => p_id,
|
|
_ => panic!("received unexpected message {:?}", message),
|
|
};
|
|
|
|
let PerParaData { min_relay_parent, head_data, pending_availability } =
|
|
leaf.para_data(para_id);
|
|
let constraints = dummy_constraints(
|
|
*min_relay_parent,
|
|
vec![*number],
|
|
head_data.clone(),
|
|
test_state.validation_code_hash,
|
|
);
|
|
let backing_state =
|
|
BackingState { constraints, pending_availability: pending_availability.clone() };
|
|
|
|
assert_matches!(
|
|
message,
|
|
AllMessages::RuntimeApi(
|
|
RuntimeApiMessage::Request(parent, RuntimeApiRequest::ParaBackingState(p_id, tx))
|
|
) if parent == *hash && p_id == para_id => {
|
|
tx.send(Ok(Some(backing_state))).unwrap();
|
|
}
|
|
);
|
|
|
|
for pending in pending_availability {
|
|
send_block_header(
|
|
virtual_overseer,
|
|
pending.descriptor.relay_parent,
|
|
pending.relay_parent_number,
|
|
)
|
|
.await;
|
|
}
|
|
}
|
|
|
|
// Get minimum relay parents.
|
|
let (tx, rx) = oneshot::channel();
|
|
virtual_overseer
|
|
.send(overseer::FromOrchestra::Communication {
|
|
msg: ProspectiveParachainsMessage::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_candidate(
|
|
virtual_overseer: &mut VirtualOverseer,
|
|
candidate: CommittedCandidateReceipt,
|
|
pvd: PersistedValidationData,
|
|
) {
|
|
let req = IntroduceCandidateRequest {
|
|
candidate_para: candidate.descriptor().para_id,
|
|
candidate_receipt: candidate,
|
|
persisted_validation_data: pvd,
|
|
};
|
|
let (tx, _) = oneshot::channel();
|
|
virtual_overseer
|
|
.send(overseer::FromOrchestra::Communication {
|
|
msg: ProspectiveParachainsMessage::IntroduceCandidate(req, tx),
|
|
})
|
|
.await;
|
|
}
|
|
|
|
async fn second_candidate(
|
|
virtual_overseer: &mut VirtualOverseer,
|
|
candidate: CommittedCandidateReceipt,
|
|
) {
|
|
virtual_overseer
|
|
.send(overseer::FromOrchestra::Communication {
|
|
msg: ProspectiveParachainsMessage::CandidateSeconded(
|
|
candidate.descriptor.para_id,
|
|
candidate.hash(),
|
|
),
|
|
})
|
|
.await;
|
|
}
|
|
|
|
async fn back_candidate(
|
|
virtual_overseer: &mut VirtualOverseer,
|
|
candidate: &CommittedCandidateReceipt,
|
|
candidate_hash: CandidateHash,
|
|
) {
|
|
virtual_overseer
|
|
.send(overseer::FromOrchestra::Communication {
|
|
msg: ProspectiveParachainsMessage::CandidateBacked(
|
|
candidate.descriptor.para_id,
|
|
candidate_hash,
|
|
),
|
|
})
|
|
.await;
|
|
}
|
|
|
|
async fn get_membership(
|
|
virtual_overseer: &mut VirtualOverseer,
|
|
para_id: ParaId,
|
|
candidate_hash: CandidateHash,
|
|
expected_membership_response: Vec<(Hash, Vec<usize>)>,
|
|
) {
|
|
let (tx, rx) = oneshot::channel();
|
|
virtual_overseer
|
|
.send(overseer::FromOrchestra::Communication {
|
|
msg: ProspectiveParachainsMessage::GetTreeMembership(para_id, candidate_hash, tx),
|
|
})
|
|
.await;
|
|
let resp = rx.await.unwrap();
|
|
assert_eq!(resp, expected_membership_response);
|
|
}
|
|
|
|
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: ProspectiveParachainsMessage::GetBackableCandidates(
|
|
leaf.hash, para_id, count, ancestors, tx,
|
|
),
|
|
})
|
|
.await;
|
|
let resp = rx.await.unwrap();
|
|
assert_eq!(resp.len(), expected_result.len());
|
|
assert_eq!(resp, expected_result);
|
|
}
|
|
|
|
async fn get_hypothetical_frontier(
|
|
virtual_overseer: &mut VirtualOverseer,
|
|
candidate_hash: CandidateHash,
|
|
receipt: CommittedCandidateReceipt,
|
|
persisted_validation_data: PersistedValidationData,
|
|
fragment_tree_relay_parent: Hash,
|
|
backed_in_path_only: bool,
|
|
expected_depths: Vec<usize>,
|
|
) {
|
|
let hypothetical_candidate = HypotheticalCandidate::Complete {
|
|
candidate_hash,
|
|
receipt: Arc::new(receipt),
|
|
persisted_validation_data,
|
|
};
|
|
let request = HypotheticalFrontierRequest {
|
|
candidates: vec![hypothetical_candidate.clone()],
|
|
fragment_tree_relay_parent: Some(fragment_tree_relay_parent),
|
|
backed_in_path_only,
|
|
};
|
|
let (tx, rx) = oneshot::channel();
|
|
virtual_overseer
|
|
.send(overseer::FromOrchestra::Communication {
|
|
msg: ProspectiveParachainsMessage::GetHypotheticalFrontier(request, tx),
|
|
})
|
|
.await;
|
|
let resp = rx.await.unwrap();
|
|
let expected_frontier = if expected_depths.is_empty() {
|
|
vec![(hypothetical_candidate, vec![])]
|
|
} else {
|
|
vec![(hypothetical_candidate, vec![(fragment_tree_relay_parent, expected_depths)])]
|
|
};
|
|
assert_eq!(resp, expected_frontier);
|
|
}
|
|
|
|
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: ProspectiveParachainsMessage::GetProspectiveValidationData(request, tx),
|
|
})
|
|
.await;
|
|
let resp = rx.await.unwrap();
|
|
assert_eq!(resp, expected_pvd);
|
|
}
|
|
|
|
#[test]
|
|
fn should_do_no_work_if_async_backing_disabled_for_leaf() {
|
|
async fn activate_leaf_async_backing_disabled(virtual_overseer: &mut VirtualOverseer) {
|
|
let hash = Hash::from_low_u64_be(130);
|
|
|
|
// Start work on some new parent.
|
|
virtual_overseer
|
|
.send(FromOrchestra::Signal(OverseerSignal::ActiveLeaves(
|
|
ActiveLeavesUpdate::start_work(new_leaf(hash, 1)),
|
|
)))
|
|
.await;
|
|
|
|
assert_matches!(
|
|
virtual_overseer.recv().await,
|
|
AllMessages::RuntimeApi(
|
|
RuntimeApiMessage::Request(parent, RuntimeApiRequest::AsyncBackingParams(tx))
|
|
) if parent == hash => {
|
|
tx.send(Err(ASYNC_BACKING_DISABLED_ERROR)).unwrap();
|
|
}
|
|
);
|
|
}
|
|
|
|
let view = test_harness(|mut virtual_overseer| async move {
|
|
activate_leaf_async_backing_disabled(&mut virtual_overseer).await;
|
|
|
|
virtual_overseer
|
|
});
|
|
|
|
assert!(view.active_leaves.is_empty());
|
|
assert!(view.candidate_storage.is_empty());
|
|
}
|
|
|
|
// Send some candidates and make sure all are found:
|
|
// - Two for the same leaf A
|
|
// - One for leaf B on parachain 1
|
|
// - One for leaf C on parachain 2
|
|
#[test]
|
|
fn send_candidates_and_check_if_found() {
|
|
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();
|
|
let response_a1 = vec![(leaf_a.hash, vec![0])];
|
|
|
|
// 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![(leaf_a.hash, vec![0])];
|
|
|
|
// 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![(leaf_b.hash, vec![0])];
|
|
|
|
// 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![(leaf_c.hash, vec![0])];
|
|
|
|
// Introduce candidates.
|
|
introduce_candidate(&mut virtual_overseer, candidate_a1, pvd_a1).await;
|
|
introduce_candidate(&mut virtual_overseer, candidate_a2, pvd_a2).await;
|
|
introduce_candidate(&mut virtual_overseer, candidate_b, pvd_b).await;
|
|
introduce_candidate(&mut virtual_overseer, candidate_c, pvd_c).await;
|
|
|
|
// Check candidate tree membership.
|
|
get_membership(&mut virtual_overseer, 1.into(), candidate_hash_a1, response_a1).await;
|
|
get_membership(&mut virtual_overseer, 2.into(), candidate_hash_a2, response_a2).await;
|
|
get_membership(&mut virtual_overseer, 1.into(), candidate_hash_b, response_b).await;
|
|
get_membership(&mut virtual_overseer, 2.into(), candidate_hash_c, response_c).await;
|
|
|
|
// The candidates should not be found on other parachains.
|
|
get_membership(&mut virtual_overseer, 2.into(), candidate_hash_a1, vec![]).await;
|
|
get_membership(&mut virtual_overseer, 1.into(), candidate_hash_a2, vec![]).await;
|
|
get_membership(&mut virtual_overseer, 2.into(), candidate_hash_b, vec![]).await;
|
|
get_membership(&mut virtual_overseer, 1.into(), candidate_hash_c, vec![]).await;
|
|
|
|
virtual_overseer
|
|
});
|
|
|
|
assert_eq!(view.active_leaves.len(), 3);
|
|
assert_eq!(view.candidate_storage.len(), 2);
|
|
// Two parents and two candidates per para.
|
|
assert_eq!(view.candidate_storage.get(&1.into()).unwrap().len(), (2, 2));
|
|
assert_eq!(view.candidate_storage.get(&2.into()).unwrap().len(), (2, 2));
|
|
}
|
|
|
|
// Send some candidates, check if the candidate won't be found once its relay parent leaves the
|
|
// view.
|
|
#[test]
|
|
fn check_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![(leaf_b.hash, vec![0])];
|
|
|
|
// 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![(leaf_c.hash, vec![0])];
|
|
|
|
// Introduce candidates.
|
|
introduce_candidate(&mut virtual_overseer, candidate_a1, pvd_a1).await;
|
|
introduce_candidate(&mut virtual_overseer, candidate_a2, pvd_a2).await;
|
|
introduce_candidate(&mut virtual_overseer, candidate_b, pvd_b).await;
|
|
introduce_candidate(&mut virtual_overseer, candidate_c, pvd_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_membership(&mut virtual_overseer, 1.into(), candidate_hash_a1, vec![]).await;
|
|
get_membership(&mut virtual_overseer, 2.into(), candidate_hash_a2, vec![]).await;
|
|
get_membership(&mut virtual_overseer, 1.into(), candidate_hash_b, response_b).await;
|
|
get_membership(&mut virtual_overseer, 2.into(), candidate_hash_c, 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_membership(&mut virtual_overseer, 1.into(), candidate_hash_a1, vec![]).await;
|
|
get_membership(&mut virtual_overseer, 2.into(), candidate_hash_a2, vec![]).await;
|
|
get_membership(&mut virtual_overseer, 1.into(), candidate_hash_b, vec![]).await;
|
|
get_membership(&mut virtual_overseer, 2.into(), candidate_hash_c, response_c).await;
|
|
|
|
// Deactivate leaf C.
|
|
deactivate_leaf(&mut virtual_overseer, leaf_c.hash).await;
|
|
|
|
// Candidate C should be gone.
|
|
get_membership(&mut virtual_overseer, 1.into(), candidate_hash_a1, vec![]).await;
|
|
get_membership(&mut virtual_overseer, 2.into(), candidate_hash_a2, vec![]).await;
|
|
get_membership(&mut virtual_overseer, 1.into(), candidate_hash_b, vec![]).await;
|
|
get_membership(&mut virtual_overseer, 2.into(), candidate_hash_c, vec![]).await;
|
|
|
|
virtual_overseer
|
|
});
|
|
|
|
assert_eq!(view.active_leaves.len(), 0);
|
|
assert_eq!(view.candidate_storage.len(), 0);
|
|
}
|
|
|
|
// Introduce a candidate to multiple forks, see how the membership is returned.
|
|
#[test]
|
|
fn check_candidate_on_multiple_forks() {
|
|
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 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![(leaf_a.hash, vec![0])];
|
|
|
|
// Candidate on leaf B.
|
|
let (candidate_b, pvd_b) = make_candidate(
|
|
leaf_b.hash,
|
|
leaf_b.number,
|
|
1.into(),
|
|
HeadData(vec![3, 4, 5]),
|
|
HeadData(vec![1]),
|
|
test_state.validation_code_hash,
|
|
);
|
|
let candidate_hash_b = candidate_b.hash();
|
|
let response_b = vec![(leaf_b.hash, vec![0])];
|
|
|
|
// Candidate on leaf C.
|
|
let (candidate_c, pvd_c) = make_candidate(
|
|
leaf_c.hash,
|
|
leaf_c.number,
|
|
1.into(),
|
|
HeadData(vec![5, 6, 7]),
|
|
HeadData(vec![1]),
|
|
test_state.validation_code_hash,
|
|
);
|
|
let candidate_hash_c = candidate_c.hash();
|
|
let response_c = vec![(leaf_c.hash, vec![0])];
|
|
|
|
// Introduce candidates on all three leaves.
|
|
introduce_candidate(&mut virtual_overseer, candidate_a.clone(), pvd_a).await;
|
|
introduce_candidate(&mut virtual_overseer, candidate_b.clone(), pvd_b).await;
|
|
introduce_candidate(&mut virtual_overseer, candidate_c.clone(), pvd_c).await;
|
|
|
|
// Check candidate tree membership.
|
|
get_membership(&mut virtual_overseer, 1.into(), candidate_hash_a, response_a).await;
|
|
get_membership(&mut virtual_overseer, 1.into(), candidate_hash_b, response_b).await;
|
|
get_membership(&mut virtual_overseer, 1.into(), candidate_hash_c, response_c).await;
|
|
|
|
virtual_overseer
|
|
});
|
|
|
|
assert_eq!(view.active_leaves.len(), 3);
|
|
assert_eq!(view.candidate_storage.len(), 2);
|
|
// Three parents and three candidates on para 1.
|
|
assert_eq!(view.candidate_storage.get(&1.into()).unwrap().len(), (3, 3));
|
|
assert_eq!(view.candidate_storage.get(&2.into()).unwrap().len(), (0, 0));
|
|
}
|
|
|
|
// Backs some candidates and tests `GetBackableCandidates` when requesting a single candidate.
|
|
#[test]
|
|
fn check_backable_query_single_candidate() {
|
|
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]))),
|
|
],
|
|
};
|
|
|
|
// 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.para_head = Hash::from_low_u64_le(1000);
|
|
let candidate_hash_b = candidate_b.hash();
|
|
|
|
// Introduce candidates.
|
|
introduce_candidate(&mut virtual_overseer, candidate_a.clone(), pvd_a).await;
|
|
introduce_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;
|
|
|
|
// Second candidates.
|
|
second_candidate(&mut virtual_overseer, candidate_a.clone()).await;
|
|
second_candidate(&mut virtual_overseer, candidate_b.clone()).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;
|
|
|
|
// Back candidates.
|
|
back_candidate(&mut virtual_overseer, &candidate_a, candidate_hash_a).await;
|
|
back_candidate(&mut virtual_overseer, &candidate_b, candidate_hash_b).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);
|
|
assert_eq!(view.candidate_storage.len(), 2);
|
|
// Two parents and two candidates on para 1.
|
|
assert_eq!(view.candidate_storage.get(&1.into()).unwrap().len(), (2, 2));
|
|
assert_eq!(view.candidate_storage.get(&2.into()).unwrap().len(), (0, 0));
|
|
}
|
|
|
|
// Backs some candidates and tests `GetBackableCandidates` when requesting a multiple candidates.
|
|
#[test]
|
|
fn check_backable_query_multiple_candidates() {
|
|
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.para_head = Hash::from_low_u64_le($index);
|
|
let candidate_hash = candidate.hash();
|
|
introduce_candidate(&mut $virtual_overseer, candidate.clone(), pvd).await;
|
|
second_candidate(&mut $virtual_overseer, candidate.clone()).await;
|
|
back_candidate(&mut $virtual_overseer, &candidate, candidate_hash).await;
|
|
|
|
(candidate, candidate_hash)
|
|
}};
|
|
}
|
|
|
|
// Parachain 1 looks like this:
|
|
// +---A----+
|
|
// | |
|
|
// +----B---+ C
|
|
// | | | |
|
|
// D E F H
|
|
// | |
|
|
// G I
|
|
// |
|
|
// J
|
|
{
|
|
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]))),
|
|
],
|
|
};
|
|
|
|
// 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_candidate(&mut virtual_overseer, candidate_a.clone(), pvd_a).await;
|
|
second_candidate(&mut virtual_overseer, candidate_a.clone()).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_a, 3);
|
|
let (_candidate_d, candidate_hash_d) =
|
|
make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_b, 4);
|
|
let (_candidate_e, candidate_hash_e) =
|
|
make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_b, 5);
|
|
let (candidate_f, candidate_hash_f) =
|
|
make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_b, 6);
|
|
let (_candidate_g, candidate_hash_g) =
|
|
make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_f, 7);
|
|
let (candidate_h, candidate_hash_h) =
|
|
make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_c, 8);
|
|
let (candidate_i, candidate_hash_i) =
|
|
make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_h, 9);
|
|
let (_candidate_j, candidate_hash_j) =
|
|
make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_i, 10);
|
|
|
|
// 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 required_path
|
|
{
|
|
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(),
|
|
Ancestors::new(),
|
|
4,
|
|
vec![
|
|
(candidate_hash_a, leaf_a.hash),
|
|
(candidate_hash_b, leaf_a.hash),
|
|
(candidate_hash_f, leaf_a.hash),
|
|
(candidate_hash_g, leaf_a.hash),
|
|
],
|
|
)
|
|
.await;
|
|
}
|
|
|
|
// required path of 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(),
|
|
3,
|
|
vec![
|
|
(candidate_hash_b, leaf_a.hash),
|
|
(candidate_hash_f, leaf_a.hash),
|
|
(candidate_hash_g, leaf_a.hash),
|
|
],
|
|
)
|
|
.await;
|
|
|
|
// If the requested count exceeds the largest chain, return the longest
|
|
// chain we can get.
|
|
for count in 5..10 {
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
1.into(),
|
|
vec![candidate_hash_a].into_iter().collect(),
|
|
count,
|
|
vec![
|
|
(candidate_hash_c, leaf_a.hash),
|
|
(candidate_hash_h, leaf_a.hash),
|
|
(candidate_hash_i, leaf_a.hash),
|
|
(candidate_hash_j, leaf_a.hash),
|
|
],
|
|
)
|
|
.await;
|
|
}
|
|
}
|
|
|
|
// required path of 2 and higher
|
|
{
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
1.into(),
|
|
vec![candidate_hash_a, candidate_hash_i, candidate_hash_h, candidate_hash_c]
|
|
.into_iter()
|
|
.collect(),
|
|
1,
|
|
vec![(candidate_hash_j, 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_d, leaf_a.hash)],
|
|
)
|
|
.await;
|
|
|
|
// If the requested count exceeds the largest chain, return the longest
|
|
// chain we can get.
|
|
for count in 4..10 {
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
1.into(),
|
|
vec![candidate_hash_a, candidate_hash_c].into_iter().collect(),
|
|
count,
|
|
vec![
|
|
(candidate_hash_h, leaf_a.hash),
|
|
(candidate_hash_i, leaf_a.hash),
|
|
(candidate_hash_j, leaf_a.hash),
|
|
],
|
|
)
|
|
.await;
|
|
}
|
|
}
|
|
|
|
// No more candidates in any 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_e]
|
|
.into_iter()
|
|
.collect(),
|
|
count,
|
|
vec![],
|
|
)
|
|
.await;
|
|
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
1.into(),
|
|
vec![
|
|
candidate_hash_a,
|
|
candidate_hash_c,
|
|
candidate_hash_h,
|
|
candidate_hash_i,
|
|
candidate_hash_j,
|
|
]
|
|
.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_f].into_iter().collect(),
|
|
3,
|
|
vec![
|
|
(candidate_hash_a, leaf_a.hash),
|
|
(candidate_hash_b, leaf_a.hash),
|
|
(candidate_hash_d, leaf_a.hash),
|
|
],
|
|
)
|
|
.await;
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
1.into(),
|
|
vec![candidate_hash_a, candidate_hash_h].into_iter().collect(),
|
|
4,
|
|
vec![
|
|
(candidate_hash_c, leaf_a.hash),
|
|
(candidate_hash_h, leaf_a.hash),
|
|
(candidate_hash_i, leaf_a.hash),
|
|
(candidate_hash_j, leaf_a.hash),
|
|
],
|
|
)
|
|
.await;
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
1.into(),
|
|
vec![candidate_hash_e, candidate_hash_h].into_iter().collect(),
|
|
2,
|
|
vec![(candidate_hash_a, leaf_a.hash), (candidate_hash_b, 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_h, leaf_a.hash), (candidate_hash_i, leaf_a.hash)],
|
|
)
|
|
.await;
|
|
|
|
// Parachain fork.
|
|
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![],
|
|
)
|
|
.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_d, 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);
|
|
assert_eq!(view.candidate_storage.len(), 2);
|
|
// 10 candidates and 7 parents on para 1.
|
|
assert_eq!(view.candidate_storage.get(&1.into()).unwrap().len(), (7, 10));
|
|
assert_eq!(view.candidate_storage.get(&2.into()).unwrap().len(), (0, 0));
|
|
}
|
|
}
|
|
|
|
// Test depth query.
|
|
#[test]
|
|
fn check_hypothetical_frontier_query() {
|
|
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]))),
|
|
],
|
|
};
|
|
|
|
// 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 (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_hash_b = candidate_b.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,
|
|
);
|
|
let candidate_hash_c = candidate_c.hash();
|
|
|
|
// Get hypothetical frontier of candidate A before adding it.
|
|
get_hypothetical_frontier(
|
|
&mut virtual_overseer,
|
|
candidate_hash_a,
|
|
candidate_a.clone(),
|
|
pvd_a.clone(),
|
|
leaf_a.hash,
|
|
false,
|
|
vec![0],
|
|
)
|
|
.await;
|
|
// Should work with `backed_in_path_only: true`, too.
|
|
get_hypothetical_frontier(
|
|
&mut virtual_overseer,
|
|
candidate_hash_a,
|
|
candidate_a.clone(),
|
|
pvd_a.clone(),
|
|
leaf_a.hash,
|
|
true,
|
|
vec![0],
|
|
)
|
|
.await;
|
|
|
|
// Add candidate A.
|
|
introduce_candidate(&mut virtual_overseer, candidate_a.clone(), pvd_a.clone()).await;
|
|
|
|
// Get frontier of candidate A after adding it.
|
|
get_hypothetical_frontier(
|
|
&mut virtual_overseer,
|
|
candidate_hash_a,
|
|
candidate_a.clone(),
|
|
pvd_a.clone(),
|
|
leaf_a.hash,
|
|
false,
|
|
vec![0],
|
|
)
|
|
.await;
|
|
|
|
// Get hypothetical frontier of candidate B before adding it.
|
|
get_hypothetical_frontier(
|
|
&mut virtual_overseer,
|
|
candidate_hash_b,
|
|
candidate_b.clone(),
|
|
pvd_b.clone(),
|
|
leaf_a.hash,
|
|
false,
|
|
vec![1],
|
|
)
|
|
.await;
|
|
|
|
// Add candidate B.
|
|
introduce_candidate(&mut virtual_overseer, candidate_b.clone(), pvd_b.clone()).await;
|
|
|
|
// Get frontier of candidate B after adding it.
|
|
get_hypothetical_frontier(
|
|
&mut virtual_overseer,
|
|
candidate_hash_b,
|
|
candidate_b,
|
|
pvd_b.clone(),
|
|
leaf_a.hash,
|
|
false,
|
|
vec![1],
|
|
)
|
|
.await;
|
|
|
|
// Get hypothetical frontier of candidate C before adding it.
|
|
get_hypothetical_frontier(
|
|
&mut virtual_overseer,
|
|
candidate_hash_c,
|
|
candidate_c.clone(),
|
|
pvd_c.clone(),
|
|
leaf_a.hash,
|
|
false,
|
|
vec![2],
|
|
)
|
|
.await;
|
|
// Should be empty with `backed_in_path_only` because we haven't backed anything.
|
|
get_hypothetical_frontier(
|
|
&mut virtual_overseer,
|
|
candidate_hash_c,
|
|
candidate_c.clone(),
|
|
pvd_c.clone(),
|
|
leaf_a.hash,
|
|
true,
|
|
vec![],
|
|
)
|
|
.await;
|
|
|
|
// Add candidate C.
|
|
introduce_candidate(&mut virtual_overseer, candidate_c.clone(), pvd_c.clone()).await;
|
|
|
|
// Get frontier of candidate C after adding it.
|
|
get_hypothetical_frontier(
|
|
&mut virtual_overseer,
|
|
candidate_hash_c,
|
|
candidate_c.clone(),
|
|
pvd_c.clone(),
|
|
leaf_a.hash,
|
|
false,
|
|
vec![2],
|
|
)
|
|
.await;
|
|
// Should be empty with `backed_in_path_only` because we haven't backed anything.
|
|
get_hypothetical_frontier(
|
|
&mut virtual_overseer,
|
|
candidate_hash_c,
|
|
candidate_c.clone(),
|
|
pvd_c.clone(),
|
|
leaf_a.hash,
|
|
true,
|
|
vec![],
|
|
)
|
|
.await;
|
|
|
|
virtual_overseer
|
|
});
|
|
|
|
assert_eq!(view.active_leaves.len(), 1);
|
|
assert_eq!(view.candidate_storage.len(), 2);
|
|
}
|
|
|
|
#[test]
|
|
fn check_pvd_query() {
|
|
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]))),
|
|
],
|
|
};
|
|
|
|
// 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,
|
|
);
|
|
|
|
// 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_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_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_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.clone()),
|
|
)
|
|
.await;
|
|
|
|
virtual_overseer
|
|
});
|
|
|
|
assert_eq!(view.active_leaves.len(), 1);
|
|
assert_eq!(view.candidate_storage.len(), 2);
|
|
}
|
|
|
|
// 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,
|
|
ASYNC_BACKING_PARAMETERS,
|
|
)
|
|
.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;
|
|
handle_leaf_activation(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
&test_state,
|
|
ASYNC_BACKING_PARAMETERS,
|
|
)
|
|
.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);
|
|
assert_eq!(view.candidate_storage.len(), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn persists_pending_availability_candidate() {
|
|
let mut test_state = TestState::default();
|
|
let para_id = ParaId::from(1);
|
|
test_state.availability_cores = test_state
|
|
.availability_cores
|
|
.into_iter()
|
|
.filter(|core| core.para_id().map_or(false, |id| id == para_id))
|
|
.collect();
|
|
assert_eq!(test_state.availability_cores.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 + ALLOWED_ANCESTRY_LEN,
|
|
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_candidate(&mut virtual_overseer, candidate_a.clone(), pvd_a).await;
|
|
second_candidate(&mut virtual_overseer, candidate_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;
|
|
|
|
introduce_candidate(&mut virtual_overseer, candidate_b.clone(), pvd_b).await;
|
|
second_candidate(&mut virtual_overseer, candidate_b.clone()).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 backwards_compatible() {
|
|
let mut test_state = TestState::default();
|
|
let para_id = ParaId::from(1);
|
|
test_state.availability_cores = test_state
|
|
.availability_cores
|
|
.into_iter()
|
|
.filter(|core| core.para_id().map_or(false, |id| id == para_id))
|
|
.collect();
|
|
assert_eq!(test_state.availability_cores.len(), 1);
|
|
|
|
test_harness(|mut virtual_overseer| async move {
|
|
let para_head = HeadData(vec![1, 2, 3]);
|
|
|
|
let leaf_b_hash = Hash::repeat_byte(15);
|
|
let candidate_relay_parent = get_parent_hash(leaf_b_hash);
|
|
let candidate_relay_parent_number = 100;
|
|
|
|
let leaf_a = TestLeaf {
|
|
number: candidate_relay_parent_number,
|
|
hash: candidate_relay_parent,
|
|
para_data: vec![(
|
|
para_id,
|
|
PerParaData::new(candidate_relay_parent_number, para_head.clone()),
|
|
)],
|
|
};
|
|
|
|
// Activate leaf.
|
|
activate_leaf_with_params(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
&test_state,
|
|
AsyncBackingParams { allowed_ancestry_len: 0, max_candidate_depth: 0 },
|
|
)
|
|
.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();
|
|
|
|
introduce_candidate(&mut virtual_overseer, candidate_a.clone(), pvd_a).await;
|
|
second_candidate(&mut virtual_overseer, candidate_a.clone()).await;
|
|
back_candidate(&mut virtual_overseer, &candidate_a, candidate_hash_a).await;
|
|
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_a,
|
|
para_id,
|
|
Ancestors::new(),
|
|
1,
|
|
vec![(candidate_hash_a, candidate_relay_parent)],
|
|
)
|
|
.await;
|
|
|
|
let leaf_b = TestLeaf {
|
|
number: candidate_relay_parent_number + 1,
|
|
hash: leaf_b_hash,
|
|
para_data: vec![(
|
|
para_id,
|
|
PerParaData::new(candidate_relay_parent_number + 1, para_head.clone()),
|
|
)],
|
|
};
|
|
activate_leaf_with_params(
|
|
&mut virtual_overseer,
|
|
&leaf_b,
|
|
&test_state,
|
|
AsyncBackingParams { allowed_ancestry_len: 0, max_candidate_depth: 0 },
|
|
)
|
|
.await;
|
|
|
|
get_backable_candidates(
|
|
&mut virtual_overseer,
|
|
&leaf_b,
|
|
para_id,
|
|
Ancestors::new(),
|
|
1,
|
|
vec![],
|
|
)
|
|
.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 ancestry_len = 3;
|
|
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::AsyncBackingParams(tx))
|
|
) if parent == hash => {
|
|
tx.send(Ok(AsyncBackingParams { max_candidate_depth: 0, allowed_ancestry_len: ancestry_len
|
|
})).unwrap(); }
|
|
);
|
|
|
|
assert_matches!(
|
|
virtual_overseer.recv().await,
|
|
AllMessages::RuntimeApi(
|
|
RuntimeApiMessage::Request(parent, RuntimeApiRequest::AvailabilityCores(tx))
|
|
) if parent == hash => {
|
|
tx.send(Ok(Vec::new())).unwrap();
|
|
}
|
|
);
|
|
|
|
send_block_header(&mut virtual_overseer, hash, number).await;
|
|
|
|
assert_matches!(
|
|
virtual_overseer.recv().await,
|
|
AllMessages::ChainApi(
|
|
ChainApiMessage::Ancestors{hash: block_hash, k, response_channel: tx}
|
|
) if block_hash == hash && k == ancestry_len as usize => {
|
|
tx.send(Ok(ancestry_hashes.clone())).unwrap();
|
|
}
|
|
);
|
|
|
|
assert_matches!(
|
|
virtual_overseer.recv().await,
|
|
AllMessages::RuntimeApi(
|
|
RuntimeApiMessage::Request(parent, RuntimeApiRequest::SessionIndexForChild(tx))
|
|
) if parent == hash => {
|
|
tx.send(Ok(session)).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
|
|
});
|
|
}
|