From b38d95328d739828310eb9ed0d7dbc80821792e1 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Wed, 7 Dec 2016 10:50:18 +0100 Subject: [PATCH 01/63] 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/63] 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/63] 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/63] 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/63] 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/63] 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/63] 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/63] 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/63] 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/63] 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/63] 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/63] 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/63] 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/63] 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/63] 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/63] 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/63] 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/63] 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/63] 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/63] 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 ad971a444cb66e4ee3a97a86f11a6c677ab67951 Mon Sep 17 00:00:00 2001 From: Jaco Greeff <jaco@ethcore.io> Date: Fri, 9 Dec 2016 00:00:56 +0100 Subject: [PATCH 21/63] Add autoRemove functionality for api.subscribe --- js/src/api/subscriptions/manager.js | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/js/src/api/subscriptions/manager.js b/js/src/api/subscriptions/manager.js index bc9632592..300ede5f8 100644 --- a/js/src/api/subscriptions/manager.js +++ b/js/src/api/subscriptions/manager.js @@ -59,7 +59,7 @@ export default class Manager { return subscription; } - subscribe (subscriptionName, callback) { + subscribe (subscriptionName, callback, autoRemove = false) { return new Promise((resolve, reject) => { const subscription = this._validateType(subscriptionName); @@ -75,6 +75,7 @@ export default class Manager { this.subscriptions[subscriptionId] = { name: subscriptionName, id: subscriptionId, + autoRemove, callback }; @@ -104,13 +105,16 @@ export default class Manager { const { callback } = this.subscriptions[subscriptionId]; try { - callback(error, data); + return callback(error, data); } catch (error) { console.error(`Unable to update callback for subscriptionId ${subscriptionId}`, error); } + + return true; } _updateSubscriptions = (subscriptionName, error, data) => { + const cleanup = []; const subscriptions = this.subscriptions .filter(subscription => subscription.name === subscriptionName); @@ -118,8 +122,16 @@ export default class Manager { subscriptions .forEach((subscription) => { - this._sendData(subscription.id, error, data); + const result = this._sendData(subscription.id, error, data); + + if (subscription.autoRemove && !result) { + cleanup.push(subscription.id); + } }); + + cleanup.forEach((subscriptionId) => { + delete this.subscriptions[subscriptionId]; + }); } } From 4ce3142c630c8ca236240aa7642536fb1d2aa777 Mon Sep 17 00:00:00 2001 From: Jaco Greeff <jaco@ethcore.io> Date: Fri, 9 Dec 2016 00:15:27 +0100 Subject: [PATCH 22/63] Add autoRemove functionality to api.contract.subscribe --- js/src/api/contract/contract.js | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/js/src/api/contract/contract.js b/js/src/api/contract/contract.js index ed922a02c..f34cd8575 100644 --- a/js/src/api/contract/contract.js +++ b/js/src/api/contract/contract.js @@ -240,8 +240,8 @@ export default class Contract { } _bindEvent = (event) => { - event.subscribe = (options = {}, callback) => { - return this._subscribe(event, options, callback); + event.subscribe = (options = {}, callback, autoRemove = false) => { + return this._subscribe(event, options, callback, autoRemove); }; event.unsubscribe = (subscriptionId) => { @@ -307,16 +307,16 @@ export default class Contract { return this._api.eth.newFilter(options); } - subscribe (eventName = null, options = {}, callback) { + subscribe (eventName = null, options = {}, callback, autoRemove = false) { try { const event = this._findEvent(eventName); - return this._subscribe(event, options, callback); + return this._subscribe(event, options, callback, autoRemove); } catch (e) { return Promise.reject(e); } } - _subscribe (event = null, _options, callback) { + _subscribe (event = null, _options, callback, autoRemove = false) { const subscriptionId = nextSubscriptionId++; const { skipInitFetch } = _options; delete _options['skipInitFetch']; @@ -326,6 +326,7 @@ export default class Contract { .then((filterId) => { this._subscriptions[subscriptionId] = { options: _options, + autoRemove, callback, filterId }; @@ -338,7 +339,11 @@ export default class Contract { return this._api.eth .getFilterLogs(filterId) .then((logs) => { - callback(null, this.parseEventLogs(logs)); + const result = callback(null, this.parseEventLogs(logs)); + + if (autoRemove && !result) { + this.unsubscribe(subscriptionId); + } this._subscribeToChanges(); return subscriptionId; @@ -438,16 +443,22 @@ export default class Contract { }) ) .then((logsArray) => { - logsArray.forEach((logs, idx) => { + logsArray.forEach((logs, subscriptionId) => { if (!logs || !logs.length) { return; } + let result = false; + try { - subscriptions[idx].callback(null, this.parseEventLogs(logs)); + result = subscriptions[subscriptionId].callback(null, this.parseEventLogs(logs)); } catch (error) { console.error('_sendSubscriptionChanges', error); } + + if (subscriptions[subscriptionId].autoRemove && !result) { + this.unsubscribe(subscriptionId); + } }); }) .catch((error) => { From 1ecda93de97b3974fc07812d9fcc4a530f65ff5e Mon Sep 17 00:00:00 2001 From: Jaco Greeff <jaco@ethcore.io> Date: Fri, 9 Dec 2016 00:15:39 +0100 Subject: [PATCH 23/63] Align api.subscribe with contract --- js/src/api/subscriptions/manager.js | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/js/src/api/subscriptions/manager.js b/js/src/api/subscriptions/manager.js index 300ede5f8..960961287 100644 --- a/js/src/api/subscriptions/manager.js +++ b/js/src/api/subscriptions/manager.js @@ -82,7 +82,11 @@ export default class Manager { if (!engine.isStarted) { engine.start(); } else { - this._sendData(subscriptionId, error, data); + const result = this._sendData(subscriptionId, error, data); + + if (autoRemove && !result) { + this.unsubscribe(subscriptionId); + } } resolve(subscriptionId); @@ -102,19 +106,18 @@ export default class Manager { } _sendData (subscriptionId, error, data) { - const { callback } = this.subscriptions[subscriptionId]; + let result = false; try { - return callback(error, data); + result = this.subscriptions[subscriptionId](error, data); } catch (error) { console.error(`Unable to update callback for subscriptionId ${subscriptionId}`, error); } - return true; + return result; } _updateSubscriptions = (subscriptionName, error, data) => { - const cleanup = []; const subscriptions = this.subscriptions .filter(subscription => subscription.name === subscriptionName); @@ -125,13 +128,9 @@ export default class Manager { const result = this._sendData(subscription.id, error, data); if (subscription.autoRemove && !result) { - cleanup.push(subscription.id); + this.unsubscribe(subscription.id); } }); - - cleanup.forEach((subscriptionId) => { - delete this.subscriptions[subscriptionId]; - }); } } From 3989e2642b9665b1b90a94b39eb2016a6bbb8550 Mon Sep 17 00:00:00 2001 From: Jaco Greeff <jaco@ethcore.io> Date: Fri, 9 Dec 2016 00:25:50 +0100 Subject: [PATCH 24/63] Typo --- js/src/api/subscriptions/manager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/src/api/subscriptions/manager.js b/js/src/api/subscriptions/manager.js index 960961287..685aa243c 100644 --- a/js/src/api/subscriptions/manager.js +++ b/js/src/api/subscriptions/manager.js @@ -109,7 +109,7 @@ export default class Manager { let result = false; try { - result = this.subscriptions[subscriptionId](error, data); + result = this.subscriptions[subscriptionId].callback(error, data); } catch (error) { console.error(`Unable to update callback for subscriptionId ${subscriptionId}`, error); } 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 25/63] 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 10c356de65bd269833ef12d6927a00d89ca3d9f8 Mon Sep 17 00:00:00 2001 From: Jaco Greeff <jaco@ethcore.io> Date: Fri, 9 Dec 2016 00:52:21 +0100 Subject: [PATCH 26/63] Simplify, consolidate uses between subscription managers --- js/src/api/contract/contract.js | 30 ++++++++++++++++------------- js/src/api/subscriptions/manager.js | 19 +++++++----------- 2 files changed, 24 insertions(+), 25 deletions(-) diff --git a/js/src/api/contract/contract.js b/js/src/api/contract/contract.js index f34cd8575..233115d68 100644 --- a/js/src/api/contract/contract.js +++ b/js/src/api/contract/contract.js @@ -316,6 +316,21 @@ export default class Contract { } } + _sendData (subscriptionId, error, logs) { + const { autoRemove, callback } = this._subscriptions[subscriptionId]; + let result = false; + + try { + result = callback(error, logs); + } catch (error) { + console.warn('_sendData', subscriptionId, error); + } + + if (autoRemove && !result) { + this.unsubscribe(subscriptionId); + } + } + _subscribe (event = null, _options, callback, autoRemove = false) { const subscriptionId = nextSubscriptionId++; const { skipInitFetch } = _options; @@ -339,12 +354,7 @@ export default class Contract { return this._api.eth .getFilterLogs(filterId) .then((logs) => { - const result = callback(null, this.parseEventLogs(logs)); - - if (autoRemove && !result) { - this.unsubscribe(subscriptionId); - } - + this._sendData(subscriptionId, null, this.parseEventLogs(logs)); this._subscribeToChanges(); return subscriptionId; }); @@ -448,17 +458,11 @@ export default class Contract { return; } - let result = false; - try { - result = subscriptions[subscriptionId].callback(null, this.parseEventLogs(logs)); + this.sendData(subscriptionId, null, this.parseEventLogs(logs)); } catch (error) { console.error('_sendSubscriptionChanges', error); } - - if (subscriptions[subscriptionId].autoRemove && !result) { - this.unsubscribe(subscriptionId); - } }); }) .catch((error) => { diff --git a/js/src/api/subscriptions/manager.js b/js/src/api/subscriptions/manager.js index 685aa243c..a2716684b 100644 --- a/js/src/api/subscriptions/manager.js +++ b/js/src/api/subscriptions/manager.js @@ -82,11 +82,7 @@ export default class Manager { if (!engine.isStarted) { engine.start(); } else { - const result = this._sendData(subscriptionId, error, data); - - if (autoRemove && !result) { - this.unsubscribe(subscriptionId); - } + this._sendData(subscriptionId, error, data); } resolve(subscriptionId); @@ -106,15 +102,18 @@ export default class Manager { } _sendData (subscriptionId, error, data) { + const { autoRemove, callback } = this.subscriptions[subscriptionId]; let result = false; try { - result = this.subscriptions[subscriptionId].callback(error, data); + result = callback(error, data); } catch (error) { console.error(`Unable to update callback for subscriptionId ${subscriptionId}`, error); } - return result; + if (autoRemove && !result) { + this.unsubscribe(subscriptionId); + } } _updateSubscriptions = (subscriptionName, error, data) => { @@ -125,11 +124,7 @@ export default class Manager { subscriptions .forEach((subscription) => { - const result = this._sendData(subscription.id, error, data); - - if (subscription.autoRemove && !result) { - this.unsubscribe(subscription.id); - } + this._sendData(subscription.id, error, data); }); } } From 13f962ae01c981ab4d4f7972af80b24dd951e246 Mon Sep 17 00:00:00 2001 From: Jaco Greeff <jaco@ethcore.io> Date: Fri, 9 Dec 2016 00:56:12 +0100 Subject: [PATCH 27/63] Attempt to fix ci webpack --- js/src/api/contract/contract.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/src/api/contract/contract.js b/js/src/api/contract/contract.js index 233115d68..2ef24c685 100644 --- a/js/src/api/contract/contract.js +++ b/js/src/api/contract/contract.js @@ -240,7 +240,7 @@ export default class Contract { } _bindEvent = (event) => { - event.subscribe = (options = {}, callback, autoRemove = false) => { + event.subscribe = (options = {}, callback, autoRemove) => { return this._subscribe(event, options, callback, autoRemove); }; @@ -307,7 +307,7 @@ export default class Contract { return this._api.eth.newFilter(options); } - subscribe (eventName = null, options = {}, callback, autoRemove = false) { + subscribe (eventName = null, options = {}, callback, autoRemove) { try { const event = this._findEvent(eventName); return this._subscribe(event, options, callback, autoRemove); From b76a717720dec7e3688f9054da1e4cec23e324df Mon Sep 17 00:00:00 2001 From: Jaco Greeff <jaco@ethcore.io> Date: Fri, 9 Dec 2016 00:58:53 +0100 Subject: [PATCH 28/63] Fix & with && --- .gitlab-ci.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2460678d4..14f7b41e6 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -108,9 +108,9 @@ linux-centos: paths: - target/release/parity name: "x86_64-unknown-centos-gnu_parity" -linux-i686: - stage: build - image: ethcore/rust-i686:latest +linux-i686: + stage: build + image: ethcore/rust-i686:latest only: - beta - tags @@ -348,7 +348,7 @@ windows: - set INCLUDE=C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Include;C:\vs2015\VC\include;C:\Program Files (x86)\Windows Kits\10\Include\10.0.10240.0\ucrt - set LIB=C:\vs2015\VC\lib;C:\Program Files (x86)\Windows Kits\10\Lib\10.0.10240.0\ucrt\x64 - set RUST_BACKTRACE=1 - - set RUSTFLAGS=%RUSTFLAGS% + - set RUSTFLAGS=%RUSTFLAGS% - rustup default stable-x86_64-pc-windows-msvc - cargo build -j 8 --release #%CARGOFLAGS% - curl -sL --url "https://github.com/ethcore/win-build/raw/master/SimpleFC.dll" -o nsis\SimpleFC.dll @@ -428,7 +428,7 @@ test-rust-stable: script: - export RUST_BACKTRACE=1 - echo $JS_FILES_MODIFIED - - if [ "$JS_FILES_MODIFIED" = 0 ]; then echo "skip js test"&./test.sh $CARGOFLAGS; else echo "skip rust test"&./js/scripts/lint.sh&./js/scripts/test.sh&./js/scripts/build.sh; fi + - if [ "$JS_FILES_MODIFIED" = 0 ]; then echo "skip js test" && ./test.sh $CARGOFLAGS; else echo "skip rust test" && ./js/scripts/lint.sh && ./js/scripts/test.sh && ./js/scripts/build.sh; fi tags: - rust - rust-stable @@ -443,7 +443,7 @@ js-test: script: - export RUST_BACKTRACE=1 - echo $JS_FILES_MODIFIED - - if [ "$JS_FILES_MODIFIED" = 0 ]; then echo "skip js test"; else echo "skip rust test"&./js/scripts/lint.sh&./js/scripts/test.sh&./js/scripts/build.sh; fi + - if [ "$JS_FILES_MODIFIED" = 0 ]; then echo "skip js test"; else echo "skip rust test" && ./js/scripts/lint.sh && ./js/scripts/test.sh && ./js/scripts/build.sh; fi tags: - rust - rust-stable @@ -489,6 +489,6 @@ js-release: - if [ "$JS_FILES_MODIFIED" = 0 ]; then echo "skip js build"; else ./js/scripts/install-deps.sh;fi script: - echo $JS_FILES_MODIFIED - - if [ "$JS_FILES_MODIFIED" = 0 ]; then echo "skip js build"; else ./js/scripts/build.sh&&./js/scripts/release.sh; fi + - if [ "$JS_FILES_MODIFIED" = 0 ]; then echo "skip js build"; else ./js/scripts/build.sh && ./js/scripts/release.sh; fi tags: - javascript 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 29/63] 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 09334ca3bd5ac999147cd9abf28d03961240c806 Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac <ngotchac@gmail.com> Date: Fri, 9 Dec 2016 01:21:51 +0100 Subject: [PATCH 30/63] Small fixes to contract (#3751) * Fix contracts execution will fallback function * Fix contract execute modal * Working execute from Contract * Don't dupliacet events * Gas * 1.2 in wallet confirmations --- .../DetailsStep/detailsStep.js | 65 +++++-------------- .../modals/ExecuteContract/executeContract.js | 25 ++----- js/src/redux/providers/compilerWorker.js | 4 +- js/src/redux/providers/walletActions.js | 2 +- js/src/views/Contract/Events/events.js | 6 +- 5 files changed, 30 insertions(+), 72 deletions(-) diff --git a/js/src/modals/ExecuteContract/DetailsStep/detailsStep.js b/js/src/modals/ExecuteContract/DetailsStep/detailsStep.js index 082fac6a7..b4488729a 100644 --- a/js/src/modals/ExecuteContract/DetailsStep/detailsStep.js +++ b/js/src/modals/ExecuteContract/DetailsStep/detailsStep.js @@ -17,7 +17,8 @@ import React, { Component, PropTypes } from 'react'; import { MenuItem } from 'material-ui'; -import { AddressSelect, Form, Input, InputAddressSelect, Select } from '~/ui'; +import { AddressSelect, Form, Input, Select, TypedInput } from '~/ui'; +import { parseAbiType } from '~/util/abi'; import styles from '../executeContract.css'; @@ -74,7 +75,7 @@ export default class DetailsStep extends Component { const functions = contract.functions .filter((func) => !func.constant) - .sort((a, b) => a.name.localeCompare(b.name)) + .sort((a, b) => (a.name || '').localeCompare(b.name || '')) .map((func) => { const params = (func.abi.inputs || []) .map((input, index) => { @@ -125,56 +126,22 @@ export default class DetailsStep extends Component { } return (func.abi.inputs || []).map((input, index) => { - const onChange = (event, value) => onValueChange(event, index, value); - const onSelect = (event, _index, value) => onValueChange(event, index, value); - const onSubmit = (value) => onValueChange(null, index, value); + const onChange = (value) => onValueChange(null, index, value); const label = `${input.name}: ${input.type}`; - let inputbox; - - switch (input.type) { - case 'address': - inputbox = ( - <InputAddressSelect - accounts={ accounts } - editing - label={ label } - value={ values[index] } - error={ valuesError[index] } - onChange={ onChange } /> - ); - break; - - case 'bool': - const boolitems = ['false', 'true'].map((bool) => { - return ( - <MenuItem - key={ bool } - value={ bool } - label={ bool }>{ bool }</MenuItem> - ); - }); - inputbox = ( - <Select - label={ label } - value={ values[index] ? 'true' : 'false' } - error={ valuesError[index] } - onChange={ onSelect }>{ boolitems }</Select> - ); - break; - - default: - inputbox = ( - <Input - label={ label } - value={ values[index] } - error={ valuesError[index] } - onSubmit={ onSubmit } /> - ); - } return ( - <div className={ styles.funcparams } key={ index }> - { inputbox } + <div + key={ `${index}_${input.name || ''}` } + className={ styles.funcparams } + > + <TypedInput + label={ label } + value={ values[index] } + error={ valuesError[index] } + onChange={ onChange } + accounts={ accounts } + param={ parseAbiType(input.type) } + /> </div> ); }); diff --git a/js/src/modals/ExecuteContract/executeContract.js b/js/src/modals/ExecuteContract/executeContract.js index c3b64d738..4a708d17a 100644 --- a/js/src/modals/ExecuteContract/executeContract.js +++ b/js/src/modals/ExecuteContract/executeContract.js @@ -23,6 +23,7 @@ import ContentClear from 'material-ui/svg-icons/content/clear'; import { BusyStep, CompletedStep, Button, IdentityIcon, Modal, TxHash } from '~/ui'; import { MAX_GAS_ESTIMATION } from '../../util/constants'; import { validateAddress, validateUint } from '../../util/validation'; +import { parseAbiType } from '~/util/abi'; import DetailsStep from './DetailsStep'; @@ -66,7 +67,7 @@ class ExecuteContract extends Component { const { contract } = this.props; const functions = contract.functions .filter((func) => !func.constant) - .sort((a, b) => a.name.localeCompare(b.name)); + .sort((a, b) => (a.name || '').localeCompare(b.name || '')); this.onFuncChange(null, functions[0]); } @@ -111,7 +112,7 @@ class ExecuteContract extends Component { <Button key='postTransaction' label='post transaction' - disabled={ sending || hasError } + disabled={ !!(sending || hasError) } icon={ <IdentityIcon address={ fromAddress } button /> } onClick={ this.postTransaction } /> ]; @@ -174,23 +175,9 @@ class ExecuteContract extends Component { } onFuncChange = (event, func) => { - const values = func.inputs.map((input) => { - switch (input.kind.type) { - case 'address': - return '0x'; - - case 'bool': - return false; - - case 'bytes': - return '0x'; - - case 'uint': - return '0'; - - default: - return ''; - } + const values = (func.abi.inputs || []).map((input) => { + const parsedType = parseAbiType(input.type); + return parsedType.default; }); this.setState({ diff --git a/js/src/redux/providers/compilerWorker.js b/js/src/redux/providers/compilerWorker.js index 247333f5e..f624a4e5f 100644 --- a/js/src/redux/providers/compilerWorker.js +++ b/js/src/redux/providers/compilerWorker.js @@ -92,7 +92,7 @@ function findImports (path) { return { error: 'File not found' }; } -function compile (data) { +function compile (data, optimized = 1) { const { sourcecode, build } = data; const { longVersion } = build; @@ -109,7 +109,7 @@ function compile (data) { '': sourcecode }; - const compiled = compiler.compile({ sources: input }, 0, findImports); + const compiled = compiler.compile({ sources: input }, optimized, findImports); self.lastCompile = { version: longVersion, result: compiled, diff --git a/js/src/redux/providers/walletActions.js b/js/src/redux/providers/walletActions.js index 10f6a278e..8e13ac1a0 100644 --- a/js/src/redux/providers/walletActions.js +++ b/js/src/redux/providers/walletActions.js @@ -58,7 +58,7 @@ function modifyOperation (method, address, owner, operation) { contract.instance[method] .estimateGas(options, values) .then((gas) => { - options.gas = gas; + options.gas = gas.mul(1.2); return contract.instance[method].postTransaction(options, values); }) .then((requestId) => { diff --git a/js/src/views/Contract/Events/events.js b/js/src/views/Contract/Events/events.js index f0bee3e25..69ae8fd6a 100644 --- a/js/src/views/Contract/Events/events.js +++ b/js/src/views/Contract/Events/events.js @@ -15,6 +15,7 @@ // along with Parity. If not, see <http://www.gnu.org/licenses/>. import React, { Component, PropTypes } from 'react'; +import { uniq } from 'lodash'; import { Container } from '~/ui'; @@ -38,7 +39,10 @@ export default class Events extends Component { return null; } - const list = events.map((event) => { + const eventsKey = uniq(events.map((e) => e.key)); + const list = eventsKey.map((eventKey) => { + const event = events.find((e) => e.key === eventKey); + return ( <Event key={ event.key } From e1ad347cdb1c3fb5da458bd3bc0c2409951c85aa Mon Sep 17 00:00:00 2001 From: GitLab Build Bot <jaco+gitlab@ethcore.io> Date: Fri, 9 Dec 2016 00:32:45 +0000 Subject: [PATCH 31/63] [ci skip] js-precompiled 20161209-003004 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f603410f0..ff4c35734 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1271,7 +1271,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#a59b62ecec8773715d1db7e070bbbe5443eb7378" +source = "git+https://github.com/ethcore/js-precompiled.git#f5365b857b006ed60c02eb9360d60d7ddef65104" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index 62bf37b49..a758936f2 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.99", + "version": "0.2.100", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team <admin@parity.io>", From f9a24f3c8e0a120c718f00e3f54e1a19f422e0f3 Mon Sep 17 00:00:00 2001 From: Jaco Greeff <jacogr@gmail.com> Date: Fri, 9 Dec 2016 02:33:12 +0100 Subject: [PATCH 32/63] Update babel, fix CI build due to breaking changes (#3754) * Update babel to latest version * Re-add circular deps plugin * Bump for build * Fixes for babel build --- js/.babelrc | 1 + js/package.json | 11 ++-- js/scripts/test.js | 2 +- js/src/api/contract/contract.js | 9 ++- .../Animated/AnimateChildren.css | 55 ---------------- .../Animated/Animated.js | 28 --------- .../Animated/children.js | 63 ------------------- .../components-compositors/Animated/index.js | 17 ----- js/src/views/Status/components/Calls/Calls.js | 31 ++++----- js/webpack/shared.js | 8 ++- 10 files changed, 31 insertions(+), 194 deletions(-) delete mode 100644 js/src/views/Status/components-compositors/Animated/AnimateChildren.css delete mode 100644 js/src/views/Status/components-compositors/Animated/Animated.js delete mode 100644 js/src/views/Status/components-compositors/Animated/children.js delete mode 100644 js/src/views/Status/components-compositors/Animated/index.js diff --git a/js/.babelrc b/js/.babelrc index 2298d98c0..8147da435 100644 --- a/js/.babelrc +++ b/js/.babelrc @@ -7,6 +7,7 @@ "transform-runtime", "transform-decorators-legacy", "transform-class-properties", + "transform-object-rest-spread", "lodash" ], "retainLines": true, diff --git a/js/package.json b/js/package.json index a758936f2..4cbac5a75 100644 --- a/js/package.json +++ b/js/package.json @@ -48,25 +48,26 @@ }, "devDependencies": { "babel-cli": "6.18.0", - "babel-core": "6.18.2", + "babel-core": "6.20.0", "babel-eslint": "7.1.1", "babel-loader": "6.2.8", "babel-plugin-lodash": "3.2.10", - "babel-plugin-transform-class-properties": "6.19.0", + "babel-plugin-transform-class-properties": "6.18.0", "babel-plugin-transform-decorators-legacy": "1.3.4", + "babel-plugin-transform-object-rest-spread": "6.20.2", "babel-plugin-transform-react-remove-prop-types": "0.2.11", "babel-plugin-transform-runtime": "6.15.0", - "babel-polyfill": "6.16.0", + "babel-polyfill": "6.20.0", "babel-preset-es2015": "6.18.0", - "babel-preset-es2015-rollup": "1.2.0", "babel-preset-es2016": "6.16.0", "babel-preset-es2017": "6.16.0", "babel-preset-react": "6.16.0", "babel-preset-stage-0": "6.16.0", "babel-register": "6.18.0", - "babel-runtime": "6.18.0", + "babel-runtime": "6.20.0", "chai": "3.5.0", "chai-enzyme": "0.6.1", + "circular-dependency-plugin": "2.0.0", "copy-webpack-plugin": "4.0.1", "core-js": "2.4.1", "coveralls": "2.11.15", diff --git a/js/scripts/test.js b/js/scripts/test.js index 318fd7c84..78f2f99bd 100644 --- a/js/scripts/test.js +++ b/js/scripts/test.js @@ -1 +1 @@ -// test script 4 +// test script 6 diff --git a/js/src/api/contract/contract.js b/js/src/api/contract/contract.js index ed922a02c..1185199e1 100644 --- a/js/src/api/contract/contract.js +++ b/js/src/api/contract/contract.js @@ -262,12 +262,11 @@ export default class Contract { } const options = this._getFilterOptions(event, _options); + options.fromBlock = 0; + options.toBlock = 'latest'; + return this._api.eth - .getLogs({ - fromBlock: 0, - toBlock: 'latest', - ...options - }) + .getLogs(options) .then((logs) => this.parseEventLogs(logs)); } diff --git a/js/src/views/Status/components-compositors/Animated/AnimateChildren.css b/js/src/views/Status/components-compositors/Animated/AnimateChildren.css deleted file mode 100644 index 5d7b5b39a..000000000 --- a/js/src/views/Status/components-compositors/Animated/AnimateChildren.css +++ /dev/null @@ -1,55 +0,0 @@ -/* 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/>. -*/ -/* todo [adgo] - make local */ -:global .transition-appear { - opacity: 0.01; -} - -:global .transition-appear.transition-appear-active { - opacity: 1; - transition: opacity .3s ease-in-out; -} - -:global .transition-enter { - opacity: 0.01; -} - -:global .transition-enter.transition-enter-active { - opacity: 1; - transition: opacity .3s ease-in-out; -} - -:global .transition-leave { - opacity: 1; -} - -:global .transition-leave.transition-leave-active { - opacity: 0.01; - transition: opacity .3s ease-in-out; -} - -:global .absoluteAnimationContainer { - position: relative; -} - -:global .absoluteAnimationContainer > .transition-leave { - position: absolute; - top: 0; - left: 0; - right: 0; - width: 100%; -} diff --git a/js/src/views/Status/components-compositors/Animated/Animated.js b/js/src/views/Status/components-compositors/Animated/Animated.js deleted file mode 100644 index c22344c01..000000000 --- a/js/src/views/Status/components-compositors/Animated/Animated.js +++ /dev/null @@ -1,28 +0,0 @@ -// 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, { Component } from 'react'; -import AnimateChildren from './children'; - -export default Wrapped => class Animated extends Component { - render () { - return ( - <AnimateChildren> - <Wrapped { ...this.props } /> - </AnimateChildren> - ); - } -}; diff --git a/js/src/views/Status/components-compositors/Animated/children.js b/js/src/views/Status/components-compositors/Animated/children.js deleted file mode 100644 index be7f910bf..000000000 --- a/js/src/views/Status/components-compositors/Animated/children.js +++ /dev/null @@ -1,63 +0,0 @@ -// 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, { Component, PropTypes } from 'react'; -import { isReactComponent } from '../../util/react'; -import ReactCSSTransitionGroup from 'react-addons-css-transition-group'; -import './AnimateChildren.css'; - -export default class AnimateChildren extends Component { - render () { - const className = this.props.absolute ? 'absoluteAnimationContainer' : ''; - return ( - <ReactCSSTransitionGroup - component='div' - className={ className } - transitionName='transition' - transitionAppear - transitionAppearTimeout={ 0 } - transitionLeaveTimeout={ 0 } - transitionEnterTimeout={ 0 } - > - { this.renderChildren() } - </ReactCSSTransitionGroup> - ); - } - - renderChildren () { - const { children, isView } = this.props; - - if (isView) { - return React.cloneElement(this.props.children, { - key: this.props.pathname - }); - } - - if (isReactComponent(children)) { - return React.cloneElement(this.props.children, { ...this.props }); - } - - return children; - } - - static propTypes = { - children: PropTypes.any.isRequired, - pathname: PropTypes.string, - isView: PropTypes.bool, - absolute: PropTypes.bool - } - -} diff --git a/js/src/views/Status/components-compositors/Animated/index.js b/js/src/views/Status/components-compositors/Animated/index.js deleted file mode 100644 index ee48d0704..000000000 --- a/js/src/views/Status/components-compositors/Animated/index.js +++ /dev/null @@ -1,17 +0,0 @@ -// 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 './Animated'; diff --git a/js/src/views/Status/components/Calls/Calls.js b/js/src/views/Status/components/Calls/Calls.js index 7734104ec..1115ec632 100644 --- a/js/src/views/Status/components/Calls/Calls.js +++ b/js/src/views/Status/components/Calls/Calls.js @@ -15,7 +15,6 @@ // along with Parity. If not, see <http://www.gnu.org/licenses/>. import React, { Component, PropTypes } from 'react'; -import AnimateChildren from '../../components-compositors/Animated/children'; import Call from '../Call'; import CallsToolbar from '../CallsToolbar'; import styles from './Calls.css'; @@ -73,13 +72,11 @@ export default class Calls extends Component { } return ( - <AnimateChildren> - <div { ...this._test('empty-wrapper') }> - <h3 className={ styles.historyInfo } { ...this._test('empty') }> - Fire up some calls and the results will be here. - </h3> - </div> - </AnimateChildren> + <div { ...this._test('empty-wrapper') }> + <h3 className={ styles.historyInfo } { ...this._test('empty') }> + Fire up some calls and the results will be here. + </h3> + </div> ); } @@ -90,17 +87,13 @@ export default class Calls extends Component { return; } - return ( - <AnimateChildren> - { calls.map((call, idx) => ( - <Call - key={ calls.length - idx } - call={ call } - setActiveCall={ this.setActiveCall } - /> - )) } - </AnimateChildren> - ); + return calls.map((call, idx) => ( + <Call + key={ calls.length - idx } + call={ call } + setActiveCall={ this.setActiveCall } + /> + )); } clearActiveCall = () => { diff --git a/js/webpack/shared.js b/js/webpack/shared.js index 8887c41fb..8b6807b2a 100644 --- a/js/webpack/shared.js +++ b/js/webpack/shared.js @@ -23,6 +23,7 @@ const postcssImport = require('postcss-import'); const postcssNested = require('postcss-nested'); const postcssVars = require('postcss-simple-vars'); const rucksack = require('rucksack-css'); +const CircularDependencyPlugin = require('circular-dependency-plugin'); const ENV = process.env.NODE_ENV || 'development'; const isProd = ENV === 'production'; @@ -102,7 +103,12 @@ function getPlugins (_isProd = isProd) { } }), - new webpack.optimize.OccurrenceOrderPlugin(!_isProd) + new webpack.optimize.OccurrenceOrderPlugin(!_isProd), + + new CircularDependencyPlugin({ + exclude: /node_modules/, + failOnError: true + }) ]; if (_isProd) { From 12592689ddef95f79364ab87a3694c86543569fe Mon Sep 17 00:00:00 2001 From: GitLab Build Bot <jaco+gitlab@ethcore.io> Date: Fri, 9 Dec 2016 01:44:45 +0000 Subject: [PATCH 33/63] [ci skip] js-precompiled 20161209-014235 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ff4c35734..6d9374ef6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1271,7 +1271,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#f5365b857b006ed60c02eb9360d60d7ddef65104" +source = "git+https://github.com/ethcore/js-precompiled.git#b3ffa8d1bb55be78f623770c745ac91861ec5c28" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index 4cbac5a75..f75b28bda 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.100", + "version": "0.2.101", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team <admin@parity.io>", From 83f791fa5d1d2387a330b89738a851b30bd7e96a Mon Sep 17 00:00:00 2001 From: GitLab Build Bot <jaco+gitlab@ethcore.io> Date: Fri, 9 Dec 2016 04:43:19 +0000 Subject: [PATCH 34/63] [ci skip] js-precompiled 20161209-044115 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6d9374ef6..5795de427 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1271,7 +1271,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#b3ffa8d1bb55be78f623770c745ac91861ec5c28" +source = "git+https://github.com/ethcore/js-precompiled.git#1bf7160f6c8f25353d790dbd0935560d3d395727" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index f75b28bda..4be14d531 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.101", + "version": "0.2.102", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team <admin@parity.io>", 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 35/63] 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 36/63] 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 37/63] 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 befcc9cc1a432b3ec7563418c0dcbe4ec9f83320 Mon Sep 17 00:00:00 2001 From: Jaco Greeff <jacogr@gmail.com> Date: Fri, 9 Dec 2016 13:44:10 +0100 Subject: [PATCH 38/63] Cleanups (#3742) * Remove unused file * Combine tx checks into single file * Move UI components into UI * DRY links * Unused rollup config * Cleanup util paths * Revert "Move UI components into UI" This reverts commit 3379e61246cde635c296d31322b71e63395a5cd4. * Re-apply ~/util/tx move * Cleanup unused styles --- js/src/3rdparty/etherscan/links.js | 8 ++- js/src/jsonrpc/rollup.config.js | 12 ----- js/src/modals/AddAddress/addAddress.js | 2 +- js/src/modals/AddContract/addContract.js | 2 +- .../modals/CreateWallet/createWalletStore.js | 5 +- .../DeployContract/DetailsStep/detailsStep.js | 4 +- .../ParametersStep/parametersStep.js | 2 +- .../modals/DeployContract/deployContract.js | 2 +- js/src/modals/EditMeta/editMeta.js | 2 +- .../modals/ExecuteContract/executeContract.js | 4 +- js/src/modals/SMSVerification/store.js | 5 +- js/src/modals/SaveContract/saveContract.js | 2 +- js/src/modals/Shapeshift/shapeshift.js | 2 +- js/src/redux/providers/walletActions.js | 6 +-- js/src/ui/Form/TypedInput/typedInput.js | 3 +- js/src/ui/TxHash/txHash.js | 2 +- js/src/ui/TxList/txList.js | 2 +- js/src/util/check-if-tx-failed.js | 28 ---------- js/src/util/is-testnet.js | 19 ------- ...{wait-for-block-confirmations.js => tx.js} | 17 ++++-- .../Account/Transactions/transactions.css | 3 -- .../Account/Transactions/transactions.js | 2 +- js/src/views/Account/account.css | 2 - js/src/views/Account/account.js | 2 +- js/src/views/Accounts/accounts.css | 2 - js/src/views/Accounts/accounts.js | 2 +- js/src/views/Address/address.css | 52 +++++++++---------- js/src/views/Address/address.js | 4 +- js/src/views/Addresses/addresses.css | 20 ++++--- js/src/views/Addresses/addresses.js | 2 +- js/src/views/Connection/connection.js | 1 - js/src/views/Contract/Events/Event/event.js | 2 +- js/src/views/Contract/contract.css | 26 ++++------ js/src/views/Contract/contract.js | 2 +- js/src/views/Contracts/contracts.css | 18 ------- js/src/views/Contracts/contracts.js | 5 +- js/src/views/Settings/settings.css | 44 +++++++--------- js/src/views/Settings/settings.js | 2 +- .../Account/AccountLink/AccountLink.js | 2 +- .../components/TxHashLink/TxHashLink.js | 2 +- .../Signer/containers/Embedded/embedded.css | 3 -- .../Signer/containers/Embedded/embedded.js | 2 +- .../containers/RequestsPage/RequestsPage.css | 6 --- .../containers/RequestsPage/RequestsPage.js | 5 +- js/src/views/Signer/signer.css | 24 --------- js/src/views/Signer/signer.js | 4 +- .../containers/StatusPage/StatusPage.js | 4 +- .../containers/StatusPage/statusPage.css | 18 ------- 48 files changed, 118 insertions(+), 272 deletions(-) delete mode 100644 js/src/jsonrpc/rollup.config.js delete mode 100644 js/src/util/check-if-tx-failed.js delete mode 100644 js/src/util/is-testnet.js rename js/src/util/{wait-for-block-confirmations.js => tx.js} (73%) delete mode 100644 js/src/views/Contracts/contracts.css delete mode 100644 js/src/views/Signer/signer.css delete mode 100644 js/src/views/Status/containers/StatusPage/statusPage.css diff --git a/js/src/3rdparty/etherscan/links.js b/js/src/3rdparty/etherscan/links.js index 2745873fc..e85572850 100644 --- a/js/src/3rdparty/etherscan/links.js +++ b/js/src/3rdparty/etherscan/links.js @@ -14,10 +14,14 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see <http://www.gnu.org/licenses/>. +export const url = (isTestnet = false) => { + return `https://${isTestnet ? 'testnet.' : ''}etherscan.io`; +}; + export const txLink = (hash, isTestnet = false) => { - return `https://${isTestnet ? 'testnet.' : ''}etherscan.io/tx/${hash}`; + return `${url(isTestnet)}/tx/${hash}`; }; export const addressLink = (address, isTestnet = false) => { - return `https://${isTestnet ? 'testnet.' : ''}etherscan.io/address/${address}`; + return `${url(isTestnet)}/address/${address}`; }; diff --git a/js/src/jsonrpc/rollup.config.js b/js/src/jsonrpc/rollup.config.js deleted file mode 100644 index cc4f8c931..000000000 --- a/js/src/jsonrpc/rollup.config.js +++ /dev/null @@ -1,12 +0,0 @@ -import babel from 'rollup-plugin-babel'; - -export default { - entry: 'src/index.js', - dest: 'release/index.js', - format: 'cjs', - plugins: [babel({ - babelrc: false, - presets: ['es2015-rollup', 'stage-0'], - runtimeHelpers: true - })] -}; diff --git a/js/src/modals/AddAddress/addAddress.js b/js/src/modals/AddAddress/addAddress.js index 590287e73..e44cb0b3c 100644 --- a/js/src/modals/AddAddress/addAddress.js +++ b/js/src/modals/AddAddress/addAddress.js @@ -19,7 +19,7 @@ import ContentAdd from 'material-ui/svg-icons/content/add'; import ContentClear from 'material-ui/svg-icons/content/clear'; import { Button, Modal, Form, Input, InputAddress } from '~/ui'; -import { ERRORS, validateAddress, validateName } from '../../util/validation'; +import { ERRORS, validateAddress, validateName } from '~/util/validation'; export default class AddAddress extends Component { static contextTypes = { diff --git a/js/src/modals/AddContract/addContract.js b/js/src/modals/AddContract/addContract.js index 71f8a911d..b19398001 100644 --- a/js/src/modals/AddContract/addContract.js +++ b/js/src/modals/AddContract/addContract.js @@ -21,7 +21,7 @@ import NavigationArrowForward from 'material-ui/svg-icons/navigation/arrow-forwa import NavigationArrowBack from 'material-ui/svg-icons/navigation/arrow-back'; import { Button, Modal, Form, Input, InputAddress, RadioButtons } from '~/ui'; -import { ERRORS, validateAbi, validateAddress, validateName } from '../../util/validation'; +import { ERRORS, validateAbi, validateAddress, validateName } from '~/util/validation'; import { eip20, wallet } from '~/contracts/abi'; diff --git a/js/src/modals/CreateWallet/createWalletStore.js b/js/src/modals/CreateWallet/createWalletStore.js index d8c308a12..1d3aa6ee2 100644 --- a/js/src/modals/CreateWallet/createWalletStore.js +++ b/js/src/modals/CreateWallet/createWalletStore.js @@ -16,13 +16,12 @@ import { observable, computed, action, transaction } from 'mobx'; -import { validateUint, validateAddress, validateName } from '~/util/validation'; -import { ERROR_CODES } from '~/api/transport/error'; - import Contract from '~/api/contract'; +import { ERROR_CODES } from '~/api/transport/error'; import { wallet as walletAbi } from '~/contracts/abi'; import { wallet as walletCode } from '~/contracts/code'; +import { validateUint, validateAddress, validateName } from '~/util/validation'; import WalletsUtils from '~/util/wallets'; const STEPS = { diff --git a/js/src/modals/DeployContract/DetailsStep/detailsStep.js b/js/src/modals/DeployContract/DetailsStep/detailsStep.js index 54ef8f850..aa0a30e55 100644 --- a/js/src/modals/DeployContract/DetailsStep/detailsStep.js +++ b/js/src/modals/DeployContract/DetailsStep/detailsStep.js @@ -18,8 +18,8 @@ import React, { Component, PropTypes } from 'react'; import { MenuItem } from 'material-ui'; import { AddressSelect, Form, Input, Select } from '~/ui'; -import { validateAbi } from '../../../util/validation'; -import { parseAbiType } from '../../../util/abi'; +import { validateAbi } from '~/util/validation'; +import { parseAbiType } from '~/util/abi'; export default class DetailsStep extends Component { static contextTypes = { diff --git a/js/src/modals/DeployContract/ParametersStep/parametersStep.js b/js/src/modals/DeployContract/ParametersStep/parametersStep.js index 4c7228b67..4ab5df693 100644 --- a/js/src/modals/DeployContract/ParametersStep/parametersStep.js +++ b/js/src/modals/DeployContract/ParametersStep/parametersStep.js @@ -32,7 +32,7 @@ import React, { Component, PropTypes } from 'react'; import { Form, TypedInput } from '~/ui'; -import { parseAbiType } from '../../../util/abi'; +import { parseAbiType } from '~/util/abi'; import styles from '../deployContract.css'; diff --git a/js/src/modals/DeployContract/deployContract.js b/js/src/modals/DeployContract/deployContract.js index ae5578e40..5bf4fc389 100644 --- a/js/src/modals/DeployContract/deployContract.js +++ b/js/src/modals/DeployContract/deployContract.js @@ -19,7 +19,7 @@ import ActionDoneAll from 'material-ui/svg-icons/action/done-all'; import ContentClear from 'material-ui/svg-icons/content/clear'; import { BusyStep, CompletedStep, CopyToClipboard, Button, IdentityIcon, Modal, TxHash } from '~/ui'; -import { ERRORS, validateAbi, validateCode, validateName } from '../../util/validation'; +import { ERRORS, validateAbi, validateCode, validateName } from '~/util/validation'; import DetailsStep from './DetailsStep'; import ParametersStep from './ParametersStep'; diff --git a/js/src/modals/EditMeta/editMeta.js b/js/src/modals/EditMeta/editMeta.js index ad26c19fe..8ab9233f1 100644 --- a/js/src/modals/EditMeta/editMeta.js +++ b/js/src/modals/EditMeta/editMeta.js @@ -19,7 +19,7 @@ import ContentClear from 'material-ui/svg-icons/content/clear'; import ContentSave from 'material-ui/svg-icons/content/save'; import { Button, Form, Input, InputChip, Modal } from '~/ui'; -import { validateName } from '../../util/validation'; +import { validateName } from '~/util/validation'; export default class EditMeta extends Component { static contextTypes = { diff --git a/js/src/modals/ExecuteContract/executeContract.js b/js/src/modals/ExecuteContract/executeContract.js index 4a708d17a..2db5e2b04 100644 --- a/js/src/modals/ExecuteContract/executeContract.js +++ b/js/src/modals/ExecuteContract/executeContract.js @@ -21,8 +21,8 @@ import ActionDoneAll from 'material-ui/svg-icons/action/done-all'; import ContentClear from 'material-ui/svg-icons/content/clear'; import { BusyStep, CompletedStep, Button, IdentityIcon, Modal, TxHash } from '~/ui'; -import { MAX_GAS_ESTIMATION } from '../../util/constants'; -import { validateAddress, validateUint } from '../../util/validation'; +import { MAX_GAS_ESTIMATION } from '~/util/constants'; +import { validateAddress, validateUint } from '~/util/validation'; import { parseAbiType } from '~/util/abi'; import DetailsStep from './DetailsStep'; diff --git a/js/src/modals/SMSVerification/store.js b/js/src/modals/SMSVerification/store.js index 49b91fa70..7a132aaf7 100644 --- a/js/src/modals/SMSVerification/store.js +++ b/js/src/modals/SMSVerification/store.js @@ -21,9 +21,8 @@ import { sha3 } from '~/api/util/sha3'; import Contracts from '~/contracts'; import { checkIfVerified, checkIfRequested, awaitPuzzle } from '~/contracts/sms-verification'; -import { postToServer } from '../../3rdparty/sms-verification'; -import checkIfTxFailed from '../../util/check-if-tx-failed'; -import waitForConfirmations from '../../util/wait-for-block-confirmations'; +import { postToServer } from '~/3rdparty/sms-verification'; +import { checkIfTxFailed, waitForConfirmations } from '~/util/tx'; export const LOADING = 'fetching-contract'; export const QUERY_DATA = 'query-data'; diff --git a/js/src/modals/SaveContract/saveContract.js b/js/src/modals/SaveContract/saveContract.js index c73fd8a21..5d7863632 100644 --- a/js/src/modals/SaveContract/saveContract.js +++ b/js/src/modals/SaveContract/saveContract.js @@ -20,7 +20,7 @@ import SaveIcon from 'material-ui/svg-icons/content/save'; import ContentClear from 'material-ui/svg-icons/content/clear'; import { Button, Modal, Editor, Form, Input } from '~/ui'; -import { ERRORS, validateName } from '../../util/validation'; +import { ERRORS, validateName } from '~/util/validation'; import styles from './saveContract.css'; diff --git a/js/src/modals/Shapeshift/shapeshift.js b/js/src/modals/Shapeshift/shapeshift.js index 562c8cbf3..9c5d696b0 100644 --- a/js/src/modals/Shapeshift/shapeshift.js +++ b/js/src/modals/Shapeshift/shapeshift.js @@ -19,7 +19,7 @@ import ActionDoneAll from 'material-ui/svg-icons/action/done-all'; import ContentClear from 'material-ui/svg-icons/content/clear'; import { Button, IdentityIcon, Modal } from '~/ui'; -import initShapeshift from '../../3rdparty/shapeshift'; +import initShapeshift from '~/3rdparty/shapeshift'; import shapeshiftLogo from '../../../assets/images/shapeshift-logo.png'; import AwaitingDepositStep from './AwaitingDepositStep'; diff --git a/js/src/redux/providers/walletActions.js b/js/src/redux/providers/walletActions.js index 8e13ac1a0..4a8a3f82a 100644 --- a/js/src/redux/providers/walletActions.js +++ b/js/src/redux/providers/walletActions.js @@ -17,12 +17,10 @@ import { isEqual, uniq } from 'lodash'; import Contract from '~/api/contract'; -import { wallet as WALLET_ABI } from '~/contracts/abi'; import { bytesToHex, toHex } from '~/api/util/format'; - import { ERROR_CODES } from '~/api/transport/error'; -import { MAX_GAS_ESTIMATION } from '../../util/constants'; - +import { wallet as WALLET_ABI } from '~/contracts/abi'; +import { MAX_GAS_ESTIMATION } from '~/util/constants'; import WalletsUtils from '~/util/wallets'; import { newError } from '~/ui/Errors/actions'; diff --git a/js/src/ui/Form/TypedInput/typedInput.js b/js/src/ui/Form/TypedInput/typedInput.js index 9162348e0..a81ec7b79 100644 --- a/js/src/ui/Form/TypedInput/typedInput.js +++ b/js/src/ui/Form/TypedInput/typedInput.js @@ -22,12 +22,11 @@ import IconButton from 'material-ui/IconButton'; import AddIcon from 'material-ui/svg-icons/content/add'; import RemoveIcon from 'material-ui/svg-icons/content/remove'; +import { fromWei, toWei } from '~/api/util/wei'; import Input from '~/ui/Form/Input'; import InputAddressSelect from '~/ui/Form/InputAddressSelect'; import Select from '~/ui/Form/Select'; - import { ABI_TYPES } from '~/util/abi'; -import { fromWei, toWei } from '~/api/util/wei'; import styles from './typedInput.css'; diff --git a/js/src/ui/TxHash/txHash.js b/js/src/ui/TxHash/txHash.js index 81c4e0bf5..715568fd9 100644 --- a/js/src/ui/TxHash/txHash.js +++ b/js/src/ui/TxHash/txHash.js @@ -20,7 +20,7 @@ import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import { LinearProgress } from 'material-ui'; -import { txLink } from '../../3rdparty/etherscan/links'; +import { txLink } from '~/3rdparty/etherscan/links'; import ShortenedHash from '../ShortenedHash'; import styles from './txHash.css'; diff --git a/js/src/ui/TxList/txList.js b/js/src/ui/TxList/txList.js index b8c53c1d9..3f5dab264 100644 --- a/js/src/ui/TxList/txList.js +++ b/js/src/ui/TxList/txList.js @@ -20,7 +20,7 @@ import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import { observer } from 'mobx-react'; -import { txLink, addressLink } from '../../3rdparty/etherscan/links'; +import { txLink, addressLink } from '~/3rdparty/etherscan/links'; import IdentityIcon from '../IdentityIcon'; import IdentityName from '../IdentityName'; diff --git a/js/src/util/check-if-tx-failed.js b/js/src/util/check-if-tx-failed.js deleted file mode 100644 index 39689bedd..000000000 --- a/js/src/util/check-if-tx-failed.js +++ /dev/null @@ -1,28 +0,0 @@ -// 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/>. - -const checkIfTxFailed = (api, tx, gasSent) => { - return api.pollMethod('eth_getTransactionReceipt', tx) - .then((receipt) => { - // TODO: Right now, there's no way to tell wether the EVM code crashed. - // Because you usually send a bit more gas than estimated (to make sure - // it gets mined quickly), we transaction probably failed if all the gas - // has been used up. - return receipt.gasUsed.eq(gasSent); - }); -}; - -export default checkIfTxFailed; diff --git a/js/src/util/is-testnet.js b/js/src/util/is-testnet.js deleted file mode 100644 index c2bf2c450..000000000 --- a/js/src/util/is-testnet.js +++ /dev/null @@ -1,19 +0,0 @@ -// 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 (chain) => { - return chain === 'morden' || chain === 'ropsten' || chain === 'testnet'; -}; diff --git a/js/src/util/wait-for-block-confirmations.js b/js/src/util/tx.js similarity index 73% rename from js/src/util/wait-for-block-confirmations.js rename to js/src/util/tx.js index 79ba2be25..b688d06fe 100644 --- a/js/src/util/wait-for-block-confirmations.js +++ b/js/src/util/tx.js @@ -18,7 +18,18 @@ const isValidReceipt = (receipt) => { return receipt && receipt.blockNumber && receipt.blockNumber.gt(0); }; -const waitForConfirmations = (api, tx, confirmations) => { +export function checkIfTxFailed (api, tx, gasSent) { + return api.pollMethod('eth_getTransactionReceipt', tx) + .then((receipt) => { + // TODO: Right now, there's no way to tell wether the EVM code crashed. + // Because you usually send a bit more gas than estimated (to make sure + // it gets mined quickly), we transaction probably failed if all the gas + // has been used up. + return receipt.gasUsed.eq(gasSent); + }); +} + +export function waitForConfirmations (api, tx, confirmations) { return new Promise((resolve, reject) => { api.pollMethod('eth_getTransactionReceipt', tx, isValidReceipt) .then((receipt) => { @@ -39,6 +50,4 @@ const waitForConfirmations = (api, tx, confirmations) => { .catch(reject); }); }); -}; - -export default waitForConfirmations; +} diff --git a/js/src/views/Account/Transactions/transactions.css b/js/src/views/Account/Transactions/transactions.css index 13d727deb..6b44d298f 100644 --- a/js/src/views/Account/Transactions/transactions.css +++ b/js/src/views/Account/Transactions/transactions.css @@ -15,9 +15,6 @@ /* along with Parity. If not, see <http://www.gnu.org/licenses/>. */ -.transactions { -} - .infonone { opacity: 0.25; } diff --git a/js/src/views/Account/Transactions/transactions.js b/js/src/views/Account/Transactions/transactions.js index e71781adf..2e98208b6 100644 --- a/js/src/views/Account/Transactions/transactions.js +++ b/js/src/views/Account/Transactions/transactions.js @@ -18,7 +18,7 @@ import React, { Component, PropTypes } from 'react'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; -import etherscan from '../../../3rdparty/etherscan'; +import etherscan from '~/3rdparty/etherscan'; import { Container, TxList } from '~/ui'; import styles from './transactions.css'; diff --git a/js/src/views/Account/account.css b/js/src/views/Account/account.css index b1410f23a..a9a039c83 100644 --- a/js/src/views/Account/account.css +++ b/js/src/views/Account/account.css @@ -14,8 +14,6 @@ /* You should have received a copy of the GNU General Public License /* along with Parity. If not, see <http://www.gnu.org/licenses/>. */ -.account { -} .btnicon { width: 24px; diff --git a/js/src/views/Account/account.js b/js/src/views/Account/account.js index 98b0a5e97..cbacd5280 100644 --- a/js/src/views/Account/account.js +++ b/js/src/views/Account/account.js @@ -105,7 +105,7 @@ class Account extends Component { } return ( - <div className={ styles.account }> + <div> { this.renderDeleteDialog(account) } { this.renderEditDialog(account) } { this.renderFundDialog() } diff --git a/js/src/views/Accounts/accounts.css b/js/src/views/Accounts/accounts.css index 469f33ddc..317905fd5 100644 --- a/js/src/views/Accounts/accounts.css +++ b/js/src/views/Accounts/accounts.css @@ -14,8 +14,6 @@ /* You should have received a copy of the GNU General Public License /* along with Parity. If not, see <http://www.gnu.org/licenses/>. */ -.accounts { -} .accountTooltip { top: 13.3em; diff --git a/js/src/views/Accounts/accounts.js b/js/src/views/Accounts/accounts.js index a8a29945f..4383b7662 100644 --- a/js/src/views/Accounts/accounts.js +++ b/js/src/views/Accounts/accounts.js @@ -82,7 +82,7 @@ class Accounts extends Component { render () { return ( - <div className={ styles.accounts }> + <div> { this.renderNewDialog() } { this.renderNewWalletDialog() } { this.renderActionbar() } diff --git a/js/src/views/Address/address.css b/js/src/views/Address/address.css index d93b88b59..cd44688bb 100644 --- a/js/src/views/Address/address.css +++ b/js/src/views/Address/address.css @@ -14,37 +14,37 @@ /* You should have received a copy of the GNU General Public License /* along with Parity. If not, see <http://www.gnu.org/licenses/>. */ -.address { -} -.delete .hero { - padding-bottom: 1em; -} +.delete { + .hero { + padding-bottom: 1em; + } -.delete .info { - display: inline-block; -} + .info { + display: inline-block; + } -.delete .icon { - display: inline-block; -} + .icon { + display: inline-block; + } -.delete .nameinfo { - display: inline-block; - text-align: left; -} + .nameinfo { + display: inline-block; + text-align: left; + } -.delete .header { - text-transform: uppercase; - font-size: 1.25em; - padding-bottom: 0.25em; -} + .header { + text-transform: uppercase; + font-size: 1.25em; + padding-bottom: 0.25em; + } -.delete .address { -} + .address { + } -.delete .description { - padding-top: 1em; - font-size: 0.75em; - color: #aaa; + .description { + padding-top: 1em; + font-size: 0.75em; + color: #aaa; + } } diff --git a/js/src/views/Address/address.js b/js/src/views/Address/address.js index 0fae3cb1f..c1427b2be 100644 --- a/js/src/views/Address/address.js +++ b/js/src/views/Address/address.js @@ -28,8 +28,6 @@ import Transactions from '../Account/Transactions'; import Delete from './Delete'; import { setVisibleAccounts } from '~/redux/providers/personalActions'; -import styles from './address.css'; - class Address extends Component { static contextTypes = { api: PropTypes.object.isRequired, @@ -85,7 +83,7 @@ class Address extends Component { } return ( - <div className={ styles.address }> + <div> { this.renderEditDialog(contact) } { this.renderActionbar(contact) } <Delete diff --git a/js/src/views/Addresses/addresses.css b/js/src/views/Addresses/addresses.css index 5dbbe9aec..5bbbf8a7e 100644 --- a/js/src/views/Addresses/addresses.css +++ b/js/src/views/Addresses/addresses.css @@ -14,8 +14,6 @@ /* You should have received a copy of the GNU General Public License /* along with Parity. If not, see <http://www.gnu.org/licenses/>. */ -.addresses { -} .list { display: flex; @@ -26,21 +24,21 @@ flex: 0 1 50%; width: 50%; position: relative; -} -.address:nth-child(odd)>div { - padding-right: 0.5em !important; -} + &:nth-child(odd)>div { + padding-right: 0.5em !important; + } -.address:nth-child(even)>div { - padding-left: 0.5em !important; + &:nth-child(even)>div { + padding-left: 0.5em !important; + } } .empty { width: 100%; display: block; -} -.empty div { - color: #aaa; + div { + color: #aaa; + } } diff --git a/js/src/views/Addresses/addresses.js b/js/src/views/Addresses/addresses.js index 4edfb0e6e..5e0ed4e18 100644 --- a/js/src/views/Addresses/addresses.js +++ b/js/src/views/Addresses/addresses.js @@ -76,7 +76,7 @@ class Addresses extends Component { const { searchValues, sortOrder } = this.state; return ( - <div className={ styles.addresses }> + <div> { this.renderActionbar() } { this.renderAddAddress() } <Page> diff --git a/js/src/views/Connection/connection.js b/js/src/views/Connection/connection.js index 451624a2b..ad0e0c140 100644 --- a/js/src/views/Connection/connection.js +++ b/js/src/views/Connection/connection.js @@ -19,7 +19,6 @@ import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import ActionCompareArrows from 'material-ui/svg-icons/action/compare-arrows'; import ActionDashboard from 'material-ui/svg-icons/action/dashboard'; -// import CommunicationVpnKey from 'material-ui/svg-icons/communication/vpn-key'; import HardwareDesktopMac from 'material-ui/svg-icons/hardware/desktop-mac'; import NotificationVpnLock from 'material-ui/svg-icons/notification/vpn-lock'; diff --git a/js/src/views/Contract/Events/Event/event.js b/js/src/views/Contract/Events/Event/event.js index b42295198..1ed114d07 100644 --- a/js/src/views/Contract/Events/Event/event.js +++ b/js/src/views/Contract/Events/Event/event.js @@ -20,7 +20,7 @@ import React, { Component, PropTypes } from 'react'; import { IdentityIcon, IdentityName, Input, InputAddress } from '~/ui'; import ShortenedHash from '~/ui/ShortenedHash'; -import { txLink } from '../../../../3rdparty/etherscan/links'; +import { txLink } from '~/3rdparty/etherscan/links'; import styles from '../../contract.css'; diff --git a/js/src/views/Contract/contract.css b/js/src/views/Contract/contract.css index eb6cabfa2..f49da0831 100644 --- a/js/src/views/Contract/contract.css +++ b/js/src/views/Contract/contract.css @@ -15,26 +15,26 @@ /* along with Parity. If not, see <http://www.gnu.org/licenses/>. */ -.contract { -} - .events { width: 100%; border: none; border-spacing: 0; -} -.events tr { - line-height: 32px; - vertical-align: top; + tr { + line-height: 32px; + vertical-align: top; + } } .event { -} + td { + vertical-align: top; + padding: 1em 0.5em; -.event td { - vertical-align: top; - padding: 1em 0.5em; + div { + white-space: nowrap; + } + } } .txhash { @@ -47,10 +47,6 @@ color: #aaa; } -.event td div { - white-space: nowrap; -} - .mined { } diff --git a/js/src/views/Contract/contract.js b/js/src/views/Contract/contract.js index 2aa37f847..35ad95fe2 100644 --- a/js/src/views/Contract/contract.js +++ b/js/src/views/Contract/contract.js @@ -124,7 +124,7 @@ class Contract extends Component { } return ( - <div className={ styles.contract }> + <div> { this.renderActionbar(account) } { this.renderDeleteDialog(account) } { this.renderEditDialog(account) } diff --git a/js/src/views/Contracts/contracts.css b/js/src/views/Contracts/contracts.css deleted file mode 100644 index eab0858e7..000000000 --- a/js/src/views/Contracts/contracts.css +++ /dev/null @@ -1,18 +0,0 @@ -/* 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/>. -*/ -.contracts { -} diff --git a/js/src/views/Contracts/contracts.js b/js/src/views/Contracts/contracts.js index d97d88b09..b84705b32 100644 --- a/js/src/views/Contracts/contracts.js +++ b/js/src/views/Contracts/contracts.js @@ -28,8 +28,6 @@ import { setVisibleAccounts } from '~/redux/providers/personalActions'; import List from '../Accounts/List'; -import styles from './contracts.css'; - class Contracts extends Component { static contextTypes = { api: PropTypes.object.isRequired @@ -80,7 +78,7 @@ class Contracts extends Component { const { searchValues, sortOrder } = this.state; return ( - <div className={ styles.contracts }> + <div> { this.renderActionbar() } { this.renderAddContract() } { this.renderAddContract() } @@ -159,7 +157,6 @@ class Contracts extends Component { return ( <Actionbar - className={ styles.toolbar } title='Contracts' buttons={ buttons } /> ); diff --git a/js/src/views/Settings/settings.css b/js/src/views/Settings/settings.css index 91138db68..1eab3aac3 100644 --- a/js/src/views/Settings/settings.css +++ b/js/src/views/Settings/settings.css @@ -15,9 +15,6 @@ /* along with Parity. If not, see <http://www.gnu.org/licenses/>. */ -.layout { -} - .menu { display: inline-block; } @@ -35,31 +32,24 @@ padding: 16px 2em !important; line-height: 24px !important; width: auto !important; -} -.tabactive { -} + &>div { + height: 24px !important; -.tab>div, -.tabactive>div { - height: 24px !important; -} + &>div { + display: inline-block !important; + } + } -.tab>div>div, -.tabactive>div>div { - display: inline-block !important; -} + svg { + margin-right: 0.5em; + margin-bottom: 0 !important; + } -.tab svg, -.tabactive svg { - margin-right: 0.5em; - margin-bottom: 0 !important; -} - -.tab .menu, -.tabactive .menu { - vertical-align: top; - display: inline-block; + .menu { + vertical-align: top; + display: inline-block; + } } .imageIcon { @@ -68,6 +58,8 @@ opacity: 0.5; } -.tabactive .imageIcon { - opacity: 1; +.tabactive { + .imageIcon { + opacity: 1; + } } diff --git a/js/src/views/Settings/settings.js b/js/src/views/Settings/settings.js index 54b4fa6dd..a6c455dad 100644 --- a/js/src/views/Settings/settings.js +++ b/js/src/views/Settings/settings.js @@ -45,7 +45,7 @@ export default class Settings extends Component { } return ( - <div className={ styles.layout }> + <div> <Actionbar title='settings' className={ styles.bar }> <Tabs className={ styles.tabs } value={ hash }> { this.renderTab(hash, 'views', <ImageRemoveRedEye />) } diff --git a/js/src/views/Signer/components/Account/AccountLink/AccountLink.js b/js/src/views/Signer/components/Account/AccountLink/AccountLink.js index 97ff35ce9..f42675474 100644 --- a/js/src/views/Signer/components/Account/AccountLink/AccountLink.js +++ b/js/src/views/Signer/components/Account/AccountLink/AccountLink.js @@ -16,7 +16,7 @@ import React, { Component, PropTypes } from 'react'; -import { addressLink } from '../../../../../3rdparty/etherscan/links'; +import { addressLink } from '~/3rdparty/etherscan/links'; import styles from './AccountLink.css'; export default class AccountLink extends Component { diff --git a/js/src/views/Signer/components/TxHashLink/TxHashLink.js b/js/src/views/Signer/components/TxHashLink/TxHashLink.js index 5fbd5695e..bce30eded 100644 --- a/js/src/views/Signer/components/TxHashLink/TxHashLink.js +++ b/js/src/views/Signer/components/TxHashLink/TxHashLink.js @@ -16,7 +16,7 @@ import React, { Component, PropTypes } from 'react'; -import { txLink } from '../../../../3rdparty/etherscan/links'; +import { txLink } from '~/3rdparty/etherscan/links'; export default class TxHashLink extends Component { diff --git a/js/src/views/Signer/containers/Embedded/embedded.css b/js/src/views/Signer/containers/Embedded/embedded.css index 94e0f3933..ffc6a209f 100644 --- a/js/src/views/Signer/containers/Embedded/embedded.css +++ b/js/src/views/Signer/containers/Embedded/embedded.css @@ -23,9 +23,6 @@ width: $embedWidth; } -.pending { -} - .none { color: #aaa; } diff --git a/js/src/views/Signer/containers/Embedded/embedded.js b/js/src/views/Signer/containers/Embedded/embedded.js index 57639ea11..4629a6522 100644 --- a/js/src/views/Signer/containers/Embedded/embedded.js +++ b/js/src/views/Signer/containers/Embedded/embedded.js @@ -71,7 +71,7 @@ class Embedded extends Component { const items = pending.sort(this._sortRequests).map(this.renderPending); return ( - <div className={ styles.pending }> + <div> { items } </div> ); diff --git a/js/src/views/Signer/containers/RequestsPage/RequestsPage.css b/js/src/views/Signer/containers/RequestsPage/RequestsPage.css index 3701c3097..662c45817 100644 --- a/js/src/views/Signer/containers/RequestsPage/RequestsPage.css +++ b/js/src/views/Signer/containers/RequestsPage/RequestsPage.css @@ -15,12 +15,6 @@ /* along with Parity. If not, see <http://www.gnu.org/licenses/>. */ -.request { -} - .noRequestsMsg { color: #aaa; } - -.items { -} diff --git a/js/src/views/Signer/containers/RequestsPage/RequestsPage.js b/js/src/views/Signer/containers/RequestsPage/RequestsPage.js index ecb2ccd43..f3cd5a267 100644 --- a/js/src/views/Signer/containers/RequestsPage/RequestsPage.js +++ b/js/src/views/Signer/containers/RequestsPage/RequestsPage.js @@ -98,9 +98,7 @@ class RequestsPage extends Component { return ( <Container title='Pending Requests'> - <div className={ styles.items }> - { items } - </div> + { items } </Container> ); } @@ -111,7 +109,6 @@ class RequestsPage extends Component { return ( <RequestPending - className={ styles.request } onConfirm={ actions.startConfirmRequest } onReject={ actions.startRejectRequest } isSending={ isSending || false } diff --git a/js/src/views/Signer/signer.css b/js/src/views/Signer/signer.css deleted file mode 100644 index 83d1b800f..000000000 --- a/js/src/views/Signer/signer.css +++ /dev/null @@ -1,24 +0,0 @@ -/* 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/>. -*/ -.signer { -} - -.container { -} - -.mainContainer { -} diff --git a/js/src/views/Signer/signer.js b/js/src/views/Signer/signer.js index 536cf1d37..6d68c1dc0 100644 --- a/js/src/views/Signer/signer.js +++ b/js/src/views/Signer/signer.js @@ -19,12 +19,10 @@ import React, { Component } from 'react'; import { Actionbar } from '~/ui'; import RequestsPage from './containers/RequestsPage'; -import styles from './signer.css'; - export default class Signer extends Component { render () { return ( - <div className={ styles.signer }> + <div> <Actionbar title='Trusted Signer' /> <RequestsPage /> diff --git a/js/src/views/Status/containers/StatusPage/StatusPage.js b/js/src/views/Status/containers/StatusPage/StatusPage.js index 286e2c20e..c7ab56c5e 100644 --- a/js/src/views/Status/containers/StatusPage/StatusPage.js +++ b/js/src/views/Status/containers/StatusPage/StatusPage.js @@ -23,8 +23,6 @@ import { clearStatusLogs, toggleStatusLogs, toggleStatusRefresh } from '~/redux/ import Debug from '../../components/Debug'; import Status from '../../components/Status'; -import styles from './statusPage.css'; - class StatusPage extends Component { static propTypes = { nodeStatus: PropTypes.object.isRequired, @@ -41,7 +39,7 @@ class StatusPage extends Component { render () { return ( - <div className={ styles.body }> + <div> <Status { ...this.props } /> <Debug { ...this.props } /> </div> diff --git a/js/src/views/Status/containers/StatusPage/statusPage.css b/js/src/views/Status/containers/StatusPage/statusPage.css deleted file mode 100644 index 850ee0856..000000000 --- a/js/src/views/Status/containers/StatusPage/statusPage.css +++ /dev/null @@ -1,18 +0,0 @@ -/* 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/>. -*/ -.body { -} From 2582514b5875be42e74a5de541937ebbc7b40a04 Mon Sep 17 00:00:00 2001 From: Jaco Greeff <jacogr@gmail.com> Date: Fri, 9 Dec 2016 13:44:35 +0100 Subject: [PATCH 39/63] GasEditor component (#3750) * Initial split of component (WIP) * GasPriceEditor externalised * Fix lint --- js/src/modals/Transfer/Extras/extras.js | 97 ++------- js/src/modals/Transfer/store.js | 85 ++------ js/src/modals/Transfer/transfer.css | 9 - js/src/modals/Transfer/transfer.js | 23 +-- .../GasPriceSelector/gasPriceSelector.css | 10 + .../GasPriceSelector/gasPriceSelector.js | 194 +++++++++--------- .../GasPriceEditor}/GasPriceSelector/index.js | 0 js/src/ui/GasPriceEditor/gasPriceEditor.css | 49 +++++ js/src/ui/GasPriceEditor/gasPriceEditor.js | 98 +++++++++ js/src/ui/GasPriceEditor/index.js | 17 ++ js/src/ui/GasPriceEditor/store.js | 105 ++++++++++ js/src/ui/index.js | 6 +- js/src/util/validation.js | 24 ++- 13 files changed, 440 insertions(+), 277 deletions(-) rename js/src/{modals/Transfer => ui/GasPriceEditor}/GasPriceSelector/gasPriceSelector.css (85%) rename js/src/{modals/Transfer => ui/GasPriceEditor}/GasPriceSelector/gasPriceSelector.js (77%) rename js/src/{modals/Transfer => ui/GasPriceEditor}/GasPriceSelector/index.js (100%) create mode 100644 js/src/ui/GasPriceEditor/gasPriceEditor.css create mode 100644 js/src/ui/GasPriceEditor/gasPriceEditor.js create mode 100644 js/src/ui/GasPriceEditor/index.js create mode 100644 js/src/ui/GasPriceEditor/store.js diff --git a/js/src/modals/Transfer/Extras/extras.js b/js/src/modals/Transfer/Extras/extras.js index f7ff4612b..6d2bfc821 100644 --- a/js/src/modals/Transfer/Extras/extras.js +++ b/js/src/modals/Transfer/Extras/extras.js @@ -16,96 +16,35 @@ import React, { Component, PropTypes } from 'react'; -import Form, { Input } from '~/ui/Form'; -import GasPriceSelector from '../GasPriceSelector'; - -import styles from '../transfer.css'; +import { GasPriceEditor, Form, Input } from '~/ui'; export default class Extras extends Component { static propTypes = { isEth: PropTypes.bool, data: PropTypes.string, dataError: PropTypes.string, - gas: PropTypes.string, - gasEst: PropTypes.string, - gasError: PropTypes.string, - gasPrice: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.object - ]), - gasPriceDefault: PropTypes.string, - gasPriceError: PropTypes.string, - gasPriceHistogram: PropTypes.object, total: PropTypes.string, totalError: PropTypes.string, - onChange: PropTypes.func.isRequired + onChange: PropTypes.func.isRequired, + gasStore: PropTypes.object.isRequired } render () { - const { gas, gasPrice, gasError, gasEst, gasPriceDefault, gasPriceError, gasPriceHistogram, total, totalError } = this.props; - - const gasLabel = `gas amount (estimated: ${gasEst})`; - const priceLabel = `gas price (current: ${gasPriceDefault})`; + const { gasStore, onChange, total, totalError } = this.props; return ( <Form> - { this.renderData() } - - <div className={ styles.columns }> - <div style={ { flex: 65 } }> - <GasPriceSelector - gasPriceHistogram={ gasPriceHistogram } - gasPrice={ gasPrice } - onChange={ this.onEditGasPrice } - /> - </div> - - <div - className={ styles.row } - style={ { - flex: 35, paddingLeft: '1rem', - justifyContent: 'space-around', - paddingBottom: 12 - } } - > - <div className={ styles.row }> - <Input - label={ gasLabel } - hint='the amount of gas to use for the transaction' - error={ gasError } - value={ gas } - onChange={ this.onEditGas } /> - - <Input - label={ priceLabel } - hint='the price of gas to use for the transaction' - error={ gasPriceError } - value={ (gasPrice || '').toString() } - onChange={ this.onEditGasPrice } /> - </div> - - <div className={ styles.row }> - <Input - disabled - label='total transaction amount' - hint='the total amount of the transaction' - error={ totalError } - value={ `${total} ETH` } /> - </div> - </div> - </div> - - <div> - <p className={ styles.gasPriceDesc }> - You can choose the gas price based on the - distribution of recent included transactions' gas prices. - The lower the gas price is, the cheaper the transaction will - be. The higher the gas price is, the faster it should - get mined by the network. - </p> - </div> - + <GasPriceEditor + store={ gasStore } + onChange={ onChange }> + <Input + disabled + label='total transaction amount' + hint='the total amount of the transaction' + error={ totalError } + value={ `${total} ETH` } /> + </GasPriceEditor> </Form> ); } @@ -129,14 +68,6 @@ export default class Extras extends Component { ); } - onEditGas = (event) => { - this.props.onChange('gas', event.target.value); - } - - onEditGasPrice = (event, value) => { - this.props.onChange('gasPrice', value); - } - onEditData = (event) => { this.props.onChange('data', event.target.value); } diff --git a/js/src/modals/Transfer/store.js b/js/src/modals/Transfer/store.js index 8f8baf55f..a43057c86 100644 --- a/js/src/modals/Transfer/store.js +++ b/js/src/modals/Transfer/store.js @@ -23,7 +23,8 @@ import { bytesToHex } from '~/api/util/format'; import Contract from '~/api/contract'; import ERRORS from './errors'; import { ERROR_CODES } from '~/api/transport/error'; -import { DEFAULT_GAS, DEFAULT_GASPRICE, MAX_GAS_ESTIMATION } from '~/util/constants'; +import { DEFAULT_GAS, MAX_GAS_ESTIMATION } from '~/util/constants'; +import GasPriceStore from '~/ui/GasPriceEditor/store'; const TITLES = { transfer: 'transfer details', @@ -48,14 +49,6 @@ export default class TransferStore { @observable data = ''; @observable dataError = null; - @observable gas = DEFAULT_GAS; - @observable gasError = null; - - @observable gasEst = '0'; - @observable gasLimitError = null; - @observable gasPrice = DEFAULT_GASPRICE; - @observable gasPriceError = null; - @observable recipient = ''; @observable recipientError = ERRORS.requireRecipient; @@ -68,11 +61,8 @@ export default class TransferStore { @observable value = '0.0'; @observable valueError = null; - gasPriceHistogram = {}; - account = null; balance = null; - gasLimit = null; onClose = null; senders = null; @@ -81,6 +71,8 @@ export default class TransferStore { isWallet = false; wallet = null; + gasStore = null; + @computed get steps () { const steps = [].concat(this.extras ? STAGES_EXTRA : STAGES_BASIC); @@ -93,7 +85,7 @@ export default class TransferStore { @computed get isValid () { const detailsValid = !this.recipientError && !this.valueError && !this.totalError && !this.senderError; - const extrasValid = !this.gasError && !this.gasPriceError && !this.totalError; + const extrasValid = !this.gasStore.errorGas && !this.gasStore.errorPrice && !this.totalError; const verifyValid = !this.passwordError; switch (this.stage) { @@ -118,11 +110,12 @@ export default class TransferStore { const { account, balance, gasLimit, senders, onClose, newError, sendersBalances } = props; this.account = account; this.balance = balance; - this.gasLimit = gasLimit; this.onClose = onClose; this.isWallet = account && account.wallet; this.newError = newError; + this.gasStore = new GasPriceStore(api, gasLimit); + if (this.isWallet) { this.wallet = props.wallet; this.walletContract = new Contract(this.api, walletAbi); @@ -179,26 +172,6 @@ export default class TransferStore { } } - @action getDefaults = () => { - Promise - .all([ - this.api.parity.gasPriceHistogram(), - this.api.eth.gasPrice() - ]) - .then(([gasPriceHistogram, gasPrice]) => { - transaction(() => { - this.gasPrice = gasPrice.toString(); - this.gasPriceDefault = gasPrice.toFormat(); - this.gasPriceHistogram = gasPriceHistogram; - - this.recalculate(); - }); - }) - .catch((error) => { - console.warn('getDefaults', error); - }); - } - @action onSend = () => { this.onNext(); this.sending = true; @@ -281,25 +254,11 @@ export default class TransferStore { } @action _onUpdateGas = (gas) => { - const gasError = this._validatePositiveNumber(gas); - - transaction(() => { - this.gas = gas; - this.gasError = gasError; - - this.recalculate(); - }); + this.recalculate(); } @action _onUpdateGasPrice = (gasPrice) => { - const gasPriceError = this._validatePositiveNumber(gasPrice); - - transaction(() => { - this.gasPrice = gasPrice; - this.gasPriceError = gasPriceError; - - this.recalculate(); - }); + this.recalculate(); } @action _onUpdateRecipient = (recipient) => { @@ -362,7 +321,7 @@ export default class TransferStore { @action recalculateGas = () => { if (!this.isValid) { - this.gas = 0; + this.gasStore.setGas('0'); return this.recalculate(); } @@ -370,28 +329,20 @@ export default class TransferStore { .estimateGas() .then((gasEst) => { let gas = gasEst; - let gasLimitError = null; if (gas.gt(DEFAULT_GAS)) { gas = gas.mul(1.2); } - if (gas.gte(MAX_GAS_ESTIMATION)) { - gasLimitError = ERRORS.gasException; - } else if (gas.gt(this.gasLimit)) { - gasLimitError = ERRORS.gasBlockLimit; - } - transaction(() => { - this.gas = gas.toFixed(0); - this.gasEst = gasEst.toFormat(); - this.gasLimitError = gasLimitError; + this.gasStore.setEstimated(gasEst.toFixed(0)); + this.gasStore.setGas(gas.toFixed(0)); this.recalculate(); }); }) .catch((error) => { - console.error('etimateGas', error); + console.warn('etimateGas', error); this.recalculate(); }); } @@ -411,9 +362,9 @@ export default class TransferStore { return; } - const { gas, gasPrice, tag, valueAll, isEth, isWallet } = this; + const { tag, valueAll, isEth, isWallet } = this; - const gasTotal = new BigNumber(gasPrice || 0).mul(new BigNumber(gas || 0)); + const gasTotal = new BigNumber(this.gasStore.price || 0).mul(new BigNumber(this.gasStore.gas || 0)); const availableEth = new BigNumber(balance.tokens[0].value); @@ -453,7 +404,7 @@ export default class TransferStore { } transaction(() => { - this.total = this.api.util.fromWei(totalEth).toString(); + this.total = this.api.util.fromWei(totalEth).toFixed(); this.totalError = totalError; this.value = value; this.valueError = valueError; @@ -522,8 +473,8 @@ export default class TransferStore { }; if (!gas) { - options.gas = this.gas; - options.gasPrice = this.gasPrice; + options.gas = this.gasStore.gas; + options.gasPrice = this.gasStore.price; } else { options.gas = MAX_GAS_ESTIMATION; } diff --git a/js/src/modals/Transfer/transfer.css b/js/src/modals/Transfer/transfer.css index 3bd17ba96..28612f299 100644 --- a/js/src/modals/Transfer/transfer.css +++ b/js/src/modals/Transfer/transfer.css @@ -144,15 +144,6 @@ font-size: 1.2rem; } -.chart { - position: absolute; - width: 100%; -} - -.gasPriceDesc { - font-size: 0.9em; -} - .warning { border-radius: 0.5em; background: #f80; diff --git a/js/src/modals/Transfer/transfer.js b/js/src/modals/Transfer/transfer.js index e1e2a0951..00e84adaf 100644 --- a/js/src/modals/Transfer/transfer.js +++ b/js/src/modals/Transfer/transfer.js @@ -56,10 +56,6 @@ class Transfer extends Component { store = new TransferStore(this.context.api, this.props); - componentDidMount () { - this.store.getDefaults(); - } - render () { const { stage, extras, steps } = this.store; @@ -186,27 +182,20 @@ class Transfer extends Component { } renderExtrasPage () { - if (!this.store.gasPriceHistogram) { + if (!this.store.gasStore.histogram) { return null; } - const { isEth, data, dataError, gas, gasEst, gasError, gasPrice } = this.store; - const { gasPriceDefault, gasPriceError, gasPriceHistogram, total, totalError } = this.store; + const { isEth, data, dataError, total, totalError } = this.store; return ( <Extras isEth={ isEth } data={ data } dataError={ dataError } - gas={ gas } - gasEst={ gasEst } - gasError={ gasError } - gasPrice={ gasPrice } - gasPriceDefault={ gasPriceDefault } - gasPriceError={ gasPriceError } - gasPriceHistogram={ gasPriceHistogram } total={ total } totalError={ totalError } + gasStore={ this.store.gasStore } onChange={ this.store.onUpdateDetails } /> ); } @@ -263,15 +252,15 @@ class Transfer extends Component { } renderWarning () { - const { gasLimitError } = this.store; + const { errorEstimated } = this.store.gasStore; - if (!gasLimitError) { + if (!errorEstimated) { return null; } return ( <div className={ styles.warning }> - { gasLimitError } + { errorEstimated } </div> ); } diff --git a/js/src/modals/Transfer/GasPriceSelector/gasPriceSelector.css b/js/src/ui/GasPriceEditor/GasPriceSelector/gasPriceSelector.css similarity index 85% rename from js/src/modals/Transfer/GasPriceSelector/gasPriceSelector.css rename to js/src/ui/GasPriceEditor/GasPriceSelector/gasPriceSelector.css index 445174c59..247211c50 100644 --- a/js/src/modals/Transfer/GasPriceSelector/gasPriceSelector.css +++ b/js/src/ui/GasPriceEditor/GasPriceSelector/gasPriceSelector.css @@ -15,3 +15,13 @@ /* along with Parity. If not, see <http://www.gnu.org/licenses/>. */ +.chart { + position: absolute; + width: 100%; +} + +.columns { + display: flex; + flex-wrap: wrap; + position: relative; +} diff --git a/js/src/modals/Transfer/GasPriceSelector/gasPriceSelector.js b/js/src/ui/GasPriceEditor/GasPriceSelector/gasPriceSelector.js similarity index 77% rename from js/src/modals/Transfer/GasPriceSelector/gasPriceSelector.js rename to js/src/ui/GasPriceEditor/GasPriceSelector/gasPriceSelector.js index adf644c4e..893a50188 100644 --- a/js/src/modals/Transfer/GasPriceSelector/gasPriceSelector.js +++ b/js/src/ui/GasPriceEditor/GasPriceSelector/gasPriceSelector.js @@ -29,10 +29,7 @@ import { import Slider from 'material-ui/Slider'; import BigNumber from 'bignumber.js'; -import componentStyles from './gasPriceSelector.css'; -import mainStyles from '../transfer.css'; - -const styles = Object.assign({}, mainStyles, componentStyles); +import styles from './gasPriceSelector.css'; const COLORS = { default: 'rgba(255, 99, 132, 0.2)', @@ -194,10 +191,7 @@ class CustomizedShape extends Component { class CustomTooltip extends Component { static propTypes = { - gasPriceHistogram: PropTypes.shape({ - bucketBounds: PropTypes.array.isRequired, - counts: PropTypes.array.isRequired - }).isRequired, + gasPriceHistogram: PropTypes.object.isRequired, type: PropTypes.string, payload: PropTypes.array, label: PropTypes.number, @@ -231,12 +225,16 @@ class CustomTooltip extends Component { } } +const TOOL_STYLE = { + color: 'rgba(255,255,255,0.5)', + backgroundColor: 'rgba(0, 0, 0, 0.75)', + padding: '0 0.5em', + fontSize: '0.75em' +}; + export default class GasPriceSelector extends Component { static propTypes = { - gasPriceHistogram: PropTypes.shape({ - bucketBounds: PropTypes.array.isRequired, - counts: PropTypes.array.isRequired - }).isRequired, + gasPriceHistogram: PropTypes.object.isRequired, onChange: PropTypes.func.isRequired, gasPrice: PropTypes.oneOfType([ @@ -287,21 +285,23 @@ export default class GasPriceSelector extends Component { renderSlider () { const { sliderValue } = this.state; - return (<div className={ styles.columns }> - <Slider - min={ 0 } - max={ 1 } - value={ sliderValue } - onChange={ this.onEditGasPriceSlider } - style={ { - flex: 1, - padding: '0 0.3em' - } } - sliderStyle={ { - marginBottom: 12 - } } - /> - </div>); + return ( + <div className={ styles.columns }> + <Slider + min={ 0 } + max={ 1 } + value={ sliderValue } + onChange={ this.onEditGasPriceSlider } + style={ { + flex: 1, + padding: '0 0.3em' + } } + sliderStyle={ { + marginBottom: 12 + } } + /> + </div> + ); } renderChart () { @@ -316,85 +316,83 @@ export default class GasPriceSelector extends Component { const countIndex = Math.max(0, Math.min(selectedIndex, gasPriceHistogram.counts.length - 1)); const selectedCount = countModifier(gasPriceHistogram.counts[countIndex]); - return (<div className={ styles.columns }> - <div style={ { flex: 1, height } }> - <div className={ styles.chart }> - <ResponsiveContainer - height={ height } - > - <ScatterChart - margin={ { top: 0, right: 0, left: 0, bottom: 0 } } + return ( + <div className={ styles.columns }> + <div style={ { flex: 1, height } }> + <div className={ styles.chart }> + <ResponsiveContainer + height={ height } > - <Scatter - data={ [ - { x: sliderValue, y: 0 }, - { x: sliderValue, y: selectedCount }, - { x: sliderValue, y: chartData.yDomain[1] } - ] } - shape={ <CustomizedShape showValue={ selectedCount } /> } - line - isAnimationActive={ false } - /> + <ScatterChart + margin={ { top: 0, right: 0, left: 0, bottom: 0 } } + > + <Scatter + data={ [ + { x: sliderValue, y: 0 }, + { x: sliderValue, y: selectedCount }, + { x: sliderValue, y: chartData.yDomain[1] } + ] } + shape={ <CustomizedShape showValue={ selectedCount } /> } + line + isAnimationActive={ false } + /> - <XAxis - hide - height={ 0 } - dataKey='x' - domain={ [0, 1] } - /> - <YAxis - hide - width={ 0 } - dataKey='y' - domain={ chartData.yDomain } - /> - </ScatterChart> - </ResponsiveContainer> - </div> + <XAxis + hide + height={ 0 } + dataKey='x' + domain={ [0, 1] } + /> + <YAxis + hide + width={ 0 } + dataKey='y' + domain={ chartData.yDomain } + /> + </ScatterChart> + </ResponsiveContainer> + </div> - <div className={ styles.chart }> - <ResponsiveContainer - height={ height } - > - <BarChart - data={ chartData.values } - margin={ { top: 0, right: 0, left: 0, bottom: 0 } } - barCategoryGap={ 1 } - ref='barChart' + <div className={ styles.chart }> + <ResponsiveContainer + height={ height } > - <Bar - dataKey='value' - stroke={ COLORS.line } - onClick={ this.onClickGasPrice } - shape={ <CustomBar selected={ selectedIndex } onClick={ this.onClickGasPrice } /> } - /> + <BarChart + data={ chartData.values } + margin={ { top: 0, right: 0, left: 0, bottom: 0 } } + barCategoryGap={ 1 } + ref='barChart' + > + <Bar + dataKey='value' + stroke={ COLORS.line } + onClick={ this.onClickGasPrice } + shape={ <CustomBar selected={ selectedIndex } onClick={ this.onClickGasPrice } /> } + /> - <Tooltip - wrapperStyle={ { - backgroundColor: 'rgba(0, 0, 0, 0.75)', - padding: '0 0.5em', - fontSize: '0.9em' - } } - cursor={ this.renderCustomCursor() } - content={ <CustomTooltip gasPriceHistogram={ gasPriceHistogram } /> } - /> + <Tooltip + wrapperStyle={ TOOL_STYLE } + cursor={ this.renderCustomCursor() } + content={ <CustomTooltip gasPriceHistogram={ gasPriceHistogram } /> } + /> - <XAxis - hide - dataKey='index' - type='category' - domain={ chartData.xDomain } - /> - <YAxis - hide - type='number' - domain={ chartData.yDomain } - /> - </BarChart> - </ResponsiveContainer> + <XAxis + hide + dataKey='index' + type='category' + domain={ chartData.xDomain } + /> + <YAxis + hide + type='number' + domain={ chartData.yDomain } + /> + </BarChart> + </ResponsiveContainer> + </div> </div> </div> - </div>); + ); } renderCustomCursor = () => { diff --git a/js/src/modals/Transfer/GasPriceSelector/index.js b/js/src/ui/GasPriceEditor/GasPriceSelector/index.js similarity index 100% rename from js/src/modals/Transfer/GasPriceSelector/index.js rename to js/src/ui/GasPriceEditor/GasPriceSelector/index.js diff --git a/js/src/ui/GasPriceEditor/gasPriceEditor.css b/js/src/ui/GasPriceEditor/gasPriceEditor.css new file mode 100644 index 000000000..cf1fff81c --- /dev/null +++ b/js/src/ui/GasPriceEditor/gasPriceEditor.css @@ -0,0 +1,49 @@ +/* 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/>. +*/ + +.columns { + display: flex; + flex-wrap: wrap; + position: relative; +} + +.graphColumn { + flex: 65; +} + +.editColumn { + flex: 35; + padding-left: 1em; + justify-ontent: space-around; + padding-bottom: 12; + display: flex; + flex-wrap: wrap; + position: relative; + flex-direction: column; +} + +.gasPriceDesc { + font-size: 0.75em; + opacity: 0.5; +} + +.row { + display: flex; + flex-wrap: wrap; + position: relative; + flex-direction: column; +} diff --git a/js/src/ui/GasPriceEditor/gasPriceEditor.js b/js/src/ui/GasPriceEditor/gasPriceEditor.js new file mode 100644 index 000000000..c6759ddf1 --- /dev/null +++ b/js/src/ui/GasPriceEditor/gasPriceEditor.js @@ -0,0 +1,98 @@ +// 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, { Component, PropTypes } from 'react'; +import { observer } from 'mobx-react'; + +import Input from '../Form/Input'; +import GasPriceSelector from './GasPriceSelector'; +import Store from './store'; + +import styles from './gasPriceEditor.css'; + +@observer +export default class GasPriceEditor extends Component { + static propTypes = { + children: PropTypes.node, + store: PropTypes.object.isRequired, + onChange: PropTypes.func + } + + static Store = Store; + + render () { + const { children, store } = this.props; + const { estimated, priceDefault, price, gas, histogram, errorGas, errorPrice } = store; + + const gasLabel = `gas (estimated: ${new BigNumber(estimated).toFormat()})`; + const priceLabel = `price (current: ${new BigNumber(priceDefault).toFormat()})`; + + return ( + <div className={ styles.columns }> + <div className={ styles.graphColumn }> + <GasPriceSelector + gasPriceHistogram={ histogram } + gasPrice={ price } + onChange={ this.onEditGasPrice } /> + <div className={ styles.gasPriceDesc }> + You can choose the gas price based on the + distribution of recent included transaction gas prices. + The lower the gas price is, the cheaper the transaction will + be. The higher the gas price is, the faster it should + get mined by the network. + </div> + </div> + + <div className={ styles.editColumn }> + <div className={ styles.row }> + <Input + label={ gasLabel } + hint='the amount of gas to use for the transaction' + error={ errorGas } + value={ gas } + onChange={ this.onEditGas } /> + + <Input + label={ priceLabel } + hint='the price of gas to use for the transaction' + error={ errorPrice } + value={ price } + onChange={ this.onEditGasPrice } /> + </div> + + <div className={ styles.row }> + { children } + </div> + </div> + </div> + ); + } + + onEditGas = (event, gas) => { + const { store, onChange } = this.props; + + store.setGas(gas); + onChange && onChange('gas', gas); + } + + onEditGasPrice = (event, price) => { + const { store, onChange } = this.props; + + store.setPrice(price); + onChange && onChange('gasPrice', price); + } +} diff --git a/js/src/ui/GasPriceEditor/index.js b/js/src/ui/GasPriceEditor/index.js new file mode 100644 index 000000000..956f6ffd2 --- /dev/null +++ b/js/src/ui/GasPriceEditor/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 './gasPriceEditor'; diff --git a/js/src/ui/GasPriceEditor/store.js b/js/src/ui/GasPriceEditor/store.js new file mode 100644 index 000000000..3f3e50430 --- /dev/null +++ b/js/src/ui/GasPriceEditor/store.js @@ -0,0 +1,105 @@ +// 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 { action, observable, transaction } from 'mobx'; + +import { ERRORS, validatePositiveNumber } from '~/util/validation'; +import { DEFAULT_GAS, DEFAULT_GASPRICE, MAX_GAS_ESTIMATION } from '~/util/constants'; + +export default class GasPriceEditor { + @observable errorEstimated = null; + @observable errorGas = null; + @observable errorPrice = null; + @observable estimated = DEFAULT_GAS; + @observable histogram = null; + @observable price = DEFAULT_GASPRICE; + @observable priceDefault = DEFAULT_GASPRICE; + @observable gas = DEFAULT_GAS; + @observable gasLimit = 0; + + constructor (api, gasLimit, loadDefaults = true) { + this._api = api; + this.gasLimit = gasLimit; + + if (loadDefaults) { + this.loadDefaults(); + } + } + + @action setEstimated = (estimated) => { + transaction(() => { + const bn = new BigNumber(estimated); + + this.estimated = estimated; + + if (bn.gte(MAX_GAS_ESTIMATION)) { + this.errorEstimated = ERRORS.gasException; + } else if (bn.gte(this.gasLimit)) { + this.errorEstimated = ERRORS.gasBlockLimit; + } else { + this.errorEstimated = null; + } + }); + } + + @action setHistogram = (gasHistogram) => { + this.histogram = gasHistogram; + } + + @action setPrice = (price) => { + transaction(() => { + this.errorPrice = validatePositiveNumber(price).numberError; + this.price = price; + }); + } + + @action setGas = (gas) => { + transaction(() => { + const { numberError } = validatePositiveNumber(gas); + const bn = new BigNumber(gas); + + this.gas = gas; + + if (numberError) { + this.errorGas = numberError; + } else if (bn.gte(this.gasLimit)) { + this.errorGas = ERRORS.gasBlockLimit; + } else { + this.errorGas = null; + } + }); + } + + @action loadDefaults () { + Promise + .all([ + this._api.parity.gasPriceHistogram(), + this._api.eth.gasPrice() + ]) + .then(([gasPriceHistogram, gasPrice]) => { + transaction(() => { + this.setPrice(gasPrice.toFixed(0)); + this.setHistogram(gasPriceHistogram); + + this.priceDefault = gasPrice.toFixed(); + }); + }) + .catch((error) => { + console.warn('getDefaults', error); + }); + } +} diff --git a/js/src/ui/index.js b/js/src/ui/index.js index 6c763f0f3..c5a965458 100644 --- a/js/src/ui/index.js +++ b/js/src/ui/index.js @@ -31,6 +31,7 @@ import CopyToClipboard from './CopyToClipboard'; import Editor from './Editor'; import Errors from './Errors'; import Form, { AddressSelect, FormWrap, TypedInput, Input, InputAddress, InputAddressSelect, InputChip, InputInline, Select, RadioButtons } from './Form'; +import GasPriceEditor from './GasPriceEditor'; import IdentityIcon from './IdentityIcon'; import IdentityName from './IdentityName'; import Loading from './Loading'; @@ -67,7 +68,7 @@ export { Errors, Form, FormWrap, - TypedInput, + GasPriceEditor, Input, InputAddress, InputAddressSelect, @@ -91,5 +92,6 @@ export { Tooltip, Tooltips, TxHash, - TxList + TxList, + TypedInput }; diff --git a/js/src/util/validation.js b/js/src/util/validation.js index 2b64141db..3d312a2f4 100644 --- a/js/src/util/validation.js +++ b/js/src/util/validation.js @@ -20,6 +20,7 @@ import util from '~/api/util'; export const ERRORS = { invalidAddress: 'address is an invalid network address', + invalidAmount: 'the supplied amount should be a valid positive number', duplicateAddress: 'the address is already in your address book', invalidChecksum: 'address has failed the checksum formatting', invalidName: 'name should not be blank and longer than 2', @@ -27,7 +28,9 @@ export const ERRORS = { invalidCode: 'code should be the compiled hex string', invalidNumber: 'invalid number format', negativeNumber: 'input number should be positive', - decimalNumber: 'input number should not contain decimals' + decimalNumber: 'input number should not contain decimals', + gasException: 'the transaction will throw an exception with the current values', + gasBlockLimit: 'the transaction execution will exceed the block gas limit' }; export function validateAbi (abi, api) { @@ -133,6 +136,25 @@ export function validateName (name) { }; } +export function validatePositiveNumber (number) { + let numberError = null; + + try { + const v = new BigNumber(number); + + if (v.lt(0)) { + numberError = ERRORS.invalidAmount; + } + } catch (e) { + numberError = ERRORS.invalidAmount; + } + + return { + number, + numberError + }; +} + export function validateUint (value) { let valueError = null; From 1213ada59ce2d48b1aed4b91c67d1dd11f5267aa Mon Sep 17 00:00:00 2001 From: GitLab Build Bot <jaco+gitlab@ethcore.io> Date: Fri, 9 Dec 2016 12:52:50 +0000 Subject: [PATCH 40/63] [ci skip] js-precompiled 20161209-125036 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5795de427..d0664d801 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1271,7 +1271,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#1bf7160f6c8f25353d790dbd0935560d3d395727" +source = "git+https://github.com/ethcore/js-precompiled.git#8e8e515f958d2d4a5abec07253a51a052f2b744d" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index 4be14d531..ab501f6a7 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.102", + "version": "0.2.103", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team <admin@parity.io>", From cc8c2ea58d64aab71c28c695390fda384ad4cd13 Mon Sep 17 00:00:00 2001 From: arkpar <arkady.paronyan@gmail.com> Date: Wed, 7 Dec 2016 23:13:53 +0100 Subject: [PATCH 41/63] Reject existing tx --- ethcore/src/client/client.rs | 4 ++++ ethcore/src/client/test_client.rs | 4 ++++ ethcore/src/client/traits.rs | 3 +++ ethcore/src/miner/miner.rs | 15 ++++++++++++--- 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 21c5a2366..dcdadd3d1 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -1060,6 +1060,10 @@ impl BlockChainClient for Client { self.transaction_address(id).and_then(|address| self.chain.read().transaction(&address)) } + fn transaction_block(&self, id: TransactionID) -> Option<H256> { + self.transaction_address(id).map(|addr| addr.block_hash) + } + fn uncle(&self, id: UncleID) -> Option<Bytes> { let index = id.position; self.block_body(id.block).and_then(|body| BodyView::new(&body).uncle_rlp_at(index)) diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index dd00db7ec..cce102622 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -432,6 +432,10 @@ impl BlockChainClient for TestBlockChainClient { None // Simple default. } + fn transaction_block(&self, _id: TransactionID) -> Option<H256> { + None // Simple default. + } + fn uncle(&self, _id: UncleID) -> Option<Bytes> { None // Simple default. } diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index 7bf17279c..e23a564d4 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -129,6 +129,9 @@ pub trait BlockChainClient : Sync + Send { /// Get transaction with given hash. fn transaction(&self, id: TransactionID) -> Option<LocalizedTransaction>; + /// Get the hash of block that contains the transaction, if any. + fn transaction_block(&self, id: TransactionID) -> Option<H256>; + /// Get uncle with given id. fn uncle(&self, id: UncleID) -> Option<Bytes>; diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 8d1f55567..b171856b9 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -23,7 +23,7 @@ use account_provider::{AccountProvider, Error as AccountError}; use views::{BlockView, HeaderView}; use header::Header; use state::{State, CleanupMode}; -use client::{MiningBlockChainClient, Executive, Executed, EnvInfo, TransactOptions, BlockID, CallAnalytics}; +use client::{MiningBlockChainClient, Executive, Executed, EnvInfo, TransactOptions, BlockID, CallAnalytics, TransactionID}; use client::TransactionImportResult; use executive::contract_address; use block::{ClosedBlock, SealedBlock, IsBlock, Block}; @@ -357,6 +357,8 @@ impl Miner { let block_number = open_block.block().fields().header.number(); // TODO Push new uncles too. + let mut tx_count: usize = 0; + let tx_total = transactions.len(); for tx in transactions { let hash = tx.hash(); let start = Instant::now(); @@ -378,7 +380,7 @@ impl Miner { }, _ => {}, } - + trace!(target: "miner", "Adding tx {:?} took {:?}", hash, took); match result { Err(Error::Execution(ExecutionError::BlockGasLimitReached { gas_limit, gas_used, gas })) => { debug!(target: "miner", "Skipping adding transaction to block because of gas limit: {:?} (limit: {:?}, used: {:?}, gas: {:?})", hash, gas_limit, gas_used, gas); @@ -407,9 +409,12 @@ impl Miner { "Error adding transaction to block: number={}. transaction_hash={:?}, Error: {:?}", block_number, hash, e); }, - _ => {} // imported ok + _ => { + tx_count += 1; + } // imported ok } } + trace!(target: "miner", "Pushed {}/{} transactions", tx_count, tx_total); let block = open_block.close(); @@ -580,6 +585,10 @@ impl Miner { let best_block_header: Header = ::rlp::decode(&chain.best_block_header()); transactions.into_iter() .map(|tx| { + if chain.transaction_block(TransactionID::Hash(tx.hash())).is_some() { + debug!(target: "miner", "Rejected tx {:?}: already in the blockchain", tx.hash()); + return Err(Error::Transaction(TransactionError::AlreadyImported)); + } match self.engine.verify_transaction_basic(&tx, &best_block_header) { Err(e) => { debug!(target: "miner", "Rejected tx {:?} with invalid signature: {:?}", tx.hash(), e); 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 42/63] 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 43/63] 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 d2494d14255cdd431471e84d46fbccc28e10f057 Mon Sep 17 00:00:00 2001 From: Jaco Greeff <jaco@ethcore.io> Date: Fri, 9 Dec 2016 15:43:24 +0100 Subject: [PATCH 44/63] GasPrice selection for contract execution --- .../DetailsStep/detailsStep.js | 35 +++- .../ExecuteContract/executeContract.css | 12 ++ .../modals/ExecuteContract/executeContract.js | 154 ++++++++++++------ js/src/modals/Transfer/Extras/extras.js | 11 +- js/src/modals/Transfer/store.js | 2 + js/src/ui/GasPriceEditor/gasPriceEditor.js | 18 +- js/src/ui/GasPriceEditor/store.js | 20 ++- 7 files changed, 184 insertions(+), 68 deletions(-) diff --git a/js/src/modals/ExecuteContract/DetailsStep/detailsStep.js b/js/src/modals/ExecuteContract/DetailsStep/detailsStep.js index b4488729a..3ffb929a9 100644 --- a/js/src/modals/ExecuteContract/DetailsStep/detailsStep.js +++ b/js/src/modals/ExecuteContract/DetailsStep/detailsStep.js @@ -15,13 +15,19 @@ // along with Parity. If not, see <http://www.gnu.org/licenses/>. import React, { Component, PropTypes } from 'react'; -import { MenuItem } from 'material-ui'; +import { Checkbox, MenuItem } from 'material-ui'; import { AddressSelect, Form, Input, Select, TypedInput } from '~/ui'; import { parseAbiType } from '~/util/abi'; import styles from '../executeContract.css'; +const CHECK_STYLE = { + position: 'absolute', + top: '38px', + left: '1em' +}; + export default class DetailsStep extends Component { static propTypes = { accounts: PropTypes.object.isRequired, @@ -31,10 +37,12 @@ export default class DetailsStep extends Component { onAmountChange: PropTypes.func.isRequired, fromAddress: PropTypes.string, fromAddressError: PropTypes.string, + gasEdit: PropTypes.bool, onFromAddressChange: PropTypes.func.isRequired, func: PropTypes.object, funcError: PropTypes.string, onFuncChange: PropTypes.func, + onGasEditClick: PropTypes.func, values: PropTypes.array.isRequired, valuesError: PropTypes.array.isRequired, warning: PropTypes.string, @@ -42,7 +50,7 @@ export default class DetailsStep extends Component { } render () { - const { accounts, amount, amountError, fromAddress, fromAddressError, onFromAddressChange, onAmountChange } = this.props; + const { accounts, amount, amountError, fromAddress, fromAddressError, gasEdit, onGasEditClick, onFromAddressChange, onAmountChange } = this.props; return ( <Form> @@ -56,12 +64,23 @@ export default class DetailsStep extends Component { onChange={ onFromAddressChange } /> { this.renderFunctionSelect() } { this.renderParameters() } - <Input - label='transaction value (in ETH)' - hint='the amount to send to with the transaction' - value={ amount } - error={ amountError } - onSubmit={ onAmountChange } /> + <div className={ styles.columns }> + <div> + <Input + label='transaction value (in ETH)' + hint='the amount to send to with the transaction' + value={ amount } + error={ amountError } + onSubmit={ onAmountChange } /> + </div> + <div> + <Checkbox + checked={ gasEdit } + label='edit gas price or value' + onCheck={ onGasEditClick } + style={ CHECK_STYLE } /> + </div> + </div> </Form> ); } diff --git a/js/src/modals/ExecuteContract/executeContract.css b/js/src/modals/ExecuteContract/executeContract.css index a83b373ee..6b7132912 100644 --- a/js/src/modals/ExecuteContract/executeContract.css +++ b/js/src/modals/ExecuteContract/executeContract.css @@ -42,3 +42,15 @@ padding: 0.75em; text-align: center; } + +.columns { + display: flex; + flex-wrap: wrap; + position: relative; + + &>div { + flex: 0 1 50%; + width: 50%; + position: relative; + } +} diff --git a/js/src/modals/ExecuteContract/executeContract.js b/js/src/modals/ExecuteContract/executeContract.js index 2db5e2b04..3f7940ca3 100644 --- a/js/src/modals/ExecuteContract/executeContract.js +++ b/js/src/modals/ExecuteContract/executeContract.js @@ -17,19 +17,36 @@ import React, { Component, PropTypes } from 'react'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; +import { observer } from 'mobx-react'; import ActionDoneAll from 'material-ui/svg-icons/action/done-all'; import ContentClear from 'material-ui/svg-icons/content/clear'; +import NavigationArrowBack from 'material-ui/svg-icons/navigation/arrow-back'; +import NavigationArrowForward from 'material-ui/svg-icons/navigation/arrow-forward'; -import { BusyStep, CompletedStep, Button, IdentityIcon, Modal, TxHash } from '~/ui'; +import { BusyStep, Button, CompletedStep, GasPriceEditor, IdentityIcon, Modal, TxHash } from '~/ui'; import { MAX_GAS_ESTIMATION } from '~/util/constants'; import { validateAddress, validateUint } from '~/util/validation'; import { parseAbiType } from '~/util/abi'; import DetailsStep from './DetailsStep'; -import ERRORS from '../Transfer/errors'; import { ERROR_CODES } from '~/api/transport/error'; +const STEP_DETAILS = 0; +const STEP_BUSY_OR_GAS = 1; +const STEP_BUSY = 2; + +const TITLES = { + transfer: 'function details', + sending: 'sending', + complete: 'complete', + gas: 'gas selection', + rejected: 'rejected' +}; +const STAGES_BASIC = [TITLES.transfer, TITLES.sending, TITLES.complete]; +const STAGES_GAS = [TITLES.transfer, TITLES.gas, TITLES.sending, TITLES.complete]; + +@observer class ExecuteContract extends Component { static contextTypes = { api: PropTypes.object.isRequired, @@ -46,21 +63,22 @@ class ExecuteContract extends Component { onFromAddressChange: PropTypes.func.isRequired } + gasStore = new GasPriceEditor.Store(this.context.api, this.props.gasLimit); + state = { amount: '0', amountError: null, + busyState: null, fromAddressError: null, func: null, funcError: null, - gas: null, - gasLimitError: null, + gasEdit: false, + rejected: false, + step: STEP_DETAILS, + sending: false, values: [], valuesError: [], - step: 0, - sending: false, - busyState: null, - txhash: null, - rejected: false + txhash: null } componentDidMount () { @@ -79,15 +97,21 @@ class ExecuteContract extends Component { } render () { - const { sending } = this.state; + const { sending, step, gasEdit, rejected } = this.state; + const steps = gasEdit ? STAGES_GAS : STAGES_BASIC; + + if (rejected) { + steps[steps.length - 1] = TITLES.rejected; + } return ( <Modal actions={ this.renderDialogActions() } - title='execute function' busy={ sending } - waiting={ [1] } - visible> + current={ step } + steps={ steps } + visible + waiting={ gasEdit ? [STEP_BUSY] : [STEP_BUSY_OR_GAS] }> { this.renderStep() } </Modal> ); @@ -95,7 +119,7 @@ class ExecuteContract extends Component { renderDialogActions () { const { onClose, fromAddress } = this.props; - const { sending, step, fromAddressError, valuesError } = this.state; + const { gasEdit, sending, step, fromAddressError, valuesError } = this.state; const hasError = fromAddressError || valuesError.find((error) => error); const cancelBtn = ( @@ -105,21 +129,44 @@ class ExecuteContract extends Component { icon={ <ContentClear /> } onClick={ onClose } /> ); + const postBtn = ( + <Button + key='postTransaction' + label='post transaction' + disabled={ !!(sending || hasError) } + icon={ <IdentityIcon address={ fromAddress } button /> } + onClick={ this.postTransaction } /> + ); + const nextBtn = ( + <Button + key='nextStep' + label='next' + icon={ <NavigationArrowForward /> } + onClick={ this.onNextClick } /> + ); + const prevBtn = ( + <Button + key='prevStep' + label='prev' + icon={ <NavigationArrowBack /> } + onClick={ this.onPrevClick } /> + ); - if (step === 0) { + if (step === STEP_DETAILS) { return [ cancelBtn, - <Button - key='postTransaction' - label='post transaction' - disabled={ !!(sending || hasError) } - icon={ <IdentityIcon address={ fromAddress } button /> } - onClick={ this.postTransaction } /> + gasEdit ? nextBtn : postBtn ]; - } else if (step === 1) { + } else if (step === (gasEdit ? STEP_BUSY : STEP_BUSY_OR_GAS)) { return [ cancelBtn ]; + } else if (gasEdit && (step === STEP_BUSY_OR_GAS)) { + return [ + cancelBtn, + prevBtn, + postBtn + ]; } return [ @@ -133,7 +180,8 @@ class ExecuteContract extends Component { renderStep () { const { onFromAddressChange } = this.props; - const { step, busyState, gasLimitError, txhash, rejected } = this.state; + const { gasEdit, step, busyState, txhash, rejected } = this.state; + const { errorEstimated } = this.gasStore; if (rejected) { return ( @@ -144,23 +192,29 @@ class ExecuteContract extends Component { ); } - if (step === 0) { + if (step === STEP_DETAILS) { return ( <DetailsStep { ...this.props } { ...this.state } - warning={ gasLimitError } + warning={ errorEstimated } onAmountChange={ this.onAmountChange } onFromAddressChange={ onFromAddressChange } onFuncChange={ this.onFuncChange } + onGasEditClick={ this.onGasEditClick } onValueChange={ this.onValueChange } /> ); - } else if (step === 1) { + } else if (step === (gasEdit ? STEP_BUSY : STEP_BUSY_OR_GAS)) { return ( <BusyStep title='The function execution is in progress' state={ busyState } /> ); + } else if (gasEdit && (step === STEP_BUSY_OR_GAS)) { + return ( + <GasPriceEditor + store={ this.gasStore } /> + ); } return ( @@ -171,6 +225,7 @@ class ExecuteContract extends Component { } onAmountChange = (amount) => { + this.gasStore.setEthValue(amount); this.setState({ amount }, this.estimateGas); } @@ -221,7 +276,7 @@ class ExecuteContract extends Component { estimateGas = (_fromAddress) => { const { api } = this.context; - const { fromAddress, gasLimit } = this.props; + const { fromAddress } = this.props; const { amount, func, values } = this.state; const options = { gas: MAX_GAS_ESTIMATION, @@ -237,18 +292,11 @@ class ExecuteContract extends Component { .estimateGas(options, values) .then((gasEst) => { const gas = gasEst.mul(1.2); - let gasLimitError = null; - if (gas.gte(MAX_GAS_ESTIMATION)) { - gasLimitError = ERRORS.gasException; - } else if (gas.gt(gasLimit)) { - gasLimitError = ERRORS.gasBlockLimit; - } + console.log(`estimateGas: received ${gasEst.toFormat(0)}, adjusted to ${gas.toFormat(0)}`); - this.setState({ - gas, - gasLimitError - }); + this.gasStore.setEstimated(gasEst.toFixed(0)); + this.gasStore.setGas(gas.toFixed(0)); }) .catch((error) => { console.warn('estimateGas', error); @@ -258,22 +306,18 @@ class ExecuteContract extends Component { postTransaction = () => { const { api, store } = this.context; const { fromAddress } = this.props; - const { amount, func, values } = this.state; + const { amount, func, gasEdit, values } = this.state; const options = { - gas: MAX_GAS_ESTIMATION, + gas: this.gasStore.gas, + gasPrice: this.gasStore.price, from: fromAddress, value: api.util.toWei(amount || 0) }; - this.setState({ sending: true, step: 1 }); + this.setState({ sending: true, step: gasEdit ? STEP_BUSY : STEP_BUSY_OR_GAS }); func - .estimateGas(options, values) - .then((gas) => { - options.gas = gas.mul(1.2).toFixed(0); - console.log(`estimateGas: received ${gas.toFormat(0)}, adjusted to ${gas.mul(1.2).toFormat(0)}`); - return func.postTransaction(options, values); - }) + .postTransaction(options, values) .then((requestId) => { this.setState({ busyState: 'Waiting for authorization in the Parity Signer' }); @@ -296,6 +340,24 @@ class ExecuteContract extends Component { store.dispatch({ type: 'newError', error }); }); } + + onGasEditClick = () => { + this.setState({ + gasEdit: !this.state.gasEdit + }); + } + + onNextClick = () => { + this.setState({ + step: this.state.step + 1 + }); + } + + onPrevClick = () => { + this.setState({ + step: this.state.step - 1 + }); + } } function mapStateToProps (state) { diff --git a/js/src/modals/Transfer/Extras/extras.js b/js/src/modals/Transfer/Extras/extras.js index 6d2bfc821..def5a22c6 100644 --- a/js/src/modals/Transfer/Extras/extras.js +++ b/js/src/modals/Transfer/Extras/extras.js @@ -30,21 +30,14 @@ export default class Extras extends Component { } render () { - const { gasStore, onChange, total, totalError } = this.props; + const { gasStore, onChange } = this.props; return ( <Form> { this.renderData() } <GasPriceEditor store={ gasStore } - onChange={ onChange }> - <Input - disabled - label='total transaction amount' - hint='the total amount of the transaction' - error={ totalError } - value={ `${total} ETH` } /> - </GasPriceEditor> + onChange={ onChange } /> </Form> ); } diff --git a/js/src/modals/Transfer/store.js b/js/src/modals/Transfer/store.js index a43057c86..cbb10f17f 100644 --- a/js/src/modals/Transfer/store.js +++ b/js/src/modals/Transfer/store.js @@ -408,6 +408,8 @@ export default class TransferStore { this.totalError = totalError; this.value = value; this.valueError = valueError; + this.gasStore.setErrorTotal(totalError); + this.gasStore.setEthValue(totalEth); }); } diff --git a/js/src/ui/GasPriceEditor/gasPriceEditor.js b/js/src/ui/GasPriceEditor/gasPriceEditor.js index c6759ddf1..8c94dfca7 100644 --- a/js/src/ui/GasPriceEditor/gasPriceEditor.js +++ b/js/src/ui/GasPriceEditor/gasPriceEditor.js @@ -26,8 +26,11 @@ import styles from './gasPriceEditor.css'; @observer export default class GasPriceEditor extends Component { + static contextTypes = { + api: PropTypes.object.isRequired + }; + static propTypes = { - children: PropTypes.node, store: PropTypes.object.isRequired, onChange: PropTypes.func } @@ -35,9 +38,11 @@ export default class GasPriceEditor extends Component { static Store = Store; render () { - const { children, store } = this.props; - const { estimated, priceDefault, price, gas, histogram, errorGas, errorPrice } = store; + const { api } = this.context; + const { store } = this.props; + const { estimated, priceDefault, price, gas, histogram, errorGas, errorPrice, errorTotal, totalValue } = store; + const eth = api.util.fromWei(totalValue).toFormat(); const gasLabel = `gas (estimated: ${new BigNumber(estimated).toFormat()})`; const priceLabel = `price (current: ${new BigNumber(priceDefault).toFormat()})`; @@ -75,7 +80,12 @@ export default class GasPriceEditor extends Component { </div> <div className={ styles.row }> - { children } + <Input + disabled + label='total transaction amount' + hint='the total amount of the transaction' + error={ errorTotal } + value={ `${eth} ETH` } /> </div> </div> </div> diff --git a/js/src/ui/GasPriceEditor/store.js b/js/src/ui/GasPriceEditor/store.js index 3f3e50430..afa5e15b2 100644 --- a/js/src/ui/GasPriceEditor/store.js +++ b/js/src/ui/GasPriceEditor/store.js @@ -15,7 +15,7 @@ // along with Parity. If not, see <http://www.gnu.org/licenses/>. import BigNumber from 'bignumber.js'; -import { action, observable, transaction } from 'mobx'; +import { action, computed, observable, transaction } from 'mobx'; import { ERRORS, validatePositiveNumber } from '~/util/validation'; import { DEFAULT_GAS, DEFAULT_GASPRICE, MAX_GAS_ESTIMATION } from '~/util/constants'; @@ -24,12 +24,14 @@ export default class GasPriceEditor { @observable errorEstimated = null; @observable errorGas = null; @observable errorPrice = null; + @observable errorTotal = null; @observable estimated = DEFAULT_GAS; @observable histogram = null; @observable price = DEFAULT_GASPRICE; @observable priceDefault = DEFAULT_GASPRICE; @observable gas = DEFAULT_GAS; @observable gasLimit = 0; + @observable weiValue = '0'; constructor (api, gasLimit, loadDefaults = true) { this._api = api; @@ -40,6 +42,18 @@ export default class GasPriceEditor { } } + @computed get totalValue () { + try { + return new BigNumber(this.gas).mul(this.price).add(this.weiValue); + } catch (error) { + return new BigNumber(0); + } + } + + @action setErrorTotal = (errorTotal) => { + this.errorTotal = errorTotal; + } + @action setEstimated = (estimated) => { transaction(() => { const bn = new BigNumber(estimated); @@ -56,6 +70,10 @@ export default class GasPriceEditor { }); } + @action setEthValue = (weiValue) => { + this.weiValue = weiValue; + } + @action setHistogram = (gasHistogram) => { this.histogram = gasHistogram; } From 63137b15482344ff9df634c086abaabed452eadc Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac <ngotchac@gmail.com> Date: Fri, 9 Dec 2016 15:52:28 +0100 Subject: [PATCH 45/63] Add enhanced Wallet solidity code --- js/src/contracts/snippets/enhanced-wallet.sol | 460 ++++++++++++++++++ 1 file changed, 460 insertions(+) create mode 100644 js/src/contracts/snippets/enhanced-wallet.sol diff --git a/js/src/contracts/snippets/enhanced-wallet.sol b/js/src/contracts/snippets/enhanced-wallet.sol new file mode 100644 index 000000000..374eb595f --- /dev/null +++ b/js/src/contracts/snippets/enhanced-wallet.sol @@ -0,0 +1,460 @@ +//sol Wallet +// Multi-sig, daily-limited account proxy/wallet. +// @authors: +// Gav Wood <g@ethdev.com> +// inheritable "property" contract that enables methods to be protected by requiring the acquiescence of either a +// single, or, crucially, each of a number of, designated owners. +// usage: +// use modifiers onlyowner (just own owned) or onlymanyowners(hash), whereby the same hash must be provided by +// some number (specified in constructor) of the set of owners (specified in the constructor, modifiable) before the +// interior is executed. +pragma solidity ^0.4.6; + +contract multisig { + // EVENTS + + // this contract can accept a confirmation, in which case + // we record owner and operation (hash) alongside it. + event Confirmation(address owner, bytes32 operation); + event Revoke(address owner, bytes32 operation); + + // some others are in the case of an owner changing. + event OwnerChanged(address oldOwner, address newOwner); + event OwnerAdded(address newOwner); + event OwnerRemoved(address oldOwner); + + // the last one is emitted if the required signatures change + event RequirementChanged(uint newRequirement); + + // Funds has arrived into the wallet (record how much). + event Deposit(address _from, uint value); + // Single transaction going out of the wallet (record who signed for it, how much, and to whom it's going). + event SingleTransact(address owner, uint value, address to, bytes data); + // Multi-sig transaction going out of the wallet (record who signed for it last, the operation hash, how much, and to whom it's going). + event MultiTransact(address owner, bytes32 operation, uint value, address to, bytes data); + // Confirmation still needed for a transaction. + event ConfirmationNeeded(bytes32 operation, address initiator, uint value, address to, bytes data); +} + +contract multisigAbi is multisig { + function isOwner(address _addr) returns (bool); + + function hasConfirmed(bytes32 _operation, address _owner) constant returns (bool); + + function confirm(bytes32 _h) returns(bool); + + // (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today. + function setDailyLimit(uint _newLimit); + + function addOwner(address _owner); + + function removeOwner(address _owner); + + function changeRequirement(uint _newRequired); + + // Revokes a prior confirmation of the given operation + function revoke(bytes32 _operation); + + function changeOwner(address _from, address _to); + + function execute(address _to, uint _value, bytes _data) returns(bool); +} + +contract WalletLibrary is multisig { + // TYPES + + // struct for the status of a pending operation. + struct PendingState { + uint yetNeeded; + uint ownersDone; + uint index; + } + + // Transaction structure to remember details of transaction lest it need be saved for a later call. + struct Transaction { + address to; + uint value; + bytes data; + } + + /****************************** + ***** MULTI OWNED SECTION **** + ******************************/ + + // MODIFIERS + + // simple single-sig function modifier. + modifier onlyowner { + if (isOwner(msg.sender)) + _; + } + // multi-sig function modifier: the operation must have an intrinsic hash in order + // that later attempts can be realised as the same underlying operation and + // thus count as confirmations. + modifier onlymanyowners(bytes32 _operation) { + if (confirmAndCheck(_operation)) + _; + } + + // METHODS + + // constructor is given number of sigs required to do protected "onlymanyowners" transactions + // as well as the selection of addresses capable of confirming them. + function initMultiowned(address[] _owners, uint _required) { + m_numOwners = _owners.length + 1; + m_owners[1] = uint(msg.sender); + m_ownerIndex[uint(msg.sender)] = 1; + m_required = _required; + + for (uint i = 0; i < _owners.length; ++i) + { + m_owners[2 + i] = uint(_owners[i]); + m_ownerIndex[uint(_owners[i])] = 2 + i; + } + } + + // Revokes a prior confirmation of the given operation + function revoke(bytes32 _operation) { + uint ownerIndex = m_ownerIndex[uint(msg.sender)]; + // make sure they're an owner + if (ownerIndex == 0) return; + uint ownerIndexBit = 2**ownerIndex; + var pending = m_pending[_operation]; + if (pending.ownersDone & ownerIndexBit > 0) { + pending.yetNeeded++; + pending.ownersDone -= ownerIndexBit; + Revoke(msg.sender, _operation); + } + } + + // Replaces an owner `_from` with another `_to`. + function changeOwner(address _from, address _to) onlymanyowners(sha3(msg.data)) { + if (isOwner(_to)) return; + uint ownerIndex = m_ownerIndex[uint(_from)]; + if (ownerIndex == 0) return; + + clearPending(); + m_owners[ownerIndex] = uint(_to); + m_ownerIndex[uint(_from)] = 0; + m_ownerIndex[uint(_to)] = ownerIndex; + OwnerChanged(_from, _to); + } + + function addOwner(address _owner) onlymanyowners(sha3(msg.data)) { + if (isOwner(_owner)) return; + + clearPending(); + if (m_numOwners >= c_maxOwners) + reorganizeOwners(); + if (m_numOwners >= c_maxOwners) + return; + m_numOwners++; + m_owners[m_numOwners] = uint(_owner); + m_ownerIndex[uint(_owner)] = m_numOwners; + OwnerAdded(_owner); + } + + function removeOwner(address _owner) onlymanyowners(sha3(msg.data)) { + uint ownerIndex = m_ownerIndex[uint(_owner)]; + if (ownerIndex == 0) return; + if (m_required > m_numOwners - 1) return; + + m_owners[ownerIndex] = 0; + m_ownerIndex[uint(_owner)] = 0; + clearPending(); + reorganizeOwners(); //make sure m_numOwner is equal to the number of owners and always points to the optimal free slot + OwnerRemoved(_owner); + } + + function changeRequirement(uint _newRequired) onlymanyowners(sha3(msg.data)) { + if (_newRequired > m_numOwners) return; + m_required = _newRequired; + clearPending(); + RequirementChanged(_newRequired); + } + + function isOwner(address _addr) returns (bool) { + return m_ownerIndex[uint(_addr)] > 0; + } + + + function hasConfirmed(bytes32 _operation, address _owner) constant returns (bool) { + var pending = m_pending[_operation]; + uint ownerIndex = m_ownerIndex[uint(_owner)]; + + // make sure they're an owner + if (ownerIndex == 0) return false; + + // determine the bit to set for this owner. + uint ownerIndexBit = 2**ownerIndex; + return !(pending.ownersDone & ownerIndexBit == 0); + } + + // INTERNAL METHODS + + function confirmAndCheck(bytes32 _operation) internal returns (bool) { + // determine what index the present sender is: + uint ownerIndex = m_ownerIndex[uint(msg.sender)]; + // make sure they're an owner + if (ownerIndex == 0) return; + + var pending = m_pending[_operation]; + // if we're not yet working on this operation, switch over and reset the confirmation status. + if (pending.yetNeeded == 0) { + // reset count of confirmations needed. + pending.yetNeeded = m_required; + // reset which owners have confirmed (none) - set our bitmap to 0. + pending.ownersDone = 0; + pending.index = m_pendingIndex.length++; + m_pendingIndex[pending.index] = _operation; + } + // determine the bit to set for this owner. + uint ownerIndexBit = 2**ownerIndex; + // make sure we (the message sender) haven't confirmed this operation previously. + if (pending.ownersDone & ownerIndexBit == 0) { + Confirmation(msg.sender, _operation); + // ok - check if count is enough to go ahead. + if (pending.yetNeeded <= 1) { + // enough confirmations: reset and run interior. + delete m_pendingIndex[m_pending[_operation].index]; + delete m_pending[_operation]; + return true; + } + else + { + // not enough: record that this owner in particular confirmed. + pending.yetNeeded--; + pending.ownersDone |= ownerIndexBit; + } + } + } + + function reorganizeOwners() private { + uint free = 1; + while (free < m_numOwners) + { + while (free < m_numOwners && m_owners[free] != 0) free++; + while (m_numOwners > 1 && m_owners[m_numOwners] == 0) m_numOwners--; + if (free < m_numOwners && m_owners[m_numOwners] != 0 && m_owners[free] == 0) + { + m_owners[free] = m_owners[m_numOwners]; + m_ownerIndex[m_owners[free]] = free; + m_owners[m_numOwners] = 0; + } + } + } + + function clearPending() internal { + uint length = m_pendingIndex.length; + for (uint i = 0; i < length; ++i) + if (m_pendingIndex[i] != 0) + delete m_pending[m_pendingIndex[i]]; + delete m_pendingIndex; + } + + + /****************************** + ****** DAY LIMIT SECTION ***** + ******************************/ + + // MODIFIERS + + // simple modifier for daily limit. + modifier limitedDaily(uint _value) { + if (underLimit(_value)) + _; + } + + // METHODS + + // constructor - stores initial daily limit and records the present day's index. + function initDaylimit(uint _limit) { + m_dailyLimit = _limit; + m_lastDay = today(); + } + // (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today. + function setDailyLimit(uint _newLimit) onlymanyowners(sha3(msg.data)) { + m_dailyLimit = _newLimit; + } + // resets the amount already spent today. needs many of the owners to confirm. + function resetSpentToday() onlymanyowners(sha3(msg.data)) { + m_spentToday = 0; + } + + // INTERNAL METHODS + + // checks to see if there is at least `_value` left from the daily limit today. if there is, subtracts it and + // returns true. otherwise just returns false. + function underLimit(uint _value) internal onlyowner returns (bool) { + // reset the spend limit if we're on a different day to last time. + if (today() > m_lastDay) { + m_spentToday = 0; + m_lastDay = today(); + } + // check to see if there's enough left - if so, subtract and return true. + // overflow protection // dailyLimit check + if (m_spentToday + _value >= m_spentToday && m_spentToday + _value <= m_dailyLimit) { + m_spentToday += _value; + return true; + } + return false; + } + + // determines today's index. + function today() private constant returns (uint) { return now / 1 days; } + + + /****************************** + ********* WALLET SECTION ***** + ******************************/ + + // METHODS + + // constructor - just pass on the owner array to the multiowned and + // the limit to daylimit + function initWallet(address[] _owners, uint _required, uint _daylimit) { + initMultiowned(_owners, _required); + initDaylimit(_daylimit) ; + } + + // kills the contract sending everything to `_to`. + function kill(address _to) onlymanyowners(sha3(msg.data)) { + suicide(_to); + } + + // Outside-visible transact entry point. Executes transaction immediately if below daily spend limit. + // If not, goes into multisig process. We provide a hash on return to allow the sender to provide + // shortcuts for the other confirmations (allowing them to avoid replicating the _to, _value + // and _data arguments). They still get the option of using them if they want, anyways. + function execute(address _to, uint _value, bytes _data) onlyowner returns(bool _callValue) { + // first, take the opportunity to check that we're under the daily limit. + if (underLimit(_value)) { + SingleTransact(msg.sender, _value, _to, _data); + // yes - just execute the call. + _callValue =_to.call.value(_value)(_data); + } else { + // determine our operation hash. + bytes32 _r = sha3(msg.data, block.number); + if (!confirm(_r) && m_txs[_r].to == 0) { + m_txs[_r].to = _to; + m_txs[_r].value = _value; + m_txs[_r].data = _data; + ConfirmationNeeded(_r, msg.sender, _value, _to, _data); + } + } + } + + // confirm a transaction through just the hash. we use the previous transactions map, m_txs, in order + // to determine the body of the transaction from the hash provided. + function confirm(bytes32 _h) onlymanyowners(_h) returns (bool) { + if (m_txs[_h].to != 0) { + m_txs[_h].to.call.value(m_txs[_h].value)(m_txs[_h].data); + MultiTransact(msg.sender, _h, m_txs[_h].value, m_txs[_h].to, m_txs[_h].data); + delete m_txs[_h]; + return true; + } + } + + // INTERNAL METHODS + + function clearWalletPending() internal { + uint length = m_pendingIndex.length; + for (uint i = 0; i < length; ++i) + delete m_txs[m_pendingIndex[i]]; + clearPending(); + } + + // FIELDS + address constant _walletLibrary = 0xcafecafecafecafecafecafecafecafecafecafe; + + // the number of owners that must confirm the same operation before it is run. + uint m_required; + // pointer used to find a free slot in m_owners + uint m_numOwners; + + uint public m_dailyLimit; + uint public m_spentToday; + uint public m_lastDay; + + // list of owners + uint[256] m_owners; + uint constant c_maxOwners = 250; + + // index on the list of owners to allow reverse lookup + mapping(uint => uint) m_ownerIndex; + // the ongoing operations. + mapping(bytes32 => PendingState) m_pending; + bytes32[] m_pendingIndex; + + // pending transactions we have at present. + mapping (bytes32 => Transaction) m_txs; +} + + +contract Wallet is multisig { + + // WALLET CONSTRUCTOR + // calls the `initWallet` method of the Library in this context + function Wallet(address[] _owners, uint _required, uint _daylimit) { + // Signature of the Wallet Library's init function + bytes4 sig = bytes4(sha3("initWallet(address[],uint256,uint256)")); + address target = _walletLibrary; + + // Compute the size of the call data : arrays has 2 + // 32bytes for offset and length, plus 32bytes per element ; + // plus 2 32bytes for each uint + uint argarraysize = (2 + _owners.length); + uint argsize = (2 + argarraysize) * 32; + + assembly { + // Add the signature first to memory + mstore(0x0, sig) + // Add the call data, which is at the end of the + // code + codecopy(0x4, sub(codesize, argsize), argsize) + // Delegate call to the library + delegatecall(sub(gas, 10000), target, 0x0, add(argsize, 0x4), 0x0, 0x0) + } + } + + // METHODS + + // gets called when no other function matches + function() payable { + // just being sent some cash? + if (msg.value > 0) + Deposit(msg.sender, msg.value); + else if (msg.data.length > 0) + _walletLibrary.delegatecall(msg.data); + } + + // Gets an owner by 0-indexed position (using numOwners as the count) + function getOwner(uint ownerIndex) constant returns (address) { + return address(m_owners[ownerIndex + 1]); + } + + // As return statement unavailable in fallback, explicit the method here + + function hasConfirmed(bytes32 _operation, address _owner) constant returns (bool) { + return _walletLibrary.delegatecall(msg.data); + } + + function isOwner(address _addr) returns (bool) { + return _walletLibrary.delegatecall(msg.data); + } + + // FIELDS + address constant _walletLibrary = 0xcafecafecafecafecafecafecafecafecafecafe; + + // the number of owners that must confirm the same operation before it is run. + uint public m_required; + // pointer used to find a free slot in m_owners + uint public m_numOwners; + + uint public m_dailyLimit; + uint public m_spentToday; + uint public m_lastDay; + + // list of owners + uint[256] m_owners; +} From d992c642ffb2aa5c50e058a51bd59209f8bb7142 Mon Sep 17 00:00:00 2001 From: Jaco Greeff <jaco@ethcore.io> Date: Fri, 9 Dec 2016 15:55:50 +0100 Subject: [PATCH 46/63] Adjust to final step on rejected/completed --- js/src/modals/ExecuteContract/executeContract.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/js/src/modals/ExecuteContract/executeContract.js b/js/src/modals/ExecuteContract/executeContract.js index 3f7940ca3..7b4e8ccd2 100644 --- a/js/src/modals/ExecuteContract/executeContract.js +++ b/js/src/modals/ExecuteContract/executeContract.js @@ -307,6 +307,8 @@ class ExecuteContract extends Component { const { api, store } = this.context; const { fromAddress } = this.props; const { amount, func, gasEdit, values } = this.state; + const steps = gasEdit ? STAGES_GAS : STAGES_BASIC; + const finalstep = steps.length - 1; const options = { gas: this.gasStore.gas, gasPrice: this.gasStore.price, @@ -325,7 +327,7 @@ class ExecuteContract extends Component { .pollMethod('parity_checkRequest', requestId) .catch((error) => { if (error.code === ERROR_CODES.REQUEST_REJECTED) { - this.setState({ rejected: true }); + this.setState({ rejected: true, step: finalstep }); return false; } @@ -333,7 +335,7 @@ class ExecuteContract extends Component { }); }) .then((txhash) => { - this.setState({ sending: false, step: 2, txhash, busyState: 'Your transaction has been posted to the network' }); + this.setState({ sending: false, step: finalstep, txhash, busyState: 'Your transaction has been posted to the network' }); }) .catch((error) => { console.error('postTransaction', error); From efee55ae84159f8cf62c54304632e1eb1b21ee9d Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac <ngotchac@gmail.com> Date: Fri, 9 Dec 2016 16:24:11 +0100 Subject: [PATCH 47/63] Updated Wallet Version ! Now only 500k gas for Wallet creation --- js/src/contracts/code/index.js | 2 +- js/src/contracts/code/wallet.js | 10 +++++--- .../CreateWallet/WalletType/walletType.js | 5 +++- .../modals/CreateWallet/createWalletStore.js | 25 +++++++++++++------ 4 files changed, 29 insertions(+), 13 deletions(-) diff --git a/js/src/contracts/code/index.js b/js/src/contracts/code/index.js index baa144979..ff6d218eb 100644 --- a/js/src/contracts/code/index.js +++ b/js/src/contracts/code/index.js @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see <http://www.gnu.org/licenses/>. -import wallet from './wallet'; +import { wallet } from './wallet'; export { wallet diff --git a/js/src/contracts/code/wallet.js b/js/src/contracts/code/wallet.js index 94aa04b7b..35ed3b106 100644 --- a/js/src/contracts/code/wallet.js +++ b/js/src/contracts/code/wallet.js @@ -16,8 +16,10 @@ /** * @version Solidity v0.4.6 - * @from https://github.com/ethereum/dapp-bin/blob/dd5c485359074d49f571693ae064ce78970f3d6d/wallet/wallet.sol - * @date 22-Nov-2016 @ 15h00 UTC + * @from https://github.com/ethcore/parity/blob/63137b15482344ff9df634c086abaabed452eadc/js/src/contracts/snippets/enhanced-wallet.sol + * @date 09-Dec-2016 @ 16h00 UTC */ -export default '0x606060405234610000576040516113bb3803806113bb83398101604090815281516020830151918301519201915b805b83835b815160019081019055600033600160a060020a03166003825b505550600160a060020a033316600090815261010260205260408120600190555b82518110156100ee57828181518110156100005790602001906020020151600160a060020a0316600282600201610100811015610000570160005b5081905550806002016101026000858481518110156100005790602001906020020151600160a060020a03168152602001908152602001600020819055505b60010161006c565b60008290555b50505061010581905561011264010000000061127861012182021704565b610107555b505b50505061012b565b6201518042045b90565b611282806101396000396000f3606060405236156100da5760e060020a6000350463173825d981146101305780632f54bf6e146101425780634123cb6b1461016657806352375093146101855780635c52c2f5146101a4578063659010e7146101b35780637065cb48146101d2578063746c9171146101e4578063797af62714610203578063b20d30a914610227578063b61d27f614610239578063b75c7dc61461026b578063ba51a6df1461027d578063c2cf73261461028f578063c41a360a146102b6578063cbf0b0c0146102e2578063f00d4b5d146102f4578063f1736d8614610309575b61012e5b600034111561012b5760408051600160a060020a033316815234602082015281517fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c929181900390910190a15b5b565b005b346100005761012e600435610328565b005b3461000057610152600435610415565b604080519115158252519081900360200190f35b3461000057610173610436565b60408051918252519081900360200190f35b346100005761017361043c565b60408051918252519081900360200190f35b346100005761012e610443565b005b346100005761017361047b565b60408051918252519081900360200190f35b346100005761012e600435610482565b005b3461000057610173610571565b60408051918252519081900360200190f35b3461000057610152600435610577565b604080519115158252519081900360200190f35b346100005761012e6004356107e3565b005b34610000576101736004803590602480359160443591820191013561081c565b60408051918252519081900360200190f35b346100005761012e600435610ab3565b005b346100005761012e600435610b5e565b005b3461000057610152600435602435610be0565b604080519115158252519081900360200190f35b34610000576102c6600435610c35565b60408051600160a060020a039092168252519081900360200190f35b346100005761012e600435610c55565b005b346100005761012e600435602435610c93565b005b3461000057610173610d8c565b60408051918252519081900360200190f35b600060003660405180838380828437820191505092505050604051809103902061035181610d93565b1561040e57600160a060020a03831660009081526101026020526040902054915081151561037e5761040e565b60016001540360005411156103925761040e565b6000600283610100811015610000570160005b5055600160a060020a038316600090815261010260205260408120556103c9610f32565b6103d1611002565b60408051600160a060020a038516815290517f58619076adf5bb0943d100ef88d52d7c3fd691b19d3a9071b555b651fbf418da9181900360200190a15b5b5b505050565b600160a060020a03811660009081526101026020526040812054115b919050565b60015481565b6101075481565b60003660405180838380828437820191505092505050604051809103902061046a81610d93565b15610476576000610106555b5b5b50565b6101065481565b6000366040518083838082843782019150509250505060405180910390206104a981610d93565b1561056b576104b782610415565b156104c15761056b565b6104c9610f32565b60015460fa90106104dc576104dc611002565b5b60015460fa90106104ed5761056b565b60018054810190819055600160a060020a03831690600290610100811015610000570160005b5055600154600160a060020a03831660008181526101026020908152604091829020939093558051918252517f994a936646fe87ffe4f1e469d3d6aa417d6b855598397f323de5b449f765f0c3929181900390910190a15b5b5b5050565b60005481565b60008161058381610d93565b156107da5760008381526101086020526040902054600160a060020a0316156107da5760008381526101086020526040908190208054600180830154935160029384018054600160a060020a0390941695949093919283928592918116156101000260001901160480156106385780601f1061060d57610100808354040283529160200191610638565b820191906000526020600020905b81548152906001019060200180831161061b57829003601f168201915b505091505060006040518083038185876185025a03f15050506000848152610108602090815260409182902060018082015482548551600160a060020a033381811683529682018c905296810183905295166060860181905260a06080870181815260029586018054958616156101000260001901909516959095049087018190527fe7c957c06e9a662c1a6c77366179f5b702b97651dc28eee7d5bf1dff6e40bb4a975094958a95929491939290919060c08301908490801561073d5780601f106107125761010080835404028352916020019161073d565b820191906000526020600020905b81548152906001019060200180831161072057829003601f168201915b5050965050505050505060405180910390a16000838152610108602052604081208054600160a060020a0319168155600180820183905560028083018054858255939493909281161561010002600019011604601f81901061079f57506107d1565b601f0160209004906000526020600020908101906107d191905b808211156107cd57600081556001016107b9565b5090565b5b505050600191505b5b5b5b50919050565b60003660405180838380828437820191505092505050604051809103902061080a81610d93565b1561056b576101058290555b5b5b5050565b600061082733610415565b15610aa85761083584611131565b156108f3577f92ca3a80853e6663fa31fa10b99225f18d4902939b4c53a9caae9043f6efd00433858786866040518086600160a060020a0316815260200185815260200184600160a060020a0316815260200180602001828103825284848281815260200192508082843760405192018290039850909650505050505050a184600160a060020a03168484846040518083838082843782019150509250505060006040518083038185876185025a03f15060009350610aa892505050565b6000364360405180848480828437820191505082815260200193505050506040518091039020905061092481610577565b158015610947575060008181526101086020526040902054600160a060020a0316155b15610aa857600081815261010860209081526040822080546c01000000000000000000000000808a0204600160a060020a0319909116178155600180820188905560029182018054818652948490209094601f928116156101000260001901169290920481019290920481019185919087908390106109d15782800160ff198235161785556109fe565b828001600101855582156109fe579182015b828111156109fe5782358255916020019190600101906109e3565b5b50610a1f9291505b808211156107cd57600081556001016107b9565b5090565b50507f1733cbb53659d713b79580f79f3f9ff215f78a7c7aa45890f3b89fc5cddfbf32813386888787604051808760001916815260200186600160a060020a0316815260200185815260200184600160a060020a031681526020018060200182810382528484828181526020019250808284376040519201829003995090975050505050505050a15b5b5b5b949350505050565b600160a060020a033316600090815261010260205260408120549080821515610adb57610b57565b50506000828152610103602052604081206001810154600284900a929083161115610b575780546001908101825581018054839003905560408051600160a060020a03331681526020810186905281517fc7fb647e59b18047309aa15aad418e5d7ca96d173ad704f1031a2c3d7591734b929181900390910190a15b5b50505050565b600036604051808383808284378201915050925050506040518091039020610b8581610d93565b1561056b57600154821115610b995761056b565b6000829055610ba6610f32565b6040805183815290517facbdb084c721332ac59f9b8e392196c9eb0e4932862da8eb9beaf0dad4f550da9181900360200190a15b5b5b5050565b600082815261010360209081526040808320600160a060020a038516845261010290925282205482811515610c185760009350610c2c565b8160020a9050808360010154166000141593505b50505092915050565b6000600282600101610100811015610000570160005b505490505b919050565b600036604051808383808284378201915050925050506040518091039020610c7c81610d93565b1561056b5781600160a060020a0316ff5b5b5b5050565b6000600036604051808383808284378201915050925050506040518091039020610cbc81610d93565b15610b5757610cca83610415565b15610cd457610b57565b600160a060020a038416600090815261010260205260409020549150811515610cfc57610b57565b610d04610f32565b82600160a060020a0316600283610100811015610000570160005b5055600160a060020a0380851660008181526101026020908152604080832083905593871680835291849020869055835192835282015281517fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c929181900390910190a15b5b5b50505050565b6101055481565b600160a060020a033316600090815261010260205260408120548180821515610dbb57610f28565b60008581526101036020526040902080549092501515610e4f576000805483556001808401919091556101048054918201808255828015829011610e2457600083815260209020610e249181019083015b808211156107cd57600081556001016107b9565b5090565b5b50505060028301819055610104805487929081101561000057906000526020600020900160005b50555b8260020a90508082600101541660001415610f285760408051600160a060020a03331681526020810187905281517fe1c52dc63b719ade82e8bea94cc41a0d5d28e4aaf536adb5e9cccc9ff8c1aeda929181900390910190a1815460019011610f155760008581526101036020526040902060020154610104805490919081101561000057906000526020600020900160005b506000908190558581526101036020526040812081815560018082018390556002909101919091559350610f2856610f28565b8154600019018255600182018054821790555b5b5b505050919050565b6101045460005b81811015610ff557610108600061010483815481101561000057906000526020600020900160005b50548152602081019190915260400160009081208054600160a060020a0319168155600180820183905560028083018054858255939493909281161561010002600019011604601f819010610fb65750610fe8565b601f016020900490600052602060002090810190610fe891905b808211156107cd57600081556001016107b9565b5090565b5b5050505b600101610f39565b61056b6111a4565b5b5050565b60015b600154811015610476575b600154811080156110325750600281610100811015610000570160005b505415155b1561103f57600101611010565b5b600160015411801561106457506002600154610100811015610000570160005b5054155b156110785760018054600019019055611040565b6001548110801561109c57506002600154610100811015610000570160005b505415155b80156110b85750600281610100811015610000570160005b5054155b15611128576002600154610100811015610000570160005b5054600282610100811015610000570160005b5055806101026000600283610100811015610000570160005b505481526020019081526020016000208190555060006002600154610100811015610000570160005b50555b611005565b5b50565b600061113c33610415565b15610431576101075461114d611278565b111561116657600061010655611161611278565b610107555b610106548281011080159061118357506101055482610106540111155b1561119957506101068054820190556001610431565b5060005b5b5b919050565b6101045460005b818110156112215761010481815481101561000057906000526020600020900160005b50541561121857610103600061010483815481101561000057906000526020600020900160005b505481526020810191909152604001600090812081815560018101829055600201555b5b6001016111ab565b610104805460008083559190915261040e907f4c0be60200faa20559308cb7b5a1bb3255c16cb1cab91f525b5ae7a03d02fabe908101905b808211156107cd57600081556001016107b9565b5090565b5b505b5050565b6201518042045b9056'; - +export const wallet = '0x6060604052346100005760405161041b38038061041b83398101604090815281516020830151918301519201915b604080517f696e697457616c6c657428616464726573735b5d2c75696e743235362c75696e81527f7432353629000000000000000000000000000000000000000000000000000000602080830191909152915190819003602501902084516000829052909173__WalletLibrary_________________________91600281019160049182010290819038829003903960006000600483016000866127105a03f45b505050505050505b610337806100e46000396000f36060604052361561006c5760e060020a60003504632f54bf6e81146101245780634123cb6b146101485780635237509314610167578063659010e714610186578063746c9171146101a5578063c2cf7326146101c4578063c41a360a146101eb578063f1736d8614610217575b6101225b60003411156100c15760408051600160a060020a033316815234602082015281517fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c929181900390910190a161011e565b600036111561011e5773__WalletLibrary_________________________600160a060020a0316600036600060405160200152604051808383808284378201915050925050506020604051808303818560325a03f4156100005750505b5b5b565b005b3461000057610134600435610236565b604080519115158252519081900360200190f35b3461000057610155610297565b60408051918252519081900360200190f35b346100005761015561029d565b60408051918252519081900360200190f35b34610000576101556102a3565b60408051918252519081900360200190f35b34610000576101556102a9565b60408051918252519081900360200190f35b34610000576101346004356024356102af565b604080519115158252519081900360200190f35b34610000576101fb600435610311565b60408051600160a060020a039092168252519081900360200190f35b3461000057610155610331565b60408051918252519081900360200190f35b600073__WalletLibrary_________________________600160a060020a0316600036600060405160200152604051808383808284378201915050925050506020604051808303818560325a03f4156100005750506040515190505b919050565b60015481565b60045481565b60035481565b60005481565b600073__WalletLibrary_________________________600160a060020a0316600036600060405160200152604051808383808284378201915050925050506020604051808303818560325a03f4156100005750506040515190505b92915050565b6000600582600101610100811015610000570160005b505490505b919050565b6002548156'; +export const walletLibrary = '0x606060405234610000575b611381806100186000396000f3606060405236156100da5760e060020a6000350463173825d981146100df5780632f54bf6e146100f157806352375093146101155780635c52c2f514610134578063659010e7146101435780637065cb4814610162578063797af627146101745780639da5e0eb14610198578063b20d30a9146101aa578063b61d27f6146101bc578063b75c7dc614610227578063ba51a6df14610239578063c2cf73261461024b578063c57c5f6014610272578063cbf0b0c0146102c6578063e46dcfeb146102d8578063f00d4b5d14610331578063f1736d8614610346575b610000565b34610000576100ef600435610365565b005b3461000057610101600435610452565b604080519115158252519081900360200190f35b3461000057610122610473565b60408051918252519081900360200190f35b34610000576100ef610479565b005b34610000576101226104b0565b60408051918252519081900360200190f35b34610000576100ef6004356104b6565b005b34610000576101016004356105a5565b604080519115158252519081900360200190f35b34610000576100ef60043561081e565b005b34610000576100ef600435610832565b005b3461000057604080516020600460443581810135601f810184900484028501840190955284845261010194823594602480359560649492939190920191819084018382808284375094965061086a95505050505050565b604080519115158252519081900360200190f35b34610000576100ef600435610bcc565b005b34610000576100ef600435610c77565b005b3461000057610101600435602435610cf9565b604080519115158252519081900360200190f35b34610000576100ef6004808035906020019082018035906020019080806020026020016040519081016040528093929190818152602001838360200280828437509496505093359350610d4e92505050565b005b34610000576100ef600435610e13565b005b34610000576100ef60048080359060200190820180359060200190808060200260200160405190810160405280939291908181526020018383602002808284375094965050843594602001359350610e5192505050565b005b34610000576100ef600435602435610e6a565b005b3461000057610122610f63565b60408051918252519081900360200190f35b600060003660405180838380828437820191505092505050604051809103902061038e81610f69565b1561044b57600160a060020a0383166000908152610105602052604090205491508115156103bb5761044b565b60016001540360005411156103cf5761044b565b6000600583610100811015610000570160005b5055600160a060020a03831660009081526101056020526040812055610406611108565b61040e6111dc565b60408051600160a060020a038516815290517f58619076adf5bb0943d100ef88d52d7c3fd691b19d3a9071b555b651fbf418da9181900360200190a15b5b5b505050565b600160a060020a03811660009081526101056020526040812054115b919050565b60045481565b6000366040518083838082843782019150509250505060405180910390206104a081610f69565b156104ab5760006003555b5b5b50565b60035481565b6000366040518083838082843782019150509250505060405180910390206104dd81610f69565b1561059f576104eb82610452565b156104f55761059f565b6104fd611108565b60015460fa9010610510576105106111dc565b5b60015460fa90106105215761059f565b60018054810190819055600160a060020a03831690600590610100811015610000570160005b5055600154600160a060020a03831660008181526101056020908152604091829020939093558051918252517f994a936646fe87ffe4f1e469d3d6aa417d6b855598397f323de5b449f765f0c3929181900390910190a15b5b5b5050565b6000816105b181610f69565b156108155760008381526101086020526040902054600160a060020a0316156108155760008381526101086020526040908190208054600180830154935160029384018054600160a060020a0390941695949093919283928592918116156101000260001901160480156106665780601f1061063b57610100808354040283529160200191610666565b820191906000526020600020905b81548152906001019060200180831161064957829003601f168201915b505091505060006040518083038185876185025a03f15050506000848152610108602090815260409182902060018082015482548551600160a060020a033381811683529682018c905296810183905295166060860181905260a06080870181815260029586018054958616156101000260001901909516959095049087018190527fe7c957c06e9a662c1a6c77366179f5b702b97651dc28eee7d5bf1dff6e40bb4a975094958a95929491939290919060c08301908490801561076b5780601f106107405761010080835404028352916020019161076b565b820191906000526020600020905b81548152906001019060200180831161074e57829003601f168201915b5050965050505050505060405180910390a1600083815261010860205260408120805473ffffffffffffffffffffffffffffffffffffffff19168155600180820183905560028083018054858255939493909281161561010002600019011604601f8190106107da575061080c565b601f01602090049060005260206000209081019061080c91905b8082111561080857600081556001016107f4565b5090565b5b505050600191505b5b5b5b50919050565b600281905561082b61130b565b6004555b50565b60003660405180838380828437820191505092505050604051809103902061085981610f69565b1561059f5760028290555b5b5b5050565b6000600061087733610452565b15610bc05761088584611315565b156109bc577f92ca3a80853e6663fa31fa10b99225f18d4902939b4c53a9caae9043f6efd004338587866040518085600160a060020a0316815260200184815260200183600160a060020a03168152602001806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156109335780820380516001836020036101000a031916815260200191505b509550505050505060405180910390a184600160a060020a03168484604051808280519060200190808383829060006004602084601f0104600302600f01f150905090810190601f16801561099c5780820380516001836020036101000a031916815260200191505b5091505060006040518083038185876185025a03f1925050509150610bc0565b600036436040518084848082843782019150508281526020019350505050604051809103902090506109ed816105a5565b158015610a10575060008181526101086020526040902054600160a060020a0316155b15610bc057600081815261010860209081526040822080546c01000000000000000000000000808a020473ffffffffffffffffffffffffffffffffffffffff199091161781556001808201889055865160029283018054818752958590209095601f9381161561010002600019011693909304820184900483019390929190880190839010610aaa57805160ff1916838001178555610ad7565b82800160010185558215610ad7579182015b82811115610ad7578251825591602001919060010190610abc565b5b50610af89291505b8082111561080857600081556001016107f4565b5090565b50507f1733cbb53659d713b79580f79f3f9ff215f78a7c7aa45890f3b89fc5cddfbf328133868887604051808660001916815260200185600160a060020a0316815260200184815260200183600160a060020a03168152602001806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f168015610bae5780820380516001836020036101000a031916815260200191505b50965050505050505060405180910390a15b5b5b5b5b509392505050565b600160a060020a033316600090815261010560205260408120549080821515610bf457610c70565b50506000828152610106602052604081206001810154600284900a929083161115610c705780546001908101825581018054839003905560408051600160a060020a03331681526020810186905281517fc7fb647e59b18047309aa15aad418e5d7ca96d173ad704f1031a2c3d7591734b929181900390910190a15b5b50505050565b600036604051808383808284378201915050925050506040518091039020610c9e81610f69565b1561059f57600154821115610cb25761059f565b6000829055610cbf611108565b6040805183815290517facbdb084c721332ac59f9b8e392196c9eb0e4932862da8eb9beaf0dad4f550da9181900360200190a15b5b5b5050565b600082815261010660209081526040808320600160a060020a038516845261010590925282205482811515610d315760009350610d45565b8160020a9050808360010154166000141593505b50505092915050565b815160019081019055600033600160a060020a03166006825b505550600160a060020a033316600090815261010560205260408120600190558181555b825181101561044b57828181518110156100005790602001906020020151600160a060020a0316600582600201610100811015610000570160005b5081905550806002016101056000858481518110156100005790602001906020020151600160a060020a03168152602001908152602001600020819055505b600101610d8b565b5b505050565b600036604051808383808284378201915050925050506040518091039020610e3a81610f69565b1561059f5781600160a060020a0316ff5b5b5b5050565b610e5b8383610d4e565b61044b8161081e565b5b505050565b6000600036604051808383808284378201915050925050506040518091039020610e9381610f69565b15610c7057610ea183610452565b15610eab57610c70565b600160a060020a038416600090815261010560205260409020549150811515610ed357610c70565b610edb611108565b82600160a060020a0316600583610100811015610000570160005b5055600160a060020a0380851660008181526101056020908152604080832083905593871680835291849020869055835192835282015281517fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c929181900390910190a15b5b5b50505050565b60025481565b600160a060020a033316600090815261010560205260408120548180821515610f91576110fe565b60008581526101066020526040902080549092501515611025576000805483556001808401919091556101078054918201808255828015829011610ffa57600083815260209020610ffa9181019083015b8082111561080857600081556001016107f4565b5090565b5b50505060028301819055610107805487929081101561000057906000526020600020900160005b50555b8260020a905080826001015416600014156110fe5760408051600160a060020a03331681526020810187905281517fe1c52dc63b719ade82e8bea94cc41a0d5d28e4aaf536adb5e9cccc9ff8c1aeda929181900390910190a18154600190116110eb5760008581526101066020526040902060020154610107805490919081101561000057906000526020600020900160005b5060009081905585815261010660205260408120818155600180820183905560029091019190915593506110fe566110fe565b8154600019018255600182018054821790555b5b5b505050919050565b6101075460005b818110156111855761010781815481101561000057906000526020600020900160005b50541561117c57610106600061010783815481101561000057906000526020600020900160005b505481526020810191909152604001600090812081815560018101829055600201555b5b60010161110f565b610107805460008083559190915261044b907f47c4908e245f386bfc1825973249847f4053a761ddb4880ad63c323a7b5a2a25908101905b8082111561080857600081556001016107f4565b5090565b5b505b5050565b60015b6001548110156104ab575b6001548110801561120c5750600581610100811015610000570160005b505415155b15611219576001016111ea565b5b600160015411801561123e57506005600154610100811015610000570160005b5054155b15611252576001805460001901905561121a565b6001548110801561127657506005600154610100811015610000570160005b505415155b80156112925750600581610100811015610000570160005b5054155b15611302576005600154610100811015610000570160005b5054600582610100811015610000570160005b5055806101056000600583610100811015610000570160005b505481526020019081526020016000208190555060006005600154610100811015610000570160005b50555b6111df565b5b50565b6201518042045b90565b600061132033610452565b1561046e5760045461133061130b565b111561134757600060035561134361130b565b6004555b600354828101108015906113615750600254826003540111155b1561137657506003805482019055600161046e565b5060005b5b5b91905056'; +export const walletSourceURL = 'https://github.com/ethcore/parity/blob/63137b15482344ff9df634c086abaabed452eadc/js/src/contracts/snippets/enhanced-wallet.sol'; +export const walletLibraryRegKey = 'walletLibrary'; diff --git a/js/src/modals/CreateWallet/WalletType/walletType.js b/js/src/modals/CreateWallet/WalletType/walletType.js index 868c6ad9b..93dd818f1 100644 --- a/js/src/modals/CreateWallet/WalletType/walletType.js +++ b/js/src/modals/CreateWallet/WalletType/walletType.js @@ -17,6 +17,7 @@ import React, { Component, PropTypes } from 'react'; import { RadioButtons } from '~/ui'; +import { walletSourceURL } from '~/contracts/code/wallet'; // import styles from '../createWallet.css'; @@ -46,7 +47,9 @@ export default class WalletType extends Component { description: ( <span> <span>Create/Deploy a </span> - <a href='https://github.com/ethereum/dapp-bin/blob/master/wallet/wallet.sol' target='_blank'>standard multi-signature </a> + <a href={ walletSourceURL } target='_blank'> + standard multi-signature + </a> <span> Wallet</span> </span> ) diff --git a/js/src/modals/CreateWallet/createWalletStore.js b/js/src/modals/CreateWallet/createWalletStore.js index d8c308a12..e6edab56b 100644 --- a/js/src/modals/CreateWallet/createWalletStore.js +++ b/js/src/modals/CreateWallet/createWalletStore.js @@ -20,8 +20,10 @@ import { validateUint, validateAddress, validateName } from '~/util/validation'; import { ERROR_CODES } from '~/api/transport/error'; import Contract from '~/api/contract'; +import Contracts from '~/contracts'; import { wallet as walletAbi } from '~/contracts/abi'; import { wallet as walletCode } from '~/contracts/code'; +import { walletLibraryRegKey } from '~/contracts/code/wallet'; import WalletsUtils from '~/util/wallets'; @@ -160,14 +162,23 @@ export default class CreateWalletStore { const { account, owners, required, daylimit } = this.wallet; - const options = { - data: walletCode, - from: account - }; + Contracts + .get() + .registry + .lookupAddress(walletLibraryRegKey) + .then((address) => { + const walletLibraryAddress = address.replace(/^0x/, '').toLowerCase(); + const code = walletCode.replace(/(_)+WalletLibrary(_)+/g, walletLibraryAddress); - this.api - .newContract(walletAbi) - .deploy(options, [ owners, required, daylimit ], this.onDeploymentState) + const options = { + data: code, + from: account + }; + + return this.api + .newContract(walletAbi) + .deploy(options, [ owners, required, daylimit ], this.onDeploymentState); + }) .then((address) => { this.deployed = true; this.wallet.address = address; From caf3a96c1999a4342c87435844f7349cdb5b0480 Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac <ngotchac@gmail.com> Date: Fri, 9 Dec 2016 16:29:57 +0100 Subject: [PATCH 48/63] Add fallback Full Fleshed Wallet if no library --- js/src/contracts/code/wallet.js | 4 ++++ js/src/modals/CreateWallet/createWalletStore.js | 8 +++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/js/src/contracts/code/wallet.js b/js/src/contracts/code/wallet.js index 35ed3b106..92bcf8795 100644 --- a/js/src/contracts/code/wallet.js +++ b/js/src/contracts/code/wallet.js @@ -23,3 +23,7 @@ export const wallet = '0x6060604052346100005760405161041b38038061041b83398101604 export const walletLibrary = '0x606060405234610000575b611381806100186000396000f3606060405236156100da5760e060020a6000350463173825d981146100df5780632f54bf6e146100f157806352375093146101155780635c52c2f514610134578063659010e7146101435780637065cb4814610162578063797af627146101745780639da5e0eb14610198578063b20d30a9146101aa578063b61d27f6146101bc578063b75c7dc614610227578063ba51a6df14610239578063c2cf73261461024b578063c57c5f6014610272578063cbf0b0c0146102c6578063e46dcfeb146102d8578063f00d4b5d14610331578063f1736d8614610346575b610000565b34610000576100ef600435610365565b005b3461000057610101600435610452565b604080519115158252519081900360200190f35b3461000057610122610473565b60408051918252519081900360200190f35b34610000576100ef610479565b005b34610000576101226104b0565b60408051918252519081900360200190f35b34610000576100ef6004356104b6565b005b34610000576101016004356105a5565b604080519115158252519081900360200190f35b34610000576100ef60043561081e565b005b34610000576100ef600435610832565b005b3461000057604080516020600460443581810135601f810184900484028501840190955284845261010194823594602480359560649492939190920191819084018382808284375094965061086a95505050505050565b604080519115158252519081900360200190f35b34610000576100ef600435610bcc565b005b34610000576100ef600435610c77565b005b3461000057610101600435602435610cf9565b604080519115158252519081900360200190f35b34610000576100ef6004808035906020019082018035906020019080806020026020016040519081016040528093929190818152602001838360200280828437509496505093359350610d4e92505050565b005b34610000576100ef600435610e13565b005b34610000576100ef60048080359060200190820180359060200190808060200260200160405190810160405280939291908181526020018383602002808284375094965050843594602001359350610e5192505050565b005b34610000576100ef600435602435610e6a565b005b3461000057610122610f63565b60408051918252519081900360200190f35b600060003660405180838380828437820191505092505050604051809103902061038e81610f69565b1561044b57600160a060020a0383166000908152610105602052604090205491508115156103bb5761044b565b60016001540360005411156103cf5761044b565b6000600583610100811015610000570160005b5055600160a060020a03831660009081526101056020526040812055610406611108565b61040e6111dc565b60408051600160a060020a038516815290517f58619076adf5bb0943d100ef88d52d7c3fd691b19d3a9071b555b651fbf418da9181900360200190a15b5b5b505050565b600160a060020a03811660009081526101056020526040812054115b919050565b60045481565b6000366040518083838082843782019150509250505060405180910390206104a081610f69565b156104ab5760006003555b5b5b50565b60035481565b6000366040518083838082843782019150509250505060405180910390206104dd81610f69565b1561059f576104eb82610452565b156104f55761059f565b6104fd611108565b60015460fa9010610510576105106111dc565b5b60015460fa90106105215761059f565b60018054810190819055600160a060020a03831690600590610100811015610000570160005b5055600154600160a060020a03831660008181526101056020908152604091829020939093558051918252517f994a936646fe87ffe4f1e469d3d6aa417d6b855598397f323de5b449f765f0c3929181900390910190a15b5b5b5050565b6000816105b181610f69565b156108155760008381526101086020526040902054600160a060020a0316156108155760008381526101086020526040908190208054600180830154935160029384018054600160a060020a0390941695949093919283928592918116156101000260001901160480156106665780601f1061063b57610100808354040283529160200191610666565b820191906000526020600020905b81548152906001019060200180831161064957829003601f168201915b505091505060006040518083038185876185025a03f15050506000848152610108602090815260409182902060018082015482548551600160a060020a033381811683529682018c905296810183905295166060860181905260a06080870181815260029586018054958616156101000260001901909516959095049087018190527fe7c957c06e9a662c1a6c77366179f5b702b97651dc28eee7d5bf1dff6e40bb4a975094958a95929491939290919060c08301908490801561076b5780601f106107405761010080835404028352916020019161076b565b820191906000526020600020905b81548152906001019060200180831161074e57829003601f168201915b5050965050505050505060405180910390a1600083815261010860205260408120805473ffffffffffffffffffffffffffffffffffffffff19168155600180820183905560028083018054858255939493909281161561010002600019011604601f8190106107da575061080c565b601f01602090049060005260206000209081019061080c91905b8082111561080857600081556001016107f4565b5090565b5b505050600191505b5b5b5b50919050565b600281905561082b61130b565b6004555b50565b60003660405180838380828437820191505092505050604051809103902061085981610f69565b1561059f5760028290555b5b5b5050565b6000600061087733610452565b15610bc05761088584611315565b156109bc577f92ca3a80853e6663fa31fa10b99225f18d4902939b4c53a9caae9043f6efd004338587866040518085600160a060020a0316815260200184815260200183600160a060020a03168152602001806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156109335780820380516001836020036101000a031916815260200191505b509550505050505060405180910390a184600160a060020a03168484604051808280519060200190808383829060006004602084601f0104600302600f01f150905090810190601f16801561099c5780820380516001836020036101000a031916815260200191505b5091505060006040518083038185876185025a03f1925050509150610bc0565b600036436040518084848082843782019150508281526020019350505050604051809103902090506109ed816105a5565b158015610a10575060008181526101086020526040902054600160a060020a0316155b15610bc057600081815261010860209081526040822080546c01000000000000000000000000808a020473ffffffffffffffffffffffffffffffffffffffff199091161781556001808201889055865160029283018054818752958590209095601f9381161561010002600019011693909304820184900483019390929190880190839010610aaa57805160ff1916838001178555610ad7565b82800160010185558215610ad7579182015b82811115610ad7578251825591602001919060010190610abc565b5b50610af89291505b8082111561080857600081556001016107f4565b5090565b50507f1733cbb53659d713b79580f79f3f9ff215f78a7c7aa45890f3b89fc5cddfbf328133868887604051808660001916815260200185600160a060020a0316815260200184815260200183600160a060020a03168152602001806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f168015610bae5780820380516001836020036101000a031916815260200191505b50965050505050505060405180910390a15b5b5b5b5b509392505050565b600160a060020a033316600090815261010560205260408120549080821515610bf457610c70565b50506000828152610106602052604081206001810154600284900a929083161115610c705780546001908101825581018054839003905560408051600160a060020a03331681526020810186905281517fc7fb647e59b18047309aa15aad418e5d7ca96d173ad704f1031a2c3d7591734b929181900390910190a15b5b50505050565b600036604051808383808284378201915050925050506040518091039020610c9e81610f69565b1561059f57600154821115610cb25761059f565b6000829055610cbf611108565b6040805183815290517facbdb084c721332ac59f9b8e392196c9eb0e4932862da8eb9beaf0dad4f550da9181900360200190a15b5b5b5050565b600082815261010660209081526040808320600160a060020a038516845261010590925282205482811515610d315760009350610d45565b8160020a9050808360010154166000141593505b50505092915050565b815160019081019055600033600160a060020a03166006825b505550600160a060020a033316600090815261010560205260408120600190558181555b825181101561044b57828181518110156100005790602001906020020151600160a060020a0316600582600201610100811015610000570160005b5081905550806002016101056000858481518110156100005790602001906020020151600160a060020a03168152602001908152602001600020819055505b600101610d8b565b5b505050565b600036604051808383808284378201915050925050506040518091039020610e3a81610f69565b1561059f5781600160a060020a0316ff5b5b5b5050565b610e5b8383610d4e565b61044b8161081e565b5b505050565b6000600036604051808383808284378201915050925050506040518091039020610e9381610f69565b15610c7057610ea183610452565b15610eab57610c70565b600160a060020a038416600090815261010560205260409020549150811515610ed357610c70565b610edb611108565b82600160a060020a0316600583610100811015610000570160005b5055600160a060020a0380851660008181526101056020908152604080832083905593871680835291849020869055835192835282015281517fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c929181900390910190a15b5b5b50505050565b60025481565b600160a060020a033316600090815261010560205260408120548180821515610f91576110fe565b60008581526101066020526040902080549092501515611025576000805483556001808401919091556101078054918201808255828015829011610ffa57600083815260209020610ffa9181019083015b8082111561080857600081556001016107f4565b5090565b5b50505060028301819055610107805487929081101561000057906000526020600020900160005b50555b8260020a905080826001015416600014156110fe5760408051600160a060020a03331681526020810187905281517fe1c52dc63b719ade82e8bea94cc41a0d5d28e4aaf536adb5e9cccc9ff8c1aeda929181900390910190a18154600190116110eb5760008581526101066020526040902060020154610107805490919081101561000057906000526020600020900160005b5060009081905585815261010660205260408120818155600180820183905560029091019190915593506110fe566110fe565b8154600019018255600182018054821790555b5b5b505050919050565b6101075460005b818110156111855761010781815481101561000057906000526020600020900160005b50541561117c57610106600061010783815481101561000057906000526020600020900160005b505481526020810191909152604001600090812081815560018101829055600201555b5b60010161110f565b610107805460008083559190915261044b907f47c4908e245f386bfc1825973249847f4053a761ddb4880ad63c323a7b5a2a25908101905b8082111561080857600081556001016107f4565b5090565b5b505b5050565b60015b6001548110156104ab575b6001548110801561120c5750600581610100811015610000570160005b505415155b15611219576001016111ea565b5b600160015411801561123e57506005600154610100811015610000570160005b5054155b15611252576001805460001901905561121a565b6001548110801561127657506005600154610100811015610000570160005b505415155b80156112925750600581610100811015610000570160005b5054155b15611302576005600154610100811015610000570160005b5054600582610100811015610000570160005b5055806101056000600583610100811015610000570160005b505481526020019081526020016000208190555060006005600154610100811015610000570160005b50555b6111df565b5b50565b6201518042045b90565b600061132033610452565b1561046e5760045461133061130b565b111561134757600060035561134361130b565b6004555b600354828101108015906113615750600254826003540111155b1561137657506003805482019055600161046e565b5060005b5b5b91905056'; export const walletSourceURL = 'https://github.com/ethcore/parity/blob/63137b15482344ff9df634c086abaabed452eadc/js/src/contracts/snippets/enhanced-wallet.sol'; export const walletLibraryRegKey = 'walletLibrary'; + +// Used if no Wallet Library found in registry... +// Compiled from `wallet.sol` using Solidity v0.4.6 +export const fullWalletCode = '0x606060405234610000576040516113bb3803806113bb83398101604090815281516020830151918301519201915b805b83835b815160019081019055600033600160a060020a03166003825b505550600160a060020a033316600090815261010260205260408120600190555b82518110156100ee57828181518110156100005790602001906020020151600160a060020a0316600282600201610100811015610000570160005b5081905550806002016101026000858481518110156100005790602001906020020151600160a060020a03168152602001908152602001600020819055505b60010161006c565b60008290555b50505061010581905561011264010000000061127861012182021704565b610107555b505b50505061012b565b6201518042045b90565b611282806101396000396000f3606060405236156100da5760e060020a6000350463173825d981146101305780632f54bf6e146101425780634123cb6b1461016657806352375093146101855780635c52c2f5146101a4578063659010e7146101b35780637065cb48146101d2578063746c9171146101e4578063797af62714610203578063b20d30a914610227578063b61d27f614610239578063b75c7dc61461026b578063ba51a6df1461027d578063c2cf73261461028f578063c41a360a146102b6578063cbf0b0c0146102e2578063f00d4b5d146102f4578063f1736d8614610309575b61012e5b600034111561012b5760408051600160a060020a033316815234602082015281517fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c929181900390910190a15b5b565b005b346100005761012e600435610328565b005b3461000057610152600435610415565b604080519115158252519081900360200190f35b3461000057610173610436565b60408051918252519081900360200190f35b346100005761017361043c565b60408051918252519081900360200190f35b346100005761012e610443565b005b346100005761017361047b565b60408051918252519081900360200190f35b346100005761012e600435610482565b005b3461000057610173610571565b60408051918252519081900360200190f35b3461000057610152600435610577565b604080519115158252519081900360200190f35b346100005761012e6004356107e3565b005b34610000576101736004803590602480359160443591820191013561081c565b60408051918252519081900360200190f35b346100005761012e600435610ab3565b005b346100005761012e600435610b5e565b005b3461000057610152600435602435610be0565b604080519115158252519081900360200190f35b34610000576102c6600435610c35565b60408051600160a060020a039092168252519081900360200190f35b346100005761012e600435610c55565b005b346100005761012e600435602435610c93565b005b3461000057610173610d8c565b60408051918252519081900360200190f35b600060003660405180838380828437820191505092505050604051809103902061035181610d93565b1561040e57600160a060020a03831660009081526101026020526040902054915081151561037e5761040e565b60016001540360005411156103925761040e565b6000600283610100811015610000570160005b5055600160a060020a038316600090815261010260205260408120556103c9610f32565b6103d1611002565b60408051600160a060020a038516815290517f58619076adf5bb0943d100ef88d52d7c3fd691b19d3a9071b555b651fbf418da9181900360200190a15b5b5b505050565b600160a060020a03811660009081526101026020526040812054115b919050565b60015481565b6101075481565b60003660405180838380828437820191505092505050604051809103902061046a81610d93565b15610476576000610106555b5b5b50565b6101065481565b6000366040518083838082843782019150509250505060405180910390206104a981610d93565b1561056b576104b782610415565b156104c15761056b565b6104c9610f32565b60015460fa90106104dc576104dc611002565b5b60015460fa90106104ed5761056b565b60018054810190819055600160a060020a03831690600290610100811015610000570160005b5055600154600160a060020a03831660008181526101026020908152604091829020939093558051918252517f994a936646fe87ffe4f1e469d3d6aa417d6b855598397f323de5b449f765f0c3929181900390910190a15b5b5b5050565b60005481565b60008161058381610d93565b156107da5760008381526101086020526040902054600160a060020a0316156107da5760008381526101086020526040908190208054600180830154935160029384018054600160a060020a0390941695949093919283928592918116156101000260001901160480156106385780601f1061060d57610100808354040283529160200191610638565b820191906000526020600020905b81548152906001019060200180831161061b57829003601f168201915b505091505060006040518083038185876185025a03f15050506000848152610108602090815260409182902060018082015482548551600160a060020a033381811683529682018c905296810183905295166060860181905260a06080870181815260029586018054958616156101000260001901909516959095049087018190527fe7c957c06e9a662c1a6c77366179f5b702b97651dc28eee7d5bf1dff6e40bb4a975094958a95929491939290919060c08301908490801561073d5780601f106107125761010080835404028352916020019161073d565b820191906000526020600020905b81548152906001019060200180831161072057829003601f168201915b5050965050505050505060405180910390a16000838152610108602052604081208054600160a060020a0319168155600180820183905560028083018054858255939493909281161561010002600019011604601f81901061079f57506107d1565b601f0160209004906000526020600020908101906107d191905b808211156107cd57600081556001016107b9565b5090565b5b505050600191505b5b5b5b50919050565b60003660405180838380828437820191505092505050604051809103902061080a81610d93565b1561056b576101058290555b5b5b5050565b600061082733610415565b15610aa85761083584611131565b156108f3577f92ca3a80853e6663fa31fa10b99225f18d4902939b4c53a9caae9043f6efd00433858786866040518086600160a060020a0316815260200185815260200184600160a060020a0316815260200180602001828103825284848281815260200192508082843760405192018290039850909650505050505050a184600160a060020a03168484846040518083838082843782019150509250505060006040518083038185876185025a03f15060009350610aa892505050565b6000364360405180848480828437820191505082815260200193505050506040518091039020905061092481610577565b158015610947575060008181526101086020526040902054600160a060020a0316155b15610aa857600081815261010860209081526040822080546c01000000000000000000000000808a0204600160a060020a0319909116178155600180820188905560029182018054818652948490209094601f928116156101000260001901169290920481019290920481019185919087908390106109d15782800160ff198235161785556109fe565b828001600101855582156109fe579182015b828111156109fe5782358255916020019190600101906109e3565b5b50610a1f9291505b808211156107cd57600081556001016107b9565b5090565b50507f1733cbb53659d713b79580f79f3f9ff215f78a7c7aa45890f3b89fc5cddfbf32813386888787604051808760001916815260200186600160a060020a0316815260200185815260200184600160a060020a031681526020018060200182810382528484828181526020019250808284376040519201829003995090975050505050505050a15b5b5b5b949350505050565b600160a060020a033316600090815261010260205260408120549080821515610adb57610b57565b50506000828152610103602052604081206001810154600284900a929083161115610b575780546001908101825581018054839003905560408051600160a060020a03331681526020810186905281517fc7fb647e59b18047309aa15aad418e5d7ca96d173ad704f1031a2c3d7591734b929181900390910190a15b5b50505050565b600036604051808383808284378201915050925050506040518091039020610b8581610d93565b1561056b57600154821115610b995761056b565b6000829055610ba6610f32565b6040805183815290517facbdb084c721332ac59f9b8e392196c9eb0e4932862da8eb9beaf0dad4f550da9181900360200190a15b5b5b5050565b600082815261010360209081526040808320600160a060020a038516845261010290925282205482811515610c185760009350610c2c565b8160020a9050808360010154166000141593505b50505092915050565b6000600282600101610100811015610000570160005b505490505b919050565b600036604051808383808284378201915050925050506040518091039020610c7c81610d93565b1561056b5781600160a060020a0316ff5b5b5b5050565b6000600036604051808383808284378201915050925050506040518091039020610cbc81610d93565b15610b5757610cca83610415565b15610cd457610b57565b600160a060020a038416600090815261010260205260409020549150811515610cfc57610b57565b610d04610f32565b82600160a060020a0316600283610100811015610000570160005b5055600160a060020a0380851660008181526101026020908152604080832083905593871680835291849020869055835192835282015281517fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c929181900390910190a15b5b5b50505050565b6101055481565b600160a060020a033316600090815261010260205260408120548180821515610dbb57610f28565b60008581526101036020526040902080549092501515610e4f576000805483556001808401919091556101048054918201808255828015829011610e2457600083815260209020610e249181019083015b808211156107cd57600081556001016107b9565b5090565b5b50505060028301819055610104805487929081101561000057906000526020600020900160005b50555b8260020a90508082600101541660001415610f285760408051600160a060020a03331681526020810187905281517fe1c52dc63b719ade82e8bea94cc41a0d5d28e4aaf536adb5e9cccc9ff8c1aeda929181900390910190a1815460019011610f155760008581526101036020526040902060020154610104805490919081101561000057906000526020600020900160005b506000908190558581526101036020526040812081815560018082018390556002909101919091559350610f2856610f28565b8154600019018255600182018054821790555b5b5b505050919050565b6101045460005b81811015610ff557610108600061010483815481101561000057906000526020600020900160005b50548152602081019190915260400160009081208054600160a060020a0319168155600180820183905560028083018054858255939493909281161561010002600019011604601f819010610fb65750610fe8565b601f016020900490600052602060002090810190610fe891905b808211156107cd57600081556001016107b9565b5090565b5b5050505b600101610f39565b61056b6111a4565b5b5050565b60015b600154811015610476575b600154811080156110325750600281610100811015610000570160005b505415155b1561103f57600101611010565b5b600160015411801561106457506002600154610100811015610000570160005b5054155b156110785760018054600019019055611040565b6001548110801561109c57506002600154610100811015610000570160005b505415155b80156110b85750600281610100811015610000570160005b5054155b15611128576002600154610100811015610000570160005b5054600282610100811015610000570160005b5055806101026000600283610100811015610000570160005b505481526020019081526020016000208190555060006002600154610100811015610000570160005b50555b611005565b5b50565b600061113c33610415565b15610431576101075461114d611278565b111561116657600061010655611161611278565b610107555b610106548281011080159061118357506101055482610106540111155b1561119957506101068054820190556001610431565b5060005b5b5b919050565b6101045460005b818110156112215761010481815481101561000057906000526020600020900160005b50541561121857610103600061010483815481101561000057906000526020600020900160005b505481526020810191909152604001600090812081815560018101829055600201555b5b6001016111ab565b610104805460008083559190915261040e907f4c0be60200faa20559308cb7b5a1bb3255c16cb1cab91f525b5ae7a03d02fabe908101905b808211156107cd57600081556001016107b9565b5090565b5b505b5050565b6201518042045b9056'; diff --git a/js/src/modals/CreateWallet/createWalletStore.js b/js/src/modals/CreateWallet/createWalletStore.js index e6edab56b..7f516eb25 100644 --- a/js/src/modals/CreateWallet/createWalletStore.js +++ b/js/src/modals/CreateWallet/createWalletStore.js @@ -23,7 +23,7 @@ import Contract from '~/api/contract'; import Contracts from '~/contracts'; import { wallet as walletAbi } from '~/contracts/abi'; import { wallet as walletCode } from '~/contracts/code'; -import { walletLibraryRegKey } from '~/contracts/code/wallet'; +import { walletLibraryRegKey, fullWalletCode } from '~/contracts/code/wallet'; import WalletsUtils from '~/util/wallets'; @@ -167,8 +167,10 @@ export default class CreateWalletStore { .registry .lookupAddress(walletLibraryRegKey) .then((address) => { - const walletLibraryAddress = address.replace(/^0x/, '').toLowerCase(); - const code = walletCode.replace(/(_)+WalletLibrary(_)+/g, walletLibraryAddress); + const walletLibraryAddress = (address || '').replace(/^0x/, '').toLowerCase(); + const code = walletLibraryAddress.length && !/^0+$/.test(walletLibraryAddress) + ? walletCode.replace(/(_)+WalletLibrary(_)+/g, walletLibraryAddress) + : fullWalletCode; const options = { data: code, From ff11634e1d4e2c0dc7945719c40f7d1952d9439e Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac <ngotchac@gmail.com> Date: Fri, 9 Dec 2016 16:55:43 +0100 Subject: [PATCH 49/63] PR Grumble --- js/src/modals/CreateWallet/createWalletStore.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/js/src/modals/CreateWallet/createWalletStore.js b/js/src/modals/CreateWallet/createWalletStore.js index 5e6e8a81a..3edf8f638 100644 --- a/js/src/modals/CreateWallet/createWalletStore.js +++ b/js/src/modals/CreateWallet/createWalletStore.js @@ -20,8 +20,7 @@ import Contract from '~/api/contract'; import Contracts from '~/contracts'; import { ERROR_CODES } from '~/api/transport/error'; import { wallet as walletAbi } from '~/contracts/abi'; -import { wallet as walletCode } from '~/contracts/code'; -import { walletLibraryRegKey, fullWalletCode } from '~/contracts/code/wallet'; +import { wallet as walletCode, walletLibraryRegKey, fullWalletCode } from '~/contracts/code/wallet'; import { validateUint, validateAddress, validateName } from '~/util/validation'; import WalletsUtils from '~/util/wallets'; From ffd8314a115b49c9dc7eeff582bd480fbaec0a8e Mon Sep 17 00:00:00 2001 From: Jaco Greeff <jacogr@gmail.com> Date: Fri, 9 Dec 2016 17:52:25 +0100 Subject: [PATCH 50/63] Be lenient around invalid owners map (#3764) * Be lenient around invalid owners map * Filter invalid owners before render --- js/src/views/Accounts/Summary/summary.js | 6 ++++-- js/src/views/Accounts/accounts.js | 18 +++++++++++------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/js/src/views/Accounts/Summary/summary.js b/js/src/views/Accounts/Summary/summary.js index 764f24edf..aeff8a2e5 100644 --- a/js/src/views/Accounts/Summary/summary.js +++ b/js/src/views/Accounts/Summary/summary.js @@ -14,6 +14,7 @@ // 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, { Component, PropTypes } from 'react'; import { Link } from 'react-router'; import { isEqual } from 'lodash'; @@ -113,15 +114,16 @@ export default class Summary extends Component { renderOwners () { const { owners } = this.props; + const ownersValid = (owners || []).filter((owner) => owner.address && new BigNumber(owner.address).gt(0)); - if (!owners || owners.length === 0) { + if (!ownersValid || ownersValid.length === 0) { return null; } return ( <div className={ styles.owners }> { - owners.map((owner) => ( + ownersValid.map((owner) => ( <div key={ owner.address }> <div data-tip diff --git a/js/src/views/Accounts/accounts.js b/js/src/views/Accounts/accounts.js index 4383b7662..a844ae989 100644 --- a/js/src/views/Accounts/accounts.js +++ b/js/src/views/Accounts/accounts.js @@ -293,13 +293,17 @@ function mapStateToProps (state) { const walletsOwners = Object .keys(walletsInfo) - .map((wallet) => ({ - owners: walletsInfo[wallet].owners.map((owner) => ({ - address: owner, - name: accountsInfo[owner] && accountsInfo[owner].name || owner - })), - address: wallet - })) + .map((wallet) => { + const owners = walletsInfo[wallet].owners || []; + + return { + owners: owners.map((owner) => ({ + address: owner, + name: accountsInfo[owner] && accountsInfo[owner].name || owner + })), + address: wallet + }; + }) .reduce((walletsOwners, wallet) => { walletsOwners[wallet.address] = wallet.owners; return walletsOwners; From fd23a2972c08455c61fc83e6af80407ee4429a88 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot <jaco+gitlab@ethcore.io> Date: Fri, 9 Dec 2016 17:00:59 +0000 Subject: [PATCH 51/63] [ci skip] js-precompiled 20161209-165845 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d0664d801..675d94ec4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1271,7 +1271,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#8e8e515f958d2d4a5abec07253a51a052f2b744d" +source = "git+https://github.com/ethcore/js-precompiled.git#b8e8e9a8482a51b9a86bb841674f71aca1e57934" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index ab501f6a7..77c9686a5 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.103", + "version": "0.2.104", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team <admin@parity.io>", From 1b0945940bb4e2162577696167aeb0b4b9745f18 Mon Sep 17 00:00:00 2001 From: Jaco Greeff <jaco@ethcore.io> Date: Fri, 9 Dec 2016 18:14:05 +0100 Subject: [PATCH 52/63] Test for boolean result before unsubscribe --- js/src/api/contract/contract.js | 2 +- js/src/api/subscriptions/manager.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/js/src/api/contract/contract.js b/js/src/api/contract/contract.js index 873dac38c..58dd62e7b 100644 --- a/js/src/api/contract/contract.js +++ b/js/src/api/contract/contract.js @@ -325,7 +325,7 @@ export default class Contract { console.warn('_sendData', subscriptionId, error); } - if (autoRemove && !result) { + if (autoRemove && !result && typeof result === 'boolean') { this.unsubscribe(subscriptionId); } } diff --git a/js/src/api/subscriptions/manager.js b/js/src/api/subscriptions/manager.js index a2716684b..f1afe685a 100644 --- a/js/src/api/subscriptions/manager.js +++ b/js/src/api/subscriptions/manager.js @@ -111,7 +111,7 @@ export default class Manager { console.error(`Unable to update callback for subscriptionId ${subscriptionId}`, error); } - if (autoRemove && !result) { + if (autoRemove && !result && typeof result === 'boolean') { this.unsubscribe(subscriptionId); } } From 0cbef3050893f3b577cd995cbb7d0dc9304dea2a Mon Sep 17 00:00:00 2001 From: Jaco Greeff <jaco@ethcore.io> Date: Fri, 9 Dec 2016 18:17:31 +0100 Subject: [PATCH 53/63] Unsubscribe on true --- js/src/api/contract/contract.js | 4 ++-- js/src/api/subscriptions/manager.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/js/src/api/contract/contract.js b/js/src/api/contract/contract.js index 58dd62e7b..bfe7cabc4 100644 --- a/js/src/api/contract/contract.js +++ b/js/src/api/contract/contract.js @@ -317,7 +317,7 @@ export default class Contract { _sendData (subscriptionId, error, logs) { const { autoRemove, callback } = this._subscriptions[subscriptionId]; - let result = false; + let result = true; try { result = callback(error, logs); @@ -325,7 +325,7 @@ export default class Contract { console.warn('_sendData', subscriptionId, error); } - if (autoRemove && !result && typeof result === 'boolean') { + if (autoRemove && result && typeof result === 'boolean') { this.unsubscribe(subscriptionId); } } diff --git a/js/src/api/subscriptions/manager.js b/js/src/api/subscriptions/manager.js index f1afe685a..25e6e6129 100644 --- a/js/src/api/subscriptions/manager.js +++ b/js/src/api/subscriptions/manager.js @@ -103,7 +103,7 @@ export default class Manager { _sendData (subscriptionId, error, data) { const { autoRemove, callback } = this.subscriptions[subscriptionId]; - let result = false; + let result = true; try { result = callback(error, data); @@ -111,7 +111,7 @@ export default class Manager { console.error(`Unable to update callback for subscriptionId ${subscriptionId}`, error); } - if (autoRemove && !result && typeof result === 'boolean') { + if (autoRemove && result && typeof result === 'boolean') { this.unsubscribe(subscriptionId); } } From d5d9d78bd974e1b2ba1dc3da798ed9f7f10b50b6 Mon Sep 17 00:00:00 2001 From: Gav Wood <gavin@ethcore.io> Date: Fri, 9 Dec 2016 18:58:37 +0100 Subject: [PATCH 54/63] It's not either-or. --- .gitlab-ci.yml | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 14f7b41e6..55d143a47 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -422,13 +422,14 @@ test-rust-stable: image: ethcore/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 \.rs | wc -l) - 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 "skip js test"; else ./js/scripts/install-deps.sh;fi + - 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 - - echo $JS_FILES_MODIFIED - - if [ "$JS_FILES_MODIFIED" = 0 ]; then echo "skip js test" && ./test.sh $CARGOFLAGS; else echo "skip rust test" && ./js/scripts/lint.sh && ./js/scripts/test.sh && ./js/scripts/build.sh; fi + - 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 - rust-stable @@ -439,11 +440,11 @@ js-test: - 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 "skip js test"; else ./js/scripts/install-deps.sh;fi + - 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 "skip js test"; else echo "skip rust test" && ./js/scripts/lint.sh && ./js/scripts/test.sh && ./js/scripts/build.sh; fi + - 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 - rust-stable @@ -486,9 +487,9 @@ js-release: before_script: - 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 "skip js build"; else ./js/scripts/install-deps.sh;fi + - if [ "$JS_FILES_MODIFIED" = 0 ]; then echo "Skipping JS deps install since no JS files modified."; else ./js/scripts/install-deps.sh;fi script: - echo $JS_FILES_MODIFIED - - if [ "$JS_FILES_MODIFIED" = 0 ]; then echo "skip js build"; else ./js/scripts/build.sh && ./js/scripts/release.sh; fi + - if [ "$JS_FILES_MODIFIED" = 0 ]; then echo "Skipping JS rebuild since no JS files modified."; else ./js/scripts/build.sh && ./js/scripts/release.sh; fi tags: - javascript From 5758bb7cac23ede07217601e014c17013c41934b Mon Sep 17 00:00:00 2001 From: Arkadiy Paronyan <arkady.paronyan@gmail.com> Date: Fri, 9 Dec 2016 19:11:00 +0100 Subject: [PATCH 55/63] Rust files are all non-js files --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 55d143a47..e78eabb18 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -422,7 +422,7 @@ test-rust-stable: image: ethcore/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 \.rs | wc -l) + - 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 From cf0b1bb9b2a1c73e81f178c919e14816daffde37 Mon Sep 17 00:00:00 2001 From: Gav Wood <gavin@ethcore.io> Date: Fri, 9 Dec 2016 19:21:39 +0100 Subject: [PATCH 56/63] use path prefix --- .gitlab-ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e78eabb18..d27b58f9a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -422,8 +422,8 @@ test-rust-stable: image: ethcore/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) + - 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: @@ -438,7 +438,7 @@ js-test: image: ethcore/rust:stable 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) + - 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: @@ -485,7 +485,7 @@ js-release: - stable image: ethcore/rust:stable before_script: - - export JS_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep \.js | wc -l) + - 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: From 647b62b4a2a2b2692e171fca30ccd00bf50c77e9 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot <jaco+gitlab@ethcore.io> Date: Fri, 9 Dec 2016 18:23:46 +0000 Subject: [PATCH 57/63] [ci skip] js-precompiled 20161209-182130 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 675d94ec4..58d3e1286 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1271,7 +1271,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#b8e8e9a8482a51b9a86bb841674f71aca1e57934" +source = "git+https://github.com/ethcore/js-precompiled.git#3d3b2f9e8e8b0fd62c172240bfd001a317cf2979" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index 77c9686a5..404a625b5 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.104", + "version": "0.2.105", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team <admin@parity.io>", From 97f358ced92898484b262c6901c7196a8f3acef2 Mon Sep 17 00:00:00 2001 From: arkpar <arkady.paronyan@gmail.com> Date: Tue, 6 Dec 2016 19:23:15 +0100 Subject: [PATCH 58/63] AuthorityRound network simulation test --- Cargo.lock | 2 + ethcore/res/authority_round.json | 3 +- ethcore/src/client/client.rs | 12 +- ethcore/src/client/test_client.rs | 4 +- ethcore/src/engines/authority_round.rs | 27 ++-- ethcore/src/engines/mod.rs | 2 + ethcore/src/spec/spec.rs | 2 +- json/src/spec/authority_round.rs | 7 +- sync/Cargo.toml | 2 + sync/src/lib.rs | 3 + sync/src/tests/chain.rs | 96 +++++++------- sync/src/tests/consensus.rs | 78 +++++++++++ sync/src/tests/helpers.rs | 171 +++++++++++++++++++------ sync/src/tests/mod.rs | 1 + sync/src/tests/snapshot.rs | 2 +- 15 files changed, 311 insertions(+), 101 deletions(-) create mode 100644 sync/src/tests/consensus.rs diff --git a/Cargo.lock b/Cargo.lock index 675d94ec4..5fa56cb26 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -660,12 +660,14 @@ dependencies = [ "clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "ethcore 1.5.0", + "ethcore-devtools 1.5.0", "ethcore-io 1.5.0", "ethcore-ipc 1.5.0", "ethcore-ipc-codegen 1.5.0", "ethcore-ipc-nano 1.5.0", "ethcore-network 1.5.0", "ethcore-util 1.5.0", + "ethkey 0.2.0", "heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/ethcore/res/authority_round.json b/ethcore/res/authority_round.json index a0f88b85b..85beb51b4 100644 --- a/ethcore/res/authority_round.json +++ b/ethcore/res/authority_round.json @@ -4,7 +4,8 @@ "AuthorityRound": { "params": { "gasLimitBoundDivisor": "0x0400", - "stepDuration": "1", + "stepDuration": 1, + "startStep": 2, "authorities" : [ "0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e", "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1" diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 21c5a2366..3ed6579c5 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -255,6 +255,11 @@ impl Client { self.notify.write().push(Arc::downgrade(&target)); } + /// Returns engine reference. + pub fn engine(&self) -> &Engine { + &*self.engine + } + fn notify<F>(&self, f: F) where F: Fn(&ChainNotify) { for np in self.notify.read().iter() { if let Some(n) = np.upgrade() { @@ -563,6 +568,11 @@ impl Client { results.len() } + /// Get shared miner reference. + pub fn miner(&self) -> Arc<Miner> { + self.miner.clone() + } + /// Used by PoA to try sealing on period change. pub fn update_sealing(&self) { self.miner.update_sealing(self) @@ -1433,4 +1443,4 @@ mod tests { assert!(client.tree_route(&genesis, &new_hash).is_none()); } -} \ No newline at end of file +} diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index dd00db7ec..8950a9e5c 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -255,7 +255,7 @@ impl TestBlockChainClient { } /// Make a bad block by setting invalid extra data. - pub fn corrupt_block(&mut self, n: BlockNumber) { + pub fn corrupt_block(&self, n: BlockNumber) { let hash = self.block_hash(BlockID::Number(n)).unwrap(); let mut header: BlockHeader = decode(&self.block_header(BlockID::Number(n)).unwrap()); header.set_extra_data(b"This extra data is way too long to be considered valid".to_vec()); @@ -267,7 +267,7 @@ impl TestBlockChainClient { } /// Make a bad block by setting invalid parent hash. - pub fn corrupt_block_parent(&mut self, n: BlockNumber) { + pub fn corrupt_block_parent(&self, n: BlockNumber) { let hash = self.block_hash(BlockID::Number(n)).unwrap(); let mut header: BlockHeader = decode(&self.block_header(BlockID::Number(n)).unwrap()); header.set_parent_hash(H256::from(42)); diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index f632c9382..21a6e4761 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -49,6 +49,8 @@ pub struct AuthorityRoundParams { pub authorities: Vec<Address>, /// Number of authorities. pub authority_n: usize, + /// Starting step, + pub start_step: Option<u64>, } impl From<ethjson::spec::AuthorityRoundParams> for AuthorityRoundParams { @@ -58,6 +60,7 @@ impl From<ethjson::spec::AuthorityRoundParams> for AuthorityRoundParams { step_duration: Duration::from_secs(p.step_duration.into()), authority_n: p.authorities.len(), authorities: p.authorities.into_iter().map(Into::into).collect::<Vec<_>>(), + start_step: p.start_step.map(Into::into), } } } @@ -97,7 +100,7 @@ impl AsMillis for Duration { impl AuthorityRound { /// Create a new instance of AuthorityRound engine. pub fn new(params: CommonParams, our_params: AuthorityRoundParams, builtins: BTreeMap<Address, Builtin>) -> Result<Arc<Self>, Error> { - let initial_step = (unix_now().as_secs() / our_params.step_duration.as_secs()) as usize; + let initial_step = our_params.start_step.unwrap_or_else(|| (unix_now().as_secs() / our_params.step_duration.as_secs())) as usize; let engine = Arc::new( AuthorityRound { params: params, @@ -160,14 +163,7 @@ impl IoHandler<()> for TransitionHandler { fn timeout(&self, io: &IoContext<()>, timer: TimerToken) { if timer == ENGINE_TIMEOUT_TOKEN { if let Some(engine) = self.engine.upgrade() { - engine.step.fetch_add(1, AtomicOrdering::SeqCst); - engine.proposed.store(false, AtomicOrdering::SeqCst); - if let Some(ref channel) = *engine.message_channel.lock() { - match channel.send(ClientIoMessage::UpdateSealing) { - Ok(_) => trace!(target: "poa", "timeout: UpdateSealing message sent for step {}.", engine.step.load(AtomicOrdering::Relaxed)), - Err(err) => trace!(target: "poa", "timeout: Could not send a sealing message {} for step {}.", err, engine.step.load(AtomicOrdering::Relaxed)), - } - } + engine.step(); io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.remaining_step_duration().as_millis()) .unwrap_or_else(|e| warn!(target: "poa", "Failed to restart consensus step timer: {}.", e)) } @@ -184,6 +180,17 @@ impl Engine for AuthorityRound { fn params(&self) -> &CommonParams { &self.params } fn builtins(&self) -> &BTreeMap<Address, Builtin> { &self.builtins } + fn step(&self) { + self.step.fetch_add(1, AtomicOrdering::SeqCst); + self.proposed.store(false, AtomicOrdering::SeqCst); + if let Some(ref channel) = *self.message_channel.lock() { + match channel.send(ClientIoMessage::UpdateSealing) { + Ok(_) => trace!(target: "poa", "timeout: UpdateSealing message sent for step {}.", self.step.load(AtomicOrdering::Relaxed)), + Err(err) => trace!(target: "poa", "timeout: Could not send a sealing message {} for step {}.", err, self.step.load(AtomicOrdering::Relaxed)), + } + } + } + /// Additional engine-specific information for the user/developer concerning `header`. fn extra_info(&self, header: &Header) -> BTreeMap<String, String> { map![ @@ -235,6 +242,8 @@ impl Engine for AuthorityRound { } else { warn!(target: "poa", "generate_seal: FAIL: Accounts not provided."); } + } else { + trace!(target: "poa", "generate_seal: Not a proposer for step {}.", step); } None } diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index d7ff06248..8e407f0b7 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -160,4 +160,6 @@ pub trait Engine : Sync + Send { /// Add an account provider useful for Engines that sign stuff. fn register_account_provider(&self, _account_provider: Arc<AccountProvider>) {} + /// Trigger next step of the consensus engine. + fn step(&self) {} } diff --git a/ethcore/src/spec/spec.rs b/ethcore/src/spec/spec.rs index c9ae087c0..e14ea3949 100644 --- a/ethcore/src/spec/spec.rs +++ b/ethcore/src/spec/spec.rs @@ -273,7 +273,7 @@ impl Spec { pub fn new_instant() -> Spec { load_bundled!("instant_seal") } /// Create a new Spec with AuthorityRound consensus which does internal sealing (not requiring work). - /// Accounts with secrets "1".sha3() and "2".sha3() are the authorities. + /// Accounts with secrets "0".sha3() and "1".sha3() are the authorities. pub fn new_test_round() -> Self { load_bundled!("authority_round") } } diff --git a/json/src/spec/authority_round.rs b/json/src/spec/authority_round.rs index 3d73ef1ef..bae17bb24 100644 --- a/json/src/spec/authority_round.rs +++ b/json/src/spec/authority_round.rs @@ -30,6 +30,10 @@ pub struct AuthorityRoundParams { pub step_duration: Uint, /// Valid authorities pub authorities: Vec<Address>, + /// Starting step. Determined automatically if not specified. + /// To be used for testing only. + #[serde(rename="startStep")] + pub start_step: Option<Uint>, } /// Authority engine deserialization. @@ -50,7 +54,8 @@ mod tests { "params": { "gasLimitBoundDivisor": "0x0400", "stepDuration": "0x02", - "authorities" : ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"] + "authorities" : ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"], + "startStep" : 24 } }"#; diff --git a/sync/Cargo.toml b/sync/Cargo.toml index 738f5f55c..d7980f0d9 100644 --- a/sync/Cargo.toml +++ b/sync/Cargo.toml @@ -26,6 +26,8 @@ heapsize = "0.3" ethcore-ipc = { path = "../ipc/rpc" } semver = "0.2" ethcore-ipc-nano = { path = "../ipc/nano" } +ethcore-devtools = { path = "../devtools" } +ethkey = { path = "../ethkey" } parking_lot = "0.3" [features] diff --git a/sync/src/lib.rs b/sync/src/lib.rs index 2061e4e3a..ced4c3f52 100644 --- a/sync/src/lib.rs +++ b/sync/src/lib.rs @@ -37,6 +37,9 @@ extern crate semver; extern crate parking_lot; extern crate rlp; +#[cfg(test)] extern crate ethcore_devtools as devtools; +#[cfg(test)] extern crate ethkey; + #[macro_use] extern crate log; #[macro_use] diff --git a/sync/src/tests/chain.rs b/sync/src/tests/chain.rs index 7705215f5..361d53e29 100644 --- a/sync/src/tests/chain.rs +++ b/sync/src/tests/chain.rs @@ -24,8 +24,8 @@ use SyncConfig; fn two_peers() { ::env_logger::init().ok(); let mut net = TestNet::new(3); - net.peer_mut(1).chain.add_blocks(1000, EachBlockWith::Uncle); - net.peer_mut(2).chain.add_blocks(1000, EachBlockWith::Uncle); + net.peer(1).chain.add_blocks(1000, EachBlockWith::Uncle); + net.peer(2).chain.add_blocks(1000, EachBlockWith::Uncle); net.sync(); assert!(net.peer(0).chain.block(BlockID::Number(1000)).is_some()); assert_eq!(*net.peer(0).chain.blocks.read(), *net.peer(1).chain.blocks.read()); @@ -35,7 +35,7 @@ fn two_peers() { fn long_chain() { ::env_logger::init().ok(); let mut net = TestNet::new(2); - net.peer_mut(1).chain.add_blocks(50000, EachBlockWith::Nothing); + net.peer(1).chain.add_blocks(50000, EachBlockWith::Nothing); net.sync(); assert!(net.peer(0).chain.block(BlockID::Number(50000)).is_some()); assert_eq!(*net.peer(0).chain.blocks.read(), *net.peer(1).chain.blocks.read()); @@ -45,8 +45,8 @@ fn long_chain() { fn status_after_sync() { ::env_logger::init().ok(); let mut net = TestNet::new(3); - net.peer_mut(1).chain.add_blocks(1000, EachBlockWith::Uncle); - net.peer_mut(2).chain.add_blocks(1000, EachBlockWith::Uncle); + net.peer(1).chain.add_blocks(1000, EachBlockWith::Uncle); + net.peer(2).chain.add_blocks(1000, EachBlockWith::Uncle); net.sync(); let status = net.peer(0).sync.read().status(); assert_eq!(status.state, SyncState::Idle); @@ -55,8 +55,8 @@ fn status_after_sync() { #[test] fn takes_few_steps() { let mut net = TestNet::new(3); - net.peer_mut(1).chain.add_blocks(100, EachBlockWith::Uncle); - net.peer_mut(2).chain.add_blocks(100, EachBlockWith::Uncle); + net.peer(1).chain.add_blocks(100, EachBlockWith::Uncle); + net.peer(2).chain.add_blocks(100, EachBlockWith::Uncle); let total_steps = net.sync(); assert!(total_steps < 20); } @@ -67,8 +67,8 @@ fn empty_blocks() { let mut net = TestNet::new(3); for n in 0..200 { let with = if n % 2 == 0 { EachBlockWith::Nothing } else { EachBlockWith::Uncle }; - net.peer_mut(1).chain.add_blocks(5, with.clone()); - net.peer_mut(2).chain.add_blocks(5, with); + net.peer(1).chain.add_blocks(5, with.clone()); + net.peer(2).chain.add_blocks(5, with); } net.sync(); assert!(net.peer(0).chain.block(BlockID::Number(1000)).is_some()); @@ -79,14 +79,14 @@ fn empty_blocks() { fn forked() { ::env_logger::init().ok(); let mut net = TestNet::new(3); - net.peer_mut(0).chain.add_blocks(30, EachBlockWith::Uncle); - net.peer_mut(1).chain.add_blocks(30, EachBlockWith::Uncle); - net.peer_mut(2).chain.add_blocks(30, EachBlockWith::Uncle); - net.peer_mut(0).chain.add_blocks(10, EachBlockWith::Nothing); //fork - net.peer_mut(1).chain.add_blocks(20, EachBlockWith::Uncle); - net.peer_mut(2).chain.add_blocks(20, EachBlockWith::Uncle); - net.peer_mut(1).chain.add_blocks(10, EachBlockWith::Uncle); //fork between 1 and 2 - net.peer_mut(2).chain.add_blocks(1, EachBlockWith::Nothing); + net.peer(0).chain.add_blocks(30, EachBlockWith::Uncle); + net.peer(1).chain.add_blocks(30, EachBlockWith::Uncle); + net.peer(2).chain.add_blocks(30, EachBlockWith::Uncle); + net.peer(0).chain.add_blocks(10, EachBlockWith::Nothing); //fork + net.peer(1).chain.add_blocks(20, EachBlockWith::Uncle); + net.peer(2).chain.add_blocks(20, EachBlockWith::Uncle); + net.peer(1).chain.add_blocks(10, EachBlockWith::Uncle); //fork between 1 and 2 + net.peer(2).chain.add_blocks(1, EachBlockWith::Nothing); // peer 1 has the best chain of 601 blocks let peer1_chain = net.peer(1).chain.numbers.read().clone(); net.sync(); @@ -102,12 +102,12 @@ fn forked_with_misbehaving_peer() { let mut net = TestNet::new(3); // peer 0 is on a totally different chain with higher total difficulty net.peer_mut(0).chain = TestBlockChainClient::new_with_extra_data(b"fork".to_vec()); - net.peer_mut(0).chain.add_blocks(50, EachBlockWith::Nothing); - net.peer_mut(1).chain.add_blocks(10, EachBlockWith::Nothing); - net.peer_mut(2).chain.add_blocks(10, EachBlockWith::Nothing); + net.peer(0).chain.add_blocks(50, EachBlockWith::Nothing); + net.peer(1).chain.add_blocks(10, EachBlockWith::Nothing); + net.peer(2).chain.add_blocks(10, EachBlockWith::Nothing); - net.peer_mut(1).chain.add_blocks(10, EachBlockWith::Nothing); - net.peer_mut(2).chain.add_blocks(20, EachBlockWith::Uncle); + net.peer(1).chain.add_blocks(10, EachBlockWith::Nothing); + net.peer(2).chain.add_blocks(20, EachBlockWith::Uncle); // peer 1 should sync to peer 2, others should not change let peer0_chain = net.peer(0).chain.numbers.read().clone(); let peer2_chain = net.peer(2).chain.numbers.read().clone(); @@ -124,13 +124,13 @@ fn net_hard_fork() { ref_client.add_blocks(50, EachBlockWith::Uncle); { let mut net = TestNet::new_with_fork(2, Some((50, ref_client.block_hash(BlockID::Number(50)).unwrap()))); - net.peer_mut(0).chain.add_blocks(100, EachBlockWith::Uncle); + net.peer(0).chain.add_blocks(100, EachBlockWith::Uncle); net.sync(); assert_eq!(net.peer(1).chain.chain_info().best_block_number, 100); } { let mut net = TestNet::new_with_fork(2, Some((50, ref_client.block_hash(BlockID::Number(50)).unwrap()))); - net.peer_mut(0).chain.add_blocks(100, EachBlockWith::Nothing); + net.peer(0).chain.add_blocks(100, EachBlockWith::Nothing); net.sync(); assert_eq!(net.peer(1).chain.chain_info().best_block_number, 0); } @@ -140,8 +140,8 @@ fn net_hard_fork() { fn restart() { ::env_logger::init().ok(); let mut net = TestNet::new(3); - net.peer_mut(1).chain.add_blocks(1000, EachBlockWith::Uncle); - net.peer_mut(2).chain.add_blocks(1000, EachBlockWith::Uncle); + net.peer(1).chain.add_blocks(1000, EachBlockWith::Uncle); + net.peer(2).chain.add_blocks(1000, EachBlockWith::Uncle); net.sync(); @@ -166,37 +166,37 @@ fn status_empty() { #[test] fn status_packet() { let mut net = TestNet::new(2); - net.peer_mut(0).chain.add_blocks(100, EachBlockWith::Uncle); - net.peer_mut(1).chain.add_blocks(1, EachBlockWith::Uncle); + net.peer(0).chain.add_blocks(100, EachBlockWith::Uncle); + net.peer(1).chain.add_blocks(1, EachBlockWith::Uncle); net.start(); net.sync_step_peer(0); - assert_eq!(1, net.peer(0).queue.len()); - assert_eq!(0x00, net.peer(0).queue[0].packet_id); + assert_eq!(1, net.peer(0).queue.read().len()); + assert_eq!(0x00, net.peer(0).queue.read()[0].packet_id); } #[test] fn propagate_hashes() { let mut net = TestNet::new(6); - net.peer_mut(1).chain.add_blocks(10, EachBlockWith::Uncle); + net.peer(1).chain.add_blocks(10, EachBlockWith::Uncle); net.sync(); - net.peer_mut(0).chain.add_blocks(10, EachBlockWith::Uncle); + net.peer(0).chain.add_blocks(10, EachBlockWith::Uncle); net.sync(); net.trigger_chain_new_blocks(0); //first event just sets the marker net.trigger_chain_new_blocks(0); // 5 peers with NewHahses, 4 with blocks - assert_eq!(9, net.peer(0).queue.len()); + assert_eq!(9, net.peer(0).queue.read().len()); let mut hashes = 0; let mut blocks = 0; - for i in 0..net.peer(0).queue.len() { - if net.peer(0).queue[i].packet_id == 0x1 { + for i in 0..net.peer(0).queue.read().len() { + if net.peer(0).queue.read()[i].packet_id == 0x1 { hashes += 1; } - if net.peer(0).queue[i].packet_id == 0x7 { + if net.peer(0).queue.read()[i].packet_id == 0x7 { blocks += 1; } } @@ -207,24 +207,24 @@ fn propagate_hashes() { #[test] fn propagate_blocks() { let mut net = TestNet::new(20); - net.peer_mut(1).chain.add_blocks(10, EachBlockWith::Uncle); + net.peer(1).chain.add_blocks(10, EachBlockWith::Uncle); net.sync(); - net.peer_mut(0).chain.add_blocks(10, EachBlockWith::Uncle); + net.peer(0).chain.add_blocks(10, EachBlockWith::Uncle); net.trigger_chain_new_blocks(0); //first event just sets the marker net.trigger_chain_new_blocks(0); - assert!(!net.peer(0).queue.is_empty()); + assert!(!net.peer(0).queue.read().is_empty()); // NEW_BLOCK_PACKET - let blocks = net.peer(0).queue.iter().filter(|p| p.packet_id == 0x7).count(); + let blocks = net.peer(0).queue.read().iter().filter(|p| p.packet_id == 0x7).count(); assert!(blocks > 0); } #[test] fn restart_on_malformed_block() { let mut net = TestNet::new(2); - net.peer_mut(1).chain.add_blocks(10, EachBlockWith::Uncle); - net.peer_mut(1).chain.corrupt_block(6); + net.peer(1).chain.add_blocks(10, EachBlockWith::Uncle); + net.peer(1).chain.corrupt_block(6); net.sync_steps(20); assert_eq!(net.peer(0).chain.chain_info().best_block_number, 5); @@ -233,8 +233,8 @@ fn restart_on_malformed_block() { #[test] fn restart_on_broken_chain() { let mut net = TestNet::new(2); - net.peer_mut(1).chain.add_blocks(10, EachBlockWith::Uncle); - net.peer_mut(1).chain.corrupt_block_parent(6); + net.peer(1).chain.add_blocks(10, EachBlockWith::Uncle); + net.peer(1).chain.corrupt_block_parent(6); net.sync_steps(20); assert_eq!(net.peer(0).chain.chain_info().best_block_number, 5); @@ -243,8 +243,8 @@ fn restart_on_broken_chain() { #[test] fn high_td_attach() { let mut net = TestNet::new(2); - net.peer_mut(1).chain.add_blocks(10, EachBlockWith::Uncle); - net.peer_mut(1).chain.corrupt_block_parent(6); + net.peer(1).chain.add_blocks(10, EachBlockWith::Uncle); + net.peer(1).chain.corrupt_block_parent(6); net.sync_steps(20); assert_eq!(net.peer(0).chain.chain_info().best_block_number, 5); @@ -255,8 +255,8 @@ fn high_td_attach() { fn disconnect_on_unrelated_chain() { ::env_logger::init().ok(); let mut net = TestNet::new(2); - net.peer_mut(0).chain.add_blocks(200, EachBlockWith::Uncle); - net.peer_mut(1).chain.add_blocks(100, EachBlockWith::Nothing); + net.peer(0).chain.add_blocks(200, EachBlockWith::Uncle); + net.peer(1).chain.add_blocks(100, EachBlockWith::Nothing); net.sync(); assert_eq!(net.disconnect_events, vec![(0, 0)]); } diff --git a/sync/src/tests/consensus.rs b/sync/src/tests/consensus.rs new file mode 100644 index 000000000..00a036a54 --- /dev/null +++ b/sync/src/tests/consensus.rs @@ -0,0 +1,78 @@ +// 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/>. + +use util::*; +use ethcore::client::BlockChainClient; +use ethcore::spec::Spec; +use ethcore::miner::MinerService; +use ethcore::transaction::*; +use ethcore::account_provider::AccountProvider; +use ethkey::KeyPair; +use super::helpers::*; +use SyncConfig; + +#[test] +fn test_authority_round() { + ::env_logger::init().ok(); + + let s1 = KeyPair::from_secret("1".sha3()).unwrap(); + let s2 = KeyPair::from_secret("0".sha3()).unwrap(); + let spec_factory = || { + let spec = Spec::new_test_round(); + let account_provider = AccountProvider::transient_provider(); + account_provider.insert_account(s1.secret().clone(), "").unwrap(); + account_provider.insert_account(s2.secret().clone(), "").unwrap(); + spec.engine.register_account_provider(Arc::new(account_provider)); + spec + }; + let mut net = TestNet::new_with_spec(2, SyncConfig::default(), spec_factory); + let mut net = &mut *net; + // Push transaction to both clients. Only one of them gets lucky to mine a block. + net.peer(0).chain.miner().set_author(s1.address()); + net.peer(0).chain.engine().set_signer(s1.address(), "".to_owned()); + net.peer(1).chain.miner().set_author(s2.address()); + net.peer(1).chain.engine().set_signer(s2.address(), "".to_owned()); + let tx1 = Transaction { + nonce: 0.into(), + gas_price: 0.into(), + gas: 21000.into(), + action: Action::Call(Address::default()), + value: 0.into(), + data: Vec::new(), + }.sign(s1.secret(), None); + // exhange statuses + net.sync_steps(5); + net.peer(0).chain.miner().import_own_transaction(&net.peer(0).chain, tx1).unwrap(); + net.sync(); + assert_eq!(net.peer(0).chain.chain_info().best_block_number, 1); + assert_eq!(net.peer(1).chain.chain_info().best_block_number, 1); + + let tx2 = Transaction { + nonce: 0.into(), + gas_price: 0.into(), + gas: 21000.into(), + action: Action::Call(Address::default()), + value: 0.into(), + data: Vec::new(), + }.sign(s2.secret(), None); + net.peer(1).chain.miner().import_own_transaction(&net.peer(1).chain, tx2).unwrap(); + net.peer(1).chain.engine().step(); + net.peer(1).chain.miner().update_sealing(&net.peer(1).chain); + net.sync(); + assert_eq!(net.peer(0).chain.chain_info().best_block_number, 2); + assert_eq!(net.peer(1).chain.chain_info().best_block_number, 2); +} + diff --git a/sync/src/tests/helpers.rs b/sync/src/tests/helpers.rs index 10c1277a6..d2eed9374 100644 --- a/sync/src/tests/helpers.rs +++ b/sync/src/tests/helpers.rs @@ -17,16 +17,33 @@ use util::*; use network::*; use tests::snapshot::*; -use ethcore::client::{TestBlockChainClient, BlockChainClient}; +use ethcore::client::{TestBlockChainClient, BlockChainClient, Client as EthcoreClient, ClientConfig, ChainNotify}; use ethcore::header::BlockNumber; use ethcore::snapshot::SnapshotService; +use ethcore::spec::Spec; +use ethcore::miner::Miner; +use ethcore::db::NUM_COLUMNS; use sync_io::SyncIo; +use io::IoChannel; use api::WARP_SYNC_PROTOCOL_ID; use chain::ChainSync; use ::SyncConfig; +use devtools::{self, GuardedTempResult}; -pub struct TestIo<'p> { - pub chain: &'p mut TestBlockChainClient, +pub trait FlushingBlockChainClient: BlockChainClient { + fn flush(&self) {} +} + +impl FlushingBlockChainClient for EthcoreClient { + fn flush(&self) { + self.flush_queue(); + } +} + +impl FlushingBlockChainClient for TestBlockChainClient {} + +pub struct TestIo<'p, C> where C: FlushingBlockChainClient, C: 'p { + pub chain: &'p C, pub snapshot_service: &'p TestSnapshotService, pub queue: &'p mut VecDeque<TestPacket>, pub sender: Option<PeerId>, @@ -34,8 +51,8 @@ pub struct TestIo<'p> { overlay: RwLock<HashMap<BlockNumber, Bytes>>, } -impl<'p> TestIo<'p> { - pub fn new(chain: &'p mut TestBlockChainClient, ss: &'p TestSnapshotService, queue: &'p mut VecDeque<TestPacket>, sender: Option<PeerId>) -> TestIo<'p> { +impl<'p, C> TestIo<'p, C> where C: FlushingBlockChainClient, C: 'p { + pub fn new(chain: &'p C, ss: &'p TestSnapshotService, queue: &'p mut VecDeque<TestPacket>, sender: Option<PeerId>) -> TestIo<'p, C> { TestIo { chain: chain, snapshot_service: ss, @@ -47,7 +64,7 @@ impl<'p> TestIo<'p> { } } -impl<'p> SyncIo for TestIo<'p> { +impl<'p, C> SyncIo for TestIo<'p, C> where C: FlushingBlockChainClient, C: 'p { fn disable_peer(&mut self, peer_id: PeerId) { self.disconnect_peer(peer_id); } @@ -99,7 +116,7 @@ impl<'p> SyncIo for TestIo<'p> { } fn protocol_version(&self, protocol: &ProtocolId, peer_id: PeerId) -> u8 { - if protocol == &WARP_SYNC_PROTOCOL_ID { 1 } else { self.eth_protocol_version(peer_id) } + if protocol == &WARP_SYNC_PROTOCOL_ID { 2 } else { self.eth_protocol_version(peer_id) } } fn chain_overlay(&self) -> &RwLock<HashMap<BlockNumber, Bytes>> { @@ -113,31 +130,31 @@ pub struct TestPacket { pub recipient: PeerId, } -pub struct TestPeer { - pub chain: TestBlockChainClient, +pub struct TestPeer<C> where C: FlushingBlockChainClient { + pub chain: C, pub snapshot_service: Arc<TestSnapshotService>, pub sync: RwLock<ChainSync>, - pub queue: VecDeque<TestPacket>, + pub queue: RwLock<VecDeque<TestPacket>>, } -pub struct TestNet { - pub peers: Vec<TestPeer>, +pub struct TestNet<C> where C: FlushingBlockChainClient { + pub peers: Vec<Arc<TestPeer<C>>>, pub started: bool, pub disconnect_events: Vec<(PeerId, PeerId)>, //disconnected (initiated by, to) } -impl TestNet { - pub fn new(n: usize) -> TestNet { +impl TestNet<TestBlockChainClient> { + pub fn new(n: usize) -> TestNet<TestBlockChainClient> { Self::new_with_config(n, SyncConfig::default()) } - pub fn new_with_fork(n: usize, fork: Option<(BlockNumber, H256)>) -> TestNet { + pub fn new_with_fork(n: usize, fork: Option<(BlockNumber, H256)>) -> TestNet<TestBlockChainClient> { let mut config = SyncConfig::default(); config.fork_block = fork; Self::new_with_config(n, config) } - pub fn new_with_config(n: usize, config: SyncConfig) -> TestNet { + pub fn new_with_config(n: usize, config: SyncConfig) -> TestNet<TestBlockChainClient> { let mut net = TestNet { peers: Vec::new(), started: false, @@ -147,31 +164,77 @@ impl TestNet { let chain = TestBlockChainClient::new(); let ss = Arc::new(TestSnapshotService::new()); let sync = ChainSync::new(config.clone(), &chain); - net.peers.push(TestPeer { + net.peers.push(Arc::new(TestPeer { sync: RwLock::new(sync), snapshot_service: ss, chain: chain, - queue: VecDeque::new(), - }); + queue: RwLock::new(VecDeque::new()), + })); } net } +} - pub fn peer(&self, i: usize) -> &TestPeer { +impl TestNet<EthcoreClient> { + pub fn new_with_spec<F>(n: usize, config: SyncConfig, spec_factory: F) -> GuardedTempResult<TestNet<EthcoreClient>> + where F: Fn() -> Spec + { + let mut net = TestNet { + peers: Vec::new(), + started: false, + disconnect_events: Vec::new(), + }; + let dir = devtools::RandomTempPath::new(); + for _ in 0..n { + let mut client_dir = dir.as_path().clone(); + client_dir.push(devtools::random_filename()); + + let db_config = DatabaseConfig::with_columns(NUM_COLUMNS); + + let spec = spec_factory(); + let client = Arc::try_unwrap(EthcoreClient::new( + ClientConfig::default(), + &spec, + client_dir.as_path(), + Arc::new(Miner::with_spec(&spec)), + IoChannel::disconnected(), + &db_config + ).unwrap()).ok().unwrap(); + + let ss = Arc::new(TestSnapshotService::new()); + let sync = ChainSync::new(config.clone(), &client); + let peer = Arc::new(TestPeer { + sync: RwLock::new(sync), + snapshot_service: ss, + chain: client, + queue: RwLock::new(VecDeque::new()), + }); + peer.chain.add_notify(peer.clone()); + net.peers.push(peer); + } + GuardedTempResult::<TestNet<EthcoreClient>> { + _temp: dir, + result: Some(net) + } + } +} + +impl<C> TestNet<C> where C: FlushingBlockChainClient { + pub fn peer(&self, i: usize) -> &TestPeer<C> { &self.peers[i] } - pub fn peer_mut(&mut self, i: usize) -> &mut TestPeer { - &mut self.peers[i] + pub fn peer_mut(&mut self, i: usize) -> &mut TestPeer<C> { + Arc::get_mut(&mut self.peers[i]).unwrap() } pub fn start(&mut self) { for peer in 0..self.peers.len() { for client in 0..self.peers.len() { if peer != client { - let mut p = &mut self.peers[peer]; + let p = &self.peers[peer]; p.sync.write().update_targets(&p.chain); - p.sync.write().on_peer_connected(&mut TestIo::new(&mut p.chain, &p.snapshot_service, &mut p.queue, Some(client as PeerId)), client as PeerId); + p.sync.write().on_peer_connected(&mut TestIo::new(&p.chain, &p.snapshot_service, &mut p.queue.write(), Some(client as PeerId)), client as PeerId); } } } @@ -179,18 +242,20 @@ impl TestNet { pub fn sync_step(&mut self) { for peer in 0..self.peers.len() { - if let Some(packet) = self.peers[peer].queue.pop_front() { + let packet = self.peers[peer].queue.write().pop_front(); + if let Some(packet) = packet { let disconnecting = { - let mut p = &mut self.peers[packet.recipient]; + let p = &self.peers[packet.recipient]; + let mut queue = p.queue.write(); trace!("--- {} -> {} ---", peer, packet.recipient); let to_disconnect = { - let mut io = TestIo::new(&mut p.chain, &p.snapshot_service, &mut p.queue, Some(peer as PeerId)); + let mut io = TestIo::new(&p.chain, &p.snapshot_service, &mut queue, Some(peer as PeerId)); ChainSync::dispatch_packet(&p.sync, &mut io, peer as PeerId, packet.packet_id, &packet.data); io.to_disconnect }; for d in &to_disconnect { // notify this that disconnecting peers are disconnecting - let mut io = TestIo::new(&mut p.chain, &p.snapshot_service, &mut p.queue, Some(*d)); + let mut io = TestIo::new(&p.chain, &p.snapshot_service, &mut queue, Some(*d)); p.sync.write().on_peer_aborting(&mut io, *d); self.disconnect_events.push((peer, *d)); } @@ -198,8 +263,9 @@ impl TestNet { }; for d in &disconnecting { // notify other peers that this peer is disconnecting - let mut p = &mut self.peers[*d]; - let mut io = TestIo::new(&mut p.chain, &p.snapshot_service, &mut p.queue, Some(peer as PeerId)); + let p = &self.peers[*d]; + let mut queue = p.queue.write(); + let mut io = TestIo::new(&p.chain, &p.snapshot_service, &mut queue, Some(peer as PeerId)); p.sync.write().on_peer_aborting(&mut io, peer as PeerId); } } @@ -209,13 +275,17 @@ impl TestNet { } pub fn sync_step_peer(&mut self, peer_num: usize) { - let mut peer = self.peer_mut(peer_num); - peer.sync.write().maintain_sync(&mut TestIo::new(&mut peer.chain, &peer.snapshot_service, &mut peer.queue, None)); + let peer = self.peer(peer_num); + peer.chain.flush(); + let mut queue = peer.queue.write(); + peer.sync.write().maintain_peers(&mut TestIo::new(&peer.chain, &peer.snapshot_service, &mut queue, None)); + peer.sync.write().maintain_sync(&mut TestIo::new(&peer.chain, &peer.snapshot_service, &mut queue, None)); + peer.sync.write().propagate_new_transactions(&mut TestIo::new(&peer.chain, &peer.snapshot_service, &mut queue, None)); } pub fn restart_peer(&mut self, i: usize) { - let peer = self.peer_mut(i); - peer.sync.write().restart(&mut TestIo::new(&mut peer.chain, &peer.snapshot_service, &mut peer.queue, None)); + let peer = self.peer(i); + peer.sync.write().restart(&mut TestIo::new(&peer.chain, &peer.snapshot_service, &mut peer.queue.write(), None)); } pub fn sync(&mut self) -> u32 { @@ -239,11 +309,38 @@ impl TestNet { } pub fn done(&self) -> bool { - self.peers.iter().all(|p| p.queue.is_empty()) + self.peers.iter().all(|p| p.queue.read().is_empty()) } pub fn trigger_chain_new_blocks(&mut self, peer_id: usize) { - let mut peer = self.peer_mut(peer_id); - peer.sync.write().chain_new_blocks(&mut TestIo::new(&mut peer.chain, &peer.snapshot_service, &mut peer.queue, None), &[], &[], &[], &[], &[]); + let peer = self.peer(peer_id); + let mut queue = peer.queue.write(); + peer.sync.write().chain_new_blocks(&mut TestIo::new(&peer.chain, &peer.snapshot_service, &mut queue, None), &[], &[], &[], &[], &[]); } } + +impl ChainNotify for TestPeer<EthcoreClient> { + fn new_blocks(&self, + imported: Vec<H256>, + invalid: Vec<H256>, + enacted: Vec<H256>, + retracted: Vec<H256>, + sealed: Vec<H256>, + _duration: u64) + { + let mut queue = self.queue.write(); + let mut io = TestIo::new(&self.chain, &self.snapshot_service, &mut queue, None); + self.sync.write().chain_new_blocks( + &mut io, + &imported, + &invalid, + &enacted, + &retracted, + &sealed); + } + + fn start(&self) {} + + fn stop(&self) {} +} + diff --git a/sync/src/tests/mod.rs b/sync/src/tests/mod.rs index bdb4ae4f9..f805f6c24 100644 --- a/sync/src/tests/mod.rs +++ b/sync/src/tests/mod.rs @@ -17,4 +17,5 @@ pub mod helpers; pub mod snapshot; mod chain; +mod consensus; mod rpc; diff --git a/sync/src/tests/snapshot.rs b/sync/src/tests/snapshot.rs index 5d0b21b47..283d59ee3 100644 --- a/sync/src/tests/snapshot.rs +++ b/sync/src/tests/snapshot.rs @@ -129,7 +129,7 @@ fn snapshot_sync() { let snapshot_service = Arc::new(TestSnapshotService::new_with_snapshot(16, H256::new(), 500000)); for i in 0..4 { net.peer_mut(i).snapshot_service = snapshot_service.clone(); - net.peer_mut(i).chain.add_blocks(1, EachBlockWith::Nothing); + net.peer(i).chain.add_blocks(1, EachBlockWith::Nothing); } net.sync_steps(50); assert_eq!(net.peer(4).snapshot_service.state_restoration_chunks.lock().len(), net.peer(0).snapshot_service.manifest.as_ref().unwrap().state_hashes.len()); 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 59/63] 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 60/63] 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 61/63] 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 62/63] 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 63/63] 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);