Resolving URLs from contract (#1964)

* Fetching dapp from github.

* Unpacking dapp

* Removing hardcodes

* Proper Host validation

* Randomizing paths

* Splitting into files

* Serving donwloaded apps from different path

* Extracting URLHint to separate module

* Whitespace and docs

* Resolving from URLHint contract

* Fixing test

* Resolving githubhint url from registrar

* Proper redirections

* Fixing test

* fixing ethstore [ci skip]

* Correct version of registrar

* Removing superfluous Box
This commit is contained in:
Tomasz Drwięga 2016-08-23 19:28:21 +02:00 committed by Gav Wood
parent dda57d9294
commit 124a5da75e
17 changed files with 434 additions and 101 deletions

15
Cargo.lock generated
View File

@ -229,6 +229,19 @@ dependencies = [
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "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]] [[package]]
name = "ethash" name = "ethash"
version = "1.4.0" version = "1.4.0"
@ -275,6 +288,7 @@ name = "ethcore-dapps"
version = "1.4.0" version = "1.4.0"
dependencies = [ dependencies = [
"clippy 0.0.85 (registry+https://github.com/rust-lang/crates.io-index)", "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-rpc 1.4.0",
"ethcore-util 1.4.0", "ethcore-util 1.4.0",
"hyper 0.9.4 (git+https://github.com/ethcore/hyper)", "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)" = "<none>" "checksum elastic-array 0.4.0 (git+https://github.com/ethcore/elastic-array)" = "<none>"
"checksum env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "aba65b63ffcc17ffacd6cf5aa843da7c5a25e3bd4bbe0b7def8b214e411250e5" "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)" = "<none>" "checksum eth-secp256k1 0.5.4 (git+https://github.com/ethcore/rust-secp256k1)" = "<none>"
"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 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 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" "checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb"

View File

@ -21,6 +21,7 @@ serde = "0.7.0"
serde_json = "0.7.0" serde_json = "0.7.0"
serde_macros = { version = "0.7.0", optional = true } serde_macros = { version = "0.7.0", optional = true }
zip = { version = "0.1", default-features = false } zip = { version = "0.1", default-features = false }
ethabi = "0.2.1"
ethcore-rpc = { path = "../rpc" } ethcore-rpc = { path = "../rpc" }
ethcore-util = { path = "../util" } ethcore-util = { path = "../util" }
parity-dapps = { git = "https://github.com/ethcore/parity-ui.git", version = "1.4" } parity-dapps = { git = "https://github.com/ethcore/parity-ui.git", version = "1.4" }

View File

@ -24,6 +24,7 @@ use std::io::{self, Read, Write};
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
use std::collections::HashMap; use std::collections::HashMap;
use rustc_serialize::hex::FromHex;
use hyper::Control; use hyper::Control;
use hyper::status::StatusCode; use hyper::status::StatusCode;
@ -54,12 +55,6 @@ impl<R: URLHint> Drop for AppFetcher<R> {
} }
} }
impl Default for AppFetcher<URLHintContract> {
fn default() -> Self {
AppFetcher::new(URLHintContract)
}
}
impl<R: URLHint> AppFetcher<R> { impl<R: URLHint> AppFetcher<R> {
pub fn new(resolver: R) -> Self { pub fn new(resolver: R) -> Self {
@ -84,7 +79,10 @@ impl<R: URLHint> AppFetcher<R> {
// Check if we already have the app // Check if we already have the app
Some(_) => true, Some(_) => true,
// fallback to resolver // 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<R: URLHint> AppFetcher<R> {
Some(&AppStatus::Fetching) => { Some(&AppStatus::Fetching) => {
(None, Box::new(ContentHandler::html( (None, Box::new(ContentHandler::html(
StatusCode::ServiceUnavailable, StatusCode::ServiceUnavailable,
"<h1>This dapp is already being downloaded.</h1>".into() format!(
"<html><head>{}</head><body>{}</body></html>",
"<meta http-equiv=\"refresh\" content=\"1\">",
"<h1>This dapp is already being downloaded.</h1><h2>Please wait...</h2>",
)
)) as Box<Handler>) )) as Box<Handler>)
}, },
// We need to start fetching app // We need to start fetching app
None => { None => {
// TODO [todr] Keep only last N dapps available! // 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( (Some(AppStatus::Fetching), Box::new(AppFetcherHandler::new(
app, app,
control, control,
path.using_dapps_domains,
DappInstaller { DappInstaller {
dapp_id: app_id.clone(), dapp_id: app_id.clone(),
dapps_path: self.dapps_path.clone(), dapps_path: self.dapps_path.clone(),
@ -265,10 +269,11 @@ mod tests {
use apps::urlhint::{GithubApp, URLHint}; use apps::urlhint::{GithubApp, URLHint};
use endpoint::EndpointInfo; use endpoint::EndpointInfo;
use page::LocalPageEndpoint; use page::LocalPageEndpoint;
use util::Bytes;
struct FakeResolver; struct FakeResolver;
impl URLHint for FakeResolver { impl URLHint for FakeResolver {
fn resolve(&self, _app_id: &str) -> Option<GithubApp> { fn resolve(&self, _app_id: Bytes) -> Option<GithubApp> {
None None
} }
} }

View File

@ -33,7 +33,14 @@ pub const API_PATH : &'static str = "api";
pub const UTILS_PATH : &'static str = "parity-utils"; pub const UTILS_PATH : &'static str = "parity-utils";
pub fn main_page() -> &'static str { 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<Endpoint> { pub fn utils() -> Box<Endpoint> {

View File

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

View File

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

View File

@ -14,13 +14,16 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::fmt;
use std::sync::Arc;
use rustc_serialize::hex::ToHex; use rustc_serialize::hex::ToHex;
use util::{Address, FromHex}; use ethabi::{Interface, Contract, Token};
use util::{Address, Bytes, Hashable};
const COMMIT_LEN: usize = 20; const COMMIT_LEN: usize = 20;
#[derive(Debug)] #[derive(Debug, PartialEq)]
pub struct GithubApp { pub struct GithubApp {
pub account: String, pub account: String,
pub repo: String, pub repo: String,
@ -48,42 +51,240 @@ impl GithubApp {
} }
} }
pub trait URLHint { /// RAW Contract interface.
fn resolve(&self, app_id: &str) -> Option<GithubApp>; /// Should execute transaction using current blockchain state.
pub trait ContractClient: Send + Sync {
/// Get registrar address
fn registrar(&self) -> Result<Address, String>;
/// Call Contract
fn call(&self, address: Address, data: Bytes) -> Result<Bytes, String>;
} }
pub struct URLHintContract; /// URLHint Contract interface
pub trait URLHint {
/// Resolves given id to registrar entry.
fn resolve(&self, app_id: Bytes) -> Option<GithubApp>;
}
impl URLHint for URLHintContract { pub struct URLHintContract {
fn resolve(&self, app_id: &str) -> Option<GithubApp> { urlhint: Contract,
// TODO [todr] use GithubHint contract to check the details registrar: Contract,
// For now we are just accepting patterns: <commithash>.<repo>.<account>.parity client: Arc<ContractClient>,
let mut app_parts = app_id.split('.'); }
let hash = app_parts.next() impl URLHintContract {
.and_then(|h| h.from_hex().ok()) pub fn new(client: Arc<ContractClient>) -> Self {
.and_then(|h| GithubApp::commit(&h)); let urlhint = Interface::load(include_bytes!("./urlhint.json")).expect("urlhint.json is valid ABI");
let repo = app_parts.next(); let registrar = Interface::load(include_bytes!("./registrar.json")).expect("registrar.json is valid ABI");
let account = app_parts.next();
match (hash, repo, account) { URLHintContract {
(Some(hash), Some(repo), Some(account)) => { urlhint: Contract::new(urlhint),
Some(GithubApp { registrar: Contract::new(registrar),
account: account.into(), client: client,
repo: repo.into(), }
commit: hash, }
owner: Address::default(),
fn urlhint_address(&self) -> Option<Address> {
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<Bytes> {
let call = self.urlhint
.function("entries".into())
.and_then(|f| f.encode_call(vec![Token::FixedBytes(app_id)]));
match call {
Ok(res) => {
Some(res)
},
Err(e) => {
warn!(target: "dapps", "Error while encoding urlhint call: {:?}", e);
None
}
}
}
fn decode_urlhint_output(&self, output: Bytes) -> Option<GithubApp> {
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,
}) })
}, },
_ => None, 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<GithubApp> {
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<T: fmt::Debug>(e: T) -> String {
format!("{:?}", e)
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::GithubApp; use std::sync::Arc;
use util::Address; use std::str::FromStr;
use rustc_serialize::hex::{ToHex, FromHex};
use super::*;
use util::{Bytes, Address, Mutex, ToPretty};
struct FakeRegistrar {
pub calls: Arc<Mutex<Vec<(String, String)>>>,
pub responses: Mutex<Vec<Result<Bytes, String>>>,
}
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<Address, String> {
Ok(REGISTRAR.parse().unwrap())
}
fn call(&self, address: Address, data: Bytes) -> Result<Bytes, String> {
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] #[test]
fn should_return_valid_url() { fn should_return_valid_url() {

View File

@ -24,6 +24,7 @@ pub struct EndpointPath {
pub app_id: String, pub app_id: String,
pub host: String, pub host: String,
pub port: u16, pub port: u16,
pub using_dapps_domains: bool,
} }
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]

View File

@ -27,7 +27,7 @@ use hyper::status::StatusCode;
use handlers::ContentHandler; use handlers::ContentHandler;
use handlers::client::{Fetch, FetchResult}; use handlers::client::{Fetch, FetchResult};
use apps::DAPPS_DOMAIN; use apps::redirection_address;
use apps::urlhint::GithubApp; use apps::urlhint::GithubApp;
use apps::manifest::Manifest; use apps::manifest::Manifest;
@ -54,6 +54,7 @@ pub struct AppFetcherHandler<H: DappHandler> {
control: Option<Control>, control: Option<Control>,
status: FetchState, status: FetchState,
client: Option<Client<Fetch>>, client: Option<Client<Fetch>>,
using_dapps_domains: bool,
dapp: H, dapp: H,
} }
@ -72,6 +73,7 @@ impl<H: DappHandler> AppFetcherHandler<H> {
pub fn new( pub fn new(
app: GithubApp, app: GithubApp,
control: Control, control: Control,
using_dapps_domains: bool,
handler: H) -> Self { handler: H) -> Self {
let client = Client::new().expect("Failed to create a Client"); let client = Client::new().expect("Failed to create a Client");
@ -79,6 +81,7 @@ impl<H: DappHandler> AppFetcherHandler<H> {
control: Some(control), control: Some(control),
client: Some(client), client: Some(client),
status: FetchState::NotStarted(app), status: FetchState::NotStarted(app),
using_dapps_domains: using_dapps_domains,
dapp: handler, dapp: handler,
} }
} }
@ -207,8 +210,7 @@ impl<H: DappHandler> server::Handler<HttpStream> for AppFetcherHandler<H> {
FetchState::Done(ref manifest) => { FetchState::Done(ref manifest) => {
trace!(target: "dapps", "Fetching dapp finished. Redirecting to {}", manifest.id); trace!(target: "dapps", "Fetching dapp finished. Redirecting to {}", manifest.id);
res.set_status(StatusCode::Found); res.set_status(StatusCode::Found);
// TODO [todr] should detect if its using nice-urls res.headers_mut().set(header::Location(redirection_address(self.using_dapps_domains, &manifest.id)));
res.headers_mut().set(header::Location(format!("http://{}{}", manifest.id, DAPPS_DOMAIN)));
Next::write() Next::write()
}, },
FetchState::Error(ref mut handler) => handler.on_response(res), FetchState::Error(ref mut handler) => handler.on_response(res),

View File

@ -21,13 +21,13 @@ use hyper::net::HttpStream;
use hyper::status::StatusCode; use hyper::status::StatusCode;
pub struct Redirection { pub struct Redirection {
to_url: &'static str to_url: String
} }
impl Redirection { impl Redirection {
pub fn new(url: &'static str) -> Box<Self> { pub fn new(url: &str) -> Box<Self> {
Box::new(Redirection { Box::new(Redirection {
to_url: url to_url: url.to_owned()
}) })
} }
} }

View File

@ -52,6 +52,7 @@ extern crate serde;
extern crate serde_json; extern crate serde_json;
extern crate zip; extern crate zip;
extern crate rand; extern crate rand;
extern crate ethabi;
extern crate jsonrpc_core; extern crate jsonrpc_core;
extern crate jsonrpc_http_server; extern crate jsonrpc_http_server;
extern crate parity_dapps; extern crate parity_dapps;
@ -70,6 +71,8 @@ mod api;
mod proxypac; mod proxypac;
mod url; mod url;
pub use self::apps::urlhint::ContractClient;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::net::SocketAddr; use std::net::SocketAddr;
use std::collections::HashMap; use std::collections::HashMap;
@ -84,6 +87,7 @@ static DAPPS_DOMAIN : &'static str = ".parity";
pub struct ServerBuilder { pub struct ServerBuilder {
dapps_path: String, dapps_path: String,
handler: Arc<IoHandler>, handler: Arc<IoHandler>,
registrar: Arc<ContractClient>,
} }
impl Extendable for ServerBuilder { impl Extendable for ServerBuilder {
@ -94,23 +98,24 @@ impl Extendable for ServerBuilder {
impl ServerBuilder { impl ServerBuilder {
/// Construct new dapps server /// Construct new dapps server
pub fn new(dapps_path: String) -> Self { pub fn new(dapps_path: String, registrar: Arc<ContractClient>) -> Self {
ServerBuilder { ServerBuilder {
dapps_path: dapps_path, dapps_path: dapps_path,
handler: Arc::new(IoHandler::new()) handler: Arc::new(IoHandler::new()),
registrar: registrar,
} }
} }
/// Asynchronously start server with no authentication, /// Asynchronously start server with no authentication,
/// returns result with `Server` handle on success or an error. /// returns result with `Server` handle on success or an error.
pub fn start_unsecure_http(&self, addr: &SocketAddr) -> Result<Server, ServerError> { pub fn start_unsecure_http(&self, addr: &SocketAddr) -> Result<Server, ServerError> {
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`, /// Asynchronously start server with `HTTP Basic Authentication`,
/// return result with `Server` handle on success or an error. /// 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, ServerError> { pub fn start_basic_auth_http(&self, addr: &SocketAddr, username: &str, password: &str) -> Result<Server, ServerError> {
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 { impl Server {
fn start_http<A: Authorization + 'static>(addr: &SocketAddr, authorization: A, handler: Arc<IoHandler>, dapps_path: String) -> Result<Server, ServerError> { fn start_http<A: Authorization + 'static>(
addr: &SocketAddr,
authorization: A,
handler: Arc<IoHandler>,
dapps_path: String,
registrar: Arc<ContractClient>,
) -> Result<Server, ServerError> {
let panic_handler = Arc::new(Mutex::new(None)); let panic_handler = Arc::new(Mutex::new(None));
let authorization = Arc::new(authorization); let authorization = Arc::new(authorization);
let 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 endpoints = Arc::new(apps::all_endpoints(dapps_path));
let special = Arc::new({ let special = Arc::new({
let mut special = HashMap::new(); let mut special = HashMap::new();

View File

@ -187,7 +187,8 @@ fn should_extract_path_with_appid() {
path: EndpointPath { path: EndpointPath {
app_id: "app".to_owned(), app_id: "app".to_owned(),
host: "".to_owned(), host: "".to_owned(),
port: 8080 port: 8080,
using_dapps_domains: true,
}, },
file: None, file: None,
safe_to_embed: true, safe_to_embed: true,

View File

@ -86,9 +86,10 @@ impl<A: Authorization + 'static> server::Handler<HttpStream> for Router<A> {
let control = self.control.take().expect("on_request is called only once, thus control is always defined."); let control = self.control.take().expect("on_request is called only once, thus control is always defined.");
self.fetch.to_handler(path.clone(), control) self.fetch.to_handler(path.clone(), control)
}, },
// Redirection to main page // Redirection to main page (maybe 404 instead?)
_ if *req.method() == hyper::method::Method::Get => { (Some(ref path), _) if *req.method() == hyper::method::Method::Get => {
Redirection::new(self.main_page) let address = apps::redirection_address(path.using_dapps_domains, self.main_page);
Redirection::new(address.as_str())
}, },
// RPC by default // RPC by default
_ => { _ => {
@ -165,6 +166,7 @@ fn extract_endpoint(url: &Option<Url>) -> (Option<EndpointPath>, SpecialEndpoint
app_id: id, app_id: id,
host: domain.clone(), host: domain.clone(),
port: url.port, port: url.port,
using_dapps_domains: true,
}), special_endpoint(url)) }), special_endpoint(url))
}, },
_ if url.path.len() > 1 => { _ if url.path.len() > 1 => {
@ -173,6 +175,7 @@ fn extract_endpoint(url: &Option<Url>) -> (Option<EndpointPath>, SpecialEndpoint
app_id: id.clone(), app_id: id.clone(),
host: format!("{}", url.host), host: format!("{}", url.host),
port: url.port, port: url.port,
using_dapps_domains: false,
}), special_endpoint(url)) }), special_endpoint(url))
}, },
_ => (None, special_endpoint(url)), _ => (None, special_endpoint(url)),
@ -192,6 +195,7 @@ fn should_extract_endpoint() {
app_id: "status".to_owned(), app_id: "status".to_owned(),
host: "localhost".to_owned(), host: "localhost".to_owned(),
port: 8080, port: 8080,
using_dapps_domains: false,
}), SpecialEndpoint::None) }), SpecialEndpoint::None)
); );
@ -202,6 +206,7 @@ fn should_extract_endpoint() {
app_id: "rpc".to_owned(), app_id: "rpc".to_owned(),
host: "localhost".to_owned(), host: "localhost".to_owned(),
port: 8080, port: 8080,
using_dapps_domains: false,
}), SpecialEndpoint::Rpc) }), SpecialEndpoint::Rpc)
); );
@ -211,6 +216,7 @@ fn should_extract_endpoint() {
app_id: "my.status".to_owned(), app_id: "my.status".to_owned(),
host: "my.status.parity".to_owned(), host: "my.status.parity".to_owned(),
port: 80, port: 80,
using_dapps_domains: true,
}), SpecialEndpoint::Utils) }), SpecialEndpoint::Utils)
); );
@ -221,6 +227,7 @@ fn should_extract_endpoint() {
app_id: "my.status".to_owned(), app_id: "my.status".to_owned(),
host: "my.status.parity".to_owned(), host: "my.status.parity".to_owned(),
port: 80, port: 80,
using_dapps_domains: true,
}), SpecialEndpoint::None) }), SpecialEndpoint::None)
); );
@ -231,6 +238,7 @@ fn should_extract_endpoint() {
app_id: "my.status".to_owned(), app_id: "my.status".to_owned(),
host: "my.status.parity".to_owned(), host: "my.status.parity".to_owned(),
port: 80, port: 80,
using_dapps_domains: true,
}), SpecialEndpoint::Rpc) }), SpecialEndpoint::Rpc)
); );
@ -241,6 +249,7 @@ fn should_extract_endpoint() {
app_id: "my.status".to_owned(), app_id: "my.status".to_owned(),
host: "my.status.parity".to_owned(), host: "my.status.parity".to_owned(),
port: 80, port: 80,
using_dapps_domains: true,
}), SpecialEndpoint::Api) }), SpecialEndpoint::Api)
); );
} }

View File

@ -723,8 +723,8 @@ impl MinerService for Miner {
.position(|t| t == *hash) .position(|t| t == *hash)
.map(|index| { .map(|index| {
let prev_gas = if index == 0 { Default::default() } else { pending.receipts()[index - 1].gas_used }; let prev_gas = if index == 0 { Default::default() } else { pending.receipts()[index - 1].gas_used };
let ref tx = txs[index]; let tx = &txs[index];
let ref receipt = pending.receipts()[index]; let receipt = &pending.receipts()[index];
RichReceipt { RichReceipt {
transaction_hash: hash.clone(), transaction_hash: hash.clone(),
transaction_index: index, transaction_index: index,

View File

@ -20,7 +20,6 @@ extern crate ethstore;
use std::{env, process, fs}; use std::{env, process, fs};
use std::io::Read; use std::io::Read;
use std::ops::Deref;
use docopt::Docopt; use docopt::Docopt;
use ethstore::ethkey::Address; use ethstore::ethkey::Address;
use ethstore::dir::{KeyDirectory, ParityDirectory, DiskDirectory, GethDirectory, DirectoryType}; use ethstore::dir::{KeyDirectory, ParityDirectory, DiskDirectory, GethDirectory, DirectoryType};
@ -142,7 +141,7 @@ fn execute<S, I>(command: I) -> Result<String, Error> where I: IntoIterator<Item
} else if args.cmd_import { } else if args.cmd_import {
let src = try!(key_dir(&args.flag_src)); let src = try!(key_dir(&args.flag_src));
let dst = try!(key_dir(&args.flag_dir)); let dst = try!(key_dir(&args.flag_dir));
let accounts = try!(import_accounts(*src, *dst)); let accounts = try!(import_accounts(&*src, &*dst));
Ok(format_accounts(&accounts)) Ok(format_accounts(&accounts))
} else if args.cmd_import_wallet { } else if args.cmd_import_wallet {
let wallet = try!(PresaleWallet::open(&args.arg_path)); let wallet = try!(PresaleWallet::open(&args.arg_path));
@ -162,7 +161,7 @@ fn execute<S, I>(command: I) -> Result<String, Error> where I: IntoIterator<Item
let signature = try!(store.sign(&address, &password, &message)); let signature = try!(store.sign(&address, &password, &message));
Ok(format!("{}", signature)) Ok(format!("{}", signature))
} else { } else {
unreachable!(); Ok(format!("{}", USAGE))
} }
} }

View File

@ -15,16 +15,11 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::sync::Arc; use std::sync::Arc;
use std::net::SocketAddr;
use io::PanicHandler; use io::PanicHandler;
use rpc_apis; use rpc_apis;
use ethcore::client::Client;
use helpers::replace_home; 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)] #[derive(Debug, PartialEq, Clone)]
pub struct Configuration { pub struct Configuration {
pub enabled: bool, pub enabled: bool,
@ -51,6 +46,7 @@ impl Default for Configuration {
pub struct Dependencies { pub struct Dependencies {
pub panic_handler: Arc<PanicHandler>, pub panic_handler: Arc<PanicHandler>,
pub apis: Arc<rpc_apis::Dependencies>, pub apis: Arc<rpc_apis::Dependencies>,
pub client: Arc<Client>,
} }
pub fn new(configuration: Configuration, deps: Dependencies) -> Result<Option<WebappServer>, String> { pub fn new(configuration: Configuration, deps: Dependencies) -> Result<Option<WebappServer>, String> {
@ -75,7 +71,15 @@ pub fn new(configuration: Configuration, deps: Dependencies) -> Result<Option<We
Ok(Some(try!(setup_dapps_server(deps, configuration.dapps_path, &addr, auth)))) Ok(Some(try!(setup_dapps_server(deps, configuration.dapps_path, &addr, auth))))
} }
pub use self::server::WebappServer;
pub use self::server::setup_dapps_server;
#[cfg(not(feature = "dapps"))] #[cfg(not(feature = "dapps"))]
mod server {
use super::Dependencies;
use std::net::SocketAddr;
pub struct WebappServer;
pub fn setup_dapps_server( pub fn setup_dapps_server(
_deps: Dependencies, _deps: Dependencies,
_dapps_path: String, _dapps_path: String,
@ -84,8 +88,23 @@ pub fn setup_dapps_server(
) -> Result<WebappServer, String> { ) -> Result<WebappServer, String> {
Err("Your Parity version has been compiled without WebApps support.".into()) Err("Your Parity version has been compiled without WebApps support.".into())
} }
}
#[cfg(feature = "dapps")] #[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( pub fn setup_dapps_server(
deps: Dependencies, deps: Dependencies,
dapps_path: String, dapps_path: String,
@ -94,7 +113,9 @@ pub fn setup_dapps_server(
) -> Result<WebappServer, String> { ) -> Result<WebappServer, String> {
use ethcore_dapps as dapps; use ethcore_dapps as dapps;
let server = dapps::ServerBuilder::new(dapps_path); 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 server = rpc_apis::setup_rpc(server, deps.apis.clone(), rpc_apis::ApiSet::UnsafeContext);
let start_result = match auth { let start_result = match auth {
None => { None => {
@ -117,3 +138,35 @@ pub fn setup_dapps_server(
} }
} }
struct Registrar {
client: Arc<Client>,
}
impl ContractClient for Registrar {
fn registrar(&self) -> Result<Address, String> {
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<Bytes, String> {
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
})
}
}
}

View File

@ -223,6 +223,7 @@ pub fn execute(cmd: RunCmd) -> Result<(), String> {
let dapps_deps = dapps::Dependencies { let dapps_deps = dapps::Dependencies {
panic_handler: panic_handler.clone(), panic_handler: panic_handler.clone(),
apis: deps_for_rpc_apis.clone(), apis: deps_for_rpc_apis.clone(),
client: client.clone(),
}; };
// start dapps server // start dapps server