Better handling of RPC errors and RPC conn with the PeerManager (#1047)

This commit is contained in:
divma 2020-05-03 08:17:12 -05:00 committed by GitHub
parent b6c027b9ec
commit b4a1a2e483
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 656 additions and 463 deletions

View File

@ -306,7 +306,7 @@ impl<TSubstream: AsyncRead + AsyncWrite, TSpec: EthSpec> Behaviour<TSubstream, T
let event = if is_request { let event = if is_request {
RPCEvent::Request(id, RPCRequest::Ping(ping)) RPCEvent::Request(id, RPCRequest::Ping(ping))
} else { } else {
RPCEvent::Response(id, RPCErrorResponse::Success(RPCResponse::Pong(ping))) RPCEvent::Response(id, RPCCodedResponse::Success(RPCResponse::Pong(ping)))
}; };
self.send_rpc(peer_id, event); self.send_rpc(peer_id, event);
} }
@ -322,7 +322,7 @@ impl<TSubstream: AsyncRead + AsyncWrite, TSpec: EthSpec> Behaviour<TSubstream, T
fn send_meta_data_response(&mut self, id: RequestId, peer_id: PeerId) { fn send_meta_data_response(&mut self, id: RequestId, peer_id: PeerId) {
let metadata_response = RPCEvent::Response( let metadata_response = RPCEvent::Response(
id, id,
RPCErrorResponse::Success(RPCResponse::MetaData(self.meta_data.clone())), RPCCodedResponse::Success(RPCResponse::MetaData(self.meta_data.clone())),
); );
self.send_rpc(peer_id, metadata_response); self.send_rpc(peer_id, metadata_response);
} }
@ -391,7 +391,7 @@ impl<TSubstream: AsyncRead + AsyncWrite, TSpec: EthSpec>
let bitfield = match enr.bitfield::<TSpec>() { let bitfield = match enr.bitfield::<TSpec>() {
Ok(v) => v, Ok(v) => v,
Err(e) => { Err(e) => {
warn!(self.log, "Peer has invalid ENR bitfield"; warn!(self.log, "Peer has invalid ENR bitfield";
"peer_id" => format!("{}", peer_id), "peer_id" => format!("{}", peer_id),
"error" => format!("{:?}", e)); "error" => format!("{:?}", e));
return; return;
@ -435,22 +435,26 @@ impl<TSubstream: AsyncRead + AsyncWrite, TSpec: EthSpec>
// send the requested meta-data // send the requested meta-data
self.send_meta_data_response(id, peer_id); self.send_meta_data_response(id, peer_id);
} }
RPCEvent::Response(_, RPCErrorResponse::Success(RPCResponse::Pong(ping))) => { RPCEvent::Response(_, RPCCodedResponse::Success(RPCResponse::Pong(ping))) => {
self.peer_manager.pong_response(&peer_id, ping.data); self.peer_manager.pong_response(&peer_id, ping.data);
} }
RPCEvent::Response( RPCEvent::Response(
_, _,
RPCErrorResponse::Success(RPCResponse::MetaData(meta_data)), RPCCodedResponse::Success(RPCResponse::MetaData(meta_data)),
) => { ) => {
self.peer_manager.meta_data_response(&peer_id, meta_data); self.peer_manager.meta_data_response(&peer_id, meta_data);
} }
RPCEvent::Request(_, RPCRequest::Status(_)) RPCEvent::Request(_, RPCRequest::Status(_))
| RPCEvent::Response(_, RPCErrorResponse::Success(RPCResponse::Status(_))) => { | RPCEvent::Response(_, RPCCodedResponse::Success(RPCResponse::Status(_))) => {
// inform the peer manager that we have received a status from a peer // inform the peer manager that we have received a status from a peer
self.peer_manager.peer_statusd(&peer_id); self.peer_manager.peer_statusd(&peer_id);
// propagate the STATUS message upwards // propagate the STATUS message upwards
self.events.push(BehaviourEvent::RPC(peer_id, rpc_event)); self.events.push(BehaviourEvent::RPC(peer_id, rpc_event));
} }
RPCEvent::Error(_, protocol, ref err) => {
self.peer_manager.handle_rpc_error(&peer_id, protocol, err);
self.events.push(BehaviourEvent::RPC(peer_id, rpc_event));
}
_ => { _ => {
// propagate all other RPC messages upwards // propagate all other RPC messages upwards
self.events.push(BehaviourEvent::RPC(peer_id, rpc_event)) self.events.push(BehaviourEvent::RPC(peer_id, rpc_event))

View File

@ -258,7 +258,7 @@ impl<TSubstream, TSpec: EthSpec> Discovery<TSubstream, TSpec> {
.network_globals .network_globals
.peers .peers
.read() .read()
.peers_on_subnet(&subnet_id) .peers_on_subnet(subnet_id)
.count() as u64; .count() as u64;
if peers_on_subnet < TARGET_SUBNET_PEERS { if peers_on_subnet < TARGET_SUBNET_PEERS {

View File

@ -98,7 +98,7 @@ impl std::fmt::Display for Client {
// helper function to identify clients from their agent_version. Returns the client // helper function to identify clients from their agent_version. Returns the client
// kind and it's associated version and the OS kind. // kind and it's associated version and the OS kind.
fn client_from_agent_version(agent_version: &str) -> (ClientKind, String, String) { fn client_from_agent_version(agent_version: &str) -> (ClientKind, String, String) {
let mut agent_split = agent_version.split("/"); let mut agent_split = agent_version.split('/');
match agent_split.next() { match agent_split.next() {
Some("Lighthouse") => { Some("Lighthouse") => {
let kind = ClientKind::Lighthouse; let kind = ClientKind::Lighthouse;
@ -116,7 +116,7 @@ fn client_from_agent_version(agent_version: &str) -> (ClientKind, String, String
let kind = ClientKind::Teku; let kind = ClientKind::Teku;
let mut version = String::from("unknown"); let mut version = String::from("unknown");
let mut os_version = version.clone(); let mut os_version = version.clone();
if let Some(_) = agent_split.next() { if agent_split.next().is_some() {
if let Some(agent_version) = agent_split.next() { if let Some(agent_version) = agent_split.next() {
version = agent_version.into(); version = agent_version.into();
if let Some(agent_os_version) = agent_split.next() { if let Some(agent_os_version) = agent_split.next() {

View File

@ -2,7 +2,7 @@
pub use self::peerdb::*; pub use self::peerdb::*;
use crate::metrics; use crate::metrics;
use crate::rpc::MetaData; use crate::rpc::{MetaData, Protocol, RPCError, RPCResponseErrorCode};
use crate::{NetworkGlobals, PeerId}; use crate::{NetworkGlobals, PeerId};
use futures::prelude::*; use futures::prelude::*;
use futures::Stream; use futures::Stream;
@ -10,6 +10,7 @@ use hashmap_delay::HashSetDelay;
use libp2p::identify::IdentifyInfo; use libp2p::identify::IdentifyInfo;
use slog::{crit, debug, error, warn}; use slog::{crit, debug, error, warn};
use smallvec::SmallVec; use smallvec::SmallVec;
use std::convert::TryInto;
use std::sync::Arc; use std::sync::Arc;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use types::EthSpec; use types::EthSpec;
@ -19,11 +20,11 @@ mod peer_info;
mod peer_sync_status; mod peer_sync_status;
mod peerdb; mod peerdb;
pub use peer_info::PeerInfo; pub use peer_info::{PeerConnectionStatus::*, PeerInfo};
pub use peer_sync_status::{PeerSyncStatus, SyncInfo}; pub use peer_sync_status::{PeerSyncStatus, SyncInfo};
/// The minimum reputation before a peer is disconnected. /// The minimum reputation before a peer is disconnected.
// Most likely this needs tweaking // Most likely this needs tweaking.
const _MINIMUM_REPUTATION_BEFORE_BAN: Rep = 20; const MIN_REP_BEFORE_BAN: Rep = 10;
/// The time in seconds between re-status's peers. /// The time in seconds between re-status's peers.
const STATUS_INTERVAL: u64 = 300; const STATUS_INTERVAL: u64 = 300;
/// The time in seconds between PING events. We do not send a ping if the other peer as PING'd us within /// The time in seconds between PING events. We do not send a ping if the other peer as PING'd us within
@ -32,7 +33,7 @@ const PING_INTERVAL: u64 = 30;
/// The main struct that handles peer's reputation and connection status. /// The main struct that handles peer's reputation and connection status.
pub struct PeerManager<TSpec: EthSpec> { pub struct PeerManager<TSpec: EthSpec> {
/// Storage of network globals to access the PeerDB. /// Storage of network globals to access the `PeerDB`.
network_globals: Arc<NetworkGlobals<TSpec>>, network_globals: Arc<NetworkGlobals<TSpec>>,
/// A queue of events that the `PeerManager` is waiting to produce. /// A queue of events that the `PeerManager` is waiting to produce.
events: SmallVec<[PeerManagerEvent; 5]>, events: SmallVec<[PeerManagerEvent; 5]>,
@ -46,22 +47,45 @@ pub struct PeerManager<TSpec: EthSpec> {
log: slog::Logger, log: slog::Logger,
} }
/// A collection of actions a peer can perform which will adjust its reputation /// A collection of actions a peer can perform which will adjust its reputation.
/// Each variant has an associated reputation change. /// Each variant has an associated reputation change.
// To easily assess the behaviour of reputation changes the number of variants should stay low, and
// somewhat generic.
pub enum PeerAction { pub enum PeerAction {
/// The peer timed out on an RPC request/response. /// We should not communicate more with this peer.
_TimedOut = -10, /// This action will cause the peer to get banned.
/// The peer sent and invalid request/response or encoding. Fatal,
_InvalidMessage = -20, /// An error occurred with this peer but it is not necessarily malicious.
/// The peer sent something objectively malicious. /// We have high tolerance for this actions: several occurrences are needed for a peer to get
_Malicious = -50, /// kicked.
/// NOTE: ~15 occurrences will get the peer banned
HighToleranceError,
/// An error occurred with this peer but it is not necessarily malicious.
/// We have high tolerance for this actions: several occurrences are needed for a peer to get
/// kicked.
/// NOTE: ~10 occurrences will get the peer banned
MidToleranceError,
/// This peer's action is not malicious but will not be tolerated. A few occurrences will cause
/// the peer to get kicked.
/// NOTE: ~5 occurrences will get the peer banned
LowToleranceError,
/// Received an expected message. /// Received an expected message.
_ValidMessage = 20, _ValidMessage,
/// Peer disconnected.
Disconnected = -30,
} }
/// The events that the PeerManager outputs (requests). impl PeerAction {
fn rep_change(&self) -> RepChange {
match self {
PeerAction::Fatal => RepChange::worst(),
PeerAction::LowToleranceError => RepChange::bad(60),
PeerAction::MidToleranceError => RepChange::bad(25),
PeerAction::HighToleranceError => RepChange::bad(15),
PeerAction::_ValidMessage => RepChange::good(20),
}
}
}
/// The events that the `PeerManager` outputs (requests).
pub enum PeerManagerEvent { pub enum PeerManagerEvent {
/// Sends a STATUS to a peer. /// Sends a STATUS to a peer.
Status(PeerId), Status(PeerId),
@ -96,24 +120,27 @@ impl<TSpec: EthSpec> PeerManager<TSpec> {
if let Some(peer_info) = self.network_globals.peers.read().peer_info(peer_id) { if let Some(peer_info) = self.network_globals.peers.read().peer_info(peer_id) {
// received a ping // received a ping
// reset the to-ping timer for this peer // reset the to-ping timer for this peer
debug!(self.log, "Received a ping request"; "peer_id" => format!("{}", peer_id), "seq_no" => seq); debug!(self.log, "Received a ping request"; "peer_id" => peer_id.to_string(), "seq_no" => seq);
self.ping_peers.insert(peer_id.clone()); self.ping_peers.insert(peer_id.clone());
// if the sequence number is unknown send update the meta data of the peer. // if the sequence number is unknown send update the meta data of the peer.
if let Some(meta_data) = &peer_info.meta_data { if let Some(meta_data) = &peer_info.meta_data {
if meta_data.seq_number < seq { if meta_data.seq_number < seq {
debug!(self.log, "Requesting new metadata from peer"; "peer_id" => format!("{}", peer_id), "known_seq_no" => meta_data.seq_number, "ping_seq_no" => seq); debug!(self.log, "Requesting new metadata from peer";
"peer_id" => peer_id.to_string(), "known_seq_no" => meta_data.seq_number, "ping_seq_no" => seq);
self.events self.events
.push(PeerManagerEvent::MetaData(peer_id.clone())); .push(PeerManagerEvent::MetaData(peer_id.clone()));
} }
} else { } else {
// if we don't know the meta-data, request it // if we don't know the meta-data, request it
debug!(self.log, "Requesting first metadata from peer"; "peer_id" => format!("{}", peer_id)); debug!(self.log, "Requesting first metadata from peer";
"peer_id" => peer_id.to_string());
self.events self.events
.push(PeerManagerEvent::MetaData(peer_id.clone())); .push(PeerManagerEvent::MetaData(peer_id.clone()));
} }
} else { } else {
crit!(self.log, "Received a PING from an unknown peer"; "peer_id" => format!("{}", peer_id)); crit!(self.log, "Received a PING from an unknown peer";
"peer_id" => peer_id.to_string());
} }
} }
@ -126,18 +153,20 @@ impl<TSpec: EthSpec> PeerManager<TSpec> {
// if the sequence number is unknown send update the meta data of the peer. // if the sequence number is unknown send update the meta data of the peer.
if let Some(meta_data) = &peer_info.meta_data { if let Some(meta_data) = &peer_info.meta_data {
if meta_data.seq_number < seq { if meta_data.seq_number < seq {
debug!(self.log, "Requesting new metadata from peer"; "peer_id" => format!("{}", peer_id), "known_seq_no" => meta_data.seq_number, "pong_seq_no" => seq); debug!(self.log, "Requesting new metadata from peer";
"peer_id" => peer_id.to_string(), "known_seq_no" => meta_data.seq_number, "pong_seq_no" => seq);
self.events self.events
.push(PeerManagerEvent::MetaData(peer_id.clone())); .push(PeerManagerEvent::MetaData(peer_id.clone()));
} }
} else { } else {
// if we don't know the meta-data, request it // if we don't know the meta-data, request it
debug!(self.log, "Requesting first metadata from peer"; "peer_id" => format!("{}", peer_id)); debug!(self.log, "Requesting first metadata from peer";
"peer_id" => peer_id.to_string());
self.events self.events
.push(PeerManagerEvent::MetaData(peer_id.clone())); .push(PeerManagerEvent::MetaData(peer_id.clone()));
} }
} else { } else {
crit!(self.log, "Received a PONG from an unknown peer"; "peer_id" => format!("{}", peer_id)); crit!(self.log, "Received a PONG from an unknown peer"; "peer_id" => peer_id.to_string());
} }
} }
@ -147,18 +176,24 @@ impl<TSpec: EthSpec> PeerManager<TSpec> {
if let Some(peer_info) = self.network_globals.peers.write().peer_info_mut(peer_id) { if let Some(peer_info) = self.network_globals.peers.write().peer_info_mut(peer_id) {
if let Some(known_meta_data) = &peer_info.meta_data { if let Some(known_meta_data) = &peer_info.meta_data {
if known_meta_data.seq_number < meta_data.seq_number { if known_meta_data.seq_number < meta_data.seq_number {
debug!(self.log, "Updating peer's metadata"; "peer_id" => format!("{}", peer_id), "known_seq_no" => known_meta_data.seq_number, "new_seq_no" => meta_data.seq_number); debug!(self.log, "Updating peer's metadata";
"peer_id" => peer_id.to_string(), "known_seq_no" => known_meta_data.seq_number, "new_seq_no" => meta_data.seq_number);
peer_info.meta_data = Some(meta_data); peer_info.meta_data = Some(meta_data);
} else { } else {
warn!(self.log, "Received old metadata"; "peer_id" => format!("{}", peer_id), "known_seq_no" => known_meta_data.seq_number, "new_seq_no" => meta_data.seq_number); // TODO: isn't this malicious/random behaviour? What happens if the seq_number
// is the same but the contents differ?
warn!(self.log, "Received old metadata";
"peer_id" => peer_id.to_string(), "known_seq_no" => known_meta_data.seq_number, "new_seq_no" => meta_data.seq_number);
} }
} else { } else {
// we have no meta-data for this peer, update // we have no meta-data for this peer, update
debug!(self.log, "Obtained peer's metadata"; "peer_id" => format!("{}", peer_id), "new_seq_no" => meta_data.seq_number); debug!(self.log, "Obtained peer's metadata";
"peer_id" => peer_id.to_string(), "new_seq_no" => meta_data.seq_number);
peer_info.meta_data = Some(meta_data); peer_info.meta_data = Some(meta_data);
} }
} else { } else {
crit!(self.log, "Received METADATA from an unknown peer"; "peer_id" => format!("{}", peer_id)); crit!(self.log, "Received METADATA from an unknown peer";
"peer_id" => peer_id.to_string());
} }
} }
@ -167,38 +202,12 @@ impl<TSpec: EthSpec> PeerManager<TSpec> {
self.status_peers.insert(peer_id.clone()); self.status_peers.insert(peer_id.clone());
} }
/// Checks the reputation of a peer and if it is too low, bans it and
/// sends the corresponding event. Informs if it got banned
fn _gets_banned(&mut self, peer_id: &PeerId) -> bool {
// if the peer was already banned don't inform again
let mut peerdb = self.network_globals.peers.write();
if let Some(connection_status) = peerdb.connection_status(peer_id) {
if peerdb.reputation(peer_id) < _MINIMUM_REPUTATION_BEFORE_BAN
&& !connection_status.is_banned()
{
peerdb.ban(peer_id);
self.events
.push(PeerManagerEvent::_BanPeer(peer_id.clone()));
return true;
}
}
false
}
/// Requests that a peer get disconnected.
pub fn _disconnect_peer(&mut self, peer_id: &PeerId) {
self.events
.push(PeerManagerEvent::_DisconnectPeer(peer_id.clone()));
}
/// Updates the state of the peer as disconnected. /// Updates the state of the peer as disconnected.
pub fn notify_disconnect(&mut self, peer_id: &PeerId) { pub fn notify_disconnect(&mut self, peer_id: &PeerId) {
self.update_reputations(); self.update_reputations();
{ {
let mut peerdb = self.network_globals.peers.write(); let mut peerdb = self.network_globals.peers.write();
peerdb.disconnect(peer_id); peerdb.disconnect(peer_id);
peerdb.add_reputation(peer_id, PeerAction::Disconnected as Rep);
} }
// remove the ping and status timer for the peer // remove the ping and status timer for the peer
@ -223,35 +232,15 @@ impl<TSpec: EthSpec> PeerManager<TSpec> {
self.connect_peer(peer_id, true) self.connect_peer(peer_id, true)
} }
/// Provides a given peer's reputation if it exists.
pub fn _get_peer_rep(&self, peer_id: &PeerId) -> Rep {
self.network_globals.peers.read().reputation(peer_id)
}
/// Updates the reputation of known peers according to their connection
/// status and the time that has passed.
pub fn update_reputations(&mut self) {
let now = Instant::now();
let elapsed = (now - self.last_updated).as_secs();
// 0 seconds means now - last_updated < 0, but (most likely) not = 0.
// In this case, do nothing (updating last_updated would propagate
// rounding errors)
if elapsed > 0 {
self.last_updated = now;
// TODO decide how reputations change with time. If they get too low
// set the peers as banned
}
}
/// Reports a peer for some action. /// Reports a peer for some action.
/// ///
/// If the peer doesn't exist, log a warning and insert defaults. /// If the peer doesn't exist, log a warning and insert defaults.
pub fn _report_peer(&mut self, peer_id: &PeerId, action: PeerAction) { pub fn report_peer(&mut self, peer_id: &PeerId, action: PeerAction) {
self.update_reputations(); self.update_reputations();
self.network_globals self.network_globals
.peers .peers
.write() .write()
.add_reputation(peer_id, action as Rep); .add_reputation(peer_id, action.rep_change());
self.update_reputations(); self.update_reputations();
} }
@ -261,10 +250,68 @@ impl<TSpec: EthSpec> PeerManager<TSpec> {
peer_info.client = client::Client::from_identify_info(info); peer_info.client = client::Client::from_identify_info(info);
peer_info.listening_addresses = info.listen_addrs.clone(); peer_info.listening_addresses = info.listen_addrs.clone();
} else { } else {
crit!(self.log, "Received an Identify response from an unknown peer"; "peer_id" => format!("{}", peer_id)); crit!(self.log, "Received an Identify response from an unknown peer"; "peer_id" => peer_id.to_string());
} }
} }
pub fn handle_rpc_error(&mut self, peer_id: &PeerId, protocol: Protocol, err: &RPCError) {
debug!(self.log, "RPCError"; "protocol" => protocol.to_string(), "err" => err.to_string());
// Map this error to a `PeerAction` (if any)
let peer_action = match err {
RPCError::IncompleteStream => {
// They closed early, this could mean poor connection
PeerAction::MidToleranceError
}
RPCError::InternalError(_reason) => {
// Our fault. Do nothing
return;
}
RPCError::InvalidData => {
// Peer is not complying with the protocol. This is considered a malicious action
PeerAction::Fatal
}
RPCError::IoError(_e) => {
// this could their fault or ours, so we tolerate this
PeerAction::HighToleranceError
}
RPCError::ErrorResponse(code) => match code {
RPCResponseErrorCode::Unknown => PeerAction::HighToleranceError,
RPCResponseErrorCode::ServerError => PeerAction::MidToleranceError,
RPCResponseErrorCode::InvalidRequest => PeerAction::LowToleranceError,
},
RPCError::SSZDecodeError(_) => PeerAction::Fatal,
RPCError::UnsupportedProtocol => {
// Not supporting a protocol shouldn't be considered a malicious action, but
// it is an action that in some cases will make the peer unfit to continue
// communicating.
// TODO: To avoid punishing a peer repeatedly for not supporting a protocol, this
// information could be stored and used to prevent sending requests for the given
// protocol to this peer. Similarly, to avoid blacklisting a peer for a protocol
// forever, if stored this information should expire.
match protocol {
Protocol::Ping => PeerAction::Fatal,
Protocol::BlocksByRange => return,
Protocol::BlocksByRoot => return,
Protocol::Goodbye => return,
Protocol::MetaData => PeerAction::LowToleranceError,
Protocol::Status => PeerAction::LowToleranceError,
}
}
RPCError::StreamTimeout => match protocol {
Protocol::Ping => PeerAction::LowToleranceError,
Protocol::BlocksByRange => PeerAction::MidToleranceError,
Protocol::BlocksByRoot => PeerAction::MidToleranceError,
Protocol::Goodbye => return,
Protocol::MetaData => return,
Protocol::Status => return,
},
RPCError::NegotiationTimeout => PeerAction::HighToleranceError,
};
self.report_peer(peer_id, peer_action);
}
/* Internal functions */ /* Internal functions */
/// Registers a peer as connected. The `ingoing` parameter determines if the peer is being /// Registers a peer as connected. The `ingoing` parameter determines if the peer is being
@ -275,7 +322,7 @@ impl<TSpec: EthSpec> PeerManager<TSpec> {
/// This informs if the peer was accepted in to the db or not. /// This informs if the peer was accepted in to the db or not.
// TODO: Drop peers if over max_peer limit // TODO: Drop peers if over max_peer limit
fn connect_peer(&mut self, peer_id: &PeerId, outgoing: bool) -> bool { fn connect_peer(&mut self, peer_id: &PeerId, outgoing: bool) -> bool {
// TODO: Call this on a timer // TODO: remove after timed updates
self.update_reputations(); self.update_reputations();
{ {
@ -288,7 +335,7 @@ impl<TSpec: EthSpec> PeerManager<TSpec> {
if outgoing { if outgoing {
peerdb.connect_outgoing(peer_id); peerdb.connect_outgoing(peer_id);
} else { } else {
peerdb.connect_outgoing(peer_id); peerdb.connect_ingoing(peer_id);
} }
} }
@ -310,6 +357,86 @@ impl<TSpec: EthSpec> PeerManager<TSpec> {
pub fn _dialing_peer(&mut self, peer_id: &PeerId) { pub fn _dialing_peer(&mut self, peer_id: &PeerId) {
self.network_globals.peers.write().dialing_peer(peer_id); self.network_globals.peers.write().dialing_peer(peer_id);
} }
/// Updates the reputation of known peers according to their connection
/// status and the time that has passed.
///
/// **Disconnected peers** get a 1rep hit every hour they stay disconnected.
/// **Banned peers** get a 1rep gain for every hour to slowly allow them back again.
///
/// A banned(disconnected) peer that gets its rep above(below) MIN_REP_BEFORE_BAN is
/// now considered a disconnected(banned) peer.
fn update_reputations(&mut self) {
// avoid locking the peerdb too often
// TODO: call this on a timer
if self.last_updated.elapsed().as_secs() < 30 {
return;
}
let now = Instant::now();
// Check for peers that get banned, unbanned and that should be disconnected
let mut ban_queue = Vec::new();
let mut unban_queue = Vec::new();
/* Check how long have peers been in this state and update their reputations if needed */
let mut pdb = self.network_globals.peers.write();
for (id, info) in pdb.peers_mut() {
// Update reputations
match info.connection_status {
Connected { .. } => {
// Connected peers gain reputation by sending useful messages
}
Disconnected { since } | Banned { since } => {
// For disconnected peers, lower their reputation by 1 for every hour they
// stay disconnected. This helps us slowly forget disconnected peers.
// In the same way, slowly allow banned peers back again.
let dc_hours = (now - since).as_secs() / 3600;
let last_dc_hours = (self.last_updated - since).as_secs() / 3600;
if dc_hours > last_dc_hours {
// this should be 1 most of the time
let rep_dif = (dc_hours - last_dc_hours)
.try_into()
.unwrap_or(Rep::max_value());
info.reputation = if info.connection_status.is_banned() {
info.reputation.saturating_add(rep_dif)
} else {
info.reputation.saturating_sub(rep_dif)
};
}
}
Dialing { since } => {
// A peer shouldn't be dialing for more than 2 minutes
if since.elapsed().as_secs() > 120 {
warn!(self.log,"Peer has been dialing for too long"; "peer_id" => id.to_string());
// TODO: decide how to handle this
}
}
}
// Check if the peer gets banned or unbanned and if it should be disconnected
if info.reputation < MIN_REP_BEFORE_BAN && !info.connection_status.is_banned() {
// This peer gets banned. Check if we should request disconnection
ban_queue.push(id.clone());
} else if info.reputation >= MIN_REP_BEFORE_BAN && info.connection_status.is_banned() {
// This peer gets unbanned
unban_queue.push(id.clone());
}
}
for id in ban_queue {
pdb.ban(&id);
self.events.push(PeerManagerEvent::_BanPeer(id.clone()));
}
for id in unban_queue {
pdb.disconnect(&id);
}
self.last_updated = Instant::now();
}
} }
impl<TSpec: EthSpec> Stream for PeerManager<TSpec> { impl<TSpec: EthSpec> Stream for PeerManager<TSpec> {
@ -322,9 +449,9 @@ impl<TSpec: EthSpec> Stream for PeerManager<TSpec> {
// These exist to handle a bug in delayqueue // These exist to handle a bug in delayqueue
let mut peers_to_add = Vec::new(); let mut peers_to_add = Vec::new();
while let Async::Ready(Some(peer_id)) = self.ping_peers.poll().map_err(|e| { while let Async::Ready(Some(peer_id)) = self.ping_peers.poll().map_err(|e| {
error!(self.log, "Failed to check for peers to ping"; "error" => format!("{}",e)); error!(self.log, "Failed to check for peers to ping"; "error" => e.to_string());
})? { })? {
debug!(self.log, "Pinging peer"; "peer_id" => format!("{}", peer_id)); debug!(self.log, "Pinging peer"; "peer_id" => peer_id.to_string());
// add the ping timer back // add the ping timer back
peers_to_add.push(peer_id.clone()); peers_to_add.push(peer_id.clone());
self.events.push(PeerManagerEvent::Ping(peer_id)); self.events.push(PeerManagerEvent::Ping(peer_id));
@ -338,9 +465,9 @@ impl<TSpec: EthSpec> Stream for PeerManager<TSpec> {
} }
while let Async::Ready(Some(peer_id)) = self.status_peers.poll().map_err(|e| { while let Async::Ready(Some(peer_id)) = self.status_peers.poll().map_err(|e| {
error!(self.log, "Failed to check for peers to status"; "error" => format!("{}",e)); error!(self.log, "Failed to check for peers to status"; "error" => e.to_string());
})? { })? {
debug!(self.log, "Sending Status to peer"; "peer_id" => format!("{}", peer_id)); debug!(self.log, "Sending Status to peer"; "peer_id" => peer_id.to_string());
// add the status timer back // add the status timer back
peers_to_add.push(peer_id.clone()); peers_to_add.push(peer_id.clone());
self.events.push(PeerManagerEvent::Status(peer_id)); self.events.push(PeerManagerEvent::Status(peer_id));

View File

@ -7,11 +7,18 @@ use std::collections::HashMap;
use std::time::Instant; use std::time::Instant;
use types::{EthSpec, SubnetId}; use types::{EthSpec, SubnetId};
/// A peer's reputation. /// A peer's reputation (perceived potential usefulness)
pub type Rep = i32; pub type Rep = u8;
/// Reputation change (positive or negative)
pub struct RepChange {
is_good: bool,
diff: Rep,
}
/// Max number of disconnected nodes to remember /// Max number of disconnected nodes to remember
const MAX_DC_PEERS: usize = 30; const MAX_DC_PEERS: usize = 30;
/// The default starting reputation for an unknown peer. /// The default starting reputation for an unknown peer.
pub const DEFAULT_REPUTATION: Rep = 50; pub const DEFAULT_REPUTATION: Rep = 50;
@ -25,6 +32,27 @@ pub struct PeerDB<TSpec: EthSpec> {
log: slog::Logger, log: slog::Logger,
} }
impl RepChange {
pub fn good(diff: Rep) -> Self {
RepChange {
is_good: true,
diff,
}
}
pub fn bad(diff: Rep) -> Self {
RepChange {
is_good: false,
diff,
}
}
pub const fn worst() -> Self {
RepChange {
is_good: false,
diff: Rep::max_value(),
}
}
}
impl<TSpec: EthSpec> PeerDB<TSpec> { impl<TSpec: EthSpec> PeerDB<TSpec> {
pub fn new(log: &slog::Logger) -> Self { pub fn new(log: &slog::Logger) -> Self {
Self { Self {
@ -48,6 +76,11 @@ impl<TSpec: EthSpec> PeerDB<TSpec> {
self.peers.iter() self.peers.iter()
} }
/// Returns an iterator over all peers in the db.
pub(super) fn peers_mut(&mut self) -> impl Iterator<Item = (&PeerId, &mut PeerInfo<TSpec>)> {
self.peers.iter_mut()
}
/// Gives the ids of all known peers. /// Gives the ids of all known peers.
pub fn peer_ids(&self) -> impl Iterator<Item = &PeerId> { pub fn peer_ids(&self) -> impl Iterator<Item = &PeerId> {
self.peers.keys() self.peers.keys()
@ -59,6 +92,7 @@ impl<TSpec: EthSpec> PeerDB<TSpec> {
} }
/// Returns a mutable reference to a peer's info if known. /// Returns a mutable reference to a peer's info if known.
/// TODO: make pub(super) to ensure that peer management is unified
pub fn peer_info_mut(&mut self, peer_id: &PeerId) -> Option<&mut PeerInfo<TSpec>> { pub fn peer_info_mut(&mut self, peer_id: &PeerId) -> Option<&mut PeerInfo<TSpec>> {
self.peers.get_mut(peer_id) self.peers.get_mut(peer_id)
} }
@ -111,12 +145,11 @@ impl<TSpec: EthSpec> PeerDB<TSpec> {
} }
/// Gives an iterator of all peers on a given subnet. /// Gives an iterator of all peers on a given subnet.
pub fn peers_on_subnet(&self, subnet_id: &SubnetId) -> impl Iterator<Item = &PeerId> { pub fn peers_on_subnet(&self, subnet_id: SubnetId) -> impl Iterator<Item = &PeerId> {
let subnet_id_filter = subnet_id.clone();
self.peers self.peers
.iter() .iter()
.filter(move |(_, info)| { .filter(move |(_, info)| {
info.connection_status.is_connected() && info.on_subnet(subnet_id_filter) info.connection_status.is_connected() && info.on_subnet(subnet_id)
}) })
.map(|(peer_id, _)| peer_id) .map(|(peer_id, _)| peer_id)
} }
@ -192,10 +225,7 @@ impl<TSpec: EthSpec> PeerDB<TSpec> {
/// A peer is being dialed. /// A peer is being dialed.
pub fn dialing_peer(&mut self, peer_id: &PeerId) { pub fn dialing_peer(&mut self, peer_id: &PeerId) {
let info = self let info = self.peers.entry(peer_id.clone()).or_default();
.peers
.entry(peer_id.clone())
.or_insert_with(|| Default::default());
if info.connection_status.is_disconnected() { if info.connection_status.is_disconnected() {
self.n_dc -= 1; self.n_dc -= 1;
@ -207,10 +237,7 @@ impl<TSpec: EthSpec> PeerDB<TSpec> {
/// Sets a peer as connected with an ingoing connection. /// Sets a peer as connected with an ingoing connection.
pub fn connect_ingoing(&mut self, peer_id: &PeerId) { pub fn connect_ingoing(&mut self, peer_id: &PeerId) {
let info = self let info = self.peers.entry(peer_id.clone()).or_default();
.peers
.entry(peer_id.clone())
.or_insert_with(|| Default::default());
if info.connection_status.is_disconnected() { if info.connection_status.is_disconnected() {
self.n_dc -= 1; self.n_dc -= 1;
@ -220,10 +247,7 @@ impl<TSpec: EthSpec> PeerDB<TSpec> {
/// Sets a peer as connected with an outgoing connection. /// Sets a peer as connected with an outgoing connection.
pub fn connect_outgoing(&mut self, peer_id: &PeerId) { pub fn connect_outgoing(&mut self, peer_id: &PeerId) {
let info = self let info = self.peers.entry(peer_id.clone()).or_default();
.peers
.entry(peer_id.clone())
.or_insert_with(|| Default::default());
if info.connection_status.is_disconnected() { if info.connection_status.is_disconnected() {
self.n_dc -= 1; self.n_dc -= 1;
@ -231,16 +255,16 @@ impl<TSpec: EthSpec> PeerDB<TSpec> {
info.connection_status.connect_outgoing(); info.connection_status.connect_outgoing();
} }
/// Sets the peer as disconnected. /// Sets the peer as disconnected. A banned peer remains banned
pub fn disconnect(&mut self, peer_id: &PeerId) { pub fn disconnect(&mut self, peer_id: &PeerId) {
let log_ref = &self.log; let log_ref = &self.log;
let info = self.peers.entry(peer_id.clone()).or_insert_with(|| { let info = self.peers.entry(peer_id.clone()).or_insert_with(|| {
warn!(log_ref, "Disconnecting unknown peer"; warn!(log_ref, "Disconnecting unknown peer";
"peer_id" => format!("{:?}",peer_id)); "peer_id" => peer_id.to_string());
PeerInfo::default() PeerInfo::default()
}); });
if !info.connection_status.is_disconnected() { if !info.connection_status.is_disconnected() && !info.connection_status.is_banned() {
info.connection_status.disconnect(); info.connection_status.disconnect();
self.n_dc += 1; self.n_dc += 1;
} }
@ -269,7 +293,7 @@ impl<TSpec: EthSpec> PeerDB<TSpec> {
let log_ref = &self.log; let log_ref = &self.log;
let info = self.peers.entry(peer_id.clone()).or_insert_with(|| { let info = self.peers.entry(peer_id.clone()).or_insert_with(|| {
warn!(log_ref, "Banning unknown peer"; warn!(log_ref, "Banning unknown peer";
"peer_id" => format!("{:?}",peer_id)); "peer_id" => peer_id.to_string());
PeerInfo::default() PeerInfo::default()
}); });
if info.connection_status.is_disconnected() { if info.connection_status.is_disconnected() {
@ -283,16 +307,17 @@ impl<TSpec: EthSpec> PeerDB<TSpec> {
if let Some(peer_info) = self.peers.get_mut(peer_id) { if let Some(peer_info) = self.peers.get_mut(peer_id) {
peer_info.meta_data = Some(meta_data); peer_info.meta_data = Some(meta_data);
} else { } else {
warn!(self.log, "Tried to add meta data for a non-existant peer"; "peer_id" => format!("{}", peer_id)); warn!(self.log, "Tried to add meta data for a non-existant peer"; "peer_id" => peer_id.to_string());
} }
} }
/// Sets the reputation of peer. /// Sets the reputation of peer.
pub fn set_reputation(&mut self, peer_id: &PeerId, rep: Rep) { #[allow(dead_code)]
pub(super) fn set_reputation(&mut self, peer_id: &PeerId, rep: Rep) {
if let Some(peer_info) = self.peers.get_mut(peer_id) { if let Some(peer_info) = self.peers.get_mut(peer_id) {
peer_info.reputation = rep; peer_info.reputation = rep;
} else { } else {
crit!(self.log, "Tried to modify reputation for an unknown peer"; "peer_id" => format!("{}",peer_id)); crit!(self.log, "Tried to modify reputation for an unknown peer"; "peer_id" => peer_id.to_string());
} }
} }
@ -301,20 +326,25 @@ impl<TSpec: EthSpec> PeerDB<TSpec> {
if let Some(peer_info) = self.peers.get_mut(peer_id) { if let Some(peer_info) = self.peers.get_mut(peer_id) {
peer_info.sync_status = sync_status; peer_info.sync_status = sync_status;
} else { } else {
crit!(self.log, "Tried to the sync status for an unknown peer"; "peer_id" => format!("{}",peer_id)); crit!(self.log, "Tried to the sync status for an unknown peer"; "peer_id" => peer_id.to_string());
} }
} }
/// Adds to a peer's reputation by `change`. If the reputation exceeds Rep's /// Adds to a peer's reputation by `change`. If the reputation exceeds Rep's
/// upper (lower) bounds, it stays at the maximum (minimum) value. /// upper (lower) bounds, it stays at the maximum (minimum) value.
pub fn add_reputation(&mut self, peer_id: &PeerId, change: Rep) { pub(super) fn add_reputation(&mut self, peer_id: &PeerId, change: RepChange) {
let log_ref = &self.log; let log_ref = &self.log;
let info = self.peers.entry(peer_id.clone()).or_insert_with(|| { let info = self.peers.entry(peer_id.clone()).or_insert_with(|| {
warn!(log_ref, "Adding to the reputation of an unknown peer"; warn!(log_ref, "Adding to the reputation of an unknown peer";
"peer_id" => format!("{:?}",peer_id)); "peer_id" => peer_id.to_string());
PeerInfo::default() PeerInfo::default()
}); });
info.reputation = info.reputation.saturating_add(change);
info.reputation = if change.is_good {
info.reputation.saturating_add(change.diff)
} else {
info.reputation.saturating_sub(change.diff)
};
} }
} }
@ -396,14 +426,20 @@ mod tests {
// 0 change does not change de reputation // 0 change does not change de reputation
let random_peer = PeerId::random(); let random_peer = PeerId::random();
let change: Rep = 0; let change = RepChange::good(0);
pdb.connect_ingoing(&random_peer); pdb.connect_ingoing(&random_peer);
pdb.add_reputation(&random_peer, change); pdb.add_reputation(&random_peer, change);
assert_eq!(pdb.reputation(&random_peer), DEFAULT_REPUTATION); assert_eq!(pdb.reputation(&random_peer), DEFAULT_REPUTATION);
// overflowing change is capped // overflowing change is capped
let random_peer = PeerId::random(); let random_peer = PeerId::random();
let change = Rep::max_value(); let change = RepChange::worst();
pdb.connect_ingoing(&random_peer);
pdb.add_reputation(&random_peer, change);
assert_eq!(pdb.reputation(&random_peer), Rep::min_value());
let random_peer = PeerId::random();
let change = RepChange::good(Rep::max_value());
pdb.connect_ingoing(&random_peer); pdb.connect_ingoing(&random_peer);
pdb.add_reputation(&random_peer, change); pdb.add_reputation(&random_peer, change);
assert_eq!(pdb.reputation(&random_peer), Rep::max_value()); assert_eq!(pdb.reputation(&random_peer), Rep::max_value());

View File

@ -1,6 +1,6 @@
//! This handles the various supported encoding mechanism for the Eth 2.0 RPC. //! This handles the various supported encoding mechanism for the Eth 2.0 RPC.
use crate::rpc::{ErrorMessage, RPCErrorResponse, RPCRequest, RPCResponse}; use crate::rpc::{ErrorMessage, RPCCodedResponse, RPCRequest, RPCResponse};
use libp2p::bytes::BufMut; use libp2p::bytes::BufMut;
use libp2p::bytes::BytesMut; use libp2p::bytes::BytesMut;
use std::marker::PhantomData; use std::marker::PhantomData;
@ -78,9 +78,9 @@ where
impl<TCodec, TSpec> Encoder for BaseInboundCodec<TCodec, TSpec> impl<TCodec, TSpec> Encoder for BaseInboundCodec<TCodec, TSpec>
where where
TSpec: EthSpec, TSpec: EthSpec,
TCodec: Decoder + Encoder<Item = RPCErrorResponse<TSpec>>, TCodec: Decoder + Encoder<Item = RPCCodedResponse<TSpec>>,
{ {
type Item = RPCErrorResponse<TSpec>; type Item = RPCCodedResponse<TSpec>;
type Error = <TCodec as Encoder>::Error; type Error = <TCodec as Encoder>::Error;
fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> { fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> {
@ -130,7 +130,7 @@ where
TSpec: EthSpec, TSpec: EthSpec,
TCodec: OutboundCodec<ErrorType = ErrorMessage> + Decoder<Item = RPCResponse<TSpec>>, TCodec: OutboundCodec<ErrorType = ErrorMessage> + Decoder<Item = RPCResponse<TSpec>>,
{ {
type Item = RPCErrorResponse<TSpec>; type Item = RPCCodedResponse<TSpec>;
type Error = <TCodec as Decoder>::Error; type Error = <TCodec as Decoder>::Error;
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> { fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
@ -146,17 +146,17 @@ where
}); });
let inner_result = { let inner_result = {
if RPCErrorResponse::<TSpec>::is_response(response_code) { if RPCCodedResponse::<TSpec>::is_response(response_code) {
// decode an actual response and mutates the buffer if enough bytes have been read // decode an actual response and mutates the buffer if enough bytes have been read
// returning the result. // returning the result.
self.inner self.inner
.decode(src) .decode(src)
.map(|r| r.map(RPCErrorResponse::Success)) .map(|r| r.map(RPCCodedResponse::Success))
} else { } else {
// decode an error // decode an error
self.inner self.inner
.decode_error(src) .decode_error(src)
.map(|r| r.map(|resp| RPCErrorResponse::from_error(response_code, resp))) .map(|r| r.map(|resp| RPCCodedResponse::from_error(response_code, resp)))
} }
}; };
// if the inner decoder was capable of decoding a chunk, we need to reset the current // if the inner decoder was capable of decoding a chunk, we need to reset the current

View File

@ -6,7 +6,7 @@ use self::base::{BaseInboundCodec, BaseOutboundCodec};
use self::ssz::{SSZInboundCodec, SSZOutboundCodec}; use self::ssz::{SSZInboundCodec, SSZOutboundCodec};
use self::ssz_snappy::{SSZSnappyInboundCodec, SSZSnappyOutboundCodec}; use self::ssz_snappy::{SSZSnappyInboundCodec, SSZSnappyOutboundCodec};
use crate::rpc::protocol::RPCError; use crate::rpc::protocol::RPCError;
use crate::rpc::{RPCErrorResponse, RPCRequest}; use crate::rpc::{RPCCodedResponse, RPCRequest};
use libp2p::bytes::BytesMut; use libp2p::bytes::BytesMut;
use tokio::codec::{Decoder, Encoder}; use tokio::codec::{Decoder, Encoder};
use types::EthSpec; use types::EthSpec;
@ -23,7 +23,7 @@ pub enum OutboundCodec<TSpec: EthSpec> {
} }
impl<T: EthSpec> Encoder for InboundCodec<T> { impl<T: EthSpec> Encoder for InboundCodec<T> {
type Item = RPCErrorResponse<T>; type Item = RPCCodedResponse<T>;
type Error = RPCError; type Error = RPCError;
fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> { fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> {
@ -59,7 +59,7 @@ impl<TSpec: EthSpec> Encoder for OutboundCodec<TSpec> {
} }
impl<T: EthSpec> Decoder for OutboundCodec<T> { impl<T: EthSpec> Decoder for OutboundCodec<T> {
type Item = RPCErrorResponse<T>; type Item = RPCCodedResponse<T>;
type Error = RPCError; type Error = RPCError;
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> { fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {

View File

@ -3,7 +3,7 @@ use crate::rpc::{
codec::base::OutboundCodec, codec::base::OutboundCodec,
protocol::{Encoding, Protocol, ProtocolId, RPCError, Version}, protocol::{Encoding, Protocol, ProtocolId, RPCError, Version},
}; };
use crate::rpc::{ErrorMessage, RPCErrorResponse, RPCRequest, RPCResponse}; use crate::rpc::{ErrorMessage, RPCCodedResponse, RPCRequest, RPCResponse};
use libp2p::bytes::{BufMut, Bytes, BytesMut}; use libp2p::bytes::{BufMut, Bytes, BytesMut};
use ssz::{Decode, Encode}; use ssz::{Decode, Encode};
use std::marker::PhantomData; use std::marker::PhantomData;
@ -37,22 +37,22 @@ impl<T: EthSpec> SSZInboundCodec<T> {
// Encoder for inbound streams: Encodes RPC Responses sent to peers. // Encoder for inbound streams: Encodes RPC Responses sent to peers.
impl<TSpec: EthSpec> Encoder for SSZInboundCodec<TSpec> { impl<TSpec: EthSpec> Encoder for SSZInboundCodec<TSpec> {
type Item = RPCErrorResponse<TSpec>; type Item = RPCCodedResponse<TSpec>;
type Error = RPCError; type Error = RPCError;
fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> { fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> {
let bytes = match item { let bytes = match item {
RPCErrorResponse::Success(resp) => match resp { RPCCodedResponse::Success(resp) => match resp {
RPCResponse::Status(res) => res.as_ssz_bytes(), RPCResponse::Status(res) => res.as_ssz_bytes(),
RPCResponse::BlocksByRange(res) => res.as_ssz_bytes(), RPCResponse::BlocksByRange(res) => res.as_ssz_bytes(),
RPCResponse::BlocksByRoot(res) => res.as_ssz_bytes(), RPCResponse::BlocksByRoot(res) => res.as_ssz_bytes(),
RPCResponse::Pong(res) => res.data.as_ssz_bytes(), RPCResponse::Pong(res) => res.data.as_ssz_bytes(),
RPCResponse::MetaData(res) => res.as_ssz_bytes(), RPCResponse::MetaData(res) => res.as_ssz_bytes(),
}, },
RPCErrorResponse::InvalidRequest(err) => err.as_ssz_bytes(), RPCCodedResponse::InvalidRequest(err) => err.as_ssz_bytes(),
RPCErrorResponse::ServerError(err) => err.as_ssz_bytes(), RPCCodedResponse::ServerError(err) => err.as_ssz_bytes(),
RPCErrorResponse::Unknown(err) => err.as_ssz_bytes(), RPCCodedResponse::Unknown(err) => err.as_ssz_bytes(),
RPCErrorResponse::StreamTermination(_) => { RPCCodedResponse::StreamTermination(_) => {
unreachable!("Code error - attempting to encode a stream termination") unreachable!("Code error - attempting to encode a stream termination")
} }
}; };
@ -107,9 +107,7 @@ impl<TSpec: EthSpec> Decoder for SSZInboundCodec<TSpec> {
Protocol::MetaData => match self.protocol.version { Protocol::MetaData => match self.protocol.version {
Version::V1 => { Version::V1 => {
if packet.len() > 0 { if packet.len() > 0 {
Err(RPCError::Custom( Err(RPCError::InvalidData)
"Get metadata request should be empty".into(),
))
} else { } else {
Ok(Some(RPCRequest::MetaData(PhantomData))) Ok(Some(RPCRequest::MetaData(PhantomData)))
} }
@ -183,32 +181,20 @@ impl<TSpec: EthSpec> Decoder for SSZOutboundCodec<TSpec> {
src.clear(); src.clear();
match self.protocol.message_name { match self.protocol.message_name {
Protocol::Status => match self.protocol.version { Protocol::Status => match self.protocol.version {
Version::V1 => Err(RPCError::Custom( Version::V1 => Err(RPCError::IncompleteStream), // cannot have an empty HELLO message. The stream has terminated unexpectedly
"Status stream terminated unexpectedly".into(),
)), // cannot have an empty HELLO message. The stream has terminated unexpectedly
}, },
Protocol::Goodbye => { Protocol::Goodbye => Err(RPCError::InvalidData),
Err(RPCError::InvalidProtocol("GOODBYE doesn't have a response"))
}
Protocol::BlocksByRange => match self.protocol.version { Protocol::BlocksByRange => match self.protocol.version {
Version::V1 => Err(RPCError::Custom( Version::V1 => Err(RPCError::IncompleteStream), // cannot have an empty block message.
"Status stream terminated unexpectedly, empty block".into(),
)), // cannot have an empty block message.
}, },
Protocol::BlocksByRoot => match self.protocol.version { Protocol::BlocksByRoot => match self.protocol.version {
Version::V1 => Err(RPCError::Custom( Version::V1 => Err(RPCError::IncompleteStream), // cannot have an empty block message.
"Status stream terminated unexpectedly, empty block".into(),
)), // cannot have an empty block message.
}, },
Protocol::Ping => match self.protocol.version { Protocol::Ping => match self.protocol.version {
Version::V1 => Err(RPCError::Custom( Version::V1 => Err(RPCError::IncompleteStream), // cannot have an empty block message.
"PING stream terminated unexpectedly".into(),
)), // cannot have an empty block message.
}, },
Protocol::MetaData => match self.protocol.version { Protocol::MetaData => match self.protocol.version {
Version::V1 => Err(RPCError::Custom( Version::V1 => Err(RPCError::IncompleteStream), // cannot have an empty block message.
"Metadata stream terminated unexpectedly".into(),
)), // cannot have an empty block message.
}, },
} }
} else { } else {
@ -223,9 +209,7 @@ impl<TSpec: EthSpec> Decoder for SSZOutboundCodec<TSpec> {
StatusMessage::from_ssz_bytes(&raw_bytes)?, StatusMessage::from_ssz_bytes(&raw_bytes)?,
))), ))),
}, },
Protocol::Goodbye => { Protocol::Goodbye => Err(RPCError::InvalidData),
Err(RPCError::InvalidProtocol("GOODBYE doesn't have a response"))
}
Protocol::BlocksByRange => match self.protocol.version { Protocol::BlocksByRange => match self.protocol.version {
Version::V1 => Ok(Some(RPCResponse::BlocksByRange(Box::new( Version::V1 => Ok(Some(RPCResponse::BlocksByRange(Box::new(
SignedBeaconBlock::from_ssz_bytes(&raw_bytes)?, SignedBeaconBlock::from_ssz_bytes(&raw_bytes)?,

View File

@ -3,7 +3,7 @@ use crate::rpc::{
codec::base::OutboundCodec, codec::base::OutboundCodec,
protocol::{Encoding, Protocol, ProtocolId, RPCError, Version}, protocol::{Encoding, Protocol, ProtocolId, RPCError, Version},
}; };
use crate::rpc::{ErrorMessage, RPCErrorResponse, RPCRequest, RPCResponse}; use crate::rpc::{ErrorMessage, RPCCodedResponse, RPCRequest, RPCResponse};
use libp2p::bytes::BytesMut; use libp2p::bytes::BytesMut;
use snap::read::FrameDecoder; use snap::read::FrameDecoder;
use snap::write::FrameEncoder; use snap::write::FrameEncoder;
@ -45,28 +45,28 @@ impl<T: EthSpec> SSZSnappyInboundCodec<T> {
// Encoder for inbound streams: Encodes RPC Responses sent to peers. // Encoder for inbound streams: Encodes RPC Responses sent to peers.
impl<TSpec: EthSpec> Encoder for SSZSnappyInboundCodec<TSpec> { impl<TSpec: EthSpec> Encoder for SSZSnappyInboundCodec<TSpec> {
type Item = RPCErrorResponse<TSpec>; type Item = RPCCodedResponse<TSpec>;
type Error = RPCError; type Error = RPCError;
fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> { fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> {
let bytes = match item { let bytes = match item {
RPCErrorResponse::Success(resp) => match resp { RPCCodedResponse::Success(resp) => match resp {
RPCResponse::Status(res) => res.as_ssz_bytes(), RPCResponse::Status(res) => res.as_ssz_bytes(),
RPCResponse::BlocksByRange(res) => res.as_ssz_bytes(), RPCResponse::BlocksByRange(res) => res.as_ssz_bytes(),
RPCResponse::BlocksByRoot(res) => res.as_ssz_bytes(), RPCResponse::BlocksByRoot(res) => res.as_ssz_bytes(),
RPCResponse::Pong(res) => res.data.as_ssz_bytes(), RPCResponse::Pong(res) => res.data.as_ssz_bytes(),
RPCResponse::MetaData(res) => res.as_ssz_bytes(), RPCResponse::MetaData(res) => res.as_ssz_bytes(),
}, },
RPCErrorResponse::InvalidRequest(err) => err.as_ssz_bytes(), RPCCodedResponse::InvalidRequest(err) => err.as_ssz_bytes(),
RPCErrorResponse::ServerError(err) => err.as_ssz_bytes(), RPCCodedResponse::ServerError(err) => err.as_ssz_bytes(),
RPCErrorResponse::Unknown(err) => err.as_ssz_bytes(), RPCCodedResponse::Unknown(err) => err.as_ssz_bytes(),
RPCErrorResponse::StreamTermination(_) => { RPCCodedResponse::StreamTermination(_) => {
unreachable!("Code error - attempting to encode a stream termination") unreachable!("Code error - attempting to encode a stream termination")
} }
}; };
// SSZ encoded bytes should be within `max_packet_size` // SSZ encoded bytes should be within `max_packet_size`
if bytes.len() > self.max_packet_size { if bytes.len() > self.max_packet_size {
return Err(RPCError::Custom( return Err(RPCError::InternalError(
"attempting to encode data > max_packet_size".into(), "attempting to encode data > max_packet_size".into(),
)); ));
} }
@ -106,9 +106,7 @@ impl<TSpec: EthSpec> Decoder for SSZSnappyInboundCodec<TSpec> {
// Should not attempt to decode rpc chunks with length > max_packet_size // Should not attempt to decode rpc chunks with length > max_packet_size
if length > self.max_packet_size { if length > self.max_packet_size {
return Err(RPCError::Custom( return Err(RPCError::InvalidData);
"attempting to decode data > max_packet_size".into(),
));
} }
let mut reader = FrameDecoder::new(Cursor::new(&src)); let mut reader = FrameDecoder::new(Cursor::new(&src));
let mut decoded_buffer = vec![0; length]; let mut decoded_buffer = vec![0; length];
@ -148,9 +146,7 @@ impl<TSpec: EthSpec> Decoder for SSZSnappyInboundCodec<TSpec> {
Protocol::MetaData => match self.protocol.version { Protocol::MetaData => match self.protocol.version {
Version::V1 => { Version::V1 => {
if decoded_buffer.len() > 0 { if decoded_buffer.len() > 0 {
Err(RPCError::Custom( Err(RPCError::InvalidData)
"Get metadata request should be empty".into(),
))
} else { } else {
Ok(Some(RPCRequest::MetaData(PhantomData))) Ok(Some(RPCRequest::MetaData(PhantomData)))
} }
@ -212,8 +208,8 @@ impl<TSpec: EthSpec> Encoder for SSZSnappyOutboundCodec<TSpec> {
}; };
// SSZ encoded bytes should be within `max_packet_size` // SSZ encoded bytes should be within `max_packet_size`
if bytes.len() > self.max_packet_size { if bytes.len() > self.max_packet_size {
return Err(RPCError::Custom( return Err(RPCError::InternalError(
"attempting to encode data > max_packet_size".into(), "attempting to encode data > max_packet_size",
)); ));
} }
@ -257,9 +253,7 @@ impl<TSpec: EthSpec> Decoder for SSZSnappyOutboundCodec<TSpec> {
// Should not attempt to decode rpc chunks with length > max_packet_size // Should not attempt to decode rpc chunks with length > max_packet_size
if length > self.max_packet_size { if length > self.max_packet_size {
return Err(RPCError::Custom( return Err(RPCError::InvalidData);
"attempting to decode data > max_packet_size".into(),
));
} }
let mut reader = FrameDecoder::new(Cursor::new(&src)); let mut reader = FrameDecoder::new(Cursor::new(&src));
let mut decoded_buffer = vec![0; length]; let mut decoded_buffer = vec![0; length];
@ -276,7 +270,8 @@ impl<TSpec: EthSpec> Decoder for SSZSnappyOutboundCodec<TSpec> {
))), ))),
}, },
Protocol::Goodbye => { Protocol::Goodbye => {
Err(RPCError::InvalidProtocol("GOODBYE doesn't have a response")) // Goodbye does not have a response
Err(RPCError::InvalidData)
} }
Protocol::BlocksByRange => match self.protocol.version { Protocol::BlocksByRange => match self.protocol.version {
Version::V1 => Ok(Some(RPCResponse::BlocksByRange(Box::new( Version::V1 => Ok(Some(RPCResponse::BlocksByRange(Box::new(
@ -330,9 +325,7 @@ impl<TSpec: EthSpec> OutboundCodec for SSZSnappyOutboundCodec<TSpec> {
// Should not attempt to decode rpc chunks with length > max_packet_size // Should not attempt to decode rpc chunks with length > max_packet_size
if length > self.max_packet_size { if length > self.max_packet_size {
return Err(RPCError::Custom( return Err(RPCError::InvalidData);
"attempting to decode data > max_packet_size".into(),
));
} }
let mut reader = FrameDecoder::new(Cursor::new(&src)); let mut reader = FrameDecoder::new(Cursor::new(&src));
let mut decoded_buffer = vec![0; length]; let mut decoded_buffer = vec![0; length];

View File

@ -1,14 +1,16 @@
#![allow(clippy::type_complexity)] #![allow(clippy::type_complexity)]
#![allow(clippy::cognitive_complexity)] #![allow(clippy::cognitive_complexity)]
use super::methods::{ErrorMessage, RPCErrorResponse, RequestId, ResponseTermination}; use super::methods::{ErrorMessage, RPCCodedResponse, RequestId, ResponseTermination};
use super::protocol::{RPCError, RPCProtocol, RPCRequest}; use super::protocol::{Protocol, RPCError, RPCProtocol, RPCRequest};
use super::RPCEvent; use super::RPCEvent;
use crate::rpc::protocol::{InboundFramed, OutboundFramed}; use crate::rpc::protocol::{InboundFramed, OutboundFramed};
use core::marker::PhantomData; use core::marker::PhantomData;
use fnv::FnvHashMap; use fnv::FnvHashMap;
use futures::prelude::*; use futures::prelude::*;
use libp2p::core::upgrade::{InboundUpgrade, OutboundUpgrade, UpgradeError}; use libp2p::core::upgrade::{
InboundUpgrade, NegotiationError, OutboundUpgrade, ProtocolError, UpgradeError,
};
use libp2p::swarm::protocols_handler::{ use libp2p::swarm::protocols_handler::{
KeepAlive, ProtocolsHandler, ProtocolsHandlerEvent, ProtocolsHandlerUpgrErr, SubstreamProtocol, KeepAlive, ProtocolsHandler, ProtocolsHandlerEvent, ProtocolsHandlerUpgrErr, SubstreamProtocol,
}; };
@ -46,13 +48,13 @@ where
listen_protocol: SubstreamProtocol<RPCProtocol<TSpec>>, listen_protocol: SubstreamProtocol<RPCProtocol<TSpec>>,
/// If something bad happened and we should shut down the handler with an error. /// If something bad happened and we should shut down the handler with an error.
pending_error: Vec<(RequestId, ProtocolsHandlerUpgrErr<RPCError>)>, pending_error: Vec<(RequestId, Protocol, RPCError)>,
/// Queue of events to produce in `poll()`. /// Queue of events to produce in `poll()`.
events_out: SmallVec<[RPCEvent<TSpec>; 4]>, events_out: SmallVec<[RPCEvent<TSpec>; 4]>,
/// Queue of outbound substreams to open. /// Queue of outbound substreams to open.
dial_queue: SmallVec<[RPCEvent<TSpec>; 4]>, dial_queue: SmallVec<[(RequestId, RPCRequest<TSpec>); 4]>,
/// Current number of concurrent outbound substreams being opened. /// Current number of concurrent outbound substreams being opened.
dial_negotiated: u32, dial_negotiated: u32,
@ -63,6 +65,7 @@ where
( (
InboundSubstreamState<TSubstream, TSpec>, InboundSubstreamState<TSubstream, TSpec>,
Option<delay_queue::Key>, Option<delay_queue::Key>,
Protocol,
), ),
>, >,
@ -73,14 +76,18 @@ where
/// maintained by the application sending the request. /// maintained by the application sending the request.
outbound_substreams: FnvHashMap< outbound_substreams: FnvHashMap<
OutboundRequestId, OutboundRequestId,
(OutboundSubstreamState<TSubstream, TSpec>, delay_queue::Key), (
OutboundSubstreamState<TSubstream, TSpec>,
delay_queue::Key,
Protocol,
),
>, >,
/// Inbound substream `DelayQueue` which keeps track of when an inbound substream will timeout. /// Inbound substream `DelayQueue` which keeps track of when an inbound substream will timeout.
outbound_substreams_delay: DelayQueue<OutboundRequestId>, outbound_substreams_delay: DelayQueue<OutboundRequestId>,
/// Map of outbound items that are queued as the stream processes them. /// Map of outbound items that are queued as the stream processes them.
queued_outbound_items: FnvHashMap<RequestId, Vec<RPCErrorResponse<TSpec>>>, queued_outbound_items: FnvHashMap<RequestId, Vec<RPCCodedResponse<TSpec>>>,
/// Sequential ID for waiting substreams. For inbound substreams, this is also the inbound request ID. /// Sequential ID for waiting substreams. For inbound substreams, this is also the inbound request ID.
current_inbound_substream_id: RequestId, current_inbound_substream_id: RequestId,
@ -152,17 +159,17 @@ where
/// Moves the substream state to closing and informs the connected peer. The /// Moves the substream state to closing and informs the connected peer. The
/// `queued_outbound_items` must be given as a parameter to add stream termination messages to /// `queued_outbound_items` must be given as a parameter to add stream termination messages to
/// the outbound queue. /// the outbound queue.
pub fn close(&mut self, outbound_queue: &mut Vec<RPCErrorResponse<TSpec>>) { pub fn close(&mut self, outbound_queue: &mut Vec<RPCCodedResponse<TSpec>>) {
// When terminating a stream, report the stream termination to the requesting user via // When terminating a stream, report the stream termination to the requesting user via
// an RPC error // an RPC error
let error = RPCErrorResponse::ServerError(ErrorMessage { let error = RPCCodedResponse::ServerError(ErrorMessage {
error_message: "Request timed out".as_bytes().to_vec(), error_message: b"Request timed out".to_vec(),
}); });
// The stream termination type is irrelevant, this will terminate the // The stream termination type is irrelevant, this will terminate the
// stream // stream
let stream_termination = let stream_termination =
RPCErrorResponse::StreamTermination(ResponseTermination::BlocksByRange); RPCCodedResponse::StreamTermination(ResponseTermination::BlocksByRange);
match std::mem::replace(self, InboundSubstreamState::Poisoned) { match std::mem::replace(self, InboundSubstreamState::Poisoned) {
InboundSubstreamState::ResponsePendingSend { substream, closing } => { InboundSubstreamState::ResponsePendingSend { substream, closing } => {
@ -244,10 +251,10 @@ where
} }
/// Opens an outbound substream with a request. /// Opens an outbound substream with a request.
pub fn send_request(&mut self, rpc_event: RPCEvent<TSpec>) { pub fn send_request(&mut self, id: RequestId, req: RPCRequest<TSpec>) {
self.keep_alive = KeepAlive::Yes; self.keep_alive = KeepAlive::Yes;
self.dial_queue.push(rpc_event); self.dial_queue.push((id, req));
} }
} }
@ -262,7 +269,7 @@ where
type Substream = TSubstream; type Substream = TSubstream;
type InboundProtocol = RPCProtocol<TSpec>; type InboundProtocol = RPCProtocol<TSpec>;
type OutboundProtocol = RPCRequest<TSpec>; type OutboundProtocol = RPCRequest<TSpec>;
type OutboundOpenInfo = RPCEvent<TSpec>; // Keep track of the id and the request type OutboundOpenInfo = (RequestId, RPCRequest<TSpec>); // Keep track of the id and the request
fn listen_protocol(&self) -> SubstreamProtocol<Self::InboundProtocol> { fn listen_protocol(&self) -> SubstreamProtocol<Self::InboundProtocol> {
self.listen_protocol.clone() self.listen_protocol.clone()
@ -292,7 +299,7 @@ where
let awaiting_stream = InboundSubstreamState::ResponseIdle(substream); let awaiting_stream = InboundSubstreamState::ResponseIdle(substream);
self.inbound_substreams.insert( self.inbound_substreams.insert(
self.current_inbound_substream_id, self.current_inbound_substream_id,
(awaiting_stream, Some(delay_key)), (awaiting_stream, Some(delay_key), req.protocol()),
); );
self.events_out self.events_out
@ -303,7 +310,7 @@ where
fn inject_fully_negotiated_outbound( fn inject_fully_negotiated_outbound(
&mut self, &mut self,
out: <RPCRequest<TSpec> as OutboundUpgrade<TSubstream>>::Output, out: <RPCRequest<TSpec> as OutboundUpgrade<TSubstream>>::Output,
rpc_event: Self::OutboundOpenInfo, request_info: Self::OutboundOpenInfo,
) { ) {
self.dial_negotiated -= 1; self.dial_negotiated -= 1;
@ -317,70 +324,80 @@ where
} }
// add the stream to substreams if we expect a response, otherwise drop the stream. // add the stream to substreams if we expect a response, otherwise drop the stream.
match rpc_event { let (mut id, request) = request_info;
RPCEvent::Request(mut id, request) if request.expect_response() => { if request.expect_response() {
// outbound requests can be sent from various aspects of lighthouse which don't // outbound requests can be sent from various aspects of lighthouse which don't
// track request ids. In the future these will be flagged as None, currently they // track request ids. In the future these will be flagged as None, currently they
// are flagged as 0. These can overlap. In this case, we pick the highest request // are flagged as 0. These can overlap. In this case, we pick the highest request
// Id available // Id available
if id == 0 && self.outbound_substreams.get(&id).is_some() { if id == 0 && self.outbound_substreams.get(&id).is_some() {
// have duplicate outbound request with no id. Pick one that will not collide // have duplicate outbound request with no id. Pick one that will not collide
let mut new_id = std::usize::MAX; let mut new_id = std::usize::MAX;
while self.outbound_substreams.get(&new_id).is_some() { while self.outbound_substreams.get(&new_id).is_some() {
// panic all outbound substreams are full // panic all outbound substreams are full
new_id -= 1; new_id -= 1;
}
trace!(self.log, "New outbound stream id created"; "id" => new_id);
id = RequestId::from(new_id);
}
// new outbound request. Store the stream and tag the output.
let delay_key = self
.outbound_substreams_delay
.insert(id, Duration::from_secs(RESPONSE_TIMEOUT));
let awaiting_stream = OutboundSubstreamState::RequestPendingResponse {
substream: out,
request,
};
if let Some(_) = self
.outbound_substreams
.insert(id, (awaiting_stream, delay_key))
{
crit!(self.log, "Duplicate outbound substream id"; "id" => format!("{:?}", id));
} }
trace!(self.log, "New outbound stream id created"; "id" => new_id);
id = RequestId::from(new_id);
} }
_ => { // a response is not expected, drop the stream for all other requests
// new outbound request. Store the stream and tag the output.
let delay_key = self
.outbound_substreams_delay
.insert(id, Duration::from_secs(RESPONSE_TIMEOUT));
let protocol = request.protocol();
let awaiting_stream = OutboundSubstreamState::RequestPendingResponse {
substream: out,
request,
};
if let Some(_) = self
.outbound_substreams
.insert(id, (awaiting_stream, delay_key, protocol))
{
crit!(self.log, "Duplicate outbound substream id"; "id" => format!("{:?}", id));
} }
} }
} }
// Note: If the substream has closed due to inactivity, or the substream is in the // NOTE: If the substream has closed due to inactivity, or the substream is in the
// wrong state a response will fail silently. // wrong state a response will fail silently.
fn inject_event(&mut self, rpc_event: Self::InEvent) { fn inject_event(&mut self, rpc_event: Self::InEvent) {
match rpc_event { match rpc_event {
RPCEvent::Request(_, _) => self.send_request(rpc_event), RPCEvent::Request(id, req) => self.send_request(id, req),
RPCEvent::Response(rpc_id, response) => { RPCEvent::Response(rpc_id, response) => {
// check if the stream matching the response still exists // Variables indicating if the response is an error response or a multi-part
// variables indicating if the response is an error response or a multi-part
// response // response
let res_is_error = response.is_error(); let res_is_error = response.is_error();
let res_is_multiple = response.multiple_responses(); let res_is_multiple = response.multiple_responses();
// check if the stream matching the response still exists
match self.inbound_substreams.get_mut(&rpc_id) { match self.inbound_substreams.get_mut(&rpc_id) {
Some((substream_state, _)) => { Some((substream_state, _, protocol)) => {
match std::mem::replace(substream_state, InboundSubstreamState::Poisoned) { match std::mem::replace(substream_state, InboundSubstreamState::Poisoned) {
InboundSubstreamState::ResponseIdle(substream) => { InboundSubstreamState::ResponseIdle(substream) => {
// close the stream if there is no response // close the stream if there is no response
if let RPCErrorResponse::StreamTermination(_) = response { match response {
//trace!(self.log, "Stream termination sent. Ending the stream"); RPCCodedResponse::StreamTermination(_) => {
*substream_state = InboundSubstreamState::Closing(substream); //trace!(self.log, "Stream termination sent. Ending the stream");
} else { *substream_state =
// send the response InboundSubstreamState::Closing(substream);
// if it's a single rpc request or an error, close the stream after }
*substream_state = InboundSubstreamState::ResponsePendingSend { _ => {
substream: substream.send(response), if let Some(error_code) = response.error_code() {
closing: !res_is_multiple | res_is_error, // close if an error or we are not expecting more responses self.pending_error.push((
}; rpc_id,
*protocol,
RPCError::ErrorResponse(error_code),
));
}
// send the response
// if it's a single rpc request or an error, close the stream after
*substream_state =
InboundSubstreamState::ResponsePendingSend {
substream: substream.send(response),
closing: !res_is_multiple | res_is_error, // close if an error or we are not expecting more responses
};
}
} }
} }
InboundSubstreamState::ResponsePendingSend { substream, closing } InboundSubstreamState::ResponsePendingSend { substream, closing }
@ -416,39 +433,55 @@ where
} }
} }
None => { None => {
warn!(self.log, "Stream has expired. Response not sent"; "response" => format!("{}",response)); warn!(self.log, "Stream has expired. Response not sent"; "response" => format!("{}", response));
} }
}; };
} }
// We do not send errors as responses // We do not send errors as responses
RPCEvent::Error(_, _) => {} RPCEvent::Error(..) => {}
} }
} }
fn inject_dial_upgrade_error( fn inject_dial_upgrade_error(
&mut self, &mut self,
request: Self::OutboundOpenInfo, request_info: Self::OutboundOpenInfo,
error: ProtocolsHandlerUpgrErr< error: ProtocolsHandlerUpgrErr<
<Self::OutboundProtocol as OutboundUpgrade<Self::Substream>>::Error, <Self::OutboundProtocol as OutboundUpgrade<Self::Substream>>::Error,
>, >,
) { ) {
let (id, req) = request_info;
if let ProtocolsHandlerUpgrErr::Upgrade(UpgradeError::Apply(RPCError::IoError(_))) = error { if let ProtocolsHandlerUpgrErr::Upgrade(UpgradeError::Apply(RPCError::IoError(_))) = error {
self.outbound_io_error_retries += 1; self.outbound_io_error_retries += 1;
if self.outbound_io_error_retries < IO_ERROR_RETRIES { if self.outbound_io_error_retries < IO_ERROR_RETRIES {
self.send_request(request); self.send_request(id, req);
return; return;
} }
} }
self.outbound_io_error_retries = 0; self.outbound_io_error_retries = 0;
// add the error // map the error
let request_id = { let rpc_error = match error {
if let RPCEvent::Request(id, _) = request { ProtocolsHandlerUpgrErr::Timer => RPCError::InternalError("Timer failed"),
id ProtocolsHandlerUpgrErr::Timeout => RPCError::NegotiationTimeout,
} else { ProtocolsHandlerUpgrErr::Upgrade(UpgradeError::Apply(e)) => e,
0 ProtocolsHandlerUpgrErr::Upgrade(UpgradeError::Select(NegotiationError::Failed)) => {
RPCError::UnsupportedProtocol
} }
ProtocolsHandlerUpgrErr::Upgrade(UpgradeError::Select(
NegotiationError::ProtocolError(e),
)) => match e {
ProtocolError::IoError(io_err) => RPCError::IoError(io_err),
ProtocolError::InvalidProtocol => {
RPCError::InternalError("Protocol was deemed invalid")
}
ProtocolError::InvalidMessage | ProtocolError::TooManyProtocols => {
// Peer is sending invalid data during the negotiation phase, not
// participating in the protocol
RPCError::InvalidData
}
},
}; };
self.pending_error.push((request_id, error)); self.pending_error.push((id, req.protocol(), rpc_error));
} }
fn connection_keep_alive(&self) -> KeepAlive { fn connection_keep_alive(&self) -> KeepAlive {
@ -461,46 +494,11 @@ where
ProtocolsHandlerEvent<Self::OutboundProtocol, Self::OutboundOpenInfo, Self::OutEvent>, ProtocolsHandlerEvent<Self::OutboundProtocol, Self::OutboundOpenInfo, Self::OutEvent>,
Self::Error, Self::Error,
> { > {
if let Some((request_id, err)) = self.pending_error.pop() { if !self.pending_error.is_empty() {
// Returning an error here will result in dropping the peer. let (id, protocol, err) = self.pending_error.remove(0);
match err { return Ok(Async::Ready(ProtocolsHandlerEvent::Custom(
ProtocolsHandlerUpgrErr::Upgrade(UpgradeError::Apply( RPCEvent::Error(id, protocol, err),
RPCError::InvalidProtocol(protocol_string), )));
)) => {
// Peer does not support the protocol.
// TODO: We currently will not drop the peer, for maximal compatibility with
// other clients testing their software. In the future, we will need to decide
// which protocols are a bare minimum to support before kicking the peer.
error!(self.log, "Peer doesn't support the RPC protocol"; "protocol" => protocol_string);
return Ok(Async::Ready(ProtocolsHandlerEvent::Custom(
RPCEvent::Error(request_id, RPCError::InvalidProtocol(protocol_string)),
)));
}
ProtocolsHandlerUpgrErr::Timeout | ProtocolsHandlerUpgrErr::Timer => {
// negotiation timeout, mark the request as failed
debug!(self.log, "Active substreams before timeout"; "len" => self.outbound_substreams.len());
return Ok(Async::Ready(ProtocolsHandlerEvent::Custom(
RPCEvent::Error(
request_id,
RPCError::Custom("Protocol negotiation timeout".into()),
),
)));
}
ProtocolsHandlerUpgrErr::Upgrade(UpgradeError::Apply(err)) => {
// IO/Decode/Custom Error, report to the application
debug!(self.log, "Upgrade Error"; "error" => format!("{}",err));
return Ok(Async::Ready(ProtocolsHandlerEvent::Custom(
RPCEvent::Error(request_id, err),
)));
}
ProtocolsHandlerUpgrErr::Upgrade(UpgradeError::Select(err)) => {
// Error during negotiation
debug!(self.log, "Upgrade Error"; "error" => format!("{}",err));
return Ok(Async::Ready(ProtocolsHandlerEvent::Custom(
RPCEvent::Error(request_id, RPCError::Custom(format!("{}", err))),
)));
}
}
} }
// return any events that need to be reported // return any events that need to be reported
@ -522,7 +520,7 @@ where
let rpc_id = stream_id.get_ref(); let rpc_id = stream_id.get_ref();
// handle a stream timeout for various states // handle a stream timeout for various states
if let Some((substream_state, delay_key)) = self.inbound_substreams.get_mut(rpc_id) { if let Some((substream_state, delay_key, _)) = self.inbound_substreams.get_mut(rpc_id) {
// the delay has been removed // the delay has been removed
*delay_key = None; *delay_key = None;
@ -541,14 +539,16 @@ where
ProtocolsHandlerUpgrErr::Timer ProtocolsHandlerUpgrErr::Timer
})? })?
{ {
self.outbound_substreams.remove(stream_id.get_ref()); if let Some((_id, _stream, protocol)) =
// notify the user self.outbound_substreams.remove(stream_id.get_ref())
return Ok(Async::Ready(ProtocolsHandlerEvent::Custom( {
RPCEvent::Error( // notify the user
*stream_id.get_ref(), return Ok(Async::Ready(ProtocolsHandlerEvent::Custom(
RPCError::Custom("Stream timed out".into()), RPCEvent::Error(*stream_id.get_ref(), protocol, RPCError::StreamTimeout),
), )));
))); } else {
crit!(self.log, "timed out substream not in the books"; "stream_id" => stream_id.get_ref());
}
} }
// drive inbound streams that need to be processed // drive inbound streams that need to be processed
@ -598,9 +598,10 @@ where
if let Some(delay_key) = &entry.get().1 { if let Some(delay_key) = &entry.get().1 {
self.inbound_substreams_delay.remove(delay_key); self.inbound_substreams_delay.remove(delay_key);
} }
let protocol = entry.get().2;
entry.remove_entry(); entry.remove_entry();
return Ok(Async::Ready(ProtocolsHandlerEvent::Custom( return Ok(Async::Ready(ProtocolsHandlerEvent::Custom(
RPCEvent::Error(0, e), RPCEvent::Error(0, protocol, e),
))); )));
} }
}; };
@ -696,7 +697,7 @@ where
return Ok(Async::Ready(ProtocolsHandlerEvent::Custom( return Ok(Async::Ready(ProtocolsHandlerEvent::Custom(
RPCEvent::Response( RPCEvent::Response(
request_id, request_id,
RPCErrorResponse::StreamTermination( RPCCodedResponse::StreamTermination(
request.stream_termination(), request.stream_termination(),
), ),
), ),
@ -705,9 +706,8 @@ where
return Ok(Async::Ready(ProtocolsHandlerEvent::Custom( return Ok(Async::Ready(ProtocolsHandlerEvent::Custom(
RPCEvent::Error( RPCEvent::Error(
request_id, request_id,
RPCError::Custom( request.protocol(),
"Stream closed early. Empty response".into(), RPCError::IncompleteStream,
),
), ),
))); )));
} }
@ -721,9 +721,10 @@ where
// drop the stream // drop the stream
let delay_key = &entry.get().1; let delay_key = &entry.get().1;
self.outbound_substreams_delay.remove(delay_key); self.outbound_substreams_delay.remove(delay_key);
let protocol = entry.get().2;
entry.remove_entry(); entry.remove_entry();
return Ok(Async::Ready(ProtocolsHandlerEvent::Custom( return Ok(Async::Ready(ProtocolsHandlerEvent::Custom(
RPCEvent::Error(request_id, e), RPCEvent::Error(request_id, protocol, e),
))); )));
} }
}, },
@ -759,16 +760,14 @@ where
// establish outbound substreams // establish outbound substreams
if !self.dial_queue.is_empty() && self.dial_negotiated < self.max_dial_negotiated { if !self.dial_queue.is_empty() && self.dial_negotiated < self.max_dial_negotiated {
self.dial_negotiated += 1; self.dial_negotiated += 1;
let rpc_event = self.dial_queue.remove(0); let (id, req) = self.dial_queue.remove(0);
self.dial_queue.shrink_to_fit(); self.dial_queue.shrink_to_fit();
if let RPCEvent::Request(id, req) = rpc_event { return Ok(Async::Ready(
return Ok(Async::Ready( ProtocolsHandlerEvent::OutboundSubstreamRequest {
ProtocolsHandlerEvent::OutboundSubstreamRequest { protocol: SubstreamProtocol::new(req.clone()),
protocol: SubstreamProtocol::new(req.clone()), info: (id, req),
info: RPCEvent::Request(id, req), },
}, ));
));
}
} }
Ok(Async::NotReady) Ok(Async::NotReady)
} }
@ -777,7 +776,7 @@ where
// Check for new items to send to the peer and update the underlying stream // Check for new items to send to the peer and update the underlying stream
fn apply_queued_responses<TSubstream: AsyncRead + AsyncWrite, TSpec: EthSpec>( fn apply_queued_responses<TSubstream: AsyncRead + AsyncWrite, TSpec: EthSpec>(
raw_substream: InboundFramed<TSubstream, TSpec>, raw_substream: InboundFramed<TSubstream, TSpec>,
queued_outbound_items: &mut Option<&mut Vec<RPCErrorResponse<TSpec>>>, queued_outbound_items: &mut Option<&mut Vec<RPCCodedResponse<TSpec>>>,
new_items_to_send: &mut bool, new_items_to_send: &mut bool,
) -> InboundSubstreamState<TSubstream, TSpec> { ) -> InboundSubstreamState<TSubstream, TSpec> {
match queued_outbound_items { match queued_outbound_items {
@ -785,7 +784,7 @@ fn apply_queued_responses<TSubstream: AsyncRead + AsyncWrite, TSpec: EthSpec>(
*new_items_to_send = true; *new_items_to_send = true;
// we have queued items // we have queued items
match queue.remove(0) { match queue.remove(0) {
RPCErrorResponse::StreamTermination(_) => { RPCCodedResponse::StreamTermination(_) => {
// close the stream if this is a stream termination // close the stream if this is a stream termination
InboundSubstreamState::Closing(raw_substream) InboundSubstreamState::Closing(raw_substream)
} }

View File

@ -173,8 +173,10 @@ pub enum ResponseTermination {
BlocksByRoot, BlocksByRoot,
} }
/// The structured response containing a result/code indicating success or failure
/// and the contents of the response
#[derive(Debug)] #[derive(Debug)]
pub enum RPCErrorResponse<T: EthSpec> { pub enum RPCCodedResponse<T: EthSpec> {
/// The response is a successful. /// The response is a successful.
Success(RPCResponse<T>), Success(RPCResponse<T>),
@ -191,15 +193,23 @@ pub enum RPCErrorResponse<T: EthSpec> {
StreamTermination(ResponseTermination), StreamTermination(ResponseTermination),
} }
impl<T: EthSpec> RPCErrorResponse<T> { /// The code assigned to an erroneous `RPCResponse`.
#[derive(Debug)]
pub enum RPCResponseErrorCode {
InvalidRequest,
ServerError,
Unknown,
}
impl<T: EthSpec> RPCCodedResponse<T> {
/// Used to encode the response in the codec. /// Used to encode the response in the codec.
pub fn as_u8(&self) -> Option<u8> { pub fn as_u8(&self) -> Option<u8> {
match self { match self {
RPCErrorResponse::Success(_) => Some(0), RPCCodedResponse::Success(_) => Some(0),
RPCErrorResponse::InvalidRequest(_) => Some(1), RPCCodedResponse::InvalidRequest(_) => Some(1),
RPCErrorResponse::ServerError(_) => Some(2), RPCCodedResponse::ServerError(_) => Some(2),
RPCErrorResponse::Unknown(_) => Some(255), RPCCodedResponse::Unknown(_) => Some(255),
RPCErrorResponse::StreamTermination(_) => None, RPCCodedResponse::StreamTermination(_) => None,
} }
} }
@ -211,30 +221,30 @@ impl<T: EthSpec> RPCErrorResponse<T> {
} }
} }
/// Builds an RPCErrorResponse from a response code and an ErrorMessage /// Builds an RPCCodedResponse from a response code and an ErrorMessage
pub fn from_error(response_code: u8, err: ErrorMessage) -> Self { pub fn from_error(response_code: u8, err: ErrorMessage) -> Self {
match response_code { match response_code {
1 => RPCErrorResponse::InvalidRequest(err), 1 => RPCCodedResponse::InvalidRequest(err),
2 => RPCErrorResponse::ServerError(err), 2 => RPCCodedResponse::ServerError(err),
_ => RPCErrorResponse::Unknown(err), _ => RPCCodedResponse::Unknown(err),
} }
} }
/// Specifies which response allows for multiple chunks for the stream handler. /// Specifies which response allows for multiple chunks for the stream handler.
pub fn multiple_responses(&self) -> bool { pub fn multiple_responses(&self) -> bool {
match self { match self {
RPCErrorResponse::Success(resp) => match resp { RPCCodedResponse::Success(resp) => match resp {
RPCResponse::Status(_) => false, RPCResponse::Status(_) => false,
RPCResponse::BlocksByRange(_) => true, RPCResponse::BlocksByRange(_) => true,
RPCResponse::BlocksByRoot(_) => true, RPCResponse::BlocksByRoot(_) => true,
RPCResponse::Pong(_) => false, RPCResponse::Pong(_) => false,
RPCResponse::MetaData(_) => false, RPCResponse::MetaData(_) => false,
}, },
RPCErrorResponse::InvalidRequest(_) => true, RPCCodedResponse::InvalidRequest(_) => true,
RPCErrorResponse::ServerError(_) => true, RPCCodedResponse::ServerError(_) => true,
RPCErrorResponse::Unknown(_) => true, RPCCodedResponse::Unknown(_) => true,
// Stream terminations are part of responses that have chunks // Stream terminations are part of responses that have chunks
RPCErrorResponse::StreamTermination(_) => true, RPCCodedResponse::StreamTermination(_) => true,
} }
} }
@ -242,10 +252,20 @@ impl<T: EthSpec> RPCErrorResponse<T> {
/// sent. /// sent.
pub fn is_error(&self) -> bool { pub fn is_error(&self) -> bool {
match self { match self {
RPCErrorResponse::Success(_) => false, RPCCodedResponse::Success(_) => false,
_ => true, _ => true,
} }
} }
pub fn error_code(&self) -> Option<RPCResponseErrorCode> {
match self {
RPCCodedResponse::Success(_) => None,
RPCCodedResponse::StreamTermination(_) => None,
RPCCodedResponse::InvalidRequest(_) => Some(RPCResponseErrorCode::InvalidRequest),
RPCCodedResponse::ServerError(_) => Some(RPCResponseErrorCode::ServerError),
RPCCodedResponse::Unknown(_) => Some(RPCResponseErrorCode::Unknown),
}
}
} }
#[derive(Encode, Decode, Debug)] #[derive(Encode, Decode, Debug)]
@ -260,6 +280,17 @@ impl ErrorMessage {
} }
} }
impl std::fmt::Display for RPCResponseErrorCode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let repr = match self {
RPCResponseErrorCode::InvalidRequest => "The request was invalid",
RPCResponseErrorCode::ServerError => "Server error occurred",
RPCResponseErrorCode::Unknown => "Unknown error occurred",
};
f.write_str(repr)
}
}
impl std::fmt::Display for StatusMessage { impl std::fmt::Display for StatusMessage {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Status Message: Fork Digest: {:?}, Finalized Root: {}, Finalized Epoch: {}, Head Root: {}, Head Slot: {}", self.fork_digest, self.finalized_root, self.finalized_epoch, self.head_root, self.head_slot) write!(f, "Status Message: Fork Digest: {:?}, Finalized Root: {}, Finalized Epoch: {}, Head Root: {}, Head Slot: {}", self.fork_digest, self.finalized_root, self.finalized_epoch, self.head_root, self.head_slot)
@ -282,14 +313,14 @@ impl<T: EthSpec> std::fmt::Display for RPCResponse<T> {
} }
} }
impl<T: EthSpec> std::fmt::Display for RPCErrorResponse<T> { impl<T: EthSpec> std::fmt::Display for RPCCodedResponse<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
RPCErrorResponse::Success(res) => write!(f, "{}", res), RPCCodedResponse::Success(res) => write!(f, "{}", res),
RPCErrorResponse::InvalidRequest(err) => write!(f, "Invalid Request: {:?}", err), RPCCodedResponse::InvalidRequest(err) => write!(f, "Invalid Request: {:?}", err),
RPCErrorResponse::ServerError(err) => write!(f, "Server Error: {:?}", err), RPCCodedResponse::ServerError(err) => write!(f, "Server Error: {:?}", err),
RPCErrorResponse::Unknown(err) => write!(f, "Unknown Error: {:?}", err), RPCCodedResponse::Unknown(err) => write!(f, "Unknown Error: {:?}", err),
RPCErrorResponse::StreamTermination(_) => write!(f, "Stream Termination"), RPCCodedResponse::StreamTermination(_) => write!(f, "Stream Termination"),
} }
} }
} }

View File

@ -13,10 +13,10 @@ use libp2p::swarm::{
}; };
use libp2p::{Multiaddr, PeerId}; use libp2p::{Multiaddr, PeerId};
pub use methods::{ pub use methods::{
ErrorMessage, MetaData, RPCErrorResponse, RPCResponse, RequestId, ResponseTermination, ErrorMessage, MetaData, RPCCodedResponse, RPCResponse, RPCResponseErrorCode, RequestId,
StatusMessage, ResponseTermination, StatusMessage,
}; };
pub use protocol::{RPCError, RPCProtocol, RPCRequest}; pub use protocol::{Protocol, RPCError, RPCProtocol, RPCRequest};
use slog::{debug, o}; use slog::{debug, o};
use std::marker::PhantomData; use std::marker::PhantomData;
use std::time::Duration; use std::time::Duration;
@ -37,9 +37,9 @@ pub enum RPCEvent<T: EthSpec> {
/// A response that is being sent or has been received from the RPC protocol. The first parameter returns /// A response that is being sent or has been received from the RPC protocol. The first parameter returns
/// that which was sent with the corresponding request, the second is a single chunk of a /// that which was sent with the corresponding request, the second is a single chunk of a
/// response. /// response.
Response(RequestId, RPCErrorResponse<T>), Response(RequestId, RPCCodedResponse<T>),
/// An Error occurred. /// An Error occurred.
Error(RequestId, RPCError), Error(RequestId, Protocol, RPCError),
} }
impl<T: EthSpec> RPCEvent<T> { impl<T: EthSpec> RPCEvent<T> {
@ -47,7 +47,7 @@ impl<T: EthSpec> RPCEvent<T> {
match *self { match *self {
RPCEvent::Request(id, _) => id, RPCEvent::Request(id, _) => id,
RPCEvent::Response(id, _) => id, RPCEvent::Response(id, _) => id,
RPCEvent::Error(id, _) => id, RPCEvent::Error(id, _, _) => id,
} }
} }
} }
@ -57,7 +57,11 @@ impl<T: EthSpec> std::fmt::Display for RPCEvent<T> {
match self { match self {
RPCEvent::Request(id, req) => write!(f, "RPC Request(id: {}, {})", id, req), RPCEvent::Request(id, req) => write!(f, "RPC Request(id: {}, {})", id, req),
RPCEvent::Response(id, res) => write!(f, "RPC Response(id: {}, {})", id, res), RPCEvent::Response(id, res) => write!(f, "RPC Response(id: {}, {})", id, res),
RPCEvent::Error(id, err) => write!(f, "RPC Request(id: {}, error: {:?})", id, err), RPCEvent::Error(id, prot, err) => write!(
f,
"RPC Error(id: {}, protocol: {:?} error: {:?})",
id, prot, err
),
} }
} }
} }

View File

@ -34,7 +34,7 @@ const TTFB_TIMEOUT: u64 = 5;
const REQUEST_TIMEOUT: u64 = 15; const REQUEST_TIMEOUT: u64 = 15;
/// Protocol names to be used. /// Protocol names to be used.
#[derive(Debug, Clone)] #[derive(Debug, Clone, Copy)]
pub enum Protocol { pub enum Protocol {
/// The Status protocol name. /// The Status protocol name.
Status, Status,
@ -128,7 +128,7 @@ impl<TSpec: EthSpec> UpgradeInfo for RPCProtocol<TSpec> {
/// Tracks the types in a protocol id. /// Tracks the types in a protocol id.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct ProtocolId { pub struct ProtocolId {
/// The rpc message type/name. /// The RPC message type/name.
pub message_name: Protocol, pub message_name: Protocol,
/// The version of the RPC. /// The version of the RPC.
@ -151,7 +151,7 @@ impl ProtocolId {
ProtocolId { ProtocolId {
message_name, message_name,
version: version, version,
encoding, encoding,
protocol_id, protocol_id,
} }
@ -172,11 +172,20 @@ impl ProtocolName for ProtocolId {
pub type InboundOutput<TSocket, TSpec> = (RPCRequest<TSpec>, InboundFramed<TSocket, TSpec>); pub type InboundOutput<TSocket, TSpec> = (RPCRequest<TSpec>, InboundFramed<TSocket, TSpec>);
pub type InboundFramed<TSocket, TSpec> = pub type InboundFramed<TSocket, TSpec> =
Framed<TimeoutStream<upgrade::Negotiated<TSocket>>, InboundCodec<TSpec>>; Framed<TimeoutStream<upgrade::Negotiated<TSocket>>, InboundCodec<TSpec>>;
// Auxiliary types
// The type of the socket timeout in the `InboundUpgrade` type `Future`
type TTimeout<TSocket, TSpec> =
timeout::Timeout<stream::StreamFuture<InboundFramed<TSocket, TSpec>>>;
// The type of the socket timeout error in the `InboundUpgrade` type `Future`
type TTimeoutErr<TSocket, TSpec> = timeout::Error<(RPCError, InboundFramed<TSocket, TSpec>)>;
// `TimeoutErr` to `RPCError` mapping function
type FnMapErr<TSocket, TSpec> = fn(TTimeoutErr<TSocket, TSpec>) -> RPCError;
type FnAndThen<TSocket, TSpec> = fn( type FnAndThen<TSocket, TSpec> = fn(
(Option<RPCRequest<TSpec>>, InboundFramed<TSocket, TSpec>), (Option<RPCRequest<TSpec>>, InboundFramed<TSocket, TSpec>),
) -> FutureResult<InboundOutput<TSocket, TSpec>, RPCError>; ) -> FutureResult<InboundOutput<TSocket, TSpec>, RPCError>;
type FnMapErr<TSocket, TSpec> =
fn(timeout::Error<(RPCError, InboundFramed<TSocket, TSpec>)>) -> RPCError;
impl<TSocket, TSpec> InboundUpgrade<TSocket> for RPCProtocol<TSpec> impl<TSocket, TSpec> InboundUpgrade<TSocket> for RPCProtocol<TSpec>
where where
@ -189,10 +198,7 @@ where
type Future = future::Either< type Future = future::Either<
FutureResult<InboundOutput<TSocket, TSpec>, RPCError>, FutureResult<InboundOutput<TSocket, TSpec>, RPCError>,
future::AndThen< future::AndThen<
future::MapErr< future::MapErr<TTimeout<TSocket, TSpec>, FnMapErr<TSocket, TSpec>>,
timeout::Timeout<stream::StreamFuture<InboundFramed<TSocket, TSpec>>>,
FnMapErr<TSocket, TSpec>,
>,
FutureResult<InboundOutput<TSocket, TSpec>, RPCError>, FutureResult<InboundOutput<TSocket, TSpec>, RPCError>,
FnAndThen<TSocket, TSpec>, FnAndThen<TSocket, TSpec>,
>, >,
@ -203,7 +209,7 @@ where
socket: upgrade::Negotiated<TSocket>, socket: upgrade::Negotiated<TSocket>,
protocol: ProtocolId, protocol: ProtocolId,
) -> Self::Future { ) -> Self::Future {
let protocol_name = protocol.message_name.clone(); let protocol_name = protocol.message_name;
let codec = match protocol.encoding { let codec = match protocol.encoding {
Encoding::SSZSnappy => { Encoding::SSZSnappy => {
let ssz_snappy_codec = let ssz_snappy_codec =
@ -220,27 +226,31 @@ where
let socket = Framed::new(timed_socket, codec); let socket = Framed::new(timed_socket, codec);
// MetaData requests should be empty, return the stream
match protocol_name { match protocol_name {
Protocol::MetaData => futures::future::Either::A(futures::future::ok(( // `MetaData` requests should be empty, return the stream
RPCRequest::MetaData(PhantomData), Protocol::MetaData => {
socket, future::Either::A(future::ok((RPCRequest::MetaData(PhantomData), socket)))
))), }
_ => future::Either::B({
_ => futures::future::Either::B(
socket socket
.into_future() .into_future()
.timeout(Duration::from_secs(REQUEST_TIMEOUT)) .timeout(Duration::from_secs(REQUEST_TIMEOUT))
.map_err(RPCError::from as FnMapErr<TSocket, TSpec>) .map_err({
|err| {
if err.is_elapsed() {
RPCError::StreamTimeout
} else {
RPCError::InternalError("Stream timer failed")
}
}
} as FnMapErr<TSocket, TSpec>)
.and_then({ .and_then({
|(req, stream)| match req { |(req, stream)| match req {
Some(request) => futures::future::ok((request, stream)), Some(request) => future::ok((request, stream)),
None => futures::future::err(RPCError::Custom( None => future::err(RPCError::IncompleteStream),
"Stream terminated early".into(),
)),
} }
} as FnAndThen<TSocket, TSpec>), } as FnAndThen<TSocket, TSpec>)
), }),
} }
} }
} }
@ -270,7 +280,7 @@ impl<TSpec: EthSpec> UpgradeInfo for RPCRequest<TSpec> {
} }
} }
/// Implements the encoding per supported protocol for RPCRequest. /// Implements the encoding per supported protocol for `RPCRequest`.
impl<TSpec: EthSpec> RPCRequest<TSpec> { impl<TSpec: EthSpec> RPCRequest<TSpec> {
pub fn supported_protocols(&self) -> Vec<ProtocolId> { pub fn supported_protocols(&self) -> Vec<ProtocolId> {
match self { match self {
@ -330,6 +340,17 @@ impl<TSpec: EthSpec> RPCRequest<TSpec> {
} }
} }
pub fn protocol(&self) -> Protocol {
match self {
RPCRequest::Status(_) => Protocol::Status,
RPCRequest::Goodbye(_) => Protocol::Goodbye,
RPCRequest::BlocksByRange(_) => Protocol::BlocksByRange,
RPCRequest::BlocksByRoot(_) => Protocol::BlocksByRoot,
RPCRequest::Ping(_) => Protocol::Ping,
RPCRequest::MetaData(_) => Protocol::MetaData,
}
}
/// Returns the `ResponseTermination` type associated with the request if a stream gets /// Returns the `ResponseTermination` type associated with the request if a stream gets
/// terminated. /// terminated.
pub fn stream_termination(&self) -> ResponseTermination { pub fn stream_termination(&self) -> ResponseTermination {
@ -361,6 +382,7 @@ where
type Output = OutboundFramed<TSocket, TSpec>; type Output = OutboundFramed<TSocket, TSpec>;
type Error = RPCError; type Error = RPCError;
type Future = sink::Send<OutboundFramed<TSocket, TSpec>>; type Future = sink::Send<OutboundFramed<TSocket, TSpec>>;
fn upgrade_outbound( fn upgrade_outbound(
self, self,
socket: upgrade::Negotiated<TSocket>, socket: upgrade::Negotiated<TSocket>,
@ -385,29 +407,25 @@ where
/// Error in RPC Encoding/Decoding. /// Error in RPC Encoding/Decoding.
#[derive(Debug)] #[derive(Debug)]
pub enum RPCError { pub enum RPCError {
/// Error when reading the packet from the socket.
ReadError(upgrade::ReadOneError),
/// Error when decoding the raw buffer from ssz. /// Error when decoding the raw buffer from ssz.
// NOTE: in the future a ssz::DecodeError should map to an InvalidData error
SSZDecodeError(ssz::DecodeError), SSZDecodeError(ssz::DecodeError),
/// Snappy error
SnappyError(snap::Error),
/// Invalid Protocol ID.
InvalidProtocol(&'static str),
/// IO Error. /// IO Error.
IoError(io::Error), IoError(io::Error),
/// Waiting for a request/response timed out, or timer error'd. /// The peer returned a valid response but the response indicated an error.
ErrorResponse(RPCResponseErrorCode),
/// Timed out waiting for a response.
StreamTimeout, StreamTimeout,
/// The peer returned a valid RPCErrorResponse but the response was an error. /// Peer does not support the protocol.
RPCErrorResponse, UnsupportedProtocol,
/// Custom message. /// Stream ended unexpectedly.
Custom(String), IncompleteStream,
} /// Peer sent invalid data.
InvalidData,
impl From<upgrade::ReadOneError> for RPCError { /// An error occurred due to internal reasons. Ex: timer failure.
#[inline] InternalError(&'static str),
fn from(err: upgrade::ReadOneError) -> Self { /// Negotiation with this peer timed out
RPCError::ReadError(err) NegotiationTimeout,
}
} }
impl From<ssz::DecodeError> for RPCError { impl From<ssz::DecodeError> for RPCError {
@ -416,21 +434,6 @@ impl From<ssz::DecodeError> for RPCError {
RPCError::SSZDecodeError(err) RPCError::SSZDecodeError(err)
} }
} }
impl<T> From<tokio::timer::timeout::Error<T>> for RPCError {
fn from(err: tokio::timer::timeout::Error<T>) -> Self {
if err.is_elapsed() {
RPCError::StreamTimeout
} else {
RPCError::Custom("Stream timer failed".into())
}
}
}
impl From<()> for RPCError {
fn from(_err: ()) -> Self {
RPCError::Custom("".into())
}
}
impl From<io::Error> for RPCError { impl From<io::Error> for RPCError {
fn from(err: io::Error) -> Self { fn from(err: io::Error) -> Self {
@ -438,24 +441,19 @@ impl From<io::Error> for RPCError {
} }
} }
impl From<snap::Error> for RPCError {
fn from(err: snap::Error) -> Self {
RPCError::SnappyError(err)
}
}
// Error trait is required for `ProtocolsHandler` // Error trait is required for `ProtocolsHandler`
impl std::fmt::Display for RPCError { impl std::fmt::Display for RPCError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match *self { match *self {
RPCError::ReadError(ref err) => write!(f, "Error while reading from socket: {}", err),
RPCError::SSZDecodeError(ref err) => write!(f, "Error while decoding ssz: {:?}", err), RPCError::SSZDecodeError(ref err) => write!(f, "Error while decoding ssz: {:?}", err),
RPCError::InvalidProtocol(ref err) => write!(f, "Invalid Protocol: {}", err), RPCError::InvalidData => write!(f, "Peer sent unexpected data"),
RPCError::IoError(ref err) => write!(f, "IO Error: {}", err), RPCError::IoError(ref err) => write!(f, "IO Error: {}", err),
RPCError::RPCErrorResponse => write!(f, "RPC Response Error"), RPCError::ErrorResponse(ref code) => write!(f, "RPC response was an error: {}", code),
RPCError::StreamTimeout => write!(f, "Stream Timeout"), RPCError::StreamTimeout => write!(f, "Stream Timeout"),
RPCError::SnappyError(ref err) => write!(f, "Snappy error: {}", err), RPCError::UnsupportedProtocol => write!(f, "Peer does not support the protocol"),
RPCError::Custom(ref err) => write!(f, "{}", err), RPCError::IncompleteStream => write!(f, "Stream ended unexpectedly"),
RPCError::InternalError(ref err) => write!(f, "Internal error: {}", err),
RPCError::NegotiationTimeout => write!(f, "Negotiation timeout"),
} }
} }
} }
@ -463,14 +461,16 @@ impl std::fmt::Display for RPCError {
impl std::error::Error for RPCError { impl std::error::Error for RPCError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match *self { match *self {
RPCError::ReadError(ref err) => Some(err), // NOTE: this does have a source
RPCError::SSZDecodeError(_) => None, RPCError::SSZDecodeError(_) => None,
RPCError::SnappyError(ref err) => Some(err),
RPCError::InvalidProtocol(_) => None,
RPCError::IoError(ref err) => Some(err), RPCError::IoError(ref err) => Some(err),
RPCError::StreamTimeout => None, RPCError::StreamTimeout => None,
RPCError::RPCErrorResponse => None, RPCError::UnsupportedProtocol => None,
RPCError::Custom(_) => None, RPCError::IncompleteStream => None,
RPCError::InvalidData => None,
RPCError::InternalError(_) => None,
RPCError::ErrorResponse(_) => None,
RPCError::NegotiationTimeout => None,
} }
} }
} }

View File

@ -62,12 +62,12 @@ fn test_status_rpc() {
} }
Async::Ready(Some(BehaviourEvent::RPC(_, event))) => match event { Async::Ready(Some(BehaviourEvent::RPC(_, event))) => match event {
// Should receive the RPC response // Should receive the RPC response
RPCEvent::Response(id, response @ RPCErrorResponse::Success(_)) => { RPCEvent::Response(id, response @ RPCCodedResponse::Success(_)) => {
if id == 1 { if id == 1 {
warn!(sender_log, "Sender Received"); warn!(sender_log, "Sender Received");
let response = { let response = {
match response { match response {
RPCErrorResponse::Success(r) => r, RPCCodedResponse::Success(r) => r,
_ => unreachable!(), _ => unreachable!(),
} }
}; };
@ -99,7 +99,7 @@ fn test_status_rpc() {
peer_id, peer_id,
RPCEvent::Response( RPCEvent::Response(
id, id,
RPCErrorResponse::Success(rpc_response.clone()), RPCCodedResponse::Success(rpc_response.clone()),
), ),
); );
} }
@ -181,12 +181,12 @@ fn test_blocks_by_range_chunked_rpc() {
if id == 1 { if id == 1 {
warn!(sender_log, "Sender received a response"); warn!(sender_log, "Sender received a response");
match response { match response {
RPCErrorResponse::Success(res) => { RPCCodedResponse::Success(res) => {
assert_eq!(res, sender_response.clone()); assert_eq!(res, sender_response.clone());
*messages_received.lock().unwrap() += 1; *messages_received.lock().unwrap() += 1;
warn!(sender_log, "Chunk received"); warn!(sender_log, "Chunk received");
} }
RPCErrorResponse::StreamTermination( RPCCodedResponse::StreamTermination(
ResponseTermination::BlocksByRange, ResponseTermination::BlocksByRange,
) => { ) => {
// should be exactly 10 messages before terminating // should be exactly 10 messages before terminating
@ -225,7 +225,7 @@ fn test_blocks_by_range_chunked_rpc() {
peer_id.clone(), peer_id.clone(),
RPCEvent::Response( RPCEvent::Response(
id, id,
RPCErrorResponse::Success(rpc_response.clone()), RPCCodedResponse::Success(rpc_response.clone()),
), ),
); );
} }
@ -234,7 +234,7 @@ fn test_blocks_by_range_chunked_rpc() {
peer_id, peer_id,
RPCEvent::Response( RPCEvent::Response(
id, id,
RPCErrorResponse::StreamTermination( RPCCodedResponse::StreamTermination(
ResponseTermination::BlocksByRange, ResponseTermination::BlocksByRange,
), ),
), ),
@ -316,12 +316,12 @@ fn test_blocks_by_range_single_empty_rpc() {
if id == 1 { if id == 1 {
warn!(sender_log, "Sender received a response"); warn!(sender_log, "Sender received a response");
match response { match response {
RPCErrorResponse::Success(res) => { RPCCodedResponse::Success(res) => {
assert_eq!(res, sender_response.clone()); assert_eq!(res, sender_response.clone());
*messages_received.lock().unwrap() += 1; *messages_received.lock().unwrap() += 1;
warn!(sender_log, "Chunk received"); warn!(sender_log, "Chunk received");
} }
RPCErrorResponse::StreamTermination( RPCCodedResponse::StreamTermination(
ResponseTermination::BlocksByRange, ResponseTermination::BlocksByRange,
) => { ) => {
// should be exactly 1 messages before terminating // should be exactly 1 messages before terminating
@ -356,7 +356,7 @@ fn test_blocks_by_range_single_empty_rpc() {
peer_id.clone(), peer_id.clone(),
RPCEvent::Response( RPCEvent::Response(
id, id,
RPCErrorResponse::Success(rpc_response.clone()), RPCCodedResponse::Success(rpc_response.clone()),
), ),
); );
// send the stream termination // send the stream termination
@ -364,7 +364,7 @@ fn test_blocks_by_range_single_empty_rpc() {
peer_id, peer_id,
RPCEvent::Response( RPCEvent::Response(
id, id,
RPCErrorResponse::StreamTermination( RPCCodedResponse::StreamTermination(
ResponseTermination::BlocksByRange, ResponseTermination::BlocksByRange,
), ),
), ),
@ -449,12 +449,12 @@ fn test_blocks_by_root_chunked_rpc() {
warn!(sender_log, "Sender received a response"); warn!(sender_log, "Sender received a response");
assert_eq!(id, 1); assert_eq!(id, 1);
match response { match response {
RPCErrorResponse::Success(res) => { RPCCodedResponse::Success(res) => {
assert_eq!(res, sender_response.clone()); assert_eq!(res, sender_response.clone());
*messages_received.lock().unwrap() += 1; *messages_received.lock().unwrap() += 1;
warn!(sender_log, "Chunk received"); warn!(sender_log, "Chunk received");
} }
RPCErrorResponse::StreamTermination( RPCCodedResponse::StreamTermination(
ResponseTermination::BlocksByRoot, ResponseTermination::BlocksByRoot,
) => { ) => {
// should be exactly 10 messages before terminating // should be exactly 10 messages before terminating
@ -489,7 +489,7 @@ fn test_blocks_by_root_chunked_rpc() {
peer_id.clone(), peer_id.clone(),
RPCEvent::Response( RPCEvent::Response(
id, id,
RPCErrorResponse::Success(rpc_response.clone()), RPCCodedResponse::Success(rpc_response.clone()),
), ),
); );
} }
@ -498,7 +498,7 @@ fn test_blocks_by_root_chunked_rpc() {
peer_id, peer_id,
RPCEvent::Response( RPCEvent::Response(
id, id,
RPCErrorResponse::StreamTermination( RPCCodedResponse::StreamTermination(
ResponseTermination::BlocksByRange, ResponseTermination::BlocksByRange,
), ),
), ),

View File

@ -10,7 +10,10 @@ use crate::error;
use crate::service::NetworkMessage; use crate::service::NetworkMessage;
use beacon_chain::{AttestationType, BeaconChain, BeaconChainTypes, BlockError}; use beacon_chain::{AttestationType, BeaconChain, BeaconChainTypes, BlockError};
use eth2_libp2p::{ use eth2_libp2p::{
rpc::{RPCError, RPCErrorResponse, RPCRequest, RPCResponse, RequestId, ResponseTermination}, rpc::{
RPCCodedResponse, RPCError, RPCRequest, RPCResponse, RPCResponseErrorCode, RequestId,
ResponseTermination,
},
MessageId, NetworkGlobals, PeerId, PubsubMessage, RPCEvent, MessageId, NetworkGlobals, PeerId, PubsubMessage, RPCEvent,
}; };
use futures::future::Future; use futures::future::Future;
@ -123,7 +126,7 @@ impl<T: BeaconChainTypes> Router<T> {
match rpc_message { match rpc_message {
RPCEvent::Request(id, req) => self.handle_rpc_request(peer_id, id, req), RPCEvent::Request(id, req) => self.handle_rpc_request(peer_id, id, req),
RPCEvent::Response(id, resp) => self.handle_rpc_response(peer_id, id, resp), RPCEvent::Response(id, resp) => self.handle_rpc_response(peer_id, id, resp),
RPCEvent::Error(id, error) => self.handle_rpc_error(peer_id, id, error), RPCEvent::Error(id, _protocol, error) => self.handle_rpc_error(peer_id, id, error),
} }
} }
@ -164,23 +167,35 @@ impl<T: BeaconChainTypes> Router<T> {
&mut self, &mut self,
peer_id: PeerId, peer_id: PeerId,
request_id: RequestId, request_id: RequestId,
error_response: RPCErrorResponse<T::EthSpec>, error_response: RPCCodedResponse<T::EthSpec>,
) { ) {
// an error could have occurred. // an error could have occurred.
match error_response { match error_response {
RPCErrorResponse::InvalidRequest(error) => { RPCCodedResponse::InvalidRequest(error) => {
warn!(self.log, "Peer indicated invalid request";"peer_id" => format!("{:?}", peer_id), "error" => error.as_string()); warn!(self.log, "Peer indicated invalid request"; "peer_id" => format!("{:?}", peer_id), "error" => error.as_string());
self.handle_rpc_error(peer_id, request_id, RPCError::RPCErrorResponse); self.handle_rpc_error(
peer_id,
request_id,
RPCError::ErrorResponse(RPCResponseErrorCode::InvalidRequest),
);
} }
RPCErrorResponse::ServerError(error) => { RPCCodedResponse::ServerError(error) => {
warn!(self.log, "Peer internal server error";"peer_id" => format!("{:?}", peer_id), "error" => error.as_string()); warn!(self.log, "Peer internal server error"; "peer_id" => format!("{:?}", peer_id), "error" => error.as_string());
self.handle_rpc_error(peer_id, request_id, RPCError::RPCErrorResponse); self.handle_rpc_error(
peer_id,
request_id,
RPCError::ErrorResponse(RPCResponseErrorCode::ServerError),
);
} }
RPCErrorResponse::Unknown(error) => { RPCCodedResponse::Unknown(error) => {
warn!(self.log, "Unknown peer error";"peer" => format!("{:?}", peer_id), "error" => error.as_string()); warn!(self.log, "Unknown peer error"; "peer" => format!("{:?}", peer_id), "error" => error.as_string());
self.handle_rpc_error(peer_id, request_id, RPCError::RPCErrorResponse); self.handle_rpc_error(
peer_id,
request_id,
RPCError::ErrorResponse(RPCResponseErrorCode::Unknown),
);
} }
RPCErrorResponse::Success(response) => match response { RPCCodedResponse::Success(response) => match response {
RPCResponse::Status(status_message) => { RPCResponse::Status(status_message) => {
self.processor.on_status_response(peer_id, status_message); self.processor.on_status_response(peer_id, status_message);
} }
@ -205,7 +220,7 @@ impl<T: BeaconChainTypes> Router<T> {
unreachable!("Meta data must be handled in the behaviour"); unreachable!("Meta data must be handled in the behaviour");
} }
}, },
RPCErrorResponse::StreamTermination(response_type) => { RPCCodedResponse::StreamTermination(response_type) => {
// have received a stream termination, notify the processing functions // have received a stream termination, notify the processing functions
match response_type { match response_type {
ResponseTermination::BlocksByRange => { ResponseTermination::BlocksByRange => {

View File

@ -5,7 +5,7 @@ use beacon_chain::{
BlockProcessingOutcome, GossipVerifiedBlock, BlockProcessingOutcome, GossipVerifiedBlock,
}; };
use eth2_libp2p::rpc::methods::*; use eth2_libp2p::rpc::methods::*;
use eth2_libp2p::rpc::{RPCEvent, RPCRequest, RPCResponse, RequestId}; use eth2_libp2p::rpc::{RPCCodedResponse, RPCEvent, RPCRequest, RPCResponse, RequestId};
use eth2_libp2p::{NetworkGlobals, PeerId}; use eth2_libp2p::{NetworkGlobals, PeerId};
use slog::{debug, error, o, trace, warn}; use slog::{debug, error, o, trace, warn};
use ssz::Encode; use ssz::Encode;
@ -314,7 +314,7 @@ impl<T: BeaconChainTypes> Processor<T> {
self.network.send_rpc_error_response( self.network.send_rpc_error_response(
peer_id, peer_id,
request_id, request_id,
RPCErrorResponse::StreamTermination(ResponseTermination::BlocksByRoot), RPCCodedResponse::StreamTermination(ResponseTermination::BlocksByRoot),
); );
} }
@ -413,7 +413,7 @@ impl<T: BeaconChainTypes> Processor<T> {
self.network.send_rpc_error_response( self.network.send_rpc_error_response(
peer_id, peer_id,
request_id, request_id,
RPCErrorResponse::StreamTermination(ResponseTermination::BlocksByRange), RPCCodedResponse::StreamTermination(ResponseTermination::BlocksByRange),
); );
} }
@ -691,16 +691,16 @@ impl<T: EthSpec> HandlerNetworkContext<T> {
) { ) {
self.send_rpc_event( self.send_rpc_event(
peer_id, peer_id,
RPCEvent::Response(request_id, RPCErrorResponse::Success(rpc_response)), RPCEvent::Response(request_id, RPCCodedResponse::Success(rpc_response)),
); );
} }
/// Send an RPCErrorResponse. This handles errors and stream terminations. /// Send an RPCCodedResponse. This handles errors and stream terminations.
pub fn send_rpc_error_response( pub fn send_rpc_error_response(
&mut self, &mut self,
peer_id: PeerId, peer_id: PeerId,
request_id: RequestId, request_id: RequestId,
rpc_error_response: RPCErrorResponse<T>, rpc_error_response: RPCCodedResponse<T>,
) { ) {
self.send_rpc_event(peer_id, RPCEvent::Response(request_id, rpc_error_response)); self.send_rpc_event(peer_id, RPCEvent::Response(request_id, rpc_error_response));
} }