merge master into sms-verification-modal

This commit is contained in:
Jannis R 2016-11-10 16:23:43 +01:00
commit 01f14901ce
No known key found for this signature in database
GPG Key ID: 0FE83946296A88A5
59 changed files with 923 additions and 244 deletions

2
Cargo.lock generated
View File

@ -1249,7 +1249,7 @@ dependencies = [
[[package]] [[package]]
name = "parity-ui-precompiled" name = "parity-ui-precompiled"
version = "1.4.0" version = "1.4.0"
source = "git+https://github.com/ethcore/js-precompiled.git#9054ef95a5d79cbd8fefe4869ec3b4de07e9a72d" source = "git+https://github.com/ethcore/js-precompiled.git#29c6f1d527db5c6a29c6b6afdb9bde32c6809079"
dependencies = [ dependencies = [
"parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
] ]

View File

@ -47,7 +47,7 @@ pub struct ContentFetcher<R: URLHint = URLHintContract> {
resolver: R, resolver: R,
cache: Arc<Mutex<ContentCache>>, cache: Arc<Mutex<ContentCache>>,
sync: Arc<SyncStatus>, sync: Arc<SyncStatus>,
embeddable_at: Option<u16>, embeddable_on: Option<(String, u16)>,
} }
impl<R: URLHint> Drop for ContentFetcher<R> { impl<R: URLHint> Drop for ContentFetcher<R> {
@ -59,7 +59,7 @@ impl<R: URLHint> Drop for ContentFetcher<R> {
impl<R: URLHint> ContentFetcher<R> { impl<R: URLHint> ContentFetcher<R> {
pub fn new(resolver: R, sync_status: Arc<SyncStatus>, embeddable_at: Option<u16>) -> Self { pub fn new(resolver: R, sync_status: Arc<SyncStatus>, embeddable_on: Option<(String, u16)>) -> Self {
let mut dapps_path = env::temp_dir(); let mut dapps_path = env::temp_dir();
dapps_path.push(random_filename()); dapps_path.push(random_filename());
@ -68,17 +68,17 @@ impl<R: URLHint> ContentFetcher<R> {
resolver: resolver, resolver: resolver,
sync: sync_status, sync: sync_status,
cache: Arc::new(Mutex::new(ContentCache::default())), cache: Arc::new(Mutex::new(ContentCache::default())),
embeddable_at: embeddable_at, embeddable_on: embeddable_on,
} }
} }
fn still_syncing(port: Option<u16>) -> Box<Handler> { fn still_syncing(address: Option<(String, u16)>) -> Box<Handler> {
Box::new(ContentHandler::error( Box::new(ContentHandler::error(
StatusCode::ServiceUnavailable, StatusCode::ServiceUnavailable,
"Sync In Progress", "Sync In Progress",
"Your node is still syncing. We cannot resolve any content before it's fully synced.", "Your node is still syncing. We cannot resolve any content before it's fully synced.",
Some("<a href=\"javascript:window.location.reload()\">Refresh</a>"), Some("<a href=\"javascript:window.location.reload()\">Refresh</a>"),
port, address,
)) ))
} }
@ -145,7 +145,7 @@ impl<R: URLHint> ContentFetcher<R> {
match content { match content {
// Don't serve dapps if we are still syncing (but serve content) // Don't serve dapps if we are still syncing (but serve content)
Some(URLHintResult::Dapp(_)) if self.sync.is_major_importing() => { 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)) => { Some(URLHintResult::Dapp(dapp)) => {
let (handler, fetch_control) = ContentFetcherHandler::new( let (handler, fetch_control) = ContentFetcherHandler::new(
@ -155,9 +155,9 @@ impl<R: URLHint> ContentFetcher<R> {
id: content_id.clone(), id: content_id.clone(),
dapps_path: self.dapps_path.clone(), dapps_path: self.dapps_path.clone(),
on_done: Box::new(on_done), 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<Handler>) (Some(ContentStatus::Fetching(fetch_control)), Box::new(handler) as Box<Handler>)
@ -172,13 +172,13 @@ impl<R: URLHint> ContentFetcher<R> {
content_path: self.dapps_path.clone(), content_path: self.dapps_path.clone(),
on_done: Box::new(on_done), on_done: Box::new(on_done),
}, },
self.embeddable_at, self.embeddable_on.clone(),
); );
(Some(ContentStatus::Fetching(fetch_control)), Box::new(handler) as Box<Handler>) (Some(ContentStatus::Fetching(fetch_control)), Box::new(handler) as Box<Handler>)
}, },
None if self.sync.is_major_importing() => { None if self.sync.is_major_importing() => {
(None, Self::still_syncing(self.embeddable_at)) (None, Self::still_syncing(self.embeddable_on.clone()))
}, },
None => { None => {
// This may happen when sync status changes in between // This may happen when sync status changes in between
@ -188,7 +188,7 @@ impl<R: URLHint> ContentFetcher<R> {
"Resource Not Found", "Resource Not Found",
"Requested resource was not found.", "Requested resource was not found.",
None, None,
self.embeddable_at, self.embeddable_on.clone(),
)) as Box<Handler>) )) as Box<Handler>)
}, },
} }
@ -293,7 +293,7 @@ struct DappInstaller {
id: String, id: String,
dapps_path: PathBuf, dapps_path: PathBuf,
on_done: Box<Fn(String, Option<LocalPageEndpoint>) + Send>, on_done: Box<Fn(String, Option<LocalPageEndpoint>) + Send>,
embeddable_at: Option<u16>, embeddable_on: Option<(String, u16)>,
} }
impl DappInstaller { impl DappInstaller {
@ -386,7 +386,7 @@ impl ContentValidator for DappInstaller {
try!(manifest_file.write_all(manifest_str.as_bytes())); try!(manifest_file.write_all(manifest_str.as_bytes()));
// Create endpoint // 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 // Return modified app manifest
Ok((manifest.id.clone(), app)) Ok((manifest.id.clone(), app))

View File

@ -97,12 +97,12 @@ fn read_manifest(name: &str, mut path: PathBuf) -> EndpointInfo {
}) })
} }
pub fn local_endpoints(dapps_path: String, signer_port: Option<u16>) -> Endpoints { pub fn local_endpoints(dapps_path: String, signer_address: Option<(String, u16)>) -> Endpoints {
let mut pages = Endpoints::new(); let mut pages = Endpoints::new();
for dapp in local_dapps(dapps_path) { for dapp in local_dapps(dapps_path) {
pages.insert( pages.insert(
dapp.id, 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 pages

View File

@ -37,26 +37,26 @@ pub fn utils() -> Box<Endpoint> {
Box::new(PageEndpoint::with_prefix(parity_ui::App::default(), UTILS_PATH.to_owned())) Box::new(PageEndpoint::with_prefix(parity_ui::App::default(), UTILS_PATH.to_owned()))
} }
pub fn all_endpoints(dapps_path: String, signer_port: Option<u16>) -> Endpoints { pub fn all_endpoints(dapps_path: String, signer_address: Option<(String, u16)>) -> Endpoints {
// fetch fs dapps at first to avoid overwriting builtins // 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 // NOTE [ToDr] Dapps will be currently embeded on 8180
insert::<parity_ui::App>(&mut pages, "ui", Embeddable::Yes(signer_port)); insert::<parity_ui::App>(&mut pages, "ui", Embeddable::Yes(signer_address.clone()));
pages.insert("proxy".into(), ProxyPac::boxed(signer_port)); pages.insert("proxy".into(), ProxyPac::boxed(signer_address));
pages pages
} }
fn insert<T : WebApp + Default + 'static>(pages: &mut Endpoints, id: &str, embed_at: Embeddable) { fn insert<T : WebApp + Default + 'static>(pages: &mut Endpoints, id: &str, embed_at: Embeddable) {
pages.insert(id.to_owned(), Box::new(match embed_at { 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()), Embeddable::No => PageEndpoint::new(T::default()),
})); }));
} }
enum Embeddable { enum Embeddable {
Yes(Option<u16>), Yes(Option<(String, u16)>),
#[allow(dead_code)] #[allow(dead_code)]
No, No,
} }

View File

