Merge branch 'master' into check-updates
This commit is contained in:
		
						commit
						8903384840
					
				
							
								
								
									
										16
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										16
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @ -24,7 +24,7 @@ dependencies = [ | ||||
|  "ethcore-stratum 1.4.0", | ||||
|  "ethcore-util 1.5.0", | ||||
|  "ethsync 1.5.0", | ||||
|  "fdlimit 0.1.0", | ||||
|  "fdlimit 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "hyper 0.9.10 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "isatty 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| @ -687,7 +687,8 @@ dependencies = [ | ||||
| 
 | ||||
| [[package]] | ||||
| name = "fdlimit" | ||||
| version = "0.1.0" | ||||
| version = "0.1.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| dependencies = [ | ||||
|  "libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| ] | ||||
| @ -843,7 +844,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| [[package]] | ||||
| name = "jsonrpc-core" | ||||
| version = "4.0.0" | ||||
| source = "git+https://github.com/ethcore/jsonrpc.git#20c7e55b84d7fd62732f062dc3058e1b71133e4a" | ||||
| source = "git+https://github.com/ethcore/jsonrpc.git#1500da1b9613a0a17fc0109d825f3ccc60199a53" | ||||
| dependencies = [ | ||||
|  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "parking_lot 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| @ -855,7 +856,7 @@ dependencies = [ | ||||
| [[package]] | ||||
| name = "jsonrpc-http-server" | ||||
| version = "6.1.1" | ||||
| source = "git+https://github.com/ethcore/jsonrpc.git#20c7e55b84d7fd62732f062dc3058e1b71133e4a" | ||||
| source = "git+https://github.com/ethcore/jsonrpc.git#1500da1b9613a0a17fc0109d825f3ccc60199a53" | ||||
| dependencies = [ | ||||
|  "hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)", | ||||
|  "jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git)", | ||||
| @ -866,7 +867,7 @@ dependencies = [ | ||||
| [[package]] | ||||
| name = "jsonrpc-ipc-server" | ||||
| version = "0.2.4" | ||||
| source = "git+https://github.com/ethcore/jsonrpc.git#20c7e55b84d7fd62732f062dc3058e1b71133e4a" | ||||
| source = "git+https://github.com/ethcore/jsonrpc.git#1500da1b9613a0a17fc0109d825f3ccc60199a53" | ||||
| dependencies = [ | ||||
|  "bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| @ -881,7 +882,7 @@ dependencies = [ | ||||
| [[package]] | ||||
| name = "jsonrpc-tcp-server" | ||||
| version = "0.1.0" | ||||
| source = "git+https://github.com/ethcore/jsonrpc.git#20c7e55b84d7fd62732f062dc3058e1b71133e4a" | ||||
| source = "git+https://github.com/ethcore/jsonrpc.git#1500da1b9613a0a17fc0109d825f3ccc60199a53" | ||||
| dependencies = [ | ||||
|  "bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| @ -1273,7 +1274,7 @@ dependencies = [ | ||||
| [[package]] | ||||
| name = "parity-ui-precompiled" | ||||
| version = "1.4.0" | ||||
| source = "git+https://github.com/ethcore/js-precompiled.git#7700411d2b0ba1372e0d6cf72a84ecf873a181f3" | ||||
| source = "git+https://github.com/ethcore/js-precompiled.git#1690a80b9f56e33c8344f28eb637bacd97c9da14" | ||||
| dependencies = [ | ||||
|  "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| ] | ||||
| @ -2047,6 +2048,7 @@ dependencies = [ | ||||
| "checksum env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "aba65b63ffcc17ffacd6cf5aa843da7c5a25e3bd4bbe0b7def8b214e411250e5" | ||||
| "checksum eth-secp256k1 0.5.4 (git+https://github.com/ethcore/rust-secp256k1)" = "<none>" | ||||
| "checksum ethabi 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f7b0c53453517f620847be51943db329276ae52f2e210cfc659e81182864be2f" | ||||
| "checksum fdlimit 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b1ee15a7050e5580b3712877157068ea713b245b080ff302ae2ca973cfcd9baa" | ||||
| "checksum flate2 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "3eeb481e957304178d2e782f2da1257f1434dfecbae883bafb61ada2a9fea3bb" | ||||
| "checksum gcc 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)" = "91ecd03771effb0c968fd6950b37e89476a578aaf1c70297d8e92b6516ec3312" | ||||
| "checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" | ||||
|  | ||||
| @ -30,7 +30,7 @@ serde = "0.8.0" | ||||
| serde_json = "0.8.0" | ||||
| hyper = { version = "0.9", default-features = false } | ||||
| ctrlc = { git = "https://github.com/ethcore/rust-ctrlc.git" } | ||||
| fdlimit = { path = "util/fdlimit" } | ||||
| fdlimit = "0.1" | ||||
| ethcore = { path = "ethcore" } | ||||
| ethcore-util = { path = "util" } | ||||
| ethsync = { path = "sync" } | ||||
|  | ||||
| @ -5,6 +5,10 @@ license = "GPL-3.0" | ||||
| name = "ethcore-light" | ||||
| version = "1.5.0" | ||||
| authors = ["Ethcore <admin@ethcore.io>"] | ||||
| build = "build.rs" | ||||
| 
 | ||||
| [build-dependencies] | ||||
| "ethcore-ipc-codegen" = { path = "../../ipc/codegen" } | ||||
| 
 | ||||
| [dependencies] | ||||
| log = "0.3" | ||||
| @ -12,5 +16,6 @@ ethcore = { path = ".." } | ||||
| ethcore-util = { path = "../../util" } | ||||
| ethcore-network = { path = "../../util/network" } | ||||
| ethcore-io = { path = "../../util/io" } | ||||
| ethcore-ipc = { path = "../../ipc/rpc" } | ||||
| rlp = { path = "../../util/rlp" } | ||||
| time = "0.1" | ||||
| @ -14,8 +14,8 @@ | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import { PropTypes } from 'react'; | ||||
| extern crate ethcore_ipc_codegen; | ||||
| 
 | ||||
| export default function nullableProptype (type) { | ||||
|   return PropTypes.oneOfType([ PropTypes.oneOf([ null ]), type ]); | ||||
| fn main() { | ||||
| 	ethcore_ipc_codegen::derive_binary("src/types/mod.rs.in").unwrap(); | ||||
| } | ||||
| @ -101,7 +101,7 @@ impl Provider for Client { | ||||
| 		Vec::new() | ||||
| 	} | ||||
| 
 | ||||
| 	fn code(&self, _req: request::ContractCodes) -> Vec<Bytes> { | ||||
| 	fn contract_code(&self, _req: request::ContractCodes) -> Vec<Bytes> { | ||||
| 		Vec::new() | ||||
| 	} | ||||
| 
 | ||||
|  | ||||
| @ -28,20 +28,25 @@ | ||||
| //! It starts by performing a header-only sync, verifying random samples
 | ||||
| //! of members of the chain to varying degrees.
 | ||||
| 
 | ||||
| // TODO: remove when integrating with parity.
 | ||||
| // TODO: remove when integrating with the rest of parity.
 | ||||
| #![allow(dead_code)] | ||||
| 
 | ||||
| pub mod client; | ||||
| pub mod net; | ||||
| pub mod provider; | ||||
| pub mod request; | ||||
| 
 | ||||
| mod types; | ||||
| 
 | ||||
| pub use self::provider::Provider; | ||||
| pub use types::les_request as request; | ||||
| 
 | ||||
| #[macro_use] | ||||
| extern crate log; | ||||
| 
 | ||||
| extern crate ethcore; | ||||
| extern crate ethcore_util as util; | ||||
| extern crate ethcore_network as network; | ||||
| extern crate ethcore_io as io; | ||||
| extern crate ethcore; | ||||
| extern crate ethcore_ipc as ipc; | ||||
| extern crate rlp; | ||||
| extern crate time; | ||||
| 
 | ||||
| #[macro_use] | ||||
| extern crate log; | ||||
| extern crate time; | ||||
| @ -206,6 +206,39 @@ impl FlowParams { | ||||
| 		cost.0 + (amount * cost.1) | ||||
| 	} | ||||
| 
 | ||||
| 	/// Compute the maximum number of costs of a specific kind which can be made 
 | ||||
| 	/// with the given buffer.
 | ||||
| 	/// Saturates at `usize::max()`. This is not a problem in practice because
 | ||||
| 	/// this amount of requests is already prohibitively large.
 | ||||
| 	pub fn max_amount(&self, buffer: &Buffer, kind: request::Kind) -> usize { | ||||
| 		use util::Uint; | ||||
| 		use std::usize; | ||||
| 
 | ||||
| 		let cost = match kind { | ||||
| 			request::Kind::Headers => &self.costs.headers, | ||||
| 			request::Kind::Bodies => &self.costs.bodies, | ||||
| 			request::Kind::Receipts => &self.costs.receipts, | ||||
| 			request::Kind::StateProofs => &self.costs.state_proofs, | ||||
| 			request::Kind::Codes => &self.costs.contract_codes, | ||||
| 			request::Kind::HeaderProofs => &self.costs.header_proofs, | ||||
| 		}; | ||||
| 
 | ||||
| 		let start = buffer.current(); | ||||
| 
 | ||||
| 		if start <= cost.0 { | ||||
| 			return 0; | ||||
| 		} else if cost.1 == U256::zero() { | ||||
| 			return usize::MAX; | ||||
| 		} | ||||
| 
 | ||||
| 		let max = (start - cost.0) / cost.1; | ||||
| 		if max >= usize::MAX.into() { | ||||
| 			usize::MAX | ||||
| 		} else { | ||||
| 			max.as_u64() as usize | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	/// Create initial buffer parameter.
 | ||||
| 	pub fn create_buffer(&self) -> Buffer { | ||||
| 		Buffer { | ||||
| @ -228,6 +261,16 @@ impl FlowParams { | ||||
| 
 | ||||
| 		buf.estimate = ::std::cmp::min(self.limit, buf.estimate + (elapsed * self.recharge)); | ||||
| 	} | ||||
| 
 | ||||
| 	/// Refund some buffer which was previously deducted.
 | ||||
| 	/// Does not update the recharge timestamp.
 | ||||
| 	pub fn refund(&self, buf: &mut Buffer, refund_amount: U256) { | ||||
| 		buf.estimate = buf.estimate + refund_amount; | ||||
| 
 | ||||
| 		if buf.estimate > self.limit { | ||||
| 			buf.estimate = self.limit | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| #[cfg(test)] | ||||
|  | ||||
| @ -52,6 +52,8 @@ pub enum Error { | ||||
| 	UnexpectedHandshake, | ||||
| 	/// Peer on wrong network (wrong NetworkId or genesis hash)
 | ||||
| 	WrongNetwork, | ||||
| 	/// Unknown peer.
 | ||||
| 	UnknownPeer, | ||||
| } | ||||
| 
 | ||||
| impl Error { | ||||
| @ -64,6 +66,7 @@ impl Error { | ||||
| 			Error::UnrecognizedPacket(_) => Punishment::Disconnect, | ||||
| 			Error::UnexpectedHandshake => Punishment::Disconnect, | ||||
| 			Error::WrongNetwork => Punishment::Disable, | ||||
| 			Error::UnknownPeer => Punishment::Disconnect, | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @ -89,6 +92,7 @@ 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"), | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @ -19,27 +19,28 @@ | ||||
| //! This uses a "Provider" to answer requests.
 | ||||
| //! See https://github.com/ethcore/parity/wiki/Light-Ethereum-Subprotocol-(LES)
 | ||||
| 
 | ||||
| use ethcore::transaction::SignedTransaction; | ||||
| use io::TimerToken; | ||||
| use network::{NetworkProtocolHandler, NetworkContext, NetworkError, PeerId}; | ||||
| use rlp::{RlpStream, Stream, UntrustedRlp, View}; | ||||
| use util::hash::H256; | ||||
| use util::RwLock; | ||||
| use util::{Mutex, RwLock, U256}; | ||||
| use time::SteadyTime; | ||||
| 
 | ||||
| use std::collections::{HashMap, HashSet}; | ||||
| use std::sync::atomic::AtomicUsize; | ||||
| use std::sync::atomic::{AtomicUsize, Ordering}; | ||||
| 
 | ||||
| use provider::Provider; | ||||
| use request::{self, Request}; | ||||
| 
 | ||||
| use self::buffer_flow::{Buffer, FlowParams}; | ||||
| use self::error::{Error, Punishment}; | ||||
| use self::status::{Status, Capabilities}; | ||||
| 
 | ||||
| mod buffer_flow; | ||||
| mod error; | ||||
| mod status; | ||||
| 
 | ||||
| pub use self::status::Announcement; | ||||
| pub use self::status::{Status, Capabilities, Announcement, NetworkId}; | ||||
| 
 | ||||
| const TIMEOUT: TimerToken = 0; | ||||
| const TIMEOUT_INTERVAL_MS: u64 = 1000; | ||||
| @ -86,6 +87,10 @@ mod packet { | ||||
| 	pub const HEADER_PROOFS: u8 = 0x0e; | ||||
| } | ||||
| 
 | ||||
| /// A request id.
 | ||||
| #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] | ||||
| pub struct ReqId(usize); | ||||
| 
 | ||||
| // A pending peer: one we've sent our status to but
 | ||||
| // may not have received one for.
 | ||||
| struct PendingPeer { | ||||
| @ -103,32 +108,162 @@ struct Peer { | ||||
| 	sent_head: H256, // last head we've given them.
 | ||||
| } | ||||
| 
 | ||||
| impl Peer { | ||||
| 	// check the maximum cost of a request, returning an error if there's
 | ||||
| 	// not enough buffer left.
 | ||||
| 	// returns the calculated maximum cost.
 | ||||
| 	fn deduct_max(&mut self, flow_params: &FlowParams, kind: request::Kind, max: usize) -> Result<U256, Error> { | ||||
| 		flow_params.recharge(&mut self.local_buffer); | ||||
| 
 | ||||
| 		let max_cost = flow_params.compute_cost(kind, max); | ||||
| 		try!(self.local_buffer.deduct_cost(max_cost)); | ||||
| 		Ok(max_cost) | ||||
| 	} | ||||
| 
 | ||||
| 	// refund buffer for a request. returns new buffer amount.
 | ||||
| 	fn refund(&mut self, flow_params: &FlowParams, amount: U256) -> U256 { | ||||
| 		flow_params.refund(&mut self.local_buffer, amount); | ||||
| 
 | ||||
| 		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.
 | ||||
| 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) { } | ||||
| 	/// Called when a peer makes an announcement.
 | ||||
| 	fn on_announcement(&self, _id: PeerId, _announcement: &Announcement) { } | ||||
| 	/// Called when a peer requests relay of some transactions.
 | ||||
| 	fn on_transactions(&self, _id: PeerId, _relay: &[SignedTransaction]) { } | ||||
| } | ||||
| 
 | ||||
| // a request and the time it was made.
 | ||||
| struct Requested { | ||||
| 	request: Request, | ||||
| 	timestamp: SteadyTime, | ||||
| } | ||||
| 
 | ||||
| /// Protocol parameters.
 | ||||
| pub struct Params { | ||||
| 	/// Genesis hash.
 | ||||
| 	pub genesis_hash: H256, | ||||
| 	/// Network id.
 | ||||
| 	pub network_id: NetworkId, | ||||
| 	/// Buffer flow parameters.
 | ||||
| 	pub flow_params: FlowParams, | ||||
| 	/// Initial capabilities.
 | ||||
| 	pub capabilities: Capabilities, | ||||
| } | ||||
| 
 | ||||
| /// This is an implementation of the light ethereum network protocol, abstracted
 | ||||
| /// over a `Provider` of data and a p2p network.
 | ||||
| ///
 | ||||
| /// This is simply designed for request-response purposes. Higher level uses
 | ||||
| /// of the protocol, such as synchronization, will function as wrappers around
 | ||||
| /// this system.
 | ||||
| // 
 | ||||
| // LOCK ORDER:
 | ||||
| //   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>, | ||||
| 	genesis_hash: H256, | ||||
| 	network_id: status::NetworkId, | ||||
| 	network_id: NetworkId, | ||||
| 	pending_peers: RwLock<HashMap<PeerId, PendingPeer>>, | ||||
| 	peers: RwLock<HashMap<PeerId, Peer>>, | ||||
| 	pending_requests: RwLock<HashMap<usize, Request>>, | ||||
| 	peers: RwLock<HashMap<PeerId, Mutex<Peer>>>, | ||||
| 	pending_requests: RwLock<HashMap<usize, Requested>>, | ||||
| 	capabilities: RwLock<Capabilities>, | ||||
| 	flow_params: FlowParams, // assumed static and same for every peer.
 | ||||
| 	handlers: Vec<Box<Handler>>, | ||||
| 	req_id: AtomicUsize, | ||||
| } | ||||
| 
 | ||||
| impl LightProtocol { | ||||
| 	/// Create a new instance of the protocol manager.
 | ||||
| 	pub fn new(provider: Box<Provider>, params: Params) -> Self { | ||||
| 		LightProtocol { | ||||
| 			provider: provider, | ||||
| 			genesis_hash: params.genesis_hash, | ||||
| 			network_id: params.network_id, | ||||
| 			pending_peers: RwLock::new(HashMap::new()), | ||||
| 			peers: RwLock::new(HashMap::new()), | ||||
| 			pending_requests: RwLock::new(HashMap::new()), | ||||
| 			capabilities: RwLock::new(params.capabilities), | ||||
| 			flow_params: params.flow_params, | ||||
| 			handlers: Vec::new(), | ||||
| 			req_id: AtomicUsize::new(0), | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	/// 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| { | ||||
| 			let mut peer = peer.lock(); | ||||
| 			peer.recharge_remote(); | ||||
| 			peer.remote_flow.max_amount(&peer.remote_buffer, kind) | ||||
| 		}) | ||||
| 	} | ||||
| 
 | ||||
| 	/// Make a request to a peer. 
 | ||||
| 	///
 | ||||
| 	/// Fails on: nonexistent peer, network error,
 | ||||
| 	/// 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> { | ||||
| 		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)); | ||||
| 
 | ||||
| 		let req_id = self.req_id.fetch_add(1, Ordering::SeqCst); | ||||
| 		let packet_data = encode_request(&request, req_id); | ||||
| 
 | ||||
| 		let packet_id = match request.kind() { | ||||
| 			request::Kind::Headers => packet::GET_BLOCK_HEADERS, | ||||
| 			request::Kind::Bodies => packet::GET_BLOCK_BODIES, | ||||
| 			request::Kind::Receipts => packet::GET_RECEIPTS, | ||||
| 			request::Kind::StateProofs => packet::GET_PROOFS, | ||||
| 			request::Kind::Codes => packet::GET_CONTRACT_CODES, | ||||
| 			request::Kind::HeaderProofs => packet::GET_HEADER_PROOFS, | ||||
| 		}; | ||||
| 
 | ||||
| 		try!(io.send(*peer_id, packet_id, packet_data)); | ||||
| 
 | ||||
| 		peer.current_asking.insert(req_id); | ||||
| 		self.pending_requests.write().insert(req_id, Requested { | ||||
| 			request: request, | ||||
| 			timestamp: SteadyTime::now(), | ||||
| 		}); | ||||
| 
 | ||||
| 		Ok(ReqId(req_id)) | ||||
| 	} | ||||
| 
 | ||||
| 	/// Make an announcement of new chain head and capabilities to all peers.
 | ||||
| 	/// The announcement is expected to be valid.
 | ||||
| 	pub fn make_announcement(&self, mut announcement: Announcement, io: &NetworkContext) { | ||||
| 	pub fn make_announcement(&self, io: &NetworkContext, mut announcement: Announcement) { | ||||
| 		let mut reorgs_map = HashMap::new(); | ||||
| 
 | ||||
| 		// update stored capabilities
 | ||||
| 		self.capabilities.write().update_from(&announcement); | ||||
| 
 | ||||
| 		// calculate reorg info and send packets
 | ||||
| 		for (peer_id, peer_info) in self.peers.write().iter_mut() { | ||||
| 		for (peer_id, peer_info) in self.peers.read().iter() { | ||||
| 			let mut peer_info = peer_info.lock(); | ||||
| 			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) { | ||||
| @ -151,6 +286,14 @@ impl LightProtocol { | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	/// Add an event handler.
 | ||||
| 	/// Ownership will be transferred to the protocol structure,
 | ||||
| 	/// and the handler will be kept alive as long as it is.
 | ||||
| 	/// These are intended to be added at the beginning of the 
 | ||||
| 	pub fn add_handler(&mut self, handler: Box<Handler>) { | ||||
| 		self.handlers.push(handler); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl LightProtocol { | ||||
| @ -173,7 +316,11 @@ impl LightProtocol { | ||||
| 	fn on_disconnect(&self, peer: PeerId) { | ||||
| 		// TODO: reassign all requests assigned to this peer.
 | ||||
| 		self.pending_peers.write().remove(&peer); | ||||
| 		self.peers.write().remove(&peer); | ||||
| 		if self.peers.write().remove(&peer).is_some() { | ||||
| 			for handler in &self.handlers { | ||||
| 				handler.on_disconnect(peer) | ||||
| 			}	
 | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// send status to a peer.
 | ||||
| @ -219,15 +366,19 @@ impl LightProtocol { | ||||
| 			return Err(Error::WrongNetwork); | ||||
| 		} | ||||
| 
 | ||||
| 		self.peers.write().insert(*peer, Peer { | ||||
| 		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, | ||||
| 			capabilities: capabilities, | ||||
| 			status: status.clone(), | ||||
| 			capabilities: capabilities.clone(), | ||||
| 			remote_flow: flow_params, | ||||
| 			sent_head: pending.sent_head, | ||||
| 		}); | ||||
| 		})); | ||||
| 
 | ||||
| 		for handler in &self.handlers { | ||||
| 			handler.on_connect(*peer, &status, &capabilities) | ||||
| 		} | ||||
| 
 | ||||
| 		Ok(()) | ||||
| 	} | ||||
| @ -240,13 +391,15 @@ impl LightProtocol { | ||||
| 		} | ||||
| 
 | ||||
| 		let announcement = try!(status::parse_announcement(data)); | ||||
| 		let mut peers = self.peers.write(); | ||||
| 		let peers = self.peers.read(); | ||||
| 
 | ||||
| 		let peer_info = match peers.get_mut(peer) { | ||||
| 		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.
 | ||||
| @ -259,15 +412,11 @@ impl LightProtocol { | ||||
| 		} | ||||
| 
 | ||||
| 		// update capabilities.
 | ||||
| 		{ | ||||
| 			let caps = &mut peer_info.capabilities; | ||||
| 			caps.serve_headers = caps.serve_headers || announcement.serve_headers; | ||||
| 			caps.serve_state_since = caps.serve_state_since.or(announcement.serve_state_since); | ||||
| 			caps.serve_chain_since = caps.serve_chain_since.or(announcement.serve_chain_since); | ||||
| 			caps.tx_relay = caps.tx_relay || announcement.tx_relay; | ||||
| 		} | ||||
| 		peer_info.capabilities.update_from(&announcement); | ||||
| 
 | ||||
| 		// TODO: notify listeners if new best block.
 | ||||
| 		for handler in &self.handlers { | ||||
| 			handler.on_announcement(*peer, &announcement); | ||||
| 		} | ||||
| 
 | ||||
| 		Ok(()) | ||||
| 	} | ||||
| @ -276,45 +425,39 @@ impl LightProtocol { | ||||
| 	fn get_block_headers(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { | ||||
| 		const MAX_HEADERS: usize = 512; | ||||
| 
 | ||||
| 		let mut present_buffer = match self.peers.read().get(peer) { | ||||
| 			Some(peer) => peer.local_buffer.clone(), | ||||
| 		let peers = self.peers.read(); | ||||
| 		let peer = match peers.get(peer) { | ||||
| 			Some(peer) => peer, | ||||
| 			None => { | ||||
| 				debug!(target: "les", "Ignoring announcement from unknown peer"); | ||||
| 				debug!(target: "les", "Ignoring request from unknown peer"); | ||||
| 				return Ok(()) | ||||
| 			} | ||||
| 		}; | ||||
| 
 | ||||
| 		self.flow_params.recharge(&mut present_buffer); | ||||
| 		let mut peer = peer.lock(); | ||||
| 
 | ||||
| 		let req_id: u64 = try!(data.val_at(0)); | ||||
| 
 | ||||
| 		let block = { | ||||
| 			let rlp = try!(data.at(1)); | ||||
| 			(try!(rlp.val_at(0)), try!(rlp.val_at(1))) | ||||
| 		}; | ||||
| 
 | ||||
| 		let req = request::Headers { | ||||
| 			block: { | ||||
| 				let rlp = try!(data.at(1)); | ||||
| 				(try!(rlp.val_at(0)), try!(rlp.val_at(1))) | ||||
| 			}, | ||||
| 			block_num: block.0, | ||||
| 			block_hash: block.1, | ||||
| 			max: ::std::cmp::min(MAX_HEADERS, try!(data.val_at(2))), | ||||
| 			skip: try!(data.val_at(3)), | ||||
| 			reverse: try!(data.val_at(4)), | ||||
| 		}; | ||||
| 
 | ||||
| 		let max_cost = self.flow_params.compute_cost(request::Kind::Headers, req.max); | ||||
| 		try!(present_buffer.deduct_cost(max_cost)); | ||||
| 		let max_cost = try!(peer.deduct_max(&self.flow_params, request::Kind::Headers, req.max)); | ||||
| 
 | ||||
| 		let response = self.provider.block_headers(req); | ||||
| 		let actual_cost = self.flow_params.compute_cost(request::Kind::Headers, response.len()); | ||||
| 		assert!(max_cost >= actual_cost, "Actual cost exceeded maximum computed cost."); | ||||
| 
 | ||||
| 		let cur_buffer = match self.peers.write().get_mut(peer) { | ||||
| 			Some(peer) => { | ||||
| 				self.flow_params.recharge(&mut peer.local_buffer); | ||||
| 				try!(peer.local_buffer.deduct_cost(actual_cost)); | ||||
| 				peer.local_buffer.current() | ||||
| 			} | ||||
| 			None => { | ||||
| 				debug!(target: "les", "peer disconnected during serving of request."); | ||||
| 				return Ok(()) | ||||
| 			} | ||||
| 		}; | ||||
| 
 | ||||
| 		let cur_buffer = peer.refund(&self.flow_params, max_cost - actual_cost); | ||||
| 		io.respond(packet::BLOCK_HEADERS, { | ||||
| 			let mut stream = RlpStream::new_list(response.len() + 2); | ||||
| 			stream.append(&req_id).append(&cur_buffer); | ||||
| @ -336,39 +479,30 @@ impl LightProtocol { | ||||
| 	fn get_block_bodies(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { | ||||
| 		const MAX_BODIES: usize = 256; | ||||
| 
 | ||||
| 		let mut present_buffer = match self.peers.read().get(peer) { | ||||
| 			Some(peer) => peer.local_buffer.clone(), | ||||
| 		let peers = self.peers.read(); | ||||
| 		let peer = match peers.get(peer) { | ||||
| 			Some(peer) => peer, | ||||
| 			None => { | ||||
| 				debug!(target: "les", "Ignoring announcement from unknown peer"); | ||||
| 				debug!(target: "les", "Ignoring request from unknown peer"); | ||||
| 				return Ok(()) | ||||
| 			} | ||||
| 		}; | ||||
| 		let mut peer = peer.lock(); | ||||
| 
 | ||||
| 		self.flow_params.recharge(&mut present_buffer); | ||||
| 		let req_id: u64 = try!(data.val_at(0)); | ||||
| 
 | ||||
| 		let req = request::Bodies { | ||||
| 			block_hashes: try!(data.iter().skip(1).take(MAX_BODIES).map(|x| x.as_val()).collect()) | ||||
| 		}; | ||||
| 
 | ||||
| 		let max_cost = self.flow_params.compute_cost(request::Kind::Bodies, req.block_hashes.len()); | ||||
| 		try!(present_buffer.deduct_cost(max_cost)); | ||||
| 		let max_cost = try!(peer.deduct_max(&self.flow_params, request::Kind::Bodies, req.block_hashes.len())); | ||||
| 
 | ||||
| 		let response = self.provider.block_bodies(req); | ||||
| 		let response_len = response.iter().filter(|x| &x[..] != &::rlp::EMPTY_LIST_RLP).count(); | ||||
| 		let actual_cost = self.flow_params.compute_cost(request::Kind::Bodies, response_len); | ||||
| 		assert!(max_cost >= actual_cost, "Actual cost exceeded maximum computed cost."); | ||||
| 
 | ||||
| 		let cur_buffer = match self.peers.write().get_mut(peer) { | ||||
| 			Some(peer) => { | ||||
| 				self.flow_params.recharge(&mut peer.local_buffer); | ||||
| 				try!(peer.local_buffer.deduct_cost(actual_cost)); | ||||
| 				peer.local_buffer.current() | ||||
| 			} | ||||
| 			None => { | ||||
| 				debug!(target: "les", "peer disconnected during serving of request."); | ||||
| 				return Ok(()) | ||||
| 			} | ||||
| 		}; | ||||
| 		let cur_buffer = peer.refund(&self.flow_params, max_cost - actual_cost); | ||||
| 
 | ||||
| 		io.respond(packet::BLOCK_BODIES, { | ||||
| 			let mut stream = RlpStream::new_list(response.len() + 2); | ||||
| @ -388,8 +522,44 @@ impl LightProtocol { | ||||
| 	} | ||||
| 
 | ||||
| 	// Handle a request for receipts.
 | ||||
| 	fn get_receipts(&self, _: &PeerId, _: &NetworkContext, _: UntrustedRlp) -> Result<(), Error> { | ||||
| 		unimplemented!() | ||||
| 	fn get_receipts(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { | ||||
| 		const MAX_RECEIPTS: usize = 256; | ||||
| 
 | ||||
| 		let peers = self.peers.read(); | ||||
| 		let peer = match peers.get(peer) { | ||||
| 			Some(peer) => peer, | ||||
| 			None => { | ||||
| 				debug!(target: "les", "Ignoring request from unknown peer"); | ||||
| 				return Ok(()) | ||||
| 			} | ||||
| 		}; | ||||
| 		let mut peer = peer.lock(); | ||||
| 
 | ||||
| 		let req_id: u64 = try!(data.val_at(0)); | ||||
| 
 | ||||
| 		let req = request::Receipts { | ||||
| 			block_hashes: try!(data.iter().skip(1).take(MAX_RECEIPTS).map(|x| x.as_val()).collect()) | ||||
| 		}; | ||||
| 
 | ||||
| 		let max_cost = try!(peer.deduct_max(&self.flow_params, request::Kind::Receipts, req.block_hashes.len())); | ||||
| 
 | ||||
| 		let response = self.provider.receipts(req); | ||||
| 		let response_len = response.iter().filter(|x| &x[..] != &::rlp::EMPTY_LIST_RLP).count(); | ||||
| 		let actual_cost = self.flow_params.compute_cost(request::Kind::Receipts, response_len); | ||||
| 		assert!(max_cost >= actual_cost, "Actual cost exceeded maximum computed cost."); | ||||
| 
 | ||||
| 		let cur_buffer = peer.refund(&self.flow_params, max_cost - actual_cost); | ||||
| 
 | ||||
| 		io.respond(packet::RECEIPTS, { | ||||
| 			let mut stream = RlpStream::new_list(response.len() + 2); | ||||
| 			stream.append(&req_id).append(&cur_buffer); | ||||
| 
 | ||||
| 			for receipts in response { | ||||
| 				stream.append_raw(&receipts, 1); | ||||
| 			} | ||||
| 
 | ||||
| 			stream.out() | ||||
| 		}).map_err(Into::into) | ||||
| 	} | ||||
| 
 | ||||
| 	// Receive a response for receipts.
 | ||||
| @ -398,8 +568,55 @@ impl LightProtocol { | ||||
| 	} | ||||
| 
 | ||||
| 	// Handle a request for proofs.
 | ||||
| 	fn get_proofs(&self, _: &PeerId, _: &NetworkContext, _: UntrustedRlp) -> Result<(), Error> { | ||||
| 		unimplemented!() | ||||
| 	fn get_proofs(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { | ||||
| 		const MAX_PROOFS: usize = 128; | ||||
| 
 | ||||
| 		let peers = self.peers.read(); | ||||
| 		let peer = match peers.get(peer) { | ||||
| 			Some(peer) => peer, | ||||
| 			None => { | ||||
| 				debug!(target: "les", "Ignoring request from unknown peer"); | ||||
| 				return Ok(()) | ||||
| 			} | ||||
| 		}; | ||||
| 		let mut peer = peer.lock(); | ||||
| 
 | ||||
| 		let req_id: u64 = try!(data.val_at(0)); | ||||
| 
 | ||||
| 		let req = { | ||||
| 			let requests: Result<Vec<_>, Error> = data.iter().skip(1).take(MAX_PROOFS).map(|x| { | ||||
| 				Ok(request::StateProof { | ||||
| 					block: try!(x.val_at(0)), | ||||
| 					key1: try!(x.val_at(1)), | ||||
| 					key2: if try!(x.at(2)).is_empty() { None } else { Some(try!(x.val_at(2))) }, | ||||
| 					from_level: try!(x.val_at(3)), | ||||
| 				}) | ||||
| 			}).collect(); | ||||
| 
 | ||||
| 			request::StateProofs { | ||||
| 				requests: try!(requests), | ||||
| 			} | ||||
| 		}; | ||||
| 
 | ||||
| 		let max_cost = try!(peer.deduct_max(&self.flow_params, request::Kind::StateProofs, req.requests.len())); | ||||
| 
 | ||||
| 		let response = self.provider.proofs(req); | ||||
| 		let response_len = response.iter().filter(|x| &x[..] != &::rlp::EMPTY_LIST_RLP).count(); | ||||
| 		let actual_cost = self.flow_params.compute_cost(request::Kind::StateProofs, response_len); | ||||
| 		assert!(max_cost >= actual_cost, "Actual cost exceeded maximum computed cost."); | ||||
| 
 | ||||
| 		let cur_buffer = peer.refund(&self.flow_params, max_cost - actual_cost); | ||||
| 
 | ||||
| 		io.respond(packet::PROOFS, { | ||||
| 			let mut stream = RlpStream::new_list(response.len() + 2); | ||||
| 			stream.append(&req_id).append(&cur_buffer); | ||||
| 
 | ||||
| 			for proof in response { | ||||
| 				stream.append_raw(&proof, 1); | ||||
| 			} | ||||
| 
 | ||||
| 			stream.out() | ||||
| 		}).map_err(Into::into) | ||||
| 	} | ||||
| 
 | ||||
| 	// Receive a response for proofs.
 | ||||
| @ -408,8 +625,53 @@ impl LightProtocol { | ||||
| 	} | ||||
| 
 | ||||
| 	// Handle a request for contract code.
 | ||||
| 	fn get_contract_code(&self, _: &PeerId, _: &NetworkContext, _: UntrustedRlp) -> Result<(), Error> { | ||||
| 		unimplemented!() | ||||
| 	fn get_contract_code(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { | ||||
| 		const MAX_CODES: usize = 256; | ||||
| 
 | ||||
| 		let peers = self.peers.read(); | ||||
| 		let peer = match peers.get(peer) { | ||||
| 			Some(peer) => peer, | ||||
| 			None => { | ||||
| 				debug!(target: "les", "Ignoring request from unknown peer"); | ||||
| 				return Ok(()) | ||||
| 			} | ||||
| 		}; | ||||
| 		let mut peer = peer.lock(); | ||||
| 
 | ||||
| 		let req_id: u64 = try!(data.val_at(0)); | ||||
| 
 | ||||
| 		let req = { | ||||
| 			let requests: Result<Vec<_>, Error> = data.iter().skip(1).take(MAX_CODES).map(|x| { | ||||
| 				Ok(request::ContractCode { | ||||
| 					block_hash: try!(x.val_at(0)), | ||||
| 					account_key: try!(x.val_at(1)), | ||||
| 				}) | ||||
| 			}).collect(); | ||||
| 
 | ||||
| 			request::ContractCodes { | ||||
| 				code_requests: try!(requests), | ||||
| 			} | ||||
| 		}; | ||||
| 
 | ||||
| 		let max_cost = try!(peer.deduct_max(&self.flow_params, request::Kind::Codes, req.code_requests.len())); | ||||
| 
 | ||||
| 		let response = self.provider.contract_code(req); | ||||
| 		let response_len = response.iter().filter(|x| !x.is_empty()).count(); | ||||
| 		let actual_cost = self.flow_params.compute_cost(request::Kind::Codes, response_len); | ||||
| 		assert!(max_cost >= actual_cost, "Actual cost exceeded maximum computed cost."); | ||||
| 
 | ||||
| 		let cur_buffer = peer.refund(&self.flow_params, max_cost - actual_cost); | ||||
| 
 | ||||
| 		io.respond(packet::CONTRACT_CODES, { | ||||
| 			let mut stream = RlpStream::new_list(response.len() + 2); | ||||
| 			stream.append(&req_id).append(&cur_buffer); | ||||
| 
 | ||||
| 			for code in response { | ||||
| 				stream.append_raw(&code, 1); | ||||
| 			} | ||||
| 
 | ||||
| 			stream.out() | ||||
| 		}).map_err(Into::into) | ||||
| 	} | ||||
| 
 | ||||
| 	// Receive a response for contract code.
 | ||||
| @ -418,8 +680,54 @@ impl LightProtocol { | ||||
| 	} | ||||
| 
 | ||||
| 	// Handle a request for header proofs
 | ||||
| 	fn get_header_proofs(&self, _: &PeerId, _: &NetworkContext, _: UntrustedRlp) -> Result<(), Error> { | ||||
| 		unimplemented!() | ||||
| 	fn get_header_proofs(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { | ||||
| 		const MAX_PROOFS: usize = 256; | ||||
| 
 | ||||
| 		let peers = self.peers.read(); | ||||
| 		let peer = match peers.get(peer) { | ||||
| 			Some(peer) => peer, | ||||
| 			None => { | ||||
| 				debug!(target: "les", "Ignoring request from unknown peer"); | ||||
| 				return Ok(()) | ||||
| 			} | ||||
| 		}; | ||||
| 		let mut peer = peer.lock(); | ||||
| 
 | ||||
| 		let req_id: u64 = try!(data.val_at(0)); | ||||
| 
 | ||||
| 		let req = { | ||||
| 			let requests: Result<Vec<_>, Error> = data.iter().skip(1).take(MAX_PROOFS).map(|x| { | ||||
| 				Ok(request::HeaderProof { | ||||
| 					cht_number: try!(x.val_at(0)), | ||||
| 					block_number: try!(x.val_at(1)), | ||||
| 					from_level: try!(x.val_at(2)), | ||||
| 				}) | ||||
| 			}).collect(); | ||||
| 
 | ||||
| 			request::HeaderProofs { | ||||
| 				requests: try!(requests), | ||||
| 			} | ||||
| 		}; | ||||
| 
 | ||||
| 		let max_cost = try!(peer.deduct_max(&self.flow_params, request::Kind::HeaderProofs, req.requests.len())); | ||||
| 
 | ||||
| 		let response = self.provider.header_proofs(req); | ||||
| 		let response_len = response.iter().filter(|x| &x[..] != ::rlp::EMPTY_LIST_RLP).count(); | ||||
| 		let actual_cost = self.flow_params.compute_cost(request::Kind::HeaderProofs, response_len); | ||||
| 		assert!(max_cost >= actual_cost, "Actual cost exceeded maximum computed cost."); | ||||
| 
 | ||||
| 		let cur_buffer = peer.refund(&self.flow_params, max_cost - actual_cost); | ||||
| 
 | ||||
| 		io.respond(packet::HEADER_PROOFS, { | ||||
| 			let mut stream = RlpStream::new_list(response.len() + 2); | ||||
| 			stream.append(&req_id).append(&cur_buffer); | ||||
| 
 | ||||
| 			for proof in response { | ||||
| 				stream.append_raw(&proof, 1); | ||||
| 			} | ||||
| 
 | ||||
| 			stream.out() | ||||
| 		}).map_err(Into::into) | ||||
| 	} | ||||
| 
 | ||||
| 	// Receive a response for header proofs
 | ||||
| @ -428,8 +736,18 @@ impl LightProtocol { | ||||
| 	} | ||||
| 
 | ||||
| 	// Receive a set of transactions to relay.
 | ||||
| 	fn relay_transactions(&self, _: &PeerId, _: &NetworkContext, _: UntrustedRlp) -> Result<(), Error> { | ||||
| 		unimplemented!() | ||||
| 	fn relay_transactions(&self, peer: &PeerId, 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()); | ||||
| 
 | ||||
| 		debug!(target: "les", "Received {} transactions to relay from peer {}", txs.len(), peer); | ||||
| 
 | ||||
| 		for handler in &self.handlers { | ||||
| 			handler.on_transactions(*peer, &txs); | ||||
| 		} | ||||
| 
 | ||||
| 		Ok(()) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| @ -464,7 +782,7 @@ impl NetworkProtocolHandler for LightProtocol { | ||||
| 			packet::GET_HEADER_PROOFS => self.get_header_proofs(peer, io, rlp), | ||||
| 			packet::HEADER_PROOFS => self.header_proofs(peer, io, rlp), | ||||
| 
 | ||||
| 			packet::SEND_TRANSACTIONS => self.relay_transactions(peer, io, rlp), | ||||
| 			packet::SEND_TRANSACTIONS => self.relay_transactions(peer, rlp), | ||||
| 
 | ||||
| 			other => { | ||||
| 				Err(Error::UnrecognizedPacket(other)) | ||||
| @ -503,4 +821,86 @@ impl NetworkProtocolHandler for LightProtocol { | ||||
| 			_ => warn!(target: "les", "received timeout on unknown token {}", timer), | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Helper for encoding the request to RLP with the given ID.
 | ||||
| fn encode_request(req: &Request, req_id: usize) -> Vec<u8> { | ||||
| 	match *req { | ||||
| 		Request::Headers(ref headers) => { | ||||
| 			let mut stream = RlpStream::new_list(5); | ||||
| 			stream | ||||
| 				.append(&req_id) | ||||
| 				.begin_list(2) | ||||
| 					.append(&headers.block_num) | ||||
| 					.append(&headers.block_hash) | ||||
| 				.append(&headers.max) | ||||
| 				.append(&headers.skip) | ||||
| 				.append(&headers.reverse); | ||||
| 			stream.out() | ||||
| 		} | ||||
| 		Request::Bodies(ref request) => { | ||||
| 			let mut stream = RlpStream::new_list(request.block_hashes.len() + 1); | ||||
| 			stream.append(&req_id); | ||||
| 
 | ||||
| 			for hash in &request.block_hashes { | ||||
| 				stream.append(hash); | ||||
| 			} | ||||
| 
 | ||||
| 			stream.out() | ||||
| 		} | ||||
| 		Request::Receipts(ref request) => { | ||||
| 			let mut stream = RlpStream::new_list(request.block_hashes.len() + 1); | ||||
| 			stream.append(&req_id); | ||||
| 
 | ||||
| 			for hash in &request.block_hashes { | ||||
| 				stream.append(hash); | ||||
| 			} | ||||
| 
 | ||||
| 			stream.out() | ||||
| 		} | ||||
| 		Request::StateProofs(ref request) => { | ||||
| 			let mut stream = RlpStream::new_list(request.requests.len() + 1); | ||||
| 			stream.append(&req_id); | ||||
| 
 | ||||
| 			for proof_req in &request.requests { | ||||
| 				stream.begin_list(4) | ||||
| 					.append(&proof_req.block) | ||||
| 					.append(&proof_req.key1); | ||||
| 
 | ||||
| 				match proof_req.key2 { | ||||
| 					Some(ref key2) => stream.append(key2), | ||||
| 					None => stream.append_empty_data(), | ||||
| 				}; | ||||
| 
 | ||||
| 				stream.append(&proof_req.from_level); | ||||
| 			} | ||||
| 
 | ||||
| 			stream.out() | ||||
| 		} | ||||
| 		Request::Codes(ref request) => { | ||||
| 			let mut stream = RlpStream::new_list(request.code_requests.len() + 1); | ||||
| 			stream.append(&req_id); | ||||
| 
 | ||||
| 			for code_req in &request.code_requests { | ||||
| 				stream.begin_list(2) | ||||
| 					.append(&code_req.block_hash) | ||||
| 					.append(&code_req.account_key); | ||||
| 			} | ||||
| 
 | ||||
| 			stream.out() | ||||
| 		} | ||||
| 		Request::HeaderProofs(ref request) => { | ||||
| 			let mut stream = RlpStream::new_list(request.requests.len() + 1); | ||||
| 			stream.append(&req_id); | ||||
| 
 | ||||
| 			for proof_req in &request.requests { | ||||
| 				stream.begin_list(3) | ||||
| 					.append(&proof_req.cht_number) | ||||
| 					.append(&proof_req.block_number) | ||||
| 					.append(&proof_req.from_level); | ||||
| 			} | ||||
| 
 | ||||
| 			stream.out() | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @ -183,8 +183,10 @@ pub struct Capabilities { | ||||
| 	/// Whether this peer can serve headers
 | ||||
| 	pub serve_headers: bool, | ||||
| 	/// Earliest block number it can serve block/receipt requests for.
 | ||||
| 	/// `None` means no requests will be servable.
 | ||||
| 	pub serve_chain_since: Option<u64>, | ||||
| 	/// Earliest block number it can serve state requests for.
 | ||||
| 	/// `None` means no requests will be servable.
 | ||||
| 	pub serve_state_since: Option<u64>, | ||||
| 	/// Whether it can relay transactions to the eth network.
 | ||||
| 	pub tx_relay: bool, | ||||
| @ -201,6 +203,16 @@ impl Default for Capabilities { | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl Capabilities { | ||||
| 	/// Update the capabilities from an announcement.
 | ||||
| 	pub fn update_from(&mut self, announcement: &Announcement) { | ||||
| 		self.serve_headers = self.serve_headers || announcement.serve_headers; | ||||
| 		self.serve_state_since = self.serve_state_since.or(announcement.serve_state_since); | ||||
| 		self.serve_chain_since = self.serve_chain_since.or(announcement.serve_chain_since); | ||||
| 		self.tx_relay = self.tx_relay || announcement.tx_relay; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| /// Attempt to parse a handshake message into its three parts:
 | ||||
| ///   - chain status
 | ||||
| ///   - serving capabilities
 | ||||
|  | ||||
| @ -17,8 +17,11 @@ | ||||
| //! A provider for the LES protocol. This is typically a full node, who can
 | ||||
| //! give as much data as necessary to its peers.
 | ||||
| 
 | ||||
| use ethcore::transaction::SignedTransaction; | ||||
| use ethcore::blockchain_info::BlockChainInfo; | ||||
| use ethcore::client::{BlockChainClient, ProvingBlockChainClient}; | ||||
| use ethcore::transaction::SignedTransaction; | ||||
| use ethcore::ids::BlockID; | ||||
| 
 | ||||
| use util::{Bytes, H256}; | ||||
| 
 | ||||
| use request; | ||||
| @ -26,7 +29,8 @@ use request; | ||||
| /// Defines the operations that a provider for `LES` must fulfill.
 | ||||
| ///
 | ||||
| /// These are defined at [1], but may be subject to change.
 | ||||
| /// Requests which can't be fulfilled should return an empty RLP list.
 | ||||
| /// Requests which can't be fulfilled should return either an empty RLP list
 | ||||
| /// or empty vector where appropriate.
 | ||||
| ///
 | ||||
| /// [1]: https://github.com/ethcore/parity/wiki/Light-Ethereum-Subprotocol-(LES)
 | ||||
| pub trait Provider: Send + Sync { | ||||
| @ -34,9 +38,12 @@ pub trait Provider: Send + Sync { | ||||
| 	fn chain_info(&self) -> BlockChainInfo; | ||||
| 
 | ||||
| 	/// Find the depth of a common ancestor between two blocks.
 | ||||
| 	/// If either block is unknown or an ancestor can't be found
 | ||||
| 	/// then return `None`.
 | ||||
| 	fn reorg_depth(&self, a: &H256, b: &H256) -> Option<u64>; | ||||
| 
 | ||||
| 	/// Earliest state.
 | ||||
| 	/// Earliest block where state queries are available.
 | ||||
| 	/// If `None`, no state queries are servable.
 | ||||
| 	fn earliest_state(&self) -> Option<u64>; | ||||
| 
 | ||||
| 	/// Provide a list of headers starting at the requested block,
 | ||||
| @ -57,15 +64,105 @@ pub trait Provider: Send + Sync { | ||||
| 	/// Provide a set of merkle proofs, as requested. Each request is a
 | ||||
| 	/// block hash and request parameters.
 | ||||
| 	///
 | ||||
| 	/// Returns a vector to RLP-encoded lists satisfying the requests.
 | ||||
| 	/// Returns a vector of RLP-encoded lists satisfying the requests.
 | ||||
| 	fn proofs(&self, req: request::StateProofs) -> Vec<Bytes>; | ||||
| 
 | ||||
| 	/// Provide contract code for the specified (block_hash, account_hash) pairs.
 | ||||
| 	fn code(&self, req: request::ContractCodes) -> Vec<Bytes>; | ||||
| 	/// 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.
 | ||||
| 	fn header_proofs(&self, req: request::HeaderProofs) -> Vec<Bytes>; | ||||
| 
 | ||||
| 	/// Provide pending transactions.
 | ||||
| 	fn pending_transactions(&self) -> Vec<SignedTransaction>; | ||||
| } | ||||
| 
 | ||||
| // Implementation of a light client data provider for a client.
 | ||||
| impl<T: ProvingBlockChainClient + ?Sized> Provider for T { | ||||
| 	fn chain_info(&self) -> BlockChainInfo { | ||||
| 		BlockChainClient::chain_info(self) | ||||
| 	} | ||||
| 
 | ||||
| 	fn reorg_depth(&self, a: &H256, b: &H256) -> Option<u64> { | ||||
| 		self.tree_route(a, b).map(|route| route.index as u64) | ||||
| 	} | ||||
| 
 | ||||
| 	fn earliest_state(&self) -> Option<u64> { | ||||
| 		Some(self.pruning_info().earliest_state) | ||||
| 	} | ||||
| 
 | ||||
| 	fn block_headers(&self, req: request::Headers) -> Vec<Bytes> { | ||||
| 		let best_num = self.chain_info().best_block_number; | ||||
| 		let start_num = 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)); | ||||
| 				return vec![] | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		(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| if req.reverse { start_num - x } else { start_num + x }) | ||||
| 			.map(|x| self.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.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.block_receipts(&hash)) | ||||
| 			.map(|receipts| receipts.unwrap_or_else(|| ::rlp::EMPTY_LIST_RLP.to_vec())) | ||||
| 			.collect() | ||||
| 	} | ||||
| 
 | ||||
| 	fn proofs(&self, req: request::StateProofs) -> Vec<Bytes> { | ||||
| 		use rlp::{RlpStream, Stream}; | ||||
| 
 | ||||
| 		let mut results = Vec::with_capacity(req.requests.len()); | ||||
| 
 | ||||
| 		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)), | ||||
| 			}; | ||||
| 
 | ||||
| 			let mut stream = RlpStream::new_list(proof.len()); | ||||
| 			for node in proof { | ||||
| 				stream.append_raw(&node, 1); | ||||
| 			} | ||||
| 
 | ||||
| 			results.push(stream.out()); | ||||
| 		} | ||||
| 
 | ||||
| 		results | ||||
| 	} | ||||
| 
 | ||||
| 	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)) | ||||
| 			}) | ||||
| 			.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> { | ||||
| 		BlockChainClient::pending_transactions(self) | ||||
| 	} | ||||
| } | ||||
| @ -16,25 +16,26 @@ | ||||
| 
 | ||||
| //! LES request types.
 | ||||
| 
 | ||||
| // TODO: make IPC compatible.
 | ||||
| 
 | ||||
| use util::H256; | ||||
| 
 | ||||
| /// A request for block headers.
 | ||||
| #[derive(Debug, Clone, PartialEq, Eq)] | ||||
| #[derive(Debug, Clone, PartialEq, Eq, Binary)] | ||||
| pub struct Headers { | ||||
| 	/// Block information for the request being made.
 | ||||
| 	pub block: (u64, H256), | ||||
| 	/// Starting block number
 | ||||
| 	pub block_num: u64, | ||||
| 	/// Starting block hash. This and number could be combined but IPC codegen is
 | ||||
| 	/// not robust enough to support it.
 | ||||
| 	pub block_hash: H256, | ||||
| 	/// The maximum amount of headers which can be returned.
 | ||||
| 	pub max: usize, | ||||
| 	/// The amount of headers to skip between each response entry.
 | ||||
| 	pub skip: usize, | ||||
| 	pub skip: u64, | ||||
| 	/// Whether the headers should proceed in falling number from the initial block.
 | ||||
| 	pub reverse: bool, | ||||
| } | ||||
| 
 | ||||
| /// A request for specific block bodies.
 | ||||
| #[derive(Debug, Clone, PartialEq, Eq)] | ||||
| #[derive(Debug, Clone, PartialEq, Eq, Binary)] | ||||
| pub struct Bodies { | ||||
| 	/// Hashes which bodies are being requested for.
 | ||||
| 	pub block_hashes: Vec<H256> | ||||
| @ -44,14 +45,14 @@ pub struct Bodies { | ||||
| ///
 | ||||
| /// This request is answered with a list of transaction receipts for each block
 | ||||
| /// requested.
 | ||||
| #[derive(Debug, Clone, PartialEq, Eq)] | ||||
| #[derive(Debug, Clone, PartialEq, Eq, 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)] | ||||
| #[derive(Debug, Clone, PartialEq, Eq, Binary)] | ||||
| pub struct StateProof { | ||||
| 	/// Block hash to query state from.
 | ||||
| 	pub block: H256, | ||||
| @ -65,21 +66,30 @@ pub struct StateProof { | ||||
| } | ||||
| 
 | ||||
| /// A request for state proofs.
 | ||||
| #[derive(Debug, Clone, PartialEq, Eq)] | ||||
| #[derive(Debug, Clone, PartialEq, Eq, Binary)] | ||||
| pub struct StateProofs { | ||||
| 	/// All the proof requests.
 | ||||
| 	pub requests: Vec<StateProof>, | ||||
| } | ||||
| 
 | ||||
| /// A request for contract code.
 | ||||
| #[derive(Debug, Clone, PartialEq, Eq)] | ||||
| #[derive(Debug, Clone, PartialEq, Eq, Binary)] | ||||
| pub struct ContractCode { | ||||
| 	/// Block hash
 | ||||
| 	pub block_hash: H256, | ||||
| 	/// Account key (== sha3(address))
 | ||||
| 	pub account_key: H256, | ||||
| } | ||||
| 
 | ||||
| /// A request for contract code.
 | ||||
| #[derive(Debug, Clone, PartialEq, Eq, Binary)] | ||||
| pub struct ContractCodes { | ||||
| 	/// Block hash and account key (== sha3(address)) pairs to fetch code for.
 | ||||
| 	pub code_requests: Vec<(H256, H256)>, | ||||
| 	pub code_requests: Vec<ContractCode>, | ||||
| } | ||||
| 
 | ||||
| /// A request for a header proof from the Canonical Hash Trie.
 | ||||
| #[derive(Debug, Clone, PartialEq, Eq)] | ||||
| #[derive(Debug, Clone, PartialEq, Eq, Binary)] | ||||
| pub struct HeaderProof { | ||||
| 	/// Number of the CHT.
 | ||||
| 	pub cht_number: u64, | ||||
| @ -90,14 +100,14 @@ pub struct HeaderProof { | ||||
| } | ||||
| 
 | ||||
| /// A request for header proofs from the CHT.
 | ||||
| #[derive(Debug, Clone, PartialEq, Eq)] | ||||
| #[derive(Debug, Clone, PartialEq, Eq, Binary)] | ||||
| pub struct HeaderProofs { | ||||
| 	/// All the proof requests.
 | ||||
| 	pub requests: Vec<HeaderProofs>, | ||||
| 	pub requests: Vec<HeaderProof>, | ||||
| } | ||||
| 
 | ||||
| /// Kinds of requests.
 | ||||
| #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||||
| #[derive(Debug, Clone, Copy, PartialEq, Eq, Binary)] | ||||
| pub enum Kind { | ||||
| 	/// Requesting headers.
 | ||||
| 	Headers, | ||||
| @ -114,7 +124,7 @@ pub enum Kind { | ||||
| } | ||||
| 
 | ||||
| /// Encompasses all possible types of requests in a single structure.
 | ||||
| #[derive(Debug, Clone, PartialEq, Eq)] | ||||
| #[derive(Debug, Clone, PartialEq, Eq, Binary)] | ||||
| pub enum Request { | ||||
| 	/// Requesting headers.
 | ||||
| 	Headers(Headers), | ||||
| @ -142,4 +152,16 @@ impl Request { | ||||
| 			Request::HeaderProofs(_) => Kind::HeaderProofs, | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	/// Get the amount of requests being made.
 | ||||
| 	pub fn amount(&self) -> usize { | ||||
| 		match *self { | ||||
| 			Request::Headers(ref req) => req.max, | ||||
| 			Request::Bodies(ref req) => req.block_hashes.len(), | ||||
| 			Request::Receipts(ref req) => req.block_hashes.len(), | ||||
| 			Request::StateProofs(ref req) => req.requests.len(), | ||||
| 			Request::Codes(ref req) => req.code_requests.len(), | ||||
| 			Request::HeaderProofs(ref req) => req.requests.len(), | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										20
									
								
								ethcore/light/src/types/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								ethcore/light/src/types/mod.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,20 @@ | ||||
| // 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/>.
 | ||||
| 
 | ||||
| //! Types used in the public (IPC) api which require custom code generation.
 | ||||
| 
 | ||||
| #![allow(dead_code, unused_assignments, unused_variables)] // codegen issues
 | ||||
| include!(concat!(env!("OUT_DIR"), "/mod.rs.in")); | ||||
							
								
								
									
										17
									
								
								ethcore/light/src/types/mod.rs.in
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								ethcore/light/src/types/mod.rs.in
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | ||||
| // Copyright 2015, 2016 Ethcore (UK) Ltd.
 | ||||
| // This file is part of Parity.
 | ||||
| 
 | ||||
| // Parity is free software: you can redistribute it and/or modify
 | ||||
| // it under the terms of the GNU General Public License as published by
 | ||||
| // the Free Software Foundation, either version 3 of the License, or
 | ||||
| // (at your option) any later version.
 | ||||
| 
 | ||||
| // Parity is distributed in the hope that it will be useful,
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | ||||
| // GNU General Public License for more details.
 | ||||
| 
 | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| pub mod les_request; | ||||
| @ -194,6 +194,11 @@ impl AccountProvider { | ||||
| 		Ok(self.address_book.write().set_meta(account, meta)) | ||||
| 	} | ||||
| 
 | ||||
| 	/// Removes and address from the addressbook
 | ||||
| 	pub fn remove_address(&self, addr: Address) -> Result<(), Error> { | ||||
| 		Ok(self.address_book.write().remove(addr)) | ||||
| 	} | ||||
| 
 | ||||
| 	/// Returns each account along with name and meta.
 | ||||
| 	pub fn accounts_info(&self) -> Result<HashMap<Address, AccountMeta>, Error> { | ||||
| 		let r: HashMap<Address, AccountMeta> = try!(self.sstore.accounts()) | ||||
|  | ||||
| @ -74,6 +74,12 @@ impl AddressBook { | ||||
| 		} | ||||
| 		self.save(); | ||||
| 	} | ||||
| 
 | ||||
| 	/// Removes an entry
 | ||||
| 	pub fn remove(&mut self, a: Address) { | ||||
| 		self.cache.remove(&a); | ||||
| 		self.save(); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| /// Dapps user settings
 | ||||
| @ -244,4 +250,22 @@ mod tests { | ||||
| 			} | ||||
| 		]); | ||||
| 	} | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn should_remove_address() { | ||||
| 		let temp = RandomTempPath::create_dir(); | ||||
| 		let path = temp.as_str().to_owned(); | ||||
| 		let mut b = AddressBook::new(path.clone()); | ||||
| 
 | ||||
| 		b.set_name(1.into(), "One".to_owned()); | ||||
| 		b.set_name(2.into(), "Two".to_owned()); | ||||
| 		b.set_name(3.into(), "Three".to_owned()); | ||||
| 		b.remove(2.into()); | ||||
| 
 | ||||
| 		let b = AddressBook::new(path); | ||||
| 		assert_eq!(b.get(), hash_map![ | ||||
| 			1.into() => AccountMeta{name: "One".to_owned(), meta: "{}".to_owned(), uuid: None}, | ||||
| 			3.into() => AccountMeta{name: "Three".to_owned(), meta: "{}".to_owned(), uuid: None} | ||||
| 		]); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -34,6 +34,7 @@ use blockchain::update::ExtrasUpdate; | ||||
| use blockchain::{CacheSize, ImportRoute, Config}; | ||||
| use db::{self, Writable, Readable, CacheUpdatePolicy}; | ||||
| use cache_manager::CacheManager; | ||||
| use engines::Engine; | ||||
| 
 | ||||
| const LOG_BLOOMS_LEVELS: usize = 3; | ||||
| const LOG_BLOOMS_ELEMENTS_PER_INDEX: usize = 16; | ||||
| @ -198,6 +199,9 @@ pub struct BlockChain { | ||||
| 	pending_block_hashes: RwLock<HashMap<BlockNumber, H256>>, | ||||
| 	pending_block_details: RwLock<HashMap<H256, BlockDetails>>, | ||||
| 	pending_transaction_addresses: RwLock<HashMap<H256, Option<TransactionAddress>>>, | ||||
| 
 | ||||
| 	// Used for block ordering.
 | ||||
| 	engine: Arc<Engine>, | ||||
| } | ||||
| 
 | ||||
| impl BlockProvider for BlockChain { | ||||
| @ -415,9 +419,8 @@ impl<'a> Iterator for AncestryIter<'a> { | ||||
| } | ||||
| 
 | ||||
| impl BlockChain { | ||||
| 	#[cfg_attr(feature="dev", allow(useless_let_if_seq))] | ||||
| 	/// Create new instance of blockchain from given Genesis
 | ||||
| 	pub fn new(config: Config, genesis: &[u8], db: Arc<Database>) -> BlockChain { | ||||
| 	/// Create new instance of blockchain from given Genesis and block picking rules of Engine.
 | ||||
| 	pub fn new(config: Config, genesis: &[u8], db: Arc<Database>, engine: Arc<Engine>) -> BlockChain { | ||||
| 		// 400 is the avarage size of the key
 | ||||
| 		let cache_man = CacheManager::new(config.pref_cache_size, config.max_cache_size, 400); | ||||
| 
 | ||||
| @ -442,6 +445,7 @@ impl BlockChain { | ||||
| 			pending_block_hashes: RwLock::new(HashMap::new()), | ||||
| 			pending_block_details: RwLock::new(HashMap::new()), | ||||
| 			pending_transaction_addresses: RwLock::new(HashMap::new()), | ||||
| 			engine: engine, | ||||
| 		}; | ||||
| 
 | ||||
| 		// load best block
 | ||||
| @ -858,13 +862,12 @@ impl BlockChain { | ||||
| 		let number = header.number(); | ||||
| 		let parent_hash = header.parent_hash(); | ||||
| 		let parent_details = self.block_details(&parent_hash).unwrap_or_else(|| panic!("Invalid parent hash: {:?}", parent_hash)); | ||||
| 		let total_difficulty = parent_details.total_difficulty + header.difficulty(); | ||||
| 		let is_new_best = total_difficulty > self.best_block_total_difficulty(); | ||||
| 		let is_new_best = self.engine.is_new_best_block(self.best_block_total_difficulty(), HeaderView::new(&self.best_block_header()), &parent_details, header); | ||||
| 
 | ||||
| 		BlockInfo { | ||||
| 			hash: hash, | ||||
| 			number: number, | ||||
| 			total_difficulty: total_difficulty, | ||||
| 			total_difficulty: parent_details.total_difficulty + header.difficulty(), | ||||
| 			location: if is_new_best { | ||||
| 				// on new best block we need to make sure that all ancestors
 | ||||
| 				// are moved to "canon chain"
 | ||||
| @ -1319,11 +1322,16 @@ mod tests { | ||||
| 	use views::BlockView; | ||||
| 	use transaction::{Transaction, Action}; | ||||
| 	use log_entry::{LogEntry, LocalizedLogEntry}; | ||||
| 	use spec::Spec; | ||||
| 
 | ||||
| 	fn new_db(path: &str) -> Arc<Database> { | ||||
| 		Arc::new(Database::open(&DatabaseConfig::with_columns(::db::NUM_COLUMNS), path).unwrap()) | ||||
| 	} | ||||
| 
 | ||||
| 	fn new_chain(genesis: &[u8], db: Arc<Database>) -> BlockChain { | ||||
| 		BlockChain::new(Config::default(), genesis, db, Spec::new_null().engine) 
 | ||||
| 	} | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn should_cache_best_block() { | ||||
| 		// given
 | ||||
| @ -1334,7 +1342,7 @@ mod tests { | ||||
| 
 | ||||
| 		let temp = RandomTempPath::new(); | ||||
| 		let db = new_db(temp.as_str()); | ||||
| 		let bc = BlockChain::new(Config::default(), &genesis, db.clone()); | ||||
| 		let bc = new_chain(&genesis, db.clone()); | ||||
| 		assert_eq!(bc.best_block_number(), 0); | ||||
| 
 | ||||
| 		// when
 | ||||
| @ -1360,7 +1368,7 @@ mod tests { | ||||
| 
 | ||||
| 		let temp = RandomTempPath::new(); | ||||
| 		let db = new_db(temp.as_str()); | ||||
| 		let bc = BlockChain::new(Config::default(), &genesis, db.clone()); | ||||
| 		let bc = new_chain(&genesis, db.clone()); | ||||
| 
 | ||||
| 		assert_eq!(bc.genesis_hash(), genesis_hash.clone()); | ||||
| 		assert_eq!(bc.best_block_hash(), genesis_hash.clone()); | ||||
| @ -1391,7 +1399,7 @@ mod tests { | ||||
| 
 | ||||
| 		let temp = RandomTempPath::new(); | ||||
| 		let db = new_db(temp.as_str()); | ||||
| 		let bc = BlockChain::new(Config::default(), &genesis, db.clone()); | ||||
| 		let bc = new_chain(&genesis, db.clone()); | ||||
| 
 | ||||
| 		let mut block_hashes = vec![genesis_hash.clone()]; | ||||
| 		let mut batch = db.transaction(); | ||||
| @ -1427,7 +1435,7 @@ mod tests { | ||||
| 
 | ||||
| 		let temp = RandomTempPath::new(); | ||||
| 		let db = new_db(temp.as_str()); | ||||
| 		let bc = BlockChain::new(Config::default(), &genesis, db.clone()); | ||||
| 		let bc = new_chain(&genesis, db.clone()); | ||||
| 
 | ||||
| 		let mut batch =db.transaction(); | ||||
| 		for b in &[&b1a, &b1b, &b2a, &b2b, &b3a, &b3b, &b4a, &b4b, &b5a, &b5b] { | ||||
| @ -1489,7 +1497,7 @@ mod tests { | ||||
| 
 | ||||
| 		let temp = RandomTempPath::new(); | ||||
| 		let db = new_db(temp.as_str()); | ||||
| 		let bc = BlockChain::new(Config::default(), &genesis, db.clone()); | ||||
| 		let bc = new_chain(&genesis, db.clone()); | ||||
| 
 | ||||
| 		let mut batch = db.transaction(); | ||||
| 		let _ = bc.insert_block(&mut batch, &b1a, vec![]); | ||||
| @ -1577,7 +1585,7 @@ mod tests { | ||||
| 
 | ||||
| 		let temp = RandomTempPath::new(); | ||||
| 		let db = new_db(temp.as_str()); | ||||
| 		let bc = BlockChain::new(Config::default(), &genesis, db.clone()); | ||||
| 		let bc = new_chain(&genesis, db.clone()); | ||||
| 
 | ||||
| 		let mut batch = db.transaction(); | ||||
| 		let _ = bc.insert_block(&mut batch, &b1a, vec![]); | ||||
| @ -1639,7 +1647,7 @@ mod tests { | ||||
| 
 | ||||
| 		let temp = RandomTempPath::new(); | ||||
| 		let db = new_db(temp.as_str()); | ||||
| 		let bc = BlockChain::new(Config::default(), &genesis, db.clone()); | ||||
| 		let bc = new_chain(&genesis, db.clone()); | ||||
| 
 | ||||
| 		let mut batch = db.transaction(); | ||||
| 		let ir1 = bc.insert_block(&mut batch, &b1, vec![]); | ||||
| @ -1755,7 +1763,7 @@ mod tests { | ||||
| 		let temp = RandomTempPath::new(); | ||||
| 		{ | ||||
| 			let db = new_db(temp.as_str()); | ||||
| 			let bc = BlockChain::new(Config::default(), &genesis, db.clone()); | ||||
| 			let bc = new_chain(&genesis, db.clone()); | ||||
| 			assert_eq!(bc.best_block_hash(), genesis_hash); | ||||
| 			let mut batch =db.transaction(); | ||||
| 			bc.insert_block(&mut batch, &first, vec![]); | ||||
| @ -1766,7 +1774,7 @@ mod tests { | ||||
| 
 | ||||
| 		{ | ||||
| 			let db = new_db(temp.as_str()); | ||||
| 			let bc = BlockChain::new(Config::default(), &genesis, db.clone()); | ||||
| 			let bc = new_chain(&genesis, db.clone()); | ||||
| 
 | ||||
| 			assert_eq!(bc.best_block_hash(), first_hash); | ||||
| 		} | ||||
| @ -1821,7 +1829,7 @@ mod tests { | ||||
| 
 | ||||
| 		let temp = RandomTempPath::new(); | ||||
| 		let db = new_db(temp.as_str()); | ||||
| 		let bc = BlockChain::new(Config::default(), &genesis, db.clone()); | ||||
| 		let bc = new_chain(&genesis, db.clone()); | ||||
| 		let mut batch =db.transaction(); | ||||
| 		bc.insert_block(&mut batch, &b1, vec![]); | ||||
| 		db.write(batch).unwrap(); | ||||
| @ -1881,7 +1889,7 @@ mod tests { | ||||
| 
 | ||||
| 		let temp = RandomTempPath::new(); | ||||
| 		let db = new_db(temp.as_str()); | ||||
| 		let bc = BlockChain::new(Config::default(), &genesis, db.clone()); | ||||
| 		let bc = new_chain(&genesis, db.clone()); | ||||
| 		insert_block(&db, &bc, &b1, vec![Receipt { | ||||
| 			state_root: H256::default(), | ||||
| 			gas_used: 10_000.into(), | ||||
| @ -1985,7 +1993,7 @@ mod tests { | ||||
| 
 | ||||
| 		let temp = RandomTempPath::new(); | ||||
| 		let db = new_db(temp.as_str()); | ||||
| 		let bc = BlockChain::new(Config::default(), &genesis, db.clone()); | ||||
| 		let bc = new_chain(&genesis, db.clone()); | ||||
| 
 | ||||
| 		let blocks_b1 = bc.blocks_with_bloom(&bloom_b1, 0, 5); | ||||
| 		let blocks_b2 = bc.blocks_with_bloom(&bloom_b2, 0, 5); | ||||
| @ -2042,7 +2050,7 @@ mod tests { | ||||
| 
 | ||||
| 		{ | ||||
| 			let db = new_db(temp.as_str()); | ||||
| 			let bc = BlockChain::new(Config::default(), &genesis, db.clone()); | ||||
| 			let bc = new_chain(&genesis, db.clone()); | ||||
| 			let uncle = canon_chain.fork(1).generate(&mut finalizer.fork()).unwrap(); | ||||
| 
 | ||||
| 			let mut batch =db.transaction(); | ||||
| @ -2061,7 +2069,7 @@ mod tests { | ||||
| 
 | ||||
| 		// re-loading the blockchain should load the correct best block.
 | ||||
| 		let db = new_db(temp.as_str()); | ||||
| 		let bc = BlockChain::new(Config::default(), &genesis, db.clone()); | ||||
| 		let bc = new_chain(&genesis, db.clone()); | ||||
| 		assert_eq!(bc.best_block_number(), 5); | ||||
| 	} | ||||
| 
 | ||||
| @ -2078,7 +2086,7 @@ mod tests { | ||||
| 
 | ||||
| 		let temp = RandomTempPath::new(); | ||||
| 		let db = new_db(temp.as_str()); | ||||
| 		let bc = BlockChain::new(Config::default(), &genesis, db.clone()); | ||||
| 		let bc = new_chain(&genesis, db.clone()); | ||||
| 
 | ||||
| 		let mut batch =db.transaction(); | ||||
| 		bc.insert_block(&mut batch, &first, vec![]); | ||||
|  | ||||
| @ -54,7 +54,7 @@ use blockchain::{BlockChain, BlockProvider, TreeRoute, ImportRoute}; | ||||
| use client::{ | ||||
| 	BlockId, TransactionId, UncleId, TraceId, ClientConfig, BlockChainClient, | ||||
| 	MiningBlockChainClient, TraceFilter, CallAnalytics, BlockImportError, Mode, | ||||
| 	ChainNotify, | ||||
| 	ChainNotify, PruningInfo, ProvingBlockChainClient, | ||||
| }; | ||||
| use client::Error as ClientError; | ||||
| use env_info::EnvInfo; | ||||
| @ -173,7 +173,7 @@ impl Client { | ||||
| 		let gb = spec.genesis_block(); | ||||
| 
 | ||||
| 		let db = Arc::new(try!(Database::open(&db_config, &path.to_str().expect("DB path could not be converted to string.")).map_err(ClientError::Database))); | ||||
| 		let chain = Arc::new(BlockChain::new(config.blockchain.clone(), &gb, db.clone())); | ||||
| 		let chain = Arc::new(BlockChain::new(config.blockchain.clone(), &gb, db.clone(), spec.engine.clone())); | ||||
| 		let tracedb = RwLock::new(TraceDB::new(config.tracing.clone(), db.clone(), chain.clone())); | ||||
| 
 | ||||
| 		let trie_spec = match config.fat_db { | ||||
| @ -854,7 +854,7 @@ impl snapshot::DatabaseRestore for Client { | ||||
| 
 | ||||
| 		let cache_size = state_db.cache_size(); | ||||
| 		*state_db = StateDB::new(journaldb::new(db.clone(), self.pruning, ::db::COL_STATE), cache_size); | ||||
| 		*chain = Arc::new(BlockChain::new(self.config.blockchain.clone(), &[], db.clone())); | ||||
| 		*chain = Arc::new(BlockChain::new(self.config.blockchain.clone(), &[], db.clone(), self.engine.clone())); | ||||
| 		*tracedb = TraceDB::new(self.config.tracing.clone(), db.clone(), chain.clone()); | ||||
| 		Ok(()) | ||||
| 	} | ||||
| @ -1339,7 +1339,7 @@ impl BlockChainClient for Client { | ||||
| 		self.miner.pending_transactions(self.chain.read().best_block_number()) | ||||
| 	} | ||||
| 
 | ||||
| 	fn signing_network_id(&self) -> Option<u8> { | ||||
| 	fn signing_network_id(&self) -> Option<u64> { | ||||
| 		self.engine.signing_network_id(&self.latest_env_info()) | ||||
| 	} | ||||
| 
 | ||||
| @ -1353,6 +1353,13 @@ impl BlockChainClient for Client { | ||||
| 		self.uncle(id) | ||||
| 			.map(|header| self.engine.extra_info(&decode(&header))) | ||||
| 	} | ||||
| 
 | ||||
| 	fn pruning_info(&self) -> PruningInfo { | ||||
| 		PruningInfo { | ||||
| 			earliest_chain: self.chain.read().first_block_number().unwrap_or(1), | ||||
| 			earliest_state: self.state_db.lock().journal_db().earliest_era().unwrap_or(0), | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl MiningBlockChainClient for Client { | ||||
| @ -1437,32 +1444,60 @@ impl MayPanic for Client { | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl 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) | ||||
| 	} | ||||
| 
 | ||||
| #[test] | ||||
| fn should_not_cache_details_before_commit() { | ||||
| 	use tests::helpers::*; | ||||
| 	use std::thread; | ||||
| 	use std::time::Duration; | ||||
| 	use std::sync::atomic::{AtomicBool, Ordering}; | ||||
| 	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) | ||||
| 	} | ||||
| 
 | ||||
| 	let client = generate_dummy_client(0); | ||||
| 	let genesis = client.chain_info().best_block_hash; | ||||
| 	let (new_hash, new_block) = get_good_dummy_block_hash(); | ||||
| 
 | ||||
| 	let go = { | ||||
| 		// Separate thread uncommited transaction
 | ||||
| 		let go = Arc::new(AtomicBool::new(false)); | ||||
| 		let go_thread = go.clone(); | ||||
| 		let another_client = client.reference().clone(); | ||||
| 		thread::spawn(move || { | ||||
| 			let mut batch = DBTransaction::new(&*another_client.chain.read().db().clone()); | ||||
| 			another_client.chain.read().insert_block(&mut batch, &new_block, Vec::new()); | ||||
| 			go_thread.store(true, Ordering::SeqCst); | ||||
| 		}); | ||||
| 		go | ||||
| 	}; | ||||
| 
 | ||||
| 	while !go.load(Ordering::SeqCst) { thread::park_timeout(Duration::from_millis(5)); } | ||||
| 
 | ||||
| 	assert!(client.tree_route(&genesis, &new_hash).is_none()); | ||||
| 	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) | ||||
| 			.unwrap_or_else(Vec::new) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn should_not_cache_details_before_commit() { | ||||
| 		use client::BlockChainClient; | ||||
| 		use tests::helpers::*; | ||||
| 
 | ||||
| 		use std::thread; | ||||
| 		use std::time::Duration; | ||||
| 		use std::sync::Arc; | ||||
| 		use std::sync::atomic::{AtomicBool, Ordering}; | ||||
| 		use util::kvdb::DBTransaction; | ||||
| 
 | ||||
| 		let client = generate_dummy_client(0); | ||||
| 		let genesis = client.chain_info().best_block_hash; | ||||
| 		let (new_hash, new_block) = get_good_dummy_block_hash(); | ||||
| 
 | ||||
| 		let go = { | ||||
| 			// Separate thread uncommited transaction
 | ||||
| 			let go = Arc::new(AtomicBool::new(false)); | ||||
| 			let go_thread = go.clone(); | ||||
| 			let another_client = client.reference().clone(); | ||||
| 			thread::spawn(move || { | ||||
| 				let mut batch = DBTransaction::new(&*another_client.chain.read().db().clone()); | ||||
| 				another_client.chain.read().insert_block(&mut batch, &new_block, Vec::new()); | ||||
| 				go_thread.store(true, Ordering::SeqCst); | ||||
| 			}); | ||||
| 			go | ||||
| 		}; | ||||
| 
 | ||||
| 		while !go.load(Ordering::SeqCst) { thread::park_timeout(Duration::from_millis(5)); } | ||||
| 
 | ||||
| 		assert!(client.tree_route(&genesis, &new_hash).is_none()); | ||||
| 	} | ||||
| } | ||||
| @ -29,18 +29,21 @@ mod fetch; | ||||
| pub use self::client::*; | ||||
| pub use self::config::{Mode, ClientConfig, UpdatePolicy, UpdateFilter, DatabaseCompactionProfile, BlockChainConfig, VMType}; | ||||
| pub use self::error::Error; | ||||
| pub use types::ids::*; | ||||
| pub use self::test_client::{TestBlockChainClient, EachBlockWith}; | ||||
| pub use self::chain_notify::ChainNotify; | ||||
| pub use self::traits::{BlockChainClient, MiningBlockChainClient, ProvingBlockChainClient}; | ||||
| 
 | ||||
| pub use types::ids::*; | ||||
| pub use types::trace_filter::Filter as TraceFilter; | ||||
| pub use types::pruning_info::PruningInfo; | ||||
| pub use types::call_analytics::CallAnalytics; | ||||
| 
 | ||||
| pub use executive::{Executed, Executive, TransactOptions}; | ||||
| pub use env_info::{LastHashes, EnvInfo}; | ||||
| pub use self::chain_notify::ChainNotify; | ||||
| 
 | ||||
| pub use types::call_analytics::CallAnalytics; | ||||
| pub use block_import_error::BlockImportError; | ||||
| pub use transaction_import::TransactionImportResult; | ||||
| pub use transaction_import::TransactionImportError; | ||||
| pub use self::traits::{BlockChainClient, MiningBlockChainClient}; | ||||
| pub use verification::VerifierType; | ||||
| 
 | ||||
| /// IPC interfaces
 | ||||
|  | ||||
| @ -38,6 +38,7 @@ use evm::{Factory as EvmFactory, VMType, Schedule}; | ||||
| use miner::{Miner, MinerService, TransactionImportResult}; | ||||
| use spec::Spec; | ||||
| use types::mode::Mode; | ||||
| use types::pruning_info::PruningInfo; | ||||
| use views::BlockView; | ||||
| 
 | ||||
| use verification::queue::QueueInfo; | ||||
| @ -662,9 +663,16 @@ impl BlockChainClient for TestBlockChainClient { | ||||
| 		self.miner.pending_transactions(self.chain_info().best_block_number) | ||||
| 	} | ||||
| 
 | ||||
| 	fn signing_network_id(&self) -> Option<u8> { None } | ||||
| 	fn signing_network_id(&self) -> Option<u64> { None } | ||||
| 
 | ||||
| 	fn mode(&self) -> Mode { Mode::Active } | ||||
| 
 | ||||
| 	fn set_mode(&self, _: Mode) { unimplemented!(); } | ||||
| 
 | ||||
| 	fn pruning_info(&self) -> PruningInfo { | ||||
| 		PruningInfo { | ||||
| 			earliest_chain: 1, | ||||
| 			earliest_state: 1, | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -39,6 +39,7 @@ use types::call_analytics::CallAnalytics; | ||||
| use types::blockchain_info::BlockChainInfo; | ||||
| use types::block_status::BlockStatus; | ||||
| use types::mode::Mode; | ||||
| use types::pruning_info::PruningInfo; | ||||
| 
 | ||||
| #[ipc(client_ident="RemoteClient")] | ||||
| /// Blockchain database client. Owns and manages a blockchain and a block queue.
 | ||||
| @ -237,7 +238,7 @@ pub trait BlockChainClient : Sync + Send { | ||||
| 	} | ||||
| 
 | ||||
| 	/// Get the preferred network ID to sign on
 | ||||
| 	fn signing_network_id(&self) -> Option<u8>; | ||||
| 	fn signing_network_id(&self) -> Option<u64>; | ||||
| 
 | ||||
| 	/// Get the mode.
 | ||||
| 	fn mode(&self) -> Mode; | ||||
| @ -248,12 +249,17 @@ pub trait BlockChainClient : Sync + Send { | ||||
| 	/// 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`.
 | ||||
| 	/// 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; | ||||
| } | ||||
| 
 | ||||
| impl IpcConfig for BlockChainClient { } | ||||
| 
 | ||||
| /// Extended client interface used for mining
 | ||||
| pub trait MiningBlockChainClient : BlockChainClient { | ||||
| pub trait MiningBlockChainClient: BlockChainClient { | ||||
| 	/// Returns OpenBlock prepared for closing.
 | ||||
| 	fn prepare_open_block(&self, | ||||
| 		author: Address, | ||||
| @ -271,4 +277,23 @@ pub trait MiningBlockChainClient : BlockChainClient { | ||||
| 	fn latest_schedule(&self) -> Schedule; | ||||
| } | ||||
| 
 | ||||
| impl IpcConfig for BlockChainClient { } | ||||
| /// Extended client interface for providing proofs of the state.
 | ||||
| pub trait ProvingBlockChainClient: BlockChainClient { | ||||
| 	/// Prove account storage at a specific block id.
 | ||||
| 	///
 | ||||
| 	/// Both provided keys assume a secure trie.
 | ||||
| 	/// 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>; | ||||
| 
 | ||||
| 	/// 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>; | ||||
| 
 | ||||
| 	/// Get code by address hash.
 | ||||
| 	fn code_by_hash(&self, account_key: H256, id: BlockID) -> Bytes; | ||||
| } | ||||
| @ -21,7 +21,7 @@ use std::sync::Weak; | ||||
| use std::time::{UNIX_EPOCH, Duration}; | ||||
| use util::*; | ||||
| use ethkey::{verify_address, Signature}; | ||||
| use rlp::{UntrustedRlp, View, encode}; | ||||
| use rlp::{Rlp, UntrustedRlp, View, encode}; | ||||
| use account_provider::AccountProvider; | ||||
| use block::*; | ||||
| use spec::CommonParams; | ||||
| @ -35,6 +35,8 @@ use service::ClientIoMessage; | ||||
| use transaction::SignedTransaction; | ||||
| use env_info::EnvInfo; | ||||
| use builtin::Builtin; | ||||
| use blockchain::extras::BlockDetails; | ||||
| use views::HeaderView; | ||||
| 
 | ||||
| /// `AuthorityRound` params.
 | ||||
| #[derive(Debug, PartialEq)] | ||||
| @ -272,7 +274,6 @@ impl Engine for AuthorityRound { | ||||
| 	} | ||||
| 
 | ||||
| 	fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> Result<(), Error> { | ||||
| 		// Don't calculate difficulty for genesis blocks.
 | ||||
| 		if header.number() == 0 { | ||||
| 			return Err(From::from(BlockError::RidiculousNumber(OutOfBounds { min: Some(1), max: None, found: header.number() }))); | ||||
| 		} | ||||
| @ -284,10 +285,6 @@ impl Engine for AuthorityRound { | ||||
| 			try!(Err(BlockError::DoubleVote(header.author().clone()))); | ||||
| 		} | ||||
| 
 | ||||
| 		// Check difficulty is correct given the two timestamps.
 | ||||
| 		if header.difficulty() != parent.difficulty() { | ||||
| 			return Err(From::from(BlockError::InvalidDifficulty(Mismatch { expected: *parent.difficulty(), found: *header.difficulty() }))) | ||||
| 		} | ||||
| 		let gas_limit_divisor = self.our_params.gas_limit_bound_divisor; | ||||
| 		let min_gas = parent.gas_limit().clone() - parent.gas_limit().clone() / gas_limit_divisor; | ||||
| 		let max_gas = parent.gas_limit().clone() + parent.gas_limit().clone() / gas_limit_divisor; | ||||
| @ -310,6 +307,19 @@ impl Engine for AuthorityRound { | ||||
| 		let mut guard = self.message_channel.lock(); | ||||
| 		*guard = Some(message_channel); | ||||
| 	} | ||||
| 
 | ||||
| 	fn is_new_best_block(&self, _best_total_difficulty: U256, best_header: HeaderView, _parent_details: &BlockDetails, new_header: &HeaderView) -> bool { | ||||
| 		let new_number = new_header.number(); | ||||
| 		let best_number = best_header.number(); | ||||
| 		if new_number != best_number { | ||||
| 			new_number > best_number | ||||
| 		} else { | ||||
| 			// Take the oldest step at given height.
 | ||||
| 			let new_step: usize = Rlp::new(&new_header.seal()[0]).as_val(); | ||||
| 			let best_step: usize = Rlp::new(&best_header.seal()[0]).as_val(); | ||||
| 			new_step < best_step | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| #[cfg(test)] | ||||
|  | ||||
| @ -38,6 +38,9 @@ use io::IoChannel; | ||||
| use service::ClientIoMessage; | ||||
| use header::Header; | ||||
| use transaction::SignedTransaction; | ||||
| use ethereum::ethash; | ||||
| use blockchain::extras::BlockDetails; | ||||
| use views::HeaderView; | ||||
| 
 | ||||
| /// A consensus mechanism for the chain. Generally either proof-of-work or proof-of-stake-based.
 | ||||
| /// Provides hooks into each of the major parts of block import.
 | ||||
| @ -113,7 +116,7 @@ pub trait Engine : Sync + Send { | ||||
| 	fn verify_transaction(&self, _t: &SignedTransaction, _header: &Header) -> Result<(), Error> { Ok(()) } | ||||
| 
 | ||||
| 	/// The network ID that transactions should be signed with.
 | ||||
| 	fn signing_network_id(&self, _env_info: &EnvInfo) -> Option<u8> { None } | ||||
| 	fn signing_network_id(&self, _env_info: &EnvInfo) -> Option<u64> { None } | ||||
| 
 | ||||
| 	/// Verify the seal of a block. This is an auxilliary method that actually just calls other `verify_` methods
 | ||||
| 	/// to get the job done. By default it must pass `verify_basic` and `verify_block_unordered`. If more or fewer
 | ||||
| @ -146,5 +149,9 @@ pub trait Engine : Sync + Send { | ||||
| 
 | ||||
| 	/// Add a channel for communication with Client which can be used for sealing.
 | ||||
| 	fn register_message_channel(&self, _message_channel: IoChannel<ClientIoMessage>) {} | ||||
| 	// TODO: sealing stuff - though might want to leave this for later.
 | ||||
| 
 | ||||
| 	/// Check if new block should be chosen as the one  in chain.
 | ||||
| 	fn is_new_best_block(&self, best_total_difficulty: U256, _best_header: HeaderView, parent_details: &BlockDetails, new_header: &HeaderView) -> bool { | ||||
| 		ethash::is_new_best_block(best_total_difficulty, parent_details, new_header) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -38,6 +38,12 @@ impl NullEngine { | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl Default for NullEngine { | ||||
| 	fn default() -> Self { | ||||
| 		Self::new(Default::default(), Default::default()) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl Engine for NullEngine { | ||||
| 	fn name(&self) -> &str { | ||||
| 		"NullEngine" | ||||
|  | ||||
| @ -21,6 +21,7 @@ use builtin::Builtin; | ||||
| use env_info::EnvInfo; | ||||
| use error::{BlockError, TransactionError, Error}; | ||||
| use header::Header; | ||||
| use views::HeaderView; | ||||
| use state::CleanupMode; | ||||
| use spec::CommonParams; | ||||
| use transaction::SignedTransaction; | ||||
| @ -28,6 +29,7 @@ use engines::Engine; | ||||
| use evm::Schedule; | ||||
| use ethjson; | ||||
| use rlp::{self, UntrustedRlp, View}; | ||||
| use blockchain::extras::BlockDetails; | ||||
| 
 | ||||
| /// Ethash params.
 | ||||
| #[derive(Debug, PartialEq)] | ||||
| @ -163,9 +165,9 @@ impl Engine for Ethash { | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	fn signing_network_id(&self, env_info: &EnvInfo) -> Option<u8> { | ||||
| 		if env_info.number >= self.ethash_params.eip155_transition && self.params().network_id < 127 { | ||||
| 			Some(self.params().network_id as u8) | ||||
| 	fn signing_network_id(&self, env_info: &EnvInfo) -> Option<u64> { | ||||
| 		if env_info.number >= self.ethash_params.eip155_transition { | ||||
| 			Some(self.params().network_id) | ||||
| 		} else { | ||||
| 			None | ||||
| 		} | ||||
| @ -314,7 +316,7 @@ impl Engine for Ethash { | ||||
| 		} | ||||
| 
 | ||||
| 		if let Some(n) = t.network_id() { | ||||
| 			if header.number() < self.ethash_params.eip155_transition || n as usize != self.params().network_id { | ||||
| 			if header.number() < self.ethash_params.eip155_transition || n != self.params().network_id { | ||||
| 				return Err(TransactionError::InvalidNetworkId.into()) | ||||
| 			} | ||||
| 		} | ||||
| @ -325,6 +327,15 @@ impl Engine for Ethash { | ||||
| 	fn verify_transaction(&self, t: &SignedTransaction, _header: &Header) -> Result<(), Error> { | ||||
| 		t.sender().map(|_|()) // Perform EC recovery and cache sender
 | ||||
| 	} | ||||
| 
 | ||||
| 	fn is_new_best_block(&self, best_total_difficulty: U256, _best_header: HeaderView, parent_details: &BlockDetails, new_header: &HeaderView) -> bool { | ||||
| 		is_new_best_block(best_total_difficulty, parent_details, new_header) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| /// Check if a new block should replace the best blockchain block.
 | ||||
| pub fn is_new_best_block(best_total_difficulty: U256, parent_details: &BlockDetails, new_header: &HeaderView) -> bool { | ||||
| 	parent_details.total_difficulty + new_header.difficulty() > best_total_difficulty | ||||
| } | ||||
| 
 | ||||
| #[cfg_attr(feature="dev", allow(wrong_self_convention))] | ||||
|  | ||||
| @ -81,6 +81,7 @@ struct Restoration { | ||||
| struct RestorationParams<'a> { | ||||
| 	manifest: ManifestData, // manifest to base restoration on.
 | ||||
| 	pruning: Algorithm, // pruning algorithm for the database.
 | ||||
| 	engine: Arc<Engine>, // consensus engine of the chain.
 | ||||
| 	db_path: PathBuf, // database path
 | ||||
| 	db_config: &'a DatabaseConfig, // configuration for the database.
 | ||||
| 	writer: Option<LooseWriter>, // writer for recovered snapshot.
 | ||||
| @ -99,7 +100,7 @@ impl Restoration { | ||||
| 		let raw_db = Arc::new(try!(Database::open(params.db_config, &*params.db_path.to_string_lossy()) | ||||
| 			.map_err(UtilError::SimpleString))); | ||||
| 
 | ||||
| 		let chain = BlockChain::new(Default::default(), params.genesis, raw_db.clone()); | ||||
| 		let chain = BlockChain::new(Default::default(), params.genesis, raw_db.clone(), params.engine); | ||||
| 		let blocks = try!(BlockRebuilder::new(chain, raw_db.clone(), &manifest)); | ||||
| 
 | ||||
| 		let root = manifest.state_root.clone(); | ||||
| @ -420,6 +421,7 @@ impl Service { | ||||
| 		let params = RestorationParams { | ||||
| 			manifest: manifest, | ||||
| 			pruning: self.pruning, | ||||
| 			engine: self.engine.clone(), | ||||
| 			db_path: self.restoration_db(), | ||||
| 			db_config: &self.db_config, | ||||
| 			writer: writer, | ||||
|  | ||||
| @ -37,13 +37,14 @@ fn chunk_and_restore(amount: u64) { | ||||
| 	let genesis = canon_chain.generate(&mut finalizer).unwrap(); | ||||
| 	let db_cfg = DatabaseConfig::with_columns(::db::NUM_COLUMNS); | ||||
| 
 | ||||
| 	let engine = Arc::new(::engines::NullEngine::default()); | ||||
| 	let orig_path = RandomTempPath::create_dir(); | ||||
| 	let new_path = RandomTempPath::create_dir(); | ||||
| 	let mut snapshot_path = new_path.as_path().to_owned(); | ||||
| 	snapshot_path.push("SNAP"); | ||||
| 
 | ||||
| 	let old_db = Arc::new(Database::open(&db_cfg, orig_path.as_str()).unwrap()); | ||||
| 	let bc = BlockChain::new(Default::default(), &genesis, old_db.clone()); | ||||
| 	let bc = BlockChain::new(Default::default(), &genesis, old_db.clone(), engine.clone()); | ||||
| 
 | ||||
| 	// build the blockchain.
 | ||||
| 	let mut batch = old_db.transaction(); | ||||
| @ -73,21 +74,20 @@ fn chunk_and_restore(amount: u64) { | ||||
| 
 | ||||
| 	// restore it.
 | ||||
| 	let new_db = Arc::new(Database::open(&db_cfg, new_path.as_str()).unwrap()); | ||||
| 	let new_chain = BlockChain::new(Default::default(), &genesis, new_db.clone()); | ||||
| 	let new_chain = BlockChain::new(Default::default(), &genesis, new_db.clone(), engine.clone()); | ||||
| 	let mut rebuilder = BlockRebuilder::new(new_chain, new_db.clone(), &manifest).unwrap(); | ||||
| 	let reader = PackedReader::new(&snapshot_path).unwrap().unwrap(); | ||||
| 	let engine = ::engines::NullEngine::new(Default::default(), Default::default()); | ||||
| 	let flag = AtomicBool::new(true); | ||||
| 	for chunk_hash in &reader.manifest().block_hashes { | ||||
| 		let compressed = reader.chunk(*chunk_hash).unwrap(); | ||||
| 		let chunk = snappy::decompress(&compressed).unwrap(); | ||||
| 		rebuilder.feed(&chunk, &engine, &flag).unwrap(); | ||||
| 		rebuilder.feed(&chunk, engine.as_ref(), &flag).unwrap(); | ||||
| 	} | ||||
| 
 | ||||
| 	rebuilder.finalize(HashMap::new()).unwrap(); | ||||
| 
 | ||||
| 	// and test it.
 | ||||
| 	let new_chain = BlockChain::new(Default::default(), &genesis, new_db); | ||||
| 	let new_chain = BlockChain::new(Default::default(), &genesis, new_db, engine); | ||||
| 	assert_eq!(new_chain.best_block_hash(), best_hash); | ||||
| } | ||||
| 
 | ||||
| @ -121,8 +121,8 @@ fn checks_flag() { | ||||
| 
 | ||||
| 	let db_cfg = DatabaseConfig::with_columns(::db::NUM_COLUMNS); | ||||
| 	let db = Arc::new(Database::open(&db_cfg, path.as_str()).unwrap()); | ||||
| 	let chain = BlockChain::new(Default::default(), &genesis, db.clone()); | ||||
| 	let engine = ::engines::NullEngine::new(Default::default(), Default::default()); | ||||
| 	let engine = Arc::new(::engines::NullEngine::default()); | ||||
| 	let chain = BlockChain::new(Default::default(), &genesis, db.clone(), engine.clone()); | ||||
| 
 | ||||
| 	let manifest = ::snapshot::ManifestData { | ||||
| 		state_hashes: Vec::new(), | ||||
| @ -134,8 +134,8 @@ fn checks_flag() { | ||||
| 
 | ||||
| 	let mut rebuilder = BlockRebuilder::new(chain, db.clone(), &manifest).unwrap(); | ||||
| 
 | ||||
| 	match rebuilder.feed(&chunk, &engine, &AtomicBool::new(false)) { | ||||
| 	match rebuilder.feed(&chunk, engine.as_ref(), &AtomicBool::new(false)) { | ||||
| 		Err(Error::Snapshot(SnapshotError::RestorationAborted)) => {} | ||||
| 		_ => panic!("Wrong result on abort flag set") | ||||
| 	} | ||||
| } | ||||
| } | ||||
|  | ||||
| @ -30,15 +30,14 @@ use ethjson; | ||||
| use rlp::{Rlp, RlpStream, View, Stream}; | ||||
| 
 | ||||
| /// Parameters common to all engines.
 | ||||
| #[derive(Debug, PartialEq, Clone)] | ||||
| #[cfg_attr(test, derive(Default))] | ||||
| #[derive(Debug, PartialEq, Clone, Default)] | ||||
| pub struct CommonParams { | ||||
| 	/// Account start nonce.
 | ||||
| 	pub account_start_nonce: U256, | ||||
| 	/// Maximum size of extra data.
 | ||||
| 	pub maximum_extra_data_size: usize, | ||||
| 	/// Network id.
 | ||||
| 	pub network_id: usize, | ||||
| 	pub network_id: u64, | ||||
| 	/// Main subprotocol name.
 | ||||
| 	pub subprotocol_name: String, | ||||
| 	/// Minimum gas limit.
 | ||||
| @ -164,7 +163,7 @@ impl Spec { | ||||
| 	pub fn nodes(&self) -> &[String] { &self.nodes } | ||||
| 
 | ||||
| 	/// Get the configured Network ID.
 | ||||
| 	pub fn network_id(&self) -> usize { self.params.network_id } | ||||
| 	pub fn network_id(&self) -> u64 { self.params.network_id } | ||||
| 
 | ||||
| 	/// Get the configured subprotocol name.
 | ||||
| 	pub fn subprotocol_name(&self) -> String { self.params.subprotocol_name.clone() } | ||||
|  | ||||
| @ -436,6 +436,27 @@ impl Account { | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // light client storage proof.
 | ||||
| impl Account { | ||||
| 	/// Prove a storage key's existence or nonexistence in the account's storage
 | ||||
| 	/// trie.
 | ||||
| 	/// `storage_key` is the hash of the desired storage key, meaning
 | ||||
| 	/// this will only work correctly under a secure trie.
 | ||||
| 	/// Returns a merkle proof of the storage trie node with all nodes before `from_level`
 | ||||
| 	/// omitted.
 | ||||
| 	pub fn prove_storage(&self, db: &HashDB, storage_key: H256, from_level: u32) -> Result<Vec<Bytes>, Box<TrieError>> { | ||||
| 		use util::trie::{Trie, TrieDB}; | ||||
| 		use util::trie::recorder::{Recorder, BasicRecorder as TrieRecorder}; | ||||
| 
 | ||||
| 		let mut recorder = TrieRecorder::with_depth(from_level); | ||||
| 
 | ||||
| 		let trie = try!(TrieDB::new(db, &self.storage_root)); | ||||
| 		let _ = try!(trie.get_recorded(&storage_key, &mut recorder)); | ||||
| 
 | ||||
| 		Ok(recorder.drain().into_iter().map(|r| r.data).collect()) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl fmt::Debug for Account { | ||||
| 	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||
| 		write!(f, "{:?}", PodAccount::from_account(self)) | ||||
|  | ||||
| @ -16,7 +16,7 @@ | ||||
| 
 | ||||
| use std::cell::{RefCell, RefMut}; | ||||
| use std::collections::hash_map::Entry; | ||||
| use util::*; | ||||
| 
 | ||||
| use receipt::Receipt; | ||||
| use engines::Engine; | ||||
| use env_info::EnvInfo; | ||||
| @ -30,6 +30,9 @@ use types::state_diff::StateDiff; | ||||
| use transaction::SignedTransaction; | ||||
| use state_db::StateDB; | ||||
| 
 | ||||
| use util::*; | ||||
| use util::trie::recorder::{Recorder, BasicRecorder as TrieRecorder}; | ||||
| 
 | ||||
| mod account; | ||||
| mod substate; | ||||
| 
 | ||||
| @ -758,6 +761,53 @@ impl State { | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // LES state proof implementations.
 | ||||
| impl State { | ||||
| 	/// Prove an account's existence or nonexistence in the state trie.
 | ||||
| 	/// Returns a merkle proof of the account's trie node with all nodes before `from_level`
 | ||||
| 	/// omitted or an encountered trie error.
 | ||||
| 	/// Requires a secure trie to be used for accurate results.
 | ||||
| 	/// `account_key` == sha3(address)
 | ||||
| 	pub fn prove_account(&self, account_key: H256, from_level: u32) -> Result<Vec<Bytes>, Box<TrieError>> { | ||||
| 		let mut recorder = TrieRecorder::with_depth(from_level); | ||||
| 		let trie = try!(TrieDB::new(self.db.as_hashdb(), &self.root)); | ||||
| 		let _  = try!(trie.get_recorded(&account_key, &mut recorder)); | ||||
| 
 | ||||
| 		Ok(recorder.drain().into_iter().map(|r| r.data).collect()) | ||||
| 	} | ||||
| 
 | ||||
| 	/// Prove an account's storage key's existence or nonexistence in the state.
 | ||||
| 	/// Returns a merkle proof of the account's storage trie with all nodes before
 | ||||
| 	/// `from_level` omitted. Requires a secure trie to be used for correctness.
 | ||||
| 	/// `account_key` == sha3(address)
 | ||||
| 	/// `storage_key` == sha3(key)
 | ||||
| 	pub fn prove_storage(&self, account_key: H256, storage_key: H256, from_level: u32) -> Result<Vec<Bytes>, Box<TrieError>> { | ||||
| 		// TODO: probably could look into cache somehow but it's keyed by
 | ||||
| 		// address, not sha3(address).
 | ||||
| 		let trie = try!(TrieDB::new(self.db.as_hashdb(), &self.root)); | ||||
| 		let acc = match try!(trie.get(&account_key)) { | ||||
| 			Some(rlp) => Account::from_rlp(&rlp), | ||||
| 			None => return Ok(Vec::new()), | ||||
| 		}; | ||||
| 
 | ||||
| 		let account_db = self.factories.accountdb.readonly(self.db.as_hashdb(), account_key); | ||||
| 		acc.prove_storage(account_db.as_hashdb(), storage_key, from_level) | ||||
| 	} | ||||
| 
 | ||||
| 	/// Get code by address hash.
 | ||||
| 	/// Only works when backed by a secure trie.
 | ||||
| 	pub fn code_by_address_hash(&self, account_key: H256) -> Result<Option<Bytes>, Box<TrieError>> { | ||||
| 		let trie = try!(TrieDB::new(self.db.as_hashdb(), &self.root)); | ||||
| 		let mut acc = match try!(trie.get(&account_key)) { | ||||
| 			Some(rlp) => Account::from_rlp(&rlp), | ||||
| 			None => return Ok(None), | ||||
| 		}; | ||||
| 
 | ||||
| 		let account_db = self.factories.accountdb.readonly(self.db.as_hashdb(), account_key); | ||||
| 		Ok(acc.cache_code(account_db.as_hashdb()).map(|c| (&*c).clone())) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl fmt::Debug for State { | ||||
| 	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||
| 		write!(f, "{:?}", self.cache.borrow()) | ||||
|  | ||||
| @ -286,7 +286,7 @@ fn new_db(path: &str) -> Arc<Database> { | ||||
| pub fn generate_dummy_blockchain(block_number: u32) -> GuardedTempResult<BlockChain> { | ||||
| 	let temp = RandomTempPath::new(); | ||||
| 	let db = new_db(temp.as_str()); | ||||
| 	let bc = BlockChain::new(BlockChainConfig::default(), &create_unverifiable_block(0, H256::zero()), db.clone()); | ||||
| 	let bc = BlockChain::new(BlockChainConfig::default(), &create_unverifiable_block(0, H256::zero()), db.clone(), Spec::new_null().engine); | ||||
| 
 | ||||
| 	let mut batch = db.transaction(); | ||||
| 	for block_order in 1..block_number { | ||||
| @ -304,7 +304,7 @@ pub fn generate_dummy_blockchain(block_number: u32) -> GuardedTempResult<BlockCh | ||||
| pub fn generate_dummy_blockchain_with_extra(block_number: u32) -> GuardedTempResult<BlockChain> { | ||||
| 	let temp = RandomTempPath::new(); | ||||
| 	let db = new_db(temp.as_str()); | ||||
| 	let bc = BlockChain::new(BlockChainConfig::default(), &create_unverifiable_block(0, H256::zero()), db.clone()); | ||||
| 	let bc = BlockChain::new(BlockChainConfig::default(), &create_unverifiable_block(0, H256::zero()), db.clone(), Spec::new_null().engine); | ||||
| 
 | ||||
| 
 | ||||
| 	let mut batch = db.transaction(); | ||||
| @ -323,7 +323,7 @@ pub fn generate_dummy_blockchain_with_extra(block_number: u32) -> GuardedTempRes | ||||
| pub fn generate_dummy_empty_blockchain() -> GuardedTempResult<BlockChain> { | ||||
| 	let temp = RandomTempPath::new(); | ||||
| 	let db = new_db(temp.as_str()); | ||||
| 	let bc = BlockChain::new(BlockChainConfig::default(), &create_unverifiable_block(0, H256::zero()), db.clone()); | ||||
| 	let bc = BlockChain::new(BlockChainConfig::default(), &create_unverifiable_block(0, H256::zero()), db.clone(), Spec::new_null().engine); | ||||
| 
 | ||||
| 	GuardedTempResult::<BlockChain> { | ||||
| 		_temp: temp, | ||||
|  | ||||
| @ -34,3 +34,4 @@ pub mod block_import_error; | ||||
| pub mod restoration_status; | ||||
| pub mod snapshot_manifest; | ||||
| pub mod mode; | ||||
| pub mod pruning_info; | ||||
							
								
								
									
										30
									
								
								ethcore/src/types/pruning_info.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								ethcore/src/types/pruning_info.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | ||||
| // 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/>.
 | ||||
| 
 | ||||
| //! Information about portions of the state and chain which the client may serve.
 | ||||
| //!
 | ||||
| //! Currently assumes that a client will store everything past a certain point
 | ||||
| //! or everything. Will be extended in the future to support a definition
 | ||||
| //! of which portions of the ancient chain and current state trie are stored as well.
 | ||||
| 
 | ||||
| /// Client pruning info. See module-level docs for more details.
 | ||||
| #[derive(Debug, Clone, Binary)] | ||||
| pub struct PruningInfo { | ||||
| 	/// The first block which everything can be served after.
 | ||||
| 	pub earliest_chain: u64, | ||||
| 	/// The first block where state requests may be served.
 | ||||
| 	pub earliest_state: u64, | ||||
| } | ||||
| @ -72,7 +72,7 @@ pub struct Transaction { | ||||
| 
 | ||||
| impl Transaction { | ||||
| 	/// Append object with a without signature into RLP stream
 | ||||
| 	pub fn rlp_append_unsigned_transaction(&self, s: &mut RlpStream, network_id: Option<u8>) { | ||||
| 	pub fn rlp_append_unsigned_transaction(&self, s: &mut RlpStream, network_id: Option<u64>) { | ||||
| 		s.begin_list(if network_id.is_none() { 6 } else { 9 }); | ||||
| 		s.append(&self.nonce); | ||||
| 		s.append(&self.gas_price); | ||||
| @ -140,26 +140,26 @@ impl From<ethjson::transaction::Transaction> for SignedTransaction { | ||||
| 
 | ||||
| impl Transaction { | ||||
| 	/// The message hash of the transaction.
 | ||||
| 	pub fn hash(&self, network_id: Option<u8>) -> H256 { | ||||
| 	pub fn hash(&self, network_id: Option<u64>) -> H256 { | ||||
| 		let mut stream = RlpStream::new(); | ||||
| 		self.rlp_append_unsigned_transaction(&mut stream, network_id); | ||||
| 		stream.out().sha3() | ||||
| 	} | ||||
| 
 | ||||
| 	/// Signs the transaction as coming from `sender`.
 | ||||
| 	pub fn sign(self, secret: &Secret, network_id: Option<u8>) -> SignedTransaction { | ||||
| 	pub fn sign(self, secret: &Secret, network_id: Option<u64>) -> SignedTransaction { | ||||
| 		let sig = ::ethkey::sign(secret, &self.hash(network_id)) | ||||
| 			.expect("data is valid and context has signing capabilities; qed"); | ||||
| 		self.with_signature(sig, network_id) | ||||
| 	} | ||||
| 
 | ||||
| 	/// Signs the transaction with signature.
 | ||||
| 	pub fn with_signature(self, sig: Signature, network_id: Option<u8>) -> SignedTransaction { | ||||
| 	pub fn with_signature(self, sig: Signature, network_id: Option<u64>) -> SignedTransaction { | ||||
| 		SignedTransaction { | ||||
| 			unsigned: self, | ||||
| 			r: sig.r().into(), | ||||
| 			s: sig.s().into(), | ||||
| 			v: sig.v() + if let Some(n) = network_id { 35 + n * 2 } else { 27 }, | ||||
| 			v: sig.v() as u64 + if let Some(n) = network_id { 35 + n * 2 } else { 27 }, | ||||
| 			hash: Cell::new(None), | ||||
| 			sender: Cell::new(None), | ||||
| 		} | ||||
| @ -211,7 +211,7 @@ pub struct SignedTransaction { | ||||
| 	unsigned: Transaction, | ||||
| 	/// The V field of the signature; the LS bit described which half of the curve our point falls
 | ||||
| 	/// in. The MS bits describe which network this transaction is for. If 27/28, its for all networks.
 | ||||
| 	v: u8, | ||||
| 	v: u64, | ||||
| 	/// The R field of the signature; helps describe the point on the curve.
 | ||||
| 	r: U256, | ||||
| 	/// The S field of the signature; helps describe the point on the curve.
 | ||||
| @ -302,10 +302,13 @@ impl SignedTransaction { | ||||
| 	} | ||||
| 
 | ||||
| 	/// 0 if `v` would have been 27 under "Electrum" notation, 1 if 28 or 4 if invalid.
 | ||||
| 	pub fn standard_v(&self) -> u8 { match self.v { v if v == 27 || v == 28 || v > 36 => (v - 1) % 2, _ => 4 } } | ||||
| 	pub fn standard_v(&self) -> u8 { match self.v { v if v == 27 || v == 28 || v > 36 => ((v - 1) % 2) as u8, _ => 4 } } | ||||
| 
 | ||||
| 	/// The `v` value that appears in the RLP.
 | ||||
| 	pub fn original_v(&self) -> u64 { self.v } | ||||
| 
 | ||||
| 	/// The network ID, or `None` if this is a global transaction.
 | ||||
| 	pub fn network_id(&self) -> Option<u8> { | ||||
| 	pub fn network_id(&self) -> Option<u64> { | ||||
| 		match self.v { | ||||
| 			v if v > 36 => Some((v - 35) / 2), | ||||
| 			_ => None, | ||||
|  | ||||
| @ -17,7 +17,7 @@ | ||||
| //! A queue of blocks. Sits between network or other I/O and the `BlockChain`.
 | ||||
| //! Sorts them ready for blockchain insertion.
 | ||||
| 
 | ||||
| use std::thread::{JoinHandle, self}; | ||||
| use std::thread::{self, JoinHandle}; | ||||
| use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering as AtomicOrdering}; | ||||
| use std::sync::{Condvar as SCondvar, Mutex as SMutex}; | ||||
| use util::*; | ||||
| @ -53,6 +53,8 @@ pub struct Config { | ||||
| 	/// Maximum heap memory to use.
 | ||||
| 	/// When the limit is reached, is_full returns true.
 | ||||
| 	pub max_mem_use: usize, | ||||
| 	/// Settings for the number of verifiers and adaptation strategy.
 | ||||
| 	pub verifier_settings: VerifierSettings, | ||||
| } | ||||
| 
 | ||||
| impl Default for Config { | ||||
| @ -60,39 +62,35 @@ impl Default for Config { | ||||
| 		Config { | ||||
| 			max_queue_size: 30000, | ||||
| 			max_mem_use: 50 * 1024 * 1024, | ||||
| 			verifier_settings: VerifierSettings::default(), | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| struct VerifierHandle { | ||||
| 	deleting: Arc<AtomicBool>, | ||||
| 	sleep: Arc<AtomicBool>, | ||||
| 	thread: JoinHandle<()>, | ||||
| /// Verifier settings.
 | ||||
| #[derive(Debug, PartialEq, Clone)] | ||||
| pub struct VerifierSettings { | ||||
| 	/// Whether to scale amount of verifiers according to load.
 | ||||
| 	// Todo: replace w/ strategy enum?
 | ||||
| 	pub scale_verifiers: bool, | ||||
| 	/// Beginning amount of verifiers.
 | ||||
| 	pub num_verifiers: usize, | ||||
| } | ||||
| 
 | ||||
| impl VerifierHandle { | ||||
| 	// signal to the verifier thread that it should sleep.
 | ||||
| 	fn sleep(&self) { | ||||
| 		self.sleep.store(true, AtomicOrdering::SeqCst); | ||||
| impl Default for VerifierSettings { | ||||
| 	fn default() -> Self { | ||||
| 		VerifierSettings { | ||||
| 			scale_verifiers: false, | ||||
| 			num_verifiers: MAX_VERIFIERS, | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| 	// signal to the verifier thread that it should wake up.
 | ||||
| 	fn wake_up(&self) { | ||||
| 		self.sleep.store(false, AtomicOrdering::SeqCst); | ||||
| 		self.thread.thread().unpark(); | ||||
| 	} | ||||
| 
 | ||||
| 	// signal to the verifier thread that it should conclude its
 | ||||
| 	// operations.
 | ||||
| 	fn conclude(&self) { | ||||
| 		self.wake_up(); | ||||
| 		self.deleting.store(true, AtomicOrdering::Release); | ||||
| 	} | ||||
| 
 | ||||
| 	// join the verifier thread.
 | ||||
| 	fn join(self) { | ||||
| 		self.thread.join().expect("Verifier thread panicked"); | ||||
| 	} | ||||
| // pool states
 | ||||
| enum State { | ||||
| 	// all threads with id < inner value are to work.
 | ||||
| 	Work(usize), | ||||
| 	Exit, | ||||
| } | ||||
| 
 | ||||
| /// An item which is in the process of being verified.
 | ||||
| @ -131,7 +129,6 @@ pub struct VerificationQueue<K: Kind> { | ||||
| 	engine: Arc<Engine>, | ||||
| 	more_to_verify: Arc<SCondvar>, | ||||
| 	verification: Arc<Verification<K>>, | ||||
| 	verifiers: Mutex<(Vec<VerifierHandle>, usize)>, | ||||
| 	deleting: Arc<AtomicBool>, | ||||
| 	ready_signal: Arc<QueueSignal>, | ||||
| 	empty: Arc<SCondvar>, | ||||
| @ -139,6 +136,9 @@ pub struct VerificationQueue<K: Kind> { | ||||
| 	ticks_since_adjustment: AtomicUsize, | ||||
| 	max_queue_size: usize, | ||||
| 	max_mem_use: usize, | ||||
| 	scale_verifiers: bool, | ||||
| 	verifier_handles: Vec<JoinHandle<()>>,	
 | ||||
| 	state: Arc<(Mutex<State>, Condvar)>, | ||||
| } | ||||
| 
 | ||||
| struct QueueSignal { | ||||
| @ -221,43 +221,45 @@ impl<K: Kind> VerificationQueue<K> { | ||||
| 		}); | ||||
| 		let empty = Arc::new(SCondvar::new()); | ||||
| 		let panic_handler = PanicHandler::new_in_arc(); | ||||
| 		let scale_verifiers = config.verifier_settings.scale_verifiers; | ||||
| 
 | ||||
| 		let max_verifiers = min(::num_cpus::get(), MAX_VERIFIERS); | ||||
| 		let default_amount = max(::num_cpus::get(), 3) - 2; | ||||
| 		let mut verifiers = Vec::with_capacity(max_verifiers); | ||||
| 		let num_cpus = ::num_cpus::get(); | ||||
| 		let max_verifiers = min(num_cpus, MAX_VERIFIERS); | ||||
| 		let default_amount = max(1, min(max_verifiers, config.verifier_settings.num_verifiers));		
 | ||||
| 		let state = Arc::new((Mutex::new(State::Work(default_amount)), Condvar::new()));		
 | ||||
| 		let mut verifier_handles = Vec::with_capacity(max_verifiers); | ||||
| 
 | ||||
| 		debug!(target: "verification", "Allocating {} verifiers, {} initially active", max_verifiers, default_amount); | ||||
| 		debug!(target: "verification", "Verifier auto-scaling {}", if scale_verifiers { "enabled" } else { "disabled" }); | ||||
| 
 | ||||
| 		for i in 0..max_verifiers { | ||||
| 			debug!(target: "verification", "Adding verification thread #{}", i); | ||||
| 
 | ||||
| 			let deleting = deleting.clone(); | ||||
| 			let panic_handler = panic_handler.clone(); | ||||
| 			let verification = verification.clone(); | ||||
| 			let engine = engine.clone(); | ||||
| 			let wait = more_to_verify.clone(); | ||||
| 			let ready = ready_signal.clone(); | ||||
| 			let empty = empty.clone(); | ||||
| 			let state = state.clone(); | ||||
| 
 | ||||
| 			// enable only the first few verifiers.
 | ||||
| 			let sleep = if i < default_amount { | ||||
| 				Arc::new(AtomicBool::new(false)) | ||||
| 			} else { | ||||
| 				Arc::new(AtomicBool::new(true)) | ||||
| 			}; | ||||
| 
 | ||||
| 			verifiers.push(VerifierHandle { | ||||
| 				deleting: deleting.clone(), | ||||
| 				sleep: sleep.clone(), | ||||
| 				thread: thread::Builder::new() | ||||
| 					.name(format!("Verifier #{}", i)) | ||||
| 					.spawn(move || { | ||||
| 						panic_handler.catch_panic(move || { | ||||
| 							VerificationQueue::verify(verification, engine, wait, ready, deleting, empty, sleep) | ||||
| 						}).unwrap() | ||||
| 					}) | ||||
| 					.expect("Failed to create verifier thread.") | ||||
| 			}); | ||||
| 			let handle = thread::Builder::new() | ||||
| 				.name(format!("Verifier #{}", i)) | ||||
| 				.spawn(move || { | ||||
| 					panic_handler.catch_panic(move || { | ||||
| 						VerificationQueue::verify( | ||||
| 							verification, 
 | ||||
| 							engine, 
 | ||||
| 							wait, 
 | ||||
| 							ready, 
 | ||||
| 							empty, 
 | ||||
| 							state, | ||||
| 							i, | ||||
| 						) | ||||
| 					}).unwrap() | ||||
| 				}) | ||||
| 				.expect("Failed to create verifier thread."); | ||||
| 			verifier_handles.push(handle); | ||||
| 		} | ||||
| 
 | ||||
| 		VerificationQueue { | ||||
| @ -266,13 +268,15 @@ impl<K: Kind> VerificationQueue<K> { | ||||
| 			ready_signal: ready_signal, | ||||
| 			more_to_verify: more_to_verify, | ||||
| 			verification: verification, | ||||
| 			verifiers: Mutex::new((verifiers, default_amount)), | ||||
| 			deleting: deleting, | ||||
| 			processing: RwLock::new(HashSet::new()), | ||||
| 			empty: empty, | ||||
| 			ticks_since_adjustment: AtomicUsize::new(0), | ||||
| 			max_queue_size: max(config.max_queue_size, MIN_QUEUE_LIMIT), | ||||
| 			max_mem_use: max(config.max_mem_use, MIN_MEM_LIMIT), | ||||
| 			scale_verifiers: scale_verifiers, | ||||
| 			verifier_handles: verifier_handles, | ||||
| 			state: state, | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| @ -281,23 +285,30 @@ impl<K: Kind> VerificationQueue<K> { | ||||
| 		engine: Arc<Engine>, | ||||
| 		wait: Arc<SCondvar>, | ||||
| 		ready: Arc<QueueSignal>, | ||||
| 		deleting: Arc<AtomicBool>, | ||||
| 		empty: Arc<SCondvar>, | ||||
| 		sleep: Arc<AtomicBool>, | ||||
| 		state: Arc<(Mutex<State>, Condvar)>, | ||||
| 		id: usize, | ||||
| 	) { | ||||
| 		while !deleting.load(AtomicOrdering::Acquire) { | ||||
| 		loop { | ||||
| 			// check current state.
 | ||||
| 			{ | ||||
| 				while sleep.load(AtomicOrdering::SeqCst) { | ||||
| 					trace!(target: "verification", "Verifier sleeping"); | ||||
| 					::std::thread::park(); | ||||
| 					trace!(target: "verification", "Verifier waking up"); | ||||
| 				let mut cur_state = state.0.lock(); | ||||
| 				while let State::Work(x) = *cur_state { | ||||
| 					// sleep until this thread is required.
 | ||||
| 					if id < x { break } | ||||
| 
 | ||||
| 					if deleting.load(AtomicOrdering::Acquire) { | ||||
| 						return; | ||||
| 					} | ||||
| 					debug!(target: "verification", "verifier {} sleeping", id); | ||||
| 					state.1.wait(&mut cur_state); | ||||
| 					debug!(target: "verification", "verifier {} waking up", id);					
 | ||||
| 				} | ||||
| 
 | ||||
| 				if let State::Exit = *cur_state { 
 | ||||
| 					debug!(target: "verification", "verifier {} exiting", id);										
 | ||||
| 					break; | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			// wait for work if empty.
 | ||||
| 			{ | ||||
| 				let mut more_to_verify = verification.more_to_verify.lock().unwrap(); | ||||
| 
 | ||||
| @ -305,15 +316,22 @@ impl<K: Kind> VerificationQueue<K> { | ||||
| 					empty.notify_all(); | ||||
| 				} | ||||
| 
 | ||||
| 				while verification.unverified.lock().is_empty() && !deleting.load(AtomicOrdering::Acquire) { | ||||
| 				while verification.unverified.lock().is_empty() { | ||||
| 					if let State::Exit = *state.0.lock() { | ||||
| 						debug!(target: "verification", "verifier {} exiting", id); | ||||
| 						return; | ||||
| 					} | ||||
| 
 | ||||
| 					more_to_verify = wait.wait(more_to_verify).unwrap(); | ||||
| 				} | ||||
| 
 | ||||
| 				if deleting.load(AtomicOrdering::Acquire) { | ||||
| 				if let State::Exit = *state.0.lock() { | ||||
| 					debug!(target: "verification", "verifier {} exiting", id);										
 | ||||
| 					return; | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			// do work.
 | ||||
| 			let item = { | ||||
| 				// acquire these locks before getting the item to verify.
 | ||||
| 				let mut unverified = verification.unverified.lock(); | ||||
| @ -568,6 +586,14 @@ impl<K: Kind> VerificationQueue<K> { | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	/// Get the current number of working verifiers.
 | ||||
| 	pub fn num_verifiers(&self) -> usize { | ||||
| 		match *self.state.0.lock() { | ||||
| 			State::Work(x) => x, | ||||
| 			State::Exit => panic!("state only set to exit on drop; queue live now; qed"), | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	/// Optimise memory footprint of the heap fields, and adjust the number of threads
 | ||||
| 	/// to better suit the workload.
 | ||||
| 	pub fn collect_garbage(&self) { | ||||
| @ -598,13 +624,15 @@ impl<K: Kind> VerificationQueue<K> { | ||||
| 
 | ||||
| 		self.processing.write().shrink_to_fit(); | ||||
| 
 | ||||
| 		if !self.scale_verifiers { return } | ||||
| 
 | ||||
| 		if self.ticks_since_adjustment.fetch_add(1, AtomicOrdering::SeqCst) + 1 >= READJUSTMENT_PERIOD { | ||||
| 			self.ticks_since_adjustment.store(0, AtomicOrdering::SeqCst); | ||||
| 		} else { | ||||
| 			return; | ||||
| 		} | ||||
| 
 | ||||
| 		let current = self.verifiers.lock().1; | ||||
| 		let current = self.num_verifiers(); | ||||
| 
 | ||||
| 		let diff = (v_len - u_len).abs(); | ||||
| 		let total = v_len + u_len; | ||||
| @ -626,27 +654,14 @@ impl<K: Kind> VerificationQueue<K> { | ||||
| 	// possible, never going over the amount of initially allocated threads
 | ||||
| 	// or below 1.
 | ||||
| 	fn scale_verifiers(&self, target: usize) { | ||||
| 		let mut verifiers = self.verifiers.lock(); | ||||
| 		let &mut (ref mut verifiers, ref mut verifier_count) = &mut *verifiers; | ||||
| 
 | ||||
| 		let target = min(verifiers.len(), target); | ||||
| 		let current = self.num_verifiers(); | ||||
| 		let target = min(self.verifier_handles.len(), target); | ||||
| 		let target = max(1, target); | ||||
| 
 | ||||
| 		debug!(target: "verification", "Scaling from {} to {} verifiers", verifier_count, target); | ||||
| 		debug!(target: "verification", "Scaling from {} to {} verifiers", current, target); | ||||
| 
 | ||||
| 		// scaling up
 | ||||
| 		for i in *verifier_count..target { | ||||
| 			debug!(target: "verification", "Waking up verifier {}", i); | ||||
| 			verifiers[i].wake_up(); | ||||
| 		} | ||||
| 
 | ||||
| 		// scaling down.
 | ||||
| 		for i in target..*verifier_count { | ||||
| 			debug!(target: "verification", "Putting verifier {} to sleep", i); | ||||
| 			verifiers[i].sleep(); | ||||
| 		} | ||||
| 
 | ||||
| 		*verifier_count = target; | ||||
| 		*self.state.0.lock() = State::Work(target); | ||||
| 		self.state.1.notify_all(); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| @ -660,22 +675,18 @@ impl<K: Kind> Drop for VerificationQueue<K> { | ||||
| 	fn drop(&mut self) { | ||||
| 		trace!(target: "shutdown", "[VerificationQueue] Closing..."); | ||||
| 		self.clear(); | ||||
| 		self.deleting.store(true, AtomicOrdering::Release); | ||||
| 		self.deleting.store(true, AtomicOrdering::SeqCst); | ||||
| 
 | ||||
| 		let mut verifiers = self.verifiers.get_mut(); | ||||
| 		let mut verifiers = &mut verifiers.0; | ||||
| 
 | ||||
| 		// first pass to signal conclusion. must be done before
 | ||||
| 		// notify or deadlock possible.
 | ||||
| 		for handle in verifiers.iter() { | ||||
| 			handle.conclude(); | ||||
| 		} | ||||
| 		// set exit state; should be done before `more_to_verify` notification.
 | ||||
| 		*self.state.0.lock() = State::Exit; | ||||
| 		self.state.1.notify_all(); | ||||
| 
 | ||||
| 		// wake up all threads waiting for more work.
 | ||||
| 		self.more_to_verify.notify_all(); | ||||
| 
 | ||||
| 		// second pass to join.
 | ||||
| 		for handle in verifiers.drain(..) { | ||||
| 			handle.join(); | ||||
| 		// wait for all verifier threads to join.
 | ||||
| 		for thread in self.verifier_handles.drain(..) { | ||||
| 			thread.join().expect("Propagating verifier thread panic on shutdown"); | ||||
| 		} | ||||
| 
 | ||||
| 		trace!(target: "shutdown", "[VerificationQueue] Closed."); | ||||
| @ -687,16 +698,21 @@ mod tests { | ||||
| 	use util::*; | ||||
| 	use io::*; | ||||
| 	use spec::*; | ||||
| 	use super::{BlockQueue, Config}; | ||||
| 	use super::{BlockQueue, Config, State}; | ||||
| 	use super::kind::blocks::Unverified; | ||||
| 	use tests::helpers::*; | ||||
| 	use error::*; | ||||
| 	use views::*; | ||||
| 
 | ||||
| 	fn get_test_queue() -> BlockQueue { | ||||
| 	// create a test block queue.
 | ||||
| 	// auto_scaling enables verifier adjustment.
 | ||||
| 	fn get_test_queue(auto_scale: bool) -> BlockQueue { | ||||
| 		let spec = get_test_spec(); | ||||
| 		let engine = spec.engine; | ||||
| 		BlockQueue::new(Config::default(), engine, IoChannel::disconnected(), true) | ||||
| 
 | ||||
| 		let mut config = Config::default(); | ||||
| 		config.verifier_settings.scale_verifiers = auto_scale; | ||||
| 		BlockQueue::new(config, engine, IoChannel::disconnected(), true) | ||||
| 	} | ||||
| 
 | ||||
| 	#[test] | ||||
| @ -709,7 +725,7 @@ mod tests { | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn can_import_blocks() { | ||||
| 		let queue = get_test_queue(); | ||||
| 		let queue = get_test_queue(false); | ||||
| 		if let Err(e) = queue.import(Unverified::new(get_good_dummy_block())) { | ||||
| 			panic!("error importing block that is valid by definition({:?})", e); | ||||
| 		} | ||||
| @ -717,7 +733,7 @@ mod tests { | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn returns_error_for_duplicates() { | ||||
| 		let queue = get_test_queue(); | ||||
| 		let queue = get_test_queue(false); | ||||
| 		if let Err(e) = queue.import(Unverified::new(get_good_dummy_block())) { | ||||
| 			panic!("error importing block that is valid by definition({:?})", e); | ||||
| 		} | ||||
| @ -736,7 +752,7 @@ mod tests { | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn returns_ok_for_drained_duplicates() { | ||||
| 		let queue = get_test_queue(); | ||||
| 		let queue = get_test_queue(false); | ||||
| 		let block = get_good_dummy_block(); | ||||
| 		let hash = BlockView::new(&block).header().hash().clone(); | ||||
| 		if let Err(e) = queue.import(Unverified::new(block)) { | ||||
| @ -753,7 +769,7 @@ mod tests { | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn returns_empty_once_finished() { | ||||
| 		let queue = get_test_queue(); | ||||
| 		let queue = get_test_queue(false); | ||||
| 		queue.import(Unverified::new(get_good_dummy_block())) | ||||
| 			.expect("error importing block that is valid by definition"); | ||||
| 		queue.flush(); | ||||
| @ -781,30 +797,23 @@ mod tests { | ||||
| 	fn scaling_limits() { | ||||
| 		use super::MAX_VERIFIERS; | ||||
| 
 | ||||
| 		let queue = get_test_queue(); | ||||
| 		let queue = get_test_queue(true); | ||||
| 		queue.scale_verifiers(MAX_VERIFIERS + 1); | ||||
| 
 | ||||
| 		assert!(queue.verifiers.lock().1 < MAX_VERIFIERS + 1); | ||||
| 		assert!(queue.num_verifiers() < MAX_VERIFIERS + 1); | ||||
| 
 | ||||
| 		queue.scale_verifiers(0); | ||||
| 
 | ||||
| 		assert!(queue.verifiers.lock().1 == 1); | ||||
| 		assert!(queue.num_verifiers() == 1); | ||||
| 	} | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn readjust_verifiers() { | ||||
| 		let queue = get_test_queue(); | ||||
| 		let queue = get_test_queue(true); | ||||
| 
 | ||||
| 		// put all the verifiers to sleep to ensure 
 | ||||
| 		// the test isn't timing sensitive.
 | ||||
| 		let num_verifiers = { | ||||
| 			let verifiers = queue.verifiers.lock(); | ||||
| 			for i in 0..verifiers.1 { | ||||
| 				verifiers.0[i].sleep(); | ||||
| 			} | ||||
| 
 | ||||
| 			verifiers.1 | ||||
| 		}; | ||||
| 		*queue.state.0.lock() = State::Work(0); | ||||
| 
 | ||||
| 		for block in get_good_dummy_block_seq(5000) { | ||||
| 			queue.import(Unverified::new(block)).expect("Block good by definition; qed"); | ||||
| @ -812,20 +821,12 @@ mod tests { | ||||
| 
 | ||||
| 		// almost all unverified == bump verifier count.
 | ||||
| 		queue.collect_garbage(); | ||||
| 		assert_eq!(queue.verifiers.lock().1, num_verifiers + 1); | ||||
| 
 | ||||
| 		// wake them up again and verify everything.
 | ||||
| 		{ | ||||
| 			let verifiers = queue.verifiers.lock(); | ||||
| 			for i in 0..verifiers.1 { | ||||
| 				verifiers.0[i].wake_up(); | ||||
| 			} | ||||
| 		} | ||||
| 		assert_eq!(queue.num_verifiers(), 1); | ||||
| 
 | ||||
| 		queue.flush(); | ||||
| 
 | ||||
| 		// nothing to verify == use minimum number of verifiers.
 | ||||
| 		queue.collect_garbage(); | ||||
| 		assert_eq!(queue.verifiers.lock().1, 1); | ||||
| 		assert_eq!(queue.num_verifiers(), 1); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "parity.js", | ||||
|   "version": "0.2.91", | ||||
|   "version": "0.2.97", | ||||
|   "main": "release/index.js", | ||||
|   "jsnext:main": "src/index.js", | ||||
|   "author": "Parity Team <admin@parity.io>", | ||||
|  | ||||
| @ -189,15 +189,21 @@ export default class Contract { | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   _encodeOptions (func, options, values) { | ||||
|   getCallData = (func, options, values) => { | ||||
|     let data = options.data; | ||||
| 
 | ||||
|     const tokens = func ? this._abi.encodeTokens(func.inputParamTypes(), values) : null; | ||||
|     const call = tokens ? func.encodeCall(tokens) : null; | ||||
| 
 | ||||
|     if (options.data && options.data.substr(0, 2) === '0x') { | ||||
|       options.data = options.data.substr(2); | ||||
|     if (data && data.substr(0, 2) === '0x') { | ||||
|       data = data.substr(2); | ||||
|     } | ||||
|     options.data = `0x${options.data || ''}${call || ''}`; | ||||
| 
 | ||||
|     return `0x${data || ''}${call || ''}`; | ||||
|   } | ||||
| 
 | ||||
|   _encodeOptions (func, options, values) { | ||||
|     options.data = this.getCallData(func, options, values); | ||||
|     return options; | ||||
|   } | ||||
| 
 | ||||
| @ -209,8 +215,10 @@ export default class Contract { | ||||
| 
 | ||||
|   _bindFunction = (func) => { | ||||
|     func.call = (options, values = []) => { | ||||
|       const callParams = this._encodeOptions(func, this._addOptionsTo(options), values); | ||||
| 
 | ||||
|       return this._api.eth | ||||
|         .call(this._encodeOptions(func, this._addOptionsTo(options), values)) | ||||
|         .call(callParams) | ||||
|         .then((encoded) => func.decodeOutput(encoded)) | ||||
|         .then((tokens) => tokens.map((token) => token.value)) | ||||
|         .then((returns) => returns.length === 1 ? returns[0] : returns); | ||||
|  | ||||
| @ -128,6 +128,11 @@ export default class Parity { | ||||
|       .execute('parity_killAccount', inAddress(account), password); | ||||
|   } | ||||
| 
 | ||||
|   removeAddress (address) { | ||||
|     return this._transport | ||||
|       .execute('parity_removeAddress', inAddress(address)); | ||||
|   } | ||||
| 
 | ||||
|   listGethAccounts () { | ||||
|     return this._transport | ||||
|       .execute('parity_listGethAccounts') | ||||
|  | ||||
							
								
								
									
										21
									
								
								js/src/contracts/code/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								js/src/contracts/code/index.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | ||||
| // 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 wallet from './wallet'; | ||||
| 
 | ||||
| export { | ||||
|   wallet | ||||
| }; | ||||
							
								
								
									
										23
									
								
								js/src/contracts/code/wallet.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								js/src/contracts/code/wallet.js
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										388
									
								
								js/src/contracts/snippets/wallet.sol
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										388
									
								
								js/src/contracts/snippets/wallet.sol
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,388 @@ | ||||
| //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 multiowned { | ||||
| 
 | ||||
|     // TYPES | ||||
| 
 | ||||
|     // struct for the status of a pending operation. | ||||
|     struct PendingState { | ||||
|         uint yetNeeded; | ||||
|         uint ownersDone; | ||||
|         uint index; | ||||
|     } | ||||
| 
 | ||||
|     // EVENTS | ||||
| 
 | ||||
|     // this contract only has six types of events: it 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); | ||||
| 
 | ||||
|     // 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 multiowned(address[] _owners, uint _required) { | ||||
|         m_numOwners = _owners.length + 1; | ||||
|         m_owners[1] = uint(msg.sender); | ||||
|         m_ownerIndex[uint(msg.sender)] = 1; | ||||
|         for (uint i = 0; i < _owners.length; ++i) | ||||
|         { | ||||
|             m_owners[2 + i] = uint(_owners[i]); | ||||
|             m_ownerIndex[uint(_owners[i])] = 2 + i; | ||||
|         } | ||||
|         m_required = _required; | ||||
|     } | ||||
| 
 | ||||
|     // Revokes a prior confirmation of the given operation | ||||
|     function revoke(bytes32 _operation) external { | ||||
|         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)) external { | ||||
|         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)) external { | ||||
|         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)) external { | ||||
|         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)) external { | ||||
|         if (_newRequired > m_numOwners) return; | ||||
|         m_required = _newRequired; | ||||
|         clearPending(); | ||||
|         RequirementChanged(_newRequired); | ||||
|     } | ||||
| 
 | ||||
|     // Gets an owner by 0-indexed position (using numOwners as the count) | ||||
|     function getOwner(uint ownerIndex) external constant returns (address) { | ||||
|         return address(m_owners[ownerIndex + 1]); | ||||
|     } | ||||
| 
 | ||||
|     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; | ||||
|     } | ||||
| 
 | ||||
|     // FIELDS | ||||
| 
 | ||||
|     // 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; | ||||
| 
 | ||||
|     // 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; | ||||
| } | ||||
| 
 | ||||
| // inheritable "property" contract that enables methods to be protected by placing a linear limit (specifiable) | ||||
| // on a particular resource per calendar day. is multiowned to allow the limit to be altered. resource that method | ||||
| // uses is specified in the modifier. | ||||
| contract daylimit is multiowned { | ||||
| 
 | ||||
|     // 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 daylimit(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)) external { | ||||
|         m_dailyLimit = _newLimit; | ||||
|     } | ||||
|     // resets the amount already spent today. needs many of the owners to confirm. | ||||
|     function resetSpentToday() onlymanyowners(sha3(msg.data)) external { | ||||
|         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; } | ||||
| 
 | ||||
|     // FIELDS | ||||
| 
 | ||||
|     uint public m_dailyLimit; | ||||
|     uint public m_spentToday; | ||||
|     uint public m_lastDay; | ||||
| } | ||||
| 
 | ||||
| // interface contract for multisig proxy contracts; see below for docs. | ||||
| contract multisig { | ||||
| 
 | ||||
|     // EVENTS | ||||
| 
 | ||||
|     // logged events: | ||||
|     // 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); | ||||
| 
 | ||||
|     // FUNCTIONS | ||||
| 
 | ||||
|     // TODO: document | ||||
|     function changeOwner(address _from, address _to) external; | ||||
|     function execute(address _to, uint _value, bytes _data) external returns (bytes32); | ||||
|     function confirm(bytes32 _h) returns (bool); | ||||
| } | ||||
| 
 | ||||
| // usage: | ||||
| // bytes32 h = Wallet(w).from(oneOwner).execute(to, value, data); | ||||
| // Wallet(w).from(anotherOwner).confirm(h); | ||||
| contract Wallet is multisig, multiowned, daylimit { | ||||
| 
 | ||||
|     // TYPES | ||||
| 
 | ||||
|     // Transaction structure to remember details of transaction lest it need be saved for a later call. | ||||
|     struct Transaction { | ||||
|         address to; | ||||
|         uint value; | ||||
|         bytes data; | ||||
|     } | ||||
| 
 | ||||
|     // METHODS | ||||
| 
 | ||||
|     // constructor - just pass on the owner array to the multiowned and | ||||
|     // the limit to daylimit | ||||
|     function Wallet(address[] _owners, uint _required, uint _daylimit) | ||||
|             multiowned(_owners, _required) daylimit(_daylimit) { | ||||
|     } | ||||
| 
 | ||||
|     // kills the contract sending everything to `_to`. | ||||
|     function kill(address _to) onlymanyowners(sha3(msg.data)) external { | ||||
|         suicide(_to); | ||||
|     } | ||||
| 
 | ||||
|     // gets called when no other function matches | ||||
|     function() payable { | ||||
|         // just being sent some cash? | ||||
|         if (msg.value > 0) | ||||
|             Deposit(msg.sender, msg.value); | ||||
|     } | ||||
| 
 | ||||
|     // 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) external onlyowner returns (bytes32 _r) { | ||||
|         // 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. | ||||
|             _to.call.value(_value)(_data); | ||||
|             return 0; | ||||
|         } | ||||
|         // determine our operation hash. | ||||
|         _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 clearPending() internal { | ||||
|         uint length = m_pendingIndex.length; | ||||
|         for (uint i = 0; i < length; ++i) | ||||
|             delete m_txs[m_pendingIndex[i]]; | ||||
|         super.clearPending(); | ||||
|     } | ||||
| 
 | ||||
|     // FIELDS | ||||
| 
 | ||||
|     // pending transactions we have at present. | ||||
|     mapping (bytes32 => Transaction) m_txs; | ||||
| } | ||||
| @ -21,6 +21,9 @@ const muiTheme = getMuiTheme(lightBaseTheme); | ||||
| 
 | ||||
| import CircularProgress from 'material-ui/CircularProgress'; | ||||
| import { Card, CardText } from 'material-ui/Card'; | ||||
| 
 | ||||
| import { nullableProptype } from '~/util/proptypes'; | ||||
| 
 | ||||
| import styles from './application.css'; | ||||
| import Accounts from '../Accounts'; | ||||
| import Events from '../Events'; | ||||
| @ -28,8 +31,6 @@ import Lookup from '../Lookup'; | ||||
| import Names from '../Names'; | ||||
| import Records from '../Records'; | ||||
| 
 | ||||
| const nullable = (type) => React.PropTypes.oneOfType([ React.PropTypes.oneOf([ null ]), type ]); | ||||
| 
 | ||||
| export default class Application extends Component { | ||||
|   static childContextTypes = { | ||||
|     muiTheme: PropTypes.object.isRequired, | ||||
| @ -44,8 +45,8 @@ export default class Application extends Component { | ||||
|     actions: PropTypes.object.isRequired, | ||||
|     accounts: PropTypes.object.isRequired, | ||||
|     contacts: PropTypes.object.isRequired, | ||||
|     contract: nullable(PropTypes.object.isRequired), | ||||
|     fee: nullable(PropTypes.object.isRequired), | ||||
|     contract: nullableProptype(PropTypes.object.isRequired), | ||||
|     fee: nullableProptype(PropTypes.object.isRequired), | ||||
|     lookup: PropTypes.object.isRequired, | ||||
|     events: PropTypes.object.isRequired, | ||||
|     names: PropTypes.object.isRequired, | ||||
|  | ||||
| @ -18,19 +18,19 @@ import React, { Component, PropTypes } from 'react'; | ||||
| import { connect } from 'react-redux'; | ||||
| import { bindActionCreators } from 'redux'; | ||||
| 
 | ||||
| import { nullableProptype } from '~/util/proptypes'; | ||||
| 
 | ||||
| import Application from './Application'; | ||||
| import * as actions from './actions'; | ||||
| 
 | ||||
| const nullable = (type) => React.PropTypes.oneOfType([ React.PropTypes.oneOf([ null ]), type ]); | ||||
| 
 | ||||
| class Container extends Component { | ||||
|   static propTypes = { | ||||
|     actions: PropTypes.object.isRequired, | ||||
|     accounts: PropTypes.object.isRequired, | ||||
|     contacts: PropTypes.object.isRequired, | ||||
|     contract: nullable(PropTypes.object.isRequired), | ||||
|     owner: nullable(PropTypes.string.isRequired), | ||||
|     fee: nullable(PropTypes.object.isRequired), | ||||
|     contract: nullableProptype(PropTypes.object.isRequired), | ||||
|     owner: nullableProptype(PropTypes.string.isRequired), | ||||
|     fee: nullableProptype(PropTypes.object.isRequired), | ||||
|     lookup: PropTypes.object.isRequired, | ||||
|     events: PropTypes.object.isRequired | ||||
|   }; | ||||
|  | ||||
| @ -19,21 +19,22 @@ import { Card, CardHeader, CardText } from 'material-ui/Card'; | ||||
| import TextField from 'material-ui/TextField'; | ||||
| import RaisedButton from 'material-ui/RaisedButton'; | ||||
| import SearchIcon from 'material-ui/svg-icons/action/search'; | ||||
| 
 | ||||
| import { nullableProptype } from '~/util/proptypes'; | ||||
| 
 | ||||
| import renderAddress from '../ui/address.js'; | ||||
| import renderImage from '../ui/image.js'; | ||||
| 
 | ||||
| import recordTypeSelect from '../ui/record-type-select.js'; | ||||
| import styles from './lookup.css'; | ||||
| 
 | ||||
| const nullable = (type) => React.PropTypes.oneOfType([ React.PropTypes.oneOf([ null ]), type ]); | ||||
| 
 | ||||
| export default class Lookup extends Component { | ||||
| 
 | ||||
|   static propTypes = { | ||||
|     actions: PropTypes.object.isRequired, | ||||
|     name: PropTypes.string.isRequired, | ||||
|     type: PropTypes.string.isRequired, | ||||
|     result: nullable(PropTypes.string.isRequired), | ||||
|     result: nullableProptype(PropTypes.string.isRequired), | ||||
|     accounts: PropTypes.object.isRequired, | ||||
|     contacts: PropTypes.object.isRequired | ||||
|   } | ||||
|  | ||||
| @ -1,3 +1,19 @@ | ||||
| // Copyright 2015, 2016 Ethcore (UK) Ltd.
 | ||||
| // This file is part of Parity.
 | ||||
| 
 | ||||
| // Parity is free software: you can redistribute it and/or modify
 | ||||
| // it under the terms of the GNU General Public License as published by
 | ||||
| // the Free Software Foundation, either version 3 of the License, or
 | ||||
| // (at your option) any later version.
 | ||||
| 
 | ||||
| // Parity is distributed in the hope that it will be useful,
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | ||||
| // GNU General Public License for more details.
 | ||||
| 
 | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| import { Card, CardHeader, CardText } from 'material-ui/Card'; | ||||
| import TextField from 'material-ui/TextField'; | ||||
|  | ||||
| @ -256,6 +256,20 @@ export default { | ||||
|     } | ||||
|   }, | ||||
| 
 | ||||
|   removeAddress: { | ||||
|     desc: 'Removes an address from the addressbook', | ||||
|     params: [ | ||||
|       { | ||||
|         type: Address, | ||||
|         desc: 'The address to remove' | ||||
|       } | ||||
|     ], | ||||
|     returns: { | ||||
|       type: Boolean, | ||||
|       desc: 'true on success' | ||||
|     } | ||||
|   }, | ||||
| 
 | ||||
|   listGethAccounts: { | ||||
|     desc: 'Returns a list of the accounts available from Geth', | ||||
|     params: [], | ||||
|  | ||||
| @ -17,7 +17,7 @@ | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| import { Redirect, Router, Route } from 'react-router'; | ||||
| 
 | ||||
| import { Accounts, Account, Addresses, Address, Application, Contract, Contracts, WriteContract, Dapp, Dapps, Settings, SettingsBackground, SettingsParity, SettingsProxy, SettingsViews, Signer, Status } from '~/views'; | ||||
| import { Accounts, Account, Addresses, Address, Application, Contract, Contracts, WriteContract, Wallet, Dapp, Dapps, Settings, SettingsBackground, SettingsParity, SettingsProxy, SettingsViews, Signer, Status } from '~/views'; | ||||
| 
 | ||||
| import styles from './reset.css'; | ||||
| 
 | ||||
| @ -37,6 +37,7 @@ export default class MainApplication extends Component { | ||||
|         <Route path='/' component={ Application }> | ||||
|           <Route path='accounts' component={ Accounts } /> | ||||
|           <Route path='account/:address' component={ Account } /> | ||||
|           <Route path='wallet/:address' component={ Wallet } /> | ||||
|           <Route path='addresses' component={ Addresses } /> | ||||
|           <Route path='address/:address' component={ Address } /> | ||||
|           <Route path='apps' component={ Dapps } /> | ||||
|  | ||||
| @ -192,8 +192,6 @@ export default class CreateAccount extends Component { | ||||
|               }; | ||||
|             }); | ||||
| 
 | ||||
|             console.log(accounts); | ||||
| 
 | ||||
|             this.setState({ | ||||
|               selectedAddress: addresses[0], | ||||
|               accounts: accounts | ||||
| @ -201,8 +199,7 @@ export default class CreateAccount extends Component { | ||||
|           }); | ||||
|       }) | ||||
|       .catch((error) => { | ||||
|         console.log('createIdentities', error); | ||||
| 
 | ||||
|         console.error('createIdentities', error); | ||||
|         setTimeout(this.createIdentities, 1000); | ||||
|         this.newError(error); | ||||
|       }); | ||||
|  | ||||
							
								
								
									
										17
									
								
								js/src/modals/CreateWallet/WalletDetails/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								js/src/modals/CreateWallet/WalletDetails/index.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | ||||
| // Copyright 2015, 2016 Ethcore (UK) Ltd.
 | ||||
| // This file is part of Parity.
 | ||||
| 
 | ||||
| // Parity is free software: you can redistribute it and/or modify
 | ||||
| // it under the terms of the GNU General Public License as published by
 | ||||
| // the Free Software Foundation, either version 3 of the License, or
 | ||||
| // (at your option) any later version.
 | ||||
| 
 | ||||
| // Parity is distributed in the hope that it will be useful,
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | ||||
| // GNU General Public License for more details.
 | ||||
| 
 | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| export default from './walletDetails'; | ||||
							
								
								
									
										162
									
								
								js/src/modals/CreateWallet/WalletDetails/walletDetails.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										162
									
								
								js/src/modals/CreateWallet/WalletDetails/walletDetails.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,162 @@ | ||||
| // Copyright 2015, 2016 Ethcore (UK) Ltd.
 | ||||
| // This file is part of Parity.
 | ||||
| 
 | ||||
| // Parity is free software: you can redistribute it and/or modify
 | ||||
| // it under the terms of the GNU General Public License as published by
 | ||||
| // the Free Software Foundation, either version 3 of the License, or
 | ||||
| // (at your option) any later version.
 | ||||
| 
 | ||||
| // Parity is distributed in the hope that it will be useful,
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | ||||
| // GNU General Public License for more details.
 | ||||
| 
 | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| 
 | ||||
| import { Form, TypedInput, Input, AddressSelect, InputAddress } from '~/ui'; | ||||
| import { parseAbiType } from '~/util/abi'; | ||||
| 
 | ||||
| import styles from '../createWallet.css'; | ||||
| 
 | ||||
| export default class WalletDetails extends Component { | ||||
|   static propTypes = { | ||||
|     accounts: PropTypes.object.isRequired, | ||||
|     wallet: PropTypes.object.isRequired, | ||||
|     errors: PropTypes.object.isRequired, | ||||
|     onChange: PropTypes.func.isRequired, | ||||
|     walletType: PropTypes.string.isRequired | ||||
|   }; | ||||
| 
 | ||||
|   render () { | ||||
|     const { walletType } = this.props; | ||||
| 
 | ||||
|     if (walletType === 'WATCH') { | ||||
|       return this.renderWatchDetails(); | ||||
|     } | ||||
| 
 | ||||
|     return this.renderMultisigDetails(); | ||||
|   } | ||||
| 
 | ||||
|   renderWatchDetails () { | ||||
|     const { wallet, errors } = this.props; | ||||
| 
 | ||||
|     return ( | ||||
|       <Form> | ||||
|         <InputAddress | ||||
|           label='wallet address' | ||||
|           hint='the wallet contract address' | ||||
|           value={ wallet.address } | ||||
|           error={ errors.address } | ||||
|           onChange={ this.onAddressChange } | ||||
|         /> | ||||
| 
 | ||||
|         <Input | ||||
|           label='wallet name' | ||||
|           hint='the local name for this wallet' | ||||
|           value={ wallet.name } | ||||
|           error={ errors.name } | ||||
|           onChange={ this.onNameChange } | ||||
|         /> | ||||
| 
 | ||||
|         <Input | ||||
|           label='wallet description (optional)' | ||||
|           hint='the local description for this wallet' | ||||
|           value={ wallet.description } | ||||
|           onChange={ this.onDescriptionChange } | ||||
|         /> | ||||
|       </Form> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   renderMultisigDetails () { | ||||
|     const { accounts, wallet, errors } = this.props; | ||||
| 
 | ||||
|     return ( | ||||
|       <Form> | ||||
|         <AddressSelect | ||||
|           label='from account (contract owner)' | ||||
|           hint='the owner account for this contract' | ||||
|           value={ wallet.account } | ||||
|           error={ errors.account } | ||||
|           onChange={ this.onAccoutChange } | ||||
|           accounts={ accounts } | ||||
|         /> | ||||
| 
 | ||||
|         <Input | ||||
|           label='wallet name' | ||||
|           hint='the local name for this wallet' | ||||
|           value={ wallet.name } | ||||
|           error={ errors.name } | ||||
|           onChange={ this.onNameChange } | ||||
|         /> | ||||
| 
 | ||||
|         <Input | ||||
|           label='wallet description (optional)' | ||||
|           hint='the local description for this wallet' | ||||
|           value={ wallet.description } | ||||
|           onChange={ this.onDescriptionChange } | ||||
|         /> | ||||
| 
 | ||||
|         <TypedInput | ||||
|           label='other wallet owners' | ||||
|           value={ wallet.owners.slice() } | ||||
|           onChange={ this.onOwnersChange } | ||||
|           accounts={ accounts } | ||||
|           param={ parseAbiType('address[]') } | ||||
|         /> | ||||
| 
 | ||||
|         <div className={ styles.splitInput }> | ||||
|           <TypedInput | ||||
|             label='required owners' | ||||
|             hint='number of required owners to accept a transaction' | ||||
|             value={ wallet.required } | ||||
|             error={ errors.required } | ||||
|             onChange={ this.onRequiredChange } | ||||
|             param={ parseAbiType('uint') } | ||||
|             min={ 1 } | ||||
|           /> | ||||
| 
 | ||||
|           <TypedInput | ||||
|             label='wallet day limit' | ||||
|             hint='number of days to wait for other owners confirmation' | ||||
|             value={ wallet.daylimit } | ||||
|             error={ errors.daylimit } | ||||
|             onChange={ this.onDaylimitChange } | ||||
|             param={ parseAbiType('uint') } | ||||
|           /> | ||||
|         </div> | ||||
|       </Form> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   onAddressChange = (_, address) => { | ||||
|     this.props.onChange({ address }); | ||||
|   } | ||||
| 
 | ||||
|   onAccoutChange = (_, account) => { | ||||
|     this.props.onChange({ account }); | ||||
|   } | ||||
| 
 | ||||
|   onNameChange = (_, name) => { | ||||
|     this.props.onChange({ name }); | ||||
|   } | ||||
| 
 | ||||
|   onDescriptionChange = (_, description) => { | ||||
|     this.props.onChange({ description }); | ||||
|   } | ||||
| 
 | ||||
|   onOwnersChange = (owners) => { | ||||
|     this.props.onChange({ owners }); | ||||
|   } | ||||
| 
 | ||||
|   onRequiredChange = (required) => { | ||||
|     this.props.onChange({ required }); | ||||
|   } | ||||
| 
 | ||||
|   onDaylimitChange = (daylimit) => { | ||||
|     this.props.onChange({ daylimit }); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										17
									
								
								js/src/modals/CreateWallet/WalletInfo/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								js/src/modals/CreateWallet/WalletInfo/index.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | ||||
| // Copyright 2015, 2016 Ethcore (UK) Ltd.
 | ||||
| // This file is part of Parity.
 | ||||
| 
 | ||||
| // Parity is free software: you can redistribute it and/or modify
 | ||||
| // it under the terms of the GNU General Public License as published by
 | ||||
| // the Free Software Foundation, either version 3 of the License, or
 | ||||
| // (at your option) any later version.
 | ||||
| 
 | ||||
| // Parity is distributed in the hope that it will be useful,
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | ||||
| // GNU General Public License for more details.
 | ||||
| 
 | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| export default from './walletInfo'; | ||||
							
								
								
									
										91
									
								
								js/src/modals/CreateWallet/WalletInfo/walletInfo.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								js/src/modals/CreateWallet/WalletInfo/walletInfo.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,91 @@ | ||||
| // Copyright 2015, 2016 Ethcore (UK) Ltd.
 | ||||
| // This file is part of Parity.
 | ||||
| 
 | ||||
| // Parity is free software: you can redistribute it and/or modify
 | ||||
| // it under the terms of the GNU General Public License as published by
 | ||||
| // the Free Software Foundation, either version 3 of the License, or
 | ||||
| // (at your option) any later version.
 | ||||
| 
 | ||||
| // Parity is distributed in the hope that it will be useful,
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | ||||
| // GNU General Public License for more details.
 | ||||
| 
 | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| 
 | ||||
| import { CompletedStep, IdentityIcon, CopyToClipboard } from '~/ui'; | ||||
| 
 | ||||
| import styles from '../createWallet.css'; | ||||
| 
 | ||||
| export default class WalletInfo extends Component { | ||||
|   static propTypes = { | ||||
|     accounts: PropTypes.object.isRequired, | ||||
|     account: PropTypes.string.isRequired, | ||||
|     name: PropTypes.string.isRequired, | ||||
|     address: PropTypes.string.isRequired, | ||||
|     owners: PropTypes.array.isRequired, | ||||
|     required: PropTypes.oneOfType([ | ||||
|       PropTypes.string, | ||||
|       PropTypes.number | ||||
|     ]).isRequired, | ||||
|     daylimit: PropTypes.oneOfType([ | ||||
|       PropTypes.string, | ||||
|       PropTypes.number | ||||
|     ]).isRequired, | ||||
| 
 | ||||
|     deployed: PropTypes.bool | ||||
|   }; | ||||
| 
 | ||||
|   render () { | ||||
|     const { address, required, daylimit, name, deployed } = this.props; | ||||
| 
 | ||||
|     return ( | ||||
|       <CompletedStep> | ||||
|         <div> | ||||
|           <code>{ name }</code> | ||||
|           <span> has been </span> | ||||
|           <span> { deployed ? 'deployed' : 'added' } at </span> | ||||
|         </div> | ||||
|         <div> | ||||
|           <CopyToClipboard data={ address } label='copy address to clipboard' /> | ||||
|           <IdentityIcon address={ address } inline center className={ styles.identityicon } /> | ||||
|           <div className={ styles.address }>{ address }</div> | ||||
|         </div> | ||||
|         <div>with the following owners</div> | ||||
|         <div> | ||||
|           { this.renderOwners() } | ||||
|         </div> | ||||
|         <p> | ||||
|           <code>{ required }</code> owners are required to confirm a transaction. | ||||
|         </p> | ||||
|         <p> | ||||
|           The daily limit is set to <code>{ daylimit }</code>. | ||||
|         </p> | ||||
|       </CompletedStep> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   renderOwners () { | ||||
|     const { account, owners, deployed } = this.props; | ||||
| 
 | ||||
|     return [].concat(deployed ? account : null, owners).filter((a) => a).map((address, id) => ( | ||||
|       <div key={ id } className={ styles.owner }> | ||||
|         <IdentityIcon address={ address } inline center className={ styles.identityicon } /> | ||||
|         <div className={ styles.address }>{ this.addressToString(address) }</div> | ||||
|       </div> | ||||
|     )); | ||||
|   } | ||||
| 
 | ||||
|   addressToString (address) { | ||||
|     const { accounts } = this.props; | ||||
| 
 | ||||
|     if (accounts[address]) { | ||||
|       return accounts[address].name || address; | ||||
|     } | ||||
| 
 | ||||
|     return address; | ||||
|   } | ||||
| } | ||||
| @ -1,19 +1,17 @@ | ||||
| // Copyright 2015, 2016 Ethcore (UK) Ltd.
 | ||||
| // This file is part of Parity.
 | ||||
| //
 | ||||
| 
 | ||||
| // Parity is free software: you can redistribute it and/or modify
 | ||||
| // it under the terms of the GNU General Public License as published by
 | ||||
| // the Free Software Foundation, either version 3 of the License, or
 | ||||
| // (at your option) any later version.
 | ||||
| //
 | ||||
| 
 | ||||
| // Parity is distributed in the hope that it will be useful,
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | ||||
| // GNU General Public License for more details.
 | ||||
| //
 | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.extern crate libc;
 | ||||
| 
 | ||||
| extern crate libc; | ||||
| mod raise_fd_limit; | ||||
| pub use raise_fd_limit::raise_fd_limit; | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| export default from './walletType.js'; | ||||
							
								
								
									
										58
									
								
								js/src/modals/CreateWallet/WalletType/walletType.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								js/src/modals/CreateWallet/WalletType/walletType.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,58 @@ | ||||
| // Copyright 2015, 2016 Ethcore (UK) Ltd.
 | ||||
| // This file is part of Parity.
 | ||||
| 
 | ||||
| // Parity is free software: you can redistribute it and/or modify
 | ||||
| // it under the terms of the GNU General Public License as published by
 | ||||
| // the Free Software Foundation, either version 3 of the License, or
 | ||||
| // (at your option) any later version.
 | ||||
| 
 | ||||
| // Parity is distributed in the hope that it will be useful,
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | ||||
| // GNU General Public License for more details.
 | ||||
| 
 | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| 
 | ||||
| import { RadioButtons } from '~/ui'; | ||||
| 
 | ||||
| // import styles from '../createWallet.css';
 | ||||
| 
 | ||||
| export default class WalletType extends Component { | ||||
|   static propTypes = { | ||||
|     onChange: PropTypes.func.isRequired, | ||||
|     type: PropTypes.string.isRequired | ||||
|   }; | ||||
| 
 | ||||
|   render () { | ||||
|     const { type } = this.props; | ||||
| 
 | ||||
|     return ( | ||||
|       <RadioButtons | ||||
|         name='contractType' | ||||
|         value={ type } | ||||
|         values={ this.getTypes() } | ||||
|         onChange={ this.onTypeChange } | ||||
|       /> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   getTypes () { | ||||
|     return [ | ||||
|       { | ||||
|         label: 'Multi-Sig wallet', key: 'MULTISIG', | ||||
|         description: 'A standard multi-signature Wallet' | ||||
|       }, | ||||
|       { | ||||
|         label: 'Watch a wallet', key: 'WATCH', | ||||
|         description: 'Add an existing wallet to your accounts' | ||||
|       } | ||||
|     ]; | ||||
|   } | ||||
| 
 | ||||
|   onTypeChange = (type) => { | ||||
|     this.props.onChange(type.key); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										58
									
								
								js/src/modals/CreateWallet/createWallet.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								js/src/modals/CreateWallet/createWallet.css
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,58 @@ | ||||
| /* 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/>. | ||||
| */ | ||||
| 
 | ||||
| .address { | ||||
|   vertical-align: top; | ||||
|   display: inline-block; | ||||
| } | ||||
| 
 | ||||
| .identityicon { | ||||
|   margin: -8px 0.5em; | ||||
| } | ||||
| 
 | ||||
| .owner { | ||||
|   height: 40px; | ||||
|   color: lightgrey; | ||||
| 
 | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   justify-content: center; | ||||
| 
 | ||||
|   .identityicon { | ||||
|     width: 24px; | ||||
|     height: 24px; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .splitInput { | ||||
|   display: flex; | ||||
|   flex-direction: row; | ||||
| 
 | ||||
|   > * { | ||||
|     flex: 1; | ||||
| 
 | ||||
|     margin: 0 0.25em; | ||||
| 
 | ||||
|     &:first-child { | ||||
|       margin-left: 0; | ||||
|     } | ||||
| 
 | ||||
|     &:last-child { | ||||
|       margin-right: 0; | ||||
|     } | ||||
|   } | ||||
| } | ||||
							
								
								
									
										214
									
								
								js/src/modals/CreateWallet/createWallet.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										214
									
								
								js/src/modals/CreateWallet/createWallet.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,214 @@ | ||||
| // Copyright 2015, 2016 Ethcore (UK) Ltd.
 | ||||
| // This file is part of Parity.
 | ||||
| 
 | ||||
| // Parity is free software: you can redistribute it and/or modify
 | ||||
| // it under the terms of the GNU General Public License as published by
 | ||||
| // the Free Software Foundation, either version 3 of the License, or
 | ||||
| // (at your option) any later version.
 | ||||
| 
 | ||||
| // Parity is distributed in the hope that it will be useful,
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | ||||
| // GNU General Public License for more details.
 | ||||
| 
 | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| import { observer } from 'mobx-react'; | ||||
| 
 | ||||
| import ActionDone from 'material-ui/svg-icons/action/done'; | ||||
| import ContentClear from 'material-ui/svg-icons/content/clear'; | ||||
| import NavigationArrowForward from 'material-ui/svg-icons/navigation/arrow-forward'; | ||||
| 
 | ||||
| import { Button, Modal, TxHash, BusyStep } from '~/ui'; | ||||
| 
 | ||||
| import WalletType from './WalletType'; | ||||
| import WalletDetails from './WalletDetails'; | ||||
| import WalletInfo from './WalletInfo'; | ||||
| import CreateWalletStore from './createWalletStore'; | ||||
| // import styles from './createWallet.css';
 | ||||
| 
 | ||||
| @observer | ||||
| export default class CreateWallet extends Component { | ||||
|   static contextTypes = { | ||||
|     api: PropTypes.object.isRequired | ||||
|   }; | ||||
| 
 | ||||
|   static propTypes = { | ||||
|     accounts: PropTypes.object.isRequired, | ||||
|     onClose: PropTypes.func.isRequired | ||||
|   }; | ||||
| 
 | ||||
|   store = new CreateWalletStore(this.context.api, this.props.accounts); | ||||
| 
 | ||||
|   render () { | ||||
|     const { stage, steps, waiting, rejected } = this.store; | ||||
| 
 | ||||
|     if (rejected) { | ||||
|       return ( | ||||
|         <Modal | ||||
|           visible | ||||
|           title='rejected' | ||||
|           actions={ this.renderDialogActions() } | ||||
|         > | ||||
|           <BusyStep | ||||
|             title='The deployment has been rejected' | ||||
|             state='The wallet will not be created. You can safely close this window.' | ||||
|           /> | ||||
|         </Modal> | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     return ( | ||||
|       <Modal | ||||
|         visible | ||||
|         actions={ this.renderDialogActions() } | ||||
|         current={ stage } | ||||
|         steps={ steps.map((s) => s.title) } | ||||
|         waiting={ waiting } | ||||
|       > | ||||
|         { this.renderPage() } | ||||
|       </Modal> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   renderPage () { | ||||
|     const { step } = this.store; | ||||
|     const { accounts } = this.props; | ||||
| 
 | ||||
|     switch (step) { | ||||
|       case 'DEPLOYMENT': | ||||
|         return ( | ||||
|           <BusyStep | ||||
|             title='The deployment is currently in progress' | ||||
|             state={ this.store.deployState } | ||||
|           > | ||||
|             { this.store.txhash ? (<TxHash hash={ this.store.txhash } />) : null } | ||||
|           </BusyStep> | ||||
|         ); | ||||
| 
 | ||||
|       case 'INFO': | ||||
|         return ( | ||||
|           <WalletInfo | ||||
|             accounts={ accounts } | ||||
| 
 | ||||
|             account={ this.store.wallet.account } | ||||
|             address={ this.store.wallet.address } | ||||
|             owners={ this.store.wallet.owners.slice() } | ||||
|             required={ this.store.wallet.required } | ||||
|             daylimit={ this.store.wallet.daylimit } | ||||
|             name={ this.store.wallet.name } | ||||
| 
 | ||||
|             deployed={ this.store.deployed } | ||||
|           /> | ||||
|         ); | ||||
| 
 | ||||
|       case 'DETAILS': | ||||
|         return ( | ||||
|           <WalletDetails | ||||
|             accounts={ accounts } | ||||
|             wallet={ this.store.wallet } | ||||
|             errors={ this.store.errors } | ||||
|             walletType={ this.store.walletType } | ||||
|             onChange={ this.store.onChange } | ||||
|           /> | ||||
|         ); | ||||
| 
 | ||||
|       default: | ||||
|       case 'TYPE': | ||||
|         return ( | ||||
|           <WalletType | ||||
|             onChange={ this.store.onTypeChange } | ||||
|             type={ this.store.walletType } | ||||
|           /> | ||||
|         ); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   renderDialogActions () { | ||||
|     const { step, hasErrors, rejected, onCreate, onNext, onAdd } = this.store; | ||||
| 
 | ||||
|     const cancelBtn = ( | ||||
|       <Button | ||||
|         icon={ <ContentClear /> } | ||||
|         label='Cancel' | ||||
|         onClick={ this.onClose } | ||||
|       /> | ||||
|     ); | ||||
| 
 | ||||
|     const closeBtn = ( | ||||
|       <Button | ||||
|         icon={ <ContentClear /> } | ||||
|         label='Close' | ||||
|         onClick={ this.onClose } | ||||
|       /> | ||||
|     ); | ||||
| 
 | ||||
|     const doneBtn = ( | ||||
|       <Button | ||||
|         icon={ <ActionDone /> } | ||||
|         label='Done' | ||||
|         onClick={ this.onClose } | ||||
|       /> | ||||
|     ); | ||||
| 
 | ||||
|     const sendingBtn = ( | ||||
|       <Button | ||||
|         icon={ <ActionDone /> } | ||||
|         label='Sending...' | ||||
|         disabled | ||||
|       /> | ||||
|     ); | ||||
| 
 | ||||
|     const nextBtn = ( | ||||
|       <Button | ||||
|         icon={ <NavigationArrowForward /> } | ||||
|         label='Next' | ||||
|         onClick={ onNext } | ||||
|       /> | ||||
|     ); | ||||
| 
 | ||||
|     if (rejected) { | ||||
|       return [ closeBtn ]; | ||||
|     } | ||||
| 
 | ||||
|     switch (step) { | ||||
|       case 'DEPLOYMENT': | ||||
|         return [ closeBtn, sendingBtn ]; | ||||
| 
 | ||||
|       case 'INFO': | ||||
|         return [ doneBtn ]; | ||||
| 
 | ||||
|       case 'DETAILS': | ||||
|         if (this.store.walletType === 'WATCH') { | ||||
|           return [ cancelBtn, ( | ||||
|             <Button | ||||
|               icon={ <NavigationArrowForward /> } | ||||
|               label='Add' | ||||
|               disabled={ hasErrors } | ||||
|               onClick={ onAdd } | ||||
|             /> | ||||
|           ) ]; | ||||
|         } | ||||
| 
 | ||||
|         return [ cancelBtn, ( | ||||
|           <Button | ||||
|             icon={ <NavigationArrowForward /> } | ||||
|             label='Create' | ||||
|             disabled={ hasErrors } | ||||
|             onClick={ onCreate } | ||||
|           /> | ||||
|         ) ]; | ||||
| 
 | ||||
|       default: | ||||
|       case 'TYPE': | ||||
|         return [ cancelBtn, nextBtn ]; | ||||
| 
 | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   onClose = () => { | ||||
|     this.props.onClose(); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										272
									
								
								js/src/modals/CreateWallet/createWalletStore.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										272
									
								
								js/src/modals/CreateWallet/createWalletStore.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,272 @@ | ||||
| // 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 { 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 { wallet as walletAbi } from '~/contracts/abi'; | ||||
| import { wallet as walletCode } from '~/contracts/code'; | ||||
| 
 | ||||
| import WalletsUtils from '~/util/wallets'; | ||||
| 
 | ||||
| const STEPS = { | ||||
|   TYPE: { title: 'wallet type' }, | ||||
|   DETAILS: { title: 'wallet details' }, | ||||
|   DEPLOYMENT: { title: 'wallet deployment', waiting: true }, | ||||
|   INFO: { title: 'wallet informaton' } | ||||
| }; | ||||
| 
 | ||||
| export default class CreateWalletStore { | ||||
|   @observable step = null; | ||||
|   @observable rejected = false; | ||||
| 
 | ||||
|   @observable deployState = null; | ||||
|   @observable deployError = null; | ||||
|   @observable deployed = false; | ||||
| 
 | ||||
|   @observable txhash = null; | ||||
| 
 | ||||
|   @observable wallet = { | ||||
|     account: '', | ||||
|     address: '', | ||||
|     owners: [], | ||||
|     required: 1, | ||||
|     daylimit: 0, | ||||
| 
 | ||||
|     name: '', | ||||
|     description: '' | ||||
|   }; | ||||
|   @observable walletType = 'MULTISIG'; | ||||
| 
 | ||||
|   @observable errors = { | ||||
|     account: null, | ||||
|     address: null, | ||||
|     owners: null, | ||||
|     required: null, | ||||
|     daylimit: null, | ||||
|     name: null | ||||
|   }; | ||||
| 
 | ||||
|   @computed get stage () { | ||||
|     return this.stepsKeys.findIndex((k) => k === this.step); | ||||
|   } | ||||
| 
 | ||||
|   @computed get hasErrors () { | ||||
|     return !!Object.keys(this.errors) | ||||
|       .filter((errorKey) => { | ||||
|         if (this.walletType === 'WATCH') { | ||||
|           return ['address', 'name'].includes(errorKey); | ||||
|         } | ||||
| 
 | ||||
|         return errorKey !== 'address'; | ||||
|       }) | ||||
|       .find((key) => !!this.errors[key]); | ||||
|   } | ||||
| 
 | ||||
|   @computed get stepsKeys () { | ||||
|     return this.steps.map((s) => s.key); | ||||
|   } | ||||
| 
 | ||||
|   @computed get steps () { | ||||
|     return Object | ||||
|       .keys(STEPS) | ||||
|       .map((key) => { | ||||
|         return { | ||||
|           ...STEPS[key], | ||||
|           key | ||||
|         }; | ||||
|       }) | ||||
|       .filter((step) => { | ||||
|         return (this.walletType !== 'WATCH' || step.key !== 'DEPLOYMENT'); | ||||
|       }); | ||||
|   } | ||||
| 
 | ||||
|   @computed get waiting () { | ||||
|     this.steps | ||||
|       .map((s, idx) => ({ idx, waiting: s.waiting })) | ||||
|       .filter((s) => s.waiting) | ||||
|       .map((s) => s.idx); | ||||
|   } | ||||
| 
 | ||||
|   constructor (api, accounts) { | ||||
|     this.api = api; | ||||
| 
 | ||||
|     this.step = this.stepsKeys[0]; | ||||
|     this.wallet.account = Object.values(accounts)[0].address; | ||||
|     this.validateWallet(this.wallet); | ||||
|   } | ||||
| 
 | ||||
|   @action onTypeChange = (type) => { | ||||
|     this.walletType = type; | ||||
|     this.validateWallet(this.wallet); | ||||
|   } | ||||
| 
 | ||||
|   @action onNext = () => { | ||||
|     const stepIndex = this.stepsKeys.findIndex((k) => k === this.step) + 1; | ||||
|     this.step = this.stepsKeys[stepIndex]; | ||||
|   } | ||||
| 
 | ||||
|   @action onChange = (_wallet) => { | ||||
|     const newWallet = Object.assign({}, this.wallet, _wallet); | ||||
|     this.validateWallet(newWallet); | ||||
|   } | ||||
| 
 | ||||
|   @action onAdd = () => { | ||||
|     if (this.hasErrors) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     const walletContract = new Contract(this.api, walletAbi).at(this.wallet.address); | ||||
| 
 | ||||
|     return Promise | ||||
|       .all([ | ||||
|         WalletsUtils.fetchRequire(walletContract), | ||||
|         WalletsUtils.fetchOwners(walletContract), | ||||
|         WalletsUtils.fetchDailylimit(walletContract) | ||||
|       ]) | ||||
|       .then(([ require, owners, dailylimit ]) => { | ||||
|         transaction(() => { | ||||
|           this.wallet.owners = owners; | ||||
|           this.wallet.required = require.toNumber(); | ||||
|           this.wallet.dailylimit = dailylimit.limit; | ||||
|         }); | ||||
| 
 | ||||
|         return this.addWallet(this.wallet); | ||||
|       }); | ||||
|   } | ||||
| 
 | ||||
|   @action onCreate = () => { | ||||
|     if (this.hasErrors) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     this.step = 'DEPLOYMENT'; | ||||
| 
 | ||||
|     const { account, owners, required, daylimit } = this.wallet; | ||||
| 
 | ||||
|     const options = { | ||||
|       data: walletCode, | ||||
|       from: account | ||||
|     }; | ||||
| 
 | ||||
|     this.api | ||||
|       .newContract(walletAbi) | ||||
|       .deploy(options, [ owners, required, daylimit ], this.onDeploymentState) | ||||
|       .then((address) => { | ||||
|         this.deployed = true; | ||||
|         this.wallet.address = address; | ||||
|         return this.addWallet(this.wallet); | ||||
|       }) | ||||
|       .catch((error) => { | ||||
|         if (error.code === ERROR_CODES.REQUEST_REJECTED) { | ||||
|           this.rejected = true; | ||||
|           return; | ||||
|         } | ||||
| 
 | ||||
|         console.error('error deploying contract', error); | ||||
|         this.deployError = error; | ||||
|       }); | ||||
|   } | ||||
| 
 | ||||
|   @action addWallet = (wallet) => { | ||||
|     const { address, name, description } = wallet; | ||||
| 
 | ||||
|     return Promise | ||||
|       .all([ | ||||
|         this.api.parity.setAccountName(address, name), | ||||
|         this.api.parity.setAccountMeta(address, { | ||||
|           abi: walletAbi, | ||||
|           wallet: true, | ||||
|           timestamp: Date.now(), | ||||
|           deleted: false, | ||||
|           description, | ||||
|           name, | ||||
|           tags: ['wallet'] | ||||
|         }) | ||||
|       ]) | ||||
|       .then(() => { | ||||
|         this.step = 'INFO'; | ||||
|       }); | ||||
|   } | ||||
| 
 | ||||
|   onDeploymentState = (error, data) => { | ||||
|     if (error) { | ||||
|       return console.error('createWallet::onDeploymentState', error); | ||||
|     } | ||||
| 
 | ||||
|     switch (data.state) { | ||||
|       case 'estimateGas': | ||||
|       case 'postTransaction': | ||||
|         this.deployState = 'Preparing transaction for network transmission'; | ||||
|         return; | ||||
| 
 | ||||
|       case 'checkRequest': | ||||
|         this.deployState = 'Waiting for confirmation of the transaction in the Parity Secure Signer'; | ||||
|         return; | ||||
| 
 | ||||
|       case 'getTransactionReceipt': | ||||
|         this.deployState = 'Waiting for the contract deployment transaction receipt'; | ||||
|         this.txhash = data.txhash; | ||||
|         return; | ||||
| 
 | ||||
|       case 'hasReceipt': | ||||
|       case 'getCode': | ||||
|         this.deployState = 'Validating the deployed contract code'; | ||||
|         return; | ||||
| 
 | ||||
|       case 'completed': | ||||
|         this.deployState = 'The contract deployment has been completed'; | ||||
|         return; | ||||
| 
 | ||||
|       default: | ||||
|         console.error('createWallet::onDeploymentState', 'unknow contract deployment state', data); | ||||
|         return; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @action validateWallet = (_wallet) => { | ||||
|     const addressValidation = validateAddress(_wallet.address); | ||||
|     const accountValidation = validateAddress(_wallet.account); | ||||
|     const requiredValidation = validateUint(_wallet.required); | ||||
|     const daylimitValidation = validateUint(_wallet.daylimit); | ||||
|     const nameValidation = validateName(_wallet.name); | ||||
| 
 | ||||
|     const errors = { | ||||
|       address: addressValidation.addressError, | ||||
|       account: accountValidation.addressError, | ||||
|       required: requiredValidation.valueError, | ||||
|       daylimit: daylimitValidation.valueError, | ||||
|       name: nameValidation.nameError | ||||
|     }; | ||||
| 
 | ||||
|     const wallet = { | ||||
|       ..._wallet, | ||||
|       address: addressValidation.address, | ||||
|       account: accountValidation.address, | ||||
|       required: requiredValidation.value, | ||||
|       daylimit: daylimitValidation.value, | ||||
|       name: nameValidation.name | ||||
|     }; | ||||
| 
 | ||||
|     transaction(() => { | ||||
|       this.wallet = wallet; | ||||
|       this.errors = errors; | ||||
|     }); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										17
									
								
								js/src/modals/CreateWallet/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								js/src/modals/CreateWallet/index.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | ||||
| // Copyright 2015, 2016 Ethcore (UK) Ltd.
 | ||||
| // This file is part of Parity.
 | ||||
| 
 | ||||
| // Parity is free software: you can redistribute it and/or modify
 | ||||
| // it under the terms of the GNU General Public License as published by
 | ||||
| // the Free Software Foundation, either version 3 of the License, or
 | ||||
| // (at your option) any later version.
 | ||||
| 
 | ||||
| // Parity is distributed in the hope that it will be useful,
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | ||||
| // GNU General Public License for more details.
 | ||||
| 
 | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| export default from './createWallet'; | ||||
| @ -19,7 +19,7 @@ import { connect } from 'react-redux'; | ||||
| import { bindActionCreators } from 'redux'; | ||||
| 
 | ||||
| import { ConfirmDialog, IdentityIcon, IdentityName, Input } from '~/ui'; | ||||
| import { newError } from '../../redux/actions'; | ||||
| import { newError } from '~/redux/actions'; | ||||
| 
 | ||||
| import styles from './deleteAccount.css'; | ||||
| 
 | ||||
|  | ||||
| @ -15,7 +15,6 @@ | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| import nullable from '../../../util/nullable-proptype'; | ||||
| import BigNumber from 'bignumber.js'; | ||||
| import { Checkbox } from 'material-ui'; | ||||
| import InfoIcon from 'material-ui/svg-icons/action/info-outline'; | ||||
| @ -24,6 +23,7 @@ import ErrorIcon from 'material-ui/svg-icons/navigation/close'; | ||||
| 
 | ||||
| import { fromWei } from '~/api/util/wei'; | ||||
| import { Form, Input } from '~/ui'; | ||||
| import { nullableProptype } from '~/util/proptypes'; | ||||
| 
 | ||||
| import { termsOfService } from '../../../3rdparty/sms-verification'; | ||||
| import styles from './gatherData.css'; | ||||
| @ -32,8 +32,8 @@ export default class GatherData extends Component { | ||||
|   static propTypes = { | ||||
|     fee: React.PropTypes.instanceOf(BigNumber), | ||||
|     isNumberValid: PropTypes.bool.isRequired, | ||||
|     isVerified: nullable(PropTypes.bool.isRequired), | ||||
|     hasRequested: nullable(PropTypes.bool.isRequired), | ||||
|     isVerified: nullableProptype(PropTypes.bool.isRequired), | ||||
|     hasRequested: nullableProptype(PropTypes.bool.isRequired), | ||||
|     setNumber: PropTypes.func.isRequired, | ||||
|     setConsentGiven: PropTypes.func.isRequired | ||||
|   } | ||||
|  | ||||
| @ -15,8 +15,8 @@ | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| import nullable from '../../../util/nullable-proptype'; | ||||
| 
 | ||||
| import { nullableProptype } from '~/util/proptypes'; | ||||
| import TxHash from '~/ui/TxHash'; | ||||
| import { | ||||
|   POSTING_CONFIRMATION, POSTED_CONFIRMATION | ||||
| @ -27,7 +27,7 @@ import styles from './sendConfirmation.css'; | ||||
| export default class SendConfirmation extends Component { | ||||
|   static propTypes = { | ||||
|     step: PropTypes.any.isRequired, | ||||
|     tx: nullable(PropTypes.any.isRequired) | ||||
|     tx: nullableProptype(PropTypes.any.isRequired) | ||||
|   } | ||||
| 
 | ||||
|   render () { | ||||
|  | ||||
| @ -15,8 +15,8 @@ | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| import nullable from '../../../util/nullable-proptype'; | ||||
| 
 | ||||
| import { nullableProptype } from '~/util/proptypes'; | ||||
| import TxHash from '~/ui/TxHash'; | ||||
| import { | ||||
|   POSTING_REQUEST, POSTED_REQUEST, REQUESTING_SMS | ||||
| @ -27,7 +27,7 @@ import styles from './sendRequest.css'; | ||||
| export default class SendRequest extends Component { | ||||
|   static propTypes = { | ||||
|     step: PropTypes.any.isRequired, | ||||
|     tx: nullable(PropTypes.any.isRequired) | ||||
|     tx: nullableProptype(PropTypes.any.isRequired) | ||||
|   } | ||||
| 
 | ||||
|   render () { | ||||
|  | ||||
| @ -17,10 +17,10 @@ | ||||
| import BigNumber from 'bignumber.js'; | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| import { Checkbox, MenuItem } from 'material-ui'; | ||||
| 
 | ||||
| import { isEqual } from 'lodash'; | ||||
| 
 | ||||
| import Form, { Input, InputAddressSelect, Select } from '~/ui/Form'; | ||||
| import Form, { Input, InputAddressSelect, AddressSelect, Select } from '~/ui/Form'; | ||||
| import { nullableProptype } from '~/util/proptypes'; | ||||
| 
 | ||||
| import imageUnknown from '../../../../assets/images/contracts/unknown-64x64.png'; | ||||
| import styles from '../transfer.css'; | ||||
| @ -132,6 +132,8 @@ export default class Details extends Component { | ||||
|     all: PropTypes.bool, | ||||
|     extras: PropTypes.bool, | ||||
|     images: PropTypes.object.isRequired, | ||||
|     sender: PropTypes.string, | ||||
|     senderError: PropTypes.string, | ||||
|     recipient: PropTypes.string, | ||||
|     recipientError: PropTypes.string, | ||||
|     tag: PropTypes.string, | ||||
| @ -139,8 +141,15 @@ export default class Details extends Component { | ||||
|     totalError: PropTypes.string, | ||||
|     value: PropTypes.string, | ||||
|     valueError: PropTypes.string, | ||||
|     onChange: PropTypes.func.isRequired | ||||
|   } | ||||
|     onChange: PropTypes.func.isRequired, | ||||
|     wallet: PropTypes.object, | ||||
|     senders: nullableProptype(PropTypes.object) | ||||
|   }; | ||||
| 
 | ||||
|   static defaultProps = { | ||||
|     wallet: null, | ||||
|     senders: null | ||||
|   }; | ||||
| 
 | ||||
|   render () { | ||||
|     const { all, extras, tag, total, totalError, value, valueError } = this.props; | ||||
| @ -149,6 +158,7 @@ export default class Details extends Component { | ||||
|     return ( | ||||
|       <Form> | ||||
|         { this.renderTokenSelect() } | ||||
|         { this.renderFromAddress() } | ||||
|         { this.renderToAddress() } | ||||
|         <div className={ styles.columns }> | ||||
|           <div> | ||||
| @ -179,6 +189,7 @@ export default class Details extends Component { | ||||
|               </div> | ||||
|             </Input> | ||||
|           </div> | ||||
| 
 | ||||
|           <div> | ||||
|             <Checkbox | ||||
|               checked={ extras } | ||||
| @ -191,6 +202,27 @@ export default class Details extends Component { | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   renderFromAddress () { | ||||
|     const { sender, senderError, senders } = this.props; | ||||
| 
 | ||||
|     if (!senders) { | ||||
|       return null; | ||||
|     } | ||||
| 
 | ||||
|     return ( | ||||
|       <div className={ styles.address }> | ||||
|         <AddressSelect | ||||
|           accounts={ senders } | ||||
|           error={ senderError } | ||||
|           label='sender address' | ||||
|           hint='the sender address' | ||||
|           value={ sender } | ||||
|           onChange={ this.onEditSender } | ||||
|         /> | ||||
|       </div> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   renderToAddress () { | ||||
|     const { recipient, recipientError } = this.props; | ||||
| 
 | ||||
| @ -223,6 +255,10 @@ export default class Details extends Component { | ||||
|     this.props.onChange('tag', tag); | ||||
|   } | ||||
| 
 | ||||
|   onEditSender = (event, sender) => { | ||||
|     this.props.onChange('sender', sender); | ||||
|   } | ||||
| 
 | ||||
|   onEditRecipient = (event, recipient) => { | ||||
|     this.props.onChange('recipient', recipient); | ||||
|   } | ||||
|  | ||||
| @ -16,7 +16,11 @@ | ||||
| 
 | ||||
| import { observable, computed, action, transaction } from 'mobx'; | ||||
| import BigNumber from 'bignumber.js'; | ||||
| import { uniq } from 'lodash'; | ||||
| 
 | ||||
| import { wallet as walletAbi } from '~/contracts/abi'; | ||||
| 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'; | ||||
| @ -33,28 +37,37 @@ const STAGES_EXTRA = [TITLES.transfer, TITLES.extras, TITLES.sending, TITLES.com | ||||
| 
 | ||||
| export default class TransferStore { | ||||
|   @observable stage = 0; | ||||
|   @observable data = ''; | ||||
|   @observable dataError = null; | ||||
|   @observable extras = false; | ||||
|   @observable gas = DEFAULT_GAS; | ||||
|   @observable gasEst = '0'; | ||||
|   @observable gasError = null; | ||||
|   @observable gasLimitError = null; | ||||
|   @observable gasPrice = DEFAULT_GASPRICE; | ||||
|   @observable gasPriceError = null; | ||||
|   @observable recipient = ''; | ||||
|   @observable recipientError = ERRORS.requireRecipient; | ||||
|   @observable valueAll = false; | ||||
|   @observable sending = false; | ||||
|   @observable tag = 'ETH'; | ||||
|   @observable total = '0.0'; | ||||
|   @observable totalError = null; | ||||
|   @observable value = '0.0'; | ||||
|   @observable valueAll = false; | ||||
|   @observable valueError = null; | ||||
|   @observable isEth = true; | ||||
|   @observable busyState = null; | ||||
|   @observable rejected = false; | ||||
| 
 | ||||
|   @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; | ||||
| 
 | ||||
|   @observable sender = ''; | ||||
|   @observable senderError = null; | ||||
| 
 | ||||
|   @observable total = '0.0'; | ||||
|   @observable totalError = null; | ||||
| 
 | ||||
|   @observable value = '0.0'; | ||||
|   @observable valueError = null; | ||||
| 
 | ||||
|   gasPriceHistogram = {}; | ||||
| 
 | ||||
|   account = null; | ||||
| @ -62,6 +75,12 @@ export default class TransferStore { | ||||
|   gasLimit = null; | ||||
|   onClose = null; | ||||
| 
 | ||||
|   senders = null; | ||||
|   sendersBalances = null; | ||||
| 
 | ||||
|   isWallet = false; | ||||
|   wallet = null; | ||||
| 
 | ||||
|   @computed get steps () { | ||||
|     const steps = [].concat(this.extras ? STAGES_EXTRA : STAGES_BASIC); | ||||
| 
 | ||||
| @ -73,7 +92,7 @@ export default class TransferStore { | ||||
|   } | ||||
| 
 | ||||
|   @computed get isValid () { | ||||
|     const detailsValid = !this.recipientError && !this.valueError && !this.totalError; | ||||
|     const detailsValid = !this.recipientError && !this.valueError && !this.totalError && !this.senderError; | ||||
|     const extrasValid = !this.gasError && !this.gasPriceError && !this.totalError; | ||||
|     const verifyValid = !this.passwordError; | ||||
| 
 | ||||
| @ -89,15 +108,32 @@ export default class TransferStore { | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   get token () { | ||||
|     return this.balance.tokens.find((balance) => balance.token.tag === this.tag).token; | ||||
|   } | ||||
| 
 | ||||
|   constructor (api, props) { | ||||
|     this.api = api; | ||||
| 
 | ||||
|     const { account, balance, gasLimit, onClose } = props; | ||||
|     const { account, balance, gasLimit, senders, onClose, newError, sendersBalances } = props; | ||||
| 
 | ||||
|     this.account = account; | ||||
|     this.balance = balance; | ||||
|     this.gasLimit = gasLimit; | ||||
|     this.onClose = onClose; | ||||
|     this.isWallet = account && account.wallet; | ||||
|     this.newError = newError; | ||||
| 
 | ||||
|     if (this.isWallet) { | ||||
|       this.wallet = props.wallet; | ||||
|       this.walletContract = new Contract(this.api, walletAbi); | ||||
|     } | ||||
| 
 | ||||
|     if (senders) { | ||||
|       this.senders = senders; | ||||
|       this.sendersBalances = sendersBalances; | ||||
|       this.senderError = ERRORS.requireSender; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @action onNext = () => { | ||||
| @ -133,6 +169,9 @@ export default class TransferStore { | ||||
|       case 'recipient': | ||||
|         return this._onUpdateRecipient(value); | ||||
| 
 | ||||
|       case 'sender': | ||||
|         return this._onUpdateSender(value); | ||||
| 
 | ||||
|       case 'tag': | ||||
|         return this._onUpdateTag(value); | ||||
| 
 | ||||
| @ -165,9 +204,8 @@ export default class TransferStore { | ||||
|     this.onNext(); | ||||
|     this.sending = true; | ||||
| 
 | ||||
|     const promise = this.isEth ? this._sendEth() : this._sendToken(); | ||||
| 
 | ||||
|     promise | ||||
|     this | ||||
|       .send() | ||||
|       .then((requestId) => { | ||||
|         this.busyState = 'Waiting for authorization in the Parity Signer'; | ||||
| 
 | ||||
| @ -190,6 +228,10 @@ export default class TransferStore { | ||||
|           this.txhash = txhash; | ||||
|           this.busyState = 'Your transaction has been posted to the network'; | ||||
|         }); | ||||
| 
 | ||||
|         if (this.isWallet) { | ||||
|           return this._attachWalletOperation(txhash); | ||||
|         } | ||||
|       }) | ||||
|       .catch((error) => { | ||||
|         this.sending = false; | ||||
| @ -197,6 +239,34 @@ export default class TransferStore { | ||||
|       }); | ||||
|   } | ||||
| 
 | ||||
|   @action _attachWalletOperation = (txhash) => { | ||||
|     let ethSubscriptionId = null; | ||||
| 
 | ||||
|     return this.api.subscribe('eth_blockNumber', () => { | ||||
|       this.api.eth | ||||
|         .getTransactionReceipt(txhash) | ||||
|         .then((tx) => { | ||||
|           if (!tx) { | ||||
|             return; | ||||
|           } | ||||
| 
 | ||||
|           const logs = this.walletContract.parseEventLogs(tx.logs); | ||||
|           const operations = uniq(logs | ||||
|             .filter((log) => log && log.params && log.params.operation) | ||||
|             .map((log) => bytesToHex(log.params.operation.value))); | ||||
| 
 | ||||
|           if (operations.length > 0) { | ||||
|             this.operation = operations[0]; | ||||
|           } | ||||
| 
 | ||||
|           this.api.unsubscribe(ethSubscriptionId); | ||||
|           ethSubscriptionId = null; | ||||
|         }); | ||||
|     }).then((subId) => { | ||||
|       ethSubscriptionId = subId; | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   @action _onUpdateAll = (valueAll) => { | ||||
|     this.valueAll = valueAll; | ||||
|     this.recalculateGas(); | ||||
| @ -250,6 +320,23 @@ export default class TransferStore { | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   @action _onUpdateSender = (sender) => { | ||||
|     let senderError = null; | ||||
| 
 | ||||
|     if (!sender || !sender.length) { | ||||
|       senderError = ERRORS.requireSender; | ||||
|     } else if (!this.api.util.isAddressValid(sender)) { | ||||
|       senderError = ERRORS.invalidAddress; | ||||
|     } | ||||
| 
 | ||||
|     transaction(() => { | ||||
|       this.sender = sender; | ||||
|       this.senderError = senderError; | ||||
| 
 | ||||
|       this.recalculateGas(); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   @action _onUpdateTag = (tag) => { | ||||
|     transaction(() => { | ||||
|       this.tag = tag; | ||||
| @ -280,9 +367,8 @@ export default class TransferStore { | ||||
|       return this.recalculate(); | ||||
|     } | ||||
| 
 | ||||
|     const promise = this.isEth ? this._estimateGasEth() : this._estimateGasToken(); | ||||
| 
 | ||||
|     promise | ||||
|     this | ||||
|       .estimateGas() | ||||
|       .then((gasEst) => { | ||||
|         let gas = gasEst; | ||||
|         let gasLimitError = null; | ||||
| @ -312,19 +398,29 @@ export default class TransferStore { | ||||
|   } | ||||
| 
 | ||||
|   @action recalculate = () => { | ||||
|     const { account, balance } = this; | ||||
|     const { account } = this; | ||||
| 
 | ||||
|     if (!account || !balance) { | ||||
|     if (!account || !this.balance) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     const balance = this.senders | ||||
|       ? this.sendersBalances[this.sender] | ||||
|       : this.balance; | ||||
| 
 | ||||
|     if (!balance) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     const { gas, gasPrice, tag, valueAll, isEth } = this; | ||||
| 
 | ||||
|     const gasTotal = new BigNumber(gasPrice || 0).mul(new BigNumber(gas || 0)); | ||||
|     const balance_ = balance.tokens.find((b) => tag === b.token.tag); | ||||
| 
 | ||||
|     const availableEth = new BigNumber(balance.tokens[0].value); | ||||
|     const available = new BigNumber(balance_.value); | ||||
|     const format = new BigNumber(balance_.token.format || 1); | ||||
| 
 | ||||
|     const senderBalance = this.balance.tokens.find((b) => tag === b.token.tag); | ||||
|     const available = new BigNumber(senderBalance.value); | ||||
|     const format = new BigNumber(senderBalance.token.format || 1); | ||||
| 
 | ||||
|     let { value, valueError } = this; | ||||
|     let totalEth = gasTotal; | ||||
| @ -361,74 +457,99 @@ export default class TransferStore { | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   _sendEth () { | ||||
|     const { account, data, gas, gasPrice, recipient, value } = this; | ||||
|   send () { | ||||
|     const { options, values } = this._getTransferParams(); | ||||
|     return this._getTransferMethod().postTransaction(options, values); | ||||
|   } | ||||
| 
 | ||||
|     const options = { | ||||
|       from: account.address, | ||||
|       to: recipient, | ||||
|       gas, | ||||
|       gasPrice, | ||||
|       value: this.api.util.toWei(value || 0) | ||||
|     }; | ||||
|   _estimateGas (forceToken = false) { | ||||
|     const { options, values } = this._getTransferParams(true, forceToken); | ||||
|     return this._getTransferMethod(true, forceToken).estimateGas(options, values); | ||||
|   } | ||||
| 
 | ||||
|     if (data && data.length) { | ||||
|       options.data = data; | ||||
|   estimateGas () { | ||||
|     if (this.isEth || !this.isWallet) { | ||||
|       return this._estimateGas(); | ||||
|     } | ||||
| 
 | ||||
|     return this.api.parity.postTransaction(options); | ||||
|     return Promise | ||||
|       .all([ | ||||
|         this._estimateGas(true), | ||||
|         this._estimateGas() | ||||
|       ]) | ||||
|       .then((results) => results[0].plus(results[1])); | ||||
|   } | ||||
| 
 | ||||
|   _sendToken () { | ||||
|     const { account, balance } = this; | ||||
|     const { gas, gasPrice, recipient, value, tag } = this; | ||||
|   _getTransferMethod (gas = false, forceToken = false) { | ||||
|     const { isEth, isWallet } = this; | ||||
| 
 | ||||
|     const token = balance.tokens.find((balance) => balance.token.tag === tag).token; | ||||
| 
 | ||||
|     return token.contract.instance.transfer | ||||
|       .postTransaction({ | ||||
|         from: account.address, | ||||
|         to: token.address, | ||||
|         gas, | ||||
|         gasPrice | ||||
|       }, [ | ||||
|         recipient, | ||||
|         new BigNumber(value).mul(token.format).toFixed(0) | ||||
|       ]); | ||||
|   } | ||||
| 
 | ||||
|   _estimateGasToken () { | ||||
|     const { account, balance } = this; | ||||
|     const { recipient, value, tag } = this; | ||||
| 
 | ||||
|     const token = balance.tokens.find((balance) => balance.token.tag === tag).token; | ||||
| 
 | ||||
|     return token.contract.instance.transfer | ||||
|       .estimateGas({ | ||||
|         gas: MAX_GAS_ESTIMATION, | ||||
|         from: account.address, | ||||
|         to: token.address | ||||
|       }, [ | ||||
|         recipient, | ||||
|         new BigNumber(value || 0).mul(token.format).toFixed(0) | ||||
|       ]); | ||||
|   } | ||||
| 
 | ||||
|   _estimateGasEth () { | ||||
|     const { account, data, recipient, value } = this; | ||||
| 
 | ||||
|     const options = { | ||||
|       gas: MAX_GAS_ESTIMATION, | ||||
|       from: account.address, | ||||
|       to: recipient, | ||||
|       value: this.api.util.toWei(value || 0) | ||||
|     }; | ||||
| 
 | ||||
|     if (data && data.length) { | ||||
|       options.data = data; | ||||
|     if (isEth && !isWallet && !forceToken) { | ||||
|       return gas ? this.api.eth : this.api.parity; | ||||
|     } | ||||
| 
 | ||||
|     return this.api.eth.estimateGas(options); | ||||
|     if (isWallet && !forceToken) { | ||||
|       return this.wallet.instance.execute; | ||||
|     } | ||||
| 
 | ||||
|     return this.token.contract.instance.transfer; | ||||
|   } | ||||
| 
 | ||||
|   _getData (gas = false) { | ||||
|     const { isEth, isWallet } = this; | ||||
| 
 | ||||
|     if (!isWallet || isEth) { | ||||
|       return this.data && this.data.length ? this.data : ''; | ||||
|     } | ||||
| 
 | ||||
|     const func = this._getTransferMethod(gas, true); | ||||
|     const { options, values } = this._getTransferParams(gas, true); | ||||
| 
 | ||||
|     return this.token.contract.getCallData(func, options, values); | ||||
|   } | ||||
| 
 | ||||
|   _getTransferParams (gas = false, forceToken = false) { | ||||
|     const { isEth, isWallet } = this; | ||||
| 
 | ||||
|     const to = (isEth && !isWallet) ? this.recipient | ||||
|       : (this.isWallet ? this.wallet.address : this.token.address); | ||||
| 
 | ||||
|     const options = { | ||||
|       from: this.sender || this.account.address, | ||||
|       to | ||||
|     }; | ||||
| 
 | ||||
|     if (!gas) { | ||||
|       options.gas = this.gas; | ||||
|       options.gasPrice = this.gasPrice; | ||||
|     } else { | ||||
|       options.gas = MAX_GAS_ESTIMATION; | ||||
|     } | ||||
| 
 | ||||
|     if (isEth && !isWallet && !forceToken) { | ||||
|       options.value = this.api.util.toWei(this.value || 0); | ||||
|       options.data = this._getData(gas); | ||||
| 
 | ||||
|       return { options, values: [] }; | ||||
|     } | ||||
| 
 | ||||
|     if (isWallet && !forceToken) { | ||||
|       const to = isEth ? this.recipient : this.token.contract.address; | ||||
|       const value = isEth ? this.api.util.toWei(this.value || 0) : new BigNumber(0); | ||||
| 
 | ||||
|       const values = [ | ||||
|         to, value, | ||||
|         this._getData(gas) | ||||
|       ]; | ||||
| 
 | ||||
|       return { options, values }; | ||||
|     } | ||||
| 
 | ||||
|     const values = [ | ||||
|       this.recipient, | ||||
|       new BigNumber(this.value || 0).mul(this.token.format).toFixed(0) | ||||
|     ]; | ||||
| 
 | ||||
|     return { options, values }; | ||||
|   } | ||||
| 
 | ||||
|   _validatePositiveNumber (num) { | ||||
|  | ||||
| @ -18,6 +18,7 @@ import React, { Component, PropTypes } from 'react'; | ||||
| import { connect } from 'react-redux'; | ||||
| import { bindActionCreators } from 'redux'; | ||||
| import { observer } from 'mobx-react'; | ||||
| import { pick } from 'lodash'; | ||||
| 
 | ||||
| import ActionDoneAll from 'material-ui/svg-icons/action/done-all'; | ||||
| import ContentClear from 'material-ui/svg-icons/content/clear'; | ||||
| @ -25,7 +26,8 @@ import NavigationArrowBack from 'material-ui/svg-icons/navigation/arrow-back'; | ||||
| import NavigationArrowForward from 'material-ui/svg-icons/navigation/arrow-forward'; | ||||
| 
 | ||||
| import { newError } from '~/ui/Errors/actions'; | ||||
| import { BusyStep, CompletedStep, Button, IdentityIcon, Modal, TxHash } from '~/ui'; | ||||
| import { BusyStep, CompletedStep, Button, IdentityIcon, Modal, TxHash, Input } from '~/ui'; | ||||
| import { nullableProptype } from '~/util/proptypes'; | ||||
| 
 | ||||
| import Details from './Details'; | ||||
| import Extras from './Extras'; | ||||
| @ -44,9 +46,11 @@ class Transfer extends Component { | ||||
|     gasLimit: PropTypes.object.isRequired, | ||||
|     images: PropTypes.object.isRequired, | ||||
| 
 | ||||
|     senders: nullableProptype(PropTypes.object), | ||||
|     sendersBalances: nullableProptype(PropTypes.object), | ||||
|     account: PropTypes.object, | ||||
|     balance: PropTypes.object, | ||||
|     balances: PropTypes.object, | ||||
|     wallet: PropTypes.object, | ||||
|     onClose: PropTypes.func | ||||
|   } | ||||
| 
 | ||||
| @ -130,14 +134,33 @@ class Transfer extends Component { | ||||
|     return ( | ||||
|       <CompletedStep> | ||||
|         <TxHash hash={ txhash } /> | ||||
|         { | ||||
|           this.store.operation | ||||
|           ? ( | ||||
|             <div> | ||||
|               <br /> | ||||
|               <p> | ||||
|                 This transaction needs confirmation from other owners. | ||||
|                 <Input | ||||
|                   style={ { width: '50%', margin: '0 auto' } } | ||||
|                   value={ this.store.operation } | ||||
|                   label='operation hash' | ||||
|                   readOnly | ||||
|                   allowCopy | ||||
|                 /> | ||||
|               </p> | ||||
|             </div> | ||||
|           ) | ||||
|           : null | ||||
|         } | ||||
|       </CompletedStep> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   renderDetailsPage () { | ||||
|     const { account, balance, images } = this.props; | ||||
|     const { valueAll, extras, recipient, recipientError, tag } = this.store; | ||||
|     const { total, totalError, value, valueError } = this.store; | ||||
|     const { account, balance, images, senders } = this.props; | ||||
|     const { valueAll, extras, recipient, recipientError, sender, senderError } = this.store; | ||||
|     const { tag, total, totalError, value, valueError } = this.store; | ||||
| 
 | ||||
|     return ( | ||||
|       <Details | ||||
| @ -146,14 +169,19 @@ class Transfer extends Component { | ||||
|         balance={ balance } | ||||
|         extras={ extras } | ||||
|         images={ images } | ||||
|         senders={ senders } | ||||
|         recipient={ recipient } | ||||
|         recipientError={ recipientError } | ||||
|         sender={ sender } | ||||
|         senderError={ senderError } | ||||
|         tag={ tag } | ||||
|         total={ total } | ||||
|         totalError={ totalError } | ||||
|         value={ value } | ||||
|         valueError={ valueError } | ||||
|         onChange={ this.store.onUpdateDetails } /> | ||||
|         onChange={ this.store.onUpdateDetails } | ||||
|         wallet={ account.wallet && this.props.wallet } | ||||
|       /> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
| @ -249,9 +277,30 @@ class Transfer extends Component { | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| function mapStateToProps (state) { | ||||
|   const { gasLimit } = state.nodeStatus; | ||||
|   return { gasLimit }; | ||||
| function mapStateToProps (initState, initProps) { | ||||
|   const { address } = initProps.account; | ||||
| 
 | ||||
|   const isWallet = initProps.account && initProps.account.wallet; | ||||
|   const wallet = isWallet | ||||
|     ? initState.wallet.wallets[address] | ||||
|     : null; | ||||
| 
 | ||||
|   const senders = isWallet | ||||
|     ? Object | ||||
|       .values(initState.personal.accounts) | ||||
|       .filter((account) => wallet.owners.includes(account.address)) | ||||
|       .reduce((accounts, account) => { | ||||
|         accounts[account.address] = account; | ||||
|         return accounts; | ||||
|       }, {}) | ||||
|     : null; | ||||
| 
 | ||||
|   return (state) => { | ||||
|     const { gasLimit } = state.nodeStatus; | ||||
|     const sendersBalances = senders ? pick(state.balances.balances, Object.keys(senders)) : null; | ||||
| 
 | ||||
|     return { gasLimit, wallet, senders, sendersBalances }; | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| function mapDispatchToProps (dispatch) { | ||||
|  | ||||
| @ -17,6 +17,7 @@ | ||||
| import AddAddress from './AddAddress'; | ||||
| import AddContract from './AddContract'; | ||||
| import CreateAccount from './CreateAccount'; | ||||
| import CreateWallet from './CreateWallet'; | ||||
| import DeleteAccount from './DeleteAccount'; | ||||
| import DeployContract from './DeployContract'; | ||||
| import EditMeta from './EditMeta'; | ||||
| @ -33,6 +34,7 @@ export { | ||||
|   AddAddress, | ||||
|   AddContract, | ||||
|   CreateAccount, | ||||
|   CreateWallet, | ||||
|   DeleteAccount, | ||||
|   DeployContract, | ||||
|   EditMeta, | ||||
|  | ||||
| @ -113,7 +113,7 @@ export function fetchTokens (_tokenIds) { | ||||
| export function fetchBalances (_addresses) { | ||||
|   return (dispatch, getState) => { | ||||
|     const { api, personal } = getState(); | ||||
|     const { visibleAccounts } = personal; | ||||
|     const { visibleAccounts, accounts } = personal; | ||||
| 
 | ||||
|     const addresses = uniq(_addresses || visibleAccounts || []); | ||||
| 
 | ||||
| @ -123,12 +123,14 @@ export function fetchBalances (_addresses) { | ||||
| 
 | ||||
|     const fullFetch = addresses.length === 1; | ||||
| 
 | ||||
|     const fetchedAddresses = uniq(addresses.concat(Object.keys(accounts))); | ||||
| 
 | ||||
|     return Promise | ||||
|       .all(addresses.map((addr) => fetchAccount(addr, api, fullFetch))) | ||||
|       .all(fetchedAddresses.map((addr) => fetchAccount(addr, api, fullFetch))) | ||||
|       .then((accountsBalances) => { | ||||
|         const balances = {}; | ||||
| 
 | ||||
|         addresses.forEach((addr, idx) => { | ||||
|         fetchedAddresses.forEach((addr, idx) => { | ||||
|           balances[addr] = accountsBalances[idx]; | ||||
|         }); | ||||
| 
 | ||||
|  | ||||
| @ -28,3 +28,4 @@ export statusReducer from './statusReducer'; | ||||
| export blockchainReducer from './blockchainReducer'; | ||||
| export compilerReducer from './compilerReducer'; | ||||
| export snackbarReducer from './snackbarReducer'; | ||||
| export walletReducer from './walletReducer'; | ||||
|  | ||||
| @ -17,11 +17,45 @@ | ||||
| import { isEqual } from 'lodash'; | ||||
| 
 | ||||
| import { fetchBalances } from './balancesActions'; | ||||
| import { attachWallets } from './walletActions'; | ||||
| 
 | ||||
| export function personalAccountsInfo (accountsInfo) { | ||||
|   const accounts = {}; | ||||
|   const contacts = {}; | ||||
|   const contracts = {}; | ||||
|   const wallets = {}; | ||||
| 
 | ||||
|   Object.keys(accountsInfo || {}) | ||||
|     .map((address) => Object.assign({}, accountsInfo[address], { address })) | ||||
|     .filter((account) => !account.meta.deleted) | ||||
|     .forEach((account) => { | ||||
|       if (account.uuid) { | ||||
|         accounts[account.address] = account; | ||||
|       } else if (account.meta.wallet) { | ||||
|         account.wallet = true; | ||||
|         wallets[account.address] = account; | ||||
|       } else if (account.meta.contract) { | ||||
|         contracts[account.address] = account; | ||||
|       } else { | ||||
|         contacts[account.address] = account; | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
|   return (dispatch) => { | ||||
|     const data = { | ||||
|       accountsInfo, | ||||
|       accounts, contacts, contracts, wallets | ||||
|     }; | ||||
| 
 | ||||
|     dispatch(_personalAccountsInfo(data)); | ||||
|     dispatch(attachWallets(wallets)); | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| function _personalAccountsInfo (data) { | ||||
|   return { | ||||
|     type: 'personalAccountsInfo', | ||||
|     accountsInfo | ||||
|     ...data | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -25,28 +25,14 @@ const initialState = { | ||||
|   hasContacts: false, | ||||
|   contracts: {}, | ||||
|   hasContracts: false, | ||||
|   wallet: {}, | ||||
|   hasWallets: false, | ||||
|   visibleAccounts: [] | ||||
| }; | ||||
| 
 | ||||
| export default handleActions({ | ||||
|   personalAccountsInfo (state, action) { | ||||
|     const { accountsInfo } = action; | ||||
|     const accounts = {}; | ||||
|     const contacts = {}; | ||||
|     const contracts = {}; | ||||
| 
 | ||||
|     Object.keys(accountsInfo || {}) | ||||
|       .map((address) => Object.assign({}, accountsInfo[address], { address })) | ||||
|       .filter((account) => !account.meta.deleted) | ||||
|       .forEach((account) => { | ||||
|         if (account.uuid) { | ||||
|           accounts[account.address] = account; | ||||
|         } else if (account.meta.contract) { | ||||
|           contracts[account.address] = account; | ||||
|         } else { | ||||
|           contacts[account.address] = account; | ||||
|         } | ||||
|       }); | ||||
|     const { accountsInfo, accounts, contacts, contracts, wallets } = action; | ||||
| 
 | ||||
|     return Object.assign({}, state, { | ||||
|       accountsInfo, | ||||
| @ -55,7 +41,9 @@ export default handleActions({ | ||||
|       contacts, | ||||
|       hasContacts: Object.keys(contacts).length !== 0, | ||||
|       contracts, | ||||
|       hasContracts: Object.keys(contracts).length !== 0 | ||||
|       hasContracts: Object.keys(contracts).length !== 0, | ||||
|       wallets, | ||||
|       hasWallets: Object.keys(wallets).length !== 0 | ||||
|     }); | ||||
|   }, | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										503
									
								
								js/src/redux/providers/walletActions.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										503
									
								
								js/src/redux/providers/walletActions.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,503 @@ | ||||
| // 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 { 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 WalletsUtils from '~/util/wallets'; | ||||
| 
 | ||||
| import { newError } from '~/ui/Errors/actions'; | ||||
| 
 | ||||
| const UPDATE_OWNERS = 'owners'; | ||||
| const UPDATE_REQUIRE = 'require'; | ||||
| const UPDATE_DAILYLIMIT = 'dailylimit'; | ||||
| const UPDATE_TRANSACTIONS = 'transactions'; | ||||
| const UPDATE_CONFIRMATIONS = 'confirmations'; | ||||
| 
 | ||||
| export function confirmOperation (address, owner, operation) { | ||||
|   return modifyOperation('confirm', address, owner, operation); | ||||
| } | ||||
| 
 | ||||
| export function revokeOperation (address, owner, operation) { | ||||
|   return modifyOperation('revoke', address, owner, operation); | ||||
| } | ||||
| 
 | ||||
| function modifyOperation (method, address, owner, operation) { | ||||
|   return (dispatch, getState) => { | ||||
|     const { api } = getState(); | ||||
|     const contract = new Contract(api, WALLET_ABI).at(address); | ||||
| 
 | ||||
|     const options = { | ||||
|       from: owner, | ||||
|       gas: MAX_GAS_ESTIMATION | ||||
|     }; | ||||
| 
 | ||||
|     const values = [ operation ]; | ||||
| 
 | ||||
|     dispatch(setOperationPendingState(address, operation, true)); | ||||
| 
 | ||||
|     contract.instance[method] | ||||
|       .estimateGas(options, values) | ||||
|       .then((gas) => { | ||||
|         options.gas = gas; | ||||
|         return contract.instance[method].postTransaction(options, values); | ||||
|       }) | ||||
|       .then((requestId) => { | ||||
|         return api | ||||
|           .pollMethod('parity_checkRequest', requestId) | ||||
|           .catch((e) => { | ||||
|             dispatch(setOperationPendingState(address, operation, false)); | ||||
|             if (e.code === ERROR_CODES.REQUEST_REJECTED) { | ||||
|               return; | ||||
|             } | ||||
| 
 | ||||
|             throw e; | ||||
|           }); | ||||
|       }) | ||||
|       .catch((error) => { | ||||
|         dispatch(setOperationPendingState(address, operation, false)); | ||||
|         dispatch(newError(error)); | ||||
|       }); | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| export function attachWallets (_wallets) { | ||||
|   return (dispatch, getState) => { | ||||
|     const { wallet, api } = getState(); | ||||
| 
 | ||||
|     const prevAddresses = wallet.walletsAddresses; | ||||
|     const nextAddresses = Object.keys(_wallets).map((a) => a.toLowerCase()).sort(); | ||||
| 
 | ||||
|     if (isEqual(prevAddresses, nextAddresses)) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     if (wallet.filterSubId) { | ||||
|       api.eth.uninstallFilter(wallet.filterSubId); | ||||
|     } | ||||
| 
 | ||||
|     if (nextAddresses.length === 0) { | ||||
|       return dispatch(updateWallets({ wallets: {}, walletsAddresses: [], filterSubId: null })); | ||||
|     } | ||||
| 
 | ||||
|     const filterOptions = { | ||||
|       fromBlock: 0, | ||||
|       toBlock: 'latest', | ||||
|       address: nextAddresses | ||||
|     }; | ||||
| 
 | ||||
|     api.eth | ||||
|       .newFilter(filterOptions) | ||||
|       .then((filterId) => { | ||||
|         dispatch(updateWallets({ wallets: _wallets, walletsAddresses: nextAddresses, filterSubId: filterId })); | ||||
|       }) | ||||
|       .catch((error) => { | ||||
|         if (process.env.NODE_ENV === 'production') { | ||||
|           console.error('walletActions::attachWallets', error); | ||||
|         } else { | ||||
|           throw error; | ||||
|         } | ||||
|       }); | ||||
| 
 | ||||
|     fetchWalletsInfo(Object.keys(_wallets))(dispatch, getState); | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| export function load (api) { | ||||
|   return (dispatch, getState) => { | ||||
|     const contract = new Contract(api, WALLET_ABI); | ||||
| 
 | ||||
|     dispatch(setWalletContract(contract)); | ||||
|     api.subscribe('eth_blockNumber', (error) => { | ||||
|       if (error) { | ||||
|         if (process.env.NODE_ENV === 'production') { | ||||
|           return console.error('[eth_blockNumber] walletActions::load', error); | ||||
|         } else { | ||||
|           throw error; | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       const { filterSubId } = getState().wallet; | ||||
| 
 | ||||
|       if (!filterSubId) { | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       api.eth | ||||
|         .getFilterChanges(filterSubId) | ||||
|         .then((logs) => contract.parseEventLogs(logs)) | ||||
|         .then((logs) => { | ||||
|           parseLogs(logs)(dispatch, getState); | ||||
|         }) | ||||
|         .catch((error) => { | ||||
|           if (process.env.NODE_ENV === 'production') { | ||||
|             return console.error('[getFilterChanges] walletActions::load', error); | ||||
|           } else { | ||||
|             throw error; | ||||
|           } | ||||
|         }); | ||||
|     }); | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| function fetchWalletsInfo (updates) { | ||||
|   return (dispatch, getState) => { | ||||
|     if (Array.isArray(updates)) { | ||||
|       const _updates = updates.reduce((updates, address) => { | ||||
|         updates[address] = { | ||||
|           [ UPDATE_OWNERS ]: true, | ||||
|           [ UPDATE_REQUIRE ]: true, | ||||
|           [ UPDATE_DAILYLIMIT ]: true, | ||||
|           [ UPDATE_CONFIRMATIONS ]: true, | ||||
|           [ UPDATE_TRANSACTIONS ]: true, | ||||
|           address | ||||
|         }; | ||||
| 
 | ||||
|         return updates; | ||||
|       }, {}); | ||||
| 
 | ||||
|       return fetchWalletsInfo(_updates)(dispatch, getState); | ||||
|     } | ||||
| 
 | ||||
|     const { api } = getState(); | ||||
|     const _updates = Object.values(updates); | ||||
| 
 | ||||
|     Promise | ||||
|       .all(_updates.map((update) => { | ||||
|         const contract = new Contract(api, WALLET_ABI).at(update.address); | ||||
|         return fetchWalletInfo(contract, update, getState); | ||||
|       })) | ||||
|       .then((updates) => { | ||||
|         dispatch(updateWalletsDetails(updates)); | ||||
|       }) | ||||
|       .catch((error) => { | ||||
|         if (process.env.NODE_ENV === 'production') { | ||||
|           return console.error('walletAction::fetchWalletsInfo', error); | ||||
|         } else { | ||||
|           throw error; | ||||
|         } | ||||
|       }); | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| function fetchWalletInfo (contract, update, getState) { | ||||
|   const promises = []; | ||||
| 
 | ||||
|   if (update[UPDATE_OWNERS]) { | ||||
|     promises.push(fetchWalletOwners(contract)); | ||||
|   } | ||||
| 
 | ||||
|   if (update[UPDATE_REQUIRE]) { | ||||
|     promises.push(fetchWalletRequire(contract)); | ||||
|   } | ||||
| 
 | ||||
|   if (update[UPDATE_DAILYLIMIT]) { | ||||
|     promises.push(fetchWalletDailylimit(contract)); | ||||
|   } | ||||
| 
 | ||||
|   if (update[UPDATE_TRANSACTIONS]) { | ||||
|     promises.push(fetchWalletTransactions(contract)); | ||||
|   } | ||||
| 
 | ||||
|   return Promise | ||||
|     .all(promises) | ||||
|     .then((updates) => { | ||||
|       if (update[UPDATE_CONFIRMATIONS]) { | ||||
|         const ownersUpdate = updates.find((u) => u.key === UPDATE_OWNERS); | ||||
|         const transactionsUpdate = updates.find((u) => u.key === UPDATE_TRANSACTIONS); | ||||
| 
 | ||||
|         const owners = ownersUpdate && ownersUpdate.value || null; | ||||
|         const transactions = transactionsUpdate && transactionsUpdate.value || null; | ||||
| 
 | ||||
|         return fetchWalletConfirmations(contract, owners, transactions, getState) | ||||
|           .then((update) => { | ||||
|             updates.push(update); | ||||
|             return updates; | ||||
|           }); | ||||
|       } | ||||
| 
 | ||||
|       return updates; | ||||
|     }) | ||||
|     .then((updates) => { | ||||
|       const wallet = { address: update.address }; | ||||
| 
 | ||||
|       updates.forEach((update) => { | ||||
|         wallet[update.key] = update.value; | ||||
|       }); | ||||
| 
 | ||||
|       return wallet; | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| function fetchWalletTransactions (contract) { | ||||
|   return WalletsUtils | ||||
|     .fetchTransactions(contract) | ||||
|     .then((transactions) => { | ||||
|       return { | ||||
|         key: UPDATE_TRANSACTIONS, | ||||
|         value: transactions | ||||
|       }; | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| function fetchWalletOwners (contract) { | ||||
|   return WalletsUtils | ||||
|     .fetchOwners(contract) | ||||
|     .then((value) => { | ||||
|       return { | ||||
|         key: UPDATE_OWNERS, | ||||
|         value | ||||
|       }; | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| function fetchWalletRequire (contract) { | ||||
|   return WalletsUtils | ||||
|     .fetchRequire(contract) | ||||
|     .then((value) => { | ||||
|       return { | ||||
|         key: UPDATE_REQUIRE, | ||||
|         value | ||||
|       }; | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| function fetchWalletDailylimit (contract) { | ||||
|   return WalletsUtils | ||||
|     .fetchDailylimit(contract) | ||||
|     .then((value) => { | ||||
|       return { | ||||
|         key: UPDATE_DAILYLIMIT, | ||||
|         value | ||||
|       }; | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| function fetchWalletConfirmations (contract, _owners = null, _transactions = null, getState) { | ||||
|   const walletInstance = contract.instance; | ||||
| 
 | ||||
|   const wallet = getState().wallet.wallets[contract.address]; | ||||
| 
 | ||||
|   const owners = _owners || (wallet && wallet.owners) || null; | ||||
|   const transactions = _transactions || (wallet && wallet.transactions) || null; | ||||
| 
 | ||||
|   return walletInstance | ||||
|     .ConfirmationNeeded | ||||
|     .getAllLogs() | ||||
|     .then((logs) => { | ||||
|       return logs.sort((logA, logB) => { | ||||
|         const comp = logA.blockNumber.comparedTo(logB.blockNumber); | ||||
| 
 | ||||
|         if (comp !== 0) { | ||||
|           return comp; | ||||
|         } | ||||
| 
 | ||||
|         return logA.transactionIndex.comparedTo(logB.transactionIndex); | ||||
|       }); | ||||
|     }) | ||||
|     .then((logs) => { | ||||
|       return logs.map((log) => ({ | ||||
|         initiator: log.params.initiator.value, | ||||
|         to: log.params.to.value, | ||||
|         data: log.params.data.value, | ||||
|         value: log.params.value.value, | ||||
|         operation: bytesToHex(log.params.operation.value), | ||||
|         transactionHash: log.transactionHash, | ||||
|         blockNumber: log.blockNumber, | ||||
|         confirmedBy: [] | ||||
|       })); | ||||
|     }) | ||||
|     .then((confirmations) => { | ||||
|       if (confirmations.length === 0) { | ||||
|         return confirmations; | ||||
|       } | ||||
| 
 | ||||
|       if (transactions) { | ||||
|         const operations = transactions | ||||
|           .filter((t) => t.operation) | ||||
|           .map((t) => t.operation); | ||||
| 
 | ||||
|         return confirmations.filter((confirmation) => { | ||||
|           return !operations.includes(confirmation.operation); | ||||
|         }); | ||||
|       } | ||||
| 
 | ||||
|       return confirmations; | ||||
|     }) | ||||
|     .then((confirmations) => { | ||||
|       if (confirmations.length === 0) { | ||||
|         return confirmations; | ||||
|       } | ||||
| 
 | ||||
|       const operations = confirmations.map((conf) => conf.operation); | ||||
|       return Promise | ||||
|         .all(operations.map((op) => fetchOperationConfirmations(contract, op, owners))) | ||||
|         .then((confirmedBys) => { | ||||
|           confirmations.forEach((_, index) => { | ||||
|             confirmations[index].confirmedBy = confirmedBys[index]; | ||||
|           }); | ||||
| 
 | ||||
|           return confirmations; | ||||
|         }); | ||||
|     }) | ||||
|     .then((confirmations) => { | ||||
|       return { | ||||
|         key: UPDATE_CONFIRMATIONS, | ||||
|         value: confirmations | ||||
|       }; | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| function fetchOperationConfirmations (contract, operation, owners = null) { | ||||
|   if (!owners) { | ||||
|     console.warn('[fetchOperationConfirmations] try to provide the owners for the Wallet', contract.address); | ||||
|   } | ||||
| 
 | ||||
|   const walletInstance = contract.instance; | ||||
| 
 | ||||
|   const promise = owners | ||||
|     ? Promise.resolve({ value: owners }) | ||||
|     : fetchWalletOwners(contract); | ||||
| 
 | ||||
|   return promise | ||||
|     .then((result) => { | ||||
|       const owners = result.value; | ||||
|       return Promise | ||||
|         .all(owners.map((owner) => walletInstance.hasConfirmed.call({}, [ operation, owner ]))) | ||||
|         .then((data) => { | ||||
|           return owners.filter((owner, index) => data[index]); | ||||
|         }); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| function parseLogs (logs) { | ||||
|   return (dispatch, getState) => { | ||||
|     if (!logs || logs.length === 0) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     const { wallet } = getState(); | ||||
|     const { contract } = wallet; | ||||
|     const walletInstance = contract.instance; | ||||
| 
 | ||||
|     const signatures = { | ||||
|       OwnerChanged: toHex(walletInstance.OwnerChanged.signature), | ||||
|       OwnerAdded: toHex(walletInstance.OwnerAdded.signature), | ||||
|       OwnerRemoved: toHex(walletInstance.OwnerRemoved.signature), | ||||
|       RequirementChanged: toHex(walletInstance.RequirementChanged.signature), | ||||
|       Confirmation: toHex(walletInstance.Confirmation.signature), | ||||
|       Revoke: toHex(walletInstance.Revoke.signature), | ||||
|       Deposit: toHex(walletInstance.Deposit.signature), | ||||
|       SingleTransact: toHex(walletInstance.SingleTransact.signature), | ||||
|       MultiTransact: toHex(walletInstance.MultiTransact.signature), | ||||
|       ConfirmationNeeded: toHex(walletInstance.ConfirmationNeeded.signature) | ||||
|     }; | ||||
| 
 | ||||
|     const updates = {}; | ||||
| 
 | ||||
|     logs.forEach((log) => { | ||||
|       const { address, topics } = log; | ||||
|       const eventSignature = toHex(topics[0]); | ||||
|       const prev = updates[address] || { address }; | ||||
| 
 | ||||
|       switch (eventSignature) { | ||||
|         case signatures.OwnerChanged: | ||||
|         case signatures.OwnerAdded: | ||||
|         case signatures.OwnerRemoved: | ||||
|           updates[address] = { | ||||
|             ...prev, | ||||
|             [ UPDATE_OWNERS ]: true | ||||
|           }; | ||||
|           return; | ||||
| 
 | ||||
|         case signatures.RequirementChanged: | ||||
|           updates[address] = { | ||||
|             ...prev, | ||||
|             [ UPDATE_REQUIRE ]: true | ||||
|           }; | ||||
|           return; | ||||
| 
 | ||||
|         case signatures.Confirmation: | ||||
|         case signatures.Revoke: | ||||
|           const operation = log.params.operation.value; | ||||
| 
 | ||||
|           updates[address] = { | ||||
|             ...prev, | ||||
|             [ UPDATE_CONFIRMATIONS ]: uniq( | ||||
|               (prev.operations || []).concat(operation) | ||||
|             ) | ||||
|           }; | ||||
|           return; | ||||
| 
 | ||||
|         case signatures.Deposit: | ||||
|         case signatures.SingleTransact: | ||||
|         case signatures.MultiTransact: | ||||
|           updates[address] = { | ||||
|             ...prev, | ||||
|             [ UPDATE_TRANSACTIONS ]: true | ||||
|           }; | ||||
|           return; | ||||
| 
 | ||||
|         case signatures.ConfirmationNeeded: | ||||
|           const op = log.params.operation.value; | ||||
| 
 | ||||
|           updates[address] = { | ||||
|             ...prev, | ||||
|             [ UPDATE_CONFIRMATIONS ]: uniq( | ||||
|               (prev.operations || []).concat(op) | ||||
|             ) | ||||
|           }; | ||||
|           return; | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
|     fetchWalletsInfo(updates)(dispatch, getState); | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| function setOperationPendingState (address, operation, isPending) { | ||||
|   return { | ||||
|     type: 'setOperationPendingState', | ||||
|     address, operation, isPending | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| function updateWalletsDetails (wallets) { | ||||
|   return { | ||||
|     type: 'updateWalletsDetails', | ||||
|     wallets | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| function setWalletContract (contract) { | ||||
|   return { | ||||
|     type: 'setWalletContract', | ||||
|     contract | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| function updateWallets (data) { | ||||
|   return { | ||||
|     type: 'updateWallets', | ||||
|     ...data | ||||
|   }; | ||||
| } | ||||
							
								
								
									
										89
									
								
								js/src/redux/providers/walletReducer.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								js/src/redux/providers/walletReducer.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,89 @@ | ||||
| // 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 { handleActions } from 'redux-actions'; | ||||
| 
 | ||||
| const initialState = { | ||||
|   wallets: {}, | ||||
|   walletsAddresses: [], | ||||
|   filterSubId: null, | ||||
|   contract: null | ||||
| }; | ||||
| 
 | ||||
| export default handleActions({ | ||||
|   updateWallets: (state, action) => { | ||||
|     const { wallets, walletsAddresses, filterSubId } = action; | ||||
| 
 | ||||
|     return { | ||||
|       ...state, | ||||
|       wallets, walletsAddresses, filterSubId | ||||
|     }; | ||||
|   }, | ||||
| 
 | ||||
|   updateWalletsDetails: (state, action) => { | ||||
|     const { wallets } = action; | ||||
|     const prevWallets = state.wallets; | ||||
| 
 | ||||
|     const nextWallets = { ...prevWallets }; | ||||
| 
 | ||||
|     Object.values(wallets).forEach((wallet) => { | ||||
|       const prevWallet = prevWallets[wallet.address] || {}; | ||||
| 
 | ||||
|       nextWallets[wallet.address] = { | ||||
|         instance: (state.contract && state.contract.instance) || null, | ||||
|         ...prevWallet, | ||||
|         ...wallet | ||||
|       }; | ||||
|     }); | ||||
| 
 | ||||
|     return { | ||||
|       ...state, | ||||
|       wallets: nextWallets | ||||
|     }; | ||||
|   }, | ||||
| 
 | ||||
|   setWalletContract: (state, action) => { | ||||
|     const { contract } = action; | ||||
| 
 | ||||
|     return { | ||||
|       ...state, | ||||
|       contract | ||||
|     }; | ||||
|   }, | ||||
| 
 | ||||
|   setOperationPendingState: (state, action) => { | ||||
|     const { address, operation, isPending } = action; | ||||
|     const { wallets } = state; | ||||
| 
 | ||||
|     const wallet = { ...wallets[address] }; | ||||
| 
 | ||||
|     wallet.confirmations = wallet.confirmations.map((conf) => { | ||||
|       if (conf.operation === operation) { | ||||
|         conf.pending = isPending; | ||||
|       } | ||||
| 
 | ||||
|       return conf; | ||||
|     }); | ||||
| 
 | ||||
|     return { | ||||
|       ...state, | ||||
|       wallets: { | ||||
|         ...wallets, | ||||
|         [ address ]: wallet | ||||
|       } | ||||
|     }; | ||||
|   } | ||||
| }, initialState); | ||||
| @ -17,7 +17,7 @@ | ||||
| import { combineReducers } from 'redux'; | ||||
| import { routerReducer } from 'react-router-redux'; | ||||
| 
 | ||||
| import { apiReducer, balancesReducer, blockchainReducer, compilerReducer, imagesReducer, personalReducer, signerReducer, statusReducer as nodeStatusReducer, snackbarReducer } from './providers'; | ||||
| import { apiReducer, balancesReducer, blockchainReducer, compilerReducer, imagesReducer, personalReducer, signerReducer, statusReducer as nodeStatusReducer, snackbarReducer, walletReducer } from './providers'; | ||||
| import certificationsReducer from './providers/certifications/reducer'; | ||||
| 
 | ||||
| import errorReducer from '~/ui/Errors/reducers'; | ||||
| @ -40,6 +40,7 @@ export default function () { | ||||
|     nodeStatus: nodeStatusReducer, | ||||
|     personal: personalReducer, | ||||
|     signer: signerReducer, | ||||
|     snackbar: snackbarReducer | ||||
|     snackbar: snackbarReducer, | ||||
|     wallet: walletReducer | ||||
|   }); | ||||
| } | ||||
|  | ||||
| @ -19,6 +19,8 @@ import { applyMiddleware, createStore } from 'redux'; | ||||
| import initMiddleware from './middleware'; | ||||
| import initReducers from './reducers'; | ||||
| 
 | ||||
| import { load as loadWallet } from './providers/walletActions'; | ||||
| 
 | ||||
| import { | ||||
|   Balances as BalancesProvider, | ||||
|   Personal as PersonalProvider, | ||||
| @ -40,5 +42,7 @@ export default function (api) { | ||||
|   new SignerProvider(store, api).start(); | ||||
|   new StatusProvider(store, api).start(); | ||||
| 
 | ||||
|   store.dispatch(loadWallet(api)); | ||||
| 
 | ||||
|   return store; | ||||
| } | ||||
|  | ||||
| @ -1,7 +1,25 @@ | ||||
| // Copyright 2015, 2016 Ethcore (UK) Ltd.
 | ||||
| // This file is part of Parity.
 | ||||
| 
 | ||||
| // Parity is free software: you can redistribute it and/or modify
 | ||||
| // it under the terms of the GNU General Public License as published by
 | ||||
| // the Free Software Foundation, either version 3 of the License, or
 | ||||
| // (at your option) any later version.
 | ||||
| 
 | ||||
| // Parity is distributed in the hope that it will be useful,
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | ||||
| // GNU General Public License for more details.
 | ||||
| 
 | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| import ActionDone from 'material-ui/svg-icons/action/done'; | ||||
| import ContentClear from 'material-ui/svg-icons/content/clear'; | ||||
| 
 | ||||
| import { nodeOrStringProptype } from '~/util/proptypes'; | ||||
| 
 | ||||
| import Button from '../Button'; | ||||
| import Modal from '../Modal'; | ||||
| 
 | ||||
| @ -15,9 +33,7 @@ export default class ConfirmDialog extends Component { | ||||
|     iconDeny: PropTypes.node, | ||||
|     labelConfirm: PropTypes.string, | ||||
|     labelDeny: PropTypes.string, | ||||
|     title: PropTypes.oneOfType([ | ||||
|       PropTypes.node, PropTypes.string | ||||
|     ]).isRequired, | ||||
|     title: nodeOrStringProptype().isRequired, | ||||
|     visible: PropTypes.bool.isRequired, | ||||
|     onConfirm: PropTypes.func.isRequired, | ||||
|     onDeny: PropTypes.func.isRequired | ||||
|  | ||||
| @ -16,17 +16,15 @@ | ||||
| 
 | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| 
 | ||||
| import { nodeOrStringProptype } from '~/util/proptypes'; | ||||
| 
 | ||||
| import styles from './title.css'; | ||||
| 
 | ||||
| export default class Title extends Component { | ||||
|   static propTypes = { | ||||
|     className: PropTypes.string, | ||||
|     title: PropTypes.oneOfType([ | ||||
|       PropTypes.string, PropTypes.node | ||||
|     ]), | ||||
|     byline: PropTypes.oneOfType([ | ||||
|       PropTypes.string, PropTypes.node | ||||
|     ]) | ||||
|     title: nodeOrStringProptype(), | ||||
|     byline: nodeOrStringProptype() | ||||
|   } | ||||
| 
 | ||||
|   state = { | ||||
|  | ||||
| @ -17,6 +17,8 @@ | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| import { Card } from 'material-ui/Card'; | ||||
| 
 | ||||
| import { nodeOrStringProptype } from '~/util/proptypes'; | ||||
| 
 | ||||
| import Title from './Title'; | ||||
| 
 | ||||
| import styles from './container.css'; | ||||
| @ -28,9 +30,7 @@ export default class Container extends Component { | ||||
|     compact: PropTypes.bool, | ||||
|     light: PropTypes.bool, | ||||
|     style: PropTypes.object, | ||||
|     title: PropTypes.oneOfType([ | ||||
|       PropTypes.string, PropTypes.node | ||||
|     ]) | ||||
|     title: nodeOrStringProptype() | ||||
|   } | ||||
| 
 | ||||
|   render () { | ||||
|  | ||||
| @ -60,7 +60,8 @@ class Errors extends Component { | ||||
|           flexDirection: 'row', | ||||
|           lineHeight: '1.5em', | ||||
|           padding: '0.75em 0', | ||||
|           alignItems: 'center' | ||||
|           alignItems: 'center', | ||||
|           justifyContent: 'space-between' | ||||
|         } } | ||||
|       /> | ||||
|     ); | ||||
|  | ||||
| @ -33,6 +33,7 @@ export default class AddressSelect extends Component { | ||||
|     accounts: PropTypes.object, | ||||
|     contacts: PropTypes.object, | ||||
|     contracts: PropTypes.object, | ||||
|     wallets: PropTypes.object, | ||||
|     label: PropTypes.string, | ||||
|     hint: PropTypes.string, | ||||
|     error: PropTypes.string, | ||||
| @ -49,8 +50,8 @@ export default class AddressSelect extends Component { | ||||
|   } | ||||
| 
 | ||||
|   entriesFromProps (props = this.props) { | ||||
|     const { accounts, contacts, contracts } = props; | ||||
|     const entries = Object.assign({}, accounts || {}, contacts || {}, contracts || {}); | ||||
|     const { accounts, contacts, contracts, wallets } = props; | ||||
|     const entries = Object.assign({}, accounts || {}, wallets || {}, contacts || {}, contracts || {}); | ||||
|     return entries; | ||||
|   } | ||||
| 
 | ||||
|  | ||||
| @ -120,7 +120,7 @@ export default class AutoComplete extends Component { | ||||
|     switch (keycode(event)) { | ||||
|       case 'down': | ||||
|         const { menu } = muiAutocomplete.refs; | ||||
|         menu.handleKeyDown(event); | ||||
|         menu && menu.handleKeyDown(event); | ||||
|         this.setState({ fakeBlur: true }); | ||||
|         break; | ||||
| 
 | ||||
| @ -133,7 +133,7 @@ export default class AutoComplete extends Component { | ||||
|         const e = new CustomEvent('down'); | ||||
|         e.which = 40; | ||||
| 
 | ||||
|         muiAutocomplete.handleKeyDown(e); | ||||
|         muiAutocomplete && muiAutocomplete.handleKeyDown(e); | ||||
|         break; | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @ -66,7 +66,8 @@ export default class Input extends Component { | ||||
|       PropTypes.number, PropTypes.string | ||||
|     ]), | ||||
|     min: PropTypes.any, | ||||
|     max: PropTypes.any | ||||
|     max: PropTypes.any, | ||||
|     style: PropTypes.object | ||||
|   }; | ||||
| 
 | ||||
|   static defaultProps = { | ||||
| @ -74,11 +75,12 @@ export default class Input extends Component { | ||||
|     readOnly: false, | ||||
|     allowCopy: false, | ||||
|     hideUnderline: false, | ||||
|     floatCopy: false | ||||
|     floatCopy: false, | ||||
|     style: {} | ||||
|   } | ||||
| 
 | ||||
|   state = { | ||||
|     value: this.props.value || '' | ||||
|     value: typeof this.props.value === 'undefined' ? '' : this.props.value | ||||
|   } | ||||
| 
 | ||||
|   componentWillReceiveProps (newProps) { | ||||
| @ -89,7 +91,8 @@ export default class Input extends Component { | ||||
| 
 | ||||
|   render () { | ||||
|     const { value } = this.state; | ||||
|     const { children, className, hideUnderline, disabled, error, label, hint, multiLine, rows, type, min, max } = this.props; | ||||
|     const { children, className, hideUnderline, disabled, error, label } = this.props; | ||||
|     const { hint, multiLine, rows, type, min, max, style } = this.props; | ||||
| 
 | ||||
|     const readOnly = this.props.readOnly || disabled; | ||||
| 
 | ||||
| @ -105,7 +108,7 @@ export default class Input extends Component { | ||||
|     } | ||||
| 
 | ||||
|     return ( | ||||
|       <div className={ styles.container }> | ||||
|       <div className={ styles.container } style={ style }> | ||||
|         { this.renderCopyButton() } | ||||
|         <TextField | ||||
|           autoComplete='off' | ||||
|  | ||||
| @ -21,12 +21,30 @@ | ||||
| .input input { | ||||
|   padding-left: 48px !important; | ||||
|   box-sizing: border-box; | ||||
| 
 | ||||
|   &.small { | ||||
|     padding-left: 40px !important; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .inputEmpty input { | ||||
|   padding-left: 0 !important; | ||||
| } | ||||
| 
 | ||||
| .small { | ||||
|   .input input { | ||||
|     padding-left: 40px !important; | ||||
|   } | ||||
| 
 | ||||
|   .icon, | ||||
|   .iconDisabled { | ||||
|     img { | ||||
|       height: 24px; | ||||
|       width: 24px; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .icon, | ||||
| .iconDisabled { | ||||
|   position: absolute; | ||||
| @ -35,6 +53,14 @@ | ||||
|   &.noLabel { | ||||
|     top: 10px; | ||||
|   } | ||||
| 
 | ||||
|   &.noCopy { | ||||
|     left: 5px; | ||||
|   } | ||||
| 
 | ||||
|   &.noUnderline { | ||||
|     top: 0; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .icon { | ||||
|  | ||||
| @ -36,22 +36,38 @@ class InputAddress extends Component { | ||||
|     tokens: PropTypes.object, | ||||
|     text: PropTypes.bool, | ||||
|     onChange: PropTypes.func, | ||||
|     onSubmit: PropTypes.func | ||||
|     onSubmit: PropTypes.func, | ||||
|     hideUnderline: PropTypes.bool, | ||||
|     allowCopy: PropTypes.bool, | ||||
|     small: PropTypes.bool | ||||
|   }; | ||||
| 
 | ||||
|   static defaultProps = { | ||||
|     allowCopy: true, | ||||
|     hideUnderline: false, | ||||
|     small: false | ||||
|   }; | ||||
| 
 | ||||
|   render () { | ||||
|     const { className, disabled, error, label, hint, value, text, onSubmit, accountsInfo, tokens } = this.props; | ||||
|     const { className, disabled, error, label, hint, value, text } = this.props; | ||||
|     const { small, allowCopy, hideUnderline, onSubmit, accountsInfo, tokens } = this.props; | ||||
| 
 | ||||
|     const account = accountsInfo[value] || tokens[value]; | ||||
|     const hasAccount = account && (!account.meta || !account.meta.deleted); | ||||
|     const hasAccount = account && !(account.meta && account.meta.deleted); | ||||
| 
 | ||||
|     const icon = this.renderIcon(); | ||||
| 
 | ||||
|     const classes = [ className ]; | ||||
|     classes.push(!icon ? styles.inputEmpty : styles.input); | ||||
| 
 | ||||
|     const containerClasses = [ styles.container ]; | ||||
| 
 | ||||
|     if (small) { | ||||
|       containerClasses.push(styles.small); | ||||
|     } | ||||
| 
 | ||||
|     return ( | ||||
|       <div className={ styles.container }> | ||||
|       <div className={ containerClasses.join(' ') }> | ||||
|         <Input | ||||
|           className={ classes.join(' ') } | ||||
|           disabled={ disabled } | ||||
| @ -61,7 +77,8 @@ class InputAddress extends Component { | ||||
|           value={ text && hasAccount ? account.name : value } | ||||
|           onChange={ this.handleInputChange } | ||||
|           onSubmit={ onSubmit } | ||||
|           allowCopy={ disabled ? value : false } | ||||
|           allowCopy={ allowCopy && (disabled ? value : false) } | ||||
|           hideUnderline={ hideUnderline } | ||||
|         /> | ||||
|         { icon } | ||||
|       </div> | ||||
| @ -69,7 +86,7 @@ class InputAddress extends Component { | ||||
|   } | ||||
| 
 | ||||
|   renderIcon () { | ||||
|     const { value, disabled, label } = this.props; | ||||
|     const { value, disabled, label, allowCopy, hideUnderline } = this.props; | ||||
| 
 | ||||
|     if (!value || !value.length || !util.isAddressValid(value)) { | ||||
|       return null; | ||||
| @ -81,6 +98,14 @@ class InputAddress extends Component { | ||||
|       classes.push(styles.noLabel); | ||||
|     } | ||||
| 
 | ||||
|     if (!allowCopy) { | ||||
|       classes.push(styles.noCopy); | ||||
|     } | ||||
| 
 | ||||
|     if (hideUnderline) { | ||||
|       classes.push(styles.noUnderline); | ||||
|     } | ||||
| 
 | ||||
|     return ( | ||||
|       <div className={ classes.join(' ') }> | ||||
|         <IdentityIcon | ||||
|  | ||||
| @ -25,6 +25,7 @@ class InputAddressSelect extends Component { | ||||
|     accounts: PropTypes.object.isRequired, | ||||
|     contacts: PropTypes.object.isRequired, | ||||
|     contracts: PropTypes.object.isRequired, | ||||
|     wallets: PropTypes.object.isRequired, | ||||
|     error: PropTypes.string, | ||||
|     label: PropTypes.string, | ||||
|     hint: PropTypes.string, | ||||
| @ -33,7 +34,7 @@ class InputAddressSelect extends Component { | ||||
|   }; | ||||
| 
 | ||||
|   render () { | ||||
|     const { accounts, contacts, contracts, label, hint, error, value, onChange } = this.props; | ||||
|     const { accounts, contacts, contracts, wallets, label, hint, error, value, onChange } = this.props; | ||||
| 
 | ||||
|     return ( | ||||
|       <AddressSelect | ||||
| @ -41,6 +42,7 @@ class InputAddressSelect extends Component { | ||||
|         accounts={ accounts } | ||||
|         contacts={ contacts } | ||||
|         contracts={ contracts } | ||||
|         wallets={ wallets } | ||||
|         error={ error } | ||||
|         label={ label } | ||||
|         hint={ hint } | ||||
| @ -51,12 +53,13 @@ class InputAddressSelect extends Component { | ||||
| } | ||||
| 
 | ||||
| function mapStateToProps (state) { | ||||
|   const { accounts, contacts, contracts } = state.personal; | ||||
|   const { accounts, contacts, contracts, wallets } = state.personal; | ||||
| 
 | ||||
|   return { | ||||
|     accounts, | ||||
|     contacts, | ||||
|     contracts | ||||
|     contracts, | ||||
|     wallets | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -16,6 +16,8 @@ | ||||
| 
 | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| 
 | ||||
| import { nodeOrStringProptype } from '~/util/proptypes'; | ||||
| 
 | ||||
| import Input from '../Input'; | ||||
| 
 | ||||
| import styles from './inputInline.css'; | ||||
| @ -33,9 +35,7 @@ export default class InputInline extends Component { | ||||
|     value: PropTypes.oneOfType([ | ||||
|       PropTypes.number, PropTypes.string | ||||
|     ]), | ||||
|     static: PropTypes.oneOfType([ | ||||
|       PropTypes.node, PropTypes.string | ||||
|     ]) | ||||
|     static: nodeOrStringProptype() | ||||
|   } | ||||
| 
 | ||||
|   state = { | ||||
|  | ||||
| @ -37,7 +37,10 @@ export default class RadioButtons extends Component { | ||||
|   render () { | ||||
|     const { value, values } = this.props; | ||||
| 
 | ||||
|     const index = parseInt(value); | ||||
|     const index = Number.isNaN(parseInt(value)) | ||||
|       ? values.findIndex((val) => val.key === value) | ||||
|       : parseInt(value); | ||||
| 
 | ||||
|     const selectedValue = typeof value !== 'object' ? values[index] : value; | ||||
|     const key = this.getKey(selectedValue, index); | ||||
| 
 | ||||
|  | ||||
| @ -34,12 +34,20 @@ export default class TypedInput extends Component { | ||||
| 
 | ||||
|   static propTypes = { | ||||
|     onChange: PropTypes.func.isRequired, | ||||
|     accounts: PropTypes.object.isRequired, | ||||
|     param: PropTypes.object.isRequired, | ||||
| 
 | ||||
|     accounts: PropTypes.object, | ||||
|     error: PropTypes.any, | ||||
|     value: PropTypes.any, | ||||
|     label: PropTypes.string | ||||
|     label: PropTypes.string, | ||||
|     hint: PropTypes.string, | ||||
|     min: PropTypes.number, | ||||
|     max: PropTypes.number | ||||
|   }; | ||||
| 
 | ||||
|   static defaultProps = { | ||||
|     min: null, | ||||
|     max: null | ||||
|   }; | ||||
| 
 | ||||
|   render () { | ||||
| @ -89,16 +97,22 @@ export default class TypedInput extends Component { | ||||
|     }; | ||||
| 
 | ||||
|     const style = { | ||||
|       width: 32, | ||||
|       height: 32, | ||||
|       width: 24, | ||||
|       height: 24, | ||||
|       padding: 0 | ||||
|     }; | ||||
| 
 | ||||
|     const plusStyle = { | ||||
|       ...style, | ||||
|       backgroundColor: 'rgba(255, 255, 255, 0.25)', | ||||
|       borderRadius: '50%' | ||||
|     }; | ||||
| 
 | ||||
|     return ( | ||||
|       <div> | ||||
|       <div style={ { marginTop: '0.75em' } }> | ||||
|         <IconButton | ||||
|           iconStyle={ iconStyle } | ||||
|           style={ style } | ||||
|           style={ plusStyle } | ||||
|           onTouchTap={ this.onAddField } | ||||
|         > | ||||
|           <AddIcon /> | ||||
| @ -144,26 +158,29 @@ export default class TypedInput extends Component { | ||||
|   } | ||||
| 
 | ||||
|   renderNumber () { | ||||
|     const { label, value, error, param } = this.props; | ||||
|     const { label, value, error, param, hint, min, max } = this.props; | ||||
| 
 | ||||
|     return ( | ||||
|       <Input | ||||
|         label={ label } | ||||
|         hint={ hint } | ||||
|         value={ value } | ||||
|         error={ error } | ||||
|         onSubmit={ this.onSubmit } | ||||
|         onChange={ this.onChange } | ||||
|         type='number' | ||||
|         min={ param.signed ? null : 0 } | ||||
|         min={ min !== null ? min : (param.signed ? null : 0) } | ||||
|         max={ max !== null ? max : null } | ||||
|       /> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   renderDefault () { | ||||
|     const { label, value, error } = this.props; | ||||
|     const { label, value, error, hint } = this.props; | ||||
| 
 | ||||
|     return ( | ||||
|       <Input | ||||
|         label={ label } | ||||
|         hint={ hint } | ||||
|         value={ value } | ||||
|         error={ error } | ||||
|         onSubmit={ this.onSubmit } | ||||
| @ -172,12 +189,13 @@ export default class TypedInput extends Component { | ||||
|   } | ||||
| 
 | ||||
|   renderAddress () { | ||||
|     const { accounts, label, value, error } = this.props; | ||||
|     const { accounts, label, value, error, hint } = this.props; | ||||
| 
 | ||||
|     return ( | ||||
|       <InputAddressSelect | ||||
|         accounts={ accounts } | ||||
|         label={ label } | ||||
|         hint={ hint } | ||||
|         value={ value } | ||||
|         error={ error } | ||||
|         onChange={ this.onChange } | ||||
| @ -187,7 +205,7 @@ export default class TypedInput extends Component { | ||||
|   } | ||||
| 
 | ||||
|   renderBoolean () { | ||||
|     const { label, value, error } = this.props; | ||||
|     const { label, value, error, hint } = this.props; | ||||
| 
 | ||||
|     const boolitems = ['false', 'true'].map((bool) => { | ||||
|       return ( | ||||
| @ -204,6 +222,7 @@ export default class TypedInput extends Component { | ||||
|     return ( | ||||
|       <Select | ||||
|         label={ label } | ||||
|         hint={ hint } | ||||
|         value={ value ? 'true' : 'false' } | ||||
|         error={ error } | ||||
|         onChange={ this.onChangeBool } | ||||
|  | ||||
| @ -457,6 +457,14 @@ class MethodDecoding extends Component { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     const { signature, paramdata } = api.util.decodeCallData(input); | ||||
|     this.setState({ methodSignature: signature, methodParams: paramdata }); | ||||
| 
 | ||||
|     if (!signature || signature === CONTRACT_CREATE || transaction.creates) { | ||||
|       this.setState({ isDeploy: true }); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     if (contractAddress === '0x') { | ||||
|       return; | ||||
|     } | ||||
| @ -472,14 +480,6 @@ class MethodDecoding extends Component { | ||||
|           return; | ||||
|         } | ||||
| 
 | ||||
|         const { signature, paramdata } = api.util.decodeCallData(input); | ||||
|         this.setState({ methodSignature: signature, methodParams: paramdata }); | ||||
| 
 | ||||
|         if (!signature || signature === CONTRACT_CREATE || transaction.creates) { | ||||
|           this.setState({ isDeploy: true }); | ||||
|           return; | ||||
|         } | ||||
| 
 | ||||
|         return Contracts.get() | ||||
|           .signatureReg | ||||
|           .lookup(signature) | ||||
|  | ||||
| @ -18,6 +18,8 @@ import React, { Component, PropTypes } from 'react'; | ||||
| import { LinearProgress } from 'material-ui'; | ||||
| import { Step, Stepper, StepLabel } from 'material-ui/Stepper'; | ||||
| 
 | ||||
| import { nodeOrStringProptype } from '~/util/proptypes'; | ||||
| 
 | ||||
| import styles from '../modal.css'; | ||||
| 
 | ||||
| export default class Title extends Component { | ||||
| @ -26,9 +28,7 @@ export default class Title extends Component { | ||||
|     current: PropTypes.number, | ||||
|     steps: PropTypes.array, | ||||
|     waiting: PropTypes.array, | ||||
|     title: React.PropTypes.oneOfType([ | ||||
|       PropTypes.node, PropTypes.string | ||||
|     ]) | ||||
|     title: nodeOrStringProptype() | ||||
|   } | ||||
| 
 | ||||
|   render () { | ||||
|  | ||||
| @ -19,6 +19,8 @@ import { connect } from 'react-redux'; | ||||
| import { bindActionCreators } from 'redux'; | ||||
| import { Dialog } from 'material-ui'; | ||||
| 
 | ||||
| import { nodeOrStringProptype } from '~/util/proptypes'; | ||||
| 
 | ||||
| import Container from '../Container'; | ||||
| import Title from './Title'; | ||||
| 
 | ||||
| @ -42,9 +44,7 @@ class Modal extends Component { | ||||
|     current: PropTypes.number, | ||||
|     waiting: PropTypes.array, | ||||
|     steps: PropTypes.array, | ||||
|     title: PropTypes.oneOfType([ | ||||
|       PropTypes.node, PropTypes.string | ||||
|     ]), | ||||
|     title: nodeOrStringProptype(), | ||||
|     visible: PropTypes.bool.isRequired, | ||||
|     settings: PropTypes.object.isRequired | ||||
|   } | ||||
|  | ||||
| @ -39,14 +39,20 @@ export class TxRow extends Component { | ||||
|     address: PropTypes.string.isRequired, | ||||
|     isTest: PropTypes.bool.isRequired, | ||||
| 
 | ||||
|     block: PropTypes.object | ||||
|     block: PropTypes.object, | ||||
|     historic: PropTypes.bool, | ||||
|     className: PropTypes.string | ||||
|   }; | ||||
| 
 | ||||
|   static defaultProps = { | ||||
|     historic: true | ||||
|   }; | ||||
| 
 | ||||
|   render () { | ||||
|     const { tx, address, isTest } = this.props; | ||||
|     const { tx, address, isTest, historic, className } = this.props; | ||||
| 
 | ||||
|     return ( | ||||
|       <tr> | ||||
|       <tr className={ className || '' }> | ||||
|         { this.renderBlockNumber(tx.blockNumber) } | ||||
|         { this.renderAddress(tx.from) } | ||||
|         <td className={ styles.transaction }> | ||||
| @ -64,7 +70,7 @@ export class TxRow extends Component { | ||||
|         { this.renderAddress(tx.to) } | ||||
|         <td className={ styles.method }> | ||||
|           <MethodDecoding | ||||
|             historic | ||||
|             historic={ historic } | ||||
|             address={ address } | ||||
|             transaction={ tx } /> | ||||
|         </td> | ||||
|  | ||||
							
								
								
									
										31
									
								
								js/src/util/proptypes.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								js/src/util/proptypes.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,31 @@ | ||||
| // 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 { PropTypes } from 'react'; | ||||
| 
 | ||||
| export function nullableProptype (type) { | ||||
|   return PropTypes.oneOfType([ | ||||
|     PropTypes.oneOf([ null ]), | ||||
|     type | ||||
|   ]); | ||||
| } | ||||
| 
 | ||||
| export function nodeOrStringProptype () { | ||||
|   return PropTypes.oneOfType([ | ||||
|     PropTypes.node, | ||||
|     PropTypes.string | ||||
|   ]); | ||||
| } | ||||
							
								
								
									
										107
									
								
								js/src/util/wallets.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								js/src/util/wallets.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,107 @@ | ||||
| // 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 { range } from 'lodash'; | ||||
| 
 | ||||
| import { bytesToHex, toHex } from '~/api/util/format'; | ||||
| 
 | ||||
| export default class WalletsUtils { | ||||
| 
 | ||||
|   static fetchRequire (walletContract) { | ||||
|     return walletContract.instance.m_required.call(); | ||||
|   } | ||||
| 
 | ||||
|   static fetchOwners (walletContract) { | ||||
|     const walletInstance = walletContract.instance; | ||||
|     return walletInstance | ||||
|       .m_numOwners.call() | ||||
|       .then((mNumOwners) => { | ||||
|         return Promise.all(range(mNumOwners.toNumber()).map((idx) => walletInstance.getOwner.call({}, [ idx ]))); | ||||
|       }); | ||||
|   } | ||||
| 
 | ||||
|   static fetchDailylimit (walletContract) { | ||||
|     const walletInstance = walletContract.instance; | ||||
| 
 | ||||
|     return Promise | ||||
|       .all([ | ||||
|         walletInstance.m_dailyLimit.call(), | ||||
|         walletInstance.m_spentToday.call(), | ||||
|         walletInstance.m_lastDay.call() | ||||
|       ]) | ||||
|       .then(([ limit, spent, last ]) => ({ | ||||
|         limit, spent, last | ||||
|       })); | ||||
|   } | ||||
| 
 | ||||
|   static fetchTransactions (walletContract) { | ||||
|     const walletInstance = walletContract.instance; | ||||
|     const signatures = { | ||||
|       single: toHex(walletInstance.SingleTransact.signature), | ||||
|       multi: toHex(walletInstance.MultiTransact.signature), | ||||
|       deposit: toHex(walletInstance.Deposit.signature) | ||||
|     }; | ||||
| 
 | ||||
|     return walletContract | ||||
|       .getAllLogs({ | ||||
|         topics: [ [ signatures.single, signatures.multi, signatures.deposit ] ] | ||||
|       }) | ||||
|       .then((logs) => { | ||||
|         return logs.sort((logA, logB) => { | ||||
|           const comp = logB.blockNumber.comparedTo(logA.blockNumber); | ||||
| 
 | ||||
|           if (comp !== 0) { | ||||
|             return comp; | ||||
|           } | ||||
| 
 | ||||
|           return logB.transactionIndex.comparedTo(logA.transactionIndex); | ||||
|         }); | ||||
|       }) | ||||
|       .then((logs) => { | ||||
|         const transactions = logs.map((log) => { | ||||
|           const signature = toHex(log.topics[0]); | ||||
| 
 | ||||
|           const value = log.params.value.value; | ||||
|           const from = signature === signatures.deposit | ||||
|             ? log.params['_from'].value | ||||
|             : walletContract.address; | ||||
| 
 | ||||
|           const to = signature === signatures.deposit | ||||
|             ? walletContract.address | ||||
|             : log.params.to.value; | ||||
| 
 | ||||
|           const transaction = { | ||||
|             transactionHash: log.transactionHash, | ||||
|             blockNumber: log.blockNumber, | ||||
|             from, to, value | ||||
|           }; | ||||
| 
 | ||||
|           if (log.params.operation) { | ||||
|             transaction.operation = bytesToHex(log.params.operation.value); | ||||
|           } | ||||
| 
 | ||||
|           if (log.params.data) { | ||||
|             transaction.data = log.params.data.value; | ||||
|           } | ||||
| 
 | ||||
|           return transaction; | ||||
|         }); | ||||
| 
 | ||||
|         return transactions; | ||||
|       }); | ||||
|   } | ||||
| 
 | ||||
| } | ||||
| @ -25,16 +25,23 @@ import styles from './header.css'; | ||||
| export default class Header extends Component { | ||||
|   static contextTypes = { | ||||
|     api: PropTypes.object | ||||
|   } | ||||
|   }; | ||||
| 
 | ||||
|   static propTypes = { | ||||
|     account: PropTypes.object, | ||||
|     balance: PropTypes.object | ||||
|   } | ||||
|     balance: PropTypes.object, | ||||
|     className: PropTypes.string, | ||||
|     children: PropTypes.node | ||||
|   }; | ||||
| 
 | ||||
|   static defaultProps = { | ||||
|     className: '', | ||||
|     children: null | ||||
|   }; | ||||
| 
 | ||||
|   render () { | ||||
|     const { api } = this.context; | ||||
|     const { account, balance } = this.props; | ||||
|     const { account, balance, className, children } = this.props; | ||||
|     const { address, meta, uuid } = account; | ||||
| 
 | ||||
|     if (!account) { | ||||
| @ -46,7 +53,7 @@ export default class Header extends Component { | ||||
|       : <div className={ styles.uuidline }>uuid: { uuid }</div>; | ||||
| 
 | ||||
|     return ( | ||||
|       <div> | ||||
|       <div className={ className }> | ||||
|         <Container> | ||||
|           <IdentityIcon | ||||
|             address={ address } /> | ||||
| @ -74,6 +81,7 @@ export default class Header extends Component { | ||||
|               dappsUrl={ api.dappsUrl } | ||||
|             /> | ||||
|           </div> | ||||
|           { children } | ||||
|         </Container> | ||||
|       </div> | ||||
|     ); | ||||
| @ -88,6 +96,10 @@ export default class Header extends Component { | ||||
| 
 | ||||
|     const { txCount } = balance; | ||||
| 
 | ||||
|     if (!txCount) { | ||||
|       return null; | ||||
|     } | ||||
| 
 | ||||
|     return ( | ||||
|       <div className={ styles.infoline }> | ||||
|         { txCount.toFormat() } outgoing transactions | ||||
|  | ||||
| @ -21,7 +21,7 @@ import ContentAdd from 'material-ui/svg-icons/content/add'; | ||||
| import { uniq, isEqual } from 'lodash'; | ||||
| 
 | ||||
| import List from './List'; | ||||
| import { CreateAccount } from '~/modals'; | ||||
| import { CreateAccount, CreateWallet } from '~/modals'; | ||||
| import { Actionbar, ActionbarExport, ActionbarSearch, ActionbarSort, Button, Page, Tooltip } from '~/ui'; | ||||
| import { setVisibleAccounts } from '~/redux/providers/personalActions'; | ||||
| 
 | ||||
| @ -34,15 +34,18 @@ class Accounts extends Component { | ||||
| 
 | ||||
|   static propTypes = { | ||||
|     setVisibleAccounts: PropTypes.func.isRequired, | ||||
|     accounts: PropTypes.object.isRequired, | ||||
|     hasAccounts: PropTypes.bool.isRequired, | ||||
|     wallets: PropTypes.object.isRequired, | ||||
|     hasWallets: PropTypes.bool.isRequired, | ||||
| 
 | ||||
|     accounts: PropTypes.object, | ||||
|     hasAccounts: PropTypes.bool, | ||||
|     balances: PropTypes.object | ||||
|   } | ||||
| 
 | ||||
|   state = { | ||||
|     addressBook: false, | ||||
|     newDialog: false, | ||||
|     newWalletDialog: false, | ||||
|     sortOrder: '', | ||||
|     searchValues: [], | ||||
|     searchTokens: [], | ||||
| @ -58,8 +61,8 @@ class Accounts extends Component { | ||||
|   } | ||||
| 
 | ||||
|   componentWillReceiveProps (nextProps) { | ||||
|     const prevAddresses = Object.keys(this.props.accounts); | ||||
|     const nextAddresses = Object.keys(nextProps.accounts); | ||||
|     const prevAddresses = Object.keys({ ...this.props.accounts, ...this.props.wallets }); | ||||
|     const nextAddresses = Object.keys({ ...nextProps.accounts, ...nextProps.wallets }); | ||||
| 
 | ||||
|     if (prevAddresses.length !== nextAddresses.length || !isEqual(prevAddresses.sort(), nextAddresses.sort())) { | ||||
|       this.setVisibleAccounts(nextProps); | ||||
| @ -71,8 +74,8 @@ class Accounts extends Component { | ||||
|   } | ||||
| 
 | ||||
|   setVisibleAccounts (props = this.props) { | ||||
|     const { accounts, setVisibleAccounts } = props; | ||||
|     const addresses = Object.keys(accounts); | ||||
|     const { accounts, wallets, setVisibleAccounts } = props; | ||||
|     const addresses = Object.keys({ ...accounts, ...wallets }); | ||||
|     setVisibleAccounts(addresses); | ||||
|   } | ||||
| 
 | ||||
| @ -80,17 +83,24 @@ class Accounts extends Component { | ||||
|     return ( | ||||
|       <div className={ styles.accounts }> | ||||
|         { this.renderNewDialog() } | ||||
|         { this.renderNewWalletDialog() } | ||||
|         { this.renderActionbar() } | ||||
| 
 | ||||
|         { this.state.show ? this.renderAccounts() : this.renderLoading() } | ||||
|         <Page> | ||||
|           <Tooltip | ||||
|             className={ styles.accountTooltip } | ||||
|             text='your accounts are visible for easy access, allowing you to edit the meta information, make transfers, view transactions and fund the account' | ||||
|           /> | ||||
| 
 | ||||
|           { this.renderWallets() } | ||||
|           { this.renderAccounts() } | ||||
|         </Page> | ||||
|       </div> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   renderLoading () { | ||||
|     const { accounts } = this.props; | ||||
| 
 | ||||
|     const loadings = ((accounts && Object.keys(accounts)) || []).map((_, idx) => ( | ||||
|   renderLoading (object) { | ||||
|     const loadings = ((object && Object.keys(object)) || []).map((_, idx) => ( | ||||
|       <div key={ idx } className={ styles.loading }> | ||||
|         <div /> | ||||
|       </div> | ||||
| @ -104,22 +114,42 @@ class Accounts extends Component { | ||||
|   } | ||||
| 
 | ||||
|   renderAccounts () { | ||||
|     if (!this.state.show) { | ||||
|       return this.renderLoading(this.props.accounts); | ||||
|     } | ||||
| 
 | ||||
|     const { accounts, hasAccounts, balances } = this.props; | ||||
|     const { searchValues, sortOrder } = this.state; | ||||
| 
 | ||||
|     return ( | ||||
|       <Page> | ||||
|         <List | ||||
|           search={ searchValues } | ||||
|           accounts={ accounts } | ||||
|           balances={ balances } | ||||
|           empty={ !hasAccounts } | ||||
|           order={ sortOrder } | ||||
|           handleAddSearchToken={ this.onAddSearchToken } /> | ||||
|         <Tooltip | ||||
|           className={ styles.accountTooltip } | ||||
|           text='your accounts are visible for easy access, allowing you to edit the meta information, make transfers, view transactions and fund the account' /> | ||||
|       </Page> | ||||
|       <List | ||||
|         search={ searchValues } | ||||
|         accounts={ accounts } | ||||
|         balances={ balances } | ||||
|         empty={ !hasAccounts } | ||||
|         order={ sortOrder } | ||||
|         handleAddSearchToken={ this.onAddSearchToken } /> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   renderWallets () { | ||||
|     if (!this.state.show) { | ||||
|       return this.renderLoading(this.props.wallets); | ||||
|     } | ||||
| 
 | ||||
|     const { wallets, hasWallets, balances } = this.props; | ||||
|     const { searchValues, sortOrder } = this.state; | ||||
| 
 | ||||
|     return ( | ||||
|       <List | ||||
|         link='wallet' | ||||
|         search={ searchValues } | ||||
|         accounts={ wallets } | ||||
|         balances={ balances } | ||||
|         empty={ !hasWallets } | ||||
|         order={ sortOrder } | ||||
|         handleAddSearchToken={ this.onAddSearchToken } | ||||
|       /> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
| @ -160,6 +190,12 @@ class Accounts extends Component { | ||||
|         label='new account' | ||||
|         onClick={ this.onNewAccountClick } />, | ||||
| 
 | ||||
|       <Button | ||||
|         key='newWallet' | ||||
|         icon={ <ContentAdd /> } | ||||
|         label='new wallet' | ||||
|         onClick={ this.onNewWalletClick } />, | ||||
| 
 | ||||
|       <ActionbarExport | ||||
|         key='exportAccounts' | ||||
|         content={ accounts } | ||||
| @ -198,6 +234,22 @@ class Accounts extends Component { | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   renderNewWalletDialog () { | ||||
|     const { accounts } = this.props; | ||||
|     const { newWalletDialog } = this.state; | ||||
| 
 | ||||
|     if (!newWalletDialog) { | ||||
|       return null; | ||||
|     } | ||||
| 
 | ||||
|     return ( | ||||
|       <CreateWallet | ||||
|         accounts={ accounts } | ||||
|         onClose={ this.onNewWalletClose } | ||||
|       /> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   onAddSearchToken = (token) => { | ||||
|     const { searchTokens } = this.state; | ||||
|     const newSearchTokens = uniq([].concat(searchTokens, token)); | ||||
| @ -210,21 +262,33 @@ class Accounts extends Component { | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   onNewWalletClick = () => { | ||||
|     this.setState({ | ||||
|       newWalletDialog: !this.state.newWalletDialog | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   onNewAccountClose = () => { | ||||
|     this.onNewAccountClick(); | ||||
|   } | ||||
| 
 | ||||
|   onNewWalletClose = () => { | ||||
|     this.onNewWalletClick(); | ||||
|   } | ||||
| 
 | ||||
|   onNewAccountUpdate = () => { | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| function mapStateToProps (state) { | ||||
|   const { accounts, hasAccounts } = state.personal; | ||||
|   const { accounts, hasAccounts, wallets, hasWallets } = state.personal; | ||||
|   const { balances } = state.balances; | ||||
| 
 | ||||
|   return { | ||||
|     accounts, | ||||
|     hasAccounts, | ||||
|     wallets, | ||||
|     hasWallets, | ||||
|     balances | ||||
|   }; | ||||
| } | ||||
|  | ||||
| @ -19,7 +19,7 @@ import { connect } from 'react-redux'; | ||||
| import { bindActionCreators } from 'redux'; | ||||
| 
 | ||||
| import { ConfirmDialog, IdentityIcon, IdentityName } from '~/ui'; | ||||
| import { newError } from '../../../redux/actions'; | ||||
| import { newError } from '~/redux/actions'; | ||||
| 
 | ||||
| import styles from '../address.css'; | ||||
| 
 | ||||
| @ -27,16 +27,17 @@ class Delete extends Component { | ||||
|   static contextTypes = { | ||||
|     api: PropTypes.object.isRequired, | ||||
|     router: PropTypes.object | ||||
|   } | ||||
|   }; | ||||
| 
 | ||||
|   static propTypes = { | ||||
|     route: PropTypes.string.isRequired, | ||||
| 
 | ||||
|     address: PropTypes.string, | ||||
|     account: PropTypes.object, | ||||
|     route: PropTypes.string.isRequired, | ||||
|     visible: PropTypes.bool, | ||||
|     onClose: PropTypes.func, | ||||
|     newError: PropTypes.func | ||||
|   } | ||||
|   }; | ||||
| 
 | ||||
|   render () { | ||||
|     const { account, visible } = this.props; | ||||
|  | ||||
| @ -28,6 +28,7 @@ import imagesEthcoreBlock from '../../../../assets/images/parity-logo-white-no-t | ||||
| 
 | ||||
| const TABMAP = { | ||||
|   accounts: 'account', | ||||
|   wallet: 'account', | ||||
|   addresses: 'address', | ||||
|   apps: 'app', | ||||
|   contracts: 'contract', | ||||
|  | ||||
| @ -23,7 +23,7 @@ import ContentCreate from 'material-ui/svg-icons/content/create'; | ||||
| import EyeIcon from 'material-ui/svg-icons/image/remove-red-eye'; | ||||
| import ContentClear from 'material-ui/svg-icons/content/clear'; | ||||
| 
 | ||||
| import { newError } from '../../redux/actions'; | ||||
| import { newError } from '~/redux/actions'; | ||||
| import { setVisibleAccounts } from '~/redux/providers/personalActions'; | ||||
| 
 | ||||
| import { EditMeta, ExecuteContract } from '~/modals'; | ||||
|  | ||||
| @ -19,7 +19,7 @@ import { action, computed, observable, transaction } from 'mobx'; | ||||
| import store from 'store'; | ||||
| 
 | ||||
| import Contracts from '~/contracts'; | ||||
| import { hashToImageUrl } from '../../redux/util'; | ||||
| import { hashToImageUrl } from '~/redux/util'; | ||||
| 
 | ||||
| import builtinApps from './builtin.json'; | ||||
| 
 | ||||
|  | ||||
| @ -18,7 +18,7 @@ import React, { Component, PropTypes } from 'react'; | ||||
| 
 | ||||
| import ReactTooltip from 'react-tooltip'; | ||||
| 
 | ||||
| import { MethodDecoding } from '../../../../ui'; | ||||
| import { MethodDecoding } from '~/ui'; | ||||
| 
 | ||||
| import * as tUtil from '../util/transaction'; | ||||
| import Account from '../Account'; | ||||
|  | ||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
		Reference in New Issue
	
	Block a user