checked request for OnDemand

This commit is contained in:
Robert Habermeier 2017-04-05 18:56:01 +02:00
parent 35740456a4
commit 08d8709ef6
4 changed files with 232 additions and 47 deletions

View File

@ -459,15 +459,13 @@ impl Handler for OnDemand {
} }
Pending::HeaderByHash(req, sender) => { Pending::HeaderByHash(req, sender) => {
if let NetworkResponse::Headers(ref response) = *response { if let NetworkResponse::Headers(ref response) = *response {
if let Some(header) = response.headers.get(0) { match req.check_response(&response.headers) {
match req.check_response(header) { Ok(header) => {
Ok(header) => { self.cache.lock().insert_block_header(req.0, header.clone());
self.cache.lock().insert_block_header(req.0, header.clone()); let _ = sender.send(header);
let _ = sender.send(header); return
return
}
Err(e) => warn!(target: "on_demand", "Error handling response for header request: {:?}", e),
} }
Err(e) => warn!(target: "on_demand", "Error handling response for header request: {:?}", e),
} }
} }
} }
@ -521,8 +519,8 @@ impl Handler for OnDemand {
Pending::Code(req, sender) => { Pending::Code(req, sender) => {
if let NetworkResponse::Code(ref response) = *response { if let NetworkResponse::Code(ref response) = *response {
match req.check_response(response.code.as_slice()) { match req.check_response(response.code.as_slice()) {
Ok(()) => { Ok(code) => {
let _ = sender.send(response.code.clone()); let _ = sender.send(code);
return return
} }
Err(e) => warn!(target: "on_demand", "Error handling response for code request: {:?}", e), Err(e) => warn!(target: "on_demand", "Error handling response for code request: {:?}", e),

View File

@ -26,17 +26,171 @@ use ethcore::receipt::Receipt;
use ethcore::state::{self, ProvedExecution}; use ethcore::state::{self, ProvedExecution};
use ethcore::transaction::SignedTransaction; use ethcore::transaction::SignedTransaction;
use request::{self as net_request, IncompleteRequest, Output, OutputKind};
use rlp::{RlpStream, UntrustedRlp}; use rlp::{RlpStream, UntrustedRlp};
use util::{Address, Bytes, DBValue, HashDB, H256, U256}; use util::{Address, Bytes, DBValue, HashDB, H256, U256};
use util::memorydb::MemoryDB; use util::memorydb::MemoryDB;
use util::sha3::Hashable; use util::sha3::Hashable;
use util::trie::{Trie, TrieDB, TrieError}; use util::trie::{Trie, TrieDB, TrieError};
/// Core unit of the API: submit batches of these to be answered with `Response`s.
pub enum Request {
/// A request for a header proof.
HeaderProof(HeaderProof),
/// A request for a header by hash.
HeaderByHash(HeaderByHash),
/// A request for block receipts.
Receipts(BlockReceipts),
/// A request for a block body.
Body(Body),
/// A request for an account.
Account(Account),
/// A request for a contract's code.
Code(Code),
/// A request for proof of execution.
Execution(TransactionProof),
}
/// Requests coupled with their required data for verification.
/// This is used internally but not part of the public API.
#[derive(Clone)]
#[allow(missing_docs)]
pub enum CheckedRequest {
HeaderProof(HeaderProof, net_request::IncompleteHeaderProofRequest),
HeaderByHash(HeaderByHash, net_request::IncompleteHeadersRequest),
Receipts(BlockReceipts, net_request::IncompleteReceiptsRequest),
Body(Body, net_request::IncompleteBodyRequest),
Account(Account, net_request::IncompleteAccountRequest),
Code(Code, net_request::IncompleteCodeRequest),
Execution(TransactionProof, net_request::IncompleteExecutionRequest),
}
impl IncompleteRequest for CheckedRequest {
type Complete = net_request::CompleteRequest;
type Response = net_request::Response;
/// Check prior outputs against the needed inputs.
///
/// This is called to ensure consistency of this request with
/// others in the same packet.
fn check_outputs<F>(&self, f: F) -> Result<(), net_request::NoSuchOutput>
where F: FnMut(usize, usize, OutputKind) -> Result<(), net_request::NoSuchOutput>
{
match *self {
CheckedRequest::HeaderProof(_, ref req) => req.check_outputs(f),
CheckedRequest::HeaderByHash(_, ref req) => req.check_outputs(f),
CheckedRequest::Receipts(_, ref req) => req.check_outputs(f),
CheckedRequest::Body(_, ref req) => req.check_outputs(f),
CheckedRequest::Account(_, ref req) => req.check_outputs(f),
CheckedRequest::Code(_, ref req) => req.check_outputs(f),
CheckedRequest::Execution(_, ref req) => req.check_outputs(f),
}
}
/// Note that this request will produce the following outputs.
fn note_outputs<F>(&self, f: F) where F: FnMut(usize, OutputKind) {
match *self {
CheckedRequest::HeaderProof(_, ref req) => req.note_outputs(f),
CheckedRequest::HeaderByHash(_, ref req) => req.note_outputs(f),
CheckedRequest::Receipts(_, ref req) => req.note_outputs(f),
CheckedRequest::Body(_, ref req) => req.note_outputs(f),
CheckedRequest::Account(_, ref req) => req.note_outputs(f),
CheckedRequest::Code(_, ref req) => req.note_outputs(f),
CheckedRequest::Execution(_, ref req) => req.note_outputs(f),
}
}
/// Fill fields of the request.
///
/// This function is provided an "output oracle" which allows fetching of
/// prior request outputs.
/// Only outputs previously checked with `check_outputs` may be available.
fn fill<F>(&mut self, f: F) where F: Fn(usize, usize) -> Result<Output, net_request::NoSuchOutput> {
match *self {
CheckedRequest::HeaderProof(_, ref mut req) => req.fill(f),
CheckedRequest::HeaderByHash(_, ref mut req) => req.fill(f),
CheckedRequest::Receipts(_, ref mut req) => req.fill(f),
CheckedRequest::Body(_, ref mut req) => req.fill(f),
CheckedRequest::Account(_, ref mut req) => req.fill(f),
CheckedRequest::Code(_, ref mut req) => req.fill(f),
CheckedRequest::Execution(_, ref mut req) => req.fill(f),
}
}
/// Will succeed if all fields have been filled, will fail otherwise.
fn complete(self) -> Result<Self::Complete, net_request::NoSuchOutput> {
use ::request::CompleteRequest;
match self {
CheckedRequest::HeaderProof(_, req) => req.complete().map(CompleteRequest::HeaderProof),
CheckedRequest::HeaderByHash(_, req) => req.complete().map(CompleteRequest::Headers),
CheckedRequest::Receipts(_, req) => req.complete().map(CompleteRequest::Receipts),
CheckedRequest::Body(_, req) => req.complete().map(CompleteRequest::Body),
CheckedRequest::Account(_, req) => req.complete().map(CompleteRequest::Account),
CheckedRequest::Code(_, req) => req.complete().map(CompleteRequest::Code),
CheckedRequest::Execution(_, req) => req.complete().map(CompleteRequest::Execution),
}
}
}
impl net_request::CheckedRequest for CheckedRequest {
type Extract = Response;
type Error = Error;
/// Check whether the response matches (beyond the type).
fn check_response(&self, response: &Self::Response) -> Result<Response, Error> {
use ::request::Response as NetResponse;
// check response against contained prover.
match (self, response) {
(&CheckedRequest::HeaderProof(ref prover, _), &NetResponse::HeaderProof(ref res)) =>
prover.check_response(&res.proof).map(|(h, s)| Response::HeaderProof(h, s)),
(&CheckedRequest::HeaderByHash(ref prover, _), &NetResponse::Headers(ref res)) =>
prover.check_response(&res.headers).map(Response::HeaderByHash),
(&CheckedRequest::Receipts(ref prover, _), &NetResponse::Receipts(ref res)) =>
prover.check_response(&res.receipts).map(Response::Receipts),
(&CheckedRequest::Body(ref prover, _), &NetResponse::Body(ref res)) =>
prover.check_response(&res.body).map(Response::Body),
(&CheckedRequest::Account(ref prover, _), &NetResponse::Account(ref res)) =>
prover.check_response(&res.proof).map(Response::Account),
(&CheckedRequest::Code(ref prover, _), &NetResponse::Code(ref res)) =>
prover.check_response(&res.code).map(Response::Code),
(&CheckedRequest::Execution(ref prover, _), &NetResponse::Execution(ref res)) =>
Ok(Response::Execution(prover.check_response(&res.items))),
_ => Err(Error::WrongKind),
}
}
}
/// Responses to on-demand requests.
/// All of these are checked.
pub enum Response {
/// Response to a header proof request.
/// Returns the hash and chain score.
HeaderProof(H256, U256),
/// Response to a header-by-hash request.
HeaderByHash(encoded::Header),
/// Response to a receipts request.
Receipts(Vec<Receipt>),
/// Response to a block body request.
Body(encoded::Block),
/// Response to an Account request.
// TODO: `unwrap_or(engine_defaults)`
Account(Option<BasicAccount>),
/// Response to a request for code.
Code(Vec<u8>),
/// Response to a request for proved execution.
Execution(ProvedExecution), // TODO: make into `Result`
}
/// Errors in verification. /// Errors in verification.
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum Error { pub enum Error {
/// RLP decoder error. /// RLP decoder error.
Decoder(::rlp::DecoderError), Decoder(::rlp::DecoderError),
/// Empty response.
Empty,
/// Trie lookup error (result of bad proof) /// Trie lookup error (result of bad proof)
Trie(TrieError), Trie(TrieError),
/// Bad inclusion proof /// Bad inclusion proof
@ -47,6 +201,8 @@ pub enum Error {
WrongHash(H256, H256), WrongHash(H256, H256),
/// Wrong trie root. /// Wrong trie root.
WrongTrieRoot(H256, H256), WrongTrieRoot(H256, H256),
/// Wrong response kind.
WrongKind,
} }
impl From<::rlp::DecoderError> for Error { impl From<::rlp::DecoderError> for Error {
@ -107,7 +263,8 @@ pub struct HeaderByHash(pub H256);
impl HeaderByHash { impl HeaderByHash {
/// Check a response for the header. /// Check a response for the header.
pub fn check_response(&self, header: &encoded::Header) -> Result<encoded::Header, Error> { pub fn check_response(&self, headers: &[encoded::Header]) -> Result<encoded::Header, Error> {
let header = headers.get(0).ok_or(Error::Empty)?;
let hash = header.sha3(); let hash = header.sha3();
match hash == self.0 { match hash == self.0 {
true => Ok(header.clone()), true => Ok(header.clone()),
@ -208,6 +365,7 @@ impl Account {
} }
/// Request for account code. /// Request for account code.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Code { pub struct Code {
/// Block hash, number pair. /// Block hash, number pair.
pub block_id: (H256, u64), pub block_id: (H256, u64),
@ -217,10 +375,10 @@ pub struct Code {
impl Code { impl Code {
/// Check a response with code against the code hash. /// Check a response with code against the code hash.
pub fn check_response(&self, code: &[u8]) -> Result<(), Error> { pub fn check_response(&self, code: &[u8]) -> Result<Vec<u8>, Error> {
let found_hash = code.sha3(); let found_hash = code.sha3();
if found_hash == self.code_hash { if found_hash == self.code_hash {
Ok(()) Ok(code.to_vec())
} else { } else {
Err(Error::WrongHash(self.code_hash, found_hash)) Err(Error::WrongHash(self.code_hash, found_hash))
} }
@ -228,6 +386,7 @@ impl Code {
} }
/// Request for transaction execution, along with the parts necessary to verify the proof. /// Request for transaction execution, along with the parts necessary to verify the proof.
#[derive(Clone)]
pub struct TransactionProof { pub struct TransactionProof {
/// The transaction to request proof of. /// The transaction to request proof of.
pub tx: SignedTransaction, pub tx: SignedTransaction,

View File

@ -80,27 +80,6 @@ pub struct Requests<T: IncompleteRequest> {
} }
impl<T: IncompleteRequest + Clone> Requests<T> { impl<T: IncompleteRequest + Clone> Requests<T> {
/// For each request, produce a response.
/// The responses vector produced goes up to the point where the responder
/// first returns `None`, an invalid response, or until all requests have been responded to.
pub fn respond_to_all<F>(mut self, responder: F) -> Vec<T::Response>
where F: Fn(T::Complete) -> Option<T::Response>
{
let mut responses = Vec::new();
while let Some(response) = self.next_complete().and_then(&responder) {
match self.supply_response(&response) {
Ok(()) => responses.push(response),
Err(e) => {
debug!(target: "pip", "produced bad response to request: {:?}", e);
return responses;
}
}
}
responses
}
/// Get access to the underlying slice of requests. /// Get access to the underlying slice of requests.
// TODO: unimplemented -> Vec<Request>, // do we _have to_ allocate? // TODO: unimplemented -> Vec<Request>, // do we _have to_ allocate?
pub fn requests(&self) -> &[T] { &self.requests } pub fn requests(&self) -> &[T] { &self.requests }
@ -118,15 +97,20 @@ impl<T: IncompleteRequest + Clone> Requests<T> {
.expect("All outputs checked as invariant of `Requests` object; qed")) .expect("All outputs checked as invariant of `Requests` object; qed"))
} }
} }
}
impl<T: super::CheckedRequest> Requests<T> {
/// Supply a response for the next request. /// Supply a response for the next request.
/// Fails on: wrong request kind, all requests answered already. /// Fails on: wrong request kind, all requests answered already.
pub fn supply_response(&mut self, response: &T::Response) -> Result<(), ResponseError> { pub fn supply_response(&mut self, response: &T::Response)
-> Result<T::Extract, ResponseError<T::Error>>
{
let idx = self.answered; let idx = self.answered;
// check validity. // check validity.
if idx == self.requests.len() { return Err(ResponseError::Unexpected) } if idx == self.requests.len() { return Err(ResponseError::Unexpected) }
if !self.requests[idx].check_response(&response) { return Err(ResponseError::WrongKind) } let extracted = self.requests[idx]
.check_response(&response).map_err(ResponseError::Validity)?;
let outputs = &mut self.outputs; let outputs = &mut self.outputs;
response.fill_outputs(|out_idx, output| { response.fill_outputs(|out_idx, output| {
@ -143,7 +127,30 @@ impl<T: IncompleteRequest + Clone> Requests<T> {
req.fill(|req_idx, out_idx| outputs.get(&(req_idx, out_idx)).cloned().ok_or(NoSuchOutput)) req.fill(|req_idx, out_idx| outputs.get(&(req_idx, out_idx)).cloned().ok_or(NoSuchOutput))
} }
Ok(()) Ok(extracted)
}
}
impl Requests<super::Request> {
/// For each request, produce a response.
/// The responses vector produced goes up to the point where the responder
/// first returns `None`, an invalid response, or until all requests have been responded to.
pub fn respond_to_all<F>(mut self, responder: F) -> Vec<super::Response>
where F: Fn(super::CompleteRequest) -> Option<super::Response>
{
let mut responses = Vec::new();
while let Some(response) = self.next_complete().and_then(&responder) {
match self.supply_response(&response) {
Ok(()) => responses.push(response),
Err(e) => {
debug!(target: "pip", "produced bad response to request: {:?}", e);
return responses;
}
}
}
responses
} }
} }

View File

@ -69,11 +69,15 @@ pub use self::builder::{RequestBuilder, Requests};
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct NoSuchOutput; pub struct NoSuchOutput;
/// Wrong kind of response corresponding to request.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct WrongKind;
/// Error on processing a response. /// Error on processing a response.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum ResponseError { pub enum ResponseError<T> {
/// Wrong kind of response. /// Error in validity.
WrongKind, Validity(T),
/// No responses expected. /// No responses expected.
Unexpected, Unexpected,
} }
@ -342,10 +346,6 @@ impl IncompleteRequest for Request {
} }
} }
fn check_response(&self, response: &Response) -> bool {
self.kind() == response.kind()
}
fn complete(self) -> Result<Self::Complete, NoSuchOutput> { fn complete(self) -> Result<Self::Complete, NoSuchOutput> {
match self { match self {
Request::Headers(req) => req.complete().map(CompleteRequest::Headers), Request::Headers(req) => req.complete().map(CompleteRequest::Headers),
@ -360,6 +360,19 @@ impl IncompleteRequest for Request {
} }
} }
impl CheckedRequest for Request {
type Extract = ();
type Error = WrongKind;
fn check_response(&self, response: &Response) -> Result<(), WrongKind> {
if self.kind() == response.kind() {
Ok(())
} else {
Err(WrongKind)
}
}
}
/// Kinds of requests. /// Kinds of requests.
/// Doubles as the "ID" field of the request. /// Doubles as the "ID" field of the request.
#[repr(u8)] #[repr(u8)]
@ -520,14 +533,22 @@ pub trait IncompleteRequest: Sized {
/// Only outputs previously checked with `check_outputs` may be available. /// Only outputs previously checked with `check_outputs` may be available.
fn fill<F>(&mut self, oracle: F) where F: Fn(usize, usize) -> Result<Output, NoSuchOutput>; fn fill<F>(&mut self, oracle: F) where F: Fn(usize, usize) -> Result<Output, NoSuchOutput>;
/// Check whether the response matches (beyond the type).
fn check_response(&self, _response: &Self::Response) -> bool { true }
/// Attempt to convert this request into its complete variant. /// Attempt to convert this request into its complete variant.
/// Will succeed if all fields have been filled, will fail otherwise. /// Will succeed if all fields have been filled, will fail otherwise.
fn complete(self) -> Result<Self::Complete, NoSuchOutput>; fn complete(self) -> Result<Self::Complete, NoSuchOutput>;
} }
/// A request which can be checked against its response for more validity.
pub trait CheckedRequest: IncompleteRequest {
/// Data extracted during the check.
type Extract;
/// Error encountered during the check.
type Error;
/// Check whether the response matches (beyond the type).
fn check_response(&self, _response: &Self::Response) -> Result<Self::Extract, Self::Error>;
}
/// A response-like object. /// A response-like object.
/// ///
/// These contain re-usable outputs. /// These contain re-usable outputs.