From 5793bb8facebf639324a5db6bbdd97560c2db72f Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Thu, 6 Apr 2017 20:01:09 +0200 Subject: [PATCH] typestrong API --- ethcore/light/src/on_demand/mod.rs | 93 +++++++++---------- ethcore/light/src/on_demand/request.rs | 124 ++++++++++++++++++++++++- rpc/src/v1/impls/light/eth.rs | 1 - 3 files changed, 165 insertions(+), 53 deletions(-) diff --git a/ethcore/light/src/on_demand/mod.rs b/ethcore/light/src/on_demand/mod.rs index f5a1fe02e..9ffd5ef1f 100644 --- a/ethcore/light/src/on_demand/mod.rs +++ b/ethcore/light/src/on_demand/mod.rs @@ -22,6 +22,7 @@ #![allow(deprecated)] use std::collections::HashMap; +use std::marker::PhantomData; use std::sync::Arc; use ethcore::basic_account::BasicAccount; @@ -111,6 +112,22 @@ fn guess_capabilities(requests: &[CheckedRequest]) -> Capabilities { caps } +/// A future extracting the concrete output type of the generic adapter +/// from a vector of responses. +pub struct OnResponses { + receiver: Receiver>, + _marker: PhantomData, +} + +impl Future for OnResponses { + type Item = T::Out; + type Error = Canceled; + + fn poll(&mut self) -> Poll { + self.receiver.poll().map(|async| async.map(T::extract_from)) + } +} + /// On demand request service. See module docs for more details. /// Accumulates info about all peers' capabilities and dispatches /// requests to them accordingly. @@ -122,8 +139,6 @@ pub struct OnDemand { cache: Arc>, } -const RESPONSES_MATCH: &'static str = "N requests always leads to N responses; qed"; - impl OnDemand { /// Create a new `OnDemand` service with the given cache. pub fn new(cache: Arc>) -> Self { @@ -146,12 +161,9 @@ impl OnDemand { match cached { Some(hash) => future::ok(hash).boxed(), None => { - self.make_requests(ctx, vec![Request::HeaderProof(req)]) + self.request(ctx, req) .expect("request given fully fleshed out; qed") - .map(|responses| match responses[0] { - Response::HeaderProof(ref hash, _) => *hash, - _ => panic!("header proof request leads to header proof response; qed") - }) + .map(|(h, _)| h) .boxed() }, } @@ -168,12 +180,9 @@ impl OnDemand { match cached { Some(score) => future::ok(score).boxed(), None => { - self.make_requests(ctx, vec![Request::HeaderProof(req)]) + self.request(ctx, req) .expect("request given fully fleshed out; qed") - .map(|responses| match responses[0] { - Response::HeaderProof(_, ref score) => *score, - _ => panic!("header proof request leads to header proof response; qed") - }) + .map(|(_, s)| s) .boxed() }, } @@ -194,12 +203,8 @@ impl OnDemand { match cached { (Some(hash), Some(score)) => future::ok((hash, score)).boxed(), _ => { - self.make_requests(ctx, vec![Request::HeaderProof(req)]) + self.request(ctx, req) .expect("request given fully fleshed out; qed") - .map(|responses| match responses[0] { - Response::HeaderProof(ref hash, ref score) => (*hash, *score), - _ => panic!("header proof request leads to header proof response; qed") - }) .boxed() }, } @@ -212,12 +217,8 @@ impl OnDemand { match { self.cache.lock().block_header(&req.0) } { Some(hdr) => future::ok(hdr).boxed(), None => { - self.make_requests(ctx, vec![Request::HeaderByHash(req)]) + self.request(ctx, req) .expect("request given fully fleshed out; qed") - .map(|mut responses| match responses.pop().expect(RESPONSES_MATCH) { - Response::HeaderByHash(header) => header, - _ => panic!("header request leads to header response; qed") - }) .boxed() }, } @@ -247,12 +248,8 @@ impl OnDemand { future::ok(encoded::Block::new(stream.out())).boxed() } None => { - self.make_requests(ctx, vec![Request::Body(req)]) + self.request(ctx, req) .expect("request given fully fleshed out; qed") - .map(|mut responses| match responses.pop().expect(RESPONSES_MATCH) { - Response::Body(body) => body, - _ => panic!("body request leads to body response; qed") - }) .boxed() } } @@ -270,12 +267,8 @@ impl OnDemand { match { self.cache.lock().block_receipts(&req.0.hash()) } { Some(receipts) => future::ok(receipts).boxed(), None => { - self.make_requests(ctx, vec![Request::Receipts(req)]) + self.request(ctx, req) .expect("request given fully fleshed out; qed") - .map(|mut responses| match responses.pop().expect(RESPONSES_MATCH) { - Response::Receipts(receipts) => receipts, - _ => panic!("receipts request leads to receipts response; qed") - }) .boxed() }, } @@ -285,12 +278,8 @@ impl OnDemand { /// to verify against. /// `None` here means that no account by the queried key exists in the queried state. pub fn account(&self, ctx: &BasicContext, req: request::Account) -> BoxFuture, Canceled> { - self.make_requests(ctx, vec![Request::Account(req)]) + self.request(ctx, req) .expect("request given fully fleshed out; qed") - .map(|mut responses| match responses.pop().expect(RESPONSES_MATCH) { - Response::Account(account) => account, - _ => panic!("account request leads to account response; qed") - }) .boxed() } @@ -300,32 +289,24 @@ impl OnDemand { if req.code_hash == SHA3_EMPTY { future::ok(Vec::new()).boxed() } else { - self.make_requests(ctx, vec![Request::Code(req)]) + self.request(ctx, req) .expect("request given fully fleshed out; qed") - .map(|mut responses| match responses.pop().expect(RESPONSES_MATCH) { - Response::Code(code) => code, - _ => panic!("code request leads to code response; qed") - }) .boxed() } } /// Request proof-of-execution for a transaction. pub fn transaction_proof(&self, ctx: &BasicContext, req: request::TransactionProof) -> BoxFuture { - self.make_requests(ctx, vec![Request::Execution(req)]) + self.request(ctx, req) .expect("request given fully fleshed out; qed") - .map(|mut responses| match responses.pop().expect(RESPONSES_MATCH) { - Response::Execution(execution) => execution, - _ => panic!("execution request leads to execution response; qed") - }) .boxed() } - /// Submit a batch of requests. + /// Submit a vector of requests to be processed together. /// /// Fails if back-references are not coherent. - /// The returned vector of responses will match the requests exactly. - pub fn make_requests(&self, ctx: &BasicContext, requests: Vec) + /// The returned vector of responses will correspond to the requests exactly. + pub fn request_raw(&self, ctx: &BasicContext, requests: Vec) -> Result>, basic_request::NoSuchOutput> { let (sender, receiver) = oneshot::channel(); @@ -359,6 +340,18 @@ impl OnDemand { Ok(receiver) } + /// Submit a strongly-typed batch of requests. + /// + /// Fails if back-reference are not coherent. + pub fn request(&self, ctx: &BasicContext, requests: T) -> Result, basic_request::NoSuchOutput> + where T: request::RequestAdapter + { + self.request_raw(ctx, requests.make_requests()).map(|recv| OnResponses { + receiver: recv, + _marker: PhantomData, + }) + } + // dispatch pending requests, and discard those for which the corresponding // receiver has been dropped. fn dispatch_pending(&self, ctx: &BasicContext) { diff --git a/ethcore/light/src/on_demand/request.rs b/ethcore/light/src/on_demand/request.rs index f7db825c3..8361661eb 100644 --- a/ethcore/light/src/on_demand/request.rs +++ b/ethcore/light/src/on_demand/request.rs @@ -34,6 +34,8 @@ use util::memorydb::MemoryDB; use util::sha3::Hashable; use util::trie::{Trie, TrieDB, TrieError}; +const SUPPLIED_MATCHES: &'static str = "supplied responses always match produced requests; qed"; + /// Core unit of the API: submit batches of these to be answered with `Response`s. #[derive(Clone)] pub enum Request { @@ -53,6 +55,124 @@ pub enum Request { Execution(TransactionProof), } +/// A request argument. +pub trait RequestArg { + /// the response type. + type Out; + + /// Create the request type. + /// `extract` must not fail when presented with the corresponding + /// `Response`. + fn make(self) -> Request; + + /// May not panic if the response corresponds with the request + /// from `make`. + /// Is free to panic otherwise. + fn extract(r: Response) -> Self::Out; +} + +/// An adapter can be thought of as a grouping of request argument types. +/// This is implemented for various tuples and convenient types. +pub trait RequestAdapter { + /// The output type. + type Out; + + /// Infallibly produce requests. When `extract_from` is presented + /// with the corresponding response vector, it may not fail. + fn make_requests(self) -> Vec; + + /// Extract the output type from the given responses. + /// If they are the corresponding responses to the requests + /// made by `make_requests`, do not panic. + fn extract_from(Vec) -> Self::Out; +} + +// helper to implement `RequestArg` and `From` for a single request kind. +macro_rules! impl_single { + ($variant: ident, $me: ty, $out: ty) => { + impl RequestArg for $me { + type Out = $out; + + fn make(self) -> Request { + Request::$variant(self) + } + + fn extract(r: Response) -> $out { + match r { + Response::$variant(x) => x, + _ => panic!(SUPPLIED_MATCHES), + } + } + } + + impl From<$me> for Request { + fn from(me: $me) -> Request { + Request::$variant(me) + } + } + } +} + +// implement traits for each kind of request. +impl_single!(HeaderProof, HeaderProof, (H256, U256)); +impl_single!(HeaderByHash, HeaderByHash, encoded::Header); +impl_single!(Receipts, BlockReceipts, Vec); +impl_single!(Body, Body, encoded::Block); +impl_single!(Account, Account, Option); +impl_single!(Code, Code, Bytes); +impl_single!(Execution, TransactionProof, super::ExecutionResult); + +macro_rules! impl_args { + () => { + impl RequestAdapter for T { + type Out = T::Out; + + fn make_requests(self) -> Vec { + vec![self.make()] + } + + fn extract_from(mut responses: Vec) -> Self::Out { + T::extract(responses.pop().expect(SUPPLIED_MATCHES)) + } + } + }; + ($first: ident, $($next: ident,)*) => { + impl< + $first: RequestArg, + $($next: RequestArg,)* + > + RequestAdapter for ($first, $($next,)*) { + type Out = ($first::Out, $($next::Out,)*); + + fn make_requests(self) -> Vec { + let ($first, $($next,)*) = self; + + vec![ + $first.make(), + $($next.make(),)* + ] + } + + fn extract_from(responses: Vec) -> Self::Out { + let mut iter = responses.into_iter(); + ( + $first::extract(iter.next().expect(SUPPLIED_MATCHES)), + $($next::extract(iter.next().expect(SUPPLIED_MATCHES)),)* + ) + } + } + impl_args!($($next,)*); + } +} + +mod impls { + #![allow(non_snake_case)] + + use super::{RequestAdapter, RequestArg, Request, Response, SUPPLIED_MATCHES}; + + impl_args!(A, B, C, D, E, F, G, H, I, J, K, L,); +} + /// Requests coupled with their required data for verification. /// This is used internally but not part of the public API. #[derive(Clone)] @@ -237,7 +357,7 @@ impl net_request::CheckedRequest for CheckedRequest { // check response against contained prover. match (self, response) { (&CheckedRequest::HeaderProof(ref prover, _), &NetResponse::HeaderProof(ref res)) => - prover.check_response(cache, &res.proof).map(|(h, s)| Response::HeaderProof(h, s)), + prover.check_response(cache, &res.proof).map(Response::HeaderProof), (&CheckedRequest::HeaderByHash(ref prover, _), &NetResponse::Headers(ref res)) => prover.check_response(cache, &res.headers).map(Response::HeaderByHash), (&CheckedRequest::Receipts(ref prover, _), &NetResponse::Receipts(ref res)) => @@ -260,7 +380,7 @@ impl net_request::CheckedRequest for CheckedRequest { pub enum Response { /// Response to a header proof request. /// Returns the hash and chain score. - HeaderProof(H256, U256), + HeaderProof((H256, U256)), /// Response to a header-by-hash request. HeaderByHash(encoded::Header), /// Response to a receipts request. diff --git a/rpc/src/v1/impls/light/eth.rs b/rpc/src/v1/impls/light/eth.rs index 752e61d64..7e53f66d0 100644 --- a/rpc/src/v1/impls/light/eth.rs +++ b/rpc/src/v1/impls/light/eth.rs @@ -115,7 +115,6 @@ impl EthClient { on_demand: self.on_demand.clone(), sync: self.sync.clone(), cache: self.cache.clone(), - } }