diff --git a/Cargo.lock b/Cargo.lock index 6597c44a1..cca8b014d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -298,6 +298,7 @@ dependencies = [ "ethcore-ipc 1.4.0", "ethcore-ipc-codegen 1.4.0", "ethcore-ipc-nano 1.4.0", + "ethcore-network 1.5.0", "ethcore-util 1.5.0", "ethjson 0.1.0", "ethkey 0.2.0", @@ -841,7 +842,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)", @@ -853,7 +854,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)", @@ -864,7 +865,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)", @@ -879,7 +880,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)", @@ -1271,7 +1272,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#3cf6c68b7d08be71d12ff142e255a42043e50c75" +source = "git+https://github.com/ethcore/js-precompiled.git#57f5bf943f24cf761ba58c1bea35a845e0b12414" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/ethcore/light/Cargo.toml b/ethcore/light/Cargo.toml index daf141de7..74400d7ab 100644 --- a/ethcore/light/Cargo.toml +++ b/ethcore/light/Cargo.toml @@ -5,6 +5,10 @@ license = "GPL-3.0" name = "ethcore-light" version = "1.5.0" authors = ["Ethcore "] +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" \ No newline at end of file diff --git a/js/src/util/nullable-proptype.js b/ethcore/light/build.rs similarity index 82% rename from js/src/util/nullable-proptype.js rename to ethcore/light/build.rs index 78c486cb6..cff92a011 100644 --- a/js/src/util/nullable-proptype.js +++ b/ethcore/light/build.rs @@ -14,8 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -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(); } diff --git a/ethcore/light/src/client.rs b/ethcore/light/src/client.rs index e3b5745b2..8a5c43c48 100644 --- a/ethcore/light/src/client.rs +++ b/ethcore/light/src/client.rs @@ -101,7 +101,7 @@ impl Provider for Client { Vec::new() } - fn code(&self, _req: request::ContractCodes) -> Vec { + fn contract_code(&self, _req: request::ContractCodes) -> Vec { Vec::new() } diff --git a/ethcore/light/src/lib.rs b/ethcore/light/src/lib.rs index 07e6833a7..e150f4ee5 100644 --- a/ethcore/light/src/lib.rs +++ b/ethcore/light/src/lib.rs @@ -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; \ No newline at end of file +extern crate time; \ No newline at end of file diff --git a/ethcore/light/src/net/buffer_flow.rs b/ethcore/light/src/net/buffer_flow.rs index b7bd30f82..6730c71a7 100644 --- a/ethcore/light/src/net/buffer_flow.rs +++ b/ethcore/light/src/net/buffer_flow.rs @@ -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)] diff --git a/ethcore/light/src/net/error.rs b/ethcore/light/src/net/error.rs index e15bd50d3..0855cdeb8 100644 --- a/ethcore/light/src/net/error.rs +++ b/ethcore/light/src/net/error.rs @@ -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"), } } } \ No newline at end of file diff --git a/ethcore/light/src/net/mod.rs b/ethcore/light/src/net/mod.rs index e72ce4bb2..a1b3b30b0 100644 --- a/ethcore/light/src/net/mod.rs +++ b/ethcore/light/src/net/mod.rs @@ -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 { + 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, genesis_hash: H256, - network_id: status::NetworkId, + network_id: NetworkId, pending_peers: RwLock>, - peers: RwLock>, - pending_requests: RwLock>, + peers: RwLock>>, + pending_requests: RwLock>, capabilities: RwLock, flow_params: FlowParams, // assumed static and same for every peer. + handlers: Vec>, req_id: AtomicUsize, } impl LightProtocol { + /// Create a new instance of the protocol manager. + pub fn new(provider: Box, 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 { + 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 { + 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) { + 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, 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, 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, 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::()).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 { + 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() + } + } } \ No newline at end of file diff --git a/ethcore/light/src/net/status.rs b/ethcore/light/src/net/status.rs index 5aaea9f3a..2c0c5f79a 100644 --- a/ethcore/light/src/net/status.rs +++ b/ethcore/light/src/net/status.rs @@ -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, /// Earliest block number it can serve state requests for. + /// `None` means no requests will be servable. pub serve_state_since: Option, /// 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 diff --git a/ethcore/light/src/provider.rs b/ethcore/light/src/provider.rs index b1625f95f..264df0397 100644 --- a/ethcore/light/src/provider.rs +++ b/ethcore/light/src/provider.rs @@ -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; - /// Earliest state. + /// Earliest block where state queries are available. + /// If `None`, no state queries are servable. fn earliest_state(&self) -> Option; /// 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; /// Provide contract code for the specified (block_hash, account_hash) pairs. - fn code(&self, req: request::ContractCodes) -> Vec; + /// Each item in the resulting vector is either the raw bytecode or empty. + fn contract_code(&self, req: request::ContractCodes) -> Vec; /// Provide header proofs from the Canonical Hash Tries. fn header_proofs(&self, req: request::HeaderProofs) -> Vec; /// Provide pending transactions. fn pending_transactions(&self) -> Vec; +} + +// Implementation of a light client data provider for a client. +impl Provider for T { + fn chain_info(&self) -> BlockChainInfo { + BlockChainClient::chain_info(self) + } + + fn reorg_depth(&self, a: &H256, b: &H256) -> Option { + self.tree_route(a, b).map(|route| route.index as u64) + } + + fn earliest_state(&self) -> Option { + Some(self.pruning_info().earliest_state) + } + + fn block_headers(&self, req: request::Headers) -> Vec { + 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 { + 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 { + 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 { + 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 { + 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 { + req.requests.into_iter().map(|_| ::rlp::EMPTY_LIST_RLP.to_vec()).collect() + } + + fn pending_transactions(&self) -> Vec { + BlockChainClient::pending_transactions(self) + } } \ No newline at end of file diff --git a/ethcore/light/src/request.rs b/ethcore/light/src/types/les_request.rs similarity index 72% rename from ethcore/light/src/request.rs rename to ethcore/light/src/types/les_request.rs index f043f0f25..d0de080ee 100644 --- a/ethcore/light/src/request.rs +++ b/ethcore/light/src/types/les_request.rs @@ -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 @@ -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, } /// 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, } /// 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, } /// 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, + pub requests: Vec, } /// 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(), + } + } } \ No newline at end of file diff --git a/ethcore/light/src/types/mod.rs b/ethcore/light/src/types/mod.rs new file mode 100644 index 000000000..2625358a3 --- /dev/null +++ b/ethcore/light/src/types/mod.rs @@ -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 . + +//! 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")); diff --git a/ethcore/light/src/types/mod.rs.in b/ethcore/light/src/types/mod.rs.in new file mode 100644 index 000000000..2b7386b5b --- /dev/null +++ b/ethcore/light/src/types/mod.rs.in @@ -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 . + +pub mod les_request; \ No newline at end of file diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 394d69a92..b26b19ad3 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -52,7 +52,7 @@ use blockchain::{BlockChain, BlockProvider, TreeRoute, ImportRoute}; use client::{ BlockID, TransactionID, UncleID, TraceId, ClientConfig, BlockChainClient, MiningBlockChainClient, TraceFilter, CallAnalytics, BlockImportError, Mode, - ChainNotify, + ChainNotify, PruningInfo, ProvingBlockChainClient, }; use client::Error as ClientError; use env_info::EnvInfo; @@ -1311,6 +1311,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 { @@ -1395,32 +1402,60 @@ impl MayPanic for Client { } } +impl ProvingBlockChainClient for Client { + fn prove_storage(&self, key1: H256, key2: H256, from_level: u32, id: BlockID) -> Vec { + self.state_at(id) + .and_then(move |state| state.prove_storage(key1, key2, from_level).ok()) + .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 { + 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()); + } +} \ No newline at end of file diff --git a/ethcore/src/client/mod.rs b/ethcore/src/client/mod.rs index 3898ab6cd..af4daece6 100644 --- a/ethcore/src/client/mod.rs +++ b/ethcore/src/client/mod.rs @@ -25,18 +25,21 @@ mod client; pub use self::client::*; pub use self::config::{Mode, ClientConfig, 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 diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index b93205762..973772f81 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -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; @@ -671,4 +672,11 @@ impl BlockChainClient for TestBlockChainClient { fn mode(&self) -> Mode { Mode::Active } fn set_mode(&self, _: Mode) { unimplemented!(); } + + fn pruning_info(&self) -> PruningInfo { + PruningInfo { + earliest_chain: 1, + earliest_state: 1, + } + } } diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index e6a71357e..4290831e8 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -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. @@ -253,10 +254,15 @@ pub trait BlockChainClient : Sync + Send { /// Returns engine-related extra info for `UncleID`. fn uncle_extra_info(&self, id: UncleID) -> Option>; + + /// 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, @@ -274,4 +280,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; + + /// 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; + + /// Get code by address hash. + fn code_by_hash(&self, account_key: H256, id: BlockID) -> Bytes; +} \ No newline at end of file diff --git a/ethcore/src/state/account.rs b/ethcore/src/state/account.rs index bdcc92bd0..c3d812c46 100644 --- a/ethcore/src/state/account.rs +++ b/ethcore/src/state/account.rs @@ -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, Box> { + 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)) diff --git a/ethcore/src/state/mod.rs b/ethcore/src/state/mod.rs index 07ac72774..b3d63d0ae 100644 --- a/ethcore/src/state/mod.rs +++ b/ethcore/src/state/mod.rs @@ -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, Box> { + 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, Box> { + // 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, Box> { + 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()) diff --git a/ethcore/src/types/mod.rs.in b/ethcore/src/types/mod.rs.in index 6ef67009a..d462d3cb9 100644 --- a/ethcore/src/types/mod.rs.in +++ b/ethcore/src/types/mod.rs.in @@ -34,3 +34,4 @@ pub mod block_import_error; pub mod restoration_status; pub mod snapshot_manifest; pub mod mode; +pub mod pruning_info; \ No newline at end of file diff --git a/ethcore/src/types/pruning_info.rs b/ethcore/src/types/pruning_info.rs new file mode 100644 index 000000000..40564f488 --- /dev/null +++ b/ethcore/src/types/pruning_info.rs @@ -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 . + +//! 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, +} \ No newline at end of file diff --git a/js/package.json b/js/package.json index 42836bb7c..6f984d901 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.92", + "version": "0.2.94", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", diff --git a/js/src/api/contract/contract.js b/js/src/api/contract/contract.js index 28bad8a3b..d9ec9b03e 100644 --- a/js/src/api/contract/contract.js +++ b/js/src/api/contract/contract.js @@ -209,8 +209,10 @@ export default class Contract { _bindFunction = (func) => { func.call = (options, values = []) => { + const callData = this._encodeOptions(func, this._addOptionsTo(options), values); + return this._api.eth - .call(this._encodeOptions(func, this._addOptionsTo(options), values)) + .call(callData) .then((encoded) => func.decodeOutput(encoded)) .then((tokens) => tokens.map((token) => token.value)) .then((returns) => returns.length === 1 ? returns[0] : returns); diff --git a/js/src/contracts/code/index.js b/js/src/contracts/code/index.js new file mode 100644 index 000000000..baa144979 --- /dev/null +++ b/js/src/contracts/code/index.js @@ -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 . + +import wallet from './wallet'; + +export { + wallet +}; diff --git a/js/src/contracts/code/wallet.js b/js/src/contracts/code/wallet.js new file mode 100644 index 000000000..a4db4459b --- /dev/null +++ b/js/src/contracts/code/wallet.js @@ -0,0 +1,23 @@ +// 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 . + +/** + * @version Solidity v0.4.6 + * @from https://github.com/ethereum/dapp-bin/blob/dd5c485359074d49f571693ae064ce78970f3d6d/wallet/wallet.sol + * @date 22-Nov-2016 @ 15h00 UTC + */ +export default '0x606060405234620000005760405162001a0638038062001a06833981016040528080518201919060200180519060200190919080519060200190919050505b805b83835b600060018351016001819055503373ffffffffffffffffffffffffffffffffffffffff166002600161010081101562000000570160005b5081905550600161010260003373ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550600090505b825181101562000158578281815181101562000000579060200190602002015173ffffffffffffffffffffffffffffffffffffffff1660028260020161010081101562000000570160005b50819055508060020161010260008584815181101562000000579060200190602002015173ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055505b806001019050620000b4565b816000819055505b5050508061010581905550620001896200019c6401000000000262001832176401000000009004565b610107819055505b505b505050620001b1565b60006201518042811562000000570490505b90565b61184680620001c06000396000f3606060405236156100f4576000357c010000000000000000000000000000000000000000000000000000000090048063173825d91461015c5780632f54bf6e146101795780634123cb6b146101ac57806352375093146101cf5780635c52c2f5146101f2578063659010e7146102015780637065cb4814610224578063746c917114610241578063797af62714610264578063b20d30a914610297578063b61d27f6146102b4578063b75c7dc614610306578063ba51a6df14610323578063c2cf732614610340578063c41a360a1461037c578063cbf0b0c0146103c3578063f00d4b5d146103e0578063f1736d8614610406575b61015a5b6000341115610157577fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c3334604051808373ffffffffffffffffffffffffffffffffffffffff1681526020018281526020019250505060405180910390a15b5b565b005b34610000576101776004808035906020019091905050610429565b005b34610000576101946004808035906020019091905050610553565b60405180821515815260200191505060405180910390f35b34610000576101b961058b565b6040518082815260200191505060405180910390f35b34610000576101dc610591565b6040518082815260200191505060405180910390f35b34610000576101ff610598565b005b346100005761020e6105d3565b6040518082815260200191505060405180910390f35b346100005761023f60048080359060200190919050506105da565b005b346100005761024e61070d565b6040518082815260200191505060405180910390f35b346100005761027f6004808035906020019091905050610713565b60405180821515815260200191505060405180910390f35b34610000576102b26004808035906020019091905050610abf565b005b34610000576102ec60048080359060200190919080359060200190919080359060200190820180359060200191909192905050610afa565b604051808260001916815260200191505060405180910390f35b34610000576103216004808035906020019091905050610e68565b005b346100005761033e6004808035906020019091905050610f5f565b005b34610000576103646004808035906020019091908035906020019091905050610fe7565b60405180821515815260200191505060405180910390f35b34610000576103976004808035906020019091905050611065565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34610000576103de6004808035906020019091905050611085565b005b346100005761040460048080359060200190919080359060200190919050506110d0565b005b3461000057610413611254565b6040518082815260200191505060405180910390f35b60006000366040518083838082843782019150509250505060405180910390206104528161125b565b1561054d5761010260008473ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054915060008214156104925761054c565b60016001540360005411156104a65761054c565b6000600283610100811015610000570160005b5081905550600061010260008573ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055506104f661147f565b6104fe61157a565b7f58619076adf5bb0943d100ef88d52d7c3fd691b19d3a9071b555b651fbf418da83604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a15b5b5b505050565b6000600061010260008473ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541190505b919050565b60015481565b6101075481565b6000366040518083838082843782019150509250505060405180910390206105bf8161125b565b156105cf576000610106819055505b5b5b50565b6101065481565b6000366040518083838082843782019150509250505060405180910390206106018161125b565b156107085761060f82610553565b1561061957610707565b61062161147f565b60fa6001541015156106365761063561157a565b5b60fa60015410151561064757610707565b6001600081548092919060010191905055508173ffffffffffffffffffffffffffffffffffffffff166002600154610100811015610000570160005b508190555060015461010260008473ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055507f994a936646fe87ffe4f1e469d3d6aa417d6b855598397f323de5b449f765f0c382604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a15b5b5b5050565b60005481565b60008161071f8161125b565b15610ab857600061010860008560001916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16141515610ab65761010860008460001916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1661010860008560001916815260200190815260200160002060010154610108600086600019168152602001908152602001600020600201604051808280546001816001161561010002031660029004801561086d5780601f106108425761010080835404028352916020019161086d565b820191906000526020600020905b81548152906001019060200180831161085057829003601f168201915b505091505060006040518083038185876185025a03f192505050507fe7c957c06e9a662c1a6c77366179f5b702b97651dc28eee7d5bf1dff6e40bb4a33846101086000876000191681526020019081526020016000206001015461010860008860001916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16610108600089600019168152602001908152602001600020600201604051808673ffffffffffffffffffffffffffffffffffffffff168152602001856000191681526020018481526020018373ffffffffffffffffffffffffffffffffffffffff168152602001806020018281038252838181546001816001161561010002031660029004815260200191508054600181600116156101000203166002900480156109ef5780601f106109c4576101008083540402835291602001916109ef565b820191906000526020600020905b8154815290600101906020018083116109d257829003601f168201915b5050965050505050505060405180910390a161010860008460001916815260200190815260200160002060006000820160006101000a81549073ffffffffffffffffffffffffffffffffffffffff0219169055600182016000905560028201805460018160011615610100020316600290046000825580601f10610a735750610aaa565b601f016020900490600052602060002090810190610aa991905b80821115610aa5576000816000905550600101610a8d565b5090565b5b50505060019150610ab7565b5b5b5b50919050565b600036604051808383808284378201915050925050506040518091039020610ae68161125b565b15610af55781610105819055505b5b5b5050565b6000610b0533610553565b15610e5f57610b13846116c9565b15610bfc577f92ca3a80853e6663fa31fa10b99225f18d4902939b4c53a9caae9043f6efd0043385878686604051808673ffffffffffffffffffffffffffffffffffffffff1681526020018581526020018473ffffffffffffffffffffffffffffffffffffffff1681526020018060200182810382528484828181526020019250808284378201915050965050505050505060405180910390a18473ffffffffffffffffffffffffffffffffffffffff168484846040518083838082843782019150509250505060006040518083038185876185025a03f1925050505060006001029050610e5e565b60003643604051808484808284378201915050828152602001935050505060405180910390209050610c2d81610713565b158015610c8b5750600061010860008360001916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16145b15610e5d578461010860008360001916815260200190815260200160002060000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff02191690836c01000000000000000000000000908102040217905550836101086000836000191681526020019081526020016000206001018190555082826101086000846000191681526020019081526020016000206002019190828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10610d6657803560ff1916838001178555610d94565b82800160010185558215610d94579182015b82811115610d93578235825591602001919060010190610d78565b5b509050610db991905b80821115610db5576000816000905550600101610d9d565b5090565b50507f1733cbb53659d713b79580f79f3f9ff215f78a7c7aa45890f3b89fc5cddfbf3281338688878760405180876000191681526020018673ffffffffffffffffffffffffffffffffffffffff1681526020018581526020018473ffffffffffffffffffffffffffffffffffffffff168152602001806020018281038252848482818152602001925080828437820191505097505050505050505060405180910390a15b5b5b5b949350505050565b60006000600061010260003373ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205492506000831415610ea957610f59565b8260020a915061010360008560001916815260200190815260200160002090506000828260010154161115610f585780600001600081548092919060010191905055508181600101600082825403925050819055507fc7fb647e59b18047309aa15aad418e5d7ca96d173ad704f1031a2c3d7591734b3385604051808373ffffffffffffffffffffffffffffffffffffffff168152602001826000191681526020019250505060405180910390a15b5b50505050565b600036604051808383808284378201915050925050506040518091039020610f868161125b565b15610fe257600154821115610f9a57610fe1565b81600081905550610fa961147f565b7facbdb084c721332ac59f9b8e392196c9eb0e4932862da8eb9beaf0dad4f550da826040518082815260200191505060405180910390a15b5b5b5050565b6000600060006000610103600087600019168152602001908152602001600020925061010260008673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205491506000821415611048576000935061105c565b8160020a9050600081846001015416141593505b50505092915050565b6000600260018301610100811015610000570160005b505490505b919050565b6000366040518083838082843782019150509250505060405180910390206110ac8161125b565b156110cb578173ffffffffffffffffffffffffffffffffffffffff16ff5b5b5b5050565b60006000366040518083838082843782019150509250505060405180910390206110f98161125b565b1561124d5761110783610553565b156111115761124c565b61010260008573ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549150600082141561114c5761124c565b61115461147f565b8273ffffffffffffffffffffffffffffffffffffffff16600283610100811015610000570160005b5081905550600061010260008673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508161010260008573ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055507fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c8484604051808373ffffffffffffffffffffffffffffffffffffffff1681526020018273ffffffffffffffffffffffffffffffffffffffff1681526020019250505060405180910390a15b5b5b50505050565b6101055481565b600060006000600061010260003373ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549250600083141561129e57611477565b61010360008660001916815260200190815260200160002091506000826000015414156113555760005482600001819055506000826001018190555061010480548091906001018154818355818115116113245781836000526020600020918201910161132391905b8082111561131f576000816000905550600101611307565b5090565b5b5050508260020181905550846101048360020154815481101561000057906000526020600020900160005b50819055505b8260020a90506000818360010154161415611476577fe1c52dc63b719ade82e8bea94cc41a0d5d28e4aaf536adb5e9cccc9ff8c1aeda3386604051808373ffffffffffffffffffffffffffffffffffffffff168152602001826000191681526020019250505060405180910390a16001826000015411151561144d5761010461010360008760001916815260200190815260200160002060020154815481101561000057906000526020600020900160005b5060009055610103600086600019168152602001908152602001600020600060008201600090556001820160009055600282016000905550506001935061147756611475565b8160000160008154809291906001900391905055508082600101600082825417925050819055505b5b5b505050919050565b60006000610104805490509150600090505b8181101561156d57610108600061010483815481101561000057906000526020600020900160005b505460001916815260200190815260200160002060006000820160006101000a81549073ffffffffffffffffffffffffffffffffffffffff0219169055600182016000905560028201805460018160011615610100020316600290046000825580601f10611527575061155e565b601f01602090049060005260206000209081019061155d91905b80821115611559576000816000905550600101611541565b5090565b5b5050505b806001019050611491565b61157561174f565b5b5050565b6000600190505b6001548110156116c5575b600154811080156115b057506000600282610100811015610000570160005b505414155b156115c257808060010191505061158c565b5b60016001541180156115e9575060006002600154610100811015610000570160005b5054145b1561160657600160008154809291906001900391905055506115c3565b6001548110801561162c575060006002600154610100811015610000570160005b505414155b801561164a57506000600282610100811015610000570160005b5054145b156116c0576002600154610100811015610000570160005b5054600282610100811015610000570160005b5081905550806101026000600284610100811015610000570160005b505481526020019081526020016000208190555060006002600154610100811015610000570160005b50819055505b611581565b5b50565b60006116d433610553565b1561174957610107546116e5611832565b1115611704576000610106819055506116fc611832565b610107819055505b610106548261010654011015801561172457506101055482610106540111155b1561174357816101066000828254019250508190555060019050611748565b600090505b5b5b919050565b60006000610104805490509150600090505b818110156117f357600060010261010482815481101561000057906000526020600020900160005b5054600019161415156117e757610103600061010483815481101561000057906000526020600020900160005b5054600019168152602001908152602001600020600060008201600090556001820160009055600282016000905550505b5b806001019050611761565b6101048054600082559060005260206000209081019061182b91905b8082111561182757600081600090555060010161180f565b5090565b5b505b5050565b600062015180428115610000570490505b9056'; + diff --git a/js/src/contracts/snippets/wallet.sol b/js/src/contracts/snippets/wallet.sol new file mode 100644 index 000000000..b369eea76 --- /dev/null +++ b/js/src/contracts/snippets/wallet.sol @@ -0,0 +1,388 @@ +//sol Wallet +// Multi-sig, daily-limited account proxy/wallet. +// @authors: +// Gav Wood +// inheritable "property" contract that enables methods to be protected by requiring the acquiescence of either a +// single, or, crucially, each of a number of, designated owners. +// usage: +// use modifiers onlyowner (just own owned) or onlymanyowners(hash), whereby the same hash must be provided by +// some number (specified in constructor) of the set of owners (specified in the constructor, modifiable) before the +// interior is executed. +pragma solidity ^0.4.6; + +contract 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; +} diff --git a/js/src/dapps/registry/Application/application.js b/js/src/dapps/registry/Application/application.js index 5102e5d57..45290937e 100644 --- a/js/src/dapps/registry/Application/application.js +++ b/js/src/dapps/registry/Application/application.js @@ -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, diff --git a/js/src/dapps/registry/Container.js b/js/src/dapps/registry/Container.js index 1464320e6..e379038fa 100644 --- a/js/src/dapps/registry/Container.js +++ b/js/src/dapps/registry/Container.js @@ -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 }; diff --git a/js/src/dapps/registry/Lookup/lookup.js b/js/src/dapps/registry/Lookup/lookup.js index 436d113b9..b8b6207cc 100644 --- a/js/src/dapps/registry/Lookup/lookup.js +++ b/js/src/dapps/registry/Lookup/lookup.js @@ -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 } diff --git a/js/src/main.js b/js/src/main.js index ae785cbb6..d508c50fc 100644 --- a/js/src/main.js +++ b/js/src/main.js @@ -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 { + diff --git a/js/src/modals/CreateAccount/NewAccount/newAccount.js b/js/src/modals/CreateAccount/NewAccount/newAccount.js index a100ab19a..bbbf6fed3 100644 --- a/js/src/modals/CreateAccount/NewAccount/newAccount.js +++ b/js/src/modals/CreateAccount/NewAccount/newAccount.js @@ -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); }); diff --git a/js/src/modals/CreateWallet/WalletDetails/index.js b/js/src/modals/CreateWallet/WalletDetails/index.js new file mode 100644 index 000000000..266385511 --- /dev/null +++ b/js/src/modals/CreateWallet/WalletDetails/index.js @@ -0,0 +1,17 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +export default from './walletDetails'; diff --git a/js/src/modals/CreateWallet/WalletDetails/walletDetails.js b/js/src/modals/CreateWallet/WalletDetails/walletDetails.js new file mode 100644 index 000000000..9126fdb72 --- /dev/null +++ b/js/src/modals/CreateWallet/WalletDetails/walletDetails.js @@ -0,0 +1,111 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import React, { Component, PropTypes } from 'react'; + +import { Form, TypedInput, Input, AddressSelect } from '../../../ui'; +import { parseAbiType } from '../../../util/abi'; + +export default class WalletDetails extends Component { + static propTypes = { + accounts: PropTypes.object.isRequired, + wallet: PropTypes.object.isRequired, + errors: PropTypes.object.isRequired, + onChange: PropTypes.func.isRequired + }; + + render () { + const { accounts, wallet, errors } = this.props; + + return ( +
+ + + + + + + + + + + + + ); + } + + 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 }); + } +} diff --git a/js/src/modals/CreateWallet/WalletInfo/index.js b/js/src/modals/CreateWallet/WalletInfo/index.js new file mode 100644 index 000000000..975e7bd59 --- /dev/null +++ b/js/src/modals/CreateWallet/WalletInfo/index.js @@ -0,0 +1,17 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +export default from './walletInfo'; diff --git a/js/src/modals/CreateWallet/WalletInfo/walletInfo.js b/js/src/modals/CreateWallet/WalletInfo/walletInfo.js new file mode 100644 index 000000000..301263314 --- /dev/null +++ b/js/src/modals/CreateWallet/WalletInfo/walletInfo.js @@ -0,0 +1,85 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import React, { 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 + }; + + render () { + const { address, required, daylimit, name } = this.props; + + return ( + +
{ name } has been deployed at
+
+ + +
{ address }
+
+
with the following owners
+
+ { this.renderOwners() } +
+

+ { required } owners are required to confirm a transaction. +

+

+ The daily limit is set to { daylimit }. +

+
+ ); + } + + renderOwners () { + const { account, owners } = this.props; + + return [].concat(account, owners).map((address, id) => ( +
+ +
{ this.addressToString(address) }
+
+ )); + } + + addressToString (address) { + const { accounts } = this.props; + + if (accounts[address]) { + return accounts[address].name || address; + } + + return address; + } +} diff --git a/js/src/modals/CreateWallet/createWallet.css b/js/src/modals/CreateWallet/createWallet.css new file mode 100644 index 000000000..a450466f0 --- /dev/null +++ b/js/src/modals/CreateWallet/createWallet.css @@ -0,0 +1,39 @@ +/* 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 . +*/ + +.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; + } +} diff --git a/js/src/modals/CreateWallet/createWallet.js b/js/src/modals/CreateWallet/createWallet.js new file mode 100644 index 000000000..8f38fec12 --- /dev/null +++ b/js/src/modals/CreateWallet/createWallet.js @@ -0,0 +1,182 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import React, { 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 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 ( + + + + ); + } + + return ( + + { this.renderPage() } + + ); + } + + renderPage () { + const { step } = this.store; + const { accounts } = this.props; + + switch (step) { + case 'DEPLOYMENT': + return ( + + { this.store.txhash ? () : null } + + ); + + case 'INFO': + return ( + + ); + + default: + case 'DETAILS': + return ( + + ); + } + } + + renderDialogActions () { + const { step, hasErrors, rejected, onCreate } = this.store; + + const cancelBtn = ( +