Skip to content

Commit 6a4e8c8

Browse files
committed
statement-distribution: implement validator disabling
1 parent 863fc7d commit 6a4e8c8

File tree

6 files changed

+220
-13
lines changed

6 files changed

+220
-13
lines changed

polkadot/node/network/statement-distribution/src/error.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ pub enum Error {
7575
#[error("Fetching availability cores failed {0:?}")]
7676
FetchAvailabilityCores(RuntimeApiError),
7777

78+
#[error("Fetching disabled validators failed {0:?}")]
79+
FetchDisabledValidators(RuntimeApiError),
80+
7881
#[error("Fetching validator groups failed {0:?}")]
7982
FetchValidatorGroups(RuntimeApiError),
8083

polkadot/node/network/statement-distribution/src/v2/grid.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,9 @@ impl GridTracker {
253253
/// This checks whether the peer is allowed to send us manifests
254254
/// about this group at this relay-parent. This also does sanity
255255
/// checks on the format of the manifest and the amount of votes
256-
/// it contains. It has effects on the stored state only when successful.
256+
/// it contains. It assumes that the votes from disabled validators
257+
/// are already filtered out.
258+
/// It has effects on the stored state only when successful.
257259
///
258260
/// This returns a `bool` on success, which if true indicates that an acknowledgement is
259261
/// to be sent in response to the received manifest. This only occurs when the

polkadot/node/network/statement-distribution/src/v2/mod.rs

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ const COST_UNEXPECTED_STATEMENT: Rep = Rep::CostMinor("Unexpected Statement");
9595
const COST_UNEXPECTED_STATEMENT_MISSING_KNOWLEDGE: Rep =
9696
Rep::CostMinor("Unexpected Statement, missing knowledge for relay parent");
9797
const COST_EXCESSIVE_SECONDED: Rep = Rep::CostMinor("Sent Excessive `Seconded` Statements");
98+
const COST_DISABLED_VALIDATOR: Rep = Rep::CostMajor("Sent a statement from a disabled validator");
9899

99100
const COST_UNEXPECTED_MANIFEST_MISSING_KNOWLEDGE: Rep =
100101
Rep::CostMinor("Unexpected Manifest, missing knowlege for relay parent");
@@ -473,6 +474,18 @@ pub(crate) async fn handle_active_leaves_update<Context>(
473474
.map_err(JfyiError::RuntimeApiUnavailable)?
474475
.map_err(JfyiError::FetchAvailabilityCores)?;
475476

477+
let disabled_validators = polkadot_node_subsystem_util::request_disabled_validators(
478+
new_relay_parent,
479+
ctx.sender(),
480+
)
481+
.await
482+
.await
483+
.map_err(JfyiError::RuntimeApiUnavailable)?
484+
.map_err(JfyiError::FetchDisabledValidators)?;
485+
486+
// deduplicate and order
487+
let disabled_validators = disabled_validators.into_iter().collect();
488+
476489
let group_rotation_info =
477490
polkadot_node_subsystem_util::request_validator_groups(new_relay_parent, ctx.sender())
478491
.await
@@ -533,7 +546,7 @@ pub(crate) async fn handle_active_leaves_update<Context>(
533546
new_relay_parent,
534547
PerRelayParentState {
535548
local_validator,
536-
statement_store: StatementStore::new(&per_session.groups),
549+
statement_store: StatementStore::new(&per_session.groups, &disabled_validators),
537550
availability_cores,
538551
group_rotation_info,
539552
seconding_limit,
@@ -1300,6 +1313,20 @@ async fn handle_incoming_statement<Context>(
13001313
},
13011314
};
13021315

1316+
if per_relay_parent
1317+
.statement_store
1318+
.is_disabled(&statement.unchecked_validator_index())
1319+
{
1320+
gum::debug!(
1321+
target: LOG_TARGET,
1322+
?relay_parent,
1323+
validator_index = ?statement.unchecked_validator_index(),
1324+
"Ignoring a statement from disabled validator."
1325+
);
1326+
modify_reputation(reputation, ctx.sender(), peer, COST_DISABLED_VALIDATOR).await;
1327+
return
1328+
}
1329+
13031330
let cluster_sender_index = {
13041331
// This block of code only returns `Some` when both the originator and
13051332
// the sending peer are in the cluster.
@@ -1412,7 +1439,7 @@ async fn handle_incoming_statement<Context>(
14121439
checked_statement.clone(),
14131440
StatementOrigin::Remote,
14141441
) {
1415-
Err(statement_store::ValidatorUnknown) => {
1442+
Err(statement_store::Error::ValidatorUnknown) => {
14161443
// sanity: should never happen.
14171444
gum::warn!(
14181445
target: LOG_TARGET,
@@ -1423,6 +1450,17 @@ async fn handle_incoming_statement<Context>(
14231450

14241451
return
14251452
},
1453+
Err(statement_store::Error::ValidatorDisabled) => {
1454+
// sanity: should never happen, checked above.
1455+
gum::warn!(
1456+
target: LOG_TARGET,
1457+
?relay_parent,
1458+
validator_index = ?originator_index,
1459+
"Error - accepted message from disabled validator."
1460+
);
1461+
1462+
return
1463+
},
14261464
Ok(known) => known,
14271465
};
14281466

@@ -1906,7 +1944,7 @@ async fn handle_incoming_manifest_common<'a, Context>(
19061944
candidate_hash: CandidateHash,
19071945
relay_parent: Hash,
19081946
para_id: ParaId,
1909-
manifest_summary: grid::ManifestSummary,
1947+
mut manifest_summary: grid::ManifestSummary,
19101948
manifest_kind: grid::ManifestKind,
19111949
reputation: &mut ReputationAggregator,
19121950
) -> Option<ManifestImportSuccess<'a>> {
@@ -1984,6 +2022,16 @@ async fn handle_incoming_manifest_common<'a, Context>(
19842022
// 2. sanity checks: peer is validator, bitvec size, import into grid tracker
19852023
let group_index = manifest_summary.claimed_group_index;
19862024
let claimed_parent_hash = manifest_summary.claimed_parent_hash;
2025+
2026+
// Ignore votes from disabled validators when counting towards the threshold.
2027+
let disabled_mask = per_session
2028+
.groups
2029+
.get(group_index)
2030+
.map(|group| relay_parent_state.statement_store.disabled_bitmask(group))
2031+
.unwrap_or_default();
2032+
manifest_summary.statement_knowledge.mask_seconded(&disabled_mask);
2033+
manifest_summary.statement_knowledge.mask_valid(&disabled_mask);
2034+
19872035
let acknowledge = match local_validator.grid_tracker.import_manifest(
19882036
grid_topology,
19892037
&per_session.groups,
@@ -2743,11 +2791,20 @@ pub(crate) fn answer_request(state: &mut State, message: ResponderMessage) {
27432791

27442792
// Transform mask with 'OR' semantics into one with 'AND' semantics for the API used
27452793
// below.
2746-
let and_mask = StatementFilter {
2794+
let mut and_mask = StatementFilter {
27472795
seconded_in_group: !mask.seconded_in_group.clone(),
27482796
validated_in_group: !mask.validated_in_group.clone(),
27492797
};
27502798

2799+
// Ignore disabled validators when sending the response.
2800+
let disabled_mask = per_session
2801+
.groups
2802+
.get(confirmed.group_index())
2803+
.map(|group| relay_parent_state.statement_store.disabled_bitmask(group))
2804+
.unwrap_or_default();
2805+
and_mask.mask_seconded(&disabled_mask);
2806+
and_mask.mask_valid(&disabled_mask);
2807+
27512808
let response = AttestedCandidateResponse {
27522809
candidate_receipt: (&**confirmed.candidate_receipt()).clone(),
27532810
persisted_validation_data: confirmed.persisted_validation_data().clone(),

polkadot/node/network/statement-distribution/src/v2/statement_store.rs

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@ use polkadot_node_network_protocol::v2::StatementFilter;
2828
use polkadot_primitives::{
2929
CandidateHash, CompactStatement, GroupIndex, SignedStatement, ValidatorIndex,
3030
};
31-
use std::collections::hash_map::{Entry as HEntry, HashMap};
31+
use std::collections::{
32+
hash_map::{Entry as HEntry, HashMap},
33+
BTreeSet,
34+
};
3235

3336
use super::groups::Groups;
3437

@@ -68,7 +71,7 @@ pub struct StatementStore {
6871

6972
impl StatementStore {
7073
/// Create a new [`StatementStore`]
71-
pub fn new(groups: &Groups) -> Self {
74+
pub fn new(groups: &Groups, disabled: &BTreeSet<ValidatorIndex>) -> Self {
7275
let mut validator_meta = HashMap::new();
7376
for (g, group) in groups.all().iter().enumerate() {
7477
for (i, v) in group.iter().enumerate() {
@@ -78,6 +81,7 @@ impl StatementStore {
7881
seconded_count: 0,
7982
within_group_index: i,
8083
group: GroupIndex(g as _),
84+
is_disabled: disabled.contains(v),
8185
},
8286
);
8387
}
@@ -91,18 +95,21 @@ impl StatementStore {
9195
}
9296

9397
/// Insert a statement. Returns `true` if was not known already, `false` if it was.
94-
/// Ignores statements by unknown validators and returns an error.
98+
/// Ignores statements by unknown and disabled validators and returns an error.
9599
pub fn insert(
96100
&mut self,
97101
groups: &Groups,
98102
statement: SignedStatement,
99103
origin: StatementOrigin,
100-
) -> Result<bool, ValidatorUnknown> {
104+
) -> Result<bool, Error> {
101105
let validator_index = statement.validator_index();
102106
let validator_meta = match self.validator_meta.get_mut(&validator_index) {
103-
None => return Err(ValidatorUnknown),
107+
None => return Err(Error::ValidatorUnknown),
104108
Some(m) => m,
105109
};
110+
if validator_meta.is_disabled {
111+
return Err(Error::ValidatorDisabled)
112+
}
106113

107114
let compact = statement.payload().clone();
108115
let fingerprint = (validator_index, compact.clone());
@@ -134,7 +141,7 @@ impl StatementStore {
134141
"groups passed into `insert` differ from those used at store creation"
135142
);
136143

137-
return Err(ValidatorUnknown)
144+
return Err(Error::ValidatorUnknown)
138145
},
139146
};
140147

@@ -154,6 +161,25 @@ impl StatementStore {
154161
Ok(true)
155162
}
156163

164+
/// Returns true if the validator is disabled as of current relay parent.
165+
pub fn is_disabled(&self, index: &ValidatorIndex) -> bool {
166+
self.validator_meta.get(&index).map(|m| m.is_disabled).unwrap_or(false)
167+
}
168+
169+
/// A convenience funtion to generate a disabled bitmask for the given backing group.
170+
/// The output bits are set to `true` for validators that are disabled.
171+
/// The group size should match the size of the backing group.
172+
pub fn disabled_bitmask(&self, group: &[ValidatorIndex]) -> BitVec<u8, BitOrderLsb0> {
173+
let group_size = group.len();
174+
let mut mask = BitVec::repeat(false, group_size);
175+
for i in group {
176+
if self.is_disabled(i) {
177+
mask.set(i.0 as usize, true);
178+
}
179+
}
180+
mask
181+
}
182+
157183
/// Fill a `StatementFilter` to be used in the grid topology with all statements
158184
/// we are already aware of.
159185
pub fn fill_statement_filter(
@@ -250,14 +276,20 @@ impl StatementStore {
250276
}
251277

252278
/// Error indicating that the validator was unknown.
253-
pub struct ValidatorUnknown;
279+
pub enum Error {
280+
/// The validator was unknown.
281+
ValidatorUnknown,
282+
/// A statement from a disabled validator should be rejected.
283+
ValidatorDisabled,
284+
}
254285

255286
type Fingerprint = (ValidatorIndex, CompactStatement);
256287

257288
struct ValidatorMeta {
258289
group: GroupIndex,
259290
within_group_index: usize,
260291
seconded_count: usize,
292+
is_disabled: bool,
261293
}
262294

263295
struct GroupStatements {

polkadot/node/network/statement-distribution/src/v2/tests/mod.rs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,12 +169,21 @@ impl TestState {
169169
collator: None,
170170
})
171171
}),
172+
disabled_validators: Default::default(),
172173
para_data: (0..self.session_info.validator_groups.len())
173174
.map(|i| (ParaId::from(i as u32), PerParaData::new(1, vec![1, 2, 3].into())))
174175
.collect(),
175176
}
176177
}
177178

179+
fn make_dummy_leaf_with_disabled_validators(
180+
&self,
181+
relay_parent: Hash,
182+
disabled_validators: Vec<ValidatorIndex>,
183+
) -> TestLeaf {
184+
TestLeaf { disabled_validators, ..self.make_dummy_leaf(relay_parent) }
185+
}
186+
178187
fn make_availability_cores(&self, f: impl Fn(usize) -> CoreState) -> Vec<CoreState> {
179188
(0..self.session_info.validator_groups.len()).map(f).collect()
180189
}
@@ -340,6 +349,7 @@ struct TestLeaf {
340349
parent_hash: Hash,
341350
session: SessionIndex,
342351
availability_cores: Vec<CoreState>,
352+
disabled_validators: Vec<ValidatorIndex>,
343353
para_data: Vec<(ParaId, PerParaData)>,
344354
}
345355

@@ -375,7 +385,15 @@ async fn handle_leaf_activation(
375385
test_state: &TestState,
376386
is_new_session: bool,
377387
) {
378-
let TestLeaf { number, hash, parent_hash, para_data, session, availability_cores } = leaf;
388+
let TestLeaf {
389+
number,
390+
hash,
391+
parent_hash,
392+
para_data,
393+
session,
394+
availability_cores,
395+
disabled_validators,
396+
} = leaf;
379397

380398
assert_matches!(
381399
virtual_overseer.recv().await,
@@ -431,6 +449,14 @@ async fn handle_leaf_activation(
431449
}
432450
);
433451

452+
assert_matches!(
453+
virtual_overseer.recv().await,
454+
AllMessages::RuntimeApi(
455+
RuntimeApiMessage::Request(parent, RuntimeApiRequest::DisabledValidators(tx))) if parent == *hash => {
456+
tx.send(Ok(disabled_validators.clone())).unwrap();
457+
}
458+
);
459+
434460
let validator_groups = test_state.session_info.validator_groups.to_vec();
435461
let group_rotation_info =
436462
GroupRotationInfo { session_start_block: 1, group_rotation_frequency: 12, now: 1 };

0 commit comments

Comments
 (0)