@ -32,7 +32,7 @@ pub struct ContentHandler {
content: String, content: String,
mimetype: Mime, mimetype: Mime,
write_pos: usize, write_pos: usize,
safe_to_embed_at_port: Option<u16>, safe_to_embed_on: Option<(String, u16)>,
} }
impl ContentHandler { impl ContentHandler {
@ -44,31 +44,31 @@ impl ContentHandler {
Self::new(StatusCode::NotFound, content, mimetype) Self::new(StatusCode::NotFound, content, mimetype)
} }
pub fn html(code: StatusCode, content: String, embeddable_at: Option<u16>) -> Self { pub fn html(code: StatusCode, content: String, embeddable_on: Option<(String, u16)>) -> Self {
Self::new_embeddable(code, content, mime!(Text/Html), embeddable_at) 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<u16>) -> Self { pub fn error(code: StatusCode, title: &str, message: &str, details: Option<&str>, embeddable_on: Option<(String, u16)>) -> Self {
Self::html(code, format!( Self::html(code, format!(
include_str!("../error_tpl.html"), include_str!("../error_tpl.html"),
title=title, title=title,
message=message, message=message,
details=details.unwrap_or_else(|| ""), details=details.unwrap_or_else(|| ""),
version=version(), version=version(),
), embeddable_at) ), embeddable_on)
} }
pub fn new(code: StatusCode, content: String, mimetype: Mime) -> Self { pub fn new(code: StatusCode, content: String, mimetype: Mime) -> Self {
Self::new_embeddable(code, content, mimetype, None) Self::new_embeddable(code, content, mimetype, None)
} }
pub fn new_embeddable(code: StatusCode, content: String, mimetype: Mime, embeddable_at: Option<u16>) -> Self { pub fn new_embeddable(code: StatusCode, content: String, mimetype: Mime, embeddable_on: Option<(String, u16)>) -> Self {
ContentHandler { ContentHandler {
code: code, code: code,
content: content, content: content,
mimetype: mimetype, mimetype: mimetype,
write_pos: 0, write_pos: 0,
safe_to_embed_at_port: embeddable_at, safe_to_embed_on: embeddable_on,
} }
} }
} }
@ -85,7 +85,7 @@ impl server::Handler<HttpStream> for ContentHandler {
fn on_response(&mut self, res: &mut server::Response) -> Next { fn on_response(&mut self, res: &mut server::Response) -> Next {
res.set_status(self.code); res.set_status(self.code);
res.headers_mut().set(header::ContentType(self.mimetype.clone())); 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() Next::write()
} }

View File

@ -138,7 +138,7 @@ pub struct ContentFetcherHandler<H: ContentValidator> {
client: Option<Client>, client: Option<Client>,
installer: H, installer: H,
request_url: Option<Url>, request_url: Option<Url>,
embeddable_at: Option<u16>, embeddable_on: Option<(String, u16)>,
} }
impl<H: ContentValidator> Drop for ContentFetcherHandler<H> { impl<H: ContentValidator> Drop for ContentFetcherHandler<H> {
@ -157,7 +157,7 @@ impl<H: ContentValidator> ContentFetcherHandler<H> {
url: String, url: String,
control: Control, control: Control,
handler: H, handler: H,
embeddable_at: Option<u16>, embeddable_on: Option<(String, u16)>,
) -> (Self, Arc<FetchControl>) { ) -> (Self, Arc<FetchControl>) {
let fetch_control = Arc::new(FetchControl::default()); let fetch_control = Arc::new(FetchControl::default());
@ -169,7 +169,7 @@ impl<H: ContentValidator> ContentFetcherHandler<H> {
status: FetchState::NotStarted(url), status: FetchState::NotStarted(url),
installer: handler, installer: handler,
request_url: None, request_url: None,
embeddable_at: embeddable_at, embeddable_on: embeddable_on,
}; };
(handler, fetch_control) (handler, fetch_control)
@ -208,7 +208,7 @@ impl<H: ContentValidator> server::Handler<HttpStream> for ContentFetcherHandler<
"Unable To Start Dapp Download", "Unable To Start Dapp Download",
"Could not initialize download of the dapp. It might be a problem with the remote server.", "Could not initialize download of the dapp. It might be a problem with the remote server.",
Some(&format!("{}", e)), Some(&format!("{}", e)),
self.embeddable_at, self.embeddable_on.clone(),
)), )),
} }
}, },
@ -218,7 +218,7 @@ impl<H: ContentValidator> server::Handler<HttpStream> for ContentFetcherHandler<
"Method Not Allowed", "Method Not Allowed",
"Only <code>GET</code> requests are allowed.", "Only <code>GET</code> requests are allowed.",
None, None,
self.embeddable_at, self.embeddable_on.clone(),
)), )),
}) })
} else { None }; } else { None };
@ -241,7 +241,7 @@ impl<H: ContentValidator> server::Handler<HttpStream> for ContentFetcherHandler<
"Download Timeout", "Download Timeout",
&format!("Could not fetch content within {} seconds.", FETCH_TIMEOUT), &format!("Could not fetch content within {} seconds.", FETCH_TIMEOUT),
None, None,
self.embeddable_at, self.embeddable_on.clone(),
); );
Self::close_client(&mut self.client); Self::close_client(&mut self.client);
(Some(FetchState::Error(timeout)), Next::write()) (Some(FetchState::Error(timeout)), Next::write())
@ -263,7 +263,7 @@ impl<H: ContentValidator> server::Handler<HttpStream> for ContentFetcherHandler<
"Invalid Dapp", "Invalid Dapp",
"Downloaded bundle does not contain a valid content.", "Downloaded bundle does not contain a valid content.",
Some(&format!("{:?}", e)), Some(&format!("{:?}", e)),
self.embeddable_at, self.embeddable_on.clone(),
)) ))
}, },
Ok((id, result)) => { Ok((id, result)) => {
@ -284,7 +284,7 @@ impl<H: ContentValidator> server::Handler<HttpStream> for ContentFetcherHandler<
"Download Error", "Download Error",
"There was an error when fetching the content.", "There was an error when fetching the content.",
Some(&format!("{:?}", e)), Some(&format!("{:?}", e)),
self.embeddable_at, self.embeddable_on.clone(),
); );
(Some(FetchState::Error(error)), Next::write()) (Some(FetchState::Error(error)), Next::write())
}, },

View File

@ -30,18 +30,18 @@ pub use self::fetch::{ContentFetcherHandler, ContentValidator, FetchControl};
use url::Url; use url::Url;
use hyper::{server, header, net, uri}; use hyper::{server, header, net, uri};
use signer_address; use address;
/// Adds security-related headers to the Response. /// Adds security-related headers to the Response.
pub fn add_security_headers(headers: &mut header::Headers, embeddable_at: Option<u16>) { 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-XSS-Protection", vec![b"1; mode=block".to_vec()]);
headers.set_raw("X-Content-Type-Options", vec![b"nosniff".to_vec()]); headers.set_raw("X-Content-Type-Options", vec![b"nosniff".to_vec()]);
// Embedding header: // Embedding header:
if let Some(port) = embeddable_at { if let Some(embeddable_on) = embeddable_on {
headers.set_raw( headers.set_raw(
"X-Frame-Options", "X-Frame-Options",
vec![format!("ALLOW-FROM http://{}", signer_address(port)).into_bytes()] vec![format!("ALLOW-FROM http://{}", address(embeddable_on)).into_bytes()]
); );
} else { } else {
// TODO [ToDr] Should we be more strict here (DENY?)? // TODO [ToDr] Should we be more strict here (DENY?)?

View File

@ -112,7 +112,7 @@ pub struct ServerBuilder {
handler: Arc<IoHandler>, handler: Arc<IoHandler>,
registrar: Arc<ContractClient>, registrar: Arc<ContractClient>,
sync_status: Arc<SyncStatus>, sync_status: Arc<SyncStatus>,
signer_port: Option<u16>, signer_address: Option<(String, u16)>,
} }
impl Extendable for ServerBuilder { impl Extendable for ServerBuilder {
@ -129,7 +129,7 @@ impl ServerBuilder {
handler: Arc::new(IoHandler::new()), handler: Arc::new(IoHandler::new()),
registrar: registrar, registrar: registrar,
sync_status: Arc::new(|| false), sync_status: Arc::new(|| false),
signer_port: None, signer_address: None,
} }
} }
@ -139,8 +139,8 @@ impl ServerBuilder {
} }
/// Change default signer port. /// Change default signer port.
pub fn with_signer_port(&mut self, signer_port: Option<u16>) { pub fn with_signer_address(&mut self, signer_address: Option<(String, u16)>) {
self.signer_port = signer_port; self.signer_address = signer_address;
} }
/// Asynchronously start server with no authentication, /// Asynchronously start server with no authentication,
@ -152,7 +152,7 @@ impl ServerBuilder {
NoAuth, NoAuth,
self.handler.clone(), self.handler.clone(),
self.dapps_path.clone(), self.dapps_path.clone(),
self.signer_port.clone(), self.signer_address.clone(),
self.registrar.clone(), self.registrar.clone(),
self.sync_status.clone(), self.sync_status.clone(),
) )
@ -167,7 +167,7 @@ impl ServerBuilder {
HttpBasicAuth::single_user(username, password), HttpBasicAuth::single_user(username, password),
self.handler.clone(), self.handler.clone(),
self.dapps_path.clone(), self.dapps_path.clone(),
self.signer_port.clone(), self.signer_address.clone(),
self.registrar.clone(), self.registrar.clone(),
self.sync_status.clone(), self.sync_status.clone(),
) )
@ -197,11 +197,11 @@ impl Server {
} }
/// Returns a list of CORS domains for API endpoint. /// Returns a list of CORS domains for API endpoint.
fn cors_domains(signer_port: Option<u16>) -> Vec<String> { fn cors_domains(signer_address: Option<(String, u16)>) -> Vec<String> {
match signer_port { match signer_address {
Some(port) => vec![ Some(signer_address) => vec![
format!("http://{}{}", HOME_PAGE, DAPPS_DOMAIN), format!("http://{}{}", HOME_PAGE, DAPPS_DOMAIN),
format!("http://{}", signer_address(port)), format!("http://{}", address(signer_address)),
], ],
None => vec![], None => vec![],
} }
@ -213,15 +213,15 @@ impl Server {
authorization: A, authorization: A,
handler: Arc<IoHandler>, handler: Arc<IoHandler>,
dapps_path: String, dapps_path: String,
signer_port: Option<u16>, signer_address: Option<(String, u16)>,
registrar: Arc<ContractClient>, registrar: Arc<ContractClient>,
sync_status: Arc<SyncStatus>, sync_status: Arc<SyncStatus>,
) -> Result<Server, ServerError> { ) -> Result<Server, ServerError> {
let panic_handler = Arc::new(Mutex::new(None)); let panic_handler = Arc::new(Mutex::new(None));
let authorization = Arc::new(authorization); let authorization = Arc::new(authorization);
let content_fetcher = Arc::new(apps::fetcher::ContentFetcher::new(apps::urlhint::URLHintContract::new(registrar), sync_status, 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_port.clone())); let endpoints = Arc::new(apps::all_endpoints(dapps_path, signer_address.clone()));
let cors_domains = Self::cors_domains(signer_port); let cors_domains = Self::cors_domains(signer_address.clone());
let special = Arc::new({ let special = Arc::new({
let mut special = HashMap::new(); let mut special = HashMap::new();
@ -238,7 +238,7 @@ impl Server {
try!(hyper::Server::http(addr)) try!(hyper::Server::http(addr))
.handle(move |ctrl| router::Router::new( .handle(move |ctrl| router::Router::new(
ctrl, ctrl,
signer_port.clone(), signer_address.clone(),
content_fetcher.clone(), content_fetcher.clone(),
endpoints.clone(), endpoints.clone(),
special.clone(), special.clone(),
@ -302,8 +302,8 @@ pub fn random_filename() -> String {
rng.gen_ascii_chars().take(12).collect() rng.gen_ascii_chars().take(12).collect()
} }
fn signer_address(port: u16) -> String { fn address(address: (String, u16)) -> String {
format!("127.0.0.1:{}", port) format!("{}:{}", address.0, address.1)
} }
#[cfg(test)] #[cfg(test)]
@ -332,7 +332,7 @@ mod util_tests {
// when // when
let none = Server::cors_domains(None); 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 // then
assert_eq!(none, Vec::<String>::new()); assert_eq!(none, Vec::<String>::new());

View File

@ -25,7 +25,7 @@ pub struct PageEndpoint<T : WebApp + 'static> {
/// Prefix to strip from the path (when `None` deducted from `app_id`) /// Prefix to strip from the path (when `None` deducted from `app_id`)
pub prefix: Option<String>, pub prefix: Option<String>,
/// Safe to be loaded in frame by other origin. (use wisely!) /// Safe to be loaded in frame by other origin. (use wisely!)
safe_to_embed_at_port: Option<u16>, safe_to_embed_on: Option<(String, u16)>,
info: EndpointInfo, info: EndpointInfo,
} }
@ -36,7 +36,7 @@ impl<T: WebApp + 'static> PageEndpoint<T> {
PageEndpoint { PageEndpoint {
app: Arc::new(app), app: Arc::new(app),
prefix: None, prefix: None,
safe_to_embed_at_port: None, safe_to_embed_on: None,
info: EndpointInfo::from(info), info: EndpointInfo::from(info),
} }
} }
@ -49,7 +49,7 @@ impl<T: WebApp + 'static> PageEndpoint<T> {
PageEndpoint { PageEndpoint {
app: Arc::new(app), app: Arc::new(app),
prefix: Some(prefix), prefix: Some(prefix),
safe_to_embed_at_port: None, safe_to_embed_on: None,
info: EndpointInfo::from(info), info: EndpointInfo::from(info),
} }
} }
@ -57,12 +57,12 @@ impl<T: WebApp + 'static> PageEndpoint<T> {
/// Creates new `PageEndpoint` which can be safely used in iframe /// Creates new `PageEndpoint` which can be safely used in iframe
/// even from different origin. It might be dangerous (clickjacking). /// even from different origin. It might be dangerous (clickjacking).
/// Use wisely! /// Use wisely!
pub fn new_safe_to_embed(app: T, port: Option<u16>) -> Self { pub fn new_safe_to_embed(app: T, address: Option<(String, u16)>) -> Self {
let info = app.info(); let info = app.info();
PageEndpoint { PageEndpoint {
app: Arc::new(app), app: Arc::new(app),
prefix: None, prefix: None,
safe_to_embed_at_port: port, safe_to_embed_on: address,
info: EndpointInfo::from(info), info: EndpointInfo::from(info),
} }
} }
@ -79,9 +79,9 @@ impl<T: WebApp> Endpoint for PageEndpoint<T> {
app: BuiltinDapp::new(self.app.clone()), app: BuiltinDapp::new(self.app.clone()),
prefix: self.prefix.clone(), prefix: self.prefix.clone(),
path: path, 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, 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(),
}) })
} }
} }

