openethereum/ethcore/light/src/on_demand/response_guard.rs

200 lines
6.9 KiB
Rust

// Copyright 2015-2019 Parity Technologies (UK) Ltd.
// This file is part of Parity Ethereum.
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
//! ResponseGuard implementation.
//! It is responsible for the receiving end of `Pending Request` (see `OnDemand` module docs for more information)
//! The major functionality is the following:
//! 1) Register non-successful responses which will reported back if it fails
//! 2) A timeout mechanism that will wait for successful response at most t seconds
use std::{
collections::HashMap,
fmt,
time::{Duration, Instant},
};
use super::{ResponseError, ValidityError};
/// Response guard error type
#[derive(Debug, Eq, PartialEq)]
pub enum Error {
/// No majority, the error reason can't be determined
NoMajority(usize),
/// Majority, with the error reason
Majority(Inner, usize, usize),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::Majority(err, majority, total) => write!(
f,
"Error cause was {:?}, (majority count: {} / total: {})",
err, majority, total
),
Error::NoMajority(total) => write!(
f,
"Error cause couldn't be determined, the total number of responses was {}",
total
),
}
}
}
/// Dummy type to convert a generic type with no trait bounds
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
pub enum Inner {
/// Bad execution proof
BadProof,
/// RLP decoding
Decoder,
/// Empty response
EmptyResponse,
/// Wrong header sequence
HeaderByNumber,
/// Too few results
TooFewResults,
/// Too many results
TooManyResults,
/// Trie error
Trie,
/// Unresolved header
UnresolvedHeader,
/// No responses expected.
Unexpected,
/// Wrong hash
WrongHash,
/// Wrong Header sequence
WrongHeaderSequence,
/// Wrong response kind
WrongKind,
/// Wrong number
WrongNumber,
/// Wrong Trie Root
WrongTrieRoot,
}
/// Handle and register responses that can fail
#[derive(Debug)]
pub struct ResponseGuard {
request_start: Instant,
time_to_live: Duration,
responses: HashMap<Inner, usize>,
number_responses: usize,
}
impl ResponseGuard {
/// Constructor
pub fn new(time_to_live: Duration) -> Self {
Self {
request_start: Instant::now(),
time_to_live,
responses: HashMap::new(),
number_responses: 0,
}
}
fn into_reason(&self, err: &ResponseError<super::request::Error>) -> Inner {
match err {
ResponseError::Unexpected => Inner::Unexpected,
ResponseError::Validity(ValidityError::BadProof) => Inner::BadProof,
ResponseError::Validity(ValidityError::Decoder(_)) => Inner::Decoder,
ResponseError::Validity(ValidityError::Empty) => Inner::EmptyResponse,
ResponseError::Validity(ValidityError::HeaderByNumber) => Inner::HeaderByNumber,
ResponseError::Validity(ValidityError::TooFewResults(_, _)) => Inner::TooFewResults,
ResponseError::Validity(ValidityError::TooManyResults(_, _)) => Inner::TooManyResults,
ResponseError::Validity(ValidityError::Trie(_)) => Inner::Trie,
ResponseError::Validity(ValidityError::UnresolvedHeader(_)) => Inner::UnresolvedHeader,
ResponseError::Validity(ValidityError::WrongHash(_, _)) => Inner::WrongHash,
ResponseError::Validity(ValidityError::WrongHeaderSequence) => {
Inner::WrongHeaderSequence
}
ResponseError::Validity(ValidityError::WrongKind) => Inner::WrongKind,
ResponseError::Validity(ValidityError::WrongNumber(_, _)) => Inner::WrongNumber,
ResponseError::Validity(ValidityError::WrongTrieRoot(_, _)) => Inner::WrongTrieRoot,
}
}
/// Update the state after a `faulty` call
pub fn register_error(
&mut self,
err: &ResponseError<super::request::Error>,
) -> Result<(), Error> {
let err = self.into_reason(err);
*self.responses.entry(err).or_insert(0) += 1;
self.number_responses = self.number_responses.saturating_add(1);
trace!(target: "circuit_breaker", "ResponseGuard: {:?}", self.responses);
// The request has exceeded its timeout
if self.request_start.elapsed() >= self.time_to_live {
let (&err, &max_count) = self
.responses
.iter()
.max_by_key(|(_k, v)| *v)
.expect("got at least one element; qed");
let majority = self.responses.values().filter(|v| **v == max_count).count() == 1;
if majority {
Err(Error::Majority(err, max_count, self.number_responses))
} else {
Err(Error::NoMajority(self.number_responses))
}
} else {
Ok(())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::thread;
#[test]
fn test_basic_by_majority() {
let mut guard = ResponseGuard::new(Duration::from_secs(5));
guard
.register_error(&ResponseError::Validity(ValidityError::Empty))
.unwrap();
guard.register_error(&ResponseError::Unexpected).unwrap();
guard.register_error(&ResponseError::Unexpected).unwrap();
guard.register_error(&ResponseError::Unexpected).unwrap();
thread::sleep(Duration::from_secs(5));
assert_eq!(
guard.register_error(&ResponseError::Validity(ValidityError::WrongKind)),
Err(Error::Majority(Inner::Unexpected, 3, 5))
);
}
#[test]
fn test_no_majority() {
let mut guard = ResponseGuard::new(Duration::from_secs(5));
guard
.register_error(&ResponseError::Validity(ValidityError::Empty))
.unwrap();
guard
.register_error(&ResponseError::Validity(ValidityError::Empty))
.unwrap();
guard.register_error(&ResponseError::Unexpected).unwrap();
guard.register_error(&ResponseError::Unexpected).unwrap();
thread::sleep(Duration::from_secs(5));
assert_eq!(
guard.register_error(&ResponseError::Validity(ValidityError::WrongKind)),
Err(Error::NoMajority(5))
);
}
}