checked request for OnDemand
This commit is contained in:
parent
35740456a4
commit
08d8709ef6
@ -459,15 +459,13 @@ impl Handler for OnDemand {
|
||||
}
|
||||
Pending::HeaderByHash(req, sender) => {
|
||||
if let NetworkResponse::Headers(ref response) = *response {
|
||||
if let Some(header) = response.headers.get(0) {
|
||||
match req.check_response(header) {
|
||||
Ok(header) => {
|
||||
self.cache.lock().insert_block_header(req.0, header.clone());
|
||||
let _ = sender.send(header);
|
||||
return
|
||||
}
|
||||
Err(e) => warn!(target: "on_demand", "Error handling response for header request: {:?}", e),
|
||||
match req.check_response(&response.headers) {
|
||||
Ok(header) => {
|
||||
self.cache.lock().insert_block_header(req.0, header.clone());
|
||||
let _ = sender.send(header);
|
||||
return
|
||||
}
|
||||
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) => {
|
||||
if let NetworkResponse::Code(ref response) = *response {
|
||||
match req.check_response(response.code.as_slice()) {
|
||||
Ok(()) => {
|
||||
let _ = sender.send(response.code.clone());
|
||||
Ok(code) => {
|
||||
let _ = sender.send(code);
|
||||
return
|
||||
}
|
||||
Err(e) => warn!(target: "on_demand", "Error handling response for code request: {:?}", e),
|
||||
|
@ -26,17 +26,171 @@ use ethcore::receipt::Receipt;
|
||||
use ethcore::state::{self, ProvedExecution};
|
||||
use ethcore::transaction::SignedTransaction;
|
||||
|
||||
use request::{self as net_request, IncompleteRequest, Output, OutputKind};
|
||||
|
||||
use rlp::{RlpStream, UntrustedRlp};
|
||||
use util::{Address, Bytes, DBValue, HashDB, H256, U256};
|
||||
use util::memorydb::MemoryDB;
|
||||
use util::sha3::Hashable;
|
||||
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.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Error {
|
||||
/// RLP decoder error.
|
||||
Decoder(::rlp::DecoderError),
|
||||
/// Empty response.
|
||||
Empty,
|
||||
/// Trie lookup error (result of bad proof)
|
||||
Trie(TrieError),
|
||||
/// Bad inclusion proof
|
||||
@ -47,6 +201,8 @@ pub enum Error {
|
||||
WrongHash(H256, H256),
|
||||
/// Wrong trie root.
|
||||
WrongTrieRoot(H256, H256),
|
||||
/// Wrong response kind.
|
||||
WrongKind,
|
||||
}
|
||||
|
||||
impl From<::rlp::DecoderError> for Error {
|
||||
@ -107,7 +263,8 @@ pub struct HeaderByHash(pub H256);
|
||||
|
||||
impl HeaderByHash {
|
||||
/// 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();
|
||||
match hash == self.0 {
|
||||
true => Ok(header.clone()),
|
||||
@ -208,6 +365,7 @@ impl Account {
|
||||
}
|
||||
|
||||
/// Request for account code.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Code {
|
||||
/// Block hash, number pair.
|
||||
pub block_id: (H256, u64),
|
||||
@ -217,10 +375,10 @@ pub struct Code {
|
||||
|
||||
impl Code {
|
||||
/// 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();
|
||||
if found_hash == self.code_hash {
|
||||
Ok(())
|
||||
Ok(code.to_vec())
|
||||
} else {
|
||||
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.
|
||||
#[derive(Clone)]
|
||||
pub struct TransactionProof {
|
||||
/// The transaction to request proof of.
|
||||
pub tx: SignedTransaction,
|
||||
|
@ -80,27 +80,6 @@ pub struct Requests<T: IncompleteRequest> {
|
||||
}
|
||||
|
||||
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.
|
||||
// TODO: unimplemented -> Vec<Request>, // do we _have to_ allocate?
|
||||
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"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: super::CheckedRequest> Requests<T> {
|
||||
/// Supply a response for the next request.
|
||||
/// 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;
|
||||
|
||||
// check validity.
|
||||
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;
|
||||
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))
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -69,11 +69,15 @@ pub use self::builder::{RequestBuilder, Requests};
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct NoSuchOutput;
|
||||
|
||||
/// Wrong kind of response corresponding to request.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct WrongKind;
|
||||
|
||||
/// Error on processing a response.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ResponseError {
|
||||
/// Wrong kind of response.
|
||||
WrongKind,
|
||||
pub enum ResponseError<T> {
|
||||
/// Error in validity.
|
||||
Validity(T),
|
||||
/// No responses expected.
|
||||
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> {
|
||||
match self {
|
||||
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.
|
||||
/// Doubles as the "ID" field of the request.
|
||||
#[repr(u8)]
|
||||
@ -520,14 +533,22 @@ pub trait IncompleteRequest: Sized {
|
||||
/// 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>;
|
||||
|
||||
/// 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.
|
||||
/// Will succeed if all fields have been filled, will fail otherwise.
|
||||
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.
|
||||
///
|
||||
/// These contain re-usable outputs.
|
||||
|
Loading…
Reference in New Issue
Block a user