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
This commit is contained in:
Robert Habermeier 2017-05-18 12:44:09 +02:00 committed by Gav Wood
parent 95d9706fe1
commit b1eab698d2
23 changed files with 347 additions and 240 deletions

2
Cargo.lock generated
View File

@ -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",

View File

@ -95,6 +95,16 @@ impl<R: URLHint + 'static, F: Fetch> ContentFetcher<F, R> {
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<u8>) -> Option<URLHintResult> {
use futures::Future;
self.resolver.resolve(content_id)
.wait()
.unwrap_or_else(|e| { warn!("Error resolving content-id: {}", e); None })
}
}
impl<R: URLHint + 'static, F: Fetch> Fetcher for ContentFetcher<F, R> {
@ -108,10 +118,8 @@ impl<R: URLHint + 'static, F: Fetch> Fetcher for ContentFetcher<F, R> {
}
// 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<R: URLHint + 'static, F: Fetch> Fetcher for ContentFetcher<F, R> {
_ => {
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<URLHintResult> {
None
fn resolve(&self, _id: Bytes) -> BoxFuture<Option<URLHintResult>, String> {
future::ok(None).boxed()
}
}

View File

@ -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);
}

View File

@ -64,9 +64,10 @@ impl ContractClient for FakeRegistrar {
Ok(REGISTRAR.parse().unwrap())
}
fn call(&self, address: Address, data: Bytes) -> Result<Bytes, String> {
fn call(&self, address: Address, data: Bytes) -> ::futures::BoxFuture<Bytes, String> {
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))
}
}

View File

@ -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");

View File

@ -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<String, Error> {
functions.push_str(&format!(r##"
/// Call the function "{abi_name}" on the contract.
///
/// Inputs: {abi_inputs:?}
/// Outputs: {abi_outputs:?}
pub fn {snake_name}<F, U>(&self, call: F, {params}) -> BoxFuture<{output_type}, String>
@ -121,7 +124,7 @@ pub fn {snake_name}<F, U>(&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<Token>`, where
// produce code for getting this output type from `outputs: Vec<Token>::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())?
"#,

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,3 @@
[
{"constant":true,"inputs":[{"name":"user","type":"address"},{"name":"document","type":"bytes32"}],"name":"checkPermissions","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"}
]

View File

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

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

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

View File

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

View File

@ -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;

View File

@ -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 <http://www.gnu.org/licenses/>.
#![allow(unused_mut, unused_variables, unused_imports)]
//! Registrar contract: maps names to arbitrary URL.
// TODO: testing.
include!(concat!(env!("OUT_DIR"), "/urlhint.rs"));

View File

@ -7,7 +7,7 @@ version = "1.7.0"
authors = ["Parity Technologies <admin@parity.io>"]
[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" }

View File

@ -87,54 +87,6 @@ impl From<io::Error> for Error {
}
}
/// Default Hash-fetching client using on-chain contract to resolve hashes to URLs.
pub struct Client<F: Fetch + 'static = FetchClient> {
contract: URLHintContract,
fetch: F,
remote: Remote,
random_path: Arc<Fn() -> PathBuf + Sync + Send>,
}
impl Client {
/// Creates new instance of the `Client` given on-chain contract client and task runner.
pub fn new(contract: Arc<ContractClient>, remote: Remote) -> Self {
Client::with_fetch(contract, FetchClient::new().unwrap(), remote)
}
}
impl<F: Fetch + 'static> Client<F> {
/// Creates new instance of the `Client` given on-chain contract client, fetch service and task runner.
pub fn with_fetch(contract: Arc<ContractClient>, fetch: F, remote: Remote) -> Self {
Client {
contract: URLHintContract::new(contract),
fetch: fetch,
remote: remote,
random_path: Arc::new(random_temp_path),
}
}
}
impl<F: Fetch + 'static> HashFetch for Client<F> {
fn fetch(&self, hash: H256, on_done: Box<Fn(Result<PathBuf, Error>) + 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<Response, FetchError>) -> Result<PathBuf, Error> {
let response = result?;
if !response.is_success() {
@ -157,6 +109,53 @@ impl<F: Fetch + 'static> HashFetch for Client<F> {
}
}
/// Default Hash-fetching client using on-chain contract to resolve hashes to URLs.
pub struct Client<F: Fetch + 'static = FetchClient> {
contract: URLHintContract,
fetch: F,
remote: Remote,
random_path: Arc<Fn() -> PathBuf + Sync + Send>,
}
impl Client {
/// Creates new instance of the `Client` given on-chain contract client and task runner.
pub fn new(contract: Arc<ContractClient>, remote: Remote) -> Self {
Client::with_fetch(contract, FetchClient::new().unwrap(), remote)
}
}
impl<F: Fetch + 'static> Client<F> {
/// Creates new instance of the `Client` given on-chain contract client, fetch service and task runner.
pub fn with_fetch(contract: Arc<ContractClient>, fetch: F, remote: Remote) -> Self {
Client {
contract: URLHintContract::new(contract),
fetch: fetch,
remote: remote,
random_path: Arc::new(random_temp_path),
}
}
}
impl<F: Fetch + 'static> HashFetch for Client<F> {
fn fetch(&self, hash: H256, on_done: Box<Fn(Result<PathBuf, Error>) + Send>) {
debug!(target: "fetch", "Fetching: {:?}", hash);
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<F: Fetch + 'static> HashFetch for Client<F> {
// 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
}

