From eba0dd5023a8284b99eeaf7d3c0d8c5562b3ed0c Mon Sep 17 00:00:00 2001 From: Arkadiy Paronyan Date: Wed, 9 Nov 2016 19:40:36 +0100 Subject: [PATCH 01/12] Additional snapshot sync checks (#3318) * Additional snapshot sync checks * Proper checks * Proper highset block check --- sync/src/chain.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/sync/src/chain.rs b/sync/src/chain.rs index 063d60e6b..2f810e754 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -457,12 +457,19 @@ impl ChainSync { if self.state != SyncState::WaitingPeers { return; } - let best_block = io.chain().chain_info().best_block_number; + // Make sure the snapshot block is not too far away from best block and network best block and + // that it is higher than fork detection block + let our_best_block = io.chain().chain_info().best_block_number; + let fork_block = self.fork_block.as_ref().map(|&(n, _)| n).unwrap_or(0); let (best_hash, max_peers, snapshot_peers) = { //collect snapshot infos from peers let snapshots = self.peers.iter() - .filter(|&(_, p)| p.is_allowed() && p.snapshot_number.map_or(false, |sn| best_block < sn && (sn - best_block) > SNAPSHOT_RESTORE_THRESHOLD)) + .filter(|&(_, p)| p.is_allowed() && p.snapshot_number.map_or(false, |sn| + our_best_block < sn && (sn - our_best_block) > SNAPSHOT_RESTORE_THRESHOLD && + sn > fork_block && + self.highest_block.map_or(true, |highest| highest >= sn && (highest - sn) <= SNAPSHOT_RESTORE_THRESHOLD) + )) .filter_map(|(p, peer)| peer.snapshot_hash.map(|hash| (p, hash.clone()))); let mut snapshot_peers = HashMap::new(); From 88c9cea04d84272405d97b29649a3fe50aa06103 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 9 Nov 2016 19:41:47 +0100 Subject: [PATCH 02/12] Dapps interface RPC (#3311) * Dapps Interface RPC * Adding JS apis * Support for signer interface in proxypac and embeds * Fixing tests * fixing tests again --- dapps/src/apps/fetcher.rs | 26 ++++++++++----------- dapps/src/apps/fs.rs | 4 ++-- dapps/src/apps/mod.rs | 12 +++++----- dapps/src/handlers/content.rs | 16 ++++++------- dapps/src/handlers/fetch.rs | 16 ++++++------- dapps/src/handlers/mod.rs | 8 +++---- dapps/src/lib.rs | 36 ++++++++++++++--------------- dapps/src/page/builtin.rs | 14 +++++------ dapps/src/page/handler.rs | 12 +++++----- dapps/src/page/local.rs | 12 +++++----- dapps/src/proxypac.rs | 12 +++++----- dapps/src/router/mod.rs | 16 ++++++------- dapps/src/tests/helpers.rs | 4 ++-- js/src/api/rpc/parity/parity.js | 5 ++++ js/src/jsonrpc/interfaces/parity.js | 9 ++++++++ parity/configuration.rs | 6 ++--- parity/dapps.rs | 10 ++++---- parity/rpc_apis.rs | 4 +++- parity/run.rs | 8 +++++-- rpc/src/v1/helpers/signer.rs | 18 +++++++-------- rpc/src/v1/impls/parity.rs | 13 ++++++++++- rpc/src/v1/tests/mocked/parity.rs | 25 ++++++++++++++++++-- rpc/src/v1/traits/parity.rs | 4 ++++ 23 files changed, 173 insertions(+), 117 deletions(-) diff --git a/dapps/src/apps/fetcher.rs b/dapps/src/apps/fetcher.rs index 041064121..2c1414201 100644 --- a/dapps/src/apps/fetcher.rs +++ b/dapps/src/apps/fetcher.rs @@ -47,7 +47,7 @@ pub struct ContentFetcher { resolver: R, cache: Arc>, sync: Arc, - embeddable_at: Option, + embeddable_on: Option<(String, u16)>, } impl Drop for ContentFetcher { @@ -59,7 +59,7 @@ impl Drop for ContentFetcher { impl ContentFetcher { - pub fn new(resolver: R, sync_status: Arc, embeddable_at: Option) -> Self { + pub fn new(resolver: R, sync_status: Arc, embeddable_on: Option<(String, u16)>) -> Self { let mut dapps_path = env::temp_dir(); dapps_path.push(random_filename()); @@ -68,17 +68,17 @@ impl ContentFetcher { resolver: resolver, sync: sync_status, cache: Arc::new(Mutex::new(ContentCache::default())), - embeddable_at: embeddable_at, + embeddable_on: embeddable_on, } } - fn still_syncing(port: Option) -> Box { + fn still_syncing(address: Option<(String, u16)>) -> Box { Box::new(ContentHandler::error( StatusCode::ServiceUnavailable, "Sync In Progress", "Your node is still syncing. We cannot resolve any content before it's fully synced.", Some("Refresh"), - port, + address, )) } @@ -145,7 +145,7 @@ impl ContentFetcher { match content { // Don't serve dapps if we are still syncing (but serve content) Some(URLHintResult::Dapp(_)) if self.sync.is_major_importing() => { - (None, Self::still_syncing(self.embeddable_at)) + (None, Self::still_syncing(self.embeddable_on.clone())) }, Some(URLHintResult::Dapp(dapp)) => { let (handler, fetch_control) = ContentFetcherHandler::new( @@ -155,9 +155,9 @@ impl ContentFetcher { id: content_id.clone(), dapps_path: self.dapps_path.clone(), on_done: Box::new(on_done), - embeddable_at: self.embeddable_at, + embeddable_on: self.embeddable_on.clone(), }, - self.embeddable_at, + self.embeddable_on.clone(), ); (Some(ContentStatus::Fetching(fetch_control)), Box::new(handler) as Box) @@ -172,13 +172,13 @@ impl ContentFetcher { content_path: self.dapps_path.clone(), on_done: Box::new(on_done), }, - self.embeddable_at, + self.embeddable_on.clone(), ); (Some(ContentStatus::Fetching(fetch_control)), Box::new(handler) as Box) }, None if self.sync.is_major_importing() => { - (None, Self::still_syncing(self.embeddable_at)) + (None, Self::still_syncing(self.embeddable_on.clone())) }, None => { // This may happen when sync status changes in between @@ -188,7 +188,7 @@ impl ContentFetcher { "Resource Not Found", "Requested resource was not found.", None, - self.embeddable_at, + self.embeddable_on.clone(), )) as Box) }, } @@ -293,7 +293,7 @@ struct DappInstaller { id: String, dapps_path: PathBuf, on_done: Box) + Send>, - embeddable_at: Option, + embeddable_on: Option<(String, u16)>, } impl DappInstaller { @@ -386,7 +386,7 @@ impl ContentValidator for DappInstaller { try!(manifest_file.write_all(manifest_str.as_bytes())); // Create endpoint - let app = LocalPageEndpoint::new(target, manifest.clone().into(), PageCache::Enabled, self.embeddable_at); + let app = LocalPageEndpoint::new(target, manifest.clone().into(), PageCache::Enabled, self.embeddable_on.clone()); // Return modified app manifest Ok((manifest.id.clone(), app)) diff --git a/dapps/src/apps/fs.rs b/dapps/src/apps/fs.rs index 104b33035..f0b4ddfa8 100644 --- a/dapps/src/apps/fs.rs +++ b/dapps/src/apps/fs.rs @@ -97,12 +97,12 @@ fn read_manifest(name: &str, mut path: PathBuf) -> EndpointInfo { }) } -pub fn local_endpoints(dapps_path: String, signer_port: Option) -> Endpoints { +pub fn local_endpoints(dapps_path: String, signer_address: Option<(String, u16)>) -> Endpoints { let mut pages = Endpoints::new(); for dapp in local_dapps(dapps_path) { pages.insert( dapp.id, - Box::new(LocalPageEndpoint::new(dapp.path, dapp.info, PageCache::Disabled, signer_port)) + Box::new(LocalPageEndpoint::new(dapp.path, dapp.info, PageCache::Disabled, signer_address.clone())) ); } pages diff --git a/dapps/src/apps/mod.rs b/dapps/src/apps/mod.rs index 11919d6d2..3cb0d8256 100644 --- a/dapps/src/apps/mod.rs +++ b/dapps/src/apps/mod.rs @@ -37,26 +37,26 @@ pub fn utils() -> Box { Box::new(PageEndpoint::with_prefix(parity_ui::App::default(), UTILS_PATH.to_owned())) } -pub fn all_endpoints(dapps_path: String, signer_port: Option) -> Endpoints { +pub fn all_endpoints(dapps_path: String, signer_address: Option<(String, u16)>) -> Endpoints { // fetch fs dapps at first to avoid overwriting builtins - let mut pages = fs::local_endpoints(dapps_path, signer_port); + let mut pages = fs::local_endpoints(dapps_path, signer_address.clone()); // NOTE [ToDr] Dapps will be currently embeded on 8180 - insert::(&mut pages, "ui", Embeddable::Yes(signer_port)); - pages.insert("proxy".into(), ProxyPac::boxed(signer_port)); + insert::(&mut pages, "ui", Embeddable::Yes(signer_address.clone())); + pages.insert("proxy".into(), ProxyPac::boxed(signer_address)); pages } fn insert(pages: &mut Endpoints, id: &str, embed_at: Embeddable) { pages.insert(id.to_owned(), Box::new(match embed_at { - Embeddable::Yes(port) => PageEndpoint::new_safe_to_embed(T::default(), port), + Embeddable::Yes(address) => PageEndpoint::new_safe_to_embed(T::default(), address), Embeddable::No => PageEndpoint::new(T::default()), })); } enum Embeddable { - Yes(Option), + Yes(Option<(String, u16)>), #[allow(dead_code)] No, } diff --git a/dapps/src/handlers/content.rs b/dapps/src/handlers/content.rs index a67fbcd0b..738a9a890 100644 --- a/dapps/src/handlers/content.rs +++ b/dapps/src/handlers/content.rs @@ -32,7 +32,7 @@ pub struct ContentHandler { content: String, mimetype: Mime, write_pos: usize, - safe_to_embed_at_port: Option, + safe_to_embed_on: Option<(String, u16)>, } impl ContentHandler { @@ -44,31 +44,31 @@ impl ContentHandler { Self::new(StatusCode::NotFound, content, mimetype) } - pub fn html(code: StatusCode, content: String, embeddable_at: Option) -> Self { - Self::new_embeddable(code, content, mime!(Text/Html), embeddable_at) + pub fn html(code: StatusCode, content: String, embeddable_on: Option<(String, u16)>) -> Self { + Self::new_embeddable(code, content, mime!(Text/Html), embeddable_on) } - pub fn error(code: StatusCode, title: &str, message: &str, details: Option<&str>, embeddable_at: Option) -> Self { + pub fn error(code: StatusCode, title: &str, message: &str, details: Option<&str>, embeddable_on: Option<(String, u16)>) -> Self { Self::html(code, format!( include_str!("../error_tpl.html"), title=title, message=message, details=details.unwrap_or_else(|| ""), version=version(), - ), embeddable_at) + ), embeddable_on) } pub fn new(code: StatusCode, content: String, mimetype: Mime) -> Self { Self::new_embeddable(code, content, mimetype, None) } - pub fn new_embeddable(code: StatusCode, content: String, mimetype: Mime, embeddable_at: Option) -> Self { + pub fn new_embeddable(code: StatusCode, content: String, mimetype: Mime, embeddable_on: Option<(String, u16)>) -> Self { ContentHandler { code: code, content: content, mimetype: mimetype, write_pos: 0, - safe_to_embed_at_port: embeddable_at, + safe_to_embed_on: embeddable_on, } } } @@ -85,7 +85,7 @@ impl server::Handler for ContentHandler { fn on_response(&mut self, res: &mut server::Response) -> Next { res.set_status(self.code); res.headers_mut().set(header::ContentType(self.mimetype.clone())); - add_security_headers(&mut res.headers_mut(), self.safe_to_embed_at_port.clone()); + add_security_headers(&mut res.headers_mut(), self.safe_to_embed_on.clone()); Next::write() } diff --git a/dapps/src/handlers/fetch.rs b/dapps/src/handlers/fetch.rs index 1a0221bff..a34b58fa7 100644 --- a/dapps/src/handlers/fetch.rs +++ b/dapps/src/handlers/fetch.rs @@ -138,7 +138,7 @@ pub struct ContentFetcherHandler { client: Option, installer: H, request_url: Option, - embeddable_at: Option, + embeddable_on: Option<(String, u16)>, } impl Drop for ContentFetcherHandler { @@ -157,7 +157,7 @@ impl ContentFetcherHandler { url: String, control: Control, handler: H, - embeddable_at: Option, + embeddable_on: Option<(String, u16)>, ) -> (Self, Arc) { let fetch_control = Arc::new(FetchControl::default()); @@ -169,7 +169,7 @@ impl ContentFetcherHandler { status: FetchState::NotStarted(url), installer: handler, request_url: None, - embeddable_at: embeddable_at, + embeddable_on: embeddable_on, }; (handler, fetch_control) @@ -208,7 +208,7 @@ impl server::Handler for ContentFetcherHandler< "Unable To Start Dapp Download", "Could not initialize download of the dapp. It might be a problem with the remote server.", Some(&format!("{}", e)), - self.embeddable_at, + self.embeddable_on.clone(), )), } }, @@ -218,7 +218,7 @@ impl server::Handler for ContentFetcherHandler< "Method Not Allowed", "Only GET requests are allowed.", None, - self.embeddable_at, + self.embeddable_on.clone(), )), }) } else { None }; @@ -241,7 +241,7 @@ impl server::Handler for ContentFetcherHandler< "Download Timeout", &format!("Could not fetch content within {} seconds.", FETCH_TIMEOUT), None, - self.embeddable_at, + self.embeddable_on.clone(), ); Self::close_client(&mut self.client); (Some(FetchState::Error(timeout)), Next::write()) @@ -263,7 +263,7 @@ impl server::Handler for ContentFetcherHandler< "Invalid Dapp", "Downloaded bundle does not contain a valid content.", Some(&format!("{:?}", e)), - self.embeddable_at, + self.embeddable_on.clone(), )) }, Ok((id, result)) => { @@ -284,7 +284,7 @@ impl server::Handler for ContentFetcherHandler< "Download Error", "There was an error when fetching the content.", Some(&format!("{:?}", e)), - self.embeddable_at, + self.embeddable_on.clone(), ); (Some(FetchState::Error(error)), Next::write()) }, diff --git a/dapps/src/handlers/mod.rs b/dapps/src/handlers/mod.rs index 3d96e8a40..b575509a5 100644 --- a/dapps/src/handlers/mod.rs +++ b/dapps/src/handlers/mod.rs @@ -30,18 +30,18 @@ pub use self::fetch::{ContentFetcherHandler, ContentValidator, FetchControl}; use url::Url; use hyper::{server, header, net, uri}; -use signer_address; +use address; /// Adds security-related headers to the Response. -pub fn add_security_headers(headers: &mut header::Headers, embeddable_at: Option) { +pub fn add_security_headers(headers: &mut header::Headers, embeddable_on: Option<(String, u16)>) { headers.set_raw("X-XSS-Protection", vec![b"1; mode=block".to_vec()]); headers.set_raw("X-Content-Type-Options", vec![b"nosniff".to_vec()]); // Embedding header: - if let Some(port) = embeddable_at { + if let Some(embeddable_on) = embeddable_on { headers.set_raw( "X-Frame-Options", - vec![format!("ALLOW-FROM http://{}", signer_address(port)).into_bytes()] + vec![format!("ALLOW-FROM http://{}", address(embeddable_on)).into_bytes()] ); } else { // TODO [ToDr] Should we be more strict here (DENY?)? diff --git a/dapps/src/lib.rs b/dapps/src/lib.rs index 4394d1183..2c9fa33d1 100644 --- a/dapps/src/lib.rs +++ b/dapps/src/lib.rs @@ -112,7 +112,7 @@ pub struct ServerBuilder { handler: Arc, registrar: Arc, sync_status: Arc, - signer_port: Option, + signer_address: Option<(String, u16)>, } impl Extendable for ServerBuilder { @@ -129,7 +129,7 @@ impl ServerBuilder { handler: Arc::new(IoHandler::new()), registrar: registrar, sync_status: Arc::new(|| false), - signer_port: None, + signer_address: None, } } @@ -139,8 +139,8 @@ impl ServerBuilder { } /// Change default signer port. - pub fn with_signer_port(&mut self, signer_port: Option) { - self.signer_port = signer_port; + pub fn with_signer_address(&mut self, signer_address: Option<(String, u16)>) { + self.signer_address = signer_address; } /// Asynchronously start server with no authentication, @@ -152,7 +152,7 @@ impl ServerBuilder { NoAuth, self.handler.clone(), self.dapps_path.clone(), - self.signer_port.clone(), + self.signer_address.clone(), self.registrar.clone(), self.sync_status.clone(), ) @@ -167,7 +167,7 @@ impl ServerBuilder { HttpBasicAuth::single_user(username, password), self.handler.clone(), self.dapps_path.clone(), - self.signer_port.clone(), + self.signer_address.clone(), self.registrar.clone(), self.sync_status.clone(), ) @@ -197,11 +197,11 @@ impl Server { } /// Returns a list of CORS domains for API endpoint. - fn cors_domains(signer_port: Option) -> Vec { - match signer_port { - Some(port) => vec![ + fn cors_domains(signer_address: Option<(String, u16)>) -> Vec { + match signer_address { + Some(signer_address) => vec![ format!("http://{}{}", HOME_PAGE, DAPPS_DOMAIN), - format!("http://{}", signer_address(port)), + format!("http://{}", address(signer_address)), ], None => vec![], } @@ -213,15 +213,15 @@ impl Server { authorization: A, handler: Arc, dapps_path: String, - signer_port: Option, + signer_address: Option<(String, u16)>, registrar: Arc, sync_status: Arc, ) -> Result { let panic_handler = Arc::new(Mutex::new(None)); let authorization = Arc::new(authorization); - let content_fetcher = Arc::new(apps::fetcher::ContentFetcher::new(apps::urlhint::URLHintContract::new(registrar), sync_status, signer_port)); - let endpoints = Arc::new(apps::all_endpoints(dapps_path, signer_port.clone())); - let cors_domains = Self::cors_domains(signer_port); + let content_fetcher = Arc::new(apps::fetcher::ContentFetcher::new(apps::urlhint::URLHintContract::new(registrar), sync_status, signer_address.clone())); + let endpoints = Arc::new(apps::all_endpoints(dapps_path, signer_address.clone())); + let cors_domains = Self::cors_domains(signer_address.clone()); let special = Arc::new({ let mut special = HashMap::new(); @@ -238,7 +238,7 @@ impl Server { try!(hyper::Server::http(addr)) .handle(move |ctrl| router::Router::new( ctrl, - signer_port.clone(), + signer_address.clone(), content_fetcher.clone(), endpoints.clone(), special.clone(), @@ -302,8 +302,8 @@ pub fn random_filename() -> String { rng.gen_ascii_chars().take(12).collect() } -fn signer_address(port: u16) -> String { - format!("127.0.0.1:{}", port) +fn address(address: (String, u16)) -> String { + format!("{}:{}", address.0, address.1) } #[cfg(test)] @@ -332,7 +332,7 @@ mod util_tests { // when let none = Server::cors_domains(None); - let some = Server::cors_domains(Some(18180)); + let some = Server::cors_domains(Some(("127.0.0.1".into(), 18180))); // then assert_eq!(none, Vec::::new()); diff --git a/dapps/src/page/builtin.rs b/dapps/src/page/builtin.rs index bf5bf088f..40c0e23a6 100644 --- a/dapps/src/page/builtin.rs +++ b/dapps/src/page/builtin.rs @@ -25,7 +25,7 @@ pub struct PageEndpoint { /// Prefix to strip from the path (when `None` deducted from `app_id`) pub prefix: Option, /// Safe to be loaded in frame by other origin. (use wisely!) - safe_to_embed_at_port: Option, + safe_to_embed_on: Option<(String, u16)>, info: EndpointInfo, } @@ -36,7 +36,7 @@ impl PageEndpoint { PageEndpoint { app: Arc::new(app), prefix: None, - safe_to_embed_at_port: None, + safe_to_embed_on: None, info: EndpointInfo::from(info), } } @@ -49,7 +49,7 @@ impl PageEndpoint { PageEndpoint { app: Arc::new(app), prefix: Some(prefix), - safe_to_embed_at_port: None, + safe_to_embed_on: None, info: EndpointInfo::from(info), } } @@ -57,12 +57,12 @@ impl PageEndpoint { /// Creates new `PageEndpoint` which can be safely used in iframe /// even from different origin. It might be dangerous (clickjacking). /// Use wisely! - pub fn new_safe_to_embed(app: T, port: Option) -> Self { + pub fn new_safe_to_embed(app: T, address: Option<(String, u16)>) -> Self { let info = app.info(); PageEndpoint { app: Arc::new(app), prefix: None, - safe_to_embed_at_port: port, + safe_to_embed_on: address, info: EndpointInfo::from(info), } } @@ -79,9 +79,9 @@ impl Endpoint for PageEndpoint { app: BuiltinDapp::new(self.app.clone()), prefix: self.prefix.clone(), path: path, - file: handler::ServedFile::new(self.safe_to_embed_at_port.clone()), + file: handler::ServedFile::new(self.safe_to_embed_on.clone()), cache: PageCache::Disabled, - safe_to_embed_at_port: self.safe_to_embed_at_port.clone(), + safe_to_embed_on: self.safe_to_embed_on.clone(), }) } } diff --git a/dapps/src/page/handler.rs b/dapps/src/page/handler.rs index f908a69c5..74eabf917 100644 --- a/dapps/src/page/handler.rs +++ b/dapps/src/page/handler.rs @@ -60,13 +60,13 @@ pub enum ServedFile { } impl ServedFile { - pub fn new(embeddable_at: Option) -> Self { + pub fn new(embeddable_on: Option<(String, u16)>) -> Self { ServedFile::Error(ContentHandler::error( StatusCode::NotFound, "404 Not Found", "Requested dapp resource was not found.", None, - embeddable_at, + embeddable_on, )) } } @@ -97,7 +97,7 @@ pub struct PageHandler { /// Requested path. pub path: EndpointPath, /// Flag indicating if the file can be safely embeded (put in iframe). - pub safe_to_embed_at_port: Option, + pub safe_to_embed_on: Option<(String, u16)>, /// Cache settings for this page. pub cache: PageCache, } @@ -133,7 +133,7 @@ impl server::Handler for PageHandler { self.app.file(&self.extract_path(url.path())) }, _ => None, - }.map_or_else(|| ServedFile::new(self.safe_to_embed_at_port.clone()), |f| ServedFile::File(f)); + }.map_or_else(|| ServedFile::new(self.safe_to_embed_on.clone()), |f| ServedFile::File(f)); Next::write() } @@ -162,7 +162,7 @@ impl server::Handler for PageHandler { } // Security headers: - add_security_headers(&mut res.headers_mut(), self.safe_to_embed_at_port); + add_security_headers(&mut res.headers_mut(), self.safe_to_embed_on.clone()); Next::write() }, ServedFile::Error(ref mut handler) => { @@ -246,7 +246,7 @@ fn should_extract_path_with_appid() { }, file: ServedFile::new(None), cache: Default::default(), - safe_to_embed_at_port: None, + safe_to_embed_on: None, }; // when diff --git a/dapps/src/page/local.rs b/dapps/src/page/local.rs index aa98a68cd..ec24cac36 100644 --- a/dapps/src/page/local.rs +++ b/dapps/src/page/local.rs @@ -27,17 +27,17 @@ pub struct LocalPageEndpoint { mime: Option, info: Option, cache: PageCache, - embeddable_at: Option, + embeddable_on: Option<(String, u16)>, } impl LocalPageEndpoint { - pub fn new(path: PathBuf, info: EndpointInfo, cache: PageCache, embeddable_at: Option) -> Self { + pub fn new(path: PathBuf, info: EndpointInfo, cache: PageCache, embeddable_on: Option<(String, u16)>) -> Self { LocalPageEndpoint { path: path, mime: None, info: Some(info), cache: cache, - embeddable_at: embeddable_at, + embeddable_on: embeddable_on, } } @@ -47,7 +47,7 @@ impl LocalPageEndpoint { mime: Some(mime), info: None, cache: cache, - embeddable_at: None, + embeddable_on: None, } } @@ -68,7 +68,7 @@ impl Endpoint for LocalPageEndpoint { prefix: None, path: path, file: handler::ServedFile::new(None), - safe_to_embed_at_port: self.embeddable_at, + safe_to_embed_on: self.embeddable_on.clone(), cache: self.cache, }) } else { @@ -77,7 +77,7 @@ impl Endpoint for LocalPageEndpoint { prefix: None, path: path, file: handler::ServedFile::new(None), - safe_to_embed_at_port: self.embeddable_at, + safe_to_embed_on: self.embeddable_on.clone(), cache: self.cache, }) } diff --git a/dapps/src/proxypac.rs b/dapps/src/proxypac.rs index 2d7c4e3ce..88ecb6ab3 100644 --- a/dapps/src/proxypac.rs +++ b/dapps/src/proxypac.rs @@ -19,24 +19,24 @@ use endpoint::{Endpoint, Handler, EndpointPath}; use handlers::ContentHandler; use apps::{HOME_PAGE, DAPPS_DOMAIN}; -use signer_address; +use address; pub struct ProxyPac { - signer_port: Option, + signer_address: Option<(String, u16)>, } impl ProxyPac { - pub fn boxed(signer_port: Option) -> Box { + pub fn boxed(signer_address: Option<(String, u16)>) -> Box { Box::new(ProxyPac { - signer_port: signer_port + signer_address: signer_address }) } } impl Endpoint for ProxyPac { fn to_handler(&self, path: EndpointPath) -> Box { - let signer = self.signer_port - .map(signer_address) + let signer = self.signer_address.clone() + .map(address) .unwrap_or_else(|| format!("{}:{}", path.host, path.port)); let content = format!( diff --git a/dapps/src/router/mod.rs b/dapps/src/router/mod.rs index fe8061ef0..cb9133886 100644 --- a/dapps/src/router/mod.rs +++ b/dapps/src/router/mod.rs @@ -20,7 +20,7 @@ pub mod auth; mod host_validation; -use signer_address; +use address; use std::sync::Arc; use std::collections::HashMap; use url::{Url, Host}; @@ -43,7 +43,7 @@ pub enum SpecialEndpoint { pub struct Router { control: Option, - signer_port: Option, + signer_address: Option<(String, u16)>, endpoints: Arc, fetch: Arc, special: Arc>>, @@ -117,14 +117,14 @@ impl server::Handler for Router { "404 Not Found", "Requested content was not found.", None, - self.signer_port, + self.signer_address.clone(), )) }, // Redirect any other GET request to signer. _ if *req.method() == hyper::Method::Get => { - if let Some(port) = self.signer_port { + if let Some(signer_address) = self.signer_address.clone() { trace!(target: "dapps", "Redirecting to signer interface."); - Redirection::boxed(&format!("http://{}", signer_address(port))) + Redirection::boxed(&format!("http://{}", address(signer_address))) } else { trace!(target: "dapps", "Signer disabled, returning 404."); Box::new(ContentHandler::error( @@ -132,7 +132,7 @@ impl server::Handler for Router { "404 Not Found", "Your homepage is not available when Trusted Signer is disabled.", Some("You can still access dapps by writing a correct address, though. Re-enabled Signer to get your homepage back."), - self.signer_port, + self.signer_address.clone(), )) } }, @@ -168,7 +168,7 @@ impl server::Handler for Router { impl Router { pub fn new( control: Control, - signer_port: Option, + signer_address: Option<(String, u16)>, content_fetcher: Arc, endpoints: Arc, special: Arc>>, @@ -181,7 +181,7 @@ impl Router { .to_handler(EndpointPath::default()); Router { control: Some(control), - signer_port: signer_port, + signer_address: signer_address, endpoints: endpoints, fetch: content_fetcher, special: special, diff --git a/dapps/src/tests/helpers.rs b/dapps/src/tests/helpers.rs index 1fa2e777a..f7c9e8ba6 100644 --- a/dapps/src/tests/helpers.rs +++ b/dapps/src/tests/helpers.rs @@ -76,7 +76,7 @@ pub fn init_server(hosts: Option>, is_syncing: bool) -> (Server, Arc dapps_path.push("non-existent-dir-to-prevent-fs-files-from-loading"); let mut builder = ServerBuilder::new(dapps_path.to_str().unwrap().into(), registrar.clone()); builder.with_sync_status(Arc::new(move || is_syncing)); - builder.with_signer_port(Some(SIGNER_PORT)); + builder.with_signer_address(Some(("127.0.0.1".into(), SIGNER_PORT))); ( builder.start_unsecured_http(&"127.0.0.1:0".parse().unwrap(), hosts).unwrap(), registrar, @@ -89,7 +89,7 @@ pub fn serve_with_auth(user: &str, pass: &str) -> Server { let mut dapps_path = env::temp_dir(); dapps_path.push("non-existent-dir-to-prevent-fs-files-from-loading"); let mut builder = ServerBuilder::new(dapps_path.to_str().unwrap().into(), registrar); - builder.with_signer_port(Some(SIGNER_PORT)); + builder.with_signer_address(Some(("127.0.0.1".into(), SIGNER_PORT))); builder.start_basic_auth_http(&"127.0.0.1:0".parse().unwrap(), None, user, pass).unwrap() } diff --git a/js/src/api/rpc/parity/parity.js b/js/src/api/rpc/parity/parity.js index f1739f848..5999c9d67 100644 --- a/js/src/api/rpc/parity/parity.js +++ b/js/src/api/rpc/parity/parity.js @@ -60,6 +60,11 @@ export default class Parity { .then(outNumber); } + dappsInterface () { + return this._transport + .execute('parity_dappsInterface'); + } + defaultExtraData () { return this._transport .execute('parity_defaultExtraData'); diff --git a/js/src/jsonrpc/interfaces/parity.js b/js/src/jsonrpc/interfaces/parity.js index 66a8ea962..883ad9675 100644 --- a/js/src/jsonrpc/interfaces/parity.js +++ b/js/src/jsonrpc/interfaces/parity.js @@ -109,6 +109,15 @@ export default { } }, + dappsInterface: { + desc: 'Returns the interface the dapps are running on, error if not enabled', + params: [], + returns: { + type: String, + desc: 'The interface' + } + }, + defaultExtraData: { desc: 'Returns the default extra data', params: [], diff --git a/parity/configuration.rs b/parity/configuration.rs index 1d46dda18..75d319272 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -95,7 +95,7 @@ impl Configuration { let wal = !self.args.flag_fast_and_loose; let warp_sync = self.args.flag_warp; let geth_compatibility = self.args.flag_geth; - let ui_port = self.ui_port(); + let ui_address = self.ui_port().map(|port| (self.ui_interface(), port)); let dapps_conf = self.dapps_config(); let signer_conf = self.signer_config(); let format = try!(self.format()); @@ -243,7 +243,7 @@ impl Configuration { vm_type: vm_type, warp_sync: warp_sync, geth_compatibility: geth_compatibility, - ui_port: ui_port, + ui_address: ui_address, net_settings: self.network_settings(), dapps_conf: dapps_conf, signer_conf: signer_conf, @@ -859,7 +859,7 @@ mod tests { wal: true, vm_type: Default::default(), geth_compatibility: false, - ui_port: Some(8180), + ui_address: Some(("127.0.0.1".into(), 8180)), net_settings: Default::default(), dapps_conf: Default::default(), signer_conf: Default::default(), diff --git a/parity/dapps.rs b/parity/dapps.rs index 6ef64c2fd..80f2f7060 100644 --- a/parity/dapps.rs +++ b/parity/dapps.rs @@ -58,7 +58,7 @@ pub fn new(configuration: Configuration, deps: Dependencies) -> Result Result>, _auth: Option<(String, String)>, - _signer_port: Option, + _signer_address: Option<(String, u16)>, ) -> Result { Err("Your Parity version has been compiled without WebApps support.".into()) } @@ -120,7 +120,7 @@ mod server { url: &SocketAddr, allowed_hosts: Option>, auth: Option<(String, String)>, - signer_port: Option, + signer_address: Option<(String, u16)>, ) -> Result { use ethcore_dapps as dapps; @@ -131,7 +131,7 @@ mod server { let sync = deps.sync.clone(); let client = deps.client.clone(); server.with_sync_status(Arc::new(move || is_major_importing(Some(sync.status().state), client.queue_info()))); - server.with_signer_port(signer_port); + server.with_signer_address(signer_address); let server = rpc_apis::setup_rpc(server, deps.apis.clone(), rpc_apis::ApiSet::UnsafeContext); let start_result = match auth { diff --git a/parity/rpc_apis.rs b/parity/rpc_apis.rs index 9ffd8e0dd..2d375fdde 100644 --- a/parity/rpc_apis.rs +++ b/parity/rpc_apis.rs @@ -37,7 +37,7 @@ pub enum Api { Net, /// Eth (Safe) Eth, - /// Geth-compatible "personal" API (DEPRECATED; only used in `--geth` mode.) + /// Geth-compatible "personal" API (DEPRECATED; only used in `--geth` mode.) Personal, /// Signer - Confirm transactions in Signer (UNSAFE: Passwords, List of transactions) Signer, @@ -119,6 +119,7 @@ pub struct Dependencies { pub settings: Arc, pub net_service: Arc, pub geth_compatibility: bool, + pub dapps_interface: Option, pub dapps_port: Option, } @@ -228,6 +229,7 @@ pub fn setup_rpc(server: T, deps: Arc, apis: ApiSet deps.logger.clone(), deps.settings.clone(), signer, + deps.dapps_interface.clone(), deps.dapps_port, ).to_delegate()); diff --git a/parity/run.rs b/parity/run.rs index 2af0d35ca..56ff92c25 100644 --- a/parity/run.rs +++ b/parity/run.rs @@ -82,7 +82,7 @@ pub struct RunCmd { pub wal: bool, pub vm_type: VMType, pub geth_compatibility: bool, - pub ui_port: Option, + pub ui_address: Option<(String, u16)>, pub net_settings: NetworkSettings, pub dapps_conf: dapps::Configuration, pub signer_conf: signer::Configuration, @@ -262,7 +262,7 @@ pub fn execute(cmd: RunCmd, logger: Arc) -> Result<(), String> { let deps_for_rpc_apis = Arc::new(rpc_apis::Dependencies { signer_service: Arc::new(rpc_apis::SignerService::new(move || { signer::generate_new_token(signer_path.clone()).map_err(|e| format!("{:?}", e)) - }, cmd.ui_port)), + }, cmd.ui_address)), snapshot: snapshot_service.clone(), client: client.clone(), sync: sync_provider.clone(), @@ -274,6 +274,10 @@ pub fn execute(cmd: RunCmd, logger: Arc) -> Result<(), String> { settings: Arc::new(cmd.net_settings.clone()), net_service: manage_network.clone(), geth_compatibility: cmd.geth_compatibility, + dapps_interface: match cmd.dapps_conf.enabled { + true => Some(cmd.dapps_conf.interface.clone()), + false => None, + }, dapps_port: match cmd.dapps_conf.enabled { true => Some(cmd.dapps_conf.port), false => None, diff --git a/rpc/src/v1/helpers/signer.rs b/rpc/src/v1/helpers/signer.rs index d4a5af273..11f8e3376 100644 --- a/rpc/src/v1/helpers/signer.rs +++ b/rpc/src/v1/helpers/signer.rs @@ -22,18 +22,18 @@ use v1::helpers::signing_queue::{ConfirmationsQueue}; pub struct SignerService { queue: Arc, generate_new_token: Box Result + Send + Sync + 'static>, - port: Option, + address: Option<(String, u16)>, } impl SignerService { /// Creates new Signer Service given function to generate new tokens. - pub fn new(new_token: F, port: Option) -> Self + pub fn new(new_token: F, address: Option<(String, u16)>) -> Self where F: Fn() -> Result + Send + Sync + 'static { SignerService { queue: Arc::new(ConfirmationsQueue::default()), generate_new_token: Box::new(new_token), - port: port, + address: address, } } @@ -47,20 +47,20 @@ impl SignerService { self.queue.clone() } - /// Returns signer port (if signer enabled) or `None` otherwise - pub fn port(&self) -> Option { - self.port + /// Returns signer address (if signer enabled) or `None` otherwise + pub fn address(&self) -> Option<(String, u16)> { + self.address.clone() } /// Returns true if Signer is enabled. pub fn is_enabled(&self) -> bool { - self.port.is_some() + self.address.is_some() } #[cfg(test)] /// Creates new Signer Service for tests. - pub fn new_test(port: Option) -> Self { - SignerService::new(|| Ok("new_token".into()), port) + pub fn new_test(address: Option<(String, u16)>) -> Self { + SignerService::new(|| Ok("new_token".into()), address) } } diff --git a/rpc/src/v1/impls/parity.rs b/rpc/src/v1/impls/parity.rs index b9c19f667..1b8ee9695 100644 --- a/rpc/src/v1/impls/parity.rs +++ b/rpc/src/v1/impls/parity.rs @@ -52,6 +52,7 @@ pub struct ParityClient where logger: Arc, settings: Arc, signer: Option>, + dapps_interface: Option, dapps_port: Option, } @@ -70,6 +71,7 @@ impl ParityClient where logger: Arc, settings: Arc, signer: Option>, + dapps_interface: Option, dapps_port: Option, ) -> Self { ParityClient { @@ -81,6 +83,7 @@ impl ParityClient where logger: logger, settings: settings, signer: signer, + dapps_interface: dapps_interface, dapps_port: dapps_port, } } @@ -261,7 +264,8 @@ impl Parity for ParityClient where self.signer .clone() - .and_then(|signer| signer.port()) + .and_then(|signer| signer.address()) + .map(|address| address.1) .ok_or_else(|| errors::signer_disabled()) } @@ -272,6 +276,13 @@ impl Parity for ParityClient where .ok_or_else(|| errors::dapps_disabled()) } + fn dapps_interface(&self) -> Result { + try!(self.active()); + + self.dapps_interface.clone() + .ok_or_else(|| errors::dapps_disabled()) + } + fn next_nonce(&self, address: H160) -> Result { try!(self.active()); let address: Address = address.into(); diff --git a/rpc/src/v1/tests/mocked/parity.rs b/rpc/src/v1/tests/mocked/parity.rs index 8baecbd90..b5c8187c7 100644 --- a/rpc/src/v1/tests/mocked/parity.rs +++ b/rpc/src/v1/tests/mocked/parity.rs @@ -38,6 +38,7 @@ pub struct Dependencies { pub settings: Arc, pub network: Arc, pub accounts: Arc, + pub dapps_interface: Option, pub dapps_port: Option, } @@ -61,6 +62,7 @@ impl Dependencies { }), network: Arc::new(TestManageNetwork), accounts: Arc::new(AccountProvider::transient_provider()), + dapps_interface: Some("127.0.0.1".into()), dapps_port: Some(18080), } } @@ -75,6 +77,7 @@ impl Dependencies { self.logger.clone(), self.settings.clone(), signer, + self.dapps_interface.clone(), self.dapps_port, ) } @@ -238,7 +241,7 @@ fn rpc_parity_node_name() { #[test] fn rpc_parity_unsigned_transactions_count() { let deps = Dependencies::new(); - let io = deps.with_signer(SignerService::new_test(Some(18180))); + let io = deps.with_signer(SignerService::new_test(Some(("127.0.0.1".into(), 18180)))); let request = r#"{"jsonrpc": "2.0", "method": "parity_unsignedTransactionsCount", "params":[], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":0,"id":1}"#; @@ -282,7 +285,7 @@ fn rpc_parity_encrypt() { fn rpc_parity_signer_port() { // given let deps = Dependencies::new(); - let io1 = deps.with_signer(SignerService::new_test(Some(18180))); + let io1 = deps.with_signer(SignerService::new_test(Some(("127.0.0.1".into(), 18180)))); let io2 = deps.default_client(); // when @@ -313,6 +316,24 @@ fn rpc_parity_dapps_port() { assert_eq!(io2.handle_request_sync(request), Some(response2.to_owned())); } +#[test] +fn rpc_parity_dapps_interface() { + // given + let mut deps = Dependencies::new(); + let io1 = deps.default_client(); + deps.dapps_interface = None; + let io2 = deps.default_client(); + + // when + let request = r#"{"jsonrpc": "2.0", "method": "parity_dappsInterface", "params": [], "id": 1}"#; + let response1 = r#"{"jsonrpc":"2.0","result":"127.0.0.1","id":1}"#; + let response2 = r#"{"jsonrpc":"2.0","error":{"code":-32031,"message":"Dapps Server is disabled. This API is not available.","data":null},"id":1}"#; + + // then + assert_eq!(io1.handle_request_sync(request), Some(response1.to_owned())); + assert_eq!(io2.handle_request_sync(request), Some(response2.to_owned())); +} + #[test] fn rpc_parity_next_nonce() { let deps = Dependencies::new(); diff --git a/rpc/src/v1/traits/parity.rs b/rpc/src/v1/traits/parity.rs index 23cf50ed3..f8c219a89 100644 --- a/rpc/src/v1/traits/parity.rs +++ b/rpc/src/v1/traits/parity.rs @@ -123,6 +123,10 @@ build_rpc_trait! { #[rpc(name = "parity_dappsPort")] fn dapps_port(&self) -> Result; + /// Returns current Dapps Server interface address or an error if dapps server is disabled. + #[rpc(name = "parity_dappsInterface")] + fn dapps_interface(&self) -> Result; + /// Returns next nonce for particular sender. Should include all transactions in the queue. #[rpc(name = "parity_nextNonce")] fn next_nonce(&self, H160) -> Result; From 90ff810e367c438d3c2943e60eaa849307af9ce3 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Wed, 9 Nov 2016 20:05:08 +0100 Subject: [PATCH 03/12] Handle redirects from /api/content on manifest.json gracefully (#3315) * Add redirect follow mode to manifest.json * Remove (now) unused count parameter * autoRewrite: true for dev mode proxy redirects --- js/src/views/Dapps/dappsStore.js | 23 +++++++---------------- js/webpack.config.js | 3 ++- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/js/src/views/Dapps/dappsStore.js b/js/src/views/Dapps/dappsStore.js index 01877c38b..6a00ff059 100644 --- a/js/src/views/Dapps/dappsStore.js +++ b/js/src/views/Dapps/dappsStore.js @@ -197,24 +197,15 @@ export default class DappsStore { }); } - _fetchManifest (manifestHash, count = 0) { - return fetch(`${this._getHost()}/api/content/${manifestHash}/`) + _fetchManifest (manifestHash) { + return fetch(`${this._getHost()}/api/content/${manifestHash}/`, { redirect: 'follow', mode: 'cors' }) .then((response) => { - if (response.ok) { - return response.json(); - } - - if (count < 1) { - return this._fetchManifest(manifestHash, count + 1); - } - - return null; + return response.ok + ? response.json() + : null; }) - .catch(() => { - if (count < 1) { - return this._fetchManifest(manifestHash, count + 1); - } - + .catch((error) => { + console.warn('DappsStore:fetchManifest', error); return null; }); } diff --git a/js/webpack.config.js b/js/webpack.config.js index a85fefa9f..4413299fa 100644 --- a/js/webpack.config.js +++ b/js/webpack.config.js @@ -198,7 +198,8 @@ module.exports = { proxy: { '/api/*': { target: 'http://127.0.0.1:8080', - changeOrigin: true + changeOrigin: true, + autoRewrite: true }, '/app/*': { target: 'http://127.0.0.1:8080', From 2f98169539bb7645a5d21b0180bfe00784b20f2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 10 Nov 2016 11:27:05 +0100 Subject: [PATCH 04/12] In-browser signing support (#3231) * Signer RAW confirmations * Returning address book as eth_accounts * UI support for in-browser signing * Post review fixes * Adding new methods to jsonrpc * Fixing eth_accounts * Deterministic accounts ordering --- ethcore/src/account_provider.rs | 12 ++- js/package.json | 2 + js/src/api/format/input.js | 4 + js/src/api/rpc/parity/parity.js | 6 ++ js/src/api/rpc/signer/signer.js | 7 +- js/src/jsonrpc/interfaces/parity.js | 14 +++ js/src/jsonrpc/interfaces/signer.js | 20 +++- js/src/redux/providers/signerMiddleware.js | 66 ++++++++++--- js/src/util/wallet.js | 82 +++++++++++++++ .../RequestPendingWeb3/RequestPendingWeb3.js | 13 ++- .../TransactionPending/TransactionPending.css | 2 +- .../TransactionPending/TransactionPending.js | 6 +- .../TransactionPendingFormConfirm.css | 4 + .../TransactionPendingFormConfirm.js | 56 +++++++++-- js/webpack.vendor.js | 1 + rpc/src/v1/helpers/errors.rs | 6 +- rpc/src/v1/impls/eth.rs | 6 +- rpc/src/v1/impls/signer.rs | 69 +++++++++++-- rpc/src/v1/tests/mocked/eth.rs | 9 +- rpc/src/v1/tests/mocked/signer.rs | 99 ++++++++++++++++++- rpc/src/v1/traits/signer.rs | 6 +- 21 files changed, 447 insertions(+), 43 deletions(-) create mode 100644 js/src/util/wallet.js diff --git a/ethcore/src/account_provider.rs b/ethcore/src/account_provider.rs index 064c3e935..e906aefe9 100644 --- a/ethcore/src/account_provider.rs +++ b/ethcore/src/account_provider.rs @@ -95,6 +95,7 @@ impl KeyDirectory for NullDir { struct AddressBook { path: PathBuf, cache: HashMap, + transient: bool, } impl AddressBook { @@ -106,11 +107,18 @@ impl AddressBook { let mut r = AddressBook { path: path, cache: HashMap::new(), + transient: false, }; r.revert(); r } + pub fn transient() -> Self { + let mut book = AddressBook::new(Default::default()); + book.transient = true; + book + } + pub fn get(&self) -> HashMap { self.cache.clone() } @@ -134,6 +142,7 @@ impl AddressBook { } fn revert(&mut self) { + if self.transient { return; } trace!(target: "addressbook", "revert"); let _ = fs::File::open(self.path.clone()) .map_err(|e| trace!(target: "addressbook", "Couldn't open address book: {}", e)) @@ -144,6 +153,7 @@ impl AddressBook { } fn save(&mut self) { + if self.transient { return; } trace!(target: "addressbook", "save"); let _ = fs::File::create(self.path.clone()) .map_err(|e| warn!(target: "addressbook", "Couldn't open address book for writing: {}", e)) @@ -175,7 +185,7 @@ impl AccountProvider { pub fn transient_provider() -> Self { AccountProvider { unlocked: Mutex::new(HashMap::new()), - address_book: Mutex::new(AddressBook::new(Default::default())), + address_book: Mutex::new(AddressBook::transient()), sstore: Box::new(EthStore::open(Box::new(NullDir::default())) .expect("NullDir load always succeeds; qed")) } diff --git a/js/package.json b/js/package.json index df9e37811..eb0926c4b 100644 --- a/js/package.json +++ b/js/package.json @@ -118,6 +118,7 @@ "bytes": "^2.4.0", "chart.js": "^2.3.0", "es6-promise": "^3.2.1", + "ethereumjs-tx": "^1.1.2", "file-saver": "^1.3.3", "format-json": "^1.0.3", "format-number": "^2.0.1", @@ -147,6 +148,7 @@ "redux-actions": "^0.10.1", "redux-thunk": "^2.1.0", "rlp": "^2.0.0", + "scryptsy": "^2.0.0", "store": "^1.3.20", "utf8": "^2.1.1", "validator": "^5.7.0", diff --git a/js/src/api/format/input.js b/js/src/api/format/input.js index b46148cdc..830ca0e21 100644 --- a/js/src/api/format/input.js +++ b/js/src/api/format/input.js @@ -93,6 +93,10 @@ export function inFilter (options) { } export function inHex (str) { + if (str && str.toString) { + str = str.toString(16); + } + if (str && str.substr(0, 2) === '0x') { return str.toLowerCase(); } diff --git a/js/src/api/rpc/parity/parity.js b/js/src/api/rpc/parity/parity.js index 5999c9d67..a33828b80 100644 --- a/js/src/api/rpc/parity/parity.js +++ b/js/src/api/rpc/parity/parity.js @@ -181,6 +181,12 @@ export default class Parity { .then(outAddress); } + nextNonce (account) { + return this._transport + .execute('parity_nextNonce', inAddress(account)) + .then(outNumber); + } + nodeName () { return this._transport .execute('parity_nodeName'); diff --git a/js/src/api/rpc/signer/signer.js b/js/src/api/rpc/signer/signer.js index 7f905cf50..126ce651a 100644 --- a/js/src/api/rpc/signer/signer.js +++ b/js/src/api/rpc/signer/signer.js @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import { inNumber16 } from '../../format/input'; +import { inNumber16, inData } from '../../format/input'; import { outSignerRequest } from '../../format/output'; export default class Signer { @@ -27,6 +27,11 @@ export default class Signer { .execute('signer_confirmRequest', inNumber16(requestId), options, password); } + confirmRequestRaw (requestId, data) { + return this._transport + .execute('signer_confirmRequestRaw', inNumber16(requestId), inData(data)); + } + generateAuthorizationToken () { return this._transport .execute('signer_generateAuthorizationToken'); diff --git a/js/src/jsonrpc/interfaces/parity.js b/js/src/jsonrpc/interfaces/parity.js index 883ad9675..5dd313e00 100644 --- a/js/src/jsonrpc/interfaces/parity.js +++ b/js/src/jsonrpc/interfaces/parity.js @@ -356,6 +356,20 @@ export default { } }, + nextNonce: { + desc: 'Returns next available nonce for transaction from given account. Includes pending block and transaction queue.', + params: [ + { + type: Address, + desc: 'Account' + } + ], + returns: { + type: Quantity, + desc: 'Next valid nonce' + } + }, + nodeName: { desc: 'Returns node name (identity)', params: [], diff --git a/js/src/jsonrpc/interfaces/signer.js b/js/src/jsonrpc/interfaces/signer.js index f394dbb61..f50bb1115 100644 --- a/js/src/jsonrpc/interfaces/signer.js +++ b/js/src/jsonrpc/interfaces/signer.js @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import { Quantity } from '../types'; +import { Quantity, Data } from '../types'; export default { generateAuthorizationToken: { @@ -57,6 +57,24 @@ export default { } }, + confirmRequestRaw: { + desc: 'Confirm a request in the signer queue providing signed request.', + params: [ + { + type: Quantity, + desc: 'The request id' + }, + { + type: Data, + desc: 'Signed request (transaction RLP)' + } + ], + returns: { + type: Boolean, + desc: 'The status of the confirmation' + } + }, + rejectRequest: { desc: 'Rejects a request in the signer queue', params: [ diff --git a/js/src/redux/providers/signerMiddleware.js b/js/src/redux/providers/signerMiddleware.js index 6d09eeb4e..4cc877ced 100644 --- a/js/src/redux/providers/signerMiddleware.js +++ b/js/src/redux/providers/signerMiddleware.js @@ -16,6 +16,9 @@ import * as actions from './signerActions'; +import { inHex } from '../../api/format/input'; +import { Wallet } from '../../util/wallet'; + export default class SignerMiddleware { constructor (api) { this._api = api; @@ -49,23 +52,58 @@ export default class SignerMiddleware { } onConfirmStart = (store, action) => { - const { id, password } = action.payload; + const { id, password, wallet, payload } = action.payload; - this._api.signer - .confirmRequest(id, {}, password) - .then((txHash) => { - console.log('confirmRequest', id, txHash); - if (!txHash) { - store.dispatch(actions.errorConfirmRequest({ id, err: 'Unable to confirm.' })); - return; + const handlePromise = promise => { + promise + .then((txHash) => { + console.log('confirmRequest', id, txHash); + if (!txHash) { + store.dispatch(actions.errorConfirmRequest({ id, err: 'Unable to confirm.' })); + return; + } + + store.dispatch(actions.successConfirmRequest({ id, txHash })); + }) + .catch((error) => { + console.error('confirmRequest', id, error); + store.dispatch(actions.errorConfirmRequest({ id, err: error.message })); + }); + }; + + // Sign request in-browser + if (wallet && payload.transaction) { + const { transaction } = payload; + + (transaction.nonce.isZero() + ? this._api.parity.nextNonce(transaction.from) + : Promise.resolve(transaction.nonce) + ).then(nonce => { + let txData = { + to: inHex(transaction.to), + nonce: inHex(transaction.nonce.isZero() ? nonce : transaction.nonce), + gasPrice: inHex(transaction.gasPrice), + gasLimit: inHex(transaction.gas), + value: inHex(transaction.value), + data: inHex(transaction.data) + }; + + try { + // NOTE: Derving the key takes significant amount of time, + // make sure to display some kind of "in-progress" state. + const signer = Wallet.fromJson(wallet, password); + const rawTx = signer.signTransaction(txData); + + handlePromise(this._api.signer.confirmRequestRaw(id, rawTx)); + } catch (error) { + console.error(error); + store.dispatch(actions.errorConfirmRequest({ id, err: error.message })); } - - store.dispatch(actions.successConfirmRequest({ id, txHash })); - }) - .catch((error) => { - console.error('confirmRequest', id, error); - store.dispatch(actions.errorConfirmRequest({ id, err: error.message })); }); + return; + } + + handlePromise(this._api.signer.confirmRequest(id, {}, password)); } onRejectStart = (store, action) => { diff --git a/js/src/util/wallet.js b/js/src/util/wallet.js new file mode 100644 index 000000000..14c3a6016 --- /dev/null +++ b/js/src/util/wallet.js @@ -0,0 +1,82 @@ +// 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 scrypt from 'scryptsy'; +import Transaction from 'ethereumjs-tx'; +import { pbkdf2Sync } from 'crypto'; +import { createDecipheriv } from 'browserify-aes'; + +import { inHex } from '../api/format/input'; +import { sha3 } from '../api/util/sha3'; + +// Adapted from https://github.com/kvhnuke/etherwallet/blob/mercury/app/scripts/myetherwallet.js + +export class Wallet { + + static fromJson (json, password) { + if (json.version !== 3) { + throw new Error('Only V3 wallets are supported'); + } + + const { kdf } = json.crypto; + const kdfparams = json.crypto.kdfparams || {}; + const pwd = Buffer.from(password); + const salt = Buffer.from(kdfparams.salt, 'hex'); + let derivedKey; + + if (kdf === 'scrypt') { + derivedKey = scrypt(pwd, salt, kdfparams.n, kdfparams.r, kdfparams.p, kdfparams.dklen); + } else if (kdf === 'pbkdf2') { + if (kdfparams.prf !== 'hmac-sha256') { + throw new Error('Unsupported parameters to PBKDF2'); + } + derivedKey = pbkdf2Sync(pwd, salt, kdfparams.c, kdfparams.dklen, 'sha256'); + } else { + throw new Error('Unsupported key derivation scheme'); + } + + const ciphertext = Buffer.from(json.crypto.ciphertext, 'hex'); + let mac = sha3(Buffer.concat([derivedKey.slice(16, 32), ciphertext])); + if (mac !== inHex(json.crypto.mac)) { + throw new Error('Key derivation failed - possibly wrong passphrase'); + } + + const decipher = createDecipheriv( + json.crypto.cipher, + derivedKey.slice(0, 16), + Buffer.from(json.crypto.cipherparams.iv, 'hex') + ); + let seed = Buffer.concat([decipher.update(ciphertext), decipher.final()]); + + while (seed.length < 32) { + const nullBuff = Buffer.from([0x00]); + seed = Buffer.concat([nullBuff, seed]); + } + + return new Wallet(seed); + } + + constructor (seed) { + this.seed = seed; + } + + signTransaction (transaction) { + const tx = new Transaction(transaction); + tx.sign(this.seed); + return inHex(tx.serialize().toString('hex')); + } + +} diff --git a/js/src/views/Signer/components/RequestPendingWeb3/RequestPendingWeb3.js b/js/src/views/Signer/components/RequestPendingWeb3/RequestPendingWeb3.js index 4014f328b..97fa43f69 100644 --- a/js/src/views/Signer/components/RequestPendingWeb3/RequestPendingWeb3.js +++ b/js/src/views/Signer/components/RequestPendingWeb3/RequestPendingWeb3.js @@ -33,15 +33,22 @@ export default class RequestPendingWeb3 extends Component { className: PropTypes.string }; + onConfirm = data => { + const { onConfirm, payload } = this.props; + + data.payload = payload; + onConfirm(data); + }; + render () { - const { payload, id, className, isSending, date, onConfirm, onReject } = this.props; + const { payload, id, className, isSending, date, onReject } = this.props; if (payload.sign) { const { sign } = payload; return ( * { vertical-align: middle; - min-height: 120px; + min-height: 190px; } .inputs { diff --git a/js/src/views/Signer/components/TransactionPending/TransactionPending.js b/js/src/views/Signer/components/TransactionPending/TransactionPending.js index 77d02d0b1..55e4f6405 100644 --- a/js/src/views/Signer/components/TransactionPending/TransactionPending.js +++ b/js/src/views/Signer/components/TransactionPending/TransactionPending.js @@ -116,9 +116,11 @@ export default class TransactionPending extends Component { ); } - onConfirm = password => { + onConfirm = data => { const { id, gasPrice } = this.props; - this.props.onConfirm({ id, password, gasPrice }); + const { password, wallet } = data; + + this.props.onConfirm({ id, password, wallet, gasPrice }); } onReject = () => { diff --git a/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/TransactionPendingFormConfirm.css b/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/TransactionPendingFormConfirm.css index 673b045d2..d10e634ae 100644 --- a/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/TransactionPendingFormConfirm.css +++ b/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/TransactionPendingFormConfirm.css @@ -40,3 +40,7 @@ .passwordHint span { opacity: 0.85; } + +.fileInput input { + top: 22px; +} diff --git a/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/TransactionPendingFormConfirm.js b/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/TransactionPendingFormConfirm.js index 5b54586a4..5765447ee 100644 --- a/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/TransactionPendingFormConfirm.js +++ b/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/TransactionPendingFormConfirm.js @@ -35,26 +35,33 @@ class TransactionPendingFormConfirm extends Component { id = Math.random(); // for tooltip state = { + walletError: null, + wallet: null, password: '' } render () { const { accounts, address, isSending } = this.props; - const { password } = this.state; + const { password, walletError, wallet } = this.state; const account = accounts[address] || {}; + const isExternal = !account.uuid; const passwordHint = account.meta && account.meta.passwordHint ? (
(hint) { account.meta.passwordHint }
) : null; + const isWalletOk = !isExternal || (walletError === null && wallet !== null); + const keyInput = isExternal ? this.renderKeyInput() : null; + return (
+ { keyInput }
@@ -71,7 +78,7 @@ class TransactionPendingFormConfirm extends Component { className={ styles.confirmButton } fullWidth primary - disabled={ isSending } + disabled={ isSending || !isWalletOk } icon={ } label={ isSending ? 'Confirming...' : 'Confirm Transaction' } /> @@ -82,6 +89,20 @@ class TransactionPendingFormConfirm extends Component { ); } + renderKeyInput () { + const { walletError } = this.state; + + return ( + + ); + } + renderTooltip () { if (this.state.password.length) { return; @@ -94,6 +115,26 @@ class TransactionPendingFormConfirm extends Component { ); } + onKeySelect = evt => { + const fileReader = new FileReader(); + fileReader.onload = e => { + try { + const wallet = JSON.parse(e.target.result); + this.setState({ + walletError: null, + wallet: wallet + }); + } catch (e) { + this.setState({ + walletError: 'Given wallet file is invalid.', + wallet: null + }); + } + }; + + fileReader.readAsText(evt.target.files[0]); + } + onModifyPassword = evt => { const password = evt.target.value; this.setState({ @@ -102,8 +143,11 @@ class TransactionPendingFormConfirm extends Component { } onConfirm = () => { - const { password } = this.state; - this.props.onConfirm(password); + const { password, wallet } = this.state; + + this.props.onConfirm({ + password, wallet + }); } onKeyDown = evt => { diff --git a/js/webpack.vendor.js b/js/webpack.vendor.js index a896cbfd8..74a51dada 100644 --- a/js/webpack.vendor.js +++ b/js/webpack.vendor.js @@ -22,6 +22,7 @@ const DEST = process.env.BUILD_DEST || '.build'; let modules = [ 'babel-polyfill', + 'browserify-aes', 'ethereumjs-tx', 'scryptsy', 'react', 'react-dom', 'react-redux', 'react-router', 'redux', 'redux-thunk', 'react-router-redux', 'lodash', 'material-ui', 'moment', 'blockies' diff --git a/rpc/src/v1/helpers/errors.rs b/rpc/src/v1/helpers/errors.rs index 50c22b187..d36feca4b 100644 --- a/rpc/src/v1/helpers/errors.rs +++ b/rpc/src/v1/helpers/errors.rs @@ -17,7 +17,7 @@ //! RPC Error codes and error objects macro_rules! rpc_unimplemented { - () => (Err(::v1::helpers::errors::unimplemented())) + () => (Err(::v1::helpers::errors::unimplemented(None))) } use std::fmt; @@ -51,11 +51,11 @@ mod codes { pub const FETCH_ERROR: i64 = -32060; } -pub fn unimplemented() -> Error { +pub fn unimplemented(details: Option) -> Error { Error { code: ErrorCode::ServerError(codes::UNSUPPORTED_REQUEST), message: "This request is not implemented yet. Please create an issue on Github repo.".into(), - data: None + data: details.map(Value::String), } } diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index 6986cf0ed..8207426ba 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -20,6 +20,7 @@ extern crate ethash; use std::io::{Write}; use std::process::{Command, Stdio}; +use std::collections::BTreeSet; use std::thread; use std::time::{Instant, Duration}; use std::sync::{Arc, Weak}; @@ -339,7 +340,10 @@ impl Eth for EthClient where let store = take_weak!(self.accounts); let accounts = try!(store.accounts().map_err(|e| errors::internal("Could not fetch accounts.", e))); - Ok(accounts.into_iter().map(Into::into).collect()) + let addresses = try!(store.addresses_info().map_err(|e| errors::internal("Could not fetch accounts.", e))); + + let set: BTreeSet
= accounts.into_iter().chain(addresses.keys().cloned()).collect(); + Ok(set.into_iter().map(Into::into).collect()) } fn block_number(&self) -> Result { diff --git a/rpc/src/v1/impls/signer.rs b/rpc/src/v1/impls/signer.rs index 0ee06b5c5..66f46ba01 100644 --- a/rpc/src/v1/impls/signer.rs +++ b/rpc/src/v1/impls/signer.rs @@ -18,14 +18,17 @@ use std::sync::{Arc, Weak}; -use jsonrpc_core::*; +use rlp::{UntrustedRlp, View}; use ethcore::account_provider::AccountProvider; use ethcore::client::MiningBlockChainClient; +use ethcore::transaction::SignedTransaction; use ethcore::miner::MinerService; + +use jsonrpc_core::Error; use v1::traits::Signer; -use v1::types::{TransactionModification, ConfirmationRequest, ConfirmationResponse, U256}; +use v1::types::{TransactionModification, ConfirmationRequest, ConfirmationResponse, U256, Bytes}; use v1::helpers::{errors, SignerService, SigningQueue, ConfirmationPayload}; -use v1::helpers::dispatch; +use v1::helpers::dispatch::{self, dispatch_transaction}; /// Transactions confirmation (personal) rpc implementation. pub struct SignerClient where C: MiningBlockChainClient, M: MinerService { @@ -66,9 +69,9 @@ impl Signer for SignerClient where C: MiningBlockC let signer = take_weak!(self.signer); Ok(signer.requests() - .into_iter() - .map(Into::into) - .collect() + .into_iter() + .map(Into::into) + .collect() ) } @@ -101,6 +104,60 @@ impl Signer for SignerClient where C: MiningBlockC }).unwrap_or_else(|| Err(errors::invalid_params("Unknown RequestID", id))) } + fn confirm_request_raw(&self, id: U256, bytes: Bytes) -> Result { + try!(self.active()); + + let id = id.into(); + let signer = take_weak!(self.signer); + let client = take_weak!(self.client); + let miner = take_weak!(self.miner); + + signer.peek(&id).map(|confirmation| { + let result = match confirmation.payload { + ConfirmationPayload::SendTransaction(request) => { + let signed_transaction: SignedTransaction = try!( + UntrustedRlp::new(&bytes.0).as_val().map_err(errors::from_rlp_error) + ); + let sender = try!( + signed_transaction.sender().map_err(|e| errors::invalid_params("Invalid signature.", e)) + ); + + // Verification + let sender_matches = sender == request.from; + let data_matches = signed_transaction.data == request.data; + let value_matches = signed_transaction.value == request.value; + let nonce_matches = match request.nonce { + Some(nonce) => signed_transaction.nonce == nonce, + None => true, + }; + + // Dispatch if everything is ok + if sender_matches && data_matches && value_matches && nonce_matches { + dispatch_transaction(&*client, &*miner, signed_transaction) + .map(Into::into) + .map(ConfirmationResponse::SendTransaction) + } else { + let mut error = Vec::new(); + if !sender_matches { error.push("from") } + if !data_matches { error.push("data") } + if !value_matches { error.push("value") } + if !nonce_matches { error.push("nonce") } + + Err(errors::invalid_params("Sent transaction does not match the request.", error)) + } + }, + // TODO [ToDr]: + // 1. Sign - verify signature + // 2. Decrypt - pass through? + _ => Err(errors::unimplemented(Some("Non-transaction requests does not support RAW signing yet.".into()))), + }; + if let Ok(ref response) = result { + signer.request_confirmed(id, Ok(response.clone())); + } + result + }).unwrap_or_else(|| Err(errors::invalid_params("Unknown RequestID", id))) + } + fn reject_request(&self, id: U256) -> Result { try!(self.active()); let signer = take_weak!(self.signer); diff --git a/rpc/src/v1/tests/mocked/eth.rs b/rpc/src/v1/tests/mocked/eth.rs index 7119de2c1..67e77a6db 100644 --- a/rpc/src/v1/tests/mocked/eth.rs +++ b/rpc/src/v1/tests/mocked/eth.rs @@ -353,9 +353,16 @@ fn rpc_eth_gas_price() { fn rpc_eth_accounts() { let tester = EthTester::default(); let address = tester.accounts_provider.new_account("").unwrap(); + let address2 = Address::default(); + + tester.accounts_provider.set_address_name(address2, "Test Account".into()).unwrap(); let request = r#"{"jsonrpc": "2.0", "method": "eth_accounts", "params": [], "id": 1}"#; - let response = r#"{"jsonrpc":"2.0","result":[""#.to_owned() + &format!("0x{:?}", address) + r#""],"id":1}"#; + let response = r#"{"jsonrpc":"2.0","result":[""#.to_owned() + + &format!("0x{:?}", address2) + + r#"",""# + + &format!("0x{:?}", address) + + r#""],"id":1}"#; assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); } diff --git a/rpc/src/v1/tests/mocked/signer.rs b/rpc/src/v1/tests/mocked/signer.rs index 8807e2373..e2ba580e0 100644 --- a/rpc/src/v1/tests/mocked/signer.rs +++ b/rpc/src/v1/tests/mocked/signer.rs @@ -16,11 +16,14 @@ use std::sync::Arc; use std::str::FromStr; -use jsonrpc_core::IoHandler; -use util::{U256, Uint, Address}; +use util::{U256, Uint, Address, ToPretty}; + use ethcore::account_provider::AccountProvider; use ethcore::client::TestBlockChainClient; use ethcore::transaction::{Transaction, Action}; +use rlp::encode; + +use jsonrpc_core::IoHandler; use v1::{SignerClient, Signer}; use v1::tests::helpers::TestMinerService; use v1::helpers::{SigningQueue, SignerService, FilledTransactionRequest, ConfirmationPayload}; @@ -206,6 +209,98 @@ fn should_confirm_transaction_and_dispatch() { assert_eq!(tester.miner.imported_transactions.lock().len(), 1); } +#[test] +fn should_confirm_transaction_with_rlp() { + // given + let tester = signer_tester(); + let address = tester.accounts.new_account("test").unwrap(); + let recipient = Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap(); + tester.signer.add_request(ConfirmationPayload::SendTransaction(FilledTransactionRequest { + from: address, + to: Some(recipient), + gas_price: U256::from(10_000), + gas: U256::from(10_000_000), + value: U256::from(1), + data: vec![], + nonce: None, + })).unwrap(); + + let t = Transaction { + nonce: U256::zero(), + gas_price: U256::from(0x1000), + gas: U256::from(10_000_000), + action: Action::Call(recipient), + value: U256::from(0x1), + data: vec![] + }; + tester.accounts.unlock_account_temporarily(address, "test".into()).unwrap(); + let signature = tester.accounts.sign(address, None, t.hash(None)).unwrap(); + let t = t.with_signature(signature, None); + let rlp = encode(&t); + + assert_eq!(tester.signer.requests().len(), 1); + + // when + let request = r#"{ + "jsonrpc":"2.0", + "method":"signer_confirmRequestRaw", + "params":["0x1", "0x"#.to_owned() + &rlp.to_hex() + r#""], + "id":1 + }"#; +println!("{:?}", request); + let response = r#"{"jsonrpc":"2.0","result":""#.to_owned() + format!("0x{:?}", t.hash()).as_ref() + r#"","id":1}"#; + + // then + assert_eq!(tester.io.handle_request_sync(&request), Some(response.to_owned())); + assert_eq!(tester.signer.requests().len(), 0); + assert_eq!(tester.miner.imported_transactions.lock().len(), 1); +} + +#[test] +fn should_return_error_when_sender_does_not_match() { + // given + let tester = signer_tester(); + let address = tester.accounts.new_account("test").unwrap(); + let recipient = Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap(); + tester.signer.add_request(ConfirmationPayload::SendTransaction(FilledTransactionRequest { + from: Address::default(), + to: Some(recipient), + gas_price: U256::from(10_000), + gas: U256::from(10_000_000), + value: U256::from(1), + data: vec![], + nonce: None, + })).unwrap(); + + let t = Transaction { + nonce: U256::zero(), + gas_price: U256::from(0x1000), + gas: U256::from(10_000_000), + action: Action::Call(recipient), + value: U256::from(0x1), + data: vec![] + }; + tester.accounts.unlock_account_temporarily(address, "test".into()).unwrap(); + let signature = tester.accounts.sign(address, None, t.hash(None)).unwrap(); + let t = t.with_signature(signature, None); + let rlp = encode(&t); + + assert_eq!(tester.signer.requests().len(), 1); + + // when + let request = r#"{ + "jsonrpc":"2.0", + "method":"signer_confirmRequestRaw", + "params":["0x1", "0x"#.to_owned() + &rlp.to_hex() + r#""], + "id":1 + }"#; + let response = r#"{"jsonrpc":"2.0","error":{"code":-32602,"message":"Couldn't parse parameters: Sent transaction does not match the request.","data":"[\"from\"]"},"id":1}"#; + + // then + assert_eq!(tester.io.handle_request_sync(&request), Some(response.to_owned())); + assert_eq!(tester.signer.requests().len(), 1); +} + #[test] fn should_generate_new_token() { // given diff --git a/rpc/src/v1/traits/signer.rs b/rpc/src/v1/traits/signer.rs index 26d6899cb..eafa520d4 100644 --- a/rpc/src/v1/traits/signer.rs +++ b/rpc/src/v1/traits/signer.rs @@ -18,7 +18,7 @@ use jsonrpc_core::Error; use v1::helpers::auto_args::Wrap; -use v1::types::{U256, TransactionModification, ConfirmationRequest, ConfirmationResponse}; +use v1::types::{U256, Bytes, TransactionModification, ConfirmationRequest, ConfirmationResponse}; build_rpc_trait! { @@ -33,6 +33,10 @@ build_rpc_trait! { #[rpc(name = "signer_confirmRequest")] fn confirm_request(&self, U256, TransactionModification, String) -> Result; + /// Confirm specific request with already signed data. + #[rpc(name = "signer_confirmRequestRaw")] + fn confirm_request_raw(&self, U256, Bytes) -> Result; + /// Reject the confirmation request. #[rpc(name = "signer_rejectRequest")] fn reject_request(&self, U256) -> Result; From 67ac05ef39828f258bdce3b5387c2952d8f0766d Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Thu, 10 Nov 2016 11:27:35 +0100 Subject: [PATCH 05/12] Default contract type on UI (#3310) * Added Token and Wallet ABI in Watch Contract #3126 * Improved ABI Validator #3281 * Select contract type on first screen #3126 * Added types decsription * Add ABI type to Contract metadata // Custom as default type #3310 --- js/src/api/contract/contract.js | 33 ++--- js/src/contracts/abi/index.js | 4 +- js/src/contracts/abi/wallet.json | 1 + js/src/modals/AddContract/addContract.css | 32 +++++ js/src/modals/AddContract/addContract.js | 153 ++++++++++++++++++++-- js/src/util/validation.js | 37 +++++- 6 files changed, 227 insertions(+), 33 deletions(-) create mode 100644 js/src/contracts/abi/wallet.json create mode 100644 js/src/modals/AddContract/addContract.css diff --git a/js/src/api/contract/contract.js b/js/src/api/contract/contract.js index bb6c15d8d..44e66c925 100644 --- a/js/src/api/contract/contract.js +++ b/js/src/api/contract/contract.js @@ -136,27 +136,30 @@ export default class Contract { } parseEventLogs (logs) { - return logs.map((log) => { - const signature = log.topics[0].substr(2); - const event = this.events.find((evt) => evt.signature === signature); + return logs + .map((log) => { + const signature = log.topics[0].substr(2); + const event = this.events.find((evt) => evt.signature === signature); - if (!event) { - throw new Error(`Unable to find event matching signature ${signature}`); - } + if (!event) { + console.warn(`Unable to find event matching signature ${signature}`); + return null; + } - const decoded = event.decodeLog(log.topics, log.data); + const decoded = event.decodeLog(log.topics, log.data); - log.params = {}; - log.event = event.name; + log.params = {}; + log.event = event.name; - decoded.params.forEach((param) => { - const { type, value } = param.token; + decoded.params.forEach((param) => { + const { type, value } = param.token; - log.params[param.name] = { type, value }; - }); + log.params[param.name] = { type, value }; + }); - return log; - }); + return log; + }) + .filter((log) => log); } parseTransactionEvents (receipt) { diff --git a/js/src/contracts/abi/index.js b/js/src/contracts/abi/index.js index 599f8a13b..80f49dc5b 100644 --- a/js/src/contracts/abi/index.js +++ b/js/src/contracts/abi/index.js @@ -24,6 +24,7 @@ import owned from './owned.json'; import registry from './registry.json'; import signaturereg from './signaturereg.json'; import tokenreg from './tokenreg.json'; +import wallet from './wallet.json'; export { basiccoin, @@ -35,5 +36,6 @@ export { owned, registry, signaturereg, - tokenreg + tokenreg, + wallet }; diff --git a/js/src/contracts/abi/wallet.json b/js/src/contracts/abi/wallet.json new file mode 100644 index 000000000..8048d239c --- /dev/null +++ b/js/src/contracts/abi/wallet.json @@ -0,0 +1 @@ +[{"constant":false,"inputs":[{"name":"_owner","type":"address"}],"name":"removeOwner","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_addr","type":"address"}],"name":"isOwner","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":true,"inputs":[],"name":"m_numOwners","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[],"name":"m_lastDay","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[],"name":"resetSpentToday","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"m_spentToday","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"_owner","type":"address"}],"name":"addOwner","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"m_required","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"_h","type":"bytes32"}],"name":"confirm","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":false,"inputs":[{"name":"_newLimit","type":"uint256"}],"name":"setDailyLimit","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"},{"name":"_data","type":"bytes"}],"name":"execute","outputs":[{"name":"_r","type":"bytes32"}],"type":"function"},{"constant":false,"inputs":[{"name":"_operation","type":"bytes32"}],"name":"revoke","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_newRequired","type":"uint256"}],"name":"changeRequirement","outputs":[],"type":"function"},{"constant":true,"inputs":[{"name":"_operation","type":"bytes32"},{"name":"_owner","type":"address"}],"name":"hasConfirmed","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":true,"inputs":[{"name":"ownerIndex","type":"uint256"}],"name":"getOwner","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"}],"name":"kill","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"}],"name":"changeOwner","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"m_dailyLimit","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"inputs":[{"name":"_owners","type":"address[]"},{"name":"_required","type":"uint256"},{"name":"_daylimit","type":"uint256"}],"type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"operation","type":"bytes32"}],"name":"Confirmation","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"operation","type":"bytes32"}],"name":"Revoke","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oldOwner","type":"address"},{"indexed":false,"name":"newOwner","type":"address"}],"name":"OwnerChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"newOwner","type":"address"}],"name":"OwnerAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oldOwner","type":"address"}],"name":"OwnerRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"newRequirement","type":"uint256"}],"name":"RequirementChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_from","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"to","type":"address"},{"indexed":false,"name":"data","type":"bytes"}],"name":"SingleTransact","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"operation","type":"bytes32"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"to","type":"address"},{"indexed":false,"name":"data","type":"bytes"}],"name":"MultiTransact","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"operation","type":"bytes32"},{"indexed":false,"name":"initiator","type":"address"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"to","type":"address"},{"indexed":false,"name":"data","type":"bytes"}],"name":"ConfirmationNeeded","type":"event"}] diff --git a/js/src/modals/AddContract/addContract.css b/js/src/modals/AddContract/addContract.css new file mode 100644 index 000000000..ed92a86d5 --- /dev/null +++ b/js/src/modals/AddContract/addContract.css @@ -0,0 +1,32 @@ +/* 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 . +*/ + +.spaced { + margin: 0.25em 0; +} + +.typeContainer { + display: flex; + flex-direction: column; + + .desc { + font-size: 0.8em; + margin-bottom: 0.5em; + color: #ccc; + z-index: 2; + } +} diff --git a/js/src/modals/AddContract/addContract.js b/js/src/modals/AddContract/addContract.js index 418378136..57773d2dc 100644 --- a/js/src/modals/AddContract/addContract.js +++ b/js/src/modals/AddContract/addContract.js @@ -17,10 +17,38 @@ import React, { Component, PropTypes } from 'react'; import ContentAdd from 'material-ui/svg-icons/content/add'; import ContentClear from 'material-ui/svg-icons/content/clear'; +import NavigationArrowForward from 'material-ui/svg-icons/navigation/arrow-forward'; +import NavigationArrowBack from 'material-ui/svg-icons/navigation/arrow-back'; + +import { RadioButton, RadioButtonGroup } from 'material-ui/RadioButton'; import { Button, Modal, Form, Input, InputAddress } from '../../ui'; import { ERRORS, validateAbi, validateAddress, validateName } from '../../util/validation'; +import { eip20, wallet } from '../../contracts/abi'; +import styles from './addContract.css'; + +const ABI_TYPES = [ + { + label: 'Token', readOnly: true, value: JSON.stringify(eip20), + type: 'token', + description: (A standard ERC 20 token) + }, + { + label: 'Multisig Wallet', readOnly: true, + type: 'multisig', + value: JSON.stringify(wallet), + description: (Official Multisig contract: see contract code) + }, + { + label: 'Custom Contract', value: '', + type: 'custom', + description: 'Contract created from custom ABI' + } +]; + +const STEPS = [ 'choose a contract type', 'enter contract details' ]; + export default class AddContract extends Component { static contextTypes = { api: PropTypes.object.isRequired @@ -34,44 +62,101 @@ export default class AddContract extends Component { state = { abi: '', abiError: ERRORS.invalidAbi, + abiType: ABI_TYPES[2], + abiTypeIndex: 2, abiParsed: null, address: '', addressError: ERRORS.invalidAddress, name: '', nameError: ERRORS.invalidName, - description: '' + description: '', + step: 0 }; + componentDidMount () { + this.onChangeABIType(null, this.state.abiTypeIndex); + } + render () { + const { step } = this.state; + return ( - { this.renderFields() } + steps={ STEPS } + current={ step } + > + { this.renderStep(step) } ); } + renderStep (step) { + switch (step) { + case 0: + return this.renderContractTypeSelector(); + default: + return this.renderFields(); + } + } + + renderContractTypeSelector () { + const { abiTypeIndex } = this.state; + + return ( + + { this.renderAbiTypes() } + + ); + } + renderDialogActions () { - const { addressError, nameError } = this.state; + const { addressError, nameError, step } = this.state; const hasError = !!(addressError || nameError); - return ([ + const cancelBtn = (
+ ) } + key={ index } + /> + )); + } - this.setState(validateAbi(abi, api)); + onNext = () => { + this.setState({ step: this.state.step + 1 }); + } + + onPrev = () => { + this.setState({ step: this.state.step - 1 }); + } + + onChangeABIType = (event, index) => { + const abiType = ABI_TYPES[index]; + this.setState({ abiTypeIndex: index, abiType }); + this.onEditAbi(abiType.value); + } + + onEditAbi = (abiIn) => { + const { api } = this.context; + const { abi, abiError, abiParsed } = validateAbi(abiIn, api); + this.setState({ abi, abiError, abiParsed }); + } + + onChangeAddress = (event, value) => { + this.onEditAddress(value); } onEditAddress = (_address) => { @@ -138,7 +262,7 @@ export default class AddContract extends Component { onAdd = () => { const { api } = this.context; - const { abiParsed, address, name, description } = this.state; + const { abiParsed, address, name, description, abiType } = this.state; Promise.all([ api.parity.setAccountName(address, name), @@ -147,6 +271,7 @@ export default class AddContract extends Component { deleted: false, timestamp: Date.now(), abi: abiParsed, + type: abiType.type, description }) ]).catch((error) => { diff --git a/js/src/util/validation.js b/js/src/util/validation.js index dd6e22911..5fad30563 100644 --- a/js/src/util/validation.js +++ b/js/src/util/validation.js @@ -38,10 +38,22 @@ export function validateAbi (abi, api) { abiParsed = JSON.parse(abi); if (!api.util.isArray(abiParsed) || !abiParsed.length) { - abiError = ERRORS.inavlidAbi; - } else { - abi = JSON.stringify(abiParsed); + abiError = ERRORS.invalidAbi; + return { abi, abiError, abiParsed }; } + + // Validate each elements of the Array + const invalidIndex = abiParsed + .map((o) => isValidAbiEvent(o, api) || isValidAbiFunction(o, api)) + .findIndex((valid) => !valid); + + if (invalidIndex !== -1) { + const invalid = abiParsed[invalidIndex]; + abiError = `${ERRORS.invalidAbi} (#${invalidIndex}: ${invalid.name || invalid.type})`; + return { abi, abiError, abiParsed }; + } + + abi = JSON.stringify(abiParsed); } catch (error) { abiError = ERRORS.invalidAbi; } @@ -53,6 +65,25 @@ export function validateAbi (abi, api) { }; } +function isValidAbiFunction (object, api) { + if (!object) { + return false; + } + + return ((object.type === 'function' && object.name) || object.type === 'constructor') && + (object.inputs && api.util.isArray(object.inputs)); +} + +function isValidAbiEvent (object, api) { + if (!object) { + return false; + } + + return (object.type === 'event') && + (object.name) && + (object.inputs && api.util.isArray(object.inputs)); +} + export function validateAddress (address) { let addressError = null; From 6ffaab15a399be463afa379f868a434badb09e95 Mon Sep 17 00:00:00 2001 From: Arkadiy Paronyan Date: Thu, 10 Nov 2016 11:28:27 +0100 Subject: [PATCH 06/12] Disarm the HF and add more bootnodes (#3323) * Disarm the HF * More bootnodes * Updated tests --- ethcore/res/ethereum/frontier.json | 16 +++++++++++----- ethcore/res/ethereum/tests | 2 +- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/ethcore/res/ethereum/frontier.json b/ethcore/res/ethereum/frontier.json index ecaefa4c3..be473237c 100644 --- a/ethcore/res/ethereum/frontier.json +++ b/ethcore/res/ethereum/frontier.json @@ -131,10 +131,10 @@ "0x807640a13483f8ac783c557fcdf27be11ea4ac7a" ], "eip150Transition": "0x259518", - "eip155Transition": 2642462, - "eip160Transition": 2642462, - "eip161abcTransition": 2642462, - "eip161dTransition": 2642462 + "eip155Transition": "0x7fffffffffffffff", + "eip160Transition": "0x7fffffffffffffff", + "eip161abcTransition": "0x7fffffffffffffff", + "eip161dTransition": "0x7fffffffffffffff" } } }, @@ -176,7 +176,13 @@ "enode://a979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@52.16.188.185:30303", "enode://de471bccee3d042261d52e9bff31458daecc406142b401d4cd848f677479f73104b9fdeb090af9583d3391b7f10cb2ba9e26865dd5fca4fcdc0fb1e3b723c786@54.94.239.50:30303", "enode://1118980bf48b0a3640bdba04e0fe78b1add18e1cd99bf22d53daac1fd9972ad650df52176e7c7d89d1114cfef2bc23a2959aa54998a46afcf7d91809f0855082@52.74.57.123:30303", - "enode://4cd540b2c3292e17cff39922e864094bf8b0741fcc8c5dcea14957e389d7944c70278d872902e3d0345927f621547efa659013c400865485ab4bfa0c6596936f@138.201.144.135:30303" + "enode://4cd540b2c3292e17cff39922e864094bf8b0741fcc8c5dcea14957e389d7944c70278d872902e3d0345927f621547efa659013c400865485ab4bfa0c6596936f@138.201.144.135:30303", + + "enode://89d5dc2a81e574c19d0465f497c1af96732d1b61a41de89c2a37f35707689ac416529fae1038809852b235c2d30fd325abdc57c122feeefbeaaf802cc7e9580d@45.55.33.62:30303", + "enode://605e04a43b1156966b3a3b66b980c87b7f18522f7f712035f84576016be909a2798a438b2b17b1a8c58db314d88539a77419ca4be36148c086900fba487c9d39@188.166.255.12:30303", + "enode://016b20125f447a3b203a3cae953b2ede8ffe51290c071e7599294be84317635730c397b8ff74404d6be412d539ee5bb5c3c700618723d3b53958c92bd33eaa82@159.203.210.80:30303", + "enode://01f76fa0561eca2b9a7e224378dd854278735f1449793c46ad0c4e79e8775d080c21dcc455be391e90a98153c3b05dcc8935c8440de7b56fe6d67251e33f4e3c@10.6.6.117:30303", + "enode://fe11ef89fc5ac9da358fc160857855f25bbf9e332c79b9ca7089330c02b728b2349988c6062f10982041702110745e203d26975a6b34bcc97144f9fe439034e8@10.1.72.117:30303" ], "accounts": { "0000000000000000000000000000000000000001": { "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } }, diff --git a/ethcore/res/ethereum/tests b/ethcore/res/ethereum/tests index 853333e7d..9028c4801 160000 --- a/ethcore/res/ethereum/tests +++ b/ethcore/res/ethereum/tests @@ -1 +1 @@ -Subproject commit 853333e7da312775fb8f32f2c2771b8578cd0d79 +Subproject commit 9028c4801fd39fbb71a9796979182549a24e81c8 From 1deeb0d9016293105a329949def64ba40b752ac9 Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Thu, 10 Nov 2016 11:42:00 +0100 Subject: [PATCH 07/12] Git pre-push checks for UI (#3072) * Added eslint cached option (#2291) * Added pre-push script running linting (#2291) * Modifies pre-push hook to run if eslint installed // auto install hook * Update pre-push script comment * Added husky for git hooks (#3072) --- js/.gitignore | 1 + js/package.json | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/js/.gitignore b/js/.gitignore index acb73a82a..c1e496d91 100644 --- a/js/.gitignore +++ b/js/.gitignore @@ -6,3 +6,4 @@ build .dist .happypack .npmjs +.eslintcache diff --git a/js/package.json b/js/package.json index eb0926c4b..e7ba88e74 100644 --- a/js/package.json +++ b/js/package.json @@ -39,9 +39,11 @@ "clean": "rm -rf ./build ./coverage", "coveralls": "npm run testCoverage && coveralls < coverage/lcov.info", "lint": "eslint --ignore-path .gitignore ./src/", + "lint:cached": "eslint --cache --ignore-path .gitignore ./src/", "test": "mocha 'src/**/*.spec.js'", "test:coverage": "istanbul cover _mocha -- 'src/**/*.spec.js'", - "test:e2e": "mocha 'src/**/*.e2e.js'" + "test:e2e": "mocha 'src/**/*.e2e.js'", + "prepush": "npm run lint:cached" }, "devDependencies": { "babel-cli": "^6.10.1", @@ -84,6 +86,7 @@ "happypack": "^2.2.1", "history": "^2.0.0", "html-loader": "^0.4.4", + "husky": "^0.11.9", "ignore-styles": "2.0.0", "image-webpack-loader": "^1.8.0", "istanbul": "^1.0.0-alpha.2", From 6ad909e7b36b69582840020c47dadc29d8ae468d Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Thu, 10 Nov 2016 14:26:06 +0100 Subject: [PATCH 08/12] Removed unnecessary test (#3342) --- js/src/api/contract/contract.spec.js | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/js/src/api/contract/contract.spec.js b/js/src/api/contract/contract.spec.js index 9065b4fad..96929cc11 100644 --- a/js/src/api/contract/contract.spec.js +++ b/js/src/api/contract/contract.spec.js @@ -119,19 +119,6 @@ describe('api/contract/Contract', () => { }); describe('parseTransactionEvents', () => { - it('checks for unmatched signatures', () => { - const contract = new Contract(eth, [{ anonymous: false, name: 'Message', type: 'event' }]); - expect(() => contract.parseTransactionEvents({ - logs: [{ - data: '0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000063cf90d3f0410092fc0fca41846f5962239791950000000000000000000000000000000000000000000000000000000056e6c85f0000000000000000000000000000000000000000000000000001000000004fcd00000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000d706f7374286d6573736167652900000000000000000000000000000000000000', - topics: [ - '0x954ba6c157daf8a26539574ffa64203c044691aa57251af95f4b48d85ec00dd5', - '0x0000000000000000000000000000000000000000000000000001000000004fe0' - ] - }] - })).to.throw(/event matching signature/); - }); - it('parses a transaction log into the data', () => { const contract = new Contract(eth, [ { From bb5da2379ba4940beca921ae9d4652a107f2ea8d Mon Sep 17 00:00:00 2001 From: Arkadiy Paronyan Date: Thu, 10 Nov 2016 14:49:16 +0100 Subject: [PATCH 09/12] Windows app and installer fixes (#3338) * Windows app and installer fixes * Sorted out comments --- nsis/installer.nsi | 5 ++++- windows/ptray/ptray.cpp | 42 +++++++++++++++++++++++++++++++++-------- 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/nsis/installer.nsi b/nsis/installer.nsi index 9c928b63b..685b4a936 100644 --- a/nsis/installer.nsi +++ b/nsis/installer.nsi @@ -12,7 +12,7 @@ !define VERSIONMINOR 5 !define VERSIONBUILD 0 !define ARGS "--warp" -!define FIRST_START_ARGS "--warp --mode=passive" +!define FIRST_START_ARGS "ui --warp --mode=passive" !addplugindir .\ @@ -160,6 +160,9 @@ section "uninstall" !insertmacro TerminateApp # Remove Start Menu launcher delete "$SMPROGRAMS\${COMPANYNAME}\${APPNAME}.lnk" + delete "$SMPROGRAMS\${COMPANYNAME}\${APPNAME} Ethereum.lnk" + delete "$DESKTOP\${APPNAME} Ethereum.lnk" + # Try to remove the Start Menu folder - this will only happen if it is empty rmDir "$SMPROGRAMS\${COMPANYNAME}" diff --git a/windows/ptray/ptray.cpp b/windows/ptray/ptray.cpp index cb33ad68a..8c50df2c1 100644 --- a/windows/ptray/ptray.cpp +++ b/windows/ptray/ptray.cpp @@ -41,6 +41,7 @@ DWORD parityProcId = 0; NOTIFYICONDATA nidApp; WCHAR szTitle[MAX_LOADSTRING]; WCHAR szWindowClass[MAX_LOADSTRING]; +LPCWCHAR commandLineFiltered = L""; LPCWSTR cParityExe = _T("parity.exe"); @@ -85,7 +86,6 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, OpenUI(); return 0; } - LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); LoadStringW(hInstance, IDC_PTRAY, szWindowClass, MAX_LOADSTRING); @@ -133,6 +133,24 @@ ATOM MyRegisterClass(HINSTANCE hInstance) bool InitInstance(HINSTANCE hInstance, int nCmdShow, LPWSTR cmdLine) { + if (lstrlen(cmdLine) > 0) + { + int commandLineArgs = 0; + LPWSTR* commandLine = CommandLineToArgvW(cmdLine, &commandLineArgs); + LPWSTR filteredArgs = new WCHAR[lstrlen(cmdLine) + 2]; + filteredArgs[0] = '\0'; + for (int i = 0; i < commandLineArgs; i++) + { + // Remove "ui" from command line + if (lstrcmp(commandLine[i], L"ui") != 0) + { + lstrcat(filteredArgs, commandLine[i]); + lstrcat(filteredArgs, L" "); + } + } + commandLineFiltered = filteredArgs; + } + // Check if already running PROCESSENTRY32 entry; entry.dwSize = sizeof(PROCESSENTRY32); @@ -190,7 +208,9 @@ bool InitInstance(HINSTANCE hInstance, int nCmdShow, LPWSTR cmdLine) nidApp.uCallbackMessage = WM_USER_SHELLICON; LoadString(hInstance, IDS_CONTROL_PARITY, nidApp.szTip, MAX_LOADSTRING); Shell_NotifyIcon(NIM_ADD, &nidApp); - return TRUE; + + SetTimer(hWnd, 0, 1000, nullptr); + return true; } @@ -294,8 +314,10 @@ void OpenUI() PROCESS_INFORMATION procInfo = { 0 }; STARTUPINFO startupInfo = { sizeof(STARTUPINFO) }; - LPWSTR cmd = _T("parity.exe ui"); - CreateProcess(path, cmd, nullptr, nullptr, false, CREATE_NO_WINDOW, nullptr, nullptr, &startupInfo, &procInfo); + LPWSTR args = new WCHAR[lstrlen(commandLineFiltered) + MAX_PATH + 2]; + lstrcpy(args, L"parity.exe ui "); + lstrcat(args, commandLineFiltered); + CreateProcess(path, args, nullptr, nullptr, false, CREATE_NO_WINDOW, nullptr, nullptr, &startupInfo, &procInfo); } bool AutostartEnabled() { @@ -321,10 +343,14 @@ void EnableAutostart(bool enable) { if (enable) { - TCHAR path[MAX_PATH] = { 0 }; - if (!GetTrayExePath(path, MAX_PATH)) - return; - RegSetValueEx(hKey, L"Parity", 0, REG_SZ, (LPBYTE)path, MAX_PATH); + LPWSTR args = new WCHAR[lstrlen(commandLineFiltered) + MAX_PATH + 2]; + if (GetTrayExePath(args, MAX_PATH)) + { + lstrcat(args, L" "); + lstrcat(args, commandLineFiltered); + RegSetValueEx(hKey, L"Parity", 0, REG_SZ, (LPBYTE)args, MAX_PATH); + } + delete[] args; } else { From 561f008c915c07a00a89cd8859e6b590a729f722 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Thu, 10 Nov 2016 15:01:07 +0100 Subject: [PATCH 10/12] Manual bump of package.json (#3345) --- js/package.json | 2 +- js/scripts/release.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/js/package.json b/js/package.json index e7ba88e74..b8bfedc71 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.24", + "version": "0.2.26", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", diff --git a/js/scripts/release.sh b/js/scripts/release.sh index fd95e00b8..5e631cf98 100755 --- a/js/scripts/release.sh +++ b/js/scripts/release.sh @@ -63,7 +63,7 @@ if [ "$BRANCH" == "master" ]; then echo "*** Publishing $PACKAGE to npmjs" cd .npmjs - npm publish --access public + npm publish --access public || true cd ../.. fi From aa0f05a18636b9a22554460c7ba66527436fff28 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Thu, 10 Nov 2016 14:07:52 +0000 Subject: [PATCH 11/12] [ci skip] js-precompiled 20161110-140631 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c3dd51492..3808bfd61 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1249,7 +1249,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#9054ef95a5d79cbd8fefe4869ec3b4de07e9a72d" +source = "git+https://github.com/ethcore/js-precompiled.git#d92b25d4c79259850cb3ae95b5c6cd4649056c3f" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index b8bfedc71..9da83e7e1 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.26", + "version": "0.2.27", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", From 0ed811472636882549288bb8f7e961daaf282280 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Thu, 10 Nov 2016 14:20:08 +0000 Subject: [PATCH 12/12] [ci skip] js-precompiled 20161110-141839 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3808bfd61..f6d5fa213 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1249,7 +1249,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#d92b25d4c79259850cb3ae95b5c6cd4649056c3f" +source = "git+https://github.com/ethcore/js-precompiled.git#29c6f1d527db5c6a29c6b6afdb9bde32c6809079" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index 9da83e7e1..ca633fd29 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.27", + "version": "0.2.28", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ",