typestrong API

This commit is contained in:
Robert Habermeier 2017-04-06 20:01:09 +02:00
parent cf75a19e8d
commit 5793bb8fac
3 changed files with 165 additions and 53 deletions

View File

@ -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<T: request::RequestAdapter> {
receiver: Receiver<Vec<Response>>,
_marker: PhantomData<T>,
}
impl<T: request::RequestAdapter> Future for OnResponses<T> {
type Item = T::Out;
type Error = Canceled;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
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<Mutex<Cache>>,
}
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<Mutex<Cache>>) -> 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<Option<BasicAccount>, 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<ExecutionResult, Canceled> {
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<Request>)
/// The returned vector of responses will correspond to the requests exactly.
pub fn request_raw(&self, ctx: &BasicContext, requests: Vec<Request>)
-> Result<Receiver<Vec<Response>>, 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<T>(&self, ctx: &BasicContext, requests: T) -> Result<OnResponses<T>, 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) {

View File

@ -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<Request>;
/// 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<Response>) -> 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<Receipt>);
impl_single!(Body, Body, encoded::Block);
impl_single!(Account, Account, Option<BasicAccount>);
impl_single!(Code, Code, Bytes);
impl_single!(Execution, TransactionProof, super::ExecutionResult);
macro_rules! impl_args {
() => {
impl<T: RequestArg> RequestAdapter for T {
type Out = T::Out;
fn make_requests(self) -> Vec<Request> {
vec![self.make()]
}
fn extract_from(mut responses: Vec<Response>) -> 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<Request> {
let ($first, $($next,)*) = self;
vec![
$first.make(),
$($next.make(),)*
]
}
fn extract_from(responses: Vec<Response>) -> 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.

View File

@ -115,7 +115,6 @@ impl EthClient {
on_demand: self.on_demand.clone(),
sync: self.sync.clone(),
cache: self.cache.clone(),
}
}