From b38d95328d739828310eb9ed0d7dbc80821792e1 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Wed, 7 Dec 2016 10:50:18 +0100 Subject: [PATCH 01/34] light: conditional compilation --- Cargo.lock | 1 - Cargo.toml | 1 + ethcore/Cargo.toml | 1 + ethcore/src/client/client.rs | 5 +++-- ethcore/src/client/mod.rs | 5 ++++- ethcore/src/client/traits.rs | 1 + ethcore/src/state/mod.rs | 3 +++ 7 files changed, 13 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cca8b014d..7dc6f7bcb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -298,7 +298,6 @@ dependencies = [ "ethcore-ipc 1.4.0", "ethcore-ipc-codegen 1.4.0", "ethcore-ipc-nano 1.4.0", - "ethcore-network 1.5.0", "ethcore-util 1.5.0", "ethjson 0.1.0", "ethkey 0.2.0", diff --git a/Cargo.toml b/Cargo.toml index 078d2916c..9d6848b6a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -80,6 +80,7 @@ ethstore-cli = ["ethcore/ethstore-cli"] evm-debug = ["ethcore/evm-debug"] evm-debug-tests = ["ethcore/evm-debug-tests"] slow-blocks = ["ethcore/slow-blocks"] +light = ["ethcore/light"] [[bin]] path = "parity/main.rs" diff --git a/ethcore/Cargo.toml b/ethcore/Cargo.toml index bd87c422f..2c3d946f4 100644 --- a/ethcore/Cargo.toml +++ b/ethcore/Cargo.toml @@ -58,5 +58,6 @@ dev = ["clippy"] default = [] benches = [] ipc = [] +light = [] ethkey-cli = ["ethkey/cli"] ethstore-cli = ["ethstore/cli"] diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 21c5a2366..ee72ea8b8 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -52,7 +52,7 @@ use blockchain::{BlockChain, BlockProvider, TreeRoute, ImportRoute}; use client::{ BlockID, TransactionID, UncleID, TraceId, ClientConfig, BlockChainClient, MiningBlockChainClient, TraceFilter, CallAnalytics, BlockImportError, Mode, - ChainNotify, PruningInfo, ProvingBlockChainClient, + ChainNotify, PruningInfo, }; use client::Error as ClientError; use env_info::EnvInfo; @@ -1377,7 +1377,8 @@ impl MayPanic for Client { } } -impl ProvingBlockChainClient for Client { +#[cfg(feature = "light")] +impl ::client::ProvingBlockChainClient for Client { fn prove_storage(&self, key1: H256, key2: H256, from_level: u32, id: BlockID) -> Vec { self.state_at(id) .and_then(move |state| state.prove_storage(key1, key2, from_level).ok()) diff --git a/ethcore/src/client/mod.rs b/ethcore/src/client/mod.rs index af4daece6..beab88e8a 100644 --- a/ethcore/src/client/mod.rs +++ b/ethcore/src/client/mod.rs @@ -27,7 +27,10 @@ pub use self::config::{Mode, ClientConfig, DatabaseCompactionProfile, BlockChain pub use self::error::Error; pub use self::test_client::{TestBlockChainClient, EachBlockWith}; pub use self::chain_notify::ChainNotify; -pub use self::traits::{BlockChainClient, MiningBlockChainClient, ProvingBlockChainClient}; +pub use self::traits::{BlockChainClient, MiningBlockChainClient}; + +#[cfg(feature = "light")] +pub use self::traits::ProvingBlockChainClient; pub use types::ids::*; pub use types::trace_filter::Filter as TraceFilter; diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index 7bf17279c..3eed9aa5d 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -278,6 +278,7 @@ pub trait MiningBlockChainClient: BlockChainClient { } /// Extended client interface for providing proofs of the state. +#[cfg(feature = "light")] pub trait ProvingBlockChainClient: BlockChainClient { /// Prove account storage at a specific block id. /// diff --git a/ethcore/src/state/mod.rs b/ethcore/src/state/mod.rs index b3d63d0ae..d39481048 100644 --- a/ethcore/src/state/mod.rs +++ b/ethcore/src/state/mod.rs @@ -31,6 +31,8 @@ use transaction::SignedTransaction; use state_db::StateDB; use util::*; + +#[cfg(feature = "light")] use util::trie::recorder::{Recorder, BasicRecorder as TrieRecorder}; mod account; @@ -762,6 +764,7 @@ impl State { } // LES state proof implementations. +#[cfg(feature = "light")] impl State { /// Prove an account's existence or nonexistence in the state trie. /// Returns a merkle proof of the account's trie node with all nodes before `from_level` From cdc758368a74c00d7d281a0cc7cd5d2888004c47 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Wed, 7 Dec 2016 13:52:45 +0100 Subject: [PATCH 02/34] les: flesh out event handler --- Cargo.lock | 16 ++++ Cargo.toml | 4 +- ethcore/light/Cargo.toml | 2 +- ethcore/light/src/net/mod.rs | 145 +++++++++++++++++++++++++---------- sync/Cargo.toml | 2 + sync/src/lib.rs | 4 + 6 files changed, 129 insertions(+), 44 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7dc6f7bcb..b00701f32 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -456,6 +456,21 @@ dependencies = [ "semver 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ethcore-light" +version = "1.5.0" +dependencies = [ + "ethcore 1.5.0", + "ethcore-io 1.5.0", + "ethcore-ipc 1.4.0", + "ethcore-ipc-codegen 1.4.0", + "ethcore-network 1.5.0", + "ethcore-util 1.5.0", + "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "rlp 0.1.0", + "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "ethcore-logger" version = "1.5.0" @@ -664,6 +679,7 @@ dependencies = [ "ethcore-ipc 1.4.0", "ethcore-ipc-codegen 1.4.0", "ethcore-ipc-nano 1.4.0", + "ethcore-light 1.5.0", "ethcore-network 1.5.0", "ethcore-util 1.5.0", "heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index 9d6848b6a..4730cef0d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,7 +55,7 @@ winapi = "0.2" daemonize = "0.2" [features] -default = ["ui-precompiled"] +default = ["ui-precompiled", "light"] ui = [ "dapps", @@ -80,7 +80,7 @@ ethstore-cli = ["ethcore/ethstore-cli"] evm-debug = ["ethcore/evm-debug"] evm-debug-tests = ["ethcore/evm-debug-tests"] slow-blocks = ["ethcore/slow-blocks"] -light = ["ethcore/light"] +light = ["ethcore/light", "ethsync/light"] [[bin]] path = "parity/main.rs" diff --git a/ethcore/light/Cargo.toml b/ethcore/light/Cargo.toml index 74400d7ab..9440bbf88 100644 --- a/ethcore/light/Cargo.toml +++ b/ethcore/light/Cargo.toml @@ -12,7 +12,7 @@ build = "build.rs" [dependencies] log = "0.3" -ethcore = { path = ".." } +ethcore = { path = "..", features = ["light"] } ethcore-util = { path = "../../util" } ethcore-network = { path = "../../util/network" } ethcore-io = { path = "../../util/io" } diff --git a/ethcore/light/src/net/mod.rs b/ethcore/light/src/net/mod.rs index a1b3b30b0..e729b4361 100644 --- a/ethcore/light/src/net/mod.rs +++ b/ethcore/light/src/net/mod.rs @@ -20,11 +20,13 @@ //! See https://github.com/ethcore/parity/wiki/Light-Ethereum-Subprotocol-(LES) use ethcore::transaction::SignedTransaction; +use ethcore::receipt::Receipt; + use io::TimerToken; use network::{NetworkProtocolHandler, NetworkContext, NetworkError, PeerId}; use rlp::{RlpStream, Stream, UntrustedRlp, View}; use util::hash::H256; -use util::{Mutex, RwLock, U256}; +use util::{Bytes, Mutex, RwLock, U256}; use time::SteadyTime; use std::collections::{HashMap, HashSet}; @@ -134,16 +136,50 @@ impl Peer { } } +/// Context for a network event. +pub struct EventContext<'a> { + /// Protocol implementation. + pub proto: &'a LightProtocol, + /// Network context to enable immediate response to + /// events. + pub io: &'a NetworkContext<'a>, + /// Relevant peer for event. + pub peer: PeerId, +} + /// An LES event handler. +/// +/// Each handler function takes a context which describes the relevant peer +/// and gives references to the IO layer and protocol structure so new messages +/// can be dispatched immediately. +/// +/// Request responses are not guaranteed to be complete or valid, but passed IDs will be correct. +/// Response handlers are not given a copy of the original request; it is assumed +/// that relevant data will be stored by interested handlers. pub trait Handler: Send + Sync { /// Called when a peer connects. - fn on_connect(&self, _id: PeerId, _status: &Status, _capabilities: &Capabilities) { } - /// Called when a peer disconnects - fn on_disconnect(&self, _id: PeerId) { } + fn on_connect(&self, _ctx: EventContext, _status: &Status, _capabilities: &Capabilities) { } + /// Called when a peer disconnects, with a list of unfulfilled request IDs as + /// of yet. + fn on_disconnect(&self, _ctx: EventContext, _unfulfilled: &[ReqId]) { } /// Called when a peer makes an announcement. - fn on_announcement(&self, _id: PeerId, _announcement: &Announcement) { } + fn on_announcement(&self, _ctx: EventContext, _announcement: &Announcement) { } /// Called when a peer requests relay of some transactions. - fn on_transactions(&self, _id: PeerId, _relay: &[SignedTransaction]) { } + fn on_transactions(&self, _ctx: EventContext, _relay: &[SignedTransaction]) { } + /// Called when a peer responds with block bodies. + fn on_block_bodies(&self, _ctx: EventContext, _req_id: ReqId, _bodies: &[Bytes]) { } + /// Called when a peer responds with block headers. + fn on_block_headers(&self, _ctx: EventContext, _req_id: ReqId, _headers: &[Bytes]) { } + /// Called when a peer responds with block receipts. + fn on_receipts(&self, _ctx: EventContext, _req_id: ReqId, _receipts: &[Vec]) { } + /// Called when a peer responds with state proofs. Each proof is a series of trie + /// nodes in ascending order by distance from the root. + fn on_state_proofs(&self, _ctx: EventContext, _req_id: ReqId, _proofs: &[Vec]) { } + /// Called when a peer responds with contract code. + fn on_code(&self, _ctx: EventContext, _req_id: ReqId, _codes: &[Bytes]) { } + /// Called when a peer responds with header proofs. Each proof is a series of trie + /// nodes is ascending order by distance from the root. + fn on_header_proofs(&self, _ctx: EventContext, _req_id: ReqId, _proofs: &[Vec]) { } } // a request and the time it was made. @@ -290,7 +326,8 @@ impl LightProtocol { /// Add an event handler. /// Ownership will be transferred to the protocol structure, /// and the handler will be kept alive as long as it is. - /// These are intended to be added at the beginning of the + /// These are intended to be added when the protocol structure + /// is initialized as a means of customizing its behavior. pub fn add_handler(&mut self, handler: Box) { self.handlers.push(handler); } @@ -313,12 +350,23 @@ impl LightProtocol { } // called when a peer disconnects. - fn on_disconnect(&self, peer: PeerId) { - // TODO: reassign all requests assigned to this peer. + fn on_disconnect(&self, peer: PeerId, io: &NetworkContext) { self.pending_peers.write().remove(&peer); - if self.peers.write().remove(&peer).is_some() { + if let Some(peer_info) = self.peers.write().remove(&peer) { + let unfulfilled: Vec<_> = peer_info.into_inner().current_asking.into_iter().map(ReqId).collect(); + { + let mut pending = self.pending_requests.write(); + for &ReqId(ref inner) in &unfulfilled { + pending.remove(inner); + } + } + for handler in &self.handlers { - handler.on_disconnect(peer) + handler.on_disconnect(EventContext { + peer: peer, + io: io, + proto: self, + }, &unfulfilled) } } } @@ -350,7 +398,7 @@ impl LightProtocol { } // Handle status message from peer. - fn status(&self, peer: &PeerId, data: UntrustedRlp) -> Result<(), Error> { + fn status(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { let pending = match self.pending_peers.write().remove(peer) { Some(pending) => pending, None => { @@ -377,45 +425,56 @@ impl LightProtocol { })); for handler in &self.handlers { - handler.on_connect(*peer, &status, &capabilities) + handler.on_connect(EventContext { + peer: *peer, + io: io, + proto: self, + }, &status, &capabilities) } Ok(()) } // Handle an announcement. - fn announcement(&self, peer: &PeerId, data: UntrustedRlp) -> Result<(), Error> { + fn announcement(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { if !self.peers.read().contains_key(peer) { debug!(target: "les", "Ignoring announcement from unknown peer"); return Ok(()) } let announcement = try!(status::parse_announcement(data)); - let peers = self.peers.read(); - let peer_info = match peers.get(peer) { - Some(info) => info, - None => return Ok(()), - }; - - let mut peer_info = peer_info.lock(); - - // update status. + // scope to ensure locks are dropped before moving into handler-space. { - // TODO: punish peer if they've moved backwards. - let status = &mut peer_info.status; - let last_head = status.head_hash; - status.head_hash = announcement.head_hash; - status.head_td = announcement.head_td; - status.head_num = announcement.head_num; - status.last_head = Some((last_head, announcement.reorg_depth)); + let peers = self.peers.read(); + let peer_info = match peers.get(peer) { + Some(info) => info, + None => return Ok(()), + }; + + let mut peer_info = peer_info.lock(); + + // update status. + { + // TODO: punish peer if they've moved backwards. + let status = &mut peer_info.status; + let last_head = status.head_hash; + status.head_hash = announcement.head_hash; + status.head_td = announcement.head_td; + status.head_num = announcement.head_num; + status.last_head = Some((last_head, announcement.reorg_depth)); + } + + // update capabilities. + peer_info.capabilities.update_from(&announcement); } - // update capabilities. - peer_info.capabilities.update_from(&announcement); - for handler in &self.handlers { - handler.on_announcement(*peer, &announcement); + handler.on_announcement(EventContext { + peer: *peer, + io: io, + proto: self, + }, &announcement); } Ok(()) @@ -736,7 +795,7 @@ impl LightProtocol { } // Receive a set of transactions to relay. - fn relay_transactions(&self, peer: &PeerId, data: UntrustedRlp) -> Result<(), Error> { + fn relay_transactions(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { const MAX_TRANSACTIONS: usize = 256; let txs: Vec<_> = try!(data.iter().take(MAX_TRANSACTIONS).map(|x| x.as_val::()).collect()); @@ -744,7 +803,11 @@ impl LightProtocol { debug!(target: "les", "Received {} transactions to relay from peer {}", txs.len(), peer); for handler in &self.handlers { - handler.on_transactions(*peer, &txs); + handler.on_transactions(EventContext { + peer: *peer, + io: io, + proto: self, + }, &txs); } Ok(()) @@ -761,8 +824,8 @@ impl NetworkProtocolHandler for LightProtocol { // handle the packet let res = match packet_id { - packet::STATUS => self.status(peer, rlp), - packet::ANNOUNCE => self.announcement(peer, rlp), + packet::STATUS => self.status(peer, io, rlp), + packet::ANNOUNCE => self.announcement(peer, io, rlp), packet::GET_BLOCK_HEADERS => self.get_block_headers(peer, io, rlp), packet::BLOCK_HEADERS => self.block_headers(peer, io, rlp), @@ -782,7 +845,7 @@ impl NetworkProtocolHandler for LightProtocol { packet::GET_HEADER_PROOFS => self.get_header_proofs(peer, io, rlp), packet::HEADER_PROOFS => self.header_proofs(peer, io, rlp), - packet::SEND_TRANSACTIONS => self.relay_transactions(peer, rlp), + packet::SEND_TRANSACTIONS => self.relay_transactions(peer, io, rlp), other => { Err(Error::UnrecognizedPacket(other)) @@ -809,8 +872,8 @@ impl NetworkProtocolHandler for LightProtocol { self.on_connect(peer, io); } - fn disconnected(&self, _io: &NetworkContext, peer: &PeerId) { - self.on_disconnect(*peer); + fn disconnected(&self, io: &NetworkContext, peer: &PeerId) { + self.on_disconnect(*peer, io); } fn timeout(&self, _io: &NetworkContext, timer: TimerToken) { diff --git a/sync/Cargo.toml b/sync/Cargo.toml index 738f5f55c..df0f4840d 100644 --- a/sync/Cargo.toml +++ b/sync/Cargo.toml @@ -15,6 +15,7 @@ ethcore-ipc-codegen = { path = "../ipc/codegen" } ethcore-util = { path = "../util" } ethcore-network = { path = "../util/network" } ethcore-io = { path = "../util/io" } +ethcore-light = { path = "../ethcore/light", optional = true } ethcore = { path = "../ethcore" } rlp = { path = "../util/rlp" } clippy = { version = "0.0.103", optional = true} @@ -31,4 +32,5 @@ parking_lot = "0.3" [features] default = ["ipc"] dev = ["clippy", "ethcore/dev", "ethcore-util/dev"] +light = ["ethcore-light"] ipc = [] diff --git a/sync/src/lib.rs b/sync/src/lib.rs index 2061e4e3a..7a4f22fd7 100644 --- a/sync/src/lib.rs +++ b/sync/src/lib.rs @@ -37,6 +37,10 @@ extern crate semver; extern crate parking_lot; extern crate rlp; +#[cfg(feature = "light")] +extern crate ethcore_light as light; + + #[macro_use] extern crate log; #[macro_use] From 10d75b6de04599880a264c07af77fa5d63fa1556 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Wed, 7 Dec 2016 15:27:04 +0100 Subject: [PATCH 03/34] light: implement all response handlers --- ethcore/light/src/net/error.rs | 6 +- ethcore/light/src/net/mod.rs | 164 ++++++++++++++++++++++++++++----- ethcore/light/src/provider.rs | 5 +- 3 files changed, 151 insertions(+), 24 deletions(-) diff --git a/ethcore/light/src/net/error.rs b/ethcore/light/src/net/error.rs index 0855cdeb8..86dbd54ba 100644 --- a/ethcore/light/src/net/error.rs +++ b/ethcore/light/src/net/error.rs @@ -54,6 +54,8 @@ pub enum Error { WrongNetwork, /// Unknown peer. UnknownPeer, + /// Unsolicited response. + UnsolicitedResponse, } impl Error { @@ -67,6 +69,7 @@ impl Error { Error::UnexpectedHandshake => Punishment::Disconnect, Error::WrongNetwork => Punishment::Disable, Error::UnknownPeer => Punishment::Disconnect, + Error::UnsolicitedResponse => Punishment::Disable, } } } @@ -92,7 +95,8 @@ impl fmt::Display for Error { Error::UnrecognizedPacket(code) => write!(f, "Unrecognized packet: 0x{:x}", code), Error::UnexpectedHandshake => write!(f, "Unexpected handshake"), Error::WrongNetwork => write!(f, "Wrong network"), - Error::UnknownPeer => write!(f, "unknown peer"), + Error::UnknownPeer => write!(f, "Unknown peer"), + Error::UnsolicitedResponse => write!(f, "Peer provided unsolicited data"), } } } \ No newline at end of file diff --git a/ethcore/light/src/net/mod.rs b/ethcore/light/src/net/mod.rs index e729b4361..9f08c4ca6 100644 --- a/ethcore/light/src/net/mod.rs +++ b/ethcore/light/src/net/mod.rs @@ -29,7 +29,7 @@ use util::hash::H256; use util::{Bytes, Mutex, RwLock, U256}; use time::SteadyTime; -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; use std::sync::atomic::{AtomicUsize, Ordering}; use provider::Provider; @@ -103,7 +103,6 @@ struct PendingPeer { struct Peer { 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, @@ -137,6 +136,7 @@ impl Peer { } /// Context for a network event. +#[derive(Clone)] pub struct EventContext<'a> { /// Protocol implementation. pub proto: &'a LightProtocol, @@ -177,15 +177,16 @@ pub trait Handler: Send + Sync { fn on_state_proofs(&self, _ctx: EventContext, _req_id: ReqId, _proofs: &[Vec]) { } /// Called when a peer responds with contract code. fn on_code(&self, _ctx: EventContext, _req_id: ReqId, _codes: &[Bytes]) { } - /// Called when a peer responds with header proofs. Each proof is a series of trie - /// nodes is ascending order by distance from the root. - fn on_header_proofs(&self, _ctx: EventContext, _req_id: ReqId, _proofs: &[Vec]) { } + /// Called when a peer responds with header proofs. Each proof is a block header coupled + /// with a series of trie nodes is ascending order by distance from the root. + fn on_header_proofs(&self, _ctx: EventContext, _req_id: ReqId, _proofs: &[(Bytes, Vec)]) { } } -// a request and the time it was made. +// a request, the peer who it was made to, and the time it was made. struct Requested { request: Request, timestamp: SteadyTime, + peer_id: PeerId, } /// Protocol parameters. @@ -280,10 +281,10 @@ impl LightProtocol { try!(io.send(*peer_id, packet_id, packet_data)); - peer.current_asking.insert(req_id); self.pending_requests.write().insert(req_id, Requested { request: request, timestamp: SteadyTime::now(), + peer_id: *peer_id, }); Ok(ReqId(req_id)) @@ -331,6 +332,38 @@ impl LightProtocol { pub fn add_handler(&mut self, handler: Box) { self.handlers.push(handler); } + + // Does the common pre-verification of responses before the response itself + // is actually decoded: + // - check whether peer exists + // - check whether request was made + // - check whether request kinds match + fn pre_verify_response(&self, peer: &PeerId, kind: request::Kind, raw: &UntrustedRlp) -> Result { + let req_id: usize = try!(raw.val_at(0)); + let cur_buffer: U256 = try!(raw.val_at(1)); + + trace!(target: "les", "pre-verifying response from peer {}, kind={:?}", peer, kind); + + match self.pending_requests.write().remove(&req_id) { + None => return Err(Error::UnsolicitedResponse), + Some(requested) => { + if requested.peer_id != *peer || requested.request.kind() != kind { + return Err(Error::UnsolicitedResponse) + } + } + } + + let peers = self.peers.read(); + match peers.get(peer) { + Some(peer_info) => { + let mut peer_info = peer_info.lock(); + let actual_buffer = ::std::cmp::min(cur_buffer, *peer_info.remote_flow.limit()); + peer_info.remote_buffer.update_to(actual_buffer); + Ok(ReqId(req_id)) + } + None => Err(Error::UnknownPeer), // probably only occurs in a race of some kind. + } + } } impl LightProtocol { @@ -352,8 +385,13 @@ impl LightProtocol { // called when a peer disconnects. fn on_disconnect(&self, peer: PeerId, io: &NetworkContext) { self.pending_peers.write().remove(&peer); - if let Some(peer_info) = self.peers.write().remove(&peer) { - let unfulfilled: Vec<_> = peer_info.into_inner().current_asking.into_iter().map(ReqId).collect(); + if self.peers.write().remove(&peer).is_some() { + let unfulfilled: Vec<_> = self.pending_requests.read() + .iter() + .filter(|&(_, r)| r.peer_id == peer) + .map(|(&id, _)| ReqId(id)) + .collect(); + { let mut pending = self.pending_requests.write(); for &ReqId(ref inner) in &unfulfilled { @@ -417,7 +455,6 @@ impl LightProtocol { self.peers.write().insert(*peer, Mutex::new(Peer { local_buffer: self.flow_params.create_buffer(), remote_buffer: flow_params.create_buffer(), - current_asking: HashSet::new(), status: status.clone(), capabilities: capabilities.clone(), remote_flow: flow_params, @@ -530,8 +567,19 @@ impl LightProtocol { } // Receive a response for block headers. - fn block_headers(&self, _: &PeerId, _: &NetworkContext, _: UntrustedRlp) -> Result<(), Error> { - unimplemented!() + fn block_headers(&self, peer: &PeerId, io: &NetworkContext, raw: UntrustedRlp) -> Result<(), Error> { + let req_id = try!(self.pre_verify_response(peer, request::Kind::Headers, &raw)); + let raw_headers: Vec<_> = raw.iter().skip(2).map(|x| x.as_raw().to_owned()).collect(); + + for handler in &self.handlers { + handler.on_block_headers(EventContext { + peer: *peer, + io: io, + proto: self, + }, req_id, &raw_headers); + } + + Ok(()) } // Handle a request for block bodies. @@ -576,8 +624,19 @@ impl LightProtocol { } // Receive a response for block bodies. - fn block_bodies(&self, _: &PeerId, _: &NetworkContext, _: UntrustedRlp) -> Result<(), Error> { - unimplemented!() + fn block_bodies(&self, peer: &PeerId, io: &NetworkContext, raw: UntrustedRlp) -> Result<(), Error> { + let req_id = try!(self.pre_verify_response(peer, request::Kind::Bodies, &raw)); + let raw_bodies: Vec = raw.iter().skip(2).map(|x| x.as_raw().to_owned()).collect(); + + for handler in &self.handlers { + handler.on_block_bodies(EventContext { + peer: *peer, + io: io, + proto: self, + }, req_id, &raw_bodies); + } + + Ok(()) } // Handle a request for receipts. @@ -622,8 +681,23 @@ impl LightProtocol { } // Receive a response for receipts. - fn receipts(&self, _: &PeerId, _: &NetworkContext, _: UntrustedRlp) -> Result<(), Error> { - unimplemented!() + fn receipts(&self, peer: &PeerId, io: &NetworkContext, raw: UntrustedRlp) -> Result<(), Error> { + let req_id = try!(self.pre_verify_response(peer, request::Kind::Receipts, &raw)); + let raw_receipts: Vec> = try!(raw + .iter() + .skip(2) + .map(|x| x.as_val()) + .collect()); + + for handler in &self.handlers { + handler.on_receipts(EventContext { + peer: *peer, + io: io, + proto: self, + }, req_id, &raw_receipts); + } + + Ok(()) } // Handle a request for proofs. @@ -679,8 +753,23 @@ impl LightProtocol { } // Receive a response for proofs. - fn proofs(&self, _: &PeerId, _: &NetworkContext, _: UntrustedRlp) -> Result<(), Error> { - unimplemented!() + fn proofs(&self, peer: &PeerId, io: &NetworkContext, raw: UntrustedRlp) -> Result<(), Error> { + let req_id = try!(self.pre_verify_response(peer, request::Kind::StateProofs, &raw)); + + let raw_proofs: Vec> = raw.iter() + .skip(2) + .map(|x| x.iter().map(|node| node.as_raw().to_owned()).collect()) + .collect(); + + for handler in &self.handlers { + handler.on_state_proofs(EventContext { + peer: *peer, + io: io, + proto: self, + }, req_id, &raw_proofs); + } + + Ok(()) } // Handle a request for contract code. @@ -734,8 +823,20 @@ impl LightProtocol { } // Receive a response for contract code. - fn contract_code(&self, _: &PeerId, _: &NetworkContext, _: UntrustedRlp) -> Result<(), Error> { - unimplemented!() + fn contract_code(&self, peer: &PeerId, io: &NetworkContext, raw: UntrustedRlp) -> Result<(), Error> { + let req_id = try!(self.pre_verify_response(peer, request::Kind::Codes, &raw)); + + let raw_code: Vec = try!(raw.iter().skip(2).map(|x| x.as_val()).collect()); + + for handler in &self.handlers { + handler.on_code(EventContext { + peer: *peer, + io: io, + proto: self, + }, req_id, &raw_code); + } + + Ok(()) } // Handle a request for header proofs @@ -790,8 +891,27 @@ impl LightProtocol { } // Receive a response for header proofs - fn header_proofs(&self, _: &PeerId, _: &NetworkContext, _: UntrustedRlp) -> Result<(), Error> { - unimplemented!() + fn header_proofs(&self, peer: &PeerId, io: &NetworkContext, raw: UntrustedRlp) -> Result<(), Error> { + fn decode_res(raw: UntrustedRlp) -> Result<(Bytes, Vec), ::rlp::DecoderError> { + Ok(( + try!(raw.val_at(0)), + try!(raw.at(1)).iter().map(|x| x.as_raw().to_owned()).collect(), + )) + } + + + let req_id = try!(self.pre_verify_response(peer, request::Kind::HeaderProofs, &raw)); + let raw_proofs: Vec<_> = try!(raw.iter().skip(2).map(decode_res).collect()); + + for handler in &self.handlers { + handler.on_header_proofs(EventContext { + peer: *peer, + io: io, + proto: self, + }, req_id, &raw_proofs); + } + + Ok(()) } // Receive a set of transactions to relay. diff --git a/ethcore/light/src/provider.rs b/ethcore/light/src/provider.rs index 264df0397..fe7156b58 100644 --- a/ethcore/light/src/provider.rs +++ b/ethcore/light/src/provider.rs @@ -71,7 +71,10 @@ pub trait Provider: Send + Sync { /// Each item in the resulting vector is either the raw bytecode or empty. fn contract_code(&self, req: request::ContractCodes) -> Vec; - /// Provide header proofs from the Canonical Hash Tries. + /// Provide header proofs from the Canonical Hash Tries as well as the headers + /// they correspond to -- each element in the returned vector is a 2-tuple. + /// The first element is a block header and the second a merkle proof of + /// the header in a requested CHT. fn header_proofs(&self, req: request::HeaderProofs) -> Vec; /// Provide pending transactions. From 2d1a3ff09154674fa918c48444d7dea7261b17b5 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Wed, 7 Dec 2016 17:52:10 +0100 Subject: [PATCH 04/34] les: generalize network and event contexts with traits --- ethcore/light/src/net/mod.rs | 223 ++++++++++++++++++----------------- 1 file changed, 114 insertions(+), 109 deletions(-) diff --git a/ethcore/light/src/net/mod.rs b/ethcore/light/src/net/mod.rs index 9f08c4ca6..965e46db6 100644 --- a/ethcore/light/src/net/mod.rs +++ b/ethcore/light/src/net/mod.rs @@ -36,9 +36,11 @@ use provider::Provider; use request::{self, Request}; use self::buffer_flow::{Buffer, FlowParams}; +use self::context::{IoContext, EventContext, Ctx}; use self::error::{Error, Punishment}; mod buffer_flow; +mod context; mod error; mod status; @@ -135,18 +137,6 @@ impl Peer { } } -/// Context for a network event. -#[derive(Clone)] -pub struct EventContext<'a> { - /// Protocol implementation. - pub proto: &'a LightProtocol, - /// Network context to enable immediate response to - /// events. - pub io: &'a NetworkContext<'a>, - /// Relevant peer for event. - pub peer: PeerId, -} - /// An LES event handler. /// /// Each handler function takes a context which describes the relevant peer @@ -158,28 +148,28 @@ pub struct EventContext<'a> { /// that relevant data will be stored by interested handlers. pub trait Handler: Send + Sync { /// Called when a peer connects. - fn on_connect(&self, _ctx: EventContext, _status: &Status, _capabilities: &Capabilities) { } + fn on_connect(&self, _ctx: &EventContext, _status: &Status, _capabilities: &Capabilities) { } /// Called when a peer disconnects, with a list of unfulfilled request IDs as /// of yet. - fn on_disconnect(&self, _ctx: EventContext, _unfulfilled: &[ReqId]) { } + fn on_disconnect(&self, _ctx: &EventContext, _unfulfilled: &[ReqId]) { } /// Called when a peer makes an announcement. - fn on_announcement(&self, _ctx: EventContext, _announcement: &Announcement) { } + fn on_announcement(&self, _ctx: &EventContext, _announcement: &Announcement) { } /// Called when a peer requests relay of some transactions. - fn on_transactions(&self, _ctx: EventContext, _relay: &[SignedTransaction]) { } + fn on_transactions(&self, _ctx: &EventContext, _relay: &[SignedTransaction]) { } /// Called when a peer responds with block bodies. - fn on_block_bodies(&self, _ctx: EventContext, _req_id: ReqId, _bodies: &[Bytes]) { } + fn on_block_bodies(&self, _ctx: &EventContext, _req_id: ReqId, _bodies: &[Bytes]) { } /// Called when a peer responds with block headers. - fn on_block_headers(&self, _ctx: EventContext, _req_id: ReqId, _headers: &[Bytes]) { } + fn on_block_headers(&self, _ctx: &EventContext, _req_id: ReqId, _headers: &[Bytes]) { } /// Called when a peer responds with block receipts. - fn on_receipts(&self, _ctx: EventContext, _req_id: ReqId, _receipts: &[Vec]) { } + fn on_receipts(&self, _ctx: &EventContext, _req_id: ReqId, _receipts: &[Vec]) { } /// Called when a peer responds with state proofs. Each proof is a series of trie /// nodes in ascending order by distance from the root. - fn on_state_proofs(&self, _ctx: EventContext, _req_id: ReqId, _proofs: &[Vec]) { } + fn on_state_proofs(&self, _ctx: &EventContext, _req_id: ReqId, _proofs: &[Vec]) { } /// Called when a peer responds with contract code. - fn on_code(&self, _ctx: EventContext, _req_id: ReqId, _codes: &[Bytes]) { } + fn on_code(&self, _ctx: &EventContext, _req_id: ReqId, _codes: &[Bytes]) { } /// Called when a peer responds with header proofs. Each proof is a block header coupled /// with a series of trie nodes is ascending order by distance from the root. - fn on_header_proofs(&self, _ctx: EventContext, _req_id: ReqId, _proofs: &[(Bytes, Vec)]) { } + fn on_header_proofs(&self, _ctx: &EventContext, _req_id: ReqId, _proofs: &[(Bytes, Vec)]) { } } // a request, the peer who it was made to, and the time it was made. @@ -257,7 +247,7 @@ impl LightProtocol { /// insufficient buffer. Does not check capabilities before sending. /// On success, returns a request id which can later be coordinated /// with an event. - pub fn request_from(&self, io: &NetworkContext, peer_id: &PeerId, request: Request) -> Result { + pub fn request_from(&self, io: &IoContext, peer_id: &PeerId, request: Request) -> Result { let peers = self.peers.read(); let peer = try!(peers.get(peer_id).ok_or_else(|| Error::UnknownPeer)); let mut peer = peer.lock(); @@ -279,7 +269,7 @@ impl LightProtocol { request::Kind::HeaderProofs => packet::GET_HEADER_PROOFS, }; - try!(io.send(*peer_id, packet_id, packet_data)); + io.send(*peer_id, packet_id, packet_data); self.pending_requests.write().insert(req_id, Requested { request: request, @@ -292,7 +282,7 @@ impl LightProtocol { /// Make an announcement of new chain head and capabilities to all peers. /// The announcement is expected to be valid. - pub fn make_announcement(&self, io: &NetworkContext, mut announcement: Announcement) { + pub fn make_announcement(&self, io: &IoContext, mut announcement: Announcement) { let mut reorgs_map = HashMap::new(); // update stored capabilities @@ -318,9 +308,7 @@ impl LightProtocol { peer_info.sent_head = announcement.head_hash; announcement.reorg_depth = *reorg_depth; - if let Err(e) = io.send(*peer_id, packet::ANNOUNCE, status::write_announcement(&announcement)) { - debug!(target: "les", "Error sending to peer {}: {}", peer_id, e); - } + io.send(*peer_id, packet::ANNOUNCE, status::write_announcement(&announcement)); } } @@ -364,11 +352,61 @@ impl LightProtocol { None => Err(Error::UnknownPeer), // probably only occurs in a race of some kind. } } + + // handle a packet using the given io context. + fn handle_packet(&self, io: &IoContext, peer: &PeerId, packet_id: u8, data: &[u8]) { + let rlp = UntrustedRlp::new(data); + + // handle the packet + let res = match packet_id { + packet::STATUS => self.status(peer, io, rlp), + packet::ANNOUNCE => self.announcement(peer, io, rlp), + + packet::GET_BLOCK_HEADERS => self.get_block_headers(peer, io, rlp), + packet::BLOCK_HEADERS => self.block_headers(peer, io, rlp), + + packet::GET_BLOCK_BODIES => self.get_block_bodies(peer, io, rlp), + packet::BLOCK_BODIES => self.block_bodies(peer, io, rlp), + + packet::GET_RECEIPTS => self.get_receipts(peer, io, rlp), + packet::RECEIPTS => self.receipts(peer, io, rlp), + + packet::GET_PROOFS => self.get_proofs(peer, io, rlp), + packet::PROOFS => self.proofs(peer, io, rlp), + + packet::GET_CONTRACT_CODES => self.get_contract_code(peer, io, rlp), + packet::CONTRACT_CODES => self.contract_code(peer, io, rlp), + + packet::GET_HEADER_PROOFS => self.get_header_proofs(peer, io, rlp), + packet::HEADER_PROOFS => self.header_proofs(peer, io, rlp), + + packet::SEND_TRANSACTIONS => self.relay_transactions(peer, io, rlp), + + other => { + Err(Error::UnrecognizedPacket(other)) + } + }; + + // if something went wrong, figure out how much to punish the peer. + 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) + } + } + } + } } impl LightProtocol { // called when a peer connects. - fn on_connect(&self, peer: &PeerId, io: &NetworkContext) { + fn on_connect(&self, peer: &PeerId, io: &IoContext) { let peer = *peer; match self.send_status(peer, io) { @@ -383,7 +421,7 @@ impl LightProtocol { } // called when a peer disconnects. - fn on_disconnect(&self, peer: PeerId, io: &NetworkContext) { + fn on_disconnect(&self, peer: PeerId, io: &IoContext) { self.pending_peers.write().remove(&peer); if self.peers.write().remove(&peer).is_some() { let unfulfilled: Vec<_> = self.pending_requests.read() @@ -400,7 +438,7 @@ impl LightProtocol { } for handler in &self.handlers { - handler.on_disconnect(EventContext { + handler.on_disconnect(&Ctx { peer: peer, io: io, proto: self, @@ -410,7 +448,7 @@ impl LightProtocol { } // send status to a peer. - fn send_status(&self, peer: PeerId, io: &NetworkContext) -> Result { + fn send_status(&self, peer: PeerId, io: &IoContext) -> Result { let chain_info = self.provider.chain_info(); // TODO: could update capabilities here. @@ -428,7 +466,7 @@ impl LightProtocol { 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)); + io.send(peer, packet::STATUS, status_packet); Ok(PendingPeer { sent_head: chain_info.best_block_hash, @@ -436,7 +474,7 @@ impl LightProtocol { } // Handle status message from peer. - fn status(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { + fn status(&self, peer: &PeerId, io: &IoContext, data: UntrustedRlp) -> Result<(), Error> { let pending = match self.pending_peers.write().remove(peer) { Some(pending) => pending, None => { @@ -462,7 +500,7 @@ impl LightProtocol { })); for handler in &self.handlers { - handler.on_connect(EventContext { + handler.on_connect(&Ctx { peer: *peer, io: io, proto: self, @@ -473,7 +511,7 @@ impl LightProtocol { } // Handle an announcement. - fn announcement(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { + fn announcement(&self, peer: &PeerId, io: &IoContext, data: UntrustedRlp) -> Result<(), Error> { if !self.peers.read().contains_key(peer) { debug!(target: "les", "Ignoring announcement from unknown peer"); return Ok(()) @@ -507,7 +545,7 @@ impl LightProtocol { } for handler in &self.handlers { - handler.on_announcement(EventContext { + handler.on_announcement(&Ctx { peer: *peer, io: io, proto: self, @@ -518,7 +556,7 @@ impl LightProtocol { } // Handle a request for block headers. - fn get_block_headers(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { + fn get_block_headers(&self, peer: &PeerId, io: &IoContext, data: UntrustedRlp) -> Result<(), Error> { const MAX_HEADERS: usize = 512; let peers = self.peers.read(); @@ -563,16 +601,18 @@ impl LightProtocol { } stream.out() - }).map_err(Into::into) + }); + + Ok(()) } // Receive a response for block headers. - fn block_headers(&self, peer: &PeerId, io: &NetworkContext, raw: UntrustedRlp) -> Result<(), Error> { + fn block_headers(&self, peer: &PeerId, io: &IoContext, raw: UntrustedRlp) -> Result<(), Error> { let req_id = try!(self.pre_verify_response(peer, request::Kind::Headers, &raw)); let raw_headers: Vec<_> = raw.iter().skip(2).map(|x| x.as_raw().to_owned()).collect(); for handler in &self.handlers { - handler.on_block_headers(EventContext { + handler.on_block_headers(&Ctx { peer: *peer, io: io, proto: self, @@ -583,7 +623,7 @@ impl LightProtocol { } // Handle a request for block bodies. - fn get_block_bodies(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { + fn get_block_bodies(&self, peer: &PeerId, io: &IoContext, data: UntrustedRlp) -> Result<(), Error> { const MAX_BODIES: usize = 256; let peers = self.peers.read(); @@ -620,16 +660,18 @@ impl LightProtocol { } stream.out() - }).map_err(Into::into) + }); + + Ok(()) } // Receive a response for block bodies. - fn block_bodies(&self, peer: &PeerId, io: &NetworkContext, raw: UntrustedRlp) -> Result<(), Error> { + fn block_bodies(&self, peer: &PeerId, io: &IoContext, raw: UntrustedRlp) -> Result<(), Error> { let req_id = try!(self.pre_verify_response(peer, request::Kind::Bodies, &raw)); let raw_bodies: Vec = raw.iter().skip(2).map(|x| x.as_raw().to_owned()).collect(); for handler in &self.handlers { - handler.on_block_bodies(EventContext { + handler.on_block_bodies(&Ctx { peer: *peer, io: io, proto: self, @@ -640,7 +682,7 @@ impl LightProtocol { } // Handle a request for receipts. - fn get_receipts(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { + fn get_receipts(&self, peer: &PeerId, io: &IoContext, data: UntrustedRlp) -> Result<(), Error> { const MAX_RECEIPTS: usize = 256; let peers = self.peers.read(); @@ -677,11 +719,13 @@ impl LightProtocol { } stream.out() - }).map_err(Into::into) + }); + + Ok(()) } // Receive a response for receipts. - fn receipts(&self, peer: &PeerId, io: &NetworkContext, raw: UntrustedRlp) -> Result<(), Error> { + fn receipts(&self, peer: &PeerId, io: &IoContext, raw: UntrustedRlp) -> Result<(), Error> { let req_id = try!(self.pre_verify_response(peer, request::Kind::Receipts, &raw)); let raw_receipts: Vec> = try!(raw .iter() @@ -690,7 +734,7 @@ impl LightProtocol { .collect()); for handler in &self.handlers { - handler.on_receipts(EventContext { + handler.on_receipts(&Ctx { peer: *peer, io: io, proto: self, @@ -701,7 +745,7 @@ impl LightProtocol { } // Handle a request for proofs. - fn get_proofs(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { + fn get_proofs(&self, peer: &PeerId, io: &IoContext, data: UntrustedRlp) -> Result<(), Error> { const MAX_PROOFS: usize = 128; let peers = self.peers.read(); @@ -749,11 +793,13 @@ impl LightProtocol { } stream.out() - }).map_err(Into::into) + }); + + Ok(()) } // Receive a response for proofs. - fn proofs(&self, peer: &PeerId, io: &NetworkContext, raw: UntrustedRlp) -> Result<(), Error> { + fn proofs(&self, peer: &PeerId, io: &IoContext, raw: UntrustedRlp) -> Result<(), Error> { let req_id = try!(self.pre_verify_response(peer, request::Kind::StateProofs, &raw)); let raw_proofs: Vec> = raw.iter() @@ -762,7 +808,7 @@ impl LightProtocol { .collect(); for handler in &self.handlers { - handler.on_state_proofs(EventContext { + handler.on_state_proofs(&Ctx { peer: *peer, io: io, proto: self, @@ -773,7 +819,7 @@ impl LightProtocol { } // Handle a request for contract code. - fn get_contract_code(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { + fn get_contract_code(&self, peer: &PeerId, io: &IoContext, data: UntrustedRlp) -> Result<(), Error> { const MAX_CODES: usize = 256; let peers = self.peers.read(); @@ -819,17 +865,19 @@ impl LightProtocol { } stream.out() - }).map_err(Into::into) + }); + + Ok(()) } // Receive a response for contract code. - fn contract_code(&self, peer: &PeerId, io: &NetworkContext, raw: UntrustedRlp) -> Result<(), Error> { + fn contract_code(&self, peer: &PeerId, io: &IoContext, raw: UntrustedRlp) -> Result<(), Error> { let req_id = try!(self.pre_verify_response(peer, request::Kind::Codes, &raw)); let raw_code: Vec = try!(raw.iter().skip(2).map(|x| x.as_val()).collect()); for handler in &self.handlers { - handler.on_code(EventContext { + handler.on_code(&Ctx { peer: *peer, io: io, proto: self, @@ -840,7 +888,7 @@ impl LightProtocol { } // Handle a request for header proofs - fn get_header_proofs(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { + fn get_header_proofs(&self, peer: &PeerId, io: &IoContext, data: UntrustedRlp) -> Result<(), Error> { const MAX_PROOFS: usize = 256; let peers = self.peers.read(); @@ -887,11 +935,13 @@ impl LightProtocol { } stream.out() - }).map_err(Into::into) + }); + + Ok(()) } // Receive a response for header proofs - fn header_proofs(&self, peer: &PeerId, io: &NetworkContext, raw: UntrustedRlp) -> Result<(), Error> { + fn header_proofs(&self, peer: &PeerId, io: &IoContext, raw: UntrustedRlp) -> Result<(), Error> { fn decode_res(raw: UntrustedRlp) -> Result<(Bytes, Vec), ::rlp::DecoderError> { Ok(( try!(raw.val_at(0)), @@ -904,7 +954,7 @@ impl LightProtocol { let raw_proofs: Vec<_> = try!(raw.iter().skip(2).map(decode_res).collect()); for handler in &self.handlers { - handler.on_header_proofs(EventContext { + handler.on_header_proofs(&Ctx { peer: *peer, io: io, proto: self, @@ -915,7 +965,7 @@ impl LightProtocol { } // Receive a set of transactions to relay. - fn relay_transactions(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { + fn relay_transactions(&self, peer: &PeerId, io: &IoContext, data: UntrustedRlp) -> Result<(), Error> { const MAX_TRANSACTIONS: usize = 256; let txs: Vec<_> = try!(data.iter().take(MAX_TRANSACTIONS).map(|x| x.as_val::()).collect()); @@ -923,7 +973,7 @@ impl LightProtocol { debug!(target: "les", "Received {} transactions to relay from peer {}", txs.len(), peer); for handler in &self.handlers { - handler.on_transactions(EventContext { + handler.on_transactions(&Ctx { peer: *peer, io: io, proto: self, @@ -940,52 +990,7 @@ impl NetworkProtocolHandler for LightProtocol { } fn read(&self, io: &NetworkContext, peer: &PeerId, packet_id: u8, data: &[u8]) { - let rlp = UntrustedRlp::new(data); - - // handle the packet - let res = match packet_id { - packet::STATUS => self.status(peer, io, rlp), - packet::ANNOUNCE => self.announcement(peer, io, rlp), - - packet::GET_BLOCK_HEADERS => self.get_block_headers(peer, io, rlp), - packet::BLOCK_HEADERS => self.block_headers(peer, io, rlp), - - packet::GET_BLOCK_BODIES => self.get_block_bodies(peer, io, rlp), - packet::BLOCK_BODIES => self.block_bodies(peer, io, rlp), - - packet::GET_RECEIPTS => self.get_receipts(peer, io, rlp), - packet::RECEIPTS => self.receipts(peer, io, rlp), - - packet::GET_PROOFS => self.get_proofs(peer, io, rlp), - packet::PROOFS => self.proofs(peer, io, rlp), - - packet::GET_CONTRACT_CODES => self.get_contract_code(peer, io, rlp), - packet::CONTRACT_CODES => self.contract_code(peer, io, rlp), - - packet::GET_HEADER_PROOFS => self.get_header_proofs(peer, io, rlp), - packet::HEADER_PROOFS => self.header_proofs(peer, io, rlp), - - packet::SEND_TRANSACTIONS => self.relay_transactions(peer, io, rlp), - - other => { - Err(Error::UnrecognizedPacket(other)) - } - }; - - // if something went wrong, figure out how much to punish the peer. - 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) - } - } - } + self.handle_packet(io, peer, packet_id, data); } fn connected(&self, io: &NetworkContext, peer: &PeerId) { From 88e5ed9ea5eb51e2e64bf4ec90c7ac2a57117f79 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Thu, 8 Dec 2016 12:18:43 +0100 Subject: [PATCH 05/34] Allow mocha to use webpack aliasses via babel --- js/.babelrc | 9 +++++++++ js/package.json | 3 ++- js/webpack/test.js | 26 ++++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 js/webpack/test.js diff --git a/js/.babelrc b/js/.babelrc index 2298d98c0..33ac89e23 100644 --- a/js/.babelrc +++ b/js/.babelrc @@ -16,6 +16,15 @@ }, "development": { "plugins": ["react-hot-loader/babel"] + }, + "test": { + "plugins": [ + [ + "babel-plugin-webpack-alias", { + "config": "webpack/test.js" + } + ] + ] } } } diff --git a/js/package.json b/js/package.json index dd4ff1046..f46619a3a 100644 --- a/js/package.json +++ b/js/package.json @@ -40,7 +40,7 @@ "coveralls": "npm run testCoverage && coveralls < coverage/lcov.info", "lint": "eslint --ignore-path .gitignore ./src/", "lint:cached": "eslint --cache --ignore-path .gitignore ./src/", - "test": "mocha 'src/**/*.spec.js'", + "test": "NODE_ENV=test mocha 'src/**/*.spec.js'", "test:coverage": "istanbul cover _mocha -- 'src/**/*.spec.js'", "test:e2e": "mocha 'src/**/*.e2e.js'", "test:npm": "(cd .npmjs && npm i) && node test/npmLibrary && (rm -rf .npmjs/node_modules)", @@ -56,6 +56,7 @@ "babel-plugin-transform-decorators-legacy": "1.3.4", "babel-plugin-transform-react-remove-prop-types": "0.2.11", "babel-plugin-transform-runtime": "6.15.0", + "babel-plugin-webpack-alias": "2.1.2", "babel-polyfill": "6.16.0", "babel-preset-es2015": "6.18.0", "babel-preset-es2015-rollup": "1.2.0", diff --git a/js/webpack/test.js b/js/webpack/test.js new file mode 100644 index 000000000..a0340b46b --- /dev/null +++ b/js/webpack/test.js @@ -0,0 +1,26 @@ +// 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 . + +const path = require('path'); + +module.exports = { + context: path.join(__dirname, '../src'), + resolve: { + alias: { + '~': path.resolve(__dirname, '../src') + } + } +}; From ad8a191e95a88187d03c3ce5a5709358b2d6657e Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Thu, 8 Dec 2016 12:19:03 +0100 Subject: [PATCH 06/34] Adjust specs to show location --- js/src/dapps/localtx/Application/application.spec.js | 2 +- js/src/dapps/localtx/Transaction/transaction.spec.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/js/src/dapps/localtx/Application/application.spec.js b/js/src/dapps/localtx/Application/application.spec.js index 2044b4e14..3ffee343d 100644 --- a/js/src/dapps/localtx/Application/application.spec.js +++ b/js/src/dapps/localtx/Application/application.spec.js @@ -21,7 +21,7 @@ import '../../../environment/tests'; import Application from './application'; -describe('localtx/Application', () => { +describe('dapps/localtx/Application', () => { describe('rendering', () => { it('renders without crashing', () => { const rendered = shallow(); diff --git a/js/src/dapps/localtx/Transaction/transaction.spec.js b/js/src/dapps/localtx/Transaction/transaction.spec.js index 5e9c39147..04f2f8de8 100644 --- a/js/src/dapps/localtx/Transaction/transaction.spec.js +++ b/js/src/dapps/localtx/Transaction/transaction.spec.js @@ -29,7 +29,7 @@ Api.api = { import BigNumber from 'bignumber.js'; import { Transaction, LocalTransaction } from './transaction'; -describe('localtx/Transaction', () => { +describe('dapps/localtx/Transaction', () => { describe('rendering', () => { it('renders without crashing', () => { const transaction = { @@ -51,7 +51,7 @@ describe('localtx/Transaction', () => { }); }); -describe('localtx/LocalTransaction', () => { +describe('dapps/localtx/LocalTransaction', () => { describe('rendering', () => { it('renders without crashing', () => { const rendered = shallow( From 8d16f737952eb4998cd35092749d78c7d870744f Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Thu, 8 Dec 2016 12:20:18 +0100 Subject: [PATCH 07/34] first few LES tests, fix get_header logic bug --- ethcore/light/src/net/context.rs | 113 +++++++++++ ethcore/light/src/net/mod.rs | 8 +- ethcore/light/src/net/tests/mod.rs | 291 +++++++++++++++++++++++++++++ ethcore/light/src/provider.rs | 4 +- ethcore/src/client/test_client.rs | 2 +- 5 files changed, 412 insertions(+), 6 deletions(-) create mode 100644 ethcore/light/src/net/context.rs create mode 100644 ethcore/light/src/net/tests/mod.rs 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>, } -#[derive(Clone)] /// Used for generating test client blocks. +#[derive(Clone)] pub enum EachBlockWith { /// Plain block. Nothing, From b3f7c00780cd4ff9c475afb880a621424d9d62d4 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Thu, 8 Dec 2016 12:22:40 +0100 Subject: [PATCH 08/34] ui/Container tests as per #2289 --- js/src/ui/Container/Title/title.js | 4 -- js/src/ui/Container/Title/title.spec.js | 52 +++++++++++++++++++++++++ js/src/ui/Container/container.spec.js | 38 ++++++++++++++++++ 3 files changed, 90 insertions(+), 4 deletions(-) create mode 100644 js/src/ui/Container/Title/title.spec.js create mode 100644 js/src/ui/Container/container.spec.js diff --git a/js/src/ui/Container/Title/title.js b/js/src/ui/Container/Title/title.js index 485340d2a..1506ecaf6 100644 --- a/js/src/ui/Container/Title/title.js +++ b/js/src/ui/Container/Title/title.js @@ -27,10 +27,6 @@ export default class Title extends Component { byline: nodeOrStringProptype() } - state = { - name: 'Unnamed' - } - render () { const { className, title, byline } = this.props; diff --git a/js/src/ui/Container/Title/title.spec.js b/js/src/ui/Container/Title/title.spec.js new file mode 100644 index 000000000..7c6dc05b6 --- /dev/null +++ b/js/src/ui/Container/Title/title.spec.js @@ -0,0 +1,52 @@ +// 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 . + +import React from 'react'; +import { mount, shallow } from 'enzyme'; + +import Title from './title'; + +function renderShallow (props) { + return shallow( + + ); +} + +function renderMount (props) { + return mount( + <Title { ...props } /> + ); +} + +describe('ui/Container/Title', () => { + describe('rendering', () => { + it('renders without crashing', () => { + expect(renderShallow()).to.be.defined; + }); + + it('renders with the specified className', () => { + expect(renderShallow({ className: 'testClass' })).to.have.className('testClass'); + }); + + it('renders the specified title', () => { + expect(renderMount({ title: 'titleText' })).to.contain.text('titleText'); + }); + + it('renders the specified byline', () => { + expect(renderMount({ byline: 'bylineText' })).to.contain.text('bylineText'); + }); + }); +}); diff --git a/js/src/ui/Container/container.spec.js b/js/src/ui/Container/container.spec.js new file mode 100644 index 000000000..865522d7e --- /dev/null +++ b/js/src/ui/Container/container.spec.js @@ -0,0 +1,38 @@ +// 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 <http://www.gnu.org/licenses/>. + +import React from 'react'; +import { shallow } from 'enzyme'; + +import Container from './title'; + +function renderShallow (props) { + return shallow( + <Container { ...props } /> + ); +} + +describe('ui/Container', () => { + describe('rendering', () => { + it('renders without crashing', () => { + expect(renderShallow()).to.be.defined; + }); + + it('renders with the specified className', () => { + expect(renderShallow({ className: 'testClass' })).to.have.className('testClass'); + }); + }); +}); From 543428fe5d3a8917cc08774111538328bdfa16e0 Mon Sep 17 00:00:00 2001 From: Jaco Greeff <jaco@ethcore.io> Date: Thu, 8 Dec 2016 12:59:59 +0100 Subject: [PATCH 09/34] NODE_ENV=test for all tests --- js/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/package.json b/js/package.json index f46619a3a..a5fab74d5 100644 --- a/js/package.json +++ b/js/package.json @@ -41,8 +41,8 @@ "lint": "eslint --ignore-path .gitignore ./src/", "lint:cached": "eslint --cache --ignore-path .gitignore ./src/", "test": "NODE_ENV=test mocha 'src/**/*.spec.js'", - "test:coverage": "istanbul cover _mocha -- 'src/**/*.spec.js'", - "test:e2e": "mocha 'src/**/*.e2e.js'", + "test:coverage": "NODE_ENV=test istanbul cover _mocha -- 'src/**/*.spec.js'", + "test:e2e": "NODE_ENV=test mocha 'src/**/*.e2e.js'", "test:npm": "(cd .npmjs && npm i) && node test/npmLibrary && (rm -rf .npmjs/node_modules)", "prepush": "npm run lint:cached" }, From a5b5277a88388dd7fda717eca85553e7eec919cd Mon Sep 17 00:00:00 2001 From: Jaco Greeff <jaco@ethcore.io> Date: Thu, 8 Dec 2016 13:00:29 +0100 Subject: [PATCH 10/34] Expanded basic tests for #2289 --- js/src/ui/Actionbar/actionbar.spec.js | 38 +++++++++++++++++++++++++ js/src/ui/Badge/badge.spec.js | 38 +++++++++++++++++++++++++ js/src/ui/Button/button.spec.js | 38 +++++++++++++++++++++++++ js/src/ui/Container/Title/title.spec.js | 4 +-- js/src/ui/Container/container.spec.js | 6 ++-- 5 files changed, 119 insertions(+), 5 deletions(-) create mode 100644 js/src/ui/Actionbar/actionbar.spec.js create mode 100644 js/src/ui/Badge/badge.spec.js create mode 100644 js/src/ui/Button/button.spec.js diff --git a/js/src/ui/Actionbar/actionbar.spec.js b/js/src/ui/Actionbar/actionbar.spec.js new file mode 100644 index 000000000..818eb0f80 --- /dev/null +++ b/js/src/ui/Actionbar/actionbar.spec.js @@ -0,0 +1,38 @@ +// 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 <http://www.gnu.org/licenses/>. + +import React from 'react'; +import { shallow } from 'enzyme'; + +import Actionbar from './actionbar'; + +function renderShallow (props) { + return shallow( + <Actionbar { ...props } /> + ); +} + +describe('ui/Actionbar', () => { + describe('rendering', () => { + it('renders defaults', () => { + expect(renderShallow()).to.be.ok; + }); + + it('renders with the specified className', () => { + expect(renderShallow({ className: 'testClass' })).to.have.className('testClass'); + }); + }); +}); diff --git a/js/src/ui/Badge/badge.spec.js b/js/src/ui/Badge/badge.spec.js new file mode 100644 index 000000000..8074a8688 --- /dev/null +++ b/js/src/ui/Badge/badge.spec.js @@ -0,0 +1,38 @@ +// 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 <http://www.gnu.org/licenses/>. + +import React from 'react'; +import { shallow } from 'enzyme'; + +import Badge from './badge'; + +function renderShallow (props) { + return shallow( + <Badge { ...props } /> + ); +} + +describe('ui/Badge', () => { + describe('rendering', () => { + it('renders defaults', () => { + expect(renderShallow()).to.be.ok; + }); + + it('renders with the specified className', () => { + expect(renderShallow({ className: 'testClass' })).to.have.className('testClass'); + }); + }); +}); diff --git a/js/src/ui/Button/button.spec.js b/js/src/ui/Button/button.spec.js new file mode 100644 index 000000000..1d5f5beed --- /dev/null +++ b/js/src/ui/Button/button.spec.js @@ -0,0 +1,38 @@ +// 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 <http://www.gnu.org/licenses/>. + +import React from 'react'; +import { shallow } from 'enzyme'; + +import Button from './button'; + +function renderShallow (props) { + return shallow( + <Button { ...props } /> + ); +} + +describe.only('ui/Button', () => { + describe('rendering', () => { + it('renders defaults', () => { + expect(renderShallow()).to.be.ok; + }); + + it('renders with the specified className', () => { + expect(renderShallow({ className: 'testClass' })).to.have.className('testClass'); + }); + }); +}); diff --git a/js/src/ui/Container/Title/title.spec.js b/js/src/ui/Container/Title/title.spec.js index 7c6dc05b6..a65e221a6 100644 --- a/js/src/ui/Container/Title/title.spec.js +++ b/js/src/ui/Container/Title/title.spec.js @@ -33,8 +33,8 @@ function renderMount (props) { describe('ui/Container/Title', () => { describe('rendering', () => { - it('renders without crashing', () => { - expect(renderShallow()).to.be.defined; + it('renders defaults', () => { + expect(renderShallow()).to.be.ok; }); it('renders with the specified className', () => { diff --git a/js/src/ui/Container/container.spec.js b/js/src/ui/Container/container.spec.js index 865522d7e..9bbc9d20f 100644 --- a/js/src/ui/Container/container.spec.js +++ b/js/src/ui/Container/container.spec.js @@ -17,7 +17,7 @@ import React from 'react'; import { shallow } from 'enzyme'; -import Container from './title'; +import Container from './container'; function renderShallow (props) { return shallow( @@ -27,8 +27,8 @@ function renderShallow (props) { describe('ui/Container', () => { describe('rendering', () => { - it('renders without crashing', () => { - expect(renderShallow()).to.be.defined; + it('renders defaults', () => { + expect(renderShallow()).to.be.ok; }); it('renders with the specified className', () => { From 3e8ee9d423e860430fb70f3b45851f2a018c34e2 Mon Sep 17 00:00:00 2001 From: Robert Habermeier <rphmeier@gmail.com> Date: Thu, 8 Dec 2016 13:44:17 +0100 Subject: [PATCH 11/34] light IPC feature and mock state proofs --- Cargo.lock | 2 - ethcore/light/Cargo.toml | 9 +- ethcore/light/build.rs | 5 + ethcore/light/src/lib.rs | 6 +- ethcore/light/src/net/tests/mod.rs | 164 ++++++++++++++++++++++++- ethcore/light/src/types/les_request.rs | 33 +++-- ethcore/light/src/types/mod.rs | 7 +- 7 files changed, 206 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b00701f32..4a5534dbe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -462,8 +462,6 @@ version = "1.5.0" dependencies = [ "ethcore 1.5.0", "ethcore-io 1.5.0", - "ethcore-ipc 1.4.0", - "ethcore-ipc-codegen 1.4.0", "ethcore-network 1.5.0", "ethcore-util 1.5.0", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/ethcore/light/Cargo.toml b/ethcore/light/Cargo.toml index 9440bbf88..e80eb3e2f 100644 --- a/ethcore/light/Cargo.toml +++ b/ethcore/light/Cargo.toml @@ -8,7 +8,7 @@ authors = ["Ethcore <admin@ethcore.io>"] build = "build.rs" [build-dependencies] -"ethcore-ipc-codegen" = { path = "../../ipc/codegen" } +"ethcore-ipc-codegen" = { path = "../../ipc/codegen", optional = true } [dependencies] log = "0.3" @@ -16,6 +16,9 @@ ethcore = { path = "..", features = ["light"] } ethcore-util = { path = "../../util" } ethcore-network = { path = "../../util/network" } ethcore-io = { path = "../../util/io" } -ethcore-ipc = { path = "../../ipc/rpc" } +ethcore-ipc = { path = "../../ipc/rpc", optional = true } rlp = { path = "../../util/rlp" } -time = "0.1" \ No newline at end of file +time = "0.1" + +[features] +ipc = ["ethcore/ipc", "ethcore-ipc", "ethcore-ipc-codegen"] \ No newline at end of file diff --git a/ethcore/light/build.rs b/ethcore/light/build.rs index cff92a011..43915c1cf 100644 --- a/ethcore/light/build.rs +++ b/ethcore/light/build.rs @@ -14,8 +14,13 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see <http://www.gnu.org/licenses/>. +#[cfg(feature = "ipc")] extern crate ethcore_ipc_codegen; +#[cfg(feature = "ipc")] fn main() { ethcore_ipc_codegen::derive_binary("src/types/mod.rs.in").unwrap(); } + +#[cfg(not(feature = "ipc"))] +fn main() { } \ No newline at end of file diff --git a/ethcore/light/src/lib.rs b/ethcore/light/src/lib.rs index e150f4ee5..c00467c4c 100644 --- a/ethcore/light/src/lib.rs +++ b/ethcore/light/src/lib.rs @@ -47,6 +47,8 @@ extern crate ethcore; extern crate ethcore_util as util; extern crate ethcore_network as network; extern crate ethcore_io as io; -extern crate ethcore_ipc as ipc; extern crate rlp; -extern crate time; \ No newline at end of file +extern crate time; + +#[cfg(feature = "ipc")] +extern crate ethcore_ipc as ipc; \ No newline at end of file diff --git a/ethcore/light/src/net/tests/mod.rs b/ethcore/light/src/net/tests/mod.rs index 7db05542e..555f6d19c 100644 --- a/ethcore/light/src/net/tests/mod.rs +++ b/ethcore/light/src/net/tests/mod.rs @@ -123,7 +123,19 @@ impl Provider for TestProvider { } fn proofs(&self, req: request::StateProofs) -> Vec<Bytes> { - req.requests.into_iter().map(|_| ::rlp::EMPTY_LIST_RLP.to_vec()).collect() + req.requests.into_iter() + .map(|req| { + match req.key2 { + Some(key2) => ::util::sha3::SHA3_NULL_RLP.to_owned(), + None => { + // sort of a leaf node + let mut stream = RlpStream::new_list(2); + stream.append(&req.key1).append_empty_data(); + stream.out() + } + } + }) + .collect() } fn contract_code(&self, req: request::ContractCodes) -> Vec<Bytes> { @@ -240,6 +252,9 @@ fn buffer_overflow() { proto.handle_packet(&Expect::Punish(1), &1, packet::GET_BLOCK_HEADERS, &request); } +// test the basic request types -- these just make sure that requests are parsed +// and sent to the provider correctly as well as testing response formatting. + #[test] fn get_block_headers() { let flow_params = FlowParams::new(5_000_000.into(), Default::default(), 0.into()); @@ -288,4 +303,151 @@ fn get_block_headers() { let expected = Expect::Respond(packet::BLOCK_HEADERS, response); proto.handle_packet(&expected, &1, packet::GET_BLOCK_HEADERS, &request_body); +} + +#[test] +fn get_block_bodies() { + 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 = request::Bodies { + block_hashes: (0..10).map(|i| provider.client.block_hash(BlockID::Number(i)).unwrap()).collect(), + }; + + let req_id = 111; + + let request_body = encode_request(&Request::Bodies(request.clone()), req_id); + let response = { + let bodies: Vec<_> = (0..10).map(|i| provider.client.block_body(BlockID::Number(i + 1)).unwrap()).collect(); + assert_eq!(bodies.len(), 10); + + let new_buf = *flow_params.limit() - flow_params.compute_cost(request::Kind::Bodies, 10); + + let mut response_stream = RlpStream::new_list(12); + + response_stream.append(&req_id).append(&new_buf); + for body in bodies { + response_stream.append_raw(&body, 1); + } + + response_stream.out() + }; + + let expected = Expect::Respond(packet::BLOCK_BODIES, response); + proto.handle_packet(&expected, &1, packet::GET_BLOCK_BODIES, &request_body); +} + +#[test] +fn get_block_receipts() { + 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(1000, 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); + } + + // find the first 10 block hashes starting with `f` because receipts are only provided + // by the test client in that case. + let block_hashes: Vec<_> = (0..1000).map(|i| provider.client.block_hash(BlockID::Number(i)).unwrap()) + .filter(|hash| format!("{}", hash).starts_with("f")).take(10).collect(); + + let request = request::Receipts { + block_hashes: block_hashes.clone(), + }; + + let req_id = 111; + + let request_body = encode_request(&Request::Receipts(request.clone()), req_id); + let response = { + let receipts: Vec<_> = block_hashes.iter() + .map(|hash| provider.client.block_receipts(hash).unwrap()) + .collect(); + + let new_buf = *flow_params.limit() - flow_params.compute_cost(request::Kind::Receipts, receipts.len()); + + let mut response_stream = RlpStream::new_list(2 + receipts.len()); + + response_stream.append(&req_id).append(&new_buf); + for block_receipts in receipts { + response_stream.append_raw(&block_receipts, 1); + } + + response_stream.out() + }; + + let expected = Expect::Respond(packet::RECEIPTS, response); + proto.handle_packet(&expected, &1, packet::GET_RECEIPTS, &request_body); +} + +#[test] +fn get_block_bodies() { + 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 = request::Bodies { + block_hashes: (0..10).map(|i| provider.client.block_hash(BlockID::Number(i)).unwrap()).collect(), + }; + + let req_id = 111; + + let request_body = encode_request(&Request::Bodies(request.clone()), req_id); + let response = { + let bodies: Vec<_> = (0..10).map(|i| provider.client.block_body(BlockID::Number(i + 1)).unwrap()).collect(); + assert_eq!(bodies.len(), 10); + + let new_buf = *flow_params.limit() - flow_params.compute_cost(request::Kind::Bodies, 10); + + let mut response_stream = RlpStream::new_list(12); + + response_stream.append(&req_id).append(&new_buf); + for body in bodies { + response_stream.append_raw(&body, 1); + } + + response_stream.out() + }; + + let expected = Expect::Respond(packet::BLOCK_BODIES, response); + proto.handle_packet(&expected, &1, packet::GET_BLOCK_BODIES, &request_body); } \ No newline at end of file diff --git a/ethcore/light/src/types/les_request.rs b/ethcore/light/src/types/les_request.rs index d0de080ee..49bd2e9cc 100644 --- a/ethcore/light/src/types/les_request.rs +++ b/ethcore/light/src/types/les_request.rs @@ -19,7 +19,8 @@ use util::H256; /// A request for block headers. -#[derive(Debug, Clone, PartialEq, Eq, Binary)] +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "ipc", derive(Binary))] pub struct Headers { /// Starting block number pub block_num: u64, @@ -35,7 +36,8 @@ pub struct Headers { } /// A request for specific block bodies. -#[derive(Debug, Clone, PartialEq, Eq, Binary)] +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "ipc", derive(Binary))] pub struct Bodies { /// Hashes which bodies are being requested for. pub block_hashes: Vec<H256> @@ -45,14 +47,16 @@ pub struct Bodies { /// /// This request is answered with a list of transaction receipts for each block /// requested. -#[derive(Debug, Clone, PartialEq, Eq, Binary)] +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "ipc", derive(Binary))] pub struct Receipts { /// Block hashes to return receipts for. pub block_hashes: Vec<H256>, } /// A request for a state proof -#[derive(Debug, Clone, PartialEq, Eq, Binary)] +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "ipc", derive(Binary))] pub struct StateProof { /// Block hash to query state from. pub block: H256, @@ -66,14 +70,16 @@ pub struct StateProof { } /// A request for state proofs. -#[derive(Debug, Clone, PartialEq, Eq, Binary)] +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "ipc", derive(Binary))] pub struct StateProofs { /// All the proof requests. pub requests: Vec<StateProof>, } /// A request for contract code. -#[derive(Debug, Clone, PartialEq, Eq, Binary)] +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "ipc", derive(Binary))] pub struct ContractCode { /// Block hash pub block_hash: H256, @@ -82,14 +88,16 @@ pub struct ContractCode { } /// A request for contract code. -#[derive(Debug, Clone, PartialEq, Eq, Binary)] +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "ipc", derive(Binary))] pub struct ContractCodes { /// Block hash and account key (== sha3(address)) pairs to fetch code for. pub code_requests: Vec<ContractCode>, } /// A request for a header proof from the Canonical Hash Trie. -#[derive(Debug, Clone, PartialEq, Eq, Binary)] +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "ipc", derive(Binary))] pub struct HeaderProof { /// Number of the CHT. pub cht_number: u64, @@ -100,14 +108,16 @@ pub struct HeaderProof { } /// A request for header proofs from the CHT. -#[derive(Debug, Clone, PartialEq, Eq, Binary)] +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "ipc", derive(Binary))] pub struct HeaderProofs { /// All the proof requests. pub requests: Vec<HeaderProof>, } /// Kinds of requests. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Binary)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "ipc", derive(Binary))] pub enum Kind { /// Requesting headers. Headers, @@ -124,7 +134,8 @@ pub enum Kind { } /// Encompasses all possible types of requests in a single structure. -#[derive(Debug, Clone, PartialEq, Eq, Binary)] +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "ipc", derive(Binary))] pub enum Request { /// Requesting headers. Headers(Headers), diff --git a/ethcore/light/src/types/mod.rs b/ethcore/light/src/types/mod.rs index 2625358a3..d7f473553 100644 --- a/ethcore/light/src/types/mod.rs +++ b/ethcore/light/src/types/mod.rs @@ -15,6 +15,11 @@ // along with Parity. If not, see <http://www.gnu.org/licenses/>. //! Types used in the public (IPC) api which require custom code generation. +#![cfg_attr(feature = "ipc", allow(dead_code, unused_assignments, unused_variables))] // codegen issues -#![allow(dead_code, unused_assignments, unused_variables)] // codegen issues + +#[cfg(feature = "ipc")] include!(concat!(env!("OUT_DIR"), "/mod.rs.in")); + +#[cfg(not(feature = "ipc"))] +include!("mod.rs.in"); \ No newline at end of file From 1ab4ee37817c61e8f039e8320c6a0f9e725b5b98 Mon Sep 17 00:00:00 2001 From: Jaco Greeff <jaco@ethcore.io> Date: Thu, 8 Dec 2016 14:53:57 +0100 Subject: [PATCH 12/34] Update Button with correct proptypes --- js/src/ui/Button/button.js | 7 +++---- js/src/ui/Button/button.spec.js | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/js/src/ui/Button/button.js b/js/src/ui/Button/button.js index 48dbc981d..6ae113fd4 100644 --- a/js/src/ui/Button/button.js +++ b/js/src/ui/Button/button.js @@ -17,16 +17,15 @@ import React, { Component, PropTypes } from 'react'; import { FlatButton } from 'material-ui'; +import { nodeOrStringProptype } from '~/util/proptypes'; + export default class Button extends Component { static propTypes = { backgroundColor: PropTypes.string, className: PropTypes.string, disabled: PropTypes.bool, icon: PropTypes.node, - label: PropTypes.oneOfType([ - React.PropTypes.string, - React.PropTypes.object - ]), + label: nodeOrStringProptype(), onClick: PropTypes.func, primary: PropTypes.bool } diff --git a/js/src/ui/Button/button.spec.js b/js/src/ui/Button/button.spec.js index 1d5f5beed..9ae4ecdfc 100644 --- a/js/src/ui/Button/button.spec.js +++ b/js/src/ui/Button/button.spec.js @@ -25,7 +25,7 @@ function renderShallow (props) { ); } -describe.only('ui/Button', () => { +describe('ui/Button', () => { describe('rendering', () => { it('renders defaults', () => { expect(renderShallow()).to.be.ok; From 64ebcd0f24bda255647daa31bcda07e6fa4dd3e2 Mon Sep 17 00:00:00 2001 From: Robert Habermeier <rphmeier@gmail.com> Date: Thu, 8 Dec 2016 16:12:00 +0100 Subject: [PATCH 13/34] fix contract code response encoding; add test --- ethcore/light/src/net/mod.rs | 2 +- ethcore/light/src/net/tests/mod.rs | 531 ++++++++++++++++------------- 2 files changed, 294 insertions(+), 239 deletions(-) diff --git a/ethcore/light/src/net/mod.rs b/ethcore/light/src/net/mod.rs index 57d091f75..57dc77a86 100644 --- a/ethcore/light/src/net/mod.rs +++ b/ethcore/light/src/net/mod.rs @@ -863,7 +863,7 @@ impl LightProtocol { stream.append(&req_id).append(&cur_buffer); for code in response { - stream.append_raw(&code, 1); + stream.append(&code); } stream.out() diff --git a/ethcore/light/src/net/tests/mod.rs b/ethcore/light/src/net/tests/mod.rs index 555f6d19c..e659d0681 100644 --- a/ethcore/light/src/net/tests/mod.rs +++ b/ethcore/light/src/net/tests/mod.rs @@ -31,62 +31,62 @@ use provider::Provider; use request::{self, Request, Headers}; use rlp::*; -use util::{Bytes, H256}; +use util::{Bytes, H256, U256}; 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<u8>), - /// Expect this response. - Respond(u8, Vec<u8>), - /// Expect a punishment (disconnect/disable) - Punish(PeerId), - /// Expect nothing. - Nothing, + /// Expect to have message sent to peer. + Send(PeerId, u8, Vec<u8>), + /// Expect this response. + Respond(u8, Vec<u8>), + /// 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<u8>) { - assert_eq!(self, &Expect::Send(peer, packet_id, packet_body)); - } + fn send(&self, peer: PeerId, packet_id: u8, packet_body: Vec<u8>) { + assert_eq!(self, &Expect::Send(peer, packet_id, packet_body)); + } - fn respond(&self, packet_id: u8, packet_body: Vec<u8>) { - assert_eq!(self, &Expect::Respond(packet_id, packet_body)); - } + fn respond(&self, packet_id: u8, packet_body: Vec<u8>) { + assert_eq!(self, &Expect::Respond(packet_id, packet_body)); + } - fn disconnect_peer(&self, peer: PeerId) { - assert_eq!(self, &Expect::Punish(peer)); - } + fn disconnect_peer(&self, peer: PeerId) { + assert_eq!(self, &Expect::Punish(peer)); + } - fn disable_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<TestProviderInner>); struct TestProviderInner { - client: TestBlockChainClient, + client: TestBlockChainClient, } impl Provider for TestProvider { - fn chain_info(&self) -> BlockChainInfo { - self.0.client.chain_info() - } + fn chain_info(&self) -> BlockChainInfo { + self.0.client.chain_info() + } - fn reorg_depth(&self, a: &H256, b: &H256) -> Option<u64> { - self.0.client.tree_route(a, b).map(|route| route.index as u64) - } + fn reorg_depth(&self, a: &H256, b: &H256) -> Option<u64> { + self.0.client.tree_route(a, b).map(|route| route.index as u64) + } - fn earliest_state(&self) -> Option<u64> { - None - } + fn earliest_state(&self) -> Option<u64> { + None + } - fn block_headers(&self, req: request::Headers) -> Vec<Bytes> { + fn block_headers(&self, req: request::Headers) -> Vec<Bytes> { let best_num = self.0.client.chain_info().best_block_number; let start_num = req.block_num; @@ -124,22 +124,26 @@ impl Provider for TestProvider { fn proofs(&self, req: request::StateProofs) -> Vec<Bytes> { req.requests.into_iter() - .map(|req| { - match req.key2 { - Some(key2) => ::util::sha3::SHA3_NULL_RLP.to_owned(), - None => { - // sort of a leaf node - let mut stream = RlpStream::new_list(2); - stream.append(&req.key1).append_empty_data(); - stream.out() - } - } - }) - .collect() + .map(|req| { + match req.key2 { + Some(_) => ::util::sha3::SHA3_NULL_RLP.to_vec(), + None => { + // sort of a leaf node + let mut stream = RlpStream::new_list(2); + stream.append(&req.key1).append_empty_data(); + stream.out() + } + } + }) + .collect() } fn contract_code(&self, req: request::ContractCodes) -> Vec<Bytes> { - req.code_requests.into_iter().map(|_| Vec::new()).collect() + req.code_requests.into_iter() + .map(|req| { + req.account_key.iter().chain(req.account_key.iter()).cloned().collect() + }) + .collect() } fn header_proofs(&self, req: request::HeaderProofs) -> Vec<Bytes> { @@ -152,104 +156,104 @@ impl Provider for TestProvider { } fn make_flow_params() -> FlowParams { - FlowParams::new(5_000_000.into(), Default::default(), 100_000.into()) + 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, - } + 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<TestProviderInner>, LightProtocol) { - let provider = Arc::new(TestProviderInner { - client: TestBlockChainClient::new(), - }); + 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, - }); + let proto = LightProtocol::new(Box::new(TestProvider(provider.clone())), Params { + network_id: NetworkId::Testnet, + flow_params: flow_params, + capabilities: capabilities, + }); - (provider, proto) + (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, - } + 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 flow_params = make_flow_params(); + let capabilities = capabilities(); - let (provider, proto) = setup(flow_params.clone(), capabilities.clone()); + let (provider, proto) = setup(flow_params.clone(), capabilities.clone()); - let status = status(provider.client.chain_info()); + let status = status(provider.client.chain_info()); - let packet_body = write_handshake(&status, &capabilities, &flow_params); + let packet_body = write_handshake(&status, &capabilities, &flow_params); - proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body)); + 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 flow_params = make_flow_params(); + let capabilities = capabilities(); - let (provider, proto) = setup(flow_params.clone(), capabilities.clone()); + let (provider, proto) = setup(flow_params.clone(), capabilities.clone()); - let mut status = status(provider.client.chain_info()); - status.genesis_hash = H256::default(); + let mut status = status(provider.client.chain_info()); + status.genesis_hash = H256::default(); - let packet_body = write_handshake(&status, &capabilities, &flow_params); + let packet_body = write_handshake(&status, &capabilities, &flow_params); - proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body)); + 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 flow_params = make_flow_params(); + let capabilities = capabilities(); - let (provider, proto) = setup(flow_params.clone(), capabilities.clone()); + let (provider, proto) = setup(flow_params.clone(), capabilities.clone()); - let status = status(provider.client.chain_info()); + 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 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); - } + { + 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); + // 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); + proto.handle_packet(&Expect::Punish(1), &1, packet::GET_BLOCK_HEADERS, &request); } // test the basic request types -- these just make sure that requests are parsed @@ -257,197 +261,248 @@ fn buffer_overflow() { #[test] fn get_block_headers() { - let flow_params = FlowParams::new(5_000_000.into(), Default::default(), 0.into()); - let capabilities = capabilities(); + 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 (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); + 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); + provider.client.add_blocks(100, EachBlockWith::Nothing); - let cur_status = status(provider.client.chain_info()); + 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 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 = 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 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 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); - } + 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() - }; + response_stream.out() + }; - let expected = Expect::Respond(packet::BLOCK_HEADERS, response); - proto.handle_packet(&expected, &1, packet::GET_BLOCK_HEADERS, &request_body); + let expected = Expect::Respond(packet::BLOCK_HEADERS, response); + proto.handle_packet(&expected, &1, packet::GET_BLOCK_HEADERS, &request_body); } #[test] fn get_block_bodies() { - let flow_params = FlowParams::new(5_000_000.into(), Default::default(), 0.into()); - let capabilities = capabilities(); + 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 (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); + 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); + provider.client.add_blocks(100, EachBlockWith::Nothing); - let cur_status = status(provider.client.chain_info()); + 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 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 = request::Bodies { - block_hashes: (0..10).map(|i| provider.client.block_hash(BlockID::Number(i)).unwrap()).collect(), - }; + let request = request::Bodies { + block_hashes: (0..10).map(|i| provider.client.block_hash(BlockID::Number(i)).unwrap()).collect(), + }; - let req_id = 111; + let req_id = 111; - let request_body = encode_request(&Request::Bodies(request.clone()), req_id); - let response = { - let bodies: Vec<_> = (0..10).map(|i| provider.client.block_body(BlockID::Number(i + 1)).unwrap()).collect(); - assert_eq!(bodies.len(), 10); + let request_body = encode_request(&Request::Bodies(request.clone()), req_id); + let response = { + let bodies: Vec<_> = (0..10).map(|i| provider.client.block_body(BlockID::Number(i + 1)).unwrap()).collect(); + assert_eq!(bodies.len(), 10); - let new_buf = *flow_params.limit() - flow_params.compute_cost(request::Kind::Bodies, 10); + let new_buf = *flow_params.limit() - flow_params.compute_cost(request::Kind::Bodies, 10); - let mut response_stream = RlpStream::new_list(12); - - response_stream.append(&req_id).append(&new_buf); - for body in bodies { - response_stream.append_raw(&body, 1); - } + let mut response_stream = RlpStream::new_list(12); + + response_stream.append(&req_id).append(&new_buf); + for body in bodies { + response_stream.append_raw(&body, 1); + } - response_stream.out() - }; + response_stream.out() + }; - let expected = Expect::Respond(packet::BLOCK_BODIES, response); - proto.handle_packet(&expected, &1, packet::GET_BLOCK_BODIES, &request_body); + let expected = Expect::Respond(packet::BLOCK_BODIES, response); + proto.handle_packet(&expected, &1, packet::GET_BLOCK_BODIES, &request_body); } #[test] fn get_block_receipts() { - let flow_params = FlowParams::new(5_000_000.into(), Default::default(), 0.into()); - let capabilities = capabilities(); + 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 (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); + let cur_status = status(provider.client.chain_info()); + let my_status = write_handshake(&cur_status, &capabilities, &flow_params); - provider.client.add_blocks(1000, EachBlockWith::Nothing); + provider.client.add_blocks(1000, EachBlockWith::Nothing); - let cur_status = status(provider.client.chain_info()); + 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 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); + } - // find the first 10 block hashes starting with `f` because receipts are only provided - // by the test client in that case. - let block_hashes: Vec<_> = (0..1000).map(|i| provider.client.block_hash(BlockID::Number(i)).unwrap()) - .filter(|hash| format!("{}", hash).starts_with("f")).take(10).collect(); + // find the first 10 block hashes starting with `f` because receipts are only provided + // by the test client in that case. + let block_hashes: Vec<_> = (0..1000).map(|i| provider.client.block_hash(BlockID::Number(i)).unwrap()) + .filter(|hash| format!("{}", hash).starts_with("f")).take(10).collect(); - let request = request::Receipts { - block_hashes: block_hashes.clone(), - }; + let request = request::Receipts { + block_hashes: block_hashes.clone(), + }; - let req_id = 111; + let req_id = 111; - let request_body = encode_request(&Request::Receipts(request.clone()), req_id); - let response = { - let receipts: Vec<_> = block_hashes.iter() - .map(|hash| provider.client.block_receipts(hash).unwrap()) - .collect(); + let request_body = encode_request(&Request::Receipts(request.clone()), req_id); + let response = { + let receipts: Vec<_> = block_hashes.iter() + .map(|hash| provider.client.block_receipts(hash).unwrap()) + .collect(); - let new_buf = *flow_params.limit() - flow_params.compute_cost(request::Kind::Receipts, receipts.len()); + let new_buf = *flow_params.limit() - flow_params.compute_cost(request::Kind::Receipts, receipts.len()); - let mut response_stream = RlpStream::new_list(2 + receipts.len()); - - response_stream.append(&req_id).append(&new_buf); - for block_receipts in receipts { - response_stream.append_raw(&block_receipts, 1); - } + let mut response_stream = RlpStream::new_list(2 + receipts.len()); + + response_stream.append(&req_id).append(&new_buf); + for block_receipts in receipts { + response_stream.append_raw(&block_receipts, 1); + } - response_stream.out() - }; + response_stream.out() + }; - let expected = Expect::Respond(packet::RECEIPTS, response); - proto.handle_packet(&expected, &1, packet::GET_RECEIPTS, &request_body); + let expected = Expect::Respond(packet::RECEIPTS, response); + proto.handle_packet(&expected, &1, packet::GET_RECEIPTS, &request_body); } #[test] -fn get_block_bodies() { - let flow_params = FlowParams::new(5_000_000.into(), Default::default(), 0.into()); - let capabilities = capabilities(); +fn get_state_proofs() { + 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 (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); + let cur_status = status(provider.client.chain_info()); - provider.client.add_blocks(100, EachBlockWith::Nothing); + { + let packet_body = write_handshake(&cur_status, &capabilities, &flow_params); + proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body.clone())); + proto.handle_packet(&Expect::Nothing, &1, packet::STATUS, &packet_body); + } - let cur_status = status(provider.client.chain_info()); + let req_id = 112; + let key1 = U256::from(11223344).into(); + let key2 = U256::from(99988887).into(); - { - 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 = Request::StateProofs (request::StateProofs { + requests: vec![ + request::StateProof { block: H256::default(), key1: key1, key2: None, from_level: 0 }, + request::StateProof { block: H256::default(), key1: key1, key2: Some(key2), from_level: 0}, + ] + }); - let request = request::Bodies { - block_hashes: (0..10).map(|i| provider.client.block_hash(BlockID::Number(i)).unwrap()).collect(), - }; + let request_body = encode_request(&request, req_id); + let response = { + let proofs = vec![ + { let mut stream = RlpStream::new_list(2); stream.append(&key1).append_empty_data(); stream.out() }, + ::util::sha3::SHA3_NULL_RLP.to_vec(), + ]; - let req_id = 111; + let new_buf = *flow_params.limit() - flow_params.compute_cost(request::Kind::StateProofs, 2); - let request_body = encode_request(&Request::Bodies(request.clone()), req_id); - let response = { - let bodies: Vec<_> = (0..10).map(|i| provider.client.block_body(BlockID::Number(i + 1)).unwrap()).collect(); - assert_eq!(bodies.len(), 10); + let mut response_stream = RlpStream::new_list(4); + + response_stream.append(&req_id).append(&new_buf); + for proof in proofs { + response_stream.append_raw(&proof, 1); + } - let new_buf = *flow_params.limit() - flow_params.compute_cost(request::Kind::Bodies, 10); + response_stream.out() + }; - let mut response_stream = RlpStream::new_list(12); - - response_stream.append(&req_id).append(&new_buf); - for body in bodies { - response_stream.append_raw(&body, 1); - } + let expected = Expect::Respond(packet::PROOFS, response); + proto.handle_packet(&expected, &1, packet::GET_PROOFS, &request_body); +} - response_stream.out() - }; +#[test] +fn get_contract_code() { + let flow_params = FlowParams::new(5_000_000.into(), Default::default(), 0.into()); + let capabilities = capabilities(); - let expected = Expect::Respond(packet::BLOCK_BODIES, response); - proto.handle_packet(&expected, &1, packet::GET_BLOCK_BODIES, &request_body); + let (provider, proto) = setup(flow_params.clone(), capabilities.clone()); + + 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.clone())); + proto.handle_packet(&Expect::Nothing, &1, packet::STATUS, &packet_body); + } + + let req_id = 112; + let key1 = U256::from(11223344).into(); + let key2 = U256::from(99988887).into(); + + let request = Request::Codes (request::ContractCodes { + code_requests: vec![ + request::ContractCode { block_hash: H256::default(), account_key: key1 }, + request::ContractCode { block_hash: H256::default(), account_key: key2 }, + ], + }); + + let request_body = encode_request(&request, req_id); + let response = { + let codes: Vec<Vec<_>> = vec![ + key1.iter().chain(key1.iter()).cloned().collect(), + key2.iter().chain(key2.iter()).cloned().collect(), + ]; + + let new_buf = *flow_params.limit() - flow_params.compute_cost(request::Kind::Codes, 2); + + let mut response_stream = RlpStream::new_list(4); + + response_stream.append(&req_id).append(&new_buf); + for code in codes { + response_stream.append(&code); + } + + response_stream.out() + }; + + let expected = Expect::Respond(packet::CONTRACT_CODES, response); + proto.handle_packet(&expected, &1, packet::GET_CONTRACT_CODES, &request_body); } \ No newline at end of file From 2e87e31157260f650fdc9371e5525e1e676a7267 Mon Sep 17 00:00:00 2001 From: Robert Habermeier <rphmeier@gmail.com> Date: Thu, 8 Dec 2016 16:46:58 +0100 Subject: [PATCH 14/34] light: no default features --- ethcore/light/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/ethcore/light/Cargo.toml b/ethcore/light/Cargo.toml index e80eb3e2f..287044c98 100644 --- a/ethcore/light/Cargo.toml +++ b/ethcore/light/Cargo.toml @@ -21,4 +21,5 @@ rlp = { path = "../../util/rlp" } time = "0.1" [features] +default = [] ipc = ["ethcore/ipc", "ethcore-ipc", "ethcore-ipc-codegen"] \ No newline at end of file From 9e8fb616d37e7004f4600b631e8f67f08668dad9 Mon Sep 17 00:00:00 2001 From: Jaco Greeff <jaco@ethcore.io> Date: Thu, 8 Dec 2016 16:50:19 +0100 Subject: [PATCH 15/34] Add store tests, fix issue with pending sort to top --- js/package.json | 1 + js/src/ui/TxList/store.js | 2 + js/src/ui/TxList/store.spec.js | 90 ++++++++++++++++++++++++++++++++++ js/test/mocha.config.js | 2 + 4 files changed, 95 insertions(+) create mode 100644 js/src/ui/TxList/store.spec.js diff --git a/js/package.json b/js/package.json index a5fab74d5..50f512823 100644 --- a/js/package.json +++ b/js/package.json @@ -67,6 +67,7 @@ "babel-register": "6.18.0", "babel-runtime": "6.18.0", "chai": "3.5.0", + "chai-as-promised": "6.0.0", "chai-enzyme": "0.6.1", "copy-webpack-plugin": "4.0.1", "core-js": "2.4.1", diff --git a/js/src/ui/TxList/store.js b/js/src/ui/TxList/store.js index 3faac193e..09cdd2226 100644 --- a/js/src/ui/TxList/store.js +++ b/js/src/ui/TxList/store.js @@ -45,6 +45,8 @@ export default class Store { if (bnB.eq(0)) { return bnB.eq(bnA) ? 0 : 1; + } else if (bnA.eq(0)) { + return -1; } return bnB.comparedTo(bnA); diff --git a/js/src/ui/TxList/store.spec.js b/js/src/ui/TxList/store.spec.js new file mode 100644 index 000000000..9fb3a709e --- /dev/null +++ b/js/src/ui/TxList/store.spec.js @@ -0,0 +1,90 @@ +// 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 <http://www.gnu.org/licenses/>. + +import BigNumber from 'bignumber.js'; +import sinon from 'sinon'; + +import Store from './store'; + +const SUBID = 123; +const BLOCKS = { + 1: { blockhash: '0x1' }, + 2: { blockhash: '0x2' } +}; +const TRANSACTIONS = { + '0x123': { blockNumber: new BigNumber(1) }, + '0x234': { blockNumber: new BigNumber(0) }, + '0x345': { blockNumber: new BigNumber(2) }, + '0x456': { blockNumber: new BigNumber(0) } +}; + +describe.only('ui/TxList/store', () => { + let api; + let store; + + beforeEach(() => { + api = { + subscribe: sinon.stub().resolves(SUBID), + eth: { + getBlockByNumber: (blockNumber) => { + return Promise.resolve(BLOCKS[blockNumber]); + } + } + }; + store = new Store(api); + }); + + describe('create', () => { + it('has empty storage', () => { + expect(store.blocks).to.deep.equal({}); + expect(store.sortedHashes.peek()).to.deep.equal([]); + expect(store.transactions).to.deep.equal({}); + }); + + it('subscribes to eth_blockNumber', () => { + expect(api.subscribe).to.have.been.calledWith('eth_blockNumber'); + expect(store._subscriptionId).to.equal(SUBID); + }); + }); + + describe('addBlocks', () => { + beforeEach(() => { + store.addBlocks(BLOCKS); + }); + + it('adds the blocks to the list', () => { + expect(store.blocks).to.deep.equal(BLOCKS); + }); + }); + + describe('addTransactions', () => { + beforeEach(() => { + store.addTransactions(TRANSACTIONS); + }); + + it('adds all transactions to the list', () => { + expect(store.transactions).to.deep.equal(TRANSACTIONS); + }); + + it('sorts transactions based on blockNumber', () => { + expect(store.sortedHashes.peek()).to.deep.equal(['0x234', '0x456', '0x345', '0x123']); + }); + + it('adds pending transactions to the pending queue', () => { + expect(store._pendingHashes).to.deep.equal(['0x234', '0x456']); + }); + }); +}); diff --git a/js/test/mocha.config.js b/js/test/mocha.config.js index adc43530e..36c91c76e 100644 --- a/js/test/mocha.config.js +++ b/js/test/mocha.config.js @@ -22,11 +22,13 @@ es6Promise.polyfill(); import 'mock-local-storage'; import chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; import chaiEnzyme from 'chai-enzyme'; import sinonChai from 'sinon-chai'; import { WebSocket } from 'mock-socket'; import jsdom from 'jsdom'; +chai.use(chaiAsPromised); chai.use(chaiEnzyme()); chai.use(sinonChai); From ae49251c81c2a510c92ea80bb130b45dca56050b Mon Sep 17 00:00:00 2001 From: Jaco Greeff <jacogr@gmail.com> Date: Thu, 8 Dec 2016 17:04:21 +0100 Subject: [PATCH 16/34] Remove .only --- js/src/ui/TxList/store.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/src/ui/TxList/store.spec.js b/js/src/ui/TxList/store.spec.js index 9fb3a709e..8e4eb848e 100644 --- a/js/src/ui/TxList/store.spec.js +++ b/js/src/ui/TxList/store.spec.js @@ -31,7 +31,7 @@ const TRANSACTIONS = { '0x456': { blockNumber: new BigNumber(0) } }; -describe.only('ui/TxList/store', () => { +describe('ui/TxList/store', () => { let api; let store; From 96d4569cf3192650ab32517ef5beceb0f95e0948 Mon Sep 17 00:00:00 2001 From: Jaco Greeff <jaco@ethcore.io> Date: Thu, 8 Dec 2016 17:31:41 +0100 Subject: [PATCH 17/34] Update test describe paths --- js/src/views/Signer/components/util/transaction.spec.js | 2 +- .../views/Status/components/AutoComplete/AutoComplete.spec.js | 2 +- js/src/views/Status/components/Box/Box.spec.js | 2 +- js/src/views/Status/components/Call/Call.spec.js | 2 +- js/src/views/Status/components/Calls/Calls.spec.js | 2 +- .../views/Status/components/CallsToolbar/CallsToolbar.spec.js | 2 +- .../Status/components/MiningSettings/decodeExtraData.spec.js | 2 +- .../Status/components/MiningSettings/numberFromString.spec.js | 2 +- js/src/views/Status/components/Response/Response.spec.js | 2 +- js/src/views/Status/middleware/localstorage.spec.js | 2 +- js/src/views/Status/util/error.spec.js | 2 +- js/src/views/Status/util/index.spec.js | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/js/src/views/Signer/components/util/transaction.spec.js b/js/src/views/Signer/components/util/transaction.spec.js index af181a7f8..a2022fe2e 100644 --- a/js/src/views/Signer/components/util/transaction.spec.js +++ b/js/src/views/Signer/components/util/transaction.spec.js @@ -17,7 +17,7 @@ import BigNumber from 'bignumber.js'; import { getShortData, getFee, getTotalValue } from './transaction'; -describe('util/transaction', () => { +describe('views/Signer/components/util/transaction', () => { describe('getEstimatedMiningTime', () => { it('should return estimated mining time', () => { }); diff --git a/js/src/views/Status/components/AutoComplete/AutoComplete.spec.js b/js/src/views/Status/components/AutoComplete/AutoComplete.spec.js index 046fcfad6..6bc04574e 100644 --- a/js/src/views/Status/components/AutoComplete/AutoComplete.spec.js +++ b/js/src/views/Status/components/AutoComplete/AutoComplete.spec.js @@ -21,7 +21,7 @@ import getMuiTheme from 'material-ui/styles/getMuiTheme'; import WrappedAutoComplete from './AutoComplete'; -describe('components/AutoComplete', () => { +describe('views/Status/components/AutoComplete', () => { describe('rendering', () => { let rendered; diff --git a/js/src/views/Status/components/Box/Box.spec.js b/js/src/views/Status/components/Box/Box.spec.js index 0630dc99f..5b6301dc9 100644 --- a/js/src/views/Status/components/Box/Box.spec.js +++ b/js/src/views/Status/components/Box/Box.spec.js @@ -19,7 +19,7 @@ import { shallow } from 'enzyme'; import Box from './Box'; -describe('components/Box', () => { +describe('views/Status/components/Box', () => { describe('rendering', () => { const title = 'test title'; let rendered; diff --git a/js/src/views/Status/components/Call/Call.spec.js b/js/src/views/Status/components/Call/Call.spec.js index a4fcf7d01..70caca49f 100644 --- a/js/src/views/Status/components/Call/Call.spec.js +++ b/js/src/views/Status/components/Call/Call.spec.js @@ -22,7 +22,7 @@ import '../../../../environment/tests'; import Call from './Call'; -describe('components/Call', () => { +describe('views/Status/components/Call', () => { const call = { callIdx: 123, callNo: 456, name: 'eth_call', params: [{ name: '123' }], response: '' }; const element = 'dummyElement'; diff --git a/js/src/views/Status/components/Calls/Calls.spec.js b/js/src/views/Status/components/Calls/Calls.spec.js index 7a73fa528..e1e4ac8c9 100644 --- a/js/src/views/Status/components/Calls/Calls.spec.js +++ b/js/src/views/Status/components/Calls/Calls.spec.js @@ -21,7 +21,7 @@ import '../../../../environment/tests'; import Calls from './Calls'; -describe('components/Calls', () => { +describe('views/Status/components/Calls', () => { describe('rendering (no calls)', () => { let rendered; diff --git a/js/src/views/Status/components/CallsToolbar/CallsToolbar.spec.js b/js/src/views/Status/components/CallsToolbar/CallsToolbar.spec.js index 0ff9f568b..6f7a40dfe 100644 --- a/js/src/views/Status/components/CallsToolbar/CallsToolbar.spec.js +++ b/js/src/views/Status/components/CallsToolbar/CallsToolbar.spec.js @@ -22,7 +22,7 @@ import '../../../../environment/tests'; import CallsToolbar from './CallsToolbar'; -describe('components/CallsToolbar', () => { +describe('views/Status/components/CallsToolbar', () => { const callEl = { offsetTop: 0 }; const containerEl = { scrollTop: 0, clientHeight: 0, scrollHeight: 999 }; diff --git a/js/src/views/Status/components/MiningSettings/decodeExtraData.spec.js b/js/src/views/Status/components/MiningSettings/decodeExtraData.spec.js index 007e564b4..b2f93240c 100644 --- a/js/src/views/Status/components/MiningSettings/decodeExtraData.spec.js +++ b/js/src/views/Status/components/MiningSettings/decodeExtraData.spec.js @@ -16,7 +16,7 @@ import { decodeExtraData } from './decodeExtraData'; -describe('MINING SETTINGS', () => { +describe('views/Status/components/MiningSettings/decodeExtraData', () => { describe('EXTRA DATA', () => { const str = 'parity/1.0.0/1.0.0-beta2'; const encoded = '0xd783010000867061726974798b312e302e302d6265746132'; diff --git a/js/src/views/Status/components/MiningSettings/numberFromString.spec.js b/js/src/views/Status/components/MiningSettings/numberFromString.spec.js index c00273495..7efa514c3 100644 --- a/js/src/views/Status/components/MiningSettings/numberFromString.spec.js +++ b/js/src/views/Status/components/MiningSettings/numberFromString.spec.js @@ -16,7 +16,7 @@ import { numberFromString } from './numberFromString'; -describe('NUMBER FROM STRING', () => { +describe('views/Status/components/MiningSettings/numberFromString', () => { it('should convert string to number', () => { expect(numberFromString('12345'), 12345); }); diff --git a/js/src/views/Status/components/Response/Response.spec.js b/js/src/views/Status/components/Response/Response.spec.js index 1eb37a340..0617721c2 100644 --- a/js/src/views/Status/components/Response/Response.spec.js +++ b/js/src/views/Status/components/Response/Response.spec.js @@ -21,7 +21,7 @@ import '../../../../environment/tests'; import Response from './Response'; -describe('components/Response', () => { +describe('views/Status/components/Response', () => { describe('rendering', () => { it('renders non-arrays/non-objects exactly as received', () => { const TEST = '1234567890'; diff --git a/js/src/views/Status/middleware/localstorage.spec.js b/js/src/views/Status/middleware/localstorage.spec.js index c522196d9..e1d6ed7ee 100644 --- a/js/src/views/Status/middleware/localstorage.spec.js +++ b/js/src/views/Status/middleware/localstorage.spec.js @@ -21,7 +21,7 @@ import { syncRpcStateFromLocalStorage } from '../actions/localstorage'; import rpcData from '../data/rpc.json'; import LocalStorageMiddleware from './localstorage'; -describe('MIDDLEWARE: LOCAL STORAGE', () => { +describe('views/Status/middleware/localstorage', () => { let cut, state; beforeEach('mock cut', () => { diff --git a/js/src/views/Status/util/error.spec.js b/js/src/views/Status/util/error.spec.js index f3ba98cc8..7512a47bc 100644 --- a/js/src/views/Status/util/error.spec.js +++ b/js/src/views/Status/util/error.spec.js @@ -17,7 +17,7 @@ import sinon from 'sinon'; import * as ErrorUtil from './error'; -describe('util/error', () => { +describe('views/Status/util/error', () => { beforeEach('spy on isError', () => { sinon.spy(ErrorUtil, 'isError'); }); diff --git a/js/src/views/Status/util/index.spec.js b/js/src/views/Status/util/index.spec.js index e648646c1..39d17322a 100644 --- a/js/src/views/Status/util/index.spec.js +++ b/js/src/views/Status/util/index.spec.js @@ -16,7 +16,7 @@ import { toPromise, identity } from './'; -describe('util', () => { +describe('views/Status/util', () => { describe('toPromise', () => { it('rejects on error result', () => { const ERROR = new Error(); From efd66f566d072f5f4dd45814d459507b5c344668 Mon Sep 17 00:00:00 2001 From: Robert Habermeier <rphmeier@gmail.com> Date: Thu, 8 Dec 2016 19:52:48 +0100 Subject: [PATCH 18/34] ethsync: optional ipc codegen --- Cargo.lock | 2 ++ Cargo.toml | 3 +-- ethcore/Cargo.toml | 1 - ethcore/light/Cargo.toml | 4 ++-- ethcore/src/client/client.rs | 1 - ethcore/src/client/mod.rs | 1 - ethcore/src/client/traits.rs | 1 - ethcore/src/state/mod.rs | 2 -- sync/Cargo.toml | 7 +++--- sync/src/api.rs | 42 ++++++++++++++++++------------------ sync/src/lib.rs | 8 ++++--- sync/src/tests/mod.rs | 2 ++ 12 files changed, 36 insertions(+), 38 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4a5534dbe..b00701f32 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -462,6 +462,8 @@ version = "1.5.0" dependencies = [ "ethcore 1.5.0", "ethcore-io 1.5.0", + "ethcore-ipc 1.4.0", + "ethcore-ipc-codegen 1.4.0", "ethcore-network 1.5.0", "ethcore-util 1.5.0", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index 4730cef0d..078d2916c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,7 +55,7 @@ winapi = "0.2" daemonize = "0.2" [features] -default = ["ui-precompiled", "light"] +default = ["ui-precompiled"] ui = [ "dapps", @@ -80,7 +80,6 @@ ethstore-cli = ["ethcore/ethstore-cli"] evm-debug = ["ethcore/evm-debug"] evm-debug-tests = ["ethcore/evm-debug-tests"] slow-blocks = ["ethcore/slow-blocks"] -light = ["ethcore/light", "ethsync/light"] [[bin]] path = "parity/main.rs" diff --git a/ethcore/Cargo.toml b/ethcore/Cargo.toml index 2c3d946f4..bd87c422f 100644 --- a/ethcore/Cargo.toml +++ b/ethcore/Cargo.toml @@ -58,6 +58,5 @@ dev = ["clippy"] default = [] benches = [] ipc = [] -light = [] ethkey-cli = ["ethkey/cli"] ethstore-cli = ["ethstore/cli"] diff --git a/ethcore/light/Cargo.toml b/ethcore/light/Cargo.toml index 287044c98..c89bbc74f 100644 --- a/ethcore/light/Cargo.toml +++ b/ethcore/light/Cargo.toml @@ -12,7 +12,7 @@ build = "build.rs" [dependencies] log = "0.3" -ethcore = { path = "..", features = ["light"] } +ethcore = { path = ".."} ethcore-util = { path = "../../util" } ethcore-network = { path = "../../util/network" } ethcore-io = { path = "../../util/io" } @@ -22,4 +22,4 @@ time = "0.1" [features] default = [] -ipc = ["ethcore/ipc", "ethcore-ipc", "ethcore-ipc-codegen"] \ No newline at end of file +ipc = ["ethcore-ipc", "ethcore-ipc-codegen"] \ No newline at end of file diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index ee72ea8b8..9cfad4b96 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -1377,7 +1377,6 @@ impl MayPanic for Client { } } -#[cfg(feature = "light")] impl ::client::ProvingBlockChainClient for Client { fn prove_storage(&self, key1: H256, key2: H256, from_level: u32, id: BlockID) -> Vec<Bytes> { self.state_at(id) diff --git a/ethcore/src/client/mod.rs b/ethcore/src/client/mod.rs index beab88e8a..4e5554b01 100644 --- a/ethcore/src/client/mod.rs +++ b/ethcore/src/client/mod.rs @@ -29,7 +29,6 @@ pub use self::test_client::{TestBlockChainClient, EachBlockWith}; pub use self::chain_notify::ChainNotify; pub use self::traits::{BlockChainClient, MiningBlockChainClient}; -#[cfg(feature = "light")] pub use self::traits::ProvingBlockChainClient; pub use types::ids::*; diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index 3eed9aa5d..7bf17279c 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -278,7 +278,6 @@ pub trait MiningBlockChainClient: BlockChainClient { } /// Extended client interface for providing proofs of the state. -#[cfg(feature = "light")] pub trait ProvingBlockChainClient: BlockChainClient { /// Prove account storage at a specific block id. /// diff --git a/ethcore/src/state/mod.rs b/ethcore/src/state/mod.rs index d39481048..8a52b62ff 100644 --- a/ethcore/src/state/mod.rs +++ b/ethcore/src/state/mod.rs @@ -32,7 +32,6 @@ use state_db::StateDB; use util::*; -#[cfg(feature = "light")] use util::trie::recorder::{Recorder, BasicRecorder as TrieRecorder}; mod account; @@ -764,7 +763,6 @@ impl State { } // LES state proof implementations. -#[cfg(feature = "light")] impl State { /// Prove an account's existence or nonexistence in the state trie. /// Returns a merkle proof of the account's trie node with all nodes before `from_level` diff --git a/sync/Cargo.toml b/sync/Cargo.toml index df0f4840d..f35852558 100644 --- a/sync/Cargo.toml +++ b/sync/Cargo.toml @@ -15,7 +15,7 @@ ethcore-ipc-codegen = { path = "../ipc/codegen" } ethcore-util = { path = "../util" } ethcore-network = { path = "../util/network" } ethcore-io = { path = "../util/io" } -ethcore-light = { path = "../ethcore/light", optional = true } +ethcore-light = { path = "../ethcore/light"} ethcore = { path = "../ethcore" } rlp = { path = "../util/rlp" } clippy = { version = "0.0.103", optional = true} @@ -30,7 +30,6 @@ ethcore-ipc-nano = { path = "../ipc/nano" } parking_lot = "0.3" [features] -default = ["ipc"] +default = [] dev = ["clippy", "ethcore/dev", "ethcore-util/dev"] -light = ["ethcore-light"] -ipc = [] +ipc = ["ethcore-light/ipc"] diff --git a/sync/src/api.rs b/sync/src/api.rs index 5613b77ff..d8df149c8 100644 --- a/sync/src/api.rs +++ b/sync/src/api.rs @@ -85,14 +85,18 @@ pub trait SyncProvider: Send + Sync { } /// Transaction stats -#[derive(Debug, Binary)] +#[derive(Debug)] +#[cfg_attr(feature = "ipc", derive(Binary))] pub struct TransactionStats { + /// Block number where this TX was first seen. pub first_seen: u64, + /// Peers it was propagated to. pub propagated_to: BTreeMap<H512, usize>, } /// Peer connection information -#[derive(Debug, Binary)] +#[derive(Debug)] +#[cfg_attr(feature = "ipc", derive(Binary))] pub struct PeerInfo { /// Public node id pub id: Option<String>, @@ -120,8 +124,6 @@ pub struct EthSync { handler: Arc<SyncProtocolHandler>, /// The main subprotocol name subprotocol_name: [u8; 3], - /// Configuration - config: NetworkConfiguration, } impl EthSync { @@ -138,14 +140,13 @@ impl EthSync { overlay: RwLock::new(HashMap::new()), }), subprotocol_name: config.subprotocol_name, - config: network_config, }); Ok(sync) } } -#[ipc(client_ident="SyncClient")] +#[cfg_attr(feature = "ipc", ipc(client_ident="SyncClient"))] impl SyncProvider for EthSync { /// Get sync status fn status(&self) -> SyncStatus { @@ -279,7 +280,7 @@ pub trait ManageNetwork : Send + Sync { } -#[ipc(client_ident="NetworkManagerClient")] +#[cfg_attr(feature = "ipc", ipc(client_ident="NetworkManagerClient"))] impl ManageNetwork for EthSync { fn accept_unreserved_peers(&self) { self.network.set_non_reserved_mode(NonReservedPeerMode::Accept); @@ -315,7 +316,8 @@ impl ManageNetwork for EthSync { } /// IP fiter -#[derive(Binary, Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "ipc", derive(Binary))] pub enum AllowIP { /// Connect to any address All, @@ -337,7 +339,8 @@ impl AllowIP { } } -#[derive(Binary, Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "ipc", derive(Binary))] /// Network service configuration pub struct NetworkConfiguration { /// Directory path to store general network configuration. None means nothing will be saved @@ -375,26 +378,18 @@ pub struct NetworkConfiguration { } impl NetworkConfiguration { + /// Create a new default config. pub fn new() -> Self { From::from(BasicNetworkConfiguration::new()) } + /// Create a new local config. pub fn new_local() -> Self { From::from(BasicNetworkConfiguration::new_local()) } - fn validate(&self) -> Result<(), AddrParseError> { - if let Some(ref addr) = self.listen_address { - try!(SocketAddr::from_str(&addr)); - } - if let Some(ref addr) = self.public_address { - try!(SocketAddr::from_str(&addr)); - } - Ok(()) - } - + /// Attempt to convert this config into a BasicNetworkConfiguration. pub fn into_basic(self) -> Result<BasicNetworkConfiguration, AddrParseError> { - Ok(BasicNetworkConfiguration { config_path: self.config_path, net_config_path: self.net_config_path, @@ -447,9 +442,14 @@ impl From<BasicNetworkConfiguration> for NetworkConfiguration { } } -#[derive(Debug, Binary, Clone)] +/// Configuration for IPC service. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "ipc", derive(Binary))] pub struct ServiceConfiguration { + /// Sync config. pub sync: SyncConfig, + /// Network configuration. pub net: NetworkConfiguration, + /// IPC path. pub io_path: String, } diff --git a/sync/src/lib.rs b/sync/src/lib.rs index 7a4f22fd7..801fcbbd5 100644 --- a/sync/src/lib.rs +++ b/sync/src/lib.rs @@ -37,10 +37,8 @@ extern crate semver; extern crate parking_lot; extern crate rlp; -#[cfg(feature = "light")] extern crate ethcore_light as light; - #[macro_use] extern crate log; #[macro_use] @@ -60,12 +58,16 @@ mod transactions_stats; #[cfg(test)] mod tests; +#[cfg(feature = "ipc")] mod api { #![allow(dead_code, unused_assignments, unused_variables, missing_docs)] // codegen issues include!(concat!(env!("OUT_DIR"), "/api.rs")); } -pub use api::{EthSync, SyncProvider, SyncClient, NetworkManagerClient, ManageNetwork, SyncConfig, +#[cfg(not(feature = "ipc"))] +mod api; + +pub use api::{EthSync, SyncProvider, ManageNetwork, SyncConfig, ServiceConfiguration, NetworkConfiguration, PeerInfo, AllowIP, TransactionStats}; pub use chain::{SyncStatus, SyncState}; pub use network::{is_valid_node_url, NonReservedPeerMode, NetworkError}; diff --git a/sync/src/tests/mod.rs b/sync/src/tests/mod.rs index bdb4ae4f9..62d110075 100644 --- a/sync/src/tests/mod.rs +++ b/sync/src/tests/mod.rs @@ -17,4 +17,6 @@ pub mod helpers; pub mod snapshot; mod chain; + +#[cfg(feature = "ipc")] mod rpc; From 6f5f1f5e26831575bee4d395989fb528f48a3272 Mon Sep 17 00:00:00 2001 From: Robert Habermeier <rphmeier@gmail.com> Date: Thu, 8 Dec 2016 23:21:47 +0100 Subject: [PATCH 19/34] light: integrate with sync + serve_light CLI --- Cargo.lock | 1 + Cargo.toml | 1 + ethcore/light/build.rs | 1 + ethcore/light/src/lib.rs | 13 +++ ethcore/light/src/net/buffer_flow.rs | 13 +++ ethcore/light/src/net/mod.rs | 46 ++++++++--- ethcore/light/src/net/status.rs | 33 ++------ ethcore/light/src/net/tests/mod.rs | 10 +-- ethcore/light/src/provider.rs | 1 + parity/cli/config.full.toml | 1 + parity/cli/mod.rs | 5 ++ parity/cli/usage.txt | 1 + parity/configuration.rs | 2 + parity/main.rs | 1 + parity/modules.rs | 30 +++++-- parity/run.rs | 15 +++- parity/sync.rs | 10 ++- sync/build.rs | 6 +- sync/src/api.rs | 118 ++++++++++++++++++++++----- sync/src/lib.rs | 2 +- 20 files changed, 236 insertions(+), 74 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b00701f32..9184d50f2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,6 +18,7 @@ dependencies = [ "ethcore-ipc-hypervisor 1.2.0", "ethcore-ipc-nano 1.4.0", "ethcore-ipc-tests 0.1.0", + "ethcore-light 1.5.0", "ethcore-logger 1.5.0", "ethcore-rpc 1.5.0", "ethcore-signer 1.5.0", diff --git a/Cargo.toml b/Cargo.toml index 078d2916c..7e989b173 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,6 +47,7 @@ rlp = { path = "util/rlp" } ethcore-stratum = { path = "stratum" } ethcore-dapps = { path = "dapps", optional = true } clippy = { version = "0.0.103", optional = true} +ethcore-light = { path = "ethcore/light" } [target.'cfg(windows)'.dependencies] winapi = "0.2" diff --git a/ethcore/light/build.rs b/ethcore/light/build.rs index 43915c1cf..7d4e0064c 100644 --- a/ethcore/light/build.rs +++ b/ethcore/light/build.rs @@ -20,6 +20,7 @@ extern crate ethcore_ipc_codegen; #[cfg(feature = "ipc")] fn main() { ethcore_ipc_codegen::derive_binary("src/types/mod.rs.in").unwrap(); + ethcore_ipc_codegen::derive_ipc_cond("src/provider.rs", true).unwrap(); } #[cfg(not(feature = "ipc"))] diff --git a/ethcore/light/src/lib.rs b/ethcore/light/src/lib.rs index c00467c4c..7fa2f5911 100644 --- a/ethcore/light/src/lib.rs +++ b/ethcore/light/src/lib.rs @@ -33,8 +33,21 @@ pub mod client; pub mod net; + +#[cfg(not(feature = "ipc"))] pub mod provider; +#[cfg(feature = "ipc")] +pub mod provider { + #![allow(dead_code, unused_assignments, unused_variables, missing_docs)] // codegen issues + include!(concat!(env!("OUT_DIR"), "/provider.rs")); +} + +#[cfg(feature = "ipc")] +pub mod remote { + pub use provider::LightProviderClient; +} + mod types; pub use self::provider::Provider; diff --git a/ethcore/light/src/net/buffer_flow.rs b/ethcore/light/src/net/buffer_flow.rs index 6730c71a7..2371c6ea4 100644 --- a/ethcore/light/src/net/buffer_flow.rs +++ b/ethcore/light/src/net/buffer_flow.rs @@ -22,6 +22,9 @@ //! //! This module provides an interface for configuration of buffer //! flow costs and recharge rates. +//! +//! Current default costs are picked completely arbitrarily, not based +//! on any empirical timings or mathematical models. use request; use super::packet; @@ -273,6 +276,16 @@ impl FlowParams { } } +impl Default for FlowParams { + fn default() -> Self { + FlowParams { + limit: 50_000_000.into(), + costs: CostTable::default(), + recharge: 100_000.into(), + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/ethcore/light/src/net/mod.rs b/ethcore/light/src/net/mod.rs index 57dc77a86..fd64f4a4b 100644 --- a/ethcore/light/src/net/mod.rs +++ b/ethcore/light/src/net/mod.rs @@ -30,13 +30,14 @@ use util::{Bytes, Mutex, RwLock, U256}; use time::SteadyTime; use std::collections::HashMap; +use std::sync::Arc; use std::sync::atomic::{AtomicUsize, Ordering}; use provider::Provider; use request::{self, Request}; use self::buffer_flow::{Buffer, FlowParams}; -use self::context::{IoContext, EventContext, Ctx}; +use self::context::Ctx; use self::error::{Error, Punishment}; mod buffer_flow; @@ -47,16 +48,20 @@ mod status; #[cfg(test)] mod tests; -pub use self::status::{Status, Capabilities, Announcement, NetworkId}; +pub use self::context::{EventContext, IoContext}; +pub use self::status::{Status, Capabilities, Announcement}; const TIMEOUT: TimerToken = 0; const TIMEOUT_INTERVAL_MS: u64 = 1000; -// LPV1 -const PROTOCOL_VERSION: u32 = 1; +// Supported protocol versions. +pub const PROTOCOL_VERSIONS: &'static [u8] = &[1]; -// TODO [rob] make configurable. -const PROTOCOL_ID: [u8; 3] = *b"les"; +// Max protocol version. +pub const MAX_PROTOCOL_VERSION: u8 = 1; + +// Packet count for LES. +pub const PACKET_COUNT: u8 = 15; // packet ID definitions. mod packet { @@ -173,6 +178,8 @@ pub trait Handler: Send + Sync { /// Called when a peer responds with header proofs. Each proof is a block header coupled /// with a series of trie nodes is ascending order by distance from the root. fn on_header_proofs(&self, _ctx: &EventContext, _req_id: ReqId, _proofs: &[(Bytes, Vec<Bytes>)]) { } + /// Called on abort. + fn on_abort(&self) { } } // a request, the peer who it was made to, and the time it was made. @@ -185,7 +192,7 @@ struct Requested { /// Protocol parameters. pub struct Params { /// Network id. - pub network_id: NetworkId, + pub network_id: u64, /// Buffer flow parameters. pub flow_params: FlowParams, /// Initial capabilities. @@ -203,9 +210,9 @@ pub struct Params { // Locks must be acquired in the order declared, and when holding a read lock // on the peers, only one peer may be held at a time. pub struct LightProtocol { - provider: Box<Provider>, + provider: Arc<Provider>, genesis_hash: H256, - network_id: NetworkId, + network_id: u64, pending_peers: RwLock<HashMap<PeerId, PendingPeer>>, peers: RwLock<HashMap<PeerId, Mutex<Peer>>>, pending_requests: RwLock<HashMap<usize, Requested>>, @@ -217,7 +224,7 @@ pub struct LightProtocol { impl LightProtocol { /// Create a new instance of the protocol manager. - pub fn new(provider: Box<Provider>, params: Params) -> Self { + pub fn new(provider: Arc<Provider>, params: Params) -> Self { let genesis_hash = provider.chain_info().genesis_hash; LightProtocol { provider: provider, @@ -323,6 +330,23 @@ impl LightProtocol { self.handlers.push(handler); } + /// Signal to handlers that network activity is being aborted + /// and clear peer data. + pub fn abort(&self) { + for handler in &self.handlers { + handler.on_abort(); + } + + // acquire in order and hold. + let mut pending_peers = self.pending_peers.write(); + let mut peers = self.peers.write(); + let mut pending_requests = self.pending_requests.write(); + + pending_peers.clear(); + peers.clear(); + pending_requests.clear(); + } + // Does the common pre-verification of responses before the response itself // is actually decoded: // - check whether peer exists @@ -460,7 +484,7 @@ impl LightProtocol { head_hash: chain_info.best_block_hash, head_num: chain_info.best_block_number, genesis_hash: chain_info.genesis_hash, - protocol_version: PROTOCOL_VERSION, + protocol_version: MAX_PROTOCOL_VERSION as u32, network_id: self.network_id, last_head: None, }; diff --git a/ethcore/light/src/net/status.rs b/ethcore/light/src/net/status.rs index 2c0c5f79a..6d0a76823 100644 --- a/ethcore/light/src/net/status.rs +++ b/ethcore/light/src/net/status.rs @@ -82,26 +82,6 @@ impl Key { } } -/// Network ID structure. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(u32)] -pub enum NetworkId { - /// ID for the mainnet - Mainnet = 1, - /// ID for the testnet - Testnet = 0, -} - -impl NetworkId { - fn from_raw(raw: u32) -> Option<Self> { - match raw { - 0 => Some(NetworkId::Testnet), - 1 => Some(NetworkId::Mainnet), - _ => None, - } - } -} - // helper for decoding key-value pairs in the handshake or an announcement. struct Parser<'a> { pos: usize, @@ -164,7 +144,7 @@ pub struct Status { /// Protocol version. pub protocol_version: u32, /// Network id of this peer. - pub network_id: NetworkId, + pub network_id: u64, /// Total difficulty of the head of the chain. pub head_td: U256, /// Hash of the best block. @@ -225,8 +205,7 @@ pub fn parse_handshake(rlp: UntrustedRlp) -> Result<(Status, Capabilities, FlowP let status = Status { protocol_version: try!(parser.expect(Key::ProtocolVersion)), - network_id: try!(parser.expect(Key::NetworkId) - .and_then(|id: u32| NetworkId::from_raw(id).ok_or(DecoderError::Custom("Invalid network ID")))), + network_id: try!(parser.expect(Key::NetworkId)), head_td: try!(parser.expect(Key::HeadTD)), head_hash: try!(parser.expect(Key::HeadHash)), head_num: try!(parser.expect(Key::HeadNum)), @@ -254,7 +233,7 @@ pub fn parse_handshake(rlp: UntrustedRlp) -> Result<(Status, Capabilities, FlowP pub fn write_handshake(status: &Status, capabilities: &Capabilities, flow_params: &FlowParams) -> Vec<u8> { let mut pairs = Vec::new(); pairs.push(encode_pair(Key::ProtocolVersion, &status.protocol_version)); - pairs.push(encode_pair(Key::NetworkId, &(status.network_id as u32))); + pairs.push(encode_pair(Key::NetworkId, &(status.network_id as u64))); 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)); @@ -385,7 +364,7 @@ mod tests { fn full_handshake() { let status = Status { protocol_version: 1, - network_id: NetworkId::Mainnet, + network_id: 1, head_td: U256::default(), head_hash: H256::default(), head_num: 10, @@ -420,7 +399,7 @@ mod tests { fn partial_handshake() { let status = Status { protocol_version: 1, - network_id: NetworkId::Mainnet, + network_id: 1, head_td: U256::default(), head_hash: H256::default(), head_num: 10, @@ -455,7 +434,7 @@ mod tests { fn skip_unknown_keys() { let status = Status { protocol_version: 1, - network_id: NetworkId::Mainnet, + network_id: 1, head_td: U256::default(), head_hash: H256::default(), head_num: 10, diff --git a/ethcore/light/src/net/tests/mod.rs b/ethcore/light/src/net/tests/mod.rs index e659d0681..30ab2bab2 100644 --- a/ethcore/light/src/net/tests/mod.rs +++ b/ethcore/light/src/net/tests/mod.rs @@ -25,7 +25,7 @@ use network::PeerId; use net::buffer_flow::FlowParams; use net::context::IoContext; -use net::status::{Capabilities, Status, NetworkId, write_handshake}; +use net::status::{Capabilities, Status, write_handshake}; use net::{encode_request, LightProtocol, Params, packet}; use provider::Provider; use request::{self, Request, Headers}; @@ -174,8 +174,8 @@ fn setup(flow_params: FlowParams, capabilities: Capabilities) -> (Arc<TestProvid client: TestBlockChainClient::new(), }); - let proto = LightProtocol::new(Box::new(TestProvider(provider.clone())), Params { - network_id: NetworkId::Testnet, + let proto = LightProtocol::new(Arc::new(TestProvider(provider.clone())), Params { + network_id: 2, flow_params: flow_params, capabilities: capabilities, }); @@ -185,8 +185,8 @@ fn setup(flow_params: FlowParams, capabilities: Capabilities) -> (Arc<TestProvid fn status(chain_info: BlockChainInfo) -> Status { Status { - protocol_version: ::net::PROTOCOL_VERSION, - network_id: NetworkId::Testnet, + protocol_version: 1, + network_id: 2, head_td: chain_info.total_difficulty, head_hash: chain_info.best_block_hash, head_num: chain_info.best_block_number, diff --git a/ethcore/light/src/provider.rs b/ethcore/light/src/provider.rs index 9446aa3f6..37a5cef4d 100644 --- a/ethcore/light/src/provider.rs +++ b/ethcore/light/src/provider.rs @@ -33,6 +33,7 @@ use request; /// or empty vector where appropriate. /// /// [1]: https://github.com/ethcore/parity/wiki/Light-Ethereum-Subprotocol-(LES) +#[cfg_attr(feature = "ipc", ipc(client_ident="LightProviderClient"))] pub trait Provider: Send + Sync { /// Provide current blockchain info. fn chain_info(&self) -> BlockChainInfo; diff --git a/parity/cli/config.full.toml b/parity/cli/config.full.toml index fcd9a9712..73b5e13be 100644 --- a/parity/cli/config.full.toml +++ b/parity/cli/config.full.toml @@ -32,6 +32,7 @@ warp = true allow_ips = "all" snapshot_peers = 0 max_pending_peers = 64 +serve_light = true reserved_only = false reserved_peers = "./path_to_file" diff --git a/parity/cli/mod.rs b/parity/cli/mod.rs index 7fcdd2209..2335ccee8 100644 --- a/parity/cli/mod.rs +++ b/parity/cli/mod.rs @@ -135,6 +135,8 @@ usage! { flag_reserved_only: bool = false, or |c: &Config| otry!(c.network).reserved_only.clone(), flag_no_ancient_blocks: bool = false, or |_| None, + flag_serve_light: bool = false, + or |c: &Config| otry!(c.network).serve_light.clone(), // -- API and Console Options // RPC @@ -334,6 +336,7 @@ struct Network { node_key: Option<String>, reserved_peers: Option<String>, reserved_only: Option<bool>, + serve_light: Option<bool>, } #[derive(Default, Debug, PartialEq, RustcDecodable)] @@ -543,6 +546,7 @@ mod tests { flag_reserved_peers: Some("./path_to_file".into()), flag_reserved_only: false, flag_no_ancient_blocks: false, + flag_serve_light: true, // -- API and Console Options // RPC @@ -713,6 +717,7 @@ mod tests { node_key: None, reserved_peers: Some("./path/to/reserved_peers".into()), reserved_only: Some(true), + serve_light: None, }), rpc: Some(Rpc { disable: Some(true), diff --git a/parity/cli/usage.txt b/parity/cli/usage.txt index b67af6110..58b836aa4 100644 --- a/parity/cli/usage.txt +++ b/parity/cli/usage.txt @@ -97,6 +97,7 @@ Networking Options: --max-pending-peers NUM Allow up to NUM pending connections. (default: {flag_max_pending_peers}) --no-ancient-blocks Disable downloading old blocks after snapshot restoration or warp sync. (default: {flag_no_ancient_blocks}) + --serve-light Experimental: Serve light client peers. (default: {flag_serve_light}) API and Console Options: --no-jsonrpc Disable the JSON-RPC API server. (default: {flag_no_jsonrpc}) diff --git a/parity/configuration.rs b/parity/configuration.rs index 37c699521..94fa20e59 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -275,6 +275,7 @@ impl Configuration { no_periodic_snapshot: self.args.flag_no_periodic_snapshot, check_seal: !self.args.flag_no_seal_check, download_old_blocks: !self.args.flag_no_ancient_blocks, + serve_light: self.args.flag_serve_light, }; Cmd::Run(run_cmd) }; @@ -921,6 +922,7 @@ mod tests { no_periodic_snapshot: false, check_seal: true, download_old_blocks: true, + serve_light: false, })); } diff --git a/parity/main.rs b/parity/main.rs index c125e87f6..17f5ed74b 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -43,6 +43,7 @@ extern crate serde; extern crate serde_json; extern crate rlp; extern crate ethcore_hash_fetch as hash_fetch; +extern crate ethcore_light as light; extern crate ethcore_ipc_hypervisor as hypervisor; extern crate ethcore_rpc; diff --git a/parity/modules.rs b/parity/modules.rs index 39e05a293..5d1d66cd7 100644 --- a/parity/modules.rs +++ b/parity/modules.rs @@ -15,16 +15,22 @@ // along with Parity. If not, see <http://www.gnu.org/licenses/>. use std::sync::Arc; +use std::path::Path; + use ethcore::client::BlockChainClient; use hypervisor::Hypervisor; -use ethsync::{SyncConfig, NetworkConfiguration, NetworkError}; +use ethsync::{SyncConfig, NetworkConfiguration, NetworkError, Params}; use ethcore::snapshot::SnapshotService; +use light::Provider; + #[cfg(not(feature="ipc"))] use self::no_ipc_deps::*; + +#[cfg(not(feature="ipc"))] +use ethcore_logger::Config as LogConfig; + #[cfg(feature="ipc")] use self::ipc_deps::*; -use ethcore_logger::Config as LogConfig; -use std::path::Path; #[cfg(feature="ipc")] pub mod service_urls { @@ -36,6 +42,8 @@ pub mod service_urls { pub const SYNC_NOTIFY: &'static str = "parity-sync-notify.ipc"; pub const NETWORK_MANAGER: &'static str = "parity-manage-net.ipc"; pub const SYNC_CONTROL: &'static str = "parity-sync-control.ipc"; + pub const LIGHT_PROVIDER: &'static str = "parity-light-provider.ipc"; + #[cfg(feature="stratum")] pub const STRATUM: &'static str = "parity-stratum.ipc"; #[cfg(feature="stratum")] @@ -75,6 +83,7 @@ mod ipc_deps { pub use nanoipc::{GuardedSocket, NanoSocket, generic_client, fast_client}; pub use ipc::IpcSocket; pub use ipc::binary::serialize; + pub use light::remote::LightProviderClient; } #[cfg(feature="ipc")] @@ -124,6 +133,7 @@ pub fn sync net_cfg: NetworkConfiguration, _client: Arc<BlockChainClient>, _snapshot_service: Arc<SnapshotService>, + _provider: Arc<Provider>, log_settings: &LogConfig, ) -> Result<SyncModules, NetworkError> @@ -141,7 +151,9 @@ pub fn sync &service_urls::with_base(&hypervisor.io_path, service_urls::SYNC_NOTIFY)).unwrap(); let manage_client = generic_client::<NetworkManagerClient<_>>( &service_urls::with_base(&hypervisor.io_path, service_urls::NETWORK_MANAGER)).unwrap(); - + let provider_client = generic_client::<LightProviderClient<_>>( + &service_urls::with_base(&hypervisor.io_path, service_urls::LIGHT_PROVIDER)).unwrap(); + *hypervisor_ref = Some(hypervisor); Ok((sync_client, manage_client, notify_client)) } @@ -154,10 +166,18 @@ pub fn sync net_cfg: NetworkConfiguration, client: Arc<BlockChainClient>, snapshot_service: Arc<SnapshotService>, + provider: Arc<Provider>, _log_settings: &LogConfig, ) -> Result<SyncModules, NetworkError> { - let eth_sync = try!(EthSync::new(sync_cfg, client, snapshot_service, net_cfg)); + let eth_sync = try!(EthSync::new(Params { + config: sync_cfg, + chain: client, + provider: provider, + snapshot_service: snapshot_service, + network_config: net_cfg, + })); + Ok((eth_sync.clone() as Arc<SyncProvider>, eth_sync.clone() as Arc<ManageNetwork>, eth_sync.clone() as Arc<ChainNotify>)) } diff --git a/parity/run.rs b/parity/run.rs index 42a972000..5c02e4021 100644 --- a/parity/run.rs +++ b/parity/run.rs @@ -92,6 +92,7 @@ pub struct RunCmd { pub no_periodic_snapshot: bool, pub check_seal: bool, pub download_old_blocks: bool, + pub serve_light: bool, } pub fn open_ui(dapps_conf: &dapps::Configuration, signer_conf: &signer::Configuration) -> Result<(), String> { @@ -185,6 +186,11 @@ pub fn execute(cmd: RunCmd, logger: Arc<RotatingLogger>) -> Result<(), String> { ); info!("Operating mode: {}", Colour::White.bold().paint(format!("{}", mode))); + if cmd.serve_light { + info!("Configured to serve light client peers. Please note this feature is {}.", + Colour::White.bold().paint("experimental".to_string())); + } + // display warning about using experimental journaldb alorithm if !algorithm.is_stable() { warn!("Your chosen strategy is {}! You can re-run with --pruning to change.", Colour::Red.bold().paint("unstable")); @@ -204,6 +210,7 @@ pub fn execute(cmd: RunCmd, logger: Arc<RotatingLogger>) -> Result<(), String> { sync_config.fork_block = spec.fork_block(); sync_config.warp_sync = cmd.warp_sync; sync_config.download_old_blocks = cmd.download_old_blocks; + sync_config.serve_light = cmd.serve_light; // prepare account provider let account_provider = Arc::new(try!(prepare_account_provider(&cmd.dirs, cmd.acc_conf))); @@ -268,7 +275,13 @@ pub fn execute(cmd: RunCmd, logger: Arc<RotatingLogger>) -> Result<(), String> { // create sync object let (sync_provider, manage_network, chain_notify) = try!(modules::sync( - &mut hypervisor, sync_config, net_conf.into(), client.clone(), snapshot_service.clone(), &cmd.logger_config, + &mut hypervisor, + sync_config, + net_conf.into(), + client.clone(), + snapshot_service.clone(), + client.clone(), + &cmd.logger_config, ).map_err(|e| format!("Sync error: {}", e))); service.add_notify(chain_notify.clone()); diff --git a/parity/sync.rs b/parity/sync.rs index 25f900b78..17f183c80 100644 --- a/parity/sync.rs +++ b/parity/sync.rs @@ -22,6 +22,7 @@ use hypervisor::{SYNC_MODULE_ID, HYPERVISOR_IPC_URL, ControlService}; use ethcore::client::ChainNotify; use ethcore::client::remote::RemoteClient; use ethcore::snapshot::remote::RemoteSnapshotService; +use light::remote::LightProviderClient; use ethsync::{SyncProvider, EthSync, ManageNetwork, ServiceConfiguration}; use modules::service_urls; use boot; @@ -48,8 +49,15 @@ pub fn main() { let remote_client = dependency!(RemoteClient, &service_urls::with_base(&service_config.io_path, service_urls::CLIENT)); let remote_snapshot = dependency!(RemoteSnapshotService, &service_urls::with_base(&service_config.io_path, service_urls::SNAPSHOT)); + let remote_provider = dependency!(LightProviderClient, &service_urls::with_base(&service_config.io_path, service_urls::LIGHT_PROVIDER)); - let sync = EthSync::new(service_config.sync, remote_client.service().clone(), remote_snapshot.service().clone(), service_config.net).unwrap(); + let sync = EthSync::new(Params { + config: service_config.sync, + chain: remote_client.service().clone(), + snapshot_service: remote_snapshot.service().clone(), + provider: remote_provider.service().clone(), + network_config: service_config.net + }).unwrap(); let _ = boot::main_thread(); let service_stop = Arc::new(AtomicBool::new(false)); diff --git a/sync/build.rs b/sync/build.rs index c465d5e34..1e08ae652 100644 --- a/sync/build.rs +++ b/sync/build.rs @@ -16,6 +16,10 @@ extern crate ethcore_ipc_codegen; +#[cfg(feature = "ipc")] fn main() { - ethcore_ipc_codegen::derive_ipc_cond("src/api.rs", cfg!(feature="ipc")).unwrap(); + ethcore_ipc_codegen::derive_ipc_cond("src/api.rs", true).unwrap(); } + +#[cfg(not(feature = "ipc"))] +fn main() {} diff --git a/sync/src/api.rs b/sync/src/api.rs index d8df149c8..acc593fb1 100644 --- a/sync/src/api.rs +++ b/sync/src/api.rs @@ -33,6 +33,7 @@ use ipc::{BinaryConvertable, BinaryConvertError, IpcConfig}; use std::str::FromStr; use parking_lot::RwLock; use chain::{ETH_PACKET_COUNT, SNAPSHOT_SYNC_PACKET_COUNT}; +use light::net::{LightProtocol, Params as LightParams, Capabilities, Handler as LightHandler, EventContext}; pub const WARP_SYNC_PROTOCOL_ID: ProtocolId = *b"par"; @@ -47,10 +48,14 @@ pub struct SyncConfig { pub network_id: u64, /// Main "eth" subprotocol name. pub subprotocol_name: [u8; 3], + /// Light "les" subprotocol name. + pub light_subprotocol_name: [u8; 3], /// Fork block to check pub fork_block: Option<(BlockNumber, H256)>, /// Enable snapshot sync pub warp_sync: bool, + /// Enable light client server. + pub serve_light: bool, } impl Default for SyncConfig { @@ -60,8 +65,10 @@ impl Default for SyncConfig { download_old_blocks: true, network_id: 1, subprotocol_name: *b"eth", + light_subprotocol_name: *b"les", fork_block: None, warp_sync: false, + serve_light: false, } } } @@ -116,30 +123,74 @@ pub struct PeerInfo { pub eth_difficulty: Option<U256>, } +/// EthSync initialization parameters. +#[cfg_attr(feature = "ipc", derive(Binary))] +pub struct Params { + /// Configuration. + pub config: SyncConfig, + /// Blockchain client. + pub chain: Arc<BlockChainClient>, + /// Snapshot service. + pub snapshot_service: Arc<SnapshotService>, + /// Light data provider. + pub provider: Arc<::light::Provider>, + /// Network layer configuration. + pub network_config: NetworkConfiguration, +} + /// Ethereum network protocol handler pub struct EthSync { /// Network service network: NetworkService, - /// Protocol handler - handler: Arc<SyncProtocolHandler>, + /// Main (eth/par) protocol handler + sync_handler: Arc<SyncProtocolHandler>, + /// Light (les) protocol handler + light_proto: Option<Arc<LightProtocol>>, /// The main subprotocol name subprotocol_name: [u8; 3], + /// Light subprotocol name. + light_subprotocol_name: [u8; 3], } impl EthSync { /// Creates and register protocol with the network service - pub fn new(config: SyncConfig, chain: Arc<BlockChainClient>, snapshot_service: Arc<SnapshotService>, network_config: NetworkConfiguration) -> Result<Arc<EthSync>, NetworkError> { - let chain_sync = ChainSync::new(config, &*chain); - let service = try!(NetworkService::new(try!(network_config.clone().into_basic()))); - let sync = Arc::new(EthSync{ + pub fn new(params: Params) -> Result<Arc<EthSync>, NetworkError> { + let pruning_info = params.chain.pruning_info(); + let light_proto = match params.config.serve_light { + false => None, + true => Some({ + let light_params = LightParams { + network_id: params.config.network_id, + flow_params: Default::default(), + capabilities: Capabilities { + serve_headers: true, + serve_chain_since: Some(pruning_info.earliest_chain), + serve_state_since: Some(pruning_info.earliest_state), + tx_relay: true, + }, + }; + + let mut light_proto = LightProtocol::new(params.provider, light_params); + light_proto.add_handler(Box::new(TxRelay(params.chain.clone()))); + + Arc::new(light_proto) + }) + }; + + let chain_sync = ChainSync::new(params.config, &*params.chain); + let service = try!(NetworkService::new(try!(params.network_config.clone().into_basic()))); + + let sync = Arc::new(EthSync { network: service, - handler: Arc::new(SyncProtocolHandler { + sync_handler: Arc::new(SyncProtocolHandler { sync: RwLock::new(chain_sync), - chain: chain, - snapshot_service: snapshot_service, + chain: params.chain, + snapshot_service: params.snapshot_service, overlay: RwLock::new(HashMap::new()), }), - subprotocol_name: config.subprotocol_name, + light_proto: light_proto, + subprotocol_name: params.config.subprotocol_name, + light_subprotocol_name: params.config.light_subprotocol_name, }); Ok(sync) @@ -150,14 +201,15 @@ impl EthSync { impl SyncProvider for EthSync { /// Get sync status fn status(&self) -> SyncStatus { - self.handler.sync.write().status() + self.sync_handler.sync.write().status() } /// Get sync peers fn peers(&self) -> Vec<PeerInfo> { + // TODO: [rob] LES peers/peer info self.network.with_context_eval(self.subprotocol_name, |context| { - let sync_io = NetSyncIo::new(context, &*self.handler.chain, &*self.handler.snapshot_service, &self.handler.overlay); - self.handler.sync.write().peers(&sync_io) + let sync_io = NetSyncIo::new(context, &*self.sync_handler.chain, &*self.sync_handler.snapshot_service, &self.sync_handler.overlay); + self.sync_handler.sync.write().peers(&sync_io) }).unwrap_or(Vec::new()) } @@ -166,7 +218,7 @@ impl SyncProvider for EthSync { } fn transactions_stats(&self) -> BTreeMap<H256, TransactionStats> { - let sync = self.handler.sync.read(); + let sync = self.sync_handler.sync.read(); sync.transactions_stats() .iter() .map(|(hash, stats)| (*hash, stats.into())) @@ -228,8 +280,8 @@ impl ChainNotify for EthSync { _duration: u64) { self.network.with_context(self.subprotocol_name, |context| { - let mut sync_io = NetSyncIo::new(context, &*self.handler.chain, &*self.handler.snapshot_service, &self.handler.overlay); - self.handler.sync.write().chain_new_blocks( + let mut sync_io = NetSyncIo::new(context, &*self.sync_handler.chain, &*self.sync_handler.snapshot_service, &self.sync_handler.overlay); + self.sync_handler.sync.write().chain_new_blocks( &mut sync_io, &imported, &invalid, @@ -245,19 +297,36 @@ impl ChainNotify for EthSync { Err(err) => warn!("Error starting network: {}", err), _ => {}, } - self.network.register_protocol(self.handler.clone(), self.subprotocol_name, ETH_PACKET_COUNT, &[62u8, 63u8]) + self.network.register_protocol(self.sync_handler.clone(), self.subprotocol_name, ETH_PACKET_COUNT, &[62u8, 63u8]) .unwrap_or_else(|e| warn!("Error registering ethereum protocol: {:?}", e)); // register the warp sync subprotocol - self.network.register_protocol(self.handler.clone(), WARP_SYNC_PROTOCOL_ID, SNAPSHOT_SYNC_PACKET_COUNT, &[1u8]) + self.network.register_protocol(self.sync_handler.clone(), WARP_SYNC_PROTOCOL_ID, SNAPSHOT_SYNC_PACKET_COUNT, &[1u8]) .unwrap_or_else(|e| warn!("Error registering snapshot sync protocol: {:?}", e)); + + // register the light protocol. + if let Some(light_proto) = self.light_proto.as_ref().map(|x| x.clone()) { + self.network.register_protocol(light_proto, self.light_subprotocol_name, ::light::net::PACKET_COUNT, ::light::net::PROTOCOL_VERSIONS) + .unwrap_or_else(|e| warn!("Error registering light client protocol: {:?}", e)); + } } fn stop(&self) { - self.handler.snapshot_service.abort_restore(); + self.sync_handler.snapshot_service.abort_restore(); self.network.stop().unwrap_or_else(|e| warn!("Error stopping network: {:?}", e)); } } +/// LES event handler. +/// Simply queues transactions from light client peers. +struct TxRelay(Arc<BlockChainClient>); + +impl LightHandler for TxRelay { + fn on_transactions(&self, ctx: &EventContext, relay: &[::ethcore::transaction::SignedTransaction]) { + trace!(target: "les", "Relaying {} transactions from peer {}", relay.len(), ctx.peer()); + self.0.queue_transactions(relay.iter().map(|tx| ::rlp::encode(tx).to_vec()).collect()) + } +} + impl IpcConfig for ManageNetwork { } impl IpcConfig for SyncProvider { } @@ -304,9 +373,14 @@ impl ManageNetwork for EthSync { fn stop_network(&self) { self.network.with_context(self.subprotocol_name, |context| { - let mut sync_io = NetSyncIo::new(context, &*self.handler.chain, &*self.handler.snapshot_service, &self.handler.overlay); - self.handler.sync.write().abort(&mut sync_io); + let mut sync_io = NetSyncIo::new(context, &*self.sync_handler.chain, &*self.sync_handler.snapshot_service, &self.sync_handler.overlay); + self.sync_handler.sync.write().abort(&mut sync_io); }); + + if let Some(light_proto) = self.light_proto.as_ref() { + light_proto.abort(); + } + self.stop(); } @@ -452,4 +526,4 @@ pub struct ServiceConfiguration { pub net: NetworkConfiguration, /// IPC path. pub io_path: String, -} +} \ No newline at end of file diff --git a/sync/src/lib.rs b/sync/src/lib.rs index 801fcbbd5..09f79f16f 100644 --- a/sync/src/lib.rs +++ b/sync/src/lib.rs @@ -67,7 +67,7 @@ mod api { #[cfg(not(feature = "ipc"))] mod api; -pub use api::{EthSync, SyncProvider, ManageNetwork, SyncConfig, +pub use api::{EthSync, Params, SyncProvider, ManageNetwork, SyncConfig, ServiceConfiguration, NetworkConfiguration, PeerInfo, AllowIP, TransactionStats}; pub use chain::{SyncStatus, SyncState}; pub use network::{is_valid_node_url, NonReservedPeerMode, NetworkError}; From e7ce8c95588878f71ffeeb6509dbda0944e82ccd Mon Sep 17 00:00:00 2001 From: Robert Habermeier <rphmeier@gmail.com> Date: Thu, 8 Dec 2016 23:57:09 +0100 Subject: [PATCH 20/34] light: broadcast status updates to peers --- ethcore/light/src/net/mod.rs | 21 ++++++++++++++++++++- sync/src/api.rs | 21 +++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/ethcore/light/src/net/mod.rs b/ethcore/light/src/net/mod.rs index fd64f4a4b..7c186aa94 100644 --- a/ethcore/light/src/net/mod.rs +++ b/ethcore/light/src/net/mod.rs @@ -27,7 +27,7 @@ use network::{NetworkProtocolHandler, NetworkContext, NetworkError, PeerId}; use rlp::{RlpStream, Stream, UntrustedRlp, View}; use util::hash::H256; use util::{Bytes, Mutex, RwLock, U256}; -use time::SteadyTime; +use time::{Duration, SteadyTime}; use std::collections::HashMap; use std::sync::Arc; @@ -54,6 +54,9 @@ pub use self::status::{Status, Capabilities, Announcement}; const TIMEOUT: TimerToken = 0; const TIMEOUT_INTERVAL_MS: u64 = 1000; +// minimum interval between updates. +const UPDATE_INTERVAL_MS: i64 = 5000; + // Supported protocol versions. pub const PROTOCOL_VERSIONS: &'static [u8] = &[1]; @@ -107,6 +110,7 @@ pub struct ReqId(usize); // may not have received one for. struct PendingPeer { sent_head: H256, + last_update: SteadyTime, } // data about each peer. @@ -117,6 +121,7 @@ struct Peer { capabilities: Capabilities, remote_flow: FlowParams, sent_head: H256, // last head we've given them. + last_update: SteadyTime, } impl Peer { @@ -293,6 +298,7 @@ impl LightProtocol { /// The announcement is expected to be valid. pub fn make_announcement(&self, io: &IoContext, mut announcement: Announcement) { let mut reorgs_map = HashMap::new(); + let now = SteadyTime::now(); // update stored capabilities self.capabilities.write().update_from(&announcement); @@ -300,6 +306,17 @@ impl LightProtocol { // calculate reorg info and send packets for (peer_id, peer_info) in self.peers.read().iter() { let mut peer_info = peer_info.lock(); + + // TODO: "urgent" announcements like new blocks? + // the timer approach will skip 1 (possibly 2) in rare occasions. + if peer_info.sent_head == announcement.head_hash || + peer_info.status.head_num >= announcement.head_num || + now - peer_info.last_update < Duration::milliseconds(UPDATE_INTERVAL_MS) { + continue + } + + peer_info.last_update = now; + let reorg_depth = reorgs_map.entry(peer_info.sent_head) .or_insert_with(|| { match self.provider.reorg_depth(&announcement.head_hash, &peer_info.sent_head) { @@ -496,6 +513,7 @@ impl LightProtocol { Ok(PendingPeer { sent_head: chain_info.best_block_hash, + last_update: SteadyTime::now(), }) } @@ -523,6 +541,7 @@ impl LightProtocol { capabilities: capabilities.clone(), remote_flow: flow_params, sent_head: pending.sent_head, + last_update: pending.last_update, })); for handler in &self.handlers { diff --git a/sync/src/api.rs b/sync/src/api.rs index acc593fb1..7c531bf7c 100644 --- a/sync/src/api.rs +++ b/sync/src/api.rs @@ -279,6 +279,8 @@ impl ChainNotify for EthSync { sealed: Vec<H256>, _duration: u64) { + use light::net::Announcement; + self.network.with_context(self.subprotocol_name, |context| { let mut sync_io = NetSyncIo::new(context, &*self.sync_handler.chain, &*self.sync_handler.snapshot_service, &self.sync_handler.overlay); self.sync_handler.sync.write().chain_new_blocks( @@ -289,6 +291,25 @@ impl ChainNotify for EthSync { &retracted, &sealed); }); + + self.network.with_context(self.light_subprotocol_name, |context| { + let light_proto = match self.light_proto.as_ref() { + Some(lp) => lp, + None => return, + }; + + let chain_info = self.sync_handler.chain.chain_info(); + light_proto.make_announcement(context, Announcement { + head_hash: chain_info.best_block_hash, + head_num: chain_info.best_block_number, + head_td: chain_info.total_difficulty, + reorg_depth: 0, // recalculated on a per-peer basis. + serve_headers: false, // these fields consist of _changes_ in capability. + serve_state_since: None, + serve_chain_since: None, + tx_relay: false, + }) + }) } fn start(&self) { From d53c47aa69bc9717a55819391961b417fb079796 Mon Sep 17 00:00:00 2001 From: Robert Habermeier <rphmeier@gmail.com> Date: Fri, 9 Dec 2016 00:35:34 +0100 Subject: [PATCH 21/34] more tracing --- ethcore/light/src/net/mod.rs | 9 +++++++++ ethcore/light/src/net/status.rs | 1 + 2 files changed, 10 insertions(+) diff --git a/ethcore/light/src/net/mod.rs b/ethcore/light/src/net/mod.rs index 7c186aa94..0eda7b77c 100644 --- a/ethcore/light/src/net/mod.rs +++ b/ethcore/light/src/net/mod.rs @@ -230,6 +230,8 @@ pub struct LightProtocol { impl LightProtocol { /// Create a new instance of the protocol manager. pub fn new(provider: Arc<Provider>, params: Params) -> Self { + debug!(target: "les", "Initializing LES handler"); + let genesis_hash = provider.chain_info().genesis_hash; LightProtocol { provider: provider, @@ -400,6 +402,8 @@ impl LightProtocol { fn handle_packet(&self, io: &IoContext, peer: &PeerId, packet_id: u8, data: &[u8]) { let rlp = UntrustedRlp::new(data); + trace!(target: "les", "Incoming packet {} from peer {}", packet_id, peer); + // handle the packet let res = match packet_id { packet::STATUS => self.status(peer, io, rlp), @@ -452,6 +456,8 @@ impl LightProtocol { fn on_connect(&self, peer: &PeerId, io: &IoContext) { let peer = *peer; + trace!(target: "les", "Peer {} connecting", peer); + match self.send_status(peer, io) { Ok(pending_peer) => { self.pending_peers.write().insert(peer, pending_peer); @@ -465,6 +471,9 @@ impl LightProtocol { // called when a peer disconnects. fn on_disconnect(&self, peer: PeerId, io: &IoContext) { + trace!(target: "les", "Peer {} disconnecting", peer); + + self.pending_peers.write().remove(&peer); if self.peers.write().remove(&peer).is_some() { let unfulfilled: Vec<_> = self.pending_requests.read() diff --git a/ethcore/light/src/net/status.rs b/ethcore/light/src/net/status.rs index 6d0a76823..eb80fbe44 100644 --- a/ethcore/light/src/net/status.rs +++ b/ethcore/light/src/net/status.rs @@ -98,6 +98,7 @@ impl<'a> Parser<'a> { // expect a specific next key, and get the value's RLP. // if the key isn't found, the position isn't advanced. fn expect_raw(&mut self, key: Key) -> Result<UntrustedRlp<'a>, DecoderError> { + trace!(target: "les", "Expecting key {}", key.as_str()); let pre_pos = self.pos; if let Some((k, val)) = try!(self.get_next()) { if k == key { return Ok(val) } From 07d7a3731958f60644bfbff599c87a7b1108d0a5 Mon Sep 17 00:00:00 2001 From: Robert Habermeier <rphmeier@gmail.com> Date: Fri, 9 Dec 2016 01:06:51 +0100 Subject: [PATCH 22/34] les: make peer buffer flow params optional --- ethcore/light/src/net/error.rs | 4 ++ ethcore/light/src/net/mod.rs | 50 ++++++++++++---------- ethcore/light/src/net/status.rs | 66 ++++++++++++++++++++++-------- ethcore/light/src/net/tests/mod.rs | 24 +++++------ 4 files changed, 95 insertions(+), 49 deletions(-) diff --git a/ethcore/light/src/net/error.rs b/ethcore/light/src/net/error.rs index 86dbd54ba..c9ba85a42 100644 --- a/ethcore/light/src/net/error.rs +++ b/ethcore/light/src/net/error.rs @@ -56,6 +56,8 @@ pub enum Error { UnknownPeer, /// Unsolicited response. UnsolicitedResponse, + /// Not a server. + NotServer, } impl Error { @@ -70,6 +72,7 @@ impl Error { Error::WrongNetwork => Punishment::Disable, Error::UnknownPeer => Punishment::Disconnect, Error::UnsolicitedResponse => Punishment::Disable, + Error::NotServer => Punishment::Disable, } } } @@ -97,6 +100,7 @@ impl fmt::Display for Error { Error::WrongNetwork => write!(f, "Wrong network"), Error::UnknownPeer => write!(f, "Unknown peer"), Error::UnsolicitedResponse => write!(f, "Peer provided unsolicited data"), + Error::NotServer => write!(f, "Peer not a server."), } } } \ No newline at end of file diff --git a/ethcore/light/src/net/mod.rs b/ethcore/light/src/net/mod.rs index 0eda7b77c..1eb89a92f 100644 --- a/ethcore/light/src/net/mod.rs +++ b/ethcore/light/src/net/mod.rs @@ -116,10 +116,9 @@ struct PendingPeer { // data about each peer. struct Peer { local_buffer: Buffer, // their buffer relative to us - remote_buffer: Buffer, // our buffer relative to them status: Status, capabilities: Capabilities, - remote_flow: FlowParams, + remote_flow: Option<(Buffer, FlowParams)>, sent_head: H256, // last head we've given them. last_update: SteadyTime, } @@ -142,12 +141,6 @@ impl Peer { self.local_buffer.current() } - - // recharge remote buffer with remote flow params. - fn recharge_remote(&mut self) { - let flow = &mut self.remote_flow; - flow.recharge(&mut self.remote_buffer); - } } /// An LES event handler. @@ -250,16 +243,21 @@ impl LightProtocol { /// Check the maximum amount of requests of a specific type /// which a peer would be able to serve. pub fn max_requests(&self, peer: PeerId, kind: request::Kind) -> Option<usize> { - self.peers.read().get(&peer).map(|peer| { + self.peers.read().get(&peer).and_then(|peer| { let mut peer = peer.lock(); - peer.recharge_remote(); - peer.remote_flow.max_amount(&peer.remote_buffer, kind) + match peer.remote_flow.as_mut() { + Some(&mut (ref mut buf, ref flow)) => { + flow.recharge(buf); + Some(flow.max_amount(&*buf, kind)) + } + None => None, + } }) } /// Make a request to a peer. /// - /// Fails on: nonexistent peer, network error, + /// Fails on: nonexistent peer, network error, peer not server, /// insufficient buffer. Does not check capabilities before sending. /// On success, returns a request id which can later be coordinated /// with an event. @@ -268,10 +266,14 @@ impl LightProtocol { let peer = try!(peers.get(peer_id).ok_or_else(|| Error::UnknownPeer)); let mut peer = peer.lock(); - peer.recharge_remote(); - - let max = peer.remote_flow.compute_cost(request.kind(), request.amount()); - try!(peer.remote_buffer.deduct_cost(max)); + match peer.remote_flow.as_mut() { + Some(&mut (ref mut buf, ref flow)) => { + flow.recharge(buf); + let max = flow.compute_cost(request.kind(), request.amount()); + try!(buf.deduct_cost(max)); + } + None => return Err(Error::NotServer), + } let req_id = self.req_id.fetch_add(1, Ordering::SeqCst); let packet_data = encode_request(&request, req_id); @@ -390,8 +392,13 @@ impl LightProtocol { match peers.get(peer) { Some(peer_info) => { let mut peer_info = peer_info.lock(); - let actual_buffer = ::std::cmp::min(cur_buffer, *peer_info.remote_flow.limit()); - peer_info.remote_buffer.update_to(actual_buffer); + match peer_info.remote_flow.as_mut() { + Some(&mut (ref mut buf, ref mut flow)) => { + let actual_buffer = ::std::cmp::min(cur_buffer, *flow.limit()); + buf.update_to(actual_buffer) + } + None => return Err(Error::NotServer), // this really should be impossible. + } Ok(ReqId(req_id)) } None => Err(Error::UnknownPeer), // probably only occurs in a race of some kind. @@ -516,7 +523,7 @@ impl LightProtocol { }; let capabilities = self.capabilities.read().clone(); - let status_packet = status::write_handshake(&status, &capabilities, &self.flow_params); + let status_packet = status::write_handshake(&status, &capabilities, Some(&self.flow_params)); io.send(peer, packet::STATUS, status_packet); @@ -543,12 +550,13 @@ impl LightProtocol { return Err(Error::WrongNetwork); } + let remote_flow = flow_params.map(|params| (params.create_buffer(), params)); + self.peers.write().insert(*peer, Mutex::new(Peer { local_buffer: self.flow_params.create_buffer(), - remote_buffer: flow_params.create_buffer(), status: status.clone(), capabilities: capabilities.clone(), - remote_flow: flow_params, + remote_flow: remote_flow, sent_head: pending.sent_head, last_update: pending.last_update, })); diff --git a/ethcore/light/src/net/status.rs b/ethcore/light/src/net/status.rs index eb80fbe44..59981b88d 100644 --- a/ethcore/light/src/net/status.rs +++ b/ethcore/light/src/net/status.rs @@ -198,7 +198,7 @@ impl Capabilities { /// - chain status /// - serving capabilities /// - buffer flow parameters -pub fn parse_handshake(rlp: UntrustedRlp) -> Result<(Status, Capabilities, FlowParams), DecoderError> { +pub fn parse_handshake(rlp: UntrustedRlp) -> Result<(Status, Capabilities, Option<FlowParams>), DecoderError> { let mut parser = Parser { pos: 0, rlp: rlp, @@ -221,17 +221,20 @@ pub fn parse_handshake(rlp: UntrustedRlp) -> Result<(Status, Capabilities, FlowP tx_relay: parser.expect_raw(Key::TxRelay).is_ok(), }; - let flow_params = FlowParams::new( - try!(parser.expect(Key::BufferLimit)), - try!(parser.expect(Key::BufferCostTable)), - try!(parser.expect(Key::BufferRechargeRate)), - ); + let flow_params = match ( + parser.expect(Key::BufferLimit), + parser.expect(Key::BufferCostTable), + parser.expect(Key::BufferRechargeRate) + ) { + (Ok(bl), Ok(bct), Ok(brr)) => Some(FlowParams::new(bl, bct, brr)), + _ => None, + }; Ok((status, capabilities, flow_params)) } /// Write a handshake, given status, capabilities, and flow parameters. -pub fn write_handshake(status: &Status, capabilities: &Capabilities, flow_params: &FlowParams) -> Vec<u8> { +pub fn write_handshake(status: &Status, capabilities: &Capabilities, flow_params: Option<&FlowParams>) -> Vec<u8> { let mut pairs = Vec::new(); pairs.push(encode_pair(Key::ProtocolVersion, &status.protocol_version)); pairs.push(encode_pair(Key::NetworkId, &(status.network_id as u64))); @@ -253,9 +256,11 @@ pub fn write_handshake(status: &Status, capabilities: &Capabilities, flow_params pairs.push(encode_flag(Key::TxRelay)); } - pairs.push(encode_pair(Key::BufferLimit, flow_params.limit())); - pairs.push(encode_pair(Key::BufferCostTable, flow_params.cost_table())); - pairs.push(encode_pair(Key::BufferRechargeRate, flow_params.recharge_rate())); + if let Some(flow_params) = flow_params { + pairs.push(encode_pair(Key::BufferLimit, flow_params.limit())); + pairs.push(encode_pair(Key::BufferCostTable, flow_params.cost_table())); + pairs.push(encode_pair(Key::BufferRechargeRate, flow_params.recharge_rate())); + } let mut stream = RlpStream::new_list(pairs.len()); @@ -386,14 +391,14 @@ mod tests { 1000.into(), ); - let handshake = write_handshake(&status, &capabilities, &flow_params); + let handshake = write_handshake(&status, &capabilities, Some(&flow_params)); let (read_status, read_capabilities, read_flow) = parse_handshake(UntrustedRlp::new(&handshake)).unwrap(); assert_eq!(read_status, status); assert_eq!(read_capabilities, capabilities); - assert_eq!(read_flow, flow_params); + assert_eq!(read_flow.unwrap(), flow_params); } #[test] @@ -421,14 +426,14 @@ mod tests { 1000.into(), ); - let handshake = write_handshake(&status, &capabilities, &flow_params); + let handshake = write_handshake(&status, &capabilities, Some(&flow_params)); let (read_status, read_capabilities, read_flow) = parse_handshake(UntrustedRlp::new(&handshake)).unwrap(); assert_eq!(read_status, status); assert_eq!(read_capabilities, capabilities); - assert_eq!(read_flow, flow_params); + assert_eq!(read_flow.unwrap(), flow_params); } #[test] @@ -456,7 +461,7 @@ mod tests { 1000.into(), ); - let handshake = write_handshake(&status, &capabilities, &flow_params); + let handshake = write_handshake(&status, &capabilities, Some(&flow_params)); let interleaved = { let handshake = UntrustedRlp::new(&handshake); let mut stream = RlpStream::new_list(handshake.item_count() * 3); @@ -478,7 +483,7 @@ mod tests { assert_eq!(read_status, status); assert_eq!(read_capabilities, capabilities); - assert_eq!(read_flow, flow_params); + assert_eq!(read_flow.unwrap(), flow_params); } #[test] @@ -528,4 +533,33 @@ mod tests { let out = stream.drain(); assert!(parse_announcement(UntrustedRlp::new(&out)).is_ok()); } + + #[test] + fn optional_flow() { + let status = Status { + protocol_version: 1, + network_id: 1, + head_td: U256::default(), + head_hash: H256::default(), + head_num: 10, + genesis_hash: H256::zero(), + last_head: None, + }; + + let capabilities = Capabilities { + serve_headers: true, + serve_chain_since: Some(5), + serve_state_since: Some(8), + tx_relay: true, + }; + + let handshake = write_handshake(&status, &capabilities, None); + + let (read_status, read_capabilities, read_flow) + = parse_handshake(UntrustedRlp::new(&handshake)).unwrap(); + + assert_eq!(read_status, status); + assert_eq!(read_capabilities, capabilities); + assert!(read_flow.is_none()); + } } \ No newline at end of file diff --git a/ethcore/light/src/net/tests/mod.rs b/ethcore/light/src/net/tests/mod.rs index 30ab2bab2..7a159b6a4 100644 --- a/ethcore/light/src/net/tests/mod.rs +++ b/ethcore/light/src/net/tests/mod.rs @@ -204,7 +204,7 @@ fn handshake_expected() { let status = status(provider.client.chain_info()); - let packet_body = write_handshake(&status, &capabilities, &flow_params); + let packet_body = write_handshake(&status, &capabilities, Some(&flow_params)); proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body)); } @@ -220,7 +220,7 @@ fn genesis_mismatch() { let mut status = status(provider.client.chain_info()); status.genesis_hash = H256::default(); - let packet_body = write_handshake(&status, &capabilities, &flow_params); + let packet_body = write_handshake(&status, &capabilities, Some(&flow_params)); proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body)); } @@ -235,12 +235,12 @@ fn buffer_overflow() { let status = status(provider.client.chain_info()); { - let packet_body = write_handshake(&status, &capabilities, &flow_params); + let packet_body = write_handshake(&status, &capabilities, Some(&flow_params)); proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body)); } { - let my_status = write_handshake(&status, &capabilities, &flow_params); + let my_status = write_handshake(&status, &capabilities, Some(&flow_params)); proto.handle_packet(&Expect::Nothing, &1, packet::STATUS, &my_status); } @@ -267,14 +267,14 @@ fn get_block_headers() { 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); + let my_status = write_handshake(&cur_status, &capabilities, Some(&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); + let packet_body = write_handshake(&cur_status, &capabilities, Some(&flow_params)); proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body)); proto.handle_packet(&Expect::Nothing, &1, packet::STATUS, &my_status); } @@ -317,14 +317,14 @@ fn get_block_bodies() { 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); + let my_status = write_handshake(&cur_status, &capabilities, Some(&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); + let packet_body = write_handshake(&cur_status, &capabilities, Some(&flow_params)); proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body)); proto.handle_packet(&Expect::Nothing, &1, packet::STATUS, &my_status); } @@ -364,14 +364,14 @@ fn get_block_receipts() { 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); + let my_status = write_handshake(&cur_status, &capabilities, Some(&flow_params)); provider.client.add_blocks(1000, EachBlockWith::Nothing); let cur_status = status(provider.client.chain_info()); { - let packet_body = write_handshake(&cur_status, &capabilities, &flow_params); + let packet_body = write_handshake(&cur_status, &capabilities, Some(&flow_params)); proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body)); proto.handle_packet(&Expect::Nothing, &1, packet::STATUS, &my_status); } @@ -419,7 +419,7 @@ fn get_state_proofs() { let cur_status = status(provider.client.chain_info()); { - let packet_body = write_handshake(&cur_status, &capabilities, &flow_params); + let packet_body = write_handshake(&cur_status, &capabilities, Some(&flow_params)); proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body.clone())); proto.handle_packet(&Expect::Nothing, &1, packet::STATUS, &packet_body); } @@ -468,7 +468,7 @@ fn get_contract_code() { let cur_status = status(provider.client.chain_info()); { - let packet_body = write_handshake(&cur_status, &capabilities, &flow_params); + let packet_body = write_handshake(&cur_status, &capabilities, Some(&flow_params)); proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body.clone())); proto.handle_packet(&Expect::Nothing, &1, packet::STATUS, &packet_body); } From 296301e284d1a827fbee54750803d58480245e74 Mon Sep 17 00:00:00 2001 From: Jaco Greeff <jaco@ethcore.io> Date: Fri, 9 Dec 2016 10:55:46 +0100 Subject: [PATCH 23/34] Extend tests for TxList --- js/src/ui/TxList/TxRow/index.js | 17 ++++ js/src/ui/TxList/TxRow/txRow.js | 133 +++++++++++++++++++++++++++ js/src/ui/TxList/TxRow/txRow.spec.js | 51 ++++++++++ js/src/ui/TxList/txList.js | 114 +---------------------- js/src/ui/TxList/txList.spec.js | 52 +++++++++++ 5 files changed, 254 insertions(+), 113 deletions(-) create mode 100644 js/src/ui/TxList/TxRow/index.js create mode 100644 js/src/ui/TxList/TxRow/txRow.js create mode 100644 js/src/ui/TxList/TxRow/txRow.spec.js create mode 100644 js/src/ui/TxList/txList.spec.js diff --git a/js/src/ui/TxList/TxRow/index.js b/js/src/ui/TxList/TxRow/index.js new file mode 100644 index 000000000..90243cd5f --- /dev/null +++ b/js/src/ui/TxList/TxRow/index.js @@ -0,0 +1,17 @@ +// 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 <http://www.gnu.org/licenses/>. + +export default from './txRow'; diff --git a/js/src/ui/TxList/TxRow/txRow.js b/js/src/ui/TxList/TxRow/txRow.js new file mode 100644 index 000000000..98e948a38 --- /dev/null +++ b/js/src/ui/TxList/TxRow/txRow.js @@ -0,0 +1,133 @@ +// 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 <http://www.gnu.org/licenses/>. + +import moment from 'moment'; +import React, { Component, PropTypes } from 'react'; + +import { txLink, addressLink } from '~/3rdparty/etherscan/links'; + +import IdentityIcon from '../../IdentityIcon'; +import IdentityName from '../../IdentityName'; +import MethodDecoding from '../../MethodDecoding'; + +import styles from '../txList.css'; + +export default class TxRow extends Component { + static contextTypes = { + api: PropTypes.object.isRequired + }; + + static propTypes = { + tx: PropTypes.object.isRequired, + address: PropTypes.string.isRequired, + isTest: PropTypes.bool.isRequired, + + block: PropTypes.object, + historic: PropTypes.bool, + className: PropTypes.string + }; + + static defaultProps = { + historic: true + }; + + render () { + const { tx, address, isTest, historic, className } = this.props; + + return ( + <tr className={ className || '' }> + { this.renderBlockNumber(tx.blockNumber) } + { this.renderAddress(tx.from) } + <td className={ styles.transaction }> + { this.renderEtherValue(tx.value) } + <div>⇒</div> + <div> + <a + className={ styles.link } + href={ txLink(tx.hash, isTest) } + target='_blank'> + { `${tx.hash.substr(2, 6)}...${tx.hash.slice(-6)}` } + </a> + </div> + </td> + { this.renderAddress(tx.to) } + <td className={ styles.method }> + <MethodDecoding + historic={ historic } + address={ address } + transaction={ tx } /> + </td> + </tr> + ); + } + + renderAddress (address) { + const { isTest } = this.props; + + let esLink = null; + if (address) { + esLink = ( + <a + href={ addressLink(address, isTest) } + target='_blank' + className={ styles.link }> + <IdentityName address={ address } shorten /> + </a> + ); + } + + return ( + <td className={ styles.address }> + <div className={ styles.center }> + <IdentityIcon + center + className={ styles.icon } + address={ address } /> + </div> + <div className={ styles.center }> + { esLink || 'DEPLOY' } + </div> + </td> + ); + } + + renderEtherValue (_value) { + const { api } = this.context; + const value = api.util.fromWei(_value); + + if (value.eq(0)) { + return <div className={ styles.value }>{ ' ' }</div>; + } + + return ( + <div className={ styles.value }> + { value.toFormat(5) }<small>ETH</small> + </div> + ); + } + + renderBlockNumber (_blockNumber) { + const { block } = this.props; + const blockNumber = _blockNumber.toNumber(); + + return ( + <td className={ styles.timestamp }> + <div>{ blockNumber && block ? moment(block.timestamp).fromNow() : null }</div> + <div>{ blockNumber ? _blockNumber.toFormat() : 'Pending' }</div> + </td> + ); + } +} diff --git a/js/src/ui/TxList/TxRow/txRow.spec.js b/js/src/ui/TxList/TxRow/txRow.spec.js new file mode 100644 index 000000000..64baa2fe7 --- /dev/null +++ b/js/src/ui/TxList/TxRow/txRow.spec.js @@ -0,0 +1,51 @@ +// 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 <http://www.gnu.org/licenses/>. + +import BigNumber from 'bignumber.js'; +import React from 'react'; +import { shallow } from 'enzyme'; +import sinon from 'sinon'; + +import Api from '~/api'; + +import TxRow from './txRow'; + +const api = new Api({ execute: sinon.stub() }); + +function renderShallow (props) { + return shallow( + <TxRow + { ...props } />, + { context: { api } } + ); +} + +describe('ui/TxRow', () => { + describe('rendering', () => { + it('renders defaults', () => { + const block = { + timestamp: new Date() + }; + const tx = { + blockNumber: new BigNumber(123), + hash: '0x123456789abcdef0123456789abcdef0123456789abcdef', + value: new BigNumber(1) + }; + + expect(renderShallow({ block, tx })).to.be.ok; + }); + }); +}); diff --git a/js/src/ui/TxList/txList.js b/js/src/ui/TxList/txList.js index b8c53c1d9..45face696 100644 --- a/js/src/ui/TxList/txList.js +++ b/js/src/ui/TxList/txList.js @@ -14,128 +14,16 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see <http://www.gnu.org/licenses/>. -import moment from 'moment'; import React, { Component, PropTypes } from 'react'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import { observer } from 'mobx-react'; -import { txLink, addressLink } from '../../3rdparty/etherscan/links'; - -import IdentityIcon from '../IdentityIcon'; -import IdentityName from '../IdentityName'; -import MethodDecoding from '../MethodDecoding'; import Store from './store'; +import TxRow from './TxRow'; import styles from './txList.css'; -export class TxRow extends Component { - static contextTypes = { - api: PropTypes.object.isRequired - }; - - static propTypes = { - tx: PropTypes.object.isRequired, - address: PropTypes.string.isRequired, - isTest: PropTypes.bool.isRequired, - - block: PropTypes.object, - historic: PropTypes.bool, - className: PropTypes.string - }; - - static defaultProps = { - historic: true - }; - - render () { - const { tx, address, isTest, historic, className } = this.props; - - return ( - <tr className={ className || '' }> - { this.renderBlockNumber(tx.blockNumber) } - { this.renderAddress(tx.from) } - <td className={ styles.transaction }> - { this.renderEtherValue(tx.value) } - <div>⇒</div> - <div> - <a - className={ styles.link } - href={ txLink(tx.hash, isTest) } - target='_blank'> - { `${tx.hash.substr(2, 6)}...${tx.hash.slice(-6)}` } - </a> - </div> - </td> - { this.renderAddress(tx.to) } - <td className={ styles.method }> - <MethodDecoding - historic={ historic } - address={ address } - transaction={ tx } /> - </td> - </tr> - ); - } - - renderAddress (address) { - const { isTest } = this.props; - - let esLink = null; - if (address) { - esLink = ( - <a - href={ addressLink(address, isTest) } - target='_blank' - className={ styles.link }> - <IdentityName address={ address } shorten /> - </a> - ); - } - - return ( - <td className={ styles.address }> - <div className={ styles.center }> - <IdentityIcon - center - className={ styles.icon } - address={ address } /> - </div> - <div className={ styles.center }> - { esLink || 'DEPLOY' } - </div> - </td> - ); - } - - renderEtherValue (_value) { - const { api } = this.context; - const value = api.util.fromWei(_value); - - if (value.eq(0)) { - return <div className={ styles.value }>{ ' ' }</div>; - } - - return ( - <div className={ styles.value }> - { value.toFormat(5) }<small>ETH</small> - </div> - ); - } - - renderBlockNumber (_blockNumber) { - const { block } = this.props; - const blockNumber = _blockNumber.toNumber(); - - return ( - <td className={ styles.timestamp }> - <div>{ blockNumber && block ? moment(block.timestamp).fromNow() : null }</div> - <div>{ blockNumber ? _blockNumber.toFormat() : 'Pending' }</div> - </td> - ); - } -} - @observer class TxList extends Component { static contextTypes = { diff --git a/js/src/ui/TxList/txList.spec.js b/js/src/ui/TxList/txList.spec.js new file mode 100644 index 000000000..dcaf5fb78 --- /dev/null +++ b/js/src/ui/TxList/txList.spec.js @@ -0,0 +1,52 @@ +// 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 <http://www.gnu.org/licenses/>. + +import React from 'react'; +import { shallow } from 'enzyme'; +import sinon from 'sinon'; + +import Api from '~/api'; + +import TxList from './txList'; + +const api = new Api({ execute: sinon.stub() }); + +const STORE = { + getState: () => { + return { + nodeStatus: { + isTest: true + } + }; + } +}; + +function renderShallow (props) { + return shallow( + <TxList + store={ STORE } + { ...props } />, + { context: { api } } + ); +} + +describe('ui/TxList', () => { + describe('rendering', () => { + it('renders defaults', () => { + expect(renderShallow()).to.be.ok; + }); + }); +}); From 1f4c84cbf1a58cef3daf62151a54e580bf7a3361 Mon Sep 17 00:00:00 2001 From: Jaco Greeff <jaco@ethcore.io> Date: Fri, 9 Dec 2016 13:03:46 +0100 Subject: [PATCH 24/34] Theme tests --- js/src/ui/Theme/theme.spec.js | 55 +++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 js/src/ui/Theme/theme.spec.js diff --git a/js/src/ui/Theme/theme.spec.js b/js/src/ui/Theme/theme.spec.js new file mode 100644 index 000000000..9d2aa1bd8 --- /dev/null +++ b/js/src/ui/Theme/theme.spec.js @@ -0,0 +1,55 @@ +// 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 <http://www.gnu.org/licenses/>. + +import getMuiTheme from 'material-ui/styles/getMuiTheme'; +import lightBaseTheme from 'material-ui/styles/baseThemes/lightBaseTheme'; + +const muiTheme = getMuiTheme(lightBaseTheme); + +import theme from './theme'; + +describe('ui/Theme', () => { + it('is MUI-based', () => { + expect(Object.keys(theme)).to.deep.equal(Object.keys(muiTheme).concat('parity')); + }); + + it('allows setting of Parity backgrounds', () => { + expect(typeof theme.parity.setBackgroundSeed === 'function').to.be.true; + expect(typeof theme.parity.getBackgroundStyle === 'function').to.be.true; + }); + + describe('parity', () => { + describe('setBackgroundSeed', () => { + const SEED = 'testseed'; + + beforeEach(() => { + theme.parity.setBackgroundSeed(SEED); + }); + + it('sets the correct theme values', () => { + expect(theme.parity.backgroundSeed).to.equal(SEED); + }); + }); + + describe('getBackgroundStyle', () => { + it('generates a style containing background', () => { + const style = theme.parity.getBackgroundStyle(); + + expect(style).to.have.property('background'); + }); + }); + }); +}); From bb209424ac5f00b30ed6202f2090c3ced55250fa Mon Sep 17 00:00:00 2001 From: Jaco Greeff <jaco@ethcore.io> Date: Fri, 9 Dec 2016 13:36:46 +0100 Subject: [PATCH 25/34] IdentityName flags testing --- js/src/ui/IdentityName/identityName.spec.js | 76 +++++++++++++++++++++ js/src/ui/TxList/txList.spec.js | 2 + 2 files changed, 78 insertions(+) create mode 100644 js/src/ui/IdentityName/identityName.spec.js diff --git a/js/src/ui/IdentityName/identityName.spec.js b/js/src/ui/IdentityName/identityName.spec.js new file mode 100644 index 000000000..554a5be0a --- /dev/null +++ b/js/src/ui/IdentityName/identityName.spec.js @@ -0,0 +1,76 @@ +// 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 <http://www.gnu.org/licenses/>. + +import React from 'react'; +import { mount } from 'enzyme'; +import sinon from 'sinon'; + +import IdentityName from './identityName'; + +const ADDR_A = '0x123456789abcdef0123456789A'; +const ADDR_B = '0x123456789abcdef0123456789B'; +const ADDR_C = '0x123456789abcdef0123456789C'; +const STORE = { + dispatch: sinon.stub(), + subscribe: sinon.stub(), + getState: () => { + return { + balances: { + tokens: {} + }, + personal: { + accountsInfo: { + [ADDR_A]: { name: 'testing' }, + [ADDR_B]: {} + } + } + }; + } +}; + +function render (props) { + return mount( + <IdentityName + store={ STORE } + { ...props } /> + ); +} + +describe('ui/IdentityName', () => { + describe('rendering', () => { + it('renders defaults', () => { + expect(render()).to.be.ok; + }); + + describe('account not found', () => { + it('renders null with empty', () => { + expect(render({ address: ADDR_C, empty: true }).html()).to.be.null; + }); + + it('renders address without empty', () => { + expect(render({ address: ADDR_C }).text()).to.equal(ADDR_C); + }); + + it('renders short address with shorten', () => { + expect(render({ address: ADDR_C, shorten: true }).text()).to.equal('123456…56789c'); + }); + + it('renders unknown with flag', () => { + expect(render({ address: ADDR_C, unknown: true }).text()).to.equal('UNNAMED'); + }); + }); + }); +}); diff --git a/js/src/ui/TxList/txList.spec.js b/js/src/ui/TxList/txList.spec.js index dcaf5fb78..3f773980f 100644 --- a/js/src/ui/TxList/txList.spec.js +++ b/js/src/ui/TxList/txList.spec.js @@ -25,6 +25,8 @@ import TxList from './txList'; const api = new Api({ execute: sinon.stub() }); const STORE = { + dispatch: sinon.stub(), + subscribe: sinon.stub(), getState: () => { return { nodeStatus: { From 5f37c936595426b367a6f04dbb38c1f3593b8244 Mon Sep 17 00:00:00 2001 From: Robert Habermeier <rphmeier@gmail.com> Date: Fri, 9 Dec 2016 15:04:54 +0100 Subject: [PATCH 26/34] les: use negotiated protocol version --- ethcore/light/src/net/context.rs | 7 +++++++ ethcore/light/src/net/error.rs | 8 ++++++++ ethcore/light/src/net/mod.rs | 22 +++++++++++++++++----- 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/ethcore/light/src/net/context.rs b/ethcore/light/src/net/context.rs index f2d5ab907..c05e69b0f 100644 --- a/ethcore/light/src/net/context.rs +++ b/ethcore/light/src/net/context.rs @@ -38,6 +38,9 @@ pub trait IoContext { /// Disable a peer -- this is a disconnect + a time-out. fn disable_peer(&self, peer: PeerId); + + /// Get a peer's protocol version. + fn protocol_version(&self, peer: PeerId) -> Option<u8>; } impl<'a> IoContext for NetworkContext<'a> { @@ -60,6 +63,10 @@ impl<'a> IoContext for NetworkContext<'a> { fn disable_peer(&self, peer: PeerId) { NetworkContext::disable_peer(self, peer); } + + fn protocol_version(&self, peer: PeerId) -> Option<u8> { + self.protocol_version(self.subprotocol_name(), peer) + } } /// Context for a protocol event. diff --git a/ethcore/light/src/net/error.rs b/ethcore/light/src/net/error.rs index c9ba85a42..01a15928b 100644 --- a/ethcore/light/src/net/error.rs +++ b/ethcore/light/src/net/error.rs @@ -58,6 +58,10 @@ pub enum Error { UnsolicitedResponse, /// Not a server. NotServer, + /// Unsupported protocol version. + UnsupportedProtocolVersion(u8), + /// Bad protocol version. + BadProtocolVersion, } impl Error { @@ -73,6 +77,8 @@ impl Error { Error::UnknownPeer => Punishment::Disconnect, Error::UnsolicitedResponse => Punishment::Disable, Error::NotServer => Punishment::Disable, + Error::UnsupportedProtocolVersion(_) => Punishment::Disable, + Error::BadProtocolVersion => Punishment::Disable, } } } @@ -101,6 +107,8 @@ impl fmt::Display for Error { Error::UnknownPeer => write!(f, "Unknown peer"), Error::UnsolicitedResponse => write!(f, "Peer provided unsolicited data"), Error::NotServer => write!(f, "Peer not a server."), + Error::UnsupportedProtocolVersion(pv) => write!(f, "Unsupported protocol version: {}", pv), + Error::BadProtocolVersion => write!(f, "Bad protocol version in handshake"), } } } \ No newline at end of file diff --git a/ethcore/light/src/net/mod.rs b/ethcore/light/src/net/mod.rs index 1eb89a92f..481740a48 100644 --- a/ethcore/light/src/net/mod.rs +++ b/ethcore/light/src/net/mod.rs @@ -23,7 +23,7 @@ use ethcore::transaction::SignedTransaction; use ethcore::receipt::Receipt; use io::TimerToken; -use network::{NetworkProtocolHandler, NetworkContext, NetworkError, PeerId}; +use network::{NetworkProtocolHandler, NetworkContext, PeerId}; use rlp::{RlpStream, Stream, UntrustedRlp, View}; use util::hash::H256; use util::{Bytes, Mutex, RwLock, U256}; @@ -111,6 +111,7 @@ pub struct ReqId(usize); struct PendingPeer { sent_head: H256, last_update: SteadyTime, + proto_version: u8, } // data about each peer. @@ -121,6 +122,7 @@ struct Peer { remote_flow: Option<(Buffer, FlowParams)>, sent_head: H256, // last head we've given them. last_update: SteadyTime, + proto_version: u8, } impl Peer { @@ -507,17 +509,21 @@ impl LightProtocol { } // send status to a peer. - fn send_status(&self, peer: PeerId, io: &IoContext) -> Result<PendingPeer, NetworkError> { - let chain_info = self.provider.chain_info(); + fn send_status(&self, peer: PeerId, io: &IoContext) -> Result<PendingPeer, Error> { + let proto_version = try!(io.protocol_version(peer).ok_or(Error::WrongNetwork)); - // TODO: could update capabilities here. + if PROTOCOL_VERSIONS.iter().find(|x| **x == proto_version).is_none() { + return Err(Error::UnsupportedProtocolVersion(proto_version)); + } + + let chain_info = self.provider.chain_info(); 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: MAX_PROTOCOL_VERSION as u32, + protocol_version: proto_version as u32, // match peer proto version network_id: self.network_id, last_head: None, }; @@ -530,6 +536,7 @@ impl LightProtocol { Ok(PendingPeer { sent_head: chain_info.best_block_hash, last_update: SteadyTime::now(), + proto_version: proto_version, }) } @@ -550,6 +557,10 @@ impl LightProtocol { return Err(Error::WrongNetwork); } + if Some(status.protocol_version as u8) != io.protocol_version(*peer) { + return Err(Error::BadProtocolVersion); + } + let remote_flow = flow_params.map(|params| (params.create_buffer(), params)); self.peers.write().insert(*peer, Mutex::new(Peer { @@ -559,6 +570,7 @@ impl LightProtocol { remote_flow: remote_flow, sent_head: pending.sent_head, last_update: pending.last_update, + proto_version: pending.proto_version, })); for handler in &self.handlers { From 415fccfffbe391a00ee5561623a47ace745dc553 Mon Sep 17 00:00:00 2001 From: Robert Habermeier <rphmeier@gmail.com> Date: Fri, 9 Dec 2016 15:20:45 +0100 Subject: [PATCH 27/34] add ethcore-light cov target --- ethcore/light/src/net/tests/mod.rs | 4 ++++ scripts/targets.sh | 1 + 2 files changed, 5 insertions(+) diff --git a/ethcore/light/src/net/tests/mod.rs b/ethcore/light/src/net/tests/mod.rs index 7a159b6a4..6e2bc9f33 100644 --- a/ethcore/light/src/net/tests/mod.rs +++ b/ethcore/light/src/net/tests/mod.rs @@ -64,6 +64,10 @@ impl IoContext for Expect { fn disable_peer(&self, peer: PeerId) { assert_eq!(self, &Expect::Punish(peer)); } + + fn protocol_version(&self, _peer: PeerId) -> Option<u8> { + Some(super::MAX_PROTOCOL_VERSION) + } } // can't implement directly for Arc due to cross-crate orphan rules. diff --git a/scripts/targets.sh b/scripts/targets.sh index 529937c23..505875336 100644 --- a/scripts/targets.sh +++ b/scripts/targets.sh @@ -17,4 +17,5 @@ export TARGETS=" -p ethcore-ipc \ -p ethcore-ipc-tests \ -p ethcore-ipc-nano \ + -p ethcore-light \ -p parity" From b772901d7737aed1434c4669a31ddd898f9a49f7 Mon Sep 17 00:00:00 2001 From: Robert Habermeier <rphmeier@gmail.com> Date: Fri, 9 Dec 2016 19:36:40 +0100 Subject: [PATCH 28/34] network: process packets only after connection handler finishes --- util/network/src/host.rs | 6 ++++++ util/network/src/session.rs | 42 +++++++++++++++++++++++++++++++++++-- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/util/network/src/host.rs b/util/network/src/host.rs index 887b7ade0..f75158e4a 100644 --- a/util/network/src/host.rs +++ b/util/network/src/host.rs @@ -868,6 +868,12 @@ impl Host { let reserved = self.reserved_nodes.read(); if let Some(h) = handlers.get(&p).clone() { h.connected(&NetworkContext::new(io, p, session.clone(), self.sessions.clone(), &reserved), &token); + + // accumulate pending packets. + if let Some(session) = session.as_ref() { + let mut session = session.lock(); + packet_data.extend(session.mark_connected(p)); + } } } for (p, packet_id, data) in packet_data { diff --git a/util/network/src/session.rs b/util/network/src/session.rs index a1336a0ad..9c8bed9da 100644 --- a/util/network/src/session.rs +++ b/util/network/src/session.rs @@ -18,6 +18,8 @@ use std::{str, io}; use std::net::SocketAddr; use std::cmp::Ordering; use std::sync::*; +use std::collections::HashMap; + use mio::*; use mio::deprecated::{Handler, EventLoop}; use mio::tcp::*; @@ -36,6 +38,14 @@ use time; const PING_TIMEOUT_SEC: u64 = 15; const PING_INTERVAL_SEC: u64 = 30; +#[derive(Debug, Clone)] +enum ProtocolState { + // Packets pending protocol on_connect event return. + Pending(Vec<(Vec<u8>, u8)>), + // Protocol connected. + Connected, +} + /// Peer session over encrypted connection. /// When created waits for Hello packet exchange and signals ready state. /// Sends and receives protocol packets and handles basic packes such as ping/pong and disconnect. @@ -49,6 +59,8 @@ pub struct Session { ping_time_ns: u64, pong_time_ns: Option<u64>, state: State, + // Protocol states -- accumulates pending packets until signaled as ready. + protocol_states: HashMap<ProtocolId, ProtocolState>, } enum State { @@ -186,6 +198,7 @@ impl Session { ping_time_ns: 0, pong_time_ns: None, expired: false, + protocol_states: HashMap::new(), }) } @@ -361,6 +374,20 @@ impl Session { self.connection().token() } + /// Signal that a subprotocol has handled the connection successfully and + /// get all pending packets in order received. + pub fn mark_connected(&mut self, protocol: ProtocolId) -> Vec<(ProtocolId, u8, Vec<u8>)> { + match self.protocol_states.insert(protocol, ProtocolState::Connected) { + None => Vec::new(), + Some(ProtocolState::Connected) => { + debug!(target: "network", "Protocol {:?} marked as connected more than once", protocol); + Vec::new() + } + Some(ProtocolState::Pending(pending)) => + pending.into_iter().map(|(data, id)| (protocol, id, data)).collect(), + } + } + fn read_packet<Message>(&mut self, io: &IoContext<Message>, packet: Packet, host: &HostInfo) -> Result<SessionData, NetworkError> where Message: Send + Sync + Clone { if packet.data.len() < 2 { @@ -409,8 +436,19 @@ impl Session { // map to protocol let protocol = self.info.capabilities[i].protocol; let pid = packet_id - self.info.capabilities[i].id_offset; - trace!(target: "network", "Packet {} mapped to {:?}:{}, i={}, capabilities={:?}", packet_id, protocol, pid, i, self.info.capabilities); - Ok(SessionData::Packet { data: packet.data, protocol: protocol, packet_id: pid } ) + + match *self.protocol_states.entry(protocol).or_insert_with(|| ProtocolState::Pending(Vec::new())) { + ProtocolState::Connected => { + trace!(target: "network", "Packet {} mapped to {:?}:{}, i={}, capabilities={:?}", packet_id, protocol, pid, i, self.info.capabilities); + Ok(SessionData::Packet { data: packet.data, protocol: protocol, packet_id: pid } ) + } + ProtocolState::Pending(ref mut pending) => { + trace!(target: "network", "Packet {} deferred until protocol connection event completion", packet_id); + pending.push((packet.data, packet_id)); + + Ok(SessionData::Continue) + } + } }, _ => { debug!(target: "network", "Unknown packet: {:?}", packet_id); From 5c555aa18f158a3824259696ebce14d10a174fad Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac <ngotchac@gmail.com> Date: Sat, 10 Dec 2016 01:25:23 +0100 Subject: [PATCH 29/34] Fixes error in Transfer modal (mix between props and own property) (#3788) --- js/src/modals/Transfer/store.js | 6 ++---- js/src/modals/Transfer/transfer.js | 11 +++++++++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/js/src/modals/Transfer/store.js b/js/src/modals/Transfer/store.js index cbb10f17f..3a8f54f92 100644 --- a/js/src/modals/Transfer/store.js +++ b/js/src/modals/Transfer/store.js @@ -107,10 +107,9 @@ export default class TransferStore { constructor (api, props) { this.api = api; - const { account, balance, gasLimit, senders, onClose, newError, sendersBalances } = props; + const { account, balance, gasLimit, senders, newError, sendersBalances } = props; this.account = account; this.balance = balance; - this.onClose = onClose; this.isWallet = account && account.wallet; this.newError = newError; @@ -136,8 +135,7 @@ export default class TransferStore { this.stage -= 1; } - @action onClose = () => { - this.onClose && this.onClose(); + @action handleClose = () => { this.stage = 0; } diff --git a/js/src/modals/Transfer/transfer.js b/js/src/modals/Transfer/transfer.js index 00e84adaf..0c96a1168 100644 --- a/js/src/modals/Transfer/transfer.js +++ b/js/src/modals/Transfer/transfer.js @@ -208,7 +208,7 @@ class Transfer extends Component { <Button icon={ <ContentClear /> } label='Cancel' - onClick={ this.store.onClose } /> + onClick={ this.handleClose } /> ); const nextBtn = ( <Button @@ -234,7 +234,7 @@ class Transfer extends Component { <Button icon={ <ActionDoneAll /> } label='Close' - onClick={ this.store.onClose } /> + onClick={ this.handleClose } /> ); switch (stage) { @@ -264,6 +264,13 @@ class Transfer extends Component { </div> ); } + + handleClose = () => { + const { onClose } = this.props; + + this.store.handleClose(); + typeof onClose === 'function' && onClose(); + } } function mapStateToProps (initState, initProps) { From 923f85d90deb4c33609c0413765a8d648d241bc1 Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac <ngotchac@gmail.com> Date: Sat, 10 Dec 2016 01:26:03 +0100 Subject: [PATCH 30/34] Update Material-UI (#3785) * Update MUI => Fixes wrong error styled inputs * Update postcss import --- js/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/js/package.json b/js/package.json index 46b0233a0..524c280dc 100644 --- a/js/package.json +++ b/js/package.json @@ -101,8 +101,8 @@ "mock-local-storage": "1.0.2", "mock-socket": "6.0.3", "nock": "9.0.2", - "postcss-import": "8.1.0", - "postcss-loader": "1.1.1", + "postcss-import": "9.0.0", + "postcss-loader": "1.2.0", "postcss-nested": "1.0.0", "postcss-simple-vars": "3.0.0", "progress": "1.1.8", @@ -139,7 +139,7 @@ "js-sha3": "0.5.5", "lodash": "4.17.2", "marked": "0.3.6", - "material-ui": "0.16.4", + "material-ui": "0.16.5", "material-ui-chip-input": "0.11.1", "mobx": "2.6.4", "mobx-react": "4.0.3", From 6655e7e3c0e2c3c23e4f3ba80c5886cf0fb78172 Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac <ngotchac@gmail.com> Date: Sat, 10 Dec 2016 01:26:28 +0100 Subject: [PATCH 31/34] Add support for wallets without getOwner() interface (#3779) * Make Wallet Mist compatible #3282 * Owners icons on load * Fix oversized logo on load * Don't fetch registry twice (even when pending) * Better logging... * Better contract view : show if no events // show loading events * Better decimal typed input * PR grumble --- js/src/contracts/registry.js | 38 +++++---- .../modals/CreateWallet/createWalletStore.js | 3 +- js/src/redux/providers/personal.js | 3 - js/src/redux/providers/signer.js | 3 - js/src/redux/providers/status.js | 3 - js/src/ui/Form/Input/input.js | 38 +++++---- js/src/ui/Form/TypedInput/typedInput.js | 52 ++++++++++--- js/src/ui/Page/page.css | 6 +- js/src/util/wallets.js | 77 ++++++++++++++++++- js/src/views/Accounts/Summary/summary.js | 11 ++- js/src/views/Application/TabBar/tabBar.js | 2 +- js/src/views/Contract/Events/events.js | 34 ++++++-- js/src/views/Contract/Queries/queries.js | 4 + js/src/views/Contract/contract.js | 24 ++++-- js/src/views/Wallet/Details/details.js | 4 +- 15 files changed, 228 insertions(+), 74 deletions(-) diff --git a/js/src/contracts/registry.js b/js/src/contracts/registry.js index 2f61f7f4a..9354a59e5 100644 --- a/js/src/contracts/registry.js +++ b/js/src/contracts/registry.js @@ -19,7 +19,10 @@ import * as abis from './abi'; export default class Registry { constructor (api) { this._api = api; - this._contracts = []; + + this._contracts = {}; + this._pendingContracts = {}; + this._instance = null; this._fetching = false; this._queue = []; @@ -59,20 +62,25 @@ export default class Registry { getContract (_name) { const name = _name.toLowerCase(); - return new Promise((resolve, reject) => { - if (this._contracts[name]) { - resolve(this._contracts[name]); - return; - } + if (this._contracts[name]) { + return Promise.resolve(this._contracts[name]); + } - this - .lookupAddress(name) - .then((address) => { - this._contracts[name] = this._api.newContract(abis[name], address); - resolve(this._contracts[name]); - }) - .catch(reject); - }); + if (this._pendingContracts[name]) { + return this._pendingContracts[name]; + } + + const promise = this + .lookupAddress(name) + .then((address) => { + this._contracts[name] = this._api.newContract(abis[name], address); + delete this._pendingContracts[name]; + return this._contracts[name]; + }); + + this._pendingContracts[name] = promise; + + return promise; } getContractInstance (_name) { @@ -89,7 +97,7 @@ export default class Registry { return instance.getAddress.call({}, [sha3, 'A']); }) .then((address) => { - console.log('lookupAddress', name, sha3, address); + console.log('[lookupAddress]', `(${sha3}) ${name}: ${address}`); return address; }); } diff --git a/js/src/modals/CreateWallet/createWalletStore.js b/js/src/modals/CreateWallet/createWalletStore.js index 3edf8f638..b28bfbd26 100644 --- a/js/src/modals/CreateWallet/createWalletStore.js +++ b/js/src/modals/CreateWallet/createWalletStore.js @@ -23,6 +23,7 @@ import { wallet as walletAbi } from '~/contracts/abi'; import { wallet as walletCode, walletLibraryRegKey, fullWalletCode } from '~/contracts/code/wallet'; import { validateUint, validateAddress, validateName } from '~/util/validation'; +import { toWei } from '~/api/util/wei'; import WalletsUtils from '~/util/wallets'; const STEPS = { @@ -47,7 +48,7 @@ export default class CreateWalletStore { address: '', owners: [], required: 1, - daylimit: 0, + daylimit: toWei(1), name: '', description: '' diff --git a/js/src/redux/providers/personal.js b/js/src/redux/providers/personal.js index e061051b0..7629c4f46 100644 --- a/js/src/redux/providers/personal.js +++ b/js/src/redux/providers/personal.js @@ -36,9 +36,6 @@ export default class Personal { } this._store.dispatch(personalAccountsInfo(accountsInfo)); - }) - .then((subscriptionId) => { - console.log('personal._subscribeAccountsInfo', 'subscriptionId', subscriptionId); }); } diff --git a/js/src/redux/providers/signer.js b/js/src/redux/providers/signer.js index 5ece371c2..11b30c6b4 100644 --- a/js/src/redux/providers/signer.js +++ b/js/src/redux/providers/signer.js @@ -34,9 +34,6 @@ export default class Signer { } this._store.dispatch(signerRequestsToConfirm(pending || [])); - }) - .then((subscriptionId) => { - console.log('signer._subscribeRequestsToConfirm', 'subscriptionId', subscriptionId); }); } } diff --git a/js/src/redux/providers/status.js b/js/src/redux/providers/status.js index 936fe3f25..830192bbe 100644 --- a/js/src/redux/providers/status.js +++ b/js/src/redux/providers/status.js @@ -59,9 +59,6 @@ export default class Status { .catch((error) => { console.warn('status._subscribeBlockNumber', 'getBlockByNumber', error); }); - }) - .then((subscriptionId) => { - console.log('status._subscribeBlockNumber', 'subscriptionId', subscriptionId); }); } diff --git a/js/src/ui/Form/Input/input.js b/js/src/ui/Form/Input/input.js index 701851ef9..f25ce207c 100644 --- a/js/src/ui/Form/Input/input.js +++ b/js/src/ui/Form/Input/input.js @@ -113,32 +113,38 @@ export default class Input extends Component { <TextField autoComplete='off' className={ className } - style={ textFieldStyle } - - readOnly={ readOnly } - errorText={ error } + floatingLabelFixed floatingLabelText={ label } - fullWidth + hintText={ hint } + id={ NAME_ID } + inputStyle={ inputStyle } + fullWidth + + max={ max } + min={ min } + multiLine={ multiLine } name={ NAME_ID } - id={ NAME_ID } - rows={ rows } - type={ type || 'text' } - underlineDisabledStyle={ UNDERLINE_DISABLED } - underlineStyle={ readOnly ? UNDERLINE_READONLY : UNDERLINE_NORMAL } - underlineFocusStyle={ readOnly ? { display: 'none' } : null } - underlineShow={ !hideUnderline } - value={ value } + onBlur={ this.onBlur } onChange={ this.onChange } onKeyDown={ this.onKeyDown } onPaste={ this.onPaste } - inputStyle={ inputStyle } - min={ min } - max={ max } + + readOnly={ readOnly } + rows={ rows } + style={ textFieldStyle } + type={ type || 'text' } + + underlineDisabledStyle={ UNDERLINE_DISABLED } + underlineStyle={ readOnly ? UNDERLINE_READONLY : UNDERLINE_NORMAL } + underlineFocusStyle={ readOnly ? { display: 'none' } : null } + underlineShow={ !hideUnderline } + + value={ value } > { children } </TextField> diff --git a/js/src/ui/Form/TypedInput/typedInput.js b/js/src/ui/Form/TypedInput/typedInput.js index a81ec7b79..a54032999 100644 --- a/js/src/ui/Form/TypedInput/typedInput.js +++ b/js/src/ui/Form/TypedInput/typedInput.js @@ -53,13 +53,13 @@ export default class TypedInput extends Component { }; state = { - isEth: true, + isEth: false, ethValue: 0 }; - componentDidMount () { + componentWillMount () { if (this.props.isEth && this.props.value) { - this.setState({ ethValue: fromWei(this.props.value) }); + this.setState({ isEth: true, ethValue: fromWei(this.props.value) }); } } @@ -164,28 +164,32 @@ export default class TypedInput extends Component { } if (type === ABI_TYPES.INT) { - return this.renderNumber(); + return this.renderEth(); } if (type === ABI_TYPES.FIXED) { - return this.renderNumber(); + return this.renderFloat(); } return this.renderDefault(); } renderEth () { - const { ethValue } = this.state; + const { ethValue, isEth } = this.state; const value = ethValue && typeof ethValue.toNumber === 'function' ? ethValue.toNumber() : ethValue; + const input = isEth + ? this.renderFloat(value, this.onEthValueChange) + : this.renderInteger(value, this.onEthValueChange); + return ( <div className={ styles.ethInput }> <div className={ styles.input }> - { this.renderNumber(value, this.onEthValueChange) } - { this.state.isEth ? (<div className={ styles.label }>ETH</div>) : null } + { input } + { isEth ? (<div className={ styles.label }>ETH</div>) : null } </div> <div className={ styles.toggle }> <Toggle @@ -198,8 +202,9 @@ export default class TypedInput extends Component { ); } - renderNumber (value = this.props.value, onChange = this.onChange) { + renderInteger (value = this.props.value, onChange = this.onChange) { const { label, error, param, hint, min, max } = this.props; + const realValue = value && typeof value.toNumber === 'function' ? value.toNumber() : value; @@ -212,6 +217,35 @@ export default class TypedInput extends Component { error={ error } onChange={ onChange } type='number' + step={ 1 } + min={ min !== null ? min : (param.signed ? null : 0) } + max={ max !== null ? max : null } + /> + ); + } + + /** + * Decimal numbers have to be input via text field + * because of some react issues with input number fields. + * Once the issue is fixed, this could be a number again. + * + * @see https://github.com/facebook/react/issues/1549 + */ + renderFloat (value = this.props.value, onChange = this.onChange) { + const { label, error, param, hint, min, max } = this.props; + + const realValue = value && typeof value.toNumber === 'function' + ? value.toNumber() + : value; + + return ( + <Input + label={ label } + hint={ hint } + value={ realValue } + error={ error } + onChange={ onChange } + type='text' min={ min !== null ? min : (param.signed ? null : 0) } max={ max !== null ? max : null } /> diff --git a/js/src/ui/Page/page.css b/js/src/ui/Page/page.css index 9b7cfd62b..72a78dc22 100644 --- a/js/src/ui/Page/page.css +++ b/js/src/ui/Page/page.css @@ -17,8 +17,8 @@ .layout { padding: 0.25em 0.25em 1em 0.25em; -} -.layout>div { - padding-bottom: 0.75em; + > * { + margin-bottom: 0.75em; + } } diff --git a/js/src/util/wallets.js b/js/src/util/wallets.js index c335ce03c..1f0bc6735 100644 --- a/js/src/util/wallets.js +++ b/js/src/util/wallets.js @@ -14,9 +14,10 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see <http://www.gnu.org/licenses/>. -import { range } from 'lodash'; +import { range, uniq } from 'lodash'; import { bytesToHex, toHex } from '~/api/util/format'; +import { validateAddress } from '~/util/validation'; export default class WalletsUtils { @@ -26,10 +27,82 @@ export default class WalletsUtils { static fetchOwners (walletContract) { const walletInstance = walletContract.instance; + return walletInstance .m_numOwners.call() .then((mNumOwners) => { - return Promise.all(range(mNumOwners.toNumber()).map((idx) => walletInstance.getOwner.call({}, [ idx ]))); + const promises = range(mNumOwners.toNumber()) + .map((idx) => walletInstance.getOwner.call({}, [ idx ])); + + return Promise + .all(promises) + .then((owners) => { + const uniqOwners = uniq(owners); + + // If all owners are the zero account : must be Mist wallet contract + if (uniqOwners.length === 1 && /^(0x)?0*$/.test(owners[0])) { + return WalletsUtils.fetchMistOwners(walletContract, mNumOwners.toNumber()); + } + + return owners; + }); + }); + } + + static fetchMistOwners (walletContract, mNumOwners) { + const walletAddress = walletContract.address; + + return WalletsUtils + .getMistOwnersOffset(walletContract) + .then((result) => { + if (!result || result.offset === -1) { + return []; + } + + const owners = [ result.address ]; + + if (mNumOwners === 1) { + return owners; + } + + const initOffset = result.offset + 1; + let promise = Promise.resolve(); + + range(initOffset, initOffset + mNumOwners - 1).forEach((offset) => { + promise = promise + .then(() => { + return walletContract.api.eth.getStorageAt(walletAddress, offset); + }) + .then((result) => { + const resultAddress = '0x' + (result || '').slice(-40); + const { address } = validateAddress(resultAddress); + + owners.push(address); + }); + }); + + return promise.then(() => owners); + }); + } + + static getMistOwnersOffset (walletContract, offset = 3) { + return walletContract.api.eth + .getStorageAt(walletContract.address, offset) + .then((result) => { + if (result && !/^(0x)?0*$/.test(result)) { + const resultAddress = '0x' + result.slice(-40); + const { address, addressError } = validateAddress(resultAddress); + + if (!addressError) { + return { offset, address }; + } + } + + if (offset >= 100) { + return { offset: -1 }; + } + + return WalletsUtils.getMistOwnersOffset(walletContract, offset + 1); }); } diff --git a/js/src/views/Accounts/Summary/summary.js b/js/src/views/Accounts/Summary/summary.js index aeff8a2e5..3183a2903 100644 --- a/js/src/views/Accounts/Summary/summary.js +++ b/js/src/views/Accounts/Summary/summary.js @@ -75,6 +75,13 @@ export default class Summary extends Component { return true; } + const prevOwners = this.props.owners; + const nextOwners = nextProps.owners; + + if (!isEqual(prevOwners, nextOwners)) { + return true; + } + return false; } @@ -123,8 +130,8 @@ export default class Summary extends Component { return ( <div className={ styles.owners }> { - ownersValid.map((owner) => ( - <div key={ owner.address }> + ownersValid.map((owner, index) => ( + <div key={ `${index}_${owner.address}` }> <div data-tip data-for={ `owner_${owner.address}` } diff --git a/js/src/views/Application/TabBar/tabBar.js b/js/src/views/Application/TabBar/tabBar.js index 8a79f216e..9d9f55874 100644 --- a/js/src/views/Application/TabBar/tabBar.js +++ b/js/src/views/Application/TabBar/tabBar.js @@ -188,7 +188,7 @@ class TabBar extends Component { return ( <ToolbarGroup> <div className={ styles.logo }> - <img src={ imagesEthcoreBlock } /> + <img src={ imagesEthcoreBlock } height={ 28 } /> </div> </ToolbarGroup> ); diff --git a/js/src/views/Contract/Events/events.js b/js/src/views/Contract/Events/events.js index 69ae8fd6a..c29e624bf 100644 --- a/js/src/views/Contract/Events/events.js +++ b/js/src/views/Contract/Events/events.js @@ -17,7 +17,7 @@ import React, { Component, PropTypes } from 'react'; import { uniq } from 'lodash'; -import { Container } from '~/ui'; +import { Container, Loading } from '~/ui'; import Event from './Event'; import styles from '../contract.css'; @@ -25,18 +25,38 @@ import styles from '../contract.css'; export default class Events extends Component { static contextTypes = { api: PropTypes.object - } + }; static propTypes = { - events: PropTypes.array, - isTest: PropTypes.bool.isRequired - } + isTest: PropTypes.bool.isRequired, + isLoading: PropTypes.bool, + events: PropTypes.array + }; + + static defaultProps = { + isLoading: false, + events: [] + }; render () { - const { events, isTest } = this.props; + const { events, isTest, isLoading } = this.props; + + if (isLoading) { + return ( + <Container title='events'> + <div> + <Loading size={ 2 } /> + </div> + </Container> + ); + } if (!events || !events.length) { - return null; + return ( + <Container title='events'> + <p>No events has been sent from this contract.</p> + </Container> + ); } const eventsKey = uniq(events.map((e) => e.key)); diff --git a/js/src/views/Contract/Queries/queries.js b/js/src/views/Contract/Queries/queries.js index 9a13037f6..1bfd2fa5f 100644 --- a/js/src/views/Contract/Queries/queries.js +++ b/js/src/views/Contract/Queries/queries.js @@ -54,6 +54,10 @@ export default class Queries extends Component { .filter((fn) => fn.inputs.length > 0) .map((fn) => this.renderInputQuery(fn)); + if (queries.length + noInputQueries.length + withInputQueries.length === 0) { + return null; + } + return ( <Container title='queries'> <div className={ styles.methods }> diff --git a/js/src/views/Contract/contract.js b/js/src/views/Contract/contract.js index 35ad95fe2..4f7570ebd 100644 --- a/js/src/views/Contract/contract.js +++ b/js/src/views/Contract/contract.js @@ -40,7 +40,7 @@ import styles from './contract.css'; class Contract extends Component { static contextTypes = { api: React.PropTypes.object.isRequired - } + }; static propTypes = { setVisibleAccounts: PropTypes.func.isRequired, @@ -50,7 +50,7 @@ class Contract extends Component { contracts: PropTypes.object, isTest: PropTypes.bool, params: PropTypes.object - } + }; state = { contract: null, @@ -64,8 +64,9 @@ class Contract extends Component { allEvents: [], minedEvents: [], pendingEvents: [], - queryValues: {} - } + queryValues: {}, + loadingEvents: true + }; componentDidMount () { const { api } = this.context; @@ -115,7 +116,7 @@ class Contract extends Component { render () { const { balances, contracts, params, isTest } = this.props; - const { allEvents, contract, queryValues } = this.state; + const { allEvents, contract, queryValues, loadingEvents } = this.state; const account = contracts[params.address]; const balance = balances[params.address]; @@ -134,12 +135,17 @@ class Contract extends Component { account={ account } balance={ balance } /> + <Queries contract={ contract } - values={ queryValues } /> + values={ queryValues } + /> + <Events isTest={ isTest } - events={ allEvents } /> + isLoading={ loadingEvents } + events={ allEvents } + /> { this.renderDetails(account) } </Page> @@ -358,6 +364,10 @@ class Contract extends Component { } _receiveEvents = (error, logs) => { + if (this.state.loadingEvents) { + this.setState({ loadingEvents: false }); + } + if (error) { console.error('_receiveEvents', error); return; diff --git a/js/src/views/Wallet/Details/details.js b/js/src/views/Wallet/Details/details.js index 547b02601..fb08bbde2 100644 --- a/js/src/views/Wallet/Details/details.js +++ b/js/src/views/Wallet/Details/details.js @@ -55,9 +55,9 @@ export default class WalletDetails extends Component { return null; } - const ownersList = owners.map((address) => ( + const ownersList = owners.map((address, idx) => ( <InputAddress - key={ address } + key={ `${idx}_${address}` } value={ address } disabled text From b9c04fcd0090a93fe0365a5d5bc2dafac184d6e4 Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac <ngotchac@gmail.com> Date: Sat, 10 Dec 2016 01:26:47 +0100 Subject: [PATCH 32/34] Fixes to the Wallet UI (#3787) * Correct number of transactions for contracts * Remove daily limit info if set to 0 * Hide tx count for contracts --- js/src/views/Account/Header/header.js | 10 ++++++---- js/src/views/Contract/contract.js | 1 + js/src/views/Wallet/wallet.js | 9 ++++++++- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/js/src/views/Account/Header/header.js b/js/src/views/Account/Header/header.js index b126951b2..6ce88aef4 100644 --- a/js/src/views/Account/Header/header.js +++ b/js/src/views/Account/Header/header.js @@ -31,12 +31,14 @@ export default class Header extends Component { account: PropTypes.object, balance: PropTypes.object, className: PropTypes.string, - children: PropTypes.node + children: PropTypes.node, + isContract: PropTypes.bool }; static defaultProps = { className: '', - children: null + children: null, + isContract: false }; render () { @@ -88,9 +90,9 @@ export default class Header extends Component { } renderTxCount () { - const { balance } = this.props; + const { balance, isContract } = this.props; - if (!balance) { + if (!balance || isContract) { return null; } diff --git a/js/src/views/Contract/contract.js b/js/src/views/Contract/contract.js index 4f7570ebd..e501e4fe5 100644 --- a/js/src/views/Contract/contract.js +++ b/js/src/views/Contract/contract.js @@ -134,6 +134,7 @@ class Contract extends Component { <Header account={ account } balance={ balance } + isContract /> <Queries diff --git a/js/src/views/Wallet/wallet.js b/js/src/views/Wallet/wallet.js index e67eaefdb..1a5fbeecd 100644 --- a/js/src/views/Wallet/wallet.js +++ b/js/src/views/Wallet/wallet.js @@ -127,6 +127,7 @@ class Wallet extends Component { className={ styles.header } account={ wallet } balance={ balance } + isContract > { this.renderInfos() } </Header> @@ -152,7 +153,13 @@ class Wallet extends Component { return null; } - const limit = api.util.fromWei(dailylimit.limit).toFormat(3); + const _limit = api.util.fromWei(dailylimit.limit); + + if (_limit.equals(0)) { + return null; + } + + const limit = _limit.toFormat(3); const spent = api.util.fromWei(dailylimit.spent).toFormat(3); const date = moment(dailylimit.last.toNumber() * 24 * 3600 * 1000); From 5f09eb9d04e238e9097f37468d68c4dc9ab93bd6 Mon Sep 17 00:00:00 2001 From: keorn <pczaban@gmail.com> Date: Sat, 10 Dec 2016 10:38:10 +0100 Subject: [PATCH 33/34] update tests to new spec (#3790) --- ethcore/src/engines/authority_round.rs | 28 ++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index 21a6e4761..98190c1ea 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -347,7 +347,6 @@ mod tests { use tests::helpers::*; use account_provider::AccountProvider; use spec::Spec; - use std::time::UNIX_EPOCH; #[test] fn has_valid_metadata() { @@ -442,13 +441,30 @@ mod tests { let engine = Spec::new_test_round().engine; let signature = tap.sign(addr, Some("0".into()), header.bare_hash()).unwrap(); - let time = UNIX_EPOCH.elapsed().unwrap().as_secs(); // Two authorities. - let mut step = time - time % 2; - header.set_seal(vec![encode(&step).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); + // Spec starts with step 2. + header.set_seal(vec![encode(&2usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); assert!(engine.verify_block_seal(&header).is_err()); - step = step + 1; - header.set_seal(vec![encode(&step).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); + header.set_seal(vec![encode(&1usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); assert!(engine.verify_block_seal(&header).is_ok()); } + + #[test] + fn rejects_future_block() { + let mut header: Header = Header::default(); + let tap = AccountProvider::transient_provider(); + let addr = tap.insert_account("0".sha3(), "0").unwrap(); + + header.set_author(addr); + + let engine = Spec::new_test_round().engine; + + let signature = tap.sign(addr, Some("0".into()), header.bare_hash()).unwrap(); + // Two authorities. + // Spec starts with step 2. + header.set_seal(vec![encode(&1usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); + assert!(engine.verify_block_seal(&header).is_ok()); + header.set_seal(vec![encode(&5usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); + assert!(engine.verify_block_seal(&header).is_err()); + } } From 6eb63a7316c3605c107a8288ce36b641ab2ea23d Mon Sep 17 00:00:00 2001 From: Jaco Greeff <jacogr@gmail.com> Date: Sat, 10 Dec 2016 10:49:39 +0100 Subject: [PATCH 34/34] Update CI builds (#3780) * Only run languages tests in appropriate areas * Drop echo --- .gitlab-ci.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d27b58f9a..6df465035 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -423,12 +423,8 @@ test-rust-stable: before_script: - git submodule update --init --recursive - export RUST_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep -v ^js/ | wc -l) - - export JS_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep ^js/ | wc -l) - - echo "rust/js modified: $RUST_FILES_MODIFIED / $JS_FILES_MODIFIED" - - if [ "$JS_FILES_MODIFIED" = 0 ]; then echo "Skipping JS deps install since no JS files modified."; else ./js/scripts/install-deps.sh;fi script: - export RUST_BACKTRACE=1 - - if [ "$JS_FILES_MODIFIED" = 0 ]; then echo "Skipping JS lint since no JS files modified."; else ./js/scripts/lint.sh && ./js/scripts/test.sh && ./js/scripts/build.sh; fi - if [ "$RUST_FILES_MODIFIED" = 0 ]; then echo "Skipping Rust tests since no Rust files modified."; else ./test.sh $CARGOFLAGS; fi tags: - rust @@ -439,11 +435,8 @@ js-test: before_script: - git submodule update --init --recursive - export JS_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep ^js/ | wc -l) - - echo $JS_FILES_MODIFIED - if [ "$JS_FILES_MODIFIED" = 0 ]; then echo "Skipping JS deps install since no JS files modified."; else ./js/scripts/install-deps.sh;fi script: - - export RUST_BACKTRACE=1 - - echo $JS_FILES_MODIFIED - if [ "$JS_FILES_MODIFIED" = 0 ]; then echo "Skipping JS lint since no JS files modified."; else ./js/scripts/lint.sh && ./js/scripts/test.sh && ./js/scripts/build.sh; fi tags: - rust