merge master into sms-verification-modal
This commit is contained in:
commit
01f14901ce
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -1249,7 +1249,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "parity-ui-precompiled"
|
||||
version = "1.4.0"
|
||||
source = "git+https://github.com/ethcore/js-precompiled.git#9054ef95a5d79cbd8fefe4869ec3b4de07e9a72d"
|
||||
source = "git+https://github.com/ethcore/js-precompiled.git#29c6f1d527db5c6a29c6b6afdb9bde32c6809079"
|
||||
dependencies = [
|
||||
"parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
@ -47,7 +47,7 @@ pub struct ContentFetcher<R: URLHint = URLHintContract> {
|
||||
resolver: R,
|
||||
cache: Arc<Mutex<ContentCache>>,
|
||||
sync: Arc<SyncStatus>,
|
||||
embeddable_at: Option<u16>,
|
||||
embeddable_on: Option<(String, u16)>,
|
||||
}
|
||||
|
||||
impl<R: URLHint> Drop for ContentFetcher<R> {
|
||||
@ -59,7 +59,7 @@ impl<R: URLHint> Drop for 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();
|
||||
dapps_path.push(random_filename());
|
||||
|
||||
@ -68,17 +68,17 @@ impl<R: URLHint> ContentFetcher<R> {
|
||||
resolver: resolver,
|
||||
sync: sync_status,
|
||||
cache: Arc::new(Mutex::new(ContentCache::default())),
|
||||
embeddable_at: embeddable_at,
|
||||
embeddable_on: embeddable_on,
|
||||
}
|
||||
}
|
||||
|
||||
fn still_syncing(port: Option<u16>) -> Box<Handler> {
|
||||
fn still_syncing(address: Option<(String, u16)>) -> Box<Handler> {
|
||||
Box::new(ContentHandler::error(
|
||||
StatusCode::ServiceUnavailable,
|
||||
"Sync In Progress",
|
||||
"Your node is still syncing. We cannot resolve any content before it's fully synced.",
|
||||
Some("<a href=\"javascript:window.location.reload()\">Refresh</a>"),
|
||||
port,
|
||||
address,
|
||||
))
|
||||
}
|
||||
|
||||
@ -145,7 +145,7 @@ impl<R: URLHint> ContentFetcher<R> {
|
||||
match content {
|
||||
// Don't serve dapps if we are still syncing (but serve content)
|
||||
Some(URLHintResult::Dapp(_)) if self.sync.is_major_importing() => {
|
||||
(None, Self::still_syncing(self.embeddable_at))
|
||||
(None, Self::still_syncing(self.embeddable_on.clone()))
|
||||
},
|
||||
Some(URLHintResult::Dapp(dapp)) => {
|
||||
let (handler, fetch_control) = ContentFetcherHandler::new(
|
||||
@ -155,9 +155,9 @@ impl<R: URLHint> ContentFetcher<R> {
|
||||
id: content_id.clone(),
|
||||
dapps_path: self.dapps_path.clone(),
|
||||
on_done: Box::new(on_done),
|
||||
embeddable_at: self.embeddable_at,
|
||||
embeddable_on: self.embeddable_on.clone(),
|
||||
},
|
||||
self.embeddable_at,
|
||||
self.embeddable_on.clone(),
|
||||
);
|
||||
|
||||
(Some(ContentStatus::Fetching(fetch_control)), Box::new(handler) as Box<Handler>)
|
||||
@ -172,13 +172,13 @@ impl<R: URLHint> ContentFetcher<R> {
|
||||
content_path: self.dapps_path.clone(),
|
||||
on_done: Box::new(on_done),
|
||||
},
|
||||
self.embeddable_at,
|
||||
self.embeddable_on.clone(),
|
||||
);
|
||||
|
||||
(Some(ContentStatus::Fetching(fetch_control)), Box::new(handler) as Box<Handler>)
|
||||
},
|
||||
None if self.sync.is_major_importing() => {
|
||||
(None, Self::still_syncing(self.embeddable_at))
|
||||
(None, Self::still_syncing(self.embeddable_on.clone()))
|
||||
},
|
||||
None => {
|
||||
// This may happen when sync status changes in between
|
||||
@ -188,7 +188,7 @@ impl<R: URLHint> ContentFetcher<R> {
|
||||
"Resource Not Found",
|
||||
"Requested resource was not found.",
|
||||
None,
|
||||
self.embeddable_at,
|
||||
self.embeddable_on.clone(),
|
||||
)) as Box<Handler>)
|
||||
},
|
||||
}
|
||||
@ -293,7 +293,7 @@ struct DappInstaller {
|
||||
id: String,
|
||||
dapps_path: PathBuf,
|
||||
on_done: Box<Fn(String, Option<LocalPageEndpoint>) + Send>,
|
||||
embeddable_at: Option<u16>,
|
||||
embeddable_on: Option<(String, u16)>,
|
||||
}
|
||||
|
||||
impl DappInstaller {
|
||||
@ -386,7 +386,7 @@ impl ContentValidator for DappInstaller {
|
||||
try!(manifest_file.write_all(manifest_str.as_bytes()));
|
||||
|
||||
// Create endpoint
|
||||
let app = LocalPageEndpoint::new(target, manifest.clone().into(), PageCache::Enabled, self.embeddable_at);
|
||||
let app = LocalPageEndpoint::new(target, manifest.clone().into(), PageCache::Enabled, self.embeddable_on.clone());
|
||||
|
||||
// Return modified app manifest
|
||||
Ok((manifest.id.clone(), app))
|
||||
|
@ -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();
|
||||
for dapp in local_dapps(dapps_path) {
|
||||
pages.insert(
|
||||
dapp.id,
|
||||
Box::new(LocalPageEndpoint::new(dapp.path, dapp.info, PageCache::Disabled, signer_port))
|
||||
Box::new(LocalPageEndpoint::new(dapp.path, dapp.info, PageCache::Disabled, signer_address.clone()))
|
||||
);
|
||||
}
|
||||
pages
|
||||
|
@ -37,26 +37,26 @@ pub fn utils() -> Box<Endpoint> {
|
||||
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
|
||||
let mut pages = fs::local_endpoints(dapps_path, signer_port);
|
||||
let mut pages = fs::local_endpoints(dapps_path, signer_address.clone());
|
||||
|
||||
// NOTE [ToDr] Dapps will be currently embeded on 8180
|
||||
insert::<parity_ui::App>(&mut pages, "ui", Embeddable::Yes(signer_port));
|
||||
pages.insert("proxy".into(), ProxyPac::boxed(signer_port));
|
||||
insert::<parity_ui::App>(&mut pages, "ui", Embeddable::Yes(signer_address.clone()));
|
||||
pages.insert("proxy".into(), ProxyPac::boxed(signer_address));
|
||||
|
||||
pages
|
||||
}
|
||||
|
||||
fn insert<T : WebApp + Default + 'static>(pages: &mut Endpoints, id: &str, embed_at: Embeddable) {
|
||||
pages.insert(id.to_owned(), Box::new(match embed_at {
|
||||
Embeddable::Yes(port) => PageEndpoint::new_safe_to_embed(T::default(), port),
|
||||
Embeddable::Yes(address) => PageEndpoint::new_safe_to_embed(T::default(), address),
|
||||
Embeddable::No => PageEndpoint::new(T::default()),
|
||||
}));
|
||||
}
|
||||
|
||||
enum Embeddable {
|
||||
Yes(Option<u16>),
|
||||
Yes(Option<(String, u16)>),
|
||||
#[allow(dead_code)]
|
||||
No,
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ pub struct ContentHandler {
|
||||
content: String,
|
||||
mimetype: Mime,
|
||||
write_pos: usize,
|
||||
safe_to_embed_at_port: Option<u16>,
|
||||
safe_to_embed_on: Option<(String, u16)>,
|
||||
}
|
||||
|
||||
impl ContentHandler {
|
||||
@ -44,31 +44,31 @@ impl ContentHandler {
|
||||
Self::new(StatusCode::NotFound, content, mimetype)
|
||||
}
|
||||
|
||||
pub fn html(code: StatusCode, content: String, embeddable_at: Option<u16>) -> Self {
|
||||
Self::new_embeddable(code, content, mime!(Text/Html), embeddable_at)
|
||||
pub fn html(code: StatusCode, content: String, embeddable_on: Option<(String, u16)>) -> Self {
|
||||
Self::new_embeddable(code, content, mime!(Text/Html), embeddable_on)
|
||||
}
|
||||
|
||||
pub fn error(code: StatusCode, title: &str, message: &str, details: Option<&str>, embeddable_at: Option<u16>) -> Self {
|
||||
pub fn error(code: StatusCode, title: &str, message: &str, details: Option<&str>, embeddable_on: Option<(String, u16)>) -> Self {
|
||||
Self::html(code, format!(
|
||||
include_str!("../error_tpl.html"),
|
||||
title=title,
|
||||
message=message,
|
||||
details=details.unwrap_or_else(|| ""),
|
||||
version=version(),
|
||||
), embeddable_at)
|
||||
), embeddable_on)
|
||||
}
|
||||
|
||||
pub fn new(code: StatusCode, content: String, mimetype: Mime) -> Self {
|
||||
Self::new_embeddable(code, content, mimetype, None)
|
||||
}
|
||||
|
||||
pub fn new_embeddable(code: StatusCode, content: String, mimetype: Mime, embeddable_at: Option<u16>) -> Self {
|
||||
pub fn new_embeddable(code: StatusCode, content: String, mimetype: Mime, embeddable_on: Option<(String, u16)>) -> Self {
|
||||
ContentHandler {
|
||||
code: code,
|
||||
content: content,
|
||||
mimetype: mimetype,
|
||||
write_pos: 0,
|
||||
safe_to_embed_at_port: embeddable_at,
|
||||
safe_to_embed_on: embeddable_on,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -85,7 +85,7 @@ impl server::Handler<HttpStream> for ContentHandler {
|
||||
fn on_response(&mut self, res: &mut server::Response) -> Next {
|
||||
res.set_status(self.code);
|
||||
res.headers_mut().set(header::ContentType(self.mimetype.clone()));
|
||||
add_security_headers(&mut res.headers_mut(), self.safe_to_embed_at_port.clone());
|
||||
add_security_headers(&mut res.headers_mut(), self.safe_to_embed_on.clone());
|
||||
Next::write()
|
||||
}
|
||||
|
||||
|
@ -138,7 +138,7 @@ pub struct ContentFetcherHandler<H: ContentValidator> {
|
||||
client: Option<Client>,
|
||||
installer: H,
|
||||
request_url: Option<Url>,
|
||||
embeddable_at: Option<u16>,
|
||||
embeddable_on: Option<(String, u16)>,
|
||||
}
|
||||
|
||||
impl<H: ContentValidator> Drop for ContentFetcherHandler<H> {
|
||||
@ -157,7 +157,7 @@ impl<H: ContentValidator> ContentFetcherHandler<H> {
|
||||
url: String,
|
||||
control: Control,
|
||||
handler: H,
|
||||
embeddable_at: Option<u16>,
|
||||
embeddable_on: Option<(String, u16)>,
|
||||
) -> (Self, Arc<FetchControl>) {
|
||||
|
||||
let fetch_control = Arc::new(FetchControl::default());
|
||||
@ -169,7 +169,7 @@ impl<H: ContentValidator> ContentFetcherHandler<H> {
|
||||
status: FetchState::NotStarted(url),
|
||||
installer: handler,
|
||||
request_url: None,
|
||||
embeddable_at: embeddable_at,
|
||||
embeddable_on: embeddable_on,
|
||||
};
|
||||
|
||||
(handler, fetch_control)
|
||||
@ -208,7 +208,7 @@ impl<H: ContentValidator> server::Handler<HttpStream> for ContentFetcherHandler<
|
||||
"Unable To Start Dapp Download",
|
||||
"Could not initialize download of the dapp. It might be a problem with the remote server.",
|
||||
Some(&format!("{}", e)),
|
||||
self.embeddable_at,
|
||||
self.embeddable_on.clone(),
|
||||
)),
|
||||
}
|
||||
},
|
||||
@ -218,7 +218,7 @@ impl<H: ContentValidator> server::Handler<HttpStream> for ContentFetcherHandler<
|
||||
"Method Not Allowed",
|
||||
"Only <code>GET</code> requests are allowed.",
|
||||
None,
|
||||
self.embeddable_at,
|
||||
self.embeddable_on.clone(),
|
||||
)),
|
||||
})
|
||||
} else { None };
|
||||
@ -241,7 +241,7 @@ impl<H: ContentValidator> server::Handler<HttpStream> for ContentFetcherHandler<
|
||||
"Download Timeout",
|
||||
&format!("Could not fetch content within {} seconds.", FETCH_TIMEOUT),
|
||||
None,
|
||||
self.embeddable_at,
|
||||
self.embeddable_on.clone(),
|
||||
);
|
||||
Self::close_client(&mut self.client);
|
||||
(Some(FetchState::Error(timeout)), Next::write())
|
||||
@ -263,7 +263,7 @@ impl<H: ContentValidator> server::Handler<HttpStream> for ContentFetcherHandler<
|
||||
"Invalid Dapp",
|
||||
"Downloaded bundle does not contain a valid content.",
|
||||
Some(&format!("{:?}", e)),
|
||||
self.embeddable_at,
|
||||
self.embeddable_on.clone(),
|
||||
))
|
||||
},
|
||||
Ok((id, result)) => {
|
||||
@ -284,7 +284,7 @@ impl<H: ContentValidator> server::Handler<HttpStream> for ContentFetcherHandler<
|
||||
"Download Error",
|
||||
"There was an error when fetching the content.",
|
||||
Some(&format!("{:?}", e)),
|
||||
self.embeddable_at,
|
||||
self.embeddable_on.clone(),
|
||||
);
|
||||
(Some(FetchState::Error(error)), Next::write())
|
||||
},
|
||||
|
@ -30,18 +30,18 @@ pub use self::fetch::{ContentFetcherHandler, ContentValidator, FetchControl};
|
||||
|
||||
use url::Url;
|
||||
use hyper::{server, header, net, uri};
|
||||
use signer_address;
|
||||
use address;
|
||||
|
||||
/// Adds security-related headers to the Response.
|
||||
pub fn add_security_headers(headers: &mut header::Headers, embeddable_at: Option<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-Content-Type-Options", vec![b"nosniff".to_vec()]);
|
||||
|
||||
// Embedding header:
|
||||
if let Some(port) = embeddable_at {
|
||||
if let Some(embeddable_on) = embeddable_on {
|
||||
headers.set_raw(
|
||||
"X-Frame-Options",
|
||||
vec![format!("ALLOW-FROM http://{}", signer_address(port)).into_bytes()]
|
||||
vec![format!("ALLOW-FROM http://{}", address(embeddable_on)).into_bytes()]
|
||||
);
|
||||
} else {
|
||||
// TODO [ToDr] Should we be more strict here (DENY?)?
|
||||
|
@ -112,7 +112,7 @@ pub struct ServerBuilder {
|
||||
handler: Arc<IoHandler>,
|
||||
registrar: Arc<ContractClient>,
|
||||
sync_status: Arc<SyncStatus>,
|
||||
signer_port: Option<u16>,
|
||||
signer_address: Option<(String, u16)>,
|
||||
}
|
||||
|
||||
impl Extendable for ServerBuilder {
|
||||
@ -129,7 +129,7 @@ impl ServerBuilder {
|
||||
handler: Arc::new(IoHandler::new()),
|
||||
registrar: registrar,
|
||||
sync_status: Arc::new(|| false),
|
||||
signer_port: None,
|
||||
signer_address: None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -139,8 +139,8 @@ impl ServerBuilder {
|
||||
}
|
||||
|
||||
/// Change default signer port.
|
||||
pub fn with_signer_port(&mut self, signer_port: Option<u16>) {
|
||||
self.signer_port = signer_port;
|
||||
pub fn with_signer_address(&mut self, signer_address: Option<(String, u16)>) {
|
||||
self.signer_address = signer_address;
|
||||
}
|
||||
|
||||
/// Asynchronously start server with no authentication,
|
||||
@ -152,7 +152,7 @@ impl ServerBuilder {
|
||||
NoAuth,
|
||||
self.handler.clone(),
|
||||
self.dapps_path.clone(),
|
||||
self.signer_port.clone(),
|
||||
self.signer_address.clone(),
|
||||
self.registrar.clone(),
|
||||
self.sync_status.clone(),
|
||||
)
|
||||
@ -167,7 +167,7 @@ impl ServerBuilder {
|
||||
HttpBasicAuth::single_user(username, password),
|
||||
self.handler.clone(),
|
||||
self.dapps_path.clone(),
|
||||
self.signer_port.clone(),
|
||||
self.signer_address.clone(),
|
||||
self.registrar.clone(),
|
||||
self.sync_status.clone(),
|
||||
)
|
||||
@ -197,11 +197,11 @@ impl Server {
|
||||
}
|
||||
|
||||
/// Returns a list of CORS domains for API endpoint.
|
||||
fn cors_domains(signer_port: Option<u16>) -> Vec<String> {
|
||||
match signer_port {
|
||||
Some(port) => vec![
|
||||
fn cors_domains(signer_address: Option<(String, u16)>) -> Vec<String> {
|
||||
match signer_address {
|
||||
Some(signer_address) => vec![
|
||||
format!("http://{}{}", HOME_PAGE, DAPPS_DOMAIN),
|
||||
format!("http://{}", signer_address(port)),
|
||||
format!("http://{}", address(signer_address)),
|
||||
],
|
||||
None => vec![],
|
||||
}
|
||||
@ -213,15 +213,15 @@ impl Server {
|
||||
authorization: A,
|
||||
handler: Arc<IoHandler>,
|
||||
dapps_path: String,
|
||||
signer_port: Option<u16>,
|
||||
signer_address: Option<(String, u16)>,
|
||||
registrar: Arc<ContractClient>,
|
||||
sync_status: Arc<SyncStatus>,
|
||||
) -> Result<Server, ServerError> {
|
||||
let panic_handler = Arc::new(Mutex::new(None));
|
||||
let authorization = Arc::new(authorization);
|
||||
let content_fetcher = Arc::new(apps::fetcher::ContentFetcher::new(apps::urlhint::URLHintContract::new(registrar), sync_status, signer_port));
|
||||
let endpoints = Arc::new(apps::all_endpoints(dapps_path, signer_port.clone()));
|
||||
let cors_domains = Self::cors_domains(signer_port);
|
||||
let content_fetcher = Arc::new(apps::fetcher::ContentFetcher::new(apps::urlhint::URLHintContract::new(registrar), sync_status, signer_address.clone()));
|
||||
let endpoints = Arc::new(apps::all_endpoints(dapps_path, signer_address.clone()));
|
||||
let cors_domains = Self::cors_domains(signer_address.clone());
|
||||
|
||||
let special = Arc::new({
|
||||
let mut special = HashMap::new();
|
||||
@ -238,7 +238,7 @@ impl Server {
|
||||
try!(hyper::Server::http(addr))
|
||||
.handle(move |ctrl| router::Router::new(
|
||||
ctrl,
|
||||
signer_port.clone(),
|
||||
signer_address.clone(),
|
||||
content_fetcher.clone(),
|
||||
endpoints.clone(),
|
||||
special.clone(),
|
||||
@ -302,8 +302,8 @@ pub fn random_filename() -> String {
|
||||
rng.gen_ascii_chars().take(12).collect()
|
||||
}
|
||||
|
||||
fn signer_address(port: u16) -> String {
|
||||
format!("127.0.0.1:{}", port)
|
||||
fn address(address: (String, u16)) -> String {
|
||||
format!("{}:{}", address.0, address.1)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -332,7 +332,7 @@ mod util_tests {
|
||||
|
||||
// when
|
||||
let none = Server::cors_domains(None);
|
||||
let some = Server::cors_domains(Some(18180));
|
||||
let some = Server::cors_domains(Some(("127.0.0.1".into(), 18180)));
|
||||
|
||||
// then
|
||||
assert_eq!(none, Vec::<String>::new());
|
||||
|
@ -25,7 +25,7 @@ pub struct PageEndpoint<T : WebApp + 'static> {
|
||||
/// Prefix to strip from the path (when `None` deducted from `app_id`)
|
||||
pub prefix: Option<String>,
|
||||
/// 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,
|
||||
}
|
||||
|
||||
@ -36,7 +36,7 @@ impl<T: WebApp + 'static> PageEndpoint<T> {
|
||||
PageEndpoint {
|
||||
app: Arc::new(app),
|
||||
prefix: None,
|
||||
safe_to_embed_at_port: None,
|
||||
safe_to_embed_on: None,
|
||||
info: EndpointInfo::from(info),
|
||||
}
|
||||
}
|
||||
@ -49,7 +49,7 @@ impl<T: WebApp + 'static> PageEndpoint<T> {
|
||||
PageEndpoint {
|
||||
app: Arc::new(app),
|
||||
prefix: Some(prefix),
|
||||
safe_to_embed_at_port: None,
|
||||
safe_to_embed_on: None,
|
||||
info: EndpointInfo::from(info),
|
||||
}
|
||||
}
|
||||
@ -57,12 +57,12 @@ impl<T: WebApp + 'static> PageEndpoint<T> {
|
||||
/// Creates new `PageEndpoint` which can be safely used in iframe
|
||||
/// even from different origin. It might be dangerous (clickjacking).
|
||||
/// Use wisely!
|
||||
pub fn new_safe_to_embed(app: T, port: Option<u16>) -> Self {
|
||||
pub fn new_safe_to_embed(app: T, address: Option<(String, u16)>) -> Self {
|
||||
let info = app.info();
|
||||
PageEndpoint {
|
||||
app: Arc::new(app),
|
||||
prefix: None,
|
||||
safe_to_embed_at_port: port,
|
||||
safe_to_embed_on: address,
|
||||
info: EndpointInfo::from(info),
|
||||
}
|
||||
}
|
||||
@ -79,9 +79,9 @@ impl<T: WebApp> Endpoint for PageEndpoint<T> {
|
||||
app: BuiltinDapp::new(self.app.clone()),
|
||||
prefix: self.prefix.clone(),
|
||||
path: path,
|
||||
file: handler::ServedFile::new(self.safe_to_embed_at_port.clone()),
|
||||
file: handler::ServedFile::new(self.safe_to_embed_on.clone()),
|
||||
cache: PageCache::Disabled,
|
||||
safe_to_embed_at_port: self.safe_to_embed_at_port.clone(),
|
||||
safe_to_embed_on: self.safe_to_embed_on.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -60,13 +60,13 @@ pub enum ServedFile<T: Dapp> {
|
||||
}
|
||||
|
||||
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(
|
||||
StatusCode::NotFound,
|
||||
"404 Not Found",
|
||||
"Requested dapp resource was not found.",
|
||||
None,
|
||||
embeddable_at,
|
||||
embeddable_on,
|
||||
))
|
||||
}
|
||||
}
|
||||
@ -97,7 +97,7 @@ pub struct PageHandler<T: Dapp> {
|
||||
/// Requested path.
|
||||
pub path: EndpointPath,
|
||||
/// 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.
|
||||
pub cache: PageCache,
|
||||
}
|
||||
@ -133,7 +133,7 @@ impl<T: Dapp> server::Handler<HttpStream> for PageHandler<T> {
|
||||
self.app.file(&self.extract_path(url.path()))
|
||||
},
|
||||
_ => None,
|
||||
}.map_or_else(|| ServedFile::new(self.safe_to_embed_at_port.clone()), |f| ServedFile::File(f));
|
||||
}.map_or_else(|| ServedFile::new(self.safe_to_embed_on.clone()), |f| ServedFile::File(f));
|
||||
Next::write()
|
||||
}
|
||||
|
||||
@ -162,7 +162,7 @@ impl<T: Dapp> server::Handler<HttpStream> for PageHandler<T> {
|
||||
}
|
||||
|
||||
// Security headers:
|
||||
add_security_headers(&mut res.headers_mut(), self.safe_to_embed_at_port);
|
||||
add_security_headers(&mut res.headers_mut(), self.safe_to_embed_on.clone());
|
||||
Next::write()
|
||||
},
|
||||
ServedFile::Error(ref mut handler) => {
|
||||
@ -246,7 +246,7 @@ fn should_extract_path_with_appid() {
|
||||
},
|
||||
file: ServedFile::new(None),
|
||||
cache: Default::default(),
|
||||
safe_to_embed_at_port: None,
|
||||
safe_to_embed_on: None,
|
||||
};
|
||||
|
||||
// when
|
||||
|
@ -27,17 +27,17 @@ pub struct LocalPageEndpoint {
|
||||
mime: Option<String>,
|
||||
info: Option<EndpointInfo>,
|
||||
cache: PageCache,
|
||||
embeddable_at: Option<u16>,
|
||||
embeddable_on: Option<(String, u16)>,
|
||||
}
|
||||
|
||||
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 {
|
||||
path: path,
|
||||
mime: None,
|
||||
info: Some(info),
|
||||
cache: cache,
|
||||
embeddable_at: embeddable_at,
|
||||
embeddable_on: embeddable_on,
|
||||
}
|
||||
}
|
||||
|
||||
@ -47,7 +47,7 @@ impl LocalPageEndpoint {
|
||||
mime: Some(mime),
|
||||
info: None,
|
||||
cache: cache,
|
||||
embeddable_at: None,
|
||||
embeddable_on: None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -68,7 +68,7 @@ impl Endpoint for LocalPageEndpoint {
|
||||
prefix: None,
|
||||
path: path,
|
||||
file: handler::ServedFile::new(None),
|
||||
safe_to_embed_at_port: self.embeddable_at,
|
||||
safe_to_embed_on: self.embeddable_on.clone(),
|
||||
cache: self.cache,
|
||||
})
|
||||
} else {
|
||||
@ -77,7 +77,7 @@ impl Endpoint for LocalPageEndpoint {
|
||||
prefix: None,
|
||||
path: path,
|
||||
file: handler::ServedFile::new(None),
|
||||
safe_to_embed_at_port: self.embeddable_at,
|
||||
safe_to_embed_on: self.embeddable_on.clone(),
|
||||
cache: self.cache,
|
||||
})
|
||||
}
|
||||
|
@ -19,24 +19,24 @@
|
||||
use endpoint::{Endpoint, Handler, EndpointPath};
|
||||
use handlers::ContentHandler;
|
||||
use apps::{HOME_PAGE, DAPPS_DOMAIN};
|
||||
use signer_address;
|
||||
use address;
|
||||
|
||||
pub struct ProxyPac {
|
||||
signer_port: Option<u16>,
|
||||
signer_address: Option<(String, u16)>,
|
||||
}
|
||||
|
||||
impl ProxyPac {
|
||||
pub fn boxed(signer_port: Option<u16>) -> Box<Endpoint> {
|
||||
pub fn boxed(signer_address: Option<(String, u16)>) -> Box<Endpoint> {
|
||||
Box::new(ProxyPac {
|
||||
signer_port: signer_port
|
||||
signer_address: signer_address
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Endpoint for ProxyPac {
|
||||
fn to_handler(&self, path: EndpointPath) -> Box<Handler> {
|
||||
let signer = self.signer_port
|
||||
.map(signer_address)
|
||||
let signer = self.signer_address.clone()
|
||||
.map(address)
|
||||
.unwrap_or_else(|| format!("{}:{}", path.host, path.port));
|
||||
|
||||
let content = format!(
|
||||
|
@ -20,7 +20,7 @@
|
||||
pub mod auth;
|
||||
mod host_validation;
|
||||
|
||||
use signer_address;
|
||||
use address;
|
||||
use std::sync::Arc;
|
||||
use std::collections::HashMap;
|
||||
use url::{Url, Host};
|
||||
@ -43,7 +43,7 @@ pub enum SpecialEndpoint {
|
||||
|
||||
pub struct Router<A: Authorization + 'static> {
|
||||
control: Option<Control>,
|
||||
signer_port: Option<u16>,
|
||||
signer_address: Option<(String, u16)>,
|
||||
endpoints: Arc<Endpoints>,
|
||||
fetch: Arc<ContentFetcher>,
|
||||
special: Arc<HashMap<SpecialEndpoint, Box<Endpoint>>>,
|
||||
@ -117,14 +117,14 @@ impl<A: Authorization + 'static> server::Handler<HttpStream> for Router<A> {
|
||||
"404 Not Found",
|
||||
"Requested content was not found.",
|
||||
None,
|
||||
self.signer_port,
|
||||
self.signer_address.clone(),
|
||||
))
|
||||
},
|
||||
// Redirect any other GET request to signer.
|
||||
_ if *req.method() == hyper::Method::Get => {
|
||||
if let Some(port) = self.signer_port {
|
||||
if let Some(signer_address) = self.signer_address.clone() {
|
||||
trace!(target: "dapps", "Redirecting to signer interface.");
|
||||
Redirection::boxed(&format!("http://{}", signer_address(port)))
|
||||
Redirection::boxed(&format!("http://{}", address(signer_address)))
|
||||
} else {
|
||||
trace!(target: "dapps", "Signer disabled, returning 404.");
|
||||
Box::new(ContentHandler::error(
|
||||
@ -132,7 +132,7 @@ impl<A: Authorization + 'static> server::Handler<HttpStream> for Router<A> {
|
||||
"404 Not Found",
|
||||
"Your homepage is not available when Trusted Signer is disabled.",
|
||||
Some("You can still access dapps by writing a correct address, though. Re-enabled Signer to get your homepage back."),
|
||||
self.signer_port,
|
||||
self.signer_address.clone(),
|
||||
))
|
||||
}
|
||||
},
|
||||
@ -168,7 +168,7 @@ impl<A: Authorization + 'static> server::Handler<HttpStream> for Router<A> {
|
||||
impl<A: Authorization> Router<A> {
|
||||
pub fn new(
|
||||
control: Control,
|
||||
signer_port: Option<u16>,
|
||||
signer_address: Option<(String, u16)>,
|
||||
content_fetcher: Arc<ContentFetcher>,
|
||||
endpoints: Arc<Endpoints>,
|
||||
special: Arc<HashMap<SpecialEndpoint, Box<Endpoint>>>,
|
||||
@ -181,7 +181,7 @@ impl<A: Authorization> Router<A> {
|
||||
.to_handler(EndpointPath::default());
|
||||
Router {
|
||||
control: Some(control),
|
||||
signer_port: signer_port,
|
||||
signer_address: signer_address,
|
||||
endpoints: endpoints,
|
||||
fetch: content_fetcher,
|
||||
special: special,
|
||||
|
@ -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");
|
||||
let mut builder = ServerBuilder::new(dapps_path.to_str().unwrap().into(), registrar.clone());
|
||||
builder.with_sync_status(Arc::new(move || is_syncing));
|
||||
builder.with_signer_port(Some(SIGNER_PORT));
|
||||
builder.with_signer_address(Some(("127.0.0.1".into(), SIGNER_PORT)));
|
||||
(
|
||||
builder.start_unsecured_http(&"127.0.0.1:0".parse().unwrap(), hosts).unwrap(),
|
||||
registrar,
|
||||
@ -89,7 +89,7 @@ pub fn serve_with_auth(user: &str, pass: &str) -> Server {
|
||||
let mut dapps_path = env::temp_dir();
|
||||
dapps_path.push("non-existent-dir-to-prevent-fs-files-from-loading");
|
||||
let mut builder = ServerBuilder::new(dapps_path.to_str().unwrap().into(), registrar);
|
||||
builder.with_signer_port(Some(SIGNER_PORT));
|
||||
builder.with_signer_address(Some(("127.0.0.1".into(), SIGNER_PORT)));
|
||||
builder.start_basic_auth_http(&"127.0.0.1:0".parse().unwrap(), None, user, pass).unwrap()
|
||||
}
|
||||
|
||||
|
@ -131,10 +131,10 @@
|
||||
"0x807640a13483f8ac783c557fcdf27be11ea4ac7a"
|
||||
],
|
||||
"eip150Transition": "0x259518",
|
||||
"eip155Transition": 2642462,
|
||||
"eip160Transition": 2642462,
|
||||
"eip161abcTransition": 2642462,
|
||||
"eip161dTransition": 2642462
|
||||
"eip155Transition": "0x7fffffffffffffff",
|
||||
"eip160Transition": "0x7fffffffffffffff",
|
||||
"eip161abcTransition": "0x7fffffffffffffff",
|
||||
"eip161dTransition": "0x7fffffffffffffff"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -176,7 +176,13 @@
|
||||
"enode://a979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@52.16.188.185:30303",
|
||||
"enode://de471bccee3d042261d52e9bff31458daecc406142b401d4cd848f677479f73104b9fdeb090af9583d3391b7f10cb2ba9e26865dd5fca4fcdc0fb1e3b723c786@54.94.239.50:30303",
|
||||
"enode://1118980bf48b0a3640bdba04e0fe78b1add18e1cd99bf22d53daac1fd9972ad650df52176e7c7d89d1114cfef2bc23a2959aa54998a46afcf7d91809f0855082@52.74.57.123:30303",
|
||||
"enode://4cd540b2c3292e17cff39922e864094bf8b0741fcc8c5dcea14957e389d7944c70278d872902e3d0345927f621547efa659013c400865485ab4bfa0c6596936f@138.201.144.135:30303"
|
||||
"enode://4cd540b2c3292e17cff39922e864094bf8b0741fcc8c5dcea14957e389d7944c70278d872902e3d0345927f621547efa659013c400865485ab4bfa0c6596936f@138.201.144.135:30303",
|
||||
|
||||
"enode://89d5dc2a81e574c19d0465f497c1af96732d1b61a41de89c2a37f35707689ac416529fae1038809852b235c2d30fd325abdc57c122feeefbeaaf802cc7e9580d@45.55.33.62:30303",
|
||||
"enode://605e04a43b1156966b3a3b66b980c87b7f18522f7f712035f84576016be909a2798a438b2b17b1a8c58db314d88539a77419ca4be36148c086900fba487c9d39@188.166.255.12:30303",
|
||||
"enode://016b20125f447a3b203a3cae953b2ede8ffe51290c071e7599294be84317635730c397b8ff74404d6be412d539ee5bb5c3c700618723d3b53958c92bd33eaa82@159.203.210.80:30303",
|
||||
"enode://01f76fa0561eca2b9a7e224378dd854278735f1449793c46ad0c4e79e8775d080c21dcc455be391e90a98153c3b05dcc8935c8440de7b56fe6d67251e33f4e3c@10.6.6.117:30303",
|
||||
"enode://fe11ef89fc5ac9da358fc160857855f25bbf9e332c79b9ca7089330c02b728b2349988c6062f10982041702110745e203d26975a6b34bcc97144f9fe439034e8@10.1.72.117:30303"
|
||||
],
|
||||
"accounts": {
|
||||
"0000000000000000000000000000000000000001": { "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } },
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 853333e7da312775fb8f32f2c2771b8578cd0d79
|
||||
Subproject commit 9028c4801fd39fbb71a9796979182549a24e81c8
|
@ -95,6 +95,7 @@ impl KeyDirectory for NullDir {
|
||||
struct AddressBook {
|
||||
path: PathBuf,
|
||||
cache: HashMap<Address, AccountMeta>,
|
||||
transient: bool,
|
||||
}
|
||||
|
||||
impl AddressBook {
|
||||
@ -106,11 +107,18 @@ impl AddressBook {
|
||||
let mut r = AddressBook {
|
||||
path: path,
|
||||
cache: HashMap::new(),
|
||||
transient: false,
|
||||
};
|
||||
r.revert();
|
||||
r
|
||||
}
|
||||
|
||||
pub fn transient() -> Self {
|
||||
let mut book = AddressBook::new(Default::default());
|
||||
book.transient = true;
|
||||
book
|
||||
}
|
||||
|
||||
pub fn get(&self) -> HashMap<Address, AccountMeta> {
|
||||
self.cache.clone()
|
||||
}
|
||||
@ -134,6 +142,7 @@ impl AddressBook {
|
||||
}
|
||||
|
||||
fn revert(&mut self) {
|
||||
if self.transient { return; }
|
||||
trace!(target: "addressbook", "revert");
|
||||
let _ = fs::File::open(self.path.clone())
|
||||
.map_err(|e| trace!(target: "addressbook", "Couldn't open address book: {}", e))
|
||||
@ -144,6 +153,7 @@ impl AddressBook {
|
||||
}
|
||||
|
||||
fn save(&mut self) {
|
||||
if self.transient { return; }
|
||||
trace!(target: "addressbook", "save");
|
||||
let _ = fs::File::create(self.path.clone())
|
||||
.map_err(|e| warn!(target: "addressbook", "Couldn't open address book for writing: {}", e))
|
||||
@ -175,7 +185,7 @@ impl AccountProvider {
|
||||
pub fn transient_provider() -> Self {
|
||||
AccountProvider {
|
||||
unlocked: Mutex::new(HashMap::new()),
|
||||
address_book: Mutex::new(AddressBook::new(Default::default())),
|
||||
address_book: Mutex::new(AddressBook::transient()),
|
||||
sstore: Box::new(EthStore::open(Box::new(NullDir::default()))
|
||||
.expect("NullDir load always succeeds; qed"))
|
||||
}
|
||||
|
1
js/.gitignore
vendored
1
js/.gitignore
vendored
@ -6,3 +6,4 @@ build
|
||||
.dist
|
||||
.happypack
|
||||
.npmjs
|
||||
.eslintcache
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "parity.js",
|
||||
"version": "0.2.24",
|
||||
"version": "0.2.28",
|
||||
"main": "release/index.js",
|
||||
"jsnext:main": "src/index.js",
|
||||
"author": "Parity Team <admin@parity.io>",
|
||||
@ -39,9 +39,11 @@
|
||||
"clean": "rm -rf ./build ./coverage",
|
||||
"coveralls": "npm run testCoverage && coveralls < coverage/lcov.info",
|
||||
"lint": "eslint --ignore-path .gitignore ./src/",
|
||||
"lint:cached": "eslint --cache --ignore-path .gitignore ./src/",
|
||||
"test": "mocha 'src/**/*.spec.js'",
|
||||
"test:coverage": "istanbul cover _mocha -- 'src/**/*.spec.js'",
|
||||
"test:e2e": "mocha 'src/**/*.e2e.js'"
|
||||
"test:e2e": "mocha 'src/**/*.e2e.js'",
|
||||
"prepush": "npm run lint:cached"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-cli": "^6.10.1",
|
||||
@ -84,6 +86,7 @@
|
||||
"happypack": "^2.2.1",
|
||||
"history": "^2.0.0",
|
||||
"html-loader": "^0.4.4",
|
||||
"husky": "^0.11.9",
|
||||
"ignore-styles": "2.0.0",
|
||||
"image-webpack-loader": "^1.8.0",
|
||||
"istanbul": "^1.0.0-alpha.2",
|
||||
@ -118,6 +121,7 @@
|
||||
"bytes": "^2.4.0",
|
||||
"chart.js": "^2.3.0",
|
||||
"es6-promise": "^3.2.1",
|
||||
"ethereumjs-tx": "^1.1.2",
|
||||
"file-saver": "^1.3.3",
|
||||
"format-json": "^1.0.3",
|
||||
"format-number": "^2.0.1",
|
||||
@ -148,6 +152,7 @@
|
||||
"redux-actions": "^0.10.1",
|
||||
"redux-thunk": "^2.1.0",
|
||||
"rlp": "^2.0.0",
|
||||
"scryptsy": "^2.0.0",
|
||||
"store": "^1.3.20",
|
||||
"utf8": "^2.1.1",
|
||||
"validator": "^5.7.0",
|
||||
|
@ -63,7 +63,7 @@ if [ "$BRANCH" == "master" ]; then
|
||||
|
||||
echo "*** Publishing $PACKAGE to npmjs"
|
||||
cd .npmjs
|
||||
npm publish --access public
|
||||
npm publish --access public || true
|
||||
cd ../..
|
||||
fi
|
||||
|
||||
|
@ -136,27 +136,30 @@ export default class Contract {
|
||||
}
|
||||
|
||||
parseEventLogs (logs) {
|
||||
return logs.map((log) => {
|
||||
const signature = log.topics[0].substr(2);
|
||||
const event = this.events.find((evt) => evt.signature === signature);
|
||||
return logs
|
||||
.map((log) => {
|
||||
const signature = log.topics[0].substr(2);
|
||||
const event = this.events.find((evt) => evt.signature === signature);
|
||||
|
||||
if (!event) {
|
||||
throw new Error(`Unable to find event matching signature ${signature}`);
|
||||
}
|
||||
if (!event) {
|
||||
console.warn(`Unable to find event matching signature ${signature}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const decoded = event.decodeLog(log.topics, log.data);
|
||||
const decoded = event.decodeLog(log.topics, log.data);
|
||||
|
||||
log.params = {};
|
||||
log.event = event.name;
|
||||
log.params = {};
|
||||
log.event = event.name;
|
||||
|
||||
decoded.params.forEach((param) => {
|
||||
const { type, value } = param.token;
|
||||
decoded.params.forEach((param) => {
|
||||
const { type, value } = param.token;
|
||||
|
||||
log.params[param.name] = { type, value };
|
||||
});
|
||||
log.params[param.name] = { type, value };
|
||||
});
|
||||
|
||||
return log;
|
||||
});
|
||||
return log;
|
||||
})
|
||||
.filter((log) => log);
|
||||
}
|
||||
|
||||
parseTransactionEvents (receipt) {
|
||||
|
@ -119,19 +119,6 @@ describe('api/contract/Contract', () => {
|
||||
});
|
||||
|
||||
describe('parseTransactionEvents', () => {
|
||||
it('checks for unmatched signatures', () => {
|
||||
const contract = new Contract(eth, [{ anonymous: false, name: 'Message', type: 'event' }]);
|
||||
expect(() => contract.parseTransactionEvents({
|
||||
logs: [{
|
||||
data: '0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000063cf90d3f0410092fc0fca41846f5962239791950000000000000000000000000000000000000000000000000000000056e6c85f0000000000000000000000000000000000000000000000000001000000004fcd00000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000d706f7374286d6573736167652900000000000000000000000000000000000000',
|
||||
topics: [
|
||||
'0x954ba6c157daf8a26539574ffa64203c044691aa57251af95f4b48d85ec00dd5',
|
||||
'0x0000000000000000000000000000000000000000000000000001000000004fe0'
|
||||
]
|
||||
}]
|
||||
})).to.throw(/event matching signature/);
|
||||
});
|
||||
|
||||
it('parses a transaction log into the data', () => {
|
||||
const contract = new Contract(eth, [
|
||||
{
|
||||
|
@ -93,6 +93,10 @@ export function inFilter (options) {
|
||||
}
|
||||
|
||||
export function inHex (str) {
|
||||
if (str && str.toString) {
|
||||
str = str.toString(16);
|
||||
}
|
||||
|
||||
if (str && str.substr(0, 2) === '0x') {
|
||||
return str.toLowerCase();
|
||||
}
|
||||
|
@ -60,6 +60,11 @@ export default class Parity {
|
||||
.then(outNumber);
|
||||
}
|
||||
|
||||
dappsInterface () {
|
||||
return this._transport
|
||||
.execute('parity_dappsInterface');
|
||||
}
|
||||
|
||||
defaultExtraData () {
|
||||
return this._transport
|
||||
.execute('parity_defaultExtraData');
|
||||
@ -176,6 +181,12 @@ export default class Parity {
|
||||
.then(outAddress);
|
||||
}
|
||||
|
||||
nextNonce (account) {
|
||||
return this._transport
|
||||
.execute('parity_nextNonce', inAddress(account))
|
||||
.then(outNumber);
|
||||
}
|
||||
|
||||
nodeName () {
|
||||
return this._transport
|
||||
.execute('parity_nodeName');
|
||||
|
@ -14,7 +14,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { inNumber16 } from '../../format/input';
|
||||
import { inNumber16, inData } from '../../format/input';
|
||||
import { outSignerRequest } from '../../format/output';
|
||||
|
||||
export default class Signer {
|
||||
@ -27,6 +27,11 @@ export default class Signer {
|
||||
.execute('signer_confirmRequest', inNumber16(requestId), options, password);
|
||||
}
|
||||
|
||||
confirmRequestRaw (requestId, data) {
|
||||
return this._transport
|
||||
.execute('signer_confirmRequestRaw', inNumber16(requestId), inData(data));
|
||||
}
|
||||
|
||||
generateAuthorizationToken () {
|
||||
return this._transport
|
||||
.execute('signer_generateAuthorizationToken');
|
||||
|
@ -24,6 +24,7 @@ import owned from './owned.json';
|
||||
import registry from './registry.json';
|
||||
import signaturereg from './signaturereg.json';
|
||||
import tokenreg from './tokenreg.json';
|
||||
import wallet from './wallet.json';
|
||||
|
||||
export {
|
||||
basiccoin,
|
||||
@ -35,5 +36,6 @@ export {
|
||||
owned,
|
||||
registry,
|
||||
signaturereg,
|
||||
tokenreg
|
||||
tokenreg,
|
||||
wallet
|
||||
};
|
||||
|
1
js/src/contracts/abi/wallet.json
Normal file
1
js/src/contracts/abi/wallet.json
Normal 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"}]
|
@ -109,6 +109,15 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
dappsInterface: {
|
||||
desc: 'Returns the interface the dapps are running on, error if not enabled',
|
||||
params: [],
|
||||
returns: {
|
||||
type: String,
|
||||
desc: 'The interface'
|
||||
}
|
||||
},
|
||||
|
||||
defaultExtraData: {
|
||||
desc: 'Returns the default extra data',
|
||||
params: [],
|
||||
@ -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: {
|
||||
desc: 'Returns node name (identity)',
|
||||
params: [],
|
||||
|
@ -14,7 +14,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { Quantity } from '../types';
|
||||
import { Quantity, Data } from '../types';
|
||||
|
||||
export default {
|
||||
generateAuthorizationToken: {
|
||||
@ -57,6 +57,24 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
confirmRequestRaw: {
|
||||
desc: 'Confirm a request in the signer queue providing signed request.',
|
||||
params: [
|
||||
{
|
||||
type: Quantity,
|
||||
desc: 'The request id'
|
||||
},
|
||||
{
|
||||
type: Data,
|
||||
desc: 'Signed request (transaction RLP)'
|
||||
}
|
||||
],
|
||||
returns: {
|
||||
type: Boolean,
|
||||
desc: 'The status of the confirmation'
|
||||
}
|
||||
},
|
||||
|
||||
rejectRequest: {
|
||||
desc: 'Rejects a request in the signer queue',
|
||||
params: [
|
||||
|
32
js/src/modals/AddContract/addContract.css
Normal file
32
js/src/modals/AddContract/addContract.css
Normal 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;
|
||||
}
|
||||
}
|
@ -17,10 +17,38 @@
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import ContentAdd from 'material-ui/svg-icons/content/add';
|
||||
import ContentClear from 'material-ui/svg-icons/content/clear';
|
||||
import NavigationArrowForward from 'material-ui/svg-icons/navigation/arrow-forward';
|
||||
import NavigationArrowBack from 'material-ui/svg-icons/navigation/arrow-back';
|
||||
|
||||
import { RadioButton, RadioButtonGroup } from 'material-ui/RadioButton';
|
||||
|
||||
import { Button, Modal, Form, Input, InputAddress } from '../../ui';
|
||||
import { ERRORS, validateAbi, validateAddress, validateName } from '../../util/validation';
|
||||
|
||||
import { eip20, wallet } from '../../contracts/abi';
|
||||
import styles from './addContract.css';
|
||||
|
||||
const ABI_TYPES = [
|
||||
{
|
||||
label: 'Token', readOnly: true, value: JSON.stringify(eip20),
|
||||
type: 'token',
|
||||
description: (<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 {
|
||||
static contextTypes = {
|
||||
api: PropTypes.object.isRequired
|
||||
@ -34,44 +62,101 @@ export default class AddContract extends Component {
|
||||
state = {
|
||||
abi: '',
|
||||
abiError: ERRORS.invalidAbi,
|
||||
abiType: ABI_TYPES[2],
|
||||
abiTypeIndex: 2,
|
||||
abiParsed: null,
|
||||
address: '',
|
||||
addressError: ERRORS.invalidAddress,
|
||||
name: '',
|
||||
nameError: ERRORS.invalidName,
|
||||
description: ''
|
||||
description: '',
|
||||
step: 0
|
||||
};
|
||||
|
||||
componentDidMount () {
|
||||
this.onChangeABIType(null, this.state.abiTypeIndex);
|
||||
}
|
||||
|
||||
render () {
|
||||
const { step } = this.state;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible
|
||||
actions={ this.renderDialogActions() }
|
||||
title='watch contract'>
|
||||
{ this.renderFields() }
|
||||
steps={ STEPS }
|
||||
current={ step }
|
||||
>
|
||||
{ this.renderStep(step) }
|
||||
</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 () {
|
||||
const { addressError, nameError } = this.state;
|
||||
const { addressError, nameError, step } = this.state;
|
||||
const hasError = !!(addressError || nameError);
|
||||
|
||||
return ([
|
||||
const cancelBtn = (
|
||||
<Button
|
||||
icon={ <ContentClear /> }
|
||||
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
|
||||
icon={ <ContentAdd /> }
|
||||
label='Add Contract'
|
||||
disabled={ hasError }
|
||||
onClick={ this.onAdd } />
|
||||
]);
|
||||
);
|
||||
|
||||
return [ cancelBtn, prevBtn, addBtn ];
|
||||
}
|
||||
|
||||
renderFields () {
|
||||
const { abi, abiError, address, addressError, description, name, nameError } = this.state;
|
||||
const { abi, abiError, address, addressError, description, name, nameError, abiType } = this.state;
|
||||
|
||||
return (
|
||||
<Form>
|
||||
@ -80,7 +165,9 @@ export default class AddContract extends Component {
|
||||
hint='the network address for the contract'
|
||||
error={ addressError }
|
||||
value={ address }
|
||||
onSubmit={ this.onEditAddress } />
|
||||
onSubmit={ this.onEditAddress }
|
||||
onChange={ this.onChangeAddress }
|
||||
/>
|
||||
<Input
|
||||
label='contract name'
|
||||
hint='a descriptive name for the contract'
|
||||
@ -94,20 +181,57 @@ export default class AddContract extends Component {
|
||||
hint='an expanded description for the entry'
|
||||
value={ description }
|
||||
onSubmit={ this.onEditDescription } />
|
||||
|
||||
<Input
|
||||
label='contract abi'
|
||||
hint='the abi for the contract'
|
||||
error={ abiError }
|
||||
value={ abi }
|
||||
onSubmit={ this.onEditAbi } />
|
||||
readOnly={ abiType.readOnly }
|
||||
onSubmit={ this.onEditAbi }
|
||||
/>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
onEditAbi = (abi) => {
|
||||
const { api } = this.context;
|
||||
renderAbiTypes () {
|
||||
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) => {
|
||||
@ -138,7 +262,7 @@ export default class AddContract extends Component {
|
||||
|
||||
onAdd = () => {
|
||||
const { api } = this.context;
|
||||
const { abiParsed, address, name, description } = this.state;
|
||||
const { abiParsed, address, name, description, abiType } = this.state;
|
||||
|
||||
Promise.all([
|
||||
api.parity.setAccountName(address, name),
|
||||
@ -147,6 +271,7 @@ export default class AddContract extends Component {
|
||||
deleted: false,
|
||||
timestamp: Date.now(),
|
||||
abi: abiParsed,
|
||||
type: abiType.type,
|
||||
description
|
||||
})
|
||||
]).catch((error) => {
|
||||
|
@ -16,6 +16,9 @@
|
||||
|
||||
import * as actions from './signerActions';
|
||||
|
||||
import { inHex } from '../../api/format/input';
|
||||
import { Wallet } from '../../util/wallet';
|
||||
|
||||
export default class SignerMiddleware {
|
||||
constructor (api) {
|
||||
this._api = api;
|
||||
@ -49,23 +52,58 @@ export default class SignerMiddleware {
|
||||
}
|
||||
|
||||
onConfirmStart = (store, action) => {
|
||||
const { id, password } = action.payload;
|
||||
const { id, password, wallet, payload } = action.payload;
|
||||
|
||||
this._api.signer
|
||||
.confirmRequest(id, {}, password)
|
||||
.then((txHash) => {
|
||||
console.log('confirmRequest', id, txHash);
|
||||
if (!txHash) {
|
||||
store.dispatch(actions.errorConfirmRequest({ id, err: 'Unable to confirm.' }));
|
||||
return;
|
||||
const handlePromise = promise => {
|
||||
promise
|
||||
.then((txHash) => {
|
||||
console.log('confirmRequest', id, txHash);
|
||||
if (!txHash) {
|
||||
store.dispatch(actions.errorConfirmRequest({ id, err: 'Unable to confirm.' }));
|
||||
return;
|
||||
}
|
||||
|
||||
store.dispatch(actions.successConfirmRequest({ id, txHash }));
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('confirmRequest', id, error);
|
||||
store.dispatch(actions.errorConfirmRequest({ id, err: error.message }));
|
||||
});
|
||||
};
|
||||
|
||||
// Sign request in-browser
|
||||
if (wallet && payload.transaction) {
|
||||
const { transaction } = payload;
|
||||
|
||||
(transaction.nonce.isZero()
|
||||
? this._api.parity.nextNonce(transaction.from)
|
||||
: Promise.resolve(transaction.nonce)
|
||||
).then(nonce => {
|
||||
let txData = {
|
||||
to: inHex(transaction.to),
|
||||
nonce: inHex(transaction.nonce.isZero() ? nonce : transaction.nonce),
|
||||
gasPrice: inHex(transaction.gasPrice),
|
||||
gasLimit: inHex(transaction.gas),
|
||||
value: inHex(transaction.value),
|
||||
data: inHex(transaction.data)
|
||||
};
|
||||
|
||||
try {
|
||||
// NOTE: Derving the key takes significant amount of time,
|
||||
// make sure to display some kind of "in-progress" state.
|
||||
const signer = Wallet.fromJson(wallet, password);
|
||||
const rawTx = signer.signTransaction(txData);
|
||||
|
||||
handlePromise(this._api.signer.confirmRequestRaw(id, rawTx));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
store.dispatch(actions.errorConfirmRequest({ id, err: error.message }));
|
||||
}
|
||||
|
||||
store.dispatch(actions.successConfirmRequest({ id, txHash }));
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('confirmRequest', id, error);
|
||||
store.dispatch(actions.errorConfirmRequest({ id, err: error.message }));
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
handlePromise(this._api.signer.confirmRequest(id, {}, password));
|
||||
}
|
||||
|
||||
onRejectStart = (store, action) => {
|
||||
|
@ -38,10 +38,22 @@ export function validateAbi (abi, api) {
|
||||
abiParsed = JSON.parse(abi);
|
||||
|
||||
if (!api.util.isArray(abiParsed) || !abiParsed.length) {
|
||||
abiError = ERRORS.inavlidAbi;
|
||||
} else {
|
||||
abi = JSON.stringify(abiParsed);
|
||||
abiError = ERRORS.invalidAbi;
|
||||
return { abi, abiError, abiParsed };
|
||||
}
|
||||
|
||||
// Validate each elements of the Array
|
||||
const invalidIndex = abiParsed
|
||||
.map((o) => isValidAbiEvent(o, api) || isValidAbiFunction(o, api))
|
||||
.findIndex((valid) => !valid);
|
||||
|
||||
if (invalidIndex !== -1) {
|
||||
const invalid = abiParsed[invalidIndex];
|
||||
abiError = `${ERRORS.invalidAbi} (#${invalidIndex}: ${invalid.name || invalid.type})`;
|
||||
return { abi, abiError, abiParsed };
|
||||
}
|
||||
|
||||
abi = JSON.stringify(abiParsed);
|
||||
} catch (error) {
|
||||
abiError = ERRORS.invalidAbi;
|
||||
}
|
||||
@ -53,6 +65,25 @@ export function validateAbi (abi, api) {
|
||||
};
|
||||
}
|
||||
|
||||
function isValidAbiFunction (object, api) {
|
||||
if (!object) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ((object.type === 'function' && object.name) || object.type === 'constructor') &&
|
||||
(object.inputs && api.util.isArray(object.inputs));
|
||||
}
|
||||
|
||||
function isValidAbiEvent (object, api) {
|
||||
if (!object) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (object.type === 'event') &&
|
||||
(object.name) &&
|
||||
(object.inputs && api.util.isArray(object.inputs));
|
||||
}
|
||||
|
||||
export function validateAddress (address) {
|
||||
let addressError = null;
|
||||
|
||||
|
82
js/src/util/wallet.js
Normal file
82
js/src/util/wallet.js
Normal 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'));
|
||||
}
|
||||
|
||||
}
|
@ -197,24 +197,15 @@ export default class DappsStore {
|
||||
});
|
||||
}
|
||||
|
||||
_fetchManifest (manifestHash, count = 0) {
|
||||
return fetch(`${this._getHost()}/api/content/${manifestHash}/`)
|
||||
_fetchManifest (manifestHash) {
|
||||
return fetch(`${this._getHost()}/api/content/${manifestHash}/`, { redirect: 'follow', mode: 'cors' })
|
||||
.then((response) => {
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
}
|
||||
|
||||
if (count < 1) {
|
||||
return this._fetchManifest(manifestHash, count + 1);
|
||||
}
|
||||
|
||||
return null;
|
||||
return response.ok
|
||||
? response.json()
|
||||
: null;
|
||||
})
|
||||
.catch(() => {
|
||||
if (count < 1) {
|
||||
return this._fetchManifest(manifestHash, count + 1);
|
||||
}
|
||||
|
||||
.catch((error) => {
|
||||
console.warn('DappsStore:fetchManifest', error);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
@ -33,15 +33,22 @@ export default class RequestPendingWeb3 extends Component {
|
||||
className: PropTypes.string
|
||||
};
|
||||
|
||||
onConfirm = data => {
|
||||
const { onConfirm, payload } = this.props;
|
||||
|
||||
data.payload = payload;
|
||||
onConfirm(data);
|
||||
};
|
||||
|
||||
render () {
|
||||
const { payload, id, className, isSending, date, onConfirm, onReject } = this.props;
|
||||
const { payload, id, className, isSending, date, onReject } = this.props;
|
||||
|
||||
if (payload.sign) {
|
||||
const { sign } = payload;
|
||||
return (
|
||||
<SignRequest
|
||||
className={ className }
|
||||
onConfirm={ onConfirm }
|
||||
onConfirm={ this.onConfirm }
|
||||
onReject={ onReject }
|
||||
isSending={ isSending }
|
||||
isFinished={ false }
|
||||
@ -57,7 +64,7 @@ export default class RequestPendingWeb3 extends Component {
|
||||
return (
|
||||
<TransactionPending
|
||||
className={ className }
|
||||
onConfirm={ onConfirm }
|
||||
onConfirm={ this.onConfirm }
|
||||
onReject={ onReject }
|
||||
isSending={ isSending }
|
||||
id={ id }
|
||||
|
@ -32,7 +32,7 @@
|
||||
|
||||
.mainContainer > * {
|
||||
vertical-align: middle;
|
||||
min-height: 120px;
|
||||
min-height: 190px;
|
||||
}
|
||||
|
||||
.inputs {
|
||||
|
@ -116,9 +116,11 @@ export default class TransactionPending extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
onConfirm = password => {
|
||||
onConfirm = data => {
|
||||
const { id, gasPrice } = this.props;
|
||||
this.props.onConfirm({ id, password, gasPrice });
|
||||
const { password, wallet } = data;
|
||||
|
||||
this.props.onConfirm({ id, password, wallet, gasPrice });
|
||||
}
|
||||
|
||||
onReject = () => {
|
||||
|
@ -40,3 +40,7 @@
|
||||
.passwordHint span {
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.fileInput input {
|
||||
top: 22px;
|
||||
}
|
||||
|
@ -35,26 +35,33 @@ class TransactionPendingFormConfirm extends Component {
|
||||
id = Math.random(); // for tooltip
|
||||
|
||||
state = {
|
||||
walletError: null,
|
||||
wallet: null,
|
||||
password: ''
|
||||
}
|
||||
|
||||
render () {
|
||||
const { accounts, address, isSending } = this.props;
|
||||
const { password } = this.state;
|
||||
const { password, walletError, wallet } = this.state;
|
||||
const account = accounts[address] || {};
|
||||
const isExternal = !account.uuid;
|
||||
|
||||
const passwordHint = account.meta && account.meta.passwordHint
|
||||
? (<div><span>(hint) </span>{ account.meta.passwordHint }</div>)
|
||||
: null;
|
||||
|
||||
const isWalletOk = !isExternal || (walletError === null && wallet !== null);
|
||||
const keyInput = isExternal ? this.renderKeyInput() : null;
|
||||
|
||||
return (
|
||||
<div className={ styles.confirmForm }>
|
||||
<Form>
|
||||
{ keyInput }
|
||||
<Input
|
||||
onChange={ this.onModifyPassword }
|
||||
onKeyDown={ this.onKeyDown }
|
||||
label='Account Password'
|
||||
hint='unlock the account'
|
||||
label={ isExternal ? 'Key Password' : 'Account Password' }
|
||||
hint={ isExternal ? 'decrypt the key' : 'unlock the account' }
|
||||
type='password'
|
||||
value={ password } />
|
||||
<div className={ styles.passwordHint }>
|
||||
@ -71,7 +78,7 @@ class TransactionPendingFormConfirm extends Component {
|
||||
className={ styles.confirmButton }
|
||||
fullWidth
|
||||
primary
|
||||
disabled={ isSending }
|
||||
disabled={ isSending || !isWalletOk }
|
||||
icon={ <IdentityIcon address={ address } button className={ styles.signerIcon } /> }
|
||||
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 () {
|
||||
if (this.state.password.length) {
|
||||
return;
|
||||
@ -94,6 +115,26 @@ class TransactionPendingFormConfirm extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
onKeySelect = evt => {
|
||||
const fileReader = new FileReader();
|
||||
fileReader.onload = e => {
|
||||
try {
|
||||
const wallet = JSON.parse(e.target.result);
|
||||
this.setState({
|
||||
walletError: null,
|
||||
wallet: wallet
|
||||
});
|
||||
} catch (e) {
|
||||
this.setState({
|
||||
walletError: 'Given wallet file is invalid.',
|
||||
wallet: null
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
fileReader.readAsText(evt.target.files[0]);
|
||||
}
|
||||
|
||||
onModifyPassword = evt => {
|
||||
const password = evt.target.value;
|
||||
this.setState({
|
||||
@ -102,8 +143,11 @@ class TransactionPendingFormConfirm extends Component {
|
||||
}
|
||||
|
||||
onConfirm = () => {
|
||||
const { password } = this.state;
|
||||
this.props.onConfirm(password);
|
||||
const { password, wallet } = this.state;
|
||||
|
||||
this.props.onConfirm({
|
||||
password, wallet
|
||||
});
|
||||
}
|
||||
|
||||
onKeyDown = evt => {
|
||||
|
@ -198,7 +198,8 @@ module.exports = {
|
||||
proxy: {
|
||||
'/api/*': {
|
||||
target: 'http://127.0.0.1:8080',
|
||||
changeOrigin: true
|
||||
changeOrigin: true,
|
||||
autoRewrite: true
|
||||
},
|
||||
'/app/*': {
|
||||
target: 'http://127.0.0.1:8080',
|
||||
|
@ -22,6 +22,7 @@ const DEST = process.env.BUILD_DEST || '.build';
|
||||
|
||||
let modules = [
|
||||
'babel-polyfill',
|
||||
'browserify-aes', 'ethereumjs-tx', 'scryptsy',
|
||||
'react', 'react-dom', 'react-redux', 'react-router',
|
||||
'redux', 'redux-thunk', 'react-router-redux',
|
||||
'lodash', 'material-ui', 'moment', 'blockies'
|
||||
|
@ -12,7 +12,7 @@
|
||||
!define VERSIONMINOR 5
|
||||
!define VERSIONBUILD 0
|
||||
!define ARGS "--warp"
|
||||
!define FIRST_START_ARGS "--warp --mode=passive"
|
||||
!define FIRST_START_ARGS "ui --warp --mode=passive"
|
||||
|
||||
!addplugindir .\
|
||||
|
||||
@ -160,6 +160,9 @@ section "uninstall"
|
||||
!insertmacro TerminateApp
|
||||
# Remove Start Menu launcher
|
||||
delete "$SMPROGRAMS\${COMPANYNAME}\${APPNAME}.lnk"
|
||||
delete "$SMPROGRAMS\${COMPANYNAME}\${APPNAME} Ethereum.lnk"
|
||||
delete "$DESKTOP\${APPNAME} Ethereum.lnk"
|
||||
|
||||
# Try to remove the Start Menu folder - this will only happen if it is empty
|
||||
rmDir "$SMPROGRAMS\${COMPANYNAME}"
|
||||
|
||||
|
@ -95,7 +95,7 @@ impl Configuration {
|
||||
let wal = !self.args.flag_fast_and_loose;
|
||||
let warp_sync = self.args.flag_warp;
|
||||
let geth_compatibility = self.args.flag_geth;
|
||||
let ui_port = self.ui_port();
|
||||
let ui_address = self.ui_port().map(|port| (self.ui_interface(), port));
|
||||
let dapps_conf = self.dapps_config();
|
||||
let signer_conf = self.signer_config();
|
||||
let format = try!(self.format());
|
||||
@ -243,7 +243,7 @@ impl Configuration {
|
||||
vm_type: vm_type,
|
||||
warp_sync: warp_sync,
|
||||
geth_compatibility: geth_compatibility,
|
||||
ui_port: ui_port,
|
||||
ui_address: ui_address,
|
||||
net_settings: self.network_settings(),
|
||||
dapps_conf: dapps_conf,
|
||||
signer_conf: signer_conf,
|
||||
@ -859,7 +859,7 @@ mod tests {
|
||||
wal: true,
|
||||
vm_type: Default::default(),
|
||||
geth_compatibility: false,
|
||||
ui_port: Some(8180),
|
||||
ui_address: Some(("127.0.0.1".into(), 8180)),
|
||||
net_settings: Default::default(),
|
||||
dapps_conf: Default::default(),
|
||||
signer_conf: Default::default(),
|
||||
|
@ -58,7 +58,7 @@ pub fn new(configuration: Configuration, deps: Dependencies) -> Result<Option<We
|
||||
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 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)
|
||||
});
|
||||
|
||||
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;
|
||||
@ -91,7 +91,7 @@ mod server {
|
||||
_url: &SocketAddr,
|
||||
_allowed_hosts: Option<Vec<String>>,
|
||||
_auth: Option<(String, String)>,
|
||||
_signer_port: Option<u16>,
|
||||
_signer_address: Option<(String, u16)>,
|
||||
) -> Result<WebappServer, String> {
|
||||
Err("Your Parity version has been compiled without WebApps support.".into())
|
||||
}
|
||||
@ -120,7 +120,7 @@ mod server {
|
||||
url: &SocketAddr,
|
||||
allowed_hosts: Option<Vec<String>>,
|
||||
auth: Option<(String, String)>,
|
||||
signer_port: Option<u16>,
|
||||
signer_address: Option<(String, u16)>,
|
||||
) -> Result<WebappServer, String> {
|
||||
use ethcore_dapps as dapps;
|
||||
|
||||
@ -131,7 +131,7 @@ mod server {
|
||||
let sync = deps.sync.clone();
|
||||
let client = deps.client.clone();
|
||||
server.with_sync_status(Arc::new(move || is_major_importing(Some(sync.status().state), client.queue_info())));
|
||||
server.with_signer_port(signer_port);
|
||||
server.with_signer_address(signer_address);
|
||||
|
||||
let server = rpc_apis::setup_rpc(server, deps.apis.clone(), rpc_apis::ApiSet::UnsafeContext);
|
||||
let start_result = match auth {
|
||||
|
@ -37,7 +37,7 @@ pub enum Api {
|
||||
Net,
|
||||
/// Eth (Safe)
|
||||
Eth,
|
||||
/// Geth-compatible "personal" API (DEPRECATED; only used in `--geth` mode.)
|
||||
/// Geth-compatible "personal" API (DEPRECATED; only used in `--geth` mode.)
|
||||
Personal,
|
||||
/// Signer - Confirm transactions in Signer (UNSAFE: Passwords, List of transactions)
|
||||
Signer,
|
||||
@ -119,6 +119,7 @@ pub struct Dependencies {
|
||||
pub settings: Arc<NetworkSettings>,
|
||||
pub net_service: Arc<ManageNetwork>,
|
||||
pub geth_compatibility: bool,
|
||||
pub dapps_interface: Option<String>,
|
||||
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.settings.clone(),
|
||||
signer,
|
||||
deps.dapps_interface.clone(),
|
||||
deps.dapps_port,
|
||||
).to_delegate());
|
||||
|
||||
|
@ -82,7 +82,7 @@ pub struct RunCmd {
|
||||
pub wal: bool,
|
||||
pub vm_type: VMType,
|
||||
pub geth_compatibility: bool,
|
||||
pub ui_port: Option<u16>,
|
||||
pub ui_address: Option<(String, u16)>,
|
||||
pub net_settings: NetworkSettings,
|
||||
pub dapps_conf: dapps::Configuration,
|
||||
pub signer_conf: signer::Configuration,
|
||||
@ -262,7 +262,7 @@ pub fn execute(cmd: RunCmd, logger: Arc<RotatingLogger>) -> Result<(), String> {
|
||||
let deps_for_rpc_apis = Arc::new(rpc_apis::Dependencies {
|
||||
signer_service: Arc::new(rpc_apis::SignerService::new(move || {
|
||||
signer::generate_new_token(signer_path.clone()).map_err(|e| format!("{:?}", e))
|
||||
}, cmd.ui_port)),
|
||||
}, cmd.ui_address)),
|
||||
snapshot: snapshot_service.clone(),
|
||||
client: client.clone(),
|
||||
sync: sync_provider.clone(),
|
||||
@ -274,6 +274,10 @@ pub fn execute(cmd: RunCmd, logger: Arc<RotatingLogger>) -> Result<(), String> {
|
||||
settings: Arc::new(cmd.net_settings.clone()),
|
||||
net_service: manage_network.clone(),
|
||||
geth_compatibility: cmd.geth_compatibility,
|
||||
dapps_interface: match cmd.dapps_conf.enabled {
|
||||
true => Some(cmd.dapps_conf.interface.clone()),
|
||||
false => None,
|
||||
},
|
||||
dapps_port: match cmd.dapps_conf.enabled {
|
||||
true => Some(cmd.dapps_conf.port),
|
||||
false => None,
|
||||
|
@ -17,7 +17,7 @@
|
||||
//! RPC Error codes and error objects
|
||||
|
||||
macro_rules! rpc_unimplemented {
|
||||
() => (Err(::v1::helpers::errors::unimplemented()))
|
||||
() => (Err(::v1::helpers::errors::unimplemented(None)))
|
||||
}
|
||||
|
||||
use std::fmt;
|
||||
@ -51,11 +51,11 @@ mod codes {
|
||||
pub const FETCH_ERROR: i64 = -32060;
|
||||
}
|
||||
|
||||
pub fn unimplemented() -> Error {
|
||||
pub fn unimplemented(details: Option<String>) -> Error {
|
||||
Error {
|
||||
code: ErrorCode::ServerError(codes::UNSUPPORTED_REQUEST),
|
||||
message: "This request is not implemented yet. Please create an issue on Github repo.".into(),
|
||||
data: None
|
||||
data: details.map(Value::String),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -22,18 +22,18 @@ use v1::helpers::signing_queue::{ConfirmationsQueue};
|
||||
pub struct SignerService {
|
||||
queue: Arc<ConfirmationsQueue>,
|
||||
generate_new_token: Box<Fn() -> Result<String, String> + Send + Sync + 'static>,
|
||||
port: Option<u16>,
|
||||
address: Option<(String, u16)>,
|
||||
}
|
||||
|
||||
impl SignerService {
|
||||
|
||||
/// 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 {
|
||||
SignerService {
|
||||
queue: Arc::new(ConfirmationsQueue::default()),
|
||||
generate_new_token: Box::new(new_token),
|
||||
port: port,
|
||||
address: address,
|
||||
}
|
||||
}
|
||||
|
||||
@ -47,20 +47,20 @@ impl SignerService {
|
||||
self.queue.clone()
|
||||
}
|
||||
|
||||
/// Returns signer port (if signer enabled) or `None` otherwise
|
||||
pub fn port(&self) -> Option<u16> {
|
||||
self.port
|
||||
/// Returns signer address (if signer enabled) or `None` otherwise
|
||||
pub fn address(&self) -> Option<(String, u16)> {
|
||||
self.address.clone()
|
||||
}
|
||||
|
||||
/// Returns true if Signer is enabled.
|
||||
pub fn is_enabled(&self) -> bool {
|
||||
self.port.is_some()
|
||||
self.address.is_some()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
/// Creates new Signer Service for tests.
|
||||
pub fn new_test(port: Option<u16>) -> Self {
|
||||
SignerService::new(|| Ok("new_token".into()), port)
|
||||
pub fn new_test(address: Option<(String, u16)>) -> Self {
|
||||
SignerService::new(|| Ok("new_token".into()), address)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,7 @@ extern crate ethash;
|
||||
|
||||
use std::io::{Write};
|
||||
use std::process::{Command, Stdio};
|
||||
use std::collections::BTreeSet;
|
||||
use std::thread;
|
||||
use std::time::{Instant, Duration};
|
||||
use std::sync::{Arc, Weak};
|
||||
@ -339,7 +340,10 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> Eth for EthClient<C, SN, S, M, EM> where
|
||||
|
||||
let store = take_weak!(self.accounts);
|
||||
let accounts = try!(store.accounts().map_err(|e| errors::internal("Could not fetch accounts.", e)));
|
||||
Ok(accounts.into_iter().map(Into::into).collect())
|
||||
let addresses = try!(store.addresses_info().map_err(|e| errors::internal("Could not fetch accounts.", e)));
|
||||
|
||||
let set: BTreeSet<Address> = accounts.into_iter().chain(addresses.keys().cloned()).collect();
|
||||
Ok(set.into_iter().map(Into::into).collect())
|
||||
}
|
||||
|
||||
fn block_number(&self) -> Result<RpcU256, Error> {
|
||||
|
@ -52,6 +52,7 @@ pub struct ParityClient<C, M, S: ?Sized> where
|
||||
logger: Arc<RotatingLogger>,
|
||||
settings: Arc<NetworkSettings>,
|
||||
signer: Option<Arc<SignerService>>,
|
||||
dapps_interface: Option<String>,
|
||||
dapps_port: Option<u16>,
|
||||
}
|
||||
|
||||
@ -70,6 +71,7 @@ impl<C, M, S: ?Sized> ParityClient<C, M, S> where
|
||||
logger: Arc<RotatingLogger>,
|
||||
settings: Arc<NetworkSettings>,
|
||||
signer: Option<Arc<SignerService>>,
|
||||
dapps_interface: Option<String>,
|
||||
dapps_port: Option<u16>,
|
||||
) -> Self {
|
||||
ParityClient {
|
||||
@ -81,6 +83,7 @@ impl<C, M, S: ?Sized> ParityClient<C, M, S> where
|
||||
logger: logger,
|
||||
settings: settings,
|
||||
signer: signer,
|
||||
dapps_interface: dapps_interface,
|
||||
dapps_port: dapps_port,
|
||||
}
|
||||
}
|
||||
@ -261,7 +264,8 @@ impl<C, M, S: ?Sized> Parity for ParityClient<C, M, S> where
|
||||
|
||||
self.signer
|
||||
.clone()
|
||||
.and_then(|signer| signer.port())
|
||||
.and_then(|signer| signer.address())
|
||||
.map(|address| address.1)
|
||||
.ok_or_else(|| errors::signer_disabled())
|
||||
}
|
||||
|
||||
@ -272,6 +276,13 @@ impl<C, M, S: ?Sized> Parity for ParityClient<C, M, S> where
|
||||
.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> {
|
||||
try!(self.active());
|
||||
let address: Address = address.into();
|
||||
|
@ -18,14 +18,17 @@
|
||||
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
use jsonrpc_core::*;
|
||||
use rlp::{UntrustedRlp, View};
|
||||
use ethcore::account_provider::AccountProvider;
|
||||
use ethcore::client::MiningBlockChainClient;
|
||||
use ethcore::transaction::SignedTransaction;
|
||||
use ethcore::miner::MinerService;
|
||||
|
||||
use jsonrpc_core::Error;
|
||||
use v1::traits::Signer;
|
||||
use v1::types::{TransactionModification, ConfirmationRequest, ConfirmationResponse, U256};
|
||||
use v1::types::{TransactionModification, ConfirmationRequest, ConfirmationResponse, U256, Bytes};
|
||||
use v1::helpers::{errors, SignerService, SigningQueue, ConfirmationPayload};
|
||||
use v1::helpers::dispatch;
|
||||
use v1::helpers::dispatch::{self, dispatch_transaction};
|
||||
|
||||
/// Transactions confirmation (personal) rpc implementation.
|
||||
pub struct SignerClient<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);
|
||||
|
||||
Ok(signer.requests()
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect()
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.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)))
|
||||
}
|
||||
|
||||
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> {
|
||||
try!(self.active());
|
||||
let signer = take_weak!(self.signer);
|
||||
|
@ -353,9 +353,16 @@ fn rpc_eth_gas_price() {
|
||||
fn rpc_eth_accounts() {
|
||||
let tester = EthTester::default();
|
||||
let address = tester.accounts_provider.new_account("").unwrap();
|
||||
let address2 = Address::default();
|
||||
|
||||
tester.accounts_provider.set_address_name(address2, "Test Account".into()).unwrap();
|
||||
|
||||
let request = r#"{"jsonrpc": "2.0", "method": "eth_accounts", "params": [], "id": 1}"#;
|
||||
let response = r#"{"jsonrpc":"2.0","result":[""#.to_owned() + &format!("0x{:?}", address) + r#""],"id":1}"#;
|
||||
let response = r#"{"jsonrpc":"2.0","result":[""#.to_owned()
|
||||
+ &format!("0x{:?}", address2)
|
||||
+ r#"",""#
|
||||
+ &format!("0x{:?}", address)
|
||||
+ r#""],"id":1}"#;
|
||||
|
||||
assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned()));
|
||||
}
|
||||
|
@ -38,6 +38,7 @@ pub struct Dependencies {
|
||||
pub settings: Arc<NetworkSettings>,
|
||||
pub network: Arc<ManageNetwork>,
|
||||
pub accounts: Arc<AccountProvider>,
|
||||
pub dapps_interface: Option<String>,
|
||||
pub dapps_port: Option<u16>,
|
||||
}
|
||||
|
||||
@ -61,6 +62,7 @@ impl Dependencies {
|
||||
}),
|
||||
network: Arc::new(TestManageNetwork),
|
||||
accounts: Arc::new(AccountProvider::transient_provider()),
|
||||
dapps_interface: Some("127.0.0.1".into()),
|
||||
dapps_port: Some(18080),
|
||||
}
|
||||
}
|
||||
@ -75,6 +77,7 @@ impl Dependencies {
|
||||
self.logger.clone(),
|
||||
self.settings.clone(),
|
||||
signer,
|
||||
self.dapps_interface.clone(),
|
||||
self.dapps_port,
|
||||
)
|
||||
}
|
||||
@ -238,7 +241,7 @@ fn rpc_parity_node_name() {
|
||||
#[test]
|
||||
fn rpc_parity_unsigned_transactions_count() {
|
||||
let deps = Dependencies::new();
|
||||
let io = deps.with_signer(SignerService::new_test(Some(18180)));
|
||||
let io = deps.with_signer(SignerService::new_test(Some(("127.0.0.1".into(), 18180))));
|
||||
|
||||
let request = r#"{"jsonrpc": "2.0", "method": "parity_unsignedTransactionsCount", "params":[], "id": 1}"#;
|
||||
let response = r#"{"jsonrpc":"2.0","result":0,"id":1}"#;
|
||||
@ -282,7 +285,7 @@ fn rpc_parity_encrypt() {
|
||||
fn rpc_parity_signer_port() {
|
||||
// given
|
||||
let deps = Dependencies::new();
|
||||
let io1 = deps.with_signer(SignerService::new_test(Some(18180)));
|
||||
let io1 = deps.with_signer(SignerService::new_test(Some(("127.0.0.1".into(), 18180))));
|
||||
let io2 = deps.default_client();
|
||||
|
||||
// when
|
||||
@ -313,6 +316,24 @@ fn rpc_parity_dapps_port() {
|
||||
assert_eq!(io2.handle_request_sync(request), Some(response2.to_owned()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rpc_parity_dapps_interface() {
|
||||
// given
|
||||
let mut deps = Dependencies::new();
|
||||
let io1 = deps.default_client();
|
||||
deps.dapps_interface = None;
|
||||
let io2 = deps.default_client();
|
||||
|
||||
// when
|
||||
let request = r#"{"jsonrpc": "2.0", "method": "parity_dappsInterface", "params": [], "id": 1}"#;
|
||||
let response1 = r#"{"jsonrpc":"2.0","result":"127.0.0.1","id":1}"#;
|
||||
let response2 = r#"{"jsonrpc":"2.0","error":{"code":-32031,"message":"Dapps Server is disabled. This API is not available.","data":null},"id":1}"#;
|
||||
|
||||
// then
|
||||
assert_eq!(io1.handle_request_sync(request), Some(response1.to_owned()));
|
||||
assert_eq!(io2.handle_request_sync(request), Some(response2.to_owned()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rpc_parity_next_nonce() {
|
||||
let deps = Dependencies::new();
|
||||
|
@ -16,11 +16,14 @@
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::str::FromStr;
|
||||
use jsonrpc_core::IoHandler;
|
||||
use util::{U256, Uint, Address};
|
||||
use util::{U256, Uint, Address, ToPretty};
|
||||
|
||||
use ethcore::account_provider::AccountProvider;
|
||||
use ethcore::client::TestBlockChainClient;
|
||||
use ethcore::transaction::{Transaction, Action};
|
||||
use rlp::encode;
|
||||
|
||||
use jsonrpc_core::IoHandler;
|
||||
use v1::{SignerClient, Signer};
|
||||
use v1::tests::helpers::TestMinerService;
|
||||
use v1::helpers::{SigningQueue, SignerService, FilledTransactionRequest, ConfirmationPayload};
|
||||
@ -206,6 +209,98 @@ fn should_confirm_transaction_and_dispatch() {
|
||||
assert_eq!(tester.miner.imported_transactions.lock().len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_confirm_transaction_with_rlp() {
|
||||
// given
|
||||
let tester = signer_tester();
|
||||
let address = tester.accounts.new_account("test").unwrap();
|
||||
let recipient = Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap();
|
||||
tester.signer.add_request(ConfirmationPayload::SendTransaction(FilledTransactionRequest {
|
||||
from: address,
|
||||
to: Some(recipient),
|
||||
gas_price: U256::from(10_000),
|
||||
gas: U256::from(10_000_000),
|
||||
value: U256::from(1),
|
||||
data: vec![],
|
||||
nonce: None,
|
||||
})).unwrap();
|
||||
|
||||
let t = Transaction {
|
||||
nonce: U256::zero(),
|
||||
gas_price: U256::from(0x1000),
|
||||
gas: U256::from(10_000_000),
|
||||
action: Action::Call(recipient),
|
||||
value: U256::from(0x1),
|
||||
data: vec![]
|
||||
};
|
||||
tester.accounts.unlock_account_temporarily(address, "test".into()).unwrap();
|
||||
let signature = tester.accounts.sign(address, None, t.hash(None)).unwrap();
|
||||
let t = t.with_signature(signature, None);
|
||||
let rlp = encode(&t);
|
||||
|
||||
assert_eq!(tester.signer.requests().len(), 1);
|
||||
|
||||
// when
|
||||
let request = r#"{
|
||||
"jsonrpc":"2.0",
|
||||
"method":"signer_confirmRequestRaw",
|
||||
"params":["0x1", "0x"#.to_owned() + &rlp.to_hex() + r#""],
|
||||
"id":1
|
||||
}"#;
|
||||
println!("{:?}", request);
|
||||
let response = r#"{"jsonrpc":"2.0","result":""#.to_owned() + format!("0x{:?}", t.hash()).as_ref() + r#"","id":1}"#;
|
||||
|
||||
// then
|
||||
assert_eq!(tester.io.handle_request_sync(&request), Some(response.to_owned()));
|
||||
assert_eq!(tester.signer.requests().len(), 0);
|
||||
assert_eq!(tester.miner.imported_transactions.lock().len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_return_error_when_sender_does_not_match() {
|
||||
// given
|
||||
let tester = signer_tester();
|
||||
let address = tester.accounts.new_account("test").unwrap();
|
||||
let recipient = Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap();
|
||||
tester.signer.add_request(ConfirmationPayload::SendTransaction(FilledTransactionRequest {
|
||||
from: Address::default(),
|
||||
to: Some(recipient),
|
||||
gas_price: U256::from(10_000),
|
||||
gas: U256::from(10_000_000),
|
||||
value: U256::from(1),
|
||||
data: vec![],
|
||||
nonce: None,
|
||||
})).unwrap();
|
||||
|
||||
let t = Transaction {
|
||||
nonce: U256::zero(),
|
||||
gas_price: U256::from(0x1000),
|
||||
gas: U256::from(10_000_000),
|
||||
action: Action::Call(recipient),
|
||||
value: U256::from(0x1),
|
||||
data: vec![]
|
||||
};
|
||||
tester.accounts.unlock_account_temporarily(address, "test".into()).unwrap();
|
||||
let signature = tester.accounts.sign(address, None, t.hash(None)).unwrap();
|
||||
let t = t.with_signature(signature, None);
|
||||
let rlp = encode(&t);
|
||||
|
||||
assert_eq!(tester.signer.requests().len(), 1);
|
||||
|
||||
// when
|
||||
let request = r#"{
|
||||
"jsonrpc":"2.0",
|
||||
"method":"signer_confirmRequestRaw",
|
||||
"params":["0x1", "0x"#.to_owned() + &rlp.to_hex() + r#""],
|
||||
"id":1
|
||||
}"#;
|
||||
let response = r#"{"jsonrpc":"2.0","error":{"code":-32602,"message":"Couldn't parse parameters: Sent transaction does not match the request.","data":"[\"from\"]"},"id":1}"#;
|
||||
|
||||
// then
|
||||
assert_eq!(tester.io.handle_request_sync(&request), Some(response.to_owned()));
|
||||
assert_eq!(tester.signer.requests().len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_generate_new_token() {
|
||||
// given
|
||||
|
@ -123,6 +123,10 @@ build_rpc_trait! {
|
||||
#[rpc(name = "parity_dappsPort")]
|
||||
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.
|
||||
#[rpc(name = "parity_nextNonce")]
|
||||
fn next_nonce(&self, H160) -> Result<U256, Error>;
|
||||
|
@ -18,7 +18,7 @@
|
||||
use jsonrpc_core::Error;
|
||||
|
||||
use v1::helpers::auto_args::Wrap;
|
||||
use v1::types::{U256, TransactionModification, ConfirmationRequest, ConfirmationResponse};
|
||||
use v1::types::{U256, Bytes, TransactionModification, ConfirmationRequest, ConfirmationResponse};
|
||||
|
||||
|
||||
build_rpc_trait! {
|
||||
@ -33,6 +33,10 @@ build_rpc_trait! {
|
||||
#[rpc(name = "signer_confirmRequest")]
|
||||
fn confirm_request(&self, U256, TransactionModification, String) -> Result<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.
|
||||
#[rpc(name = "signer_rejectRequest")]
|
||||
fn reject_request(&self, U256) -> Result<bool, Error>;
|
||||
|
@ -457,12 +457,19 @@ impl ChainSync {
|
||||
if self.state != SyncState::WaitingPeers {
|
||||
return;
|
||||
}
|
||||
let best_block = io.chain().chain_info().best_block_number;
|
||||
// Make sure the snapshot block is not too far away from best block and network best block and
|
||||
// that it is higher than fork detection block
|
||||
let our_best_block = io.chain().chain_info().best_block_number;
|
||||
let fork_block = self.fork_block.as_ref().map(|&(n, _)| n).unwrap_or(0);
|
||||
|
||||
let (best_hash, max_peers, snapshot_peers) = {
|
||||
//collect snapshot infos from peers
|
||||
let snapshots = self.peers.iter()
|
||||
.filter(|&(_, p)| p.is_allowed() && p.snapshot_number.map_or(false, |sn| best_block < sn && (sn - best_block) > SNAPSHOT_RESTORE_THRESHOLD))
|
||||
.filter(|&(_, p)| p.is_allowed() && p.snapshot_number.map_or(false, |sn|
|
||||
our_best_block < sn && (sn - our_best_block) > SNAPSHOT_RESTORE_THRESHOLD &&
|
||||
sn > fork_block &&
|
||||
self.highest_block.map_or(true, |highest| highest >= sn && (highest - sn) <= SNAPSHOT_RESTORE_THRESHOLD)
|
||||
))
|
||||
.filter_map(|(p, peer)| peer.snapshot_hash.map(|hash| (p, hash.clone())));
|
||||
|
||||
let mut snapshot_peers = HashMap::new();
|
||||
|
@ -41,6 +41,7 @@ DWORD parityProcId = 0;
|
||||
NOTIFYICONDATA nidApp;
|
||||
WCHAR szTitle[MAX_LOADSTRING];
|
||||
WCHAR szWindowClass[MAX_LOADSTRING];
|
||||
LPCWCHAR commandLineFiltered = L"";
|
||||
|
||||
LPCWSTR cParityExe = _T("parity.exe");
|
||||
|
||||
@ -85,7 +86,6 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
|
||||
OpenUI();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
|
||||
LoadStringW(hInstance, IDC_PTRAY, szWindowClass, MAX_LOADSTRING);
|
||||
@ -133,6 +133,24 @@ ATOM MyRegisterClass(HINSTANCE hInstance)
|
||||
|
||||
bool InitInstance(HINSTANCE hInstance, int nCmdShow, LPWSTR cmdLine)
|
||||
{
|
||||
if (lstrlen(cmdLine) > 0)
|
||||
{
|
||||
int commandLineArgs = 0;
|
||||
LPWSTR* commandLine = CommandLineToArgvW(cmdLine, &commandLineArgs);
|
||||
LPWSTR filteredArgs = new WCHAR[lstrlen(cmdLine) + 2];
|
||||
filteredArgs[0] = '\0';
|
||||
for (int i = 0; i < commandLineArgs; i++)
|
||||
{
|
||||
// Remove "ui" from command line
|
||||
if (lstrcmp(commandLine[i], L"ui") != 0)
|
||||
{
|
||||
lstrcat(filteredArgs, commandLine[i]);
|
||||
lstrcat(filteredArgs, L" ");
|
||||
}
|
||||
}
|
||||
commandLineFiltered = filteredArgs;
|
||||
}
|
||||
|
||||
// Check if already running
|
||||
PROCESSENTRY32 entry;
|
||||
entry.dwSize = sizeof(PROCESSENTRY32);
|
||||
@ -190,7 +208,9 @@ bool InitInstance(HINSTANCE hInstance, int nCmdShow, LPWSTR cmdLine)
|
||||
nidApp.uCallbackMessage = WM_USER_SHELLICON;
|
||||
LoadString(hInstance, IDS_CONTROL_PARITY, nidApp.szTip, MAX_LOADSTRING);
|
||||
Shell_NotifyIcon(NIM_ADD, &nidApp);
|
||||
return TRUE;
|
||||
|
||||
SetTimer(hWnd, 0, 1000, nullptr);
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
@ -294,8 +314,10 @@ void OpenUI()
|
||||
PROCESS_INFORMATION procInfo = { 0 };
|
||||
STARTUPINFO startupInfo = { sizeof(STARTUPINFO) };
|
||||
|
||||
LPWSTR cmd = _T("parity.exe ui");
|
||||
CreateProcess(path, cmd, nullptr, nullptr, false, CREATE_NO_WINDOW, nullptr, nullptr, &startupInfo, &procInfo);
|
||||
LPWSTR args = new WCHAR[lstrlen(commandLineFiltered) + MAX_PATH + 2];
|
||||
lstrcpy(args, L"parity.exe ui ");
|
||||
lstrcat(args, commandLineFiltered);
|
||||
CreateProcess(path, args, nullptr, nullptr, false, CREATE_NO_WINDOW, nullptr, nullptr, &startupInfo, &procInfo);
|
||||
}
|
||||
|
||||
bool AutostartEnabled() {
|
||||
@ -321,10 +343,14 @@ void EnableAutostart(bool enable) {
|
||||
|
||||
if (enable)
|
||||
{
|
||||
TCHAR path[MAX_PATH] = { 0 };
|
||||
if (!GetTrayExePath(path, MAX_PATH))
|
||||
return;
|
||||
RegSetValueEx(hKey, L"Parity", 0, REG_SZ, (LPBYTE)path, MAX_PATH);
|
||||
LPWSTR args = new WCHAR[lstrlen(commandLineFiltered) + MAX_PATH + 2];
|
||||
if (GetTrayExePath(args, MAX_PATH))
|
||||
{
|
||||
lstrcat(args, L" ");
|
||||
lstrcat(args, commandLineFiltered);
|
||||
RegSetValueEx(hKey, L"Parity", 0, REG_SZ, (LPBYTE)args, MAX_PATH);
|
||||
}
|
||||
delete[] args;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user