View File

@ -60,13 +60,13 @@ pub enum ServedFile<T: Dapp> {
} }
impl<T: Dapp> ServedFile<T> { impl<T: Dapp> ServedFile<T> {
pub fn new(embeddable_at: Option<u16>) -> Self { pub fn new(embeddable_on: Option<(String, u16)>) -> Self {
ServedFile::Error(ContentHandler::error( ServedFile::Error(ContentHandler::error(
StatusCode::NotFound, StatusCode::NotFound,
"404 Not Found", "404 Not Found",
"Requested dapp resource was not found.", "Requested dapp resource was not found.",
None, None,
embeddable_at, embeddable_on,
)) ))
} }
} }
@ -97,7 +97,7 @@ pub struct PageHandler<T: Dapp> {
/// Requested path. /// Requested path.
pub path: EndpointPath, pub path: EndpointPath,
/// Flag indicating if the file can be safely embeded (put in iframe). /// Flag indicating if the file can be safely embeded (put in iframe).
pub safe_to_embed_at_port: Option<u16>, pub safe_to_embed_on: Option<(String, u16)>,
/// Cache settings for this page. /// Cache settings for this page.
pub cache: PageCache, pub cache: PageCache,
} }
@ -133,7 +133,7 @@ impl<T: Dapp> server::Handler<HttpStream> for PageHandler<T> {
self.app.file(&self.extract_path(url.path())) self.app.file(&self.extract_path(url.path()))
}, },
_ => None, _ => 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() Next::write()
} }
@ -162,7 +162,7 @@ impl<T: Dapp> server::Handler<HttpStream> for PageHandler<T> {
} }
// Security headers: // 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() Next::write()
}, },
ServedFile::Error(ref mut handler) => { ServedFile::Error(ref mut handler) => {
@ -246,7 +246,7 @@ fn should_extract_path_with_appid() {
}, },
file: ServedFile::new(None), file: ServedFile::new(None),
cache: Default::default(), cache: Default::default(),
safe_to_embed_at_port: None, safe_to_embed_on: None,
}; };
// when // when

View File

@ -27,17 +27,17 @@ pub struct LocalPageEndpoint {
mime: Option<String>, mime: Option<String>,
info: Option<EndpointInfo>, info: Option<EndpointInfo>,
cache: PageCache, cache: PageCache,
embeddable_at: Option<u16>, embeddable_on: Option<(String, u16)>,
} }
impl LocalPageEndpoint { impl LocalPageEndpoint {
pub fn new(path: PathBuf, info: EndpointInfo, cache: PageCache, embeddable_at: Option<u16>) -> Self { pub fn new(path: PathBuf, info: EndpointInfo, cache: PageCache, embeddable_on: Option<(String, u16)>) -> Self {
LocalPageEndpoint { LocalPageEndpoint {
path: path, path: path,
mime: None, mime: None,
info: Some(info), info: Some(info),
cache: cache, cache: cache,
embeddable_at: embeddable_at, embeddable_on: embeddable_on,
} }
} }
@ -47,7 +47,7 @@ impl LocalPageEndpoint {
mime: Some(mime), mime: Some(mime),
info: None, info: None,
cache: cache, cache: cache,
embeddable_at: None, embeddable_on: None,
} }
} }
@ -68,7 +68,7 @@ impl Endpoint for LocalPageEndpoint {
prefix: None, prefix: None,
path: path, path: path,
file: handler::ServedFile::new(None), file: handler::ServedFile::new(None),
safe_to_embed_at_port: self.embeddable_at, safe_to_embed_on: self.embeddable_on.clone(),
cache: self.cache, cache: self.cache,
}) })
} else { } else {
@ -77,7 +77,7 @@ impl Endpoint for LocalPageEndpoint {
prefix: None, prefix: None,
path: path, path: path,
file: handler::ServedFile::new(None), file: handler::ServedFile::new(None),
safe_to_embed_at_port: self.embeddable_at, safe_to_embed_on: self.embeddable_on.clone(),
cache: self.cache, cache: self.cache,
}) })
} }

View File

