From b1eab698d26070660a4fe2ec4a8a4e65ba9a1f40 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Thu, 18 May 2017 12:44:09 +0200 Subject: [PATCH] Light friendly dapps (#5634) * move native_contracts ABIs to JSON files, add urlhint * port hash-fetch to futures, fix tests * fix dapps compilation, defer async port to later * activate dapps server in the light client * better formatting --- Cargo.lock | 2 + dapps/src/apps/fetcher/mod.rs | 21 +- dapps/src/tests/fetch.rs | 2 +- dapps/src/tests/helpers/registrar.rs | 5 +- ethcore/native_contracts/build.rs | 20 +- ethcore/native_contracts/generator/src/lib.rs | 9 +- ethcore/native_contracts/res/registrar.json | 21 ++ .../res/secretstore_acl_storage.json | 3 + .../res/service_transaction.json | 12 + ethcore/native_contracts/res/urlhint.json | 6 + .../res/validator_report.json | 4 + .../native_contracts/res/validator_set.json | 5 + ethcore/native_contracts/src/lib.rs | 2 + ethcore/native_contracts/src/urlhint.rs | 22 ++ hash-fetch/Cargo.toml | 3 +- hash-fetch/src/client.rs | 91 ++++--- hash-fetch/src/lib.rs | 6 +- hash-fetch/src/urlhint.rs | 223 +++++++----------- parity/dapps.rs | 75 ++++-- parity/run.rs | 28 ++- updater/Cargo.toml | 1 + updater/src/lib.rs | 1 + updater/src/updater.rs | 25 +- 23 files changed, 347 insertions(+), 240 deletions(-) create mode 100644 ethcore/native_contracts/res/registrar.json create mode 100644 ethcore/native_contracts/res/secretstore_acl_storage.json create mode 100644 ethcore/native_contracts/res/service_transaction.json create mode 100644 ethcore/native_contracts/res/urlhint.json create mode 100644 ethcore/native_contracts/res/validator_report.json create mode 100644 ethcore/native_contracts/res/validator_set.json create mode 100644 ethcore/native_contracts/src/urlhint.rs diff --git a/Cargo.lock b/Cargo.lock index 83bb943a0..e8c94d9c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1640,6 +1640,7 @@ dependencies = [ "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "mime 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "mime_guess 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)", + "native-contracts 0.1.0", "parity-reactor 0.1.0", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1798,6 +1799,7 @@ dependencies = [ "ethcore-ipc-codegen 1.7.0", "ethcore-util 1.7.0", "ethsync 1.7.0", + "futures 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", "ipc-common-types 1.7.0", "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "parity-hash-fetch 1.7.0", diff --git a/dapps/src/apps/fetcher/mod.rs b/dapps/src/apps/fetcher/mod.rs index 09d275014..ec8004b30 100644 --- a/dapps/src/apps/fetcher/mod.rs +++ b/dapps/src/apps/fetcher/mod.rs @@ -95,6 +95,16 @@ impl ContentFetcher { fn set_status(&self, content_id: &str, status: ContentStatus) { self.cache.lock().insert(content_id.to_owned(), status); } + + // resolve contract call synchronously. + // TODO: port to futures-based hyper and make it all async. + fn resolve(&self, content_id: Vec) -> Option { + use futures::Future; + + self.resolver.resolve(content_id) + .wait() + .unwrap_or_else(|e| { warn!("Error resolving content-id: {}", e); None }) + } } impl Fetcher for ContentFetcher { @@ -108,10 +118,8 @@ impl Fetcher for ContentFetcher { } // fallback to resolver if let Ok(content_id) = content_id.from_hex() { - // else try to resolve the app_id - let has_content = self.resolver.resolve(content_id).is_some(); // if there is content or we are syncing return true - has_content || self.sync.is_major_importing() + self.sync.is_major_importing() || self.resolve(content_id).is_some() } else { false } @@ -137,7 +145,7 @@ impl Fetcher for ContentFetcher { _ => { trace!(target: "dapps", "Content unavailable. Fetching... {:?}", content_id); let content_hex = content_id.from_hex().expect("to_handler is called only when `contains` returns true."); - let content = self.resolver.resolve(content_hex); + let content = self.resolve(content_hex); let cache = self.cache.clone(); let id = content_id.clone(); @@ -225,6 +233,7 @@ mod tests { use std::sync::Arc; use util::Bytes; use fetch::{Fetch, Client}; + use futures::{future, Future, BoxFuture}; use hash_fetch::urlhint::{URLHint, URLHintResult}; use parity_reactor::Remote; @@ -236,8 +245,8 @@ mod tests { #[derive(Clone)] struct FakeResolver; impl URLHint for FakeResolver { - fn resolve(&self, _id: Bytes) -> Option { - None + fn resolve(&self, _id: Bytes) -> BoxFuture, String> { + future::ok(None).boxed() } } diff --git a/dapps/src/tests/fetch.rs b/dapps/src/tests/fetch.rs index 4f343b3a3..7a9773f07 100644 --- a/dapps/src/tests/fetch.rs +++ b/dapps/src/tests/fetch.rs @@ -60,7 +60,7 @@ fn should_return_503_when_syncing_but_should_make_the_calls() { // then response.assert_status("HTTP/1.1 503 Service Unavailable"); - assert_eq!(registrar.calls.lock().len(), 4); + assert_eq!(registrar.calls.lock().len(), 2); assert_security_headers_for_embed(&response.headers); } diff --git a/dapps/src/tests/helpers/registrar.rs b/dapps/src/tests/helpers/registrar.rs index 21496a750..58aad9886 100644 --- a/dapps/src/tests/helpers/registrar.rs +++ b/dapps/src/tests/helpers/registrar.rs @@ -64,9 +64,10 @@ impl ContractClient for FakeRegistrar { Ok(REGISTRAR.parse().unwrap()) } - fn call(&self, address: Address, data: Bytes) -> Result { + fn call(&self, address: Address, data: Bytes) -> ::futures::BoxFuture { let call = (address.to_hex(), data.to_hex()); self.calls.lock().push(call.clone()); - self.responses.lock().get(&call).cloned().expect(&format!("No response for call: {:?}", call)) + let res = self.responses.lock().get(&call).cloned().expect(&format!("No response for call: {:?}", call)); + Box::new(::futures::future::done(res)) } } diff --git a/ethcore/native_contracts/build.rs b/ethcore/native_contracts/build.rs index a56605f75..6b3b4bfac 100644 --- a/ethcore/native_contracts/build.rs +++ b/ethcore/native_contracts/build.rs @@ -20,18 +20,13 @@ use std::path::Path; use std::fs::File; use std::io::Write; -// TODO: `include!` these from files where they're pretty-printed? -const REGISTRY_ABI: &'static str = r#"[{"constant":true,"inputs":[{"name":"_data","type":"address"}],"name":"canReverse","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"bytes32"}],"name":"setData","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"string"}],"name":"confirmReverse","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"reserve","outputs":[{"name":"success","type":"bool"}],"payable":true,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"drop","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getAddress","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_amount","type":"uint256"}],"name":"setFee","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_to","type":"address"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getData","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"reserved","outputs":[{"name":"reserved","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"drain","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"string"},{"name":"_who","type":"address"}],"name":"proposeReverse","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"hasReverse","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getUint","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"fee","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"getOwner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"getReverse","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_data","type":"address"}],"name":"reverse","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"uint256"}],"name":"setUint","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"string"},{"name":"_who","type":"address"}],"name":"confirmReverseAs","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"removeReverse","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"address"}],"name":"setAddress","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"}]"#; - -const SERVICE_TRANSACTION_ABI: &'static str = r#"[{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_who","type":"address"}],"name":"certify","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getAddress","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_who","type":"address"}],"name":"revoke","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"delegate","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getUint","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setDelegate","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"}],"name":"certified","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"get","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"}]"#; - -const SECRETSTORE_ACL_STORAGE_ABI: &'static str = r#"[{"constant":true,"inputs":[{"name":"user","type":"address"},{"name":"document","type":"bytes32"}],"name":"checkPermissions","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"}]"#; - -// be very careful changing these: ensure `ethcore/engines` validator sets have corresponding -// changes. -const VALIDATOR_SET_ABI: &'static str = r#"[{"constant":true,"inputs":[],"name":"transitionNonce","outputs":[{"name":"nonce","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"validators","type":"address[]"}],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_parent_hash","type":"bytes32"},{"indexed":true,"name":"_nonce","type":"uint256"},{"indexed":false,"name":"_new_set","type":"address[]"}],"name":"ValidatorsChanged","type":"event"}]"#; - -const VALIDATOR_REPORT_ABI: &'static str = r#"[{"constant":false,"inputs":[{"name":"validator","type":"address"},{"name":"blockNumber","type":"uint256"},{"name":"proof","type":"bytes"}],"name":"reportMalicious","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"validator","type":"address"},{"name":"blockNumber","type":"uint256"}],"name":"reportBenign","outputs":[],"payable":false,"type":"function"}]"#; +// TODO: just walk the "res" directory and generate whole crate automatically. +const REGISTRY_ABI: &'static str = include_str!("res/registrar.json"); +const URLHINT_ABI: &'static str = include_str!("res/urlhint.json"); +const SERVICE_TRANSACTION_ABI: &'static str = include_str!("res/service_transaction.json"); +const SECRETSTORE_ACL_STORAGE_ABI: &'static str = include_str!("res/secretstore_acl_storage.json"); +const VALIDATOR_SET_ABI: &'static str = include_str!("res/validator_set.json"); +const VALIDATOR_REPORT_ABI: &'static str = include_str!("res/validator_report.json"); const TEST_VALIDATOR_SET_ABI: &'static str = r#"[{"constant":true,"inputs":[],"name":"transitionNonce","outputs":[{"name":"n","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"newValidators","type":"address[]"}],"name":"setValidators","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"vals","type":"address[]"}],"payable":false,"type":"function"},{"inputs":[],"payable":false,"type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_parent_hash","type":"bytes32"},{"indexed":true,"name":"_nonce","type":"uint256"},{"indexed":false,"name":"_new_set","type":"address[]"}],"name":"ValidatorsChanged","type":"event"}]"#; @@ -51,6 +46,7 @@ fn build_test_contracts() { fn main() { build_file("Registry", REGISTRY_ABI, "registry.rs"); + build_file("Urlhint", URLHINT_ABI, "urlhint.rs"); build_file("ServiceTransactionChecker", SERVICE_TRANSACTION_ABI, "service_transaction.rs"); build_file("SecretStoreAclStorage", SECRETSTORE_ACL_STORAGE_ABI, "secretstore_acl_storage.rs"); build_file("ValidatorSet", VALIDATOR_SET_ABI, "validator_set.rs"); diff --git a/ethcore/native_contracts/generator/src/lib.rs b/ethcore/native_contracts/generator/src/lib.rs index 76985e682..41d5319bc 100644 --- a/ethcore/native_contracts/generator/src/lib.rs +++ b/ethcore/native_contracts/generator/src/lib.rs @@ -50,6 +50,8 @@ use futures::{{future, Future, IntoFuture, BoxFuture}}; use ethabi::{{Contract, Interface, Token, Event}}; use util::{{self, Uint}}; +/// Generated Rust bindings to an Ethereum contract. +#[derive(Clone, Debug)] pub struct {name} {{ contract: Contract, /// Address to make calls to. @@ -101,6 +103,7 @@ fn generate_functions(contract: &Contract) -> Result { functions.push_str(&format!(r##" /// Call the function "{abi_name}" on the contract. +/// /// Inputs: {abi_inputs:?} /// Outputs: {abi_outputs:?} pub fn {snake_name}(&self, call: F, {params}) -> BoxFuture<{output_type}, String> @@ -121,7 +124,7 @@ pub fn {snake_name}(&self, call: F, {params}) -> BoxFuture<{output_type}, call_future .into_future() .and_then(move |out| function.decode_output(out).map_err(|e| format!("{{:?}}", e))) - .map(::std::collections::VecDeque::from) + .map(Vec::into_iter) .and_then(|mut outputs| {decode_outputs}) .boxed() }} @@ -174,7 +177,7 @@ fn input_params_codegen(inputs: &[ParamType]) -> Result<(String, String), ParamT // as a tuple, and the second gives code to get that tuple from a deque of tokens. // // produce output type of the form (type_1, type_2, ...) without trailing comma. -// produce code for getting this output type from `outputs: VecDeque`, where +// produce code for getting this output type from `outputs: Vec::IntoIter`, where // an `Err(String)` can be returned. // // returns any unsupported param type encountered. @@ -190,7 +193,7 @@ fn output_params_codegen(outputs: &[ParamType]) -> Result<(String, String), Para decode_outputs.push_str(&format!( r#" outputs - .pop_front() + .next() .and_then(|output| {{ {} }}) .ok_or_else(|| "Wrong output type".to_string())? "#, diff --git a/ethcore/native_contracts/res/registrar.json b/ethcore/native_contracts/res/registrar.json new file mode 100644 index 000000000..38edcc787 --- /dev/null +++ b/ethcore/native_contracts/res/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/ethcore/native_contracts/res/secretstore_acl_storage.json b/ethcore/native_contracts/res/secretstore_acl_storage.json new file mode 100644 index 000000000..cfdefd9c7 --- /dev/null +++ b/ethcore/native_contracts/res/secretstore_acl_storage.json @@ -0,0 +1,3 @@ +[ + {"constant":true,"inputs":[{"name":"user","type":"address"},{"name":"document","type":"bytes32"}],"name":"checkPermissions","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"} +] diff --git a/ethcore/native_contracts/res/service_transaction.json b/ethcore/native_contracts/res/service_transaction.json new file mode 100644 index 000000000..6d556a2ea --- /dev/null +++ b/ethcore/native_contracts/res/service_transaction.json @@ -0,0 +1,12 @@ +[ + {"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"}, + {"constant":false,"inputs":[{"name":"_who","type":"address"}],"name":"certify","outputs":[],"payable":false,"type":"function"}, + {"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getAddress","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"}, + {"constant":false,"inputs":[{"name":"_who","type":"address"}],"name":"revoke","outputs":[],"payable":false,"type":"function"}, + {"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"}, + {"constant":true,"inputs":[],"name":"delegate","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"}, + {"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getUint","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"}, + {"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setDelegate","outputs":[],"payable":false,"type":"function"}, + {"constant":true,"inputs":[{"name":"_who","type":"address"}],"name":"certified","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"}, + {"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"get","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"} +] diff --git a/ethcore/native_contracts/res/urlhint.json b/ethcore/native_contracts/res/urlhint.json new file mode 100644 index 000000000..629f166bb --- /dev/null +++ b/ethcore/native_contracts/res/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/ethcore/native_contracts/res/validator_report.json b/ethcore/native_contracts/res/validator_report.json new file mode 100644 index 000000000..093f4bebd --- /dev/null +++ b/ethcore/native_contracts/res/validator_report.json @@ -0,0 +1,4 @@ +[ + {"constant":false,"inputs":[{"name":"validator","type":"address"},{"name":"blockNumber","type":"uint256"},{"name":"proof","type":"bytes"}],"name":"reportMalicious","outputs":[],"payable":false,"type":"function"}, + {"constant":false,"inputs":[{"name":"validator","type":"address"},{"name":"blockNumber","type":"uint256"}],"name":"reportBenign","outputs":[],"payable":false,"type":"function"} +] diff --git a/ethcore/native_contracts/res/validator_set.json b/ethcore/native_contracts/res/validator_set.json new file mode 100644 index 000000000..8c2a5e8c8 --- /dev/null +++ b/ethcore/native_contracts/res/validator_set.json @@ -0,0 +1,5 @@ +[ + {"constant":true,"inputs":[],"name":"transitionNonce","outputs":[{"name":"nonce","type":"uint256"}],"payable":false,"type":"function"}, + {"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"validators","type":"address[]"}],"payable":false,"type":"function"}, + {"anonymous":false,"inputs":[{"indexed":true,"name":"_parent_hash","type":"bytes32"},{"indexed":true,"name":"_nonce","type":"uint256"},{"indexed":false,"name":"_new_set","type":"address[]"}],"name":"ValidatorsChanged","type":"event"} +] diff --git a/ethcore/native_contracts/src/lib.rs b/ethcore/native_contracts/src/lib.rs index d33d7a22a..e35a4ec19 100644 --- a/ethcore/native_contracts/src/lib.rs +++ b/ethcore/native_contracts/src/lib.rs @@ -24,6 +24,7 @@ extern crate ethabi; extern crate ethcore_util as util; mod registry; +mod urlhint; mod service_transaction; mod secretstore_acl_storage; mod validator_set; @@ -32,6 +33,7 @@ mod validator_report; pub mod test_contracts; pub use self::registry::Registry; +pub use self::urlhint::Urlhint; pub use self::service_transaction::ServiceTransactionChecker; pub use self::secretstore_acl_storage::SecretStoreAclStorage; pub use self::validator_set::ValidatorSet; diff --git a/ethcore/native_contracts/src/urlhint.rs b/ethcore/native_contracts/src/urlhint.rs new file mode 100644 index 000000000..d2085e593 --- /dev/null +++ b/ethcore/native_contracts/src/urlhint.rs @@ -0,0 +1,22 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +#![allow(unused_mut, unused_variables, unused_imports)] + +//! Registrar contract: maps names to arbitrary URL. +// TODO: testing. + +include!(concat!(env!("OUT_DIR"), "/urlhint.rs")); diff --git a/hash-fetch/Cargo.toml b/hash-fetch/Cargo.toml index d24315eb0..0ad1149aa 100644 --- a/hash-fetch/Cargo.toml +++ b/hash-fetch/Cargo.toml @@ -7,7 +7,7 @@ version = "1.7.0" authors = ["Parity Technologies "] [dependencies] -ethabi = "1.0.0" +ethabi = "1.0.4" futures = "0.1" log = "0.3" mime = "0.2" @@ -17,3 +17,4 @@ rustc-serialize = "0.3" fetch = { path = "../util/fetch" } ethcore-util = { path = "../util" } parity-reactor = { path = "../util/reactor" } +native-contracts = { path = "../ethcore/native_contracts" } diff --git a/hash-fetch/src/client.rs b/hash-fetch/src/client.rs index 958fc7aa2..d52bda7d9 100644 --- a/hash-fetch/src/client.rs +++ b/hash-fetch/src/client.rs @@ -87,6 +87,28 @@ impl From for Error { } } +fn validate_hash(path: PathBuf, hash: H256, result: Result) -> Result { + let response = result?; + if !response.is_success() { + return Err(Error::InvalidStatus); + } + + // Read the response + let mut reader = io::BufReader::new(response); + let mut writer = io::BufWriter::new(fs::File::create(&path)?); + io::copy(&mut reader, &mut writer)?; + writer.flush()?; + + // And validate the hash + let mut file_reader = io::BufReader::new(fs::File::open(&path)?); + let content_hash = sha3(&mut file_reader)?; + if content_hash != hash { + Err(Error::HashMismatch{ got: content_hash, expected: hash }) + } else { + Ok(path) + } +} + /// Default Hash-fetching client using on-chain contract to resolve hashes to URLs. pub struct Client { contract: URLHintContract, @@ -103,7 +125,6 @@ impl Client { } impl Client { - /// Creates new instance of the `Client` given on-chain contract client, fetch service and task runner. pub fn with_fetch(contract: Arc, fetch: F, remote: Remote) -> Self { Client { @@ -119,44 +140,22 @@ impl HashFetch for Client { fn fetch(&self, hash: H256, on_done: Box) + Send>) { debug!(target: "fetch", "Fetching: {:?}", hash); - let url = self.contract.resolve(hash.to_vec()).map(|content| match content { - URLHintResult::Dapp(dapp) => { - dapp.url() - }, - URLHintResult::Content(content) => { - content.url - }, - }).ok_or_else(|| Error::NoResolution); - - debug!(target: "fetch", "Resolved {:?} to {:?}. Fetching...", hash, url); - - match url { - Err(err) => on_done(Err(err)), - Ok(url) => { - let random_path = self.random_path.clone(); - let future = self.fetch.fetch(&url).then(move |result| { - fn validate_hash(path: PathBuf, hash: H256, result: Result) -> Result { - let response = result?; - if !response.is_success() { - return Err(Error::InvalidStatus); - } - - // Read the response - let mut reader = io::BufReader::new(response); - let mut writer = io::BufWriter::new(fs::File::create(&path)?); - io::copy(&mut reader, &mut writer)?; - writer.flush()?; - - // And validate the hash - let mut file_reader = io::BufReader::new(fs::File::open(&path)?); - let content_hash = sha3(&mut file_reader)?; - if content_hash != hash { - Err(Error::HashMismatch{ got: content_hash, expected: hash }) - } else { - Ok(path) - } - } - + let random_path = self.random_path.clone(); + let remote_fetch = self.fetch.clone(); + let future = self.contract.resolve(hash.to_vec()) + .map_err(|e| { warn!("Error resolving URL: {}", e); Error::NoResolution }) + .and_then(|maybe_url| maybe_url.ok_or(Error::NoResolution)) + .map(|content| match content { + URLHintResult::Dapp(dapp) => { + dapp.url() + }, + URLHintResult::Content(content) => { + content.url + }, + }) + .and_then(move |url| { + debug!(target: "fetch", "Resolved {:?} to {:?}. Fetching...", hash, url); + let future = remote_fetch.fetch(&url).then(move |result| { debug!(target: "fetch", "Content fetched, validating hash ({:?})", hash); let path = random_path(); let res = validate_hash(path.clone(), hash, result); @@ -165,13 +164,13 @@ impl HashFetch for Client { // Remove temporary file in case of error let _ = fs::remove_file(&path); } - on_done(res); - - Ok(()) as Result<(), ()> + res }); - self.remote.spawn(self.fetch.process(future)); - }, - } + remote_fetch.process(future) + }) + .then(move |res| { on_done(res); Ok(()) as Result<(), ()> }); + + self.remote.spawn(future); } } @@ -225,7 +224,7 @@ mod tests { let mut registrar = FakeRegistrar::new(); registrar.responses = Mutex::new(vec![ Ok(format!("000000000000000000000000{}", URLHINT).from_hex().unwrap()), - Ok("00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000deadcafebeefbeefcafedeaddeedfeedffffffff000000000000000000000000000000000000000000000000000000000000003d68747470733a2f2f657468636f72652e696f2f6173736574732f696d616765732f657468636f72652d626c61636b2d686f72697a6f6e74616c2e706e67000000".from_hex().unwrap()), + Ok("00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000deadcafebeefbeefcafedeaddeedfeedffffffff000000000000000000000000000000000000000000000000000000000000003c68747470733a2f2f7061726974792e696f2f6173736574732f696d616765732f657468636f72652d626c61636b2d686f72697a6f6e74616c2e706e6700000000".from_hex().unwrap()), ]); registrar } diff --git a/hash-fetch/src/lib.rs b/hash-fetch/src/lib.rs index e55b2d841..9019f744d 100644 --- a/hash-fetch/src/lib.rs +++ b/hash-fetch/src/lib.rs @@ -25,12 +25,14 @@ extern crate mime; extern crate ethabi; extern crate ethcore_util as util; -pub extern crate fetch; extern crate futures; extern crate mime_guess; +extern crate native_contracts; +extern crate parity_reactor; extern crate rand; extern crate rustc_serialize; -extern crate parity_reactor; + +pub extern crate fetch; mod client; diff --git a/hash-fetch/src/urlhint.rs b/hash-fetch/src/urlhint.rs index 156420dad..cd7d2a2c5 100644 --- a/hash-fetch/src/urlhint.rs +++ b/hash-fetch/src/urlhint.rs @@ -16,13 +16,13 @@ //! URLHint Contract -use std::fmt; use std::sync::Arc; use rustc_serialize::hex::ToHex; use mime::Mime; use mime_guess; -use ethabi::{Interface, Contract, Token}; +use futures::{future, BoxFuture, Future}; +use native_contracts::{Registry, Urlhint}; use util::{Address, Bytes, Hashable}; const COMMIT_LEN: usize = 20; @@ -33,7 +33,7 @@ pub trait ContractClient: Send + Sync { /// Get registrar address fn registrar(&self) -> Result; /// Call Contract - fn call(&self, address: Address, data: Bytes) -> Result; + fn call(&self, address: Address, data: Bytes) -> BoxFuture; } /// Github-hosted dapp. @@ -44,7 +44,7 @@ pub struct GithubApp { /// Github Repository pub repo: String, /// Commit on Github - pub commit: [u8;COMMIT_LEN], + pub commit: [u8; COMMIT_LEN], /// Dapp owner address pub owner: Address, } @@ -94,146 +94,91 @@ pub enum URLHintResult { /// URLHint Contract interface pub trait URLHint: Send + Sync { /// Resolves given id to registrar entry. - fn resolve(&self, id: Bytes) -> Option; + fn resolve(&self, id: Bytes) -> BoxFuture, String>; } /// `URLHintContract` API #[derive(Clone)] pub struct URLHintContract { - urlhint: Contract, - registrar: Contract, + urlhint: Arc, + registrar: Registry, client: Arc, } impl URLHintContract { /// Creates new `URLHintContract` pub fn new(client: Arc) -> Self { - let urlhint = Interface::load(include_bytes!("../res/urlhint.json")).expect("urlhint.json is valid ABI"); - let registrar = Interface::load(include_bytes!("../res/registrar.json")).expect("registrar.json is valid ABI"); - URLHintContract { - urlhint: Contract::new(urlhint), - registrar: Contract::new(registrar), + urlhint: Arc::new(Urlhint::new(Default::default())), + registrar: Registry::new(Default::default()), client: client, } } - - fn urlhint_address(&self) -> Option
{ - let res = || { - let get_address = self.registrar.function("getAddress".into()).map_err(as_string)?; - let params = get_address.encode_call( - vec![Token::FixedBytes((*"githubhint".sha3()).to_vec()), Token::String("A".into())] - ).map_err(as_string)?; - let output = self.client.call(self.client.registrar()?, params)?; - let result = 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, id: Bytes) -> Option { - let call = self.urlhint - .function("entries".into()) - .and_then(|f| f.encode_call(vec![Token::FixedBytes(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 { - 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().expect("element 0 of 3-len vector known to exist; qed"); - let commit = it.next().expect("element 1 of 3-len vector known to exist; qed"); - let owner = it.next().expect("element 2 of 3-len vector known to exist qed"); - - 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 commit = GithubApp::commit(&commit); - if commit == Some(Default::default()) { - let mime = guess_mime_type(&account_slash_repo).unwrap_or(mime!(Application/_)); - return Some(URLHintResult::Content(Content { - url: account_slash_repo, - mime: mime, - owner: owner, - })); - } - - 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, - } - }; - - commit.map(|commit| URLHintResult::Dapp(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 - } - } +} + +fn decode_urlhint_output(output: (String, ::util::H160, Address)) -> Option { + let (account_slash_repo, commit, owner) = output; + + if owner == Address::default() { + return None; + } + + let commit = GithubApp::commit(&commit); + if commit == Some(Default::default()) { + let mime = guess_mime_type(&account_slash_repo).unwrap_or(mime!(Application/_)); + return Some(URLHintResult::Content(Content { + url: account_slash_repo, + mime: mime, + owner: owner, + })); + } + + 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, + } + }; + + commit.map(|commit| URLHintResult::Dapp(GithubApp { + account: account, + repo: repo, + commit: commit, + owner: owner, + })) } impl URLHint for URLHintContract { - fn resolve(&self, id: Bytes) -> Option { - self.urlhint_address().and_then(|address| { - // Prepare contract call - self.encode_urlhint_call(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); + fn resolve(&self, id: Bytes) -> BoxFuture, String> { + use futures::future::Either; + + let do_call = |_, data| { + let addr = match self.client.registrar() { + Ok(addr) => addr, + Err(e) => return future::err(e).boxed(), + }; + + self.client.call(addr, data) + }; + + let urlhint = self.urlhint.clone(); + let client = self.client.clone(); + self.registrar.get_address(do_call, "githubhint".sha3(), "A".into()) + .map(|addr| if addr == Address::default() { None } else { Some(addr) }) + .and_then(move |address| { + let mut fixed_id = [0; 32]; + let len = ::std::cmp::min(32, id.len()); + fixed_id[..len].copy_from_slice(&id[..len]); + + match address { + None => Either::A(future::ok(None)), + Some(address) => { + let do_call = move |_, data| client.call(address, data); + Either::B(urlhint.entries(do_call, ::util::H256(fixed_id)).map(decode_urlhint_output)) } - call.ok() - }) - .and_then(|output| self.decode_urlhint_output(output)) - }) + } + }).boxed() } } @@ -260,16 +205,14 @@ fn guess_mime_type(url: &str) -> Option { }) } -fn as_string(e: T) -> String { - format!("{:?}", e) -} - #[cfg(test)] pub mod tests { use std::sync::Arc; use std::str::FromStr; use rustc_serialize::hex::FromHex; + use futures::{BoxFuture, Future, IntoFuture}; + use super::*; use super::guess_mime_type; use util::{Bytes, Address, Mutex, ToPretty}; @@ -297,14 +240,14 @@ pub mod tests { } impl ContractClient for FakeRegistrar { - fn registrar(&self) -> Result { Ok(REGISTRAR.parse().unwrap()) } - fn call(&self, address: Address, data: Bytes) -> Result { + fn call(&self, address: Address, data: Bytes) -> BoxFuture { self.calls.lock().push((address.to_hex(), data.to_hex())); - self.responses.lock().remove(0) + let res = self.responses.lock().remove(0); + res.into_future().boxed() } } @@ -312,11 +255,19 @@ pub mod tests { fn should_call_registrar_and_urlhint_contracts() { // given let registrar = FakeRegistrar::new(); + let resolve_result = { + use ethabi::{Encoder, Token}; + Encoder::encode(vec![Token::String(String::new()), Token::FixedBytes(vec![0; 20]), Token::Address([0; 20])]) + }; + registrar.responses.lock()[1] = Ok(resolve_result); + let calls = registrar.calls.clone(); let urlhint = URLHintContract::new(Arc::new(registrar)); + + // when - let res = urlhint.resolve("test".bytes().collect()); + let res = urlhint.resolve("test".bytes().collect()).wait().unwrap(); let calls = calls.lock(); let call0 = calls.get(0).expect("Registrar resolve called"); let call1 = calls.get(1).expect("URLHint Resolve called"); @@ -344,7 +295,7 @@ pub mod tests { let urlhint = URLHintContract::new(Arc::new(registrar)); // when - let res = urlhint.resolve("test".bytes().collect()); + let res = urlhint.resolve("test".bytes().collect()).wait().unwrap(); // then assert_eq!(res, Some(URLHintResult::Dapp(GithubApp { @@ -361,12 +312,12 @@ pub mod tests { let mut registrar = FakeRegistrar::new(); registrar.responses = Mutex::new(vec![ Ok(format!("000000000000000000000000{}", URLHINT).from_hex().unwrap()), - Ok("00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000deadcafebeefbeefcafedeaddeedfeedffffffff000000000000000000000000000000000000000000000000000000000000003d68747470733a2f2f657468636f72652e696f2f6173736574732f696d616765732f657468636f72652d626c61636b2d686f72697a6f6e74616c2e706e67000000".from_hex().unwrap()), + Ok("00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000deadcafebeefbeefcafedeaddeedfeedffffffff000000000000000000000000000000000000000000000000000000000000003c68747470733a2f2f7061726974792e696f2f6173736574732f696d616765732f657468636f72652d626c61636b2d686f72697a6f6e74616c2e706e6700000000".from_hex().unwrap()), ]); let urlhint = URLHintContract::new(Arc::new(registrar)); // when - let res = urlhint.resolve("test".bytes().collect()); + let res = urlhint.resolve("test".bytes().collect()).wait().unwrap(); // then assert_eq!(res, Some(URLHintResult::Content(Content { diff --git a/parity/dapps.rs b/parity/dapps.rs index b2dfe16d4..a265a72b6 100644 --- a/parity/dapps.rs +++ b/parity/dapps.rs @@ -20,12 +20,16 @@ use std::sync::Arc; use dir::default_data_path; use ethcore::client::{Client, BlockChainClient, BlockId}; use ethcore::transaction::{Transaction, Action}; +use ethsync::LightSync; +use futures::{future, IntoFuture, Future, BoxFuture}; use hash_fetch::fetch::Client as FetchClient; use hash_fetch::urlhint::ContractClient; use helpers::replace_home; +use light::client::Client as LightClient; +use light::on_demand::{self, OnDemand}; use rpc_apis::SignerService; use parity_reactor; -use util::{Bytes, Address, U256}; +use util::{Bytes, Address}; #[derive(Debug, PartialEq, Clone)] pub struct Configuration { @@ -60,22 +64,61 @@ impl ContractClient for FullRegistrar { }) } - 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); + fn call(&self, address: Address, data: Bytes) -> BoxFuture { + self.client.call_contract(BlockId::Latest, address, data) + .into_future() + .boxed() + } +} - self.client.call(&transaction, BlockId::Latest, Default::default()) - .map_err(|e| format!("{:?}", e)) - .map(|executed| { - executed.output - }) +/// Registrar implementation for the light client. +pub struct LightRegistrar { + /// The light client. + pub client: Arc, + /// Handle to the on-demand service. + pub on_demand: Arc, + /// Handle to the light network service. + pub sync: Arc, +} + +impl ContractClient for LightRegistrar { + fn registrar(&self) -> Result { + self.client.engine().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) -> BoxFuture { + let (header, env_info) = (self.client.best_block_header(), self.client.latest_env_info()); + + let maybe_future = self.sync.with_context(move |ctx| { + self.on_demand + .transaction_proof(ctx, on_demand::request::TransactionProof { + tx: Transaction { + nonce: self.client.engine().account_start_nonce(), + action: Action::Call(address), + gas: 50_000_000.into(), + gas_price: 0.into(), + value: 0.into(), + data: data, + }.fake_sign(Address::default()), + header: header, + env_info: env_info, + engine: self.client.engine().clone(), + }) + .then(|res| match res { + Ok(Ok(executed)) => Ok(executed.output), + Ok(Err(e)) => Err(format!("Failed to execute transaction: {}", e)), + Err(_) => Err(format!("On-demand service dropped request unexpectedly.")), + }) + }); + + match maybe_future { + Some(fut) => fut.boxed(), + None => future::err("cannot query registry: network disabled".into()).boxed(), + } } } diff --git a/parity/run.rs b/parity/run.rs index 8b530adf1..20b553541 100644 --- a/parity/run.rs +++ b/parity/run.rs @@ -279,7 +279,7 @@ fn execute_light(cmd: RunCmd, can_restart: bool, logger: Arc) -> secret_store: account_provider, logger: logger, settings: Arc::new(cmd.net_settings), - on_demand: on_demand, + on_demand: on_demand.clone(), cache: cache, transaction_queue: txq, dapps_interface: match cmd.dapps_conf.enabled { @@ -290,7 +290,7 @@ fn execute_light(cmd: RunCmd, can_restart: bool, logger: Arc) -> true => Some(cmd.http_conf.port), false => None, }, - fetch: fetch, + fetch: fetch.clone(), geth_compatibility: cmd.geth_compatibility, remote: event_loop.remote(), }); @@ -301,9 +301,29 @@ fn execute_light(cmd: RunCmd, can_restart: bool, logger: Arc) -> stats: rpc_stats.clone(), }; + // the dapps server + let dapps_deps = { + let contract_client = Arc::new(::dapps::LightRegistrar { + client: service.client().clone(), + sync: light_sync.clone(), + on_demand: on_demand, + }); + + let sync = light_sync.clone(); + dapps::Dependencies { + sync_status: Arc::new(move || sync.is_major_importing()), + contract_client: contract_client, + remote: event_loop.raw_remote(), + fetch: fetch, + signer: deps_for_rpc_apis.signer_service.clone(), + } + }; + + let dapps_middleware = dapps::new(cmd.dapps_conf.clone(), dapps_deps)?; + // start rpc servers let _ws_server = rpc::new_ws(cmd.ws_conf, &dependencies)?; - let _http_server = rpc::new_http(cmd.http_conf.clone(), &dependencies, None)?; + let _http_server = rpc::new_http(cmd.http_conf.clone(), &dependencies, dapps_middleware)?; let _ipc_server = rpc::new_ipc(cmd.ipc_conf, &dependencies)?; // the signer server @@ -315,8 +335,6 @@ fn execute_light(cmd: RunCmd, can_restart: bool, logger: Arc) -> let signing_queue = deps_for_rpc_apis.signer_service.queue(); let _signer_server = signer::start(cmd.signer_conf.clone(), signing_queue, signer_deps)?; - // TODO: Dapps - // minimal informant thread. Just prints block number every 5 seconds. // TODO: integrate with informant.rs let informant_client = service.client().clone(); diff --git a/updater/Cargo.toml b/updater/Cargo.toml index 53088d030..252549b04 100644 --- a/updater/Cargo.toml +++ b/updater/Cargo.toml @@ -16,6 +16,7 @@ target_info = "0.1" ethcore = { path = "../ethcore" } ethsync = { path = "../sync" } ethcore-util = { path = "../util" } +futures = "0.1" parity-hash-fetch = { path = "../hash-fetch" } ipc-common-types = { path = "../ipc-common-types" } ethcore-ipc = { path = "../ipc/rpc" } diff --git a/updater/src/lib.rs b/updater/src/lib.rs index 730af956d..b27f2039e 100644 --- a/updater/src/lib.rs +++ b/updater/src/lib.rs @@ -24,6 +24,7 @@ extern crate ethcore; extern crate ethabi; extern crate ethsync; extern crate ethcore_ipc as ipc; +extern crate futures; extern crate target_info; extern crate parity_reactor; extern crate path; diff --git a/updater/src/updater.rs b/updater/src/updater.rs index 442b2268d..817688688 100644 --- a/updater/src/updater.rs +++ b/updater/src/updater.rs @@ -14,23 +14,25 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use std::sync::{Arc, Weak}; use std::fs; use std::io::Write; use std::path::{PathBuf}; -use target_info::Target; -use util::misc; -use ipc_common_types::{VersionInfo, ReleaseTrack}; -use path::restrict_permissions_owner; -use util::{Address, H160, H256, Mutex, Bytes}; -use ethsync::{SyncProvider}; +use std::sync::{Arc, Weak}; + use ethcore::client::{BlockId, BlockChainClient, ChainNotify}; +use ethsync::{SyncProvider}; +use futures::{future, Future, BoxFuture}; use hash_fetch::{self as fetch, HashFetch}; use hash_fetch::fetch::Client as FetchService; +use ipc_common_types::{VersionInfo, ReleaseTrack}; use operations::Operations; use parity_reactor::Remote; +use path::restrict_permissions_owner; use service::{Service}; +use target_info::Target; use types::all::{ReleaseInfo, OperationsInfo, CapState}; +use util::{Address, H160, H256, Mutex, Bytes}; +use util::misc; /// Filter for releases. #[derive(Debug, Eq, PartialEq, Clone)] @@ -338,9 +340,12 @@ impl fetch::urlhint::ContractClient for Updater { .ok_or_else(|| "Registrar not available".into()) } - fn call(&self, address: Address, data: Bytes) -> Result { - self.client.upgrade().ok_or_else(|| "Client not available".to_owned())? - .call_contract(BlockId::Latest, address, data) + fn call(&self, address: Address, data: Bytes) -> BoxFuture { + future::done( + self.client.upgrade() + .ok_or_else(|| "Client not available".into()) + .and_then(move |c| c.call_contract(BlockId::Latest, address, data)) + ).boxed() } }