Skip to content

Commit 9f6736d

Browse files
committed
ignore disabled statements on responses
1 parent 3b7fd1a commit 9f6736d

File tree

3 files changed

+141
-23
lines changed

3 files changed

+141
-23
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2622,6 +2622,8 @@ pub(crate) async fn handle_response<Context>(
26222622
Some(g) => g,
26232623
};
26242624

2625+
let disabled_mask = relay_parent_state.statement_store.disabled_bitmask(group);
2626+
26252627
let res = response.validate_response(
26262628
&mut state.request_manager,
26272629
group,
@@ -2636,6 +2638,7 @@ pub(crate) async fn handle_response<Context>(
26362638

26372639
Some(g_index) == expected_group
26382640
},
2641+
disabled_mask,
26392642
);
26402643

26412644
for (peer, rep) in res.reputation_changes {

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

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,13 @@
3030
//! (which requires state not owned by the request manager).
3131
3232
use super::{
33-
BENEFIT_VALID_RESPONSE, BENEFIT_VALID_STATEMENT, COST_IMPROPERLY_DECODED_RESPONSE,
34-
COST_INVALID_RESPONSE, COST_INVALID_SIGNATURE, COST_UNREQUESTED_RESPONSE_STATEMENT,
35-
REQUEST_RETRY_DELAY,
33+
BENEFIT_VALID_RESPONSE, BENEFIT_VALID_STATEMENT, COST_DISABLED_VALIDATOR,
34+
COST_IMPROPERLY_DECODED_RESPONSE, COST_INVALID_RESPONSE, COST_INVALID_SIGNATURE,
35+
COST_UNREQUESTED_RESPONSE_STATEMENT, REQUEST_RETRY_DELAY,
3636
};
3737
use crate::LOG_TARGET;
3838

39+
use bitvec::prelude::{BitVec, Lsb0};
3940
use polkadot_node_network_protocol::{
4041
request_response::{
4142
outgoing::{Recipient as RequestRecipient, RequestError},
@@ -510,6 +511,7 @@ impl UnhandledResponse {
510511
/// * If the response is partially valid, misbehavior by the responding peer.
511512
/// * If there are other peers which have advertised the same candidate for different
512513
/// relay-parents or para-ids, misbehavior reports for those peers will also be generated.
514+
/// * Statements from disabled validators are filtered out and reported as misbehavior.
513515
///
514516
/// Finally, in the case that the response is either valid or partially valid,
515517
/// this will clean up all remaining requests for the candidate in the manager.
@@ -524,6 +526,7 @@ impl UnhandledResponse {
524526
session: SessionIndex,
525527
validator_key_lookup: impl Fn(ValidatorIndex) -> Option<ValidatorId>,
526528
allowed_para_lookup: impl Fn(ParaId, GroupIndex) -> bool,
529+
disabled_mask: BitVec<u8, Lsb0>,
527530
) -> ResponseValidationOutput {
528531
let UnhandledResponse {
529532
response: TaggedResponse { identifier, requested_peer, props, response },
@@ -600,6 +603,7 @@ impl UnhandledResponse {
600603
session,
601604
validator_key_lookup,
602605
allowed_para_lookup,
606+
disabled_mask,
603607
);
604608

605609
if let CandidateRequestStatus::Complete { .. } = output.request_status {
@@ -619,10 +623,11 @@ fn validate_complete_response(
619623
session: SessionIndex,
620624
validator_key_lookup: impl Fn(ValidatorIndex) -> Option<ValidatorId>,
621625
allowed_para_lookup: impl Fn(ParaId, GroupIndex) -> bool,
626+
mut disabled_mask: BitVec<u8, Lsb0>,
622627
) -> ResponseValidationOutput {
623628
let RequestProperties { backing_threshold, mut unwanted_mask } = props;
624629

625-
// sanity check bitmask size. this is based entirely on
630+
// sanity check bitmasks size. this is based entirely on
626631
// local logic here.
627632
if !unwanted_mask.has_len(group.len()) {
628633
gum::error!(
@@ -635,6 +640,16 @@ fn validate_complete_response(
635640
unwanted_mask.seconded_in_group.resize(group.len(), true);
636641
unwanted_mask.validated_in_group.resize(group.len(), true);
637642
}
643+
if disabled_mask.len() != group.len() {
644+
gum::error!(
645+
target: LOG_TARGET,
646+
group_len = group.len(),
647+
"Logic bug: group size != disabled bitmask len"
648+
);
649+
650+
// resize and attempt to continue.
651+
disabled_mask.resize(group.len(), false);
652+
}
638653

639654
let invalid_candidate_output = || ResponseValidationOutput {
640655
request_status: CandidateRequestStatus::Incomplete,
@@ -712,6 +727,11 @@ fn validate_complete_response(
712727
rep_changes.push((requested_peer, COST_UNREQUESTED_RESPONSE_STATEMENT));
713728
continue
714729
}
730+
731+
if disabled_mask[i] {
732+
rep_changes.push((requested_peer, COST_DISABLED_VALIDATOR));
733+
continue
734+
}
715735
},
716736
CompactStatement::Valid(_) => {
717737
if unwanted_mask.validated_in_group[i] {
@@ -723,6 +743,11 @@ fn validate_complete_response(
723743
rep_changes.push((requested_peer, COST_UNREQUESTED_RESPONSE_STATEMENT));
724744
continue
725745
}
746+
747+
if disabled_mask[i] {
748+
rep_changes.push((requested_peer, COST_DISABLED_VALIDATOR));
749+
continue
750+
}
726751
},
727752
}
728753

@@ -988,6 +1013,7 @@ mod tests {
9881013
let group = &[ValidatorIndex(0), ValidatorIndex(1), ValidatorIndex(2)];
9891014

9901015
let unwanted_mask = StatementFilter::blank(group_size);
1016+
let disabled_mask = unwanted_mask.seconded_in_group.clone();
9911017
let request_properties = RequestProperties { unwanted_mask, backing_threshold: None };
9921018

9931019
// Get requests.
@@ -1031,6 +1057,7 @@ mod tests {
10311057
0,
10321058
validator_key_lookup,
10331059
allowed_para_lookup,
1060+
disabled_mask.clone(),
10341061
);
10351062
assert_eq!(
10361063
output,
@@ -1069,6 +1096,7 @@ mod tests {
10691096
0,
10701097
validator_key_lookup,
10711098
allowed_para_lookup,
1099+
disabled_mask,
10721100
);
10731101
assert_eq!(
10741102
output,
@@ -1108,6 +1136,7 @@ mod tests {
11081136
let group = &[ValidatorIndex(0), ValidatorIndex(1), ValidatorIndex(2)];
11091137

11101138
let unwanted_mask = StatementFilter::blank(group_size);
1139+
let disabled_mask = unwanted_mask.seconded_in_group.clone();
11111140
let request_properties = RequestProperties { unwanted_mask, backing_threshold: None };
11121141
let peer_advertised =
11131142
|_identifier: &CandidateIdentifier, _peer: &_| Some(StatementFilter::full(group_size));
@@ -1148,6 +1177,7 @@ mod tests {
11481177
0,
11491178
validator_key_lookup,
11501179
allowed_para_lookup,
1180+
disabled_mask,
11511181
);
11521182
assert_eq!(
11531183
output,
@@ -1188,6 +1218,7 @@ mod tests {
11881218
let group = &[ValidatorIndex(0), ValidatorIndex(1), ValidatorIndex(2)];
11891219

11901220
let unwanted_mask = StatementFilter::blank(group_size);
1221+
let disabled_mask = unwanted_mask.seconded_in_group.clone();
11911222
let request_properties = RequestProperties { unwanted_mask, backing_threshold: None };
11921223
let peer_advertised =
11931224
|_identifier: &CandidateIdentifier, _peer: &_| Some(StatementFilter::full(group_size));
@@ -1226,6 +1257,7 @@ mod tests {
12261257
0,
12271258
validator_key_lookup,
12281259
allowed_para_lookup,
1260+
disabled_mask,
12291261
);
12301262
assert_eq!(
12311263
output,

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

Lines changed: 102 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1111,20 +1111,22 @@ fn peer_reported_for_providing_statement_from_disabled_validator() {
11111111
};
11121112

11131113
let relay_parent = Hash::repeat_byte(1);
1114-
let peer_a = PeerId::random();
1114+
let peer_disabled = PeerId::random();
1115+
let peer_b = PeerId::random();
11151116

11161117
test_harness(config, |state, mut overseer| async move {
11171118
let local_validator = state.local.clone().unwrap();
11181119
let local_para = ParaId::from(local_validator.group_index.0);
11191120

11201121
let other_group_validators = state.group_validators(local_validator.group_index, true);
1121-
let v_a = other_group_validators[0];
1122+
let index_disabled = other_group_validators[0];
1123+
let index_b = other_group_validators[1];
11221124

1123-
let disabled_validators = vec![v_a];
1125+
let disabled_validators = vec![index_disabled];
11241126
let test_leaf =
11251127
state.make_dummy_leaf_with_disabled_validators(relay_parent, disabled_validators);
11261128

1127-
let (candidate, _) = make_candidate(
1129+
let (candidate, pvd) = make_candidate(
11281130
relay_parent,
11291131
1,
11301132
local_para,
@@ -1134,15 +1136,23 @@ fn peer_reported_for_providing_statement_from_disabled_validator() {
11341136
);
11351137
let candidate_hash = candidate.hash();
11361138

1137-
// peer A is in group, has relay parent in view.
1139+
// peer A is in group, has relay parent in view and disabled.
1140+
// peer B is in group, has relay parent in view.
11381141
{
11391142
connect_peer(
11401143
&mut overseer,
1141-
peer_a.clone(),
1142-
Some(vec![state.discovery_id(v_a)].into_iter().collect()),
1144+
peer_disabled.clone(),
1145+
Some(vec![state.discovery_id(index_disabled)].into_iter().collect()),
11431146
)
11441147
.await;
1145-
send_peer_view_change(&mut overseer, peer_a.clone(), view![relay_parent]).await;
1148+
connect_peer(
1149+
&mut overseer,
1150+
peer_b.clone(),
1151+
Some(vec![state.discovery_id(index_b)].into_iter().collect()),
1152+
)
1153+
.await;
1154+
send_peer_view_change(&mut overseer, peer_disabled.clone(), view![relay_parent]).await;
1155+
send_peer_view_change(&mut overseer, peer_b.clone(), view![relay_parent]).await;
11461156
}
11471157

11481158
activate_leaf(&mut overseer, &test_leaf, &state, true).await;
@@ -1155,31 +1165,104 @@ fn peer_reported_for_providing_statement_from_disabled_validator() {
11551165
)
11561166
.await;
11571167

1168+
let seconded_disabled = state
1169+
.sign_statement(
1170+
index_disabled,
1171+
CompactStatement::Seconded(candidate_hash),
1172+
&SigningContext { parent_hash: relay_parent, session_index: 1 },
1173+
)
1174+
.as_unchecked()
1175+
.clone();
1176+
1177+
let seconded_b = state
1178+
.sign_statement(
1179+
index_b,
1180+
CompactStatement::Seconded(candidate_hash),
1181+
&SigningContext { parent_hash: relay_parent, session_index: 1 },
1182+
)
1183+
.as_unchecked()
1184+
.clone();
11581185
{
1159-
let a_seconded_disabled = state
1160-
.sign_statement(
1161-
v_a,
1162-
CompactStatement::Seconded(candidate_hash),
1163-
&SigningContext { parent_hash: relay_parent, session_index: 1 },
1164-
)
1165-
.as_unchecked()
1166-
.clone();
1186+
send_peer_message(
1187+
&mut overseer,
1188+
peer_disabled.clone(),
1189+
protocol_v2::StatementDistributionMessage::Statement(
1190+
relay_parent,
1191+
seconded_disabled.clone(),
1192+
),
1193+
)
1194+
.await;
1195+
1196+
assert_matches!(
1197+
overseer.recv().await,
1198+
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r)))
1199+
if p == peer_disabled && r == COST_DISABLED_VALIDATOR.into() => { }
1200+
);
1201+
}
11671202

1203+
{
11681204
send_peer_message(
11691205
&mut overseer,
1170-
peer_a.clone(),
1206+
peer_b.clone(),
11711207
protocol_v2::StatementDistributionMessage::Statement(
11721208
relay_parent,
1173-
a_seconded_disabled,
1209+
seconded_b.clone(),
11741210
),
11751211
)
11761212
.await;
11771213

11781214
assert_matches!(
11791215
overseer.recv().await,
11801216
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r)))
1181-
if p == peer_a && r == COST_DISABLED_VALIDATOR.into() => { }
1217+
if p == peer_b && r == BENEFIT_VALID_STATEMENT_FIRST.into() => { }
1218+
);
1219+
}
1220+
1221+
// Send a request to peer and mock its response with a statement from disabled validator.
1222+
{
1223+
let statements = vec![seconded_disabled];
1224+
1225+
handle_sent_request(
1226+
&mut overseer,
1227+
peer_b,
1228+
candidate_hash,
1229+
StatementFilter::blank(group_size),
1230+
candidate.clone(),
1231+
pvd.clone(),
1232+
statements,
1233+
)
1234+
.await;
1235+
1236+
assert_matches!(
1237+
overseer.recv().await,
1238+
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r)))
1239+
if p == peer_b && r == COST_DISABLED_VALIDATOR.into() => { }
11821240
);
1241+
1242+
assert_matches!(
1243+
overseer.recv().await,
1244+
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r)))
1245+
if p == peer_b && r == BENEFIT_VALID_RESPONSE.into() => { }
1246+
);
1247+
1248+
assert_matches!(
1249+
overseer.recv().await,
1250+
AllMessages:: NetworkBridgeTx(
1251+
NetworkBridgeTxMessage::SendValidationMessage(
1252+
peers,
1253+
Versioned::V2(
1254+
protocol_v2::ValidationProtocol::StatementDistribution(
1255+
protocol_v2::StatementDistributionMessage::Statement(hash, statement),
1256+
),
1257+
),
1258+
)
1259+
) => {
1260+
assert_eq!(peers, vec![peer_disabled]);
1261+
assert_eq!(hash, relay_parent);
1262+
assert_eq!(statement, seconded_b);
1263+
}
1264+
);
1265+
answer_expected_hypothetical_depth_request(&mut overseer, vec![], None, false).await;
11831266
}
11841267

11851268
overseer

0 commit comments

Comments
 (0)