@ -19,24 +19,24 @@
use endpoint::{Endpoint, Handler, EndpointPath}; use endpoint::{Endpoint, Handler, EndpointPath};
use handlers::ContentHandler; use handlers::ContentHandler;
use apps::{HOME_PAGE, DAPPS_DOMAIN}; use apps::{HOME_PAGE, DAPPS_DOMAIN};
use signer_address; use address;
pub struct ProxyPac { pub struct ProxyPac {
signer_port: Option<u16>, signer_address: Option<(String, u16)>,
} }
impl ProxyPac { impl ProxyPac {
pub fn boxed(signer_port: Option<u16>) -> Box<Endpoint> { pub fn boxed(signer_address: Option<(String, u16)>) -> Box<Endpoint> {
Box::new(ProxyPac { Box::new(ProxyPac {
signer_port: signer_port signer_address: signer_address
}) })
} }
} }
impl Endpoint for ProxyPac { impl Endpoint for ProxyPac {
fn to_handler(&self, path: EndpointPath) -> Box<Handler> { fn to_handler(&self, path: EndpointPath) -> Box<Handler> {
let signer = self.signer_port let signer = self.signer_address.clone()
.map(signer_address) .map(address)
.unwrap_or_else(|| format!("{}:{}", path.host, path.port)); .unwrap_or_else(|| format!("{}:{}", path.host, path.port));
let content = format!( let content = format!(

View File

@ -20,7 +20,7 @@
pub mod auth; pub mod auth;
mod host_validation; mod host_validation;
use signer_address; use address;
use std::sync::Arc; use std::sync::Arc;
use std::collections::HashMap; use std::collections::HashMap;
use url::{Url, Host}; use url::{Url, Host};
@ -43,7 +43,7 @@ pub enum SpecialEndpoint {
pub struct Router<A: Authorization + 'static> { pub struct Router<A: Authorization + 'static> {
control: Option<Control>, control: Option<Control>,
signer_port: Option<u16>, signer_address: Option<(String, u16)>,
endpoints: Arc<Endpoints>, endpoints: Arc<Endpoints>,
fetch: Arc<ContentFetcher>, fetch: Arc<ContentFetcher>,
special: Arc<HashMap<SpecialEndpoint, Box<Endpoint>>>, special: Arc<HashMap<SpecialEndpoint, Box<Endpoint>>>,
@ -117,14 +117,14 @@ impl<A: Authorization + 'static> server::Handler<HttpStream> for Router<A> {
"404 Not Found", "404 Not Found",
"Requested content was not found.", "Requested content was not found.",
None, None,
self.signer_port, self.signer_address.clone(),
)) ))
}, },
// Redirect any other GET request to signer. // Redirect any other GET request to signer.
_ if *req.method() == hyper::Method::Get => { _ 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."); trace!(target: "dapps", "Redirecting to signer interface.");
Redirection::boxed(&format!("http://{}", signer_address(port))) Redirection::boxed(&format!("http://{}", address(signer_address)))
} else { } else {
trace!(target: "dapps", "Signer disabled, returning 404."); trace!(target: "dapps", "Signer disabled, returning 404.");
Box::new(ContentHandler::error( Box::new(ContentHandler::error(
@ -132,7 +132,7 @@ impl<A: Authorization + 'static> server::Handler<HttpStream> for Router<A> {
"404 Not Found", "404 Not Found",
"Your homepage is not available when Trusted Signer is disabled.", "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."), 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<A: Authorization + 'static> server::Handler<HttpStream> for Router<A> {
impl<A: Authorization> Router<A> { impl<A: Authorization> Router<A> {
pub fn new( pub fn new(
control: Control, control: Control,
signer_port: Option<u16>, signer_address: Option<(String, u16)>,
content_fetcher: Arc<ContentFetcher>, content_fetcher: Arc<ContentFetcher>,
endpoints: Arc<Endpoints>, endpoints: Arc<Endpoints>,
special: Arc<HashMap<SpecialEndpoint, Box<Endpoint>>>, special: Arc<HashMap<SpecialEndpoint, Box<Endpoint>>>,
@ -181,7 +181,7 @@ impl<A: Authorization> Router<A> {
.to_handler(EndpointPath::default()); .to_handler(EndpointPath::default());
Router { Router {
control: Some(control), control: Some(control),
signer_port: signer_port, signer_address: signer_address,
endpoints: endpoints, endpoints: endpoints,
fetch: content_fetcher, fetch: content_fetcher,
special: special, special: special,

View File

@ -76,7 +76,7 @@ pub fn init_server(hosts: Option<Vec<String>>, is_syncing: bool) -> (Server, Arc
dapps_path.push("non-existent-dir-to-prevent-fs-files-from-loading"); 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()); let mut builder = ServerBuilder::new(dapps_path.to_str().unwrap().into(), registrar.clone());
builder.with_sync_status(Arc::new(move || is_syncing)); 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(), builder.start_unsecured_http(&"127.0.0.1:0".parse().unwrap(), hosts).unwrap(),
registrar, registrar,
@ -89,7 +89,7 @@ pub fn serve_with_auth(user: &str, pass: &str) -> Server {
let mut dapps_path = env::temp_dir(); let mut dapps_path = env::temp_dir();
dapps_path.push("non-existent-dir-to-prevent-fs-files-from-loading"); dapps_path.push("non-existent-dir-to-prevent-fs-files-from-loading");
let mut builder = ServerBuilder::new(dapps_path.to_str().unwrap().into(), registrar); 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() builder.start_basic_auth_http(&"127.0.0.1:0".parse().unwrap(), None, user, pass).unwrap()
} }

View File

@ -131,10 +131,10 @@
"0x807640a13483f8ac783c557fcdf27be11ea4ac7a" "0x807640a13483f8ac783c557fcdf27be11ea4ac7a"
], ],
"eip150Transition": "0x259518", "eip150Transition": "0x259518",
"eip155Transition": 2642462, "eip155Transition": "0x7fffffffffffffff",
"eip160Transition": 2642462, "eip160Transition": "0x7fffffffffffffff",
"eip161abcTransition": 2642462, "eip161abcTransition": "0x7fffffffffffffff",
"eip161dTransition": 2642462 "eip161dTransition": "0x7fffffffffffffff"
} }
} }
}, },
@ -176,7 +176,13 @@
"enode://a979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@52.16.188.185:30303", "enode://a979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@52.16.188.185:30303",
"enode://de471bccee3d042261d52e9bff31458daecc406142b401d4cd848f677479f73104b9fdeb090af9583d3391b7f10cb2ba9e26865dd5fca4fcdc0fb1e3b723c786@54.94.239.50:30303", "enode://de471bccee3d042261d52e9bff31458daecc406142b401d4cd848f677479f73104b9fdeb090af9583d3391b7f10cb2ba9e26865dd5fca4fcdc0fb1e3b723c786@54.94.239.50:30303",
"enode://1118980bf48b0a3640bdba04e0fe78b1add18e1cd99bf22d53daac1fd9972ad650df52176e7c7d89d1114cfef2bc23a2959aa54998a46afcf7d91809f0855082@52.74.57.123: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": { "accounts": {
"0000000000000000000000000000000000000001": { "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } }, "0000000000000000000000000000000000000001": { "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } },

@ -1 +1 @@
Subproject commit 853333e7da312775fb8f32f2c2771b8578cd0d79 Subproject commit 9028c4801fd39fbb71a9796979182549a24e81c8

View File

@ -95,6 +95,7 @@ impl KeyDirectory for NullDir {
struct AddressBook { struct AddressBook {
path: PathBuf, path: PathBuf,
cache: HashMap<Address, AccountMeta>, cache: HashMap<Address, AccountMeta>,
transient: bool,
} }
impl AddressBook { impl AddressBook {
@ -106,11 +107,18 @@ impl AddressBook {
let mut r = AddressBook { let mut r = AddressBook {
path: path, path: path,
cache: HashMap::new(), cache: HashMap::new(),
transient: false,
}; };
r.revert(); r.revert();
r r
} }
pub fn transient() -> Self {
let mut book = AddressBook::new(Default::default());
book.transient = true;
book
}
pub fn get(&self) -> HashMap<Address, AccountMeta> { pub fn get(&self) -> HashMap<Address, AccountMeta> {
self.cache.clone() self.cache.clone()
} }
@ -134,6 +142,7 @@ impl AddressBook {
} }
fn revert(&mut self) { fn revert(&mut self) {
if self.transient { return; }
trace!(target: "addressbook", "revert"); trace!(target: "addressbook", "revert");
let _ = fs::File::open(self.path.clone()) let _ = fs::File::open(self.path.clone())
.map_err(|e| trace!(target: "addressbook", "Couldn't open address book: {}", e)) .map_err(|e| trace!(target: "addressbook", "Couldn't open address book: {}", e))
@ -144,6 +153,7 @@ impl AddressBook {
} }
fn save(&mut self) { fn save(&mut self) {
if self.transient { return; }
trace!(target: "addressbook", "save"); trace!(target: "addressbook", "save");
let _ = fs::File::create(self.path.clone()) let _ = fs::File::create(self.path.clone())
.map_err(|e| warn!(target: "addressbook", "Couldn't open address book for writing: {}", e)) .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 { pub fn transient_provider() -> Self {
AccountProvider { AccountProvider {
unlocked: Mutex::new(HashMap::new()), 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())) sstore: Box::new(EthStore::open(Box::new(NullDir::default()))
.expect("NullDir load always succeeds; qed")) .expect("NullDir load always succeeds; qed"))
} }

1
js/.gitignore vendored
View File

@ -6,3 +6,4 @@ build
.dist .dist
.happypack .happypack
.npmjs .npmjs
.eslintcache

View File

@ -1,6 +1,6 @@
{ {
"name": "parity.js", "name": "parity.js",
"version": "0.2.24", "version": "0.2.28",
"main": "release/index.js", "main": "release/index.js",
"jsnext:main": "src/index.js", "jsnext:main": "src/index.js",
"author": "Parity Team <admin@parity.io>", "author": "Parity Team <admin@parity.io>",
@ -39,9 +39,11 @@
"clean": "rm -rf ./build ./coverage", "clean": "rm -rf ./build ./coverage",
"coveralls": "npm run testCoverage && coveralls < coverage/lcov.info", "coveralls": "npm run testCoverage && coveralls < coverage/lcov.info",
"lint": "eslint --ignore-path .gitignore ./src/", "lint": "eslint --ignore-path .gitignore ./src/",
"lint:cached": "eslint --cache --ignore-path .gitignore ./src/",
"test": "mocha 'src/**/*.spec.js'", "test": "mocha 'src/**/*.spec.js'",
"test:coverage": "istanbul cover _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": { "devDependencies": {
"babel-cli": "^6.10.1", "babel-cli": "^6.10.1",
@ -84,6 +86,7 @@
"happypack": "^2.2.1", "happypack": "^2.2.1",
"history": "^2.0.0", "history": "^2.0.0",
"html-loader": "^0.4.4", "html-loader": "^0.4.4",
"husky": "^0.11.9",
"ignore-styles": "2.0.0", "ignore-styles": "2.0.0",
"image-webpack-loader": "^1.8.0", "image-webpack-loader": "^1.8.0",
"istanbul": "^1.0.0-alpha.2", "istanbul": "^1.0.0-alpha.2",
@ -118,6 +121,7 @@
"bytes": "^2.4.0", "bytes": "^2.4.0",
"chart.js": "^2.3.0", "chart.js": "^2.3.0",
"es6-promise": "^3.2.1", "es6-promise": "^3.2.1",
"ethereumjs-tx": "^1.1.2",
"file-saver": "^1.3.3", "file-saver": "^1.3.3",
"format-json": "^1.0.3", "format-json": "^1.0.3",
"format-number": "^2.0.1", "format-number": "^2.0.1",
@ -148,6 +152,7 @@
"redux-actions": "^0.10.1", "redux-actions": "^0.10.1",
"redux-thunk": "^2.1.0", "redux-thunk": "^2.1.0",
"rlp": "^2.0.0", "rlp": "^2.0.0",
"scryptsy": "^2.0.0",
"store": "^1.3.20", "store": "^1.3.20",
"utf8": "^2.1.1", "utf8": "^2.1.1",
"validator": "^5.7.0", "validator": "^5.7.0",

View File

@ -63,7 +63,7 @@ if [ "$BRANCH" == "master" ]; then
echo "*** Publishing $PACKAGE to npmjs" echo "*** Publishing $PACKAGE to npmjs"
cd .npmjs cd .npmjs
npm publish --access public npm publish --access public || true
cd ../.. cd ../..
fi fi

View File

@ -136,27 +136,30 @@ export default class Contract {
} }
parseEventLogs (logs) { parseEventLogs (logs) {
return logs.map((log) => { return logs
const signature = log.topics[0].substr(2); .map((log) => {
const event = this.events.find((evt) => evt.signature === signature); const signature = log.topics[0].substr(2);
const event = this.events.find((evt) => evt.signature === signature);
if (!event) { if (!event) {
throw new Error(`Unable to find event matching signature ${signature}`); 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.params = {};
log.event = event.name; log.event = event.name;
decoded.params.forEach((param) => { decoded.params.forEach((param) => {
const { type, value } = param.token; 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) { parseTransactionEvents (receipt) {

View File

@ -119,19 +119,6 @@ describe('api/contract/Contract', () => {
}); });
describe('parseTransactionEvents', () => { 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', () => { it('parses a transaction log into the data', () => {
const contract = new Contract(eth, [ const contract = new Contract(eth, [
{ {

View File

@ -93,6 +93,10 @@ export function inFilter (options) {
} }
export function inHex (str) { export function inHex (str) {
if (str && str.toString) {
str = str.toString(16);
}
if (str && str.substr(0, 2) === '0x') { if (str && str.substr(0, 2) === '0x') {
return str.toLowerCase(); return str.toLowerCase();
} }

View File

@ -60,6 +60,11 @@ export default class Parity {
.then(outNumber); .then(outNumber);
} }
dappsInterface () {
return this._transport
.execute('parity_dappsInterface');
}
defaultExtraData () { defaultExtraData () {
return this._transport return this._transport
.execute('parity_defaultExtraData'); .execute('parity_defaultExtraData');
@ -176,6 +181,12 @@ export default class Parity {
.then(outAddress); .then(outAddress);
} }
nextNonce (account) {
return this._transport
.execute('parity_nextNonce', inAddress(account))
.then(outNumber);
}
nodeName () { nodeName () {
return this._transport return this._transport
.execute('parity_nodeName'); .execute('parity_nodeName');

View File

@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { inNumber16 } from '../../format/input'; import { inNumber16, inData } from '../../format/input';
import { outSignerRequest } from '../../format/output'; import { outSignerRequest } from '../../format/output';
export default class Signer { export default class Signer {
@ -27,6 +27,11 @@ export default class Signer {
.execute('signer_confirmRequest', inNumber16(requestId), options, password); .execute('signer_confirmRequest', inNumber16(requestId), options, password);
} }
confirmRequestRaw (requestId, data) {
return this._transport
.execute('signer_confirmRequestRaw', inNumber16(requestId), inData(data));
}
generateAuthorizationToken () { generateAuthorizationToken () {
return this._transport return this._transport
.execute('signer_generateAuthorizationToken'); .execute('signer_generateAuthorizationToken');

View File

@ -24,6 +24,7 @@ import owned from './owned.json';
import registry from './registry.json'; import registry from './registry.json';
import signaturereg from './signaturereg.json'; import signaturereg from './signaturereg.json';
import tokenreg from './tokenreg.json'; import tokenreg from './tokenreg.json';
import wallet from './wallet.json';
export { export {
basiccoin, basiccoin,
@ -35,5 +36,6 @@ export {
owned, owned,
registry, registry,
signaturereg, signaturereg,
tokenreg tokenreg,
wallet
}; };

View File

@ -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"}]

View File

@ -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: { defaultExtraData: {
desc: 'Returns the default extra data', desc: 'Returns the default extra data',
params: [], params: [],
@ -347,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: { nodeName: {
desc: 'Returns node name (identity)', desc: 'Returns node name (identity)',
params: [], params: [],

View File

@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { Quantity } from '../types'; import { Quantity, Data } from '../types';
export default { export default {
generateAuthorizationToken: { 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: { rejectRequest: {
desc: 'Rejects a request in the signer queue', desc: 'Rejects a request in the signer queue',
params: [ params: [

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
.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;
}
}

View File

@ -17,10 +17,38 @@
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import ContentAdd from 'material-ui/svg-icons/content/add'; import ContentAdd from 'material-ui/svg-icons/content/add';
import ContentClear from 'material-ui/svg-icons/content/clear'; 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 { Button, Modal, Form, Input, InputAddress } from '../../ui';
import { ERRORS, validateAbi, validateAddress, validateName } from '../../util/validation'; 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: (<span>A standard <a href='https://github.com/ethereum/EIPs/issues/20' target='_blank'>ERC 20</a> token</span>)
},
{
label: 'Multisig Wallet', readOnly: true,
type: 'multisig',
value: JSON.stringify(wallet),
description: (<span>Official Multisig contract: <a href='https://github.com/ethereum/dapp-bin/blob/master/wallet/wallet.sol' target='_blank'>see contract code</a></span>)
},
{
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 { export default class AddContract extends Component {
static contextTypes = { static contextTypes = {
api: PropTypes.object.isRequired api: PropTypes.object.isRequired
@ -34,44 +62,101 @@ export default class AddContract extends Component {
state = { state = {
abi: '', abi: '',
abiError: ERRORS.invalidAbi, abiError: ERRORS.invalidAbi,
abiType: ABI_TYPES[2],
abiTypeIndex: 2,
abiParsed: null, abiParsed: null,
address: '', address: '',
addressError: ERRORS.invalidAddress, addressError: ERRORS.invalidAddress,
name: '', name: '',
nameError: ERRORS.invalidName, nameError: ERRORS.invalidName,
description: '' description: '',
step: 0
}; };
componentDidMount () {
this.onChangeABIType(null, this.state.abiTypeIndex);
}
render () { render () {
const { step } = this.state;
return ( return (
<Modal <Modal
visible visible
actions={ this.renderDialogActions() } actions={ this.renderDialogActions() }
title='watch contract'> steps={ STEPS }
{ this.renderFields() } current={ step }
>
{ this.renderStep(step) }
</Modal> </Modal>
); );
} }
renderStep (step) {
switch (step) {
case 0:
return this.renderContractTypeSelector();
default:
return this.renderFields();
}
}
renderContractTypeSelector () {
const { abiTypeIndex } = this.state;
return (
<RadioButtonGroup
valueSelected={ abiTypeIndex }
name='contractType'
onChange={ this.onChangeABIType }
>
{ this.renderAbiTypes() }
</RadioButtonGroup>
);
}
renderDialogActions () { renderDialogActions () {
const { addressError, nameError } = this.state; const { addressError, nameError, step } = this.state;
const hasError = !!(addressError || nameError); const hasError = !!(addressError || nameError);
return ([ const cancelBtn = (
<Button <Button
icon={ <ContentClear /> } icon={ <ContentClear /> }
label='Cancel' label='Cancel'
onClick={ this.onClose } />, onClick={ this.onClose } />
);
if (step === 0) {
const nextBtn = (
<Button
icon={ <NavigationArrowForward /> }
label='Next'
onClick={ this.onNext } />
);
return [ cancelBtn, nextBtn ];
}
const prevBtn = (
<Button
icon={ <NavigationArrowBack /> }
label='Back'
onClick={ this.onPrev } />
);
const addBtn = (
<Button <Button
icon={ <ContentAdd /> } icon={ <ContentAdd /> }
label='Add Contract' label='Add Contract'
disabled={ hasError } disabled={ hasError }
onClick={ this.onAdd } /> onClick={ this.onAdd } />
]); );
return [ cancelBtn, prevBtn, addBtn ];
} }
renderFields () { renderFields () {
const { abi, abiError, address, addressError, description, name, nameError } = this.state; const { abi, abiError, address, addressError, description, name, nameError, abiType } = this.state;
return ( return (
<Form> <Form>
@ -80,7 +165,9 @@ export default class AddContract extends Component {
hint='the network address for the contract' hint='the network address for the contract'
error={ addressError } error={ addressError }
value={ address } value={ address }
onSubmit={ this.onEditAddress } /> onSubmit={ this.onEditAddress }
onChange={ this.onChangeAddress }
/>
<Input <Input
label='contract name' label='contract name'
hint='a descriptive name for the contract' hint='a descriptive name for the contract'
@ -94,20 +181,57 @@ export default class AddContract extends Component {
hint='an expanded description for the entry' hint='an expanded description for the entry'
value={ description } value={ description }
onSubmit={ this.onEditDescription } /> onSubmit={ this.onEditDescription } />
<Input <Input
label='contract abi' label='contract abi'
hint='the abi for the contract' hint='the abi for the contract'
error={ abiError } error={ abiError }
value={ abi } value={ abi }
onSubmit={ this.onEditAbi } /> readOnly={ abiType.readOnly }
onSubmit={ this.onEditAbi }
/>
</Form> </Form>
); );
} }
onEditAbi = (abi) => { renderAbiTypes () {
const { api } = this.context; return ABI_TYPES.map((type, index) => (
<RadioButton
className={ styles.spaced }
value={ index }
label={ (
<div className={ styles.typeContainer }>
<span>{ type.label }</span>
<span className={ styles.desc }>{ type.description }</span>
</div>
) }
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) => { onEditAddress = (_address) => {
@ -138,7 +262,7 @@ export default class AddContract extends Component {
onAdd = () => { onAdd = () => {
const { api } = this.context; const { api } = this.context;
const { abiParsed, address, name, description } = this.state; const { abiParsed, address, name, description, abiType } = this.state;
Promise.all([ Promise.all([
api.parity.setAccountName(address, name), api.parity.setAccountName(address, name),
@ -147,6 +271,7 @@ export default class AddContract extends Component {
deleted: false, deleted: false,
timestamp: Date.now(), timestamp: Date.now(),
abi: abiParsed, abi: abiParsed,
type: abiType.type,
description description
}) })
]).catch((error) => { ]).catch((error) => {

View File

@ -16,6 +16,9 @@
import * as actions from './signerActions'; import * as actions from './signerActions';
import { inHex } from '../../api/format/input';
import { Wallet } from '../../util/wallet';
export default class SignerMiddleware { export default class SignerMiddleware {
constructor (api) { constructor (api) {
this._api = api; this._api = api;
@ -49,23 +52,58 @@ export default class SignerMiddleware {
} }
onConfirmStart = (store, action) => { onConfirmStart = (store, action) => {
const { id, password } = action.payload; const { id, password, wallet, payload } = action.payload;
this._api.signer const handlePromise = promise => {
.confirmRequest(id, {}, password) promise
.then((txHash) => { .then((txHash) => {
console.log('confirmRequest', id, txHash); console.log('confirmRequest', id, txHash);
if (!txHash) { if (!txHash) {
store.dispatch(actions.errorConfirmRequest({ id, err: 'Unable to confirm.' })); store.dispatch(actions.errorConfirmRequest({ id, err: 'Unable to confirm.' }));
return; 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) => { onRejectStart = (store, action) => {

View File

@ -38,10 +38,22 @@ export function validateAbi (abi, api) {
abiParsed = JSON.parse(abi); abiParsed = JSON.parse(abi);
if (!api.util.isArray(abiParsed) || !abiParsed.length) { if (!api.util.isArray(abiParsed) || !abiParsed.length) {
abiError = ERRORS.inavlidAbi; abiError = ERRORS.invalidAbi;
} else { return { abi, abiError, abiParsed };
abi = JSON.stringify(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) { } catch (error) {
abiError = ERRORS.invalidAbi; 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) { export function validateAddress (address) {
let addressError = null; let addressError = null;

82
js/src/util/wallet.js Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
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'));
}
}

View File

@ -197,24 +197,15 @@ export default class DappsStore {
}); });
} }
_fetchManifest (manifestHash, count = 0) { _fetchManifest (manifestHash) {
return fetch(`${this._getHost()}/api/content/${manifestHash}/`) return fetch(`${this._getHost()}/api/content/${manifestHash}/`, { redirect: 'follow', mode: 'cors' })
.then((response) => { .then((response) => {
if (response.ok) { return response.ok
return response.json(); ? response.json()
} : null;
if (count < 1) {
return this._fetchManifest(manifestHash, count + 1);
}
return null;
}) })
.catch(() => { .catch((error) => {
if (count < 1) { console.warn('DappsStore:fetchManifest', error);
return this._fetchManifest(manifestHash, count + 1);
}
return null; return null;
}); });
} }

View File

@ -33,15 +33,22 @@ export default class RequestPendingWeb3 extends Component {
className: PropTypes.string className: PropTypes.string
}; };
onConfirm = data => {
const { onConfirm, payload } = this.props;
data.payload = payload;
onConfirm(data);
};
render () { render () {
const { payload, id, className, isSending, date, onConfirm, onReject } = this.props; const { payload, id, className, isSending, date, onReject } = this.props;
if (payload.sign) { if (payload.sign) {
const { sign } = payload; const { sign } = payload;
return ( return (
<SignRequest <SignRequest
className={ className } className={ className }
onConfirm={ onConfirm } onConfirm={ this.onConfirm }
onReject={ onReject } onReject={ onReject }
isSending={ isSending } isSending={ isSending }
isFinished={ false } isFinished={ false }
@ -57,7 +64,7 @@ export default class RequestPendingWeb3 extends Component {
return ( return (
<TransactionPending <TransactionPending
className={ className } className={ className }
onConfirm={ onConfirm } onConfirm={ this.onConfirm }
onReject={ onReject } onReject={ onReject }
isSending={ isSending } isSending={ isSending }
id={ id } id={ id }

View File

@ -32,7 +32,7 @@
.mainContainer > * { .mainContainer > * {
vertical-align: middle; vertical-align: middle;
min-height: 120px; min-height: 190px;
} }
.inputs { .inputs {

View File

@ -116,9 +116,11 @@ export default class TransactionPending extends Component {
); );
} }
onConfirm = password => { onConfirm = data => {
const { id, gasPrice } = this.props; const { id, gasPrice } = this.props;
this.props.onConfirm({ id, password, gasPrice }); const { password, wallet } = data;
this.props.onConfirm({ id, password, wallet, gasPrice });
} }
onReject = () => { onReject = () => {

View File

@ -40,3 +40,7 @@
.passwordHint span { .passwordHint span {
opacity: 0.85; opacity: 0.85;
} }
.fileInput input {
top: 22px;
}

View File

@ -35,26 +35,33 @@ class TransactionPendingFormConfirm extends Component {
id = Math.random(); // for tooltip id = Math.random(); // for tooltip
state = { state = {
walletError: null,
wallet: null,
password: '' password: ''
} }
render () { render () {
const { accounts, address, isSending } = this.props; const { accounts, address, isSending } = this.props;
const { password } = this.state; const { password, walletError, wallet } = this.state;
const account = accounts[address] || {}; const account = accounts[address] || {};
const isExternal = !account.uuid;
const passwordHint = account.meta && account.meta.passwordHint const passwordHint = account.meta && account.meta.passwordHint
? (<div><span>(hint) </span>{ account.meta.passwordHint }</div>) ? (<div><span>(hint) </span>{ account.meta.passwordHint }</div>)
: null; : null;
const isWalletOk = !isExternal || (walletError === null && wallet !== null);
const keyInput = isExternal ? this.renderKeyInput() : null;
return ( return (
<div className={ styles.confirmForm }> <div className={ styles.confirmForm }>
<Form> <Form>
{ keyInput }
<Input <Input
onChange={ this.onModifyPassword } onChange={ this.onModifyPassword }
onKeyDown={ this.onKeyDown } onKeyDown={ this.onKeyDown }
label='Account Password' label={ isExternal ? 'Key Password' : 'Account Password' }
hint='unlock the account' hint={ isExternal ? 'decrypt the key' : 'unlock the account' }
type='password' type='password'
value={ password } /> value={ password } />
<div className={ styles.passwordHint }> <div className={ styles.passwordHint }>
@ -71,7 +78,7 @@ class TransactionPendingFormConfirm extends Component {
className={ styles.confirmButton } className={ styles.confirmButton }
fullWidth fullWidth
primary primary
disabled={ isSending } disabled={ isSending || !isWalletOk }
icon={ <IdentityIcon address={ address } button className={ styles.signerIcon } /> } icon={ <IdentityIcon address={ address } button className={ styles.signerIcon } /> }
label={ isSending ? 'Confirming...' : 'Confirm Transaction' } label={ isSending ? 'Confirming...' : 'Confirm Transaction' }
/> />
@ -82,6 +89,20 @@ class TransactionPendingFormConfirm extends Component {
); );
} }
renderKeyInput () {
const { walletError } = this.state;
return (
<Input
className={ styles.fileInput }
onChange={ this.onKeySelect }
error={ walletError }
label='Select Local Key'
type='file'
/>
);
}
renderTooltip () { renderTooltip () {
if (this.state.password.length) { if (this.state.password.length) {
return; 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 => { onModifyPassword = evt => {
const password = evt.target.value; const password = evt.target.value;
this.setState({ this.setState({
@ -102,8 +143,11 @@ class TransactionPendingFormConfirm extends Component {
} }
onConfirm = () => { onConfirm = () => {
const { password } = this.state; const { password, wallet } = this.state;
this.props.onConfirm(password);
this.props.onConfirm({
password, wallet
});
} }
onKeyDown = evt => { onKeyDown = evt => {

View File

@ -198,7 +198,8 @@ module.exports = {
proxy: { proxy: {
'/api/*': { '/api/*': {
target: 'http://127.0.0.1:8080', target: 'http://127.0.0.1:8080',
changeOrigin: true changeOrigin: true,
autoRewrite: true
}, },
'/app/*': { '/app/*': {
target: 'http://127.0.0.1:8080', target: 'http://127.0.0.1:8080',

View File

@ -22,6 +22,7 @@ const DEST = process.env.BUILD_DEST || '.build';
let modules = [ let modules = [
'babel-polyfill', 'babel-polyfill',
'browserify-aes', 'ethereumjs-tx', 'scryptsy',
'react', 'react-dom', 'react-redux', 'react-router', 'react', 'react-dom', 'react-redux', 'react-router',
'redux', 'redux-thunk', 'react-router-redux', 'redux', 'redux-thunk', 'react-router-redux',
'lodash', 'material-ui', 'moment', 'blockies' 'lodash', 'material-ui', 'moment', 'blockies'

View File

@ -12,7 +12,7 @@
!define VERSIONMINOR 5 !define VERSIONMINOR 5
!define VERSIONBUILD 0 !define VERSIONBUILD 0
!define ARGS "--warp" !define ARGS "--warp"
!define FIRST_START_ARGS "--warp --mode=passive" !define FIRST_START_ARGS "ui --warp --mode=passive"
!addplugindir .\ !addplugindir .\
@ -160,6 +160,9 @@ section "uninstall"
!insertmacro TerminateApp !insertmacro TerminateApp
# Remove Start Menu launcher # Remove Start Menu launcher
delete "$SMPROGRAMS\${COMPANYNAME}\${APPNAME}.lnk" 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 # Try to remove the Start Menu folder - this will only happen if it is empty
rmDir "$SMPROGRAMS\${COMPANYNAME}" rmDir "$SMPROGRAMS\${COMPANYNAME}"

View File

@ -95,7 +95,7 @@ impl Configuration {
let wal = !self.args.flag_fast_and_loose; let wal = !self.args.flag_fast_and_loose;
let warp_sync = self.args.flag_warp; let warp_sync = self.args.flag_warp;
let geth_compatibility = self.args.flag_geth; 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 dapps_conf = self.dapps_config();
let signer_conf = self.signer_config(); let signer_conf = self.signer_config();
let format = try!(self.format()); let format = try!(self.format());
@ -243,7 +243,7 @@ impl Configuration {
vm_type: vm_type, vm_type: vm_type,
warp_sync: warp_sync, warp_sync: warp_sync,
geth_compatibility: geth_compatibility, geth_compatibility: geth_compatibility,
ui_port: ui_port, ui_address: ui_address,
net_settings: self.network_settings(), net_settings: self.network_settings(),
dapps_conf: dapps_conf, dapps_conf: dapps_conf,
signer_conf: signer_conf, signer_conf: signer_conf,
@ -859,7 +859,7 @@ mod tests {
wal: true, wal: true,
vm_type: Default::default(), vm_type: Default::default(),
geth_compatibility: false, geth_compatibility: false,
ui_port: Some(8180), ui_address: Some(("127.0.0.1".into(), 8180)),
net_settings: Default::default(), net_settings: Default::default(),
dapps_conf: Default::default(), dapps_conf: Default::default(),
signer_conf: Default::default(), signer_conf: Default::default(),

View File

@ -58,7 +58,7 @@ pub fn new(configuration: Configuration, deps: Dependencies) -> Result<Option<We
return Ok(None); return Ok(None);
} }
let signer_port = deps.apis.signer_service.port(); let signer_address = deps.apis.signer_service.address();
let url = format!("{}:{}", configuration.interface, configuration.port); let url = format!("{}:{}", configuration.interface, configuration.port);
let addr = try!(url.parse().map_err(|_| format!("Invalid Webapps listen host/port given: {}", url))); let addr = try!(url.parse().map_err(|_| format!("Invalid Webapps listen host/port given: {}", url)));
@ -73,7 +73,7 @@ pub fn new(configuration: Configuration, deps: Dependencies) -> Result<Option<We
(username.to_owned(), password) (username.to_owned(), password)
}); });
Ok(Some(try!(setup_dapps_server(deps, configuration.dapps_path, &addr, configuration.hosts, auth, signer_port)))) Ok(Some(try!(setup_dapps_server(deps, configuration.dapps_path, &addr, configuration.hosts, auth, signer_address))))
} }
pub use self::server::WebappServer; pub use self::server::WebappServer;
@ -91,7 +91,7 @@ mod server {
_url: &SocketAddr, _url: &SocketAddr,
_allowed_hosts: Option<Vec<String>>, _allowed_hosts: Option<Vec<String>>,
_auth: Option<(String, String)>, _auth: Option<(String, String)>,
_signer_port: Option<u16>, _signer_address: Option<(String, u16)>,
) -> Result<WebappServer, String> { ) -> Result<WebappServer, String> {
Err("Your Parity version has been compiled without WebApps support.".into()) Err("Your Parity version has been compiled without WebApps support.".into())
} }
@ -120,7 +120,7 @@ mod server {
url: &SocketAddr, url: &SocketAddr,
allowed_hosts: Option<Vec<String>>, allowed_hosts: Option<Vec<String>>,
auth: Option<(String, String)>, auth: Option<(String, String)>,
signer_port: Option<u16>, signer_address: Option<(String, u16)>,
) -> Result<WebappServer, String> { ) -> Result<WebappServer, String> {
use ethcore_dapps as dapps; use ethcore_dapps as dapps;
@ -131,7 +131,7 @@ mod server {
let sync = deps.sync.clone(); let sync = deps.sync.clone();
let client = deps.client.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_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 server = rpc_apis::setup_rpc(server, deps.apis.clone(), rpc_apis::ApiSet::UnsafeContext);
let start_result = match auth { let start_result = match auth {

View File

@ -119,6 +119,7 @@ pub struct Dependencies {
pub settings: Arc<NetworkSettings>, pub settings: Arc<NetworkSettings>,
pub net_service: Arc<ManageNetwork>, pub net_service: Arc<ManageNetwork>,
pub geth_compatibility: bool, pub geth_compatibility: bool,
pub dapps_interface: Option<String>,
pub dapps_port: Option<u16>, pub dapps_port: Option<u16>,
} }
@ -228,6 +229,7 @@ pub fn setup_rpc<T: Extendable>(server: T, deps: Arc<Dependencies>, apis: ApiSet
deps.logger.clone(), deps.logger.clone(),
deps.settings.clone(), deps.settings.clone(),
signer, signer,
deps.dapps_interface.clone(),
deps.dapps_port, deps.dapps_port,
).to_delegate()); ).to_delegate());

View File

@ -82,7 +82,7 @@ pub struct RunCmd {
pub wal: bool, pub wal: bool,
pub vm_type: VMType, pub vm_type: VMType,
pub geth_compatibility: bool, pub geth_compatibility: bool,
pub ui_port: Option<u16>, pub ui_address: Option<(String, u16)>,
pub net_settings: NetworkSettings, pub net_settings: NetworkSettings,
pub dapps_conf: dapps::Configuration, pub dapps_conf: dapps::Configuration,
pub signer_conf: signer::Configuration, pub signer_conf: signer::Configuration,
@ -262,7 +262,7 @@ pub fn execute(cmd: RunCmd, logger: Arc<RotatingLogger>) -> Result<(), String> {
let deps_for_rpc_apis = Arc::new(rpc_apis::Dependencies { let deps_for_rpc_apis = Arc::new(rpc_apis::Dependencies {
signer_service: Arc::new(rpc_apis::SignerService::new(move || { signer_service: Arc::new(rpc_apis::SignerService::new(move || {
signer::generate_new_token(signer_path.clone()).map_err(|e| format!("{:?}", e)) signer::generate_new_token(signer_path.clone()).map_err(|e| format!("{:?}", e))
}, cmd.ui_port)), }, cmd.ui_address)),
snapshot: snapshot_service.clone(), snapshot: snapshot_service.clone(),
client: client.clone(), client: client.clone(),
sync: sync_provider.clone(), sync: sync_provider.clone(),
@ -274,6 +274,10 @@ pub fn execute(cmd: RunCmd, logger: Arc<RotatingLogger>) -> Result<(), String> {
settings: Arc::new(cmd.net_settings.clone()), settings: Arc::new(cmd.net_settings.clone()),
net_service: manage_network.clone(), net_service: manage_network.clone(),
geth_compatibility: cmd.geth_compatibility, 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 { dapps_port: match cmd.dapps_conf.enabled {
true => Some(cmd.dapps_conf.port), true => Some(cmd.dapps_conf.port),
false => None, false => None,

View File

@ -17,7 +17,7 @@
//! RPC Error codes and error objects //! RPC Error codes and error objects
macro_rules! rpc_unimplemented { macro_rules! rpc_unimplemented {
() => (Err(::v1::helpers::errors::unimplemented())) () => (Err(::v1::helpers::errors::unimplemented(None)))
} }
use std::fmt; use std::fmt;
@ -51,11 +51,11 @@ mod codes {
pub const FETCH_ERROR: i64 = -32060; pub const FETCH_ERROR: i64 = -32060;
} }
pub fn unimplemented() -> Error { pub fn unimplemented(details: Option<String>) -> Error {
Error { Error {
code: ErrorCode::ServerError(codes::UNSUPPORTED_REQUEST), code: ErrorCode::ServerError(codes::UNSUPPORTED_REQUEST),
message: "This request is not implemented yet. Please create an issue on Github repo.".into(), message: "This request is not implemented yet. Please create an issue on Github repo.".into(),
data: None data: details.map(Value::String),
} }
} }

View File

@ -22,18 +22,18 @@ use v1::helpers::signing_queue::{ConfirmationsQueue};
pub struct SignerService { pub struct SignerService {
queue: Arc<ConfirmationsQueue>, queue: Arc<ConfirmationsQueue>,
generate_new_token: Box<Fn() -> Result<String, String> + Send + Sync + 'static>, generate_new_token: Box<Fn() -> Result<String, String> + Send + Sync + 'static>,
port: Option<u16>, address: Option<(String, u16)>,
} }
impl SignerService { impl SignerService {
/// Creates new Signer Service given function to generate new tokens. /// Creates new Signer Service given function to generate new tokens.
pub fn new<F>(new_token: F, port: Option<u16>) -> Self pub fn new<F>(new_token: F, address: Option<(String, u16)>) -> Self
where F: Fn() -> Result<String, String> + Send + Sync + 'static { where F: Fn() -> Result<String, String> + Send + Sync + 'static {
SignerService { SignerService {
queue: Arc::new(ConfirmationsQueue::default()), queue: Arc::new(ConfirmationsQueue::default()),
generate_new_token: Box::new(new_token), generate_new_token: Box::new(new_token),
port: port, address: address,
} }
} }
@ -47,20 +47,20 @@ impl SignerService {
self.queue.clone() self.queue.clone()
} }
/// Returns signer port (if signer enabled) or `None` otherwise /// Returns signer address (if signer enabled) or `None` otherwise
pub fn port(&self) -> Option<u16> { pub fn address(&self) -> Option<(String, u16)> {
self.port self.address.clone()
} }
/// Returns true if Signer is enabled. /// Returns true if Signer is enabled.
pub fn is_enabled(&self) -> bool { pub fn is_enabled(&self) -> bool {
self.port.is_some() self.address.is_some()
} }
#[cfg(test)] #[cfg(test)]
/// Creates new Signer Service for tests. /// Creates new Signer Service for tests.
pub fn new_test(port: Option<u16>) -> Self { pub fn new_test(address: Option<(String, u16)>) -> Self {
SignerService::new(|| Ok("new_token".into()), port) SignerService::new(|| Ok("new_token".into()), address)
} }
} }

View File

@ -20,6 +20,7 @@ extern crate ethash;
use std::io::{Write}; use std::io::{Write};
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};
use std::collections::BTreeSet;
use std::thread; use std::thread;
use std::time::{Instant, Duration}; use std::time::{Instant, Duration};
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
@ -339,7 +340,10 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> Eth for EthClient<C, SN, S, M, EM> where
let store = take_weak!(self.accounts); let store = take_weak!(self.accounts);
let accounts = try!(store.accounts().map_err(|e| errors::internal("Could not fetch accounts.", e))); 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<Address> = accounts.into_iter().chain(addresses.keys().cloned()).collect();
Ok(set.into_iter().map(Into::into).collect())
} }
fn block_number(&self) -> Result<RpcU256, Error> { fn block_number(&self) -> Result<RpcU256, Error> {

View File

@ -52,6 +52,7 @@ pub struct ParityClient<C, M, S: ?Sized> where
logger: Arc<RotatingLogger>, logger: Arc<RotatingLogger>,
settings: Arc<NetworkSettings>, settings: Arc<NetworkSettings>,
signer: Option<Arc<SignerService>>, signer: Option<Arc<SignerService>>,
dapps_interface: Option<String>,
dapps_port: Option<u16>, dapps_port: Option<u16>,
} }
@ -70,6 +71,7 @@ impl<C, M, S: ?Sized> ParityClient<C, M, S> where
logger: Arc<RotatingLogger>, logger: Arc<RotatingLogger>,
settings: Arc<NetworkSettings>, settings: Arc<NetworkSettings>,
signer: Option<Arc<SignerService>>, signer: Option<Arc<SignerService>>,
dapps_interface: Option<String>,
dapps_port: Option<u16>, dapps_port: Option<u16>,
) -> Self { ) -> Self {
ParityClient { ParityClient {
@ -81,6 +83,7 @@ impl<C, M, S: ?Sized> ParityClient<C, M, S> where
logger: logger, logger: logger,
settings: settings, settings: settings,
signer: signer, signer: signer,
dapps_interface: dapps_interface,
dapps_port: dapps_port, dapps_port: dapps_port,
} }
} }
@ -261,7 +264,8 @@ impl<C, M, S: ?Sized> Parity for ParityClient<C, M, S> where
self.signer self.signer
.clone() .clone()
.and_then(|signer| signer.port()) .and_then(|signer| signer.address())
.map(|address| address.1)
.ok_or_else(|| errors::signer_disabled()) .ok_or_else(|| errors::signer_disabled())
} }
@ -272,6 +276,13 @@ impl<C, M, S: ?Sized> Parity for ParityClient<C, M, S> where
.ok_or_else(|| errors::dapps_disabled()) .ok_or_else(|| errors::dapps_disabled())
} }
fn dapps_interface(&self) -> Result<String, Error> {
try!(self.active());
self.dapps_interface.clone()
.ok_or_else(|| errors::dapps_disabled())
}
fn next_nonce(&self, address: H160) -> Result<U256, Error> { fn next_nonce(&self, address: H160) -> Result<U256, Error> {
try!(self.active()); try!(self.active());
let address: Address = address.into(); let address: Address = address.into();

View File

@ -18,14 +18,17 @@
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
use jsonrpc_core::*; use rlp::{UntrustedRlp, View};
use ethcore::account_provider::AccountProvider; use ethcore::account_provider::AccountProvider;
use ethcore::client::MiningBlockChainClient; use ethcore::client::MiningBlockChainClient;
use ethcore::transaction::SignedTransaction;
use ethcore::miner::MinerService; use ethcore::miner::MinerService;
use jsonrpc_core::Error;
use v1::traits::Signer; 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::{errors, SignerService, SigningQueue, ConfirmationPayload};
use v1::helpers::dispatch; use v1::helpers::dispatch::{self, dispatch_transaction};
/// Transactions confirmation (personal) rpc implementation. /// Transactions confirmation (personal) rpc implementation.
pub struct SignerClient<C, M> where C: MiningBlockChainClient, M: MinerService { pub struct SignerClient<C, M> where C: MiningBlockChainClient, M: MinerService {
@ -66,9 +69,9 @@ impl<C: 'static, M: 'static> Signer for SignerClient<C, M> where C: MiningBlockC
let signer = take_weak!(self.signer); let signer = take_weak!(self.signer);
Ok(signer.requests() Ok(signer.requests()
.into_iter() .into_iter()
.map(Into::into) .map(Into::into)
.collect() .collect()
) )
} }
@ -101,6 +104,60 @@ impl<C: 'static, M: 'static> Signer for SignerClient<C, M> where C: MiningBlockC
}).unwrap_or_else(|| Err(errors::invalid_params("Unknown RequestID", id))) }).unwrap_or_else(|| Err(errors::invalid_params("Unknown RequestID", id)))
} }
fn confirm_request_raw(&self, id: U256, bytes: Bytes) -> Result<ConfirmationResponse, Error> {
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<bool, Error> { fn reject_request(&self, id: U256) -> Result<bool, Error> {
try!(self.active()); try!(self.active());
let signer = take_weak!(self.signer); let signer = take_weak!(self.signer);

View File

@ -353,9 +353,16 @@ fn rpc_eth_gas_price() {
fn rpc_eth_accounts() { fn rpc_eth_accounts() {
let tester = EthTester::default(); let tester = EthTester::default();
let address = tester.accounts_provider.new_account("").unwrap(); 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 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())); assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned()));
} }

View File

@ -38,6 +38,7 @@ pub struct Dependencies {
pub settings: Arc<NetworkSettings>, pub settings: Arc<NetworkSettings>,
pub network: Arc<ManageNetwork>, pub network: Arc<ManageNetwork>,
pub accounts: Arc<AccountProvider>, pub accounts: Arc<AccountProvider>,
pub dapps_interface: Option<String>,
pub dapps_port: Option<u16>, pub dapps_port: Option<u16>,
} }
@ -61,6 +62,7 @@ impl Dependencies {
}), }),
network: Arc::new(TestManageNetwork), network: Arc::new(TestManageNetwork),
accounts: Arc::new(AccountProvider::transient_provider()), accounts: Arc::new(AccountProvider::transient_provider()),
dapps_interface: Some("127.0.0.1".into()),
dapps_port: Some(18080), dapps_port: Some(18080),
} }
} }
@ -75,6 +77,7 @@ impl Dependencies {
self.logger.clone(), self.logger.clone(),
self.settings.clone(), self.settings.clone(),
signer, signer,
self.dapps_interface.clone(),
self.dapps_port, self.dapps_port,
) )
} }
@ -238,7 +241,7 @@ fn rpc_parity_node_name() {
#[test] #[test]
fn rpc_parity_unsigned_transactions_count() { fn rpc_parity_unsigned_transactions_count() {
let deps = Dependencies::new(); 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 request = r#"{"jsonrpc": "2.0", "method": "parity_unsignedTransactionsCount", "params":[], "id": 1}"#;
let response = r#"{"jsonrpc":"2.0","result":0,"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() { fn rpc_parity_signer_port() {
// given // given
let deps = Dependencies::new(); 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(); let io2 = deps.default_client();
// when // when
@ -313,6 +316,24 @@ fn rpc_parity_dapps_port() {
assert_eq!(io2.handle_request_sync(request), Some(response2.to_owned())); 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] #[test]
fn rpc_parity_next_nonce() { fn rpc_parity_next_nonce() {
let deps = Dependencies::new(); let deps = Dependencies::new();

View File

@ -16,11 +16,14 @@
use std::sync::Arc; use std::sync::Arc;
use std::str::FromStr; use std::str::FromStr;
use jsonrpc_core::IoHandler; use util::{U256, Uint, Address, ToPretty};
use util::{U256, Uint, Address};
use ethcore::account_provider::AccountProvider; use ethcore::account_provider::AccountProvider;
use ethcore::client::TestBlockChainClient; use ethcore::client::TestBlockChainClient;
use ethcore::transaction::{Transaction, Action}; use ethcore::transaction::{Transaction, Action};
use rlp::encode;
use jsonrpc_core::IoHandler;
use v1::{SignerClient, Signer}; use v1::{SignerClient, Signer};
use v1::tests::helpers::TestMinerService; use v1::tests::helpers::TestMinerService;
use v1::helpers::{SigningQueue, SignerService, FilledTransactionRequest, ConfirmationPayload}; 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); 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] #[test]
fn should_generate_new_token() { fn should_generate_new_token() {
// given // given

View File

@ -123,6 +123,10 @@ build_rpc_trait! {
#[rpc(name = "parity_dappsPort")] #[rpc(name = "parity_dappsPort")]
fn dapps_port(&self) -> Result<u16, Error>; fn dapps_port(&self) -> Result<u16, Error>;
/// Returns current Dapps Server interface address or an error if dapps server is disabled.
#[rpc(name = "parity_dappsInterface")]
fn dapps_interface(&self) -> Result<String, Error>;
/// Returns next nonce for particular sender. Should include all transactions in the queue. /// Returns next nonce for particular sender. Should include all transactions in the queue.
#[rpc(name = "parity_nextNonce")] #[rpc(name = "parity_nextNonce")]
fn next_nonce(&self, H160) -> Result<U256, Error>; fn next_nonce(&self, H160) -> Result<U256, Error>;

View File

@ -18,7 +18,7 @@
use jsonrpc_core::Error; use jsonrpc_core::Error;
use v1::helpers::auto_args::Wrap; use v1::helpers::auto_args::Wrap;
use v1::types::{U256, TransactionModification, ConfirmationRequest, ConfirmationResponse}; use v1::types::{U256, Bytes, TransactionModification, ConfirmationRequest, ConfirmationResponse};
build_rpc_trait! { build_rpc_trait! {
@ -33,6 +33,10 @@ build_rpc_trait! {
#[rpc(name = "signer_confirmRequest")] #[rpc(name = "signer_confirmRequest")]
fn confirm_request(&self, U256, TransactionModification, String) -> Result<ConfirmationResponse, Error>; fn confirm_request(&self, U256, TransactionModification, String) -> Result<ConfirmationResponse, Error>;
/// Confirm specific request with already signed data.
#[rpc(name = "signer_confirmRequestRaw")]
fn confirm_request_raw(&self, U256, Bytes) -> Result<ConfirmationResponse, Error>;
/// Reject the confirmation request. /// Reject the confirmation request.
#[rpc(name = "signer_rejectRequest")] #[rpc(name = "signer_rejectRequest")]
fn reject_request(&self, U256) -> Result<bool, Error>; fn reject_request(&self, U256) -> Result<bool, Error>;

View File

@ -457,12 +457,19 @@ impl ChainSync {
if self.state != SyncState::WaitingPeers { if self.state != SyncState::WaitingPeers {
return; 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) = { let (best_hash, max_peers, snapshot_peers) = {
//collect snapshot infos from peers //collect snapshot infos from peers
let snapshots = self.peers.iter() 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()))); .filter_map(|(p, peer)| peer.snapshot_hash.map(|hash| (p, hash.clone())));
let mut snapshot_peers = HashMap::new(); let mut snapshot_peers = HashMap::new();

View File

@ -41,6 +41,7 @@ DWORD parityProcId = 0;
NOTIFYICONDATA nidApp; NOTIFYICONDATA nidApp;
WCHAR szTitle[MAX_LOADSTRING]; WCHAR szTitle[MAX_LOADSTRING];
WCHAR szWindowClass[MAX_LOADSTRING]; WCHAR szWindowClass[MAX_LOADSTRING];
LPCWCHAR commandLineFiltered = L"";
LPCWSTR cParityExe = _T("parity.exe"); LPCWSTR cParityExe = _T("parity.exe");
@ -86,7 +87,6 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
return 0; return 0;
} }
LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadStringW(hInstance, IDC_PTRAY, szWindowClass, MAX_LOADSTRING); LoadStringW(hInstance, IDC_PTRAY, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance); MyRegisterClass(hInstance);
@ -133,6 +133,24 @@ ATOM MyRegisterClass(HINSTANCE hInstance)
bool InitInstance(HINSTANCE hInstance, int nCmdShow, LPWSTR cmdLine) 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 // Check if already running
PROCESSENTRY32 entry; PROCESSENTRY32 entry;
entry.dwSize = sizeof(PROCESSENTRY32); entry.dwSize = sizeof(PROCESSENTRY32);
@ -190,7 +208,9 @@ bool InitInstance(HINSTANCE hInstance, int nCmdShow, LPWSTR cmdLine)
nidApp.uCallbackMessage = WM_USER_SHELLICON; nidApp.uCallbackMessage = WM_USER_SHELLICON;
LoadString(hInstance, IDS_CONTROL_PARITY, nidApp.szTip, MAX_LOADSTRING); LoadString(hInstance, IDS_CONTROL_PARITY, nidApp.szTip, MAX_LOADSTRING);
Shell_NotifyIcon(NIM_ADD, &nidApp); Shell_NotifyIcon(NIM_ADD, &nidApp);
return TRUE;
SetTimer(hWnd, 0, 1000, nullptr);
return true;
} }
@ -294,8 +314,10 @@ void OpenUI()
PROCESS_INFORMATION procInfo = { 0 }; PROCESS_INFORMATION procInfo = { 0 };
STARTUPINFO startupInfo = { sizeof(STARTUPINFO) }; STARTUPINFO startupInfo = { sizeof(STARTUPINFO) };
LPWSTR cmd = _T("parity.exe ui"); LPWSTR args = new WCHAR[lstrlen(commandLineFiltered) + MAX_PATH + 2];
CreateProcess(path, cmd, nullptr, nullptr, false, CREATE_NO_WINDOW, nullptr, nullptr, &startupInfo, &procInfo); lstrcpy(args, L"parity.exe ui ");
lstrcat(args, commandLineFiltered);
CreateProcess(path, args, nullptr, nullptr, false, CREATE_NO_WINDOW, nullptr, nullptr, &startupInfo, &procInfo);
} }
bool AutostartEnabled() { bool AutostartEnabled() {
@ -321,10 +343,14 @@ void EnableAutostart(bool enable) {
if (enable) if (enable)
{ {
TCHAR path[MAX_PATH] = { 0 }; LPWSTR args = new WCHAR[lstrlen(commandLineFiltered) + MAX_PATH + 2];
if (!GetTrayExePath(path, MAX_PATH)) if (GetTrayExePath(args, MAX_PATH))
return; {
RegSetValueEx(hKey, L"Parity", 0, REG_SZ, (LPBYTE)path, MAX_PATH); lstrcat(args, L" ");
lstrcat(args, commandLineFiltered);
RegSetValueEx(hKey, L"Parity", 0, REG_SZ, (LPBYTE)args, MAX_PATH);
}
delete[] args;
} }
else else
{ {