Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.

Commit 25694f6

Browse files
authored
misbehavior: report multiple offenses per validator as necessary (#2222)
* use proper descriptive generic type names * cleanup * Table stores a list of detected misbehavior per authority * add Table::drain_misbehaviors_for * WIP: unify misbehavior types; report multiple misbehaviors per validator Code checks, but tests don't yet pass. * update drain_misbehaviors: return authority id as well as specific misbehavior * enable unchecked construction of Signed structs in tests * remove test-features feature & unnecessary generic * fix backing tests This took a while to figure out, because where we'd previously been passing around `SignedFullStatement`s, we now needed to construct those on the fly within the test, to take advantage of the signature- checking in the constructor. That, in turn, necessitated changing the iterable type of `drain_misbehaviors` to return the validator index, and passing that validator index along within the misbehavior report. Once that was sorted, however, it became relatively straightforward: just needed to add appropriate methods to deconstruct the misbehavior reports, and then we could construct the signed statements directly. * fix bad merge
1 parent 3396396 commit 25694f6

File tree

5 files changed

+605
-575
lines changed

5 files changed

+605
-575
lines changed

node/core/backing/src/lib.rs

Lines changed: 64 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
#![deny(unused_crate_dependencies)]
2020

2121
use std::collections::{HashMap, HashSet};
22-
use std::convert::TryFrom;
2322
use std::pin::Pin;
2423
use std::sync::Arc;
2524

@@ -28,13 +27,12 @@ use futures::{channel::{mpsc, oneshot}, Future, FutureExt, SinkExt, StreamExt};
2827

2928
use sp_keystore::SyncCryptoStorePtr;
3029
use polkadot_primitives::v1::{
31-
CommittedCandidateReceipt, BackedCandidate, Id as ParaId, ValidatorId,
32-
ValidatorIndex, SigningContext, PoV, CandidateHash,
33-
CandidateDescriptor, AvailableData, ValidatorSignature, Hash, CandidateReceipt,
34-
CoreState, CoreIndex, CollatorId, ValidityAttestation, CandidateCommitments,
30+
AvailableData, BackedCandidate, CandidateCommitments, CandidateDescriptor, CandidateHash,
31+
CandidateReceipt, CollatorId, CommittedCandidateReceipt, CoreIndex, CoreState, Hash, Id as ParaId,
32+
PoV, SigningContext, ValidatorId, ValidatorIndex, ValidatorSignature, ValidityAttestation,
3533
};
3634
use polkadot_node_primitives::{
37-
FromTableMisbehavior, Statement, SignedFullStatement, MisbehaviorReport, ValidationResult,
35+
Statement, SignedFullStatement, ValidationResult,
3836
};
3937
use polkadot_subsystem::{
4038
JaegerSpan, PerLeafSpan,
@@ -60,8 +58,9 @@ use statement_table::{
6058
Context as TableContextTrait,
6159
Table,
6260
v1::{
61+
SignedStatement as TableSignedStatement,
6362
Statement as TableStatement,
64-
SignedStatement as TableSignedStatement, Summary as TableSummary,
63+
Summary as TableSummary,
6564
},
6665
};
6766
use thiserror::Error;
@@ -145,8 +144,6 @@ struct CandidateBackingJob {
145144
/// The candidates that are includable, by hash. Each entry here indicates
146145
/// that we've sent the provisioner the backed candidate.
147146
backed: HashSet<CandidateHash>,
148-
/// We have already reported misbehaviors for these validators.
149-
reported_misbehavior_for: HashSet<ValidatorIndex>,
150147
keystore: SyncCryptoStorePtr,
151148
table: Table<TableContext>,
152149
table_context: TableContext,
@@ -644,36 +641,17 @@ impl CandidateBackingJob {
644641
}
645642

646643
/// Check if there have happened any new misbehaviors and issue necessary messages.
647-
///
648-
/// TODO: Report multiple misbehaviors (https://github.com/paritytech/polkadot/issues/1387)
649644
#[tracing::instrument(level = "trace", skip(self), fields(subsystem = LOG_TARGET))]
650645
async fn issue_new_misbehaviors(&mut self) -> Result<(), Error> {
651-
let mut reports = Vec::new();
652-
653-
for (k, v) in self.table.get_misbehavior().iter() {
654-
if !self.reported_misbehavior_for.contains(k) {
655-
self.reported_misbehavior_for.insert(*k);
656-
657-
let f = FromTableMisbehavior {
658-
id: *k,
659-
report: v.clone(),
660-
signing_context: self.table_context.signing_context.clone(),
661-
key: self.table_context.validators[*k as usize].clone(),
662-
};
663-
664-
if let Ok(report) = MisbehaviorReport::try_from(f) {
665-
let message = ProvisionerMessage::ProvisionableData(
666-
self.parent,
667-
ProvisionableData::MisbehaviorReport(self.parent, report),
668-
);
669-
670-
reports.push(message);
671-
}
672-
}
673-
}
674-
675-
for report in reports.drain(..) {
676-
self.send_to_provisioner(report).await?
646+
// collect the misbehaviors to avoid double mutable self borrow issues
647+
let misbehaviors: Vec<_> = self.table.drain_misbehaviors().collect();
648+
for (validator_id, report) in misbehaviors {
649+
self.send_to_provisioner(
650+
ProvisionerMessage::ProvisionableData(
651+
self.parent,
652+
ProvisionableData::MisbehaviorReport(self.parent, validator_id, report)
653+
)
654+
).await?
677655
}
678656

679657
Ok(())
@@ -1086,7 +1064,6 @@ impl util::JobTrait for CandidateBackingJob {
10861064
seconded: None,
10871065
unbacked_candidates: HashMap::new(),
10881066
backed: HashSet::new(),
1089-
reported_misbehavior_for: HashSet::new(),
10901067
keystore,
10911068
table: Table::default(),
10921069
table_context,
@@ -1199,9 +1176,7 @@ mod tests {
11991176
use super::*;
12001177
use assert_matches::assert_matches;
12011178
use futures::{future, Future};
1202-
use polkadot_primitives::v1::{
1203-
ScheduledCore, BlockData, PersistedValidationData, HeadData, GroupRotationInfo,
1204-
};
1179+
use polkadot_primitives::v1::{BlockData, GroupRotationInfo, HeadData, PersistedValidationData, ScheduledCore};
12051180
use polkadot_subsystem::{
12061181
messages::{RuntimeApiRequest, RuntimeApiMessage},
12071182
ActiveLeavesUpdate, FromOverseer, OverseerSignal,
@@ -1210,12 +1185,23 @@ mod tests {
12101185
use sp_keyring::Sr25519Keyring;
12111186
use sp_application_crypto::AppKey;
12121187
use sp_keystore::{CryptoStore, SyncCryptoStore};
1188+
use statement_table::v1::Misbehavior;
12131189
use std::collections::HashMap;
12141190

12151191
fn validator_pubkeys(val_ids: &[Sr25519Keyring]) -> Vec<ValidatorId> {
12161192
val_ids.iter().map(|v| v.public().into()).collect()
12171193
}
12181194

1195+
fn table_statement_to_primitive(
1196+
statement: TableStatement,
1197+
) -> Statement {
1198+
match statement {
1199+
TableStatement::Candidate(committed_candidate_receipt) => Statement::Seconded(committed_candidate_receipt),
1200+
TableStatement::Valid(candidate_hash) => Statement::Valid(candidate_hash),
1201+
TableStatement::Invalid(candidate_hash) => Statement::Invalid(candidate_hash),
1202+
}
1203+
}
1204+
12191205
struct TestState {
12201206
chain_ids: Vec<ParaId>,
12211207
keystore: SyncCryptoStorePtr,
@@ -1950,19 +1936,30 @@ mod tests {
19501936
_,
19511937
ProvisionableData::MisbehaviorReport(
19521938
relay_parent,
1953-
MisbehaviorReport::SelfContradiction(_, s1, s2),
1939+
validator_index,
1940+
Misbehavior::ValidityDoubleVote(vdv),
19541941
)
19551942
)
19561943
) if relay_parent == test_state.relay_parent => {
1957-
s1.check_signature(
1944+
let ((t1, s1), (t2, s2)) = vdv.deconstruct::<TableContext>();
1945+
let t1 = table_statement_to_primitive(t1);
1946+
let t2 = table_statement_to_primitive(t2);
1947+
1948+
SignedFullStatement::new(
1949+
t1,
1950+
validator_index,
1951+
s1,
19581952
&test_state.signing_context,
1959-
&test_state.validator_public[s1.validator_index() as usize],
1960-
).unwrap();
1953+
&test_state.validator_public[validator_index as usize],
1954+
).expect("signature must be valid");
19611955

1962-
s2.check_signature(
1956+
SignedFullStatement::new(
1957+
t2,
1958+
validator_index,
1959+
s2,
19631960
&test_state.signing_context,
1964-
&test_state.validator_public[s2.validator_index() as usize],
1965-
).unwrap();
1961+
&test_state.validator_public[validator_index as usize],
1962+
).expect("signature must be valid");
19661963
}
19671964
);
19681965

@@ -1979,19 +1976,30 @@ mod tests {
19791976
_,
19801977
ProvisionableData::MisbehaviorReport(
19811978
relay_parent,
1982-
MisbehaviorReport::SelfContradiction(_, s1, s2),
1979+
validator_index,
1980+
Misbehavior::ValidityDoubleVote(vdv),
19831981
)
19841982
)
19851983
) if relay_parent == test_state.relay_parent => {
1986-
s1.check_signature(
1984+
let ((t1, s1), (t2, s2)) = vdv.deconstruct::<TableContext>();
1985+
let t1 = table_statement_to_primitive(t1);
1986+
let t2 = table_statement_to_primitive(t2);
1987+
1988+
SignedFullStatement::new(
1989+
t1,
1990+
validator_index,
1991+
s1,
19871992
&test_state.signing_context,
1988-
&test_state.validator_public[s1.validator_index() as usize],
1989-
).unwrap();
1993+
&test_state.validator_public[validator_index as usize],
1994+
).expect("signature must be valid");
19901995

1991-
s2.check_signature(
1996+
SignedFullStatement::new(
1997+
t2,
1998+
validator_index,
1999+
s2,
19922000
&test_state.signing_context,
1993-
&test_state.validator_public[s2.validator_index() as usize],
1994-
).unwrap();
2001+
&test_state.validator_public[validator_index as usize],
2002+
).expect("signature must be valid");
19952003
}
19962004
);
19972005
});
@@ -2464,6 +2472,7 @@ mod tests {
24642472
#[test]
24652473
fn candidate_backing_reorders_votes() {
24662474
use sp_core::Encode;
2475+
use std::convert::TryFrom;
24672476

24682477
let relay_parent = [1; 32].into();
24692478
let para_id = ParaId::from(10);

node/primitives/src/lib.rs

Lines changed: 3 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,9 @@
2525
use futures::Future;
2626
use parity_scale_codec::{Decode, Encode};
2727
use polkadot_primitives::v1::{
28-
Hash, CommittedCandidateReceipt, CandidateReceipt, CompactStatement,
29-
EncodeAs, Signed, SigningContext, ValidatorIndex, ValidatorId,
30-
UpwardMessage, ValidationCode, PersistedValidationData,
31-
HeadData, PoV, CollatorPair, Id as ParaId, OutboundHrmpMessage, CandidateCommitments, CandidateHash,
32-
};
33-
use polkadot_statement_table::{
34-
generic::{
35-
ValidityDoubleVote as TableValidityDoubleVote,
36-
MultipleCandidates as TableMultipleCandidates,
37-
},
38-
v1::Misbehavior as TableMisbehavior,
28+
CandidateCommitments, CandidateHash, CollatorPair, CommittedCandidateReceipt, CompactStatement,
29+
EncodeAs, Hash, HeadData, Id as ParaId, OutboundHrmpMessage, PersistedValidationData, PoV,
30+
Signed, UpwardMessage, ValidationCode,
3931
};
4032
use std::pin::Pin;
4133

@@ -105,36 +97,6 @@ impl EncodeAs<CompactStatement> for Statement {
10597
/// Only the compact `SignedStatement` is suitable for submission to the chain.
10698
pub type SignedFullStatement = Signed<Statement, CompactStatement>;
10799

108-
/// A misbehaviour report.
109-
#[derive(Debug, Clone)]
110-
pub enum MisbehaviorReport {
111-
/// These validator nodes disagree on this candidate's validity, please figure it out
112-
///
113-
/// Most likely, the list of statments all agree except for the final one. That's not
114-
/// guaranteed, though; if somehow we become aware of lots of
115-
/// statements disagreeing about the validity of a candidate before taking action,
116-
/// this message should be dispatched with all of them, in arbitrary order.
117-
///
118-
/// This variant is also used when our own validity checks disagree with others'.
119-
CandidateValidityDisagreement(CandidateReceipt, Vec<SignedFullStatement>),
120-
/// I've noticed a peer contradicting itself about a particular candidate
121-
SelfContradiction(CandidateReceipt, SignedFullStatement, SignedFullStatement),
122-
/// This peer has seconded more than one parachain candidate for this relay parent head
123-
DoubleVote(SignedFullStatement, SignedFullStatement),
124-
}
125-
126-
/// A utility struct used to convert `TableMisbehavior` to `MisbehaviorReport`s.
127-
pub struct FromTableMisbehavior {
128-
/// Index of the validator.
129-
pub id: ValidatorIndex,
130-
/// The misbehavior reported by the table.
131-
pub report: TableMisbehavior,
132-
/// Signing context.
133-
pub signing_context: SigningContext,
134-
/// Misbehaving validator's public key.
135-
pub key: ValidatorId,
136-
}
137-
138100
/// Candidate invalidity details
139101
#[derive(Debug)]
140102
pub enum InvalidCandidate {
@@ -170,102 +132,6 @@ pub enum ValidationResult {
170132
Invalid(InvalidCandidate),
171133
}
172134

173-
impl std::convert::TryFrom<FromTableMisbehavior> for MisbehaviorReport {
174-
type Error = ();
175-
176-
fn try_from(f: FromTableMisbehavior) -> Result<Self, Self::Error> {
177-
match f.report {
178-
TableMisbehavior::ValidityDoubleVote(
179-
TableValidityDoubleVote::IssuedAndValidity((c, s1), (d, s2))
180-
) => {
181-
let receipt = c.clone();
182-
let signed_1 = SignedFullStatement::new(
183-
Statement::Seconded(c),
184-
f.id,
185-
s1,
186-
&f.signing_context,
187-
&f.key,
188-
).ok_or(())?;
189-
let signed_2 = SignedFullStatement::new(
190-
Statement::Valid(d),
191-
f.id,
192-
s2,
193-
&f.signing_context,
194-
&f.key,
195-
).ok_or(())?;
196-
197-
Ok(MisbehaviorReport::SelfContradiction(receipt.to_plain(), signed_1, signed_2))
198-
}
199-
TableMisbehavior::ValidityDoubleVote(
200-
TableValidityDoubleVote::IssuedAndInvalidity((c, s1), (d, s2))
201-
) => {
202-
let receipt = c.clone();
203-
let signed_1 = SignedFullStatement::new(
204-
Statement::Seconded(c),
205-
f.id,
206-
s1,
207-
&f.signing_context,
208-
&f.key,
209-
).ok_or(())?;
210-
let signed_2 = SignedFullStatement::new(
211-
Statement::Invalid(d),
212-
f.id,
213-
s2,
214-
&f.signing_context,
215-
&f.key,
216-
).ok_or(())?;
217-
218-
Ok(MisbehaviorReport::SelfContradiction(receipt.to_plain(), signed_1, signed_2))
219-
}
220-
TableMisbehavior::ValidityDoubleVote(
221-
TableValidityDoubleVote::ValidityAndInvalidity(c, s1, s2)
222-
) => {
223-
let signed_1 = SignedFullStatement::new(
224-
Statement::Valid(c.hash()),
225-
f.id,
226-
s1,
227-
&f.signing_context,
228-
&f.key,
229-
).ok_or(())?;
230-
let signed_2 = SignedFullStatement::new(
231-
Statement::Invalid(c.hash()),
232-
f.id,
233-
s2,
234-
&f.signing_context,
235-
&f.key,
236-
).ok_or(())?;
237-
238-
Ok(MisbehaviorReport::SelfContradiction(c.to_plain(), signed_1, signed_2))
239-
}
240-
TableMisbehavior::MultipleCandidates(
241-
TableMultipleCandidates {
242-
first,
243-
second,
244-
}
245-
) => {
246-
let signed_1 = SignedFullStatement::new(
247-
Statement::Seconded(first.0),
248-
f.id,
249-
first.1,
250-
&f.signing_context,
251-
&f.key,
252-
).ok_or(())?;
253-
254-
let signed_2 = SignedFullStatement::new(
255-
Statement::Seconded(second.0),
256-
f.id,
257-
second.1,
258-
&f.signing_context,
259-
&f.key,
260-
).ok_or(())?;
261-
262-
Ok(MisbehaviorReport::DoubleVote(signed_1, signed_2))
263-
}
264-
_ => Err(()),
265-
}
266-
}
267-
}
268-
269135
/// The output of a collator.
270136
///
271137
/// This differs from `CandidateCommitments` in two ways:

0 commit comments

Comments
 (0)