approval votes checking logs (#3233)

* approval-voting: logs for invalid votes

* proper errors for assignment checks

* proper errors for approval checks
This commit is contained in:
Andronik Ordian
2021-06-13 17:34:05 +02:00
committed by GitHub
parent 4797fb7dfb
commit 93e42fb213
6 changed files with 149 additions and 50 deletions
+54 -34
View File
@@ -23,9 +23,10 @@
use polkadot_node_subsystem::{ use polkadot_node_subsystem::{
messages::{ messages::{
AssignmentCheckResult, ApprovalCheckResult, ApprovalVotingMessage, AssignmentCheckError, AssignmentCheckResult, ApprovalCheckError, ApprovalCheckResult,
RuntimeApiMessage, RuntimeApiRequest, ChainApiMessage, ApprovalDistributionMessage, ApprovalVotingMessage, RuntimeApiMessage, RuntimeApiRequest, ChainApiMessage,
ValidationFailed, CandidateValidationMessage, AvailabilityRecoveryMessage, ApprovalDistributionMessage, ValidationFailed, CandidateValidationMessage,
AvailabilityRecoveryMessage,
}, },
errors::RecoveryError, errors::RecoveryError,
Subsystem, SubsystemContext, SubsystemError, SubsystemResult, SpawnedSubsystem, Subsystem, SubsystemContext, SubsystemError, SubsystemResult, SpawnedSubsystem,
@@ -1334,14 +1335,17 @@ fn check_and_import_assignment(
let tick_now = state.clock.tick_now(); let tick_now = state.clock.tick_now();
let block_entry = match state.db.load_block_entry(&assignment.block_hash)? { let block_entry = match state.db.load_block_entry(&assignment.block_hash)? {
Some(b) => b, Some(b) => b,
None => return Ok((AssignmentCheckResult::Bad, Vec::new())), None => return Ok((AssignmentCheckResult::Bad(
AssignmentCheckError::UnknownBlock(assignment.block_hash),
), Vec::new())),
}; };
let session_info = match state.session_info(block_entry.session()) { let session_info = match state.session_info(block_entry.session()) {
Some(s) => s, Some(s) => s,
None => { None => {
tracing::warn!(target: LOG_TARGET, "Unknown session info for {}", block_entry.session()); return Ok((AssignmentCheckResult::Bad(
return Ok((AssignmentCheckResult::Bad, Vec::new())); AssignmentCheckError::UnknownSessionIndex(block_entry.session()),
), Vec::new()));
} }
}; };
@@ -1349,20 +1353,17 @@ fn check_and_import_assignment(
= match block_entry.candidate(candidate_index as usize) = match block_entry.candidate(candidate_index as usize)
{ {
Some((c, h)) => (*c, *h), Some((c, h)) => (*c, *h),
None => return Ok((AssignmentCheckResult::Bad, Vec::new())), // no candidate at core. None => return Ok((AssignmentCheckResult::Bad(
AssignmentCheckError::InvalidCandidateIndex(candidate_index),
), Vec::new())), // no candidate at core.
}; };
let mut candidate_entry = match state.db.load_candidate_entry(&assigned_candidate_hash)? { let mut candidate_entry = match state.db.load_candidate_entry(&assigned_candidate_hash)? {
Some(c) => c, Some(c) => c,
None => { None => {
tracing::warn!( return Ok((AssignmentCheckResult::Bad(
target: LOG_TARGET, AssignmentCheckError::InvalidCandidate(candidate_index, assigned_candidate_hash),
"Missing candidate entry {} referenced in live block {}", ), Vec::new()));
assigned_candidate_hash,
assignment.block_hash,
);
return Ok((AssignmentCheckResult::Bad, Vec::new()));
} }
}; };
@@ -1372,7 +1373,9 @@ fn check_and_import_assignment(
candidate_entry.approval_entry_mut(&assignment.block_hash) candidate_entry.approval_entry_mut(&assignment.block_hash)
{ {
Some(a) => a, Some(a) => a,
None => return Ok((AssignmentCheckResult::Bad, Vec::new())), None => return Ok((AssignmentCheckResult::Bad(
AssignmentCheckError::Internal(assignment.block_hash, assigned_candidate_hash),
), Vec::new())),
}; };
let res = state.assignment_criteria.check_assignment_cert( let res = state.assignment_criteria.check_assignment_cert(
@@ -1385,7 +1388,9 @@ fn check_and_import_assignment(
); );
let tranche = match res { let tranche = match res {
Err(crate::criteria::InvalidAssignment) => return Ok((AssignmentCheckResult::Bad, Vec::new())), Err(crate::criteria::InvalidAssignment) => return Ok((AssignmentCheckResult::Bad(
AssignmentCheckError::InvalidCert(assignment.validator),
), Vec::new())),
Ok(tranche) => { Ok(tranche) => {
let current_tranche = state.clock.tranche_now( let current_tranche = state.clock.tranche_now(
state.slot_duration_millis, state.slot_duration_millis,
@@ -1455,20 +1460,27 @@ fn check_and_import_approval<T>(
let block_entry = match state.db.load_block_entry(&approval.block_hash)? { let block_entry = match state.db.load_block_entry(&approval.block_hash)? {
Some(b) => b, Some(b) => b,
None => respond_early!(ApprovalCheckResult::Bad) None => {
respond_early!(ApprovalCheckResult::Bad(
ApprovalCheckError::UnknownBlock(approval.block_hash),
))
}
}; };
let session_info = match state.session_info(block_entry.session()) { let session_info = match state.session_info(block_entry.session()) {
Some(s) => s, Some(s) => s,
None => { None => {
tracing::warn!(target: LOG_TARGET, "Unknown session info for {}", block_entry.session()); respond_early!(ApprovalCheckResult::Bad(
respond_early!(ApprovalCheckResult::Bad) ApprovalCheckError::UnknownSessionIndex(block_entry.session()),
))
} }
}; };
let approved_candidate_hash = match block_entry.candidate(approval.candidate_index as usize) { let approved_candidate_hash = match block_entry.candidate(approval.candidate_index as usize) {
Some((_, h)) => *h, Some((_, h)) => *h,
None => respond_early!(ApprovalCheckResult::Bad) None => respond_early!(ApprovalCheckResult::Bad(
ApprovalCheckError::InvalidCandidateIndex(approval.candidate_index),
))
}; };
let approval_payload = ApprovalVote(approved_candidate_hash) let approval_payload = ApprovalVote(approved_candidate_hash)
@@ -1476,33 +1488,41 @@ fn check_and_import_approval<T>(
let pubkey = match session_info.validators.get(approval.validator.0 as usize) { let pubkey = match session_info.validators.get(approval.validator.0 as usize) {
Some(k) => k, Some(k) => k,
None => respond_early!(ApprovalCheckResult::Bad) None => respond_early!(ApprovalCheckResult::Bad(
ApprovalCheckError::InvalidValidatorIndex(approval.validator),
))
}; };
let approval_sig_valid = approval.signature.verify(approval_payload.as_slice(), pubkey); let approval_sig_valid = approval.signature.verify(approval_payload.as_slice(), pubkey);
if !approval_sig_valid { if !approval_sig_valid {
respond_early!(ApprovalCheckResult::Bad) respond_early!(ApprovalCheckResult::Bad(
ApprovalCheckError::InvalidSignature(approval.validator),
))
} }
let candidate_entry = match state.db.load_candidate_entry(&approved_candidate_hash)? { let candidate_entry = match state.db.load_candidate_entry(&approved_candidate_hash)? {
Some(c) => c, Some(c) => c,
None => { None => {
tracing::warn!( respond_early!(ApprovalCheckResult::Bad(
target: LOG_TARGET, ApprovalCheckError::InvalidCandidate(approval.candidate_index, approved_candidate_hash),
"Unknown candidate entry for {}", ))
approved_candidate_hash,
);
respond_early!(ApprovalCheckResult::Bad)
} }
}; };
// Don't accept approvals until assignment. // Don't accept approvals until assignment.
if candidate_entry.approval_entry(&approval.block_hash) match candidate_entry.approval_entry(&approval.block_hash) {
.map_or(true, |e| !e.is_assigned(approval.validator)) None => {
{ respond_early!(ApprovalCheckResult::Bad(
respond_early!(ApprovalCheckResult::Bad) ApprovalCheckError::Internal(approval.block_hash, approved_candidate_hash),
))
}
Some(e) if !e.is_assigned(approval.validator) => {
respond_early!(ApprovalCheckResult::Bad(
ApprovalCheckError::NoAssignment(approval.validator),
))
}
_ => {},
} }
// importing the approval can be heavy as it may trigger acceptance for a series of blocks. // importing the approval can be heavy as it may trigger acceptance for a series of blocks.
@@ -397,8 +397,9 @@ fn rejects_bad_assignment() {
assert!(res.1.iter().any(|action| matches!(action, Action::WriteCandidateEntry(..)))); assert!(res.1.iter().any(|action| matches!(action, Action::WriteCandidateEntry(..))));
// unknown hash // unknown hash
let unknown_hash = Hash::repeat_byte(0x02);
let assignment = IndirectAssignmentCert { let assignment = IndirectAssignmentCert {
block_hash: Hash::repeat_byte(0x02), block_hash: unknown_hash,
validator: ValidatorIndex(0), validator: ValidatorIndex(0),
cert: garbage_assignment_cert( cert: garbage_assignment_cert(
AssignmentCertKind::RelayVRFModulo { AssignmentCertKind::RelayVRFModulo {
@@ -412,7 +413,7 @@ fn rejects_bad_assignment() {
assignment, assignment,
candidate_index, candidate_index,
).unwrap(); ).unwrap();
assert_eq!(res.0, AssignmentCheckResult::Bad); assert_eq!(res.0, AssignmentCheckResult::Bad(AssignmentCheckError::UnknownBlock(unknown_hash)));
let mut state = State { let mut state = State {
assignment_criteria: Box::new(MockAssignmentCriteria::check_only(|| { assignment_criteria: Box::new(MockAssignmentCriteria::check_only(|| {
@@ -427,7 +428,7 @@ fn rejects_bad_assignment() {
assignment_good, assignment_good,
candidate_index, candidate_index,
).unwrap(); ).unwrap();
assert_eq!(res.0, AssignmentCheckResult::Bad); assert_eq!(res.0, AssignmentCheckResult::Bad(AssignmentCheckError::InvalidCert(ValidatorIndex(0))));
} }
#[test] #[test]
@@ -495,7 +496,7 @@ fn rejects_assignment_with_unknown_candidate() {
assignment.clone(), assignment.clone(),
candidate_index, candidate_index,
).unwrap(); ).unwrap();
assert_eq!(res.0, AssignmentCheckResult::Bad); assert_eq!(res.0, AssignmentCheckResult::Bad(AssignmentCheckError::InvalidCandidateIndex(candidate_index)));
} }
#[test] #[test]
@@ -579,7 +580,7 @@ fn rejects_approval_before_assignment() {
|r| r |r| r
).unwrap(); ).unwrap();
assert_eq!(res, ApprovalCheckResult::Bad); assert_eq!(res, ApprovalCheckResult::Bad(ApprovalCheckError::NoAssignment(ValidatorIndex(0))));
assert!(actions.is_empty()); assert!(actions.is_empty());
} }
@@ -611,7 +612,7 @@ fn rejects_approval_if_no_candidate_entry() {
|r| r |r| r
).unwrap(); ).unwrap();
assert_eq!(res, ApprovalCheckResult::Bad); assert_eq!(res, ApprovalCheckResult::Bad(ApprovalCheckError::InvalidCandidate(0, candidate_hash)));
assert!(actions.is_empty()); assert!(actions.is_empty());
} }
@@ -649,7 +650,7 @@ fn rejects_approval_if_no_block_entry() {
|r| r |r| r
).unwrap(); ).unwrap();
assert_eq!(res, ApprovalCheckResult::Bad); assert_eq!(res, ApprovalCheckResult::Bad(ApprovalCheckError::UnknownBlock(block_hash)));
assert!(actions.is_empty()); assert!(actions.is_empty());
} }
@@ -624,13 +624,29 @@ impl State {
if let Some(peer_knowledge) = entry.known_by.get_mut(&peer_id) { if let Some(peer_knowledge) = entry.known_by.get_mut(&peer_id) {
peer_knowledge.received.insert(fingerprint); peer_knowledge.received.insert(fingerprint);
} }
tracing::debug!(
target: LOG_TARGET,
?peer_id,
"Got an `AcceptedDuplicate` assignment",
);
return; return;
} }
AssignmentCheckResult::TooFarInFuture => { AssignmentCheckResult::TooFarInFuture => {
tracing::debug!(
target: LOG_TARGET,
?peer_id,
"Got an assignment too far in the future",
);
modify_reputation(ctx, peer_id, COST_ASSIGNMENT_TOO_FAR_IN_THE_FUTURE).await; modify_reputation(ctx, peer_id, COST_ASSIGNMENT_TOO_FAR_IN_THE_FUTURE).await;
return; return;
} }
AssignmentCheckResult::Bad => { AssignmentCheckResult::Bad(error) => {
tracing::info!(
target: LOG_TARGET,
?peer_id,
%error,
"Got a bad assignment from peer",
);
modify_reputation(ctx, peer_id, COST_INVALID_MESSAGE).await; modify_reputation(ctx, peer_id, COST_INVALID_MESSAGE).await;
return; return;
} }
@@ -844,11 +860,12 @@ impl State {
peer_knowledge.received.insert(fingerprint.clone()); peer_knowledge.received.insert(fingerprint.clone());
} }
} }
ApprovalCheckResult::Bad => { ApprovalCheckResult::Bad(error) => {
modify_reputation(ctx, peer_id, COST_INVALID_MESSAGE).await; modify_reputation(ctx, peer_id, COST_INVALID_MESSAGE).await;
tracing::info!( tracing::info!(
target: LOG_TARGET, target: LOG_TARGET,
?peer_id, ?peer_id,
%error,
"Got a bad approval from peer", "Got a bad approval from peer",
); );
return; return;
@@ -17,6 +17,7 @@
use std::time::Duration; use std::time::Duration;
use futures::{future, Future, executor}; use futures::{future, Future, executor};
use assert_matches::assert_matches; use assert_matches::assert_matches;
use polkadot_node_subsystem::messages::ApprovalCheckError;
use polkadot_node_subsystem_test_helpers as test_helpers; use polkadot_node_subsystem_test_helpers as test_helpers;
use polkadot_node_subsystem_util::TimeoutExt as _; use polkadot_node_subsystem_util::TimeoutExt as _;
use polkadot_node_network_protocol::{view, ObservedRole}; use polkadot_node_network_protocol::{view, ObservedRole};
@@ -594,7 +595,7 @@ fn import_approval_bad() {
tx, tx,
)) => { )) => {
assert_eq!(vote, approval); assert_eq!(vote, approval);
tx.send(ApprovalCheckResult::Bad).unwrap(); tx.send(ApprovalCheckResult::Bad(ApprovalCheckError::UnknownBlock(hash))).unwrap();
} }
); );
+44 -4
View File
@@ -646,7 +646,7 @@ impl CollationGenerationMessage {
} }
/// The result type of [`ApprovalVotingMessage::CheckAndImportAssignment`] request. /// The result type of [`ApprovalVotingMessage::CheckAndImportAssignment`] request.
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum AssignmentCheckResult { pub enum AssignmentCheckResult {
/// The vote was accepted and should be propagated onwards. /// The vote was accepted and should be propagated onwards.
Accepted, Accepted,
@@ -655,16 +655,56 @@ pub enum AssignmentCheckResult {
/// The vote was valid but too far in the future to accept right now. /// The vote was valid but too far in the future to accept right now.
TooFarInFuture, TooFarInFuture,
/// The vote was bad and should be ignored, reporting the peer who propagated it. /// The vote was bad and should be ignored, reporting the peer who propagated it.
Bad, Bad(AssignmentCheckError),
}
/// The error result type of [`ApprovalVotingMessage::CheckAndImportAssignment`] request.
#[derive(Error, Debug, Clone, PartialEq, Eq)]
#[allow(missing_docs)]
pub enum AssignmentCheckError {
#[error("Unknown block: {0:?}")]
UnknownBlock(Hash),
#[error("Unknown session index: {0}")]
UnknownSessionIndex(SessionIndex),
#[error("Invalid candidate index: {0}")]
InvalidCandidateIndex(CandidateIndex),
#[error("Invalid candidate {0}: {1:?}")]
InvalidCandidate(CandidateIndex, CandidateHash),
#[error("Invalid cert: {0:?}")]
InvalidCert(ValidatorIndex),
#[error("Internal state mismatch: {0:?}, {1:?}")]
Internal(Hash, CandidateHash),
} }
/// The result type of [`ApprovalVotingMessage::CheckAndImportApproval`] request. /// The result type of [`ApprovalVotingMessage::CheckAndImportApproval`] request.
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum ApprovalCheckResult { pub enum ApprovalCheckResult {
/// The vote was accepted and should be propagated onwards. /// The vote was accepted and should be propagated onwards.
Accepted, Accepted,
/// The vote was bad and should be ignored, reporting the peer who propagated it. /// The vote was bad and should be ignored, reporting the peer who propagated it.
Bad, Bad(ApprovalCheckError)
}
/// The error result type of [`ApprovalVotingMessage::CheckAndImportApproval`] request.
#[derive(Error, Debug, Clone, PartialEq, Eq)]
#[allow(missing_docs)]
pub enum ApprovalCheckError {
#[error("Unknown block: {0:?}")]
UnknownBlock(Hash),
#[error("Unknown session index: {0}")]
UnknownSessionIndex(SessionIndex),
#[error("Invalid candidate index: {0}")]
InvalidCandidateIndex(CandidateIndex),
#[error("Invalid validator index: {0:?}")]
InvalidValidatorIndex(ValidatorIndex),
#[error("Invalid candidate {0}: {1:?}")]
InvalidCandidate(CandidateIndex, CandidateHash),
#[error("Invalid signature: {0:?}")]
InvalidSignature(ValidatorIndex),
#[error("No assignment for {0:?}")]
NoAssignment(ValidatorIndex),
#[error("Internal state mismatch: {0:?}, {1:?}")]
Internal(Hash, CandidateHash),
} }
/// Message to the Approval Voting subsystem. /// Message to the Approval Voting subsystem.
@@ -58,14 +58,34 @@ enum AssignmentCheckResult {
// The vote was valid but too far in the future to accept right now. // The vote was valid but too far in the future to accept right now.
TooFarInFuture, TooFarInFuture,
// The vote was bad and should be ignored, reporting the peer who propagated it. // The vote was bad and should be ignored, reporting the peer who propagated it.
Bad, Bad(AssignmentCheckError),
}
pub enum AssignmentCheckError {
UnknownBlock(Hash),
UnknownSessionIndex(SessionIndex),
InvalidCandidateIndex(CandidateIndex),
InvalidCandidate(CandidateIndex, CandidateHash),
InvalidCert(ValidatorIndex),
Internal(Hash, CandidateHash),
} }
enum ApprovalCheckResult { enum ApprovalCheckResult {
// The vote was accepted and should be propagated onwards. // The vote was accepted and should be propagated onwards.
Accepted, Accepted,
// The vote was bad and should be ignored, reporting the peer who propagated it. // The vote was bad and should be ignored, reporting the peer who propagated it.
Bad, Bad(ApprovalCheckError),
}
pub enum ApprovalCheckError {
UnknownBlock(Hash),
UnknownSessionIndex(SessionIndex),
InvalidCandidateIndex(CandidateIndex),
InvalidValidatorIndex(ValidatorIndex),
InvalidCandidate(CandidateIndex, CandidateHash),
InvalidSignature(ValidatorIndex),
NoAssignment(ValidatorIndex),
Internal(Hash, CandidateHash),
} }
enum ApprovalVotingMessage { enum ApprovalVotingMessage {