diff --git a/Cargo.lock b/Cargo.lock index 90a2b76fdd0..eea24fe9d80 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,7 +28,7 @@ dependencies = [ "fdlimit 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.9.14 (registry+https://github.com/rust-lang/crates.io-index)", "isatty 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "jsonrpc-core 5.0.0 (git+https://github.com/ethcore/jsonrpc.git)", + "jsonrpc-core 5.0.0 (git+https://github.com/ethcore/jsonrpc.git?branch=mio)", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", @@ -406,8 +406,8 @@ dependencies = [ "fetch 0.1.0", "futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)", - "jsonrpc-core 5.0.0 (git+https://github.com/ethcore/jsonrpc.git)", - "jsonrpc-http-server 7.0.0 (git+https://github.com/ethcore/jsonrpc.git)", + "jsonrpc-core 5.0.0 (git+https://github.com/ethcore/jsonrpc.git?branch=mio)", + "jsonrpc-http-server 7.0.0 (git+https://github.com/ethcore/jsonrpc.git?branch=mio)", "linked-hash-map 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "mime 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -577,10 +577,10 @@ dependencies = [ "ethsync 1.6.0", "fetch 0.1.0", "futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "jsonrpc-core 5.0.0 (git+https://github.com/ethcore/jsonrpc.git)", - "jsonrpc-http-server 7.0.0 (git+https://github.com/ethcore/jsonrpc.git)", - "jsonrpc-ipc-server 1.0.0 (git+https://github.com/ethcore/jsonrpc.git)", - "jsonrpc-macros 0.2.0 (git+https://github.com/ethcore/jsonrpc.git)", + "jsonrpc-core 5.0.0 (git+https://github.com/ethcore/jsonrpc.git?branch=mio)", + "jsonrpc-http-server 7.0.0 (git+https://github.com/ethcore/jsonrpc.git?branch=mio)", + "jsonrpc-ipc-server 1.0.0 (git+https://github.com/ethcore/jsonrpc.git?branch=mio)", + "jsonrpc-macros 0.2.0 (git+https://github.com/ethcore/jsonrpc.git?branch=mio)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "parity-reactor 0.1.0", "parity-updater 1.6.0", @@ -604,7 +604,7 @@ dependencies = [ "ethcore-io 1.6.0", "ethcore-rpc 1.6.0", "ethcore-util 1.6.0", - "jsonrpc-core 5.0.0 (git+https://github.com/ethcore/jsonrpc.git)", + "jsonrpc-core 5.0.0 (git+https://github.com/ethcore/jsonrpc.git?branch=mio)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "parity-ui 1.6.0", @@ -623,9 +623,9 @@ dependencies = [ "ethcore-ipc-codegen 1.6.0", "ethcore-ipc-nano 1.6.0", "ethcore-util 1.6.0", - "jsonrpc-core 5.0.0 (git+https://github.com/ethcore/jsonrpc.git)", - "jsonrpc-macros 0.2.0 (git+https://github.com/ethcore/jsonrpc.git)", - "jsonrpc-tcp-server 1.0.0 (git+https://github.com/ethcore/jsonrpc.git)", + "jsonrpc-core 5.0.0 (git+https://github.com/ethcore/jsonrpc.git?branch=mio)", + "jsonrpc-macros 0.2.0 (git+https://github.com/ethcore/jsonrpc.git?branch=mio)", + "jsonrpc-tcp-server 1.0.0 (git+https://github.com/ethcore/jsonrpc.git?branch=mio)", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "mio 0.5.1 (git+https://github.com/ethcore/mio?branch=v0.5.x)", @@ -946,7 +946,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "jsonrpc-core" version = "5.0.0" -source = "git+https://github.com/ethcore/jsonrpc.git#2187ebc32f67620b000afb03a43a3c2b1020abb3" +source = "git+https://github.com/ethcore/jsonrpc.git?branch=mio#472abedcbf5198326912bc62d485acb1f5a0989c" dependencies = [ "futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -959,11 +959,11 @@ dependencies = [ [[package]] name = "jsonrpc-http-server" version = "7.0.0" -source = "git+https://github.com/ethcore/jsonrpc.git#2187ebc32f67620b000afb03a43a3c2b1020abb3" +source = "git+https://github.com/ethcore/jsonrpc.git?branch=mio#472abedcbf5198326912bc62d485acb1f5a0989c" dependencies = [ "futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)", - "jsonrpc-core 5.0.0 (git+https://github.com/ethcore/jsonrpc.git)", + "jsonrpc-core 5.0.0 (git+https://github.com/ethcore/jsonrpc.git?branch=mio)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-core 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -972,11 +972,11 @@ dependencies = [ [[package]] name = "jsonrpc-ipc-server" version = "1.0.0" -source = "git+https://github.com/ethcore/jsonrpc.git#2187ebc32f67620b000afb03a43a3c2b1020abb3" +source = "git+https://github.com/ethcore/jsonrpc.git?branch=mio#472abedcbf5198326912bc62d485acb1f5a0989c" dependencies = [ "bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "jsonrpc-core 5.0.0 (git+https://github.com/ethcore/jsonrpc.git)", + "jsonrpc-core 5.0.0 (git+https://github.com/ethcore/jsonrpc.git?branch=mio)", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "mio 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -988,21 +988,21 @@ dependencies = [ [[package]] name = "jsonrpc-macros" version = "0.2.0" -source = "git+https://github.com/ethcore/jsonrpc.git#2187ebc32f67620b000afb03a43a3c2b1020abb3" +source = "git+https://github.com/ethcore/jsonrpc.git?branch=mio#472abedcbf5198326912bc62d485acb1f5a0989c" dependencies = [ "futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "jsonrpc-core 5.0.0 (git+https://github.com/ethcore/jsonrpc.git)", + "jsonrpc-core 5.0.0 (git+https://github.com/ethcore/jsonrpc.git?branch=mio)", "serde 0.8.19 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "jsonrpc-tcp-server" version = "1.0.0" -source = "git+https://github.com/ethcore/jsonrpc.git#2187ebc32f67620b000afb03a43a3c2b1020abb3" +source = "git+https://github.com/ethcore/jsonrpc.git?branch=mio#472abedcbf5198326912bc62d485acb1f5a0989c" dependencies = [ "bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "jsonrpc-core 5.0.0 (git+https://github.com/ethcore/jsonrpc.git)", + "jsonrpc-core 5.0.0 (git+https://github.com/ethcore/jsonrpc.git?branch=mio)", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "mio 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1471,7 +1471,7 @@ dependencies = [ "ethcore-signer 1.6.0", "ethcore-util 1.6.0", "futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "jsonrpc-core 5.0.0 (git+https://github.com/ethcore/jsonrpc.git)", + "jsonrpc-core 5.0.0 (git+https://github.com/ethcore/jsonrpc.git?branch=mio)", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1502,7 +1502,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#e8262f8db959a8f779e561e42a84c26de32e05ea" +source = "git+https://github.com/ethcore/js-precompiled.git#8b893e9c3d0c5285e6030d6d6b8406e7d4099a7b" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2406,11 +2406,11 @@ dependencies = [ "checksum isatty 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7408a548dc0e406b7912d9f84c261cc533c1866e047644a811c133c56041ac0c" "checksum itertools 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)" = "086e1fa5fe48840b1cfdef3a20c7e3115599f8d5c4c87ef32a794a7cdd184d76" "checksum itoa 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ae3088ea4baeceb0284ee9eea42f591226e6beaecf65373e41b38d95a1b8e7a1" -"checksum jsonrpc-core 5.0.0 (git+https://github.com/ethcore/jsonrpc.git)" = "" -"checksum jsonrpc-http-server 7.0.0 (git+https://github.com/ethcore/jsonrpc.git)" = "" -"checksum jsonrpc-ipc-server 1.0.0 (git+https://github.com/ethcore/jsonrpc.git)" = "" -"checksum jsonrpc-macros 0.2.0 (git+https://github.com/ethcore/jsonrpc.git)" = "" -"checksum jsonrpc-tcp-server 1.0.0 (git+https://github.com/ethcore/jsonrpc.git)" = "" +"checksum jsonrpc-core 5.0.0 (git+https://github.com/ethcore/jsonrpc.git?branch=mio)" = "" +"checksum jsonrpc-http-server 7.0.0 (git+https://github.com/ethcore/jsonrpc.git?branch=mio)" = "" +"checksum jsonrpc-ipc-server 1.0.0 (git+https://github.com/ethcore/jsonrpc.git?branch=mio)" = "" +"checksum jsonrpc-macros 0.2.0 (git+https://github.com/ethcore/jsonrpc.git?branch=mio)" = "" +"checksum jsonrpc-tcp-server 1.0.0 (git+https://github.com/ethcore/jsonrpc.git?branch=mio)" = "" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" "checksum lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "49247ec2a285bb3dcb23cbd9c35193c025e7251bfce77c1d5da97e6362dffe7f" diff --git a/Cargo.toml b/Cargo.toml index 359ec77b5da..c0823be688f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ app_dirs = "1.1.1" fdlimit = "0.1" hyper = { version = "0.9", default-features = false } ctrlc = { git = "https://github.com/ethcore/rust-ctrlc.git" } -jsonrpc-core = { git = "https://github.com/ethcore/jsonrpc.git" } +jsonrpc-core = { git = "https://github.com/ethcore/jsonrpc.git", branch="mio" } ethsync = { path = "sync" } ethcore = { path = "ethcore" } ethcore-util = { path = "util" } diff --git a/dapps/Cargo.toml b/dapps/Cargo.toml index 09c72cb4737..af6e2af5d24 100644 --- a/dapps/Cargo.toml +++ b/dapps/Cargo.toml @@ -13,8 +13,8 @@ rand = "0.3" log = "0.3" env_logger = "0.3" futures = "0.1" -jsonrpc-core = { git = "https://github.com/ethcore/jsonrpc.git" } -jsonrpc-http-server = { git = "https://github.com/ethcore/jsonrpc.git" } +jsonrpc-core = { git = "https://github.com/ethcore/jsonrpc.git", branch="mio" } +jsonrpc-http-server = { git = "https://github.com/ethcore/jsonrpc.git", branch="mio" } hyper = { default-features = false, git = "https://github.com/ethcore/hyper" } unicase = "1.3" url = "1.0" diff --git a/ethcore/light/src/net/mod.rs b/ethcore/light/src/net/mod.rs index 18425152c8d..c5071cc0270 100644 --- a/ethcore/light/src/net/mod.rs +++ b/ethcore/light/src/net/mod.rs @@ -40,10 +40,13 @@ use request::{self, HashOrNumber, Request}; use self::buffer_flow::{Buffer, FlowParams}; use self::context::{Ctx, TickCtx}; use self::error::Punishment; +use self::request_set::RequestSet; +use self::id_guard::IdGuard; mod context; mod error; mod status; +mod request_set; #[cfg(test)] mod tests; @@ -121,7 +124,7 @@ mod timeout { } /// A request id. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)] pub struct ReqId(usize); impl fmt::Display for ReqId { @@ -137,15 +140,17 @@ struct PendingPeer { last_update: SteadyTime, } -// data about each peer. -struct Peer { +/// Relevant data to each peer. Not accessible publicly, only `pub` due to +/// limitations of the privacy system. +pub struct Peer { local_buffer: Buffer, // their buffer relative to us status: Status, capabilities: Capabilities, remote_flow: Option<(Buffer, FlowParams)>, sent_head: H256, // last chain head we've given them. last_update: SteadyTime, - idle: bool, // make into a current percentage of max buffer being requested? + pending_requests: RequestSet, + failed_requests: Vec, } impl Peer { @@ -209,13 +214,6 @@ pub trait Handler: Send + Sync { fn on_abort(&self) { } } -// 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. pub struct Params { /// Network id. @@ -226,6 +224,57 @@ pub struct Params { pub capabilities: Capabilities, } +/// Type alias for convenience. +pub type PeerMap = HashMap>; + +mod id_guard { + + use network::PeerId; + use util::RwLockReadGuard; + + use super::{PeerMap, ReqId}; + + // Guards success or failure of given request. + // On drop, inserts the req_id into the "failed requests" + // set for the peer unless defused. In separate module to enforce correct usage. + pub struct IdGuard<'a> { + peers: RwLockReadGuard<'a, PeerMap>, + peer_id: PeerId, + req_id: ReqId, + active: bool, + } + + impl<'a> IdGuard<'a> { + /// Create a new `IdGuard`, which will prevent access of the inner ReqId + /// (for forming responses, triggering handlers) until defused + pub fn new(peers: RwLockReadGuard<'a, PeerMap>, peer_id: PeerId, req_id: ReqId) -> Self { + IdGuard { + peers: peers, + peer_id: peer_id, + req_id: req_id, + active: true, + } + } + + /// Defuse the guard, signalling that the request has been successfully decoded. + pub fn defuse(mut self) -> ReqId { + // can't use the mem::forget trick here since we need the + // read guard to drop. + self.active = false; + self.req_id + } + } + + impl<'a> Drop for IdGuard<'a> { + fn drop(&mut self) { + if !self.active { return } + if let Some(p) = self.peers.get(&self.peer_id) { + p.lock().failed_requests.push(self.req_id); + } + } + } +} + /// This is an implementation of the light ethereum network protocol, abstracted /// over a `Provider` of data and a p2p network. /// @@ -241,8 +290,7 @@ pub struct LightProtocol { genesis_hash: H256, network_id: u64, pending_peers: RwLock>, - peers: RwLock>>, - pending_requests: RwLock>, + peers: RwLock, capabilities: RwLock, flow_params: FlowParams, // assumed static and same for every peer. handlers: Vec>, @@ -261,7 +309,6 @@ impl LightProtocol { network_id: params.network_id, pending_peers: RwLock::new(HashMap::new()), peers: RwLock::new(HashMap::new()), - pending_requests: RwLock::new(HashMap::new()), capabilities: RwLock::new(params.capabilities), flow_params: params.flow_params, handlers: Vec::new(), @@ -275,16 +322,10 @@ impl LightProtocol { fn max_requests(&self, peer: PeerId, kind: request::Kind) -> usize { self.peers.read().get(&peer).and_then(|peer| { let mut peer = peer.lock(); - let idle = peer.idle; match peer.remote_flow { Some((ref mut buf, ref flow)) => { flow.recharge(buf); - - if !idle { - Some(0) - } else { - Some(flow.max_amount(&*buf, kind)) - } + Some(flow.max_amount(&*buf, kind)) } None => None, } @@ -302,8 +343,6 @@ impl LightProtocol { let peer = peers.get(peer_id).ok_or_else(|| Error::UnknownPeer)?; let mut peer = peer.lock(); - if !peer.idle { return Err(Error::Overburdened) } - match peer.remote_flow { Some((ref mut buf, ref flow)) => { flow.recharge(buf); @@ -329,12 +368,7 @@ impl LightProtocol { io.send(*peer_id, packet_id, packet_data); - peer.idle = false; - self.pending_requests.write().insert(req_id, Requested { - request: request, - timestamp: SteadyTime::now(), - peer_id: *peer_id, - }); + peer.pending_requests.insert(ReqId(req_id), request, SteadyTime::now()); Ok(ReqId(req_id)) } @@ -402,11 +436,9 @@ impl LightProtocol { // 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 @@ -414,37 +446,49 @@ impl LightProtocol { // - 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 = raw.val_at(0)?; + fn pre_verify_response(&self, peer: &PeerId, kind: request::Kind, raw: &UntrustedRlp) -> Result { + let req_id = ReqId(raw.val_at(0)?); let cur_buffer: U256 = 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 mut had_req = false; let peers = self.peers.read(); - match peers.get(peer) { + let maybe_err = match peers.get(peer) { Some(peer_info) => { let mut peer_info = peer_info.lock(); - peer_info.idle = true; + let req_info = peer_info.pending_requests.remove(&req_id, SteadyTime::now()); + let flow_info = peer_info.remote_flow.as_mut(); + + match (req_info, flow_info) { + (Some(request), Some(flow_info)) => { + had_req = true; - match peer_info.remote_flow.as_mut() { - Some(&mut (ref mut buf, ref mut flow)) => { + let &mut (ref mut buf, ref mut flow) = flow_info; let actual_buffer = ::std::cmp::min(cur_buffer, *flow.limit()); - buf.update_to(actual_buffer) + buf.update_to(actual_buffer); + + if request.kind() != kind { + Some(Error::UnsolicitedResponse) + } else { + None + } } - None => return Err(Error::NotServer), // this really should be impossible. + (None, _) => Some(Error::UnsolicitedResponse), + (_, None) => Some(Error::NotServer), // really should be impossible. } - Ok(ReqId(req_id)) } - None => Err(Error::UnknownPeer), // probably only occurs in a race of some kind. + None => Some(Error::UnknownPeer), // probably only occurs in a race of some kind. + }; + + if had_req { + let id_guard = IdGuard::new(peers, *peer, req_id); + match maybe_err { + Some(err) => Err(err), + None => Ok(id_guard) + } + } else { + Err(maybe_err.expect("every branch without a request leads to error; qed")) } } @@ -491,7 +535,39 @@ impl LightProtocol { } } - /// called when a peer connects. + // check timeouts and punish peers. + fn timeout_check(&self, io: &IoContext) { + let now = SteadyTime::now(); + + // handshake timeout + { + let mut pending = self.pending_peers.write(); + let slowpokes: Vec<_> = pending.iter() + .filter(|&(_, ref peer)| { + peer.last_update + Duration::milliseconds(timeout::HANDSHAKE) <= now + }) + .map(|(&p, _)| p) + .collect(); + + for slowpoke in slowpokes { + debug!(target: "les", "Peer {} handshake timed out", slowpoke); + pending.remove(&slowpoke); + io.disconnect_peer(slowpoke); + } + } + + // request timeouts + { + for (peer_id, peer) in self.peers.read().iter() { + if peer.lock().pending_requests.check_timeout(now) { + debug!(target: "les", "Peer {} request timeout", peer_id); + io.disconnect_peer(*peer_id); + } + } + } + } + + /// called when a peer connects. pub fn on_connect(&self, peer: &PeerId, io: &IoContext) { let proto_version = match io.protocol_version(*peer).ok_or(Error::WrongNetwork) { Ok(pv) => pv, @@ -530,21 +606,11 @@ impl LightProtocol { pub 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() - .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 { - pending.remove(inner); - } - } + if let Some(peer_info) = self.peers.write().remove(&peer) { + let peer_info = peer_info.into_inner(); + let mut unfulfilled: Vec<_> = peer_info.pending_requests.collect_ids(); + unfulfilled.extend(peer_info.failed_requests); for handler in &self.handlers { handler.on_disconnect(&Ctx { @@ -556,54 +622,6 @@ impl LightProtocol { } } - // check timeouts and punish peers. - fn timeout_check(&self, io: &IoContext) { - let now = SteadyTime::now(); - - // handshake timeout - { - let mut pending = self.pending_peers.write(); - let slowpokes: Vec<_> = pending.iter() - .filter(|&(_, ref peer)| { - peer.last_update + Duration::milliseconds(timeout::HANDSHAKE) <= now - }) - .map(|(&p, _)| p) - .collect(); - - for slowpoke in slowpokes { - debug!(target: "les", "Peer {} handshake timed out", slowpoke); - pending.remove(&slowpoke); - io.disconnect_peer(slowpoke); - } - } - - // request timeouts - { - for r in self.pending_requests.read().values() { - let kind_timeout = match r.request.kind() { - request::Kind::Headers => timeout::HEADERS, - request::Kind::Bodies => timeout::BODIES, - request::Kind::Receipts => timeout::RECEIPTS, - request::Kind::StateProofs => timeout::PROOFS, - request::Kind::Codes => timeout::CONTRACT_CODES, - request::Kind::HeaderProofs => timeout::HEADER_PROOFS, - }; - - if r.timestamp + Duration::milliseconds(kind_timeout) <= now { - debug!(target: "les", "Request for {:?} from peer {} timed out", - r.request.kind(), r.peer_id); - - // keep the request in the `pending` set for now so - // on_disconnect will pass unfulfilled ReqIds to handlers. - // in the case that a response is received after this, the - // disconnect won't be cancelled but the ReqId won't be - // marked as abandoned. - io.disconnect_peer(r.peer_id); - } - } - } - } - /// Execute the given closure with a basic context derived from the I/O context. pub fn with_context(&self, io: &IoContext, f: F) -> T where F: FnOnce(&BasicContext) -> T @@ -655,7 +673,8 @@ impl LightProtocol { remote_flow: remote_flow, sent_head: pending.sent_head, last_update: pending.last_update, - idle: true, + pending_requests: RequestSet::default(), + failed_requests: Vec::new(), })); for handler in &self.handlers { @@ -770,9 +789,10 @@ impl LightProtocol { // Receive a response for block headers. fn block_headers(&self, peer: &PeerId, io: &IoContext, raw: UntrustedRlp) -> Result<(), Error> { - let req_id = self.pre_verify_response(peer, request::Kind::Headers, &raw)?; + let id_guard = self.pre_verify_response(peer, request::Kind::Headers, &raw)?; let raw_headers: Vec<_> = raw.at(2)?.iter().map(|x| x.as_raw().to_owned()).collect(); + let req_id = id_guard.defuse(); for handler in &self.handlers { handler.on_block_headers(&Ctx { peer: *peer, @@ -835,9 +855,10 @@ impl LightProtocol { // Receive a response for block bodies. fn block_bodies(&self, peer: &PeerId, io: &IoContext, raw: UntrustedRlp) -> Result<(), Error> { - let req_id = self.pre_verify_response(peer, request::Kind::Bodies, &raw)?; + let id_guard = self.pre_verify_response(peer, request::Kind::Bodies, &raw)?; let raw_bodies: Vec = raw.at(2)?.iter().map(|x| x.as_raw().to_owned()).collect(); + let req_id = id_guard.defuse(); for handler in &self.handlers { handler.on_block_bodies(&Ctx { peer: *peer, @@ -897,12 +918,13 @@ impl LightProtocol { // Receive a response for receipts. fn receipts(&self, peer: &PeerId, io: &IoContext, raw: UntrustedRlp) -> Result<(), Error> { - let req_id = self.pre_verify_response(peer, request::Kind::Receipts, &raw)?; + let id_guard = self.pre_verify_response(peer, request::Kind::Receipts, &raw)?; let raw_receipts: Vec> = raw.at(2)? .iter() .map(|x| x.as_val()) .collect::>()?; + let req_id = id_guard.defuse(); for handler in &self.handlers { handler.on_receipts(&Ctx { peer: *peer, @@ -970,12 +992,13 @@ impl LightProtocol { // Receive a response for proofs. fn proofs(&self, peer: &PeerId, io: &IoContext, raw: UntrustedRlp) -> Result<(), Error> { - let req_id = self.pre_verify_response(peer, request::Kind::StateProofs, &raw)?; + let id_guard = self.pre_verify_response(peer, request::Kind::StateProofs, &raw)?; let raw_proofs: Vec> = raw.at(2)?.iter() .map(|x| x.iter().map(|node| node.as_raw().to_owned()).collect()) .collect(); + let req_id = id_guard.defuse(); for handler in &self.handlers { handler.on_state_proofs(&Ctx { peer: *peer, @@ -1041,12 +1064,13 @@ impl LightProtocol { // Receive a response for contract code. fn contract_code(&self, peer: &PeerId, io: &IoContext, raw: UntrustedRlp) -> Result<(), Error> { - let req_id = self.pre_verify_response(peer, request::Kind::Codes, &raw)?; + let id_guard = self.pre_verify_response(peer, request::Kind::Codes, &raw)?; let raw_code: Vec = raw.at(2)?.iter() .map(|x| x.as_val()) .collect::>()?; + let req_id = id_guard.defuse(); for handler in &self.handlers { handler.on_code(&Ctx { peer: *peer, @@ -1120,11 +1144,12 @@ impl LightProtocol { )) } - let req_id = self.pre_verify_response(peer, request::Kind::HeaderProofs, &raw)?; + let id_guard = self.pre_verify_response(peer, request::Kind::HeaderProofs, &raw)?; let raw_proofs: Vec<_> = raw.at(2)?.iter() .map(decode_res) .collect::>()?; + let req_id = id_guard.defuse(); for handler in &self.handlers { handler.on_header_proofs(&Ctx { peer: *peer, diff --git a/ethcore/light/src/net/request_set.rs b/ethcore/light/src/net/request_set.rs new file mode 100644 index 00000000000..506affa6397 --- /dev/null +++ b/ethcore/light/src/net/request_set.rs @@ -0,0 +1,140 @@ +// Copyright 2015, 2016 Parity Technologies (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 . + +//! Pending request set. +//! +//! Stores pending requests and does timeout computation according to the rule +//! that only the earliest submitted request within the structure may time out. +//! +//! Whenever a request becomes the earliest, its timeout period begins at that moment. + +use std::collections::{BTreeMap, HashMap}; +use std::iter::FromIterator; + +use request::{self, Request}; +use net::{timeout, ReqId}; + +use time::{Duration, SteadyTime}; + +/// Request set. +#[derive(Debug)] +pub struct RequestSet { + counter: u64, + base: Option, + ids: HashMap, + reqs: BTreeMap, +} + +impl Default for RequestSet { + fn default() -> Self { + RequestSet { + counter: 0, + base: None, + ids: HashMap::new(), + reqs: BTreeMap::new(), + } + } +} + +impl RequestSet { + /// Push a request onto the stack. + pub fn insert(&mut self, req_id: ReqId, req: Request, now: SteadyTime) { + let counter = self.counter; + self.ids.insert(req_id, counter); + self.reqs.insert(counter, req); + + if self.reqs.keys().next().map_or(true, |x| *x == counter) { + self.base = Some(now); + } + + self.counter += 1; + } + + /// Remove a request from the stack. + pub fn remove(&mut self, req_id: &ReqId, now: SteadyTime) -> Option { + let id = match self.ids.remove(&req_id) { + Some(id) => id, + None => return None, + }; + + let req = self.reqs.remove(&id).expect("entry in `ids` implies entry in `reqs`; qed"); + + match self.reqs.keys().next() { + Some(k) if *k > id => self.base = Some(now), + None => self.base = None, + _ => {} + } + + Some(req) + } + + /// Check for timeout against the given time. Returns true if + /// has timed out, false otherwise. + pub fn check_timeout(&self, now: SteadyTime) -> bool { + let base = match self.base.as_ref().cloned() { + Some(base) => base, + None => return false, + }; + + let kind = self.reqs.values() + .next() + .map(|r| r.kind()) + .expect("base time implies `reqs` non-empty; qed"); + + let kind_timeout = match kind { + request::Kind::Headers => timeout::HEADERS, + request::Kind::Bodies => timeout::BODIES, + request::Kind::Receipts => timeout::RECEIPTS, + request::Kind::StateProofs => timeout::PROOFS, + request::Kind::Codes => timeout::CONTRACT_CODES, + request::Kind::HeaderProofs => timeout::HEADER_PROOFS, + }; + + base + Duration::milliseconds(kind_timeout) <= now + } + + /// Collect all pending request ids. + pub fn collect_ids(&self) -> F where F: FromIterator { + self.ids.keys().cloned().collect() + } +} + +#[cfg(test)] +mod tests { + use net::{timeout, ReqId}; + use request::{Request, Receipts}; + use time::{SteadyTime, Duration}; + use super::RequestSet; + + #[test] + fn multi_timeout() { + let test_begin = SteadyTime::now(); + let mut req_set = RequestSet::default(); + + let the_req = Request::Receipts(Receipts { block_hashes: Vec::new() }); + req_set.insert(ReqId(0), the_req.clone(), test_begin); + req_set.insert(ReqId(1), the_req, test_begin + Duration::seconds(1)); + + assert_eq!(req_set.base, Some(test_begin)); + + let test_end = test_begin + Duration::milliseconds(timeout::RECEIPTS); + assert!(req_set.check_timeout(test_end)); + + req_set.remove(&ReqId(0), test_begin + Duration::seconds(1)).unwrap(); + assert!(!req_set.check_timeout(test_end)); + assert!(req_set.check_timeout(test_end + Duration::seconds(1))); + } +} diff --git a/ethcore/light/src/net/tests/mod.rs b/ethcore/light/src/net/tests/mod.rs index a0a9feee448..8a55c5ee6f7 100644 --- a/ethcore/light/src/net/tests/mod.rs +++ b/ethcore/light/src/net/tests/mod.rs @@ -27,7 +27,7 @@ use network::{PeerId, NodeId}; use net::buffer_flow::FlowParams; use net::context::IoContext; use net::status::{Capabilities, Status, write_handshake}; -use net::{encode_request, LightProtocol, Params, packet}; +use net::{encode_request, LightProtocol, Params, packet, Peer}; use provider::Provider; use request::{self, Request, Headers}; @@ -487,3 +487,81 @@ fn get_contract_code() { let expected = Expect::Respond(packet::CONTRACT_CODES, response); proto.handle_packet(&expected, &1, packet::GET_CONTRACT_CODES, &request_body); } + +#[test] +fn id_guard() { + use super::request_set::RequestSet; + use super::ReqId; + + 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 req_id_1 = ReqId(5143); + let req_id_2 = ReqId(1111); + let req = Request::Headers(request::Headers { + start: 5u64.into(), + max: 100, + skip: 0, + reverse: false, + }); + + let peer_id = 9876; + + let mut pending_requests = RequestSet::default(); + + pending_requests.insert(req_id_1, req.clone(), ::time::SteadyTime::now()); + pending_requests.insert(req_id_2, req, ::time::SteadyTime::now()); + + proto.peers.write().insert(peer_id, ::util::Mutex::new(Peer { + local_buffer: flow_params.create_buffer(), + status: status(provider.client.chain_info()), + capabilities: capabilities.clone(), + remote_flow: Some((flow_params.create_buffer(), flow_params)), + sent_head: provider.client.chain_info().best_block_hash, + last_update: ::time::SteadyTime::now(), + pending_requests: pending_requests, + failed_requests: Vec::new(), + })); + + // first, supply wrong request type. + { + let mut stream = RlpStream::new_list(3); + stream.append(&req_id_1.0); + stream.append(&4_000_000usize); + stream.begin_list(0); + + let packet = stream.out(); + assert!(proto.block_bodies(&peer_id, &Expect::Nothing, UntrustedRlp::new(&packet)).is_err()); + } + + // next, do an unexpected response. + { + let mut stream = RlpStream::new_list(3); + stream.append(&10000usize); + stream.append(&3_000_000usize); + stream.begin_list(0); + + let packet = stream.out(); + assert!(proto.receipts(&peer_id, &Expect::Nothing, UntrustedRlp::new(&packet)).is_err()); + } + + // lastly, do a valid (but empty) response. + { + let mut stream = RlpStream::new_list(3); + stream.append(&req_id_2.0); + stream.append(&3_000_000usize); + stream.begin_list(0); + + let packet = stream.out(); + assert!(proto.block_headers(&peer_id, &Expect::Nothing, UntrustedRlp::new(&packet)).is_ok()); + } + + let peers = proto.peers.read(); + if let Some(ref peer_info) = peers.get(&peer_id) { + let peer_info = peer_info.lock(); + assert!(peer_info.pending_requests.collect_ids::>().is_empty()); + assert_eq!(peer_info.failed_requests, &[req_id_1]); + } +} diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index c420aa646f1..8c9369790ec 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -250,7 +250,7 @@ impl TestBlockChainClient { value: U256::from(100), data: "3331600055".from_hex().unwrap(), gas: U256::from(100_000), - gas_price: U256::one(), + gas_price: U256::from(200_000_000_000u64), nonce: U256::zero() }; let signed_tx = tx.sign(keypair.secret(), None); @@ -316,11 +316,11 @@ impl TestBlockChainClient { value: U256::from(100), data: "3331600055".from_hex().unwrap(), gas: U256::from(100_000), - gas_price: U256::one(), + gas_price: U256::from(20_000_000_000u64), nonce: U256::zero() }; let signed_tx = tx.sign(keypair.secret(), None); - self.set_balance(signed_tx.sender(), 10_000_000.into()); + self.set_balance(signed_tx.sender(), U256::from(10_000_000_000_000_000_000u64)); let res = self.miner.import_external_transactions(self, vec![signed_tx.into()]); let res = res.into_iter().next().unwrap().expect("Successful import"); assert_eq!(res, TransactionImportResult::Current); diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index 1a1462507c4..c3f28326524 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -234,7 +234,7 @@ impl Engine for AuthorityRound { let header = block.header(); let step = self.step.load(AtomicOrdering::SeqCst); if self.is_step_proposer(step, header.author()) { - let ref ap = *self.account_provider.lock(); + let ap = self.account_provider.lock(); if let Ok(signature) = ap.sign(*header.author(), self.password.read().clone(), header.bare_hash()) { trace!(target: "poa", "generate_seal: Issuing a block for step {}.", step); self.proposed.store(true, AtomicOrdering::SeqCst); @@ -329,12 +329,10 @@ impl Engine for AuthorityRound { self.validators.register_call_contract(client); } - fn set_signer(&self, _address: Address, password: String) { + fn set_signer(&self, ap: Arc, address: Address, password: String) { + *self.account_provider.lock() = ap; *self.password.write() = Some(password); - } - - fn register_account_provider(&self, account_provider: Arc) { - *self.account_provider.lock() = account_provider; + debug!(target: "poa", "Setting Engine signer to {}", address); } } @@ -401,13 +399,12 @@ mod tests { #[test] fn generates_seal_and_does_not_double_propose() { - let tap = AccountProvider::transient_provider(); + let tap = Arc::new(AccountProvider::transient_provider()); let addr1 = tap.insert_account(Secret::from_slice(&"1".sha3()).unwrap(), "1").unwrap(); let addr2 = tap.insert_account(Secret::from_slice(&"2".sha3()).unwrap(), "2").unwrap(); let spec = Spec::new_test_round(); let engine = &*spec.engine; - engine.register_account_provider(Arc::new(tap)); let genesis_header = spec.genesis_header(); let db1 = spec.ensure_db_good(get_temp_state_db().take(), &Default::default()).unwrap(); let db2 = spec.ensure_db_good(get_temp_state_db().take(), &Default::default()).unwrap(); @@ -417,14 +414,14 @@ mod tests { let b2 = OpenBlock::new(engine, Default::default(), false, db2, &genesis_header, last_hashes, addr2, (3141562.into(), 31415620.into()), vec![]).unwrap(); let b2 = b2.close_and_lock(); - engine.set_signer(addr1, "1".into()); + engine.set_signer(tap.clone(), addr1, "1".into()); if let Seal::Regular(seal) = engine.generate_seal(b1.block()) { assert!(b1.clone().try_seal(engine, seal).is_ok()); // Second proposal is forbidden. assert!(engine.generate_seal(b1.block()) == Seal::None); } - engine.set_signer(addr2, "2".into()); + engine.set_signer(tap, addr2, "2".into()); if let Seal::Regular(seal) = engine.generate_seal(b2.block()) { assert!(b2.clone().try_seal(engine, seal).is_ok()); // Second proposal is forbidden. diff --git a/ethcore/src/engines/basic_authority.rs b/ethcore/src/engines/basic_authority.rs index 1d8d92557e0..fbee7c811fd 100644 --- a/ethcore/src/engines/basic_authority.rs +++ b/ethcore/src/engines/basic_authority.rs @@ -56,7 +56,7 @@ pub struct BasicAuthority { params: CommonParams, gas_limit_bound_divisor: U256, builtins: BTreeMap, - account_provider: Mutex>>, + account_provider: Mutex>, password: RwLock>, validators: Box, } @@ -69,7 +69,7 @@ impl BasicAuthority { gas_limit_bound_divisor: our_params.gas_limit_bound_divisor, builtins: builtins, validators: new_validator_set(our_params.validators), - account_provider: Mutex::new(None), + account_provider: Mutex::new(Arc::new(AccountProvider::transient_provider())), password: RwLock::new(None), } } @@ -110,20 +110,17 @@ impl Engine for BasicAuthority { /// Attempt to seal the block internally. fn generate_seal(&self, block: &ExecutedBlock) -> Seal { - if let Some(ref ap) = *self.account_provider.lock() { - let header = block.header(); - let author = header.author(); - if self.validators.contains(author) { - let message = header.bare_hash(); - // account should be pernamently unlocked, otherwise sealing will fail - if let Ok(signature) = ap.sign(*author, self.password.read().clone(), message) { - return Seal::Regular(vec![::rlp::encode(&(&*signature as &[u8])).to_vec()]); - } else { - trace!(target: "basicauthority", "generate_seal: FAIL: accounts secret key unavailable"); - } + let ref ap = *self.account_provider.lock(); + let header = block.header(); + let author = header.author(); + if self.validators.contains(author) { + let message = header.bare_hash(); + // account should be pernamently unlocked, otherwise sealing will fail + if let Ok(signature) = ap.sign(*author, self.password.read().clone(), message) { + return Seal::Regular(vec![::rlp::encode(&(&*signature as &[u8])).to_vec()]); + } else { + trace!(target: "basicauthority", "generate_seal: FAIL: accounts secret key unavailable"); } - } else { - trace!(target: "basicauthority", "generate_seal: FAIL: accounts not provided"); } Seal::None } @@ -174,12 +171,9 @@ impl Engine for BasicAuthority { self.validators.register_call_contract(client); } - fn set_signer(&self, _address: Address, password: String) { + fn set_signer(&self, ap: Arc, _address: Address, password: String) { *self.password.write() = Some(password); - } - - fn register_account_provider(&self, ap: Arc) { - *self.account_provider.lock() = Some(ap); + *self.account_provider.lock() = ap; } } @@ -256,8 +250,7 @@ mod tests { let spec = new_test_authority(); let engine = &*spec.engine; - engine.set_signer(addr, "".into()); - engine.register_account_provider(Arc::new(tap)); + engine.set_signer(Arc::new(tap), addr, "".into()); let genesis_header = spec.genesis_header(); let mut db_result = get_temp_state_db(); let db = spec.ensure_db_good(db_result.take(), &Default::default()).unwrap(); diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index 85a45c3a3ac..86f51bf2b91 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -212,14 +212,11 @@ pub trait Engine : Sync + Send { fn is_proposal(&self, _verified_header: &Header) -> bool { false } /// Register an account which signs consensus messages. - fn set_signer(&self, _address: Address, _password: String) {} + fn set_signer(&self, _account_provider: Arc, _address: Address, _password: String) {} /// Add Client which can be used for sealing, querying the state and sending messages. fn register_client(&self, _client: Weak) {} - /// Add an account provider useful for Engines that sign stuff. - fn register_account_provider(&self, _account_provider: Arc) {} - /// Trigger next step of the consensus engine. fn step(&self) {} diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 9f44a9bcff1..661044ddb87 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -94,7 +94,7 @@ pub struct Tendermint { /// Vote accumulator. votes: VoteCollector, /// Used to sign messages and proposals. - account_provider: Mutex>>, + account_provider: Mutex>, /// Message for the last PoLC. lock_change: RwLock>, /// Last lock round. @@ -122,7 +122,7 @@ impl Tendermint { round: AtomicUsize::new(0), step: RwLock::new(Step::Propose), votes: VoteCollector::new(), - account_provider: Mutex::new(None), + account_provider: Mutex::new(Arc::new(AccountProvider::transient_provider())), lock_change: RwLock::new(None), last_lock: AtomicUsize::new(0), proposal: RwLock::new(None), @@ -158,30 +158,26 @@ impl Tendermint { } fn generate_message(&self, block_hash: Option) -> Option { - if let Some(ref ap) = *self.account_provider.lock() { - let h = self.height.load(AtomicOrdering::SeqCst); - let r = self.round.load(AtomicOrdering::SeqCst); - let s = self.step.read(); - let vote_info = message_info_rlp(&VoteStep::new(h, r, *s), block_hash); - let authority = self.authority.read(); - match ap.sign(*authority, self.password.read().clone(), vote_info.sha3()).map(Into::into) { - Ok(signature) => { - let message_rlp = message_full_rlp(&signature, &vote_info); - let message = ConsensusMessage::new(signature, h, r, *s, block_hash); - self.votes.vote(message.clone(), &*authority); - debug!(target: "poa", "Generated {:?} as {}.", message, *authority); - self.handle_valid_message(&message); - - Some(message_rlp) - }, - Err(e) => { - trace!(target: "poa", "Could not sign the message {}", e); - None - }, - } - } else { - warn!(target: "poa", "No AccountProvider available."); - None + let ref ap = *self.account_provider.lock(); + let h = self.height.load(AtomicOrdering::SeqCst); + let r = self.round.load(AtomicOrdering::SeqCst); + let s = self.step.read(); + let vote_info = message_info_rlp(&VoteStep::new(h, r, *s), block_hash); + let authority = self.authority.read(); + match ap.sign(*authority, self.password.read().clone(), vote_info.sha3()).map(Into::into) { + Ok(signature) => { + let message_rlp = message_full_rlp(&signature, &vote_info); + let message = ConsensusMessage::new(signature, h, r, *s, block_hash); + self.votes.vote(message.clone(), &*authority); + debug!(target: "poa", "Generated {:?} as {}.", message, *authority); + self.handle_valid_message(&message); + + Some(message_rlp) + }, + Err(e) => { + trace!(target: "poa", "Could not sign the message {}", e); + None + }, } } @@ -420,35 +416,31 @@ impl Engine for Tendermint { /// Attempt to seal generate a proposal seal. fn generate_seal(&self, block: &ExecutedBlock) -> Seal { - if let Some(ref ap) = *self.account_provider.lock() { - let header = block.header(); - let author = header.author(); - // Only proposer can generate seal if None was generated. - if self.is_proposer(author).is_err() || self.proposal.read().is_some() { - return Seal::None; - } + let ref ap = *self.account_provider.lock(); + let header = block.header(); + let author = header.author(); + // Only proposer can generate seal if None was generated. + if self.is_proposer(author).is_err() || self.proposal.read().is_some() { + return Seal::None; + } - let height = header.number() as Height; - let round = self.round.load(AtomicOrdering::SeqCst); - let bh = Some(header.bare_hash()); - let vote_info = message_info_rlp(&VoteStep::new(height, round, Step::Propose), bh.clone()); - if let Ok(signature) = ap.sign(*author, self.password.read().clone(), vote_info.sha3()).map(H520::from) { - // Insert Propose vote. - debug!(target: "poa", "Submitting proposal {} at height {} round {}.", header.bare_hash(), height, round); - self.votes.vote(ConsensusMessage::new(signature, height, round, Step::Propose, bh), author); - // Remember proposal for later seal submission. - *self.proposal.write() = bh; - Seal::Proposal(vec![ - ::rlp::encode(&round).to_vec(), - ::rlp::encode(&signature).to_vec(), - ::rlp::EMPTY_LIST_RLP.to_vec() - ]) - } else { - warn!(target: "poa", "generate_seal: FAIL: accounts secret key unavailable"); - Seal::None - } + let height = header.number() as Height; + let round = self.round.load(AtomicOrdering::SeqCst); + let bh = Some(header.bare_hash()); + let vote_info = message_info_rlp(&VoteStep::new(height, round, Step::Propose), bh.clone()); + if let Ok(signature) = ap.sign(*author, self.password.read().clone(), vote_info.sha3()).map(H520::from) { + // Insert Propose vote. + debug!(target: "poa", "Submitting proposal {} at height {} round {}.", header.bare_hash(), height, round); + self.votes.vote(ConsensusMessage::new(signature, height, round, Step::Propose, bh), author); + // Remember proposal for later seal submission. + *self.proposal.write() = bh; + Seal::Proposal(vec![ + ::rlp::encode(&round).to_vec(), + ::rlp::encode(&signature).to_vec(), + ::rlp::EMPTY_LIST_RLP.to_vec() + ]) } else { - warn!(target: "poa", "generate_seal: FAIL: accounts not provided"); + warn!(target: "poa", "generate_seal: FAIL: accounts secret key unavailable"); Seal::None } } @@ -563,9 +555,12 @@ impl Engine for Tendermint { Ok(()) } - fn set_signer(&self, address: Address, password: String) { - *self.authority.write() = address; - *self.password.write() = Some(password); + fn set_signer(&self, ap: Arc, address: Address, password: String) { + { + *self.authority.write() = address; + *self.password.write() = Some(password); + *self.account_provider.lock() = ap; + } self.to_step(Step::Propose); } @@ -651,10 +646,6 @@ impl Engine for Tendermint { *self.client.write() = Some(client.clone()); self.validators.register_call_contract(client); } - - fn register_account_provider(&self, account_provider: Arc) { - *self.account_provider.lock() = Some(account_provider); - } } #[cfg(test)] @@ -678,7 +669,6 @@ mod tests { fn setup() -> (Spec, Arc) { let tap = Arc::new(AccountProvider::transient_provider()); let spec = Spec::new_test_tendermint(); - spec.engine.register_account_provider(tap.clone()); (spec, tap) } @@ -722,7 +712,7 @@ mod tests { fn insert_and_register(tap: &Arc, engine: &Engine, acc: &str) -> Address { let addr = insert_and_unlock(tap, acc); - engine.set_signer(addr.clone(), acc.into()); + engine.set_signer(tap.clone(), addr.clone(), acc.into()); addr } @@ -884,7 +874,7 @@ mod tests { let v0 = insert_and_register(&tap, engine.as_ref(), "0"); let v1 = insert_and_register(&tap, engine.as_ref(), "1"); - let h = 0; + let h = 1; let r = 0; // Propose @@ -915,16 +905,16 @@ mod tests { use types::transaction::{Transaction, Action}; use client::BlockChainClient; - let client = generate_dummy_client_with_spec_and_data(Spec::new_test_tendermint, 0, 0, &[]); - let engine = client.engine(); let tap = Arc::new(AccountProvider::transient_provider()); - // Accounts for signing votes. let v0 = insert_and_unlock(&tap, "0"); let v1 = insert_and_unlock(&tap, "1"); + let client = generate_dummy_client_with_spec_and_accounts(Spec::new_test_tendermint, Some(tap.clone())); + let engine = client.engine(); + + client.miner().set_engine_signer(v1.clone(), "1".into()).unwrap(); let notify = Arc::new(TestNotify::default()); - engine.register_account_provider(tap.clone()); client.add_notify(notify.clone()); engine.register_client(Arc::downgrade(&client)); @@ -939,8 +929,6 @@ mod tests { }.sign(keypair.secret(), None); client.miner().import_own_transaction(client.as_ref(), transaction.into()).unwrap(); - client.miner().set_engine_signer(v1.clone(), "1".into()).unwrap(); - // Propose let proposal = Some(client.miner().pending_block().unwrap().header.bare_hash()); // Propose timeout diff --git a/ethcore/src/engines/validator_set/contract.rs b/ethcore/src/engines/validator_set/contract.rs index 7efe668e62f..fa7dbea41da 100644 --- a/ethcore/src/engines/validator_set/contract.rs +++ b/ethcore/src/engines/validator_set/contract.rs @@ -142,13 +142,13 @@ mod tests { use client::{BlockChainClient, EngineClient}; use ethkey::Secret; use miner::MinerService; - use tests::helpers::generate_dummy_client_with_spec_and_data; + use tests::helpers::generate_dummy_client_with_spec_and_accounts; use super::super::ValidatorSet; use super::ValidatorContract; #[test] fn fetches_validators() { - let client = generate_dummy_client_with_spec_and_data(Spec::new_validator_contract, 0, 0, &[]); + let client = generate_dummy_client_with_spec_and_accounts(Spec::new_validator_contract, None); let vc = Arc::new(ValidatorContract::new(Address::from_str("0000000000000000000000000000000000000005").unwrap())); vc.register_call_contract(Arc::downgrade(&client)); vc.update(); @@ -162,12 +162,7 @@ mod tests { let s0 = Secret::from_slice(&"1".sha3()).unwrap(); let v0 = tap.insert_account(s0.clone(), "").unwrap(); let v1 = tap.insert_account(Secret::from_slice(&"0".sha3()).unwrap(), "").unwrap(); - let spec_factory = || { - let spec = Spec::new_validator_contract(); - spec.engine.register_account_provider(tap.clone()); - spec - }; - let client = generate_dummy_client_with_spec_and_data(spec_factory, 0, 0, &[]); + let client = generate_dummy_client_with_spec_and_accounts(Spec::new_validator_contract, Some(tap)); client.engine().register_client(Arc::downgrade(&client)); let validator_contract = Address::from_str("0000000000000000000000000000000000000005").unwrap(); diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 2235e278f9c..187fcfa95d0 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -148,7 +148,8 @@ impl GasPriceCalibrator { if Instant::now() >= self.next_calibration { let usd_per_tx = self.options.usd_per_tx; trace!(target: "miner", "Getting price info"); - let price_info = PriceInfo::get(move |price: PriceInfo| { + + PriceInfo::get(move |price: PriceInfo| { trace!(target: "miner", "Price info arrived: {:?}", price); let usd_per_eth = price.ethusd; let wei_per_usd: f32 = 1.0e18 / usd_per_eth; @@ -158,11 +159,7 @@ impl GasPriceCalibrator { set_price(U256::from(wei_per_gas as u64)); }); - if price_info.is_ok() { - self.next_calibration = Instant::now() + self.options.recalibration_period; - } else { - warn!(target: "miner", "Unable to update Ether price."); - } + self.next_calibration = Instant::now() + self.options.recalibration_period; } } } @@ -306,16 +303,6 @@ impl Miner { #[cfg_attr(feature="dev", allow(match_same_arms))] /// Prepares new block for sealing including top transactions from queue. fn prepare_block(&self, chain: &MiningBlockChainClient) -> (ClosedBlock, Option) { - { - trace!(target: "miner", "prepare_block: recalibrating..."); - let txq = self.transaction_queue.clone(); - self.gas_pricer.lock().recalibrate(move |price| { - trace!(target: "miner", "prepare_block: Got gas price! {}", price); - txq.lock().set_minimal_gas_price(price); - }); - trace!(target: "miner", "prepare_block: done recalibration."); - } - let _timer = PerfTimer::new("prepare_block"); let chain_info = chain.chain_info(); let (transactions, mut open_block, original_work_hash) = { @@ -430,6 +417,16 @@ impl Miner { (block, original_work_hash) } + /// Asynchronously updates minimal gas price for transaction queue + pub fn recalibrate_minimal_gas_price(&self) { + debug!(target: "miner", "minimal_gas_price: recalibrating..."); + let txq = self.transaction_queue.clone(); + self.gas_pricer.lock().recalibrate(move |price| { + debug!(target: "miner", "minimal_gas_price: Got gas price! {}", price); + txq.lock().set_minimal_gas_price(price); + }); + } + /// Check is reseal is allowed and necessary. fn requires_reseal(&self, best_block: BlockNumber) -> bool { let has_local_transactions = self.transaction_queue.lock().has_local_pending_transactions(); @@ -760,19 +757,19 @@ impl MinerService for Miner { if self.seals_internally { if let Some(ref ap) = self.accounts { ap.sign(address.clone(), Some(password.clone()), Default::default())?; + // Limit the scope of the locks. + { + let mut sealing_work = self.sealing_work.lock(); + sealing_work.enabled = self.engine.is_sealer(&address).unwrap_or(false); + *self.author.write() = address; + } + // -------------------------------------------------------------------------- + // | NOTE Code below may require author and sealing_work locks | + // | (some `Engine`s call `EngineClient.update_sealing()`) |. + // | Make sure to release the locks before calling that method. | + // -------------------------------------------------------------------------- + self.engine.set_signer(ap.clone(), address, password); } - // Limit the scope of the locks. - { - let mut sealing_work = self.sealing_work.lock(); - sealing_work.enabled = self.engine.is_sealer(&address).unwrap_or(false); - *self.author.write() = address; - } - // -------------------------------------------------------------------------- - // | NOTE Code below may require author and sealing_work locks | - // | (some `Engine`s call `EngineClient.update_sealing()`) |. - // | Make sure to release the locks before calling that method. | - // -------------------------------------------------------------------------- - self.engine.set_signer(address, password); } Ok(()) } @@ -1123,6 +1120,9 @@ impl MinerService for Miner { // First update gas limit in transaction queue self.update_gas_limit(chain); + // Update minimal gas price + self.recalibrate_minimal_gas_price(); + // Then import all transactions... { diff --git a/ethcore/src/miner/price_info.rs b/ethcore/src/miner/price_info.rs index 9cd97e28742..3f38ffbb2cb 100644 --- a/ethcore/src/miner/price_info.rs +++ b/ethcore/src/miner/price_info.rs @@ -21,7 +21,7 @@ use std::time::Duration; use std::str::FromStr; use std::sync::mpsc; use hyper::client::{Handler, Request, Response, Client}; -use hyper::{Next, Encoder, Decoder}; +use hyper::{Url, Next, Encoder, Decoder}; use hyper::net::HttpStream; #[derive(Debug)] @@ -29,12 +29,12 @@ pub struct PriceInfo { pub ethusd: f32, } -pub struct SetPriceHandler { +pub struct SetPriceHandler { set_price: F, channel: mpsc::Sender<()>, } -impl Drop for SetPriceHandler { +impl Drop for SetPriceHandler { fn drop(&mut self) { let _ = self.channel.send(()); } @@ -47,37 +47,61 @@ impl Handler for SetPriceH fn on_response_readable(&mut self, r: &mut Decoder) -> Next { let mut body = String::new(); - let _ = r.read_to_string(&mut body).ok() - .and_then(|_| Json::from_str(&body).ok()) - .and_then(|json| json.find_path(&["result", "ethusd"]) - .and_then(|obj| match *obj { - Json::String(ref s) => Some((self.set_price)(PriceInfo { - ethusd: FromStr::from_str(s) - .expect("Etherscan API will always return properly formatted price; qed") - })), - _ => None, - })); + let info = r.read_to_string(&mut body) + .map_err(|e| format!("Unable to read response: {:?}", e)) + .and_then(|_| self.process_response(&body)); + + if let Err(e) = info { + warn!("Failed to auto-update latest ETH price: {:?}", e); + } Next::end() } +} + +impl SetPriceHandler { + fn process_response(&self, body: &str) -> Result<(), String> { + let json = Json::from_str(body).map_err(|e| format!("Invalid JSON returned: {:?}", e))?; + let obj = json.find_path(&["result", "ethusd"]).ok_or("USD price not found".to_owned())?; + let ethusd = match *obj { + Json::String(ref s) => FromStr::from_str(s).ok(), + _ => None, + }.ok_or("Unexpected price format.".to_owned())?; + (self.set_price)(PriceInfo { + ethusd: ethusd, + }); + Ok(()) + } } impl PriceInfo { - pub fn get(set_price: F) -> Result<(), ()> { - // TODO: Handle each error type properly - let client = Client::new().map_err(|_| ())?; + pub fn get(set_price: F) { thread::spawn(move || { - let (tx, rx) = mpsc::channel(); let url = FromStr::from_str("http://api.etherscan.io/api?module=stats&action=ethprice") .expect("string known to be a valid URL; qed"); - let _ = client.request( - url, - SetPriceHandler { - set_price: set_price, - channel: tx, - }).ok().and_then(|_| rx.recv().ok()); - client.close(); + + if let Err(e) = Self::request(url, set_price) { + warn!("Failed to auto-update latest ETH price: {:?}", e); + } }); + } + + fn request(url: Url, set_price: F) -> Result<(), String> { + let (tx, rx) = mpsc::channel(); + let client = Client::new().map_err(|e| format!("Unable to start client: {:?}", e))?; + + client.request( + url, + SetPriceHandler { + set_price: set_price, + channel: tx, + }, + ).map_err(|_| "Request failed.".to_owned())?; + + // Wait for exit + let _ = rx.recv().map_err(|e| format!("Request interrupted: {:?}", e))?; + client.close(); + Ok(()) } } @@ -93,7 +117,7 @@ fn should_get_price_info() { let done = Arc::new((Mutex::new(PriceInfo { ethusd: 0f32 }), Condvar::new())); let rdone = done.clone(); - PriceInfo::get(move |price| { let mut p = rdone.0.lock(); *p = price; rdone.1.notify_one(); }).unwrap(); + PriceInfo::get(move |price| { let mut p = rdone.0.lock(); *p = price; rdone.1.notify_one(); }); let mut p = done.0.lock(); let t = done.1.wait_for(&mut p, Duration::from_millis(10000)); assert!(!t.timed_out()); diff --git a/ethcore/src/tests/helpers.rs b/ethcore/src/tests/helpers.rs index 0af84a52887..80419d8060e 100644 --- a/ethcore/src/tests/helpers.rs +++ b/ethcore/src/tests/helpers.rs @@ -19,6 +19,7 @@ use io::*; use client::{BlockChainClient, Client, ClientConfig}; use util::*; use spec::*; +use account_provider::AccountProvider; use state_db::StateDB; use block::{OpenBlock, Drain}; use blockchain::{BlockChain, Config as BlockChainConfig}; @@ -140,7 +141,16 @@ pub fn generate_dummy_client_with_data(block_number: u32, txs_per_block: usize, generate_dummy_client_with_spec_and_data(Spec::new_null, block_number, txs_per_block, tx_gas_prices) } + pub fn generate_dummy_client_with_spec_and_data(get_test_spec: F, block_number: u32, txs_per_block: usize, tx_gas_prices: &[U256]) -> GuardedTempResult> where F: Fn()->Spec { + generate_dummy_client_with_spec_accounts_and_data(get_test_spec, None, block_number, txs_per_block, tx_gas_prices) +} + +pub fn generate_dummy_client_with_spec_and_accounts(get_test_spec: F, accounts: Option>) -> GuardedTempResult> where F: Fn()->Spec { + generate_dummy_client_with_spec_accounts_and_data(get_test_spec, accounts, 0, 0, &[]) +} + +pub fn generate_dummy_client_with_spec_accounts_and_data(get_test_spec: F, accounts: Option>, block_number: u32, txs_per_block: usize, tx_gas_prices: &[U256]) -> GuardedTempResult> where F: Fn()->Spec { let dir = RandomTempPath::new(); let test_spec = get_test_spec(); let db_config = DatabaseConfig::with_columns(::db::NUM_COLUMNS); @@ -149,7 +159,7 @@ pub fn generate_dummy_client_with_spec_and_data(get_test_spec: F, block_numbe ClientConfig::default(), &test_spec, dir.as_path(), - Arc::new(Miner::with_spec(&test_spec)), + Arc::new(Miner::with_spec_and_accounts(&test_spec, accounts)), IoChannel::disconnected(), &db_config ).unwrap(); diff --git a/js/.eslintrc.json b/js/.eslintrc.json index 686745982a6..42f9b055485 100644 --- a/js/.eslintrc.json +++ b/js/.eslintrc.json @@ -15,8 +15,17 @@ "jsx-quotes": ["error", "prefer-single"], "no-alert": "error", "no-debugger": "error", + "no-duplicate-imports": ["error", { + "includeExports": true + }], "object-curly-spacing": ["error", "always"], "object-property-newline": 0, + "padded-blocks": ["error", { + "blocks": "never", + "classes": "never", + "switches": "never" + }], + "react/jsx-closing-bracket-location": "error", "react/jsx-curly-spacing": ["error", "always"] } } diff --git a/js/package.json b/js/package.json index 24a2bf09ba7..a9c5e8a79a5 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.196", + "version": "0.2.198", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", diff --git a/js/src/dapps/basiccoin/AddressSelect/addressSelect.js b/js/src/dapps/basiccoin/AddressSelect/addressSelect.js index d29d078fea5..24250ae9407 100644 --- a/js/src/dapps/basiccoin/AddressSelect/addressSelect.js +++ b/js/src/dapps/basiccoin/AddressSelect/addressSelect.js @@ -67,7 +67,8 @@ export default class AddressSelect extends Component { ); @@ -80,7 +81,8 @@ export default class AddressSelect extends Component { return ( ); diff --git a/js/src/dapps/basiccoin/Application/Header/header.js b/js/src/dapps/basiccoin/Application/Header/header.js index 31674bd0fa6..10cd852774a 100644 --- a/js/src/dapps/basiccoin/Application/Header/header.js +++ b/js/src/dapps/basiccoin/Application/Header/header.js @@ -56,7 +56,8 @@ export default class Header extends Component { style={ { background } } colSpan={ position ? 1 : 2 } rowSpan={ position ? 1 : 2 } - onClick={ this.onNavigate(page.path) }> + onClick={ this.onNavigate(page.path) } + >
{ page.title }
diff --git a/js/src/dapps/basiccoin/Deploy/Deployment/deployment.js b/js/src/dapps/basiccoin/Deploy/Deployment/deployment.js index c6b8e215241..63f0bbe2108 100644 --- a/js/src/dapps/basiccoin/Deploy/Deployment/deployment.js +++ b/js/src/dapps/basiccoin/Deploy/Deployment/deployment.js @@ -146,7 +146,8 @@ export default class Deployment extends Component { + onChange={ this.onChangeFrom } + />
the owner account to deploy from
@@ -156,7 +157,8 @@ export default class Deployment extends Component { + onChange={ this.onChangeName } + />
{ nameError || 'an identifying name for the token' }
@@ -167,7 +169,8 @@ export default class Deployment extends Component { className={ styles.small } name='tla' value={ tla } - onChange={ this.onChangeTla } /> + onChange={ this.onChangeTla } + />
{ tlaError || 'unique network acronym for this token' }
@@ -180,7 +183,8 @@ export default class Deployment extends Component { max='999999999999' name='totalSupply' value={ totalSupply } - onChange={ this.onChangeSupply } /> + onChange={ this.onChangeSupply } + />
{ totalSupplyError || `number of tokens (base: ${baseText})` }
@@ -191,7 +195,8 @@ export default class Deployment extends Component {
+ onClick={ this.onDeploy } + > Deploy Token
diff --git a/js/src/dapps/basiccoin/IdentityIcon/identityIcon.js b/js/src/dapps/basiccoin/IdentityIcon/identityIcon.js index b22f62923fe..9707787614b 100644 --- a/js/src/dapps/basiccoin/IdentityIcon/identityIcon.js +++ b/js/src/dapps/basiccoin/IdentityIcon/identityIcon.js @@ -32,7 +32,8 @@ export default class IdentityIcon extends Component { return ( + src={ api.util.createIdentityImg(address, 3) } + /> ); } } diff --git a/js/src/dapps/basiccoin/Overview/Owner/owner.js b/js/src/dapps/basiccoin/Overview/Owner/owner.js index 35bac6160c8..55994da57c5 100644 --- a/js/src/dapps/basiccoin/Overview/Owner/owner.js +++ b/js/src/dapps/basiccoin/Overview/Owner/owner.js @@ -50,7 +50,8 @@ export default class Owner extends Component { { accounts[address].name } + address={ address } + /> @@ -67,7 +68,8 @@ export default class Owner extends Component {
+ tokenreg={ token.tokenreg } + />
{ token.address }
diff --git a/js/src/dapps/basiccoin/Overview/overview.js b/js/src/dapps/basiccoin/Overview/overview.js index 22a983387f4..4fd6bac11a7 100644 --- a/js/src/dapps/basiccoin/Overview/overview.js +++ b/js/src/dapps/basiccoin/Overview/overview.js @@ -88,7 +88,8 @@ export default class Overview extends Component { + address={ address } + /> )); } diff --git a/js/src/dapps/basiccoin/Transfer/Events/events.js b/js/src/dapps/basiccoin/Transfer/Events/events.js index 0bdca084f9f..c85898fc96d 100644 --- a/js/src/dapps/basiccoin/Transfer/Events/events.js +++ b/js/src/dapps/basiccoin/Transfer/Events/events.js @@ -99,7 +99,8 @@ export default class Events extends Component { + event={ event } + /> ); }); diff --git a/js/src/dapps/basiccoin/Transfer/Send/send.js b/js/src/dapps/basiccoin/Transfer/Send/send.js index 459c58da224..74ef907a7e6 100644 --- a/js/src/dapps/basiccoin/Transfer/Send/send.js +++ b/js/src/dapps/basiccoin/Transfer/Send/send.js @@ -149,7 +149,8 @@ export default class Send extends Component { + onChange={ this.onSelectFrom } + />
account to transfer from
@@ -179,7 +180,8 @@ export default class Send extends Component { step='0.1' value={ amount } max={ fromBalance ? fromBalance.balance.div(1000000).toFixed(6) : 1 } - onChange={ this.onAmountChange } /> + onChange={ this.onAmountChange } + />
{ amountError || maxAmountHint }
@@ -190,7 +192,8 @@ export default class Send extends Component {
+ onClick={ this.onSend } + > Transfer Tokens
@@ -206,7 +209,8 @@ export default class Send extends Component { return tokens.map((token) => ( )); diff --git a/js/src/dapps/dappreg/Button/button.js b/js/src/dapps/dappreg/Button/button.js index f37a9fa4719..ad11d2a1148 100644 --- a/js/src/dapps/dappreg/Button/button.js +++ b/js/src/dapps/dappreg/Button/button.js @@ -36,7 +36,8 @@ export default class Button extends Component { className={ classes } data-warning={ warning } disabled={ disabled } - onClick={ this.onClick }> + onClick={ this.onClick } + > { label } ); diff --git a/js/src/dapps/dappreg/ButtonBar/buttonBar.js b/js/src/dapps/dappreg/ButtonBar/buttonBar.js index f885acb0fd3..b402daaa3ae 100644 --- a/js/src/dapps/dappreg/ButtonBar/buttonBar.js +++ b/js/src/dapps/dappreg/ButtonBar/buttonBar.js @@ -37,12 +37,14 @@ export default class ButtonBar extends Component { key='cancel' label='Cancel' warning - onClick={ this.onCancelClick } />, + onClick={ this.onCancelClick } + />, + onClick={ this.onClickTypeNormal } + > + File Link + + onClick={ this.onClickTypeContent } + > + Content Bundle +
@@ -148,7 +157,8 @@ export default class Application extends Component {
+ events={ this.state.events } + />
); } @@ -167,7 +177,8 @@ export default class Application extends Component { + disabled={ (contentHashError && contentHashOwner !== fromAddress) || urlError || repoError || commitError } + >register url ); } diff --git a/js/src/dapps/githubhint/IdentityIcon/identityIcon.js b/js/src/dapps/githubhint/IdentityIcon/identityIcon.js index 60012bc5d09..aab44f3b142 100644 --- a/js/src/dapps/githubhint/IdentityIcon/identityIcon.js +++ b/js/src/dapps/githubhint/IdentityIcon/identityIcon.js @@ -30,7 +30,8 @@ export default class IdentityIcon extends Component { return ( + src={ api.util.createIdentityImg(address, 3) } + /> ); } } diff --git a/js/src/dapps/localtx/Application/application.js b/js/src/dapps/localtx/Application/application.js index 33c5d0558c3..0e264265705 100644 --- a/js/src/dapps/localtx/Application/application.js +++ b/js/src/dapps/localtx/Application/application.js @@ -167,7 +167,7 @@ export default class Application extends Component { transaction={ tx.transaction } stats={ tx.stats } blockNumber={ blockNumber } - /> + /> )) } @@ -198,7 +198,7 @@ export default class Application extends Component { status={ tx.status } stats={ tx.stats } details={ tx } - /> + /> )) } diff --git a/js/src/dapps/localtx/Transaction/transaction.js b/js/src/dapps/localtx/Transaction/transaction.js index 1756597abdd..8eecaad4551 100644 --- a/js/src/dapps/localtx/Transaction/transaction.js +++ b/js/src/dapps/localtx/Transaction/transaction.js @@ -25,7 +25,6 @@ import styles from './transaction.css'; import IdentityIcon from '../../githubhint/IdentityIcon'; class BaseTransaction extends Component { - shortHash (hash) { return `${hash.substr(0, 5)}..${hash.substr(hash.length - 3)}`; } @@ -47,7 +46,7 @@ class BaseTransaction extends Component {
+ />
); } @@ -89,7 +88,6 @@ class BaseTransaction extends Component { } export class Transaction extends BaseTransaction { - static propTypes = { idx: PropTypes.number.isRequired, transaction: PropTypes.object.isRequired, @@ -180,7 +178,6 @@ export class Transaction extends BaseTransaction { } export class LocalTransaction extends BaseTransaction { - static propTypes = { hash: PropTypes.string.isRequired, status: PropTypes.string.isRequired, @@ -361,12 +358,12 @@ export class LocalTransaction extends BaseTransaction { type='text' value={ gasPrice } onChange={ this.setGasPrice } - /> + /> + /> @@ -376,5 +373,4 @@ export class LocalTransaction extends BaseTransaction { ); } - } diff --git a/js/src/dapps/registry/Accounts/accounts.js b/js/src/dapps/registry/Accounts/accounts.js index 29bcb7adda1..26681f122f6 100644 --- a/js/src/dapps/registry/Accounts/accounts.js +++ b/js/src/dapps/registry/Accounts/accounts.js @@ -29,7 +29,6 @@ import { select } from './actions'; import styles from './accounts.css'; class Accounts extends Component { - static propTypes = { all: PropTypes.object.isRequired, selected: PropTypes.object, diff --git a/js/src/dapps/registry/Application/application.js b/js/src/dapps/registry/Application/application.js index a1d5c0ef22d..47cc9390035 100644 --- a/js/src/dapps/registry/Application/application.js +++ b/js/src/dapps/registry/Application/application.js @@ -98,5 +98,4 @@ export default class Application extends Component { ); } - } diff --git a/js/src/dapps/registry/Events/events.js b/js/src/dapps/registry/Events/events.js index d204822d1b3..58cfc120f6c 100644 --- a/js/src/dapps/registry/Events/events.js +++ b/js/src/dapps/registry/Events/events.js @@ -147,7 +147,6 @@ const eventTypes = { }; class Events extends Component { - static propTypes = { events: PropTypes.array.isRequired, pending: PropTypes.object.isRequired, diff --git a/js/src/dapps/registry/IdentityIcon/identityIcon.js b/js/src/dapps/registry/IdentityIcon/identityIcon.js index 873c2eb88d5..6cd6139e7d1 100644 --- a/js/src/dapps/registry/IdentityIcon/identityIcon.js +++ b/js/src/dapps/registry/IdentityIcon/identityIcon.js @@ -33,7 +33,8 @@ export default class IdentityIcon extends Component { + src={ api.util.createIdentityImg(address, 3) } + /> ); } } diff --git a/js/src/dapps/registry/Lookup/lookup.js b/js/src/dapps/registry/Lookup/lookup.js index f572cbb7d9b..c34b538f83d 100644 --- a/js/src/dapps/registry/Lookup/lookup.js +++ b/js/src/dapps/registry/Lookup/lookup.js @@ -34,7 +34,6 @@ import { clear, lookup, ownerLookup, reverseLookup } from './actions'; import styles from './lookup.css'; class Lookup extends Component { - static propTypes = { result: nullableProptype(PropTypes.string.isRequired), diff --git a/js/src/dapps/registry/Names/names.js b/js/src/dapps/registry/Names/names.js index c34e172b9be..f9ce24cd70a 100644 --- a/js/src/dapps/registry/Names/names.js +++ b/js/src/dapps/registry/Names/names.js @@ -77,7 +77,6 @@ const renderQueue = (queue) => { }; class Names extends Component { - static propTypes = { error: nullableProptype(PropTypes.object.isRequired), fee: PropTypes.object.isRequired, diff --git a/js/src/dapps/registry/Records/records.js b/js/src/dapps/registry/Records/records.js index f9c9cea7631..73e5133425a 100644 --- a/js/src/dapps/registry/Records/records.js +++ b/js/src/dapps/registry/Records/records.js @@ -29,7 +29,6 @@ import { clearError, update } from './actions'; import styles from './records.css'; class Records extends Component { - static propTypes = { error: nullableProptype(PropTypes.object.isRequired), pending: PropTypes.bool.isRequired, diff --git a/js/src/dapps/signaturereg/Application/application.js b/js/src/dapps/signaturereg/Application/application.js index afddbfcaf99..e54f3deadc0 100644 --- a/js/src/dapps/signaturereg/Application/application.js +++ b/js/src/dapps/signaturereg/Application/application.js @@ -80,7 +80,8 @@ export default class Application extends Component { return (
+ totalSignatures={ totalSignatures } + /> ); } @@ -95,7 +96,8 @@ export default class Application extends Component { instance={ instance } visible={ showImport } onClose={ this.toggleImport } - onSetFromAddress={ this.setFromAddress } /> + onSetFromAddress={ this.setFromAddress } + /> ); } @@ -112,7 +114,8 @@ export default class Application extends Component { return ( + contract={ contract } + /> ); } diff --git a/js/src/dapps/signaturereg/IdentityIcon/identityIcon.js b/js/src/dapps/signaturereg/IdentityIcon/identityIcon.js index 60012bc5d09..aab44f3b142 100644 --- a/js/src/dapps/signaturereg/IdentityIcon/identityIcon.js +++ b/js/src/dapps/signaturereg/IdentityIcon/identityIcon.js @@ -30,7 +30,8 @@ export default class IdentityIcon extends Component { return ( + src={ api.util.createIdentityImg(address, 3) } + /> ); } } diff --git a/js/src/dapps/tokenreg/Accounts/AccountSelector/account-selector.js b/js/src/dapps/tokenreg/Accounts/AccountSelector/account-selector.js index 5cb84f5a9a1..6c71578a099 100644 --- a/js/src/dapps/tokenreg/Accounts/AccountSelector/account-selector.js +++ b/js/src/dapps/tokenreg/Accounts/AccountSelector/account-selector.js @@ -24,7 +24,6 @@ import IdentityIcon from '../../IdentityIcon'; import styles from './account-selector.css'; class AccountSelectorItem extends Component { - static propTypes = { onSelectAccount: PropTypes.func.isRequired, account: PropTypes.object.isRequired @@ -37,15 +36,19 @@ class AccountSelectorItem extends Component { delete props.account; delete props.onSelectAccount; - const icon = ( + const icon = ( + ); - const avatar = ( + const avatar = ( + ); return ( @@ -55,18 +58,17 @@ class AccountSelectorItem extends Component { primaryText={ account.name } secondaryText={ account.address } leftAvatar={ avatar } - { ...props } /> + { ...props } + /> ); } onSelectAccount = () => { this.props.onSelectAccount(this.props.account.address); } - } export default class AccountSelector extends Component { - static propTypes = { list: PropTypes.array.isRequired, selected: PropTypes.object.isRequired, @@ -87,7 +89,8 @@ export default class AccountSelector extends Component { open={ this.state.open } onSelectAccount={ this.onToggleOpen } autoGenerateNestedIndicator={ false } - nestedListStyle={ { maxHeight: '14em', overflow: 'auto' } } /> + nestedListStyle={ { maxHeight: '14em', overflow: 'auto' } } + /> ); return ( @@ -106,7 +109,8 @@ export default class AccountSelector extends Component { + key={ index } + /> )); } @@ -122,5 +126,4 @@ export default class AccountSelector extends Component { this.props.handleSetSelected(address); this.onToggleOpen(); } - } diff --git a/js/src/dapps/tokenreg/Accounts/AccountSelector/container.js b/js/src/dapps/tokenreg/Accounts/AccountSelector/container.js index 6cd0dcc3a83..9711c05bfad 100644 --- a/js/src/dapps/tokenreg/Accounts/AccountSelector/container.js +++ b/js/src/dapps/tokenreg/Accounts/AccountSelector/container.js @@ -23,9 +23,11 @@ import { setSelectedAccount } from '../actions'; class AccountSelectorContainer extends Component { render () { - return (); + return ( + + ); } } @@ -42,4 +44,7 @@ const mapDispatchToProps = (dispatch) => { }; }; -export default connect(mapStateToProps, mapDispatchToProps)(AccountSelectorContainer); +export default connect( + mapStateToProps, + mapDispatchToProps +)(AccountSelectorContainer); diff --git a/js/src/dapps/tokenreg/Actions/Query/query.js b/js/src/dapps/tokenreg/Actions/Query/query.js index 452b387a556..ba008bc0aeb 100644 --- a/js/src/dapps/tokenreg/Actions/Query/query.js +++ b/js/src/dapps/tokenreg/Actions/Query/query.js @@ -35,7 +35,6 @@ const initState = { }; export default class QueryAction extends Component { - static propTypes = { show: PropTypes.bool.isRequired, loading: PropTypes.bool.isRequired, @@ -56,7 +55,8 @@ export default class QueryAction extends Component { open={ this.props.show } className={ styles.dialog } onRequestClose={ this.onClose } - actions={ this.renderActions() } > + actions={ this.renderActions() } + > { this.renderContent() } ); @@ -70,7 +70,8 @@ export default class QueryAction extends Component { + disabled + /> ); } @@ -81,7 +82,8 @@ export default class QueryAction extends Component { + onTouchTap={ this.onClose } + /> ]); } @@ -91,12 +93,14 @@ export default class QueryAction extends Component { , + onTouchTap={ this.onClose } + />, + onTouchTap={ this.onQuery } + /> ]); } @@ -140,31 +144,38 @@ export default class QueryAction extends Component { floatingLabelText='Select which field to query' fullWidth value={ this.state.queryKey } - onChange={ this.onQueryKeyChange }> + onChange={ this.onQueryKeyChange } + > { this.state.queryKey !== 'tla' - ? () - : () + ? ( + + ) + : ( + + ) } ); @@ -203,5 +214,4 @@ export default class QueryAction extends Component { this.setState(initState); this.props.onClose(); } - } diff --git a/js/src/dapps/tokenreg/Actions/Register/register.js b/js/src/dapps/tokenreg/Actions/Register/register.js index ec1199f3d32..2c084cec281 100644 --- a/js/src/dapps/tokenreg/Actions/Register/register.js +++ b/js/src/dapps/tokenreg/Actions/Register/register.js @@ -57,7 +57,6 @@ const initState = { }; export default class RegisterAction extends Component { - static propTypes = { show: PropTypes.bool.isRequired, sending: PropTypes.bool.isRequired, @@ -97,7 +96,8 @@ export default class RegisterAction extends Component { + onTouchTap={ this.onClose } + /> ); } @@ -106,7 +106,8 @@ export default class RegisterAction extends Component { + onTouchTap={ this.onClose } + /> ); } @@ -116,12 +117,14 @@ export default class RegisterAction extends Component { , + onTouchTap={ this.onClose } + />, + onTouchTap={ this.onRegister } + /> ]); } @@ -177,7 +180,8 @@ export default class RegisterAction extends Component { hintText={ field.hintText } validationType={ field.type } - onChange={ onChange } /> + onChange={ onChange } + /> ); }); } @@ -227,5 +231,4 @@ export default class RegisterAction extends Component { this.setState(initState); this.props.onClose(); } - } diff --git a/js/src/dapps/tokenreg/Actions/component.js b/js/src/dapps/tokenreg/Actions/component.js index c79f2563a86..0152832c4ee 100644 --- a/js/src/dapps/tokenreg/Actions/component.js +++ b/js/src/dapps/tokenreg/Actions/component.js @@ -29,7 +29,6 @@ const REGISTER_ACTION = 'REGISTER_ACTION'; const QUERY_ACTION = 'QUERY_ACTION'; export default class Actions extends Component { - static propTypes = { handleRegisterToken: PropTypes.func.isRequired, handleRegisterClose: PropTypes.func.isRequired, @@ -62,26 +61,30 @@ export default class Actions extends Component { icon={ } label='Register Token' primary - onTouchTap={ this.onShowRegister } /> + onTouchTap={ this.onShowRegister } + /> } label='Search Token' primary - onTouchTap={ this.onShowQuery } /> + onTouchTap={ this.onShowQuery } + /> + { ...this.props.register } + /> + { ...this.props.query } + /> ); } @@ -113,5 +116,4 @@ export default class Actions extends Component { } }); } - } diff --git a/js/src/dapps/tokenreg/Actions/container.js b/js/src/dapps/tokenreg/Actions/container.js index 2a4800a671b..eecca7ac8d3 100644 --- a/js/src/dapps/tokenreg/Actions/container.js +++ b/js/src/dapps/tokenreg/Actions/container.js @@ -22,11 +22,12 @@ import Actions from './component'; import { registerToken, registerReset, queryToken, queryReset } from './actions'; class TokensContainer extends Component { - render () { - return (); + return ( + + ); } } diff --git a/js/src/dapps/tokenreg/Application/application.js b/js/src/dapps/tokenreg/Application/application.js index 0ac78402318..4ae405683a7 100644 --- a/js/src/dapps/tokenreg/Application/application.js +++ b/js/src/dapps/tokenreg/Application/application.js @@ -56,7 +56,8 @@ export default class Application extends Component {
+ fee={ contract.fee } + /> @@ -73,5 +74,4 @@ export default class Application extends Component { muiTheme }; } - } diff --git a/js/src/dapps/tokenreg/Chip/chip.js b/js/src/dapps/tokenreg/Chip/chip.js index b5407af119e..6729670be0c 100644 --- a/js/src/dapps/tokenreg/Chip/chip.js +++ b/js/src/dapps/tokenreg/Chip/chip.js @@ -44,7 +44,8 @@ export default class CustomChip extends Component { background: '#27ae60', display: 'flex', flexDirection: 'column' - } }> + } } + > { this.renderIcon(isAddress, value) } { displayValue } @@ -64,7 +65,8 @@ export default class CustomChip extends Component { return ( + address={ address } + /> ); } } diff --git a/js/src/dapps/tokenreg/Container.js b/js/src/dapps/tokenreg/Container.js index 00f726cf9df..d6e62ad92b8 100644 --- a/js/src/dapps/tokenreg/Container.js +++ b/js/src/dapps/tokenreg/Container.js @@ -36,10 +36,12 @@ class Container extends Component { render () { const { isLoading, contract } = this.props; - return (); + return ( + + ); } } diff --git a/js/src/dapps/tokenreg/IdentityIcon/identityIcon.js b/js/src/dapps/tokenreg/IdentityIcon/identityIcon.js index 76dfb00793b..5ceef1aca04 100644 --- a/js/src/dapps/tokenreg/IdentityIcon/identityIcon.js +++ b/js/src/dapps/tokenreg/IdentityIcon/identityIcon.js @@ -30,7 +30,8 @@ export default class IdentityIcon extends Component { return ( + src={ api.util.createIdentityImg(address, 4) } + /> ); } } diff --git a/js/src/dapps/tokenreg/Inputs/Text/container.js b/js/src/dapps/tokenreg/Inputs/Text/container.js index fe8ae4c2630..77ebb9e5cc6 100644 --- a/js/src/dapps/tokenreg/Inputs/Text/container.js +++ b/js/src/dapps/tokenreg/Inputs/Text/container.js @@ -20,11 +20,12 @@ import { connect } from 'react-redux'; import InputText from './input-text'; class InputTextContainer extends Component { - render () { - return (); + return ( + + ); } } diff --git a/js/src/dapps/tokenreg/Inputs/Text/input-text.js b/js/src/dapps/tokenreg/Inputs/Text/input-text.js index 3ddd7591f67..2184b6b5071 100644 --- a/js/src/dapps/tokenreg/Inputs/Text/input-text.js +++ b/js/src/dapps/tokenreg/Inputs/Text/input-text.js @@ -35,7 +35,6 @@ const initState = { }; export default class InputText extends Component { - static propTypes = { validationType: PropTypes.string.isRequired, onChange: PropTypes.func.isRequired, @@ -64,7 +63,8 @@ export default class InputText extends Component { disabled={ disabled } errorText={ error } onChange={ this.onChange } - onKeyDown={ this.onKeyDown } /> + onKeyDown={ this.onKeyDown } + /> { this.renderLoading() } { this.renderIsValid() } @@ -146,5 +146,4 @@ export default class InputText extends Component { return this.props.onChange(false, value); } - } diff --git a/js/src/dapps/tokenreg/Status/status.js b/js/src/dapps/tokenreg/Status/status.js index 64ebba3b6f2..290d1720638 100644 --- a/js/src/dapps/tokenreg/Status/status.js +++ b/js/src/dapps/tokenreg/Status/status.js @@ -38,7 +38,8 @@ export default class Status extends Component { + label='Fee' + />
); } diff --git a/js/src/dapps/tokenreg/Tokens/Token/add-meta.js b/js/src/dapps/tokenreg/Tokens/Token/add-meta.js index 3dc4bdbe105..b4b1a3cf1eb 100644 --- a/js/src/dapps/tokenreg/Tokens/Token/add-meta.js +++ b/js/src/dapps/tokenreg/Tokens/Token/add-meta.js @@ -56,7 +56,8 @@ export default class AddMeta extends Component { icon={ } primary fullWidth - onTouchTap={ this.onShowDialog } /> + onTouchTap={ this.onShowDialog } + /> + actions={ this.renderActions() } + > { this.renderContent() } ); @@ -78,7 +80,8 @@ export default class AddMeta extends Component { + onTouchTap={ this.onClose } + /> ); } @@ -88,12 +91,14 @@ export default class AddMeta extends Component { , + onTouchTap={ this.onClose } + />, + onTouchTap={ this.onAdd } + /> ]); } @@ -130,7 +135,8 @@ export default class AddMeta extends Component { floatingLabelText='Choose the meta-data to add' fullWidth value={ this.state.metaKeyIndex } - onChange={ this.onMetaKeyChange }> + onChange={ this.onMetaKeyChange } + > { this.renderMetaKeyItems() } @@ -142,7 +148,8 @@ export default class AddMeta extends Component { hintText={ `The value of the ${selectedMeta.label.toLowerCase()} (${selectedMeta.validation === ADDRESS_TYPE ? 'Address' : 'Url Hint'})` } validationType={ selectedMeta.validation } - onChange={ this.onChange } /> + onChange={ this.onChange } + /> ); } @@ -152,7 +159,8 @@ export default class AddMeta extends Component { + label={ key.label } primaryText={ key.label } + /> )); } @@ -194,5 +202,4 @@ export default class AddMeta extends Component { value: '' } }); } - } diff --git a/js/src/dapps/tokenreg/Tokens/Token/token.js b/js/src/dapps/tokenreg/Tokens/Token/token.js index 2b54306c2aa..1500d194e52 100644 --- a/js/src/dapps/tokenreg/Tokens/Token/token.js +++ b/js/src/dapps/tokenreg/Tokens/Token/token.js @@ -99,7 +99,8 @@ export default class Token extends Component { return (
+ } } + >
{ this.renderContent() } @@ -128,7 +129,8 @@ export default class Token extends Component { floatingLabelText='Choose the meta-data to look-up' fullWidth value={ this.state.metaKeyIndex } - onChange={ this.onMetaKeyChange }> + onChange={ this.onMetaKeyChange } + > { this.renderMetaKeyItems() } @@ -139,7 +141,8 @@ export default class Token extends Component { icon={ } primary fullWidth - onTouchTap={ this.onMetaLookup } /> + onTouchTap={ this.onMetaLookup } + />
{ this.renderMeta(meta) } @@ -157,7 +160,8 @@ export default class Token extends Component { + label={ key.label } primaryText={ key.label } + /> )); } @@ -169,7 +173,8 @@ export default class Token extends Component { return ( + label='Decimals' + /> ); } @@ -182,7 +187,8 @@ export default class Token extends Component { + label='Address' + /> ); } @@ -192,7 +198,8 @@ export default class Token extends Component { return ( + label='Total' + /> ); } @@ -212,7 +219,8 @@ export default class Token extends Component { isAddress displayValue={ displayValue } value={ owner } - label='Owner' /> + label='Owner' + /> ); } @@ -237,7 +245,8 @@ export default class Token extends Component { + index={ this.props.index } + /> ); } @@ -253,7 +262,8 @@ export default class Token extends Component { icon={ } secondary fullWidth - onTouchTap={ this.onUnregister } /> + onTouchTap={ this.onUnregister } + /> ); } diff --git a/js/src/modals/AddAddress/addAddress.js b/js/src/modals/AddAddress/addAddress.js index 437b876639c..567be169f47 100644 --- a/js/src/modals/AddAddress/addAddress.js +++ b/js/src/modals/AddAddress/addAddress.js @@ -51,9 +51,11 @@ export default class AddAddress extends Component { title={ + defaultMessage='add saved address' + /> } - visible> + visible + > { this.renderFields() } ); @@ -68,20 +70,24 @@ export default class AddAddress extends Component { label={ + defaultMessage='Cancel' + /> } onClick={ this.onClose } - ref='closeButton' />, + ref='closeButton' + />,
diff --git a/js/src/modals/CreateAccount/NewGeth/newGeth.js b/js/src/modals/CreateAccount/NewGeth/newGeth.js index f8cd1fbe692..96e48fa2563 100644 --- a/js/src/modals/CreateAccount/NewGeth/newGeth.js +++ b/js/src/modals/CreateAccount/NewGeth/newGeth.js @@ -54,7 +54,8 @@ export default class NewGeth extends Component {
+ address={ account.address } + />
{ account.address }
@@ -69,7 +70,8 @@ export default class NewGeth extends Component { checked={ account.checked } label={ label } data-address={ account.address } - onCheck={ this.onSelect } /> + onCheck={ this.onSelect } + /> ); }); diff --git a/js/src/modals/CreateAccount/NewImport/newImport.js b/js/src/modals/CreateAccount/NewImport/newImport.js index 91ef39d9505..d1dfdc5d6c2 100644 --- a/js/src/modals/CreateAccount/NewImport/newImport.js +++ b/js/src/modals/CreateAccount/NewImport/newImport.js @@ -59,12 +59,14 @@ export default class NewImport extends Component { hint='a descriptive name for the account' error={ this.state.accountNameError } value={ this.state.accountName } - onChange={ this.onEditAccountName } /> + onChange={ this.onEditAccountName } + /> + onChange={ this.onEditpasswordHint } + />
+ onChange={ this.onEditPassword } + />
@@ -82,18 +85,21 @@ export default class NewImport extends Component { label='wallet file' hint='the wallet file for import' error={ this.state.walletFileError } - value={ this.state.walletFile } /> + value={ this.state.walletFile } + />
+ onTouchTap={ this.openFileDialog } + > + onChange={ this.onFileChange } + />
diff --git a/js/src/modals/CreateAccount/RawKey/rawKey.js b/js/src/modals/CreateAccount/RawKey/rawKey.js index d0b3a4c7118..5a60fb5df2a 100644 --- a/js/src/modals/CreateAccount/RawKey/rawKey.js +++ b/js/src/modals/CreateAccount/RawKey/rawKey.js @@ -60,18 +60,21 @@ export default class RawKey extends Component { label='private key' error={ rawKeyError } value={ rawKey } - onChange={ this.onEditKey } /> + onChange={ this.onEditKey } + /> + onChange={ this.onEditAccountName } + /> + onChange={ this.onEditPasswordHint } + />
+ onChange={ this.onEditPassword1 } + />
+ onChange={ this.onEditPassword2 } + />
diff --git a/js/src/modals/CreateAccount/RecoveryPhrase/recoveryPhrase.js b/js/src/modals/CreateAccount/RecoveryPhrase/recoveryPhrase.js index 9d76cebfaaf..da91b4ba2b5 100644 --- a/js/src/modals/CreateAccount/RecoveryPhrase/recoveryPhrase.js +++ b/js/src/modals/CreateAccount/RecoveryPhrase/recoveryPhrase.js @@ -57,18 +57,21 @@ export default class RecoveryPhrase extends Component { hint='the account recovery phrase' label='account recovery phrase' value={ recoveryPhrase } - onChange={ this.onEditPhrase } /> + onChange={ this.onEditPhrase } + /> + onChange={ this.onEditAccountName } + /> + onChange={ this.onEditPasswordHint } + />
+ onChange={ this.onEditPassword1 } + />
+ onChange={ this.onEditPassword2 } + />
+ steps={ steps } + > { this.renderWarning() } { this.renderPage() } @@ -162,11 +163,13 @@ export default class CreateAccount extends Component {
- } /> + } + /> ); }); } diff --git a/js/src/modals/DeleteAccount/deleteAccount.js b/js/src/modals/DeleteAccount/deleteAccount.js index f5b988a8e5e..664f94d88e4 100644 --- a/js/src/modals/DeleteAccount/deleteAccount.js +++ b/js/src/modals/DeleteAccount/deleteAccount.js @@ -49,14 +49,16 @@ class DeleteAccount extends Component { title='confirm removal' visible onDeny={ this.closeDeleteDialog } - onConfirm={ this.onDeleteConfirmed }> + onConfirm={ this.onDeleteConfirmed } + >
Are you sure you want to permanently delete the following account?
+ address={ account.address } + />
@@ -75,7 +77,8 @@ class DeleteAccount extends Component { hint='provide the account password to confirm the account deletion' type='password' value={ password } - onChange={ this.onChangePassword } /> + onChange={ this.onChangePassword } + />
); diff --git a/js/src/modals/DeployContract/DetailsStep/detailsStep.js b/js/src/modals/DeployContract/DetailsStep/detailsStep.js index cfb8aa40f52..041f71527ae 100644 --- a/js/src/modals/DeployContract/DetailsStep/detailsStep.js +++ b/js/src/modals/DeployContract/DetailsStep/detailsStep.js @@ -99,12 +99,14 @@ export default class DetailsStep extends Component { hint={ + defaultMessage='the owner account for this contract' + /> } label={ + defaultMessage='from account (contract owner)' + /> } onChange={ this.onFromAddressChange } value={ fromAddress } @@ -115,12 +117,14 @@ export default class DetailsStep extends Component { hint={ + defaultMessage='a name for the deployed contract' + /> } label={ + defaultMessage='contract name' + /> } onChange={ this.onNameChange } value={ name || '' } @@ -131,12 +135,14 @@ export default class DetailsStep extends Component { hint={ + defaultMessage='a description for the contract' + /> } label={ + defaultMessage='contract description (optional)' + /> } onChange={ this.onDescriptionChange } value={ description } @@ -149,12 +155,14 @@ export default class DetailsStep extends Component { hint={ + defaultMessage='the abi of the contract to deploy or solc combined-output' + /> } label={ + defaultMessage='abi / solc combined-output' + /> } onChange={ this.onSolcChange } onSubmit={ this.onSolcSubmit } @@ -166,12 +174,14 @@ export default class DetailsStep extends Component { hint={ + defaultMessage='the compiled code of the contract to deploy' + /> } label={ + defaultMessage='code' + /> } onSubmit={ this.onCodeChange } readOnly={ readOnly || solc } @@ -196,7 +206,8 @@ export default class DetailsStep extends Component { + value={ index } + > { name } )); @@ -206,10 +217,12 @@ export default class DetailsStep extends Component { label={ + defaultMessage='select a contract' + /> } onChange={ this.onContractChange } - value={ selectedContractIndex }> + value={ selectedContractIndex } + > { contractsItems } ); diff --git a/js/src/modals/DeployContract/ParametersStep/parametersStep.js b/js/src/modals/DeployContract/ParametersStep/parametersStep.js index 8e557a95a53..952ddbc3416 100644 --- a/js/src/modals/DeployContract/ParametersStep/parametersStep.js +++ b/js/src/modals/DeployContract/ParametersStep/parametersStep.js @@ -84,7 +84,8 @@ export default class ParametersStep extends Component { label={ label } onChange={ onChange } param={ param } - value={ value } /> + value={ value } + />
); }); @@ -94,7 +95,8 @@ export default class ParametersStep extends Component {

+ defaultMessage='Choose the contract parameters' + />

{ inputsComponents }
diff --git a/js/src/modals/DeployContract/deployContract.js b/js/src/modals/DeployContract/deployContract.js index 701c689ad6d..d3db3937f8a 100644 --- a/js/src/modals/DeployContract/deployContract.js +++ b/js/src/modals/DeployContract/deployContract.js @@ -37,14 +37,16 @@ const STEPS = { title: ( + defaultMessage='contract details' + /> ) }, CONTRACT_PARAMETERS: { title: ( + defaultMessage='contract parameters' + /> ) }, DEPLOYMENT: { @@ -52,14 +54,16 @@ const STEPS = { title: ( + defaultMessage='deployment' + /> ) }, COMPLETED: { title: ( + defaultMessage='completed' + /> ) } }; @@ -149,10 +153,12 @@ class DeployContract extends Component { : (deployError ? + defaultMessage='deployment failed' + /> : + defaultMessage='rejected' + /> ); const waiting = realSteps @@ -170,7 +176,8 @@ class DeployContract extends Component { } title={ title } visible - waiting={ waiting }> + waiting={ waiting } + > { this.renderExceptionWarning() } { this.renderStep() } @@ -188,7 +195,8 @@ class DeployContract extends Component { return ( + warning={ errorEstimated } + /> ); } @@ -202,9 +210,11 @@ class DeployContract extends Component { label={ + defaultMessage='Cancel' + /> } - onClick={ this.onClose } /> + onClick={ this.onClose } + /> ); const closeBtn = ( @@ -213,9 +223,11 @@ class DeployContract extends Component { label={ + defaultMessage='Close' + /> } - onClick={ this.onClose } /> + onClick={ this.onClose } + /> ); const closeBtnOk = ( @@ -224,9 +236,11 @@ class DeployContract extends Component { label={ + defaultMessage='Done' + /> } - onClick={ this.onClose } /> + onClick={ this.onClose } + /> ); if (deployError) { @@ -242,14 +256,17 @@ class DeployContract extends Component { icon={ + button + /> } label={ + defaultMessage='Next' + /> } - onClick={ this.onParametersStep } /> + onClick={ this.onParametersStep } + /> ]; case 'CONTRACT_PARAMETERS': @@ -259,14 +276,17 @@ class DeployContract extends Component { icon={ + button + /> } label={ + defaultMessage='Create' + /> } - onClick={ this.onDeployStart } /> + onClick={ this.onDeployStart } + /> ]; case 'DEPLOYMENT': @@ -293,13 +313,16 @@ class DeployContract extends Component { title={ + defaultMessage='The deployment has been rejected' + /> } state={ - } /> + defaultMessage='You can safely close this window, the contract deployment will not occur.' + /> + } + /> ); } @@ -340,9 +363,11 @@ class DeployContract extends Component { title={ + defaultMessage='The deployment is currently in progress' + /> } - state={ deployState }> + state={ deployState } + > { body } ); @@ -353,7 +378,8 @@ class DeployContract extends Component {
+ defaultMessage='Your contract has been deployed at' + />
@@ -361,7 +387,8 @@ class DeployContract extends Component { address={ address } center className={ styles.identityicon } - inline /> + inline + />
{ address }
@@ -420,7 +447,8 @@ class DeployContract extends Component { : ( + defaultMessage='a valid account as the contract owner needs to be selected' + /> ); this.setState({ fromAddress, fromAddressError }, this.estimateGas); @@ -510,7 +538,8 @@ class DeployContract extends Component { deployState: ( + defaultMessage='Preparing transaction for network transmission' + /> ) }); return; @@ -520,7 +549,8 @@ class DeployContract extends Component { deployState: ( + defaultMessage='Waiting for confirmation of the transaction in the Parity Secure Signer' + /> ) }); return; @@ -531,7 +561,8 @@ class DeployContract extends Component { deployState: ( + defaultMessage='Waiting for the contract deployment transaction receipt' + /> ) }); return; @@ -542,7 +573,8 @@ class DeployContract extends Component { deployState: ( + defaultMessage='Validating the deployed contract code' + /> ) }); return; @@ -552,7 +584,8 @@ class DeployContract extends Component { deployState: ( + defaultMessage='The contract deployment has been completed' + /> ) }); return; diff --git a/js/src/modals/DeployContract/deployContract.spec.js b/js/src/modals/DeployContract/deployContract.spec.js index d61ada393f3..145248a4389 100644 --- a/js/src/modals/DeployContract/deployContract.spec.js +++ b/js/src/modals/DeployContract/deployContract.spec.js @@ -41,7 +41,8 @@ function renderShallow () { + onClose={ sinon.stub() } + /> ); } diff --git a/js/src/modals/EditMeta/editMeta.js b/js/src/modals/EditMeta/editMeta.js index a232492ea7e..a09b5f07c69 100644 --- a/js/src/modals/EditMeta/editMeta.js +++ b/js/src/modals/EditMeta/editMeta.js @@ -49,47 +49,57 @@ class EditMeta extends Component { title={ + defaultMessage='edit metadata' + /> } - visible> + visible + >
+ defaultMessage='name' + /> } onSubmit={ this.store.setName } - value={ name } /> + value={ name } + /> + defaultMessage='description for this address' + /> } label={ + defaultMessage='address description' + /> } value={ description } - onSubmit={ this.store.setDescription } /> + onSubmit={ this.store.setDescription } + /> { this.renderAccountFields() } + defaultMessage='press to add a tag' + /> } label={ + defaultMessage='(optional) tags' + /> } onTokensChange={ this.store.setTags } - tokens={ tags.slice() } /> + tokens={ tags.slice() } + /> ); @@ -102,12 +112,14 @@ class EditMeta extends Component {
@@ -140,7 +148,8 @@ export default class DetailsStep extends Component { + label={ func.name || '()' } + > { name } ); @@ -152,15 +161,18 @@ export default class DetailsStep extends Component { hint={ + defaultMessage='the function to call on the contract' + /> } label={ + defaultMessage='function to execute' + /> } onChange={ this.onFuncChange } - value={ func.signature }> + value={ func.signature } + > { functions } ); diff --git a/js/src/modals/ExecuteContract/DetailsStep/detailsStep.spec.js b/js/src/modals/ExecuteContract/DetailsStep/detailsStep.spec.js index 060b25c17b7..14b90ef0569 100644 --- a/js/src/modals/ExecuteContract/DetailsStep/detailsStep.spec.js +++ b/js/src/modals/ExecuteContract/DetailsStep/detailsStep.spec.js @@ -47,7 +47,8 @@ function render (props) { onFromAddressChange={ onFromAddressChange } onFuncChange={ onFuncChange } onGasEditClick={ onGasEditClick } - onValueChange={ onValueChange } /> + onValueChange={ onValueChange } + /> ); return component; diff --git a/js/src/modals/ExecuteContract/executeContract.js b/js/src/modals/ExecuteContract/executeContract.js index 689678a7c74..a4ac132eeda 100644 --- a/js/src/modals/ExecuteContract/executeContract.js +++ b/js/src/modals/ExecuteContract/executeContract.js @@ -41,27 +41,32 @@ const TITLES = { transfer: ( + defaultMessage='function details' + /> ), sending: ( + defaultMessage='sending' + /> ), complete: ( + defaultMessage='complete' + /> ), advanced: ( + defaultMessage='advanced options' + /> ), rejected: ( + defaultMessage='rejected' + /> ) }; const STAGES_BASIC = [TITLES.transfer, TITLES.sending, TITLES.complete]; @@ -139,7 +144,8 @@ class ExecuteContract extends Component { advancedOptions ? [STEP_BUSY] : [STEP_BUSY_OR_ADVANCED] - }> + } + > { this.renderExceptionWarning() } { this.renderStep() } @@ -170,10 +176,12 @@ class ExecuteContract extends Component { label={ + defaultMessage='cancel' + /> } icon={ } - onClick={ onClose } /> + onClick={ onClose } + /> ); const postBtn = (
- - - { this.renderDetails(account) } @@ -194,7 +191,8 @@ class Contract extends Component { ); @@ -333,5 +332,4 @@ export default class RpcCalls extends Component { resetRpcPrevCalls: PropTypes.func.isRequired }).isRequired } - } diff --git a/js/src/views/Status/components/RpcDocs/RpcDocs.js b/js/src/views/Status/components/RpcDocs/RpcDocs.js index 4eec9129054..6252dc0f635 100644 --- a/js/src/views/Status/components/RpcDocs/RpcDocs.js +++ b/js/src/views/Status/components/RpcDocs/RpcDocs.js @@ -31,7 +31,6 @@ import RpcNav from '../RpcNav'; const rpcMethods = sortBy(rpcData.methods, 'name'); class RpcDocs extends Component { - render () { return (
@@ -98,7 +97,6 @@ class RpcDocs extends Component { handleMethodChange = name => { ReactDOM.findDOMNode(this[`_method-${name}`]).scrollIntoViewIfNeeded(); } - } export default RpcDocs; diff --git a/js/src/views/Status/components/RpcNav/RpcNav.js b/js/src/views/Status/components/RpcNav/RpcNav.js index 33ef0ebc40a..df0ca1c7af8 100644 --- a/js/src/views/Status/components/RpcNav/RpcNav.js +++ b/js/src/views/Status/components/RpcNav/RpcNav.js @@ -19,7 +19,6 @@ import { Link } from 'react-router'; import styles from './RpcNav.css'; export default class RpcNav extends Component { - render () { return (
diff --git a/js/src/views/Status/components/ScrollTopButton/ScrollTopButton.js b/js/src/views/Status/components/ScrollTopButton/ScrollTopButton.js index 3857a65b10e..d660e3c271e 100644 --- a/js/src/views/Status/components/ScrollTopButton/ScrollTopButton.js +++ b/js/src/views/Status/components/ScrollTopButton/ScrollTopButton.js @@ -24,7 +24,6 @@ import styles from './ScrollTopButton.css'; const scrollTopThreshold = 600; export default class ScrollTopButton extends Component { - state = {} componentDidMount () { @@ -45,7 +44,8 @@ export default class ScrollTopButton extends Component { return ( + onTouchTap={ this._scrollToTop } + > ); @@ -67,5 +67,4 @@ export default class ScrollTopButton extends Component { }); } } - } diff --git a/js/src/views/Status/components/Status/status.js b/js/src/views/Status/components/Status/status.js index c1dd9125683..de98ad7470e 100644 --- a/js/src/views/Status/components/Status/status.js +++ b/js/src/views/Status/components/Status/status.js @@ -72,7 +72,8 @@ export default class Status extends Component { + actions={ this.props.actions } + />
{ this.renderSettings() } @@ -112,7 +113,8 @@ export default class Status extends Component { readOnly label='chain' value={ nodeStatus.netChain } - { ...this._test('chain') } /> + { ...this._test('chain') } + />
+ { ...this._test('peers') } + />
+ { ...this._test('network-port') } + />
@@ -136,8 +140,13 @@ export default class Status extends Component { allowCopy readOnly label='rpc enabled' - value={ rpcSettings.enabled ? 'yes' : 'no' } - { ...this._test('rpc-enabled') } /> + value={ + rpcSettings.enabled + ? 'yes' + : 'no' + } + { ...this._test('rpc-enabled') } + />
+ { ...this._test('rpc-interface') } + />
+ { ...this._test('rpc-port') } + />
@@ -164,7 +175,8 @@ export default class Status extends Component { readOnly label='enode' value={ nodeStatus.enode } - { ...this._test('node-enode') } /> + { ...this._test('node-enode') } + />
diff --git a/js/src/views/Status/middleware/localstorage.js b/js/src/views/Status/middleware/localstorage.js index e0b103a33ab..a5d64cab108 100644 --- a/js/src/views/Status/middleware/localstorage.js +++ b/js/src/views/Status/middleware/localstorage.js @@ -19,7 +19,6 @@ import { syncRpcStateFromLocalStorage } from '../actions/localstorage'; import rpcMetods from '../data/rpc.json'; export default class localStorageMiddleware { - toMiddleware () { return store => next => action => { let delegate; @@ -70,5 +69,4 @@ export default class localStorageMiddleware { const newArr = [value].concat(localStore.get(key) || []); localStore.set(key, newArr); } - } diff --git a/js/src/views/Wallet/wallet.js b/js/src/views/Wallet/wallet.js index 7a895d6dcb0..1d8daa03419 100644 --- a/js/src/views/Wallet/wallet.js +++ b/js/src/views/Wallet/wallet.js @@ -220,7 +220,8 @@ class Wallet extends Component { icon={ } label='transfer' disabled={ !showTransferButton } - onClick={ this.onTransferClick } /> + onClick={ this.onTransferClick } + /> ); } @@ -229,7 +230,8 @@ class Wallet extends Component { key='delete' icon={ } label='delete' - onClick={ this.showDeleteDialog } /> + onClick={ this.showDeleteDialog } + /> ); buttons.push( @@ -237,7 +239,8 @@ class Wallet extends Component { key='editmeta' icon={ } label='edit' - onClick={ this.onEditClick } /> + onClick={ this.onEditClick } + /> ); if (owned) { @@ -246,14 +249,16 @@ class Wallet extends Component { key='settings' icon={ } label='settings' - onClick={ this.onSettingsClick } /> + onClick={ this.onSettingsClick } + /> ); } return ( + buttons={ buttons } + /> ); } @@ -265,7 +270,8 @@ class Wallet extends Component { account={ account } visible={ showDeleteDialog } route='/accounts' - onClose={ this.closeDeleteDialog } /> + onClose={ this.closeDeleteDialog } + /> ); } @@ -280,7 +286,8 @@ class Wallet extends Component { + onClose={ this.onEditClick } + /> ); } diff --git a/js/src/views/Web/AddressBar/addressBar.js b/js/src/views/Web/AddressBar/addressBar.js index 16e2132f3b4..1f7498be403 100644 --- a/js/src/views/Web/AddressBar/addressBar.js +++ b/js/src/views/Web/AddressBar/addressBar.js @@ -60,20 +60,24 @@ export default class AddressBar extends Component {
); } diff --git a/js/src/views/Web/web.js b/js/src/views/Web/web.js index a7b63ce30f2..d5f0cd27d39 100644 --- a/js/src/views/Web/web.js +++ b/js/src/views/Web/web.js @@ -109,7 +109,8 @@ export default class Web extends Component { onLoad={ this.iframeOnLoad } sandbox='allow-forms allow-same-origin allow-scripts' scrolling='auto' - src={ address } /> + src={ address } + /> ); } @@ -150,4 +151,3 @@ export default class Web extends Component { }); }; } - diff --git a/js/src/views/WriteContract/writeContract.js b/js/src/views/WriteContract/writeContract.js index 8a3ddf3d11b..aed1d6b3e8d 100644 --- a/js/src/views/WriteContract/writeContract.js +++ b/js/src/views/WriteContract/writeContract.js @@ -36,7 +36,6 @@ import styles from './writeContract.css'; @observer class WriteContract extends Component { - static propTypes = { accounts: PropTypes.object.isRequired, worker: PropTypes.object, @@ -241,7 +240,10 @@ class WriteContract extends Component { if (selectedBuild < 0) { return (
- +

Loading...

); @@ -253,7 +255,10 @@ class WriteContract extends Component { return (
- +

Loading Solidity { longVersion }

@@ -272,13 +277,15 @@ class WriteContract extends Component { /> { contract - ?