Skip to content

Commit 464bbbc

Browse files
sandreimgithub-actions[bot]
authored andcommitted
Parachains runtime: properly filter backed candidate votes (paritytech#9564)
The `filter_backed_statements_from_disabled_validators` function does not properly map indices in the validator group to indices in the validity votes vec. This PR fixes that. TODO: - [x] add more tests - [x] PRDoc --------- Signed-off-by: Andrei Sandu <andrei-mihail@parity.io> Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent ab4c718 commit 464bbbc

3 files changed

Lines changed: 133 additions & 22 deletions

File tree

polkadot/runtime/parachains/src/paras_inherent/mod.rs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1316,16 +1316,24 @@ fn filter_backed_statements_from_disabled_validators<
13161316
// The indices of statements from disabled validators in `BackedCandidate`. We have to drop
13171317
// these.
13181318
let indices_to_drop = disabled_indices.clone() & &validator_indices;
1319-
// Apply the bitmask to drop the disabled validator from `validator_indices`
1320-
validator_indices &= !disabled_indices;
1321-
// Update the backed candidate
1322-
bc.set_validator_indices_and_core_index(validator_indices, maybe_injected_core_index);
13231319

13241320
// Remove the corresponding votes from `validity_votes`
13251321
for idx in indices_to_drop.iter_ones().rev() {
1326-
bc.validity_votes_mut().remove(idx);
1322+
// Map the index in `indices_to_drop` (which is an index into the validator group)
1323+
// to the index in the validity votes vector, which might have less number of votes,
1324+
// than validators assigned to the group.
1325+
//
1326+
// For each index `idx` in `indices_to_drop`, the corresponding index in the
1327+
// validity votes vector is the number of `1` bits in `validator_indices` before `idx`.
1328+
let mapped_idx = validator_indices[..idx].count_ones();
1329+
bc.validity_votes_mut().remove(mapped_idx);
13271330
}
13281331

1332+
// Apply the bitmask to drop the disabled validator from `validator_indices`
1333+
validator_indices &= !disabled_indices;
1334+
// Update the backed candidate
1335+
bc.set_validator_indices_and_core_index(validator_indices, maybe_injected_core_index);
1336+
13291337
// By filtering votes we might render the candidate invalid and cause a failure in
13301338
// [`process_candidates`]. To avoid this we have to perform a sanity check here. If there
13311339
// are not enough backing votes after filtering we will remove the whole candidate.

polkadot/runtime/parachains/src/paras_inherent/tests.rs

Lines changed: 113 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2489,7 +2489,7 @@ mod sanitizers {
24892489

24902490
// Generate test data for the candidates and assert that the environment is set as expected
24912491
// (check the comments for details)
2492-
fn get_test_data_one_core_per_para() -> TestData {
2492+
fn get_test_data_one_core_per_para(backing_kind: BackingKind) -> TestData {
24932493
const RELAY_PARENT_NUM: u32 = 3;
24942494

24952495
// Add the relay parent to `shared` pallet. Otherwise some code (e.g. filtering backing
@@ -2516,6 +2516,10 @@ mod sanitizers {
25162516
sp_keyring::Sr25519Keyring::Charlie,
25172517
sp_keyring::Sr25519Keyring::Dave,
25182518
sp_keyring::Sr25519Keyring::Eve,
2519+
sp_keyring::Sr25519Keyring::Ferdie,
2520+
sp_keyring::Sr25519Keyring::One,
2521+
sp_keyring::Sr25519Keyring::Two,
2522+
sp_keyring::Sr25519Keyring::AliceStash,
25192523
];
25202524
for validator in validators.iter() {
25212525
Keystore::sr25519_generate_new(
@@ -2544,8 +2548,8 @@ mod sanitizers {
25442548

25452549
// Set the validator groups in `scheduler`
25462550
scheduler::Pallet::<Test>::set_validator_groups(vec![
2547-
vec![ValidatorIndex(0), ValidatorIndex(1)],
2548-
vec![ValidatorIndex(2), ValidatorIndex(3)],
2551+
vec![ValidatorIndex(0), ValidatorIndex(1), ValidatorIndex(2), ValidatorIndex(3)],
2552+
vec![ValidatorIndex(4), ValidatorIndex(5), ValidatorIndex(6), ValidatorIndex(7)],
25492553
]);
25502554

25512555
// Update scheduler's claimqueue with the parachains
@@ -2600,8 +2604,8 @@ mod sanitizers {
26002604
// Callback used for backing candidates
26012605
let group_validators = |group_index: GroupIndex| {
26022606
match group_index {
2603-
group_index if group_index == GroupIndex::from(0) => Some(vec![0, 1]),
2604-
group_index if group_index == GroupIndex::from(1) => Some(vec![2, 3]),
2607+
group_index if group_index == GroupIndex::from(0) => Some(vec![0, 1, 2, 3]),
2608+
group_index if group_index == GroupIndex::from(1) => Some(vec![4, 5, 6, 7]),
26052609
_ => panic!("Group index out of bounds"),
26062610
}
26072611
.map(|m| m.into_iter().map(ValidatorIndex).collect::<Vec<_>>())
@@ -2635,7 +2639,7 @@ mod sanitizers {
26352639
group_validators(GroupIndex::from(idx0 as u32)).unwrap().as_ref(),
26362640
&keystore,
26372641
&signing_context,
2638-
BackingKind::Threshold,
2642+
backing_kind,
26392643
CoreIndex(idx0 as u32),
26402644
);
26412645
backed
@@ -2654,7 +2658,11 @@ mod sanitizers {
26542658
ValidatorIndex(1),
26552659
ValidatorIndex(2),
26562660
ValidatorIndex(3),
2657-
ValidatorIndex(4)
2661+
ValidatorIndex(4),
2662+
ValidatorIndex(5),
2663+
ValidatorIndex(6),
2664+
ValidatorIndex(7),
2665+
ValidatorIndex(8),
26582666
]
26592667
);
26602668

@@ -4105,7 +4113,7 @@ mod sanitizers {
41054113
backed_candidates,
41064114
expected_backed_candidates_with_core,
41074115
scheduled_paras: scheduled,
4108-
} = get_test_data_one_core_per_para();
4116+
} = get_test_data_one_core_per_para(BackingKind::Threshold);
41094117

41104118
assert_eq!(
41114119
sanitize_backed_candidates::<Test>(
@@ -4317,7 +4325,7 @@ mod sanitizers {
43174325
let TestData { backed_candidates, .. } = if multiple_cores_per_para {
43184326
get_test_data_multiple_cores_per_para(v2_descriptor)
43194327
} else {
4320-
get_test_data_one_core_per_para()
4328+
get_test_data_one_core_per_para(BackingKind::Threshold)
43214329
};
43224330
let scheduled = BTreeMap::new();
43234331

@@ -4338,7 +4346,7 @@ mod sanitizers {
43384346
fn concluded_invalid_are_filtered_out_single_core_per_para() {
43394347
new_test_ext(default_config()).execute_with(|| {
43404348
let TestData { backed_candidates, scheduled_paras: scheduled, .. } =
4341-
get_test_data_one_core_per_para();
4349+
get_test_data_one_core_per_para(BackingKind::Threshold);
43424350

43434351
// mark every second one as concluded invalid
43444352
let set = {
@@ -4451,15 +4459,15 @@ mod sanitizers {
44514459
fn disabled_non_signing_validator_doesnt_get_filtered() {
44524460
new_test_ext(default_config()).execute_with(|| {
44534461
let TestData { mut expected_backed_candidates_with_core, .. } =
4454-
get_test_data_one_core_per_para();
4462+
get_test_data_one_core_per_para(BackingKind::Threshold);
44554463

4456-
// Disable Eve
4457-
set_disabled_validators(vec![4]);
4464+
// Disable `AliceStash`
4465+
set_disabled_validators(vec![7]);
44584466

44594467
let before = expected_backed_candidates_with_core.clone();
44604468

4461-
// Eve is disabled but no backing statement is signed by it so nothing should be
4462-
// filtered
4469+
// AliceStash is disabled but no backing statement is signed by it so nothing should
4470+
// be filtered
44634471
filter_backed_statements_from_disabled_validators::<Test>(
44644472
&mut expected_backed_candidates_with_core,
44654473
&shared::AllowedRelayParents::<Test>::get(),
@@ -4472,7 +4480,7 @@ mod sanitizers {
44724480
fn drop_statements_from_disabled_without_dropping_candidate() {
44734481
new_test_ext(default_config()).execute_with(|| {
44744482
let TestData { mut expected_backed_candidates_with_core, .. } =
4475-
get_test_data_one_core_per_para();
4483+
get_test_data_one_core_per_para(BackingKind::Threshold);
44764484

44774485
// Disable Alice
44784486
set_disabled_validators(vec![0]);
@@ -4573,7 +4581,7 @@ mod sanitizers {
45734581
fn drop_candidate_if_all_statements_are_from_disabled_single_core_per_para() {
45744582
new_test_ext(default_config()).execute_with(|| {
45754583
let TestData { mut expected_backed_candidates_with_core, .. } =
4576-
get_test_data_one_core_per_para();
4584+
get_test_data_one_core_per_para(BackingKind::Threshold);
45774585

45784586
// Disable Alice and Bob
45794587
set_disabled_validators(vec![0, 1]);
@@ -4663,5 +4671,93 @@ mod sanitizers {
46634671
});
46644672
}
46654673
}
4674+
4675+
// Disable Dave which is 4th validator in group for core 0.
4676+
// Para 0 candidate has a single valid vote that is removed
4677+
// because Dave is disabled.
4678+
//
4679+
// This test ensures we remove the right validity vote from the backed candidate.
4680+
#[test]
4681+
fn drop_right_vote_basic() {
4682+
new_test_ext(default_config()).execute_with(|| {
4683+
let TestData { mut expected_backed_candidates_with_core, .. } =
4684+
get_test_data_one_core_per_para(BackingKind::Unanimous);
4685+
4686+
set_disabled_validators(vec![3]);
4687+
4688+
let (backed, _) = expected_backed_candidates_with_core
4689+
.get_mut(&ParaId::from(1))
4690+
.unwrap()
4691+
.first_mut()
4692+
.unwrap();
4693+
let (indices, core) = backed.validator_indices_and_core_index();
4694+
let mut indices = BitVec::<_>::from(indices);
4695+
4696+
indices.set(0, false);
4697+
indices.set(1, false);
4698+
backed.validity_votes_mut().remove(0);
4699+
backed.validity_votes_mut().remove(1);
4700+
4701+
backed.set_validator_indices_and_core_index(indices, core);
4702+
4703+
let mut untouched = expected_backed_candidates_with_core.clone();
4704+
4705+
filter_backed_statements_from_disabled_validators::<Test>(
4706+
&mut expected_backed_candidates_with_core,
4707+
&shared::AllowedRelayParents::<Test>::get(),
4708+
);
4709+
4710+
untouched.remove(&ParaId::from(1)).unwrap();
4711+
4712+
assert_eq!(expected_backed_candidates_with_core, untouched);
4713+
});
4714+
}
4715+
4716+
// Disable Bob which is second validator in group for core 0.
4717+
//
4718+
// This test ensures we remove the right validty votes from the backed candidate,
4719+
// and calls `process_candidates` that will check the vote signatures.
4720+
// Will fail if we remove the wrong vote.
4721+
#[test]
4722+
fn drop_right_vote_and_process_candidates() {
4723+
new_test_ext(default_config()).execute_with(|| {
4724+
let TestData { mut expected_backed_candidates_with_core, .. } =
4725+
get_test_data_one_core_per_para(BackingKind::Unanimous);
4726+
4727+
// Second validator in group is disabled.
4728+
set_disabled_validators(vec![1]);
4729+
4730+
let (backed, _) = expected_backed_candidates_with_core
4731+
.get_mut(&ParaId::from(1))
4732+
.unwrap()
4733+
.first_mut()
4734+
.unwrap();
4735+
let (indices, core) = backed.validator_indices_and_core_index();
4736+
let mut indices = BitVec::<_>::from(indices);
4737+
4738+
// First validator did not provide vote.
4739+
indices.set(0, false);
4740+
backed.validity_votes_mut().remove(0);
4741+
backed.set_validator_indices_and_core_index(indices, core);
4742+
4743+
let untouched = expected_backed_candidates_with_core.clone();
4744+
4745+
filter_backed_statements_from_disabled_validators::<Test>(
4746+
&mut expected_backed_candidates_with_core,
4747+
&shared::AllowedRelayParents::<Test>::get(),
4748+
);
4749+
4750+
let candidate_receipt_with_backing_validator_indices =
4751+
inclusion::Pallet::<Test>::process_candidates(
4752+
&shared::AllowedRelayParents::<Test>::get(),
4753+
&expected_backed_candidates_with_core,
4754+
scheduler::Pallet::<Test>::group_validators,
4755+
)
4756+
.unwrap();
4757+
4758+
// No candidate was dropped.
4759+
assert_eq!(candidate_receipt_with_backing_validator_indices.len(), untouched.len());
4760+
});
4761+
}
46664762
}
46674763
}

prdoc/pr_9564.prdoc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
title: 'Fix disabled validator filtering in the parachains runtime'
2+
doc:
3+
- audience: Runtime Dev
4+
description: Correctly map group indices to vote indices when filtering backing statements.
5+
crates:
6+
- name: polkadot-runtime-parachains
7+
bump: patch

0 commit comments

Comments
 (0)