diff --git a/Cargo.lock b/Cargo.lock index 7a10ec3c8..a8c97516d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -229,6 +229,19 @@ dependencies = [ "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ethabi" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "docopt 0.6.80 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 0.7.9 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_codegen 0.7.9 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", + "tiny-keccak 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "ethash" version = "1.4.0" @@ -275,6 +288,7 @@ name = "ethcore-dapps" version = "1.4.0" dependencies = [ "clippy 0.0.85 (registry+https://github.com/rust-lang/crates.io-index)", + "ethabi 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "ethcore-rpc 1.4.0", "ethcore-util 1.4.0", "hyper 0.9.4 (git+https://github.com/ethcore/hyper)", @@ -1642,6 +1656,7 @@ dependencies = [ "checksum elastic-array 0.4.0 (git+https://github.com/ethcore/elastic-array)" = "" "checksum env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "aba65b63ffcc17ffacd6cf5aa843da7c5a25e3bd4bbe0b7def8b214e411250e5" "checksum eth-secp256k1 0.5.4 (git+https://github.com/ethcore/rust-secp256k1)" = "" +"checksum ethabi 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bc7789d1518abba0c61606826a5229284d47a9d0934feb62a1ee218882780a9b" "checksum flate2 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "3eeb481e957304178d2e782f2da1257f1434dfecbae883bafb61ada2a9fea3bb" "checksum gcc 0.3.28 (registry+https://github.com/rust-lang/crates.io-index)" = "3da3a2cbaeb01363c8e3704fd9fd0eb2ceb17c6f27abd4c1ef040fb57d20dc79" "checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" diff --git a/dapps/Cargo.toml b/dapps/Cargo.toml index 8212538f8..2c7c9db9c 100644 --- a/dapps/Cargo.toml +++ b/dapps/Cargo.toml @@ -21,6 +21,7 @@ serde = "0.7.0" serde_json = "0.7.0" serde_macros = { version = "0.7.0", optional = true } zip = { version = "0.1", default-features = false } +ethabi = "0.2.1" ethcore-rpc = { path = "../rpc" } ethcore-util = { path = "../util" } parity-dapps = { git = "https://github.com/ethcore/parity-ui.git", version = "1.4" } diff --git a/dapps/src/apps/fetcher.rs b/dapps/src/apps/fetcher.rs index 2b1b9e658..0119b921e 100644 --- a/dapps/src/apps/fetcher.rs +++ b/dapps/src/apps/fetcher.rs @@ -24,6 +24,7 @@ use std::io::{self, Read, Write}; use std::path::PathBuf; use std::sync::Arc; use std::collections::HashMap; +use rustc_serialize::hex::FromHex; use hyper::Control; use hyper::status::StatusCode; @@ -54,12 +55,6 @@ impl Drop for AppFetcher { } } -impl Default for AppFetcher { - fn default() -> Self { - AppFetcher::new(URLHintContract) - } -} - impl AppFetcher { pub fn new(resolver: R) -> Self { @@ -84,7 +79,10 @@ impl AppFetcher { // Check if we already have the app Some(_) => true, // fallback to resolver - None => self.resolver.resolve(app_id).is_some(), + None => match app_id.from_hex() { + Ok(app_id) => self.resolver.resolve(app_id).is_some(), + _ => false, + }, } } @@ -103,16 +101,22 @@ impl AppFetcher { Some(&AppStatus::Fetching) => { (None, Box::new(ContentHandler::html( StatusCode::ServiceUnavailable, - "

This dapp is already being downloaded.

".into() + format!( + "{}{}", + "", + "

This dapp is already being downloaded.

Please wait...

", + ) )) as Box) }, // We need to start fetching app None => { // TODO [todr] Keep only last N dapps available! - let app = self.resolver.resolve(&app_id).expect("to_handler is called only when `contains` returns true."); + let app_hex = app_id.from_hex().expect("to_handler is called only when `contains` returns true."); + let app = self.resolver.resolve(app_hex).expect("to_handler is called only when `contains` returns true."); (Some(AppStatus::Fetching), Box::new(AppFetcherHandler::new( app, control, + path.using_dapps_domains, DappInstaller { dapp_id: app_id.clone(), dapps_path: self.dapps_path.clone(), @@ -265,10 +269,11 @@ mod tests { use apps::urlhint::{GithubApp, URLHint}; use endpoint::EndpointInfo; use page::LocalPageEndpoint; + use util::Bytes; struct FakeResolver; impl URLHint for FakeResolver { - fn resolve(&self, _app_id: &str) -> Option { + fn resolve(&self, _app_id: Bytes) -> Option { None } } diff --git a/dapps/src/apps/mod.rs b/dapps/src/apps/mod.rs index 97b018e68..84a3c5ddf 100644 --- a/dapps/src/apps/mod.rs +++ b/dapps/src/apps/mod.rs @@ -33,7 +33,14 @@ pub const API_PATH : &'static str = "api"; pub const UTILS_PATH : &'static str = "parity-utils"; pub fn main_page() -> &'static str { - "/home/" + "home" +} +pub fn redirection_address(using_dapps_domains: bool, app_id: &str) -> String { + if using_dapps_domains { + format!("http://{}{}/", app_id, DAPPS_DOMAIN) + } else { + format!("/{}/", app_id) + } } pub fn utils() -> Box { diff --git a/dapps/src/apps/registrar.json b/dapps/src/apps/registrar.json new file mode 100644 index 000000000..38edcc787 --- /dev/null +++ b/dapps/src/apps/registrar.json @@ -0,0 +1,21 @@ +[ + {"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"type":"function"}, + {"constant":false,"inputs":[{"name":"_name","type":"string"}],"name":"confirmReverse","outputs":[{"name":"success","type":"bool"}],"type":"function"}, + {"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"reserve","outputs":[{"name":"success","type":"bool"}],"type":"function"}, + {"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"bytes32"}],"name":"set","outputs":[{"name":"success","type":"bool"}],"type":"function"}, + {"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"drop","outputs":[{"name":"success","type":"bool"}],"type":"function"}, + {"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getAddress","outputs":[{"name":"","type":"address"}],"type":"function"}, + {"constant":false,"inputs":[{"name":"_amount","type":"uint256"}],"name":"setFee","outputs":[],"type":"function"}, + {"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_to","type":"address"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"type":"function"}, + {"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"type":"function"}, + {"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"reserved","outputs":[{"name":"reserved","type":"bool"}],"type":"function"}, + {"constant":false,"inputs":[],"name":"drain","outputs":[],"type":"function"}, + {"constant":false,"inputs":[{"name":"_name","type":"string"},{"name":"_who","type":"address"}],"name":"proposeReverse","outputs":[{"name":"success","type":"bool"}],"type":"function"}, + {"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getUint","outputs":[{"name":"","type":"uint256"}],"type":"function"}, + {"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"get","outputs":[{"name":"","type":"bytes32"}],"type":"function"}, + {"constant":true,"inputs":[],"name":"fee","outputs":[{"name":"","type":"uint256"}],"type":"function"}, + {"constant":true,"inputs":[{"name":"","type":"address"}],"name":"reverse","outputs":[{"name":"","type":"string"}],"type":"function"}, + {"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"uint256"}],"name":"setUint","outputs":[{"name":"success","type":"bool"}],"type":"function"}, + {"constant":false,"inputs":[],"name":"removeReverse","outputs":[],"type":"function"}, + {"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"address"}],"name":"setAddress","outputs":[{"name":"success","type":"bool"}],"type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"Drained","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"FeeChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"bytes32"},{"indexed":true,"name":"owner","type":"address"}],"name":"Reserved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"bytes32"},{"indexed":true,"name":"oldOwner","type":"address"},{"indexed":true,"name":"newOwner","type":"address"}],"name":"Transferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"bytes32"},{"indexed":true,"name":"owner","type":"address"}],"name":"Dropped","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"bytes32"},{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"key","type":"string"}],"name":"DataChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"string"},{"indexed":true,"name":"reverse","type":"address"}],"name":"ReverseProposed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"string"},{"indexed":true,"name":"reverse","type":"address"}],"name":"ReverseConfirmed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"string"},{"indexed":true,"name":"reverse","type":"address"}],"name":"ReverseRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"old","type":"address"},{"indexed":true,"name":"current","type":"address"}],"name":"NewOwner","type":"event"} +] diff --git a/dapps/src/apps/urlhint.json b/dapps/src/apps/urlhint.json new file mode 100644 index 000000000..629f166bb --- /dev/null +++ b/dapps/src/apps/urlhint.json @@ -0,0 +1,6 @@ +[ + {"constant":false,"inputs":[{"name":"_content","type":"bytes32"},{"name":"_url","type":"string"}],"name":"hintURL","outputs":[],"type":"function"}, + {"constant":false,"inputs":[{"name":"_content","type":"bytes32"},{"name":"_accountSlashRepo","type":"string"},{"name":"_commit","type":"bytes20"}],"name":"hint","outputs":[],"type":"function"}, + {"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"entries","outputs":[{"name":"accountSlashRepo","type":"string"},{"name":"commit","type":"bytes20"},{"name":"owner","type":"address"}],"type":"function"}, + {"constant":false,"inputs":[{"name":"_content","type":"bytes32"}],"name":"unhint","outputs":[],"type":"function"} +] diff --git a/dapps/src/apps/urlhint.rs b/dapps/src/apps/urlhint.rs index 61dbc0dec..cbf85b10a 100644 --- a/dapps/src/apps/urlhint.rs +++ b/dapps/src/apps/urlhint.rs @@ -14,13 +14,16 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +use std::fmt; +use std::sync::Arc; use rustc_serialize::hex::ToHex; -use util::{Address, FromHex}; +use ethabi::{Interface, Contract, Token}; +use util::{Address, Bytes, Hashable}; const COMMIT_LEN: usize = 20; -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub struct GithubApp { pub account: String, pub repo: String, @@ -48,42 +51,240 @@ impl GithubApp { } } -pub trait URLHint { - fn resolve(&self, app_id: &str) -> Option; +/// RAW Contract interface. +/// Should execute transaction using current blockchain state. +pub trait ContractClient: Send + Sync { + /// Get registrar address + fn registrar(&self) -> Result; + /// Call Contract + fn call(&self, address: Address, data: Bytes) -> Result; } -pub struct URLHintContract; +/// URLHint Contract interface +pub trait URLHint { + /// Resolves given id to registrar entry. + fn resolve(&self, app_id: Bytes) -> Option; +} -impl URLHint for URLHintContract { - fn resolve(&self, app_id: &str) -> Option { - // TODO [todr] use GithubHint contract to check the details - // For now we are just accepting patterns: ...parity - let mut app_parts = app_id.split('.'); +pub struct URLHintContract { + urlhint: Contract, + registrar: Contract, + client: Arc, +} - let hash = app_parts.next() - .and_then(|h| h.from_hex().ok()) - .and_then(|h| GithubApp::commit(&h)); - let repo = app_parts.next(); - let account = app_parts.next(); +impl URLHintContract { + pub fn new(client: Arc) -> Self { + let urlhint = Interface::load(include_bytes!("./urlhint.json")).expect("urlhint.json is valid ABI"); + let registrar = Interface::load(include_bytes!("./registrar.json")).expect("registrar.json is valid ABI"); - match (hash, repo, account) { - (Some(hash), Some(repo), Some(account)) => { - Some(GithubApp { - account: account.into(), - repo: repo.into(), - commit: hash, - owner: Address::default(), - }) + URLHintContract { + urlhint: Contract::new(urlhint), + registrar: Contract::new(registrar), + client: client, + } + } + + fn urlhint_address(&self) -> Option
{ + let res = || { + let get_address = try!(self.registrar.function("getAddress".into()).map_err(as_string)); + let params = try!(get_address.encode_call( + vec![Token::FixedBytes((*"githubhint".sha3()).to_vec()), Token::String("A".into())] + ).map_err(as_string)); + let output = try!(self.client.call(try!(self.client.registrar()), params)); + let result = try!(get_address.decode_output(output).map_err(as_string)); + + match result.get(0) { + Some(&Token::Address(address)) if address != *Address::default() => Ok(address.into()), + Some(&Token::Address(_)) => Err(format!("Contract not found.")), + e => Err(format!("Invalid result: {:?}", e)), + } + }; + + match res() { + Ok(res) => Some(res), + Err(e) => { + warn!(target: "dapps", "Error while calling registrar: {:?}", e); + None + } + } + } + + fn encode_urlhint_call(&self, app_id: Bytes) -> Option { + let call = self.urlhint + .function("entries".into()) + .and_then(|f| f.encode_call(vec![Token::FixedBytes(app_id)])); + + match call { + Ok(res) => { + Some(res) }, - _ => None, + Err(e) => { + warn!(target: "dapps", "Error while encoding urlhint call: {:?}", e); + None + } + } + } + + fn decode_urlhint_output(&self, output: Bytes) -> Option { + trace!(target: "dapps", "Output: {:?}", output.to_hex()); + let output = self.urlhint + .function("entries".into()) + .and_then(|f| f.decode_output(output)); + + if let Ok(vec) = output { + if vec.len() != 3 { + warn!(target: "dapps", "Invalid contract output: {:?}", vec); + return None; + } + + let mut it = vec.into_iter(); + let account_slash_repo = it.next().unwrap(); + let commit = it.next().unwrap(); + let owner = it.next().unwrap(); + + match (account_slash_repo, commit, owner) { + (Token::String(account_slash_repo), Token::FixedBytes(commit), Token::Address(owner)) => { + let owner = owner.into(); + if owner == Address::default() { + return None; + } + let (account, repo) = { + let mut it = account_slash_repo.split('/'); + match (it.next(), it.next()) { + (Some(account), Some(repo)) => (account.into(), repo.into()), + _ => return None, + } + }; + + GithubApp::commit(&commit).map(|commit| GithubApp { + account: account, + repo: repo, + commit: commit, + owner: owner, + }) + }, + e => { + warn!(target: "dapps", "Invalid contract output parameters: {:?}", e); + None + }, + } + } else { + warn!(target: "dapps", "Invalid contract output: {:?}", output); + None } } } +impl URLHint for URLHintContract { + fn resolve(&self, app_id: Bytes) -> Option { + self.urlhint_address().and_then(|address| { + // Prepare contract call + self.encode_urlhint_call(app_id) + .and_then(|data| { + let call = self.client.call(address, data); + if let Err(ref e) = call { + warn!(target: "dapps", "Error while calling urlhint: {:?}", e); + } + call.ok() + }) + .and_then(|output| self.decode_urlhint_output(output)) + }) + } +} + +fn as_string(e: T) -> String { + format!("{:?}", e) +} + #[cfg(test)] mod tests { - use super::GithubApp; - use util::Address; + use std::sync::Arc; + use std::str::FromStr; + use rustc_serialize::hex::{ToHex, FromHex}; + + use super::*; + use util::{Bytes, Address, Mutex, ToPretty}; + + struct FakeRegistrar { + pub calls: Arc>>, + pub responses: Mutex>>, + } + + const REGISTRAR: &'static str = "8e4e9b13d4b45cb0befc93c3061b1408f67316b2"; + const URLHINT: &'static str = "deadbeefcafe0000000000000000000000000000"; + + impl FakeRegistrar { + fn new() -> Self { + FakeRegistrar { + calls: Arc::new(Mutex::new(Vec::new())), + responses: Mutex::new( + vec![ + Ok(format!("000000000000000000000000{}", URLHINT).from_hex().unwrap()), + Ok(Vec::new()) + ] + ), + } + } + } + + impl ContractClient for FakeRegistrar { + + fn registrar(&self) -> Result { + Ok(REGISTRAR.parse().unwrap()) + } + + fn call(&self, address: Address, data: Bytes) -> Result { + self.calls.lock().push((address.to_hex(), data.to_hex())); + self.responses.lock().remove(0) + } + } + + #[test] + fn should_call_registrar_and_urlhint_contracts() { + // given + let registrar = FakeRegistrar::new(); + let calls = registrar.calls.clone(); + let urlhint = URLHintContract::new(Arc::new(registrar)); + + // when + let res = urlhint.resolve("test".bytes().collect()); + let calls = calls.lock(); + let call0 = calls.get(0).expect("Registrar resolve called"); + let call1 = calls.get(1).expect("URLHint Resolve called"); + + // then + assert!(res.is_none()); + assert_eq!(call0.0, REGISTRAR); + assert_eq!(call0.1, + "6795dbcd058740ee9a5a3fb9f1cfa10752baec87e09cc45cd7027fd54708271aca300c75000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000014100000000000000000000000000000000000000000000000000000000000000".to_owned() + ); + assert_eq!(call1.0, URLHINT); + assert_eq!(call1.1, + "267b69227465737400000000000000000000000000000000000000000000000000000000".to_owned() + ); + } + + #[test] + fn should_decode_urlhint_output() { + // given + let mut registrar = FakeRegistrar::new(); + registrar.responses = Mutex::new(vec![ + Ok(format!("000000000000000000000000{}", URLHINT).from_hex().unwrap()), + Ok("0000000000000000000000000000000000000000000000000000000000000060ec4c1fe06c808fe3739858c347109b1f5f1ed4b5000000000000000000000000000000000000000000000000deadcafebeefbeefcafedeaddeedfeedffffffff0000000000000000000000000000000000000000000000000000000000000011657468636f72652f64616f2e636c61696d000000000000000000000000000000".from_hex().unwrap()), + ]); + let urlhint = URLHintContract::new(Arc::new(registrar)); + + // when + let res = urlhint.resolve("test".bytes().collect()); + + // then + assert_eq!(res, Some(GithubApp { + account: "ethcore".into(), + repo: "dao.claim".into(), + commit: GithubApp::commit(&"ec4c1fe06c808fe3739858c347109b1f5f1ed4b5".from_hex().unwrap()).unwrap(), + owner: Address::from_str("deadcafebeefbeefcafedeaddeedfeedffffffff").unwrap(), + })) + } #[test] fn should_return_valid_url() { diff --git a/dapps/src/endpoint.rs b/dapps/src/endpoint.rs index 62816b088..dbb02a3d3 100644 --- a/dapps/src/endpoint.rs +++ b/dapps/src/endpoint.rs @@ -24,6 +24,7 @@ pub struct EndpointPath { pub app_id: String, pub host: String, pub port: u16, + pub using_dapps_domains: bool, } #[derive(Debug, PartialEq, Clone)] diff --git a/dapps/src/handlers/fetch.rs b/dapps/src/handlers/fetch.rs index 3ada8fb82..94bce1492 100644 --- a/dapps/src/handlers/fetch.rs +++ b/dapps/src/handlers/fetch.rs @@ -27,7 +27,7 @@ use hyper::status::StatusCode; use handlers::ContentHandler; use handlers::client::{Fetch, FetchResult}; -use apps::DAPPS_DOMAIN; +use apps::redirection_address; use apps::urlhint::GithubApp; use apps::manifest::Manifest; @@ -54,6 +54,7 @@ pub struct AppFetcherHandler { control: Option, status: FetchState, client: Option>, + using_dapps_domains: bool, dapp: H, } @@ -72,6 +73,7 @@ impl AppFetcherHandler { pub fn new( app: GithubApp, control: Control, + using_dapps_domains: bool, handler: H) -> Self { let client = Client::new().expect("Failed to create a Client"); @@ -79,6 +81,7 @@ impl AppFetcherHandler { control: Some(control), client: Some(client), status: FetchState::NotStarted(app), + using_dapps_domains: using_dapps_domains, dapp: handler, } } @@ -207,8 +210,7 @@ impl server::Handler for AppFetcherHandler { FetchState::Done(ref manifest) => { trace!(target: "dapps", "Fetching dapp finished. Redirecting to {}", manifest.id); res.set_status(StatusCode::Found); - // TODO [todr] should detect if its using nice-urls - res.headers_mut().set(header::Location(format!("http://{}{}", manifest.id, DAPPS_DOMAIN))); + res.headers_mut().set(header::Location(redirection_address(self.using_dapps_domains, &manifest.id))); Next::write() }, FetchState::Error(ref mut handler) => handler.on_response(res), diff --git a/dapps/src/handlers/redirect.rs b/dapps/src/handlers/redirect.rs index 6d738115d..dbe5f6e4a 100644 --- a/dapps/src/handlers/redirect.rs +++ b/dapps/src/handlers/redirect.rs @@ -21,13 +21,13 @@ use hyper::net::HttpStream; use hyper::status::StatusCode; pub struct Redirection { - to_url: &'static str + to_url: String } impl Redirection { - pub fn new(url: &'static str) -> Box { + pub fn new(url: &str) -> Box { Box::new(Redirection { - to_url: url + to_url: url.to_owned() }) } } diff --git a/dapps/src/lib.rs b/dapps/src/lib.rs index 49940080f..3373f5c58 100644 --- a/dapps/src/lib.rs +++ b/dapps/src/lib.rs @@ -52,6 +52,7 @@ extern crate serde; extern crate serde_json; extern crate zip; extern crate rand; +extern crate ethabi; extern crate jsonrpc_core; extern crate jsonrpc_http_server; extern crate parity_dapps; @@ -70,6 +71,8 @@ mod api; mod proxypac; mod url; +pub use self::apps::urlhint::ContractClient; + use std::sync::{Arc, Mutex}; use std::net::SocketAddr; use std::collections::HashMap; @@ -84,6 +87,7 @@ static DAPPS_DOMAIN : &'static str = ".parity"; pub struct ServerBuilder { dapps_path: String, handler: Arc, + registrar: Arc, } impl Extendable for ServerBuilder { @@ -94,23 +98,24 @@ impl Extendable for ServerBuilder { impl ServerBuilder { /// Construct new dapps server - pub fn new(dapps_path: String) -> Self { + pub fn new(dapps_path: String, registrar: Arc) -> Self { ServerBuilder { dapps_path: dapps_path, - handler: Arc::new(IoHandler::new()) + handler: Arc::new(IoHandler::new()), + registrar: registrar, } } /// 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(), self.dapps_path.clone()) + Server::start_http(addr, NoAuth, self.handler.clone(), self.dapps_path.clone(), self.registrar.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(), self.dapps_path.clone()) + Server::start_http(addr, HttpBasicAuth::single_user(username, password), self.handler.clone(), self.dapps_path.clone(), self.registrar.clone()) } } @@ -121,10 +126,16 @@ pub struct Server { } impl Server { - fn start_http(addr: &SocketAddr, authorization: A, handler: Arc, dapps_path: String) -> Result { + fn start_http( + addr: &SocketAddr, + authorization: A, + handler: Arc, + dapps_path: String, + registrar: Arc, + ) -> Result { let panic_handler = Arc::new(Mutex::new(None)); let authorization = Arc::new(authorization); - let apps_fetcher = Arc::new(apps::fetcher::AppFetcher::default()); + let apps_fetcher = Arc::new(apps::fetcher::AppFetcher::new(apps::urlhint::URLHintContract::new(registrar))); let endpoints = Arc::new(apps::all_endpoints(dapps_path)); let special = Arc::new({ let mut special = HashMap::new(); diff --git a/dapps/src/page/handler.rs b/dapps/src/page/handler.rs index c3ec354e0..eca242e7b 100644 --- a/dapps/src/page/handler.rs +++ b/dapps/src/page/handler.rs @@ -187,7 +187,8 @@ fn should_extract_path_with_appid() { path: EndpointPath { app_id: "app".to_owned(), host: "".to_owned(), - port: 8080 + port: 8080, + using_dapps_domains: true, }, file: None, safe_to_embed: true, diff --git a/dapps/src/router/mod.rs b/dapps/src/router/mod.rs index 3dad8250b..568dc00da 100644 --- a/dapps/src/router/mod.rs +++ b/dapps/src/router/mod.rs @@ -86,9 +86,10 @@ impl server::Handler for Router { let control = self.control.take().expect("on_request is called only once, thus control is always defined."); self.fetch.to_handler(path.clone(), control) }, - // Redirection to main page - _ if *req.method() == hyper::method::Method::Get => { - Redirection::new(self.main_page) + // Redirection to main page (maybe 404 instead?) + (Some(ref path), _) if *req.method() == hyper::method::Method::Get => { + let address = apps::redirection_address(path.using_dapps_domains, self.main_page); + Redirection::new(address.as_str()) }, // RPC by default _ => { @@ -165,6 +166,7 @@ fn extract_endpoint(url: &Option) -> (Option, SpecialEndpoint app_id: id, host: domain.clone(), port: url.port, + using_dapps_domains: true, }), special_endpoint(url)) }, _ if url.path.len() > 1 => { @@ -173,6 +175,7 @@ fn extract_endpoint(url: &Option) -> (Option, SpecialEndpoint app_id: id.clone(), host: format!("{}", url.host), port: url.port, + using_dapps_domains: false, }), special_endpoint(url)) }, _ => (None, special_endpoint(url)), @@ -192,6 +195,7 @@ fn should_extract_endpoint() { app_id: "status".to_owned(), host: "localhost".to_owned(), port: 8080, + using_dapps_domains: false, }), SpecialEndpoint::None) ); @@ -202,6 +206,7 @@ fn should_extract_endpoint() { app_id: "rpc".to_owned(), host: "localhost".to_owned(), port: 8080, + using_dapps_domains: false, }), SpecialEndpoint::Rpc) ); @@ -211,6 +216,7 @@ fn should_extract_endpoint() { app_id: "my.status".to_owned(), host: "my.status.parity".to_owned(), port: 80, + using_dapps_domains: true, }), SpecialEndpoint::Utils) ); @@ -221,6 +227,7 @@ fn should_extract_endpoint() { app_id: "my.status".to_owned(), host: "my.status.parity".to_owned(), port: 80, + using_dapps_domains: true, }), SpecialEndpoint::None) ); @@ -231,6 +238,7 @@ fn should_extract_endpoint() { app_id: "my.status".to_owned(), host: "my.status.parity".to_owned(), port: 80, + using_dapps_domains: true, }), SpecialEndpoint::Rpc) ); @@ -241,6 +249,7 @@ fn should_extract_endpoint() { app_id: "my.status".to_owned(), host: "my.status.parity".to_owned(), port: 80, + using_dapps_domains: true, }), SpecialEndpoint::Api) ); } diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index e034c86f2..06879ca81 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -723,8 +723,8 @@ impl MinerService for Miner { .position(|t| t == *hash) .map(|index| { let prev_gas = if index == 0 { Default::default() } else { pending.receipts()[index - 1].gas_used }; - let ref tx = txs[index]; - let ref receipt = pending.receipts()[index]; + let tx = &txs[index]; + let receipt = &pending.receipts()[index]; RichReceipt { transaction_hash: hash.clone(), transaction_index: index, diff --git a/ethstore/src/bin/ethstore.rs b/ethstore/src/bin/ethstore.rs index 71dedee5c..94823dc06 100644 --- a/ethstore/src/bin/ethstore.rs +++ b/ethstore/src/bin/ethstore.rs @@ -20,7 +20,6 @@ extern crate ethstore; use std::{env, process, fs}; use std::io::Read; -use std::ops::Deref; use docopt::Docopt; use ethstore::ethkey::Address; use ethstore::dir::{KeyDirectory, ParityDirectory, DiskDirectory, GethDirectory, DirectoryType}; @@ -142,7 +141,7 @@ fn execute(command: I) -> Result where I: IntoIterator(command: I) -> Result where I: IntoIterator. use std::sync::Arc; -use std::net::SocketAddr; use io::PanicHandler; use rpc_apis; +use ethcore::client::Client; use helpers::replace_home; -#[cfg(feature = "dapps")] -pub use ethcore_dapps::Server as WebappServer; -#[cfg(not(feature = "dapps"))] -pub struct WebappServer; - #[derive(Debug, PartialEq, Clone)] pub struct Configuration { pub enabled: bool, @@ -51,6 +46,7 @@ impl Default for Configuration { pub struct Dependencies { pub panic_handler: Arc, pub apis: Arc, + pub client: Arc, } pub fn new(configuration: Configuration, deps: Dependencies) -> Result, String> { @@ -75,45 +71,102 @@ pub fn new(configuration: Configuration, deps: Dependencies) -> Result, -) -> Result { - Err("Your Parity version has been compiled without WebApps support.".into()) -} +mod server { + use super::Dependencies; + use std::net::SocketAddr; -#[cfg(feature = "dapps")] -pub fn setup_dapps_server( - deps: Dependencies, - dapps_path: String, - url: &SocketAddr, - auth: Option<(String, String)> -) -> Result { - use ethcore_dapps as dapps; - - 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 => { - server.start_unsecure_http(url) - }, - Some((username, password)) => { - server.start_basic_auth_http(url, &username, &password) - }, - }; - - match start_result { - Err(dapps::ServerError::IoError(err)) => Err(format!("WebApps io error: {}", err)), - Err(e) => Err(format!("WebApps error: {:?}", e)), - Ok(server) => { - server.set_panic_handler(move || { - deps.panic_handler.notify_all("Panic in WebApp thread.".to_owned()); - }); - Ok(server) - }, + pub struct WebappServer; + pub fn setup_dapps_server( + _deps: Dependencies, + _dapps_path: String, + _url: &SocketAddr, + _auth: Option<(String, String)>, + ) -> Result { + Err("Your Parity version has been compiled without WebApps support.".into()) } } +#[cfg(feature = "dapps")] +mod server { + use super::Dependencies; + use std::sync::Arc; + use std::net::SocketAddr; + use util::{Bytes, Address, U256}; + + use ethcore::transaction::{Transaction, Action}; + use ethcore::client::{Client, BlockChainClient, BlockID}; + + use rpc_apis; + use ethcore_dapps::ContractClient; + + pub use ethcore_dapps::Server as WebappServer; + + pub fn setup_dapps_server( + deps: Dependencies, + dapps_path: String, + url: &SocketAddr, + auth: Option<(String, String)> + ) -> Result { + use ethcore_dapps as dapps; + + let server = dapps::ServerBuilder::new(dapps_path, Arc::new(Registrar { + client: deps.client.clone(), + })); + let server = rpc_apis::setup_rpc(server, deps.apis.clone(), rpc_apis::ApiSet::UnsafeContext); + let start_result = match auth { + None => { + server.start_unsecure_http(url) + }, + Some((username, password)) => { + server.start_basic_auth_http(url, &username, &password) + }, + }; + + match start_result { + Err(dapps::ServerError::IoError(err)) => Err(format!("WebApps io error: {}", err)), + Err(e) => Err(format!("WebApps error: {:?}", e)), + Ok(server) => { + server.set_panic_handler(move || { + deps.panic_handler.notify_all("Panic in WebApp thread.".to_owned()); + }); + Ok(server) + }, + } + } + + struct Registrar { + client: Arc, + } + + impl ContractClient for Registrar { + fn registrar(&self) -> Result { + self.client.additional_params().get("registrar") + .ok_or_else(|| "Registrar not defined.".into()) + .and_then(|registrar| { + registrar.parse().map_err(|e| format!("Invalid registrar address: {:?}", e)) + }) + } + + fn call(&self, address: Address, data: Bytes) -> Result { + let from = Address::default(); + let transaction = Transaction { + nonce: self.client.latest_nonce(&from), + action: Action::Call(address), + gas: U256::from(50_000_000), + gas_price: U256::default(), + value: U256::default(), + data: data, + }.fake_sign(from); + + self.client.call(&transaction, BlockID::Latest, Default::default()) + .map_err(|e| format!("{:?}", e)) + .map(|executed| { + executed.output + }) + } + } +} diff --git a/parity/run.rs b/parity/run.rs index 91f8d5bfa..220f77376 100644 --- a/parity/run.rs +++ b/parity/run.rs @@ -223,6 +223,7 @@ pub fn execute(cmd: RunCmd) -> Result<(), String> { let dapps_deps = dapps::Dependencies { panic_handler: panic_handler.clone(), apis: deps_for_rpc_apis.clone(), + client: client.clone(), }; // start dapps server