Reputation changes requires reason (#4277)

This commit is contained in:
Arkadiy Paronyan
2019-12-03 11:33:33 +01:00
committed by Gavin Wood
parent 5edc4350b4
commit 5ec0923285
14 changed files with 233 additions and 156 deletions
@@ -61,15 +61,19 @@ use crate::config::Roles;
const KNOWN_MESSAGES_CACHE_SIZE: usize = 4096;
const REBROADCAST_INTERVAL: time::Duration = time::Duration::from_secs(30);
/// Reputation change when a peer sends us a gossip message that we didn't know about.
const GOSSIP_SUCCESS_REPUTATION_CHANGE: i32 = 1 << 4;
/// Reputation change when a peer sends us a gossip message that we already knew about.
const DUPLICATE_GOSSIP_REPUTATION_CHANGE: i32 = -(1 << 2);
/// Reputation change when a peer sends us a gossip message for an unknown engine, whatever that
/// means.
const UNKNOWN_GOSSIP_REPUTATION_CHANGE: i32 = -(1 << 6);
/// Reputation change when a peer sends a message from a topic it isn't registered on.
const UNREGISTERED_TOPIC_REPUTATION_CHANGE: i32 = -(1 << 10);
mod rep {
use peerset::ReputationChange as Rep;
/// Reputation change when a peer sends us a gossip message that we didn't know about.
pub const GOSSIP_SUCCESS: Rep = Rep::new(1 << 4, "Successfull gossip");
/// Reputation change when a peer sends us a gossip message that we already knew about.
pub const DUPLICATE_GOSSIP: Rep = Rep::new(-(1 << 2), "Duplicate gossip");
/// Reputation change when a peer sends us a gossip message for an unknown engine, whatever that
/// means.
pub const UNKNOWN_GOSSIP: Rep = Rep::new(-(1 << 6), "Unknown gossup message engine id");
/// Reputation change when a peer sends a message from a topic it isn't registered on.
pub const UNREGISTERED_TOPIC: Rep = Rep::new(-(1 << 10), "Unregistered gossip message topic");
}
struct PeerConsensus<H> {
known_messages: HashSet<H>,
@@ -470,7 +474,7 @@ impl<B: BlockT> ConsensusGossip<B> {
if self.known_messages.contains(&message_hash) {
trace!(target:"gossip", "Ignored already known message from {}", who);
protocol.report_peer(who.clone(), DUPLICATE_GOSSIP_REPUTATION_CHANGE);
protocol.report_peer(who.clone(), rep::DUPLICATE_GOSSIP);
continue;
}
@@ -489,14 +493,14 @@ impl<B: BlockT> ConsensusGossip<B> {
Some(ValidationResult::Discard) => None,
None => {
trace!(target:"gossip", "Unknown message engine id {:?} from {}", engine_id, who);
protocol.report_peer(who.clone(), UNKNOWN_GOSSIP_REPUTATION_CHANGE);
protocol.report_peer(who.clone(), rep::UNKNOWN_GOSSIP);
protocol.disconnect_peer(who.clone());
continue;
}
};
if let Some((topic, keep)) = validation_result {
protocol.report_peer(who.clone(), GOSSIP_SUCCESS_REPUTATION_CHANGE);
protocol.report_peer(who.clone(), rep::GOSSIP_SUCCESS);
if let Some(ref mut peer) = self.peers.get_mut(&who) {
peer.known_messages.insert(message_hash);
if let Entry::Occupied(mut entry) = self.live_message_sinks.entry((engine_id, topic)) {
@@ -519,7 +523,7 @@ impl<B: BlockT> ConsensusGossip<B> {
}
} else {
trace!(target:"gossip", "Ignored statement from unregistered peer {}", who);
protocol.report_peer(who.clone(), UNREGISTERED_TOPIC_REPUTATION_CHANGE);
protocol.report_peer(who.clone(), rep::UNREGISTERED_TOPIC);
}
} else {
trace!(target:"gossip", "Handled valid one hop message from peer {}", who);
@@ -33,6 +33,7 @@ use crate::message::{self, BlockAttributes, Direction, FromBlock, RequestId};
use libp2p::PeerId;
use crate::config::Roles;
use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor};
use peerset::ReputationChange;
/// Remote request timeout.
const REQUEST_TIMEOUT: Duration = Duration::from_secs(15);
@@ -44,7 +45,7 @@ const TIMEOUT_REPUTATION_CHANGE: i32 = -(1 << 8);
/// Trait used by the `LightDispatch` service to communicate messages back to the network.
pub trait LightDispatchNetwork<B: BlockT> {
/// Adjusts the reputation of the given peer.
fn report_peer(&mut self, who: &PeerId, reputation_change: i32);
fn report_peer(&mut self, who: &PeerId, reputation_change: ReputationChange);
/// Disconnect from the given peer. Used in case of misbehaviour.
fn disconnect_peer(&mut self, who: &PeerId);
@@ -267,7 +268,7 @@ impl<B: BlockT> LightDispatch<B> where
Some(request) => request,
None => {
info!("Invalid remote {} response from peer {}", rtype, peer);
network.report_peer(&peer, i32::min_value());
network.report_peer(&peer, ReputationChange::new_fatal("Invalid remote response"));
network.disconnect_peer(&peer);
self.remove_peer(peer);
return;
@@ -279,7 +280,7 @@ impl<B: BlockT> LightDispatch<B> where
Accept::Ok => (retry_count, None),
Accept::CheckFailed(error, retry_request_data) => {
info!("Failed to check remote {} response from peer {}: {}", rtype, peer, error);
network.report_peer(&peer, i32::min_value());
network.report_peer(&peer, ReputationChange::new_fatal("Failed remote response check"));
network.disconnect_peer(&peer);
self.remove_peer(peer);
@@ -293,7 +294,7 @@ impl<B: BlockT> LightDispatch<B> where
},
Accept::Unexpected(retry_request_data) => {
info!("Unexpected response to remote {} from peer", rtype);
network.report_peer(&peer, i32::min_value());
network.report_peer(&peer, ReputationChange::new_fatal("Unexpected remote response"));
network.disconnect_peer(&peer);
self.remove_peer(peer);
@@ -350,7 +351,7 @@ impl<B: BlockT> LightDispatch<B> where
let (bad_peer, request) = self.active_peers.pop_front().expect("front() is Some as checked above");
self.pending_requests.push_front(request);
network.report_peer(&bad_peer, TIMEOUT_REPUTATION_CHANGE);
network.report_peer(&bad_peer, ReputationChange::new(TIMEOUT_REPUTATION_CHANGE, "Light request timeout"));
network.disconnect_peer(&bad_peer);
}
@@ -800,7 +801,7 @@ pub mod tests {
}
impl<'a, B: BlockT> LightDispatchNetwork<B> for &'a mut DummyNetwork {
fn report_peer(&mut self, _: &PeerId, _: i32) {}
fn report_peer(&mut self, _: &PeerId, _: crate::ReputationChange) {}
fn disconnect_peer(&mut self, who: &PeerId) {
self.disconnected_peers.insert(who.clone());
}
+43 -32
View File
@@ -72,29 +72,34 @@ const MAJOR_SYNC_BLOCKS: u8 = 5;
/// Number of recently announced blocks to track for each peer.
const ANNOUNCE_HISTORY_SIZE: usize = 64;
/// Reputation change when a peer sent us a status message that led to a
/// database read error.
const BLOCKCHAIN_STATUS_READ_ERROR_REPUTATION_CHANGE: i32 = -(1 << 16);
mod rep {
use peerset::ReputationChange as Rep;
/// Reputation change when a peer sent us a message that led to a
/// database read error.
pub const BLOCKCHAIN_READ_ERROR: Rep = Rep::new(-(1 << 16), "DB Error");
/// Reputation change when a peer failed to answer our legitimate ancestry
/// block search.
const ANCESTRY_BLOCK_ERROR_REPUTATION_CHANGE: i32 = -(1 << 9);
/// Reputation change when a peer sent us a status message with a different
/// genesis than us.
pub const GENESIS_MISMATCH: Rep = Rep::new(i32::min_value(), "Genesis mismatch");
/// Reputation change when a peer sent us a status message with a different
/// genesis than us.
const GENESIS_MISMATCH_REPUTATION_CHANGE: i32 = i32::min_value() + 1;
/// Reputation change for peers which send us a block with an incomplete header.
pub const INCOMPLETE_HEADER: Rep = Rep::new(-(1 << 20), "Incomplete header");
/// Reputation change for peers which send us a block with an incomplete header.
const INCOMPLETE_HEADER_REPUTATION_CHANGE: i32 = -(1 << 20);
/// Reputation change for peers which send us a block which we fail to verify.
pub const VERIFICATION_FAIL: Rep = Rep::new(-(1 << 20), "Block verification failed");
/// Reputation change for peers which send us a block which we fail to verify.
const VERIFICATION_FAIL_REPUTATION_CHANGE: i32 = -(1 << 20);
/// Reputation change for peers which send us a known bad block.
pub const BAD_BLOCK: Rep = Rep::new(-(1 << 29), "Bad block");
/// Reputation change for peers which send us a bad block.
const BAD_BLOCK_REPUTATION_CHANGE: i32 = -(1 << 29);
/// Reputation change for peers which send us a block with bad justifications.
pub const BAD_JUSTIFICATION: Rep = Rep::new(-(1 << 16), "Bad justification");
/// Reputation change for peers which send us a block with bad justifications.
const BAD_JUSTIFICATION_REPUTATION_CHANGE: i32 = -(1 << 16);
/// Reputation change for peers which send us a block with bad finality proof.
pub const BAD_FINALITY_PROOF: Rep = Rep::new(-(1 << 16), "Bad finality proof");
/// Reputation change when a peer sent us invlid ancestry result.
pub const UNKNOWN_ANCESTOR:Rep = Rep::new(-(1 << 16), "DB Error");
}
/// The main data structure which contains all the state for a chains
/// active syncing strategy.
@@ -225,11 +230,11 @@ pub struct Status<B: BlockT> {
/// A peer did not behave as expected and should be reported.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BadPeer(pub PeerId, pub i32);
pub struct BadPeer(pub PeerId, pub peerset::ReputationChange);
impl fmt::Display for BadPeer {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "bad peer {}; reputation change: {}", self.0, self.1)
write!(f, "Bad peer {}; Reputation change: {:?}", self.0, self.1)
}
}
@@ -358,16 +363,16 @@ impl<B: BlockT> ChainSync<B> {
match self.block_status(&best_hash) {
Err(e) => {
debug!(target:"sync", "Error reading blockchain: {:?}", e);
Err(BadPeer(who, BLOCKCHAIN_STATUS_READ_ERROR_REPUTATION_CHANGE))
Err(BadPeer(who, rep::BLOCKCHAIN_READ_ERROR))
}
Ok(BlockStatus::KnownBad) => {
info!("New peer with known bad best block {} ({}).", best_hash, best_number);
Err(BadPeer(who, i32::min_value()))
Err(BadPeer(who, rep::BAD_BLOCK))
}
Ok(BlockStatus::Unknown) => {
if best_number.is_zero() {
info!("New peer with unknown genesis hash {} ({}).", best_hash, best_number);
return Err(BadPeer(who, i32::min_value()))
return Err(BadPeer(who, rep::GENESIS_MISMATCH));
}
// If there are more than `MAJOR_SYNC_BLOCKS` in the import queue then we have
// enough to do in the import queue that it's not worth kicking off
@@ -688,11 +693,11 @@ impl<B: BlockT> ChainSync<B> {
},
(None, _) => {
debug!(target: "sync", "Invalid response when searching for ancestor from {}", who);
return Err(BadPeer(who, i32::min_value()))
return Err(BadPeer(who, rep::UNKNOWN_ANCESTOR))
},
(_, Err(e)) => {
info!("Error answering legitimate blockchain query: {:?}", e);
return Err(BadPeer(who, ANCESTRY_BLOCK_ERROR_REPUTATION_CHANGE))
return Err(BadPeer(who, rep::BLOCKCHAIN_READ_ERROR))
}
};
if matching_hash.is_some() && peer.common_number < *num {
@@ -700,7 +705,7 @@ impl<B: BlockT> ChainSync<B> {
}
if matching_hash.is_none() && num.is_zero() {
trace!(target:"sync", "Ancestry search: genesis mismatch for peer {}", who);
return Err(BadPeer(who, GENESIS_MISMATCH_REPUTATION_CHANGE))
return Err(BadPeer(who, rep::GENESIS_MISMATCH))
}
if let Some((next_state, next_num)) = handle_ancestor_search_state(state, *num, matching_hash.is_some()) {
peer.state = PeerSyncState::AncestorSearch(next_num, next_state);
@@ -790,9 +795,10 @@ impl<B: BlockT> ChainSync<B> {
if let Some(block) = response.blocks.into_iter().next() {
if hash != block.hash {
info!(
target: "sync",
"Invalid block justification provided by {}: requested: {:?} got: {:?}", who, hash, block.hash
);
return Err(BadPeer(who, i32::min_value()))
return Err(BadPeer(who, rep::BAD_JUSTIFICATION));
}
if let Some((peer, hash, number, j)) = self.extra_justifications.on_response(who, block.justification) {
return Ok(OnBlockJustification::Import { peer, hash, number, justification: j })
@@ -825,8 +831,13 @@ impl<B: BlockT> ChainSync<B> {
// We only request one finality proof at a time.
if hash != resp.block {
info!("Invalid block finality proof provided: requested: {:?} got: {:?}", hash, resp.block);
return Err(BadPeer(who, i32::min_value()))
info!(
target: "sync",
"Invalid block finality proof provided: requested: {:?} got: {:?}",
hash,
resp.block
);
return Err(BadPeer(who, rep::BAD_FINALITY_PROOF));
}
if let Some((peer, hash, number, p)) = self.extra_finality_proofs.on_response(who, resp.proof) {
@@ -887,7 +898,7 @@ impl<B: BlockT> ChainSync<B> {
if aux.bad_justification {
if let Some(peer) = who {
info!("Sent block with bad justification to import");
output.push(Err(BadPeer(peer, BAD_JUSTIFICATION_REPUTATION_CHANGE)));
output.push(Err(BadPeer(peer, rep::BAD_JUSTIFICATION)));
}
}
@@ -903,21 +914,21 @@ impl<B: BlockT> ChainSync<B> {
Err(BlockImportError::IncompleteHeader(who)) => {
if let Some(peer) = who {
info!("Peer sent block with incomplete header to import");
output.push(Err(BadPeer(peer, INCOMPLETE_HEADER_REPUTATION_CHANGE)));
output.push(Err(BadPeer(peer, rep::INCOMPLETE_HEADER)));
output.extend(self.restart());
}
},
Err(BlockImportError::VerificationFailed(who, e)) => {
if let Some(peer) = who {
info!("Verification failed from peer: {}", e);
output.push(Err(BadPeer(peer, VERIFICATION_FAIL_REPUTATION_CHANGE)));
output.push(Err(BadPeer(peer, rep::VERIFICATION_FAIL)));
output.extend(self.restart());
}
},
Err(BlockImportError::BadBlock(who)) => {
if let Some(peer) = who {
info!("Bad block");
output.push(Err(BadPeer(peer, BAD_BLOCK_REPUTATION_CHANGE)));
output.push(Err(BadPeer(peer, rep::BAD_BLOCK)));
output.extend(self.restart());
}
},