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:
parent
dda57d9294
commit
124a5da75e
15
Cargo.lock
generated
15
Cargo.lock
generated
@ -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)" = "<none>"
|
||||
"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 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"
|
||||
|
@ -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" }
|
||||
|
@ -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<R: URLHint> Drop for AppFetcher<R> {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AppFetcher<URLHintContract> {
|
||||
fn default() -> Self {
|
||||
AppFetcher::new(URLHintContract)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: URLHint> AppFetcher<R> {
|
||||
|
||||
pub fn new(resolver: R) -> Self {
|
||||
@ -84,7 +79,10 @@ impl<R: URLHint> AppFetcher<R> {
|
||||
// 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<R: URLHint> AppFetcher<R> {
|
||||
Some(&AppStatus::Fetching) => {
|
||||
(None, Box::new(ContentHandler::html(
|
||||
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>)
|
||||
},
|
||||
// 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<GithubApp> {
|
||||
fn resolve(&self, _app_id: Bytes) -> Option<GithubApp> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
@ -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<Endpoint> {
|
||||
|
21
dapps/src/apps/registrar.json
Normal file
21
dapps/src/apps/registrar.json
Normal 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"}
|
||||
]
|
6
dapps/src/apps/urlhint.json
Normal file
6
dapps/src/apps/urlhint.json
Normal 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"}
|
||||
]
|
@ -14,13 +14,16 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<GithubApp>;
|
||||
/// RAW Contract interface.
|
||||
/// 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 {
|
||||
fn resolve(&self, app_id: &str) -> Option<GithubApp> {
|
||||
// TODO [todr] use GithubHint contract to check the details
|
||||
// For now we are just accepting patterns: <commithash>.<repo>.<account>.parity
|
||||
let mut app_parts = app_id.split('.');
|
||||
pub struct URLHintContract {
|
||||
urlhint: Contract,
|
||||
registrar: Contract,
|
||||
client: Arc<ContractClient>,
|
||||
}
|
||||
|
||||
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<ContractClient>) -> 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<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)]
|
||||
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<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]
|
||||
fn should_return_valid_url() {
|
||||
|
@ -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)]
|
||||
|
@ -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<H: DappHandler> {
|
||||
control: Option<Control>,
|
||||
status: FetchState,
|
||||
client: Option<Client<Fetch>>,
|
||||
using_dapps_domains: bool,
|
||||
dapp: H,
|
||||
}
|
||||
|
||||
@ -72,6 +73,7 @@ impl<H: DappHandler> AppFetcherHandler<H> {
|
||||
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<H: DappHandler> AppFetcherHandler<H> {
|
||||
control: Some(control),
|
||||
client: Some(client),
|
||||
status: FetchState::NotStarted(app),
|
||||
using_dapps_domains: using_dapps_domains,
|
||||
dapp: handler,
|
||||
}
|
||||
}
|
||||
@ -207,8 +210,7 @@ impl<H: DappHandler> server::Handler<HttpStream> for AppFetcherHandler<H> {
|
||||
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),
|
||||
|
@ -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<Self> {
|
||||
pub fn new(url: &str) -> Box<Self> {
|
||||
Box::new(Redirection {
|
||||
to_url: url
|
||||
to_url: url.to_owned()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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<IoHandler>,
|
||||
registrar: Arc<ContractClient>,
|
||||
}
|
||||
|
||||
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<ContractClient>) -> 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, 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`,
|
||||
/// 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> {
|
||||
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<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 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();
|
||||
|
@ -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,
|
||||
|
@ -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.");
|
||||
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<Url>) -> (Option<EndpointPath>, 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<Url>) -> (Option<EndpointPath>, 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)
|
||||
);
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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<S, I>(command: I) -> Result<String, Error> where I: IntoIterator<Item
|
||||
} else if args.cmd_import {
|
||||
let src = try!(key_dir(&args.flag_src));
|
||||
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))
|
||||
} else if args.cmd_import_wallet {
|
||||
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));
|
||||
Ok(format!("{}", signature))
|
||||
} else {
|
||||
unreachable!();
|
||||
Ok(format!("{}", USAGE))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,16 +15,11 @@
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<PanicHandler>,
|
||||
pub apis: Arc<rpc_apis::Dependencies>,
|
||||
pub client: Arc<Client>,
|
||||
}
|
||||
|
||||
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))))
|
||||
}
|
||||
|
||||
pub use self::server::WebappServer;
|
||||
pub use self::server::setup_dapps_server;
|
||||
|
||||
#[cfg(not(feature = "dapps"))]
|
||||
mod server {
|
||||
use super::Dependencies;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
pub struct WebappServer;
|
||||
pub fn setup_dapps_server(
|
||||
_deps: Dependencies,
|
||||
_dapps_path: String,
|
||||
@ -84,8 +88,23 @@ pub fn setup_dapps_server(
|
||||
) -> Result<WebappServer, String> {
|
||||
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,
|
||||
@ -94,7 +113,9 @@ pub fn setup_dapps_server(
|
||||
) -> Result<WebappServer, String> {
|
||||
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 start_result = match auth {
|
||||
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
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user