diff --git a/Cargo.lock b/Cargo.lock
index 30cc49392..c2f4cd443 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -287,6 +287,7 @@ dependencies = [
"jsonrpc-core 2.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"jsonrpc-http-server 5.1.0 (git+https://github.com/ethcore/jsonrpc-http-server.git)",
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "mime_guess 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
"parity-dapps 0.3.0 (git+https://github.com/ethcore/parity-dapps-rs.git)",
"parity-dapps-builtins 0.5.0 (git+https://github.com/ethcore/parity-dapps-builtins-rs.git)",
"parity-dapps-dao 0.3.0 (git+https://github.com/ethcore/parity-dapps-dao-rs.git)",
@@ -883,7 +884,7 @@ dependencies = [
[[package]]
name = "parity-dapps-builtins"
version = "0.5.0"
-source = "git+https://github.com/ethcore/parity-dapps-builtins-rs.git#eb86a2954f04d3aa5547a8c4bb77ae7aad09bf55"
+source = "git+https://github.com/ethcore/parity-dapps-builtins-rs.git#8bbf0421e376f9496d70adc62c1c6d7f492df817"
dependencies = [
"parity-dapps 0.3.0 (git+https://github.com/ethcore/parity-dapps-rs.git)",
]
diff --git a/dapps/Cargo.toml b/dapps/Cargo.toml
index 774c49de0..1b87159ea 100644
--- a/dapps/Cargo.toml
+++ b/dapps/Cargo.toml
@@ -27,6 +27,7 @@ parity-dapps-builtins = { git = "https://github.com/ethcore/parity-dapps-builtin
parity-dapps-wallet = { git = "https://github.com/ethcore/parity-dapps-wallet-rs.git", version = "0.5.0", optional = true }
parity-dapps-dao = { git = "https://github.com/ethcore/parity-dapps-dao-rs.git", version = "0.3.0", optional = true }
parity-dapps-makerotc = { git = "https://github.com/ethcore/parity-dapps-makerotc-rs.git", version = "0.2.0", optional = true }
+mime_guess = { version = "1.6.1" }
clippy = { version = "0.0.71", optional = true}
[build-dependencies]
diff --git a/dapps/src/api/api.rs b/dapps/src/api/api.rs
index c460dcf20..95b01d442 100644
--- a/dapps/src/api/api.rs
+++ b/dapps/src/api/api.rs
@@ -15,7 +15,7 @@
// along with Parity. If not, see .
use std::sync::Arc;
-use endpoint::{Endpoint, Endpoints, Handler, EndpointPath};
+use endpoint::{Endpoint, Endpoints, EndpointInfo, Handler, EndpointPath};
use api::response::as_json;
@@ -23,8 +23,8 @@ pub struct RestApi {
endpoints: Arc,
}
-#[derive(Debug, PartialEq, Serialize)]
-struct App {
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+pub struct App {
pub id: String,
pub name: String,
pub description: String,
@@ -34,6 +34,19 @@ struct App {
pub icon_url: String,
}
+impl App {
+ fn from_info(id: &str, info: &EndpointInfo) -> Self {
+ App {
+ id: id.to_owned(),
+ name: info.name.to_owned(),
+ description: info.description.to_owned(),
+ version: info.version.to_owned(),
+ author: info.author.to_owned(),
+ icon_url: info.icon_url.to_owned(),
+ }
+ }
+}
+
impl RestApi {
pub fn new(endpoints: Arc) -> Box {
Box::new(RestApi {
@@ -43,14 +56,7 @@ impl RestApi {
fn list_apps(&self) -> Vec {
self.endpoints.iter().filter_map(|(ref k, ref e)| {
- e.info().map(|ref info| App {
- id: k.to_owned().clone(),
- name: info.name.to_owned(),
- description: info.description.to_owned(),
- version: info.version.to_owned(),
- author: info.author.to_owned(),
- icon_url: info.icon_url.to_owned(),
- })
+ e.info().map(|ref info| App::from_info(k, info))
}).collect()
}
}
diff --git a/dapps/src/api/mod.rs.in b/dapps/src/api/mod.rs.in
index 0eff6b397..a069c06b0 100644
--- a/dapps/src/api/mod.rs.in
+++ b/dapps/src/api/mod.rs.in
@@ -18,3 +18,4 @@ mod api;
mod response;
pub use self::api::RestApi;
+pub use self::api::App;
diff --git a/dapps/src/apps/fs.rs b/dapps/src/apps/fs.rs
new file mode 100644
index 000000000..fa3b0ab4c
--- /dev/null
+++ b/dapps/src/apps/fs.rs
@@ -0,0 +1,116 @@
+// 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 .
+
+use serde_json;
+use std::io;
+use std::io::Read;
+use std::fs;
+use std::path::PathBuf;
+use page::LocalPageEndpoint;
+use endpoint::{Endpoints, EndpointInfo};
+use api::App;
+
+struct LocalDapp {
+ id: String,
+ path: PathBuf,
+ info: EndpointInfo,
+}
+
+fn local_dapps(dapps_path: String) -> Vec {
+ let files = fs::read_dir(dapps_path.as_str());
+ if let Err(e) = files {
+ warn!(target: "dapps", "Unable to load local dapps from: {}. Reason: {:?}", dapps_path, e);
+ return vec![];
+ }
+
+ let files = files.expect("Check is done earlier");
+ files.map(|dir| {
+ let entry = try!(dir);
+ let file_type = try!(entry.file_type());
+
+ // skip files
+ if file_type.is_file() {
+ return Err(io::Error::new(io::ErrorKind::NotFound, "Not a file"));
+ }
+
+ // take directory name and path
+ entry.file_name().into_string()
+ .map(|name| (name, entry.path()))
+ .map_err(|e| {
+ info!(target: "dapps", "Unable to load dapp: {:?}. Reason: {:?}", entry.path(), e);
+ io::Error::new(io::ErrorKind::NotFound, "Invalid name")
+ })
+ })
+ .filter_map(|m| {
+ if let Err(ref e) = m {
+ debug!(target: "dapps", "Ignoring local dapp: {:?}", e);
+ }
+ m.ok()
+ })
+ .map(|(name, path)| {
+ // try to get manifest file
+ let info = read_manifest(&name, path.clone());
+ LocalDapp {
+ id: name,
+ path: path,
+ info: info,
+ }
+ })
+ .collect()
+}
+
+fn read_manifest(name: &str, mut path: PathBuf) -> EndpointInfo {
+ path.push("manifest.json");
+
+ fs::File::open(path.clone())
+ .map_err(|e| format!("{:?}", e))
+ .and_then(|mut f| {
+ // Reat file
+ let mut s = String::new();
+ try!(f.read_to_string(&mut s).map_err(|e| format!("{:?}", e)));
+ // Try to deserialize manifest
+ serde_json::from_str::(&s).map_err(|e| format!("{:?}", e))
+ })
+ .map(|app| EndpointInfo {
+ name: app.name,
+ description: app.description,
+ version: app.version,
+ author: app.author,
+ icon_url: app.icon_url,
+ })
+ .unwrap_or_else(|e| {
+ warn!(target: "dapps", "Cannot read manifest file at: {:?}. Error: {:?}", path, e);
+
+ EndpointInfo {
+ name: name.into(),
+ description: name.into(),
+ version: "0.0.0".into(),
+ author: "?".into(),
+ icon_url: "icon.png".into(),
+ }
+ })
+}
+
+pub fn local_endpoints(dapps_path: String) -> 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))
+ );
+ }
+ pages
+}
diff --git a/dapps/src/apps.rs b/dapps/src/apps/mod.rs
similarity index 93%
rename from dapps/src/apps.rs
rename to dapps/src/apps/mod.rs
index 130b20fb9..7f849cf65 100644
--- a/dapps/src/apps.rs
+++ b/dapps/src/apps/mod.rs
@@ -19,10 +19,11 @@ use page::PageEndpoint;
use proxypac::ProxyPac;
use parity_dapps::WebApp;
+mod fs;
+
extern crate parity_dapps_status;
extern crate parity_dapps_builtins;
-
pub const DAPPS_DOMAIN : &'static str = ".parity";
pub const RPC_PATH : &'static str = "rpc";
pub const API_PATH : &'static str = "api";
@@ -36,22 +37,24 @@ pub fn utils() -> Box {
Box::new(PageEndpoint::with_prefix(parity_dapps_builtins::App::default(), UTILS_PATH.to_owned()))
}
-pub fn all_endpoints() -> Endpoints {
- let mut pages = Endpoints::new();
- pages.insert("proxy".into(), ProxyPac::boxed());
-
+pub fn all_endpoints(dapps_path: String) -> Endpoints {
+ // fetch fs dapps at first to avoid overwriting builtins
+ let mut pages = fs::local_endpoints(dapps_path);
// Home page needs to be safe embed
// because we use Cross-Origin LocalStorage.
// TODO [ToDr] Account naming should be moved to parity.
pages.insert("home".into(), Box::new(
PageEndpoint::new_safe_to_embed(parity_dapps_builtins::App::default())
));
+ pages.insert("proxy".into(), ProxyPac::boxed());
insert::(&mut pages, "status");
insert::(&mut pages, "parity");
+ // Optional dapps
wallet_page(&mut pages);
daodapp_page(&mut pages);
makerotc_page(&mut pages);
+
pages
}
diff --git a/dapps/src/endpoint.rs b/dapps/src/endpoint.rs
index 28ca6ea11..592bc7f8f 100644
--- a/dapps/src/endpoint.rs
+++ b/dapps/src/endpoint.rs
@@ -30,17 +30,17 @@ pub struct EndpointPath {
pub port: u16,
}
-#[derive(Debug, PartialEq)]
+#[derive(Debug, PartialEq, Clone)]
pub struct EndpointInfo {
- pub name: &'static str,
- pub description: &'static str,
- pub version: &'static str,
- pub author: &'static str,
- pub icon_url: &'static str,
+ pub name: String,
+ pub description: String,
+ pub version: String,
+ pub author: String,
+ pub icon_url: String,
}
pub trait Endpoint : Send + Sync {
- fn info(&self) -> Option { None }
+ fn info(&self) -> Option<&EndpointInfo> { None }
fn to_handler(&self, path: EndpointPath) -> Box>;
}
diff --git a/dapps/src/lib.rs b/dapps/src/lib.rs
index 231e7b080..a7fbd5963 100644
--- a/dapps/src/lib.rs
+++ b/dapps/src/lib.rs
@@ -53,6 +53,7 @@ extern crate jsonrpc_core;
extern crate jsonrpc_http_server;
extern crate parity_dapps;
extern crate ethcore_rpc;
+extern crate mime_guess;
mod endpoint;
mod apps;
@@ -73,6 +74,7 @@ static DAPPS_DOMAIN : &'static str = ".parity";
/// Webapps HTTP+RPC server build.
pub struct ServerBuilder {
+ dapps_path: String,
handler: Arc,
}
@@ -84,8 +86,9 @@ impl Extendable for ServerBuilder {
impl ServerBuilder {
/// Construct new dapps server
- pub fn new() -> Self {
+ pub fn new(dapps_path: String) -> Self {
ServerBuilder {
+ dapps_path: dapps_path,
handler: Arc::new(IoHandler::new())
}
}
@@ -93,13 +96,13 @@ impl ServerBuilder {
/// Asynchronously start server with no authentication,
/// returns result with `Server` handle on success or an error.
pub fn start_unsecure_http(&self, addr: &SocketAddr) -> Result {
- Server::start_http(addr, NoAuth, self.handler.clone())
+ Server::start_http(addr, NoAuth, self.handler.clone(), self.dapps_path.clone())
}
/// Asynchronously start server with `HTTP Basic Authentication`,
/// return result with `Server` handle on success or an error.
pub fn start_basic_auth_http(&self, addr: &SocketAddr, username: &str, password: &str) -> Result {
- Server::start_http(addr, HttpBasicAuth::single_user(username, password), self.handler.clone())
+ Server::start_http(addr, HttpBasicAuth::single_user(username, password), self.handler.clone(), self.dapps_path.clone())
}
}
@@ -110,10 +113,10 @@ pub struct Server {
}
impl Server {
- fn start_http(addr: &SocketAddr, authorization: A, handler: Arc) -> Result {
+ fn start_http(addr: &SocketAddr, authorization: A, handler: Arc, dapps_path: String) -> Result {
let panic_handler = Arc::new(Mutex::new(None));
let authorization = Arc::new(authorization);
- let endpoints = Arc::new(apps::all_endpoints());
+ let endpoints = Arc::new(apps::all_endpoints(dapps_path));
let special = Arc::new({
let mut special = HashMap::new();
special.insert(router::SpecialEndpoint::Rpc, rpc::rpc(handler, panic_handler.clone()));
diff --git a/dapps/src/page/builtin.rs b/dapps/src/page/builtin.rs
new file mode 100644
index 000000000..1c7ca32d4
--- /dev/null
+++ b/dapps/src/page/builtin.rs
@@ -0,0 +1,154 @@
+// 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 .
+
+use page::handler;
+use std::sync::Arc;
+use endpoint::{Endpoint, EndpointInfo, EndpointPath, Handler};
+use parity_dapps::{WebApp, File, Info};
+
+pub struct PageEndpoint {
+ /// Content of the files
+ pub app: Arc,
+ /// Prefix to strip from the path (when `None` deducted from `app_id`)
+ pub prefix: Option,
+ /// Safe to be loaded in frame by other origin. (use wisely!)
+ safe_to_embed: bool,
+ info: EndpointInfo,
+}
+
+impl PageEndpoint {
+ /// Creates new `PageEndpoint` for builtin (compile time) Dapp.
+ pub fn new(app: T) -> Self {
+ let info = app.info();
+ PageEndpoint {
+ app: Arc::new(app),
+ prefix: None,
+ safe_to_embed: false,
+ info: EndpointInfo::from(info),
+ }
+ }
+
+ /// Create new `PageEndpoint` and specify prefix that should be removed before looking for a file.
+ /// It's used only for special endpoints (i.e. `/parity-utils/`)
+ /// So `/parity-utils/inject.js` will be resolved to `/inject.js` is prefix is set.
+ pub fn with_prefix(app: T, prefix: String) -> Self {
+ let info = app.info();
+ PageEndpoint {
+ app: Arc::new(app),
+ prefix: Some(prefix),
+ safe_to_embed: false,
+ info: EndpointInfo::from(info),
+ }
+ }
+
+ /// 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) -> Self {
+ let info = app.info();
+ PageEndpoint {
+ app: Arc::new(app),
+ prefix: None,
+ safe_to_embed: true,
+ info: EndpointInfo::from(info),
+ }
+ }
+}
+
+impl Endpoint for PageEndpoint {
+
+ fn info(&self) -> Option<&EndpointInfo> {
+ Some(&self.info)
+ }
+
+ fn to_handler(&self, path: EndpointPath) -> Box {
+ Box::new(handler::PageHandler {
+ app: BuiltinDapp::new(self.app.clone()),
+ prefix: self.prefix.clone(),
+ path: path,
+ file: None,
+ safe_to_embed: self.safe_to_embed,
+ })
+ }
+}
+
+impl From for EndpointInfo {
+ fn from(info: Info) -> Self {
+ EndpointInfo {
+ name: info.name.into(),
+ description: info.description.into(),
+ author: info.author.into(),
+ icon_url: info.icon_url.into(),
+ version: info.version.into(),
+ }
+ }
+}
+
+struct BuiltinDapp {
+ app: Arc,
+}
+
+impl BuiltinDapp {
+ fn new(app: Arc) -> Self {
+ BuiltinDapp {
+ app: app,
+ }
+ }
+}
+
+impl handler::Dapp for BuiltinDapp {
+ type DappFile = BuiltinDappFile;
+
+ fn file(&self, path: &str) -> Option {
+ self.app.file(path).map(|_| {
+ BuiltinDappFile {
+ app: self.app.clone(),
+ path: path.into(),
+ write_pos: 0,
+ }
+ })
+ }
+}
+
+struct BuiltinDappFile {
+ app: Arc,
+ path: String,
+ write_pos: usize,
+}
+
+impl BuiltinDappFile {
+ fn file(&self) -> &File {
+ self.app.file(&self.path).expect("Check is done when structure is created.")
+ }
+}
+
+impl handler::DappFile for BuiltinDappFile {
+ fn content_type(&self) -> &str {
+ self.file().content_type
+ }
+
+ fn is_drained(&self) -> bool {
+ self.write_pos == self.file().content.len()
+ }
+
+ fn next_chunk(&mut self) -> &[u8] {
+ &self.file().content[self.write_pos..]
+ }
+
+ fn bytes_written(&mut self, bytes: usize) {
+ self.write_pos += bytes;
+ }
+}
diff --git a/dapps/src/page/handler.rs b/dapps/src/page/handler.rs
new file mode 100644
index 000000000..5167c85c8
--- /dev/null
+++ b/dapps/src/page/handler.rs
@@ -0,0 +1,207 @@
+// 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 .
+
+use std::io::Write;
+use hyper::header;
+use hyper::server;
+use hyper::uri::RequestUri;
+use hyper::net::HttpStream;
+use hyper::status::StatusCode;
+use hyper::{Decoder, Encoder, Next};
+use endpoint::EndpointPath;
+
+/// Represents a file that can be sent to client.
+/// Implementation should keep track of bytes already sent internally.
+pub trait DappFile: Send {
+ /// Returns a content-type of this file.
+ fn content_type(&self) -> &str;
+
+ /// Checks if all bytes from that file were written.
+ fn is_drained(&self) -> bool;
+
+ /// Fetch next chunk to write to the client.
+ fn next_chunk(&mut self) -> &[u8];
+
+ /// How many files have been written to the client.
+ fn bytes_written(&mut self, bytes: usize);
+}
+
+/// Dapp as a (dynamic) set of files.
+pub trait Dapp: Send + 'static {
+ /// File type
+ type DappFile: DappFile;
+
+ /// Returns file under given path.
+ fn file(&self, path: &str) -> Option;
+}
+
+/// A handler for a single webapp.
+/// Resolves correct paths and serves as a plumbing code between
+/// hyper server and dapp.
+pub struct PageHandler {
+ /// A Dapp.
+ pub app: T,
+ /// File currently being served (or `None` if file does not exist).
+ pub file: Option,
+ /// Optional prefix to strip from path.
+ pub prefix: Option,
+ /// Requested path.
+ pub path: EndpointPath,
+ /// Flag indicating if the file can be safely embeded (put in iframe).
+ pub safe_to_embed: bool,
+}
+
+impl PageHandler {
+ fn extract_path(&self, path: &str) -> String {
+ let app_id = &self.path.app_id;
+ let prefix = "/".to_owned() + self.prefix.as_ref().unwrap_or(app_id);
+ let prefix_with_slash = prefix.clone() + "/";
+ let query_pos = path.find('?').unwrap_or_else(|| path.len());
+
+ // Index file support
+ match path == "/" || path == &prefix || path == &prefix_with_slash {
+ true => "index.html".to_owned(),
+ false => if path.starts_with(&prefix_with_slash) {
+ path[prefix_with_slash.len()..query_pos].to_owned()
+ } else if path.starts_with("/") {
+ path[1..query_pos].to_owned()
+ } else {
+ path[0..query_pos].to_owned()
+ }
+ }
+ }
+}
+
+impl server::Handler for PageHandler {
+ fn on_request(&mut self, req: server::Request) -> Next {
+ self.file = match *req.uri() {
+ RequestUri::AbsolutePath(ref path) => {
+ self.app.file(&self.extract_path(path))
+ },
+ RequestUri::AbsoluteUri(ref url) => {
+ self.app.file(&self.extract_path(url.path()))
+ },
+ _ => None,
+ };
+ Next::write()
+ }
+
+ fn on_request_readable(&mut self, _decoder: &mut Decoder) -> Next {
+ Next::write()
+ }
+
+ fn on_response(&mut self, res: &mut server::Response) -> Next {
+ if let Some(ref f) = self.file {
+ res.set_status(StatusCode::Ok);
+ res.headers_mut().set(header::ContentType(f.content_type().parse().unwrap()));
+ if !self.safe_to_embed {
+ res.headers_mut().set_raw("X-Frame-Options", vec![b"SAMEORIGIN".to_vec()]);
+ }
+ Next::write()
+ } else {
+ res.set_status(StatusCode::NotFound);
+ Next::write()
+ }
+ }
+
+ fn on_response_writable(&mut self, encoder: &mut Encoder) -> Next {
+ match self.file {
+ None => Next::end(),
+ Some(ref f) if f.is_drained() => Next::end(),
+ Some(ref mut f) => match encoder.write(f.next_chunk()) {
+ Ok(bytes) => {
+ f.bytes_written(bytes);
+ Next::write()
+ },
+ Err(e) => match e.kind() {
+ ::std::io::ErrorKind::WouldBlock => Next::write(),
+ _ => Next::end(),
+ },
+ }
+ }
+ }
+}
+
+
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ pub struct TestWebAppFile;
+
+ impl DappFile for TestWebAppFile {
+ fn content_type(&self) -> &str {
+ unimplemented!()
+ }
+
+ fn is_drained(&self) -> bool {
+ unimplemented!()
+ }
+
+ fn next_chunk(&mut self) -> &[u8] {
+ unimplemented!()
+ }
+
+ fn bytes_written(&mut self, _bytes: usize) {
+ unimplemented!()
+ }
+ }
+
+ #[derive(Default)]
+ pub struct TestWebapp;
+
+ impl Dapp for TestWebapp {
+ type DappFile = TestWebAppFile;
+
+ fn file(&self, _path: &str) -> Option {
+ None
+ }
+ }
+}
+
+#[test]
+fn should_extract_path_with_appid() {
+
+ // given
+ let path1 = "/";
+ let path2= "/test.css";
+ let path3 = "/app/myfile.txt";
+ let path4 = "/app/myfile.txt?query=123";
+ let page_handler = PageHandler {
+ app: test::TestWebapp,
+ prefix: None,
+ path: EndpointPath {
+ app_id: "app".to_owned(),
+ host: "".to_owned(),
+ port: 8080
+ },
+ file: None,
+ safe_to_embed: true,
+ };
+
+ // when
+ let res1 = page_handler.extract_path(path1);
+ let res2 = page_handler.extract_path(path2);
+ let res3 = page_handler.extract_path(path3);
+ let res4 = page_handler.extract_path(path4);
+
+ // then
+ assert_eq!(&res1, "index.html");
+ assert_eq!(&res2, "test.css");
+ assert_eq!(&res3, "myfile.txt");
+ assert_eq!(&res4, "myfile.txt");
+}
diff --git a/dapps/src/page/local.rs b/dapps/src/page/local.rs
new file mode 100644
index 000000000..52e32bf5e
--- /dev/null
+++ b/dapps/src/page/local.rs
@@ -0,0 +1,118 @@
+// 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 .
+
+use mime_guess;
+use std::io::{Seek, Read, SeekFrom};
+use std::fs;
+use std::path::PathBuf;
+use page::handler;
+use endpoint::{Endpoint, EndpointInfo, EndpointPath, Handler};
+
+pub struct LocalPageEndpoint {
+ path: PathBuf,
+ info: EndpointInfo,
+}
+
+impl LocalPageEndpoint {
+ pub fn new(path: PathBuf, info: EndpointInfo) -> Self {
+ LocalPageEndpoint {
+ path: path,
+ info: info,
+ }
+ }
+}
+
+impl Endpoint for LocalPageEndpoint {
+ fn info(&self) -> Option<&EndpointInfo> {
+ Some(&self.info)
+ }
+
+ fn to_handler(&self, path: EndpointPath) -> Box {
+ Box::new(handler::PageHandler {
+ app: LocalDapp::new(self.path.clone()),
+ prefix: None,
+ path: path,
+ file: None,
+ safe_to_embed: false,
+ })
+ }
+}
+
+struct LocalDapp {
+ path: PathBuf,
+}
+
+impl LocalDapp {
+ fn new(path: PathBuf) -> Self {
+ LocalDapp {
+ path: path
+ }
+ }
+}
+
+impl handler::Dapp for LocalDapp {
+ type DappFile = LocalFile;
+
+ fn file(&self, file_path: &str) -> Option {
+ let mut path = self.path.clone();
+ for part in file_path.split('/') {
+ path.push(part);
+ }
+ // Check if file exists
+ fs::File::open(path.clone()).ok().map(|file| {
+ let content_type = mime_guess::guess_mime_type(path);
+ let len = file.metadata().ok().map_or(0, |meta| meta.len());
+ LocalFile {
+ content_type: content_type.to_string(),
+ buffer: [0; 4096],
+ file: file,
+ pos: 0,
+ len: len,
+ }
+ })
+ }
+}
+
+struct LocalFile {
+ content_type: String,
+ buffer: [u8; 4096],
+ file: fs::File,
+ len: u64,
+ pos: u64,
+}
+
+impl handler::DappFile for LocalFile {
+ fn content_type(&self) -> &str {
+ &self.content_type
+ }
+
+ fn is_drained(&self) -> bool {
+ self.pos == self.len
+ }
+
+ fn next_chunk(&mut self) -> &[u8] {
+ let _ = self.file.seek(SeekFrom::Start(self.pos));
+ if let Ok(n) = self.file.read(&mut self.buffer) {
+ &self.buffer[0..n]
+ } else {
+ &self.buffer[0..0]
+ }
+ }
+
+ fn bytes_written(&mut self, bytes: usize) {
+ self.pos += bytes as u64;
+ }
+}
diff --git a/dapps/src/page/mod.rs b/dapps/src/page/mod.rs
index 819988310..349c979c7 100644
--- a/dapps/src/page/mod.rs
+++ b/dapps/src/page/mod.rs
@@ -14,216 +14,11 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see .
-use std::sync::Arc;
-use std::io::Write;
-use hyper::uri::RequestUri;
-use hyper::server;
-use hyper::header;
-use hyper::status::StatusCode;
-use hyper::net::HttpStream;
-use hyper::{Decoder, Encoder, Next};
-use endpoint::{Endpoint, EndpointInfo, EndpointPath};
-use parity_dapps::{WebApp, Info};
-pub struct PageEndpoint {
- /// Content of the files
- pub app: Arc,
- /// Prefix to strip from the path (when `None` deducted from `app_id`)
- pub prefix: Option,
- /// Safe to be loaded in frame by other origin. (use wisely!)
- safe_to_embed: bool,
-}
+mod builtin;
+mod local;
+mod handler;
-impl PageEndpoint {
- pub fn new(app: T) -> Self {
- PageEndpoint {
- app: Arc::new(app),
- prefix: None,
- safe_to_embed: false,
- }
- }
+pub use self::local::LocalPageEndpoint;
+pub use self::builtin::PageEndpoint;
- pub fn with_prefix(app: T, prefix: String) -> Self {
- PageEndpoint {
- app: Arc::new(app),
- prefix: Some(prefix),
- safe_to_embed: false,
- }
- }
-
- /// 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) -> Self {
- PageEndpoint {
- app: Arc::new(app),
- prefix: None,
- safe_to_embed: true,
- }
- }
-}
-
-impl Endpoint for PageEndpoint {
-
- fn info(&self) -> Option {
- Some(EndpointInfo::from(self.app.info()))
- }
-
- fn to_handler(&self, path: EndpointPath) -> Box> {
- Box::new(PageHandler {
- app: self.app.clone(),
- prefix: self.prefix.clone(),
- path: path,
- file: None,
- write_pos: 0,
- safe_to_embed: self.safe_to_embed,
- })
- }
-}
-
-impl From for EndpointInfo {
- fn from(info: Info) -> Self {
- EndpointInfo {
- name: info.name,
- description: info.description,
- author: info.author,
- icon_url: info.icon_url,
- version: info.version,
- }
- }
-}
-
-struct PageHandler {
- app: Arc,
- prefix: Option,
- path: EndpointPath,
- file: Option,
- write_pos: usize,
- safe_to_embed: bool,
-}
-
-impl PageHandler {
- fn extract_path(&self, path: &str) -> String {
- let app_id = &self.path.app_id;
- let prefix = "/".to_owned() + self.prefix.as_ref().unwrap_or(app_id);
- let prefix_with_slash = prefix.clone() + "/";
- let query_pos = path.find('?').unwrap_or_else(|| path.len());
-
- // Index file support
- match path == "/" || path == &prefix || path == &prefix_with_slash {
- true => "index.html".to_owned(),
- false => if path.starts_with(&prefix_with_slash) {
- path[prefix_with_slash.len()..query_pos].to_owned()
- } else if path.starts_with("/") {
- path[1..query_pos].to_owned()
- } else {
- path[0..query_pos].to_owned()
- }
- }
- }
-}
-
-impl server::Handler for PageHandler {
- fn on_request(&mut self, req: server::Request) -> Next {
- self.file = match *req.uri() {
- RequestUri::AbsolutePath(ref path) => {
- Some(self.extract_path(path))
- },
- RequestUri::AbsoluteUri(ref url) => {
- Some(self.extract_path(url.path()))
- },
- _ => None,
- };
- Next::write()
- }
-
- fn on_request_readable(&mut self, _decoder: &mut Decoder) -> Next {
- Next::write()
- }
-
- fn on_response(&mut self, res: &mut server::Response) -> Next {
- if let Some(f) = self.file.as_ref().and_then(|f| self.app.file(f)) {
- res.set_status(StatusCode::Ok);
- res.headers_mut().set(header::ContentType(f.content_type.parse().unwrap()));
- if !self.safe_to_embed {
- res.headers_mut().set_raw("X-Frame-Options", vec![b"SAMEORIGIN".to_vec()]);
- }
- Next::write()
- } else {
- res.set_status(StatusCode::NotFound);
- Next::write()
- }
- }
-
- fn on_response_writable(&mut self, encoder: &mut Encoder) -> Next {
- let (wrote, res) = {
- let file = self.file.as_ref().and_then(|f| self.app.file(f));
- match file {
- None => (None, Next::end()),
- Some(f) if self.write_pos == f.content.len() => (None, Next::end()),
- Some(f) => match encoder.write(&f.content[self.write_pos..]) {
- Ok(bytes) => (Some(bytes), Next::write()),
- Err(e) => match e.kind() {
- ::std::io::ErrorKind::WouldBlock => (None, Next::write()),
- _ => (None, Next::end())
- },
- }
- }
- };
- if let Some(bytes) = wrote {
- self.write_pos += bytes;
- }
- res
- }
-}
-
-
-#[cfg(test)]
-use parity_dapps::File;
-
-#[cfg(test)]
-#[derive(Default)]
-struct TestWebapp;
-
-#[cfg(test)]
-impl WebApp for TestWebapp {
- fn file(&self, _path: &str) -> Option<&File> {
- None
- }
- fn info(&self) -> Info {
- unimplemented!()
- }
-}
-
-#[test]
-fn should_extract_path_with_appid() {
- // given
- let path1 = "/";
- let path2= "/test.css";
- let path3 = "/app/myfile.txt";
- let path4 = "/app/myfile.txt?query=123";
- let page_handler = PageHandler {
- app: Arc::new(TestWebapp),
- prefix: None,
- path: EndpointPath {
- app_id: "app".to_owned(),
- host: "".to_owned(),
- port: 8080
- },
- file: None,
- write_pos: 0,
- safe_to_embed: true,
- };
-
- // when
- let res1 = page_handler.extract_path(path1);
- let res2 = page_handler.extract_path(path2);
- let res3 = page_handler.extract_path(path3);
- let res4 = page_handler.extract_path(path4);
-
- // then
- assert_eq!(&res1, "index.html");
- assert_eq!(&res2, "test.css");
- assert_eq!(&res3, "myfile.txt");
- assert_eq!(&res4, "myfile.txt");
-}
diff --git a/ethcore/src/account.rs b/ethcore/src/account.rs
index 7ba213393..5e0dc335a 100644
--- a/ethcore/src/account.rs
+++ b/ethcore/src/account.rs
@@ -51,8 +51,6 @@ impl Account {
}
}
- #[cfg(test)]
- #[cfg(feature = "json-tests")]
/// General constructor.
pub fn from_pod(pod: PodAccount) -> Account {
Account {
diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs
index 3ddf7018a..97f8dd5a4 100644
--- a/ethcore/src/client/client.rs
+++ b/ethcore/src/client/client.rs
@@ -37,7 +37,7 @@ use filter::Filter;
use log_entry::LocalizedLogEntry;
use block_queue::{BlockQueue, BlockQueueInfo};
use blockchain::{BlockChain, BlockProvider, TreeRoute, ImportRoute};
-use client::{BlockID, TransactionID, UncleID, TraceId, ClientConfig, BlockChainClient, MiningBlockChainClient, TraceFilter};
+use client::{BlockID, TransactionID, UncleID, TraceId, ClientConfig, BlockChainClient, MiningBlockChainClient, TraceFilter, CallAnalytics};
use client::Error as ClientError;
use env_info::EnvInfo;
use executive::{Executive, Executed, TransactOptions, contract_address};
@@ -429,14 +429,14 @@ impl Client where V: Verifier {
TransactionID::Hash(ref hash) => self.chain.transaction_address(hash),
TransactionID::Location(id, index) => Self::block_hash(&self.chain, id).map(|hash| TransactionAddress {
block_hash: hash,
- index: index
+ index: index,
})
}
}
}
impl BlockChainClient for Client where V: Verifier {
- fn call(&self, t: &SignedTransaction, vm_tracing: bool) -> Result {
+ fn call(&self, t: &SignedTransaction, analytics: CallAnalytics) -> Result {
let header = self.block_header(BlockID::Latest).unwrap();
let view = HeaderView::new(&header);
let last_hashes = self.build_last_hashes(view.hash());
@@ -456,11 +456,21 @@ impl BlockChainClient for Client where V: Verifier {
ExecutionError::TransactionMalformed(message)
}));
let balance = state.balance(&sender);
- // give the sender a decent balance
- state.sub_balance(&sender, &balance);
- state.add_balance(&sender, &(U256::from(1) << 200));
- let options = TransactOptions { tracing: false, vm_tracing: vm_tracing, check_nonce: false };
- Executive::new(&mut state, &env_info, self.engine.deref().deref(), &self.vm_factory).transact(t, options)
+ let needed_balance = t.value + t.gas * t.gas_price;
+ if balance < needed_balance {
+ // give the sender a sufficient balance
+ state.add_balance(&sender, &(needed_balance - balance));
+ }
+ let options = TransactOptions { tracing: false, vm_tracing: analytics.vm_tracing, check_nonce: false };
+ let mut ret = Executive::new(&mut state, &env_info, self.engine.deref().deref(), &self.vm_factory).transact(t, options);
+
+ // TODO gav move this into Executive.
+ if analytics.state_diffing {
+ if let Ok(ref mut x) = ret {
+ x.state_diff = Some(state.diff_from(self.state()));
+ }
+ }
+ ret
}
fn vm_factory(&self) -> &EvmFactory {
@@ -505,7 +515,6 @@ impl BlockChainClient for Client where V: Verifier {
self.state_at(id).map(|s| s.nonce(address))
}
-
fn block_hash(&self, id: BlockID) -> Option {
Self::block_hash(&self.chain, id)
}
diff --git a/ethcore/src/client/mod.rs b/ethcore/src/client/mod.rs
index 84eea7494..0dffb1a1c 100644
--- a/ethcore/src/client/mod.rs
+++ b/ethcore/src/client/mod.rs
@@ -49,6 +49,15 @@ use evm::Factory as EvmFactory;
use miner::{TransactionImportResult};
use error::Error as EthError;
+/// Options concerning what analytics we run on the call.
+#[derive(Eq, PartialEq, Default, Clone, Copy, Debug)]
+pub struct CallAnalytics {
+ /// Make a VM trace.
+ pub vm_tracing: bool,
+ /// Make a diff.
+ pub state_diffing: bool,
+}
+
/// Blockchain database client. Owns and manages a blockchain and a block queue.
pub trait BlockChainClient : Sync + Send {
/// Get raw block header data by block id.
@@ -158,7 +167,7 @@ pub trait BlockChainClient : Sync + Send {
/// Makes a non-persistent transaction call.
// TODO: should be able to accept blockchain location for call.
- fn call(&self, t: &SignedTransaction, vm_tracing: bool) -> Result;
+ fn call(&self, t: &SignedTransaction, analytics: CallAnalytics) -> Result;
/// Returns EvmFactory.
fn vm_factory(&self) -> &EvmFactory;
diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs
index f3768d0a6..17905a905 100644
--- a/ethcore/src/client/test_client.rs
+++ b/ethcore/src/client/test_client.rs
@@ -20,7 +20,7 @@ use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrder};
use util::*;
use transaction::{Transaction, LocalizedTransaction, SignedTransaction, Action};
use blockchain::TreeRoute;
-use client::{BlockChainClient, MiningBlockChainClient, BlockChainInfo, BlockStatus, BlockID, TransactionID, UncleID, TraceId, TraceFilter, LastHashes};
+use client::{BlockChainClient, MiningBlockChainClient, BlockChainInfo, BlockStatus, BlockID, TransactionID, UncleID, TraceId, TraceFilter, LastHashes, CallAnalytics};
use header::{Header as BlockHeader, BlockNumber};
use filter::Filter;
use log_entry::LocalizedLogEntry;
@@ -251,7 +251,7 @@ impl MiningBlockChainClient for TestBlockChainClient {
}
impl BlockChainClient for TestBlockChainClient {
- fn call(&self, _t: &SignedTransaction, _vm_tracing: bool) -> Result {
+ fn call(&self, _t: &SignedTransaction, _analytics: CallAnalytics) -> Result {
Ok(self.execution_result.read().unwrap().clone().unwrap())
}
diff --git a/ethcore/src/executive.rs b/ethcore/src/executive.rs
index f7615be09..4525d6a24 100644
--- a/ethcore/src/executive.rs
+++ b/ethcore/src/executive.rs
@@ -450,6 +450,7 @@ impl<'a> Executive<'a> {
output: output,
trace: trace,
vm_trace: vm_trace,
+ state_diff: None,
})
},
_ => {
@@ -463,6 +464,7 @@ impl<'a> Executive<'a> {
output: output,
trace: trace,
vm_trace: vm_trace,
+ state_diff: None,
})
},
}
diff --git a/ethcore/src/json_tests/state.rs b/ethcore/src/json_tests/state.rs
index 5cc611491..5307fb319 100644
--- a/ethcore/src/json_tests/state.rs
+++ b/ethcore/src/json_tests/state.rs
@@ -16,8 +16,7 @@
use super::test_common::*;
use tests::helpers::*;
-use pod_state::*;
-use state_diff::*;
+use pod_state::{self, PodState};
use ethereum;
use ethjson;
@@ -71,7 +70,7 @@ pub fn json_chain_test(json_data: &[u8], era: ChainEra) -> Vec {
let our_post = state.to_pod();
println!("Got:\n{}", our_post);
println!("Expect:\n{}", post);
- println!("Diff ---expect -> +++got:\n{}", StateDiff::diff_pod(&post, &our_post));
+ println!("Diff ---expect -> +++got:\n{}", pod_state::diff_pod(&post, &our_post));
}
if let Ok(r) = res {
diff --git a/ethcore/src/lib.rs b/ethcore/src/lib.rs
index cb7942f23..f69048a95 100644
--- a/ethcore/src/lib.rs
+++ b/ethcore/src/lib.rs
@@ -119,8 +119,6 @@ mod basic_types;
#[macro_use] mod evm;
mod env_info;
mod pod_account;
-mod account_diff;
-mod state_diff;
mod state;
mod account;
mod account_db;
diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs
index 46b5ae733..5dc3864c3 100644
--- a/ethcore/src/miner/miner.rs
+++ b/ethcore/src/miner/miner.rs
@@ -20,10 +20,9 @@ use std::sync::atomic::AtomicBool;
use util::*;
use util::keys::store::{AccountProvider};
use views::{BlockView, HeaderView};
-use client::{MiningBlockChainClient, BlockID};
+use client::{MiningBlockChainClient, Executive, Executed, EnvInfo, TransactOptions, BlockChainClient, BlockID, CallAnalytics};
use block::{ClosedBlock, IsBlock};
use error::*;
-use client::{Executive, Executed, EnvInfo, TransactOptions};
use transaction::SignedTransaction;
use receipt::{Receipt};
use spec::Spec;
@@ -251,11 +250,13 @@ impl MinerService for Miner {
}
}
- fn call(&self, chain: &MiningBlockChainClient, t: &SignedTransaction, vm_tracing: bool) -> Result {
+ fn call(&self, chain: &MiningBlockChainClient, t: &SignedTransaction, analytics: CallAnalytics) -> Result {
let sealing_work = self.sealing_work.lock().unwrap();
match sealing_work.peek_last_ref() {
Some(work) => {
let block = work.block();
+
+ // TODO: merge this code with client.rs's fn call somwhow.
let header = block.header();
let last_hashes = chain.last_hashes();
let env_info = EnvInfo {
@@ -274,16 +275,24 @@ impl MinerService for Miner {
ExecutionError::TransactionMalformed(message)
}));
let balance = state.balance(&sender);
- // give the sender max balance
- state.sub_balance(&sender, &balance);
- state.add_balance(&sender, &U256::max_value());
- let options = TransactOptions { tracing: false, vm_tracing: vm_tracing, check_nonce: false };
-
- // TODO: use vm_trace here.
- Executive::new(&mut state, &env_info, self.engine(), chain.vm_factory()).transact(t, options)
+ let needed_balance = t.value + t.gas * t.gas_price;
+ if balance < needed_balance {
+ // give the sender a sufficient balance
+ state.add_balance(&sender, &(needed_balance - balance));
+ }
+ let options = TransactOptions { tracing: false, vm_tracing: analytics.vm_tracing, check_nonce: false };
+ let mut ret = Executive::new(&mut state, &env_info, self.engine(), chain.vm_factory()).transact(t, options);
+
+ // TODO gav move this into Executive.
+ if analytics.state_diffing {
+ if let Ok(ref mut x) = ret {
+ x.state_diff = Some(state.diff_from(block.state().clone()));
+ }
+ }
+ ret
},
None => {
- chain.call(t, vm_tracing)
+ chain.call(t, analytics)
}
}
}
diff --git a/ethcore/src/miner/mod.rs b/ethcore/src/miner/mod.rs
index 8165dfbba..71727f51d 100644
--- a/ethcore/src/miner/mod.rs
+++ b/ethcore/src/miner/mod.rs
@@ -51,7 +51,7 @@ pub use self::external::{ExternalMiner, ExternalMinerService};
use std::collections::BTreeMap;
use util::{H256, U256, Address, Bytes};
-use client::{MiningBlockChainClient, Executed};
+use client::{MiningBlockChainClient, Executed, CallAnalytics};
use block::ClosedBlock;
use receipt::Receipt;
use error::{Error, ExecutionError};
@@ -148,7 +148,7 @@ pub trait MinerService : Send + Sync {
fn balance(&self, chain: &MiningBlockChainClient, address: &Address) -> U256;
/// Call into contract code using pending state.
- fn call(&self, chain: &MiningBlockChainClient, t: &SignedTransaction, vm_tracing: bool) -> Result;
+ fn call(&self, chain: &MiningBlockChainClient, t: &SignedTransaction, analytics: CallAnalytics) -> Result;
/// Get storage value in pending state.
fn storage_at(&self, chain: &MiningBlockChainClient, address: &Address, position: &H256) -> H256;
diff --git a/ethcore/src/pod_account.rs b/ethcore/src/pod_account.rs
index 65146296a..d04833cab 100644
--- a/ethcore/src/pod_account.rs
+++ b/ethcore/src/pod_account.rs
@@ -18,6 +18,7 @@ use util::*;
use account::*;
use account_db::*;
use ethjson;
+use types::account_diff::*;
#[derive(Debug, Clone, PartialEq, Eq)]
/// An account, expressed as Plain-Old-Data (hence the name).
@@ -106,17 +107,58 @@ impl fmt::Display for PodAccount {
}
}
+/// Determine difference between two optionally existant `Account`s. Returns None
+/// if they are the same.
+pub fn diff_pod(pre: Option<&PodAccount>, post: Option<&PodAccount>) -> Option {
+ match (pre, post) {
+ (None, Some(x)) => Some(AccountDiff {
+ balance: Diff::Born(x.balance),
+ nonce: Diff::Born(x.nonce),
+ code: Diff::Born(x.code.clone()),
+ storage: x.storage.iter().map(|(k, v)| (k.clone(), Diff::Born(v.clone()))).collect(),
+ }),
+ (Some(x), None) => Some(AccountDiff {
+ balance: Diff::Died(x.balance),
+ nonce: Diff::Died(x.nonce),
+ code: Diff::Died(x.code.clone()),
+ storage: x.storage.iter().map(|(k, v)| (k.clone(), Diff::Died(v.clone()))).collect(),
+ }),
+ (Some(pre), Some(post)) => {
+ let storage: Vec<_> = pre.storage.keys().merge(post.storage.keys())
+ .filter(|k| pre.storage.get(k).unwrap_or(&H256::new()) != post.storage.get(k).unwrap_or(&H256::new()))
+ .collect();
+ let r = AccountDiff {
+ balance: Diff::new(pre.balance, post.balance),
+ nonce: Diff::new(pre.nonce, post.nonce),
+ code: Diff::new(pre.code.clone(), post.code.clone()),
+ storage: storage.into_iter().map(|k|
+ (k.clone(), Diff::new(
+ pre.storage.get(&k).cloned().unwrap_or_else(H256::new),
+ post.storage.get(&k).cloned().unwrap_or_else(H256::new)
+ ))).collect(),
+ };
+ if r.balance.is_same() && r.nonce.is_same() && r.code.is_same() && r.storage.is_empty() {
+ None
+ } else {
+ Some(r)
+ }
+ },
+ _ => None,
+ }
+}
+
+
#[cfg(test)]
mod test {
use common::*;
- use account_diff::*;
- use super::*;
+ use types::account_diff::*;
+ use super::{PodAccount, diff_pod};
#[test]
fn existence() {
let a = PodAccount{balance: 69.into(), nonce: 0.into(), code: vec![], storage: map![]};
- assert_eq!(AccountDiff::diff_pod(Some(&a), Some(&a)), None);
- assert_eq!(AccountDiff::diff_pod(None, Some(&a)), Some(AccountDiff{
+ assert_eq!(diff_pod(Some(&a), Some(&a)), None);
+ assert_eq!(diff_pod(None, Some(&a)), Some(AccountDiff{
balance: Diff::Born(69.into()),
nonce: Diff::Born(0.into()),
code: Diff::Born(vec![]),
@@ -128,7 +170,7 @@ mod test {
fn basic() {
let a = PodAccount{balance: 69.into(), nonce: 0.into(), code: vec![], storage: map![]};
let b = PodAccount{balance: 42.into(), nonce: 1.into(), code: vec![], storage: map![]};
- assert_eq!(AccountDiff::diff_pod(Some(&a), Some(&b)), Some(AccountDiff {
+ assert_eq!(diff_pod(Some(&a), Some(&b)), Some(AccountDiff {
balance: Diff::Changed(69.into(), 42.into()),
nonce: Diff::Changed(0.into(), 1.into()),
code: Diff::Same,
@@ -140,7 +182,7 @@ mod test {
fn code() {
let a = PodAccount{balance: 0.into(), nonce: 0.into(), code: vec![], storage: map![]};
let b = PodAccount{balance: 0.into(), nonce: 1.into(), code: vec![0], storage: map![]};
- assert_eq!(AccountDiff::diff_pod(Some(&a), Some(&b)), Some(AccountDiff {
+ assert_eq!(diff_pod(Some(&a), Some(&b)), Some(AccountDiff {
balance: Diff::Same,
nonce: Diff::Changed(0.into(), 1.into()),
code: Diff::Changed(vec![], vec![0]),
@@ -162,7 +204,7 @@ mod test {
code: vec![],
storage: map_into![1 => 1, 2 => 3, 3 => 0, 5 => 0, 7 => 7, 8 => 0, 9 => 9]
};
- assert_eq!(AccountDiff::diff_pod(Some(&a), Some(&b)), Some(AccountDiff {
+ assert_eq!(diff_pod(Some(&a), Some(&b)), Some(AccountDiff {
balance: Diff::Same,
nonce: Diff::Same,
code: Diff::Same,
diff --git a/ethcore/src/pod_state.rs b/ethcore/src/pod_state.rs
index d9d0cf764..76dac214b 100644
--- a/ethcore/src/pod_state.rs
+++ b/ethcore/src/pod_state.rs
@@ -17,7 +17,8 @@
//! State of all accounts in the system expressed in Plain Old Data.
use util::*;
-use pod_account::*;
+use pod_account::{self, PodAccount};
+use types::state_diff::StateDiff;
use ethjson;
/// State of all accounts in the system expressed in Plain Old Data.
@@ -29,7 +30,6 @@ impl PodState {
pub fn new() -> PodState { Default::default() }
/// Contruct a new object from the `m`.
- #[cfg(test)]
pub fn from(m: BTreeMap) -> PodState { PodState(m) }
/// Get the underlying map.
@@ -41,8 +41,6 @@ impl PodState {
}
/// Drain object to get the underlying map.
- #[cfg(test)]
- #[cfg(feature = "json-tests")]
pub fn drain(self) -> BTreeMap { self.0 }
}
@@ -72,3 +70,83 @@ impl fmt::Display for PodState {
}
}
+/// Calculate and return diff between `pre` state and `post` state.
+pub fn diff_pod(pre: &PodState, post: &PodState) -> StateDiff {
+ StateDiff(pre.get().keys().merge(post.get().keys()).filter_map(|acc| pod_account::diff_pod(pre.get().get(acc), post.get().get(acc)).map(|d|(acc.clone(), d))).collect())
+}
+
+#[cfg(test)]
+mod test {
+ use common::*;
+ use types::state_diff::*;
+ use types::account_diff::*;
+ use pod_account::PodAccount;
+ use super::PodState;
+
+ #[test]
+ fn create_delete() {
+ let a = PodState::from(map![ 1.into() => PodAccount::new(69.into(), 0.into(), vec![], map![]) ]);
+ assert_eq!(super::diff_pod(&a, &PodState::new()), StateDiff(map![
+ 1.into() => AccountDiff{
+ balance: Diff::Died(69.into()),
+ nonce: Diff::Died(0.into()),
+ code: Diff::Died(vec![]),
+ storage: map![],
+ }
+ ]));
+ assert_eq!(super::diff_pod(&PodState::new(), &a), StateDiff(map![
+ 1.into() => AccountDiff{
+ balance: Diff::Born(69.into()),
+ nonce: Diff::Born(0.into()),
+ code: Diff::Born(vec![]),
+ storage: map![],
+ }
+ ]));
+ }
+
+ #[test]
+ fn create_delete_with_unchanged() {
+ let a = PodState::from(map![ 1.into() => PodAccount::new(69.into(), 0.into(), vec![], map![]) ]);
+ let b = PodState::from(map![
+ 1.into() => PodAccount::new(69.into(), 0.into(), vec![], map![]),
+ 2.into() => PodAccount::new(69.into(), 0.into(), vec![], map![])
+ ]);
+ assert_eq!(super::diff_pod(&a, &b), StateDiff(map![
+ 2.into() => AccountDiff{
+ balance: Diff::Born(69.into()),
+ nonce: Diff::Born(0.into()),
+ code: Diff::Born(vec![]),
+ storage: map![],
+ }
+ ]));
+ assert_eq!(super::diff_pod(&b, &a), StateDiff(map![
+ 2.into() => AccountDiff{
+ balance: Diff::Died(69.into()),
+ nonce: Diff::Died(0.into()),
+ code: Diff::Died(vec![]),
+ storage: map![],
+ }
+ ]));
+ }
+
+ #[test]
+ fn change_with_unchanged() {
+ let a = PodState::from(map![
+ 1.into() => PodAccount::new(69.into(), 0.into(), vec![], map![]),
+ 2.into() => PodAccount::new(69.into(), 0.into(), vec![], map![])
+ ]);
+ let b = PodState::from(map![
+ 1.into() => PodAccount::new(69.into(), 1.into(), vec![], map![]),
+ 2.into() => PodAccount::new(69.into(), 0.into(), vec![], map![])
+ ]);
+ assert_eq!(super::diff_pod(&a, &b), StateDiff(map![
+ 1.into() => AccountDiff{
+ balance: Diff::Same,
+ nonce: Diff::Changed(0.into(), 1.into()),
+ code: Diff::Same,
+ storage: map![],
+ }
+ ]));
+ }
+
+}
diff --git a/ethcore/src/state.rs b/ethcore/src/state.rs
index 51162bb8e..06a365bdd 100644
--- a/ethcore/src/state.rs
+++ b/ethcore/src/state.rs
@@ -20,13 +20,9 @@ use executive::{Executive, TransactOptions};
use evm::Factory as EvmFactory;
use account_db::*;
use trace::Trace;
-#[cfg(test)]
-#[cfg(feature = "json-tests")]
use pod_account::*;
-#[cfg(test)]
-#[cfg(feature = "json-tests")]
-use pod_state::PodState;
-//use state_diff::*; // TODO: uncomment once to_pod() works correctly.
+use pod_state::{self, PodState};
+use types::state_diff::StateDiff;
/// Used to return information about an `State::apply` operation.
pub struct ApplyOutcome {
@@ -224,7 +220,7 @@ impl State {
let e = try!(Executive::new(self, env_info, engine, vm_factory).transact(t, options));
// TODO uncomment once to_pod() works correctly.
-// trace!("Applied transaction. Diff:\n{}\n", StateDiff::diff_pod(&old, &self.to_pod()));
+// trace!("Applied transaction. Diff:\n{}\n", state_diff::diff_pod(&old, &self.to_pod()));
self.commit();
let receipt = Receipt::new(self.root().clone(), e.cumulative_gas_used, e.logs);
// trace!("Transaction receipt: {:?}", receipt);
@@ -275,12 +271,11 @@ impl State {
}
}
- #[cfg(test)]
- #[cfg(feature = "json-tests")]
/// Populate a PodAccount map from this state.
pub fn to_pod(&self) -> PodState {
assert!(self.snapshots.borrow().is_empty());
// TODO: handle database rather than just the cache.
+ // will need fat db.
PodState::from(self.cache.borrow().iter().fold(BTreeMap::new(), |mut m, (add, opt)| {
if let Some(ref acc) = *opt {
m.insert(add.clone(), PodAccount::from_account(acc));
@@ -289,6 +284,25 @@ impl State {
}))
}
+ fn query_pod(&mut self, query: &PodState) {
+ for (ref address, ref pod_account) in query.get() {
+ if self.get(address, true).is_some() {
+ for (ref key, _) in &pod_account.storage {
+ self.storage_at(address, key);
+ }
+ }
+ }
+ }
+
+ /// Returns a `StateDiff` describing the difference from `orig` to `self`.
+ /// Consumes self.
+ pub fn diff_from(&self, orig: State) -> StateDiff {
+ let pod_state_post = self.to_pod();
+ let mut state_pre = orig;
+ state_pre.query_pod(&pod_state_post);
+ pod_state::diff_pod(&state_pre.to_pod(), &pod_state_post)
+ }
+
/// Pull account `a` in our cache from the trie DB and return it.
/// `require_code` requires that the code be cached, too.
fn get<'a>(&'a self, a: &Address, require_code: bool) -> &'a Option {
diff --git a/ethcore/src/account_diff.rs b/ethcore/src/types/account_diff.rs
similarity index 68%
rename from ethcore/src/account_diff.rs
rename to ethcore/src/types/account_diff.rs
index c02b7ec7b..49fc51110 100644
--- a/ethcore/src/account_diff.rs
+++ b/ethcore/src/types/account_diff.rs
@@ -17,10 +17,48 @@
//! Diff between two accounts.
use util::*;
-#[cfg(test)]
-use pod_account::*;
-#[derive(Debug,Clone,PartialEq,Eq)]
+#[derive(Debug, PartialEq, Eq, Clone)]
+/// Diff type for specifying a change (or not).
+pub enum Diff where T: Eq {
+ /// Both sides are the same.
+ Same,
+ /// Left (pre, source) side doesn't include value, right side (post, destination) does.
+ Born(T),
+ /// Both sides include data; it chaged value between them.
+ Changed(T, T),
+ /// Left (pre, source) side does include value, right side (post, destination) does not.
+ Died(T),
+}
+
+impl Diff where T: Eq {
+ /// Construct new object with given `pre` and `post`.
+ pub fn new(pre: T, post: T) -> Self { if pre == post { Diff::Same } else { Diff::Changed(pre, post) } }
+
+ /// Get the before value, if there is one.
+ pub fn pre(&self) -> Option<&T> { match *self { Diff::Died(ref x) | Diff::Changed(ref x, _) => Some(x), _ => None } }
+
+ /// Get the after value, if there is one.
+ pub fn post(&self) -> Option<&T> { match *self { Diff::Born(ref x) | Diff::Changed(_, ref x) => Some(x), _ => None } }
+
+ /// Determine whether there was a change or not.
+ pub fn is_same(&self) -> bool { match *self { Diff::Same => true, _ => false }}
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+/// Account diff.
+pub struct AccountDiff {
+ /// Change in balance, allowed to be `Diff::Same`.
+ pub balance: Diff,
+ /// Change in nonce, allowed to be `Diff::Same`.
+ pub nonce: Diff, // Allowed to be Same
+ /// Change in code, allowed to be `Diff::Same`.
+ pub code: Diff, // Allowed to be Same
+ /// Change in storage, values are not allowed to be `Diff::Same`.
+ pub storage: BTreeMap>,
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
/// Change in existance type.
// TODO: include other types of change.
pub enum Existance {
@@ -43,19 +81,6 @@ impl fmt::Display for Existance {
}
}
-#[derive(Debug,Clone,PartialEq,Eq)]
-/// Account diff.
-pub struct AccountDiff {
- /// Change in balance, allowed to be `Diff::Same`.
- pub balance: Diff,
- /// Change in nonce, allowed to be `Diff::Same`.
- pub nonce: Diff, // Allowed to be Same
- /// Change in code, allowed to be `Diff::Same`.
- pub code: Diff, // Allowed to be Same
- /// Change in storage, values are not allowed to be `Diff::Same`.
- pub storage: BTreeMap>,
-}
-
impl AccountDiff {
/// Get `Existance` projection.
pub fn existance(&self) -> Existance {
@@ -65,47 +90,6 @@ impl AccountDiff {
_ => Existance::Alive,
}
}
-
- #[cfg(test)]
- /// Determine difference between two optionally existant `Account`s. Returns None
- /// if they are the same.
- pub fn diff_pod(pre: Option<&PodAccount>, post: Option<&PodAccount>) -> Option {
- match (pre, post) {
- (None, Some(x)) => Some(AccountDiff {
- balance: Diff::Born(x.balance),
- nonce: Diff::Born(x.nonce),
- code: Diff::Born(x.code.clone()),
- storage: x.storage.iter().map(|(k, v)| (k.clone(), Diff::Born(v.clone()))).collect(),
- }),
- (Some(x), None) => Some(AccountDiff {
- balance: Diff::Died(x.balance),
- nonce: Diff::Died(x.nonce),
- code: Diff::Died(x.code.clone()),
- storage: x.storage.iter().map(|(k, v)| (k.clone(), Diff::Died(v.clone()))).collect(),
- }),
- (Some(pre), Some(post)) => {
- let storage: Vec<_> = pre.storage.keys().merge(post.storage.keys())
- .filter(|k| pre.storage.get(k).unwrap_or(&H256::new()) != post.storage.get(k).unwrap_or(&H256::new()))
- .collect();
- let r = AccountDiff {
- balance: Diff::new(pre.balance, post.balance),
- nonce: Diff::new(pre.nonce, post.nonce),
- code: Diff::new(pre.code.clone(), post.code.clone()),
- storage: storage.into_iter().map(|k|
- (k.clone(), Diff::new(
- pre.storage.get(&k).cloned().unwrap_or_else(H256::new),
- post.storage.get(&k).cloned().unwrap_or_else(H256::new)
- ))).collect(),
- };
- if r.balance.is_same() && r.nonce.is_same() && r.code.is_same() && r.storage.is_empty() {
- None
- } else {
- Some(r)
- }
- },
- _ => None,
- }
- }
}
// TODO: refactor into something nicer.
diff --git a/ethcore/src/types/executed.rs b/ethcore/src/types/executed.rs
index 823c9dda1..4d31b9fe5 100644
--- a/ethcore/src/types/executed.rs
+++ b/ethcore/src/types/executed.rs
@@ -20,13 +20,14 @@ use util::numbers::*;
use util::Bytes;
use trace::{Trace, VMTrace};
use types::log_entry::LogEntry;
+use types::state_diff::StateDiff;
use ipc::binary::BinaryConvertError;
use std::fmt;
use std::mem;
use std::collections::VecDeque;
/// Transaction execution receipt.
-#[derive(Debug, PartialEq, Clone, Binary)]
+#[derive(Debug, PartialEq, Clone)]
pub struct Executed {
/// Gas paid up front for execution of transaction.
pub gas: U256,
@@ -61,6 +62,8 @@ pub struct Executed {
pub trace: Option,
/// The VM trace of this transaction.
pub vm_trace: Option,
+ /// The state diff, if we traced it.
+ pub state_diff: Option,
}
/// Result of executing the transaction.
diff --git a/ethcore/src/types/mod.rs.in b/ethcore/src/types/mod.rs.in
index 1f67a9184..b51e9e57b 100644
--- a/ethcore/src/types/mod.rs.in
+++ b/ethcore/src/types/mod.rs.in
@@ -23,3 +23,5 @@ pub mod log_entry;
pub mod trace_types;
pub mod executed;
pub mod block_status;
+pub mod account_diff;
+pub mod state_diff;
diff --git a/ethcore/src/types/state_diff.rs b/ethcore/src/types/state_diff.rs
new file mode 100644
index 000000000..4257d5b07
--- /dev/null
+++ b/ethcore/src/types/state_diff.rs
@@ -0,0 +1,49 @@
+// 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 .
+
+//! State diff module.
+
+use util::*;
+use account_diff::*;
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+/// Expression for the delta between two system states. Encoded the
+/// delta of every altered account.
+pub struct StateDiff (pub BTreeMap);
+
+impl StateDiff {
+ /// Get the actual data.
+ pub fn get(&self) -> &BTreeMap {
+ &self.0
+ }
+}
+
+impl fmt::Display for StateDiff {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ for (add, acc) in &self.0 {
+ try!(write!(f, "{} {}: {}", acc.existance(), add, acc));
+ }
+ Ok(())
+ }
+}
+
+impl Deref for StateDiff {
+ type Target = BTreeMap;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
diff --git a/parity/cli.rs b/parity/cli.rs
index 95b77a00d..bb67ee5c6 100644
--- a/parity/cli.rs
+++ b/parity/cli.rs
@@ -96,6 +96,8 @@ API and Console Options:
asked for password on startup.
--dapps-pass PASSWORD Specify password for Dapps server. Use only in
conjunction with --dapps-user.
+ --dapps-path PATH Specify directory where dapps should be installed.
+ [default: $HOME/.parity/dapps]
--signer Enable Trusted Signer WebSocket endpoint used by
System UIs.
@@ -239,6 +241,7 @@ pub struct Args {
pub flag_dapps_interface: String,
pub flag_dapps_user: Option,
pub flag_dapps_pass: Option,
+ pub flag_dapps_path: String,
pub flag_signer: bool,
pub flag_signer_port: u16,
pub flag_force_sealing: bool,
diff --git a/parity/configuration.rs b/parity/configuration.rs
index 61a571ae1..6185d2cf7 100644
--- a/parity/configuration.rs
+++ b/parity/configuration.rs
@@ -40,6 +40,7 @@ pub struct Configuration {
pub struct Directories {
pub keys: String,
pub db: String,
+ pub dapps: String,
}
impl Configuration {
@@ -325,11 +326,14 @@ impl Configuration {
&self.args.flag_keys_path
}
);
- ::std::fs::create_dir_all(&db_path).unwrap_or_else(|e| die_with_io_error("main", e));
+ ::std::fs::create_dir_all(&keys_path).unwrap_or_else(|e| die_with_io_error("main", e));
+ let dapps_path = Configuration::replace_home(&self.args.flag_dapps_path);
+ ::std::fs::create_dir_all(&dapps_path).unwrap_or_else(|e| die_with_io_error("main", e));
Directories {
keys: keys_path,
db: db_path,
+ dapps: dapps_path,
}
}
diff --git a/parity/dapps.rs b/parity/dapps.rs
index 91742d9e3..59a9ee552 100644
--- a/parity/dapps.rs
+++ b/parity/dapps.rs
@@ -32,6 +32,7 @@ pub struct Configuration {
pub port: u16,
pub user: Option,
pub pass: Option,
+ pub dapps_path: String,
}
pub struct Dependencies {
@@ -63,12 +64,13 @@ pub fn new(configuration: Configuration, deps: Dependencies) -> Option,
) -> ! {
@@ -78,12 +80,13 @@ pub fn setup_dapps_server(
#[cfg(feature = "dapps")]
pub fn setup_dapps_server(
deps: Dependencies,
+ dapps_path: String,
url: &SocketAddr,
auth: Option<(String, String)>
) -> WebappServer {
use ethcore_dapps as dapps;
- let server = dapps::ServerBuilder::new();
+ let server = dapps::ServerBuilder::new(dapps_path);
let server = rpc_apis::setup_rpc(server, deps.apis.clone(), rpc_apis::ApiSet::UnsafeContext);
let start_result = match auth {
None => {
diff --git a/parity/main.rs b/parity/main.rs
index ca5e39a1a..74d14bfd6 100644
--- a/parity/main.rs
+++ b/parity/main.rs
@@ -231,6 +231,7 @@ fn execute_client(conf: Configuration, spec: Spec, client_config: ClientConfig)
port: conf.args.flag_dapps_port,
user: conf.args.flag_dapps_user.clone(),
pass: conf.args.flag_dapps_pass.clone(),
+ dapps_path: conf.directories().dapps,
}, dapps::Dependencies {
panic_handler: panic_handler.clone(),
apis: deps_for_rpc_apis.clone(),
diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs
index ec98d341e..b0d6abcf0 100644
--- a/rpc/src/v1/impls/eth.rs
+++ b/rpc/src/v1/impls/eth.rs
@@ -511,8 +511,8 @@ impl Eth for EthClient where
.and_then(|(request, block_number,)| {
let signed = try!(self.sign_call(request));
let r = match block_number {
- BlockNumber::Pending => take_weak!(self.miner).call(take_weak!(self.client).deref(), &signed, false),
- BlockNumber::Latest => take_weak!(self.client).call(&signed, false),
+ BlockNumber::Pending => take_weak!(self.miner).call(take_weak!(self.client).deref(), &signed, Default::default()),
+ BlockNumber::Latest => take_weak!(self.client).call(&signed, Default::default()),
_ => panic!("{:?}", block_number),
};
to_value(&r.map(|e| Bytes(e.output)).unwrap_or(Bytes::new(vec![])))
@@ -524,8 +524,8 @@ impl Eth for EthClient where
.and_then(|(request, block_number,)| {
let signed = try!(self.sign_call(request));
let r = match block_number {
- BlockNumber::Pending => take_weak!(self.miner).call(take_weak!(self.client).deref(), &signed, false),
- BlockNumber::Latest => take_weak!(self.client).call(&signed, false),
+ BlockNumber::Pending => take_weak!(self.miner).call(take_weak!(self.client).deref(), &signed, Default::default()),
+ BlockNumber::Latest => take_weak!(self.client).call(&signed, Default::default()),
_ => return Err(Error::invalid_params()),
};
to_value(&r.map(|res| res.gas_used + res.refunded).unwrap_or(From::from(0)))
diff --git a/rpc/src/v1/impls/ethcore.rs b/rpc/src/v1/impls/ethcore.rs
index ca14ca021..69d036500 100644
--- a/rpc/src/v1/impls/ethcore.rs
+++ b/rpc/src/v1/impls/ethcore.rs
@@ -22,9 +22,12 @@ use std::sync::{Arc, Weak};
use std::ops::Deref;
use std::collections::BTreeMap;
use jsonrpc_core::*;
+use serde;
use ethcore::miner::MinerService;
+use ethcore::state_diff::StateDiff;
+use ethcore::account_diff::{Diff, Existance};
use ethcore::transaction::{Transaction as EthTransaction, SignedTransaction, Action};
-use ethcore::client::BlockChainClient;
+use ethcore::client::{BlockChainClient, CallAnalytics};
use ethcore::trace::VMTrace;
use v1::traits::Ethcore;
use v1::types::{Bytes, CallRequest};
@@ -115,6 +118,47 @@ fn vm_trace_to_object(t: &VMTrace) -> Value {
Value::Object(ret)
}
+fn diff_to_object(d: &Diff) -> Value where T: serde::Serialize + Eq {
+ let mut ret = BTreeMap::new();
+ match *d {
+ Diff::Same => {
+ ret.insert("diff".to_owned(), Value::String("=".to_owned()));
+ }
+ Diff::Born(ref x) => {
+ ret.insert("diff".to_owned(), Value::String("+".to_owned()));
+ ret.insert("+".to_owned(), to_value(x).unwrap());
+ }
+ Diff::Died(ref x) => {
+ ret.insert("diff".to_owned(), Value::String("-".to_owned()));
+ ret.insert("-".to_owned(), to_value(x).unwrap());
+ }
+ Diff::Changed(ref from, ref to) => {
+ ret.insert("diff".to_owned(), Value::String("*".to_owned()));
+ ret.insert("-".to_owned(), to_value(from).unwrap());
+ ret.insert("+".to_owned(), to_value(to).unwrap());
+ }
+ };
+ Value::Object(ret)
+}
+
+fn state_diff_to_object(t: &StateDiff) -> Value {
+ Value::Object(t.iter().map(|(address, account)| {
+ (address.hex(), Value::Object(map![
+ "existance".to_owned() => Value::String(match account.existance() {
+ Existance::Born => "+",
+ Existance::Alive => ".",
+ Existance::Died => "-",
+ }.to_owned()),
+ "balance".to_owned() => diff_to_object(&account.balance),
+ "nonce".to_owned() => diff_to_object(&account.nonce),
+ "code".to_owned() => diff_to_object(&account.code),
+ "storage".to_owned() => Value::Object(account.storage.iter().map(|(key, val)| {
+ (key.hex(), diff_to_object(&val))
+ }).collect::>())
+ ]))
+ }).collect::>())
+}
+
impl Ethcore for EthcoreClient where C: BlockChainClient + 'static, M: MinerService + 'static {
fn set_min_gas_price(&self, params: Params) -> Result {
@@ -211,7 +255,7 @@ impl Ethcore for EthcoreClient where C: BlockChainClient + 'static,
from_params(params)
.and_then(|(request,)| {
let signed = try!(self.sign_call(request));
- let r = take_weak!(self.client).call(&signed, true);
+ let r = take_weak!(self.client).call(&signed, CallAnalytics{ vm_tracing: true, state_diffing: false });
if let Ok(executed) = r {
if let Some(vm_trace) = executed.vm_trace {
return Ok(vm_trace_to_object(&vm_trace));
@@ -220,4 +264,19 @@ impl Ethcore for EthcoreClient where C: BlockChainClient + 'static,
Ok(Value::Null)
})
}
+
+ fn state_diff_call(&self, params: Params) -> Result {
+ trace!(target: "jsonrpc", "state_diff_call: {:?}", params);
+ from_params(params)
+ .and_then(|(request,)| {
+ let signed = try!(self.sign_call(request));
+ let r = take_weak!(self.client).call(&signed, CallAnalytics{ vm_tracing: false, state_diffing: true });
+ if let Ok(executed) = r {
+ if let Some(state_diff) = executed.state_diff {
+ return Ok(state_diff_to_object(&state_diff));
+ }
+ }
+ Ok(Value::Null)
+ })
+ }
}
diff --git a/rpc/src/v1/tests/helpers/miner_service.rs b/rpc/src/v1/tests/helpers/miner_service.rs
index 0f9327887..daa5c9497 100644
--- a/rpc/src/v1/tests/helpers/miner_service.rs
+++ b/rpc/src/v1/tests/helpers/miner_service.rs
@@ -19,7 +19,7 @@
use util::{Address, H256, Bytes, U256, FixedHash, Uint};
use util::standard::*;
use ethcore::error::{Error, ExecutionError};
-use ethcore::client::{MiningBlockChainClient, Executed};
+use ethcore::client::{MiningBlockChainClient, Executed, CallAnalytics};
use ethcore::block::{ClosedBlock, IsBlock};
use ethcore::transaction::SignedTransaction;
use ethcore::receipt::Receipt;
@@ -202,7 +202,7 @@ impl MinerService for TestMinerService {
self.latest_closed_block.lock().unwrap().as_ref().map_or_else(U256::zero, |b| b.block().fields().state.balance(address).clone())
}
- fn call(&self, _chain: &MiningBlockChainClient, _t: &SignedTransaction, _vm_tracing: bool) -> Result {
+ fn call(&self, _chain: &MiningBlockChainClient, _t: &SignedTransaction, _analytics: CallAnalytics) -> Result {
unimplemented!();
}
diff --git a/rpc/src/v1/tests/mocked/eth.rs b/rpc/src/v1/tests/mocked/eth.rs
index 4f43be827..34708e232 100644
--- a/rpc/src/v1/tests/mocked/eth.rs
+++ b/rpc/src/v1/tests/mocked/eth.rs
@@ -431,6 +431,7 @@ fn rpc_eth_call() {
output: vec![0x12, 0x34, 0xff],
trace: None,
vm_trace: None,
+ state_diff: None,
});
let request = r#"{
@@ -465,6 +466,7 @@ fn rpc_eth_call_default_block() {
output: vec![0x12, 0x34, 0xff],
trace: None,
vm_trace: None,
+ state_diff: None,
});
let request = r#"{
@@ -498,6 +500,7 @@ fn rpc_eth_estimate_gas() {
output: vec![0x12, 0x34, 0xff],
trace: None,
vm_trace: None,
+ state_diff: None,
});
let request = r#"{
@@ -532,6 +535,7 @@ fn rpc_eth_estimate_gas_default_block() {
output: vec![0x12, 0x34, 0xff],
trace: None,
vm_trace: None,
+ state_diff: None,
});
let request = r#"{
diff --git a/rpc/src/v1/traits/ethcore.rs b/rpc/src/v1/traits/ethcore.rs
index abc87bbb8..116a4cd05 100644
--- a/rpc/src/v1/traits/ethcore.rs
+++ b/rpc/src/v1/traits/ethcore.rs
@@ -75,6 +75,9 @@ pub trait Ethcore: Sized + Send + Sync + 'static {
/// Executes the given call and returns the VM trace for it.
fn vm_trace_call(&self, _: Params) -> Result;
+ /// Executes the given call and returns the diff for it.
+ fn state_diff_call(&self, params: Params) -> Result;
+
/// Should be used to convert object to io delegate.
fn to_delegate(self) -> IoDelegate {
let mut delegate = IoDelegate::new(Arc::new(self));
@@ -98,6 +101,7 @@ pub trait Ethcore: Sized + Send + Sync + 'static {
delegate.add_method("ethcore_defaultExtraData", Ethcore::default_extra_data);
delegate.add_method("ethcore_vmTraceCall", Ethcore::vm_trace_call);
+ delegate.add_method("ethcore_stateDiffCall", Ethcore::state_diff_call);
delegate
}
diff --git a/util/src/misc.rs b/util/src/misc.rs
index 159381603..8d22e04d7 100644
--- a/util/src/misc.rs
+++ b/util/src/misc.rs
@@ -24,33 +24,6 @@ use target_info::Target;
include!(concat!(env!("OUT_DIR"), "/version.rs"));
include!(concat!(env!("OUT_DIR"), "/rustc_version.rs"));
-#[derive(Debug,Clone,PartialEq,Eq)]
-/// Diff type for specifying a change (or not).
-pub enum Diff where T: Eq {
- /// Both sides are the same.
- Same,
- /// Left (pre, source) side doesn't include value, right side (post, destination) does.
- Born(T),
- /// Both sides include data; it chaged value between them.
- Changed(T, T),
- /// Left (pre, source) side does include value, right side (post, destination) does not.
- Died(T),
-}
-
-impl Diff where T: Eq {
- /// Construct new object with given `pre` and `post`.
- pub fn new(pre: T, post: T) -> Self { if pre == post { Diff::Same } else { Diff::Changed(pre, post) } }
-
- /// Get the before value, if there is one.
- pub fn pre(&self) -> Option<&T> { match *self { Diff::Died(ref x) | Diff::Changed(ref x, _) => Some(x), _ => None } }
-
- /// Get the after value, if there is one.
- pub fn post(&self) -> Option<&T> { match *self { Diff::Born(ref x) | Diff::Changed(_, ref x) => Some(x), _ => None } }
-
- /// Determine whether there was a change or not.
- pub fn is_same(&self) -> bool { match *self { Diff::Same => true, _ => false }}
-}
-
#[derive(PartialEq,Eq,Clone,Copy)]
/// Boolean type for clean/dirty status.
pub enum Filth {