statement-distribution: validator disabling (#1841)

Closes #1591.

The purpose of this PR is filter out backing statements from the network
signed by disabled validators. This is just an optimization, since we
will do filtering in the runtime in #1863 to avoid nodes to filter
garbage out at block production time.

- [x] Ensure it's ok to fiddle with the mask of manifests
- [x] Write more unit tests
- [x] Test locally
- [x] simple zombienet test
- [x] PRDoc

---------

Co-authored-by: Tsvetomir Dimitrov <tsvetomir@parity.io>
This commit is contained in:
ordian
2024-01-10 10:32:52 +01:00
committed by GitHub
parent 01ea45c3a1
commit a4195326b9
14 changed files with 1577 additions and 833 deletions
@@ -187,12 +187,30 @@ impl TestState {
collator: None,
})
}),
disabled_validators: Default::default(),
para_data: (0..self.session_info.validator_groups.len())
.map(|i| (ParaId::from(i as u32), PerParaData::new(1, vec![1, 2, 3].into())))
.collect(),
minimum_backing_votes: 2,
}
}
fn make_dummy_leaf_with_disabled_validators(
&self,
relay_parent: Hash,
disabled_validators: Vec<ValidatorIndex>,
) -> TestLeaf {
TestLeaf { disabled_validators, ..self.make_dummy_leaf(relay_parent) }
}
fn make_dummy_leaf_with_min_backing_votes(
&self,
relay_parent: Hash,
minimum_backing_votes: u32,
) -> TestLeaf {
TestLeaf { minimum_backing_votes, ..self.make_dummy_leaf(relay_parent) }
}
fn make_availability_cores(&self, f: impl Fn(usize) -> CoreState) -> Vec<CoreState> {
(0..self.session_info.validator_groups.len()).map(f).collect()
}
@@ -240,6 +258,19 @@ impl TestState {
.collect()
}
fn index_within_group(
&self,
group_index: GroupIndex,
validator_index: ValidatorIndex,
) -> Option<usize> {
self.session_info
.validator_groups
.get(group_index)
.unwrap()
.iter()
.position(|&i| i == validator_index)
}
fn discovery_id(&self, validator_index: ValidatorIndex) -> AuthorityDiscoveryId {
self.session_info.discovery_keys[validator_index.0 as usize].clone()
}
@@ -284,7 +315,7 @@ impl TestState {
&mut self,
peer: PeerId,
request: AttestedCandidateRequest,
) -> impl Future<Output = sc_network::config::OutgoingResponse> {
) -> impl Future<Output = Option<sc_network::config::OutgoingResponse>> {
let (tx, rx) = futures::channel::oneshot::channel();
let req = sc_network::config::IncomingRequest {
peer,
@@ -293,7 +324,7 @@ impl TestState {
};
self.req_sender.send(req).await.unwrap();
rx.map(|r| r.unwrap())
rx.map(|r| r.ok())
}
}
@@ -366,7 +397,9 @@ struct TestLeaf {
parent_hash: Hash,
session: SessionIndex,
availability_cores: Vec<CoreState>,
disabled_validators: Vec<ValidatorIndex>,
para_data: Vec<(ParaId, PerParaData)>,
minimum_backing_votes: u32,
}
impl TestLeaf {
@@ -447,9 +480,7 @@ async fn setup_test_and_connect_peers(
}
}
activate_leaf(overseer, &test_leaf, &state, true).await;
answer_expected_hypothetical_depth_request(overseer, vec![], Some(relay_parent), false).await;
activate_leaf(overseer, &test_leaf, &state, true, vec![]).await;
// Send gossip topology.
send_new_topology(overseer, state.make_dummy_topology()).await;
@@ -472,6 +503,7 @@ async fn activate_leaf(
leaf: &TestLeaf,
test_state: &TestState,
is_new_session: bool,
hypothetical_frontier: Vec<(HypotheticalCandidate, FragmentTreeMembership)>,
) {
let activated = new_leaf(leaf.hash, leaf.number);
@@ -481,7 +513,14 @@ async fn activate_leaf(
))))
.await;
handle_leaf_activation(virtual_overseer, leaf, test_state, is_new_session).await;
handle_leaf_activation(
virtual_overseer,
leaf,
test_state,
is_new_session,
hypothetical_frontier,
)
.await;
}
async fn handle_leaf_activation(
@@ -489,8 +528,18 @@ async fn handle_leaf_activation(
leaf: &TestLeaf,
test_state: &TestState,
is_new_session: bool,
hypothetical_frontier: Vec<(HypotheticalCandidate, FragmentTreeMembership)>,
) {
let TestLeaf { number, hash, parent_hash, para_data, session, availability_cores } = leaf;
let TestLeaf {
number,
hash,
parent_hash,
para_data,
session,
availability_cores,
disabled_validators,
minimum_backing_votes,
} = leaf;
assert_matches!(
virtual_overseer.recv().await,
@@ -530,51 +579,82 @@ async fn handle_leaf_activation(
}
);
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::AvailabilityCores(tx))) if parent == *hash => {
tx.send(Ok(availability_cores.clone())).unwrap();
}
);
let validator_groups = test_state.session_info.validator_groups.to_vec();
let group_rotation_info =
GroupRotationInfo { session_start_block: 1, group_rotation_frequency: 12, now: 1 };
assert_matches!(
virtual_overseer.recv().await,
AllMessages::RuntimeApi(
RuntimeApiMessage::Request(parent, RuntimeApiRequest::ValidatorGroups(tx))) if parent == *hash => {
tx.send(Ok((validator_groups, group_rotation_info))).unwrap();
}
);
if is_new_session {
assert_matches!(
virtual_overseer.recv().await,
AllMessages::RuntimeApi(
RuntimeApiMessage::Request(parent, RuntimeApiRequest::SessionInfo(s, tx))) if parent == *hash && s == *session => {
loop {
match virtual_overseer.recv().await {
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
_parent,
RuntimeApiRequest::Version(tx),
)) => {
tx.send(Ok(RuntimeApiRequest::DISABLED_VALIDATORS_RUNTIME_REQUIREMENT)).unwrap();
},
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
parent,
RuntimeApiRequest::DisabledValidators(tx),
)) if parent == *hash => {
tx.send(Ok(disabled_validators.clone())).unwrap();
},
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
_parent,
RuntimeApiRequest::DisabledValidators(tx),
)) => {
tx.send(Ok(Vec::new())).unwrap();
},
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
_parent, // assume all active leaves are in the same session
RuntimeApiRequest::SessionIndexForChild(tx),
)) => {
tx.send(Ok(*session)).unwrap();
},
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
parent,
RuntimeApiRequest::SessionInfo(s, tx),
)) if parent == *hash && s == *session => {
assert!(is_new_session, "only expecting this call in a new session");
tx.send(Ok(Some(test_state.session_info.clone()))).unwrap();
}
);
assert_matches!(
virtual_overseer.recv().await,
},
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
parent,
RuntimeApiRequest::MinimumBackingVotes(session_index, tx),
)) if parent == *hash && session_index == *session => {
tx.send(Ok(2)).unwrap();
}
);
assert!(is_new_session, "only expecting this call in a new session");
tx.send(Ok(*minimum_backing_votes)).unwrap();
},
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
parent,
RuntimeApiRequest::AvailabilityCores(tx),
)) if parent == *hash => {
tx.send(Ok(availability_cores.clone())).unwrap();
},
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
parent,
RuntimeApiRequest::ValidatorGroups(tx),
)) if parent == *hash => {
let validator_groups = test_state.session_info.validator_groups.to_vec();
let group_rotation_info = GroupRotationInfo {
session_start_block: 1,
group_rotation_frequency: 12,
now: 1,
};
tx.send(Ok((validator_groups, group_rotation_info))).unwrap();
},
AllMessages::ProspectiveParachains(
ProspectiveParachainsMessage::GetHypotheticalFrontier(req, tx),
) => {
assert_eq!(req.fragment_tree_relay_parent, Some(*hash));
assert!(!req.backed_in_path_only);
for (i, (candidate, _)) in hypothetical_frontier.iter().enumerate() {
assert!(
req.candidates.iter().any(|c| &c == &candidate),
"did not receive request for hypothetical candidate {}",
i,
);
}
tx.send(hypothetical_frontier).unwrap();
// this is the last expected runtime api call
break
},
msg => panic!("unexpected runtime API call: {msg:?}"),
}
}
}
@@ -614,16 +694,14 @@ async fn handle_sent_request(
async fn answer_expected_hypothetical_depth_request(
virtual_overseer: &mut VirtualOverseer,
responses: Vec<(HypotheticalCandidate, FragmentTreeMembership)>,
expected_leaf_hash: Option<Hash>,
expected_backed_in_path_only: bool,
) {
assert_matches!(
virtual_overseer.recv().await,
AllMessages::ProspectiveParachains(
ProspectiveParachainsMessage::GetHypotheticalFrontier(req, tx)
) => {
assert_eq!(req.fragment_tree_relay_parent, expected_leaf_hash);
assert_eq!(req.backed_in_path_only, expected_backed_in_path_only);
assert_eq!(req.fragment_tree_relay_parent, None);
assert!(!req.backed_in_path_only);
for (i, (candidate, _)) in responses.iter().enumerate() {
assert!(
req.candidates.iter().any(|c| &c == &candidate),