View File

@ -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;

View File

@ -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<Address, String>;
/// Call Contract
fn call(&self, address: Address, data: Bytes) -> Result<Bytes, String>;
fn call(&self, address: Address, data: Bytes) -> BoxFuture<Bytes, String>;
}
/// Github-hosted dapp.
@ -94,91 +94,31 @@ pub enum URLHintResult {
/// URLHint Contract interface
pub trait URLHint: Send + Sync {
/// Resolves given id to registrar entry.
fn resolve(&self, id: Bytes) -> Option<URLHintResult>;
fn resolve(&self, id: Bytes) -> BoxFuture<Option<URLHintResult>, String>;
}
/// `URLHintContract` API
#[derive(Clone)]
pub struct URLHintContract {
urlhint: Contract,
registrar: Contract,
urlhint: Arc<Urlhint>,
registrar: Registry,
client: Arc<ContractClient>,
}
impl URLHintContract {
/// Creates new `URLHintContract`
pub fn new(client: Arc<ContractClient>) -> 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<Address> {
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<Bytes> {
let call = self.urlhint
.function("entries".into())
.and_then(|f| f.encode_call(vec![Token::FixedBytes(id)]));
fn decode_urlhint_output(output: (String, ::util::H160, Address)) -> Option<URLHintResult> {
let (account_slash_repo, commit, owner) = output;
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<URLHintResult> {
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;
}
@ -207,33 +147,38 @@ impl URLHintContract {
commit: commit,
owner: owner,
}))
},
e => {
warn!(target: "dapps", "Invalid contract output parameters: {:?}", e);
None
},
}
} else {
warn!(target: "dapps", "Invalid contract output: {:?}", output);
None
}
}
}
impl URLHint for URLHintContract {
fn resolve(&self, id: Bytes) -> Option<URLHintResult> {
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<Option<URLHintResult>, 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<Mime> {
})
}
fn as_string<T: fmt::Debug>(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<Address, String> {
Ok(REGISTRAR.parse().unwrap())
}
fn call(&self, address: Address, data: Bytes) -> Result<Bytes, String> {
fn call(&self, address: Address, data: Bytes) -> BoxFuture<Bytes, String> {
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 {

View File

@ -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,23 +64,62 @@ impl ContractClient for FullRegistrar {
})
}
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);
fn call(&self, address: Address, data: Bytes) -> BoxFuture<Bytes, String> {
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<LightClient>,
/// Handle to the on-demand service.
pub on_demand: Arc<OnDemand>,
/// Handle to the light network service.
pub sync: Arc<LightSync>,
}
impl ContractClient for LightRegistrar {
fn registrar(&self) -> Result<Address, String> {
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<Bytes, String> {
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(),
}
}
}
// TODO: light client implementation forwarding to OnDemand and waiting for future

View File

@ -279,7 +279,7 @@ fn execute_light(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) ->
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<RotatingLogger>) ->
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<RotatingLogger>) ->
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<RotatingLogger>) ->
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();

View File

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

View File

@ -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;

View File

@ -14,23 +14,25 @@
// 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::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<Bytes, String> {
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<Bytes, String> {
future::done(
self.client.upgrade()
.ok_or_else(|| "Client not available".into())
.and_then(move |c| c.call_contract(BlockId::Latest, address, data))
).boxed()
}
}