diff --git a/ethcore/light/src/net/context.rs b/ethcore/light/src/net/context.rs
new file mode 100644
index 000000000..f2d5ab907
--- /dev/null
+++ b/ethcore/light/src/net/context.rs
@@ -0,0 +1,113 @@
+// 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 .
+
+//! I/O and event context generalizations.
+
+use network::{NetworkContext, PeerId};
+
+use super::{Announcement, LightProtocol, ReqId};
+use super::error::Error;
+use request::Request;
+
+/// An I/O context which allows sending and receiving packets as well as
+/// disconnecting peers. This is used as a generalization of the portions
+/// of a p2p network which the light protocol structure makes use of.
+pub trait IoContext {
+ /// Send a packet to a specific peer.
+ fn send(&self, peer: PeerId, packet_id: u8, packet_body: Vec);
+
+ /// Respond to a peer's message. Only works if this context is a byproduct
+ /// of a packet handler.
+ fn respond(&self, packet_id: u8, packet_body: Vec);
+
+ /// Disconnect a peer.
+ fn disconnect_peer(&self, peer: PeerId);
+
+ /// Disable a peer -- this is a disconnect + a time-out.
+ fn disable_peer(&self, peer: PeerId);
+}
+
+impl<'a> IoContext for NetworkContext<'a> {
+ fn send(&self, peer: PeerId, packet_id: u8, packet_body: Vec) {
+ if let Err(e) = self.send(peer, packet_id, packet_body) {
+ debug!(target: "les", "Error sending packet to peer {}: {}", peer, e);
+ }
+ }
+
+ fn respond(&self, packet_id: u8, packet_body: Vec) {
+ if let Err(e) = self.respond(packet_id, packet_body) {
+ debug!(target: "les", "Error responding to peer message: {}", e);
+ }
+ }
+
+ fn disconnect_peer(&self, peer: PeerId) {
+ NetworkContext::disconnect_peer(self, peer);
+ }
+
+ fn disable_peer(&self, peer: PeerId) {
+ NetworkContext::disable_peer(self, peer);
+ }
+}
+
+/// Context for a protocol event.
+pub trait EventContext {
+ /// Get the peer relevant to the event e.g. message sender,
+ /// disconnected/connected peer.
+ fn peer(&self) -> PeerId;
+
+ /// Make a request from a peer.
+ fn request_from(&self, peer: PeerId, request: Request) -> Result;
+
+ /// Make an announcement of new capabilities to the rest of the peers.
+ // TODO: maybe just put this on a timer in LightProtocol?
+ fn make_announcement(&self, announcement: Announcement);
+
+ /// Disconnect a peer.
+ fn disconnect_peer(&self, peer: PeerId);
+
+ /// Disable a peer.
+ fn disable_peer(&self, peer: PeerId);
+}
+
+/// Concrete implementation of `EventContext` over the light protocol struct and
+/// an io context.
+pub struct Ctx<'a> {
+ /// Io context to enable immediate response to events.
+ pub io: &'a IoContext,
+ /// Protocol implementation.
+ pub proto: &'a LightProtocol,
+ /// Relevant peer for event.
+ pub peer: PeerId,
+}
+
+impl<'a> EventContext for Ctx<'a> {
+ fn peer(&self) -> PeerId { self.peer }
+ fn request_from(&self, peer: PeerId, request: Request) -> Result {
+ self.proto.request_from(self.io, &peer, request)
+ }
+
+ fn make_announcement(&self, announcement: Announcement) {
+ self.proto.make_announcement(self.io, announcement);
+ }
+
+ fn disconnect_peer(&self, peer: PeerId) {
+ self.io.disconnect_peer(peer);
+ }
+
+ fn disable_peer(&self, peer: PeerId) {
+ self.io.disable_peer(peer);
+ }
+}
\ No newline at end of file
diff --git a/ethcore/light/src/net/mod.rs b/ethcore/light/src/net/mod.rs
index 965e46db6..57d091f75 100644
--- a/ethcore/light/src/net/mod.rs
+++ b/ethcore/light/src/net/mod.rs
@@ -44,6 +44,9 @@ mod context;
mod error;
mod status;
+#[cfg(test)]
+mod tests;
+
pub use self::status::{Status, Capabilities, Announcement, NetworkId};
const TIMEOUT: TimerToken = 0;
@@ -181,8 +184,6 @@ struct Requested {
/// Protocol parameters.
pub struct Params {
- /// Genesis hash.
- pub genesis_hash: H256,
/// Network id.
pub network_id: NetworkId,
/// Buffer flow parameters.
@@ -217,9 +218,10 @@ pub struct LightProtocol {
impl LightProtocol {
/// Create a new instance of the protocol manager.
pub fn new(provider: Box, params: Params) -> Self {
+ let genesis_hash = provider.chain_info().genesis_hash;
LightProtocol {
provider: provider,
- genesis_hash: params.genesis_hash,
+ genesis_hash: genesis_hash,
network_id: params.network_id,
pending_peers: RwLock::new(HashMap::new()),
peers: RwLock::new(HashMap::new()),
diff --git a/ethcore/light/src/net/tests/mod.rs b/ethcore/light/src/net/tests/mod.rs
new file mode 100644
index 000000000..7db05542e
--- /dev/null
+++ b/ethcore/light/src/net/tests/mod.rs
@@ -0,0 +1,291 @@
+// 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 .
+
+//! Tests for the `LightProtocol` implementation.
+//! These don't test of the higher level logic on top of
+
+use ethcore::blockchain_info::BlockChainInfo;
+use ethcore::client::{BlockChainClient, EachBlockWith, TestBlockChainClient};
+use ethcore::ids::BlockID;
+use ethcore::transaction::SignedTransaction;
+use network::PeerId;
+
+use net::buffer_flow::FlowParams;
+use net::context::IoContext;
+use net::status::{Capabilities, Status, NetworkId, write_handshake};
+use net::{encode_request, LightProtocol, Params, packet};
+use provider::Provider;
+use request::{self, Request, Headers};
+
+use rlp::*;
+use util::{Bytes, H256};
+
+use std::sync::Arc;
+
+// expected result from a call.
+#[derive(Debug, PartialEq, Eq)]
+enum Expect {
+ /// Expect to have message sent to peer.
+ Send(PeerId, u8, Vec),
+ /// Expect this response.
+ Respond(u8, Vec),
+ /// Expect a punishment (disconnect/disable)
+ Punish(PeerId),
+ /// Expect nothing.
+ Nothing,
+}
+
+impl IoContext for Expect {
+ fn send(&self, peer: PeerId, packet_id: u8, packet_body: Vec) {
+ assert_eq!(self, &Expect::Send(peer, packet_id, packet_body));
+ }
+
+ fn respond(&self, packet_id: u8, packet_body: Vec) {
+ assert_eq!(self, &Expect::Respond(packet_id, packet_body));
+ }
+
+ fn disconnect_peer(&self, peer: PeerId) {
+ assert_eq!(self, &Expect::Punish(peer));
+ }
+
+ fn disable_peer(&self, peer: PeerId) {
+ assert_eq!(self, &Expect::Punish(peer));
+ }
+}
+
+// can't implement directly for Arc due to cross-crate orphan rules.
+struct TestProvider(Arc);
+
+struct TestProviderInner {
+ client: TestBlockChainClient,
+}
+
+impl Provider for TestProvider {
+ fn chain_info(&self) -> BlockChainInfo {
+ self.0.client.chain_info()
+ }
+
+ fn reorg_depth(&self, a: &H256, b: &H256) -> Option {
+ self.0.client.tree_route(a, b).map(|route| route.index as u64)
+ }
+
+ fn earliest_state(&self) -> Option {
+ None
+ }
+
+ fn block_headers(&self, req: request::Headers) -> Vec {
+ let best_num = self.0.client.chain_info().best_block_number;
+ let start_num = req.block_num;
+
+ match self.0.client.block_hash(BlockID::Number(req.block_num)) {
+ Some(hash) if hash == req.block_hash => {}
+ _=> {
+ trace!(target: "les_provider", "unknown/non-canonical start block in header request: {:?}", (req.block_num, req.block_hash));
+ return vec![]
+ }
+ }
+
+ (0u64..req.max as u64)
+ .map(|x: u64| x.saturating_mul(req.skip + 1))
+ .take_while(|x| if req.reverse { x < &start_num } else { best_num - start_num >= *x })
+ .map(|x| if req.reverse { start_num - x } else { start_num + x })
+ .map(|x| self.0.client.block_header(BlockID::Number(x)))
+ .take_while(|x| x.is_some())
+ .flat_map(|x| x)
+ .collect()
+ }
+
+ fn block_bodies(&self, req: request::Bodies) -> Vec {
+ req.block_hashes.into_iter()
+ .map(|hash| self.0.client.block_body(BlockID::Hash(hash)))
+ .map(|body| body.unwrap_or_else(|| ::rlp::EMPTY_LIST_RLP.to_vec()))
+ .collect()
+ }
+
+ fn receipts(&self, req: request::Receipts) -> Vec {
+ req.block_hashes.into_iter()
+ .map(|hash| self.0.client.block_receipts(&hash))
+ .map(|receipts| receipts.unwrap_or_else(|| ::rlp::EMPTY_LIST_RLP.to_vec()))
+ .collect()
+ }
+
+ fn proofs(&self, req: request::StateProofs) -> Vec {
+ req.requests.into_iter().map(|_| ::rlp::EMPTY_LIST_RLP.to_vec()).collect()
+ }
+
+ fn contract_code(&self, req: request::ContractCodes) -> Vec {
+ req.code_requests.into_iter().map(|_| Vec::new()).collect()
+ }
+
+ fn header_proofs(&self, req: request::HeaderProofs) -> Vec {
+ req.requests.into_iter().map(|_| ::rlp::EMPTY_LIST_RLP.to_vec()).collect()
+ }
+
+ fn pending_transactions(&self) -> Vec {
+ self.0.client.pending_transactions()
+ }
+}
+
+fn make_flow_params() -> FlowParams {
+ FlowParams::new(5_000_000.into(), Default::default(), 100_000.into())
+}
+
+fn capabilities() -> Capabilities {
+ Capabilities {
+ serve_headers: true,
+ serve_chain_since: Some(1),
+ serve_state_since: Some(1),
+ tx_relay: true,
+ }
+}
+
+// helper for setting up the protocol handler and provider.
+fn setup(flow_params: FlowParams, capabilities: Capabilities) -> (Arc, LightProtocol) {
+ let provider = Arc::new(TestProviderInner {
+ client: TestBlockChainClient::new(),
+ });
+
+ let proto = LightProtocol::new(Box::new(TestProvider(provider.clone())), Params {
+ network_id: NetworkId::Testnet,
+ flow_params: flow_params,
+ capabilities: capabilities,
+ });
+
+ (provider, proto)
+}
+
+fn status(chain_info: BlockChainInfo) -> Status {
+ Status {
+ protocol_version: ::net::PROTOCOL_VERSION,
+ network_id: NetworkId::Testnet,
+ 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,
+ last_head: None,
+ }
+}
+
+#[test]
+fn handshake_expected() {
+ let flow_params = make_flow_params();
+ let capabilities = capabilities();
+
+ let (provider, proto) = setup(flow_params.clone(), capabilities.clone());
+
+ let status = status(provider.client.chain_info());
+
+ let packet_body = write_handshake(&status, &capabilities, &flow_params);
+
+ proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body));
+}
+
+#[test]
+#[should_panic]
+fn genesis_mismatch() {
+ let flow_params = make_flow_params();
+ let capabilities = capabilities();
+
+ let (provider, proto) = setup(flow_params.clone(), capabilities.clone());
+
+ let mut status = status(provider.client.chain_info());
+ status.genesis_hash = H256::default();
+
+ let packet_body = write_handshake(&status, &capabilities, &flow_params);
+
+ proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body));
+}
+
+#[test]
+fn buffer_overflow() {
+ let flow_params = make_flow_params();
+ let capabilities = capabilities();
+
+ let (provider, proto) = setup(flow_params.clone(), capabilities.clone());
+
+ let status = status(provider.client.chain_info());
+
+ {
+ let packet_body = write_handshake(&status, &capabilities, &flow_params);
+ proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body));
+ }
+
+ {
+ let my_status = write_handshake(&status, &capabilities, &flow_params);
+ proto.handle_packet(&Expect::Nothing, &1, packet::STATUS, &my_status);
+ }
+
+ // 1000 requests is far too many for the default flow params.
+ let request = encode_request(&Request::Headers(Headers {
+ block_num: 1,
+ block_hash: provider.client.chain_info().genesis_hash,
+ max: 1000,
+ skip: 0,
+ reverse: false,
+ }), 111);
+
+ proto.handle_packet(&Expect::Punish(1), &1, packet::GET_BLOCK_HEADERS, &request);
+}
+
+#[test]
+fn get_block_headers() {
+ let flow_params = FlowParams::new(5_000_000.into(), Default::default(), 0.into());
+ let capabilities = capabilities();
+
+ let (provider, proto) = setup(flow_params.clone(), capabilities.clone());
+
+ let cur_status = status(provider.client.chain_info());
+ let my_status = write_handshake(&cur_status, &capabilities, &flow_params);
+
+ provider.client.add_blocks(100, EachBlockWith::Nothing);
+
+ let cur_status = status(provider.client.chain_info());
+
+ {
+ let packet_body = write_handshake(&cur_status, &capabilities, &flow_params);
+ proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body));
+ proto.handle_packet(&Expect::Nothing, &1, packet::STATUS, &my_status);
+ }
+
+ let request = Headers {
+ block_num: 1,
+ block_hash: provider.client.block_hash(BlockID::Number(1)).unwrap(),
+ max: 10,
+ skip: 0,
+ reverse: false,
+ };
+ let req_id = 111;
+
+ let request_body = encode_request(&Request::Headers(request.clone()), req_id);
+ let response = {
+ let headers: Vec<_> = (0..10).map(|i| provider.client.block_header(BlockID::Number(i + 1)).unwrap()).collect();
+ assert_eq!(headers.len(), 10);
+
+ let new_buf = *flow_params.limit() - flow_params.compute_cost(request::Kind::Headers, 10);
+
+ let mut response_stream = RlpStream::new_list(12);
+
+ response_stream.append(&req_id).append(&new_buf);
+ for header in headers {
+ response_stream.append_raw(&header, 1);
+ }
+
+ response_stream.out()
+ };
+
+ let expected = Expect::Respond(packet::BLOCK_HEADERS, response);
+ proto.handle_packet(&expected, &1, packet::GET_BLOCK_HEADERS, &request_body);
+}
\ No newline at end of file
diff --git a/ethcore/light/src/provider.rs b/ethcore/light/src/provider.rs
index fe7156b58..9446aa3f6 100644
--- a/ethcore/light/src/provider.rs
+++ b/ethcore/light/src/provider.rs
@@ -108,8 +108,8 @@ impl Provider for T {
}
(0u64..req.max as u64)
- .map(|x: u64| x.saturating_mul(req.skip))
- .take_while(|x| if req.reverse { x < &start_num } else { best_num - start_num < *x })
+ .map(|x: u64| x.saturating_mul(req.skip + 1))
+ .take_while(|x| if req.reverse { x < &start_num } else { best_num - start_num >= *x })
.map(|x| if req.reverse { start_num - x } else { start_num + x })
.map(|x| self.block_header(BlockID::Number(x)))
.take_while(|x| x.is_some())
diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs
index dd00db7ec..37674e29e 100644
--- a/ethcore/src/client/test_client.rs
+++ b/ethcore/src/client/test_client.rs
@@ -92,8 +92,8 @@ pub struct TestBlockChainClient {
pub first_block: RwLock