Merge branch 'master' into rotating-key
Conflicts: ethstore/src/ethstore.rs ethstore/src/secret_store.rs
This commit is contained in:
		
						commit
						27503e8e8c
					
				| @ -422,13 +422,10 @@ 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) | ||||
|   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 [ "$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 +434,10 @@ 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) | ||||
|     - echo $JS_FILES_MODIFIED | ||||
|     - if [ "$JS_FILES_MODIFIED" = 0 ]; then echo "skip js test"; else ./js/scripts/install-deps.sh;fi | ||||
|     - export JS_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep ^js/ | wc -l) | ||||
|     - 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 +478,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 | ||||
|  | ||||
							
								
								
									
										21
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										21
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @ -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" | ||||
| @ -660,12 +676,15 @@ dependencies = [ | ||||
|  "clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "ethcore 1.5.0", | ||||
|  "ethcore-devtools 1.5.0", | ||||
|  "ethcore-io 1.5.0", | ||||
|  "ethcore-ipc 1.5.0", | ||||
|  "ethcore-ipc-codegen 1.5.0", | ||||
|  "ethcore-ipc-nano 1.5.0", | ||||
|  "ethcore-light 1.5.0", | ||||
|  "ethcore-network 1.5.0", | ||||
|  "ethcore-util 1.5.0", | ||||
|  "ethkey 0.2.0", | ||||
|  "heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "parking_lot 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| @ -1271,7 +1290,7 @@ dependencies = [ | ||||
| [[package]] | ||||
| name = "parity-ui-precompiled" | ||||
| version = "1.4.0" | ||||
| source = "git+https://github.com/ethcore/js-precompiled.git#1bf7160f6c8f25353d790dbd0935560d3d395727" | ||||
| source = "git+https://github.com/ethcore/js-precompiled.git#3d3b2f9e8e8b0fd62c172240bfd001a317cf2979" | ||||
| dependencies = [ | ||||
|  "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| ] | ||||
|  | ||||
| @ -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" | ||||
|  | ||||
| @ -8,14 +8,18 @@ authors = ["Ethcore <admin@ethcore.io>"] | ||||
| build = "build.rs" | ||||
| 
 | ||||
| [build-dependencies] | ||||
| "ethcore-ipc-codegen" = { path = "../../ipc/codegen" } | ||||
| "ethcore-ipc-codegen" = { path = "../../ipc/codegen", optional = true } | ||||
| 
 | ||||
| [dependencies] | ||||
| log = "0.3" | ||||
| 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" | ||||
| 
 | ||||
| [features] | ||||
| default = [] | ||||
| ipc = ["ethcore-ipc", "ethcore-ipc-codegen"] | ||||
| @ -14,8 +14,14 @@ | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| #[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() { } | ||||
| @ -20,7 +20,7 @@ | ||||
| use std::sync::Arc; | ||||
| 
 | ||||
| use ethcore::engines::Engine; | ||||
| use ethcore::ids::BlockID; | ||||
| use ethcore::ids::BlockId; | ||||
| use ethcore::service::ClientIoMessage; | ||||
| use ethcore::block_import_error::BlockImportError; | ||||
| use ethcore::block_status::BlockStatus; | ||||
| @ -51,7 +51,7 @@ impl Client { | ||||
| 	} | ||||
| 
 | ||||
| 	/// Whether the block is already known (but not necessarily part of the canonical chain)
 | ||||
| 	pub fn is_known(&self, _id: BlockID) -> bool { | ||||
| 	pub fn is_known(&self, _id: BlockId) -> bool { | ||||
| 		false | ||||
| 	} | ||||
| 
 | ||||
| @ -61,7 +61,7 @@ impl Client { | ||||
| 	} | ||||
| 
 | ||||
| 	/// Inquire about the status of a given block.
 | ||||
| 	pub fn status(&self, _id: BlockID) -> BlockStatus { | ||||
| 	pub fn status(&self, _id: BlockId) -> BlockStatus { | ||||
| 		BlockStatus::Unknown | ||||
| 	} | ||||
| 
 | ||||
|  | ||||
| @ -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; | ||||
| 
 | ||||
| #[cfg(feature = "ipc")] | ||||
| extern crate ethcore_ipc as ipc; | ||||
| @ -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::*; | ||||
|  | ||||
							
								
								
									
										120
									
								
								ethcore/light/src/net/context.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								ethcore/light/src/net/context.rs
									
									
									
									
									
										Normal file
									
								
							| @ -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 <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| //! 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<u8>); | ||||
| 
 | ||||
|     /// 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<u8>); | ||||
| 
 | ||||
|     /// 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<u8>; | ||||
| } | ||||
| 
 | ||||
| impl<'a> IoContext for NetworkContext<'a> { | ||||
|     fn send(&self, peer: PeerId, packet_id: u8, packet_body: Vec<u8>) { | ||||
|         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<u8>) { | ||||
|         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<u8> { | ||||
|         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<ReqId, Error>; | ||||
| 
 | ||||
|     /// 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<ReqId, Error> { | ||||
|         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); | ||||
|     } | ||||
| } | ||||
| @ -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"), | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @ -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<usize>, // 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<Receipt>]) { } | ||||
| 	/// 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<Bytes>]) { } | ||||
| 	/// 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<Bytes>)]) { } | ||||
| 	/// 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>, | ||||
| 	provider: Arc<Provider>, | ||||
| 	genesis_hash: H256, | ||||
| 	network_id: NetworkId, | ||||
| 	network_id: u64, | ||||
| 	pending_peers: RwLock<HashMap<PeerId, PendingPeer>>, | ||||
| 	peers: RwLock<HashMap<PeerId, Mutex<Peer>>>, | ||||
| 	pending_requests: RwLock<HashMap<usize, Requested>>, | ||||
| @ -189,10 +224,13 @@ pub struct LightProtocol { | ||||
| 
 | ||||
| impl LightProtocol { | ||||
| 	/// Create a new instance of the protocol manager.
 | ||||
| 	pub fn new(provider: Box<Provider>, params: Params) -> Self { | ||||
| 	pub fn new(provider: Arc<Provider>, params: Params) -> Self { | ||||
| 		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<usize> { | ||||
| 		self.peers.read().get(&peer).map(|peer| { | ||||
| 		self.peers.read().get(&peer).and_then(|peer| { | ||||
| 			let mut peer = peer.lock(); | ||||
| 			peer.recharge_remote(); | ||||
| 			peer.remote_flow.max_amount(&peer.remote_buffer, kind) | ||||
| 			match peer.remote_flow.as_mut() { | ||||
| 				Some(&mut (ref mut buf, ref flow)) => { | ||||
| 					flow.recharge(buf); | ||||
| 					Some(flow.max_amount(&*buf, kind)) | ||||
| 				} | ||||
| 				None => None, | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| 
 | ||||
| 	/// Make a request to a peer. 
 | ||||
| 	///
 | ||||
| 	/// Fails on: nonexistent peer, network error,
 | ||||
| 	/// Fails on: nonexistent peer, network error, peer not server,
 | ||||
| 	/// insufficient buffer. Does not check capabilities before sending.
 | ||||
| 	/// On success, returns a request id which can later be coordinated 
 | ||||
| 	/// with an event.
 | ||||
| 	pub fn request_from(&self, io: &NetworkContext, peer_id: &PeerId, request: Request) -> Result<ReqId, Error> { | ||||
| 	pub fn request_from(&self, io: &IoContext, peer_id: &PeerId, request: Request) -> Result<ReqId, Error> { | ||||
| 		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<Handler>) { | ||||
| 		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<ReqId, Error> { | ||||
| 		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<PendingPeer, NetworkError> { | ||||
| 		let chain_info = self.provider.chain_info(); | ||||
| 	fn send_status(&self, peer: PeerId, io: &IoContext) -> Result<PendingPeer, Error> { | ||||
| 		let proto_version = try!(io.protocol_version(peer).ok_or(Error::WrongNetwork)); | ||||
| 
 | ||||
| 		// TODO: could update capabilities here.
 | ||||
| 		if PROTOCOL_VERSIONS.iter().find(|x| **x == proto_version).is_none() { | ||||
| 			return Err(Error::UnsupportedProtocolVersion(proto_version)); | ||||
| 		} | ||||
| 		
 | ||||
| 		let chain_info = self.provider.chain_info(); | ||||
| 
 | ||||
| 		let status = Status { | ||||
| 			head_td: chain_info.total_difficulty, | ||||
| 			head_hash: chain_info.best_block_hash, | ||||
| 			head_num: chain_info.best_block_number, | ||||
| 			genesis_hash: chain_info.genesis_hash, | ||||
| 			protocol_version: 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<Bytes> = 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<Vec<Receipt>> = 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<Vec<Bytes>> = 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<Bytes> = 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<Bytes>), ::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::<SignedTransaction>()).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) { | ||||
|  | ||||
| @ -82,26 +82,6 @@ impl Key { | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| /// Network ID structure.
 | ||||
| #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||||
| #[repr(u32)] | ||||
| pub enum NetworkId { | ||||
| 	/// ID for the mainnet
 | ||||
| 	Mainnet = 1, | ||||
| 	/// ID for the testnet
 | ||||
| 	Testnet = 0, | ||||
| } | ||||
| 
 | ||||
| impl NetworkId { | ||||
| 	fn from_raw(raw: u32) -> Option<Self> { | ||||
| 		match raw { | ||||
| 			0 => Some(NetworkId::Testnet), | ||||
| 			1 => Some(NetworkId::Mainnet), | ||||
| 			_ => None, | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // helper for decoding key-value pairs in the handshake or an announcement.
 | ||||
| struct Parser<'a> { | ||||
| 	pos: usize, | ||||
| @ -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<UntrustedRlp<'a>, DecoderError> { | ||||
| 		trace!(target: "les", "Expecting key {}", key.as_str()); | ||||
| 		let pre_pos = self.pos; | ||||
| 		if let Some((k, val)) = try!(self.get_next()) { | ||||
| 			if k == key { return Ok(val) } | ||||
| @ -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<FlowParams>), 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<u8> { | ||||
| pub fn write_handshake(status: &Status, capabilities: &Capabilities, flow_params: Option<&FlowParams>) -> Vec<u8> { | ||||
| 	let mut pairs = Vec::new(); | ||||
| 	pairs.push(encode_pair(Key::ProtocolVersion, &status.protocol_version)); | ||||
| 	pairs.push(encode_pair(Key::NetworkId, &(status.network_id as 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()); | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										512
									
								
								ethcore/light/src/net/tests/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										512
									
								
								ethcore/light/src/net/tests/mod.rs
									
									
									
									
									
										Normal file
									
								
							| @ -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 <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| //! 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<u8>), | ||||
| 	/// Expect this response.
 | ||||
| 	Respond(u8, Vec<u8>), | ||||
| 	/// Expect a punishment (disconnect/disable)
 | ||||
| 	Punish(PeerId), | ||||
| 	/// Expect nothing.
 | ||||
| 	Nothing, | ||||
| } | ||||
| 
 | ||||
| impl IoContext for Expect { | ||||
| 	fn send(&self, peer: PeerId, packet_id: u8, packet_body: Vec<u8>) { | ||||
| 		assert_eq!(self, &Expect::Send(peer, packet_id, packet_body)); | ||||
| 	} | ||||
| 
 | ||||
| 	fn respond(&self, packet_id: u8, packet_body: Vec<u8>) { | ||||
| 		assert_eq!(self, &Expect::Respond(packet_id, packet_body)); | ||||
| 	} | ||||
| 
 | ||||
| 	fn disconnect_peer(&self, peer: PeerId) { | ||||
| 		assert_eq!(self, &Expect::Punish(peer)); | ||||
| 	} | ||||
| 
 | ||||
| 	fn disable_peer(&self, peer: PeerId) { | ||||
| 		assert_eq!(self, &Expect::Punish(peer)); | ||||
| 	} | ||||
| 
 | ||||
| 	fn protocol_version(&self, _peer: PeerId) -> Option<u8> { | ||||
| 		Some(super::MAX_PROTOCOL_VERSION) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // can't implement directly for Arc due to cross-crate orphan rules.
 | ||||
| struct TestProvider(Arc<TestProviderInner>); | ||||
| 
 | ||||
| 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<u64> { | ||||
| 		self.0.client.tree_route(a, b).map(|route| route.index as u64) | ||||
| 	} | ||||
| 
 | ||||
| 	fn earliest_state(&self) -> Option<u64> { | ||||
| 		None | ||||
| 	} | ||||
| 
 | ||||
| 	fn block_headers(&self, req: request::Headers) -> Vec<Bytes> { | ||||
| 		let best_num = self.0.client.chain_info().best_block_number; | ||||
| 		let start_num = req.block_num; | ||||
| 
 | ||||
| 		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<Bytes> { | ||||
| 		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<Bytes> { | ||||
| 		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<Bytes> { | ||||
| 		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<Bytes> { | ||||
| 		req.code_requests.into_iter() | ||||
| 			.map(|req| { 
 | ||||
|                 req.account_key.iter().chain(req.account_key.iter()).cloned().collect() | ||||
| 			 }) | ||||
| 			.collect() | ||||
| 	} | ||||
| 
 | ||||
| 	fn header_proofs(&self, req: request::HeaderProofs) -> Vec<Bytes> { | ||||
| 		req.requests.into_iter().map(|_| ::rlp::EMPTY_LIST_RLP.to_vec()).collect() | ||||
| 	} | ||||
| 
 | ||||
| 	fn pending_transactions(&self) -> Vec<SignedTransaction> { | ||||
| 		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<TestProviderInner>, 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<_>> = 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); | ||||
| } | ||||
| @ -20,7 +20,7 @@ | ||||
| use ethcore::blockchain_info::BlockChainInfo; | ||||
| use ethcore::client::{BlockChainClient, ProvingBlockChainClient}; | ||||
| use ethcore::transaction::SignedTransaction; | ||||
| use ethcore::ids::BlockID; | ||||
| use ethcore::ids::BlockId; | ||||
| 
 | ||||
| use util::{Bytes, H256}; | ||||
| 
 | ||||
| @ -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<Bytes>; | ||||
| 
 | ||||
| 	/// 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<Bytes>; | ||||
| 
 | ||||
| 	/// Provide pending transactions.
 | ||||
| @ -96,7 +100,7 @@ impl<T: ProvingBlockChainClient + ?Sized> Provider for T { | ||||
| 		let best_num = self.chain_info().best_block_number; | ||||
| 		let start_num = req.block_num; | ||||
| 
 | ||||
| 		match self.block_hash(BlockID::Number(req.block_num)) { | ||||
| 		match self.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)); | ||||
| @ -105,10 +109,10 @@ impl<T: ProvingBlockChainClient + ?Sized> 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))) | ||||
| 			.map(|x| self.block_header(BlockId::Number(x))) | ||||
| 			.take_while(|x| x.is_some()) | ||||
| 			.flat_map(|x| x) | ||||
| 			.collect() | ||||
| @ -116,7 +120,7 @@ impl<T: ProvingBlockChainClient + ?Sized> Provider for T { | ||||
| 
 | ||||
| 	fn block_bodies(&self, req: request::Bodies) -> Vec<Bytes> { | ||||
| 		req.block_hashes.into_iter() | ||||
| 			.map(|hash| self.block_body(BlockID::Hash(hash))) | ||||
| 			.map(|hash| self.block_body(BlockId::Hash(hash))) | ||||
| 			.map(|body| body.unwrap_or_else(|| ::rlp::EMPTY_LIST_RLP.to_vec())) | ||||
| 			.collect() | ||||
| 	} | ||||
| @ -135,8 +139,8 @@ impl<T: ProvingBlockChainClient + ?Sized> Provider for T { | ||||
| 
 | ||||
| 		for request in req.requests { | ||||
| 			let proof = match request.key2 { | ||||
| 				Some(key2) => self.prove_storage(request.key1, key2, request.from_level, BlockID::Hash(request.block)), | ||||
| 				None => self.prove_account(request.key1, request.from_level, BlockID::Hash(request.block)), | ||||
| 				Some(key2) => self.prove_storage(request.key1, key2, request.from_level, BlockId::Hash(request.block)), | ||||
| 				None => self.prove_account(request.key1, request.from_level, BlockId::Hash(request.block)), | ||||
| 			}; | ||||
| 
 | ||||
| 			let mut stream = RlpStream::new_list(proof.len()); | ||||
| @ -153,7 +157,7 @@ impl<T: ProvingBlockChainClient + ?Sized> Provider for T { | ||||
| 	fn contract_code(&self, req: request::ContractCodes) -> Vec<Bytes> { | ||||
| 		req.code_requests.into_iter() | ||||
| 			.map(|req| { | ||||
| 				self.code_by_hash(req.account_key, BlockID::Hash(req.block_hash)) | ||||
| 				self.code_by_hash(req.account_key, BlockId::Hash(req.block_hash)) | ||||
| 			}) | ||||
| 			.collect() | ||||
| 	} | ||||
|  | ||||
| @ -19,7 +19,8 @@ | ||||
| use util::H256; | ||||
| 
 | ||||
| /// A request for block headers.
 | ||||
| #[derive(Debug, Clone, PartialEq, Eq, Binary)] | ||||
| #[derive(Debug, Clone, PartialEq, Eq)] | ||||
| #[cfg_attr(feature = "ipc", derive(Binary))] | ||||
| pub struct Headers { | ||||
| 	/// Starting block number
 | ||||
| 	pub block_num: u64, | ||||
| @ -35,7 +36,8 @@ pub struct Headers { | ||||
| } | ||||
| 
 | ||||
| /// A request for specific block bodies.
 | ||||
| #[derive(Debug, Clone, PartialEq, Eq, Binary)] | ||||
| #[derive(Debug, Clone, PartialEq, Eq)] | ||||
| #[cfg_attr(feature = "ipc", derive(Binary))] | ||||
| pub struct Bodies { | ||||
| 	/// Hashes which bodies are being requested for.
 | ||||
| 	pub block_hashes: Vec<H256> | ||||
| @ -45,14 +47,16 @@ pub struct Bodies { | ||||
| ///
 | ||||
| /// This request is answered with a list of transaction receipts for each block
 | ||||
| /// requested.
 | ||||
| #[derive(Debug, Clone, PartialEq, Eq, Binary)] | ||||
| #[derive(Debug, Clone, PartialEq, Eq)] | ||||
| #[cfg_attr(feature = "ipc", derive(Binary))] | ||||
| pub struct Receipts { | ||||
| 	/// Block hashes to return receipts for.
 | ||||
| 	pub block_hashes: Vec<H256>, | ||||
| } | ||||
| 
 | ||||
| /// A request for a state proof
 | ||||
| #[derive(Debug, Clone, PartialEq, Eq, Binary)] | ||||
| #[derive(Debug, Clone, PartialEq, Eq)] | ||||
| #[cfg_attr(feature = "ipc", derive(Binary))] | ||||
| pub struct StateProof { | ||||
| 	/// Block hash to query state from.
 | ||||
| 	pub block: H256, | ||||
| @ -66,14 +70,16 @@ pub struct StateProof { | ||||
| } | ||||
| 
 | ||||
| /// A request for state proofs.
 | ||||
| #[derive(Debug, Clone, PartialEq, Eq, Binary)] | ||||
| #[derive(Debug, Clone, PartialEq, Eq)] | ||||
| #[cfg_attr(feature = "ipc", derive(Binary))] | ||||
| pub struct StateProofs { | ||||
| 	/// All the proof requests.
 | ||||
| 	pub requests: Vec<StateProof>, | ||||
| } | ||||
| 
 | ||||
| /// A request for contract code.
 | ||||
| #[derive(Debug, Clone, PartialEq, Eq, Binary)] | ||||
| #[derive(Debug, Clone, PartialEq, Eq)] | ||||
| #[cfg_attr(feature = "ipc", derive(Binary))] | ||||
| pub struct ContractCode { | ||||
| 	/// Block hash
 | ||||
| 	pub block_hash: H256, | ||||
| @ -82,14 +88,16 @@ pub struct ContractCode { | ||||
| } | ||||
| 
 | ||||
| /// A request for contract code.
 | ||||
| #[derive(Debug, Clone, PartialEq, Eq, Binary)] | ||||
| #[derive(Debug, Clone, PartialEq, Eq)] | ||||
| #[cfg_attr(feature = "ipc", derive(Binary))] | ||||
| pub struct ContractCodes { | ||||
| 	/// Block hash and account key (== sha3(address)) pairs to fetch code for.
 | ||||
| 	pub code_requests: Vec<ContractCode>, | ||||
| } | ||||
| 
 | ||||
| /// A request for a header proof from the Canonical Hash Trie.
 | ||||
| #[derive(Debug, Clone, PartialEq, Eq, Binary)] | ||||
| #[derive(Debug, Clone, PartialEq, Eq)] | ||||
| #[cfg_attr(feature = "ipc", derive(Binary))] | ||||
| pub struct HeaderProof { | ||||
| 	/// Number of the CHT.
 | ||||
| 	pub cht_number: u64, | ||||
| @ -100,14 +108,16 @@ pub struct HeaderProof { | ||||
| } | ||||
| 
 | ||||
| /// A request for header proofs from the CHT.
 | ||||
| #[derive(Debug, Clone, PartialEq, Eq, Binary)] | ||||
| #[derive(Debug, Clone, PartialEq, Eq)] | ||||
| #[cfg_attr(feature = "ipc", derive(Binary))] | ||||
| pub struct HeaderProofs { | ||||
| 	/// All the proof requests.
 | ||||
| 	pub requests: Vec<HeaderProof>, | ||||
| } | ||||
| 
 | ||||
| /// Kinds of requests.
 | ||||
| #[derive(Debug, Clone, Copy, PartialEq, Eq, Binary)] | ||||
| #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||||
| #[cfg_attr(feature = "ipc", derive(Binary))] | ||||
| pub enum Kind { | ||||
| 	/// Requesting headers.
 | ||||
| 	Headers, | ||||
| @ -124,7 +134,8 @@ pub enum Kind { | ||||
| } | ||||
| 
 | ||||
| /// Encompasses all possible types of requests in a single structure.
 | ||||
| #[derive(Debug, Clone, PartialEq, Eq, Binary)] | ||||
| #[derive(Debug, Clone, PartialEq, Eq)] | ||||
| #[cfg_attr(feature = "ipc", derive(Binary))] | ||||
| pub enum Request { | ||||
| 	/// Requesting headers.
 | ||||
| 	Headers(Headers), | ||||
|  | ||||
| @ -15,6 +15,11 @@ | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| //! Types used in the public (IPC) api which require custom code generation.
 | ||||
| #![cfg_attr(feature = "ipc", allow(dead_code, unused_assignments, unused_variables))] // codegen issues
 | ||||
| 
 | ||||
| #![allow(dead_code, unused_assignments, unused_variables)] // codegen issues
 | ||||
| 
 | ||||
| #[cfg(feature = "ipc")] | ||||
| include!(concat!(env!("OUT_DIR"), "/mod.rs.in")); | ||||
| 
 | ||||
| #[cfg(not(feature = "ipc"))] | ||||
| include!("mod.rs.in"); | ||||
| @ -4,7 +4,8 @@ | ||||
| 		"AuthorityRound": { | ||||
| 			"params": { | ||||
| 				"gasLimitBoundDivisor": "0x0400", | ||||
| 				"stepDuration": "1", | ||||
| 				"stepDuration": 1, | ||||
| 				"startStep": 2, | ||||
| 				"authorities" : [ | ||||
| 					"0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e", | ||||
| 					"0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1" | ||||
|  | ||||
| @ -1 +1 @@ | ||||
| Subproject commit e8f4624b7f1a15c63674eecf577c7ab76c3b16be | ||||
| Subproject commit d509c75936ec6cbba683ee1916aa0bca436bc376 | ||||
| @ -202,7 +202,7 @@ impl AccountProvider { | ||||
| 		Ok(AccountMeta { | ||||
| 			name: try!(self.sstore.name(&account)), | ||||
| 			meta: try!(self.sstore.meta(&account)), | ||||
| 			uuid: self.sstore.uuid(&account).ok().map(Into::into),	// allowed to not have a UUID
 | ||||
| 			uuid: self.sstore.uuid(&account).ok().map(Into::into),	// allowed to not have a Uuid
 | ||||
| 		}) | ||||
| 	} | ||||
| 
 | ||||
|  | ||||
| @ -146,7 +146,7 @@ pub trait BlockProvider { | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Hash, Eq, PartialEq, Clone)] | ||||
| enum CacheID { | ||||
| enum CacheId { | ||||
| 	BlockHeader(H256), | ||||
| 	BlockBody(H256), | ||||
| 	BlockDetails(H256), | ||||
| @ -160,7 +160,7 @@ impl bc::group::BloomGroupDatabase for BlockChain { | ||||
| 	fn blooms_at(&self, position: &bc::group::GroupPosition) -> Option<bc::group::BloomGroup> { | ||||
| 		let position = LogGroupPosition::from(position.clone()); | ||||
| 		let result = self.db.read_with_cache(db::COL_EXTRA, &self.blocks_blooms, &position).map(Into::into); | ||||
| 		self.cache_man.lock().note_used(CacheID::BlocksBlooms(position)); | ||||
| 		self.cache_man.lock().note_used(CacheId::BlocksBlooms(position)); | ||||
| 		result | ||||
| 	} | ||||
| } | ||||
| @ -193,7 +193,7 @@ pub struct BlockChain { | ||||
| 
 | ||||
| 	db: Arc<Database>, | ||||
| 
 | ||||
| 	cache_man: Mutex<CacheManager<CacheID>>, | ||||
| 	cache_man: Mutex<CacheManager<CacheId>>, | ||||
| 
 | ||||
| 	pending_best_block: RwLock<Option<BestBlock>>, | ||||
| 	pending_block_hashes: RwLock<HashMap<BlockNumber, H256>>, | ||||
| @ -270,7 +270,7 @@ impl BlockProvider for BlockChain { | ||||
| 			None => None | ||||
| 		}; | ||||
| 
 | ||||
| 		self.cache_man.lock().note_used(CacheID::BlockHeader(hash.clone())); | ||||
| 		self.cache_man.lock().note_used(CacheId::BlockHeader(hash.clone())); | ||||
| 		result | ||||
| 	} | ||||
| 
 | ||||
| @ -306,7 +306,7 @@ impl BlockProvider for BlockChain { | ||||
| 			None => None | ||||
| 		}; | ||||
| 
 | ||||
| 		self.cache_man.lock().note_used(CacheID::BlockBody(hash.clone())); | ||||
| 		self.cache_man.lock().note_used(CacheId::BlockBody(hash.clone())); | ||||
| 
 | ||||
| 		result | ||||
| 	} | ||||
| @ -314,28 +314,28 @@ impl BlockProvider for BlockChain { | ||||
| 	/// Get the familial details concerning a block.
 | ||||
| 	fn block_details(&self, hash: &H256) -> Option<BlockDetails> { | ||||
| 		let result = self.db.read_with_cache(db::COL_EXTRA, &self.block_details, hash); | ||||
| 		self.cache_man.lock().note_used(CacheID::BlockDetails(hash.clone())); | ||||
| 		self.cache_man.lock().note_used(CacheId::BlockDetails(hash.clone())); | ||||
| 		result | ||||
| 	} | ||||
| 
 | ||||
| 	/// Get the hash of given block's number.
 | ||||
| 	fn block_hash(&self, index: BlockNumber) -> Option<H256> { | ||||
| 		let result = self.db.read_with_cache(db::COL_EXTRA, &self.block_hashes, &index); | ||||
| 		self.cache_man.lock().note_used(CacheID::BlockHashes(index)); | ||||
| 		self.cache_man.lock().note_used(CacheId::BlockHashes(index)); | ||||
| 		result | ||||
| 	} | ||||
| 
 | ||||
| 	/// Get the address of transaction with given hash.
 | ||||
| 	fn transaction_address(&self, hash: &H256) -> Option<TransactionAddress> { | ||||
| 		let result = self.db.read_with_cache(db::COL_EXTRA, &self.transaction_addresses, hash); | ||||
| 		self.cache_man.lock().note_used(CacheID::TransactionAddresses(hash.clone())); | ||||
| 		self.cache_man.lock().note_used(CacheId::TransactionAddresses(hash.clone())); | ||||
| 		result | ||||
| 	} | ||||
| 
 | ||||
| 	/// Get receipts of block with given hash.
 | ||||
| 	fn block_receipts(&self, hash: &H256) -> Option<BlockReceipts> { | ||||
| 		let result = self.db.read_with_cache(db::COL_EXTRA, &self.block_receipts, hash); | ||||
| 		self.cache_man.lock().note_used(CacheID::BlockReceipts(hash.clone())); | ||||
| 		self.cache_man.lock().note_used(CacheId::BlockReceipts(hash.clone())); | ||||
| 		result | ||||
| 	} | ||||
| 
 | ||||
| @ -809,7 +809,7 @@ impl BlockChain { | ||||
| 		let mut write_details = self.block_details.write(); | ||||
| 		batch.extend_with_cache(db::COL_EXTRA, &mut *write_details, update, CacheUpdatePolicy::Overwrite); | ||||
| 
 | ||||
| 		self.cache_man.lock().note_used(CacheID::BlockDetails(block_hash)); | ||||
| 		self.cache_man.lock().note_used(CacheId::BlockDetails(block_hash)); | ||||
| 	} | ||||
| 
 | ||||
| 	#[cfg_attr(feature="dev", allow(similar_names))] | ||||
| @ -968,15 +968,15 @@ impl BlockChain { | ||||
| 
 | ||||
| 		let mut cache_man = self.cache_man.lock(); | ||||
| 		for n in pending_hashes_keys { | ||||
| 			cache_man.note_used(CacheID::BlockHashes(n)); | ||||
| 			cache_man.note_used(CacheId::BlockHashes(n)); | ||||
| 		} | ||||
| 
 | ||||
| 		for hash in enacted_txs_keys { | ||||
| 			cache_man.note_used(CacheID::TransactionAddresses(hash)); | ||||
| 			cache_man.note_used(CacheId::TransactionAddresses(hash)); | ||||
| 		} | ||||
| 
 | ||||
| 		for hash in pending_block_hashes { | ||||
| 			cache_man.note_used(CacheID::BlockDetails(hash)); | ||||
| 			cache_man.note_used(CacheId::BlockDetails(hash)); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| @ -1244,13 +1244,13 @@ impl BlockChain { | ||||
| 		cache_man.collect_garbage(current_size, | ids | { | ||||
| 			for id in &ids { | ||||
| 				match *id { | ||||
| 					CacheID::BlockHeader(ref h) => { block_headers.remove(h); }, | ||||
| 					CacheID::BlockBody(ref h) => { block_bodies.remove(h); }, | ||||
| 					CacheID::BlockDetails(ref h) => { block_details.remove(h); } | ||||
| 					CacheID::BlockHashes(ref h) => { block_hashes.remove(h); } | ||||
| 					CacheID::TransactionAddresses(ref h) => { transaction_addresses.remove(h); } | ||||
| 					CacheID::BlocksBlooms(ref h) => { blocks_blooms.remove(h); } | ||||
| 					CacheID::BlockReceipts(ref h) => { block_receipts.remove(h); } | ||||
| 					CacheId::BlockHeader(ref h) => { block_headers.remove(h); }, | ||||
| 					CacheId::BlockBody(ref h) => { block_bodies.remove(h); }, | ||||
| 					CacheId::BlockDetails(ref h) => { block_details.remove(h); } | ||||
| 					CacheId::BlockHashes(ref h) => { block_hashes.remove(h); } | ||||
| 					CacheId::TransactionAddresses(ref h) => { transaction_addresses.remove(h); } | ||||
| 					CacheId::BlocksBlooms(ref h) => { blocks_blooms.remove(h); } | ||||
| 					CacheId::BlockReceipts(ref h) => { block_receipts.remove(h); } | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
|  | ||||
| @ -50,9 +50,9 @@ use log_entry::LocalizedLogEntry; | ||||
| use verification::queue::BlockQueue; | ||||
| use blockchain::{BlockChain, BlockProvider, TreeRoute, ImportRoute}; | ||||
| use client::{ | ||||
| 	BlockID, TransactionID, UncleID, TraceId, ClientConfig, BlockChainClient, | ||||
| 	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; | ||||
| @ -255,6 +255,11 @@ impl Client { | ||||
| 		self.notify.write().push(Arc::downgrade(&target)); | ||||
| 	} | ||||
| 
 | ||||
| 	/// Returns engine reference.
 | ||||
| 	pub fn engine(&self) -> &Engine { | ||||
| 		&*self.engine | ||||
| 	} | ||||
| 
 | ||||
| 	fn notify<F>(&self, f: F) where F: Fn(&ChainNotify) { | ||||
| 		for np in self.notify.read().iter() { | ||||
| 			if let Some(n) = np.upgrade() { | ||||
| @ -563,6 +568,11 @@ impl Client { | ||||
| 		results.len() | ||||
| 	} | ||||
| 
 | ||||
| 	/// Get shared miner reference.
 | ||||
| 	pub fn miner(&self) -> Arc<Miner> { | ||||
| 		self.miner.clone() | ||||
| 	} | ||||
| 
 | ||||
| 	/// Used by PoA to try sealing on period change.
 | ||||
| 	pub fn update_sealing(&self) { | ||||
| 		self.miner.update_sealing(self) | ||||
| @ -570,13 +580,13 @@ impl Client { | ||||
| 
 | ||||
| 	/// Attempt to get a copy of a specific block's final state.
 | ||||
| 	///
 | ||||
| 	/// This will not fail if given BlockID::Latest.
 | ||||
| 	/// This will not fail if given BlockId::Latest.
 | ||||
| 	/// Otherwise, this can fail (but may not) if the DB prunes state.
 | ||||
| 	pub fn state_at(&self, id: BlockID) -> Option<State> { | ||||
| 	pub fn state_at(&self, id: BlockId) -> Option<State> { | ||||
| 		// fast path for latest state.
 | ||||
| 		match id.clone() { | ||||
| 			BlockID::Pending => return self.miner.pending_state().or_else(|| Some(self.state())), | ||||
| 			BlockID::Latest => return Some(self.state()), | ||||
| 			BlockId::Pending => return self.miner.pending_state().or_else(|| Some(self.state())), | ||||
| 			BlockId::Latest => return Some(self.state()), | ||||
| 			_ => {}, | ||||
| 		} | ||||
| 
 | ||||
| @ -601,15 +611,15 @@ impl Client { | ||||
| 
 | ||||
| 	/// Attempt to get a copy of a specific block's beginning state.
 | ||||
| 	///
 | ||||
| 	/// This will not fail if given BlockID::Latest.
 | ||||
| 	/// This will not fail if given BlockId::Latest.
 | ||||
| 	/// Otherwise, this can fail (but may not) if the DB prunes state.
 | ||||
| 	pub fn state_at_beginning(&self, id: BlockID) -> Option<State> { | ||||
| 	pub fn state_at_beginning(&self, id: BlockId) -> Option<State> { | ||||
| 		// fast path for latest state.
 | ||||
| 		match id { | ||||
| 			BlockID::Pending => self.state_at(BlockID::Latest), | ||||
| 			BlockId::Pending => self.state_at(BlockId::Latest), | ||||
| 			id => match self.block_number(id) { | ||||
| 				None | Some(0) => None, | ||||
| 				Some(n) => self.state_at(BlockID::Number(n - 1)), | ||||
| 				Some(n) => self.state_at(BlockId::Number(n - 1)), | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| @ -679,18 +689,18 @@ impl Client { | ||||
| 	} | ||||
| 
 | ||||
| 	/// Look up the block number for the given block ID.
 | ||||
| 	pub fn block_number(&self, id: BlockID) -> Option<BlockNumber> { | ||||
| 	pub fn block_number(&self, id: BlockId) -> Option<BlockNumber> { | ||||
| 		match id { | ||||
| 			BlockID::Number(number) => Some(number), | ||||
| 			BlockID::Hash(ref hash) => self.chain.read().block_number(hash), | ||||
| 			BlockID::Earliest => Some(0), | ||||
| 			BlockID::Latest | BlockID::Pending => Some(self.chain.read().best_block_number()), | ||||
| 			BlockId::Number(number) => Some(number), | ||||
| 			BlockId::Hash(ref hash) => self.chain.read().block_number(hash), | ||||
| 			BlockId::Earliest => Some(0), | ||||
| 			BlockId::Latest | BlockId::Pending => Some(self.chain.read().best_block_number()), | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	/// Take a snapshot at the given block.
 | ||||
| 	/// If the ID given is "latest", this will default to 1000 blocks behind.
 | ||||
| 	pub fn take_snapshot<W: snapshot_io::SnapshotWriter + Send>(&self, writer: W, at: BlockID, p: &snapshot::Progress) -> Result<(), EthcoreError> { | ||||
| 	pub fn take_snapshot<W: snapshot_io::SnapshotWriter + Send>(&self, writer: W, at: BlockId, p: &snapshot::Progress) -> Result<(), EthcoreError> { | ||||
| 		let db = self.state_db.lock().journal_db().boxed_clone(); | ||||
| 		let best_block_number = self.chain_info().best_block_number; | ||||
| 		let block_number = try!(self.block_number(at).ok_or(snapshot::Error::InvalidStartingBlock(at))); | ||||
| @ -702,13 +712,13 @@ impl Client { | ||||
| 		let history = ::std::cmp::min(self.history, 1000); | ||||
| 
 | ||||
| 		let start_hash = match at { | ||||
| 			BlockID::Latest => { | ||||
| 			BlockId::Latest => { | ||||
| 				let start_num = match db.earliest_era() { | ||||
| 					Some(era) => ::std::cmp::max(era, best_block_number - history), | ||||
| 					None => best_block_number - history, | ||||
| 				}; | ||||
| 
 | ||||
| 				match self.block_hash(BlockID::Number(start_num)) { | ||||
| 				match self.block_hash(BlockId::Number(start_num)) { | ||||
| 					Some(h) => h, | ||||
| 					None => return Err(snapshot::Error::InvalidStartingBlock(at).into()), | ||||
| 				} | ||||
| @ -729,19 +739,19 @@ impl Client { | ||||
| 		self.history | ||||
| 	} | ||||
| 
 | ||||
| 	fn block_hash(chain: &BlockChain, id: BlockID) -> Option<H256> { | ||||
| 	fn block_hash(chain: &BlockChain, id: BlockId) -> Option<H256> { | ||||
| 		match id { | ||||
| 			BlockID::Hash(hash) => Some(hash), | ||||
| 			BlockID::Number(number) => chain.block_hash(number), | ||||
| 			BlockID::Earliest => chain.block_hash(0), | ||||
| 			BlockID::Latest | BlockID::Pending => Some(chain.best_block_hash()), | ||||
| 			BlockId::Hash(hash) => Some(hash), | ||||
| 			BlockId::Number(number) => chain.block_hash(number), | ||||
| 			BlockId::Earliest => chain.block_hash(0), | ||||
| 			BlockId::Latest | BlockId::Pending => Some(chain.best_block_hash()), | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	fn transaction_address(&self, id: TransactionID) -> Option<TransactionAddress> { | ||||
| 	fn transaction_address(&self, id: TransactionId) -> Option<TransactionAddress> { | ||||
| 		match id { | ||||
| 			TransactionID::Hash(ref hash) => self.chain.read().transaction_address(hash), | ||||
| 			TransactionID::Location(id, index) => Self::block_hash(&self.chain.read(), id).map(|hash| TransactionAddress { | ||||
| 			TransactionId::Hash(ref hash) => self.chain.read().transaction_address(hash), | ||||
| 			TransactionId::Location(id, index) => Self::block_hash(&self.chain.read(), id).map(|hash| TransactionAddress { | ||||
| 				block_hash: hash, | ||||
| 				index: index, | ||||
| 			}) | ||||
| @ -795,7 +805,7 @@ impl snapshot::DatabaseRestore for Client { | ||||
| 
 | ||||
| 
 | ||||
| impl BlockChainClient for Client { | ||||
| 	fn call(&self, t: &SignedTransaction, block: BlockID, analytics: CallAnalytics) -> Result<Executed, CallError> { | ||||
| 	fn call(&self, t: &SignedTransaction, block: BlockId, analytics: CallAnalytics) -> Result<Executed, CallError> { | ||||
| 		let header = try!(self.block_header(block).ok_or(CallError::StatePruned)); | ||||
| 		let view = HeaderView::new(&header); | ||||
| 		let last_hashes = self.build_last_hashes(view.parent_hash()); | ||||
| @ -831,11 +841,11 @@ impl BlockChainClient for Client { | ||||
| 		Ok(ret) | ||||
| 	} | ||||
| 
 | ||||
| 	fn replay(&self, id: TransactionID, analytics: CallAnalytics) -> Result<Executed, CallError> { | ||||
| 	fn replay(&self, id: TransactionId, analytics: CallAnalytics) -> Result<Executed, CallError> { | ||||
| 		let address = try!(self.transaction_address(id).ok_or(CallError::TransactionNotFound)); | ||||
| 		let header_data = try!(self.block_header(BlockID::Hash(address.block_hash)).ok_or(CallError::StatePruned)); | ||||
| 		let body_data = try!(self.block_body(BlockID::Hash(address.block_hash)).ok_or(CallError::StatePruned)); | ||||
| 		let mut state = try!(self.state_at_beginning(BlockID::Hash(address.block_hash)).ok_or(CallError::StatePruned)); | ||||
| 		let header_data = try!(self.block_header(BlockId::Hash(address.block_hash)).ok_or(CallError::StatePruned)); | ||||
| 		let body_data = try!(self.block_body(BlockId::Hash(address.block_hash)).ok_or(CallError::StatePruned)); | ||||
| 		let mut state = try!(self.state_at_beginning(BlockId::Hash(address.block_hash)).ok_or(CallError::StatePruned)); | ||||
| 		let txs = BodyView::new(&body_data).transactions(); | ||||
| 
 | ||||
| 		if address.index >= txs.len() { | ||||
| @ -908,18 +918,18 @@ impl BlockChainClient for Client { | ||||
| 		self.chain.read().best_block_header() | ||||
| 	} | ||||
| 
 | ||||
| 	fn block_header(&self, id: BlockID) -> Option<Bytes> { | ||||
| 	fn block_header(&self, id: BlockId) -> Option<Bytes> { | ||||
| 		let chain = self.chain.read(); | ||||
| 		Self::block_hash(&chain, id).and_then(|hash| chain.block_header_data(&hash)) | ||||
| 	} | ||||
| 
 | ||||
| 	fn block_body(&self, id: BlockID) -> Option<Bytes> { | ||||
| 	fn block_body(&self, id: BlockId) -> Option<Bytes> { | ||||
| 		let chain = self.chain.read(); | ||||
| 		Self::block_hash(&chain, id).and_then(|hash| chain.block_body(&hash)) | ||||
| 	} | ||||
| 
 | ||||
| 	fn block(&self, id: BlockID) -> Option<Bytes> { | ||||
| 		if let BlockID::Pending = id { | ||||
| 	fn block(&self, id: BlockId) -> Option<Bytes> { | ||||
| 		if let BlockId::Pending = id { | ||||
| 			if let Some(block) = self.miner.pending_block() { | ||||
| 				return Some(block.rlp_bytes(Seal::Without)); | ||||
| 			} | ||||
| @ -930,7 +940,7 @@ impl BlockChainClient for Client { | ||||
| 		}) | ||||
| 	} | ||||
| 
 | ||||
| 	fn block_status(&self, id: BlockID) -> BlockStatus { | ||||
| 	fn block_status(&self, id: BlockId) -> BlockStatus { | ||||
| 		let chain = self.chain.read(); | ||||
| 		match Self::block_hash(&chain, id) { | ||||
| 			Some(ref hash) if chain.is_known(hash) => BlockStatus::InChain, | ||||
| @ -939,42 +949,42 @@ impl BlockChainClient for Client { | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	fn block_total_difficulty(&self, id: BlockID) -> Option<U256> { | ||||
| 		if let BlockID::Pending = id { | ||||
| 	fn block_total_difficulty(&self, id: BlockId) -> Option<U256> { | ||||
| 		if let BlockId::Pending = id { | ||||
| 			if let Some(block) = self.miner.pending_block() { | ||||
| 				return Some(*block.header.difficulty() + self.block_total_difficulty(BlockID::Latest).expect("blocks in chain have details; qed")); | ||||
| 				return Some(*block.header.difficulty() + self.block_total_difficulty(BlockId::Latest).expect("blocks in chain have details; qed")); | ||||
| 			} | ||||
| 		} | ||||
| 		let chain = self.chain.read(); | ||||
| 		Self::block_hash(&chain, id).and_then(|hash| chain.block_details(&hash)).map(|d| d.total_difficulty) | ||||
| 	} | ||||
| 
 | ||||
| 	fn nonce(&self, address: &Address, id: BlockID) -> Option<U256> { | ||||
| 	fn nonce(&self, address: &Address, id: BlockId) -> Option<U256> { | ||||
| 		self.state_at(id).map(|s| s.nonce(address)) | ||||
| 	} | ||||
| 
 | ||||
| 	fn storage_root(&self, address: &Address, id: BlockID) -> Option<H256> { | ||||
| 	fn storage_root(&self, address: &Address, id: BlockId) -> Option<H256> { | ||||
| 		self.state_at(id).and_then(|s| s.storage_root(address)) | ||||
| 	} | ||||
| 
 | ||||
| 	fn block_hash(&self, id: BlockID) -> Option<H256> { | ||||
| 	fn block_hash(&self, id: BlockId) -> Option<H256> { | ||||
| 		let chain = self.chain.read(); | ||||
| 		Self::block_hash(&chain, id) | ||||
| 	} | ||||
| 
 | ||||
| 	fn code(&self, address: &Address, id: BlockID) -> Option<Option<Bytes>> { | ||||
| 	fn code(&self, address: &Address, id: BlockId) -> Option<Option<Bytes>> { | ||||
| 		self.state_at(id).map(|s| s.code(address).map(|c| (*c).clone())) | ||||
| 	} | ||||
| 
 | ||||
| 	fn balance(&self, address: &Address, id: BlockID) -> Option<U256> { | ||||
| 	fn balance(&self, address: &Address, id: BlockId) -> Option<U256> { | ||||
| 		self.state_at(id).map(|s| s.balance(address)) | ||||
| 	} | ||||
| 
 | ||||
| 	fn storage_at(&self, address: &Address, position: &H256, id: BlockID) -> Option<H256> { | ||||
| 	fn storage_at(&self, address: &Address, position: &H256, id: BlockId) -> Option<H256> { | ||||
| 		self.state_at(id).map(|s| s.storage_at(address, position)) | ||||
| 	} | ||||
| 
 | ||||
| 	fn list_accounts(&self, id: BlockID, after: Option<&Address>, count: u64) -> Option<Vec<Address>> { | ||||
| 	fn list_accounts(&self, id: BlockId, after: Option<&Address>, count: u64) -> Option<Vec<Address>> { | ||||
| 		if !self.factories.trie.is_fat() { | ||||
| 			trace!(target: "fatdb", "list_accounts: Not a fat DB"); | ||||
| 			return None; | ||||
| @ -1012,7 +1022,7 @@ impl BlockChainClient for Client { | ||||
| 		Some(accounts) | ||||
| 	} | ||||
| 
 | ||||
| 	fn list_storage(&self, id: BlockID, account: &Address, after: Option<&H256>, count: u64) -> Option<Vec<H256>> { | ||||
| 	fn list_storage(&self, id: BlockId, account: &Address, after: Option<&H256>, count: u64) -> Option<Vec<H256>> { | ||||
| 		if !self.factories.trie.is_fat() { | ||||
| 			trace!(target: "fatdb", "list_stroage: Not a fat DB"); | ||||
| 			return None; | ||||
| @ -1056,16 +1066,20 @@ impl BlockChainClient for Client { | ||||
| 		Some(keys) | ||||
| 	} | ||||
| 
 | ||||
| 	fn transaction(&self, id: TransactionID) -> Option<LocalizedTransaction> { | ||||
| 	fn transaction(&self, id: TransactionId) -> Option<LocalizedTransaction> { | ||||
| 		self.transaction_address(id).and_then(|address| self.chain.read().transaction(&address)) | ||||
| 	} | ||||
| 
 | ||||
| 	fn uncle(&self, id: UncleID) -> Option<Bytes> { | ||||
| 	fn transaction_block(&self, id: TransactionId) -> Option<H256> { | ||||
| 		self.transaction_address(id).map(|addr| addr.block_hash) | ||||
| 	} | ||||
| 
 | ||||
| 	fn uncle(&self, id: UncleId) -> Option<Bytes> { | ||||
| 		let index = id.position; | ||||
| 		self.block_body(id.block).and_then(|body| BodyView::new(&body).uncle_rlp_at(index)) | ||||
| 	} | ||||
| 
 | ||||
| 	fn transaction_receipt(&self, id: TransactionID) -> Option<LocalizedReceipt> { | ||||
| 	fn transaction_receipt(&self, id: TransactionId) -> Option<LocalizedReceipt> { | ||||
| 		let chain = self.chain.read(); | ||||
| 		self.transaction_address(id) | ||||
| 			.and_then(|address| chain.block_number(&address.block_hash).and_then(|block_number| { | ||||
| @ -1149,7 +1163,7 @@ impl BlockChainClient for Client { | ||||
| 			if self.chain.read().is_known(&unverified.hash()) { | ||||
| 				return Err(BlockImportError::Import(ImportError::AlreadyInChain)); | ||||
| 			} | ||||
| 			if self.block_status(BlockID::Hash(unverified.parent_hash())) == BlockStatus::Unknown { | ||||
| 			if self.block_status(BlockId::Hash(unverified.parent_hash())) == BlockStatus::Unknown { | ||||
| 				return Err(BlockImportError::Block(BlockError::UnknownParent(unverified.parent_hash()))); | ||||
| 			} | ||||
| 		} | ||||
| @ -1163,7 +1177,7 @@ impl BlockChainClient for Client { | ||||
| 			if self.chain.read().is_known(&header.hash()) { | ||||
| 				return Err(BlockImportError::Import(ImportError::AlreadyInChain)); | ||||
| 			} | ||||
| 			if self.block_status(BlockID::Hash(header.parent_hash())) == BlockStatus::Unknown { | ||||
| 			if self.block_status(BlockId::Hash(header.parent_hash())) == BlockStatus::Unknown { | ||||
| 				return Err(BlockImportError::Block(BlockError::UnknownParent(header.parent_hash()))); | ||||
| 			} | ||||
| 		} | ||||
| @ -1186,7 +1200,7 @@ impl BlockChainClient for Client { | ||||
| 		self.engine.additional_params().into_iter().collect() | ||||
| 	} | ||||
| 
 | ||||
| 	fn blocks_with_bloom(&self, bloom: &H2048, from_block: BlockID, to_block: BlockID) -> Option<Vec<BlockNumber>> { | ||||
| 	fn blocks_with_bloom(&self, bloom: &H2048, from_block: BlockId, to_block: BlockId) -> Option<Vec<BlockNumber>> { | ||||
| 		match (self.block_number(from_block), self.block_number(to_block)) { | ||||
| 			(Some(from), Some(to)) => Some(self.chain.read().blocks_with_bloom(bloom, from, to)), | ||||
| 			_ => None | ||||
| @ -1228,20 +1242,20 @@ impl BlockChainClient for Client { | ||||
| 		let trace_address = trace.address; | ||||
| 		self.transaction_address(trace.transaction) | ||||
| 			.and_then(|tx_address| { | ||||
| 				self.block_number(BlockID::Hash(tx_address.block_hash)) | ||||
| 				self.block_number(BlockId::Hash(tx_address.block_hash)) | ||||
| 					.and_then(|number| self.tracedb.read().trace(number, tx_address.index, trace_address)) | ||||
| 			}) | ||||
| 	} | ||||
| 
 | ||||
| 	fn transaction_traces(&self, transaction: TransactionID) -> Option<Vec<LocalizedTrace>> { | ||||
| 	fn transaction_traces(&self, transaction: TransactionId) -> Option<Vec<LocalizedTrace>> { | ||||
| 		self.transaction_address(transaction) | ||||
| 			.and_then(|tx_address| { | ||||
| 				self.block_number(BlockID::Hash(tx_address.block_hash)) | ||||
| 				self.block_number(BlockId::Hash(tx_address.block_hash)) | ||||
| 					.and_then(|number| self.tracedb.read().transaction_traces(number, tx_address.index)) | ||||
| 			}) | ||||
| 	} | ||||
| 
 | ||||
| 	fn block_traces(&self, block: BlockID) -> Option<Vec<LocalizedTrace>> { | ||||
| 	fn block_traces(&self, block: BlockId) -> Option<Vec<LocalizedTrace>> { | ||||
| 		self.block_number(block) | ||||
| 			.and_then(|number| self.tracedb.read().block_traces(number)) | ||||
| 	} | ||||
| @ -1276,13 +1290,13 @@ impl BlockChainClient for Client { | ||||
| 		self.engine.signing_network_id(&self.latest_env_info()) | ||||
| 	} | ||||
| 
 | ||||
| 	fn block_extra_info(&self, id: BlockID) -> Option<BTreeMap<String, String>> { | ||||
| 	fn block_extra_info(&self, id: BlockId) -> Option<BTreeMap<String, String>> { | ||||
| 		self.block_header(id) | ||||
| 			.map(|block| decode(&block)) | ||||
| 			.map(|header| self.engine.extra_info(&header)) | ||||
| 	} | ||||
| 
 | ||||
| 	fn uncle_extra_info(&self, id: UncleID) -> Option<BTreeMap<String, String>> { | ||||
| 	fn uncle_extra_info(&self, id: UncleId) -> Option<BTreeMap<String, String>> { | ||||
| 		self.uncle(id) | ||||
| 			.map(|header| self.engine.extra_info(&decode(&header))) | ||||
| 	} | ||||
| @ -1377,20 +1391,20 @@ impl MayPanic for Client { | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl ProvingBlockChainClient for Client { | ||||
| 	fn prove_storage(&self, key1: H256, key2: H256, from_level: u32, id: BlockID) -> Vec<Bytes> { | ||||
| impl ::client::ProvingBlockChainClient for Client { | ||||
| 	fn prove_storage(&self, key1: H256, key2: H256, from_level: u32, id: BlockId) -> Vec<Bytes> { | ||||
| 		self.state_at(id) | ||||
| 			.and_then(move |state| state.prove_storage(key1, key2, from_level).ok()) | ||||
| 			.unwrap_or_else(Vec::new) | ||||
| 	} | ||||
| 
 | ||||
| 	fn prove_account(&self, key1: H256, from_level: u32, id: BlockID) -> Vec<Bytes> { | ||||
| 	fn prove_account(&self, key1: H256, from_level: u32, id: BlockId) -> Vec<Bytes> { | ||||
| 		self.state_at(id) | ||||
| 			.and_then(move |state| state.prove_account(key1, from_level).ok()) | ||||
| 			.unwrap_or_else(Vec::new) | ||||
| 	} | ||||
| 
 | ||||
| 	fn code_by_hash(&self, account_key: H256, id: BlockID) -> Bytes { | ||||
| 	fn code_by_hash(&self, account_key: H256, id: BlockId) -> Bytes { | ||||
| 		self.state_at(id) | ||||
| 			.and_then(move |state| state.code_by_address_hash(account_key).ok()) | ||||
| 			.and_then(|x| x) | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
| @ -24,8 +24,8 @@ use devtools::*; | ||||
| use transaction::{Transaction, LocalizedTransaction, SignedTransaction, Action}; | ||||
| use blockchain::TreeRoute; | ||||
| use client::{ | ||||
| 	BlockChainClient, MiningBlockChainClient, BlockChainInfo, BlockStatus, BlockID, | ||||
| 	TransactionID, UncleID, TraceId, TraceFilter, LastHashes, CallAnalytics, BlockImportError, | ||||
| 	BlockChainClient, MiningBlockChainClient, BlockChainInfo, BlockStatus, BlockId, | ||||
| 	TransactionId, UncleId, TraceId, TraceFilter, LastHashes, CallAnalytics, BlockImportError, | ||||
| }; | ||||
| use db::{NUM_COLUMNS, COL_STATE}; | ||||
| use header::{Header as BlockHeader, BlockNumber}; | ||||
| @ -73,7 +73,7 @@ pub struct TestBlockChainClient { | ||||
| 	/// Execution result.
 | ||||
| 	pub execution_result: RwLock<Option<Result<Executed, CallError>>>, | ||||
| 	/// Transaction receipts.
 | ||||
| 	pub receipts: RwLock<HashMap<TransactionID, LocalizedReceipt>>, | ||||
| 	pub receipts: RwLock<HashMap<TransactionId, LocalizedReceipt>>, | ||||
| 	/// Logs
 | ||||
| 	pub logs: RwLock<Vec<LocalizedLogEntry>>, | ||||
| 	/// Block queue size.
 | ||||
| @ -92,8 +92,8 @@ pub struct TestBlockChainClient { | ||||
| 	pub first_block: RwLock<Option<(H256, u64)>>, | ||||
| } | ||||
| 
 | ||||
| #[derive(Clone)] | ||||
| /// Used for generating test client blocks.
 | ||||
| #[derive(Clone)] | ||||
| pub enum EachBlockWith { | ||||
| 	/// Plain block.
 | ||||
| 	Nothing, | ||||
| @ -158,7 +158,7 @@ impl TestBlockChainClient { | ||||
| 	} | ||||
| 
 | ||||
| 	/// Set the transaction receipt result
 | ||||
| 	pub fn set_transaction_receipt(&self, id: TransactionID, receipt: LocalizedReceipt) { | ||||
| 	pub fn set_transaction_receipt(&self, id: TransactionId, receipt: LocalizedReceipt) { | ||||
| 		self.receipts.write().insert(id, receipt); | ||||
| 	} | ||||
| 
 | ||||
| @ -255,9 +255,9 @@ impl TestBlockChainClient { | ||||
| 	} | ||||
| 
 | ||||
| 	/// Make a bad block by setting invalid extra data.
 | ||||
| 	pub fn corrupt_block(&mut self, n: BlockNumber) { | ||||
| 		let hash = self.block_hash(BlockID::Number(n)).unwrap(); | ||||
| 		let mut header: BlockHeader = decode(&self.block_header(BlockID::Number(n)).unwrap()); | ||||
| 	pub fn corrupt_block(&self, n: BlockNumber) { | ||||
| 		let hash = self.block_hash(BlockId::Number(n)).unwrap(); | ||||
| 		let mut header: BlockHeader = decode(&self.block_header(BlockId::Number(n)).unwrap()); | ||||
| 		header.set_extra_data(b"This extra data is way too long to be considered valid".to_vec()); | ||||
| 		let mut rlp = RlpStream::new_list(3); | ||||
| 		rlp.append(&header); | ||||
| @ -267,9 +267,9 @@ impl TestBlockChainClient { | ||||
| 	} | ||||
| 
 | ||||
| 	/// Make a bad block by setting invalid parent hash.
 | ||||
| 	pub fn corrupt_block_parent(&mut self, n: BlockNumber) { | ||||
| 		let hash = self.block_hash(BlockID::Number(n)).unwrap(); | ||||
| 		let mut header: BlockHeader = decode(&self.block_header(BlockID::Number(n)).unwrap()); | ||||
| 	pub fn corrupt_block_parent(&self, n: BlockNumber) { | ||||
| 		let hash = self.block_hash(BlockId::Number(n)).unwrap(); | ||||
| 		let mut header: BlockHeader = decode(&self.block_header(BlockId::Number(n)).unwrap()); | ||||
| 		header.set_parent_hash(H256::from(42)); | ||||
| 		let mut rlp = RlpStream::new_list(3); | ||||
| 		rlp.append(&header); | ||||
| @ -285,12 +285,12 @@ impl TestBlockChainClient { | ||||
| 		blocks_read[&index].clone() | ||||
| 	} | ||||
| 
 | ||||
| 	fn block_hash(&self, id: BlockID) -> Option<H256> { | ||||
| 	fn block_hash(&self, id: BlockId) -> Option<H256> { | ||||
| 		match id { | ||||
| 			BlockID::Hash(hash) => Some(hash), | ||||
| 			BlockID::Number(n) => self.numbers.read().get(&(n as usize)).cloned(), | ||||
| 			BlockID::Earliest => self.numbers.read().get(&0).cloned(), | ||||
| 			BlockID::Latest | BlockID::Pending => self.numbers.read().get(&(self.numbers.read().len() - 1)).cloned() | ||||
| 			BlockId::Hash(hash) => Some(hash), | ||||
| 			BlockId::Number(n) => self.numbers.read().get(&(n as usize)).cloned(), | ||||
| 			BlockId::Earliest => self.numbers.read().get(&0).cloned(), | ||||
| 			BlockId::Latest | BlockId::Pending => self.numbers.read().get(&(self.numbers.read().len() - 1)).cloned() | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| @ -363,46 +363,46 @@ impl MiningBlockChainClient for TestBlockChainClient { | ||||
| } | ||||
| 
 | ||||
| impl BlockChainClient for TestBlockChainClient { | ||||
| 	fn call(&self, _t: &SignedTransaction, _block: BlockID, _analytics: CallAnalytics) -> Result<Executed, CallError> { | ||||
| 	fn call(&self, _t: &SignedTransaction, _block: BlockId, _analytics: CallAnalytics) -> Result<Executed, CallError> { | ||||
| 		self.execution_result.read().clone().unwrap() | ||||
| 	} | ||||
| 
 | ||||
| 	fn replay(&self, _id: TransactionID, _analytics: CallAnalytics) -> Result<Executed, CallError> { | ||||
| 	fn replay(&self, _id: TransactionId, _analytics: CallAnalytics) -> Result<Executed, CallError> { | ||||
| 		self.execution_result.read().clone().unwrap() | ||||
| 	} | ||||
| 
 | ||||
| 	fn block_total_difficulty(&self, _id: BlockID) -> Option<U256> { | ||||
| 	fn block_total_difficulty(&self, _id: BlockId) -> Option<U256> { | ||||
| 		Some(U256::zero()) | ||||
| 	} | ||||
| 
 | ||||
| 	fn block_hash(&self, id: BlockID) -> Option<H256> { | ||||
| 	fn block_hash(&self, id: BlockId) -> Option<H256> { | ||||
| 		Self::block_hash(self, id) | ||||
| 	} | ||||
| 
 | ||||
| 	fn nonce(&self, address: &Address, id: BlockID) -> Option<U256> { | ||||
| 	fn nonce(&self, address: &Address, id: BlockId) -> Option<U256> { | ||||
| 		match id { | ||||
| 			BlockID::Latest => Some(self.nonces.read().get(address).cloned().unwrap_or(self.spec.params.account_start_nonce)), | ||||
| 			BlockId::Latest => Some(self.nonces.read().get(address).cloned().unwrap_or(self.spec.params.account_start_nonce)), | ||||
| 			_ => None, | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	fn storage_root(&self, _address: &Address, _id: BlockID) -> Option<H256> { | ||||
| 	fn storage_root(&self, _address: &Address, _id: BlockId) -> Option<H256> { | ||||
| 		None | ||||
| 	} | ||||
| 
 | ||||
| 	fn latest_nonce(&self, address: &Address) -> U256 { | ||||
| 		self.nonce(address, BlockID::Latest).unwrap() | ||||
| 		self.nonce(address, BlockId::Latest).unwrap() | ||||
| 	} | ||||
| 
 | ||||
| 	fn code(&self, address: &Address, id: BlockID) -> Option<Option<Bytes>> { | ||||
| 	fn code(&self, address: &Address, id: BlockId) -> Option<Option<Bytes>> { | ||||
| 		match id { | ||||
| 			BlockID::Latest => Some(self.code.read().get(address).cloned()), | ||||
| 			BlockId::Latest => Some(self.code.read().get(address).cloned()), | ||||
| 			_ => None, | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	fn balance(&self, address: &Address, id: BlockID) -> Option<U256> { | ||||
| 		if let BlockID::Latest = id { | ||||
| 	fn balance(&self, address: &Address, id: BlockId) -> Option<U256> { | ||||
| 		if let BlockId::Latest = id { | ||||
| 			Some(self.balances.read().get(address).cloned().unwrap_or_else(U256::zero)) | ||||
| 		} else { | ||||
| 			None | ||||
| @ -410,41 +410,45 @@ impl BlockChainClient for TestBlockChainClient { | ||||
| 	} | ||||
| 
 | ||||
| 	fn latest_balance(&self, address: &Address) -> U256 { | ||||
| 		self.balance(address, BlockID::Latest).unwrap() | ||||
| 		self.balance(address, BlockId::Latest).unwrap() | ||||
| 	} | ||||
| 
 | ||||
| 	fn storage_at(&self, address: &Address, position: &H256, id: BlockID) -> Option<H256> { | ||||
| 		if let BlockID::Latest = id { | ||||
| 	fn storage_at(&self, address: &Address, position: &H256, id: BlockId) -> Option<H256> { | ||||
| 		if let BlockId::Latest = id { | ||||
| 			Some(self.storage.read().get(&(address.clone(), position.clone())).cloned().unwrap_or_else(H256::new)) | ||||
| 		} else { | ||||
| 			None | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	fn list_accounts(&self, _id: BlockID, _after: Option<&Address>, _count: u64) -> Option<Vec<Address>> { | ||||
| 	fn list_accounts(&self, _id: BlockId, _after: Option<&Address>, _count: u64) -> Option<Vec<Address>> { | ||||
| 		None | ||||
| 	} | ||||
| 
 | ||||
| 	fn list_storage(&self, _id: BlockID, _account: &Address, _after: Option<&H256>, _count: u64) -> Option<Vec<H256>> { | ||||
| 	fn list_storage(&self, _id: BlockId, _account: &Address, _after: Option<&H256>, _count: u64) -> Option<Vec<H256>> { | ||||
| 		None | ||||
| 	} | ||||
| 	fn transaction(&self, _id: TransactionID) -> Option<LocalizedTransaction> { | ||||
| 	fn transaction(&self, _id: TransactionId) -> Option<LocalizedTransaction> { | ||||
| 		None	// Simple default.
 | ||||
| 	} | ||||
| 
 | ||||
| 	fn uncle(&self, _id: UncleID) -> Option<Bytes> { | ||||
| 	fn transaction_block(&self, _id: TransactionId) -> Option<H256> { | ||||
| 		None	// Simple default.
 | ||||
| 	} | ||||
| 
 | ||||
| 	fn uncle_extra_info(&self, _id: UncleID) -> Option<BTreeMap<String, String>> { | ||||
| 	fn uncle(&self, _id: UncleId) -> Option<Bytes> { | ||||
| 		None	// Simple default.
 | ||||
| 	} | ||||
| 
 | ||||
| 	fn uncle_extra_info(&self, _id: UncleId) -> Option<BTreeMap<String, String>> { | ||||
| 		None | ||||
| 	} | ||||
| 
 | ||||
| 	fn transaction_receipt(&self, id: TransactionID) -> Option<LocalizedReceipt> { | ||||
| 	fn transaction_receipt(&self, id: TransactionId) -> Option<LocalizedReceipt> { | ||||
| 		self.receipts.read().get(&id).cloned() | ||||
| 	} | ||||
| 
 | ||||
| 	fn blocks_with_bloom(&self, _bloom: &H2048, _from_block: BlockID, _to_block: BlockID) -> Option<Vec<BlockNumber>> { | ||||
| 	fn blocks_with_bloom(&self, _bloom: &H2048, _from_block: BlockId, _to_block: BlockId) -> Option<Vec<BlockNumber>> { | ||||
| 		unimplemented!(); | ||||
| 	} | ||||
| 
 | ||||
| @ -462,14 +466,14 @@ impl BlockChainClient for TestBlockChainClient { | ||||
| 	} | ||||
| 
 | ||||
| 	fn best_block_header(&self) -> Bytes { | ||||
| 		self.block_header(BlockID::Hash(self.chain_info().best_block_hash)).expect("Best block always have header.") | ||||
| 		self.block_header(BlockId::Hash(self.chain_info().best_block_hash)).expect("Best block always have header.") | ||||
| 	} | ||||
| 
 | ||||
| 	fn block_header(&self, id: BlockID) -> Option<Bytes> { | ||||
| 	fn block_header(&self, id: BlockId) -> Option<Bytes> { | ||||
| 		self.block_hash(id).and_then(|hash| self.blocks.read().get(&hash).map(|r| Rlp::new(r).at(0).as_raw().to_vec())) | ||||
| 	} | ||||
| 
 | ||||
| 	fn block_body(&self, id: BlockID) -> Option<Bytes> { | ||||
| 	fn block_body(&self, id: BlockId) -> Option<Bytes> { | ||||
| 		self.block_hash(id).and_then(|hash| self.blocks.read().get(&hash).map(|r| { | ||||
| 			let mut stream = RlpStream::new_list(2); | ||||
| 			stream.append_raw(Rlp::new(r).at(1).as_raw(), 1); | ||||
| @ -478,21 +482,21 @@ impl BlockChainClient for TestBlockChainClient { | ||||
| 		})) | ||||
| 	} | ||||
| 
 | ||||
| 	fn block(&self, id: BlockID) -> Option<Bytes> { | ||||
| 	fn block(&self, id: BlockId) -> Option<Bytes> { | ||||
| 		self.block_hash(id).and_then(|hash| self.blocks.read().get(&hash).cloned()) | ||||
| 	} | ||||
| 
 | ||||
| 	fn block_extra_info(&self, id: BlockID) -> Option<BTreeMap<String, String>> { | ||||
| 	fn block_extra_info(&self, id: BlockId) -> Option<BTreeMap<String, String>> { | ||||
| 		self.block(id) | ||||
| 			.map(|block| BlockView::new(&block).header()) | ||||
| 			.map(|header| self.spec.engine.extra_info(&header)) | ||||
| 	} | ||||
| 
 | ||||
| 
 | ||||
| 	fn block_status(&self, id: BlockID) -> BlockStatus { | ||||
| 	fn block_status(&self, id: BlockId) -> BlockStatus { | ||||
| 		match id { | ||||
| 			BlockID::Number(number) if (number as usize) < self.blocks.read().len() => BlockStatus::InChain, | ||||
| 			BlockID::Hash(ref hash) if self.blocks.read().get(hash).is_some() => BlockStatus::InChain, | ||||
| 			BlockId::Number(number) if (number as usize) < self.blocks.read().len() => BlockStatus::InChain, | ||||
| 			BlockId::Hash(ref hash) if self.blocks.read().get(hash).is_some() => BlockStatus::InChain, | ||||
| 			_ => BlockStatus::Unknown | ||||
| 		} | ||||
| 	} | ||||
| @ -645,11 +649,11 @@ impl BlockChainClient for TestBlockChainClient { | ||||
| 		unimplemented!(); | ||||
| 	} | ||||
| 
 | ||||
| 	fn transaction_traces(&self, _trace: TransactionID) -> Option<Vec<LocalizedTrace>> { | ||||
| 	fn transaction_traces(&self, _trace: TransactionId) -> Option<Vec<LocalizedTrace>> { | ||||
| 		unimplemented!(); | ||||
| 	} | ||||
| 
 | ||||
| 	fn block_traces(&self, _trace: BlockID) -> Option<Vec<LocalizedTrace>> { | ||||
| 	fn block_traces(&self, _trace: BlockId) -> Option<Vec<LocalizedTrace>> { | ||||
| 		unimplemented!(); | ||||
| 	} | ||||
| 
 | ||||
|  | ||||
| @ -50,90 +50,93 @@ pub trait BlockChainClient : Sync + Send { | ||||
| 	fn keep_alive(&self) {} | ||||
| 
 | ||||
| 	/// Get raw block header data by block id.
 | ||||
| 	fn block_header(&self, id: BlockID) -> Option<Bytes>; | ||||
| 	fn block_header(&self, id: BlockId) -> Option<Bytes>; | ||||
| 
 | ||||
| 	/// Get raw block body data by block id.
 | ||||
| 	/// Block body is an RLP list of two items: uncles and transactions.
 | ||||
| 	fn block_body(&self, id: BlockID) -> Option<Bytes>; | ||||
| 	fn block_body(&self, id: BlockId) -> Option<Bytes>; | ||||
| 
 | ||||
| 	/// Get raw block data by block header hash.
 | ||||
| 	fn block(&self, id: BlockID) -> Option<Bytes>; | ||||
| 	fn block(&self, id: BlockId) -> Option<Bytes>; | ||||
| 
 | ||||
| 	/// Get block status by block header hash.
 | ||||
| 	fn block_status(&self, id: BlockID) -> BlockStatus; | ||||
| 	fn block_status(&self, id: BlockId) -> BlockStatus; | ||||
| 
 | ||||
| 	/// Get block total difficulty.
 | ||||
| 	fn block_total_difficulty(&self, id: BlockID) -> Option<U256>; | ||||
| 	fn block_total_difficulty(&self, id: BlockId) -> Option<U256>; | ||||
| 
 | ||||
| 	/// Attempt to get address nonce at given block.
 | ||||
| 	/// May not fail on BlockID::Latest.
 | ||||
| 	fn nonce(&self, address: &Address, id: BlockID) -> Option<U256>; | ||||
| 	/// May not fail on BlockId::Latest.
 | ||||
| 	fn nonce(&self, address: &Address, id: BlockId) -> Option<U256>; | ||||
| 
 | ||||
| 	/// Attempt to get address storage root at given block.
 | ||||
| 	/// May not fail on BlockID::Latest.
 | ||||
| 	fn storage_root(&self, address: &Address, id: BlockID) -> Option<H256>; | ||||
| 	/// May not fail on BlockId::Latest.
 | ||||
| 	fn storage_root(&self, address: &Address, id: BlockId) -> Option<H256>; | ||||
| 
 | ||||
| 	/// Get address nonce at the latest block's state.
 | ||||
| 	fn latest_nonce(&self, address: &Address) -> U256 { | ||||
| 		self.nonce(address, BlockID::Latest) | ||||
| 			.expect("nonce will return Some when given BlockID::Latest. nonce was given BlockID::Latest. \ | ||||
| 		self.nonce(address, BlockId::Latest) | ||||
| 			.expect("nonce will return Some when given BlockId::Latest. nonce was given BlockId::Latest. \ | ||||
| 			Therefore nonce has returned Some; qed")
 | ||||
| 	} | ||||
| 
 | ||||
| 	/// Get block hash.
 | ||||
| 	fn block_hash(&self, id: BlockID) -> Option<H256>; | ||||
| 	fn block_hash(&self, id: BlockId) -> Option<H256>; | ||||
| 
 | ||||
| 	/// Get address code at given block's state.
 | ||||
| 	fn code(&self, address: &Address, id: BlockID) -> Option<Option<Bytes>>; | ||||
| 	fn code(&self, address: &Address, id: BlockId) -> Option<Option<Bytes>>; | ||||
| 
 | ||||
| 	/// Get address code at the latest block's state.
 | ||||
| 	fn latest_code(&self, address: &Address) -> Option<Bytes> { | ||||
| 		self.code(address, BlockID::Latest) | ||||
| 			.expect("code will return Some if given BlockID::Latest; qed") | ||||
| 		self.code(address, BlockId::Latest) | ||||
| 			.expect("code will return Some if given BlockId::Latest; qed") | ||||
| 	} | ||||
| 
 | ||||
| 	/// Get address balance at the given block's state.
 | ||||
| 	///
 | ||||
| 	/// May not return None if given BlockID::Latest.
 | ||||
| 	/// May not return None if given BlockId::Latest.
 | ||||
| 	/// Returns None if and only if the block's root hash has been pruned from the DB.
 | ||||
| 	fn balance(&self, address: &Address, id: BlockID) -> Option<U256>; | ||||
| 	fn balance(&self, address: &Address, id: BlockId) -> Option<U256>; | ||||
| 
 | ||||
| 	/// Get address balance at the latest block's state.
 | ||||
| 	fn latest_balance(&self, address: &Address) -> U256 { | ||||
| 		self.balance(address, BlockID::Latest) | ||||
| 			.expect("balance will return Some if given BlockID::Latest. balance was given BlockID::Latest \ | ||||
| 		self.balance(address, BlockId::Latest) | ||||
| 			.expect("balance will return Some if given BlockId::Latest. balance was given BlockId::Latest \ | ||||
| 			Therefore balance has returned Some; qed")
 | ||||
| 	} | ||||
| 
 | ||||
| 	/// Get value of the storage at given position at the given block's state.
 | ||||
| 	///
 | ||||
| 	/// May not return None if given BlockID::Latest.
 | ||||
| 	/// May not return None if given BlockId::Latest.
 | ||||
| 	/// Returns None if and only if the block's root hash has been pruned from the DB.
 | ||||
| 	fn storage_at(&self, address: &Address, position: &H256, id: BlockID) -> Option<H256>; | ||||
| 	fn storage_at(&self, address: &Address, position: &H256, id: BlockId) -> Option<H256>; | ||||
| 
 | ||||
| 	/// Get value of the storage at given position at the latest block's state.
 | ||||
| 	fn latest_storage_at(&self, address: &Address, position: &H256) -> H256 { | ||||
| 		self.storage_at(address, position, BlockID::Latest) | ||||
| 			.expect("storage_at will return Some if given BlockID::Latest. storage_at was given BlockID::Latest. \ | ||||
| 		self.storage_at(address, position, BlockId::Latest) | ||||
| 			.expect("storage_at will return Some if given BlockId::Latest. storage_at was given BlockId::Latest. \ | ||||
| 			Therefore storage_at has returned Some; qed")
 | ||||
| 	} | ||||
| 
 | ||||
| 	/// Get a list of all accounts in the block `id`, if fat DB is in operation, otherwise `None`.
 | ||||
| 	/// If `after` is set the list starts with the following item.
 | ||||
| 	fn list_accounts(&self, id: BlockID, after: Option<&Address>, count: u64) -> Option<Vec<Address>>; | ||||
| 	fn list_accounts(&self, id: BlockId, after: Option<&Address>, count: u64) -> Option<Vec<Address>>; | ||||
| 
 | ||||
| 	/// Get a list of all storage keys in the block `id`, if fat DB is in operation, otherwise `None`.
 | ||||
| 	/// If `after` is set the list starts with the following item.
 | ||||
| 	fn list_storage(&self, id: BlockID, account: &Address, after: Option<&H256>, count: u64) -> Option<Vec<H256>>; | ||||
| 	fn list_storage(&self, id: BlockId, account: &Address, after: Option<&H256>, count: u64) -> Option<Vec<H256>>; | ||||
| 
 | ||||
| 	/// Get transaction with given hash.
 | ||||
| 	fn transaction(&self, id: TransactionID) -> Option<LocalizedTransaction>; | ||||
| 	fn transaction(&self, id: TransactionId) -> Option<LocalizedTransaction>; | ||||
| 
 | ||||
| 	/// Get the hash of block that contains the transaction, if any.
 | ||||
| 	fn transaction_block(&self, id: TransactionId) -> Option<H256>; | ||||
| 
 | ||||
| 	/// Get uncle with given id.
 | ||||
| 	fn uncle(&self, id: UncleID) -> Option<Bytes>; | ||||
| 	fn uncle(&self, id: UncleId) -> Option<Bytes>; | ||||
| 
 | ||||
| 	/// Get transaction receipt with given hash.
 | ||||
| 	fn transaction_receipt(&self, id: TransactionID) -> Option<LocalizedReceipt>; | ||||
| 	fn transaction_receipt(&self, id: TransactionId) -> Option<LocalizedReceipt>; | ||||
| 
 | ||||
| 	/// Get a tree route between `from` and `to`.
 | ||||
| 	/// See `BlockChain::tree_route`.
 | ||||
| @ -170,16 +173,16 @@ pub trait BlockChainClient : Sync + Send { | ||||
| 	fn best_block_header(&self) -> Bytes; | ||||
| 
 | ||||
| 	/// Returns numbers of blocks containing given bloom.
 | ||||
| 	fn blocks_with_bloom(&self, bloom: &H2048, from_block: BlockID, to_block: BlockID) -> Option<Vec<BlockNumber>>; | ||||
| 	fn blocks_with_bloom(&self, bloom: &H2048, from_block: BlockId, to_block: BlockId) -> Option<Vec<BlockNumber>>; | ||||
| 
 | ||||
| 	/// Returns logs matching given filter.
 | ||||
| 	fn logs(&self, filter: Filter) -> Vec<LocalizedLogEntry>; | ||||
| 
 | ||||
| 	/// Makes a non-persistent transaction call.
 | ||||
| 	fn call(&self, t: &SignedTransaction, block: BlockID, analytics: CallAnalytics) -> Result<Executed, CallError>; | ||||
| 	fn call(&self, t: &SignedTransaction, block: BlockId, analytics: CallAnalytics) -> Result<Executed, CallError>; | ||||
| 
 | ||||
| 	/// Replays a given transaction for inspection.
 | ||||
| 	fn replay(&self, t: TransactionID, analytics: CallAnalytics) -> Result<Executed, CallError>; | ||||
| 	fn replay(&self, t: TransactionId, analytics: CallAnalytics) -> Result<Executed, CallError>; | ||||
| 
 | ||||
| 	/// Returns traces matching given filter.
 | ||||
| 	fn filter_traces(&self, filter: TraceFilter) -> Option<Vec<LocalizedTrace>>; | ||||
| @ -188,10 +191,10 @@ pub trait BlockChainClient : Sync + Send { | ||||
| 	fn trace(&self, trace: TraceId) -> Option<LocalizedTrace>; | ||||
| 
 | ||||
| 	/// Returns traces created by transaction.
 | ||||
| 	fn transaction_traces(&self, trace: TransactionID) -> Option<Vec<LocalizedTrace>>; | ||||
| 	fn transaction_traces(&self, trace: TransactionId) -> Option<Vec<LocalizedTrace>>; | ||||
| 
 | ||||
| 	/// Returns traces created by transaction from block.
 | ||||
| 	fn block_traces(&self, trace: BlockID) -> Option<Vec<LocalizedTrace>>; | ||||
| 	fn block_traces(&self, trace: BlockId) -> Option<Vec<LocalizedTrace>>; | ||||
| 
 | ||||
| 	/// Get last hashes starting from best block.
 | ||||
| 	fn last_hashes(&self) -> LastHashes; | ||||
| @ -208,7 +211,7 @@ pub trait BlockChainClient : Sync + Send { | ||||
| 		let mut corpus = Vec::new(); | ||||
| 		while corpus.is_empty() { | ||||
| 			for _ in 0..sample_size { | ||||
| 				let block_bytes = self.block(BlockID::Hash(h)).expect("h is either the best_block_hash or an ancestor; qed"); | ||||
| 				let block_bytes = self.block(BlockId::Hash(h)).expect("h is either the best_block_hash or an ancestor; qed"); | ||||
| 				let block = BlockView::new(&block_bytes); | ||||
| 				let header = block.header_view(); | ||||
| 				if header.number() == 0 { | ||||
| @ -246,11 +249,11 @@ pub trait BlockChainClient : Sync + Send { | ||||
| 	/// Set the mode.
 | ||||
| 	fn set_mode(&self, mode: Mode); | ||||
| 
 | ||||
| 	/// Returns engine-related extra info for `BlockID`.
 | ||||
| 	fn block_extra_info(&self, id: BlockID) -> Option<BTreeMap<String, String>>; | ||||
| 	/// Returns engine-related extra info for `BlockId`.
 | ||||
| 	fn block_extra_info(&self, id: BlockId) -> Option<BTreeMap<String, String>>; | ||||
| 
 | ||||
| 	/// Returns engine-related extra info for `UncleID`.
 | ||||
| 	fn uncle_extra_info(&self, id: UncleID) -> Option<BTreeMap<String, String>>; | ||||
| 	/// Returns engine-related extra info for `UncleId`.
 | ||||
| 	fn uncle_extra_info(&self, id: UncleId) -> Option<BTreeMap<String, String>>; | ||||
| 
 | ||||
| 	/// Returns information about pruning/data availability.
 | ||||
| 	fn pruning_info(&self) -> PruningInfo; | ||||
| @ -285,15 +288,15 @@ pub trait ProvingBlockChainClient: BlockChainClient { | ||||
| 	/// Returns a vector of raw trie nodes (in order from the root) proving the storage query.
 | ||||
| 	/// Nodes after `from_level` may be omitted.
 | ||||
| 	/// An empty vector indicates unservable query.
 | ||||
| 	fn prove_storage(&self, key1: H256, key2: H256, from_level: u32, id: BlockID) -> Vec<Bytes>; | ||||
| 	fn prove_storage(&self, key1: H256, key2: H256, from_level: u32, id: BlockId) -> Vec<Bytes>; | ||||
| 
 | ||||
| 	/// Prove account existence at a specific block id.
 | ||||
| 	/// The key is the keccak hash of the account's address.
 | ||||
| 	/// Returns a vector of raw trie nodes (in order from the root) proving the query.
 | ||||
| 	/// Nodes after `from_level` may be omitted.
 | ||||
| 	/// An empty vector indicates unservable query.	
 | ||||
| 	fn prove_account(&self, key1: H256, from_level: u32, id: BlockID) -> Vec<Bytes>; | ||||
| 	fn prove_account(&self, key1: H256, from_level: u32, id: BlockId) -> Vec<Bytes>; | ||||
| 
 | ||||
| 	/// Get code by address hash.
 | ||||
| 	fn code_by_hash(&self, account_key: H256, id: BlockID) -> Bytes; | ||||
| 	fn code_by_hash(&self, account_key: H256, id: BlockId) -> Bytes; | ||||
| } | ||||
| @ -49,6 +49,8 @@ pub struct AuthorityRoundParams { | ||||
| 	pub authorities: Vec<Address>, | ||||
| 	/// Number of authorities.
 | ||||
| 	pub authority_n: usize, | ||||
| 	/// Starting step,
 | ||||
| 	pub start_step: Option<u64>, | ||||
| } | ||||
| 
 | ||||
| impl From<ethjson::spec::AuthorityRoundParams> for AuthorityRoundParams { | ||||
| @ -58,6 +60,7 @@ impl From<ethjson::spec::AuthorityRoundParams> for AuthorityRoundParams { | ||||
| 			step_duration: Duration::from_secs(p.step_duration.into()), | ||||
| 			authority_n: p.authorities.len(), | ||||
| 			authorities: p.authorities.into_iter().map(Into::into).collect::<Vec<_>>(), | ||||
| 			start_step: p.start_step.map(Into::into), | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @ -97,7 +100,7 @@ impl AsMillis for Duration { | ||||
| impl AuthorityRound { | ||||
| 	/// Create a new instance of AuthorityRound engine.
 | ||||
| 	pub fn new(params: CommonParams, our_params: AuthorityRoundParams, builtins: BTreeMap<Address, Builtin>) -> Result<Arc<Self>, Error> { | ||||
| 		let initial_step = (unix_now().as_secs() / our_params.step_duration.as_secs()) as usize; | ||||
| 		let initial_step = our_params.start_step.unwrap_or_else(|| (unix_now().as_secs() / our_params.step_duration.as_secs())) as usize; | ||||
| 		let engine = Arc::new( | ||||
| 			AuthorityRound { | ||||
| 				params: params, | ||||
| @ -160,14 +163,7 @@ impl IoHandler<()> for TransitionHandler { | ||||
| 	fn timeout(&self, io: &IoContext<()>, timer: TimerToken) { | ||||
| 		if timer == ENGINE_TIMEOUT_TOKEN { | ||||
| 			if let Some(engine) = self.engine.upgrade() { | ||||
| 				engine.step.fetch_add(1, AtomicOrdering::SeqCst); | ||||
| 				engine.proposed.store(false, AtomicOrdering::SeqCst); | ||||
| 				if let Some(ref channel) = *engine.message_channel.lock() { | ||||
| 					match channel.send(ClientIoMessage::UpdateSealing) { | ||||
| 						Ok(_) => trace!(target: "poa", "timeout: UpdateSealing message sent for step {}.", engine.step.load(AtomicOrdering::Relaxed)), | ||||
| 						Err(err) => trace!(target: "poa", "timeout: Could not send a sealing message {} for step {}.", err, engine.step.load(AtomicOrdering::Relaxed)), | ||||
| 					} | ||||
| 				} | ||||
| 				engine.step(); | ||||
| 				io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.remaining_step_duration().as_millis()) | ||||
| 					.unwrap_or_else(|e| warn!(target: "poa", "Failed to restart consensus step timer: {}.", e)) | ||||
| 			} | ||||
| @ -184,6 +180,17 @@ impl Engine for AuthorityRound { | ||||
| 	fn params(&self) -> &CommonParams { &self.params } | ||||
| 	fn builtins(&self) -> &BTreeMap<Address, Builtin> { &self.builtins } | ||||
| 
 | ||||
| 	fn step(&self) { | ||||
| 		self.step.fetch_add(1, AtomicOrdering::SeqCst); | ||||
| 		self.proposed.store(false, AtomicOrdering::SeqCst); | ||||
| 		if let Some(ref channel) = *self.message_channel.lock() { | ||||
| 			match channel.send(ClientIoMessage::UpdateSealing) { | ||||
| 				Ok(_) => trace!(target: "poa", "timeout: UpdateSealing message sent for step {}.", self.step.load(AtomicOrdering::Relaxed)), | ||||
| 				Err(err) => trace!(target: "poa", "timeout: Could not send a sealing message {} for step {}.", err, self.step.load(AtomicOrdering::Relaxed)), | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	/// Additional engine-specific information for the user/developer concerning `header`.
 | ||||
| 	fn extra_info(&self, header: &Header) -> BTreeMap<String, String> { | ||||
| 		map![ | ||||
| @ -235,6 +242,8 @@ impl Engine for AuthorityRound { | ||||
| 			} else { | ||||
| 				warn!(target: "poa", "generate_seal: FAIL: Accounts not provided."); | ||||
| 			} | ||||
| 		} else { | ||||
| 			trace!(target: "poa", "generate_seal: Not a proposer for step {}.", step); | ||||
| 		} | ||||
| 		None | ||||
| 	} | ||||
| @ -338,7 +347,6 @@ mod tests { | ||||
| 	use tests::helpers::*; | ||||
| 	use account_provider::AccountProvider; | ||||
| 	use spec::Spec; | ||||
| 	use std::time::UNIX_EPOCH; | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn has_valid_metadata() { | ||||
| @ -433,13 +441,30 @@ mod tests { | ||||
| 		let engine = Spec::new_test_round().engine; | ||||
| 
 | ||||
| 		let signature = tap.sign(addr, Some("0".into()), header.bare_hash()).unwrap(); | ||||
| 		let time = UNIX_EPOCH.elapsed().unwrap().as_secs(); | ||||
| 		// Two authorities.
 | ||||
| 		let mut step =  time - time % 2; | ||||
| 		header.set_seal(vec![encode(&step).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); | ||||
| 		// Spec starts with step 2.
 | ||||
| 		header.set_seal(vec![encode(&2usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); | ||||
| 		assert!(engine.verify_block_seal(&header).is_err()); | ||||
| 		step = step + 1; | ||||
| 		header.set_seal(vec![encode(&step).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); | ||||
| 		header.set_seal(vec![encode(&1usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); | ||||
| 		assert!(engine.verify_block_seal(&header).is_ok()); | ||||
| 	} | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn rejects_future_block() { | ||||
| 		let mut header: Header = Header::default(); | ||||
| 		let tap = AccountProvider::transient_provider(); | ||||
| 		let addr = tap.insert_account("0".sha3(), "0").unwrap(); | ||||
| 
 | ||||
| 		header.set_author(addr); | ||||
| 
 | ||||
| 		let engine = Spec::new_test_round().engine; | ||||
| 
 | ||||
| 		let signature = tap.sign(addr, Some("0".into()), header.bare_hash()).unwrap(); | ||||
| 		// Two authorities.
 | ||||
| 		// Spec starts with step 2.
 | ||||
| 		header.set_seal(vec![encode(&1usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); | ||||
| 		assert!(engine.verify_block_seal(&header).is_ok()); | ||||
| 		header.set_seal(vec![encode(&5usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); | ||||
| 		assert!(engine.verify_block_seal(&header).is_err()); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -160,4 +160,6 @@ pub trait Engine : Sync + Send { | ||||
| 
 | ||||
| 	/// Add an account provider useful for Engines that sign stuff.
 | ||||
| 	fn register_account_provider(&self, _account_provider: Arc<AccountProvider>) {} | ||||
| 	/// Trigger next step of the consensus engine.
 | ||||
| 	fn step(&self) {} | ||||
| } | ||||
|  | ||||
| @ -23,7 +23,7 @@ use account_provider::{AccountProvider, Error as AccountError}; | ||||
| use views::{BlockView, HeaderView}; | ||||
| use header::Header; | ||||
| use state::{State, CleanupMode}; | ||||
| use client::{MiningBlockChainClient, Executive, Executed, EnvInfo, TransactOptions, BlockID, CallAnalytics}; | ||||
| use client::{MiningBlockChainClient, Executive, Executed, EnvInfo, TransactOptions, BlockId, CallAnalytics, TransactionId}; | ||||
| use client::TransactionImportResult; | ||||
| use executive::contract_address; | ||||
| use block::{ClosedBlock, SealedBlock, IsBlock, Block}; | ||||
| @ -357,6 +357,8 @@ impl Miner { | ||||
| 		let block_number = open_block.block().fields().header.number(); | ||||
| 
 | ||||
| 		// TODO Push new uncles too.
 | ||||
| 		let mut tx_count: usize = 0; | ||||
| 		let tx_total = transactions.len(); | ||||
| 		for tx in transactions { | ||||
| 			let hash = tx.hash(); | ||||
| 			let start = Instant::now(); | ||||
| @ -378,7 +380,7 @@ impl Miner { | ||||
| 				}, | ||||
| 				_ => {}, | ||||
| 			} | ||||
| 
 | ||||
| 			trace!(target: "miner", "Adding tx {:?} took {:?}", hash, took); | ||||
| 			match result { | ||||
| 				Err(Error::Execution(ExecutionError::BlockGasLimitReached { gas_limit, gas_used, gas })) => { | ||||
| 					debug!(target: "miner", "Skipping adding transaction to block because of gas limit: {:?} (limit: {:?}, used: {:?}, gas: {:?})", hash, gas_limit, gas_used, gas); | ||||
| @ -407,9 +409,12 @@ impl Miner { | ||||
| 						   "Error adding transaction to block: number={}. transaction_hash={:?}, Error: {:?}", | ||||
| 						   block_number, hash, e); | ||||
| 				}, | ||||
| 				_ => {}	// imported ok
 | ||||
| 				_ => { | ||||
| 					tx_count += 1; | ||||
| 				}	// imported ok
 | ||||
| 			} | ||||
| 		} | ||||
| 		trace!(target: "miner", "Pushed {}/{} transactions", tx_count, tx_total); | ||||
| 
 | ||||
| 		let block = open_block.close(); | ||||
| 
 | ||||
| @ -580,6 +585,10 @@ impl Miner { | ||||
| 		let best_block_header: Header = ::rlp::decode(&chain.best_block_header()); | ||||
| 		transactions.into_iter() | ||||
| 			.map(|tx| { | ||||
| 				if chain.transaction_block(TransactionId::Hash(tx.hash())).is_some() { | ||||
| 					debug!(target: "miner", "Rejected tx {:?}: already in the blockchain", tx.hash()); | ||||
| 					return Err(Error::Transaction(TransactionError::AlreadyImported)); | ||||
| 				} | ||||
| 				match self.engine.verify_transaction_basic(&tx, &best_block_header) { | ||||
| 					Err(e) => { | ||||
| 						debug!(target: "miner", "Rejected tx {:?} with invalid signature: {:?}", tx.hash(), e); | ||||
| @ -692,7 +701,7 @@ impl MinerService for Miner { | ||||
| 				Ok(ret) | ||||
| 			}, | ||||
| 			None => { | ||||
| 				chain.call(t, BlockID::Latest, analytics) | ||||
| 				chain.call(t, BlockId::Latest, analytics) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| @ -1085,7 +1094,7 @@ impl MinerService for Miner { | ||||
| 
 | ||||
| 		fn fetch_transactions(chain: &MiningBlockChainClient, hash: &H256) -> Vec<SignedTransaction> { | ||||
| 			let block = chain | ||||
| 				.block(BlockID::Hash(*hash)) | ||||
| 				.block(BlockId::Hash(*hash)) | ||||
| 				// Client should send message after commit to db and inserting to chain.
 | ||||
| 				.expect("Expected in-chain blocks."); | ||||
| 			let block = BlockView::new(&block); | ||||
|  | ||||
| @ -18,7 +18,7 @@ | ||||
| 
 | ||||
| use std::fmt; | ||||
| 
 | ||||
| use ids::BlockID; | ||||
| use ids::BlockId; | ||||
| 
 | ||||
| use util::H256; | ||||
| use util::trie::TrieError; | ||||
| @ -28,7 +28,7 @@ use rlp::DecoderError; | ||||
| #[derive(Debug)] | ||||
| pub enum Error { | ||||
| 	/// Invalid starting block for snapshot.
 | ||||
| 	InvalidStartingBlock(BlockID), | ||||
| 	InvalidStartingBlock(BlockId), | ||||
| 	/// Block not found.
 | ||||
| 	BlockNotFound(H256), | ||||
| 	/// Incomplete chain.
 | ||||
|  | ||||
| @ -27,7 +27,7 @@ use account_db::{AccountDB, AccountDBMut}; | ||||
| use blockchain::{BlockChain, BlockProvider}; | ||||
| use engines::Engine; | ||||
| use header::Header; | ||||
| use ids::BlockID; | ||||
| use ids::BlockId; | ||||
| use views::BlockView; | ||||
| 
 | ||||
| use util::{Bytes, Hashable, HashDB, DBValue, snappy, U256, Uint}; | ||||
| @ -129,7 +129,7 @@ pub fn take_snapshot<W: SnapshotWriter + Send>( | ||||
| 	p: &Progress | ||||
| ) -> Result<(), Error> { | ||||
| 	let start_header = try!(chain.block_header(&block_at) | ||||
| 		.ok_or(Error::InvalidStartingBlock(BlockID::Hash(block_at)))); | ||||
| 		.ok_or(Error::InvalidStartingBlock(BlockId::Hash(block_at)))); | ||||
| 	let state_root = start_header.state_root(); | ||||
| 	let number = start_header.number(); | ||||
| 
 | ||||
|  | ||||
| @ -30,7 +30,7 @@ use blockchain::BlockChain; | ||||
| use client::{BlockChainClient, Client}; | ||||
| use engines::Engine; | ||||
| use error::Error; | ||||
| use ids::BlockID; | ||||
| use ids::BlockId; | ||||
| use service::ClientIoMessage; | ||||
| 
 | ||||
| use io::IoChannel; | ||||
| @ -354,7 +354,7 @@ impl Service { | ||||
| 		let writer = try!(LooseWriter::new(temp_dir.clone())); | ||||
| 
 | ||||
| 		let guard = Guard::new(temp_dir.clone()); | ||||
| 		let res = client.take_snapshot(writer, BlockID::Number(num), &self.progress); | ||||
| 		let res = client.take_snapshot(writer, BlockId::Number(num), &self.progress); | ||||
| 
 | ||||
| 		self.taking_snapshot.store(false, Ordering::SeqCst); | ||||
| 		if let Err(e) = res { | ||||
|  | ||||
| @ -19,7 +19,7 @@ | ||||
| use std::sync::Arc; | ||||
| 
 | ||||
| use client::{BlockChainClient, Client}; | ||||
| use ids::BlockID; | ||||
| use ids::BlockId; | ||||
| use snapshot::service::{Service, ServiceParams}; | ||||
| use snapshot::{self, ManifestData, SnapshotService}; | ||||
| use spec::Spec; | ||||
| @ -96,8 +96,8 @@ fn restored_is_equivalent() { | ||||
| 	assert_eq!(service.status(), ::snapshot::RestorationStatus::Inactive); | ||||
| 
 | ||||
| 	for x in 0..NUM_BLOCKS { | ||||
| 		let block1 = client.block(BlockID::Number(x as u64)).unwrap(); | ||||
| 		let block2 = client2.block(BlockID::Number(x as u64)).unwrap(); | ||||
| 		let block1 = client.block(BlockId::Number(x as u64)).unwrap(); | ||||
| 		let block2 = client2.block(BlockId::Number(x as u64)).unwrap(); | ||||
| 
 | ||||
| 		assert_eq!(block1, block2); | ||||
| 	} | ||||
|  | ||||
| @ -18,7 +18,7 @@ | ||||
| 
 | ||||
| use util::Mutex; | ||||
| use client::{BlockChainClient, Client, ChainNotify}; | ||||
| use ids::BlockID; | ||||
| use ids::BlockId; | ||||
| use service::ClientIoMessage; | ||||
| use views::HeaderView; | ||||
| 
 | ||||
| @ -43,7 +43,7 @@ impl<F> Oracle for StandardOracle<F> | ||||
| 	where F: Send + Sync + Fn() -> bool | ||||
| { | ||||
| 	fn to_number(&self, hash: H256) -> Option<u64> { | ||||
| 		self.client.block_header(BlockID::Hash(hash)).map(|h| HeaderView::new(&h).number()) | ||||
| 		self.client.block_header(BlockId::Hash(hash)).map(|h| HeaderView::new(&h).number()) | ||||
| 	} | ||||
| 
 | ||||
| 	fn is_major_importing(&self) -> bool { | ||||
|  | ||||
| @ -273,7 +273,7 @@ impl Spec { | ||||
| 	pub fn new_instant() -> Spec { load_bundled!("instant_seal") } | ||||
| 
 | ||||
| 	/// Create a new Spec with AuthorityRound consensus which does internal sealing (not requiring work).
 | ||||
| 	/// Accounts with secrets "1".sha3() and "2".sha3() are the authorities.
 | ||||
| 	/// Accounts with secrets "0".sha3() and "1".sha3() are the authorities.
 | ||||
| 	pub fn new_test_round() -> Self { load_bundled!("authority_round") } | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -31,6 +31,7 @@ use transaction::SignedTransaction; | ||||
| use state_db::StateDB; | ||||
| 
 | ||||
| use util::*; | ||||
| 
 | ||||
| use util::trie::recorder::{Recorder, BasicRecorder as TrieRecorder}; | ||||
| 
 | ||||
| mod account; | ||||
|  | ||||
| @ -15,7 +15,7 @@ | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| use io::IoChannel; | ||||
| use client::{BlockChainClient, MiningBlockChainClient, Client, ClientConfig, BlockID}; | ||||
| use client::{BlockChainClient, MiningBlockChainClient, Client, ClientConfig, BlockId}; | ||||
| use state::CleanupMode; | ||||
| use ethereum; | ||||
| use block::IsBlock; | ||||
| @ -99,7 +99,7 @@ fn imports_good_block() { | ||||
| 	client.flush_queue(); | ||||
| 	client.import_verified_blocks(); | ||||
| 
 | ||||
| 	let block = client.block_header(BlockID::Number(1)).unwrap(); | ||||
| 	let block = client.block_header(BlockId::Number(1)).unwrap(); | ||||
| 	assert!(!block.is_empty()); | ||||
| } | ||||
| 
 | ||||
| @ -117,7 +117,7 @@ fn query_none_block() { | ||||
| 		IoChannel::disconnected(), | ||||
| 		&db_config | ||||
| 	).unwrap(); | ||||
|     let non_existant = client.block_header(BlockID::Number(188)); | ||||
|     let non_existant = client.block_header(BlockId::Number(188)); | ||||
| 	assert!(non_existant.is_none()); | ||||
| } | ||||
| 
 | ||||
| @ -125,7 +125,7 @@ fn query_none_block() { | ||||
| fn query_bad_block() { | ||||
| 	let client_result = get_test_client_with_blocks(vec![get_bad_state_dummy_block()]); | ||||
| 	let client = client_result.reference(); | ||||
| 	let bad_block:Option<Bytes> = client.block_header(BlockID::Number(1)); | ||||
| 	let bad_block:Option<Bytes> = client.block_header(BlockId::Number(1)); | ||||
| 
 | ||||
| 	assert!(bad_block.is_none()); | ||||
| } | ||||
| @ -146,8 +146,8 @@ fn returns_logs() { | ||||
| 	let client_result = get_test_client_with_blocks(vec![dummy_block.clone()]); | ||||
| 	let client = client_result.reference(); | ||||
| 	let logs = client.logs(Filter { | ||||
| 		from_block: BlockID::Earliest, | ||||
| 		to_block: BlockID::Latest, | ||||
| 		from_block: BlockId::Earliest, | ||||
| 		to_block: BlockId::Latest, | ||||
| 		address: None, | ||||
| 		topics: vec![], | ||||
| 		limit: None, | ||||
| @ -161,8 +161,8 @@ fn returns_logs_with_limit() { | ||||
| 	let client_result = get_test_client_with_blocks(vec![dummy_block.clone()]); | ||||
| 	let client = client_result.reference(); | ||||
| 	let logs = client.logs(Filter { | ||||
| 		from_block: BlockID::Earliest, | ||||
| 		to_block: BlockID::Latest, | ||||
| 		from_block: BlockId::Earliest, | ||||
| 		to_block: BlockId::Latest, | ||||
| 		address: None, | ||||
| 		topics: vec![], | ||||
| 		limit: Some(2), | ||||
| @ -176,7 +176,7 @@ fn returns_block_body() { | ||||
| 	let client_result = get_test_client_with_blocks(vec![dummy_block.clone()]); | ||||
| 	let client = client_result.reference(); | ||||
| 	let block = BlockView::new(&dummy_block); | ||||
| 	let body = client.block_body(BlockID::Hash(block.header().hash())).unwrap(); | ||||
| 	let body = client.block_body(BlockId::Hash(block.header().hash())).unwrap(); | ||||
| 	let body = Rlp::new(&body); | ||||
| 	assert_eq!(body.item_count(), 2); | ||||
| 	assert_eq!(body.at(0).as_raw()[..], block.rlp().at(1).as_raw()[..]); | ||||
| @ -187,7 +187,7 @@ fn returns_block_body() { | ||||
| fn imports_block_sequence() { | ||||
| 	let client_result = generate_dummy_client(6); | ||||
| 	let client = client_result.reference(); | ||||
| 	let block = client.block_header(BlockID::Number(5)).unwrap(); | ||||
| 	let block = client.block_header(BlockId::Number(5)).unwrap(); | ||||
| 
 | ||||
| 	assert!(!block.is_empty()); | ||||
| } | ||||
|  | ||||
| @ -19,7 +19,7 @@ | ||||
| use nanoipc; | ||||
| use std::sync::Arc; | ||||
| use std::sync::atomic::{Ordering, AtomicBool}; | ||||
| use client::{Client, BlockChainClient, ClientConfig, BlockID}; | ||||
| use client::{Client, BlockChainClient, ClientConfig, BlockId}; | ||||
| use client::remote::RemoteClient; | ||||
| use tests::helpers::*; | ||||
| use devtools::*; | ||||
| @ -71,7 +71,7 @@ fn can_query_block() { | ||||
| 		run_test_worker(scope, stop_guard.share(), socket_path); | ||||
| 		let remote_client = nanoipc::generic_client::<RemoteClient<_>>(socket_path).unwrap(); | ||||
| 
 | ||||
| 		let non_existant_block = remote_client.block_header(BlockID::Number(999)); | ||||
| 		let non_existant_block = remote_client.block_header(BlockId::Number(999)); | ||||
| 
 | ||||
| 		assert!(non_existant_block.is_none()); | ||||
| 	}) | ||||
|  | ||||
| @ -94,7 +94,7 @@ impl Key<blooms::BloomGroup> for TraceGroupPosition { | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Hash, Eq, PartialEq)] | ||||
| enum CacheID { | ||||
| enum CacheId { | ||||
| 	Trace(H256), | ||||
| 	Bloom(TraceGroupPosition), | ||||
| } | ||||
| @ -104,7 +104,7 @@ pub struct TraceDB<T> where T: DatabaseExtras { | ||||
| 	// cache
 | ||||
| 	traces: RwLock<HashMap<H256, FlatBlockTraces>>, | ||||
| 	blooms: RwLock<HashMap<TraceGroupPosition, blooms::BloomGroup>>, | ||||
| 	cache_manager: RwLock<CacheManager<CacheID>>, | ||||
| 	cache_manager: RwLock<CacheManager<CacheId>>, | ||||
| 	// db
 | ||||
| 	tracesdb: Arc<Database>, | ||||
| 	// config,
 | ||||
| @ -119,7 +119,7 @@ impl<T> BloomGroupDatabase for TraceDB<T> where T: DatabaseExtras { | ||||
| 	fn blooms_at(&self, position: &GroupPosition) -> Option<BloomGroup> { | ||||
| 		let position = TraceGroupPosition::from(position.clone()); | ||||
| 		let result = self.tracesdb.read_with_cache(db::COL_TRACE, &self.blooms, &position).map(Into::into); | ||||
| 		self.note_used(CacheID::Bloom(position)); | ||||
| 		self.note_used(CacheId::Bloom(position)); | ||||
| 		result | ||||
| 	} | ||||
| } | ||||
| @ -152,7 +152,7 @@ impl<T> TraceDB<T> where T: DatabaseExtras { | ||||
| 	} | ||||
| 
 | ||||
| 	/// Let the cache system know that a cacheable item has been used.
 | ||||
| 	fn note_used(&self, id: CacheID) { | ||||
| 	fn note_used(&self, id: CacheId) { | ||||
| 		let mut cache_manager = self.cache_manager.write(); | ||||
| 		cache_manager.note_used(id); | ||||
| 	} | ||||
| @ -168,8 +168,8 @@ impl<T> TraceDB<T> where T: DatabaseExtras { | ||||
| 		cache_manager.collect_garbage(current_size, | ids | { | ||||
| 			for id in &ids { | ||||
| 				match *id { | ||||
| 					CacheID::Trace(ref h) => { traces.remove(h); }, | ||||
| 					CacheID::Bloom(ref h) => { blooms.remove(h); }, | ||||
| 					CacheId::Trace(ref h) => { traces.remove(h); }, | ||||
| 					CacheId::Bloom(ref h) => { blooms.remove(h); }, | ||||
| 				} | ||||
| 			} | ||||
| 			traces.shrink_to_fit(); | ||||
| @ -182,7 +182,7 @@ impl<T> TraceDB<T> where T: DatabaseExtras { | ||||
| 	/// Returns traces for block with hash.
 | ||||
| 	fn traces(&self, block_hash: &H256) -> Option<FlatBlockTraces> { | ||||
| 		let result = self.tracesdb.read_with_cache(db::COL_TRACE, &self.traces, block_hash); | ||||
| 		self.note_used(CacheID::Trace(block_hash.clone())); | ||||
| 		self.note_used(CacheId::Trace(block_hash.clone())); | ||||
| 		result | ||||
| 	} | ||||
| 
 | ||||
| @ -289,7 +289,7 @@ impl<T> TraceDatabase for TraceDB<T> where T: DatabaseExtras { | ||||
| 			batch.extend_with_cache(db::COL_TRACE, &mut *blooms, blooms_to_insert, CacheUpdatePolicy::Remove); | ||||
| 			// note_used must be called after locking blooms to avoid cache/traces deadlock on garbage collection
 | ||||
| 			for key in blooms_keys { | ||||
| 				self.note_used(CacheID::Bloom(key)); | ||||
| 				self.note_used(CacheId::Bloom(key)); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| @ -300,7 +300,7 @@ impl<T> TraceDatabase for TraceDB<T> where T: DatabaseExtras { | ||||
| 			// cause this value might be queried by hash later
 | ||||
| 			batch.write_with_cache(db::COL_TRACE, &mut *traces, request.block_hash, request.traces, CacheUpdatePolicy::Overwrite); | ||||
| 			// note_used must be called after locking traces to avoid cache/traces deadlock on garbage collection
 | ||||
| 			self.note_used(CacheID::Trace(request.block_hash.clone())); | ||||
| 			self.note_used(CacheId::Trace(request.block_hash.clone())); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|  | ||||
| @ -18,17 +18,17 @@ | ||||
| 
 | ||||
| use util::{Address, H256, Hashable, H2048}; | ||||
| use util::bloom::Bloomable; | ||||
| use client::BlockID; | ||||
| use client::BlockId; | ||||
| use log_entry::LogEntry; | ||||
| 
 | ||||
| /// Blockchain Filter.
 | ||||
| #[derive(Binary, Debug, PartialEq)] | ||||
| pub struct Filter { | ||||
| 	/// Blockchain will be searched from this block.
 | ||||
| 	pub from_block: BlockID, | ||||
| 	pub from_block: BlockId, | ||||
| 
 | ||||
| 	/// Till this block.
 | ||||
| 	pub to_block: BlockID, | ||||
| 	pub to_block: BlockId, | ||||
| 
 | ||||
| 	/// Search addresses.
 | ||||
| 	///
 | ||||
| @ -114,14 +114,14 @@ impl Filter { | ||||
| mod tests { | ||||
| 	use util::FixedHash; | ||||
| 	use filter::Filter; | ||||
| 	use client::BlockID; | ||||
| 	use client::BlockId; | ||||
| 	use log_entry::LogEntry; | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn test_bloom_possibilities_none() { | ||||
| 		let none_filter = Filter { | ||||
| 			from_block: BlockID::Earliest, | ||||
| 			to_block: BlockID::Latest, | ||||
| 			from_block: BlockId::Earliest, | ||||
| 			to_block: BlockId::Latest, | ||||
| 			address: None, | ||||
| 			topics: vec![None, None, None, None], | ||||
| 			limit: None, | ||||
| @ -136,8 +136,8 @@ mod tests { | ||||
| 	#[test] | ||||
| 	fn test_bloom_possibilities_single_address_and_topic() { | ||||
| 		let filter = Filter { | ||||
| 			from_block: BlockID::Earliest, | ||||
| 			to_block: BlockID::Latest, | ||||
| 			from_block: BlockId::Earliest, | ||||
| 			to_block: BlockId::Latest, | ||||
| 			address: Some(vec!["b372018f3be9e171df0581136b59d2faf73a7d5d".into()]), | ||||
| 			topics: vec![ | ||||
| 				Some(vec!["ff74e91598aed6ae5d2fdcf8b24cd2c7be49a0808112a305069355b7160f23f9".into()]), | ||||
| @ -155,8 +155,8 @@ mod tests { | ||||
| 	#[test] | ||||
| 	fn test_bloom_possibilities_single_address_and_many_topics() { | ||||
| 		let filter = Filter { | ||||
| 			from_block: BlockID::Earliest, | ||||
| 			to_block: BlockID::Latest, | ||||
| 			from_block: BlockId::Earliest, | ||||
| 			to_block: BlockId::Latest, | ||||
| 			address: Some(vec!["b372018f3be9e171df0581136b59d2faf73a7d5d".into()]), | ||||
| 			topics: vec![ | ||||
| 				Some(vec!["ff74e91598aed6ae5d2fdcf8b24cd2c7be49a0808112a305069355b7160f23f9".into()]), | ||||
| @ -174,8 +174,8 @@ mod tests { | ||||
| 	#[test] | ||||
| 	fn test_bloom_possibilites_multiple_addresses_and_topics() { | ||||
| 		let filter = Filter { | ||||
| 			from_block: BlockID::Earliest, | ||||
| 			to_block: BlockID::Latest, | ||||
| 			from_block: BlockId::Earliest, | ||||
| 			to_block: BlockId::Latest, | ||||
| 			address: Some(vec![ | ||||
| 						  "b372018f3be9e171df0581136b59d2faf73a7d5d".into(), | ||||
| 						  "b372018f3be9e171df0581136b59d2faf73a7d5d".into(), | ||||
| @ -204,8 +204,8 @@ mod tests { | ||||
| 	#[test] | ||||
| 	fn test_filter_matches() { | ||||
| 		let filter = Filter { | ||||
| 			from_block: BlockID::Earliest, | ||||
| 			to_block: BlockID::Latest, | ||||
| 			from_block: BlockId::Earliest, | ||||
| 			to_block: BlockId::Latest, | ||||
| 			address: Some(vec!["b372018f3be9e171df0581136b59d2faf73a7d5d".into()]), | ||||
| 			topics: vec![ | ||||
| 				Some(vec!["ff74e91598aed6ae5d2fdcf8b24cd2c7be49a0808112a305069355b7160f23f9".into()]), | ||||
|  | ||||
| @ -21,7 +21,7 @@ use header::BlockNumber; | ||||
| 
 | ||||
| /// Uniquely identifies block.
 | ||||
| #[derive(Debug, PartialEq, Copy, Clone, Hash, Eq, Binary)] | ||||
| pub enum BlockID { | ||||
| pub enum BlockId { | ||||
| 	/// Block's sha3.
 | ||||
| 	/// Querying by hash is always faster.
 | ||||
| 	Hash(H256), | ||||
| @ -37,28 +37,28 @@ pub enum BlockID { | ||||
| 
 | ||||
| /// Uniquely identifies transaction.
 | ||||
| #[derive(Debug, PartialEq, Clone, Hash, Eq, Binary)] | ||||
| pub enum TransactionID { | ||||
| pub enum TransactionId { | ||||
| 	/// Transaction's sha3.
 | ||||
| 	Hash(H256), | ||||
| 	/// Block id and transaction index within this block.
 | ||||
| 	/// Querying by block position is always faster.
 | ||||
| 	Location(BlockID, usize) | ||||
| 	Location(BlockId, usize) | ||||
| } | ||||
| 
 | ||||
| /// Uniquely identifies Trace.
 | ||||
| #[derive(Binary)] | ||||
| pub struct TraceId { | ||||
| 	/// Transaction
 | ||||
| 	pub transaction: TransactionID, | ||||
| 	pub transaction: TransactionId, | ||||
| 	/// Trace address within transaction.
 | ||||
| 	pub address: Vec<usize>, | ||||
| } | ||||
| 
 | ||||
| /// Uniquely identifies Uncle.
 | ||||
| #[derive(Debug, PartialEq, Eq, Copy, Clone, Binary)] | ||||
| pub struct UncleID { | ||||
| pub struct UncleId { | ||||
| 	/// Block id.
 | ||||
| 	pub block: BlockID, | ||||
| 	pub block: BlockId, | ||||
| 	/// Position in block.
 | ||||
| 	pub position: usize | ||||
| } | ||||
|  | ||||
| @ -18,13 +18,13 @@ | ||||
| 
 | ||||
| use std::ops::Range; | ||||
| use util::{Address}; | ||||
| use types::ids::BlockID; | ||||
| use types::ids::BlockId; | ||||
| 
 | ||||
| /// Easy to use trace filter.
 | ||||
| #[derive(Binary)] | ||||
| pub struct Filter { | ||||
| 	/// Range of filtering.
 | ||||
| 	pub range: Range<BlockID>, | ||||
| 	pub range: Range<BlockId>, | ||||
| 	/// From address.
 | ||||
| 	pub from_address: Vec<Address>, | ||||
| 	/// To address.
 | ||||
|  | ||||
| @ -19,7 +19,7 @@ use std::path::{PathBuf, Path}; | ||||
| use std::collections::HashMap; | ||||
| use time; | ||||
| use {json, SafeAccount, Error}; | ||||
| use json::UUID; | ||||
| use json::Uuid; | ||||
| use super::KeyDirectory; | ||||
| 
 | ||||
| const IGNORED_FILES: &'static [&'static str] = &["thumbs.db", "address_book.json"]; | ||||
| @ -117,7 +117,7 @@ impl KeyDirectory for DiskDirectory { | ||||
| 		// build file path
 | ||||
| 		let filename = account.filename.as_ref().cloned().unwrap_or_else(|| { | ||||
| 			let timestamp = time::strftime("%Y-%m-%dT%H-%M-%S", &time::now_utc()).expect("Time-format string is valid."); | ||||
| 			format!("UTC--{}Z--{}", timestamp, UUID::from(account.id)) | ||||
| 			format!("UTC--{}Z--{}", timestamp, Uuid::from(account.id)) | ||||
| 		}); | ||||
| 
 | ||||
| 		// update account filename
 | ||||
|  | ||||
| @ -23,8 +23,8 @@ use random::Random; | ||||
| use ethkey::{Signature, Address, Message, Secret, Public, KeyPair}; | ||||
| use dir::KeyDirectory; | ||||
| use account::SafeAccount; | ||||
| use json::{self, UUID}; | ||||
| use presale::PresaleWallet; | ||||
| use json::{self, Uuid}; | ||||
| use {import, Error, SimpleSecretStore, SecretStore}; | ||||
| 
 | ||||
| pub struct EthStore { | ||||
| @ -111,7 +111,7 @@ impl SecretStore for EthStore { | ||||
| 		account.public(password) | ||||
| 	} | ||||
| 
 | ||||
| 	fn uuid(&self, address: &Address) -> Result<UUID, Error> { | ||||
| 	fn uuid(&self, address: &Address) -> Result<Uuid, Error> { | ||||
| 		let account = try!(self.get(address)); | ||||
| 		Ok(account.id.into()) | ||||
| 	} | ||||
|  | ||||
| @ -21,7 +21,7 @@ pub enum Error { | ||||
| 	UnsupportedCipher, | ||||
| 	InvalidCipherParams, | ||||
| 	UnsupportedKdf, | ||||
| 	InvalidUUID, | ||||
| 	InvalidUuid, | ||||
| 	UnsupportedVersion, | ||||
| 	InvalidCiphertext, | ||||
| 	InvalidH256, | ||||
| @ -31,7 +31,7 @@ pub enum Error { | ||||
| impl fmt::Display for Error { | ||||
| 	fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { | ||||
| 		match *self { | ||||
| 			Error::InvalidUUID => write!(f, "Invalid UUID"), | ||||
| 			Error::InvalidUuid => write!(f, "Invalid Uuid"), | ||||
| 			Error::UnsupportedVersion => write!(f, "Unsupported version"), | ||||
| 			Error::UnsupportedKdf => write!(f, "Unsupported kdf"), | ||||
| 			Error::InvalidCiphertext => write!(f, "Invalid ciphertext"), | ||||
|  | ||||
| @ -23,15 +23,15 @@ use super::Error; | ||||
| 
 | ||||
| /// Universaly unique identifier.
 | ||||
| #[derive(Debug, PartialEq)] | ||||
| pub struct UUID([u8; 16]); | ||||
| pub struct Uuid([u8; 16]); | ||||
| 
 | ||||
| impl From<[u8; 16]> for UUID { | ||||
| impl From<[u8; 16]> for Uuid { | ||||
| 	fn from(uuid: [u8; 16]) -> Self { | ||||
| 		UUID(uuid) | ||||
| 		Uuid(uuid) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl<'a> Into<String> for &'a UUID { | ||||
| impl<'a> Into<String> for &'a Uuid { | ||||
| 	fn into(self) -> String { | ||||
| 		let d1 = &self.0[0..4]; | ||||
| 		let d2 = &self.0[4..6]; | ||||
| @ -42,44 +42,44 @@ impl<'a> Into<String> for &'a UUID { | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl Into<String> for UUID { | ||||
| impl Into<String> for Uuid { | ||||
| 	fn into(self) -> String { | ||||
| 		Into::into(&self) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl Into<[u8; 16]> for UUID { | ||||
| impl Into<[u8; 16]> for Uuid { | ||||
| 	fn into(self) -> [u8; 16] { | ||||
| 		self.0 | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl fmt::Display for UUID { | ||||
| impl fmt::Display for Uuid { | ||||
| 	fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { | ||||
| 		let s: String = (self as &UUID).into(); | ||||
| 		let s: String = (self as &Uuid).into(); | ||||
| 		write!(f, "{}", s) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| fn copy_into(from: &str, into: &mut [u8]) -> Result<(), Error> { | ||||
| 	let from = try!(from.from_hex().map_err(|_| Error::InvalidUUID)); | ||||
| 	let from = try!(from.from_hex().map_err(|_| Error::InvalidUuid)); | ||||
| 
 | ||||
| 	if from.len() != into.len() { | ||||
| 		return Err(Error::InvalidUUID); | ||||
| 		return Err(Error::InvalidUuid); | ||||
| 	} | ||||
| 
 | ||||
| 	into.copy_from_slice(&from); | ||||
| 	Ok(()) | ||||
| } | ||||
| 
 | ||||
| impl str::FromStr for UUID { | ||||
| impl str::FromStr for Uuid { | ||||
| 	type Err = Error; | ||||
| 
 | ||||
| 	fn from_str(s: &str) -> Result<Self, Self::Err> { | ||||
| 		let parts: Vec<&str> = s.split("-").collect(); | ||||
| 
 | ||||
| 		if parts.len() != 5 { | ||||
| 			return Err(Error::InvalidUUID); | ||||
| 			return Err(Error::InvalidUuid); | ||||
| 		} | ||||
| 
 | ||||
| 		let mut uuid = [0u8; 16]; | ||||
| @ -90,17 +90,17 @@ impl str::FromStr for UUID { | ||||
| 		try!(copy_into(parts[3], &mut uuid[8..10])); | ||||
| 		try!(copy_into(parts[4], &mut uuid[10..16])); | ||||
| 
 | ||||
| 		Ok(UUID(uuid)) | ||||
| 		Ok(Uuid(uuid)) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl From<&'static str> for UUID { | ||||
| impl From<&'static str> for Uuid { | ||||
| 	fn from(s: &'static str) -> Self { | ||||
| 		s.parse().expect(&format!("invalid string literal for {}: '{}'", stringify!(Self), s)) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl Serialize for UUID { | ||||
| impl Serialize for Uuid { | ||||
| 	fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error> | ||||
| 	where S: Serializer { | ||||
| 		let s: String = self.into(); | ||||
| @ -108,17 +108,17 @@ impl Serialize for UUID { | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl Deserialize for UUID { | ||||
| impl Deserialize for Uuid { | ||||
| 	fn deserialize<D>(deserializer: &mut D) -> Result<Self, D::Error> | ||||
| 	where D: Deserializer { | ||||
| 		deserializer.deserialize(UUIDVisitor) | ||||
| 		deserializer.deserialize(UuidVisitor) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| struct UUIDVisitor; | ||||
| struct UuidVisitor; | ||||
| 
 | ||||
| impl Visitor for UUIDVisitor { | ||||
| 	type Value = UUID; | ||||
| impl Visitor for UuidVisitor { | ||||
| 	type Value = Uuid; | ||||
| 
 | ||||
| 	fn visit_str<E>(&mut self, value: &str) -> Result<Self::Value, E> where E: SerdeError { | ||||
| 		value.parse().map_err(SerdeError::custom) | ||||
| @ -131,18 +131,18 @@ impl Visitor for UUIDVisitor { | ||||
| 
 | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
| 	use super::UUID; | ||||
| 	use super::Uuid; | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn uuid_from_str() { | ||||
| 		let uuid: UUID = "3198bc9c-6672-5ab3-d995-4942343ae5b6".into(); | ||||
| 		assert_eq!(uuid, UUID::from([0x31, 0x98, 0xbc, 0x9c, 0x66, 0x72, 0x5a, 0xb3, 0xd9, 0x95, 0x49, 0x42, 0x34, 0x3a, 0xe5, 0xb6])); | ||||
| 		let uuid: Uuid = "3198bc9c-6672-5ab3-d995-4942343ae5b6".into(); | ||||
| 		assert_eq!(uuid, Uuid::from([0x31, 0x98, 0xbc, 0x9c, 0x66, 0x72, 0x5a, 0xb3, 0xd9, 0x95, 0x49, 0x42, 0x34, 0x3a, 0xe5, 0xb6])); | ||||
| 	} | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn uuid_from_and_to_str() { | ||||
| 		let from = "3198bc9c-6672-5ab3-d995-4942343ae5b6"; | ||||
| 		let uuid: UUID = from.into(); | ||||
| 		let uuid: Uuid = from.into(); | ||||
| 		let to: String = uuid.into(); | ||||
| 		assert_eq!(from, &to); | ||||
| 	} | ||||
|  | ||||
| @ -18,11 +18,11 @@ use std::io::{Read, Write}; | ||||
| use serde::{Deserialize, Deserializer, Error}; | ||||
| use serde::de::{Visitor, MapVisitor}; | ||||
| use serde_json; | ||||
| use super::{UUID, Version, Crypto, H160}; | ||||
| use super::{Uuid, Version, Crypto, H160}; | ||||
| 
 | ||||
| #[derive(Debug, PartialEq, Serialize)] | ||||
| pub struct KeyFile { | ||||
| 	pub id: UUID, | ||||
| 	pub id: Uuid, | ||||
| 	pub version: Version, | ||||
| 	pub crypto: Crypto, | ||||
| 	pub address: H160, | ||||
| @ -31,7 +31,7 @@ pub struct KeyFile { | ||||
| } | ||||
| 
 | ||||
| enum KeyFileField { | ||||
| 	ID, | ||||
| 	Id, | ||||
| 	Version, | ||||
| 	Crypto, | ||||
| 	Address, | ||||
| @ -56,7 +56,7 @@ impl Visitor for KeyFileFieldVisitor { | ||||
| 		where E: Error | ||||
| 	{ | ||||
| 		match value { | ||||
| 			"id" => Ok(KeyFileField::ID), | ||||
| 			"id" => Ok(KeyFileField::Id), | ||||
| 			"version" => Ok(KeyFileField::Version), | ||||
| 			"crypto" => Ok(KeyFileField::Crypto), | ||||
| 			"Crypto" => Ok(KeyFileField::Crypto), | ||||
| @ -94,7 +94,7 @@ impl Visitor for KeyFileVisitor { | ||||
| 
 | ||||
| 		loop { | ||||
| 			match try!(visitor.visit_key()) { | ||||
| 				Some(KeyFileField::ID) => { id = Some(try!(visitor.visit_value())); } | ||||
| 				Some(KeyFileField::Id) => { id = Some(try!(visitor.visit_value())); } | ||||
| 				Some(KeyFileField::Version) => { version = Some(try!(visitor.visit_value())); } | ||||
| 				Some(KeyFileField::Crypto) => { crypto = Some(try!(visitor.visit_value())); } | ||||
| 				Some(KeyFileField::Address) => { address = Some(try!(visitor.visit_value())); } | ||||
| @ -153,7 +153,7 @@ impl KeyFile { | ||||
| mod tests { | ||||
| 	use std::str::FromStr; | ||||
| 	use serde_json; | ||||
| 	use json::{KeyFile, UUID, Version, Crypto, Cipher, Aes128Ctr, Kdf, Scrypt}; | ||||
| 	use json::{KeyFile, Uuid, Version, Crypto, Cipher, Aes128Ctr, Kdf, Scrypt}; | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn basic_keyfile() { | ||||
| @ -183,7 +183,7 @@ mod tests { | ||||
| 		}"#;
 | ||||
| 
 | ||||
| 		let expected = KeyFile { | ||||
| 			id: UUID::from_str("8777d9f6-7860-4b9b-88b7-0b57ee6b3a73").unwrap(), | ||||
| 			id: Uuid::from_str("8777d9f6-7860-4b9b-88b7-0b57ee6b3a73").unwrap(), | ||||
| 			version: Version::V3, | ||||
| 			address: "6edddfc6349aff20bc6467ccf276c5b52487f7a8".into(), | ||||
| 			crypto: Crypto { | ||||
|  | ||||
| @ -14,7 +14,7 @@ pub use self::cipher::{Cipher, CipherSer, CipherSerParams, Aes128Ctr}; | ||||
| pub use self::crypto::{Crypto, CipherText}; | ||||
| pub use self::error::Error; | ||||
| pub use self::hash::{H128, H160, H256}; | ||||
| pub use self::id::UUID; | ||||
| pub use self::id::Uuid; | ||||
| pub use self::kdf::{Kdf, KdfSer, Prf, Pbkdf2, Scrypt, KdfSerParams}; | ||||
| pub use self::key_file::KeyFile; | ||||
| pub use self::presale::{PresaleWallet, Encseed}; | ||||
|  | ||||
| @ -16,7 +16,7 @@ | ||||
| 
 | ||||
| use ethkey::{Address, Message, Signature, Secret, Public}; | ||||
| use Error; | ||||
| use json::UUID; | ||||
| use json::Uuid; | ||||
| 
 | ||||
| pub trait SimpleSecretStore: Send + Sync { | ||||
| 	fn insert_account(&self, secret: Secret, password: &str) -> Result<Address, Error>; | ||||
| @ -37,7 +37,7 @@ pub trait SecretStore: SimpleSecretStore { | ||||
| 
 | ||||
| 	fn public(&self, account: &Address, password: &str) -> Result<Public, Error>; | ||||
| 
 | ||||
| 	fn uuid(&self, account: &Address) -> Result<UUID, Error>; | ||||
| 	fn uuid(&self, account: &Address) -> Result<Uuid, Error>; | ||||
| 	fn name(&self, account: &Address) -> Result<String, Error>; | ||||
| 	fn meta(&self, account: &Address) -> Result<String, Error>; | ||||
| 
 | ||||
|  | ||||
| @ -17,6 +17,15 @@ | ||||
|     }, | ||||
|     "development": { | ||||
|       "plugins": ["react-hot-loader/babel"] | ||||
|     }, | ||||
|     "test": { | ||||
|       "plugins": [ | ||||
|         [ | ||||
|           "babel-plugin-webpack-alias", { | ||||
|             "config": "webpack/test.js" | ||||
|           } | ||||
|         ] | ||||
|       ] | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "parity.js", | ||||
|   "version": "0.2.102", | ||||
|   "version": "0.2.105", | ||||
|   "main": "release/index.js", | ||||
|   "jsnext:main": "src/index.js", | ||||
|   "author": "Parity Team <admin@parity.io>", | ||||
| @ -40,9 +40,9 @@ | ||||
|     "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" | ||||
|   }, | ||||
| @ -57,6 +57,7 @@ | ||||
|     "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-plugin-webpack-alias": "2.1.2", | ||||
|     "babel-polyfill": "6.20.0", | ||||
|     "babel-preset-es2015": "6.18.0", | ||||
|     "babel-preset-es2016": "6.16.0", | ||||
| @ -66,6 +67,7 @@ | ||||
|     "babel-register": "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", | ||||
| @ -99,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", | ||||
| @ -137,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", | ||||
|  | ||||
							
								
								
									
										8
									
								
								js/src/3rdparty/etherscan/links.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								js/src/3rdparty/etherscan/links.js
									
									
									
									
										vendored
									
									
								
							| @ -14,10 +14,14 @@ | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| export const url = (isTestnet = false) => { | ||||
|   return `https://${isTestnet ? 'testnet.' : ''}etherscan.io`; | ||||
| }; | ||||
| 
 | ||||
| export const txLink = (hash, isTestnet = false) => { | ||||
|   return `https://${isTestnet ? 'testnet.' : ''}etherscan.io/tx/${hash}`; | ||||
|   return `${url(isTestnet)}/tx/${hash}`; | ||||
| }; | ||||
| 
 | ||||
| export const addressLink = (address, isTestnet = false) => { | ||||
|   return `https://${isTestnet ? 'testnet.' : ''}etherscan.io/address/${address}`; | ||||
|   return `${url(isTestnet)}/address/${address}`; | ||||
| }; | ||||
|  | ||||
| @ -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) => { | ||||
| @ -306,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']; | ||||
| @ -325,6 +340,7 @@ export default class Contract { | ||||
|       .then((filterId) => { | ||||
|         this._subscriptions[subscriptionId] = { | ||||
|           options: _options, | ||||
|           autoRemove, | ||||
|           callback, | ||||
|           filterId | ||||
|         }; | ||||
| @ -337,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; | ||||
|           }); | ||||
| @ -437,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); | ||||
|           } | ||||
|  | ||||
| @ -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) => { | ||||
|  | ||||
| @ -14,7 +14,7 @@ | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import wallet from './wallet'; | ||||
| import { wallet } from './wallet'; | ||||
| 
 | ||||
| export { | ||||
|   wallet | ||||
|  | ||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @ -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; | ||||
|     }); | ||||
|   } | ||||
|  | ||||
							
								
								
									
										460
									
								
								js/src/contracts/snippets/enhanced-wallet.sol
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										460
									
								
								js/src/contracts/snippets/enhanced-wallet.sol
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,460 @@ | ||||
| //sol Wallet | ||||
| // Multi-sig, daily-limited account proxy/wallet. | ||||
| // @authors: | ||||
| // Gav Wood <g@ethdev.com> | ||||
| // inheritable "property" contract that enables methods to be protected by requiring the acquiescence of either a | ||||
| // single, or, crucially, each of a number of, designated owners. | ||||
| // usage: | ||||
| // use modifiers onlyowner (just own owned) or onlymanyowners(hash), whereby the same hash must be provided by | ||||
| // some number (specified in constructor) of the set of owners (specified in the constructor, modifiable) before the | ||||
| // interior is executed. | ||||
| pragma solidity ^0.4.6; | ||||
| 
 | ||||
| contract multisig { | ||||
|     // EVENTS | ||||
| 
 | ||||
|     // this contract can accept a confirmation, in which case | ||||
|     // we record owner and operation (hash) alongside it. | ||||
|     event Confirmation(address owner, bytes32 operation); | ||||
|     event Revoke(address owner, bytes32 operation); | ||||
| 
 | ||||
|     // some others are in the case of an owner changing. | ||||
|     event OwnerChanged(address oldOwner, address newOwner); | ||||
|     event OwnerAdded(address newOwner); | ||||
|     event OwnerRemoved(address oldOwner); | ||||
| 
 | ||||
|     // the last one is emitted if the required signatures change | ||||
|     event RequirementChanged(uint newRequirement); | ||||
| 
 | ||||
|     // Funds has arrived into the wallet (record how much). | ||||
|     event Deposit(address _from, uint value); | ||||
|     // Single transaction going out of the wallet (record who signed for it, how much, and to whom it's going). | ||||
|     event SingleTransact(address owner, uint value, address to, bytes data); | ||||
|     // Multi-sig transaction going out of the wallet (record who signed for it last, the operation hash, how much, and to whom it's going). | ||||
|     event MultiTransact(address owner, bytes32 operation, uint value, address to, bytes data); | ||||
|     // Confirmation still needed for a transaction. | ||||
|     event ConfirmationNeeded(bytes32 operation, address initiator, uint value, address to, bytes data); | ||||
| } | ||||
| 
 | ||||
| contract multisigAbi is multisig { | ||||
|     function isOwner(address _addr) returns (bool); | ||||
| 
 | ||||
|     function hasConfirmed(bytes32 _operation, address _owner) constant returns (bool); | ||||
| 
 | ||||
|     function confirm(bytes32 _h) returns(bool); | ||||
| 
 | ||||
|     // (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today. | ||||
|     function setDailyLimit(uint _newLimit); | ||||
| 
 | ||||
|     function addOwner(address _owner); | ||||
| 
 | ||||
|     function removeOwner(address _owner); | ||||
| 
 | ||||
|     function changeRequirement(uint _newRequired); | ||||
| 
 | ||||
|     // Revokes a prior confirmation of the given operation | ||||
|     function revoke(bytes32 _operation); | ||||
| 
 | ||||
|     function changeOwner(address _from, address _to); | ||||
| 
 | ||||
|     function execute(address _to, uint _value, bytes _data) returns(bool); | ||||
| } | ||||
| 
 | ||||
| contract WalletLibrary is multisig { | ||||
|     // TYPES | ||||
| 
 | ||||
|     // struct for the status of a pending operation. | ||||
|     struct PendingState { | ||||
|         uint yetNeeded; | ||||
|         uint ownersDone; | ||||
|         uint index; | ||||
|     } | ||||
| 
 | ||||
|     // Transaction structure to remember details of transaction lest it need be saved for a later call. | ||||
|     struct Transaction { | ||||
|         address to; | ||||
|         uint value; | ||||
|         bytes data; | ||||
|     } | ||||
| 
 | ||||
|     /****************************** | ||||
|      ***** MULTI OWNED SECTION **** | ||||
|      ******************************/ | ||||
| 
 | ||||
|     // MODIFIERS | ||||
| 
 | ||||
|     // simple single-sig function modifier. | ||||
|     modifier onlyowner { | ||||
|         if (isOwner(msg.sender)) | ||||
|             _; | ||||
|     } | ||||
|     // multi-sig function modifier: the operation must have an intrinsic hash in order | ||||
|     // that later attempts can be realised as the same underlying operation and | ||||
|     // thus count as confirmations. | ||||
|     modifier onlymanyowners(bytes32 _operation) { | ||||
|         if (confirmAndCheck(_operation)) | ||||
|             _; | ||||
|     } | ||||
| 
 | ||||
|     // METHODS | ||||
| 
 | ||||
|     // constructor is given number of sigs required to do protected "onlymanyowners" transactions | ||||
|     // as well as the selection of addresses capable of confirming them. | ||||
|     function initMultiowned(address[] _owners, uint _required) { | ||||
|         m_numOwners = _owners.length + 1; | ||||
|         m_owners[1] = uint(msg.sender); | ||||
|         m_ownerIndex[uint(msg.sender)] = 1; | ||||
|         m_required = _required; | ||||
| 
 | ||||
|         for (uint i = 0; i < _owners.length; ++i) | ||||
|         { | ||||
|             m_owners[2 + i] = uint(_owners[i]); | ||||
|             m_ownerIndex[uint(_owners[i])] = 2 + i; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // Revokes a prior confirmation of the given operation | ||||
|     function revoke(bytes32 _operation) { | ||||
|         uint ownerIndex = m_ownerIndex[uint(msg.sender)]; | ||||
|         // make sure they're an owner | ||||
|         if (ownerIndex == 0) return; | ||||
|         uint ownerIndexBit = 2**ownerIndex; | ||||
|         var pending = m_pending[_operation]; | ||||
|         if (pending.ownersDone & ownerIndexBit > 0) { | ||||
|             pending.yetNeeded++; | ||||
|             pending.ownersDone -= ownerIndexBit; | ||||
|             Revoke(msg.sender, _operation); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // Replaces an owner `_from` with another `_to`. | ||||
|     function changeOwner(address _from, address _to) onlymanyowners(sha3(msg.data)) { | ||||
|         if (isOwner(_to)) return; | ||||
|         uint ownerIndex = m_ownerIndex[uint(_from)]; | ||||
|         if (ownerIndex == 0) return; | ||||
| 
 | ||||
|         clearPending(); | ||||
|         m_owners[ownerIndex] = uint(_to); | ||||
|         m_ownerIndex[uint(_from)] = 0; | ||||
|         m_ownerIndex[uint(_to)] = ownerIndex; | ||||
|         OwnerChanged(_from, _to); | ||||
|     } | ||||
| 
 | ||||
|     function addOwner(address _owner) onlymanyowners(sha3(msg.data)) { | ||||
|         if (isOwner(_owner)) return; | ||||
| 
 | ||||
|         clearPending(); | ||||
|         if (m_numOwners >= c_maxOwners) | ||||
|             reorganizeOwners(); | ||||
|         if (m_numOwners >= c_maxOwners) | ||||
|             return; | ||||
|         m_numOwners++; | ||||
|         m_owners[m_numOwners] = uint(_owner); | ||||
|         m_ownerIndex[uint(_owner)] = m_numOwners; | ||||
|         OwnerAdded(_owner); | ||||
|     } | ||||
| 
 | ||||
|     function removeOwner(address _owner) onlymanyowners(sha3(msg.data)) { | ||||
|         uint ownerIndex = m_ownerIndex[uint(_owner)]; | ||||
|         if (ownerIndex == 0) return; | ||||
|         if (m_required > m_numOwners - 1) return; | ||||
| 
 | ||||
|         m_owners[ownerIndex] = 0; | ||||
|         m_ownerIndex[uint(_owner)] = 0; | ||||
|         clearPending(); | ||||
|         reorganizeOwners(); //make sure m_numOwner is equal to the number of owners and always points to the optimal free slot | ||||
|         OwnerRemoved(_owner); | ||||
|     } | ||||
| 
 | ||||
|     function changeRequirement(uint _newRequired) onlymanyowners(sha3(msg.data)) { | ||||
|         if (_newRequired > m_numOwners) return; | ||||
|         m_required = _newRequired; | ||||
|         clearPending(); | ||||
|         RequirementChanged(_newRequired); | ||||
|     } | ||||
| 
 | ||||
|     function isOwner(address _addr) returns (bool) { | ||||
|         return m_ownerIndex[uint(_addr)] > 0; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     function hasConfirmed(bytes32 _operation, address _owner) constant returns (bool) { | ||||
|         var pending = m_pending[_operation]; | ||||
|         uint ownerIndex = m_ownerIndex[uint(_owner)]; | ||||
| 
 | ||||
|         // make sure they're an owner | ||||
|         if (ownerIndex == 0) return false; | ||||
| 
 | ||||
|         // determine the bit to set for this owner. | ||||
|         uint ownerIndexBit = 2**ownerIndex; | ||||
|         return !(pending.ownersDone & ownerIndexBit == 0); | ||||
|     } | ||||
| 
 | ||||
|     // INTERNAL METHODS | ||||
| 
 | ||||
|     function confirmAndCheck(bytes32 _operation) internal returns (bool) { | ||||
|         // determine what index the present sender is: | ||||
|         uint ownerIndex = m_ownerIndex[uint(msg.sender)]; | ||||
|         // make sure they're an owner | ||||
|         if (ownerIndex == 0) return; | ||||
| 
 | ||||
|         var pending = m_pending[_operation]; | ||||
|         // if we're not yet working on this operation, switch over and reset the confirmation status. | ||||
|         if (pending.yetNeeded == 0) { | ||||
|             // reset count of confirmations needed. | ||||
|             pending.yetNeeded = m_required; | ||||
|             // reset which owners have confirmed (none) - set our bitmap to 0. | ||||
|             pending.ownersDone = 0; | ||||
|             pending.index = m_pendingIndex.length++; | ||||
|             m_pendingIndex[pending.index] = _operation; | ||||
|         } | ||||
|         // determine the bit to set for this owner. | ||||
|         uint ownerIndexBit = 2**ownerIndex; | ||||
|         // make sure we (the message sender) haven't confirmed this operation previously. | ||||
|         if (pending.ownersDone & ownerIndexBit == 0) { | ||||
|             Confirmation(msg.sender, _operation); | ||||
|             // ok - check if count is enough to go ahead. | ||||
|             if (pending.yetNeeded <= 1) { | ||||
|                 // enough confirmations: reset and run interior. | ||||
|                 delete m_pendingIndex[m_pending[_operation].index]; | ||||
|                 delete m_pending[_operation]; | ||||
|                 return true; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 // not enough: record that this owner in particular confirmed. | ||||
|                 pending.yetNeeded--; | ||||
|                 pending.ownersDone |= ownerIndexBit; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     function reorganizeOwners() private { | ||||
|         uint free = 1; | ||||
|         while (free < m_numOwners) | ||||
|         { | ||||
|             while (free < m_numOwners && m_owners[free] != 0) free++; | ||||
|             while (m_numOwners > 1 && m_owners[m_numOwners] == 0) m_numOwners--; | ||||
|             if (free < m_numOwners && m_owners[m_numOwners] != 0 && m_owners[free] == 0) | ||||
|             { | ||||
|                 m_owners[free] = m_owners[m_numOwners]; | ||||
|                 m_ownerIndex[m_owners[free]] = free; | ||||
|                 m_owners[m_numOwners] = 0; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     function clearPending() internal { | ||||
|         uint length = m_pendingIndex.length; | ||||
|         for (uint i = 0; i < length; ++i) | ||||
|             if (m_pendingIndex[i] != 0) | ||||
|                 delete m_pending[m_pendingIndex[i]]; | ||||
|         delete m_pendingIndex; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     /****************************** | ||||
|      ****** DAY LIMIT SECTION ***** | ||||
|      ******************************/ | ||||
| 
 | ||||
|     // MODIFIERS | ||||
| 
 | ||||
|     // simple modifier for daily limit. | ||||
|     modifier limitedDaily(uint _value) { | ||||
|         if (underLimit(_value)) | ||||
|             _; | ||||
|     } | ||||
| 
 | ||||
|     // METHODS | ||||
| 
 | ||||
|     // constructor - stores initial daily limit and records the present day's index. | ||||
|     function initDaylimit(uint _limit) { | ||||
|         m_dailyLimit = _limit; | ||||
|         m_lastDay = today(); | ||||
|     } | ||||
|     // (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today. | ||||
|     function setDailyLimit(uint _newLimit) onlymanyowners(sha3(msg.data)) { | ||||
|         m_dailyLimit = _newLimit; | ||||
|     } | ||||
|     // resets the amount already spent today. needs many of the owners to confirm. | ||||
|     function resetSpentToday() onlymanyowners(sha3(msg.data)) { | ||||
|         m_spentToday = 0; | ||||
|     } | ||||
| 
 | ||||
|     // INTERNAL METHODS | ||||
| 
 | ||||
|     // checks to see if there is at least `_value` left from the daily limit today. if there is, subtracts it and | ||||
|     // returns true. otherwise just returns false. | ||||
|     function underLimit(uint _value) internal onlyowner returns (bool) { | ||||
|         // reset the spend limit if we're on a different day to last time. | ||||
|         if (today() > m_lastDay) { | ||||
|             m_spentToday = 0; | ||||
|             m_lastDay = today(); | ||||
|         } | ||||
|         // check to see if there's enough left - if so, subtract and return true. | ||||
|         // overflow protection                    // dailyLimit check | ||||
|         if (m_spentToday + _value >= m_spentToday && m_spentToday + _value <= m_dailyLimit) { | ||||
|             m_spentToday += _value; | ||||
|             return true; | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     // determines today's index. | ||||
|     function today() private constant returns (uint) { return now / 1 days; } | ||||
| 
 | ||||
| 
 | ||||
|     /****************************** | ||||
|      ********* WALLET SECTION ***** | ||||
|      ******************************/ | ||||
| 
 | ||||
|     // METHODS | ||||
| 
 | ||||
|     // constructor - just pass on the owner array to the multiowned and | ||||
|     // the limit to daylimit | ||||
|     function initWallet(address[] _owners, uint _required, uint _daylimit) { | ||||
|         initMultiowned(_owners, _required); | ||||
|         initDaylimit(_daylimit) ; | ||||
|     } | ||||
| 
 | ||||
|     // kills the contract sending everything to `_to`. | ||||
|     function kill(address _to) onlymanyowners(sha3(msg.data)) { | ||||
|         suicide(_to); | ||||
|     } | ||||
| 
 | ||||
|     // Outside-visible transact entry point. Executes transaction immediately if below daily spend limit. | ||||
|     // If not, goes into multisig process. We provide a hash on return to allow the sender to provide | ||||
|     // shortcuts for the other confirmations (allowing them to avoid replicating the _to, _value | ||||
|     // and _data arguments). They still get the option of using them if they want, anyways. | ||||
|     function execute(address _to, uint _value, bytes _data) onlyowner returns(bool _callValue) { | ||||
|         // first, take the opportunity to check that we're under the daily limit. | ||||
|         if (underLimit(_value)) { | ||||
|             SingleTransact(msg.sender, _value, _to, _data); | ||||
|             // yes - just execute the call. | ||||
|             _callValue =_to.call.value(_value)(_data); | ||||
|         } else { | ||||
|             // determine our operation hash. | ||||
|             bytes32 _r = sha3(msg.data, block.number); | ||||
|             if (!confirm(_r) && m_txs[_r].to == 0) { | ||||
|                 m_txs[_r].to = _to; | ||||
|                 m_txs[_r].value = _value; | ||||
|                 m_txs[_r].data = _data; | ||||
|                 ConfirmationNeeded(_r, msg.sender, _value, _to, _data); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // confirm a transaction through just the hash. we use the previous transactions map, m_txs, in order | ||||
|     // to determine the body of the transaction from the hash provided. | ||||
|     function confirm(bytes32 _h) onlymanyowners(_h) returns (bool) { | ||||
|         if (m_txs[_h].to != 0) { | ||||
|             m_txs[_h].to.call.value(m_txs[_h].value)(m_txs[_h].data); | ||||
|             MultiTransact(msg.sender, _h, m_txs[_h].value, m_txs[_h].to, m_txs[_h].data); | ||||
|             delete m_txs[_h]; | ||||
|             return true; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // INTERNAL METHODS | ||||
| 
 | ||||
|     function clearWalletPending() internal { | ||||
|         uint length = m_pendingIndex.length; | ||||
|         for (uint i = 0; i < length; ++i) | ||||
|             delete m_txs[m_pendingIndex[i]]; | ||||
|         clearPending(); | ||||
|     } | ||||
| 
 | ||||
|     // FIELDS | ||||
|     address constant _walletLibrary = 0xcafecafecafecafecafecafecafecafecafecafe; | ||||
| 
 | ||||
|     // the number of owners that must confirm the same operation before it is run. | ||||
|     uint m_required; | ||||
|     // pointer used to find a free slot in m_owners | ||||
|     uint m_numOwners; | ||||
| 
 | ||||
|     uint public m_dailyLimit; | ||||
|     uint public m_spentToday; | ||||
|     uint public m_lastDay; | ||||
| 
 | ||||
|     // list of owners | ||||
|     uint[256] m_owners; | ||||
|     uint constant c_maxOwners = 250; | ||||
| 
 | ||||
|     // index on the list of owners to allow reverse lookup | ||||
|     mapping(uint => uint) m_ownerIndex; | ||||
|     // the ongoing operations. | ||||
|     mapping(bytes32 => PendingState) m_pending; | ||||
|     bytes32[] m_pendingIndex; | ||||
| 
 | ||||
|     // pending transactions we have at present. | ||||
|     mapping (bytes32 => Transaction) m_txs; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| contract Wallet is multisig { | ||||
| 
 | ||||
|     // WALLET CONSTRUCTOR | ||||
|     //   calls the `initWallet` method of the Library in this context | ||||
|     function Wallet(address[] _owners, uint _required, uint _daylimit) { | ||||
|         // Signature of the Wallet Library's init function | ||||
|         bytes4 sig = bytes4(sha3("initWallet(address[],uint256,uint256)")); | ||||
|         address target = _walletLibrary; | ||||
| 
 | ||||
|         // Compute the size of the call data : arrays has 2 | ||||
|         // 32bytes for offset and length, plus 32bytes per element ; | ||||
|         // plus 2 32bytes for each uint | ||||
|         uint argarraysize = (2 + _owners.length); | ||||
|         uint argsize = (2 + argarraysize) * 32; | ||||
| 
 | ||||
|         assembly { | ||||
|             // Add the signature first to memory | ||||
|             mstore(0x0, sig) | ||||
|             // Add the call data, which is at the end of the | ||||
|             // code | ||||
|             codecopy(0x4,  sub(codesize, argsize), argsize) | ||||
|             // Delegate call to the library | ||||
|             delegatecall(sub(gas, 10000), target, 0x0, add(argsize, 0x4), 0x0, 0x0) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // METHODS | ||||
| 
 | ||||
|     // gets called when no other function matches | ||||
|     function() payable { | ||||
|         // just being sent some cash? | ||||
|         if (msg.value > 0) | ||||
|             Deposit(msg.sender, msg.value); | ||||
|         else if (msg.data.length > 0) | ||||
|             _walletLibrary.delegatecall(msg.data); | ||||
|     } | ||||
| 
 | ||||
|     // Gets an owner by 0-indexed position (using numOwners as the count) | ||||
|     function getOwner(uint ownerIndex) constant returns (address) { | ||||
|         return address(m_owners[ownerIndex + 1]); | ||||
|     } | ||||
| 
 | ||||
|     // As return statement unavailable in fallback, explicit the method here | ||||
| 
 | ||||
|     function hasConfirmed(bytes32 _operation, address _owner) constant returns (bool) { | ||||
|         return _walletLibrary.delegatecall(msg.data); | ||||
|     } | ||||
| 
 | ||||
|     function isOwner(address _addr) returns (bool) { | ||||
|         return _walletLibrary.delegatecall(msg.data); | ||||
|     } | ||||
| 
 | ||||
|     // FIELDS | ||||
|     address constant _walletLibrary = 0xcafecafecafecafecafecafecafecafecafecafe; | ||||
| 
 | ||||
|     // the number of owners that must confirm the same operation before it is run. | ||||
|     uint public m_required; | ||||
|     // pointer used to find a free slot in m_owners | ||||
|     uint public m_numOwners; | ||||
| 
 | ||||
|     uint public m_dailyLimit; | ||||
|     uint public m_spentToday; | ||||
|     uint public m_lastDay; | ||||
| 
 | ||||
|     // list of owners | ||||
|     uint[256] m_owners; | ||||
| } | ||||
| @ -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(<Application />); | ||||
|  | ||||
| @ -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( | ||||
|  | ||||
| @ -43,7 +43,7 @@ export default { | ||||
|         }, | ||||
|         uuid: { | ||||
|           type: String, | ||||
|           desc: 'The account UUID, or null if not available/unknown/not applicable.' | ||||
|           desc: 'The account Uuid, or null if not available/unknown/not applicable.' | ||||
|         } | ||||
|       } | ||||
|     } | ||||
| @ -66,7 +66,7 @@ export default { | ||||
|         }, | ||||
|         uuid: { | ||||
|           type: String, | ||||
|           desc: 'The account UUID, or null if not available/unknown/not applicable.' | ||||
|           desc: 'The account Uuid, or null if not available/unknown/not applicable.' | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|  | ||||
| @ -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 | ||||
|   })] | ||||
| }; | ||||
| @ -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 = { | ||||
|  | ||||
| @ -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'; | ||||
| 
 | ||||
|  | ||||
| @ -17,6 +17,7 @@ | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| 
 | ||||
| import { RadioButtons } from '~/ui'; | ||||
| import { walletSourceURL } from '~/contracts/code/wallet'; | ||||
| 
 | ||||
| // import styles from '../createWallet.css';
 | ||||
| 
 | ||||
| @ -46,7 +47,9 @@ export default class WalletType extends Component { | ||||
|         description: ( | ||||
|           <span> | ||||
|             <span>Create/Deploy a </span> | ||||
|             <a href='https://github.com/ethereum/dapp-bin/blob/master/wallet/wallet.sol' target='_blank'>standard multi-signature </a> | ||||
|             <a href={ walletSourceURL } target='_blank'> | ||||
|               standard multi-signature | ||||
|             </a> | ||||
|             <span> Wallet</span> | ||||
|           </span> | ||||
|         ) | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
| @ -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 = { | ||||
|  | ||||
| @ -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'; | ||||
| 
 | ||||
|  | ||||
| @ -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'; | ||||
|  | ||||
| @ -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 = { | ||||
|  | ||||
| @ -15,13 +15,19 @@ | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| import { MenuItem } from 'material-ui'; | ||||
| import { Checkbox, MenuItem } from 'material-ui'; | ||||
| 
 | ||||
| import { AddressSelect, Form, Input, Select, TypedInput } from '~/ui'; | ||||
| import { parseAbiType } from '~/util/abi'; | ||||
| 
 | ||||
| import styles from '../executeContract.css'; | ||||
| 
 | ||||
| const CHECK_STYLE = { | ||||
|   position: 'absolute', | ||||
|   top: '38px', | ||||
|   left: '1em' | ||||
| }; | ||||
| 
 | ||||
| export default class DetailsStep extends Component { | ||||
|   static propTypes = { | ||||
|     accounts: PropTypes.object.isRequired, | ||||
| @ -31,10 +37,12 @@ export default class DetailsStep extends Component { | ||||
|     onAmountChange: PropTypes.func.isRequired, | ||||
|     fromAddress: PropTypes.string, | ||||
|     fromAddressError: PropTypes.string, | ||||
|     gasEdit: PropTypes.bool, | ||||
|     onFromAddressChange: PropTypes.func.isRequired, | ||||
|     func: PropTypes.object, | ||||
|     funcError: PropTypes.string, | ||||
|     onFuncChange: PropTypes.func, | ||||
|     onGasEditClick: PropTypes.func, | ||||
|     values: PropTypes.array.isRequired, | ||||
|     valuesError: PropTypes.array.isRequired, | ||||
|     warning: PropTypes.string, | ||||
| @ -42,7 +50,7 @@ export default class DetailsStep extends Component { | ||||
|   } | ||||
| 
 | ||||
|   render () { | ||||
|     const { accounts, amount, amountError, fromAddress, fromAddressError, onFromAddressChange, onAmountChange } = this.props; | ||||
|     const { accounts, amount, amountError, fromAddress, fromAddressError, gasEdit, onGasEditClick, onFromAddressChange, onAmountChange } = this.props; | ||||
| 
 | ||||
|     return ( | ||||
|       <Form> | ||||
| @ -56,12 +64,23 @@ export default class DetailsStep extends Component { | ||||
|           onChange={ onFromAddressChange } /> | ||||
|         { this.renderFunctionSelect() } | ||||
|         { this.renderParameters() } | ||||
|         <Input | ||||
|           label='transaction value (in ETH)' | ||||
|           hint='the amount to send to with the transaction' | ||||
|           value={ amount } | ||||
|           error={ amountError } | ||||
|           onSubmit={ onAmountChange } /> | ||||
|         <div className={ styles.columns }> | ||||
|           <div> | ||||
|             <Input | ||||
|               label='transaction value (in ETH)' | ||||
|               hint='the amount to send to with the transaction' | ||||
|               value={ amount } | ||||
|               error={ amountError } | ||||
|               onSubmit={ onAmountChange } /> | ||||
|           </div> | ||||
|           <div> | ||||
|             <Checkbox | ||||
|               checked={ gasEdit } | ||||
|               label='edit gas price or value' | ||||
|               onCheck={ onGasEditClick } | ||||
|               style={ CHECK_STYLE } /> | ||||
|           </div> | ||||
|         </div> | ||||
|       </Form> | ||||
|     ); | ||||
|   } | ||||
|  | ||||
| @ -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; | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -17,19 +17,36 @@ | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| import { connect } from 'react-redux'; | ||||
| import { bindActionCreators } from 'redux'; | ||||
| import { observer } from 'mobx-react'; | ||||
| import ActionDoneAll from 'material-ui/svg-icons/action/done-all'; | ||||
| import ContentClear from 'material-ui/svg-icons/content/clear'; | ||||
| import NavigationArrowBack from 'material-ui/svg-icons/navigation/arrow-back'; | ||||
| import NavigationArrowForward from 'material-ui/svg-icons/navigation/arrow-forward'; | ||||
| 
 | ||||
| import { BusyStep, CompletedStep, Button, IdentityIcon, Modal, TxHash } from '~/ui'; | ||||
| import { 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, | ||||
| @ -46,21 +63,22 @@ class ExecuteContract extends Component { | ||||
|     onFromAddressChange: PropTypes.func.isRequired | ||||
|   } | ||||
| 
 | ||||
|   gasStore = new GasPriceEditor.Store(this.context.api, this.props.gasLimit); | ||||
| 
 | ||||
|   state = { | ||||
|     amount: '0', | ||||
|     amountError: null, | ||||
|     busyState: null, | ||||
|     fromAddressError: null, | ||||
|     func: null, | ||||
|     funcError: null, | ||||
|     gas: null, | ||||
|     gasLimitError: null, | ||||
|     gasEdit: false, | ||||
|     rejected: false, | ||||
|     step: STEP_DETAILS, | ||||
|     sending: false, | ||||
|     values: [], | ||||
|     valuesError: [], | ||||
|     step: 0, | ||||
|     sending: false, | ||||
|     busyState: null, | ||||
|     txhash: null, | ||||
|     rejected: false | ||||
|     txhash: null | ||||
|   } | ||||
| 
 | ||||
|   componentDidMount () { | ||||
| @ -79,15 +97,21 @@ class ExecuteContract extends Component { | ||||
|   } | ||||
| 
 | ||||
|   render () { | ||||
|     const { sending } = this.state; | ||||
|     const { sending, step, gasEdit, rejected } = this.state; | ||||
|     const steps = gasEdit ? STAGES_GAS : STAGES_BASIC; | ||||
| 
 | ||||
|     if (rejected) { | ||||
|       steps[steps.length - 1] = TITLES.rejected; | ||||
|     } | ||||
| 
 | ||||
|     return ( | ||||
|       <Modal | ||||
|         actions={ this.renderDialogActions() } | ||||
|         title='execute function' | ||||
|         busy={ sending } | ||||
|         waiting={ [1] } | ||||
|         visible> | ||||
|         current={ step } | ||||
|         steps={ steps } | ||||
|         visible | ||||
|         waiting={ gasEdit ? [STEP_BUSY] : [STEP_BUSY_OR_GAS] }> | ||||
|         { this.renderStep() } | ||||
|       </Modal> | ||||
|     ); | ||||
| @ -95,7 +119,7 @@ class ExecuteContract extends Component { | ||||
| 
 | ||||
|   renderDialogActions () { | ||||
|     const { onClose, fromAddress } = this.props; | ||||
|     const { sending, step, fromAddressError, valuesError } = this.state; | ||||
|     const { gasEdit, sending, step, fromAddressError, valuesError } = this.state; | ||||
|     const hasError = fromAddressError || valuesError.find((error) => error); | ||||
| 
 | ||||
|     const cancelBtn = ( | ||||
| @ -105,21 +129,44 @@ class ExecuteContract extends Component { | ||||
|         icon={ <ContentClear /> } | ||||
|         onClick={ onClose } /> | ||||
|     ); | ||||
|     const postBtn = ( | ||||
|       <Button | ||||
|         key='postTransaction' | ||||
|         label='post transaction' | ||||
|         disabled={ !!(sending || hasError) } | ||||
|         icon={ <IdentityIcon address={ fromAddress } button /> } | ||||
|         onClick={ this.postTransaction } /> | ||||
|     ); | ||||
|     const nextBtn = ( | ||||
|       <Button | ||||
|         key='nextStep' | ||||
|         label='next' | ||||
|         icon={ <NavigationArrowForward /> } | ||||
|         onClick={ this.onNextClick } /> | ||||
|     ); | ||||
|     const prevBtn = ( | ||||
|       <Button | ||||
|         key='prevStep' | ||||
|         label='prev' | ||||
|         icon={ <NavigationArrowBack /> } | ||||
|         onClick={ this.onPrevClick } /> | ||||
|     ); | ||||
| 
 | ||||
|     if (step === 0) { | ||||
|     if (step === STEP_DETAILS) { | ||||
|       return [ | ||||
|         cancelBtn, | ||||
|         <Button | ||||
|           key='postTransaction' | ||||
|           label='post transaction' | ||||
|           disabled={ !!(sending || hasError) } | ||||
|           icon={ <IdentityIcon address={ fromAddress } button /> } | ||||
|           onClick={ this.postTransaction } /> | ||||
|         gasEdit ? nextBtn : postBtn | ||||
|       ]; | ||||
|     } else if (step === 1) { | ||||
|     } else if (step === (gasEdit ? STEP_BUSY : STEP_BUSY_OR_GAS)) { | ||||
|       return [ | ||||
|         cancelBtn | ||||
|       ]; | ||||
|     } else if (gasEdit && (step === STEP_BUSY_OR_GAS)) { | ||||
|       return [ | ||||
|         cancelBtn, | ||||
|         prevBtn, | ||||
|         postBtn | ||||
|       ]; | ||||
|     } | ||||
| 
 | ||||
|     return [ | ||||
| @ -133,7 +180,8 @@ class ExecuteContract extends Component { | ||||
| 
 | ||||
|   renderStep () { | ||||
|     const { onFromAddressChange } = this.props; | ||||
|     const { step, busyState, gasLimitError, txhash, rejected } = this.state; | ||||
|     const { gasEdit, step, busyState, txhash, rejected } = this.state; | ||||
|     const { errorEstimated } = this.gasStore; | ||||
| 
 | ||||
|     if (rejected) { | ||||
|       return ( | ||||
| @ -144,23 +192,29 @@ class ExecuteContract extends Component { | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     if (step === 0) { | ||||
|     if (step === STEP_DETAILS) { | ||||
|       return ( | ||||
|         <DetailsStep | ||||
|           { ...this.props } | ||||
|           { ...this.state } | ||||
|           warning={ gasLimitError } | ||||
|           warning={ errorEstimated } | ||||
|           onAmountChange={ this.onAmountChange } | ||||
|           onFromAddressChange={ onFromAddressChange } | ||||
|           onFuncChange={ this.onFuncChange } | ||||
|           onGasEditClick={ this.onGasEditClick } | ||||
|           onValueChange={ this.onValueChange } /> | ||||
|       ); | ||||
|     } else if (step === 1) { | ||||
|     } else if (step === (gasEdit ? STEP_BUSY : STEP_BUSY_OR_GAS)) { | ||||
|       return ( | ||||
|         <BusyStep | ||||
|           title='The function execution is in progress' | ||||
|           state={ busyState } /> | ||||
|       ); | ||||
|     } else if (gasEdit && (step === STEP_BUSY_OR_GAS)) { | ||||
|       return ( | ||||
|         <GasPriceEditor | ||||
|           store={ this.gasStore } /> | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     return ( | ||||
| @ -171,6 +225,7 @@ class ExecuteContract extends Component { | ||||
|   } | ||||
| 
 | ||||
|   onAmountChange = (amount) => { | ||||
|     this.gasStore.setEthValue(amount); | ||||
|     this.setState({ amount }, this.estimateGas); | ||||
|   } | ||||
| 
 | ||||
| @ -221,7 +276,7 @@ class ExecuteContract extends Component { | ||||
| 
 | ||||
|   estimateGas = (_fromAddress) => { | ||||
|     const { api } = this.context; | ||||
|     const { fromAddress, gasLimit } = this.props; | ||||
|     const { fromAddress } = this.props; | ||||
|     const { amount, func, values } = this.state; | ||||
|     const options = { | ||||
|       gas: MAX_GAS_ESTIMATION, | ||||
| @ -237,18 +292,11 @@ class ExecuteContract extends Component { | ||||
|       .estimateGas(options, values) | ||||
|       .then((gasEst) => { | ||||
|         const gas = gasEst.mul(1.2); | ||||
|         let gasLimitError = null; | ||||
| 
 | ||||
|         if (gas.gte(MAX_GAS_ESTIMATION)) { | ||||
|           gasLimitError = ERRORS.gasException; | ||||
|         } else if (gas.gt(gasLimit)) { | ||||
|           gasLimitError = ERRORS.gasBlockLimit; | ||||
|         } | ||||
|         console.log(`estimateGas: received ${gasEst.toFormat(0)}, adjusted to ${gas.toFormat(0)}`); | ||||
| 
 | ||||
|         this.setState({ | ||||
|           gas, | ||||
|           gasLimitError | ||||
|         }); | ||||
|         this.gasStore.setEstimated(gasEst.toFixed(0)); | ||||
|         this.gasStore.setGas(gas.toFixed(0)); | ||||
|       }) | ||||
|       .catch((error) => { | ||||
|         console.warn('estimateGas', error); | ||||
| @ -258,22 +306,20 @@ class ExecuteContract extends Component { | ||||
|   postTransaction = () => { | ||||
|     const { api, store } = this.context; | ||||
|     const { fromAddress } = this.props; | ||||
|     const { amount, func, values } = this.state; | ||||
|     const { amount, func, gasEdit, values } = this.state; | ||||
|     const steps = gasEdit ? STAGES_GAS : STAGES_BASIC; | ||||
|     const finalstep = steps.length - 1; | ||||
|     const options = { | ||||
|       gas: MAX_GAS_ESTIMATION, | ||||
|       gas: this.gasStore.gas, | ||||
|       gasPrice: this.gasStore.price, | ||||
|       from: fromAddress, | ||||
|       value: api.util.toWei(amount || 0) | ||||
|     }; | ||||
| 
 | ||||
|     this.setState({ sending: true, step: 1 }); | ||||
|     this.setState({ sending: true, step: gasEdit ? STEP_BUSY : STEP_BUSY_OR_GAS }); | ||||
| 
 | ||||
|     func | ||||
|       .estimateGas(options, values) | ||||
|       .then((gas) => { | ||||
|         options.gas = gas.mul(1.2).toFixed(0); | ||||
|         console.log(`estimateGas: received ${gas.toFormat(0)}, adjusted to ${gas.mul(1.2).toFormat(0)}`); | ||||
|         return func.postTransaction(options, values); | ||||
|       }) | ||||
|       .postTransaction(options, values) | ||||
|       .then((requestId) => { | ||||
|         this.setState({ busyState: 'Waiting for authorization in the Parity Signer' }); | ||||
| 
 | ||||
| @ -281,7 +327,7 @@ class ExecuteContract extends Component { | ||||
|           .pollMethod('parity_checkRequest', requestId) | ||||
|           .catch((error) => { | ||||
|             if (error.code === ERROR_CODES.REQUEST_REJECTED) { | ||||
|               this.setState({ rejected: true }); | ||||
|               this.setState({ rejected: true, step: finalstep }); | ||||
|               return false; | ||||
|             } | ||||
| 
 | ||||
| @ -289,13 +335,31 @@ class ExecuteContract extends Component { | ||||
|           }); | ||||
|       }) | ||||
|       .then((txhash) => { | ||||
|         this.setState({ sending: false, step: 2, txhash, busyState: 'Your transaction has been posted to the network' }); | ||||
|         this.setState({ sending: false, step: finalstep, txhash, busyState: 'Your transaction has been posted to the network' }); | ||||
|       }) | ||||
|       .catch((error) => { | ||||
|         console.error('postTransaction', error); | ||||
|         store.dispatch({ type: 'newError', error }); | ||||
|       }); | ||||
|   } | ||||
| 
 | ||||
|   onGasEditClick = () => { | ||||
|     this.setState({ | ||||
|       gasEdit: !this.state.gasEdit | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   onNextClick = () => { | ||||
|     this.setState({ | ||||
|       step: this.state.step + 1 | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   onPrevClick = () => { | ||||
|     this.setState({ | ||||
|       step: this.state.step - 1 | ||||
|     }); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| function mapStateToProps (state) { | ||||
|  | ||||
| @ -21,9 +21,8 @@ import { sha3 } from '~/api/util/sha3'; | ||||
| import Contracts from '~/contracts'; | ||||
| 
 | ||||
| import { checkIfVerified, checkIfRequested, awaitPuzzle } from '~/contracts/sms-verification'; | ||||
| import { postToServer } from '../../3rdparty/sms-verification'; | ||||
| import checkIfTxFailed from '../../util/check-if-tx-failed'; | ||||
| import waitForConfirmations from '../../util/wait-for-block-confirmations'; | ||||
| import { postToServer } from '~/3rdparty/sms-verification'; | ||||
| import { checkIfTxFailed, waitForConfirmations } from '~/util/tx'; | ||||
| 
 | ||||
| export const LOADING = 'fetching-contract'; | ||||
| export const QUERY_DATA = 'query-data'; | ||||
|  | ||||
| @ -20,7 +20,7 @@ import SaveIcon from 'material-ui/svg-icons/content/save'; | ||||
| import ContentClear from 'material-ui/svg-icons/content/clear'; | ||||
| 
 | ||||
| import { Button, Modal, Editor, Form, Input } from '~/ui'; | ||||
| import { ERRORS, validateName } from '../../util/validation'; | ||||
| import { ERRORS, validateName } from '~/util/validation'; | ||||
| 
 | ||||
| import styles from './saveContract.css'; | ||||
| 
 | ||||
|  | ||||
| @ -19,7 +19,7 @@ import ActionDoneAll from 'material-ui/svg-icons/action/done-all'; | ||||
| import ContentClear from 'material-ui/svg-icons/content/clear'; | ||||
| 
 | ||||
| import { Button, IdentityIcon, Modal } from '~/ui'; | ||||
| import initShapeshift from '../../3rdparty/shapeshift'; | ||||
| import initShapeshift from '~/3rdparty/shapeshift'; | ||||
| import shapeshiftLogo from '../../../assets/images/shapeshift-logo.png'; | ||||
| 
 | ||||
| import AwaitingDepositStep from './AwaitingDepositStep'; | ||||
|  | ||||
| @ -16,96 +16,28 @@ | ||||
| 
 | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| 
 | ||||
| import Form, { Input } from '~/ui/Form'; | ||||
| import GasPriceSelector from '../GasPriceSelector'; | ||||
| 
 | ||||
| import styles from '../transfer.css'; | ||||
| import { GasPriceEditor, Form, Input } from '~/ui'; | ||||
| 
 | ||||
| export default class Extras extends Component { | ||||
|   static propTypes = { | ||||
|     isEth: PropTypes.bool, | ||||
|     data: PropTypes.string, | ||||
|     dataError: PropTypes.string, | ||||
|     gas: PropTypes.string, | ||||
|     gasEst: PropTypes.string, | ||||
|     gasError: PropTypes.string, | ||||
|     gasPrice: PropTypes.oneOfType([ | ||||
|       PropTypes.string, | ||||
|       PropTypes.object | ||||
|     ]), | ||||
|     gasPriceDefault: PropTypes.string, | ||||
|     gasPriceError: PropTypes.string, | ||||
|     gasPriceHistogram: PropTypes.object, | ||||
|     total: PropTypes.string, | ||||
|     totalError: PropTypes.string, | ||||
|     onChange: PropTypes.func.isRequired | ||||
|     onChange: PropTypes.func.isRequired, | ||||
|     gasStore: PropTypes.object.isRequired | ||||
|   } | ||||
| 
 | ||||
|   render () { | ||||
|     const { gas, gasPrice, gasError, gasEst, gasPriceDefault, gasPriceError, gasPriceHistogram, total, totalError } = this.props; | ||||
| 
 | ||||
|     const gasLabel = `gas amount (estimated: ${gasEst})`; | ||||
|     const priceLabel = `gas price (current: ${gasPriceDefault})`; | ||||
|     const { gasStore, onChange } = this.props; | ||||
| 
 | ||||
|     return ( | ||||
|       <Form> | ||||
| 
 | ||||
|         { this.renderData() } | ||||
| 
 | ||||
|         <div className={ styles.columns }> | ||||
|           <div style={ { flex: 65 } }> | ||||
|             <GasPriceSelector | ||||
|               gasPriceHistogram={ gasPriceHistogram } | ||||
|               gasPrice={ gasPrice } | ||||
|               onChange={ this.onEditGasPrice } | ||||
|             /> | ||||
|           </div> | ||||
| 
 | ||||
|           <div | ||||
|             className={ styles.row } | ||||
|             style={ { | ||||
|               flex: 35, paddingLeft: '1rem', | ||||
|               justifyContent: 'space-around', | ||||
|               paddingBottom: 12 | ||||
|             } } | ||||
|           > | ||||
|             <div className={ styles.row }> | ||||
|               <Input | ||||
|                 label={ gasLabel } | ||||
|                 hint='the amount of gas to use for the transaction' | ||||
|                 error={ gasError } | ||||
|                 value={ gas } | ||||
|                 onChange={ this.onEditGas } /> | ||||
| 
 | ||||
|               <Input | ||||
|                 label={ priceLabel } | ||||
|                 hint='the price of gas to use for the transaction' | ||||
|                 error={ gasPriceError } | ||||
|                 value={ (gasPrice || '').toString() } | ||||
|                 onChange={ this.onEditGasPrice } /> | ||||
|             </div> | ||||
| 
 | ||||
|             <div className={ styles.row }> | ||||
|               <Input | ||||
|                 disabled | ||||
|                 label='total transaction amount' | ||||
|                 hint='the total amount of the transaction' | ||||
|                 error={ totalError } | ||||
|                 value={ `${total} ETH` } /> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
| 
 | ||||
|         <div> | ||||
|           <p className={ styles.gasPriceDesc }> | ||||
|             You can choose the gas price based on the | ||||
|             distribution of recent included transactions' gas prices. | ||||
|             The lower the gas price is, the cheaper the transaction will | ||||
|             be. The higher the gas price is, the faster it should | ||||
|             get mined by the network. | ||||
|           </p> | ||||
|         </div> | ||||
| 
 | ||||
|         <GasPriceEditor | ||||
|           store={ gasStore } | ||||
|           onChange={ onChange } /> | ||||
|       </Form> | ||||
|     ); | ||||
|   } | ||||
| @ -129,14 +61,6 @@ export default class Extras extends Component { | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   onEditGas = (event) => { | ||||
|     this.props.onChange('gas', event.target.value); | ||||
|   } | ||||
| 
 | ||||
|   onEditGasPrice = (event, value) => { | ||||
|     this.props.onChange('gasPrice', value); | ||||
|   } | ||||
| 
 | ||||
|   onEditData = (event) => { | ||||
|     this.props.onChange('data', event.target.value); | ||||
|   } | ||||
|  | ||||
| @ -23,7 +23,8 @@ import { bytesToHex } from '~/api/util/format'; | ||||
| import Contract from '~/api/contract'; | ||||
| import ERRORS from './errors'; | ||||
| import { ERROR_CODES } from '~/api/transport/error'; | ||||
| import { DEFAULT_GAS, DEFAULT_GASPRICE, MAX_GAS_ESTIMATION } from '~/util/constants'; | ||||
| import { DEFAULT_GAS, MAX_GAS_ESTIMATION } from '~/util/constants'; | ||||
| import GasPriceStore from '~/ui/GasPriceEditor/store'; | ||||
| 
 | ||||
| const TITLES = { | ||||
|   transfer: 'transfer details', | ||||
| @ -48,14 +49,6 @@ export default class TransferStore { | ||||
|   @observable data = ''; | ||||
|   @observable dataError = null; | ||||
| 
 | ||||
|   @observable gas = DEFAULT_GAS; | ||||
|   @observable gasError = null; | ||||
| 
 | ||||
|   @observable gasEst = '0'; | ||||
|   @observable gasLimitError = null; | ||||
|   @observable gasPrice = DEFAULT_GASPRICE; | ||||
|   @observable gasPriceError = null; | ||||
| 
 | ||||
|   @observable recipient = ''; | ||||
|   @observable recipientError = ERRORS.requireRecipient; | ||||
| 
 | ||||
| @ -68,11 +61,8 @@ export default class TransferStore { | ||||
|   @observable value = '0.0'; | ||||
|   @observable valueError = null; | ||||
| 
 | ||||
|   gasPriceHistogram = {}; | ||||
| 
 | ||||
|   account = null; | ||||
|   balance = null; | ||||
|   gasLimit = null; | ||||
|   onClose = null; | ||||
| 
 | ||||
|   senders = null; | ||||
| @ -81,6 +71,8 @@ export default class TransferStore { | ||||
|   isWallet = false; | ||||
|   wallet = null; | ||||
| 
 | ||||
|   gasStore = null; | ||||
| 
 | ||||
|   @computed get steps () { | ||||
|     const steps = [].concat(this.extras ? STAGES_EXTRA : STAGES_BASIC); | ||||
| 
 | ||||
| @ -93,7 +85,7 @@ export default class TransferStore { | ||||
| 
 | ||||
|   @computed get isValid () { | ||||
|     const detailsValid = !this.recipientError && !this.valueError && !this.totalError && !this.senderError; | ||||
|     const extrasValid = !this.gasError && !this.gasPriceError && !this.totalError; | ||||
|     const extrasValid = !this.gasStore.errorGas && !this.gasStore.errorPrice && !this.totalError; | ||||
|     const verifyValid = !this.passwordError; | ||||
| 
 | ||||
|     switch (this.stage) { | ||||
| @ -115,14 +107,14 @@ export default class TransferStore { | ||||
|   constructor (api, props) { | ||||
|     this.api = api; | ||||
| 
 | ||||
|     const { account, balance, gasLimit, senders, onClose, newError, sendersBalances } = props; | ||||
|     const { account, balance, gasLimit, senders, newError, sendersBalances } = props; | ||||
|     this.account = account; | ||||
|     this.balance = balance; | ||||
|     this.gasLimit = gasLimit; | ||||
|     this.onClose = onClose; | ||||
|     this.isWallet = account && account.wallet; | ||||
|     this.newError = newError; | ||||
| 
 | ||||
|     this.gasStore = new GasPriceStore(api, gasLimit); | ||||
| 
 | ||||
|     if (this.isWallet) { | ||||
|       this.wallet = props.wallet; | ||||
|       this.walletContract = new Contract(this.api, walletAbi); | ||||
| @ -143,8 +135,7 @@ export default class TransferStore { | ||||
|     this.stage -= 1; | ||||
|   } | ||||
| 
 | ||||
|   @action onClose = () => { | ||||
|     this.onClose && this.onClose(); | ||||
|   @action handleClose = () => { | ||||
|     this.stage = 0; | ||||
|   } | ||||
| 
 | ||||
| @ -179,26 +170,6 @@ export default class TransferStore { | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @action getDefaults = () => { | ||||
|     Promise | ||||
|       .all([ | ||||
|         this.api.parity.gasPriceHistogram(), | ||||
|         this.api.eth.gasPrice() | ||||
|       ]) | ||||
|       .then(([gasPriceHistogram, gasPrice]) => { | ||||
|         transaction(() => { | ||||
|           this.gasPrice = gasPrice.toString(); | ||||
|           this.gasPriceDefault = gasPrice.toFormat(); | ||||
|           this.gasPriceHistogram = gasPriceHistogram; | ||||
| 
 | ||||
|           this.recalculate(); | ||||
|         }); | ||||
|       }) | ||||
|       .catch((error) => { | ||||
|         console.warn('getDefaults', error); | ||||
|       }); | ||||
|   } | ||||
| 
 | ||||
|   @action onSend = () => { | ||||
|     this.onNext(); | ||||
|     this.sending = true; | ||||
| @ -281,25 +252,11 @@ export default class TransferStore { | ||||
|   } | ||||
| 
 | ||||
|   @action _onUpdateGas = (gas) => { | ||||
|     const gasError = this._validatePositiveNumber(gas); | ||||
| 
 | ||||
|     transaction(() => { | ||||
|       this.gas = gas; | ||||
|       this.gasError = gasError; | ||||
| 
 | ||||
|       this.recalculate(); | ||||
|     }); | ||||
|     this.recalculate(); | ||||
|   } | ||||
| 
 | ||||
|   @action _onUpdateGasPrice = (gasPrice) => { | ||||
|     const gasPriceError = this._validatePositiveNumber(gasPrice); | ||||
| 
 | ||||
|     transaction(() => { | ||||
|       this.gasPrice = gasPrice; | ||||
|       this.gasPriceError = gasPriceError; | ||||
| 
 | ||||
|       this.recalculate(); | ||||
|     }); | ||||
|     this.recalculate(); | ||||
|   } | ||||
| 
 | ||||
|   @action _onUpdateRecipient = (recipient) => { | ||||
| @ -362,7 +319,7 @@ export default class TransferStore { | ||||
| 
 | ||||
|   @action recalculateGas = () => { | ||||
|     if (!this.isValid) { | ||||
|       this.gas = 0; | ||||
|       this.gasStore.setGas('0'); | ||||
|       return this.recalculate(); | ||||
|     } | ||||
| 
 | ||||
| @ -370,28 +327,20 @@ export default class TransferStore { | ||||
|       .estimateGas() | ||||
|       .then((gasEst) => { | ||||
|         let gas = gasEst; | ||||
|         let gasLimitError = null; | ||||
| 
 | ||||
|         if (gas.gt(DEFAULT_GAS)) { | ||||
|           gas = gas.mul(1.2); | ||||
|         } | ||||
| 
 | ||||
|         if (gas.gte(MAX_GAS_ESTIMATION)) { | ||||
|           gasLimitError = ERRORS.gasException; | ||||
|         } else if (gas.gt(this.gasLimit)) { | ||||
|           gasLimitError = ERRORS.gasBlockLimit; | ||||
|         } | ||||
| 
 | ||||
|         transaction(() => { | ||||
|           this.gas = gas.toFixed(0); | ||||
|           this.gasEst = gasEst.toFormat(); | ||||
|           this.gasLimitError = gasLimitError; | ||||
|           this.gasStore.setEstimated(gasEst.toFixed(0)); | ||||
|           this.gasStore.setGas(gas.toFixed(0)); | ||||
| 
 | ||||
|           this.recalculate(); | ||||
|         }); | ||||
|       }) | ||||
|       .catch((error) => { | ||||
|         console.error('etimateGas', error); | ||||
|         console.warn('etimateGas', error); | ||||
|         this.recalculate(); | ||||
|       }); | ||||
|   } | ||||
| @ -411,9 +360,9 @@ export default class TransferStore { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     const { gas, gasPrice, tag, valueAll, isEth, isWallet } = this; | ||||
|     const { tag, valueAll, isEth, isWallet } = this; | ||||
| 
 | ||||
|     const gasTotal = new BigNumber(gasPrice || 0).mul(new BigNumber(gas || 0)); | ||||
|     const gasTotal = new BigNumber(this.gasStore.price || 0).mul(new BigNumber(this.gasStore.gas || 0)); | ||||
| 
 | ||||
|     const availableEth = new BigNumber(balance.tokens[0].value); | ||||
| 
 | ||||
| @ -453,10 +402,12 @@ export default class TransferStore { | ||||
|     } | ||||
| 
 | ||||
|     transaction(() => { | ||||
|       this.total = this.api.util.fromWei(totalEth).toString(); | ||||
|       this.total = this.api.util.fromWei(totalEth).toFixed(); | ||||
|       this.totalError = totalError; | ||||
|       this.value = value; | ||||
|       this.valueError = valueError; | ||||
|       this.gasStore.setErrorTotal(totalError); | ||||
|       this.gasStore.setEthValue(totalEth); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
| @ -522,8 +473,8 @@ export default class TransferStore { | ||||
|     }; | ||||
| 
 | ||||
|     if (!gas) { | ||||
|       options.gas = this.gas; | ||||
|       options.gasPrice = this.gasPrice; | ||||
|       options.gas = this.gasStore.gas; | ||||
|       options.gasPrice = this.gasStore.price; | ||||
|     } else { | ||||
|       options.gas = MAX_GAS_ESTIMATION; | ||||
|     } | ||||
|  | ||||
| @ -144,15 +144,6 @@ | ||||
|   font-size: 1.2rem; | ||||
| } | ||||
| 
 | ||||
| .chart { | ||||
|   position: absolute; | ||||
|   width: 100%; | ||||
| } | ||||
| 
 | ||||
| .gasPriceDesc { | ||||
|   font-size: 0.9em; | ||||
| } | ||||
| 
 | ||||
| .warning { | ||||
|   border-radius: 0.5em; | ||||
|   background: #f80; | ||||
|  | ||||
| @ -56,10 +56,6 @@ class Transfer extends Component { | ||||
| 
 | ||||
|   store = new TransferStore(this.context.api, this.props); | ||||
| 
 | ||||
|   componentDidMount () { | ||||
|     this.store.getDefaults(); | ||||
|   } | ||||
| 
 | ||||
|   render () { | ||||
|     const { stage, extras, steps } = this.store; | ||||
| 
 | ||||
| @ -186,27 +182,20 @@ class Transfer extends Component { | ||||
|   } | ||||
| 
 | ||||
|   renderExtrasPage () { | ||||
|     if (!this.store.gasPriceHistogram) { | ||||
|     if (!this.store.gasStore.histogram) { | ||||
|       return null; | ||||
|     } | ||||
| 
 | ||||
|     const { isEth, data, dataError, gas, gasEst, gasError, gasPrice } = this.store; | ||||
|     const { gasPriceDefault, gasPriceError, gasPriceHistogram, total, totalError } = this.store; | ||||
|     const { isEth, data, dataError, total, totalError } = this.store; | ||||
| 
 | ||||
|     return ( | ||||
|       <Extras | ||||
|         isEth={ isEth } | ||||
|         data={ data } | ||||
|         dataError={ dataError } | ||||
|         gas={ gas } | ||||
|         gasEst={ gasEst } | ||||
|         gasError={ gasError } | ||||
|         gasPrice={ gasPrice } | ||||
|         gasPriceDefault={ gasPriceDefault } | ||||
|         gasPriceError={ gasPriceError } | ||||
|         gasPriceHistogram={ gasPriceHistogram } | ||||
|         total={ total } | ||||
|         totalError={ totalError } | ||||
|         gasStore={ this.store.gasStore } | ||||
|         onChange={ this.store.onUpdateDetails } /> | ||||
|     ); | ||||
|   } | ||||
| @ -219,7 +208,7 @@ class Transfer extends Component { | ||||
|       <Button | ||||
|         icon={ <ContentClear /> } | ||||
|         label='Cancel' | ||||
|         onClick={ this.store.onClose } /> | ||||
|         onClick={ this.handleClose } /> | ||||
|     ); | ||||
|     const nextBtn = ( | ||||
|       <Button | ||||
| @ -245,7 +234,7 @@ class Transfer extends Component { | ||||
|       <Button | ||||
|         icon={ <ActionDoneAll /> } | ||||
|         label='Close' | ||||
|         onClick={ this.store.onClose } /> | ||||
|         onClick={ this.handleClose } /> | ||||
|     ); | ||||
| 
 | ||||
|     switch (stage) { | ||||
| @ -263,18 +252,25 @@ class Transfer extends Component { | ||||
|   } | ||||
| 
 | ||||
|   renderWarning () { | ||||
|     const { gasLimitError } = this.store; | ||||
|     const { errorEstimated } = this.store.gasStore; | ||||
| 
 | ||||
|     if (!gasLimitError) { | ||||
|     if (!errorEstimated) { | ||||
|       return null; | ||||
|     } | ||||
| 
 | ||||
|     return ( | ||||
|       <div className={ styles.warning }> | ||||
|         { gasLimitError } | ||||
|         { errorEstimated } | ||||
|       </div> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   handleClose = () => { | ||||
|     const { onClose } = this.props; | ||||
| 
 | ||||
|     this.store.handleClose(); | ||||
|     typeof onClose === 'function' && onClose(); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| function mapStateToProps (initState, initProps) { | ||||
|  | ||||
| @ -36,9 +36,6 @@ export default class Personal { | ||||
|         } | ||||
| 
 | ||||
|         this._store.dispatch(personalAccountsInfo(accountsInfo)); | ||||
|       }) | ||||
|       .then((subscriptionId) => { | ||||
|         console.log('personal._subscribeAccountsInfo', 'subscriptionId', subscriptionId); | ||||
|       }); | ||||
|   } | ||||
| 
 | ||||
|  | ||||
| @ -34,9 +34,6 @@ export default class Signer { | ||||
|         } | ||||
| 
 | ||||
|         this._store.dispatch(signerRequestsToConfirm(pending || [])); | ||||
|       }) | ||||
|       .then((subscriptionId) => { | ||||
|         console.log('signer._subscribeRequestsToConfirm', 'subscriptionId', subscriptionId); | ||||
|       }); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -59,9 +59,6 @@ export default class Status { | ||||
|           .catch((error) => { | ||||
|             console.warn('status._subscribeBlockNumber', 'getBlockByNumber', error); | ||||
|           }); | ||||
|       }) | ||||
|       .then((subscriptionId) => { | ||||
|         console.log('status._subscribeBlockNumber', 'subscriptionId', subscriptionId); | ||||
|       }); | ||||
|   } | ||||
| 
 | ||||
|  | ||||
| @ -17,12 +17,10 @@ | ||||
| import { isEqual, uniq } from 'lodash'; | ||||
| 
 | ||||
| import Contract from '~/api/contract'; | ||||
| import { wallet as WALLET_ABI } from '~/contracts/abi'; | ||||
| import { bytesToHex, toHex } from '~/api/util/format'; | ||||
| 
 | ||||
| import { ERROR_CODES } from '~/api/transport/error'; | ||||
| import { MAX_GAS_ESTIMATION } from '../../util/constants'; | ||||
| 
 | ||||
| import { wallet as WALLET_ABI } from '~/contracts/abi'; | ||||
| import { MAX_GAS_ESTIMATION } from '~/util/constants'; | ||||
| import WalletsUtils from '~/util/wallets'; | ||||
| 
 | ||||
| import { newError } from '~/ui/Errors/actions'; | ||||
|  | ||||
							
								
								
									
										38
									
								
								js/src/ui/Actionbar/actionbar.spec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								js/src/ui/Actionbar/actionbar.spec.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,38 @@ | ||||
| // Copyright 2015, 2016 Ethcore (UK) Ltd.
 | ||||
| // This file is part of Parity.
 | ||||
| 
 | ||||
| // Parity is free software: you can redistribute it and/or modify
 | ||||
| // it under the terms of the GNU General Public License as published by
 | ||||
| // the Free Software Foundation, either version 3 of the License, or
 | ||||
| // (at your option) any later version.
 | ||||
| 
 | ||||
| // Parity is distributed in the hope that it will be useful,
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | ||||
| // GNU General Public License for more details.
 | ||||
| 
 | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import React from 'react'; | ||||
| import { shallow } from 'enzyme'; | ||||
| 
 | ||||
| import Actionbar from './actionbar'; | ||||
| 
 | ||||
| function renderShallow (props) { | ||||
|   return shallow( | ||||
|     <Actionbar { ...props } /> | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| describe('ui/Actionbar', () => { | ||||
|   describe('rendering', () => { | ||||
|     it('renders defaults', () => { | ||||
|       expect(renderShallow()).to.be.ok; | ||||
|     }); | ||||
| 
 | ||||
|     it('renders with the specified className', () => { | ||||
|       expect(renderShallow({ className: 'testClass' })).to.have.className('testClass'); | ||||
|     }); | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										38
									
								
								js/src/ui/Badge/badge.spec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								js/src/ui/Badge/badge.spec.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,38 @@ | ||||
| // Copyright 2015, 2016 Ethcore (UK) Ltd.
 | ||||
| // This file is part of Parity.
 | ||||
| 
 | ||||
| // Parity is free software: you can redistribute it and/or modify
 | ||||
| // it under the terms of the GNU General Public License as published by
 | ||||
| // the Free Software Foundation, either version 3 of the License, or
 | ||||
| // (at your option) any later version.
 | ||||
| 
 | ||||
| // Parity is distributed in the hope that it will be useful,
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | ||||
| // GNU General Public License for more details.
 | ||||
| 
 | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import React from 'react'; | ||||
| import { shallow } from 'enzyme'; | ||||
| 
 | ||||
| import Badge from './badge'; | ||||
| 
 | ||||
| function renderShallow (props) { | ||||
|   return shallow( | ||||
|     <Badge { ...props } /> | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| describe('ui/Badge', () => { | ||||
|   describe('rendering', () => { | ||||
|     it('renders defaults', () => { | ||||
|       expect(renderShallow()).to.be.ok; | ||||
|     }); | ||||
| 
 | ||||
|     it('renders with the specified className', () => { | ||||
|       expect(renderShallow({ className: 'testClass' })).to.have.className('testClass'); | ||||
|     }); | ||||
|   }); | ||||
| }); | ||||
| @ -17,16 +17,15 @@ | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| import { FlatButton } from 'material-ui'; | ||||
| 
 | ||||
| import { nodeOrStringProptype } from '~/util/proptypes'; | ||||
| 
 | ||||
| export default class Button extends Component { | ||||
|   static propTypes = { | ||||
|     backgroundColor: PropTypes.string, | ||||
|     className: PropTypes.string, | ||||
|     disabled: PropTypes.bool, | ||||
|     icon: PropTypes.node, | ||||
|     label: PropTypes.oneOfType([ | ||||
|       React.PropTypes.string, | ||||
|       React.PropTypes.object | ||||
|     ]), | ||||
|     label: nodeOrStringProptype(), | ||||
|     onClick: PropTypes.func, | ||||
|     primary: PropTypes.bool | ||||
|   } | ||||
|  | ||||
							
								
								
									
										38
									
								
								js/src/ui/Button/button.spec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								js/src/ui/Button/button.spec.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,38 @@ | ||||
| // Copyright 2015, 2016 Ethcore (UK) Ltd.
 | ||||
| // This file is part of Parity.
 | ||||
| 
 | ||||
| // Parity is free software: you can redistribute it and/or modify
 | ||||
| // it under the terms of the GNU General Public License as published by
 | ||||
| // the Free Software Foundation, either version 3 of the License, or
 | ||||
| // (at your option) any later version.
 | ||||
| 
 | ||||
| // Parity is distributed in the hope that it will be useful,
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | ||||
| // GNU General Public License for more details.
 | ||||
| 
 | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import React from 'react'; | ||||
| import { shallow } from 'enzyme'; | ||||
| 
 | ||||
| import Button from './button'; | ||||
| 
 | ||||
| function renderShallow (props) { | ||||
|   return shallow( | ||||
|     <Button { ...props } /> | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| describe('ui/Button', () => { | ||||
|   describe('rendering', () => { | ||||
|     it('renders defaults', () => { | ||||
|       expect(renderShallow()).to.be.ok; | ||||
|     }); | ||||
| 
 | ||||
|     it('renders with the specified className', () => { | ||||
|       expect(renderShallow({ className: 'testClass' })).to.have.className('testClass'); | ||||
|     }); | ||||
|   }); | ||||
| }); | ||||
| @ -27,10 +27,6 @@ export default class Title extends Component { | ||||
|     byline: nodeOrStringProptype() | ||||
|   } | ||||
| 
 | ||||
|   state = { | ||||
|     name: 'Unnamed' | ||||
|   } | ||||
| 
 | ||||
|   render () { | ||||
|     const { className, title, byline } = this.props; | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										52
									
								
								js/src/ui/Container/Title/title.spec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								js/src/ui/Container/Title/title.spec.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,52 @@ | ||||
| // Copyright 2015, 2016 Ethcore (UK) Ltd.
 | ||||
| // This file is part of Parity.
 | ||||
| 
 | ||||
| // Parity is free software: you can redistribute it and/or modify
 | ||||
| // it under the terms of the GNU General Public License as published by
 | ||||
| // the Free Software Foundation, either version 3 of the License, or
 | ||||
| // (at your option) any later version.
 | ||||
| 
 | ||||
| // Parity is distributed in the hope that it will be useful,
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | ||||
| // GNU General Public License for more details.
 | ||||
| 
 | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import React from 'react'; | ||||
| import { mount, shallow } from 'enzyme'; | ||||
| 
 | ||||
| import Title from './title'; | ||||
| 
 | ||||
| function renderShallow (props) { | ||||
|   return shallow( | ||||
|     <Title { ...props } /> | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| function renderMount (props) { | ||||
|   return mount( | ||||
|     <Title { ...props } /> | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| describe('ui/Container/Title', () => { | ||||
|   describe('rendering', () => { | ||||
|     it('renders defaults', () => { | ||||
|       expect(renderShallow()).to.be.ok; | ||||
|     }); | ||||
| 
 | ||||
|     it('renders with the specified className', () => { | ||||
|       expect(renderShallow({ className: 'testClass' })).to.have.className('testClass'); | ||||
|     }); | ||||
| 
 | ||||
|     it('renders the specified title', () => { | ||||
|       expect(renderMount({ title: 'titleText' })).to.contain.text('titleText'); | ||||
|     }); | ||||
| 
 | ||||
|     it('renders the specified byline', () => { | ||||
|       expect(renderMount({ byline: 'bylineText' })).to.contain.text('bylineText'); | ||||
|     }); | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										38
									
								
								js/src/ui/Container/container.spec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								js/src/ui/Container/container.spec.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,38 @@ | ||||
| // Copyright 2015, 2016 Ethcore (UK) Ltd.
 | ||||
| // This file is part of Parity.
 | ||||
| 
 | ||||
| // Parity is free software: you can redistribute it and/or modify
 | ||||
| // it under the terms of the GNU General Public License as published by
 | ||||
| // the Free Software Foundation, either version 3 of the License, or
 | ||||
| // (at your option) any later version.
 | ||||
| 
 | ||||
| // Parity is distributed in the hope that it will be useful,
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | ||||
| // GNU General Public License for more details.
 | ||||
| 
 | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import React from 'react'; | ||||
| import { shallow } from 'enzyme'; | ||||
| 
 | ||||
| import Container from './container'; | ||||
| 
 | ||||
| function renderShallow (props) { | ||||
|   return shallow( | ||||
|     <Container { ...props } /> | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| describe('ui/Container', () => { | ||||
|   describe('rendering', () => { | ||||
|     it('renders defaults', () => { | ||||
|       expect(renderShallow()).to.be.ok; | ||||
|     }); | ||||
| 
 | ||||
|     it('renders with the specified className', () => { | ||||
|       expect(renderShallow({ className: 'testClass' })).to.have.className('testClass'); | ||||
|     }); | ||||
|   }); | ||||
| }); | ||||
| @ -113,32 +113,38 @@ export default class Input extends Component { | ||||
|         <TextField | ||||
|           autoComplete='off' | ||||
|           className={ className } | ||||
|           style={ textFieldStyle } | ||||
| 
 | ||||
|           readOnly={ readOnly } | ||||
| 
 | ||||
|           errorText={ error } | ||||
| 
 | ||||
|           floatingLabelFixed | ||||
|           floatingLabelText={ label } | ||||
|           fullWidth | ||||
| 
 | ||||
|           hintText={ hint } | ||||
|           id={ NAME_ID } | ||||
|           inputStyle={ inputStyle } | ||||
|           fullWidth | ||||
| 
 | ||||
|           max={ max } | ||||
|           min={ min } | ||||
| 
 | ||||
|           multiLine={ multiLine } | ||||
|           name={ NAME_ID } | ||||
|           id={ NAME_ID } | ||||
|           rows={ rows } | ||||
|           type={ type || 'text' } | ||||
|           underlineDisabledStyle={ UNDERLINE_DISABLED } | ||||
|           underlineStyle={ readOnly ? UNDERLINE_READONLY : UNDERLINE_NORMAL } | ||||
|           underlineFocusStyle={ readOnly ? { display: 'none' } : null } | ||||
|           underlineShow={ !hideUnderline } | ||||
|           value={ value } | ||||
| 
 | ||||
|           onBlur={ this.onBlur } | ||||
|           onChange={ this.onChange } | ||||
|           onKeyDown={ this.onKeyDown } | ||||
|           onPaste={ this.onPaste } | ||||
|           inputStyle={ inputStyle } | ||||
|           min={ min } | ||||
|           max={ max } | ||||
| 
 | ||||
|           readOnly={ readOnly } | ||||
|           rows={ rows } | ||||
|           style={ textFieldStyle } | ||||
|           type={ type || 'text' } | ||||
| 
 | ||||
|           underlineDisabledStyle={ UNDERLINE_DISABLED } | ||||
|           underlineStyle={ readOnly ? UNDERLINE_READONLY : UNDERLINE_NORMAL } | ||||
|           underlineFocusStyle={ readOnly ? { display: 'none' } : null } | ||||
|           underlineShow={ !hideUnderline } | ||||
| 
 | ||||
|           value={ value } | ||||
|         > | ||||
|           { children } | ||||
|         </TextField> | ||||
|  | ||||
| @ -22,12 +22,11 @@ import IconButton from 'material-ui/IconButton'; | ||||
| import AddIcon from 'material-ui/svg-icons/content/add'; | ||||
| import RemoveIcon from 'material-ui/svg-icons/content/remove'; | ||||
| 
 | ||||
| import { fromWei, toWei } from '~/api/util/wei'; | ||||
| import Input from '~/ui/Form/Input'; | ||||
| import InputAddressSelect from '~/ui/Form/InputAddressSelect'; | ||||
| import Select from '~/ui/Form/Select'; | ||||
| 
 | ||||
| import { ABI_TYPES } from '~/util/abi'; | ||||
| import { fromWei, toWei } from '~/api/util/wei'; | ||||
| 
 | ||||
| import styles from './typedInput.css'; | ||||
| 
 | ||||
| @ -54,13 +53,13 @@ export default class TypedInput extends Component { | ||||
|   }; | ||||
| 
 | ||||
|   state = { | ||||
|     isEth: true, | ||||
|     isEth: false, | ||||
|     ethValue: 0 | ||||
|   }; | ||||
| 
 | ||||
|   componentDidMount () { | ||||
|   componentWillMount () { | ||||
|     if (this.props.isEth && this.props.value) { | ||||
|       this.setState({ ethValue: fromWei(this.props.value) }); | ||||
|       this.setState({ isEth: true, ethValue: fromWei(this.props.value) }); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| @ -165,28 +164,32 @@ export default class TypedInput extends Component { | ||||
|     } | ||||
| 
 | ||||
|     if (type === ABI_TYPES.INT) { | ||||
|       return this.renderNumber(); | ||||
|       return this.renderEth(); | ||||
|     } | ||||
| 
 | ||||
|     if (type === ABI_TYPES.FIXED) { | ||||
|       return this.renderNumber(); | ||||
|       return this.renderFloat(); | ||||
|     } | ||||
| 
 | ||||
|     return this.renderDefault(); | ||||
|   } | ||||
| 
 | ||||
|   renderEth () { | ||||
|     const { ethValue } = this.state; | ||||
|     const { ethValue, isEth } = this.state; | ||||
| 
 | ||||
|     const value = ethValue && typeof ethValue.toNumber === 'function' | ||||
|       ? ethValue.toNumber() | ||||
|       : ethValue; | ||||
| 
 | ||||
|     const input = isEth | ||||
|       ? this.renderFloat(value, this.onEthValueChange) | ||||
|       : this.renderInteger(value, this.onEthValueChange); | ||||
| 
 | ||||
|     return ( | ||||
|       <div className={ styles.ethInput }> | ||||
|         <div className={ styles.input }> | ||||
|           { this.renderNumber(value, this.onEthValueChange) } | ||||
|           { this.state.isEth ? (<div className={ styles.label }>ETH</div>) : null } | ||||
|           { input } | ||||
|           { isEth ? (<div className={ styles.label }>ETH</div>) : null } | ||||
|         </div> | ||||
|         <div className={ styles.toggle }> | ||||
|           <Toggle | ||||
| @ -199,8 +202,9 @@ export default class TypedInput extends Component { | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   renderNumber (value = this.props.value, onChange = this.onChange) { | ||||
|   renderInteger (value = this.props.value, onChange = this.onChange) { | ||||
|     const { label, error, param, hint, min, max } = this.props; | ||||
| 
 | ||||
|     const realValue = value && typeof value.toNumber === 'function' | ||||
|       ? value.toNumber() | ||||
|       : value; | ||||
| @ -213,6 +217,35 @@ export default class TypedInput extends Component { | ||||
|         error={ error } | ||||
|         onChange={ onChange } | ||||
|         type='number' | ||||
|         step={ 1 } | ||||
|         min={ min !== null ? min : (param.signed ? null : 0) } | ||||
|         max={ max !== null ? max : null } | ||||
|       /> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Decimal numbers have to be input via text field | ||||
|    * because of some react issues with input number fields. | ||||
|    * Once the issue is fixed, this could be a number again. | ||||
|    * | ||||
|    * @see https://github.com/facebook/react/issues/1549
 | ||||
|    */ | ||||
|   renderFloat (value = this.props.value, onChange = this.onChange) { | ||||
|     const { label, error, param, hint, min, max } = this.props; | ||||
| 
 | ||||
|     const realValue = value && typeof value.toNumber === 'function' | ||||
|       ? value.toNumber() | ||||
|       : value; | ||||
| 
 | ||||
|     return ( | ||||
|       <Input | ||||
|         label={ label } | ||||
|         hint={ hint } | ||||
|         value={ realValue } | ||||
|         error={ error } | ||||
|         onChange={ onChange } | ||||
|         type='text' | ||||
|         min={ min !== null ? min : (param.signed ? null : 0) } | ||||
|         max={ max !== null ? max : null } | ||||
|       /> | ||||
|  | ||||
| @ -15,3 +15,13 @@ | ||||
| /* along with Parity.  If not, see <http://www.gnu.org/licenses/>. | ||||
| */ | ||||
| 
 | ||||
| .chart { | ||||
|   position: absolute; | ||||
|   width: 100%; | ||||
| } | ||||
| 
 | ||||
| .columns { | ||||
|   display: flex; | ||||
|   flex-wrap: wrap; | ||||
|   position: relative; | ||||
| } | ||||
| @ -29,10 +29,7 @@ import { | ||||
| import Slider from 'material-ui/Slider'; | ||||
| import BigNumber from 'bignumber.js'; | ||||
| 
 | ||||
| import componentStyles from './gasPriceSelector.css'; | ||||
| import mainStyles from '../transfer.css'; | ||||
| 
 | ||||
| const styles = Object.assign({}, mainStyles, componentStyles); | ||||
| import styles from './gasPriceSelector.css'; | ||||
| 
 | ||||
| const COLORS = { | ||||
|   default: 'rgba(255, 99, 132, 0.2)', | ||||
| @ -194,10 +191,7 @@ class CustomizedShape extends Component { | ||||
| 
 | ||||
| class CustomTooltip extends Component { | ||||
|   static propTypes = { | ||||
|     gasPriceHistogram: PropTypes.shape({ | ||||
|       bucketBounds: PropTypes.array.isRequired, | ||||
|       counts: PropTypes.array.isRequired | ||||
|     }).isRequired, | ||||
|     gasPriceHistogram: PropTypes.object.isRequired, | ||||
|     type: PropTypes.string, | ||||
|     payload: PropTypes.array, | ||||
|     label: PropTypes.number, | ||||
| @ -231,12 +225,16 @@ class CustomTooltip extends Component { | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| const TOOL_STYLE = { | ||||
|   color: 'rgba(255,255,255,0.5)', | ||||
|   backgroundColor: 'rgba(0, 0, 0, 0.75)', | ||||
|   padding: '0 0.5em', | ||||
|   fontSize: '0.75em' | ||||
| }; | ||||
| 
 | ||||
| export default class GasPriceSelector extends Component { | ||||
|   static propTypes = { | ||||
|     gasPriceHistogram: PropTypes.shape({ | ||||
|       bucketBounds: PropTypes.array.isRequired, | ||||
|       counts: PropTypes.array.isRequired | ||||
|     }).isRequired, | ||||
|     gasPriceHistogram: PropTypes.object.isRequired, | ||||
|     onChange: PropTypes.func.isRequired, | ||||
| 
 | ||||
|     gasPrice: PropTypes.oneOfType([ | ||||
| @ -287,21 +285,23 @@ export default class GasPriceSelector extends Component { | ||||
|   renderSlider () { | ||||
|     const { sliderValue } = this.state; | ||||
| 
 | ||||
|     return (<div className={ styles.columns }> | ||||
|       <Slider | ||||
|         min={ 0 } | ||||
|         max={ 1 } | ||||
|         value={ sliderValue } | ||||
|         onChange={ this.onEditGasPriceSlider } | ||||
|         style={ { | ||||
|           flex: 1, | ||||
|           padding: '0 0.3em' | ||||
|         } } | ||||
|         sliderStyle={ { | ||||
|           marginBottom: 12 | ||||
|         } } | ||||
|       /> | ||||
|     </div>); | ||||
|     return ( | ||||
|       <div className={ styles.columns }> | ||||
|         <Slider | ||||
|           min={ 0 } | ||||
|           max={ 1 } | ||||
|           value={ sliderValue } | ||||
|           onChange={ this.onEditGasPriceSlider } | ||||
|           style={ { | ||||
|             flex: 1, | ||||
|             padding: '0 0.3em' | ||||
|           } } | ||||
|           sliderStyle={ { | ||||
|             marginBottom: 12 | ||||
|           } } | ||||
|         /> | ||||
|       </div> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   renderChart () { | ||||
| @ -316,85 +316,83 @@ export default class GasPriceSelector extends Component { | ||||
|     const countIndex = Math.max(0, Math.min(selectedIndex, gasPriceHistogram.counts.length - 1)); | ||||
|     const selectedCount = countModifier(gasPriceHistogram.counts[countIndex]); | ||||
| 
 | ||||
|     return (<div className={ styles.columns }> | ||||
|       <div style={ { flex: 1, height } }> | ||||
|         <div className={ styles.chart }> | ||||
|           <ResponsiveContainer | ||||
|             height={ height } | ||||
|           > | ||||
|             <ScatterChart | ||||
|               margin={ { top: 0, right: 0, left: 0, bottom: 0 } } | ||||
|     return ( | ||||
|       <div className={ styles.columns }> | ||||
|         <div style={ { flex: 1, height } }> | ||||
|           <div className={ styles.chart }> | ||||
|             <ResponsiveContainer | ||||
|               height={ height } | ||||
|             > | ||||
|               <Scatter | ||||
|                 data={ [ | ||||
|                   { x: sliderValue, y: 0 }, | ||||
|                   { x: sliderValue, y: selectedCount }, | ||||
|                   { x: sliderValue, y: chartData.yDomain[1] } | ||||
|                 ] } | ||||
|                 shape={ <CustomizedShape showValue={ selectedCount } /> } | ||||
|                 line | ||||
|                 isAnimationActive={ false } | ||||
|               /> | ||||
|               <ScatterChart | ||||
|                 margin={ { top: 0, right: 0, left: 0, bottom: 0 } } | ||||
|               > | ||||
|                 <Scatter | ||||
|                   data={ [ | ||||
|                     { x: sliderValue, y: 0 }, | ||||
|                     { x: sliderValue, y: selectedCount }, | ||||
|                     { x: sliderValue, y: chartData.yDomain[1] } | ||||
|                   ] } | ||||
|                   shape={ <CustomizedShape showValue={ selectedCount } /> } | ||||
|                   line | ||||
|                   isAnimationActive={ false } | ||||
|                 /> | ||||
| 
 | ||||
|               <XAxis | ||||
|                 hide | ||||
|                 height={ 0 } | ||||
|                 dataKey='x' | ||||
|                 domain={ [0, 1] } | ||||
|               /> | ||||
|               <YAxis | ||||
|                 hide | ||||
|                 width={ 0 } | ||||
|                 dataKey='y' | ||||
|                 domain={ chartData.yDomain } | ||||
|               /> | ||||
|             </ScatterChart> | ||||
|           </ResponsiveContainer> | ||||
|         </div> | ||||
|                 <XAxis | ||||
|                   hide | ||||
|                   height={ 0 } | ||||
|                   dataKey='x' | ||||
|                   domain={ [0, 1] } | ||||
|                 /> | ||||
|                 <YAxis | ||||
|                   hide | ||||
|                   width={ 0 } | ||||
|                   dataKey='y' | ||||
|                   domain={ chartData.yDomain } | ||||
|                 /> | ||||
|               </ScatterChart> | ||||
|             </ResponsiveContainer> | ||||
|           </div> | ||||
| 
 | ||||
|         <div className={ styles.chart }> | ||||
|           <ResponsiveContainer | ||||
|             height={ height } | ||||
|           > | ||||
|             <BarChart | ||||
|               data={ chartData.values } | ||||
|               margin={ { top: 0, right: 0, left: 0, bottom: 0 } } | ||||
|               barCategoryGap={ 1 } | ||||
|               ref='barChart' | ||||
|           <div className={ styles.chart }> | ||||
|             <ResponsiveContainer | ||||
|               height={ height } | ||||
|             > | ||||
|               <Bar | ||||
|                 dataKey='value' | ||||
|                 stroke={ COLORS.line } | ||||
|                 onClick={ this.onClickGasPrice } | ||||
|                 shape={ <CustomBar selected={ selectedIndex } onClick={ this.onClickGasPrice } /> } | ||||
|               /> | ||||
|               <BarChart | ||||
|                 data={ chartData.values } | ||||
|                 margin={ { top: 0, right: 0, left: 0, bottom: 0 } } | ||||
|                 barCategoryGap={ 1 } | ||||
|                 ref='barChart' | ||||
|               > | ||||
|                 <Bar | ||||
|                   dataKey='value' | ||||
|                   stroke={ COLORS.line } | ||||
|                   onClick={ this.onClickGasPrice } | ||||
|                   shape={ <CustomBar selected={ selectedIndex } onClick={ this.onClickGasPrice } /> } | ||||
|                 /> | ||||
| 
 | ||||
|               <Tooltip | ||||
|                 wrapperStyle={ { | ||||
|                   backgroundColor: 'rgba(0, 0, 0, 0.75)', | ||||
|                   padding: '0 0.5em', | ||||
|                   fontSize: '0.9em' | ||||
|                 } } | ||||
|                 cursor={ this.renderCustomCursor() } | ||||
|                 content={ <CustomTooltip gasPriceHistogram={ gasPriceHistogram } /> } | ||||
|               /> | ||||
|                 <Tooltip | ||||
|                   wrapperStyle={ TOOL_STYLE } | ||||
|                   cursor={ this.renderCustomCursor() } | ||||
|                   content={ <CustomTooltip gasPriceHistogram={ gasPriceHistogram } /> } | ||||
|                 /> | ||||
| 
 | ||||
|               <XAxis | ||||
|                 hide | ||||
|                 dataKey='index' | ||||
|                 type='category' | ||||
|                 domain={ chartData.xDomain } | ||||
|               /> | ||||
|               <YAxis | ||||
|                 hide | ||||
|                 type='number' | ||||
|                 domain={ chartData.yDomain } | ||||
|               /> | ||||
|             </BarChart> | ||||
|           </ResponsiveContainer> | ||||
|                 <XAxis | ||||
|                   hide | ||||
|                   dataKey='index' | ||||
|                   type='category' | ||||
|                   domain={ chartData.xDomain } | ||||
|                 /> | ||||
|                 <YAxis | ||||
|                   hide | ||||
|                   type='number' | ||||
|                   domain={ chartData.yDomain } | ||||
|                 /> | ||||
|               </BarChart> | ||||
|             </ResponsiveContainer> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div>); | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   renderCustomCursor = () => { | ||||
| @ -14,11 +14,36 @@ | ||||
| /* You should have received a copy of the GNU General Public License | ||||
| /* along with Parity.  If not, see <http://www.gnu.org/licenses/>. | ||||
| */ | ||||
| .signer { | ||||
| 
 | ||||
| .columns { | ||||
|   display: flex; | ||||
|   flex-wrap: wrap; | ||||
|   position: relative; | ||||
| } | ||||
| 
 | ||||
| .container { | ||||
| .graphColumn { | ||||
|   flex: 65; | ||||
| } | ||||
| 
 | ||||
| .mainContainer { | ||||
| .editColumn { | ||||
|   flex: 35; | ||||
|   padding-left: 1em; | ||||
|   justify-ontent: space-around; | ||||
|   padding-bottom: 12; | ||||
|   display: flex; | ||||
|   flex-wrap: wrap; | ||||
|   position: relative; | ||||
|   flex-direction: column; | ||||
| } | ||||
| 
 | ||||
| .gasPriceDesc { | ||||
|   font-size: 0.75em; | ||||
|   opacity: 0.5; | ||||
| } | ||||
| 
 | ||||
| .row { | ||||
|   display: flex; | ||||
|   flex-wrap: wrap; | ||||
|   position: relative; | ||||
|   flex-direction: column; | ||||
| } | ||||
							
								
								
									
										108
									
								
								js/src/ui/GasPriceEditor/gasPriceEditor.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								js/src/ui/GasPriceEditor/gasPriceEditor.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,108 @@ | ||||
| // Copyright 2015, 2016 Ethcore (UK) Ltd.
 | ||||
| // This file is part of Parity.
 | ||||
| 
 | ||||
| // Parity is free software: you can redistribute it and/or modify
 | ||||
| // it under the terms of the GNU General Public License as published by
 | ||||
| // the Free Software Foundation, either version 3 of the License, or
 | ||||
| // (at your option) any later version.
 | ||||
| 
 | ||||
| // Parity is distributed in the hope that it will be useful,
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | ||||
| // GNU General Public License for more details.
 | ||||
| 
 | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import BigNumber from 'bignumber.js'; | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| import { observer } from 'mobx-react'; | ||||
| 
 | ||||
| import Input from '../Form/Input'; | ||||
| import GasPriceSelector from './GasPriceSelector'; | ||||
| import Store from './store'; | ||||
| 
 | ||||
| import styles from './gasPriceEditor.css'; | ||||
| 
 | ||||
| @observer | ||||
| export default class GasPriceEditor extends Component { | ||||
|   static contextTypes = { | ||||
|     api: PropTypes.object.isRequired | ||||
|   }; | ||||
| 
 | ||||
|   static propTypes = { | ||||
|     store: PropTypes.object.isRequired, | ||||
|     onChange: PropTypes.func | ||||
|   } | ||||
| 
 | ||||
|   static Store = Store; | ||||
| 
 | ||||
|   render () { | ||||
|     const { api } = this.context; | ||||
|     const { store } = this.props; | ||||
|     const { estimated, priceDefault, price, gas, histogram, errorGas, errorPrice, errorTotal, totalValue } = store; | ||||
| 
 | ||||
|     const eth = api.util.fromWei(totalValue).toFormat(); | ||||
|     const gasLabel = `gas (estimated: ${new BigNumber(estimated).toFormat()})`; | ||||
|     const priceLabel = `price (current: ${new BigNumber(priceDefault).toFormat()})`; | ||||
| 
 | ||||
|     return ( | ||||
|       <div className={ styles.columns }> | ||||
|         <div className={ styles.graphColumn }> | ||||
|           <GasPriceSelector | ||||
|             gasPriceHistogram={ histogram } | ||||
|             gasPrice={ price } | ||||
|             onChange={ this.onEditGasPrice } /> | ||||
|           <div className={ styles.gasPriceDesc }> | ||||
|             You can choose the gas price based on the | ||||
|             distribution of recent included transaction gas prices. | ||||
|             The lower the gas price is, the cheaper the transaction will | ||||
|             be. The higher the gas price is, the faster it should | ||||
|             get mined by the network. | ||||
|           </div> | ||||
|         </div> | ||||
| 
 | ||||
|         <div className={ styles.editColumn }> | ||||
|           <div className={ styles.row }> | ||||
|             <Input | ||||
|               label={ gasLabel } | ||||
|               hint='the amount of gas to use for the transaction' | ||||
|               error={ errorGas } | ||||
|               value={ gas } | ||||
|               onChange={ this.onEditGas } /> | ||||
| 
 | ||||
|             <Input | ||||
|               label={ priceLabel } | ||||
|               hint='the price of gas to use for the transaction' | ||||
|               error={ errorPrice } | ||||
|               value={ price } | ||||
|               onChange={ this.onEditGasPrice } /> | ||||
|           </div> | ||||
| 
 | ||||
|           <div className={ styles.row }> | ||||
|             <Input | ||||
|               disabled | ||||
|               label='total transaction amount' | ||||
|               hint='the total amount of the transaction' | ||||
|               error={ errorTotal } | ||||
|               value={ `${eth} ETH` } /> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   onEditGas = (event, gas) => { | ||||
|     const { store, onChange } = this.props; | ||||
| 
 | ||||
|     store.setGas(gas); | ||||
|     onChange && onChange('gas', gas); | ||||
|   } | ||||
| 
 | ||||
|   onEditGasPrice = (event, price) => { | ||||
|     const { store, onChange } = this.props; | ||||
| 
 | ||||
|     store.setPrice(price); | ||||
|     onChange && onChange('gasPrice', price); | ||||
|   } | ||||
| } | ||||
| @ -14,6 +14,4 @@ | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| export default (chain) => { | ||||
|   return chain === 'morden' || chain === 'ropsten' || chain === 'testnet'; | ||||
| }; | ||||
| export default from './gasPriceEditor'; | ||||
							
								
								
									
										123
									
								
								js/src/ui/GasPriceEditor/store.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								js/src/ui/GasPriceEditor/store.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,123 @@ | ||||
| // Copyright 2015, 2016 Ethcore (UK) Ltd.
 | ||||
| // This file is part of Parity.
 | ||||
| 
 | ||||
| // Parity is free software: you can redistribute it and/or modify
 | ||||
| // it under the terms of the GNU General Public License as published by
 | ||||
| // the Free Software Foundation, either version 3 of the License, or
 | ||||
| // (at your option) any later version.
 | ||||
| 
 | ||||
| // Parity is distributed in the hope that it will be useful,
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | ||||
| // GNU General Public License for more details.
 | ||||
| 
 | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import BigNumber from 'bignumber.js'; | ||||
| import { action, computed, observable, transaction } from 'mobx'; | ||||
| 
 | ||||
| import { ERRORS, validatePositiveNumber } from '~/util/validation'; | ||||
| import { DEFAULT_GAS, DEFAULT_GASPRICE, MAX_GAS_ESTIMATION } from '~/util/constants'; | ||||
| 
 | ||||
| export default class GasPriceEditor { | ||||
|   @observable errorEstimated = null; | ||||
|   @observable errorGas = null; | ||||
|   @observable errorPrice = null; | ||||
|   @observable errorTotal = null; | ||||
|   @observable estimated = DEFAULT_GAS; | ||||
|   @observable histogram = null; | ||||
|   @observable price = DEFAULT_GASPRICE; | ||||
|   @observable priceDefault = DEFAULT_GASPRICE; | ||||
|   @observable gas = DEFAULT_GAS; | ||||
|   @observable gasLimit = 0; | ||||
|   @observable weiValue = '0'; | ||||
| 
 | ||||
|   constructor (api, gasLimit, loadDefaults = true) { | ||||
|     this._api = api; | ||||
|     this.gasLimit = gasLimit; | ||||
| 
 | ||||
|     if (loadDefaults) { | ||||
|       this.loadDefaults(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @computed get totalValue () { | ||||
|     try { | ||||
|       return new BigNumber(this.gas).mul(this.price).add(this.weiValue); | ||||
|     } catch (error) { | ||||
|       return new BigNumber(0); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @action setErrorTotal = (errorTotal) => { | ||||
|     this.errorTotal = errorTotal; | ||||
|   } | ||||
| 
 | ||||
|   @action setEstimated = (estimated) => { | ||||
|     transaction(() => { | ||||
|       const bn = new BigNumber(estimated); | ||||
| 
 | ||||
|       this.estimated = estimated; | ||||
| 
 | ||||
|       if (bn.gte(MAX_GAS_ESTIMATION)) { | ||||
|         this.errorEstimated = ERRORS.gasException; | ||||
|       } else if (bn.gte(this.gasLimit)) { | ||||
|         this.errorEstimated = ERRORS.gasBlockLimit; | ||||
|       } else { | ||||
|         this.errorEstimated = null; | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   @action setEthValue = (weiValue) => { | ||||
|     this.weiValue = weiValue; | ||||
|   } | ||||
| 
 | ||||
|   @action setHistogram = (gasHistogram) => { | ||||
|     this.histogram = gasHistogram; | ||||
|   } | ||||
| 
 | ||||
|   @action setPrice = (price) => { | ||||
|     transaction(() => { | ||||
|       this.errorPrice = validatePositiveNumber(price).numberError; | ||||
|       this.price = price; | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   @action setGas = (gas) => { | ||||
|     transaction(() => { | ||||
|       const { numberError } = validatePositiveNumber(gas); | ||||
|       const bn = new BigNumber(gas); | ||||
| 
 | ||||
|       this.gas = gas; | ||||
| 
 | ||||
|       if (numberError) { | ||||
|         this.errorGas = numberError; | ||||
|       } else if (bn.gte(this.gasLimit)) { | ||||
|         this.errorGas = ERRORS.gasBlockLimit; | ||||
|       } else { | ||||
|         this.errorGas = null; | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   @action loadDefaults () { | ||||
|     Promise | ||||
|       .all([ | ||||
|         this._api.parity.gasPriceHistogram(), | ||||
|         this._api.eth.gasPrice() | ||||
|       ]) | ||||
|       .then(([gasPriceHistogram, gasPrice]) => { | ||||
|         transaction(() => { | ||||
|           this.setPrice(gasPrice.toFixed(0)); | ||||
|           this.setHistogram(gasPriceHistogram); | ||||
| 
 | ||||
|           this.priceDefault = gasPrice.toFixed(); | ||||
|         }); | ||||
|       }) | ||||
|       .catch((error) => { | ||||
|         console.warn('getDefaults', error); | ||||
|       }); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										76
									
								
								js/src/ui/IdentityName/identityName.spec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								js/src/ui/IdentityName/identityName.spec.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,76 @@ | ||||
| // Copyright 2015, 2016 Ethcore (UK) Ltd.
 | ||||
| // This file is part of Parity.
 | ||||
| 
 | ||||
| // Parity is free software: you can redistribute it and/or modify
 | ||||
| // it under the terms of the GNU General Public License as published by
 | ||||
| // the Free Software Foundation, either version 3 of the License, or
 | ||||
| // (at your option) any later version.
 | ||||
| 
 | ||||
| // Parity is distributed in the hope that it will be useful,
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | ||||
| // GNU General Public License for more details.
 | ||||
| 
 | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import React from 'react'; | ||||
| import { mount } from 'enzyme'; | ||||
| import sinon from 'sinon'; | ||||
| 
 | ||||
| import IdentityName from './identityName'; | ||||
| 
 | ||||
| const ADDR_A = '0x123456789abcdef0123456789A'; | ||||
| const ADDR_B = '0x123456789abcdef0123456789B'; | ||||
| const ADDR_C = '0x123456789abcdef0123456789C'; | ||||
| const STORE = { | ||||
|   dispatch: sinon.stub(), | ||||
|   subscribe: sinon.stub(), | ||||
|   getState: () => { | ||||
|     return { | ||||
|       balances: { | ||||
|         tokens: {} | ||||
|       }, | ||||
|       personal: { | ||||
|         accountsInfo: { | ||||
|           [ADDR_A]: { name: 'testing' }, | ||||
|           [ADDR_B]: {} | ||||
|         } | ||||
|       } | ||||
|     }; | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| function render (props) { | ||||
|   return mount( | ||||
|     <IdentityName | ||||
|       store={ STORE } | ||||
|       { ...props } /> | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| describe('ui/IdentityName', () => { | ||||
|   describe('rendering', () => { | ||||
|     it('renders defaults', () => { | ||||
|       expect(render()).to.be.ok; | ||||
|     }); | ||||
| 
 | ||||
|     describe('account not found', () => { | ||||
|       it('renders null with empty', () => { | ||||
|         expect(render({ address: ADDR_C, empty: true }).html()).to.be.null; | ||||
|       }); | ||||
| 
 | ||||
|       it('renders address without empty', () => { | ||||
|         expect(render({ address: ADDR_C }).text()).to.equal(ADDR_C); | ||||
|       }); | ||||
| 
 | ||||
|       it('renders short address with shorten', () => { | ||||
|         expect(render({ address: ADDR_C, shorten: true }).text()).to.equal('123456…56789c'); | ||||
|       }); | ||||
| 
 | ||||
|       it('renders unknown with flag', () => { | ||||
|         expect(render({ address: ADDR_C, unknown: true }).text()).to.equal('UNNAMED'); | ||||
|       }); | ||||
|     }); | ||||
|   }); | ||||
| }); | ||||
| @ -17,8 +17,8 @@ | ||||
| 
 | ||||
| .layout { | ||||
|   padding: 0.25em 0.25em 1em 0.25em; | ||||
| } | ||||
| 
 | ||||
| .layout>div { | ||||
|   padding-bottom: 0.75em; | ||||
|   > * { | ||||
|     margin-bottom: 0.75em; | ||||
|   } | ||||
| } | ||||
|  | ||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
		Reference in New Issue
	
	Block a user