Check signatures as "Compact" in statement distribution (#5071)

* allow converting payloads _up_

* convert to superpayload in statement-distribution

* Update primitives/src/v2/signed.rs

Co-authored-by: Andronik <write@reusable.software>

Co-authored-by: Andronik <write@reusable.software>
This commit is contained in:
Robert Habermeier
2022-03-10 14:49:35 -06:00
committed by GitHub
parent 22a7fad75f
commit 8a17c614f0
5 changed files with 109 additions and 27 deletions
@@ -357,6 +357,14 @@ pub mod v1 {
}
}
/// Get the signature from the statement.
pub fn get_signature(&self) -> ValidatorSignature {
match self {
Self::Statement(_, statement) => statement.unchecked_signature().clone(),
Self::LargeStatement(metadata) => metadata.signature.clone(),
}
}
/// Get contained relay parent.
pub fn get_relay_parent(&self) -> Hash {
match self {
@@ -36,7 +36,8 @@ use polkadot_node_subsystem_util::{self as util, MIN_GOSSIP_PEERS};
use polkadot_primitives::v2::{
AuthorityDiscoveryId, CandidateHash, CommittedCandidateReceipt, CompactStatement, Hash,
SigningContext, ValidatorId, ValidatorIndex, ValidatorSignature,
SignedStatement, SigningContext, UncheckedSignedStatement, ValidatorId, ValidatorIndex,
ValidatorSignature,
};
use polkadot_subsystem::{
jaeger,
@@ -780,10 +781,10 @@ impl ActiveHeadData {
/// without modifying the internal state.
fn check_useful_or_unknown(
&self,
statement: &UncheckedSignedFullStatement,
statement: &UncheckedSignedStatement,
) -> std::result::Result<(), DeniedStatement> {
let validator_index = statement.unchecked_validator_index();
let compact = statement.unchecked_payload().to_compact();
let compact = statement.unchecked_payload();
let comparator = StoredStatementComparator {
compact: compact.clone(),
validator_index,
@@ -857,8 +858,8 @@ impl ActiveHeadData {
fn check_statement_signature(
head: &ActiveHeadData,
relay_parent: Hash,
statement: UncheckedSignedFullStatement,
) -> std::result::Result<SignedFullStatement, UncheckedSignedFullStatement> {
statement: UncheckedSignedStatement,
) -> std::result::Result<SignedStatement, UncheckedSignedStatement> {
let signing_context =
SigningContext { session_index: head.session_index, parent_hash: relay_parent };
@@ -1387,24 +1388,57 @@ async fn handle_incoming_message<'a>(
return None
}
let checked_compact = {
let (compact, validator_index) = message.get_fingerprint();
let signature = message.get_signature();
let unchecked_compact = UncheckedSignedStatement::new(compact, validator_index, signature);
match active_head.check_useful_or_unknown(&unchecked_compact) {
Ok(()) => {},
Err(DeniedStatement::NotUseful) => return None,
Err(DeniedStatement::UsefulButKnown) => {
report_peer(ctx, peer, BENEFIT_VALID_STATEMENT).await;
return None
},
}
// check the signature on the statement.
match check_statement_signature(&active_head, relay_parent, unchecked_compact) {
Err(statement) => {
tracing::debug!(
target: LOG_TARGET,
?peer,
?statement,
"Invalid statement signature"
);
report_peer(ctx, peer, COST_INVALID_SIGNATURE).await;
return None
},
Ok(statement) => statement,
}
};
// Fetch from the network only after signature and usefulness checks are completed.
let is_large_statement = message.is_large_statement();
let statement =
retrieve_statement_from_message(peer, message, active_head, ctx, req_sender, metrics)
.await?;
match active_head.check_useful_or_unknown(&statement) {
Ok(()) => {},
Err(DeniedStatement::NotUseful) => return None,
Err(DeniedStatement::UsefulButKnown) => {
report_peer(ctx, peer, BENEFIT_VALID_STATEMENT).await;
return None
},
}
let payload = statement.unchecked_into_payload();
// check the signature on the statement.
let statement = match check_statement_signature(&active_head, relay_parent, statement) {
Err(statement) => {
tracing::debug!(target: LOG_TARGET, ?peer, ?statement, "Invalid statement signature");
report_peer(ctx, peer, COST_INVALID_SIGNATURE).await;
// Upgrade the `Signed` wrapper from the compact payload to the full payload.
// This fails if the payload doesn't encode correctly.
let statement: SignedFullStatement = match checked_compact.convert_to_superpayload(payload) {
Err((compact, _)) => {
tracing::debug!(
target: LOG_TARGET,
?peer,
?compact,
is_large_statement,
"Full statement had bad payload."
);
report_peer(ctx, peer, COST_WRONG_HASH).await;
return None
},
Ok(statement) => statement,
@@ -105,14 +105,16 @@ fn active_head_accepts_only_2_seconded_per_validator() {
.ok()
.flatten()
.expect("should be signed");
assert!(head_data.check_useful_or_unknown(&a_seconded_val_0.clone().into()).is_ok());
assert!(head_data
.check_useful_or_unknown(&a_seconded_val_0.clone().convert_payload().into())
.is_ok());
let noted = head_data.note_statement(a_seconded_val_0.clone());
assert_matches!(noted, NotedStatement::Fresh(_));
// note A (duplicate)
assert_eq!(
head_data.check_useful_or_unknown(&a_seconded_val_0.clone().into()),
head_data.check_useful_or_unknown(&a_seconded_val_0.clone().convert_payload().into()),
Err(DeniedStatement::UsefulButKnown),
);
let noted = head_data.note_statement(a_seconded_val_0);
@@ -130,7 +132,9 @@ fn active_head_accepts_only_2_seconded_per_validator() {
.ok()
.flatten()
.expect("should be signed");
assert!(head_data.check_useful_or_unknown(&statement.clone().into()).is_ok());
assert!(head_data
.check_useful_or_unknown(&statement.clone().convert_payload().into())
.is_ok());
let noted = head_data.note_statement(statement);
assert_matches!(noted, NotedStatement::Fresh(_));
@@ -146,7 +150,7 @@ fn active_head_accepts_only_2_seconded_per_validator() {
.flatten()
.expect("should be signed");
assert_eq!(
head_data.check_useful_or_unknown(&statement.clone().into()),
head_data.check_useful_or_unknown(&statement.clone().convert_payload().into()),
Err(DeniedStatement::NotUseful),
);
let noted = head_data.note_statement(statement);
@@ -163,7 +167,9 @@ fn active_head_accepts_only_2_seconded_per_validator() {
.ok()
.flatten()
.expect("should be signed");
assert!(head_data.check_useful_or_unknown(&statement.clone().into()).is_ok());
assert!(head_data
.check_useful_or_unknown(&statement.clone().convert_payload().into())
.is_ok());
let noted = head_data.note_statement(statement);
assert_matches!(noted, NotedStatement::Fresh(_));
@@ -178,7 +184,9 @@ fn active_head_accepts_only_2_seconded_per_validator() {
.ok()
.flatten()
.expect("should be signed");
assert!(head_data.check_useful_or_unknown(&statement.clone().into()).is_ok());
assert!(head_data
.check_useful_or_unknown(&statement.clone().convert_payload().into())
.is_ok());
let noted = head_data.note_statement(statement);
assert_matches!(noted, NotedStatement::Fresh(_));
}
@@ -428,7 +436,9 @@ fn peer_view_update_sends_messages() {
.ok()
.flatten()
.expect("should be signed");
assert!(data.check_useful_or_unknown(&statement.clone().into()).is_ok());
assert!(data
.check_useful_or_unknown(&statement.clone().convert_payload().into())
.is_ok());
let noted = data.note_statement(statement);
assert_matches!(noted, NotedStatement::Fresh(_));
@@ -443,7 +453,9 @@ fn peer_view_update_sends_messages() {
.ok()
.flatten()
.expect("should be signed");
assert!(data.check_useful_or_unknown(&statement.clone().into()).is_ok());
assert!(data
.check_useful_or_unknown(&statement.clone().convert_payload().into())
.is_ok());
let noted = data.note_statement(statement);
assert_matches!(noted, NotedStatement::Fresh(_));
@@ -458,7 +470,9 @@ fn peer_view_update_sends_messages() {
.ok()
.flatten()
.expect("should be signed");
assert!(data.check_useful_or_unknown(&statement.clone().into()).is_ok());
assert!(data
.check_useful_or_unknown(&statement.clone().convert_payload().into())
.is_ok());
let noted = data.note_statement(statement);
assert_matches!(noted, NotedStatement::Fresh(_));
+2
View File
@@ -660,6 +660,8 @@ impl From<BitVec<u8, bitvec::order::Lsb0>> for AvailabilityBitfield {
/// A signed compact statement, suitable to be sent to the chain.
pub type SignedStatement = Signed<CompactStatement>;
/// A signed compact statement, with signature not yet checked.
pub type UncheckedSignedStatement = UncheckedSigned<CompactStatement>;
/// A bitfield signed by a particular validator about the availability of pending candidates.
pub type SignedAvailabilityBitfield = Signed<AvailabilityBitfield>;
+24
View File
@@ -148,6 +148,30 @@ impl<Payload: EncodeAs<RealPayload>, RealPayload: Encode> Signed<Payload, RealPa
{
Signed(self.0.unchecked_convert_payload())
}
/// Convert `Payload` into some claimed `SuperPayload` if the encoding matches.
///
/// Succeeds if and only if the super-payload provided actually encodes as
/// the expected payload.
pub fn convert_to_superpayload<SuperPayload>(
self,
claimed: SuperPayload,
) -> Result<Signed<SuperPayload, RealPayload>, (Self, SuperPayload)>
where
SuperPayload: EncodeAs<RealPayload>,
Payload: Encode,
{
if claimed.encode_as() == self.0.payload.encode_as() {
Ok(Signed(UncheckedSigned {
payload: claimed,
validator_index: self.0.validator_index,
signature: self.0.signature,
real_payload: sp_std::marker::PhantomData,
}))
} else {
Err((self, claimed))
}
}
}
// We can't bound this on `Payload: Into<RealPayload>` because that conversion consumes