diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2460678d4..d27b58f9a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -108,9 +108,9 @@ linux-centos: paths: - target/release/parity name: "x86_64-unknown-centos-gnu_parity" -linux-i686: - stage: build - image: ethcore/rust-i686:latest +linux-i686: + stage: build + image: ethcore/rust-i686:latest only: - beta - tags @@ -348,7 +348,7 @@ windows: - set INCLUDE=C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Include;C:\vs2015\VC\include;C:\Program Files (x86)\Windows Kits\10\Include\10.0.10240.0\ucrt - set LIB=C:\vs2015\VC\lib;C:\Program Files (x86)\Windows Kits\10\Lib\10.0.10240.0\ucrt\x64 - set RUST_BACKTRACE=1 - - set RUSTFLAGS=%RUSTFLAGS% + - set RUSTFLAGS=%RUSTFLAGS% - rustup default stable-x86_64-pc-windows-msvc - cargo build -j 8 --release #%CARGOFLAGS% - curl -sL --url "https://github.com/ethcore/win-build/raw/master/SimpleFC.dll" -o nsis\SimpleFC.dll @@ -422,13 +422,14 @@ test-rust-stable: image: ethcore/rust:stable before_script: - git submodule update --init --recursive - - export JS_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep \.js | wc -l) - - echo $JS_FILES_MODIFIED - - if [ "$JS_FILES_MODIFIED" = 0 ]; then echo "skip js test"; else ./js/scripts/install-deps.sh;fi + - export RUST_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep -v ^js/ | wc -l) + - export JS_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep ^js/ | wc -l) + - echo "rust/js modified: $RUST_FILES_MODIFIED / $JS_FILES_MODIFIED" + - if [ "$JS_FILES_MODIFIED" = 0 ]; then echo "Skipping JS deps install since no JS files modified."; else ./js/scripts/install-deps.sh;fi script: - export RUST_BACKTRACE=1 - - echo $JS_FILES_MODIFIED - - if [ "$JS_FILES_MODIFIED" = 0 ]; then echo "skip js test"&./test.sh $CARGOFLAGS; else echo "skip rust test"&./js/scripts/lint.sh&./js/scripts/test.sh&./js/scripts/build.sh; fi + - if [ "$JS_FILES_MODIFIED" = 0 ]; then echo "Skipping JS lint since no JS files modified."; else ./js/scripts/lint.sh && ./js/scripts/test.sh && ./js/scripts/build.sh; fi + - if [ "$RUST_FILES_MODIFIED" = 0 ]; then echo "Skipping Rust tests since no Rust files modified."; else ./test.sh $CARGOFLAGS; fi tags: - rust - rust-stable @@ -437,13 +438,13 @@ js-test: image: ethcore/rust:stable before_script: - git submodule update --init --recursive - - export JS_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep \.js | wc -l) + - export JS_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep ^js/ | wc -l) - echo $JS_FILES_MODIFIED - - if [ "$JS_FILES_MODIFIED" = 0 ]; then echo "skip js test"; else ./js/scripts/install-deps.sh;fi + - if [ "$JS_FILES_MODIFIED" = 0 ]; then echo "Skipping JS deps install since no JS files modified."; else ./js/scripts/install-deps.sh;fi script: - export RUST_BACKTRACE=1 - echo $JS_FILES_MODIFIED - - if [ "$JS_FILES_MODIFIED" = 0 ]; then echo "skip js test"; else echo "skip rust test"&./js/scripts/lint.sh&./js/scripts/test.sh&./js/scripts/build.sh; fi + - if [ "$JS_FILES_MODIFIED" = 0 ]; then echo "Skipping JS lint since no JS files modified."; else ./js/scripts/lint.sh && ./js/scripts/test.sh && ./js/scripts/build.sh; fi tags: - rust - rust-stable @@ -484,11 +485,11 @@ js-release: - stable image: ethcore/rust:stable before_script: - - export JS_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep \.js | wc -l) + - export JS_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep ^js/ | wc -l) - echo $JS_FILES_MODIFIED - - if [ "$JS_FILES_MODIFIED" = 0 ]; then echo "skip js build"; else ./js/scripts/install-deps.sh;fi + - if [ "$JS_FILES_MODIFIED" = 0 ]; then echo "Skipping JS deps install since no JS files modified."; else ./js/scripts/install-deps.sh;fi script: - echo $JS_FILES_MODIFIED - - if [ "$JS_FILES_MODIFIED" = 0 ]; then echo "skip js build"; else ./js/scripts/build.sh&&./js/scripts/release.sh; fi + - if [ "$JS_FILES_MODIFIED" = 0 ]; then echo "Skipping JS rebuild since no JS files modified."; else ./js/scripts/build.sh && ./js/scripts/release.sh; fi tags: - javascript diff --git a/Cargo.lock b/Cargo.lock index bedcfebe4..7e970051c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,6 +18,7 @@ dependencies = [ "ethcore-ipc-hypervisor 1.2.0", "ethcore-ipc-nano 1.5.0", "ethcore-ipc-tests 0.1.0", + "ethcore-light 1.5.0", "ethcore-logger 1.5.0", "ethcore-rpc 1.5.0", "ethcore-signer 1.5.0", @@ -456,6 +457,21 @@ dependencies = [ "semver 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ethcore-light" +version = "1.5.0" +dependencies = [ + "ethcore 1.5.0", + "ethcore-io 1.5.0", + "ethcore-ipc 1.5.0", + "ethcore-ipc-codegen 1.5.0", + "ethcore-network 1.5.0", + "ethcore-util 1.5.0", + "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "rlp 0.1.0", + "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "ethcore-logger" version = "1.5.0" @@ -665,6 +681,7 @@ dependencies = [ "ethcore-ipc 1.5.0", "ethcore-ipc-codegen 1.5.0", "ethcore-ipc-nano 1.5.0", + "ethcore-light 1.5.0", "ethcore-network 1.5.0", "ethcore-util 1.5.0", "ethkey 0.2.0", @@ -1273,7 +1290,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#a59b62ecec8773715d1db7e070bbbe5443eb7378" +source = "git+https://github.com/ethcore/js-precompiled.git#3d3b2f9e8e8b0fd62c172240bfd001a317cf2979" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/Cargo.toml b/Cargo.toml index 65bb0dbc6..c3a44e1cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,6 +47,7 @@ rlp = { path = "util/rlp" } ethcore-stratum = { path = "stratum" } ethcore-dapps = { path = "dapps", optional = true } clippy = { version = "0.0.103", optional = true} +ethcore-light = { path = "ethcore/light" } [target.'cfg(windows)'.dependencies] winapi = "0.2" diff --git a/ethcore/light/Cargo.toml b/ethcore/light/Cargo.toml index 74400d7ab..c89bbc74f 100644 --- a/ethcore/light/Cargo.toml +++ b/ethcore/light/Cargo.toml @@ -8,14 +8,18 @@ authors = ["Ethcore "] build = "build.rs" [build-dependencies] -"ethcore-ipc-codegen" = { path = "../../ipc/codegen" } +"ethcore-ipc-codegen" = { path = "../../ipc/codegen", optional = true } [dependencies] log = "0.3" -ethcore = { path = ".." } +ethcore = { path = ".."} ethcore-util = { path = "../../util" } ethcore-network = { path = "../../util/network" } ethcore-io = { path = "../../util/io" } -ethcore-ipc = { path = "../../ipc/rpc" } +ethcore-ipc = { path = "../../ipc/rpc", optional = true } rlp = { path = "../../util/rlp" } -time = "0.1" \ No newline at end of file +time = "0.1" + +[features] +default = [] +ipc = ["ethcore-ipc", "ethcore-ipc-codegen"] \ No newline at end of file diff --git a/ethcore/light/build.rs b/ethcore/light/build.rs index cff92a011..7d4e0064c 100644 --- a/ethcore/light/build.rs +++ b/ethcore/light/build.rs @@ -14,8 +14,14 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +#[cfg(feature = "ipc")] extern crate ethcore_ipc_codegen; +#[cfg(feature = "ipc")] fn main() { ethcore_ipc_codegen::derive_binary("src/types/mod.rs.in").unwrap(); + ethcore_ipc_codegen::derive_ipc_cond("src/provider.rs", true).unwrap(); } + +#[cfg(not(feature = "ipc"))] +fn main() { } \ No newline at end of file diff --git a/ethcore/light/src/lib.rs b/ethcore/light/src/lib.rs index e150f4ee5..7fa2f5911 100644 --- a/ethcore/light/src/lib.rs +++ b/ethcore/light/src/lib.rs @@ -33,8 +33,21 @@ pub mod client; pub mod net; + +#[cfg(not(feature = "ipc"))] pub mod provider; +#[cfg(feature = "ipc")] +pub mod provider { + #![allow(dead_code, unused_assignments, unused_variables, missing_docs)] // codegen issues + include!(concat!(env!("OUT_DIR"), "/provider.rs")); +} + +#[cfg(feature = "ipc")] +pub mod remote { + pub use provider::LightProviderClient; +} + mod types; pub use self::provider::Provider; @@ -47,6 +60,8 @@ extern crate ethcore; extern crate ethcore_util as util; extern crate ethcore_network as network; extern crate ethcore_io as io; -extern crate ethcore_ipc as ipc; extern crate rlp; -extern crate time; \ No newline at end of file +extern crate time; + +#[cfg(feature = "ipc")] +extern crate ethcore_ipc as ipc; \ No newline at end of file diff --git a/ethcore/light/src/net/buffer_flow.rs b/ethcore/light/src/net/buffer_flow.rs index 6730c71a7..2371c6ea4 100644 --- a/ethcore/light/src/net/buffer_flow.rs +++ b/ethcore/light/src/net/buffer_flow.rs @@ -22,6 +22,9 @@ //! //! This module provides an interface for configuration of buffer //! flow costs and recharge rates. +//! +//! Current default costs are picked completely arbitrarily, not based +//! on any empirical timings or mathematical models. use request; use super::packet; @@ -273,6 +276,16 @@ impl FlowParams { } } +impl Default for FlowParams { + fn default() -> Self { + FlowParams { + limit: 50_000_000.into(), + costs: CostTable::default(), + recharge: 100_000.into(), + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/ethcore/light/src/net/context.rs b/ethcore/light/src/net/context.rs new file mode 100644 index 000000000..c05e69b0f --- /dev/null +++ b/ethcore/light/src/net/context.rs @@ -0,0 +1,120 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +//! I/O and event context generalizations. + +use network::{NetworkContext, PeerId}; + +use super::{Announcement, LightProtocol, ReqId}; +use super::error::Error; +use request::Request; + +/// An I/O context which allows sending and receiving packets as well as +/// disconnecting peers. This is used as a generalization of the portions +/// of a p2p network which the light protocol structure makes use of. +pub trait IoContext { + /// Send a packet to a specific peer. + fn send(&self, peer: PeerId, packet_id: u8, packet_body: Vec); + + /// Respond to a peer's message. Only works if this context is a byproduct + /// of a packet handler. + fn respond(&self, packet_id: u8, packet_body: Vec); + + /// Disconnect a peer. + fn disconnect_peer(&self, peer: PeerId); + + /// Disable a peer -- this is a disconnect + a time-out. + fn disable_peer(&self, peer: PeerId); + + /// Get a peer's protocol version. + fn protocol_version(&self, peer: PeerId) -> Option; +} + +impl<'a> IoContext for NetworkContext<'a> { + fn send(&self, peer: PeerId, packet_id: u8, packet_body: Vec) { + if let Err(e) = self.send(peer, packet_id, packet_body) { + debug!(target: "les", "Error sending packet to peer {}: {}", peer, e); + } + } + + fn respond(&self, packet_id: u8, packet_body: Vec) { + if let Err(e) = self.respond(packet_id, packet_body) { + debug!(target: "les", "Error responding to peer message: {}", e); + } + } + + fn disconnect_peer(&self, peer: PeerId) { + NetworkContext::disconnect_peer(self, peer); + } + + fn disable_peer(&self, peer: PeerId) { + NetworkContext::disable_peer(self, peer); + } + + fn protocol_version(&self, peer: PeerId) -> Option { + self.protocol_version(self.subprotocol_name(), peer) + } +} + +/// Context for a protocol event. +pub trait EventContext { + /// Get the peer relevant to the event e.g. message sender, + /// disconnected/connected peer. + fn peer(&self) -> PeerId; + + /// Make a request from a peer. + fn request_from(&self, peer: PeerId, request: Request) -> Result; + + /// Make an announcement of new capabilities to the rest of the peers. + // TODO: maybe just put this on a timer in LightProtocol? + fn make_announcement(&self, announcement: Announcement); + + /// Disconnect a peer. + fn disconnect_peer(&self, peer: PeerId); + + /// Disable a peer. + fn disable_peer(&self, peer: PeerId); +} + +/// Concrete implementation of `EventContext` over the light protocol struct and +/// an io context. +pub struct Ctx<'a> { + /// Io context to enable immediate response to events. + pub io: &'a IoContext, + /// Protocol implementation. + pub proto: &'a LightProtocol, + /// Relevant peer for event. + pub peer: PeerId, +} + +impl<'a> EventContext for Ctx<'a> { + fn peer(&self) -> PeerId { self.peer } + fn request_from(&self, peer: PeerId, request: Request) -> Result { + self.proto.request_from(self.io, &peer, request) + } + + fn make_announcement(&self, announcement: Announcement) { + self.proto.make_announcement(self.io, announcement); + } + + fn disconnect_peer(&self, peer: PeerId) { + self.io.disconnect_peer(peer); + } + + fn disable_peer(&self, peer: PeerId) { + self.io.disable_peer(peer); + } +} \ No newline at end of file diff --git a/ethcore/light/src/net/error.rs b/ethcore/light/src/net/error.rs index 0855cdeb8..01a15928b 100644 --- a/ethcore/light/src/net/error.rs +++ b/ethcore/light/src/net/error.rs @@ -54,6 +54,14 @@ pub enum Error { WrongNetwork, /// Unknown peer. UnknownPeer, + /// Unsolicited response. + UnsolicitedResponse, + /// Not a server. + NotServer, + /// Unsupported protocol version. + UnsupportedProtocolVersion(u8), + /// Bad protocol version. + BadProtocolVersion, } impl Error { @@ -67,6 +75,10 @@ impl Error { Error::UnexpectedHandshake => Punishment::Disconnect, Error::WrongNetwork => Punishment::Disable, Error::UnknownPeer => Punishment::Disconnect, + Error::UnsolicitedResponse => Punishment::Disable, + Error::NotServer => Punishment::Disable, + Error::UnsupportedProtocolVersion(_) => Punishment::Disable, + Error::BadProtocolVersion => Punishment::Disable, } } } @@ -92,7 +104,11 @@ impl fmt::Display for Error { Error::UnrecognizedPacket(code) => write!(f, "Unrecognized packet: 0x{:x}", code), Error::UnexpectedHandshake => write!(f, "Unexpected handshake"), Error::WrongNetwork => write!(f, "Wrong network"), - Error::UnknownPeer => write!(f, "unknown peer"), + Error::UnknownPeer => write!(f, "Unknown peer"), + Error::UnsolicitedResponse => write!(f, "Peer provided unsolicited data"), + Error::NotServer => write!(f, "Peer not a server."), + Error::UnsupportedProtocolVersion(pv) => write!(f, "Unsupported protocol version: {}", pv), + Error::BadProtocolVersion => write!(f, "Bad protocol version in handshake"), } } } \ No newline at end of file diff --git a/ethcore/light/src/net/mod.rs b/ethcore/light/src/net/mod.rs index a1b3b30b0..481740a48 100644 --- a/ethcore/light/src/net/mod.rs +++ b/ethcore/light/src/net/mod.rs @@ -20,36 +20,51 @@ //! See https://github.com/ethcore/parity/wiki/Light-Ethereum-Subprotocol-(LES) use ethcore::transaction::SignedTransaction; +use ethcore::receipt::Receipt; + use io::TimerToken; -use network::{NetworkProtocolHandler, NetworkContext, NetworkError, PeerId}; +use network::{NetworkProtocolHandler, NetworkContext, PeerId}; use rlp::{RlpStream, Stream, UntrustedRlp, View}; use util::hash::H256; -use util::{Mutex, RwLock, U256}; -use time::SteadyTime; +use util::{Bytes, Mutex, RwLock, U256}; +use time::{Duration, SteadyTime}; -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; +use std::sync::Arc; use std::sync::atomic::{AtomicUsize, Ordering}; use provider::Provider; use request::{self, Request}; use self::buffer_flow::{Buffer, FlowParams}; +use self::context::Ctx; use self::error::{Error, Punishment}; mod buffer_flow; +mod context; mod error; mod status; -pub use self::status::{Status, Capabilities, Announcement, NetworkId}; +#[cfg(test)] +mod tests; + +pub use self::context::{EventContext, IoContext}; +pub use self::status::{Status, Capabilities, Announcement}; const TIMEOUT: TimerToken = 0; const TIMEOUT_INTERVAL_MS: u64 = 1000; -// LPV1 -const PROTOCOL_VERSION: u32 = 1; +// minimum interval between updates. +const UPDATE_INTERVAL_MS: i64 = 5000; -// TODO [rob] make configurable. -const PROTOCOL_ID: [u8; 3] = *b"les"; +// Supported protocol versions. +pub const PROTOCOL_VERSIONS: &'static [u8] = &[1]; + +// Max protocol version. +pub const MAX_PROTOCOL_VERSION: u8 = 1; + +// Packet count for LES. +pub const PACKET_COUNT: u8 = 15; // packet ID definitions. mod packet { @@ -95,17 +110,19 @@ pub struct ReqId(usize); // may not have received one for. struct PendingPeer { sent_head: H256, + last_update: SteadyTime, + proto_version: u8, } // data about each peer. struct Peer { local_buffer: Buffer, // their buffer relative to us - remote_buffer: Buffer, // our buffer relative to them - current_asking: HashSet, // pending request ids. status: Status, capabilities: Capabilities, - remote_flow: FlowParams, + remote_flow: Option<(Buffer, FlowParams)>, sent_head: H256, // last head we've given them. + last_update: SteadyTime, + proto_version: u8, } impl Peer { @@ -126,38 +143,56 @@ impl Peer { self.local_buffer.current() } - - // recharge remote buffer with remote flow params. - fn recharge_remote(&mut self) { - let flow = &mut self.remote_flow; - flow.recharge(&mut self.remote_buffer); - } } /// An LES event handler. +/// +/// Each handler function takes a context which describes the relevant peer +/// and gives references to the IO layer and protocol structure so new messages +/// can be dispatched immediately. +/// +/// Request responses are not guaranteed to be complete or valid, but passed IDs will be correct. +/// Response handlers are not given a copy of the original request; it is assumed +/// that relevant data will be stored by interested handlers. pub trait Handler: Send + Sync { /// Called when a peer connects. - fn on_connect(&self, _id: PeerId, _status: &Status, _capabilities: &Capabilities) { } - /// Called when a peer disconnects - fn on_disconnect(&self, _id: PeerId) { } + fn on_connect(&self, _ctx: &EventContext, _status: &Status, _capabilities: &Capabilities) { } + /// Called when a peer disconnects, with a list of unfulfilled request IDs as + /// of yet. + fn on_disconnect(&self, _ctx: &EventContext, _unfulfilled: &[ReqId]) { } /// Called when a peer makes an announcement. - fn on_announcement(&self, _id: PeerId, _announcement: &Announcement) { } + fn on_announcement(&self, _ctx: &EventContext, _announcement: &Announcement) { } /// Called when a peer requests relay of some transactions. - fn on_transactions(&self, _id: PeerId, _relay: &[SignedTransaction]) { } + fn on_transactions(&self, _ctx: &EventContext, _relay: &[SignedTransaction]) { } + /// Called when a peer responds with block bodies. + fn on_block_bodies(&self, _ctx: &EventContext, _req_id: ReqId, _bodies: &[Bytes]) { } + /// Called when a peer responds with block headers. + fn on_block_headers(&self, _ctx: &EventContext, _req_id: ReqId, _headers: &[Bytes]) { } + /// Called when a peer responds with block receipts. + fn on_receipts(&self, _ctx: &EventContext, _req_id: ReqId, _receipts: &[Vec]) { } + /// Called when a peer responds with state proofs. Each proof is a series of trie + /// nodes in ascending order by distance from the root. + fn on_state_proofs(&self, _ctx: &EventContext, _req_id: ReqId, _proofs: &[Vec]) { } + /// Called when a peer responds with contract code. + fn on_code(&self, _ctx: &EventContext, _req_id: ReqId, _codes: &[Bytes]) { } + /// Called when a peer responds with header proofs. Each proof is a block header coupled + /// with a series of trie nodes is ascending order by distance from the root. + fn on_header_proofs(&self, _ctx: &EventContext, _req_id: ReqId, _proofs: &[(Bytes, Vec)]) { } + /// Called on abort. + fn on_abort(&self) { } } -// a request and the time it was made. +// a request, the peer who it was made to, and the time it was made. struct Requested { request: Request, timestamp: SteadyTime, + peer_id: PeerId, } /// Protocol parameters. pub struct Params { - /// Genesis hash. - pub genesis_hash: H256, /// Network id. - pub network_id: NetworkId, + pub network_id: u64, /// Buffer flow parameters. pub flow_params: FlowParams, /// Initial capabilities. @@ -175,9 +210,9 @@ pub struct Params { // Locks must be acquired in the order declared, and when holding a read lock // on the peers, only one peer may be held at a time. pub struct LightProtocol { - provider: Box, + provider: Arc, genesis_hash: H256, - network_id: NetworkId, + network_id: u64, pending_peers: RwLock>, peers: RwLock>>, pending_requests: RwLock>, @@ -189,10 +224,13 @@ pub struct LightProtocol { impl LightProtocol { /// Create a new instance of the protocol manager. - pub fn new(provider: Box, params: Params) -> Self { + pub fn new(provider: Arc, params: Params) -> Self { + debug!(target: "les", "Initializing LES handler"); + + let genesis_hash = provider.chain_info().genesis_hash; LightProtocol { provider: provider, - genesis_hash: params.genesis_hash, + genesis_hash: genesis_hash, network_id: params.network_id, pending_peers: RwLock::new(HashMap::new()), peers: RwLock::new(HashMap::new()), @@ -207,28 +245,37 @@ impl LightProtocol { /// Check the maximum amount of requests of a specific type /// which a peer would be able to serve. pub fn max_requests(&self, peer: PeerId, kind: request::Kind) -> Option { - self.peers.read().get(&peer).map(|peer| { + self.peers.read().get(&peer).and_then(|peer| { let mut peer = peer.lock(); - peer.recharge_remote(); - peer.remote_flow.max_amount(&peer.remote_buffer, kind) + match peer.remote_flow.as_mut() { + Some(&mut (ref mut buf, ref flow)) => { + flow.recharge(buf); + Some(flow.max_amount(&*buf, kind)) + } + None => None, + } }) } /// Make a request to a peer. /// - /// Fails on: nonexistent peer, network error, + /// Fails on: nonexistent peer, network error, peer not server, /// insufficient buffer. Does not check capabilities before sending. /// On success, returns a request id which can later be coordinated /// with an event. - pub fn request_from(&self, io: &NetworkContext, peer_id: &PeerId, request: Request) -> Result { + pub fn request_from(&self, io: &IoContext, peer_id: &PeerId, request: Request) -> Result { let peers = self.peers.read(); let peer = try!(peers.get(peer_id).ok_or_else(|| Error::UnknownPeer)); let mut peer = peer.lock(); - peer.recharge_remote(); - - let max = peer.remote_flow.compute_cost(request.kind(), request.amount()); - try!(peer.remote_buffer.deduct_cost(max)); + match peer.remote_flow.as_mut() { + Some(&mut (ref mut buf, ref flow)) => { + flow.recharge(buf); + let max = flow.compute_cost(request.kind(), request.amount()); + try!(buf.deduct_cost(max)); + } + None => return Err(Error::NotServer), + } let req_id = self.req_id.fetch_add(1, Ordering::SeqCst); let packet_data = encode_request(&request, req_id); @@ -242,12 +289,12 @@ impl LightProtocol { request::Kind::HeaderProofs => packet::GET_HEADER_PROOFS, }; - try!(io.send(*peer_id, packet_id, packet_data)); + io.send(*peer_id, packet_id, packet_data); - peer.current_asking.insert(req_id); self.pending_requests.write().insert(req_id, Requested { request: request, timestamp: SteadyTime::now(), + peer_id: *peer_id, }); Ok(ReqId(req_id)) @@ -255,8 +302,9 @@ impl LightProtocol { /// Make an announcement of new chain head and capabilities to all peers. /// The announcement is expected to be valid. - pub fn make_announcement(&self, io: &NetworkContext, mut announcement: Announcement) { + pub fn make_announcement(&self, io: &IoContext, mut announcement: Announcement) { let mut reorgs_map = HashMap::new(); + let now = SteadyTime::now(); // update stored capabilities self.capabilities.write().update_from(&announcement); @@ -264,6 +312,17 @@ impl LightProtocol { // calculate reorg info and send packets for (peer_id, peer_info) in self.peers.read().iter() { let mut peer_info = peer_info.lock(); + + // TODO: "urgent" announcements like new blocks? + // the timer approach will skip 1 (possibly 2) in rare occasions. + if peer_info.sent_head == announcement.head_hash || + peer_info.status.head_num >= announcement.head_num || + now - peer_info.last_update < Duration::milliseconds(UPDATE_INTERVAL_MS) { + continue + } + + peer_info.last_update = now; + let reorg_depth = reorgs_map.entry(peer_info.sent_head) .or_insert_with(|| { match self.provider.reorg_depth(&announcement.head_hash, &peer_info.sent_head) { @@ -281,26 +340,133 @@ impl LightProtocol { peer_info.sent_head = announcement.head_hash; announcement.reorg_depth = *reorg_depth; - if let Err(e) = io.send(*peer_id, packet::ANNOUNCE, status::write_announcement(&announcement)) { - debug!(target: "les", "Error sending to peer {}: {}", peer_id, e); - } + io.send(*peer_id, packet::ANNOUNCE, status::write_announcement(&announcement)); } } /// Add an event handler. /// Ownership will be transferred to the protocol structure, /// and the handler will be kept alive as long as it is. - /// These are intended to be added at the beginning of the + /// These are intended to be added when the protocol structure + /// is initialized as a means of customizing its behavior. pub fn add_handler(&mut self, handler: Box) { self.handlers.push(handler); } + + /// Signal to handlers that network activity is being aborted + /// and clear peer data. + pub fn abort(&self) { + for handler in &self.handlers { + handler.on_abort(); + } + + // acquire in order and hold. + let mut pending_peers = self.pending_peers.write(); + let mut peers = self.peers.write(); + let mut pending_requests = self.pending_requests.write(); + + pending_peers.clear(); + peers.clear(); + pending_requests.clear(); + } + + // Does the common pre-verification of responses before the response itself + // is actually decoded: + // - check whether peer exists + // - check whether request was made + // - check whether request kinds match + fn pre_verify_response(&self, peer: &PeerId, kind: request::Kind, raw: &UntrustedRlp) -> Result { + let req_id: usize = try!(raw.val_at(0)); + let cur_buffer: U256 = try!(raw.val_at(1)); + + trace!(target: "les", "pre-verifying response from peer {}, kind={:?}", peer, kind); + + match self.pending_requests.write().remove(&req_id) { + None => return Err(Error::UnsolicitedResponse), + Some(requested) => { + if requested.peer_id != *peer || requested.request.kind() != kind { + return Err(Error::UnsolicitedResponse) + } + } + } + + let peers = self.peers.read(); + match peers.get(peer) { + Some(peer_info) => { + let mut peer_info = peer_info.lock(); + match peer_info.remote_flow.as_mut() { + Some(&mut (ref mut buf, ref mut flow)) => { + let actual_buffer = ::std::cmp::min(cur_buffer, *flow.limit()); + buf.update_to(actual_buffer) + } + None => return Err(Error::NotServer), // this really should be impossible. + } + Ok(ReqId(req_id)) + } + None => Err(Error::UnknownPeer), // probably only occurs in a race of some kind. + } + } + + // handle a packet using the given io context. + fn handle_packet(&self, io: &IoContext, peer: &PeerId, packet_id: u8, data: &[u8]) { + let rlp = UntrustedRlp::new(data); + + trace!(target: "les", "Incoming packet {} from peer {}", packet_id, peer); + + // handle the packet + let res = match packet_id { + packet::STATUS => self.status(peer, io, rlp), + packet::ANNOUNCE => self.announcement(peer, io, rlp), + + packet::GET_BLOCK_HEADERS => self.get_block_headers(peer, io, rlp), + packet::BLOCK_HEADERS => self.block_headers(peer, io, rlp), + + packet::GET_BLOCK_BODIES => self.get_block_bodies(peer, io, rlp), + packet::BLOCK_BODIES => self.block_bodies(peer, io, rlp), + + packet::GET_RECEIPTS => self.get_receipts(peer, io, rlp), + packet::RECEIPTS => self.receipts(peer, io, rlp), + + packet::GET_PROOFS => self.get_proofs(peer, io, rlp), + packet::PROOFS => self.proofs(peer, io, rlp), + + packet::GET_CONTRACT_CODES => self.get_contract_code(peer, io, rlp), + packet::CONTRACT_CODES => self.contract_code(peer, io, rlp), + + packet::GET_HEADER_PROOFS => self.get_header_proofs(peer, io, rlp), + packet::HEADER_PROOFS => self.header_proofs(peer, io, rlp), + + packet::SEND_TRANSACTIONS => self.relay_transactions(peer, io, rlp), + + other => { + Err(Error::UnrecognizedPacket(other)) + } + }; + + // if something went wrong, figure out how much to punish the peer. + if let Err(e) = res { + match e.punishment() { + Punishment::None => {} + Punishment::Disconnect => { + debug!(target: "les", "Disconnecting peer {}: {}", peer, e); + io.disconnect_peer(*peer) + } + Punishment::Disable => { + debug!(target: "les", "Disabling peer {}: {}", peer, e); + io.disable_peer(*peer) + } + } + } + } } impl LightProtocol { // called when a peer connects. - fn on_connect(&self, peer: &PeerId, io: &NetworkContext) { + fn on_connect(&self, peer: &PeerId, io: &IoContext) { let peer = *peer; + trace!(target: "les", "Peer {} connecting", peer); + match self.send_status(peer, io) { Ok(pending_peer) => { self.pending_peers.write().insert(peer, pending_peer); @@ -313,44 +479,69 @@ impl LightProtocol { } // called when a peer disconnects. - fn on_disconnect(&self, peer: PeerId) { - // TODO: reassign all requests assigned to this peer. + fn on_disconnect(&self, peer: PeerId, io: &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); + } + } + for handler in &self.handlers { - handler.on_disconnect(peer) + handler.on_disconnect(&Ctx { + peer: peer, + io: io, + proto: self, + }, &unfulfilled) } } } // send status to a peer. - fn send_status(&self, peer: PeerId, io: &NetworkContext) -> Result { - let chain_info = self.provider.chain_info(); + fn send_status(&self, peer: PeerId, io: &IoContext) -> Result { + let proto_version = try!(io.protocol_version(peer).ok_or(Error::WrongNetwork)); - // TODO: could update capabilities here. + if PROTOCOL_VERSIONS.iter().find(|x| **x == proto_version).is_none() { + return Err(Error::UnsupportedProtocolVersion(proto_version)); + } + + let chain_info = self.provider.chain_info(); let status = Status { head_td: chain_info.total_difficulty, head_hash: chain_info.best_block_hash, head_num: chain_info.best_block_number, genesis_hash: chain_info.genesis_hash, - protocol_version: PROTOCOL_VERSION, + protocol_version: proto_version as u32, // match peer proto version network_id: self.network_id, last_head: None, }; let capabilities = self.capabilities.read().clone(); - let status_packet = status::write_handshake(&status, &capabilities, &self.flow_params); + let status_packet = status::write_handshake(&status, &capabilities, Some(&self.flow_params)); - try!(io.send(peer, packet::STATUS, status_packet)); + io.send(peer, packet::STATUS, status_packet); Ok(PendingPeer { sent_head: chain_info.best_block_hash, + last_update: SteadyTime::now(), + proto_version: proto_version, }) } // Handle status message from peer. - fn status(&self, peer: &PeerId, data: UntrustedRlp) -> Result<(), Error> { + fn status(&self, peer: &PeerId, io: &IoContext, data: UntrustedRlp) -> Result<(), Error> { let pending = match self.pending_peers.write().remove(peer) { Some(pending) => pending, None => { @@ -366,63 +557,80 @@ impl LightProtocol { return Err(Error::WrongNetwork); } + if Some(status.protocol_version as u8) != io.protocol_version(*peer) { + return Err(Error::BadProtocolVersion); + } + + let remote_flow = flow_params.map(|params| (params.create_buffer(), params)); + self.peers.write().insert(*peer, Mutex::new(Peer { local_buffer: self.flow_params.create_buffer(), - remote_buffer: flow_params.create_buffer(), - current_asking: HashSet::new(), status: status.clone(), capabilities: capabilities.clone(), - remote_flow: flow_params, + remote_flow: remote_flow, sent_head: pending.sent_head, + last_update: pending.last_update, + proto_version: pending.proto_version, })); for handler in &self.handlers { - handler.on_connect(*peer, &status, &capabilities) + handler.on_connect(&Ctx { + peer: *peer, + io: io, + proto: self, + }, &status, &capabilities) } Ok(()) } // Handle an announcement. - fn announcement(&self, peer: &PeerId, data: UntrustedRlp) -> Result<(), Error> { + fn announcement(&self, peer: &PeerId, io: &IoContext, data: UntrustedRlp) -> Result<(), Error> { if !self.peers.read().contains_key(peer) { debug!(target: "les", "Ignoring announcement from unknown peer"); return Ok(()) } let announcement = try!(status::parse_announcement(data)); - let peers = self.peers.read(); - let peer_info = match peers.get(peer) { - Some(info) => info, - None => return Ok(()), - }; - - let mut peer_info = peer_info.lock(); - - // update status. + // scope to ensure locks are dropped before moving into handler-space. { - // TODO: punish peer if they've moved backwards. - let status = &mut peer_info.status; - let last_head = status.head_hash; - status.head_hash = announcement.head_hash; - status.head_td = announcement.head_td; - status.head_num = announcement.head_num; - status.last_head = Some((last_head, announcement.reorg_depth)); + let peers = self.peers.read(); + let peer_info = match peers.get(peer) { + Some(info) => info, + None => return Ok(()), + }; + + let mut peer_info = peer_info.lock(); + + // update status. + { + // TODO: punish peer if they've moved backwards. + let status = &mut peer_info.status; + let last_head = status.head_hash; + status.head_hash = announcement.head_hash; + status.head_td = announcement.head_td; + status.head_num = announcement.head_num; + status.last_head = Some((last_head, announcement.reorg_depth)); + } + + // update capabilities. + peer_info.capabilities.update_from(&announcement); } - // update capabilities. - peer_info.capabilities.update_from(&announcement); - for handler in &self.handlers { - handler.on_announcement(*peer, &announcement); + handler.on_announcement(&Ctx { + peer: *peer, + io: io, + proto: self, + }, &announcement); } Ok(()) } // Handle a request for block headers. - fn get_block_headers(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { + fn get_block_headers(&self, peer: &PeerId, io: &IoContext, data: UntrustedRlp) -> Result<(), Error> { const MAX_HEADERS: usize = 512; let peers = self.peers.read(); @@ -467,16 +675,29 @@ impl LightProtocol { } stream.out() - }).map_err(Into::into) + }); + + Ok(()) } // Receive a response for block headers. - fn block_headers(&self, _: &PeerId, _: &NetworkContext, _: UntrustedRlp) -> Result<(), Error> { - unimplemented!() + fn block_headers(&self, peer: &PeerId, io: &IoContext, raw: UntrustedRlp) -> Result<(), Error> { + let req_id = try!(self.pre_verify_response(peer, request::Kind::Headers, &raw)); + let raw_headers: Vec<_> = raw.iter().skip(2).map(|x| x.as_raw().to_owned()).collect(); + + for handler in &self.handlers { + handler.on_block_headers(&Ctx { + peer: *peer, + io: io, + proto: self, + }, req_id, &raw_headers); + } + + Ok(()) } // Handle a request for block bodies. - fn get_block_bodies(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { + fn get_block_bodies(&self, peer: &PeerId, io: &IoContext, data: UntrustedRlp) -> Result<(), Error> { const MAX_BODIES: usize = 256; let peers = self.peers.read(); @@ -513,16 +734,29 @@ impl LightProtocol { } stream.out() - }).map_err(Into::into) + }); + + Ok(()) } // Receive a response for block bodies. - fn block_bodies(&self, _: &PeerId, _: &NetworkContext, _: UntrustedRlp) -> Result<(), Error> { - unimplemented!() + fn block_bodies(&self, peer: &PeerId, io: &IoContext, raw: UntrustedRlp) -> Result<(), Error> { + let req_id = try!(self.pre_verify_response(peer, request::Kind::Bodies, &raw)); + let raw_bodies: Vec = raw.iter().skip(2).map(|x| x.as_raw().to_owned()).collect(); + + for handler in &self.handlers { + handler.on_block_bodies(&Ctx { + peer: *peer, + io: io, + proto: self, + }, req_id, &raw_bodies); + } + + Ok(()) } // Handle a request for receipts. - fn get_receipts(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { + fn get_receipts(&self, peer: &PeerId, io: &IoContext, data: UntrustedRlp) -> Result<(), Error> { const MAX_RECEIPTS: usize = 256; let peers = self.peers.read(); @@ -559,16 +793,33 @@ impl LightProtocol { } stream.out() - }).map_err(Into::into) + }); + + Ok(()) } // Receive a response for receipts. - fn receipts(&self, _: &PeerId, _: &NetworkContext, _: UntrustedRlp) -> Result<(), Error> { - unimplemented!() + fn receipts(&self, peer: &PeerId, io: &IoContext, raw: UntrustedRlp) -> Result<(), Error> { + let req_id = try!(self.pre_verify_response(peer, request::Kind::Receipts, &raw)); + let raw_receipts: Vec> = try!(raw + .iter() + .skip(2) + .map(|x| x.as_val()) + .collect()); + + for handler in &self.handlers { + handler.on_receipts(&Ctx { + peer: *peer, + io: io, + proto: self, + }, req_id, &raw_receipts); + } + + Ok(()) } // Handle a request for proofs. - fn get_proofs(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { + fn get_proofs(&self, peer: &PeerId, io: &IoContext, data: UntrustedRlp) -> Result<(), Error> { const MAX_PROOFS: usize = 128; let peers = self.peers.read(); @@ -616,16 +867,33 @@ impl LightProtocol { } stream.out() - }).map_err(Into::into) + }); + + Ok(()) } // Receive a response for proofs. - fn proofs(&self, _: &PeerId, _: &NetworkContext, _: UntrustedRlp) -> Result<(), Error> { - unimplemented!() + fn proofs(&self, peer: &PeerId, io: &IoContext, raw: UntrustedRlp) -> Result<(), Error> { + let req_id = try!(self.pre_verify_response(peer, request::Kind::StateProofs, &raw)); + + let raw_proofs: Vec> = raw.iter() + .skip(2) + .map(|x| x.iter().map(|node| node.as_raw().to_owned()).collect()) + .collect(); + + for handler in &self.handlers { + handler.on_state_proofs(&Ctx { + peer: *peer, + io: io, + proto: self, + }, req_id, &raw_proofs); + } + + Ok(()) } // Handle a request for contract code. - fn get_contract_code(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { + fn get_contract_code(&self, peer: &PeerId, io: &IoContext, data: UntrustedRlp) -> Result<(), Error> { const MAX_CODES: usize = 256; let peers = self.peers.read(); @@ -667,20 +935,34 @@ impl LightProtocol { stream.append(&req_id).append(&cur_buffer); for code in response { - stream.append_raw(&code, 1); + stream.append(&code); } stream.out() - }).map_err(Into::into) + }); + + Ok(()) } // Receive a response for contract code. - fn contract_code(&self, _: &PeerId, _: &NetworkContext, _: UntrustedRlp) -> Result<(), Error> { - unimplemented!() + fn contract_code(&self, peer: &PeerId, io: &IoContext, raw: UntrustedRlp) -> Result<(), Error> { + let req_id = try!(self.pre_verify_response(peer, request::Kind::Codes, &raw)); + + let raw_code: Vec = try!(raw.iter().skip(2).map(|x| x.as_val()).collect()); + + for handler in &self.handlers { + handler.on_code(&Ctx { + peer: *peer, + io: io, + proto: self, + }, req_id, &raw_code); + } + + Ok(()) } // Handle a request for header proofs - fn get_header_proofs(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { + fn get_header_proofs(&self, peer: &PeerId, io: &IoContext, data: UntrustedRlp) -> Result<(), Error> { const MAX_PROOFS: usize = 256; let peers = self.peers.read(); @@ -727,16 +1009,37 @@ impl LightProtocol { } stream.out() - }).map_err(Into::into) + }); + + Ok(()) } // Receive a response for header proofs - fn header_proofs(&self, _: &PeerId, _: &NetworkContext, _: UntrustedRlp) -> Result<(), Error> { - unimplemented!() + fn header_proofs(&self, peer: &PeerId, io: &IoContext, raw: UntrustedRlp) -> Result<(), Error> { + fn decode_res(raw: UntrustedRlp) -> Result<(Bytes, Vec), ::rlp::DecoderError> { + Ok(( + try!(raw.val_at(0)), + try!(raw.at(1)).iter().map(|x| x.as_raw().to_owned()).collect(), + )) + } + + + let req_id = try!(self.pre_verify_response(peer, request::Kind::HeaderProofs, &raw)); + let raw_proofs: Vec<_> = try!(raw.iter().skip(2).map(decode_res).collect()); + + for handler in &self.handlers { + handler.on_header_proofs(&Ctx { + peer: *peer, + io: io, + proto: self, + }, req_id, &raw_proofs); + } + + Ok(()) } // Receive a set of transactions to relay. - fn relay_transactions(&self, peer: &PeerId, data: UntrustedRlp) -> Result<(), Error> { + fn relay_transactions(&self, peer: &PeerId, io: &IoContext, data: UntrustedRlp) -> Result<(), Error> { const MAX_TRANSACTIONS: usize = 256; let txs: Vec<_> = try!(data.iter().take(MAX_TRANSACTIONS).map(|x| x.as_val::()).collect()); @@ -744,7 +1047,11 @@ impl LightProtocol { debug!(target: "les", "Received {} transactions to relay from peer {}", txs.len(), peer); for handler in &self.handlers { - handler.on_transactions(*peer, &txs); + handler.on_transactions(&Ctx { + peer: *peer, + io: io, + proto: self, + }, &txs); } Ok(()) @@ -757,60 +1064,15 @@ impl NetworkProtocolHandler for LightProtocol { } fn read(&self, io: &NetworkContext, peer: &PeerId, packet_id: u8, data: &[u8]) { - let rlp = UntrustedRlp::new(data); - - // handle the packet - let res = match packet_id { - packet::STATUS => self.status(peer, rlp), - packet::ANNOUNCE => self.announcement(peer, rlp), - - packet::GET_BLOCK_HEADERS => self.get_block_headers(peer, io, rlp), - packet::BLOCK_HEADERS => self.block_headers(peer, io, rlp), - - packet::GET_BLOCK_BODIES => self.get_block_bodies(peer, io, rlp), - packet::BLOCK_BODIES => self.block_bodies(peer, io, rlp), - - packet::GET_RECEIPTS => self.get_receipts(peer, io, rlp), - packet::RECEIPTS => self.receipts(peer, io, rlp), - - packet::GET_PROOFS => self.get_proofs(peer, io, rlp), - packet::PROOFS => self.proofs(peer, io, rlp), - - packet::GET_CONTRACT_CODES => self.get_contract_code(peer, io, rlp), - packet::CONTRACT_CODES => self.contract_code(peer, io, rlp), - - packet::GET_HEADER_PROOFS => self.get_header_proofs(peer, io, rlp), - packet::HEADER_PROOFS => self.header_proofs(peer, io, rlp), - - packet::SEND_TRANSACTIONS => self.relay_transactions(peer, rlp), - - other => { - Err(Error::UnrecognizedPacket(other)) - } - }; - - // if something went wrong, figure out how much to punish the peer. - if let Err(e) = res { - match e.punishment() { - Punishment::None => {} - Punishment::Disconnect => { - debug!(target: "les", "Disconnecting peer {}: {}", peer, e); - io.disconnect_peer(*peer) - } - Punishment::Disable => { - debug!(target: "les", "Disabling peer {}: {}", peer, e); - io.disable_peer(*peer) - } - } - } + self.handle_packet(io, peer, packet_id, data); } fn connected(&self, io: &NetworkContext, peer: &PeerId) { self.on_connect(peer, io); } - fn disconnected(&self, _io: &NetworkContext, peer: &PeerId) { - self.on_disconnect(*peer); + fn disconnected(&self, io: &NetworkContext, peer: &PeerId) { + self.on_disconnect(*peer, io); } fn timeout(&self, _io: &NetworkContext, timer: TimerToken) { diff --git a/ethcore/light/src/net/status.rs b/ethcore/light/src/net/status.rs index 2c0c5f79a..59981b88d 100644 --- a/ethcore/light/src/net/status.rs +++ b/ethcore/light/src/net/status.rs @@ -82,26 +82,6 @@ impl Key { } } -/// Network ID structure. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(u32)] -pub enum NetworkId { - /// ID for the mainnet - Mainnet = 1, - /// ID for the testnet - Testnet = 0, -} - -impl NetworkId { - fn from_raw(raw: u32) -> Option { - match raw { - 0 => Some(NetworkId::Testnet), - 1 => Some(NetworkId::Mainnet), - _ => None, - } - } -} - // helper for decoding key-value pairs in the handshake or an announcement. struct Parser<'a> { pos: usize, @@ -118,6 +98,7 @@ impl<'a> Parser<'a> { // expect a specific next key, and get the value's RLP. // if the key isn't found, the position isn't advanced. fn expect_raw(&mut self, key: Key) -> Result, DecoderError> { + trace!(target: "les", "Expecting key {}", key.as_str()); let pre_pos = self.pos; if let Some((k, val)) = try!(self.get_next()) { if k == key { return Ok(val) } @@ -164,7 +145,7 @@ pub struct Status { /// Protocol version. pub protocol_version: u32, /// Network id of this peer. - pub network_id: NetworkId, + pub network_id: u64, /// Total difficulty of the head of the chain. pub head_td: U256, /// Hash of the best block. @@ -217,7 +198,7 @@ impl Capabilities { /// - chain status /// - serving capabilities /// - buffer flow parameters -pub fn parse_handshake(rlp: UntrustedRlp) -> Result<(Status, Capabilities, FlowParams), DecoderError> { +pub fn parse_handshake(rlp: UntrustedRlp) -> Result<(Status, Capabilities, Option), DecoderError> { let mut parser = Parser { pos: 0, rlp: rlp, @@ -225,8 +206,7 @@ pub fn parse_handshake(rlp: UntrustedRlp) -> Result<(Status, Capabilities, FlowP let status = Status { protocol_version: try!(parser.expect(Key::ProtocolVersion)), - network_id: try!(parser.expect(Key::NetworkId) - .and_then(|id: u32| NetworkId::from_raw(id).ok_or(DecoderError::Custom("Invalid network ID")))), + network_id: try!(parser.expect(Key::NetworkId)), head_td: try!(parser.expect(Key::HeadTD)), head_hash: try!(parser.expect(Key::HeadHash)), head_num: try!(parser.expect(Key::HeadNum)), @@ -241,20 +221,23 @@ pub fn parse_handshake(rlp: UntrustedRlp) -> Result<(Status, Capabilities, FlowP tx_relay: parser.expect_raw(Key::TxRelay).is_ok(), }; - let flow_params = FlowParams::new( - try!(parser.expect(Key::BufferLimit)), - try!(parser.expect(Key::BufferCostTable)), - try!(parser.expect(Key::BufferRechargeRate)), - ); + let flow_params = match ( + parser.expect(Key::BufferLimit), + parser.expect(Key::BufferCostTable), + parser.expect(Key::BufferRechargeRate) + ) { + (Ok(bl), Ok(bct), Ok(brr)) => Some(FlowParams::new(bl, bct, brr)), + _ => None, + }; Ok((status, capabilities, flow_params)) } /// Write a handshake, given status, capabilities, and flow parameters. -pub fn write_handshake(status: &Status, capabilities: &Capabilities, flow_params: &FlowParams) -> Vec { +pub fn write_handshake(status: &Status, capabilities: &Capabilities, flow_params: Option<&FlowParams>) -> Vec { let mut pairs = Vec::new(); pairs.push(encode_pair(Key::ProtocolVersion, &status.protocol_version)); - pairs.push(encode_pair(Key::NetworkId, &(status.network_id as u32))); + pairs.push(encode_pair(Key::NetworkId, &(status.network_id as u64))); pairs.push(encode_pair(Key::HeadTD, &status.head_td)); pairs.push(encode_pair(Key::HeadHash, &status.head_hash)); pairs.push(encode_pair(Key::HeadNum, &status.head_num)); @@ -273,9 +256,11 @@ pub fn write_handshake(status: &Status, capabilities: &Capabilities, flow_params pairs.push(encode_flag(Key::TxRelay)); } - pairs.push(encode_pair(Key::BufferLimit, flow_params.limit())); - pairs.push(encode_pair(Key::BufferCostTable, flow_params.cost_table())); - pairs.push(encode_pair(Key::BufferRechargeRate, flow_params.recharge_rate())); + if let Some(flow_params) = flow_params { + pairs.push(encode_pair(Key::BufferLimit, flow_params.limit())); + pairs.push(encode_pair(Key::BufferCostTable, flow_params.cost_table())); + pairs.push(encode_pair(Key::BufferRechargeRate, flow_params.recharge_rate())); + } let mut stream = RlpStream::new_list(pairs.len()); @@ -385,7 +370,7 @@ mod tests { fn full_handshake() { let status = Status { protocol_version: 1, - network_id: NetworkId::Mainnet, + network_id: 1, head_td: U256::default(), head_hash: H256::default(), head_num: 10, @@ -406,21 +391,21 @@ mod tests { 1000.into(), ); - let handshake = write_handshake(&status, &capabilities, &flow_params); + let handshake = write_handshake(&status, &capabilities, Some(&flow_params)); let (read_status, read_capabilities, read_flow) = parse_handshake(UntrustedRlp::new(&handshake)).unwrap(); assert_eq!(read_status, status); assert_eq!(read_capabilities, capabilities); - assert_eq!(read_flow, flow_params); + assert_eq!(read_flow.unwrap(), flow_params); } #[test] fn partial_handshake() { let status = Status { protocol_version: 1, - network_id: NetworkId::Mainnet, + network_id: 1, head_td: U256::default(), head_hash: H256::default(), head_num: 10, @@ -441,21 +426,21 @@ mod tests { 1000.into(), ); - let handshake = write_handshake(&status, &capabilities, &flow_params); + let handshake = write_handshake(&status, &capabilities, Some(&flow_params)); let (read_status, read_capabilities, read_flow) = parse_handshake(UntrustedRlp::new(&handshake)).unwrap(); assert_eq!(read_status, status); assert_eq!(read_capabilities, capabilities); - assert_eq!(read_flow, flow_params); + assert_eq!(read_flow.unwrap(), flow_params); } #[test] fn skip_unknown_keys() { let status = Status { protocol_version: 1, - network_id: NetworkId::Mainnet, + network_id: 1, head_td: U256::default(), head_hash: H256::default(), head_num: 10, @@ -476,7 +461,7 @@ mod tests { 1000.into(), ); - let handshake = write_handshake(&status, &capabilities, &flow_params); + let handshake = write_handshake(&status, &capabilities, Some(&flow_params)); let interleaved = { let handshake = UntrustedRlp::new(&handshake); let mut stream = RlpStream::new_list(handshake.item_count() * 3); @@ -498,7 +483,7 @@ mod tests { assert_eq!(read_status, status); assert_eq!(read_capabilities, capabilities); - assert_eq!(read_flow, flow_params); + assert_eq!(read_flow.unwrap(), flow_params); } #[test] @@ -548,4 +533,33 @@ mod tests { let out = stream.drain(); assert!(parse_announcement(UntrustedRlp::new(&out)).is_ok()); } + + #[test] + fn optional_flow() { + let status = Status { + protocol_version: 1, + network_id: 1, + head_td: U256::default(), + head_hash: H256::default(), + head_num: 10, + genesis_hash: H256::zero(), + last_head: None, + }; + + let capabilities = Capabilities { + serve_headers: true, + serve_chain_since: Some(5), + serve_state_since: Some(8), + tx_relay: true, + }; + + let handshake = write_handshake(&status, &capabilities, None); + + let (read_status, read_capabilities, read_flow) + = parse_handshake(UntrustedRlp::new(&handshake)).unwrap(); + + assert_eq!(read_status, status); + assert_eq!(read_capabilities, capabilities); + assert!(read_flow.is_none()); + } } \ No newline at end of file diff --git a/ethcore/light/src/net/tests/mod.rs b/ethcore/light/src/net/tests/mod.rs new file mode 100644 index 000000000..6e2bc9f33 --- /dev/null +++ b/ethcore/light/src/net/tests/mod.rs @@ -0,0 +1,512 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +//! Tests for the `LightProtocol` implementation. +//! These don't test of the higher level logic on top of + +use ethcore::blockchain_info::BlockChainInfo; +use ethcore::client::{BlockChainClient, EachBlockWith, TestBlockChainClient}; +use ethcore::ids::BlockID; +use ethcore::transaction::SignedTransaction; +use network::PeerId; + +use net::buffer_flow::FlowParams; +use net::context::IoContext; +use net::status::{Capabilities, Status, write_handshake}; +use net::{encode_request, LightProtocol, Params, packet}; +use provider::Provider; +use request::{self, Request, Headers}; + +use rlp::*; +use util::{Bytes, H256, U256}; + +use std::sync::Arc; + +// expected result from a call. +#[derive(Debug, PartialEq, Eq)] +enum Expect { + /// Expect to have message sent to peer. + Send(PeerId, u8, Vec), + /// Expect this response. + Respond(u8, Vec), + /// Expect a punishment (disconnect/disable) + Punish(PeerId), + /// Expect nothing. + Nothing, +} + +impl IoContext for Expect { + fn send(&self, peer: PeerId, packet_id: u8, packet_body: Vec) { + assert_eq!(self, &Expect::Send(peer, packet_id, packet_body)); + } + + fn respond(&self, packet_id: u8, packet_body: Vec) { + assert_eq!(self, &Expect::Respond(packet_id, packet_body)); + } + + fn disconnect_peer(&self, peer: PeerId) { + assert_eq!(self, &Expect::Punish(peer)); + } + + fn disable_peer(&self, peer: PeerId) { + assert_eq!(self, &Expect::Punish(peer)); + } + + fn protocol_version(&self, _peer: PeerId) -> Option { + Some(super::MAX_PROTOCOL_VERSION) + } +} + +// can't implement directly for Arc due to cross-crate orphan rules. +struct TestProvider(Arc); + +struct TestProviderInner { + client: TestBlockChainClient, +} + +impl Provider for TestProvider { + fn chain_info(&self) -> BlockChainInfo { + self.0.client.chain_info() + } + + fn reorg_depth(&self, a: &H256, b: &H256) -> Option { + self.0.client.tree_route(a, b).map(|route| route.index as u64) + } + + fn earliest_state(&self) -> Option { + None + } + + fn block_headers(&self, req: request::Headers) -> Vec { + let best_num = self.0.client.chain_info().best_block_number; + let start_num = req.block_num; + + match self.0.client.block_hash(BlockID::Number(req.block_num)) { + Some(hash) if hash == req.block_hash => {} + _=> { + trace!(target: "les_provider", "unknown/non-canonical start block in header request: {:?}", (req.block_num, req.block_hash)); + return vec![] + } + } + + (0u64..req.max as u64) + .map(|x: u64| x.saturating_mul(req.skip + 1)) + .take_while(|x| if req.reverse { x < &start_num } else { best_num - start_num >= *x }) + .map(|x| if req.reverse { start_num - x } else { start_num + x }) + .map(|x| self.0.client.block_header(BlockID::Number(x))) + .take_while(|x| x.is_some()) + .flat_map(|x| x) + .collect() + } + + fn block_bodies(&self, req: request::Bodies) -> Vec { + req.block_hashes.into_iter() + .map(|hash| self.0.client.block_body(BlockID::Hash(hash))) + .map(|body| body.unwrap_or_else(|| ::rlp::EMPTY_LIST_RLP.to_vec())) + .collect() + } + + fn receipts(&self, req: request::Receipts) -> Vec { + req.block_hashes.into_iter() + .map(|hash| self.0.client.block_receipts(&hash)) + .map(|receipts| receipts.unwrap_or_else(|| ::rlp::EMPTY_LIST_RLP.to_vec())) + .collect() + } + + fn proofs(&self, req: request::StateProofs) -> Vec { + req.requests.into_iter() + .map(|req| { + match req.key2 { + Some(_) => ::util::sha3::SHA3_NULL_RLP.to_vec(), + None => { + // sort of a leaf node + let mut stream = RlpStream::new_list(2); + stream.append(&req.key1).append_empty_data(); + stream.out() + } + } + }) + .collect() + } + + fn contract_code(&self, req: request::ContractCodes) -> Vec { + req.code_requests.into_iter() + .map(|req| { + req.account_key.iter().chain(req.account_key.iter()).cloned().collect() + }) + .collect() + } + + fn header_proofs(&self, req: request::HeaderProofs) -> Vec { + req.requests.into_iter().map(|_| ::rlp::EMPTY_LIST_RLP.to_vec()).collect() + } + + fn pending_transactions(&self) -> Vec { + self.0.client.pending_transactions() + } +} + +fn make_flow_params() -> FlowParams { + FlowParams::new(5_000_000.into(), Default::default(), 100_000.into()) +} + +fn capabilities() -> Capabilities { + Capabilities { + serve_headers: true, + serve_chain_since: Some(1), + serve_state_since: Some(1), + tx_relay: true, + } +} + +// helper for setting up the protocol handler and provider. +fn setup(flow_params: FlowParams, capabilities: Capabilities) -> (Arc, LightProtocol) { + let provider = Arc::new(TestProviderInner { + client: TestBlockChainClient::new(), + }); + + let proto = LightProtocol::new(Arc::new(TestProvider(provider.clone())), Params { + network_id: 2, + flow_params: flow_params, + capabilities: capabilities, + }); + + (provider, proto) +} + +fn status(chain_info: BlockChainInfo) -> Status { + Status { + protocol_version: 1, + network_id: 2, + head_td: chain_info.total_difficulty, + head_hash: chain_info.best_block_hash, + head_num: chain_info.best_block_number, + genesis_hash: chain_info.genesis_hash, + last_head: None, + } +} + +#[test] +fn handshake_expected() { + let flow_params = make_flow_params(); + let capabilities = capabilities(); + + let (provider, proto) = setup(flow_params.clone(), capabilities.clone()); + + let status = status(provider.client.chain_info()); + + let packet_body = write_handshake(&status, &capabilities, Some(&flow_params)); + + proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body)); +} + +#[test] +#[should_panic] +fn genesis_mismatch() { + let flow_params = make_flow_params(); + let capabilities = capabilities(); + + let (provider, proto) = setup(flow_params.clone(), capabilities.clone()); + + let mut status = status(provider.client.chain_info()); + status.genesis_hash = H256::default(); + + let packet_body = write_handshake(&status, &capabilities, Some(&flow_params)); + + proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body)); +} + +#[test] +fn buffer_overflow() { + let flow_params = make_flow_params(); + let capabilities = capabilities(); + + let (provider, proto) = setup(flow_params.clone(), capabilities.clone()); + + let status = status(provider.client.chain_info()); + + { + let packet_body = write_handshake(&status, &capabilities, Some(&flow_params)); + proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body)); + } + + { + let my_status = write_handshake(&status, &capabilities, Some(&flow_params)); + proto.handle_packet(&Expect::Nothing, &1, packet::STATUS, &my_status); + } + + // 1000 requests is far too many for the default flow params. + let request = encode_request(&Request::Headers(Headers { + block_num: 1, + block_hash: provider.client.chain_info().genesis_hash, + max: 1000, + skip: 0, + reverse: false, + }), 111); + + proto.handle_packet(&Expect::Punish(1), &1, packet::GET_BLOCK_HEADERS, &request); +} + +// test the basic request types -- these just make sure that requests are parsed +// and sent to the provider correctly as well as testing response formatting. + +#[test] +fn get_block_headers() { + let flow_params = FlowParams::new(5_000_000.into(), Default::default(), 0.into()); + let capabilities = capabilities(); + + let (provider, proto) = setup(flow_params.clone(), capabilities.clone()); + + let cur_status = status(provider.client.chain_info()); + let my_status = write_handshake(&cur_status, &capabilities, Some(&flow_params)); + + provider.client.add_blocks(100, EachBlockWith::Nothing); + + let cur_status = status(provider.client.chain_info()); + + { + let packet_body = write_handshake(&cur_status, &capabilities, Some(&flow_params)); + proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body)); + proto.handle_packet(&Expect::Nothing, &1, packet::STATUS, &my_status); + } + + let request = Headers { + block_num: 1, + block_hash: provider.client.block_hash(BlockID::Number(1)).unwrap(), + max: 10, + skip: 0, + reverse: false, + }; + let req_id = 111; + + let request_body = encode_request(&Request::Headers(request.clone()), req_id); + let response = { + let headers: Vec<_> = (0..10).map(|i| provider.client.block_header(BlockID::Number(i + 1)).unwrap()).collect(); + assert_eq!(headers.len(), 10); + + let new_buf = *flow_params.limit() - flow_params.compute_cost(request::Kind::Headers, 10); + + let mut response_stream = RlpStream::new_list(12); + + response_stream.append(&req_id).append(&new_buf); + for header in headers { + response_stream.append_raw(&header, 1); + } + + response_stream.out() + }; + + let expected = Expect::Respond(packet::BLOCK_HEADERS, response); + proto.handle_packet(&expected, &1, packet::GET_BLOCK_HEADERS, &request_body); +} + +#[test] +fn get_block_bodies() { + let flow_params = FlowParams::new(5_000_000.into(), Default::default(), 0.into()); + let capabilities = capabilities(); + + let (provider, proto) = setup(flow_params.clone(), capabilities.clone()); + + let cur_status = status(provider.client.chain_info()); + let my_status = write_handshake(&cur_status, &capabilities, Some(&flow_params)); + + provider.client.add_blocks(100, EachBlockWith::Nothing); + + let cur_status = status(provider.client.chain_info()); + + { + let packet_body = write_handshake(&cur_status, &capabilities, Some(&flow_params)); + proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body)); + proto.handle_packet(&Expect::Nothing, &1, packet::STATUS, &my_status); + } + + let request = request::Bodies { + block_hashes: (0..10).map(|i| provider.client.block_hash(BlockID::Number(i)).unwrap()).collect(), + }; + + let req_id = 111; + + let request_body = encode_request(&Request::Bodies(request.clone()), req_id); + let response = { + let bodies: Vec<_> = (0..10).map(|i| provider.client.block_body(BlockID::Number(i + 1)).unwrap()).collect(); + assert_eq!(bodies.len(), 10); + + let new_buf = *flow_params.limit() - flow_params.compute_cost(request::Kind::Bodies, 10); + + let mut response_stream = RlpStream::new_list(12); + + response_stream.append(&req_id).append(&new_buf); + for body in bodies { + response_stream.append_raw(&body, 1); + } + + response_stream.out() + }; + + let expected = Expect::Respond(packet::BLOCK_BODIES, response); + proto.handle_packet(&expected, &1, packet::GET_BLOCK_BODIES, &request_body); +} + +#[test] +fn get_block_receipts() { + let flow_params = FlowParams::new(5_000_000.into(), Default::default(), 0.into()); + let capabilities = capabilities(); + + let (provider, proto) = setup(flow_params.clone(), capabilities.clone()); + + let cur_status = status(provider.client.chain_info()); + let my_status = write_handshake(&cur_status, &capabilities, Some(&flow_params)); + + provider.client.add_blocks(1000, EachBlockWith::Nothing); + + let cur_status = status(provider.client.chain_info()); + + { + let packet_body = write_handshake(&cur_status, &capabilities, Some(&flow_params)); + proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body)); + proto.handle_packet(&Expect::Nothing, &1, packet::STATUS, &my_status); + } + + // find the first 10 block hashes starting with `f` because receipts are only provided + // by the test client in that case. + let block_hashes: Vec<_> = (0..1000).map(|i| provider.client.block_hash(BlockID::Number(i)).unwrap()) + .filter(|hash| format!("{}", hash).starts_with("f")).take(10).collect(); + + let request = request::Receipts { + block_hashes: block_hashes.clone(), + }; + + let req_id = 111; + + let request_body = encode_request(&Request::Receipts(request.clone()), req_id); + let response = { + let receipts: Vec<_> = block_hashes.iter() + .map(|hash| provider.client.block_receipts(hash).unwrap()) + .collect(); + + let new_buf = *flow_params.limit() - flow_params.compute_cost(request::Kind::Receipts, receipts.len()); + + let mut response_stream = RlpStream::new_list(2 + receipts.len()); + + response_stream.append(&req_id).append(&new_buf); + for block_receipts in receipts { + response_stream.append_raw(&block_receipts, 1); + } + + response_stream.out() + }; + + let expected = Expect::Respond(packet::RECEIPTS, response); + proto.handle_packet(&expected, &1, packet::GET_RECEIPTS, &request_body); +} + +#[test] +fn get_state_proofs() { + let flow_params = FlowParams::new(5_000_000.into(), Default::default(), 0.into()); + let capabilities = capabilities(); + + let (provider, proto) = setup(flow_params.clone(), capabilities.clone()); + + let cur_status = status(provider.client.chain_info()); + + { + let packet_body = write_handshake(&cur_status, &capabilities, Some(&flow_params)); + proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body.clone())); + proto.handle_packet(&Expect::Nothing, &1, packet::STATUS, &packet_body); + } + + let req_id = 112; + let key1 = U256::from(11223344).into(); + let key2 = U256::from(99988887).into(); + + let request = Request::StateProofs (request::StateProofs { + requests: vec![ + request::StateProof { block: H256::default(), key1: key1, key2: None, from_level: 0 }, + request::StateProof { block: H256::default(), key1: key1, key2: Some(key2), from_level: 0}, + ] + }); + + let request_body = encode_request(&request, req_id); + let response = { + let proofs = vec![ + { let mut stream = RlpStream::new_list(2); stream.append(&key1).append_empty_data(); stream.out() }, + ::util::sha3::SHA3_NULL_RLP.to_vec(), + ]; + + let new_buf = *flow_params.limit() - flow_params.compute_cost(request::Kind::StateProofs, 2); + + let mut response_stream = RlpStream::new_list(4); + + response_stream.append(&req_id).append(&new_buf); + for proof in proofs { + response_stream.append_raw(&proof, 1); + } + + response_stream.out() + }; + + let expected = Expect::Respond(packet::PROOFS, response); + proto.handle_packet(&expected, &1, packet::GET_PROOFS, &request_body); +} + +#[test] +fn get_contract_code() { + let flow_params = FlowParams::new(5_000_000.into(), Default::default(), 0.into()); + let capabilities = capabilities(); + + let (provider, proto) = setup(flow_params.clone(), capabilities.clone()); + + let cur_status = status(provider.client.chain_info()); + + { + let packet_body = write_handshake(&cur_status, &capabilities, Some(&flow_params)); + proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body.clone())); + proto.handle_packet(&Expect::Nothing, &1, packet::STATUS, &packet_body); + } + + let req_id = 112; + let key1 = U256::from(11223344).into(); + let key2 = U256::from(99988887).into(); + + let request = Request::Codes (request::ContractCodes { + code_requests: vec![ + request::ContractCode { block_hash: H256::default(), account_key: key1 }, + request::ContractCode { block_hash: H256::default(), account_key: key2 }, + ], + }); + + let request_body = encode_request(&request, req_id); + let response = { + let codes: Vec> = vec![ + key1.iter().chain(key1.iter()).cloned().collect(), + key2.iter().chain(key2.iter()).cloned().collect(), + ]; + + let new_buf = *flow_params.limit() - flow_params.compute_cost(request::Kind::Codes, 2); + + let mut response_stream = RlpStream::new_list(4); + + response_stream.append(&req_id).append(&new_buf); + for code in codes { + response_stream.append(&code); + } + + response_stream.out() + }; + + let expected = Expect::Respond(packet::CONTRACT_CODES, response); + proto.handle_packet(&expected, &1, packet::GET_CONTRACT_CODES, &request_body); +} \ No newline at end of file diff --git a/ethcore/light/src/provider.rs b/ethcore/light/src/provider.rs index 264df0397..37a5cef4d 100644 --- a/ethcore/light/src/provider.rs +++ b/ethcore/light/src/provider.rs @@ -33,6 +33,7 @@ use request; /// or empty vector where appropriate. /// /// [1]: https://github.com/ethcore/parity/wiki/Light-Ethereum-Subprotocol-(LES) +#[cfg_attr(feature = "ipc", ipc(client_ident="LightProviderClient"))] pub trait Provider: Send + Sync { /// Provide current blockchain info. fn chain_info(&self) -> BlockChainInfo; @@ -71,7 +72,10 @@ pub trait Provider: Send + Sync { /// Each item in the resulting vector is either the raw bytecode or empty. fn contract_code(&self, req: request::ContractCodes) -> Vec; - /// Provide header proofs from the Canonical Hash Tries. + /// Provide header proofs from the Canonical Hash Tries as well as the headers + /// they correspond to -- each element in the returned vector is a 2-tuple. + /// The first element is a block header and the second a merkle proof of + /// the header in a requested CHT. fn header_proofs(&self, req: request::HeaderProofs) -> Vec; /// Provide pending transactions. @@ -105,8 +109,8 @@ impl Provider for T { } (0u64..req.max as u64) - .map(|x: u64| x.saturating_mul(req.skip)) - .take_while(|x| if req.reverse { x < &start_num } else { best_num - start_num < *x }) + .map(|x: u64| x.saturating_mul(req.skip + 1)) + .take_while(|x| if req.reverse { x < &start_num } else { best_num - start_num >= *x }) .map(|x| if req.reverse { start_num - x } else { start_num + x }) .map(|x| self.block_header(BlockID::Number(x))) .take_while(|x| x.is_some()) diff --git a/ethcore/light/src/types/les_request.rs b/ethcore/light/src/types/les_request.rs index d0de080ee..49bd2e9cc 100644 --- a/ethcore/light/src/types/les_request.rs +++ b/ethcore/light/src/types/les_request.rs @@ -19,7 +19,8 @@ use util::H256; /// A request for block headers. -#[derive(Debug, Clone, PartialEq, Eq, Binary)] +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "ipc", derive(Binary))] pub struct Headers { /// Starting block number pub block_num: u64, @@ -35,7 +36,8 @@ pub struct Headers { } /// A request for specific block bodies. -#[derive(Debug, Clone, PartialEq, Eq, Binary)] +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "ipc", derive(Binary))] pub struct Bodies { /// Hashes which bodies are being requested for. pub block_hashes: Vec @@ -45,14 +47,16 @@ pub struct Bodies { /// /// This request is answered with a list of transaction receipts for each block /// requested. -#[derive(Debug, Clone, PartialEq, Eq, Binary)] +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "ipc", derive(Binary))] pub struct Receipts { /// Block hashes to return receipts for. pub block_hashes: Vec, } /// A request for a state proof -#[derive(Debug, Clone, PartialEq, Eq, Binary)] +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "ipc", derive(Binary))] pub struct StateProof { /// Block hash to query state from. pub block: H256, @@ -66,14 +70,16 @@ pub struct StateProof { } /// A request for state proofs. -#[derive(Debug, Clone, PartialEq, Eq, Binary)] +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "ipc", derive(Binary))] pub struct StateProofs { /// All the proof requests. pub requests: Vec, } /// A request for contract code. -#[derive(Debug, Clone, PartialEq, Eq, Binary)] +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "ipc", derive(Binary))] pub struct ContractCode { /// Block hash pub block_hash: H256, @@ -82,14 +88,16 @@ pub struct ContractCode { } /// A request for contract code. -#[derive(Debug, Clone, PartialEq, Eq, Binary)] +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "ipc", derive(Binary))] pub struct ContractCodes { /// Block hash and account key (== sha3(address)) pairs to fetch code for. pub code_requests: Vec, } /// A request for a header proof from the Canonical Hash Trie. -#[derive(Debug, Clone, PartialEq, Eq, Binary)] +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "ipc", derive(Binary))] pub struct HeaderProof { /// Number of the CHT. pub cht_number: u64, @@ -100,14 +108,16 @@ pub struct HeaderProof { } /// A request for header proofs from the CHT. -#[derive(Debug, Clone, PartialEq, Eq, Binary)] +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "ipc", derive(Binary))] pub struct HeaderProofs { /// All the proof requests. pub requests: Vec, } /// Kinds of requests. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Binary)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "ipc", derive(Binary))] pub enum Kind { /// Requesting headers. Headers, @@ -124,7 +134,8 @@ pub enum Kind { } /// Encompasses all possible types of requests in a single structure. -#[derive(Debug, Clone, PartialEq, Eq, Binary)] +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "ipc", derive(Binary))] pub enum Request { /// Requesting headers. Headers(Headers), diff --git a/ethcore/light/src/types/mod.rs b/ethcore/light/src/types/mod.rs index 2625358a3..d7f473553 100644 --- a/ethcore/light/src/types/mod.rs +++ b/ethcore/light/src/types/mod.rs @@ -15,6 +15,11 @@ // along with Parity. If not, see . //! Types used in the public (IPC) api which require custom code generation. +#![cfg_attr(feature = "ipc", allow(dead_code, unused_assignments, unused_variables))] // codegen issues -#![allow(dead_code, unused_assignments, unused_variables)] // codegen issues + +#[cfg(feature = "ipc")] include!(concat!(env!("OUT_DIR"), "/mod.rs.in")); + +#[cfg(not(feature = "ipc"))] +include!("mod.rs.in"); \ No newline at end of file diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index bad09794a..e685969cc 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -52,7 +52,7 @@ use blockchain::{BlockChain, BlockProvider, TreeRoute, ImportRoute}; use client::{ BlockID, TransactionID, UncleID, TraceId, ClientConfig, BlockChainClient, MiningBlockChainClient, TraceFilter, CallAnalytics, BlockImportError, Mode, - ChainNotify, PruningInfo, ProvingBlockChainClient, + ChainNotify, PruningInfo, }; use client::Error as ClientError; use env_info::EnvInfo; @@ -580,13 +580,6 @@ impl Client { self.miner.clone() } - /// Handle messages from the IO queue - pub fn handle_queued_message(&self, message: &Bytes) { - if let Err(e) = self.engine.handle_message(UntrustedRlp::new(message)) { - trace!(target: "poa", "Invalid message received: {}", e); - } - } - /// Used by PoA to try sealing on period change. pub fn update_sealing(&self) { self.miner.update_sealing(self) @@ -1096,6 +1089,10 @@ impl BlockChainClient for Client { self.transaction_address(id).and_then(|address| self.chain.read().transaction(&address)) } + fn transaction_block(&self, id: TransactionID) -> Option { + self.transaction_address(id).map(|addr| addr.block_hash) + } + fn uncle(&self, id: UncleID) -> Option { let index = id.position; self.block_body(id.block).and_then(|body| BodyView::new(&body).uncle_rlp_at(index)) @@ -1434,7 +1431,7 @@ impl MayPanic for Client { } } -impl ProvingBlockChainClient for Client { +impl ::client::ProvingBlockChainClient for Client { fn prove_storage(&self, key1: H256, key2: H256, from_level: u32, id: BlockID) -> Vec { self.state_at(id) .and_then(move |state| state.prove_storage(key1, key2, from_level).ok()) diff --git a/ethcore/src/client/mod.rs b/ethcore/src/client/mod.rs index af4daece6..4e5554b01 100644 --- a/ethcore/src/client/mod.rs +++ b/ethcore/src/client/mod.rs @@ -27,7 +27,9 @@ pub use self::config::{Mode, ClientConfig, DatabaseCompactionProfile, BlockChain pub use self::error::Error; pub use self::test_client::{TestBlockChainClient, EachBlockWith}; pub use self::chain_notify::ChainNotify; -pub use self::traits::{BlockChainClient, MiningBlockChainClient, ProvingBlockChainClient}; +pub use self::traits::{BlockChainClient, MiningBlockChainClient}; + +pub use self::traits::ProvingBlockChainClient; pub use types::ids::*; pub use types::trace_filter::Filter as TraceFilter; diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index 01a91fa66..43dd366b0 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -92,8 +92,8 @@ pub struct TestBlockChainClient { pub first_block: RwLock>, } -#[derive(Clone)] /// Used for generating test client blocks. +#[derive(Clone)] pub enum EachBlockWith { /// Plain block. Nothing, @@ -434,6 +434,10 @@ impl BlockChainClient for TestBlockChainClient { None // Simple default. } + fn transaction_block(&self, _id: TransactionID) -> Option { + None // Simple default. + } + fn uncle(&self, _id: UncleID) -> Option { None // Simple default. } diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index cb5e77499..7488b8baf 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -129,6 +129,9 @@ pub trait BlockChainClient : Sync + Send { /// Get transaction with given hash. fn transaction(&self, id: TransactionID) -> Option; + /// Get the hash of block that contains the transaction, if any. + fn transaction_block(&self, id: TransactionID) -> Option; + /// Get uncle with given id. fn uncle(&self, id: UncleID) -> Option; diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index f5072d04b..ee244a0e4 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -23,7 +23,7 @@ use account_provider::{AccountProvider, Error as AccountError}; use views::{BlockView, HeaderView}; use header::Header; use state::{State, CleanupMode}; -use client::{MiningBlockChainClient, Executive, Executed, EnvInfo, TransactOptions, BlockID, CallAnalytics}; +use client::{MiningBlockChainClient, Executive, Executed, EnvInfo, TransactOptions, BlockID, CallAnalytics, TransactionID}; use client::TransactionImportResult; use executive::contract_address; use block::{ClosedBlock, IsBlock, Block}; @@ -357,6 +357,8 @@ impl Miner { let block_number = open_block.block().fields().header.number(); // TODO Push new uncles too. + let mut tx_count: usize = 0; + let tx_total = transactions.len(); for tx in transactions { let hash = tx.hash(); let start = Instant::now(); @@ -378,7 +380,7 @@ impl Miner { }, _ => {}, } - + trace!(target: "miner", "Adding tx {:?} took {:?}", hash, took); match result { Err(Error::Execution(ExecutionError::BlockGasLimitReached { gas_limit, gas_used, gas })) => { debug!(target: "miner", "Skipping adding transaction to block because of gas limit: {:?} (limit: {:?}, used: {:?}, gas: {:?})", hash, gas_limit, gas_used, gas); @@ -407,9 +409,12 @@ impl Miner { "Error adding transaction to block: number={}. transaction_hash={:?}, Error: {:?}", block_number, hash, e); }, - _ => {} // imported ok + _ => { + tx_count += 1; + } // imported ok } } + trace!(target: "miner", "Pushed {}/{} transactions", tx_count, tx_total); let block = open_block.close(); @@ -589,6 +594,10 @@ impl Miner { let best_block_header: Header = ::rlp::decode(&chain.best_block_header()); transactions.into_iter() .map(|tx| { + if chain.transaction_block(TransactionID::Hash(tx.hash())).is_some() { + debug!(target: "miner", "Rejected tx {:?}: already in the blockchain", tx.hash()); + return Err(Error::Transaction(TransactionError::AlreadyImported)); + } match self.engine.verify_transaction_basic(&tx, &best_block_header) { Err(e) => { debug!(target: "miner", "Rejected tx {:?} with invalid signature: {:?}", tx.hash(), e); diff --git a/ethcore/src/service.rs b/ethcore/src/service.rs index ea4a92bed..0429d3539 100644 --- a/ethcore/src/service.rs +++ b/ethcore/src/service.rs @@ -17,6 +17,7 @@ //! Creates and registers client and network services. use util::*; +use rlp::{UntrustedRlp, View}; use io::*; use spec::Spec; use error::*; @@ -227,7 +228,9 @@ impl IoHandler for ClientIoHandler { ClientIoMessage::UpdateSealing => self.client.update_sealing(), ClientIoMessage::SubmitSeal(ref hash, ref seal) => self.client.submit_seal(*hash, seal.clone()), ClientIoMessage::BroadcastMessage(ref message) => self.client.broadcast_message(message.clone()), - ClientIoMessage::NewMessage(ref message) => self.client.handle_queued_message(message), + ClientIoMessage::NewMessage(ref message) => if let Err(e) = self.client.engine().handle_message(UntrustedRlp::new(message)) { + trace!(target: "poa", "Invalid message received: {}", e); + }, _ => {} // ignore other messages } } diff --git a/ethcore/src/state/mod.rs b/ethcore/src/state/mod.rs index b3d63d0ae..8a52b62ff 100644 --- a/ethcore/src/state/mod.rs +++ b/ethcore/src/state/mod.rs @@ -31,6 +31,7 @@ use transaction::SignedTransaction; use state_db::StateDB; use util::*; + use util::trie::recorder::{Recorder, BasicRecorder as TrieRecorder}; mod account; diff --git a/js/.babelrc b/js/.babelrc index 2298d98c0..9f44b6bd2 100644 --- a/js/.babelrc +++ b/js/.babelrc @@ -7,6 +7,7 @@ "transform-runtime", "transform-decorators-legacy", "transform-class-properties", + "transform-object-rest-spread", "lodash" ], "retainLines": true, @@ -16,6 +17,15 @@ }, "development": { "plugins": ["react-hot-loader/babel"] + }, + "test": { + "plugins": [ + [ + "babel-plugin-webpack-alias", { + "config": "webpack/test.js" + } + ] + ] } } } diff --git a/js/package.json b/js/package.json index 62bf37b49..524c280dc 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.99", + "version": "0.2.105", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", @@ -40,33 +40,36 @@ "coveralls": "npm run testCoverage && coveralls < coverage/lcov.info", "lint": "eslint --ignore-path .gitignore ./src/", "lint:cached": "eslint --cache --ignore-path .gitignore ./src/", - "test": "mocha 'src/**/*.spec.js'", - "test:coverage": "istanbul cover _mocha -- 'src/**/*.spec.js'", - "test:e2e": "mocha 'src/**/*.e2e.js'", + "test": "NODE_ENV=test mocha 'src/**/*.spec.js'", + "test:coverage": "NODE_ENV=test istanbul cover _mocha -- 'src/**/*.spec.js'", + "test:e2e": "NODE_ENV=test mocha 'src/**/*.e2e.js'", "test:npm": "(cd .npmjs && npm i) && node test/npmLibrary && (rm -rf .npmjs/node_modules)", "prepush": "npm run lint:cached" }, "devDependencies": { "babel-cli": "6.18.0", - "babel-core": "6.18.2", + "babel-core": "6.20.0", "babel-eslint": "7.1.1", "babel-loader": "6.2.8", "babel-plugin-lodash": "3.2.10", - "babel-plugin-transform-class-properties": "6.19.0", + "babel-plugin-transform-class-properties": "6.18.0", "babel-plugin-transform-decorators-legacy": "1.3.4", + "babel-plugin-transform-object-rest-spread": "6.20.2", "babel-plugin-transform-react-remove-prop-types": "0.2.11", "babel-plugin-transform-runtime": "6.15.0", - "babel-polyfill": "6.16.0", + "babel-plugin-webpack-alias": "2.1.2", + "babel-polyfill": "6.20.0", "babel-preset-es2015": "6.18.0", - "babel-preset-es2015-rollup": "1.2.0", "babel-preset-es2016": "6.16.0", "babel-preset-es2017": "6.16.0", "babel-preset-react": "6.16.0", "babel-preset-stage-0": "6.16.0", "babel-register": "6.18.0", - "babel-runtime": "6.18.0", + "babel-runtime": "6.20.0", "chai": "3.5.0", + "chai-as-promised": "6.0.0", "chai-enzyme": "0.6.1", + "circular-dependency-plugin": "2.0.0", "copy-webpack-plugin": "4.0.1", "core-js": "2.4.1", "coveralls": "2.11.15", @@ -98,8 +101,8 @@ "mock-local-storage": "1.0.2", "mock-socket": "6.0.3", "nock": "9.0.2", - "postcss-import": "8.1.0", - "postcss-loader": "1.1.1", + "postcss-import": "9.0.0", + "postcss-loader": "1.2.0", "postcss-nested": "1.0.0", "postcss-simple-vars": "3.0.0", "progress": "1.1.8", @@ -136,7 +139,7 @@ "js-sha3": "0.5.5", "lodash": "4.17.2", "marked": "0.3.6", - "material-ui": "0.16.4", + "material-ui": "0.16.5", "material-ui-chip-input": "0.11.1", "mobx": "2.6.4", "mobx-react": "4.0.3", diff --git a/js/scripts/test.js b/js/scripts/test.js index 318fd7c84..78f2f99bd 100644 --- a/js/scripts/test.js +++ b/js/scripts/test.js @@ -1 +1 @@ -// test script 4 +// test script 6 diff --git a/js/src/3rdparty/etherscan/links.js b/js/src/3rdparty/etherscan/links.js index 2745873fc..e85572850 100644 --- a/js/src/3rdparty/etherscan/links.js +++ b/js/src/3rdparty/etherscan/links.js @@ -14,10 +14,14 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +export const url = (isTestnet = false) => { + return `https://${isTestnet ? 'testnet.' : ''}etherscan.io`; +}; + export const txLink = (hash, isTestnet = false) => { - return `https://${isTestnet ? 'testnet.' : ''}etherscan.io/tx/${hash}`; + return `${url(isTestnet)}/tx/${hash}`; }; export const addressLink = (address, isTestnet = false) => { - return `https://${isTestnet ? 'testnet.' : ''}etherscan.io/address/${address}`; + return `${url(isTestnet)}/address/${address}`; }; diff --git a/js/src/api/contract/contract.js b/js/src/api/contract/contract.js index ed922a02c..bfe7cabc4 100644 --- a/js/src/api/contract/contract.js +++ b/js/src/api/contract/contract.js @@ -240,8 +240,8 @@ export default class Contract { } _bindEvent = (event) => { - event.subscribe = (options = {}, callback) => { - return this._subscribe(event, options, callback); + event.subscribe = (options = {}, callback, autoRemove) => { + return this._subscribe(event, options, callback, autoRemove); }; event.unsubscribe = (subscriptionId) => { @@ -262,12 +262,11 @@ export default class Contract { } const options = this._getFilterOptions(event, _options); + options.fromBlock = 0; + options.toBlock = 'latest'; + return this._api.eth - .getLogs({ - fromBlock: 0, - toBlock: 'latest', - ...options - }) + .getLogs(options) .then((logs) => this.parseEventLogs(logs)); } @@ -307,16 +306,31 @@ export default class Contract { return this._api.eth.newFilter(options); } - subscribe (eventName = null, options = {}, callback) { + subscribe (eventName = null, options = {}, callback, autoRemove) { try { const event = this._findEvent(eventName); - return this._subscribe(event, options, callback); + return this._subscribe(event, options, callback, autoRemove); } catch (e) { return Promise.reject(e); } } - _subscribe (event = null, _options, callback) { + _sendData (subscriptionId, error, logs) { + const { autoRemove, callback } = this._subscriptions[subscriptionId]; + let result = true; + + try { + result = callback(error, logs); + } catch (error) { + console.warn('_sendData', subscriptionId, error); + } + + if (autoRemove && result && typeof result === 'boolean') { + this.unsubscribe(subscriptionId); + } + } + + _subscribe (event = null, _options, callback, autoRemove = false) { const subscriptionId = nextSubscriptionId++; const { skipInitFetch } = _options; delete _options['skipInitFetch']; @@ -326,6 +340,7 @@ export default class Contract { .then((filterId) => { this._subscriptions[subscriptionId] = { options: _options, + autoRemove, callback, filterId }; @@ -338,8 +353,7 @@ export default class Contract { return this._api.eth .getFilterLogs(filterId) .then((logs) => { - callback(null, this.parseEventLogs(logs)); - + this._sendData(subscriptionId, null, this.parseEventLogs(logs)); this._subscribeToChanges(); return subscriptionId; }); @@ -438,13 +452,13 @@ export default class Contract { }) ) .then((logsArray) => { - logsArray.forEach((logs, idx) => { + logsArray.forEach((logs, subscriptionId) => { if (!logs || !logs.length) { return; } try { - subscriptions[idx].callback(null, this.parseEventLogs(logs)); + this.sendData(subscriptionId, null, this.parseEventLogs(logs)); } catch (error) { console.error('_sendSubscriptionChanges', error); } diff --git a/js/src/api/subscriptions/manager.js b/js/src/api/subscriptions/manager.js index bc9632592..25e6e6129 100644 --- a/js/src/api/subscriptions/manager.js +++ b/js/src/api/subscriptions/manager.js @@ -59,7 +59,7 @@ export default class Manager { return subscription; } - subscribe (subscriptionName, callback) { + subscribe (subscriptionName, callback, autoRemove = false) { return new Promise((resolve, reject) => { const subscription = this._validateType(subscriptionName); @@ -75,6 +75,7 @@ export default class Manager { this.subscriptions[subscriptionId] = { name: subscriptionName, id: subscriptionId, + autoRemove, callback }; @@ -101,13 +102,18 @@ export default class Manager { } _sendData (subscriptionId, error, data) { - const { callback } = this.subscriptions[subscriptionId]; + const { autoRemove, callback } = this.subscriptions[subscriptionId]; + let result = true; try { - callback(error, data); + result = callback(error, data); } catch (error) { console.error(`Unable to update callback for subscriptionId ${subscriptionId}`, error); } + + if (autoRemove && result && typeof result === 'boolean') { + this.unsubscribe(subscriptionId); + } } _updateSubscriptions = (subscriptionName, error, data) => { diff --git a/js/src/contracts/code/index.js b/js/src/contracts/code/index.js index baa144979..ff6d218eb 100644 --- a/js/src/contracts/code/index.js +++ b/js/src/contracts/code/index.js @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import wallet from './wallet'; +import { wallet } from './wallet'; export { wallet diff --git a/js/src/contracts/code/wallet.js b/js/src/contracts/code/wallet.js index 94aa04b7b..92bcf8795 100644 --- a/js/src/contracts/code/wallet.js +++ b/js/src/contracts/code/wallet.js @@ -16,8 +16,14 @@ /** * @version Solidity v0.4.6 - * @from https://github.com/ethereum/dapp-bin/blob/dd5c485359074d49f571693ae064ce78970f3d6d/wallet/wallet.sol - * @date 22-Nov-2016 @ 15h00 UTC + * @from https://github.com/ethcore/parity/blob/63137b15482344ff9df634c086abaabed452eadc/js/src/contracts/snippets/enhanced-wallet.sol + * @date 09-Dec-2016 @ 16h00 UTC */ -export default '0x606060405234610000576040516113bb3803806113bb83398101604090815281516020830151918301519201915b805b83835b815160019081019055600033600160a060020a03166003825b505550600160a060020a033316600090815261010260205260408120600190555b82518110156100ee57828181518110156100005790602001906020020151600160a060020a0316600282600201610100811015610000570160005b5081905550806002016101026000858481518110156100005790602001906020020151600160a060020a03168152602001908152602001600020819055505b60010161006c565b60008290555b50505061010581905561011264010000000061127861012182021704565b610107555b505b50505061012b565b6201518042045b90565b611282806101396000396000f3606060405236156100da5760e060020a6000350463173825d981146101305780632f54bf6e146101425780634123cb6b1461016657806352375093146101855780635c52c2f5146101a4578063659010e7146101b35780637065cb48146101d2578063746c9171146101e4578063797af62714610203578063b20d30a914610227578063b61d27f614610239578063b75c7dc61461026b578063ba51a6df1461027d578063c2cf73261461028f578063c41a360a146102b6578063cbf0b0c0146102e2578063f00d4b5d146102f4578063f1736d8614610309575b61012e5b600034111561012b5760408051600160a060020a033316815234602082015281517fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c929181900390910190a15b5b565b005b346100005761012e600435610328565b005b3461000057610152600435610415565b604080519115158252519081900360200190f35b3461000057610173610436565b60408051918252519081900360200190f35b346100005761017361043c565b60408051918252519081900360200190f35b346100005761012e610443565b005b346100005761017361047b565b60408051918252519081900360200190f35b346100005761012e600435610482565b005b3461000057610173610571565b60408051918252519081900360200190f35b3461000057610152600435610577565b604080519115158252519081900360200190f35b346100005761012e6004356107e3565b005b34610000576101736004803590602480359160443591820191013561081c565b60408051918252519081900360200190f35b346100005761012e600435610ab3565b005b346100005761012e600435610b5e565b005b3461000057610152600435602435610be0565b604080519115158252519081900360200190f35b34610000576102c6600435610c35565b60408051600160a060020a039092168252519081900360200190f35b346100005761012e600435610c55565b005b346100005761012e600435602435610c93565b005b3461000057610173610d8c565b60408051918252519081900360200190f35b600060003660405180838380828437820191505092505050604051809103902061035181610d93565b1561040e57600160a060020a03831660009081526101026020526040902054915081151561037e5761040e565b60016001540360005411156103925761040e565b6000600283610100811015610000570160005b5055600160a060020a038316600090815261010260205260408120556103c9610f32565b6103d1611002565b60408051600160a060020a038516815290517f58619076adf5bb0943d100ef88d52d7c3fd691b19d3a9071b555b651fbf418da9181900360200190a15b5b5b505050565b600160a060020a03811660009081526101026020526040812054115b919050565b60015481565b6101075481565b60003660405180838380828437820191505092505050604051809103902061046a81610d93565b15610476576000610106555b5b5b50565b6101065481565b6000366040518083838082843782019150509250505060405180910390206104a981610d93565b1561056b576104b782610415565b156104c15761056b565b6104c9610f32565b60015460fa90106104dc576104dc611002565b5b60015460fa90106104ed5761056b565b60018054810190819055600160a060020a03831690600290610100811015610000570160005b5055600154600160a060020a03831660008181526101026020908152604091829020939093558051918252517f994a936646fe87ffe4f1e469d3d6aa417d6b855598397f323de5b449f765f0c3929181900390910190a15b5b5b5050565b60005481565b60008161058381610d93565b156107da5760008381526101086020526040902054600160a060020a0316156107da5760008381526101086020526040908190208054600180830154935160029384018054600160a060020a0390941695949093919283928592918116156101000260001901160480156106385780601f1061060d57610100808354040283529160200191610638565b820191906000526020600020905b81548152906001019060200180831161061b57829003601f168201915b505091505060006040518083038185876185025a03f15050506000848152610108602090815260409182902060018082015482548551600160a060020a033381811683529682018c905296810183905295166060860181905260a06080870181815260029586018054958616156101000260001901909516959095049087018190527fe7c957c06e9a662c1a6c77366179f5b702b97651dc28eee7d5bf1dff6e40bb4a975094958a95929491939290919060c08301908490801561073d5780601f106107125761010080835404028352916020019161073d565b820191906000526020600020905b81548152906001019060200180831161072057829003601f168201915b5050965050505050505060405180910390a16000838152610108602052604081208054600160a060020a0319168155600180820183905560028083018054858255939493909281161561010002600019011604601f81901061079f57506107d1565b601f0160209004906000526020600020908101906107d191905b808211156107cd57600081556001016107b9565b5090565b5b505050600191505b5b5b5b50919050565b60003660405180838380828437820191505092505050604051809103902061080a81610d93565b1561056b576101058290555b5b5b5050565b600061082733610415565b15610aa85761083584611131565b156108f3577f92ca3a80853e6663fa31fa10b99225f18d4902939b4c53a9caae9043f6efd00433858786866040518086600160a060020a0316815260200185815260200184600160a060020a0316815260200180602001828103825284848281815260200192508082843760405192018290039850909650505050505050a184600160a060020a03168484846040518083838082843782019150509250505060006040518083038185876185025a03f15060009350610aa892505050565b6000364360405180848480828437820191505082815260200193505050506040518091039020905061092481610577565b158015610947575060008181526101086020526040902054600160a060020a0316155b15610aa857600081815261010860209081526040822080546c01000000000000000000000000808a0204600160a060020a0319909116178155600180820188905560029182018054818652948490209094601f928116156101000260001901169290920481019290920481019185919087908390106109d15782800160ff198235161785556109fe565b828001600101855582156109fe579182015b828111156109fe5782358255916020019190600101906109e3565b5b50610a1f9291505b808211156107cd57600081556001016107b9565b5090565b50507f1733cbb53659d713b79580f79f3f9ff215f78a7c7aa45890f3b89fc5cddfbf32813386888787604051808760001916815260200186600160a060020a0316815260200185815260200184600160a060020a031681526020018060200182810382528484828181526020019250808284376040519201829003995090975050505050505050a15b5b5b5b949350505050565b600160a060020a033316600090815261010260205260408120549080821515610adb57610b57565b50506000828152610103602052604081206001810154600284900a929083161115610b575780546001908101825581018054839003905560408051600160a060020a03331681526020810186905281517fc7fb647e59b18047309aa15aad418e5d7ca96d173ad704f1031a2c3d7591734b929181900390910190a15b5b50505050565b600036604051808383808284378201915050925050506040518091039020610b8581610d93565b1561056b57600154821115610b995761056b565b6000829055610ba6610f32565b6040805183815290517facbdb084c721332ac59f9b8e392196c9eb0e4932862da8eb9beaf0dad4f550da9181900360200190a15b5b5b5050565b600082815261010360209081526040808320600160a060020a038516845261010290925282205482811515610c185760009350610c2c565b8160020a9050808360010154166000141593505b50505092915050565b6000600282600101610100811015610000570160005b505490505b919050565b600036604051808383808284378201915050925050506040518091039020610c7c81610d93565b1561056b5781600160a060020a0316ff5b5b5b5050565b6000600036604051808383808284378201915050925050506040518091039020610cbc81610d93565b15610b5757610cca83610415565b15610cd457610b57565b600160a060020a038416600090815261010260205260409020549150811515610cfc57610b57565b610d04610f32565b82600160a060020a0316600283610100811015610000570160005b5055600160a060020a0380851660008181526101026020908152604080832083905593871680835291849020869055835192835282015281517fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c929181900390910190a15b5b5b50505050565b6101055481565b600160a060020a033316600090815261010260205260408120548180821515610dbb57610f28565b60008581526101036020526040902080549092501515610e4f576000805483556001808401919091556101048054918201808255828015829011610e2457600083815260209020610e249181019083015b808211156107cd57600081556001016107b9565b5090565b5b50505060028301819055610104805487929081101561000057906000526020600020900160005b50555b8260020a90508082600101541660001415610f285760408051600160a060020a03331681526020810187905281517fe1c52dc63b719ade82e8bea94cc41a0d5d28e4aaf536adb5e9cccc9ff8c1aeda929181900390910190a1815460019011610f155760008581526101036020526040902060020154610104805490919081101561000057906000526020600020900160005b506000908190558581526101036020526040812081815560018082018390556002909101919091559350610f2856610f28565b8154600019018255600182018054821790555b5b5b505050919050565b6101045460005b81811015610ff557610108600061010483815481101561000057906000526020600020900160005b50548152602081019190915260400160009081208054600160a060020a0319168155600180820183905560028083018054858255939493909281161561010002600019011604601f819010610fb65750610fe8565b601f016020900490600052602060002090810190610fe891905b808211156107cd57600081556001016107b9565b5090565b5b5050505b600101610f39565b61056b6111a4565b5b5050565b60015b600154811015610476575b600154811080156110325750600281610100811015610000570160005b505415155b1561103f57600101611010565b5b600160015411801561106457506002600154610100811015610000570160005b5054155b156110785760018054600019019055611040565b6001548110801561109c57506002600154610100811015610000570160005b505415155b80156110b85750600281610100811015610000570160005b5054155b15611128576002600154610100811015610000570160005b5054600282610100811015610000570160005b5055806101026000600283610100811015610000570160005b505481526020019081526020016000208190555060006002600154610100811015610000570160005b50555b611005565b5b50565b600061113c33610415565b15610431576101075461114d611278565b111561116657600061010655611161611278565b610107555b610106548281011080159061118357506101055482610106540111155b1561119957506101068054820190556001610431565b5060005b5b5b919050565b6101045460005b818110156112215761010481815481101561000057906000526020600020900160005b50541561121857610103600061010483815481101561000057906000526020600020900160005b505481526020810191909152604001600090812081815560018101829055600201555b5b6001016111ab565b610104805460008083559190915261040e907f4c0be60200faa20559308cb7b5a1bb3255c16cb1cab91f525b5ae7a03d02fabe908101905b808211156107cd57600081556001016107b9565b5090565b5b505b5050565b6201518042045b9056'; +export const wallet = '0x6060604052346100005760405161041b38038061041b83398101604090815281516020830151918301519201915b604080517f696e697457616c6c657428616464726573735b5d2c75696e743235362c75696e81527f7432353629000000000000000000000000000000000000000000000000000000602080830191909152915190819003602501902084516000829052909173__WalletLibrary_________________________91600281019160049182010290819038829003903960006000600483016000866127105a03f45b505050505050505b610337806100e46000396000f36060604052361561006c5760e060020a60003504632f54bf6e81146101245780634123cb6b146101485780635237509314610167578063659010e714610186578063746c9171146101a5578063c2cf7326146101c4578063c41a360a146101eb578063f1736d8614610217575b6101225b60003411156100c15760408051600160a060020a033316815234602082015281517fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c929181900390910190a161011e565b600036111561011e5773__WalletLibrary_________________________600160a060020a0316600036600060405160200152604051808383808284378201915050925050506020604051808303818560325a03f4156100005750505b5b5b565b005b3461000057610134600435610236565b604080519115158252519081900360200190f35b3461000057610155610297565b60408051918252519081900360200190f35b346100005761015561029d565b60408051918252519081900360200190f35b34610000576101556102a3565b60408051918252519081900360200190f35b34610000576101556102a9565b60408051918252519081900360200190f35b34610000576101346004356024356102af565b604080519115158252519081900360200190f35b34610000576101fb600435610311565b60408051600160a060020a039092168252519081900360200190f35b3461000057610155610331565b60408051918252519081900360200190f35b600073__WalletLibrary_________________________600160a060020a0316600036600060405160200152604051808383808284378201915050925050506020604051808303818560325a03f4156100005750506040515190505b919050565b60015481565b60045481565b60035481565b60005481565b600073__WalletLibrary_________________________600160a060020a0316600036600060405160200152604051808383808284378201915050925050506020604051808303818560325a03f4156100005750506040515190505b92915050565b6000600582600101610100811015610000570160005b505490505b919050565b6002548156'; +export const walletLibrary = '0x606060405234610000575b611381806100186000396000f3606060405236156100da5760e060020a6000350463173825d981146100df5780632f54bf6e146100f157806352375093146101155780635c52c2f514610134578063659010e7146101435780637065cb4814610162578063797af627146101745780639da5e0eb14610198578063b20d30a9146101aa578063b61d27f6146101bc578063b75c7dc614610227578063ba51a6df14610239578063c2cf73261461024b578063c57c5f6014610272578063cbf0b0c0146102c6578063e46dcfeb146102d8578063f00d4b5d14610331578063f1736d8614610346575b610000565b34610000576100ef600435610365565b005b3461000057610101600435610452565b604080519115158252519081900360200190f35b3461000057610122610473565b60408051918252519081900360200190f35b34610000576100ef610479565b005b34610000576101226104b0565b60408051918252519081900360200190f35b34610000576100ef6004356104b6565b005b34610000576101016004356105a5565b604080519115158252519081900360200190f35b34610000576100ef60043561081e565b005b34610000576100ef600435610832565b005b3461000057604080516020600460443581810135601f810184900484028501840190955284845261010194823594602480359560649492939190920191819084018382808284375094965061086a95505050505050565b604080519115158252519081900360200190f35b34610000576100ef600435610bcc565b005b34610000576100ef600435610c77565b005b3461000057610101600435602435610cf9565b604080519115158252519081900360200190f35b34610000576100ef6004808035906020019082018035906020019080806020026020016040519081016040528093929190818152602001838360200280828437509496505093359350610d4e92505050565b005b34610000576100ef600435610e13565b005b34610000576100ef60048080359060200190820180359060200190808060200260200160405190810160405280939291908181526020018383602002808284375094965050843594602001359350610e5192505050565b005b34610000576100ef600435602435610e6a565b005b3461000057610122610f63565b60408051918252519081900360200190f35b600060003660405180838380828437820191505092505050604051809103902061038e81610f69565b1561044b57600160a060020a0383166000908152610105602052604090205491508115156103bb5761044b565b60016001540360005411156103cf5761044b565b6000600583610100811015610000570160005b5055600160a060020a03831660009081526101056020526040812055610406611108565b61040e6111dc565b60408051600160a060020a038516815290517f58619076adf5bb0943d100ef88d52d7c3fd691b19d3a9071b555b651fbf418da9181900360200190a15b5b5b505050565b600160a060020a03811660009081526101056020526040812054115b919050565b60045481565b6000366040518083838082843782019150509250505060405180910390206104a081610f69565b156104ab5760006003555b5b5b50565b60035481565b6000366040518083838082843782019150509250505060405180910390206104dd81610f69565b1561059f576104eb82610452565b156104f55761059f565b6104fd611108565b60015460fa9010610510576105106111dc565b5b60015460fa90106105215761059f565b60018054810190819055600160a060020a03831690600590610100811015610000570160005b5055600154600160a060020a03831660008181526101056020908152604091829020939093558051918252517f994a936646fe87ffe4f1e469d3d6aa417d6b855598397f323de5b449f765f0c3929181900390910190a15b5b5b5050565b6000816105b181610f69565b156108155760008381526101086020526040902054600160a060020a0316156108155760008381526101086020526040908190208054600180830154935160029384018054600160a060020a0390941695949093919283928592918116156101000260001901160480156106665780601f1061063b57610100808354040283529160200191610666565b820191906000526020600020905b81548152906001019060200180831161064957829003601f168201915b505091505060006040518083038185876185025a03f15050506000848152610108602090815260409182902060018082015482548551600160a060020a033381811683529682018c905296810183905295166060860181905260a06080870181815260029586018054958616156101000260001901909516959095049087018190527fe7c957c06e9a662c1a6c77366179f5b702b97651dc28eee7d5bf1dff6e40bb4a975094958a95929491939290919060c08301908490801561076b5780601f106107405761010080835404028352916020019161076b565b820191906000526020600020905b81548152906001019060200180831161074e57829003601f168201915b5050965050505050505060405180910390a1600083815261010860205260408120805473ffffffffffffffffffffffffffffffffffffffff19168155600180820183905560028083018054858255939493909281161561010002600019011604601f8190106107da575061080c565b601f01602090049060005260206000209081019061080c91905b8082111561080857600081556001016107f4565b5090565b5b505050600191505b5b5b5b50919050565b600281905561082b61130b565b6004555b50565b60003660405180838380828437820191505092505050604051809103902061085981610f69565b1561059f5760028290555b5b5b5050565b6000600061087733610452565b15610bc05761088584611315565b156109bc577f92ca3a80853e6663fa31fa10b99225f18d4902939b4c53a9caae9043f6efd004338587866040518085600160a060020a0316815260200184815260200183600160a060020a03168152602001806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156109335780820380516001836020036101000a031916815260200191505b509550505050505060405180910390a184600160a060020a03168484604051808280519060200190808383829060006004602084601f0104600302600f01f150905090810190601f16801561099c5780820380516001836020036101000a031916815260200191505b5091505060006040518083038185876185025a03f1925050509150610bc0565b600036436040518084848082843782019150508281526020019350505050604051809103902090506109ed816105a5565b158015610a10575060008181526101086020526040902054600160a060020a0316155b15610bc057600081815261010860209081526040822080546c01000000000000000000000000808a020473ffffffffffffffffffffffffffffffffffffffff199091161781556001808201889055865160029283018054818752958590209095601f9381161561010002600019011693909304820184900483019390929190880190839010610aaa57805160ff1916838001178555610ad7565b82800160010185558215610ad7579182015b82811115610ad7578251825591602001919060010190610abc565b5b50610af89291505b8082111561080857600081556001016107f4565b5090565b50507f1733cbb53659d713b79580f79f3f9ff215f78a7c7aa45890f3b89fc5cddfbf328133868887604051808660001916815260200185600160a060020a0316815260200184815260200183600160a060020a03168152602001806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f168015610bae5780820380516001836020036101000a031916815260200191505b50965050505050505060405180910390a15b5b5b5b5b509392505050565b600160a060020a033316600090815261010560205260408120549080821515610bf457610c70565b50506000828152610106602052604081206001810154600284900a929083161115610c705780546001908101825581018054839003905560408051600160a060020a03331681526020810186905281517fc7fb647e59b18047309aa15aad418e5d7ca96d173ad704f1031a2c3d7591734b929181900390910190a15b5b50505050565b600036604051808383808284378201915050925050506040518091039020610c9e81610f69565b1561059f57600154821115610cb25761059f565b6000829055610cbf611108565b6040805183815290517facbdb084c721332ac59f9b8e392196c9eb0e4932862da8eb9beaf0dad4f550da9181900360200190a15b5b5b5050565b600082815261010660209081526040808320600160a060020a038516845261010590925282205482811515610d315760009350610d45565b8160020a9050808360010154166000141593505b50505092915050565b815160019081019055600033600160a060020a03166006825b505550600160a060020a033316600090815261010560205260408120600190558181555b825181101561044b57828181518110156100005790602001906020020151600160a060020a0316600582600201610100811015610000570160005b5081905550806002016101056000858481518110156100005790602001906020020151600160a060020a03168152602001908152602001600020819055505b600101610d8b565b5b505050565b600036604051808383808284378201915050925050506040518091039020610e3a81610f69565b1561059f5781600160a060020a0316ff5b5b5b5050565b610e5b8383610d4e565b61044b8161081e565b5b505050565b6000600036604051808383808284378201915050925050506040518091039020610e9381610f69565b15610c7057610ea183610452565b15610eab57610c70565b600160a060020a038416600090815261010560205260409020549150811515610ed357610c70565b610edb611108565b82600160a060020a0316600583610100811015610000570160005b5055600160a060020a0380851660008181526101056020908152604080832083905593871680835291849020869055835192835282015281517fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c929181900390910190a15b5b5b50505050565b60025481565b600160a060020a033316600090815261010560205260408120548180821515610f91576110fe565b60008581526101066020526040902080549092501515611025576000805483556001808401919091556101078054918201808255828015829011610ffa57600083815260209020610ffa9181019083015b8082111561080857600081556001016107f4565b5090565b5b50505060028301819055610107805487929081101561000057906000526020600020900160005b50555b8260020a905080826001015416600014156110fe5760408051600160a060020a03331681526020810187905281517fe1c52dc63b719ade82e8bea94cc41a0d5d28e4aaf536adb5e9cccc9ff8c1aeda929181900390910190a18154600190116110eb5760008581526101066020526040902060020154610107805490919081101561000057906000526020600020900160005b5060009081905585815261010660205260408120818155600180820183905560029091019190915593506110fe566110fe565b8154600019018255600182018054821790555b5b5b505050919050565b6101075460005b818110156111855761010781815481101561000057906000526020600020900160005b50541561117c57610106600061010783815481101561000057906000526020600020900160005b505481526020810191909152604001600090812081815560018101829055600201555b5b60010161110f565b610107805460008083559190915261044b907f47c4908e245f386bfc1825973249847f4053a761ddb4880ad63c323a7b5a2a25908101905b8082111561080857600081556001016107f4565b5090565b5b505b5050565b60015b6001548110156104ab575b6001548110801561120c5750600581610100811015610000570160005b505415155b15611219576001016111ea565b5b600160015411801561123e57506005600154610100811015610000570160005b5054155b15611252576001805460001901905561121a565b6001548110801561127657506005600154610100811015610000570160005b505415155b80156112925750600581610100811015610000570160005b5054155b15611302576005600154610100811015610000570160005b5054600582610100811015610000570160005b5055806101056000600583610100811015610000570160005b505481526020019081526020016000208190555060006005600154610100811015610000570160005b50555b6111df565b5b50565b6201518042045b90565b600061132033610452565b1561046e5760045461133061130b565b111561134757600060035561134361130b565b6004555b600354828101108015906113615750600254826003540111155b1561137657506003805482019055600161046e565b5060005b5b5b91905056'; +export const walletSourceURL = 'https://github.com/ethcore/parity/blob/63137b15482344ff9df634c086abaabed452eadc/js/src/contracts/snippets/enhanced-wallet.sol'; +export const walletLibraryRegKey = 'walletLibrary'; +// Used if no Wallet Library found in registry... +// Compiled from `wallet.sol` using Solidity v0.4.6 +export const fullWalletCode = '0x606060405234610000576040516113bb3803806113bb83398101604090815281516020830151918301519201915b805b83835b815160019081019055600033600160a060020a03166003825b505550600160a060020a033316600090815261010260205260408120600190555b82518110156100ee57828181518110156100005790602001906020020151600160a060020a0316600282600201610100811015610000570160005b5081905550806002016101026000858481518110156100005790602001906020020151600160a060020a03168152602001908152602001600020819055505b60010161006c565b60008290555b50505061010581905561011264010000000061127861012182021704565b610107555b505b50505061012b565b6201518042045b90565b611282806101396000396000f3606060405236156100da5760e060020a6000350463173825d981146101305780632f54bf6e146101425780634123cb6b1461016657806352375093146101855780635c52c2f5146101a4578063659010e7146101b35780637065cb48146101d2578063746c9171146101e4578063797af62714610203578063b20d30a914610227578063b61d27f614610239578063b75c7dc61461026b578063ba51a6df1461027d578063c2cf73261461028f578063c41a360a146102b6578063cbf0b0c0146102e2578063f00d4b5d146102f4578063f1736d8614610309575b61012e5b600034111561012b5760408051600160a060020a033316815234602082015281517fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c929181900390910190a15b5b565b005b346100005761012e600435610328565b005b3461000057610152600435610415565b604080519115158252519081900360200190f35b3461000057610173610436565b60408051918252519081900360200190f35b346100005761017361043c565b60408051918252519081900360200190f35b346100005761012e610443565b005b346100005761017361047b565b60408051918252519081900360200190f35b346100005761012e600435610482565b005b3461000057610173610571565b60408051918252519081900360200190f35b3461000057610152600435610577565b604080519115158252519081900360200190f35b346100005761012e6004356107e3565b005b34610000576101736004803590602480359160443591820191013561081c565b60408051918252519081900360200190f35b346100005761012e600435610ab3565b005b346100005761012e600435610b5e565b005b3461000057610152600435602435610be0565b604080519115158252519081900360200190f35b34610000576102c6600435610c35565b60408051600160a060020a039092168252519081900360200190f35b346100005761012e600435610c55565b005b346100005761012e600435602435610c93565b005b3461000057610173610d8c565b60408051918252519081900360200190f35b600060003660405180838380828437820191505092505050604051809103902061035181610d93565b1561040e57600160a060020a03831660009081526101026020526040902054915081151561037e5761040e565b60016001540360005411156103925761040e565b6000600283610100811015610000570160005b5055600160a060020a038316600090815261010260205260408120556103c9610f32565b6103d1611002565b60408051600160a060020a038516815290517f58619076adf5bb0943d100ef88d52d7c3fd691b19d3a9071b555b651fbf418da9181900360200190a15b5b5b505050565b600160a060020a03811660009081526101026020526040812054115b919050565b60015481565b6101075481565b60003660405180838380828437820191505092505050604051809103902061046a81610d93565b15610476576000610106555b5b5b50565b6101065481565b6000366040518083838082843782019150509250505060405180910390206104a981610d93565b1561056b576104b782610415565b156104c15761056b565b6104c9610f32565b60015460fa90106104dc576104dc611002565b5b60015460fa90106104ed5761056b565b60018054810190819055600160a060020a03831690600290610100811015610000570160005b5055600154600160a060020a03831660008181526101026020908152604091829020939093558051918252517f994a936646fe87ffe4f1e469d3d6aa417d6b855598397f323de5b449f765f0c3929181900390910190a15b5b5b5050565b60005481565b60008161058381610d93565b156107da5760008381526101086020526040902054600160a060020a0316156107da5760008381526101086020526040908190208054600180830154935160029384018054600160a060020a0390941695949093919283928592918116156101000260001901160480156106385780601f1061060d57610100808354040283529160200191610638565b820191906000526020600020905b81548152906001019060200180831161061b57829003601f168201915b505091505060006040518083038185876185025a03f15050506000848152610108602090815260409182902060018082015482548551600160a060020a033381811683529682018c905296810183905295166060860181905260a06080870181815260029586018054958616156101000260001901909516959095049087018190527fe7c957c06e9a662c1a6c77366179f5b702b97651dc28eee7d5bf1dff6e40bb4a975094958a95929491939290919060c08301908490801561073d5780601f106107125761010080835404028352916020019161073d565b820191906000526020600020905b81548152906001019060200180831161072057829003601f168201915b5050965050505050505060405180910390a16000838152610108602052604081208054600160a060020a0319168155600180820183905560028083018054858255939493909281161561010002600019011604601f81901061079f57506107d1565b601f0160209004906000526020600020908101906107d191905b808211156107cd57600081556001016107b9565b5090565b5b505050600191505b5b5b5b50919050565b60003660405180838380828437820191505092505050604051809103902061080a81610d93565b1561056b576101058290555b5b5b5050565b600061082733610415565b15610aa85761083584611131565b156108f3577f92ca3a80853e6663fa31fa10b99225f18d4902939b4c53a9caae9043f6efd00433858786866040518086600160a060020a0316815260200185815260200184600160a060020a0316815260200180602001828103825284848281815260200192508082843760405192018290039850909650505050505050a184600160a060020a03168484846040518083838082843782019150509250505060006040518083038185876185025a03f15060009350610aa892505050565b6000364360405180848480828437820191505082815260200193505050506040518091039020905061092481610577565b158015610947575060008181526101086020526040902054600160a060020a0316155b15610aa857600081815261010860209081526040822080546c01000000000000000000000000808a0204600160a060020a0319909116178155600180820188905560029182018054818652948490209094601f928116156101000260001901169290920481019290920481019185919087908390106109d15782800160ff198235161785556109fe565b828001600101855582156109fe579182015b828111156109fe5782358255916020019190600101906109e3565b5b50610a1f9291505b808211156107cd57600081556001016107b9565b5090565b50507f1733cbb53659d713b79580f79f3f9ff215f78a7c7aa45890f3b89fc5cddfbf32813386888787604051808760001916815260200186600160a060020a0316815260200185815260200184600160a060020a031681526020018060200182810382528484828181526020019250808284376040519201829003995090975050505050505050a15b5b5b5b949350505050565b600160a060020a033316600090815261010260205260408120549080821515610adb57610b57565b50506000828152610103602052604081206001810154600284900a929083161115610b575780546001908101825581018054839003905560408051600160a060020a03331681526020810186905281517fc7fb647e59b18047309aa15aad418e5d7ca96d173ad704f1031a2c3d7591734b929181900390910190a15b5b50505050565b600036604051808383808284378201915050925050506040518091039020610b8581610d93565b1561056b57600154821115610b995761056b565b6000829055610ba6610f32565b6040805183815290517facbdb084c721332ac59f9b8e392196c9eb0e4932862da8eb9beaf0dad4f550da9181900360200190a15b5b5b5050565b600082815261010360209081526040808320600160a060020a038516845261010290925282205482811515610c185760009350610c2c565b8160020a9050808360010154166000141593505b50505092915050565b6000600282600101610100811015610000570160005b505490505b919050565b600036604051808383808284378201915050925050506040518091039020610c7c81610d93565b1561056b5781600160a060020a0316ff5b5b5b5050565b6000600036604051808383808284378201915050925050506040518091039020610cbc81610d93565b15610b5757610cca83610415565b15610cd457610b57565b600160a060020a038416600090815261010260205260409020549150811515610cfc57610b57565b610d04610f32565b82600160a060020a0316600283610100811015610000570160005b5055600160a060020a0380851660008181526101026020908152604080832083905593871680835291849020869055835192835282015281517fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c929181900390910190a15b5b5b50505050565b6101055481565b600160a060020a033316600090815261010260205260408120548180821515610dbb57610f28565b60008581526101036020526040902080549092501515610e4f576000805483556001808401919091556101048054918201808255828015829011610e2457600083815260209020610e249181019083015b808211156107cd57600081556001016107b9565b5090565b5b50505060028301819055610104805487929081101561000057906000526020600020900160005b50555b8260020a90508082600101541660001415610f285760408051600160a060020a03331681526020810187905281517fe1c52dc63b719ade82e8bea94cc41a0d5d28e4aaf536adb5e9cccc9ff8c1aeda929181900390910190a1815460019011610f155760008581526101036020526040902060020154610104805490919081101561000057906000526020600020900160005b506000908190558581526101036020526040812081815560018082018390556002909101919091559350610f2856610f28565b8154600019018255600182018054821790555b5b5b505050919050565b6101045460005b81811015610ff557610108600061010483815481101561000057906000526020600020900160005b50548152602081019190915260400160009081208054600160a060020a0319168155600180820183905560028083018054858255939493909281161561010002600019011604601f819010610fb65750610fe8565b601f016020900490600052602060002090810190610fe891905b808211156107cd57600081556001016107b9565b5090565b5b5050505b600101610f39565b61056b6111a4565b5b5050565b60015b600154811015610476575b600154811080156110325750600281610100811015610000570160005b505415155b1561103f57600101611010565b5b600160015411801561106457506002600154610100811015610000570160005b5054155b156110785760018054600019019055611040565b6001548110801561109c57506002600154610100811015610000570160005b505415155b80156110b85750600281610100811015610000570160005b5054155b15611128576002600154610100811015610000570160005b5054600282610100811015610000570160005b5055806101026000600283610100811015610000570160005b505481526020019081526020016000208190555060006002600154610100811015610000570160005b50555b611005565b5b50565b600061113c33610415565b15610431576101075461114d611278565b111561116657600061010655611161611278565b610107555b610106548281011080159061118357506101055482610106540111155b1561119957506101068054820190556001610431565b5060005b5b5b919050565b6101045460005b818110156112215761010481815481101561000057906000526020600020900160005b50541561121857610103600061010483815481101561000057906000526020600020900160005b505481526020810191909152604001600090812081815560018101829055600201555b5b6001016111ab565b610104805460008083559190915261040e907f4c0be60200faa20559308cb7b5a1bb3255c16cb1cab91f525b5ae7a03d02fabe908101905b808211156107cd57600081556001016107b9565b5090565b5b505b5050565b6201518042045b9056'; diff --git a/js/src/contracts/registry.js b/js/src/contracts/registry.js index 2f61f7f4a..9354a59e5 100644 --- a/js/src/contracts/registry.js +++ b/js/src/contracts/registry.js @@ -19,7 +19,10 @@ import * as abis from './abi'; export default class Registry { constructor (api) { this._api = api; - this._contracts = []; + + this._contracts = {}; + this._pendingContracts = {}; + this._instance = null; this._fetching = false; this._queue = []; @@ -59,20 +62,25 @@ export default class Registry { getContract (_name) { const name = _name.toLowerCase(); - return new Promise((resolve, reject) => { - if (this._contracts[name]) { - resolve(this._contracts[name]); - return; - } + if (this._contracts[name]) { + return Promise.resolve(this._contracts[name]); + } - this - .lookupAddress(name) - .then((address) => { - this._contracts[name] = this._api.newContract(abis[name], address); - resolve(this._contracts[name]); - }) - .catch(reject); - }); + if (this._pendingContracts[name]) { + return this._pendingContracts[name]; + } + + const promise = this + .lookupAddress(name) + .then((address) => { + this._contracts[name] = this._api.newContract(abis[name], address); + delete this._pendingContracts[name]; + return this._contracts[name]; + }); + + this._pendingContracts[name] = promise; + + return promise; } getContractInstance (_name) { @@ -89,7 +97,7 @@ export default class Registry { return instance.getAddress.call({}, [sha3, 'A']); }) .then((address) => { - console.log('lookupAddress', name, sha3, address); + console.log('[lookupAddress]', `(${sha3}) ${name}: ${address}`); return address; }); } diff --git a/js/src/contracts/snippets/enhanced-wallet.sol b/js/src/contracts/snippets/enhanced-wallet.sol new file mode 100644 index 000000000..374eb595f --- /dev/null +++ b/js/src/contracts/snippets/enhanced-wallet.sol @@ -0,0 +1,460 @@ +//sol Wallet +// Multi-sig, daily-limited account proxy/wallet. +// @authors: +// Gav Wood +// inheritable "property" contract that enables methods to be protected by requiring the acquiescence of either a +// single, or, crucially, each of a number of, designated owners. +// usage: +// use modifiers onlyowner (just own owned) or onlymanyowners(hash), whereby the same hash must be provided by +// some number (specified in constructor) of the set of owners (specified in the constructor, modifiable) before the +// interior is executed. +pragma solidity ^0.4.6; + +contract multisig { + // EVENTS + + // this contract can accept a confirmation, in which case + // we record owner and operation (hash) alongside it. + event Confirmation(address owner, bytes32 operation); + event Revoke(address owner, bytes32 operation); + + // some others are in the case of an owner changing. + event OwnerChanged(address oldOwner, address newOwner); + event OwnerAdded(address newOwner); + event OwnerRemoved(address oldOwner); + + // the last one is emitted if the required signatures change + event RequirementChanged(uint newRequirement); + + // Funds has arrived into the wallet (record how much). + event Deposit(address _from, uint value); + // Single transaction going out of the wallet (record who signed for it, how much, and to whom it's going). + event SingleTransact(address owner, uint value, address to, bytes data); + // Multi-sig transaction going out of the wallet (record who signed for it last, the operation hash, how much, and to whom it's going). + event MultiTransact(address owner, bytes32 operation, uint value, address to, bytes data); + // Confirmation still needed for a transaction. + event ConfirmationNeeded(bytes32 operation, address initiator, uint value, address to, bytes data); +} + +contract multisigAbi is multisig { + function isOwner(address _addr) returns (bool); + + function hasConfirmed(bytes32 _operation, address _owner) constant returns (bool); + + function confirm(bytes32 _h) returns(bool); + + // (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today. + function setDailyLimit(uint _newLimit); + + function addOwner(address _owner); + + function removeOwner(address _owner); + + function changeRequirement(uint _newRequired); + + // Revokes a prior confirmation of the given operation + function revoke(bytes32 _operation); + + function changeOwner(address _from, address _to); + + function execute(address _to, uint _value, bytes _data) returns(bool); +} + +contract WalletLibrary is multisig { + // TYPES + + // struct for the status of a pending operation. + struct PendingState { + uint yetNeeded; + uint ownersDone; + uint index; + } + + // Transaction structure to remember details of transaction lest it need be saved for a later call. + struct Transaction { + address to; + uint value; + bytes data; + } + + /****************************** + ***** MULTI OWNED SECTION **** + ******************************/ + + // MODIFIERS + + // simple single-sig function modifier. + modifier onlyowner { + if (isOwner(msg.sender)) + _; + } + // multi-sig function modifier: the operation must have an intrinsic hash in order + // that later attempts can be realised as the same underlying operation and + // thus count as confirmations. + modifier onlymanyowners(bytes32 _operation) { + if (confirmAndCheck(_operation)) + _; + } + + // METHODS + + // constructor is given number of sigs required to do protected "onlymanyowners" transactions + // as well as the selection of addresses capable of confirming them. + function initMultiowned(address[] _owners, uint _required) { + m_numOwners = _owners.length + 1; + m_owners[1] = uint(msg.sender); + m_ownerIndex[uint(msg.sender)] = 1; + m_required = _required; + + for (uint i = 0; i < _owners.length; ++i) + { + m_owners[2 + i] = uint(_owners[i]); + m_ownerIndex[uint(_owners[i])] = 2 + i; + } + } + + // Revokes a prior confirmation of the given operation + function revoke(bytes32 _operation) { + uint ownerIndex = m_ownerIndex[uint(msg.sender)]; + // make sure they're an owner + if (ownerIndex == 0) return; + uint ownerIndexBit = 2**ownerIndex; + var pending = m_pending[_operation]; + if (pending.ownersDone & ownerIndexBit > 0) { + pending.yetNeeded++; + pending.ownersDone -= ownerIndexBit; + Revoke(msg.sender, _operation); + } + } + + // Replaces an owner `_from` with another `_to`. + function changeOwner(address _from, address _to) onlymanyowners(sha3(msg.data)) { + if (isOwner(_to)) return; + uint ownerIndex = m_ownerIndex[uint(_from)]; + if (ownerIndex == 0) return; + + clearPending(); + m_owners[ownerIndex] = uint(_to); + m_ownerIndex[uint(_from)] = 0; + m_ownerIndex[uint(_to)] = ownerIndex; + OwnerChanged(_from, _to); + } + + function addOwner(address _owner) onlymanyowners(sha3(msg.data)) { + if (isOwner(_owner)) return; + + clearPending(); + if (m_numOwners >= c_maxOwners) + reorganizeOwners(); + if (m_numOwners >= c_maxOwners) + return; + m_numOwners++; + m_owners[m_numOwners] = uint(_owner); + m_ownerIndex[uint(_owner)] = m_numOwners; + OwnerAdded(_owner); + } + + function removeOwner(address _owner) onlymanyowners(sha3(msg.data)) { + uint ownerIndex = m_ownerIndex[uint(_owner)]; + if (ownerIndex == 0) return; + if (m_required > m_numOwners - 1) return; + + m_owners[ownerIndex] = 0; + m_ownerIndex[uint(_owner)] = 0; + clearPending(); + reorganizeOwners(); //make sure m_numOwner is equal to the number of owners and always points to the optimal free slot + OwnerRemoved(_owner); + } + + function changeRequirement(uint _newRequired) onlymanyowners(sha3(msg.data)) { + if (_newRequired > m_numOwners) return; + m_required = _newRequired; + clearPending(); + RequirementChanged(_newRequired); + } + + function isOwner(address _addr) returns (bool) { + return m_ownerIndex[uint(_addr)] > 0; + } + + + function hasConfirmed(bytes32 _operation, address _owner) constant returns (bool) { + var pending = m_pending[_operation]; + uint ownerIndex = m_ownerIndex[uint(_owner)]; + + // make sure they're an owner + if (ownerIndex == 0) return false; + + // determine the bit to set for this owner. + uint ownerIndexBit = 2**ownerIndex; + return !(pending.ownersDone & ownerIndexBit == 0); + } + + // INTERNAL METHODS + + function confirmAndCheck(bytes32 _operation) internal returns (bool) { + // determine what index the present sender is: + uint ownerIndex = m_ownerIndex[uint(msg.sender)]; + // make sure they're an owner + if (ownerIndex == 0) return; + + var pending = m_pending[_operation]; + // if we're not yet working on this operation, switch over and reset the confirmation status. + if (pending.yetNeeded == 0) { + // reset count of confirmations needed. + pending.yetNeeded = m_required; + // reset which owners have confirmed (none) - set our bitmap to 0. + pending.ownersDone = 0; + pending.index = m_pendingIndex.length++; + m_pendingIndex[pending.index] = _operation; + } + // determine the bit to set for this owner. + uint ownerIndexBit = 2**ownerIndex; + // make sure we (the message sender) haven't confirmed this operation previously. + if (pending.ownersDone & ownerIndexBit == 0) { + Confirmation(msg.sender, _operation); + // ok - check if count is enough to go ahead. + if (pending.yetNeeded <= 1) { + // enough confirmations: reset and run interior. + delete m_pendingIndex[m_pending[_operation].index]; + delete m_pending[_operation]; + return true; + } + else + { + // not enough: record that this owner in particular confirmed. + pending.yetNeeded--; + pending.ownersDone |= ownerIndexBit; + } + } + } + + function reorganizeOwners() private { + uint free = 1; + while (free < m_numOwners) + { + while (free < m_numOwners && m_owners[free] != 0) free++; + while (m_numOwners > 1 && m_owners[m_numOwners] == 0) m_numOwners--; + if (free < m_numOwners && m_owners[m_numOwners] != 0 && m_owners[free] == 0) + { + m_owners[free] = m_owners[m_numOwners]; + m_ownerIndex[m_owners[free]] = free; + m_owners[m_numOwners] = 0; + } + } + } + + function clearPending() internal { + uint length = m_pendingIndex.length; + for (uint i = 0; i < length; ++i) + if (m_pendingIndex[i] != 0) + delete m_pending[m_pendingIndex[i]]; + delete m_pendingIndex; + } + + + /****************************** + ****** DAY LIMIT SECTION ***** + ******************************/ + + // MODIFIERS + + // simple modifier for daily limit. + modifier limitedDaily(uint _value) { + if (underLimit(_value)) + _; + } + + // METHODS + + // constructor - stores initial daily limit and records the present day's index. + function initDaylimit(uint _limit) { + m_dailyLimit = _limit; + m_lastDay = today(); + } + // (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today. + function setDailyLimit(uint _newLimit) onlymanyowners(sha3(msg.data)) { + m_dailyLimit = _newLimit; + } + // resets the amount already spent today. needs many of the owners to confirm. + function resetSpentToday() onlymanyowners(sha3(msg.data)) { + m_spentToday = 0; + } + + // INTERNAL METHODS + + // checks to see if there is at least `_value` left from the daily limit today. if there is, subtracts it and + // returns true. otherwise just returns false. + function underLimit(uint _value) internal onlyowner returns (bool) { + // reset the spend limit if we're on a different day to last time. + if (today() > m_lastDay) { + m_spentToday = 0; + m_lastDay = today(); + } + // check to see if there's enough left - if so, subtract and return true. + // overflow protection // dailyLimit check + if (m_spentToday + _value >= m_spentToday && m_spentToday + _value <= m_dailyLimit) { + m_spentToday += _value; + return true; + } + return false; + } + + // determines today's index. + function today() private constant returns (uint) { return now / 1 days; } + + + /****************************** + ********* WALLET SECTION ***** + ******************************/ + + // METHODS + + // constructor - just pass on the owner array to the multiowned and + // the limit to daylimit + function initWallet(address[] _owners, uint _required, uint _daylimit) { + initMultiowned(_owners, _required); + initDaylimit(_daylimit) ; + } + + // kills the contract sending everything to `_to`. + function kill(address _to) onlymanyowners(sha3(msg.data)) { + suicide(_to); + } + + // Outside-visible transact entry point. Executes transaction immediately if below daily spend limit. + // If not, goes into multisig process. We provide a hash on return to allow the sender to provide + // shortcuts for the other confirmations (allowing them to avoid replicating the _to, _value + // and _data arguments). They still get the option of using them if they want, anyways. + function execute(address _to, uint _value, bytes _data) onlyowner returns(bool _callValue) { + // first, take the opportunity to check that we're under the daily limit. + if (underLimit(_value)) { + SingleTransact(msg.sender, _value, _to, _data); + // yes - just execute the call. + _callValue =_to.call.value(_value)(_data); + } else { + // determine our operation hash. + bytes32 _r = sha3(msg.data, block.number); + if (!confirm(_r) && m_txs[_r].to == 0) { + m_txs[_r].to = _to; + m_txs[_r].value = _value; + m_txs[_r].data = _data; + ConfirmationNeeded(_r, msg.sender, _value, _to, _data); + } + } + } + + // confirm a transaction through just the hash. we use the previous transactions map, m_txs, in order + // to determine the body of the transaction from the hash provided. + function confirm(bytes32 _h) onlymanyowners(_h) returns (bool) { + if (m_txs[_h].to != 0) { + m_txs[_h].to.call.value(m_txs[_h].value)(m_txs[_h].data); + MultiTransact(msg.sender, _h, m_txs[_h].value, m_txs[_h].to, m_txs[_h].data); + delete m_txs[_h]; + return true; + } + } + + // INTERNAL METHODS + + function clearWalletPending() internal { + uint length = m_pendingIndex.length; + for (uint i = 0; i < length; ++i) + delete m_txs[m_pendingIndex[i]]; + clearPending(); + } + + // FIELDS + address constant _walletLibrary = 0xcafecafecafecafecafecafecafecafecafecafe; + + // the number of owners that must confirm the same operation before it is run. + uint m_required; + // pointer used to find a free slot in m_owners + uint m_numOwners; + + uint public m_dailyLimit; + uint public m_spentToday; + uint public m_lastDay; + + // list of owners + uint[256] m_owners; + uint constant c_maxOwners = 250; + + // index on the list of owners to allow reverse lookup + mapping(uint => uint) m_ownerIndex; + // the ongoing operations. + mapping(bytes32 => PendingState) m_pending; + bytes32[] m_pendingIndex; + + // pending transactions we have at present. + mapping (bytes32 => Transaction) m_txs; +} + + +contract Wallet is multisig { + + // WALLET CONSTRUCTOR + // calls the `initWallet` method of the Library in this context + function Wallet(address[] _owners, uint _required, uint _daylimit) { + // Signature of the Wallet Library's init function + bytes4 sig = bytes4(sha3("initWallet(address[],uint256,uint256)")); + address target = _walletLibrary; + + // Compute the size of the call data : arrays has 2 + // 32bytes for offset and length, plus 32bytes per element ; + // plus 2 32bytes for each uint + uint argarraysize = (2 + _owners.length); + uint argsize = (2 + argarraysize) * 32; + + assembly { + // Add the signature first to memory + mstore(0x0, sig) + // Add the call data, which is at the end of the + // code + codecopy(0x4, sub(codesize, argsize), argsize) + // Delegate call to the library + delegatecall(sub(gas, 10000), target, 0x0, add(argsize, 0x4), 0x0, 0x0) + } + } + + // METHODS + + // gets called when no other function matches + function() payable { + // just being sent some cash? + if (msg.value > 0) + Deposit(msg.sender, msg.value); + else if (msg.data.length > 0) + _walletLibrary.delegatecall(msg.data); + } + + // Gets an owner by 0-indexed position (using numOwners as the count) + function getOwner(uint ownerIndex) constant returns (address) { + return address(m_owners[ownerIndex + 1]); + } + + // As return statement unavailable in fallback, explicit the method here + + function hasConfirmed(bytes32 _operation, address _owner) constant returns (bool) { + return _walletLibrary.delegatecall(msg.data); + } + + function isOwner(address _addr) returns (bool) { + return _walletLibrary.delegatecall(msg.data); + } + + // FIELDS + address constant _walletLibrary = 0xcafecafecafecafecafecafecafecafecafecafe; + + // the number of owners that must confirm the same operation before it is run. + uint public m_required; + // pointer used to find a free slot in m_owners + uint public m_numOwners; + + uint public m_dailyLimit; + uint public m_spentToday; + uint public m_lastDay; + + // list of owners + uint[256] m_owners; +} diff --git a/js/src/dapps/localtx/Application/application.spec.js b/js/src/dapps/localtx/Application/application.spec.js index 2044b4e14..3ffee343d 100644 --- a/js/src/dapps/localtx/Application/application.spec.js +++ b/js/src/dapps/localtx/Application/application.spec.js @@ -21,7 +21,7 @@ import '../../../environment/tests'; import Application from './application'; -describe('localtx/Application', () => { +describe('dapps/localtx/Application', () => { describe('rendering', () => { it('renders without crashing', () => { const rendered = shallow(); diff --git a/js/src/dapps/localtx/Transaction/transaction.spec.js b/js/src/dapps/localtx/Transaction/transaction.spec.js index 5e9c39147..04f2f8de8 100644 --- a/js/src/dapps/localtx/Transaction/transaction.spec.js +++ b/js/src/dapps/localtx/Transaction/transaction.spec.js @@ -29,7 +29,7 @@ Api.api = { import BigNumber from 'bignumber.js'; import { Transaction, LocalTransaction } from './transaction'; -describe('localtx/Transaction', () => { +describe('dapps/localtx/Transaction', () => { describe('rendering', () => { it('renders without crashing', () => { const transaction = { @@ -51,7 +51,7 @@ describe('localtx/Transaction', () => { }); }); -describe('localtx/LocalTransaction', () => { +describe('dapps/localtx/LocalTransaction', () => { describe('rendering', () => { it('renders without crashing', () => { const rendered = shallow( diff --git a/js/src/jsonrpc/rollup.config.js b/js/src/jsonrpc/rollup.config.js deleted file mode 100644 index cc4f8c931..000000000 --- a/js/src/jsonrpc/rollup.config.js +++ /dev/null @@ -1,12 +0,0 @@ -import babel from 'rollup-plugin-babel'; - -export default { - entry: 'src/index.js', - dest: 'release/index.js', - format: 'cjs', - plugins: [babel({ - babelrc: false, - presets: ['es2015-rollup', 'stage-0'], - runtimeHelpers: true - })] -}; diff --git a/js/src/modals/AddAddress/addAddress.js b/js/src/modals/AddAddress/addAddress.js index 590287e73..e44cb0b3c 100644 --- a/js/src/modals/AddAddress/addAddress.js +++ b/js/src/modals/AddAddress/addAddress.js @@ -19,7 +19,7 @@ import ContentAdd from 'material-ui/svg-icons/content/add'; import ContentClear from 'material-ui/svg-icons/content/clear'; import { Button, Modal, Form, Input, InputAddress } from '~/ui'; -import { ERRORS, validateAddress, validateName } from '../../util/validation'; +import { ERRORS, validateAddress, validateName } from '~/util/validation'; export default class AddAddress extends Component { static contextTypes = { diff --git a/js/src/modals/AddContract/addContract.js b/js/src/modals/AddContract/addContract.js index 71f8a911d..b19398001 100644 --- a/js/src/modals/AddContract/addContract.js +++ b/js/src/modals/AddContract/addContract.js @@ -21,7 +21,7 @@ import NavigationArrowForward from 'material-ui/svg-icons/navigation/arrow-forwa import NavigationArrowBack from 'material-ui/svg-icons/navigation/arrow-back'; import { Button, Modal, Form, Input, InputAddress, RadioButtons } from '~/ui'; -import { ERRORS, validateAbi, validateAddress, validateName } from '../../util/validation'; +import { ERRORS, validateAbi, validateAddress, validateName } from '~/util/validation'; import { eip20, wallet } from '~/contracts/abi'; diff --git a/js/src/modals/CreateWallet/WalletType/walletType.js b/js/src/modals/CreateWallet/WalletType/walletType.js index 868c6ad9b..93dd818f1 100644 --- a/js/src/modals/CreateWallet/WalletType/walletType.js +++ b/js/src/modals/CreateWallet/WalletType/walletType.js @@ -17,6 +17,7 @@ import React, { Component, PropTypes } from 'react'; import { RadioButtons } from '~/ui'; +import { walletSourceURL } from '~/contracts/code/wallet'; // import styles from '../createWallet.css'; @@ -46,7 +47,9 @@ export default class WalletType extends Component { description: ( Create/Deploy a - standard multi-signature + + standard multi-signature + Wallet ) diff --git a/js/src/modals/CreateWallet/createWalletStore.js b/js/src/modals/CreateWallet/createWalletStore.js index d8c308a12..b28bfbd26 100644 --- a/js/src/modals/CreateWallet/createWalletStore.js +++ b/js/src/modals/CreateWallet/createWalletStore.js @@ -16,13 +16,14 @@ import { observable, computed, action, transaction } from 'mobx'; -import { validateUint, validateAddress, validateName } from '~/util/validation'; -import { ERROR_CODES } from '~/api/transport/error'; - import Contract from '~/api/contract'; +import Contracts from '~/contracts'; +import { ERROR_CODES } from '~/api/transport/error'; import { wallet as walletAbi } from '~/contracts/abi'; -import { wallet as walletCode } from '~/contracts/code'; +import { wallet as walletCode, walletLibraryRegKey, fullWalletCode } from '~/contracts/code/wallet'; +import { validateUint, validateAddress, validateName } from '~/util/validation'; +import { toWei } from '~/api/util/wei'; import WalletsUtils from '~/util/wallets'; const STEPS = { @@ -47,7 +48,7 @@ export default class CreateWalletStore { address: '', owners: [], required: 1, - daylimit: 0, + daylimit: toWei(1), name: '', description: '' @@ -160,14 +161,25 @@ export default class CreateWalletStore { const { account, owners, required, daylimit } = this.wallet; - const options = { - data: walletCode, - from: account - }; + Contracts + .get() + .registry + .lookupAddress(walletLibraryRegKey) + .then((address) => { + const walletLibraryAddress = (address || '').replace(/^0x/, '').toLowerCase(); + const code = walletLibraryAddress.length && !/^0+$/.test(walletLibraryAddress) + ? walletCode.replace(/(_)+WalletLibrary(_)+/g, walletLibraryAddress) + : fullWalletCode; - this.api - .newContract(walletAbi) - .deploy(options, [ owners, required, daylimit ], this.onDeploymentState) + const options = { + data: code, + from: account + }; + + return this.api + .newContract(walletAbi) + .deploy(options, [ owners, required, daylimit ], this.onDeploymentState); + }) .then((address) => { this.deployed = true; this.wallet.address = address; diff --git a/js/src/modals/DeployContract/DetailsStep/detailsStep.js b/js/src/modals/DeployContract/DetailsStep/detailsStep.js index 54ef8f850..aa0a30e55 100644 --- a/js/src/modals/DeployContract/DetailsStep/detailsStep.js +++ b/js/src/modals/DeployContract/DetailsStep/detailsStep.js @@ -18,8 +18,8 @@ import React, { Component, PropTypes } from 'react'; import { MenuItem } from 'material-ui'; import { AddressSelect, Form, Input, Select } from '~/ui'; -import { validateAbi } from '../../../util/validation'; -import { parseAbiType } from '../../../util/abi'; +import { validateAbi } from '~/util/validation'; +import { parseAbiType } from '~/util/abi'; export default class DetailsStep extends Component { static contextTypes = { diff --git a/js/src/modals/DeployContract/ParametersStep/parametersStep.js b/js/src/modals/DeployContract/ParametersStep/parametersStep.js index 4c7228b67..4ab5df693 100644 --- a/js/src/modals/DeployContract/ParametersStep/parametersStep.js +++ b/js/src/modals/DeployContract/ParametersStep/parametersStep.js @@ -32,7 +32,7 @@ import React, { Component, PropTypes } from 'react'; import { Form, TypedInput } from '~/ui'; -import { parseAbiType } from '../../../util/abi'; +import { parseAbiType } from '~/util/abi'; import styles from '../deployContract.css'; diff --git a/js/src/modals/DeployContract/deployContract.js b/js/src/modals/DeployContract/deployContract.js index ae5578e40..5bf4fc389 100644 --- a/js/src/modals/DeployContract/deployContract.js +++ b/js/src/modals/DeployContract/deployContract.js @@ -19,7 +19,7 @@ import ActionDoneAll from 'material-ui/svg-icons/action/done-all'; import ContentClear from 'material-ui/svg-icons/content/clear'; import { BusyStep, CompletedStep, CopyToClipboard, Button, IdentityIcon, Modal, TxHash } from '~/ui'; -import { ERRORS, validateAbi, validateCode, validateName } from '../../util/validation'; +import { ERRORS, validateAbi, validateCode, validateName } from '~/util/validation'; import DetailsStep from './DetailsStep'; import ParametersStep from './ParametersStep'; diff --git a/js/src/modals/EditMeta/editMeta.js b/js/src/modals/EditMeta/editMeta.js index ad26c19fe..8ab9233f1 100644 --- a/js/src/modals/EditMeta/editMeta.js +++ b/js/src/modals/EditMeta/editMeta.js @@ -19,7 +19,7 @@ import ContentClear from 'material-ui/svg-icons/content/clear'; import ContentSave from 'material-ui/svg-icons/content/save'; import { Button, Form, Input, InputChip, Modal } from '~/ui'; -import { validateName } from '../../util/validation'; +import { validateName } from '~/util/validation'; export default class EditMeta extends Component { static contextTypes = { diff --git a/js/src/modals/ExecuteContract/DetailsStep/detailsStep.js b/js/src/modals/ExecuteContract/DetailsStep/detailsStep.js index 082fac6a7..3ffb929a9 100644 --- a/js/src/modals/ExecuteContract/DetailsStep/detailsStep.js +++ b/js/src/modals/ExecuteContract/DetailsStep/detailsStep.js @@ -15,12 +15,19 @@ // along with Parity. If not, see . import React, { Component, PropTypes } from 'react'; -import { MenuItem } from 'material-ui'; +import { Checkbox, MenuItem } from 'material-ui'; -import { AddressSelect, Form, Input, InputAddressSelect, Select } from '~/ui'; +import { AddressSelect, Form, Input, Select, TypedInput } from '~/ui'; +import { parseAbiType } from '~/util/abi'; import styles from '../executeContract.css'; +const CHECK_STYLE = { + position: 'absolute', + top: '38px', + left: '1em' +}; + export default class DetailsStep extends Component { static propTypes = { accounts: PropTypes.object.isRequired, @@ -30,10 +37,12 @@ export default class DetailsStep extends Component { onAmountChange: PropTypes.func.isRequired, fromAddress: PropTypes.string, fromAddressError: PropTypes.string, + gasEdit: PropTypes.bool, onFromAddressChange: PropTypes.func.isRequired, func: PropTypes.object, funcError: PropTypes.string, onFuncChange: PropTypes.func, + onGasEditClick: PropTypes.func, values: PropTypes.array.isRequired, valuesError: PropTypes.array.isRequired, warning: PropTypes.string, @@ -41,7 +50,7 @@ export default class DetailsStep extends Component { } render () { - const { accounts, amount, amountError, fromAddress, fromAddressError, onFromAddressChange, onAmountChange } = this.props; + const { accounts, amount, amountError, fromAddress, fromAddressError, gasEdit, onGasEditClick, onFromAddressChange, onAmountChange } = this.props; return (
@@ -55,12 +64,23 @@ export default class DetailsStep extends Component { onChange={ onFromAddressChange } /> { this.renderFunctionSelect() } { this.renderParameters() } - +
+
+ +
+
+ +
+
); } @@ -74,7 +94,7 @@ export default class DetailsStep extends Component { const functions = contract.functions .filter((func) => !func.constant) - .sort((a, b) => a.name.localeCompare(b.name)) + .sort((a, b) => (a.name || '').localeCompare(b.name || '')) .map((func) => { const params = (func.abi.inputs || []) .map((input, index) => { @@ -125,56 +145,22 @@ export default class DetailsStep extends Component { } return (func.abi.inputs || []).map((input, index) => { - const onChange = (event, value) => onValueChange(event, index, value); - const onSelect = (event, _index, value) => onValueChange(event, index, value); - const onSubmit = (value) => onValueChange(null, index, value); + const onChange = (value) => onValueChange(null, index, value); const label = `${input.name}: ${input.type}`; - let inputbox; - - switch (input.type) { - case 'address': - inputbox = ( - - ); - break; - - case 'bool': - const boolitems = ['false', 'true'].map((bool) => { - return ( - { bool } - ); - }); - inputbox = ( - - ); - break; - - default: - inputbox = ( - - ); - } return ( -
- { inputbox } +
+
); }); diff --git a/js/src/modals/ExecuteContract/executeContract.css b/js/src/modals/ExecuteContract/executeContract.css index a83b373ee..6b7132912 100644 --- a/js/src/modals/ExecuteContract/executeContract.css +++ b/js/src/modals/ExecuteContract/executeContract.css @@ -42,3 +42,15 @@ padding: 0.75em; text-align: center; } + +.columns { + display: flex; + flex-wrap: wrap; + position: relative; + + &>div { + flex: 0 1 50%; + width: 50%; + position: relative; + } +} diff --git a/js/src/modals/ExecuteContract/executeContract.js b/js/src/modals/ExecuteContract/executeContract.js index c3b64d738..7b4e8ccd2 100644 --- a/js/src/modals/ExecuteContract/executeContract.js +++ b/js/src/modals/ExecuteContract/executeContract.js @@ -17,18 +17,36 @@ import React, { Component, PropTypes } from 'react'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; +import { observer } from 'mobx-react'; import ActionDoneAll from 'material-ui/svg-icons/action/done-all'; import ContentClear from 'material-ui/svg-icons/content/clear'; +import NavigationArrowBack from 'material-ui/svg-icons/navigation/arrow-back'; +import NavigationArrowForward from 'material-ui/svg-icons/navigation/arrow-forward'; -import { BusyStep, CompletedStep, Button, IdentityIcon, Modal, TxHash } from '~/ui'; -import { MAX_GAS_ESTIMATION } from '../../util/constants'; -import { validateAddress, validateUint } from '../../util/validation'; +import { BusyStep, Button, CompletedStep, GasPriceEditor, IdentityIcon, Modal, TxHash } from '~/ui'; +import { MAX_GAS_ESTIMATION } from '~/util/constants'; +import { validateAddress, validateUint } from '~/util/validation'; +import { parseAbiType } from '~/util/abi'; import DetailsStep from './DetailsStep'; -import ERRORS from '../Transfer/errors'; import { ERROR_CODES } from '~/api/transport/error'; +const STEP_DETAILS = 0; +const STEP_BUSY_OR_GAS = 1; +const STEP_BUSY = 2; + +const TITLES = { + transfer: 'function details', + sending: 'sending', + complete: 'complete', + gas: 'gas selection', + rejected: 'rejected' +}; +const STAGES_BASIC = [TITLES.transfer, TITLES.sending, TITLES.complete]; +const STAGES_GAS = [TITLES.transfer, TITLES.gas, TITLES.sending, TITLES.complete]; + +@observer class ExecuteContract extends Component { static contextTypes = { api: PropTypes.object.isRequired, @@ -45,28 +63,29 @@ class ExecuteContract extends Component { onFromAddressChange: PropTypes.func.isRequired } + gasStore = new GasPriceEditor.Store(this.context.api, this.props.gasLimit); + state = { amount: '0', amountError: null, + busyState: null, fromAddressError: null, func: null, funcError: null, - gas: null, - gasLimitError: null, + gasEdit: false, + rejected: false, + step: STEP_DETAILS, + sending: false, values: [], valuesError: [], - step: 0, - sending: false, - busyState: null, - txhash: null, - rejected: false + txhash: null } componentDidMount () { const { contract } = this.props; const functions = contract.functions .filter((func) => !func.constant) - .sort((a, b) => a.name.localeCompare(b.name)); + .sort((a, b) => (a.name || '').localeCompare(b.name || '')); this.onFuncChange(null, functions[0]); } @@ -78,15 +97,21 @@ class ExecuteContract extends Component { } render () { - const { sending } = this.state; + const { sending, step, gasEdit, rejected } = this.state; + const steps = gasEdit ? STAGES_GAS : STAGES_BASIC; + + if (rejected) { + steps[steps.length - 1] = TITLES.rejected; + } return ( + current={ step } + steps={ steps } + visible + waiting={ gasEdit ? [STEP_BUSY] : [STEP_BUSY_OR_GAS] }> { this.renderStep() } ); @@ -94,7 +119,7 @@ class ExecuteContract extends Component { renderDialogActions () { const { onClose, fromAddress } = this.props; - const { sending, step, fromAddressError, valuesError } = this.state; + const { gasEdit, sending, step, fromAddressError, valuesError } = this.state; const hasError = fromAddressError || valuesError.find((error) => error); const cancelBtn = ( @@ -104,21 +129,44 @@ class ExecuteContract extends Component { icon={ } onClick={ onClose } /> ); + const postBtn = ( +