From ec1b982b528205dcabe3d75d810ee77322306e7f Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Wed, 9 Nov 2016 15:36:26 +0100 Subject: [PATCH] errors, punishment, and handshake --- ethcore/light/src/client.rs | 11 ++- ethcore/light/src/net/error.rs | 94 ++++++++++++++++++ ethcore/light/src/net/mod.rs | 164 +++++++++++++++++++++----------- ethcore/light/src/net/status.rs | 19 ++-- ethcore/light/src/provider.rs | 8 +- 5 files changed, 225 insertions(+), 71 deletions(-) create mode 100644 ethcore/light/src/net/error.rs diff --git a/ethcore/light/src/client.rs b/ethcore/light/src/client.rs index 81355bf0f..fb4cb251f 100644 --- a/ethcore/light/src/client.rs +++ b/ethcore/light/src/client.rs @@ -52,7 +52,7 @@ impl Client { } /// Whether the block is already known (but not necessarily part of the canonical chain) - pub fn is_known(&self, id: BlockID) -> bool { + pub fn is_known(&self, _id: BlockID) -> bool { false } @@ -74,11 +74,18 @@ impl Client { // dummy implementation -- may draw from canonical cache further on. impl Provider for Client { - /// Get the chain info. fn chain_info(&self) -> BlockChainInfo { unimplemented!() } + fn reorg_depth(&self, _a: &H256, _b: &H256) -> Option { + None + } + + fn earliest_state(&self) -> Option { + None + } + fn block_headers(&self, _req: request::Headers) -> Vec { Vec::new() } diff --git a/ethcore/light/src/net/error.rs b/ethcore/light/src/net/error.rs new file mode 100644 index 000000000..e15bd50d3 --- /dev/null +++ b/ethcore/light/src/net/error.rs @@ -0,0 +1,94 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +//! Defines error types and levels of punishment to use upon +//! encountering. + +use rlp::DecoderError; +use network::NetworkError; + +use std::fmt; + +/// Levels of punishment. +/// +/// Currently just encompasses two different kinds of disconnect and +/// no punishment, but this is where reputation systems might come into play. +// In ascending order +#[derive(Debug, PartialEq, Eq)] +pub enum Punishment { + /// Perform no punishment. + None, + /// Disconnect the peer, but don't prevent them from reconnecting. + Disconnect, + /// Disconnect the peer and prevent them from reconnecting. + Disable, +} + +/// Kinds of errors which can be encountered in the course of LES. +#[derive(Debug)] +pub enum Error { + /// An RLP decoding error. + Rlp(DecoderError), + /// A network error. + Network(NetworkError), + /// Out of buffer. + BufferEmpty, + /// Unrecognized packet code. + UnrecognizedPacket(u8), + /// Unexpected handshake. + UnexpectedHandshake, + /// Peer on wrong network (wrong NetworkId or genesis hash) + WrongNetwork, +} + +impl Error { + /// What level of punishment does this error warrant? + pub fn punishment(&self) -> Punishment { + match *self { + Error::Rlp(_) => Punishment::Disable, + Error::Network(_) => Punishment::None, + Error::BufferEmpty => Punishment::Disable, + Error::UnrecognizedPacket(_) => Punishment::Disconnect, + Error::UnexpectedHandshake => Punishment::Disconnect, + Error::WrongNetwork => Punishment::Disable, + } + } +} + +impl From for Error { + fn from(err: DecoderError) -> Self { + Error::Rlp(err) + } +} + +impl From for Error { + fn from(err: NetworkError) -> Self { + Error::Network(err) + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Error::Rlp(ref err) => err.fmt(f), + Error::Network(ref err) => err.fmt(f), + Error::BufferEmpty => write!(f, "Out of buffer"), + Error::UnrecognizedPacket(code) => write!(f, "Unrecognized packet: 0x{:x}", code), + Error::UnexpectedHandshake => write!(f, "Unexpected handshake"), + Error::WrongNetwork => write!(f, "Wrong network"), + } + } +} \ No newline at end of file diff --git a/ethcore/light/src/net/mod.rs b/ethcore/light/src/net/mod.rs index 4f0543526..c0697407d 100644 --- a/ethcore/light/src/net/mod.rs +++ b/ethcore/light/src/net/mod.rs @@ -30,9 +30,13 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use provider::Provider; use request::{self, Request}; -use self::buffer_flow::FlowParams; + +use self::buffer_flow::{Buffer, FlowParams}; +use self::error::{Error, Punishment}; +use self::status::{Status, Capabilities}; mod buffer_flow; +mod error; mod status; const TIMEOUT: TimerToken = 0; @@ -80,10 +84,21 @@ mod packet { pub const HEADER_PROOFS: u8 = 0x0e; } +// A pending peer: one we've sent our status to but +// may not have received one for. +struct PendingPeer { + sent_head: H256, +} + // data about each peer. struct Peer { - buffer: u64, // remaining buffer value. + local_buffer: Buffer, // their buffer relative to us + remote_buffer: Buffer, // our buffer relative to them current_asking: HashSet, // pending request ids. + status: Status, + capabilities: Capabilities, + remote_flow: FlowParams, + sent_head: H256, // last head we've given them. } /// This is an implementation of the light ethereum network protocol, abstracted @@ -95,31 +110,32 @@ struct Peer { pub struct LightProtocol { provider: Box, genesis_hash: H256, - mainnet: bool, + network_id: status::NetworkId, + pending_peers: RwLock>, peers: RwLock>, pending_requests: RwLock>, + capabilities: RwLock, + flow_params: FlowParams, // assumed static and same for every peer. req_id: AtomicUsize, } impl LightProtocol { - // make a request to a given peer. - fn request_from(&self, peer: &PeerId, req: Request) { + // Check on the status of all pending requests. + fn check_pending_requests(&self) { unimplemented!() } // called when a peer connects. fn on_connect(&self, peer: &PeerId, io: &NetworkContext) { let peer = *peer; + match self.send_status(peer, io) { - Ok(()) => { - self.peers.write().insert(peer, Peer { - buffer: 0, - current_asking: HashSet::new(), - }); + Ok(pending_peer) => { + self.pending_peers.write().insert(peer, pending_peer); } Err(e) => { trace!(target: "les", "Error while sending status: {}", e); - io.disable_peer(peer); + io.disconnect_peer(peer); } } } @@ -127,119 +143,140 @@ impl LightProtocol { // called when a peer disconnects. fn on_disconnect(&self, peer: PeerId, io: &NetworkContext) { // TODO: reassign all requests assigned to this peer. + self.pending_peers.write().remove(&peer); self.peers.write().remove(&peer); } - fn send_status(&self, peer: PeerId, io: &NetworkContext) -> Result<(), NetworkError> { + // send status to a peer. + fn send_status(&self, peer: PeerId, io: &NetworkContext) -> Result { let chain_info = self.provider.chain_info(); - // TODO [rob] use optional keys too. - let mut stream = RlpStream::new_list(6); - stream - .begin_list(2) - .append(&"protocolVersion") - .append(&PROTOCOL_VERSION) - .begin_list(2) - .append(&"networkId") - .append(&(self.mainnet as u8)) - .begin_list(2) - .append(&"headTd") - .append(&chain_info.total_difficulty) - .begin_list(2) - .append(&"headHash") - .append(&chain_info.best_block_hash) - .begin_list(2) - .append(&"headNum") - .append(&chain_info.best_block_number) - .begin_list(2) - .append(&"genesisHash") - .append(&self.genesis_hash); + // TODO: could update capabilities here. - io.send(peer, packet::STATUS, stream.out()) + let status = Status { + head_td: chain_info.total_difficulty, + head_hash: chain_info.best_block_hash, + head_num: chain_info.best_block_number, + genesis_hash: chain_info.genesis_hash, + protocol_version: PROTOCOL_VERSION, + network_id: self.network_id, + last_head: None, + }; + + let capabilities = self.capabilities.read().clone(); + let status_packet = status::write_handshake(&status, &capabilities, &self.flow_params); + + try!(io.send(peer, packet::STATUS, status_packet)); + + Ok(PendingPeer { + sent_head: chain_info.best_block_hash, + }) } - /// Check on the status of all pending requests. - fn check_pending_requests(&self) { - unimplemented!() - } + // Handle status message from peer. + fn status(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { + let pending = match self.pending_peers.write().remove(peer) { + Some(pending) => pending, + None => { + return Err(Error::UnexpectedHandshake); + } + }; - fn status(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { - unimplemented!() + let (status, capabilities, flow_params) = try!(status::parse_handshake(data)); + + trace!(target: "les", "Connected peer with chain head {:?}", (status.head_hash, status.head_num)); + + if (status.network_id, status.genesis_hash) != (self.network_id, self.genesis_hash) { + return Err(Error::WrongNetwork); + } + + self.peers.write().insert(*peer, Peer { + local_buffer: self.flow_params.create_buffer(), + remote_buffer: flow_params.create_buffer(), + current_asking: HashSet::new(), + status: status, + capabilities: capabilities, + remote_flow: flow_params, + sent_head: pending.sent_head, + }); + + + Ok(()) } // Handle an announcement. - fn announcement(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { + fn announcement(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { const MAX_NEW_HASHES: usize = 256; unimplemented!() } // Handle a request for block headers. - fn get_block_headers(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { + fn get_block_headers(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { const MAX_HEADERS: u64 = 512; unimplemented!() } // Receive a response for block headers. - fn block_headers(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { + fn block_headers(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { unimplemented!() } // Handle a request for block bodies. - fn get_block_bodies(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { + fn get_block_bodies(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { const MAX_BODIES: usize = 512; unimplemented!() } // Receive a response for block bodies. - fn block_bodies(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { + fn block_bodies(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { unimplemented!() } // Handle a request for receipts. - fn get_receipts(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { + fn get_receipts(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { unimplemented!() } // Receive a response for receipts. - fn receipts(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { + fn receipts(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { unimplemented!() } // Handle a request for proofs. - fn get_proofs(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { + fn get_proofs(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { unimplemented!() } // Receive a response for proofs. - fn proofs(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { + fn proofs(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { unimplemented!() } // Handle a request for contract code. - fn get_contract_code(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { + fn get_contract_code(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { unimplemented!() } // Receive a response for contract code. - fn contract_code(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { + fn contract_code(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { unimplemented!() } // Handle a request for header proofs - fn get_header_proofs(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { + fn get_header_proofs(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { unimplemented!() } // Receive a response for header proofs - fn header_proofs(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { + fn header_proofs(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { unimplemented!() } // Receive a set of transactions to relay. - fn relay_transactions(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { + fn relay_transactions(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { unimplemented!() } } @@ -251,7 +288,7 @@ impl NetworkProtocolHandler for LightProtocol { fn read(&self, io: &NetworkContext, peer: &PeerId, packet_id: u8, data: &[u8]) { let rlp = UntrustedRlp::new(data); - match packet_id { + let res = match packet_id { packet::STATUS => self.status(peer, io, rlp), packet::ANNOUNCE => self.announcement(peer, io, rlp), @@ -273,8 +310,21 @@ impl NetworkProtocolHandler for LightProtocol { packet::SEND_TRANSACTIONS => self.relay_transactions(peer, io, rlp), other => { - debug!(target: "les", "Disconnecting peer {} on unexpected packet {}", peer, other); - io.disconnect_peer(*peer); + Err(Error::UnrecognizedPacket(other)) + } + }; + + if let Err(e) = res { + match e.punishment() { + Punishment::None => {} + Punishment::Disconnect => { + debug!(target: "les", "Disconnecting peer {}: {}", peer, e); + io.disconnect_peer(*peer) + } + Punishment::Disable => { + debug!(target: "les", "Disabling peer {}: {}", peer, e); + io.disable_peer(*peer) + } } } } diff --git a/ethcore/light/src/net/status.rs b/ethcore/light/src/net/status.rs index c48b73234..1cac14845 100644 --- a/ethcore/light/src/net/status.rs +++ b/ethcore/light/src/net/status.rs @@ -172,7 +172,7 @@ pub struct Status { /// Number of the best block. pub head_num: u64, /// Genesis hash - pub genesis_hash: Option, + pub genesis_hash: H256, /// Last announced chain head and reorg depth to common ancestor. pub last_head: Option<(H256, u64)>, } @@ -182,7 +182,7 @@ pub struct Status { pub struct Capabilities { /// Whether this peer can serve headers pub serve_headers: bool, - /// Earliest block number it can serve chain requests for. + /// Earliest block number it can serve block/receipt requests for. pub serve_chain_since: Option, /// Earliest block number it can serve state requests for. pub serve_state_since: Option, @@ -193,7 +193,7 @@ pub struct Capabilities { impl Default for Capabilities { fn default() -> Self { Capabilities { - serve_headers: false, + serve_headers: true, serve_chain_since: None, serve_state_since: None, tx_relay: false, @@ -218,7 +218,7 @@ pub fn parse_handshake(rlp: UntrustedRlp) -> Result<(Status, Capabilities, FlowP head_td: try!(parser.expect(Key::HeadTD)), head_hash: try!(parser.expect(Key::HeadHash)), head_num: try!(parser.expect(Key::HeadNum)), - genesis_hash: parser.expect(Key::GenesisHash).ok(), + genesis_hash: try!(parser.expect(Key::GenesisHash)), last_head: None, }; @@ -246,10 +246,7 @@ pub fn write_handshake(status: &Status, capabilities: &Capabilities, flow_params pairs.push(encode_pair(Key::HeadTD, &status.head_td)); pairs.push(encode_pair(Key::HeadHash, &status.head_hash)); pairs.push(encode_pair(Key::HeadNum, &status.head_num)); - - if let Some(ref genesis_hash) = status.genesis_hash { - pairs.push(encode_pair(Key::GenesisHash, genesis_hash)); - } + pairs.push(encode_pair(Key::GenesisHash, &status.genesis_hash)); if capabilities.serve_headers { pairs.push(encode_flag(Key::ServeHeaders)); @@ -380,7 +377,7 @@ mod tests { head_td: U256::default(), head_hash: H256::default(), head_num: 10, - genesis_hash: Some(H256::zero()), + genesis_hash: H256::zero(), last_head: None, }; @@ -415,7 +412,7 @@ mod tests { head_td: U256::default(), head_hash: H256::default(), head_num: 10, - genesis_hash: None, + genesis_hash: H256::zero(), last_head: None, }; @@ -450,7 +447,7 @@ mod tests { head_td: U256::default(), head_hash: H256::default(), head_num: 10, - genesis_hash: None, + genesis_hash: H256::zero(), last_head: None, }; diff --git a/ethcore/light/src/provider.rs b/ethcore/light/src/provider.rs index d6458bedd..b1625f95f 100644 --- a/ethcore/light/src/provider.rs +++ b/ethcore/light/src/provider.rs @@ -19,7 +19,7 @@ use ethcore::transaction::SignedTransaction; use ethcore::blockchain_info::BlockChainInfo; -use util::Bytes; +use util::{Bytes, H256}; use request; @@ -33,6 +33,12 @@ pub trait Provider: Send + Sync { /// Provide current blockchain info. fn chain_info(&self) -> BlockChainInfo; + /// Find the depth of a common ancestor between two blocks. + fn reorg_depth(&self, a: &H256, b: &H256) -> Option; + + /// Earliest state. + fn earliest_state(&self) -> Option; + /// Provide a list of headers starting at the requested block, /// possibly in reverse and skipping `skip` at a time. ///