prospective-parachains: respond with multiple backable candidates (#3160)

Fixes https://github.com/paritytech/polkadot-sdk/issues/3129
This commit is contained in:
Alin Dima
2024-02-06 10:21:28 +02:00
committed by GitHub
parent 53f615de50
commit 7df1ae3b81
9 changed files with 844 additions and 92 deletions
@@ -756,53 +756,127 @@ impl FragmentTree {
depths.iter_ones().collect()
}
/// Select a candidate after the given `required_path` which passes
/// the predicate.
/// Select `count` candidates after the given `required_path` which pass
/// the predicate and have not already been backed on chain.
///
/// If there are multiple possibilities, this will select the first one.
///
/// This returns `None` if there is no candidate meeting those criteria.
/// Does an exhaustive search into the tree starting after `required_path`.
/// If there are multiple possibilities of size `count`, this will select the first one.
/// If there is no chain of size `count` that matches the criteria, this will return the largest
/// chain it could find with the criteria.
/// If there are no candidates meeting those criteria, returns an empty `Vec`.
/// Cycles are accepted, see module docs for the `Cycles` section.
///
/// The intention of the `required_path` is to allow queries on the basis of
/// one or more candidates which were previously pending availability becoming
/// available and opening up more room on the core.
pub(crate) fn select_child(
pub(crate) fn select_children(
&self,
required_path: &[CandidateHash],
count: u32,
pred: impl Fn(&CandidateHash) -> bool,
) -> Option<CandidateHash> {
) -> Vec<CandidateHash> {
let base_node = {
// traverse the required path.
let mut node = NodePointer::Root;
for required_step in required_path {
node = self.node_candidate_child(node, &required_step)?;
if let Some(next_node) = self.node_candidate_child(node, &required_step) {
node = next_node;
} else {
return vec![]
};
}
node
};
// TODO [now]: taking the first selection might introduce bias
// TODO: taking the first best selection might introduce bias
// or become gameable.
//
// For plausibly unique parachains, this shouldn't matter much.
// figure out alternative selection criteria?
match base_node {
self.select_children_inner(base_node, count, count, &pred, &mut vec![])
}
// Try finding a candidate chain starting from `base_node` of length `expected_count`.
// If not possible, return the longest one we could find.
// Does a depth-first search, since we're optimistic that there won't be more than one such
// chains (parachains shouldn't usually have forks). So in the usual case, this will conclude
// in `O(expected_count)`.
// Cycles are accepted, but this doesn't allow for infinite execution time, because the maximum
// depth we'll reach is `expected_count`.
//
// Worst case performance is `O(num_forks ^ expected_count)`.
// Although an exponential function, this is actually a constant that can only be altered via
// sudo/governance, because:
// 1. `num_forks` at a given level is at most `max_candidate_depth * max_validators_per_core`
// (because each validator in the assigned group can second `max_candidate_depth`
// candidates). The prospective-parachains subsystem assumes that the number of para forks is
// limited by collator-protocol and backing subsystems. In practice, this is a constant which
// can only be altered by sudo or governance.
// 2. `expected_count` is equal to the number of cores a para is scheduled on (in an elastic
// scaling scenario). For non-elastic-scaling, this is just 1. In practice, this should be a
// small number (1-3), capped by the total number of available cores (a constant alterable
// only via governance/sudo).
fn select_children_inner(
&self,
base_node: NodePointer,
expected_count: u32,
remaining_count: u32,
pred: &dyn Fn(&CandidateHash) -> bool,
accumulator: &mut Vec<CandidateHash>,
) -> Vec<CandidateHash> {
if remaining_count == 0 {
// The best option is the chain we've accumulated so far.
return accumulator.to_vec();
}
let children: Vec<_> = match base_node {
NodePointer::Root => self
.nodes
.iter()
.take_while(|n| n.parent == NodePointer::Root)
.filter(|n| self.scope.get_pending_availability(&n.candidate_hash).is_none())
.filter(|n| pred(&n.candidate_hash))
.map(|n| n.candidate_hash)
.next(),
NodePointer::Storage(ptr) => self.nodes[ptr]
.children
.iter()
.filter(|n| self.scope.get_pending_availability(&n.1).is_none())
.filter(|n| pred(&n.1))
.map(|n| n.1)
.next(),
.enumerate()
.take_while(|(_, n)| n.parent == NodePointer::Root)
.filter(|(_, n)| self.scope.get_pending_availability(&n.candidate_hash).is_none())
.filter(|(_, n)| pred(&n.candidate_hash))
.map(|(ptr, n)| (NodePointer::Storage(ptr), n.candidate_hash))
.collect(),
NodePointer::Storage(base_node_ptr) => {
let base_node = &self.nodes[base_node_ptr];
base_node
.children
.iter()
.filter(|(_, hash)| self.scope.get_pending_availability(&hash).is_none())
.filter(|(_, hash)| pred(&hash))
.map(|(ptr, hash)| (*ptr, *hash))
.collect()
},
};
let mut best_result = accumulator.clone();
for (child_ptr, child_hash) in children {
accumulator.push(child_hash);
let result = self.select_children_inner(
child_ptr,
expected_count,
remaining_count - 1,
&pred,
accumulator,
);
accumulator.pop();
// Short-circuit the search if we've found the right length. Otherwise, we'll
// search for a max.
if result.len() == expected_count as usize {
return result
} else if best_result.len() < result.len() {
best_result = result;
}
}
best_result
}
fn populate_from_bases(&mut self, storage: &CandidateStorage, initial_bases: Vec<NodePointer>) {
@@ -987,6 +1061,7 @@ mod tests {
use polkadot_node_subsystem_util::inclusion_emulator::InboundHrmpLimitations;
use polkadot_primitives::{BlockNumber, CandidateCommitments, CandidateDescriptor, HeadData};
use polkadot_primitives_test_helpers as test_helpers;
use std::iter;
fn make_constraints(
min_relay_parent_number: BlockNumber,
@@ -1524,6 +1599,21 @@ mod tests {
assert_eq!(tree.nodes[2].candidate_hash, candidate_a_hash);
assert_eq!(tree.nodes[3].candidate_hash, candidate_a_hash);
assert_eq!(tree.nodes[4].candidate_hash, candidate_a_hash);
for count in 1..10 {
assert_eq!(
tree.select_children(&[], count, |_| true),
iter::repeat(candidate_a_hash)
.take(std::cmp::min(count as usize, max_depth + 1))
.collect::<Vec<_>>()
);
assert_eq!(
tree.select_children(&[candidate_a_hash], count - 1, |_| true),
iter::repeat(candidate_a_hash)
.take(std::cmp::min(count as usize - 1, max_depth))
.collect::<Vec<_>>()
);
}
}
#[test]
@@ -1591,6 +1681,35 @@ mod tests {
assert_eq!(tree.nodes[2].candidate_hash, candidate_a_hash);
assert_eq!(tree.nodes[3].candidate_hash, candidate_b_hash);
assert_eq!(tree.nodes[4].candidate_hash, candidate_a_hash);
assert_eq!(tree.select_children(&[], 1, |_| true), vec![candidate_a_hash],);
assert_eq!(
tree.select_children(&[], 2, |_| true),
vec![candidate_a_hash, candidate_b_hash],
);
assert_eq!(
tree.select_children(&[], 3, |_| true),
vec![candidate_a_hash, candidate_b_hash, candidate_a_hash],
);
assert_eq!(
tree.select_children(&[candidate_a_hash], 2, |_| true),
vec![candidate_b_hash, candidate_a_hash],
);
assert_eq!(
tree.select_children(&[], 6, |_| true),
vec![
candidate_a_hash,
candidate_b_hash,
candidate_a_hash,
candidate_b_hash,
candidate_a_hash
],
);
assert_eq!(
tree.select_children(&[candidate_a_hash, candidate_b_hash], 6, |_| true),
vec![candidate_a_hash, candidate_b_hash, candidate_a_hash,],
);
}
#[test]
@@ -146,12 +146,20 @@ async fn run_iteration<Context>(
handle_candidate_seconded(view, para, candidate_hash),
ProspectiveParachainsMessage::CandidateBacked(para, candidate_hash) =>
handle_candidate_backed(&mut *ctx, view, para, candidate_hash).await?,
ProspectiveParachainsMessage::GetBackableCandidate(
ProspectiveParachainsMessage::GetBackableCandidates(
relay_parent,
para,
count,
required_path,
tx,
) => answer_get_backable_candidate(&view, relay_parent, para, required_path, tx),
) => answer_get_backable_candidates(
&view,
relay_parent,
para,
count,
required_path,
tx,
),
ProspectiveParachainsMessage::GetHypotheticalFrontier(request, tx) =>
answer_hypothetical_frontier_request(&view, request, tx),
ProspectiveParachainsMessage::GetTreeMembership(para, candidate, tx) =>
@@ -552,12 +560,13 @@ async fn handle_candidate_backed<Context>(
Ok(())
}
fn answer_get_backable_candidate(
fn answer_get_backable_candidates(
view: &View,
relay_parent: Hash,
para: ParaId,
count: u32,
required_path: Vec<CandidateHash>,
tx: oneshot::Sender<Option<(CandidateHash, Hash)>>,
tx: oneshot::Sender<Vec<(CandidateHash, Hash)>>,
) {
let data = match view.active_leaves.get(&relay_parent) {
None => {
@@ -568,7 +577,7 @@ fn answer_get_backable_candidate(
"Requested backable candidate for inactive relay-parent."
);
let _ = tx.send(None);
let _ = tx.send(vec![]);
return
},
Some(d) => d,
@@ -583,7 +592,7 @@ fn answer_get_backable_candidate(
"Requested backable candidate for inactive para."
);
let _ = tx.send(None);
let _ = tx.send(vec![]);
return
},
Some(tree) => tree,
@@ -598,15 +607,32 @@ fn answer_get_backable_candidate(
"No candidate storage for active para",
);
let _ = tx.send(None);
let _ = tx.send(vec![]);
return
},
Some(s) => s,
};
let Some(child_hash) =
tree.select_child(&required_path, |candidate| storage.is_backed(candidate))
else {
let backable_candidates: Vec<_> = tree
.select_children(&required_path, count, |candidate| storage.is_backed(candidate))
.into_iter()
.filter_map(|child_hash| {
storage.relay_parent_by_candidate_hash(&child_hash).map_or_else(
|| {
gum::error!(
target: LOG_TARGET,
?child_hash,
para_id = ?para,
"Candidate is present in fragment tree but not in candidate's storage!",
);
None
},
|parent_hash| Some((child_hash, parent_hash)),
)
})
.collect();
if backable_candidates.is_empty() {
gum::trace!(
target: LOG_TARGET,
?required_path,
@@ -614,30 +640,16 @@ fn answer_get_backable_candidate(
%relay_parent,
"Could not find any backable candidate",
);
let _ = tx.send(None);
return
};
let Some(candidate_relay_parent) = storage.relay_parent_by_candidate_hash(&child_hash) else {
gum::error!(
} else {
gum::trace!(
target: LOG_TARGET,
?child_hash,
para_id = ?para,
"Candidate is present in fragment tree but not in candidate's storage!",
?relay_parent,
?backable_candidates,
"Found backable candidates",
);
let _ = tx.send(None);
return
};
}
gum::trace!(
target: LOG_TARGET,
?relay_parent,
candidate_hash = ?child_hash,
?candidate_relay_parent,
"Found backable candidate",
);
let _ = tx.send(Some((child_hash, candidate_relay_parent)));
let _ = tx.send(backable_candidates);
}
fn answer_hypothetical_frontier_request(
@@ -403,25 +403,28 @@ async fn get_membership(
assert_eq!(resp, expected_membership_response);
}
async fn get_backable_candidate(
async fn get_backable_candidates(
virtual_overseer: &mut VirtualOverseer,
leaf: &TestLeaf,
para_id: ParaId,
required_path: Vec<CandidateHash>,
expected_result: Option<(CandidateHash, Hash)>,
count: u32,
expected_result: Vec<(CandidateHash, Hash)>,
) {
let (tx, rx) = oneshot::channel();
virtual_overseer
.send(overseer::FromOrchestra::Communication {
msg: ProspectiveParachainsMessage::GetBackableCandidate(
msg: ProspectiveParachainsMessage::GetBackableCandidates(
leaf.hash,
para_id,
count,
required_path,
tx,
),
})
.await;
let resp = rx.await.unwrap();
assert_eq!(resp.len(), expected_result.len());
assert_eq!(resp, expected_result);
}
@@ -849,9 +852,9 @@ fn check_candidate_on_multiple_forks() {
assert_eq!(view.candidate_storage.get(&2.into()).unwrap().len(), (0, 0));
}
// Backs some candidates and tests `GetBackableCandidate`.
// Backs some candidates and tests `GetBackableCandidates` when requesting a single candidate.
#[test]
fn check_backable_query() {
fn check_backable_query_single_candidate() {
let test_state = TestState::default();
let view = test_harness(|mut virtual_overseer| async move {
// Leaf A
@@ -896,26 +899,38 @@ fn check_backable_query() {
introduce_candidate(&mut virtual_overseer, candidate_b.clone(), pvd_b).await;
// Should not get any backable candidates.
get_backable_candidate(
get_backable_candidates(
&mut virtual_overseer,
&leaf_a,
1.into(),
vec![candidate_hash_a],
None,
1,
vec![],
)
.await;
get_backable_candidates(
&mut virtual_overseer,
&leaf_a,
1.into(),
vec![candidate_hash_a],
0,
vec![],
)
.await;
get_backable_candidates(&mut virtual_overseer, &leaf_a, 1.into(), vec![], 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_candidate(
get_backable_candidates(
&mut virtual_overseer,
&leaf_a,
1.into(),
vec![candidate_hash_a],
None,
1,
vec![],
)
.await;
@@ -923,31 +938,46 @@ fn check_backable_query() {
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(), vec![], 1, vec![]).await;
get_backable_candidates(
&mut virtual_overseer,
&leaf_a,
2.into(),
vec![candidate_hash_a],
1,
vec![],
)
.await;
// Get backable candidate.
get_backable_candidate(
get_backable_candidates(
&mut virtual_overseer,
&leaf_a,
1.into(),
vec![],
Some((candidate_hash_a, leaf_a.hash)),
1,
vec![(candidate_hash_a, leaf_a.hash)],
)
.await;
get_backable_candidate(
get_backable_candidates(
&mut virtual_overseer,
&leaf_a,
1.into(),
vec![candidate_hash_a],
Some((candidate_hash_b, leaf_a.hash)),
1,
vec![(candidate_hash_b, leaf_a.hash)],
)
.await;
// Should not get anything at the wrong path.
get_backable_candidate(
get_backable_candidates(
&mut virtual_overseer,
&leaf_a,
1.into(),
vec![candidate_hash_b],
None,
1,
vec![],
)
.await;
@@ -961,6 +991,572 @@ fn check_backable_query() {
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(), vec![], 1, vec![])
.await;
get_backable_candidates(&mut virtual_overseer, &leaf_a, 2.into(), vec![], 5, vec![])
.await;
get_backable_candidates(
&mut virtual_overseer,
&leaf_a,
2.into(),
vec![candidate_hash_a],
1,
vec![],
)
.await;
// Test various scenarios with various counts.
// empty required_path
{
get_backable_candidates(
&mut virtual_overseer,
&leaf_a,
1.into(),
vec![],
1,
vec![(candidate_hash_a, leaf_a.hash)],
)
.await;
get_backable_candidates(
&mut virtual_overseer,
&leaf_a,
1.into(),
vec![],
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],
1,
vec![(candidate_hash_b, leaf_a.hash)],
)
.await;
get_backable_candidates(
&mut virtual_overseer,
&leaf_a,
1.into(),
vec![candidate_hash_a],
2,
vec![(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],
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],
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
{
get_backable_candidates(
&mut virtual_overseer,
&leaf_a,
1.into(),
vec![candidate_hash_a, candidate_hash_b],
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_c],
1,
vec![(candidate_hash_h, 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],
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.
{
let required_paths = vec![
vec![candidate_hash_a, candidate_hash_b, candidate_hash_e],
vec![
candidate_hash_a,
candidate_hash_c,
candidate_hash_h,
candidate_hash_i,
candidate_hash_j,
],
];
for path in required_paths {
for count in 1..4 {
get_backable_candidates(
&mut virtual_overseer,
&leaf_a,
1.into(),
path.clone(),
count,
vec![],
)
.await;
}
}
}
// Should not get anything at the wrong path.
get_backable_candidates(
&mut virtual_overseer,
&leaf_a,
1.into(),
vec![candidate_hash_b],
1,
vec![],
)
.await;
get_backable_candidates(
&mut virtual_overseer,
&leaf_a,
1.into(),
vec![candidate_hash_b, candidate_hash_a],
3,
vec![],
)
.await;
get_backable_candidates(
&mut virtual_overseer,
&leaf_a,
1.into(),
vec![candidate_hash_a, candidate_hash_b, candidate_hash_c],
3,
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));
}
// A tree with multiple roots.
// Parachain 1 looks like this:
// (imaginary root)
// | |
// +----B---+ A
// | | | |
// | | | 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 B
let (candidate_b, pvd_b) = make_candidate(
leaf_a.hash,
leaf_a.number,
1.into(),
HeadData(vec![1, 2, 3]),
HeadData(vec![2]),
test_state.validation_code_hash,
);
let candidate_hash_b = candidate_b.hash();
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;
// 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_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(), vec![], 1, vec![])
.await;
get_backable_candidates(&mut virtual_overseer, &leaf_a, 2.into(), vec![], 5, vec![])
.await;
get_backable_candidates(
&mut virtual_overseer,
&leaf_a,
2.into(),
vec![candidate_hash_a],
1,
vec![],
)
.await;
// Test various scenarios with various counts.
// empty required_path
{
get_backable_candidates(
&mut virtual_overseer,
&leaf_a,
1.into(),
vec![],
1,
vec![(candidate_hash_b, leaf_a.hash)],
)
.await;
get_backable_candidates(
&mut virtual_overseer,
&leaf_a,
1.into(),
vec![],
2,
vec![(candidate_hash_b, leaf_a.hash), (candidate_hash_d, leaf_a.hash)],
)
.await;
get_backable_candidates(
&mut virtual_overseer,
&leaf_a,
1.into(),
vec![],
4,
vec![
(candidate_hash_a, leaf_a.hash),
(candidate_hash_c, leaf_a.hash),
(candidate_hash_h, leaf_a.hash),
(candidate_hash_i, leaf_a.hash),
],
)
.await;
}
// required path of 1
{
get_backable_candidates(
&mut virtual_overseer,
&leaf_a,
1.into(),
vec![candidate_hash_a],
1,
vec![(candidate_hash_c, leaf_a.hash)],
)
.await;
get_backable_candidates(
&mut virtual_overseer,
&leaf_a,
1.into(),
vec![candidate_hash_b],
1,
vec![(candidate_hash_d, leaf_a.hash)],
)
.await;
get_backable_candidates(
&mut virtual_overseer,
&leaf_a,
1.into(),
vec![candidate_hash_a],
2,
vec![(candidate_hash_c, leaf_a.hash), (candidate_hash_h, leaf_a.hash)],
)
.await;
// If the requested count exceeds the largest chain, return the longest
// chain we can get.
for count in 2..10 {
get_backable_candidates(
&mut virtual_overseer,
&leaf_a,
1.into(),
vec![candidate_hash_b],
count,
vec![(candidate_hash_f, leaf_a.hash), (candidate_hash_g, leaf_a.hash)],
)
.await;
}
}
// required path of 2
{
get_backable_candidates(
&mut virtual_overseer,
&leaf_a,
1.into(),
vec![candidate_hash_b, candidate_hash_f],
1,
vec![(candidate_hash_g, leaf_a.hash)],
)
.await;
get_backable_candidates(
&mut virtual_overseer,
&leaf_a,
1.into(),
vec![candidate_hash_a, candidate_hash_c],
1,
vec![(candidate_hash_h, 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],
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.
{
let required_paths = vec![
vec![candidate_hash_b, candidate_hash_f, candidate_hash_g],
vec![candidate_hash_b, candidate_hash_e],
vec![candidate_hash_b, candidate_hash_d],
vec![
candidate_hash_a,
candidate_hash_c,
candidate_hash_h,
candidate_hash_i,
candidate_hash_j,
],
];
for path in required_paths {
for count in 1..4 {
get_backable_candidates(
&mut virtual_overseer,
&leaf_a,
1.into(),
path.clone(),
count,
vec![],
)
.await;
}
}
}
// Should not get anything at the wrong path.
get_backable_candidates(
&mut virtual_overseer,
&leaf_a,
1.into(),
vec![candidate_hash_d],
1,
vec![],
)
.await;
get_backable_candidates(
&mut virtual_overseer,
&leaf_a,
1.into(),
vec![candidate_hash_b, candidate_hash_a],
3,
vec![],
)
.await;
get_backable_candidates(
&mut virtual_overseer,
&leaf_a,
1.into(),
vec![candidate_hash_a, candidate_hash_c, candidate_hash_d],
3,
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() {
@@ -1448,12 +2044,13 @@ fn persists_pending_availability_candidate() {
second_candidate(&mut virtual_overseer, candidate_b.clone()).await;
back_candidate(&mut virtual_overseer, &candidate_b, candidate_hash_b).await;
get_backable_candidate(
get_backable_candidates(
&mut virtual_overseer,
&leaf_b,
para_id,
vec![candidate_hash_a],
Some((candidate_hash_b, leaf_b_hash)),
1,
vec![(candidate_hash_b, leaf_b_hash)],
)
.await;
@@ -1512,12 +2109,13 @@ fn backwards_compatible() {
second_candidate(&mut virtual_overseer, candidate_a.clone()).await;
back_candidate(&mut virtual_overseer, &candidate_a, candidate_hash_a).await;
get_backable_candidate(
get_backable_candidates(
&mut virtual_overseer,
&leaf_a,
para_id,
vec![],
Some((candidate_hash_a, candidate_relay_parent)),
1,
vec![(candidate_hash_a, candidate_relay_parent)],
)
.await;
@@ -1537,7 +2135,7 @@ fn backwards_compatible() {
)
.await;
get_backable_candidate(&mut virtual_overseer, &leaf_b, para_id, vec![], None).await;
get_backable_candidates(&mut virtual_overseer, &leaf_b, para_id, vec![], 1, vec![]).await;
virtual_overseer
});
+5 -2
View File
@@ -806,15 +806,18 @@ async fn get_backable_candidate(
) -> Result<Option<(CandidateHash, Hash)>, Error> {
let (tx, rx) = oneshot::channel();
sender
.send_message(ProspectiveParachainsMessage::GetBackableCandidate(
.send_message(ProspectiveParachainsMessage::GetBackableCandidates(
relay_parent,
para_id,
1, // core count hardcoded to 1, until elastic scaling is implemented and enabled.
required_path,
tx,
))
.await;
rx.await.map_err(Error::CanceledBackableCandidate)
rx.await
.map_err(Error::CanceledBackableCandidate)
.map(|res| res.get(0).copied())
}
/// The availability bitfield for a given core is the transpose
+12 -7
View File
@@ -373,13 +373,18 @@ mod select_candidates {
let _ = sender.send(response);
},
AllMessages::ProspectiveParachains(
ProspectiveParachainsMessage::GetBackableCandidate(.., tx),
) => match prospective_parachains_mode {
ProspectiveParachainsMode::Enabled { .. } => {
let _ = tx.send(candidates_iter.next());
},
ProspectiveParachainsMode::Disabled =>
panic!("unexpected prospective parachains request"),
ProspectiveParachainsMessage::GetBackableCandidates(_, _, count, _, tx),
) => {
assert_eq!(count, 1);
match prospective_parachains_mode {
ProspectiveParachainsMode::Enabled { .. } => {
let _ =
tx.send(candidates_iter.next().map_or_else(Vec::new, |c| vec![c]));
},
ProspectiveParachainsMode::Disabled =>
panic!("unexpected prospective parachains request"),
}
},
_ => panic!("Unexpected message: {:?}", from_job),
}
@@ -1128,14 +1128,16 @@ pub enum ProspectiveParachainsMessage {
/// has been backed. This requires that the candidate was successfully introduced in
/// the past.
CandidateBacked(ParaId, CandidateHash),
/// Get a backable candidate hash along with its relay parent for the given parachain,
/// Get N backable candidate hashes along with their relay parents for the given parachain,
/// under the given relay-parent hash, which is a descendant of the given candidate hashes.
/// N should represent the number of scheduled cores of this ParaId.
/// Returns `None` on the channel if no such candidate exists.
GetBackableCandidate(
GetBackableCandidates(
Hash,
ParaId,
u32,
Vec<CandidateHash>,
oneshot::Sender<Option<(CandidateHash, Hash)>>,
oneshot::Sender<Vec<(CandidateHash, Hash)>>,
),
/// Get the hypothetical frontier membership of candidates with the given properties
/// under the specified active leaves' fragment trees.