Reformat the source code

This commit is contained in:
Artem Vorotnikov
2020-08-05 07:08:03 +03:00
parent 253ff3f37b
commit 610d9baba4
742 changed files with 175791 additions and 141379 deletions

View File

@@ -20,11 +20,9 @@
//! Furthermore, stores a "gas price corpus" of relative recency, which is a sorted
//! vector of all gas prices from a recent range of blocks.
use std::time::{Instant, Duration};
use std::time::{Duration, Instant};
use common_types::encoded;
use common_types::BlockNumber;
use common_types::receipt::Receipt;
use common_types::{encoded, receipt::Receipt, BlockNumber};
use ethereum_types::{H256, U256};
use heapsize::HeapSizeOf;
use memory_cache::MemoryLruCache;
@@ -33,29 +31,29 @@ use stats::Corpus;
/// Configuration for how much data to cache.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct CacheSizes {
/// Maximum size, in bytes, of cached headers.
pub headers: usize,
/// Maximum size, in bytes, of cached canonical hashes.
pub canon_hashes: usize,
/// Maximum size, in bytes, of cached block bodies.
pub bodies: usize,
/// Maximum size, in bytes, of cached block receipts.
pub receipts: usize,
/// Maximum size, in bytes, of cached chain score for the block.
pub chain_score: usize,
/// Maximum size, in bytes, of cached headers.
pub headers: usize,
/// Maximum size, in bytes, of cached canonical hashes.
pub canon_hashes: usize,
/// Maximum size, in bytes, of cached block bodies.
pub bodies: usize,
/// Maximum size, in bytes, of cached block receipts.
pub receipts: usize,
/// Maximum size, in bytes, of cached chain score for the block.
pub chain_score: usize,
}
impl Default for CacheSizes {
fn default() -> Self {
const MB: usize = 1024 * 1024;
CacheSizes {
headers: 10 * MB,
canon_hashes: 3 * MB,
bodies: 20 * MB,
receipts: 10 * MB,
chain_score: 7 * MB,
}
}
fn default() -> Self {
const MB: usize = 1024 * 1024;
CacheSizes {
headers: 10 * MB,
canon_hashes: 3 * MB,
bodies: 20 * MB,
receipts: 10 * MB,
chain_score: 7 * MB,
}
}
}
/// The light client data cache.
@@ -64,131 +62,131 @@ impl Default for CacheSizes {
/// the underlying LRU-caches on read.
/// [LRU-cache](https://en.wikipedia.org/wiki/Cache_replacement_policies#Least_Recently_Used_.28LRU.29)
pub struct Cache {
headers: MemoryLruCache<H256, encoded::Header>,
canon_hashes: MemoryLruCache<BlockNumber, H256>,
bodies: MemoryLruCache<H256, encoded::Body>,
receipts: MemoryLruCache<H256, Vec<Receipt>>,
chain_score: MemoryLruCache<H256, U256>,
corpus: Option<(Corpus<U256>, Instant)>,
corpus_expiration: Duration,
headers: MemoryLruCache<H256, encoded::Header>,
canon_hashes: MemoryLruCache<BlockNumber, H256>,
bodies: MemoryLruCache<H256, encoded::Body>,
receipts: MemoryLruCache<H256, Vec<Receipt>>,
chain_score: MemoryLruCache<H256, U256>,
corpus: Option<(Corpus<U256>, Instant)>,
corpus_expiration: Duration,
}
impl Cache {
/// Create a new data cache with the given sizes and gas price corpus expiration time.
pub fn new(sizes: CacheSizes, corpus_expiration: Duration) -> Self {
Cache {
headers: MemoryLruCache::new(sizes.headers),
canon_hashes: MemoryLruCache::new(sizes.canon_hashes),
bodies: MemoryLruCache::new(sizes.bodies),
receipts: MemoryLruCache::new(sizes.receipts),
chain_score: MemoryLruCache::new(sizes.chain_score),
corpus: None,
corpus_expiration,
}
}
/// Create a new data cache with the given sizes and gas price corpus expiration time.
pub fn new(sizes: CacheSizes, corpus_expiration: Duration) -> Self {
Cache {
headers: MemoryLruCache::new(sizes.headers),
canon_hashes: MemoryLruCache::new(sizes.canon_hashes),
bodies: MemoryLruCache::new(sizes.bodies),
receipts: MemoryLruCache::new(sizes.receipts),
chain_score: MemoryLruCache::new(sizes.chain_score),
corpus: None,
corpus_expiration,
}
}
/// Query header by hash.
pub fn block_header(&mut self, hash: &H256) -> Option<encoded::Header> {
self.headers.get_mut(hash).cloned()
}
/// Query header by hash.
pub fn block_header(&mut self, hash: &H256) -> Option<encoded::Header> {
self.headers.get_mut(hash).cloned()
}
/// Query hash by number.
pub fn block_hash(&mut self, num: BlockNumber) -> Option<H256> {
self.canon_hashes.get_mut(&num).map(|h| *h)
}
/// Query hash by number.
pub fn block_hash(&mut self, num: BlockNumber) -> Option<H256> {
self.canon_hashes.get_mut(&num).map(|h| *h)
}
/// Query block body by block hash.
pub fn block_body(&mut self, hash: &H256) -> Option<encoded::Body> {
self.bodies.get_mut(hash).cloned()
}
/// Query block body by block hash.
pub fn block_body(&mut self, hash: &H256) -> Option<encoded::Body> {
self.bodies.get_mut(hash).cloned()
}
/// Query block receipts by block hash.
pub fn block_receipts(&mut self, hash: &H256) -> Option<Vec<Receipt>> {
self.receipts.get_mut(hash).cloned()
}
/// Query block receipts by block hash.
pub fn block_receipts(&mut self, hash: &H256) -> Option<Vec<Receipt>> {
self.receipts.get_mut(hash).cloned()
}
/// Query chain score by block hash.
pub fn chain_score(&mut self, hash: &H256) -> Option<U256> {
self.chain_score.get_mut(hash).map(|h| *h)
}
/// Query chain score by block hash.
pub fn chain_score(&mut self, hash: &H256) -> Option<U256> {
self.chain_score.get_mut(hash).map(|h| *h)
}
/// Cache the given header.
pub fn insert_block_header(&mut self, hash: H256, hdr: encoded::Header) {
self.headers.insert(hash, hdr);
}
/// Cache the given header.
pub fn insert_block_header(&mut self, hash: H256, hdr: encoded::Header) {
self.headers.insert(hash, hdr);
}
/// Cache the given canonical block hash.
pub fn insert_block_hash(&mut self, num: BlockNumber, hash: H256) {
self.canon_hashes.insert(num, hash);
}
/// Cache the given canonical block hash.
pub fn insert_block_hash(&mut self, num: BlockNumber, hash: H256) {
self.canon_hashes.insert(num, hash);
}
/// Cache the given block body.
pub fn insert_block_body(&mut self, hash: H256, body: encoded::Body) {
self.bodies.insert(hash, body);
}
/// Cache the given block body.
pub fn insert_block_body(&mut self, hash: H256, body: encoded::Body) {
self.bodies.insert(hash, body);
}
/// Cache the given block receipts.
pub fn insert_block_receipts(&mut self, hash: H256, receipts: Vec<Receipt>) {
self.receipts.insert(hash, receipts);
}
/// Cache the given block receipts.
pub fn insert_block_receipts(&mut self, hash: H256, receipts: Vec<Receipt>) {
self.receipts.insert(hash, receipts);
}
/// Cache the given chain scoring.
pub fn insert_chain_score(&mut self, hash: H256, score: U256) {
self.chain_score.insert(hash, score);
}
/// Cache the given chain scoring.
pub fn insert_chain_score(&mut self, hash: H256, score: U256) {
self.chain_score.insert(hash, score);
}
/// Get gas price corpus, if recent enough.
pub fn gas_price_corpus(&self) -> Option<Corpus<U256>> {
let now = Instant::now();
/// Get gas price corpus, if recent enough.
pub fn gas_price_corpus(&self) -> Option<Corpus<U256>> {
let now = Instant::now();
self.corpus.as_ref().and_then(|&(ref corpus, ref tm)| {
if *tm + self.corpus_expiration >= now {
Some(corpus.clone())
} else {
None
}
})
}
self.corpus.as_ref().and_then(|&(ref corpus, ref tm)| {
if *tm + self.corpus_expiration >= now {
Some(corpus.clone())
} else {
None
}
})
}
/// Set the cached gas price corpus.
pub fn set_gas_price_corpus(&mut self, corpus: Corpus<U256>) {
self.corpus = Some((corpus, Instant::now()))
}
/// Set the cached gas price corpus.
pub fn set_gas_price_corpus(&mut self, corpus: Corpus<U256>) {
self.corpus = Some((corpus, Instant::now()))
}
/// Get the memory used.
pub fn mem_used(&self) -> usize {
self.heap_size_of_children()
}
/// Get the memory used.
pub fn mem_used(&self) -> usize {
self.heap_size_of_children()
}
}
impl HeapSizeOf for Cache {
fn heap_size_of_children(&self) -> usize {
self.headers.current_size()
+ self.canon_hashes.current_size()
+ self.bodies.current_size()
+ self.receipts.current_size()
+ self.chain_score.current_size()
// TODO: + corpus
}
fn heap_size_of_children(&self) -> usize {
self.headers.current_size()
+ self.canon_hashes.current_size()
+ self.bodies.current_size()
+ self.receipts.current_size()
+ self.chain_score.current_size()
// TODO: + corpus
}
}
#[cfg(test)]
mod tests {
use super::Cache;
use std::time::Duration;
use super::Cache;
use std::time::Duration;
#[test]
fn corpus_inaccessible() {
let duration = Duration::from_secs(20);
let mut cache = Cache::new(Default::default(), duration.clone());
#[test]
fn corpus_inaccessible() {
let duration = Duration::from_secs(20);
let mut cache = Cache::new(Default::default(), duration.clone());
cache.set_gas_price_corpus(vec![].into());
assert_eq!(cache.gas_price_corpus(), Some(vec![].into()));
cache.set_gas_price_corpus(vec![].into());
assert_eq!(cache.gas_price_corpus(), Some(vec![].into()));
{
let corpus_time = &mut cache.corpus.as_mut().unwrap().1;
*corpus_time = *corpus_time - duration;
}
assert!(cache.gas_price_corpus().is_none());
}
{
let corpus_time = &mut cache.corpus.as_mut().unwrap().1;
*corpus_time = *corpus_time - duration;
}
assert!(cache.gas_price_corpus().is_none());
}
}

View File

@@ -23,29 +23,31 @@
//! root has. A correct proof implies that the claimed block is identical to the one
//! we discarded.
use bytes::Bytes;
use common_types::ids::BlockId;
use ethereum_types::{H256, U256};
use ethtrie::{self, TrieDB, TrieDBMut};
use hash_db::HashDB;
use journaldb::new_memory_db;
use keccak_hasher::KeccakHasher;
use kvdb::DBValue;
use memory_db::MemoryDB;
use journaldb::new_memory_db;
use bytes::Bytes;
use trie::{TrieMut, Trie, Recorder};
use ethtrie::{self, TrieDB, TrieDBMut};
use rlp::{RlpStream, Rlp};
use rlp::{Rlp, RlpStream};
use trie::{Recorder, Trie, TrieMut};
// encode a key.
macro_rules! key {
($num: expr) => { ::rlp::encode(&$num) }
($num: expr) => {
::rlp::encode(&$num)
};
}
macro_rules! val {
($hash: expr, $td: expr) => {{
let mut stream = RlpStream::new_list(2);
stream.append(&$hash).append(&$td);
stream.drain()
}}
($hash: expr, $td: expr) => {{
let mut stream = RlpStream::new_list(2);
stream.append(&$hash).append(&$td);
stream.drain()
}};
}
/// The size of each CHT.
@@ -55,96 +57,104 @@ pub const SIZE: u64 = 2048;
/// See module docs for more details.
#[derive(Debug, Clone)]
pub struct CHT<DB: HashDB<KeccakHasher, DBValue>> {
db: DB,
root: H256, // the root of this CHT.
number: u64,
db: DB,
root: H256, // the root of this CHT.
number: u64,
}
impl<DB: HashDB<KeccakHasher, DBValue>> CHT<DB> {
/// Query the root of the CHT.
pub fn root(&self) -> H256 { self.root }
/// Query the root of the CHT.
pub fn root(&self) -> H256 {
self.root
}
/// Query the number of the CHT.
pub fn number(&self) -> u64 { self.number }
/// Query the number of the CHT.
pub fn number(&self) -> u64 {
self.number
}
/// Generate an inclusion proof for the entry at a specific block.
/// Nodes before level `from_level` will be omitted.
/// Returns an error on an incomplete trie, and `Ok(None)` on an unprovable request.
pub fn prove(&self, num: u64, from_level: u32) -> ethtrie::Result<Option<Vec<Bytes>>> {
if block_to_cht_number(num) != Some(self.number) { return Ok(None) }
/// Generate an inclusion proof for the entry at a specific block.
/// Nodes before level `from_level` will be omitted.
/// Returns an error on an incomplete trie, and `Ok(None)` on an unprovable request.
pub fn prove(&self, num: u64, from_level: u32) -> ethtrie::Result<Option<Vec<Bytes>>> {
if block_to_cht_number(num) != Some(self.number) {
return Ok(None);
}
let mut recorder = Recorder::with_depth(from_level);
let db: &HashDB<_,_> = &self.db;
let t = TrieDB::new(&db, &self.root)?;
t.get_with(&key!(num), &mut recorder)?;
let mut recorder = Recorder::with_depth(from_level);
let db: &HashDB<_, _> = &self.db;
let t = TrieDB::new(&db, &self.root)?;
t.get_with(&key!(num), &mut recorder)?;
Ok(Some(recorder.drain().into_iter().map(|x| x.data).collect()))
}
Ok(Some(recorder.drain().into_iter().map(|x| x.data).collect()))
}
}
/// Block information necessary to build a CHT.
pub struct BlockInfo {
/// The block's hash.
pub hash: H256,
/// The block's parent's hash.
pub parent_hash: H256,
/// The block's total difficulty.
pub total_difficulty: U256,
/// The block's hash.
pub hash: H256,
/// The block's parent's hash.
pub parent_hash: H256,
/// The block's total difficulty.
pub total_difficulty: U256,
}
/// Build an in-memory CHT from a closure which provides necessary information
/// about blocks. If the fetcher ever fails to provide the info, the CHT
/// will not be generated.
pub fn build<F>(cht_num: u64, mut fetcher: F) -> Option<CHT<MemoryDB<KeccakHasher, DBValue>>>
where F: FnMut(BlockId) -> Option<BlockInfo>
where
F: FnMut(BlockId) -> Option<BlockInfo>,
{
let mut db = new_memory_db();
let mut db = new_memory_db();
// start from the last block by number and work backwards.
let last_num = start_number(cht_num + 1) - 1;
let mut id = BlockId::Number(last_num);
// start from the last block by number and work backwards.
let last_num = start_number(cht_num + 1) - 1;
let mut id = BlockId::Number(last_num);
let mut root = H256::default();
let mut root = H256::default();
{
let mut t = TrieDBMut::new(&mut db, &mut root);
for blk_num in (0..SIZE).map(|n| last_num - n) {
let info = match fetcher(id) {
Some(info) => info,
None => return None,
};
{
let mut t = TrieDBMut::new(&mut db, &mut root);
for blk_num in (0..SIZE).map(|n| last_num - n) {
let info = match fetcher(id) {
Some(info) => info,
None => return None,
};
id = BlockId::Hash(info.parent_hash);
t.insert(&key!(blk_num), &val!(info.hash, info.total_difficulty))
.expect("fresh in-memory database is infallible; qed");
}
}
id = BlockId::Hash(info.parent_hash);
t.insert(&key!(blk_num), &val!(info.hash, info.total_difficulty))
.expect("fresh in-memory database is infallible; qed");
}
}
Some(CHT {
db,
root,
number: cht_num,
})
Some(CHT {
db,
root,
number: cht_num,
})
}
/// Compute a CHT root from an iterator of (hash, td) pairs. Fails if shorter than
/// SIZE items. The items are assumed to proceed sequentially from `start_number(cht_num)`.
/// Discards the trie's nodes.
pub fn compute_root<I>(cht_num: u64, iterable: I) -> Option<H256>
where I: IntoIterator<Item=(H256, U256)>
where
I: IntoIterator<Item = (H256, U256)>,
{
let mut v = Vec::with_capacity(SIZE as usize);
let start_num = start_number(cht_num) as usize;
let mut v = Vec::with_capacity(SIZE as usize);
let start_num = start_number(cht_num) as usize;
for (i, (h, td)) in iterable.into_iter().take(SIZE as usize).enumerate() {
v.push((key!(i + start_num), val!(h, td)))
}
for (i, (h, td)) in iterable.into_iter().take(SIZE as usize).enumerate() {
v.push((key!(i + start_num), val!(h, td)))
}
if v.len() == SIZE as usize {
Some(::triehash::trie_root(v))
} else {
None
}
if v.len() == SIZE as usize {
Some(::triehash::trie_root(v))
} else {
None
}
}
/// Check a proof for a CHT.
@@ -152,32 +162,34 @@ pub fn compute_root<I>(cht_num: u64, iterable: I) -> Option<H256>
/// verify the given trie branch and extract the canonical hash and total difficulty.
// TODO: better support for partially-checked queries.
pub fn check_proof(proof: &[Bytes], num: u64, root: H256) -> Option<(H256, U256)> {
let mut db = new_memory_db();
let mut db = new_memory_db();
for node in proof { db.insert(&node[..]); }
let res = match TrieDB::new(&db, &root) {
Err(_) => return None,
Ok(trie) => trie.get_with(&key!(num), |val: &[u8]| {
let rlp = Rlp::new(val);
rlp.val_at::<H256>(0)
.and_then(|h| rlp.val_at::<U256>(1).map(|td| (h, td)))
.ok()
})
};
for node in proof {
db.insert(&node[..]);
}
let res = match TrieDB::new(&db, &root) {
Err(_) => return None,
Ok(trie) => trie.get_with(&key!(num), |val: &[u8]| {
let rlp = Rlp::new(val);
rlp.val_at::<H256>(0)
.and_then(|h| rlp.val_at::<U256>(1).map(|td| (h, td)))
.ok()
}),
};
match res {
Ok(Some(Some((hash, td)))) => Some((hash, td)),
_ => None,
}
match res {
Ok(Some(Some((hash, td)))) => Some((hash, td)),
_ => None,
}
}
/// Convert a block number to a CHT number.
/// Returns `None` for `block_num` == 0, `Some` otherwise.
pub fn block_to_cht_number(block_num: u64) -> Option<u64> {
match block_num {
0 => None,
n => Some((n - 1) / SIZE),
}
match block_num {
0 => None,
n => Some((n - 1) / SIZE),
}
}
/// Get the starting block of a given CHT.
@@ -187,29 +199,29 @@ pub fn block_to_cht_number(block_num: u64) -> Option<u64> {
/// This is because the genesis hash is assumed to be known
/// and including it would be redundant.
pub fn start_number(cht_num: u64) -> u64 {
(cht_num * SIZE) + 1
(cht_num * SIZE) + 1
}
#[cfg(test)]
mod tests {
#[test]
fn size_is_lt_usize() {
// to ensure safe casting on the target platform.
assert!(::cht::SIZE < usize::max_value() as u64)
}
#[test]
fn size_is_lt_usize() {
// to ensure safe casting on the target platform.
assert!(::cht::SIZE < usize::max_value() as u64)
}
#[test]
fn block_to_cht_number() {
assert!(::cht::block_to_cht_number(0).is_none());
assert_eq!(::cht::block_to_cht_number(1).unwrap(), 0);
assert_eq!(::cht::block_to_cht_number(::cht::SIZE + 1).unwrap(), 1);
assert_eq!(::cht::block_to_cht_number(::cht::SIZE).unwrap(), 0);
}
#[test]
fn block_to_cht_number() {
assert!(::cht::block_to_cht_number(0).is_none());
assert_eq!(::cht::block_to_cht_number(1).unwrap(), 0);
assert_eq!(::cht::block_to_cht_number(::cht::SIZE + 1).unwrap(), 1);
assert_eq!(::cht::block_to_cht_number(::cht::SIZE).unwrap(), 0);
}
#[test]
fn start_number() {
assert_eq!(::cht::start_number(0), 1);
assert_eq!(::cht::start_number(1), ::cht::SIZE + 1);
assert_eq!(::cht::start_number(2), ::cht::SIZE * 2 + 1);
}
#[test]
fn start_number() {
assert_eq!(::cht::start_number(0), 1);
assert_eq!(::cht::start_number(1), ::cht::SIZE + 1);
assert_eq!(::cht::start_number(2), ::cht::SIZE * 2 + 1);
}
}

View File

@@ -18,68 +18,70 @@
use std::sync::Arc;
use common_types::encoded;
use common_types::header::Header;
use common_types::receipt::Receipt;
use ethcore::engines::{EthEngine, StateDependentProof};
use ethcore::machine::EthereumMachine;
use common_types::{encoded, header::Header, receipt::Receipt};
use ethcore::{
engines::{EthEngine, StateDependentProof},
machine::EthereumMachine,
};
use ethereum_types::H256;
use futures::future::IntoFuture;
/// Provides full chain data.
pub trait ChainDataFetcher: Send + Sync + 'static {
/// Error type when data unavailable.
type Error: ::std::fmt::Debug;
/// Error type when data unavailable.
type Error: ::std::fmt::Debug;
/// Future for fetching block body.
type Body: IntoFuture<Item=encoded::Block, Error=Self::Error>;
/// Future for fetching block receipts.
type Receipts: IntoFuture<Item=Vec<Receipt>, Error=Self::Error>;
/// Future for fetching epoch transition
type Transition: IntoFuture<Item=Vec<u8>, Error=Self::Error>;
/// Future for fetching block body.
type Body: IntoFuture<Item = encoded::Block, Error = Self::Error>;
/// Future for fetching block receipts.
type Receipts: IntoFuture<Item = Vec<Receipt>, Error = Self::Error>;
/// Future for fetching epoch transition
type Transition: IntoFuture<Item = Vec<u8>, Error = Self::Error>;
/// Fetch a block body.
fn block_body(&self, header: &Header) -> Self::Body;
/// Fetch a block body.
fn block_body(&self, header: &Header) -> Self::Body;
/// Fetch block receipts.
fn block_receipts(&self, header: &Header) -> Self::Receipts;
/// Fetch block receipts.
fn block_receipts(&self, header: &Header) -> Self::Receipts;
/// Fetch epoch transition proof at given header.
fn epoch_transition(
&self,
_hash: H256,
_engine: Arc<EthEngine>,
_checker: Arc<StateDependentProof<EthereumMachine>>
) -> Self::Transition;
/// Fetch epoch transition proof at given header.
fn epoch_transition(
&self,
_hash: H256,
_engine: Arc<EthEngine>,
_checker: Arc<StateDependentProof<EthereumMachine>>,
) -> Self::Transition;
}
/// Fetcher implementation which cannot fetch anything.
pub struct Unavailable;
/// Create a fetcher which has all data unavailable.
pub fn unavailable() -> Unavailable { Unavailable }
pub fn unavailable() -> Unavailable {
Unavailable
}
impl ChainDataFetcher for Unavailable {
type Error = &'static str;
type Error = &'static str;
type Body = Result<encoded::Block, &'static str>;
type Receipts = Result<Vec<Receipt>, &'static str>;
type Transition = Result<Vec<u8>, &'static str>;
type Body = Result<encoded::Block, &'static str>;
type Receipts = Result<Vec<Receipt>, &'static str>;
type Transition = Result<Vec<u8>, &'static str>;
fn block_body(&self, _header: &Header) -> Self::Body {
Err("fetching block bodies unavailable")
}
fn block_body(&self, _header: &Header) -> Self::Body {
Err("fetching block bodies unavailable")
}
fn block_receipts(&self, _header: &Header) -> Self::Receipts {
Err("fetching block receipts unavailable")
}
fn block_receipts(&self, _header: &Header) -> Self::Receipts {
Err("fetching block receipts unavailable")
}
fn epoch_transition(
&self,
_hash: H256,
_engine: Arc<EthEngine>,
_checker: Arc<StateDependentProof<EthereumMachine>>
) -> Self::Transition {
Err("fetching epoch transition proofs unavailable")
}
fn epoch_transition(
&self,
_hash: H256,
_engine: Arc<EthEngine>,
_checker: Arc<StateDependentProof<EthereumMachine>>,
) -> Self::Transition {
Err("fetching epoch transition proofs unavailable")
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -17,118 +17,126 @@
//! Minimal IO service for light client.
//! Just handles block import messages and passes them to the client.
use std::fmt;
use std::sync::Arc;
use std::{fmt, sync::Arc};
use ethcore_db as db;
use ethcore::{client::ClientIoMessage, error::Error as CoreError, spec::Spec};
use ethcore_blockchain::BlockChainDB;
use ethcore::client::ClientIoMessage;
use ethcore::error::Error as CoreError;
use ethcore::spec::Spec;
use ethcore_db as db;
use io::{IoContext, IoError, IoHandler, IoService};
use cache::Cache;
use parking_lot::Mutex;
use super::{ChainDataFetcher, LightChainNotify, Client, Config as ClientConfig};
use super::{ChainDataFetcher, Client, Config as ClientConfig, LightChainNotify};
/// Errors on service initialization.
#[derive(Debug)]
pub enum Error {
/// Core error.
Core(CoreError),
/// I/O service error.
Io(IoError),
/// Core error.
Core(CoreError),
/// I/O service error.
Io(IoError),
}
impl From<CoreError> for Error {
#[inline]
fn from(err: CoreError) -> Error {
Error::Core(err)
}
#[inline]
fn from(err: CoreError) -> Error {
Error::Core(err)
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Error::Core(ref msg) => write!(f, "Core error: {}", msg),
Error::Io(ref err) => write!(f, "I/O service error: {}", err),
}
}
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Error::Core(ref msg) => write!(f, "Core error: {}", msg),
Error::Io(ref err) => write!(f, "I/O service error: {}", err),
}
}
}
/// Light client service.
pub struct Service<T> {
client: Arc<Client<T>>,
io_service: IoService<ClientIoMessage>,
client: Arc<Client<T>>,
io_service: IoService<ClientIoMessage>,
}
impl<T: ChainDataFetcher> Service<T> {
/// Start the service: initialize I/O workers and client itself.
pub fn start(config: ClientConfig, spec: &Spec, fetcher: T, db: Arc<BlockChainDB>, cache: Arc<Mutex<Cache>>) -> Result<Self, Error> {
let io_service = IoService::<ClientIoMessage>::start().map_err(Error::Io)?;
let client = Arc::new(Client::new(config,
db.key_value().clone(),
db::COL_LIGHT_CHAIN,
spec,
fetcher,
io_service.channel(),
cache,
)?);
/// Start the service: initialize I/O workers and client itself.
pub fn start(
config: ClientConfig,
spec: &Spec,
fetcher: T,
db: Arc<BlockChainDB>,
cache: Arc<Mutex<Cache>>,
) -> Result<Self, Error> {
let io_service = IoService::<ClientIoMessage>::start().map_err(Error::Io)?;
let client = Arc::new(Client::new(
config,
db.key_value().clone(),
db::COL_LIGHT_CHAIN,
spec,
fetcher,
io_service.channel(),
cache,
)?);
io_service.register_handler(Arc::new(ImportBlocks(client.clone()))).map_err(Error::Io)?;
spec.engine.register_client(Arc::downgrade(&client) as _);
io_service
.register_handler(Arc::new(ImportBlocks(client.clone())))
.map_err(Error::Io)?;
spec.engine.register_client(Arc::downgrade(&client) as _);
Ok(Service {
client,
io_service,
})
}
Ok(Service { client, io_service })
}
/// Set the actor to be notified on certain chain events
pub fn add_notify(&self, notify: Arc<LightChainNotify>) {
self.client.add_listener(Arc::downgrade(&notify));
}
/// Set the actor to be notified on certain chain events
pub fn add_notify(&self, notify: Arc<LightChainNotify>) {
self.client.add_listener(Arc::downgrade(&notify));
}
/// Register an I/O handler on the service.
pub fn register_handler(&self, handler: Arc<IoHandler<ClientIoMessage> + Send>) -> Result<(), IoError> {
self.io_service.register_handler(handler)
}
/// Register an I/O handler on the service.
pub fn register_handler(
&self,
handler: Arc<IoHandler<ClientIoMessage> + Send>,
) -> Result<(), IoError> {
self.io_service.register_handler(handler)
}
/// Get a handle to the client.
pub fn client(&self) -> &Arc<Client<T>> {
&self.client
}
/// Get a handle to the client.
pub fn client(&self) -> &Arc<Client<T>> {
&self.client
}
}
struct ImportBlocks<T>(Arc<Client<T>>);
impl<T: ChainDataFetcher> IoHandler<ClientIoMessage> for ImportBlocks<T> {
fn message(&self, _io: &IoContext<ClientIoMessage>, message: &ClientIoMessage) {
if let ClientIoMessage::BlockVerified = *message {
self.0.import_verified();
}
}
fn message(&self, _io: &IoContext<ClientIoMessage>, message: &ClientIoMessage) {
if let ClientIoMessage::BlockVerified = *message {
self.0.import_verified();
}
}
}
#[cfg(test)]
mod tests {
use super::Service;
use ethcore::spec::Spec;
use super::Service;
use ethcore::spec::Spec;
use std::sync::Arc;
use cache::Cache;
use client::fetch;
use std::time::Duration;
use parking_lot::Mutex;
use ethcore::test_helpers;
use cache::Cache;
use client::fetch;
use ethcore::test_helpers;
use parking_lot::Mutex;
use std::{sync::Arc, time::Duration};
#[test]
fn it_works() {
let db = test_helpers::new_db();
let spec = Spec::new_test();
let cache = Arc::new(Mutex::new(Cache::new(Default::default(), Duration::from_secs(6 * 3600))));
#[test]
fn it_works() {
let db = test_helpers::new_db();
let spec = Spec::new_test();
let cache = Arc::new(Mutex::new(Cache::new(
Default::default(),
Duration::from_secs(6 * 3600),
)));
Service::start(Default::default(), &spec, fetch::unavailable(), db, cache).unwrap();
}
Service::start(Default::default(), &spec, fetch::unavailable(), db, cache).unwrap();
}
}

View File

@@ -32,20 +32,22 @@
#![deny(missing_docs)]
pub mod client;
pub mod cache;
pub mod cht;
pub mod client;
pub mod net;
pub mod on_demand;
pub mod transaction_queue;
pub mod cache;
pub mod provider;
pub mod transaction_queue;
mod types;
pub use self::cache::Cache;
pub use self::provider::{Provider, MAX_HEADERS_PER_REQUEST};
pub use self::transaction_queue::TransactionQueue;
pub use types::request as request;
pub use self::{
cache::Cache,
provider::{Provider, MAX_HEADERS_PER_REQUEST},
transaction_queue::TransactionQueue,
};
pub use types::request;
#[macro_use]
extern crate serde_derive;
@@ -55,41 +57,41 @@ extern crate log;
extern crate bincode;
extern crate common_types;
extern crate ethcore;
extern crate ethcore_blockchain;
extern crate ethcore_db;
extern crate ethcore_io as io;
extern crate ethcore_network as network;
extern crate parity_bytes as bytes;
extern crate ethereum_types;
extern crate ethcore;
extern crate failsafe;
extern crate fastmap;
extern crate futures;
extern crate hash_db;
extern crate heapsize;
extern crate failsafe;
extern crate futures;
extern crate itertools;
extern crate keccak_hasher;
extern crate memory_db;
extern crate trie_db as trie;
extern crate parity_bytes as bytes;
extern crate parking_lot;
extern crate patricia_trie_ethereum as ethtrie;
extern crate fastmap;
extern crate rand;
extern crate rlp;
extern crate parking_lot;
extern crate trie_db as trie;
#[macro_use]
extern crate rlp_derive;
extern crate keccak_hash as hash;
extern crate kvdb;
extern crate memory_cache;
extern crate serde;
extern crate smallvec;
extern crate stats;
extern crate vm;
extern crate keccak_hash as hash;
extern crate triehash_ethereum as triehash;
extern crate kvdb;
extern crate memory_cache;
extern crate vm;
#[macro_use]
extern crate error_chain;
extern crate journaldb;
#[cfg(test)]
extern crate kvdb_memorydb;
#[cfg(test)]
extern crate tempdir;
extern crate journaldb;

View File

@@ -16,178 +16,180 @@
//! I/O and event context generalizations.
use network::{NetworkContext, PeerId, NodeId};
use network::{NetworkContext, NodeId, PeerId};
use super::{Announcement, LightProtocol, ReqId};
use super::error::Error;
use super::{error::Error, Announcement, LightProtocol, ReqId};
use request::NetworkRequests as Requests;
/// An I/O context which allows sending and receiving packets as well as
/// disconnecting peers. This is used as a generalization of the portions
/// of a p2p network which the light protocol structure makes use of.
pub trait IoContext {
/// Send a packet to a specific peer.
fn send(&self, peer: PeerId, packet_id: u8, packet_body: Vec<u8>);
/// Send a packet to a specific peer.
fn send(&self, peer: PeerId, packet_id: u8, packet_body: Vec<u8>);
/// Respond to a peer's message. Only works if this context is a byproduct
/// of a packet handler.
fn respond(&self, packet_id: u8, packet_body: Vec<u8>);
/// Respond to a peer's message. Only works if this context is a byproduct
/// of a packet handler.
fn respond(&self, packet_id: u8, packet_body: Vec<u8>);
/// Disconnect a peer.
fn disconnect_peer(&self, peer: PeerId);
/// Disconnect a peer.
fn disconnect_peer(&self, peer: PeerId);
/// Disable a peer -- this is a disconnect + a time-out.
fn disable_peer(&self, peer: PeerId);
/// Disable a peer -- this is a disconnect + a time-out.
fn disable_peer(&self, peer: PeerId);
/// Get a peer's protocol version.
fn protocol_version(&self, peer: PeerId) -> Option<u8>;
/// Get a peer's protocol version.
fn protocol_version(&self, peer: PeerId) -> Option<u8>;
/// Persistent peer id
fn persistent_peer_id(&self, peer: PeerId) -> Option<NodeId>;
/// Persistent peer id
fn persistent_peer_id(&self, peer: PeerId) -> Option<NodeId>;
/// Whether given peer id is reserved peer
fn is_reserved_peer(&self, peer: PeerId) -> bool;
/// Whether given peer id is reserved peer
fn is_reserved_peer(&self, peer: PeerId) -> bool;
}
impl<T> IoContext for T where T: ?Sized + NetworkContext {
fn send(&self, peer: PeerId, packet_id: u8, packet_body: Vec<u8>) {
if let Err(e) = self.send(peer, packet_id, packet_body) {
debug!(target: "pip", "Error sending packet to peer {}: {}", peer, e);
}
}
impl<T> IoContext for T
where
T: ?Sized + NetworkContext,
{
fn send(&self, peer: PeerId, packet_id: u8, packet_body: Vec<u8>) {
if let Err(e) = self.send(peer, packet_id, packet_body) {
debug!(target: "pip", "Error sending packet to peer {}: {}", peer, e);
}
}
fn respond(&self, packet_id: u8, packet_body: Vec<u8>) {
if let Err(e) = self.respond(packet_id, packet_body) {
debug!(target: "pip", "Error responding to peer message: {}", e);
}
}
fn respond(&self, packet_id: u8, packet_body: Vec<u8>) {
if let Err(e) = self.respond(packet_id, packet_body) {
debug!(target: "pip", "Error responding to peer message: {}", e);
}
}
fn disconnect_peer(&self, peer: PeerId) {
trace!(target: "pip", "Initiating disconnect of peer {}", peer);
NetworkContext::disconnect_peer(self, peer);
}
fn disconnect_peer(&self, peer: PeerId) {
trace!(target: "pip", "Initiating disconnect of peer {}", peer);
NetworkContext::disconnect_peer(self, peer);
}
fn disable_peer(&self, peer: PeerId) {
trace!(target: "pip", "Initiating disable of peer {}", peer);
NetworkContext::disable_peer(self, peer);
}
fn disable_peer(&self, peer: PeerId) {
trace!(target: "pip", "Initiating disable of peer {}", peer);
NetworkContext::disable_peer(self, peer);
}
fn protocol_version(&self, peer: PeerId) -> Option<u8> {
self.protocol_version(self.subprotocol_name(), peer)
}
fn protocol_version(&self, peer: PeerId) -> Option<u8> {
self.protocol_version(self.subprotocol_name(), peer)
}
fn persistent_peer_id(&self, peer: PeerId) -> Option<NodeId> {
self.session_info(peer).and_then(|info| info.id)
}
fn persistent_peer_id(&self, peer: PeerId) -> Option<NodeId> {
self.session_info(peer).and_then(|info| info.id)
}
fn is_reserved_peer(&self, peer: PeerId) -> bool {
NetworkContext::is_reserved_peer(self, peer)
}
fn is_reserved_peer(&self, peer: PeerId) -> bool {
NetworkContext::is_reserved_peer(self, peer)
}
}
/// Basic context for the protocol.
pub trait BasicContext {
/// Returns the relevant's peer persistent Id (aka NodeId).
fn persistent_peer_id(&self, peer: PeerId) -> Option<NodeId>;
/// Returns the relevant's peer persistent Id (aka NodeId).
fn persistent_peer_id(&self, peer: PeerId) -> Option<NodeId>;
/// Make a request from a peer.
///
/// Fails on: nonexistent peer, network error, peer not server,
/// insufficient credits. Does not check capabilities before sending.
/// On success, returns a request id which can later be coordinated
/// with an event.
fn request_from(&self, peer: PeerId, request: Requests) -> Result<ReqId, Error>;
/// Make a request from a peer.
///
/// Fails on: nonexistent peer, network error, peer not server,
/// insufficient credits. Does not check capabilities before sending.
/// On success, returns a request id which can later be coordinated
/// with an event.
fn request_from(&self, peer: PeerId, request: Requests) -> Result<ReqId, Error>;
/// Make an announcement of new capabilities to the rest of the peers.
// TODO: maybe just put this on a timer in LightProtocol?
fn make_announcement(&self, announcement: Announcement);
/// Make an announcement of new capabilities to the rest of the peers.
// TODO: maybe just put this on a timer in LightProtocol?
fn make_announcement(&self, announcement: Announcement);
/// Disconnect a peer.
fn disconnect_peer(&self, peer: PeerId);
/// Disconnect a peer.
fn disconnect_peer(&self, peer: PeerId);
/// Disable a peer.
fn disable_peer(&self, peer: PeerId);
/// Disable a peer.
fn disable_peer(&self, peer: PeerId);
}
/// Context for a protocol event which has a peer ID attached.
pub trait EventContext: BasicContext {
/// Get the peer relevant to the event e.g. message sender,
/// disconnected/connected peer.
fn peer(&self) -> PeerId;
/// Get the peer relevant to the event e.g. message sender,
/// disconnected/connected peer.
fn peer(&self) -> PeerId;
/// Treat the event context as a basic context.
fn as_basic(&self) -> &BasicContext;
/// Treat the event context as a basic context.
fn as_basic(&self) -> &BasicContext;
}
/// Basic context.
pub struct TickCtx<'a> {
/// Io context to enable dispatch.
pub io: &'a IoContext,
/// Protocol implementation.
pub proto: &'a LightProtocol,
/// Io context to enable dispatch.
pub io: &'a IoContext,
/// Protocol implementation.
pub proto: &'a LightProtocol,
}
impl<'a> BasicContext for TickCtx<'a> {
fn persistent_peer_id(&self, id: PeerId) -> Option<NodeId> {
self.io.persistent_peer_id(id)
}
fn persistent_peer_id(&self, id: PeerId) -> Option<NodeId> {
self.io.persistent_peer_id(id)
}
fn request_from(&self, peer: PeerId, requests: Requests) -> Result<ReqId, Error> {
self.proto.request_from(self.io, peer, requests)
}
fn request_from(&self, peer: PeerId, requests: Requests) -> Result<ReqId, Error> {
self.proto.request_from(self.io, peer, requests)
}
fn make_announcement(&self, announcement: Announcement) {
self.proto.make_announcement(self.io, announcement);
}
fn make_announcement(&self, announcement: Announcement) {
self.proto.make_announcement(self.io, announcement);
}
fn disconnect_peer(&self, peer: PeerId) {
self.io.disconnect_peer(peer);
}
fn disconnect_peer(&self, peer: PeerId) {
self.io.disconnect_peer(peer);
}
fn disable_peer(&self, peer: PeerId) {
self.io.disable_peer(peer);
}
fn disable_peer(&self, peer: PeerId) {
self.io.disable_peer(peer);
}
}
/// Concrete implementation of `EventContext` over the light protocol struct and
/// an io context.
pub struct Ctx<'a> {
/// Io context to enable immediate response to events.
pub io: &'a IoContext,
/// Protocol implementation.
pub proto: &'a LightProtocol,
/// Relevant peer for event.
pub peer: PeerId,
/// Io context to enable immediate response to events.
pub io: &'a IoContext,
/// Protocol implementation.
pub proto: &'a LightProtocol,
/// Relevant peer for event.
pub peer: PeerId,
}
impl<'a> BasicContext for Ctx<'a> {
fn persistent_peer_id(&self, id: PeerId) -> Option<NodeId> {
self.io.persistent_peer_id(id)
}
fn persistent_peer_id(&self, id: PeerId) -> Option<NodeId> {
self.io.persistent_peer_id(id)
}
fn request_from(&self, peer: PeerId, requests: Requests) -> Result<ReqId, Error> {
self.proto.request_from(self.io, peer, requests)
}
fn request_from(&self, peer: PeerId, requests: Requests) -> Result<ReqId, Error> {
self.proto.request_from(self.io, peer, requests)
}
fn make_announcement(&self, announcement: Announcement) {
self.proto.make_announcement(self.io, announcement);
}
fn make_announcement(&self, announcement: Announcement) {
self.proto.make_announcement(self.io, announcement);
}
fn disconnect_peer(&self, peer: PeerId) {
self.io.disconnect_peer(peer);
}
fn disconnect_peer(&self, peer: PeerId) {
self.io.disconnect_peer(peer);
}
fn disable_peer(&self, peer: PeerId) {
self.io.disable_peer(peer);
}
fn disable_peer(&self, peer: PeerId) {
self.io.disable_peer(peer);
}
}
impl<'a> EventContext for Ctx<'a> {
fn peer(&self) -> PeerId {
self.peer
}
fn peer(&self) -> PeerId {
self.peer
}
fn as_basic(&self) -> &BasicContext {
&*self
}
fn as_basic(&self) -> &BasicContext {
&*self
}
}

View File

@@ -17,8 +17,9 @@
//! Defines error types and levels of punishment to use upon
//! encountering.
use network;
use rlp;
use std::fmt;
use {rlp, network};
/// Levels of punishment.
///
@@ -27,98 +28,100 @@ use {rlp, network};
// In ascending order
#[derive(Debug, PartialEq, Eq)]
pub enum Punishment {
/// Perform no punishment.
None,
/// Disconnect the peer, but don't prevent them from reconnecting.
Disconnect,
/// Disconnect the peer and prevent them from reconnecting.
Disable,
/// Perform no punishment.
None,
/// Disconnect the peer, but don't prevent them from reconnecting.
Disconnect,
/// Disconnect the peer and prevent them from reconnecting.
Disable,
}
/// Kinds of errors which can be encountered in the course of LES.
#[derive(Debug)]
pub enum Error {
/// An RLP decoding error.
Rlp(rlp::DecoderError),
/// A network error.
Network(network::Error),
/// Out of credits.
NoCredits,
/// Unrecognized packet code.
UnrecognizedPacket(u8),
/// Unexpected handshake.
UnexpectedHandshake,
/// Peer on wrong network (wrong NetworkId or genesis hash)
WrongNetwork,
/// Unknown peer.
UnknownPeer,
/// Unsolicited response.
UnsolicitedResponse,
/// Bad back-reference in request.
BadBackReference,
/// Not a server.
NotServer,
/// Unsupported protocol version.
UnsupportedProtocolVersion(u8),
/// Bad protocol version.
BadProtocolVersion,
/// Peer is overburdened.
Overburdened,
/// No handler kept the peer.
RejectedByHandlers,
/// An RLP decoding error.
Rlp(rlp::DecoderError),
/// A network error.
Network(network::Error),
/// Out of credits.
NoCredits,
/// Unrecognized packet code.
UnrecognizedPacket(u8),
/// Unexpected handshake.
UnexpectedHandshake,
/// Peer on wrong network (wrong NetworkId or genesis hash)
WrongNetwork,
/// Unknown peer.
UnknownPeer,
/// Unsolicited response.
UnsolicitedResponse,
/// Bad back-reference in request.
BadBackReference,
/// Not a server.
NotServer,
/// Unsupported protocol version.
UnsupportedProtocolVersion(u8),
/// Bad protocol version.
BadProtocolVersion,
/// Peer is overburdened.
Overburdened,
/// No handler kept the peer.
RejectedByHandlers,
}
impl Error {
/// What level of punishment does this error warrant?
pub fn punishment(&self) -> Punishment {
match *self {
Error::Rlp(_) => Punishment::Disable,
Error::Network(_) => Punishment::None,
Error::NoCredits => Punishment::Disable,
Error::UnrecognizedPacket(_) => Punishment::Disconnect,
Error::UnexpectedHandshake => Punishment::Disconnect,
Error::WrongNetwork => Punishment::Disable,
Error::UnknownPeer => Punishment::Disconnect,
Error::UnsolicitedResponse => Punishment::Disable,
Error::BadBackReference => Punishment::Disable,
Error::NotServer => Punishment::Disable,
Error::UnsupportedProtocolVersion(_) => Punishment::Disable,
Error::BadProtocolVersion => Punishment::Disable,
Error::Overburdened => Punishment::None,
Error::RejectedByHandlers => Punishment::Disconnect,
}
}
/// What level of punishment does this error warrant?
pub fn punishment(&self) -> Punishment {
match *self {
Error::Rlp(_) => Punishment::Disable,
Error::Network(_) => Punishment::None,
Error::NoCredits => Punishment::Disable,
Error::UnrecognizedPacket(_) => Punishment::Disconnect,
Error::UnexpectedHandshake => Punishment::Disconnect,
Error::WrongNetwork => Punishment::Disable,
Error::UnknownPeer => Punishment::Disconnect,
Error::UnsolicitedResponse => Punishment::Disable,
Error::BadBackReference => Punishment::Disable,
Error::NotServer => Punishment::Disable,
Error::UnsupportedProtocolVersion(_) => Punishment::Disable,
Error::BadProtocolVersion => Punishment::Disable,
Error::Overburdened => Punishment::None,
Error::RejectedByHandlers => Punishment::Disconnect,
}
}
}
impl From<rlp::DecoderError> for Error {
fn from(err: rlp::DecoderError) -> Self {
Error::Rlp(err)
}
fn from(err: rlp::DecoderError) -> Self {
Error::Rlp(err)
}
}
impl From<network::Error> for Error {
fn from(err: network::Error) -> Self {
Error::Network(err)
}
fn from(err: network::Error) -> Self {
Error::Network(err)
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Error::Rlp(ref err) => err.fmt(f),
Error::Network(ref err) => err.fmt(f),
Error::NoCredits => write!(f, "Out of request credits"),
Error::UnrecognizedPacket(code) => write!(f, "Unrecognized packet: 0x{:x}", code),
Error::UnexpectedHandshake => write!(f, "Unexpected handshake"),
Error::WrongNetwork => write!(f, "Wrong network"),
Error::UnknownPeer => write!(f, "Unknown peer"),
Error::UnsolicitedResponse => write!(f, "Peer provided unsolicited data"),
Error::BadBackReference => write!(f, "Bad back-reference in request."),
Error::NotServer => write!(f, "Peer not a server."),
Error::UnsupportedProtocolVersion(pv) => write!(f, "Unsupported protocol version: {}", pv),
Error::BadProtocolVersion => write!(f, "Bad protocol version in handshake"),
Error::Overburdened => write!(f, "Peer overburdened"),
Error::RejectedByHandlers => write!(f, "No handler kept this peer"),
}
}
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Error::Rlp(ref err) => err.fmt(f),
Error::Network(ref err) => err.fmt(f),
Error::NoCredits => write!(f, "Out of request credits"),
Error::UnrecognizedPacket(code) => write!(f, "Unrecognized packet: 0x{:x}", code),
Error::UnexpectedHandshake => write!(f, "Unexpected handshake"),
Error::WrongNetwork => write!(f, "Wrong network"),
Error::UnknownPeer => write!(f, "Unknown peer"),
Error::UnsolicitedResponse => write!(f, "Peer provided unsolicited data"),
Error::BadBackReference => write!(f, "Bad back-reference in request."),
Error::NotServer => write!(f, "Peer not a server."),
Error::UnsupportedProtocolVersion(pv) => {
write!(f, "Unsupported protocol version: {}", pv)
}
Error::BadProtocolVersion => write!(f, "Bad protocol version in handshake"),
Error::Overburdened => write!(f, "Peer overburdened"),
Error::RejectedByHandlers => write!(f, "No handler kept this peer"),
}
}
}

View File

@@ -24,15 +24,17 @@
//! length `TIME_PERIOD_MS`, with the exception that time periods where no data is
//! gathered are excluded.
use std::collections::{HashMap, VecDeque};
use std::fs::File;
use std::path::PathBuf;
use std::time::{Duration, Instant};
use std::{
collections::{HashMap, VecDeque},
fs::File,
path::PathBuf,
time::{Duration, Instant},
};
use request::{CompleteRequest, Kind};
use bincode;
use parking_lot::{RwLock, Mutex};
use parking_lot::{Mutex, RwLock};
/// Number of time periods samples should be kept for.
pub const MOVING_SAMPLE_SIZE: usize = 256;
@@ -40,11 +42,11 @@ pub const MOVING_SAMPLE_SIZE: usize = 256;
/// Stores rolling load timer samples.
// TODO: switch to bigint if possible (FP casts aren't available)
pub trait SampleStore: Send + Sync {
/// Load samples.
fn load(&self) -> HashMap<Kind, VecDeque<u64>>;
/// Load samples.
fn load(&self) -> HashMap<Kind, VecDeque<u64>>;
/// Store all samples.
fn store(&self, samples: &HashMap<Kind, VecDeque<u64>>);
/// Store all samples.
fn store(&self, samples: &HashMap<Kind, VecDeque<u64>>);
}
// get a hardcoded, arbitrarily determined (but intended overestimate)
@@ -52,231 +54,252 @@ pub trait SampleStore: Send + Sync {
//
// TODO: seed this with empirical data.
fn hardcoded_serve_time(kind: Kind) -> Duration {
Duration::new(0, match kind {
Kind::Headers => 500_000,
Kind::HeaderProof => 500_000,
Kind::TransactionIndex => 500_000,
Kind::Receipts => 1_000_000,
Kind::Body => 1_000_000,
Kind::Account => 1_500_000,
Kind::Storage => 2_000_000,
Kind::Code => 1_500_000,
Kind::Execution => 250, // per gas.
Kind::Signal => 500_000,
})
Duration::new(
0,
match kind {
Kind::Headers => 500_000,
Kind::HeaderProof => 500_000,
Kind::TransactionIndex => 500_000,
Kind::Receipts => 1_000_000,
Kind::Body => 1_000_000,
Kind::Account => 1_500_000,
Kind::Storage => 2_000_000,
Kind::Code => 1_500_000,
Kind::Execution => 250, // per gas.
Kind::Signal => 500_000,
},
)
}
/// A no-op store.
pub struct NullStore;
impl SampleStore for NullStore {
fn load(&self) -> HashMap<Kind, VecDeque<u64>> { HashMap::new() }
fn store(&self, _samples: &HashMap<Kind, VecDeque<u64>>) { }
fn load(&self) -> HashMap<Kind, VecDeque<u64>> {
HashMap::new()
}
fn store(&self, _samples: &HashMap<Kind, VecDeque<u64>>) {}
}
/// Request load distributions.
pub struct LoadDistribution {
active_period: RwLock<HashMap<Kind, Mutex<(u64, u64)>>>,
samples: RwLock<HashMap<Kind, VecDeque<u64>>>,
active_period: RwLock<HashMap<Kind, Mutex<(u64, u64)>>>,
samples: RwLock<HashMap<Kind, VecDeque<u64>>>,
}
impl LoadDistribution {
/// Load rolling samples from the given store.
pub fn load(store: &SampleStore) -> Self {
let mut samples = store.load();
/// Load rolling samples from the given store.
pub fn load(store: &SampleStore) -> Self {
let mut samples = store.load();
for kind_samples in samples.values_mut() {
while kind_samples.len() > MOVING_SAMPLE_SIZE {
kind_samples.pop_front();
}
}
for kind_samples in samples.values_mut() {
while kind_samples.len() > MOVING_SAMPLE_SIZE {
kind_samples.pop_front();
}
}
LoadDistribution {
active_period: RwLock::new(HashMap::new()),
samples: RwLock::new(samples),
}
}
LoadDistribution {
active_period: RwLock::new(HashMap::new()),
samples: RwLock::new(samples),
}
}
/// Begin a timer.
pub fn begin_timer<'a>(&'a self, req: &CompleteRequest) -> LoadTimer<'a> {
let kind = req.kind();
let n = match *req {
CompleteRequest::Headers(ref req) => req.max,
CompleteRequest::Execution(ref req) => req.gas.low_u64(),
_ => 1,
};
/// Begin a timer.
pub fn begin_timer<'a>(&'a self, req: &CompleteRequest) -> LoadTimer<'a> {
let kind = req.kind();
let n = match *req {
CompleteRequest::Headers(ref req) => req.max,
CompleteRequest::Execution(ref req) => req.gas.low_u64(),
_ => 1,
};
LoadTimer {
start: Instant::now(),
n,
dist: self,
kind,
}
}
LoadTimer {
start: Instant::now(),
n,
dist: self,
kind,
}
}
/// Calculate EMA of load for a specific request kind.
/// If there is no data for the given request kind, no EMA will be calculated,
/// but a hardcoded time will be returned.
pub fn expected_time(&self, kind: Kind) -> Duration {
let samples = self.samples.read();
samples.get(&kind).and_then(|s| {
if s.is_empty() { return None }
/// Calculate EMA of load for a specific request kind.
/// If there is no data for the given request kind, no EMA will be calculated,
/// but a hardcoded time will be returned.
pub fn expected_time(&self, kind: Kind) -> Duration {
let samples = self.samples.read();
samples
.get(&kind)
.and_then(|s| {
if s.is_empty() {
return None;
}
let alpha: f64 = 1_f64 / s.len() as f64;
let start = *s.front().expect("length known to be non-zero; qed") as f64;
let ema = s.iter().skip(1).fold(start, |a, &c| {
(alpha * c as f64) + ((1.0 - alpha) * a)
});
let alpha: f64 = 1_f64 / s.len() as f64;
let start = *s.front().expect("length known to be non-zero; qed") as f64;
let ema = s
.iter()
.skip(1)
.fold(start, |a, &c| (alpha * c as f64) + ((1.0 - alpha) * a));
Some(Duration::from_nanos(ema as u64))
}).unwrap_or_else(move || hardcoded_serve_time(kind))
}
Some(Duration::from_nanos(ema as u64))
})
.unwrap_or_else(move || hardcoded_serve_time(kind))
}
/// End the current time period. Provide a store to
pub fn end_period(&self, store: &SampleStore) {
let active_period = self.active_period.read();
let mut samples = self.samples.write();
/// End the current time period. Provide a store to
pub fn end_period(&self, store: &SampleStore) {
let active_period = self.active_period.read();
let mut samples = self.samples.write();
for (&kind, set) in active_period.iter() {
let (elapsed, n) = ::std::mem::replace(&mut *set.lock(), (0, 0));
if n == 0 { continue }
for (&kind, set) in active_period.iter() {
let (elapsed, n) = ::std::mem::replace(&mut *set.lock(), (0, 0));
if n == 0 {
continue;
}
let kind_samples = samples.entry(kind)
.or_insert_with(|| VecDeque::with_capacity(MOVING_SAMPLE_SIZE));
let kind_samples = samples
.entry(kind)
.or_insert_with(|| VecDeque::with_capacity(MOVING_SAMPLE_SIZE));
if kind_samples.len() == MOVING_SAMPLE_SIZE { kind_samples.pop_front(); }
kind_samples.push_back(elapsed / n);
}
if kind_samples.len() == MOVING_SAMPLE_SIZE {
kind_samples.pop_front();
}
kind_samples.push_back(elapsed / n);
}
store.store(&*samples);
}
store.store(&*samples);
}
fn update(&self, kind: Kind, elapsed: Duration, n: u64) {
macro_rules! update_counters {
($counters: expr) => {
$counters.0 = $counters.0.saturating_add({ elapsed.as_secs() * 1_000_000_000 + elapsed.subsec_nanos() as u64 });
$counters.1 = $counters.1.saturating_add(n);
}
};
fn update(&self, kind: Kind, elapsed: Duration, n: u64) {
macro_rules! update_counters {
($counters: expr) => {
$counters.0 = $counters.0.saturating_add({
elapsed.as_secs() * 1_000_000_000 + elapsed.subsec_nanos() as u64
});
$counters.1 = $counters.1.saturating_add(n);
};
};
{
let set = self.active_period.read();
if let Some(counters) = set.get(&kind) {
let mut counters = counters.lock();
update_counters!(counters);
return;
}
}
{
let set = self.active_period.read();
if let Some(counters) = set.get(&kind) {
let mut counters = counters.lock();
update_counters!(counters);
return;
}
}
let mut set = self.active_period.write();
let counters = set
.entry(kind)
.or_insert_with(|| Mutex::new((0, 0)));
let mut set = self.active_period.write();
let counters = set.entry(kind).or_insert_with(|| Mutex::new((0, 0)));
update_counters!(counters.get_mut());
}
update_counters!(counters.get_mut());
}
}
/// A timer for a single request.
/// On drop, this will update the distribution.
pub struct LoadTimer<'a> {
start: Instant,
n: u64,
dist: &'a LoadDistribution,
kind: Kind,
start: Instant,
n: u64,
dist: &'a LoadDistribution,
kind: Kind,
}
impl<'a> Drop for LoadTimer<'a> {
fn drop(&mut self) {
let elapsed = self.start.elapsed();
self.dist.update(self.kind, elapsed, self.n);
}
fn drop(&mut self) {
let elapsed = self.start.elapsed();
self.dist.update(self.kind, elapsed, self.n);
}
}
/// A store which writes directly to a file.
pub struct FileStore(pub PathBuf);
impl SampleStore for FileStore {
fn load(&self) -> HashMap<Kind, VecDeque<u64>> {
File::open(&self.0)
.map_err(|e| Box::new(bincode::ErrorKind::IoError(e)))
.and_then(|mut file| bincode::deserialize_from(&mut file, bincode::Infinite))
.unwrap_or_else(|_| HashMap::new())
}
fn load(&self) -> HashMap<Kind, VecDeque<u64>> {
File::open(&self.0)
.map_err(|e| Box::new(bincode::ErrorKind::IoError(e)))
.and_then(|mut file| bincode::deserialize_from(&mut file, bincode::Infinite))
.unwrap_or_else(|_| HashMap::new())
}
fn store(&self, samples: &HashMap<Kind, VecDeque<u64>>) {
let res = File::create(&self.0)
.map_err(|e| Box::new(bincode::ErrorKind::IoError(e)))
.and_then(|mut file| bincode::serialize_into(&mut file, samples, bincode::Infinite));
fn store(&self, samples: &HashMap<Kind, VecDeque<u64>>) {
let res = File::create(&self.0)
.map_err(|e| Box::new(bincode::ErrorKind::IoError(e)))
.and_then(|mut file| bincode::serialize_into(&mut file, samples, bincode::Infinite));
if let Err(e) = res {
warn!(target: "pip", "Error writing light request timing samples to file: {}", e);
}
}
if let Err(e) = res {
warn!(target: "pip", "Error writing light request timing samples to file: {}", e);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use request::Kind;
use super::*;
use request::Kind;
#[test]
fn hardcoded_before_data() {
let dist = LoadDistribution::load(&NullStore);
assert_eq!(dist.expected_time(Kind::Headers), hardcoded_serve_time(Kind::Headers));
#[test]
fn hardcoded_before_data() {
let dist = LoadDistribution::load(&NullStore);
assert_eq!(
dist.expected_time(Kind::Headers),
hardcoded_serve_time(Kind::Headers)
);
dist.update(Kind::Headers, Duration::new(0, 100_000), 100);
dist.end_period(&NullStore);
dist.update(Kind::Headers, Duration::new(0, 100_000), 100);
dist.end_period(&NullStore);
assert_eq!(dist.expected_time(Kind::Headers), Duration::new(0, 1000));
}
assert_eq!(dist.expected_time(Kind::Headers), Duration::new(0, 1000));
}
#[test]
fn moving_average() {
let dist = LoadDistribution::load(&NullStore);
#[test]
fn moving_average() {
let dist = LoadDistribution::load(&NullStore);
let mut sum = 0;
let mut sum = 0;
for (i, x) in (0..10).map(|x| x * 10_000).enumerate() {
dist.update(Kind::Headers, Duration::new(0, x), 1);
dist.end_period(&NullStore);
for (i, x) in (0..10).map(|x| x * 10_000).enumerate() {
dist.update(Kind::Headers, Duration::new(0, x), 1);
dist.end_period(&NullStore);
sum += x;
if i == 0 { continue }
sum += x;
if i == 0 {
continue;
}
let moving_average = dist.expected_time(Kind::Headers);
let moving_average = dist.expected_time(Kind::Headers);
// should be weighted below the maximum entry.
let arith_average = (sum as f64 / (i + 1) as f64) as u32;
assert!(moving_average < Duration::new(0, x));
// should be weighted below the maximum entry.
let arith_average = (sum as f64 / (i + 1) as f64) as u32;
assert!(moving_average < Duration::new(0, x));
// when there are only 2 entries, they should be equal due to choice of
// ALPHA = 1/N.
// otherwise, the weight should be below the arithmetic mean because the much
// smaller previous values are discounted less.
if i == 1 {
assert_eq!(moving_average, Duration::new(0, arith_average));
} else {
assert!(moving_average < Duration::new(0, arith_average))
}
}
}
// when there are only 2 entries, they should be equal due to choice of
// ALPHA = 1/N.
// otherwise, the weight should be below the arithmetic mean because the much
// smaller previous values are discounted less.
if i == 1 {
assert_eq!(moving_average, Duration::new(0, arith_average));
} else {
assert!(moving_average < Duration::new(0, arith_average))
}
}
}
#[test]
fn file_store() {
let tempdir = ::tempdir::TempDir::new("").unwrap();
let path = tempdir.path().join("file");
let store = FileStore(path);
#[test]
fn file_store() {
let tempdir = ::tempdir::TempDir::new("").unwrap();
let path = tempdir.path().join("file");
let store = FileStore(path);
let mut samples = store.load();
assert!(samples.is_empty());
samples.insert(Kind::Headers, vec![5, 2, 7, 2, 2, 4].into());
samples.insert(Kind::Execution, vec![1, 1, 100, 250].into());
let mut samples = store.load();
assert!(samples.is_empty());
samples.insert(Kind::Headers, vec![5, 2, 7, 2, 2, 4].into());
samples.insert(Kind::Execution, vec![1, 1, 100, 250].into());
store.store(&samples);
store.store(&samples);
let dup = store.load();
let dup = store.load();
assert_eq!(samples, dup);
}
assert_eq!(samples, dup);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -26,11 +26,11 @@
//! Current default costs are picked completely arbitrarily, not based
//! on any empirical timings or mathematical models.
use request::{self, Request};
use super::error::Error;
use request::{self, Request};
use rlp::{Rlp, RlpStream, Decodable, Encodable, DecoderError};
use ethereum_types::U256;
use rlp::{Decodable, DecoderError, Encodable, Rlp, RlpStream};
use std::time::{Duration, Instant};
/// Credits value.
@@ -40,416 +40,434 @@ use std::time::{Duration, Instant};
/// point to the time of the update.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Credits {
estimate: U256,
recharge_point: Instant,
estimate: U256,
recharge_point: Instant,
}
impl Credits {
/// Get the current amount of credits..
pub fn current(&self) -> U256 { self.estimate }
/// Get the current amount of credits..
pub fn current(&self) -> U256 {
self.estimate
}
/// Make a definitive update.
/// This will be the value obtained after receiving
/// a response to a request.
pub fn update_to(&mut self, value: U256) {
self.estimate = value;
self.recharge_point = Instant::now();
}
/// Make a definitive update.
/// This will be the value obtained after receiving
/// a response to a request.
pub fn update_to(&mut self, value: U256) {
self.estimate = value;
self.recharge_point = Instant::now();
}
/// Maintain ratio to current limit against an old limit.
pub fn maintain_ratio(&mut self, old_limit: U256, new_limit: U256) {
self.estimate = (new_limit * self.estimate) / old_limit;
}
/// Maintain ratio to current limit against an old limit.
pub fn maintain_ratio(&mut self, old_limit: U256, new_limit: U256) {
self.estimate = (new_limit * self.estimate) / old_limit;
}
/// Attempt to apply the given cost to the amount of credits.
///
/// If successful, the cost will be deducted successfully.
///
/// If unsuccessful, the structure will be unaltered an an
/// error will be produced.
pub fn deduct_cost(&mut self, cost: U256) -> Result<(), Error> {
if cost > self.estimate {
Err(Error::NoCredits)
} else {
self.estimate = self.estimate - cost;
Ok(())
}
}
/// Attempt to apply the given cost to the amount of credits.
///
/// If successful, the cost will be deducted successfully.
///
/// If unsuccessful, the structure will be unaltered an an
/// error will be produced.
pub fn deduct_cost(&mut self, cost: U256) -> Result<(), Error> {
if cost > self.estimate {
Err(Error::NoCredits)
} else {
self.estimate = self.estimate - cost;
Ok(())
}
}
}
/// A cost table, mapping requests to base and per-request costs.
/// Costs themselves may be missing.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CostTable {
base: U256, // cost per packet.
headers: Option<U256>, // cost per header
transaction_index: Option<U256>,
body: Option<U256>,
receipts: Option<U256>,
account: Option<U256>,
storage: Option<U256>,
code: Option<U256>,
header_proof: Option<U256>,
transaction_proof: Option<U256>, // cost per gas.
epoch_signal: Option<U256>,
base: U256, // cost per packet.
headers: Option<U256>, // cost per header
transaction_index: Option<U256>,
body: Option<U256>,
receipts: Option<U256>,
account: Option<U256>,
storage: Option<U256>,
code: Option<U256>,
header_proof: Option<U256>,
transaction_proof: Option<U256>, // cost per gas.
epoch_signal: Option<U256>,
}
impl CostTable {
fn costs_set(&self) -> usize {
let mut num_set = 0;
fn costs_set(&self) -> usize {
let mut num_set = 0;
{
let mut incr_if_set = |cost: &Option<_>| if cost.is_some() { num_set += 1 };
incr_if_set(&self.headers);
incr_if_set(&self.transaction_index);
incr_if_set(&self.body);
incr_if_set(&self.receipts);
incr_if_set(&self.account);
incr_if_set(&self.storage);
incr_if_set(&self.code);
incr_if_set(&self.header_proof);
incr_if_set(&self.transaction_proof);
incr_if_set(&self.epoch_signal);
}
{
let mut incr_if_set = |cost: &Option<_>| {
if cost.is_some() {
num_set += 1
}
};
incr_if_set(&self.headers);
incr_if_set(&self.transaction_index);
incr_if_set(&self.body);
incr_if_set(&self.receipts);
incr_if_set(&self.account);
incr_if_set(&self.storage);
incr_if_set(&self.code);
incr_if_set(&self.header_proof);
incr_if_set(&self.transaction_proof);
incr_if_set(&self.epoch_signal);
}
num_set
}
num_set
}
}
impl Default for CostTable {
fn default() -> Self {
// arbitrarily chosen constants.
CostTable {
base: 100_000.into(),
headers: Some(10000.into()),
transaction_index: Some(10000.into()),
body: Some(15000.into()),
receipts: Some(5000.into()),
account: Some(25000.into()),
storage: Some(25000.into()),
code: Some(20000.into()),
header_proof: Some(15000.into()),
transaction_proof: Some(2.into()),
epoch_signal: Some(10000.into()),
}
}
fn default() -> Self {
// arbitrarily chosen constants.
CostTable {
base: 100_000.into(),
headers: Some(10000.into()),
transaction_index: Some(10000.into()),
body: Some(15000.into()),
receipts: Some(5000.into()),
account: Some(25000.into()),
storage: Some(25000.into()),
code: Some(20000.into()),
header_proof: Some(15000.into()),
transaction_proof: Some(2.into()),
epoch_signal: Some(10000.into()),
}
}
}
impl Encodable for CostTable {
fn rlp_append(&self, s: &mut RlpStream) {
fn append_cost(s: &mut RlpStream, cost: &Option<U256>, kind: request::Kind) {
if let Some(ref cost) = *cost {
s.begin_list(2);
// hack around https://github.com/paritytech/parity-ethereum/issues/4356
Encodable::rlp_append(&kind, s);
s.append(cost);
}
}
fn rlp_append(&self, s: &mut RlpStream) {
fn append_cost(s: &mut RlpStream, cost: &Option<U256>, kind: request::Kind) {
if let Some(ref cost) = *cost {
s.begin_list(2);
// hack around https://github.com/paritytech/parity-ethereum/issues/4356
Encodable::rlp_append(&kind, s);
s.append(cost);
}
}
s.begin_list(1 + self.costs_set()).append(&self.base);
append_cost(s, &self.headers, request::Kind::Headers);
append_cost(s, &self.transaction_index, request::Kind::TransactionIndex);
append_cost(s, &self.body, request::Kind::Body);
append_cost(s, &self.receipts, request::Kind::Receipts);
append_cost(s, &self.account, request::Kind::Account);
append_cost(s, &self.storage, request::Kind::Storage);
append_cost(s, &self.code, request::Kind::Code);
append_cost(s, &self.header_proof, request::Kind::HeaderProof);
append_cost(s, &self.transaction_proof, request::Kind::Execution);
append_cost(s, &self.epoch_signal, request::Kind::Signal);
}
s.begin_list(1 + self.costs_set()).append(&self.base);
append_cost(s, &self.headers, request::Kind::Headers);
append_cost(s, &self.transaction_index, request::Kind::TransactionIndex);
append_cost(s, &self.body, request::Kind::Body);
append_cost(s, &self.receipts, request::Kind::Receipts);
append_cost(s, &self.account, request::Kind::Account);
append_cost(s, &self.storage, request::Kind::Storage);
append_cost(s, &self.code, request::Kind::Code);
append_cost(s, &self.header_proof, request::Kind::HeaderProof);
append_cost(s, &self.transaction_proof, request::Kind::Execution);
append_cost(s, &self.epoch_signal, request::Kind::Signal);
}
}
impl Decodable for CostTable {
fn decode(rlp: &Rlp) -> Result<Self, DecoderError> {
let base = rlp.val_at(0)?;
fn decode(rlp: &Rlp) -> Result<Self, DecoderError> {
let base = rlp.val_at(0)?;
let mut headers = None;
let mut transaction_index = None;
let mut body = None;
let mut receipts = None;
let mut account = None;
let mut storage = None;
let mut code = None;
let mut header_proof = None;
let mut transaction_proof = None;
let mut epoch_signal = None;
let mut headers = None;
let mut transaction_index = None;
let mut body = None;
let mut receipts = None;
let mut account = None;
let mut storage = None;
let mut code = None;
let mut header_proof = None;
let mut transaction_proof = None;
let mut epoch_signal = None;
for cost_list in rlp.iter().skip(1) {
let cost = cost_list.val_at(1)?;
match cost_list.val_at(0)? {
request::Kind::Headers => headers = Some(cost),
request::Kind::TransactionIndex => transaction_index = Some(cost),
request::Kind::Body => body = Some(cost),
request::Kind::Receipts => receipts = Some(cost),
request::Kind::Account => account = Some(cost),
request::Kind::Storage => storage = Some(cost),
request::Kind::Code => code = Some(cost),
request::Kind::HeaderProof => header_proof = Some(cost),
request::Kind::Execution => transaction_proof = Some(cost),
request::Kind::Signal => epoch_signal = Some(cost),
}
}
for cost_list in rlp.iter().skip(1) {
let cost = cost_list.val_at(1)?;
match cost_list.val_at(0)? {
request::Kind::Headers => headers = Some(cost),
request::Kind::TransactionIndex => transaction_index = Some(cost),
request::Kind::Body => body = Some(cost),
request::Kind::Receipts => receipts = Some(cost),
request::Kind::Account => account = Some(cost),
request::Kind::Storage => storage = Some(cost),
request::Kind::Code => code = Some(cost),
request::Kind::HeaderProof => header_proof = Some(cost),
request::Kind::Execution => transaction_proof = Some(cost),
request::Kind::Signal => epoch_signal = Some(cost),
}
}
let table = CostTable {
base,
headers,
transaction_index,
body,
receipts,
account,
storage,
code,
header_proof,
transaction_proof,
epoch_signal,
};
let table = CostTable {
base,
headers,
transaction_index,
body,
receipts,
account,
storage,
code,
header_proof,
transaction_proof,
epoch_signal,
};
if table.costs_set() == 0 {
Err(DecoderError::Custom("no cost types set."))
} else {
Ok(table)
}
}
if table.costs_set() == 0 {
Err(DecoderError::Custom("no cost types set."))
} else {
Ok(table)
}
}
}
/// Handles costs, recharge, limits of request credits.
#[derive(Debug, Clone, PartialEq)]
pub struct FlowParams {
costs: CostTable,
limit: U256,
recharge: U256,
costs: CostTable,
limit: U256,
recharge: U256,
}
impl FlowParams {
/// Create new flow parameters from a request cost table,
/// credit limit, and (minimum) rate of recharge.
pub fn new(limit: U256, costs: CostTable, recharge: U256) -> Self {
FlowParams {
costs,
limit,
recharge,
}
}
/// Create new flow parameters from a request cost table,
/// credit limit, and (minimum) rate of recharge.
pub fn new(limit: U256, costs: CostTable, recharge: U256) -> Self {
FlowParams {
costs,
limit,
recharge,
}
}
/// Create new flow parameters from ,
/// proportion of total capacity which should be given to a peer,
/// and stored capacity a peer can accumulate.
pub fn from_request_times<F: Fn(::request::Kind) -> Duration>(
request_time: F,
load_share: f64,
max_stored: Duration
) -> Self {
use request::Kind;
/// Create new flow parameters from ,
/// proportion of total capacity which should be given to a peer,
/// and stored capacity a peer can accumulate.
pub fn from_request_times<F: Fn(::request::Kind) -> Duration>(
request_time: F,
load_share: f64,
max_stored: Duration,
) -> Self {
use request::Kind;
let load_share = load_share.abs();
let load_share = load_share.abs();
let recharge: u64 = 100_000_000;
let max = {
let sec = max_stored.as_secs().saturating_mul(recharge);
let nanos = (max_stored.subsec_nanos() as u64).saturating_mul(recharge) / 1_000_000_000;
sec + nanos
};
let recharge: u64 = 100_000_000;
let max = {
let sec = max_stored.as_secs().saturating_mul(recharge);
let nanos = (max_stored.subsec_nanos() as u64).saturating_mul(recharge) / 1_000_000_000;
sec + nanos
};
let cost_for_kind = |kind| {
// how many requests we can handle per second
let rq_dur = request_time(kind);
let second_duration = {
let as_ns = rq_dur.as_secs() as f64 * 1_000_000_000f64 + rq_dur.subsec_nanos() as f64;
1_000_000_000f64 / as_ns
};
let cost_for_kind = |kind| {
// how many requests we can handle per second
let rq_dur = request_time(kind);
let second_duration = {
let as_ns =
rq_dur.as_secs() as f64 * 1_000_000_000f64 + rq_dur.subsec_nanos() as f64;
1_000_000_000f64 / as_ns
};
// scale by share of the load given to this peer.
let serve_per_second = second_duration * load_share;
let serve_per_second = serve_per_second.max(1.0 / 10_000.0);
// scale by share of the load given to this peer.
let serve_per_second = second_duration * load_share;
let serve_per_second = serve_per_second.max(1.0 / 10_000.0);
// as a percentage of the recharge per second.
Some(U256::from((recharge as f64 / serve_per_second) as u64))
};
// as a percentage of the recharge per second.
Some(U256::from((recharge as f64 / serve_per_second) as u64))
};
let costs = CostTable {
base: 0.into(),
headers: cost_for_kind(Kind::Headers),
transaction_index: cost_for_kind(Kind::TransactionIndex),
body: cost_for_kind(Kind::Body),
receipts: cost_for_kind(Kind::Receipts),
account: cost_for_kind(Kind::Account),
storage: cost_for_kind(Kind::Storage),
code: cost_for_kind(Kind::Code),
header_proof: cost_for_kind(Kind::HeaderProof),
transaction_proof: cost_for_kind(Kind::Execution),
epoch_signal: cost_for_kind(Kind::Signal),
};
let costs = CostTable {
base: 0.into(),
headers: cost_for_kind(Kind::Headers),
transaction_index: cost_for_kind(Kind::TransactionIndex),
body: cost_for_kind(Kind::Body),
receipts: cost_for_kind(Kind::Receipts),
account: cost_for_kind(Kind::Account),
storage: cost_for_kind(Kind::Storage),
code: cost_for_kind(Kind::Code),
header_proof: cost_for_kind(Kind::HeaderProof),
transaction_proof: cost_for_kind(Kind::Execution),
epoch_signal: cost_for_kind(Kind::Signal),
};
FlowParams {
costs,
limit: max.into(),
recharge: recharge.into(),
}
}
FlowParams {
costs,
limit: max.into(),
recharge: recharge.into(),
}
}
/// Create effectively infinite flow params.
pub fn free() -> Self {
let free_cost: Option<U256> = Some(0.into());
FlowParams {
limit: (!0_u64).into(),
recharge: 1.into(),
costs: CostTable {
base: 0.into(),
headers: free_cost,
transaction_index: free_cost,
body: free_cost,
receipts: free_cost,
account: free_cost,
storage: free_cost,
code: free_cost,
header_proof: free_cost,
transaction_proof: free_cost,
epoch_signal: free_cost,
}
}
}
/// Create effectively infinite flow params.
pub fn free() -> Self {
let free_cost: Option<U256> = Some(0.into());
FlowParams {
limit: (!0_u64).into(),
recharge: 1.into(),
costs: CostTable {
base: 0.into(),
headers: free_cost,
transaction_index: free_cost,
body: free_cost,
receipts: free_cost,
account: free_cost,
storage: free_cost,
code: free_cost,
header_proof: free_cost,
transaction_proof: free_cost,
epoch_signal: free_cost,
},
}
}
/// Get a reference to the credit limit.
pub fn limit(&self) -> &U256 { &self.limit }
/// Get a reference to the credit limit.
pub fn limit(&self) -> &U256 {
&self.limit
}
/// Get a reference to the cost table.
pub fn cost_table(&self) -> &CostTable { &self.costs }
/// Get a reference to the cost table.
pub fn cost_table(&self) -> &CostTable {
&self.costs
}
/// Get the base cost of a request.
pub fn base_cost(&self) -> U256 { self.costs.base }
/// Get the base cost of a request.
pub fn base_cost(&self) -> U256 {
self.costs.base
}
/// Get a reference to the recharge rate.
pub fn recharge_rate(&self) -> &U256 { &self.recharge }
/// Get a reference to the recharge rate.
pub fn recharge_rate(&self) -> &U256 {
&self.recharge
}
/// Compute the actual cost of a request, given the kind of request
/// and number of requests made.
pub fn compute_cost(&self, request: &Request) -> Option<U256> {
match *request {
Request::Headers(ref req) => self.costs.headers.map(|c| c * U256::from(req.max)),
Request::HeaderProof(_) => self.costs.header_proof,
Request::TransactionIndex(_) => self.costs.transaction_index,
Request::Body(_) => self.costs.body,
Request::Receipts(_) => self.costs.receipts,
Request::Account(_) => self.costs.account,
Request::Storage(_) => self.costs.storage,
Request::Code(_) => self.costs.code,
Request::Execution(ref req) => self.costs.transaction_proof.map(|c| c * req.gas),
Request::Signal(_) => self.costs.epoch_signal,
}
}
/// Compute the actual cost of a request, given the kind of request
/// and number of requests made.
pub fn compute_cost(&self, request: &Request) -> Option<U256> {
match *request {
Request::Headers(ref req) => self.costs.headers.map(|c| c * U256::from(req.max)),
Request::HeaderProof(_) => self.costs.header_proof,
Request::TransactionIndex(_) => self.costs.transaction_index,
Request::Body(_) => self.costs.body,
Request::Receipts(_) => self.costs.receipts,
Request::Account(_) => self.costs.account,
Request::Storage(_) => self.costs.storage,
Request::Code(_) => self.costs.code,
Request::Execution(ref req) => self.costs.transaction_proof.map(|c| c * req.gas),
Request::Signal(_) => self.costs.epoch_signal,
}
}
/// Compute the cost of a set of requests.
/// This is the base cost plus the cost of each individual request.
pub fn compute_cost_multi(&self, requests: &[Request]) -> Option<U256> {
let mut cost = self.costs.base;
for request in requests {
match self.compute_cost(request) {
Some(c) => cost = cost + c,
None => return None,
}
}
/// Compute the cost of a set of requests.
/// This is the base cost plus the cost of each individual request.
pub fn compute_cost_multi(&self, requests: &[Request]) -> Option<U256> {
let mut cost = self.costs.base;
for request in requests {
match self.compute_cost(request) {
Some(c) => cost = cost + c,
None => return None,
}
}
Some(cost)
}
Some(cost)
}
/// Create initial credits.
pub fn create_credits(&self) -> Credits {
Credits {
estimate: self.limit,
recharge_point: Instant::now(),
}
}
/// Create initial credits.
pub fn create_credits(&self) -> Credits {
Credits {
estimate: self.limit,
recharge_point: Instant::now(),
}
}
/// Recharge the given credits based on time passed since last
/// update.
pub fn recharge(&self, credits: &mut Credits) {
let now = Instant::now();
/// Recharge the given credits based on time passed since last
/// update.
pub fn recharge(&self, credits: &mut Credits) {
let now = Instant::now();
// recompute and update only in terms of full seconds elapsed
// in order to keep the estimate as an underestimate.
let elapsed = (now - credits.recharge_point).as_secs();
credits.recharge_point += Duration::from_secs(elapsed);
// recompute and update only in terms of full seconds elapsed
// in order to keep the estimate as an underestimate.
let elapsed = (now - credits.recharge_point).as_secs();
credits.recharge_point += Duration::from_secs(elapsed);
let elapsed: U256 = elapsed.into();
let elapsed: U256 = elapsed.into();
credits.estimate = ::std::cmp::min(self.limit, credits.estimate + (elapsed * self.recharge));
}
credits.estimate =
::std::cmp::min(self.limit, credits.estimate + (elapsed * self.recharge));
}
/// Refund some credits which were previously deducted.
/// Does not update the recharge timestamp.
pub fn refund(&self, credits: &mut Credits, refund_amount: U256) {
credits.estimate = credits.estimate + refund_amount;
/// Refund some credits which were previously deducted.
/// Does not update the recharge timestamp.
pub fn refund(&self, credits: &mut Credits, refund_amount: U256) {
credits.estimate = credits.estimate + refund_amount;
if credits.estimate > self.limit {
credits.estimate = self.limit
}
}
if credits.estimate > self.limit {
credits.estimate = self.limit
}
}
}
impl Default for FlowParams {
fn default() -> Self {
FlowParams {
limit: 50_000_000.into(),
costs: CostTable::default(),
recharge: 100_000.into(),
}
}
fn default() -> Self {
FlowParams {
limit: 50_000_000.into(),
costs: CostTable::default(),
recharge: 100_000.into(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use super::*;
#[test]
fn should_serialize_cost_table() {
let costs = CostTable::default();
let serialized = ::rlp::encode(&costs);
#[test]
fn should_serialize_cost_table() {
let costs = CostTable::default();
let serialized = ::rlp::encode(&costs);
let new_costs: CostTable = ::rlp::decode(&*serialized).unwrap();
let new_costs: CostTable = ::rlp::decode(&*serialized).unwrap();
assert_eq!(costs, new_costs);
}
assert_eq!(costs, new_costs);
}
#[test]
fn credits_mechanism() {
use std::thread;
use std::time::Duration;
#[test]
fn credits_mechanism() {
use std::{thread, time::Duration};
let flow_params = FlowParams::new(100.into(), Default::default(), 20.into());
let mut credits = flow_params.create_credits();
let flow_params = FlowParams::new(100.into(), Default::default(), 20.into());
let mut credits = flow_params.create_credits();
assert!(credits.deduct_cost(101.into()).is_err());
assert!(credits.deduct_cost(10.into()).is_ok());
assert!(credits.deduct_cost(101.into()).is_err());
assert!(credits.deduct_cost(10.into()).is_ok());
thread::sleep(Duration::from_secs(1));
thread::sleep(Duration::from_secs(1));
flow_params.recharge(&mut credits);
flow_params.recharge(&mut credits);
assert_eq!(credits.estimate, 100.into());
}
assert_eq!(credits.estimate, 100.into());
}
#[test]
fn scale_by_load_share_and_time() {
let flow_params = FlowParams::from_request_times(
|_| Duration::new(0, 10_000),
0.05,
Duration::from_secs(60),
);
#[test]
fn scale_by_load_share_and_time() {
let flow_params = FlowParams::from_request_times(
|_| Duration::new(0, 10_000),
0.05,
Duration::from_secs(60),
);
let flow_params2 = FlowParams::from_request_times(
|_| Duration::new(0, 10_000),
0.1,
Duration::from_secs(60),
);
let flow_params2 = FlowParams::from_request_times(
|_| Duration::new(0, 10_000),
0.1,
Duration::from_secs(60),
);
let flow_params3 = FlowParams::from_request_times(
|_| Duration::new(0, 5_000),
0.05,
Duration::from_secs(60),
);
let flow_params3 = FlowParams::from_request_times(
|_| Duration::new(0, 5_000),
0.05,
Duration::from_secs(60),
);
assert_eq!(flow_params2.costs, flow_params3.costs);
assert_eq!(flow_params.costs.headers.unwrap(), flow_params2.costs.headers.unwrap() * 2u32);
}
assert_eq!(flow_params2.costs, flow_params3.costs);
assert_eq!(
flow_params.costs.headers.unwrap(),
flow_params2.costs.headers.unwrap() * 2u32
);
}
}

View File

@@ -21,14 +21,15 @@
//!
//! Whenever a request becomes the earliest, its timeout period begins at that moment.
use std::collections::{BTreeMap, HashMap};
use std::iter::FromIterator;
use std::time::{Duration, Instant};
use std::{
collections::{BTreeMap, HashMap},
iter::FromIterator,
time::{Duration, Instant},
};
use request::Request;
use request::NetworkRequests as Requests;
use net::{timeout, ReqId};
use ethereum_types::U256;
use net::{timeout, ReqId};
use request::{NetworkRequests as Requests, Request};
// Request set entry: requests + cost.
#[derive(Debug)]
@@ -37,154 +38,174 @@ struct Entry(Requests, U256);
/// Request set.
#[derive(Debug)]
pub struct RequestSet {
counter: u64,
cumulative_cost: U256,
base: Option<Instant>,
ids: HashMap<ReqId, u64>,
reqs: BTreeMap<u64, Entry>,
counter: u64,
cumulative_cost: U256,
base: Option<Instant>,
ids: HashMap<ReqId, u64>,
reqs: BTreeMap<u64, Entry>,
}
impl Default for RequestSet {
fn default() -> Self {
RequestSet {
counter: 0,
cumulative_cost: 0.into(),
base: None,
ids: HashMap::new(),
reqs: BTreeMap::new(),
}
}
fn default() -> Self {
RequestSet {
counter: 0,
cumulative_cost: 0.into(),
base: None,
ids: HashMap::new(),
reqs: BTreeMap::new(),
}
}
}
impl RequestSet {
/// Push requests onto the stack.
pub fn insert(&mut self, req_id: ReqId, req: Requests, cost: U256, now: Instant) {
let counter = self.counter;
self.cumulative_cost = self.cumulative_cost + cost;
/// Push requests onto the stack.
pub fn insert(&mut self, req_id: ReqId, req: Requests, cost: U256, now: Instant) {
let counter = self.counter;
self.cumulative_cost = self.cumulative_cost + cost;
self.ids.insert(req_id, counter);
self.reqs.insert(counter, Entry(req, cost));
self.ids.insert(req_id, counter);
self.reqs.insert(counter, Entry(req, cost));
if self.reqs.keys().next().map_or(true, |x| *x == counter) {
self.base = Some(now);
}
if self.reqs.keys().next().map_or(true, |x| *x == counter) {
self.base = Some(now);
}
self.counter += 1;
}
self.counter += 1;
}
/// Remove a set of requests from the stack.
pub fn remove(&mut self, req_id: ReqId, now: Instant) -> Option<Requests> {
let id = match self.ids.remove(&req_id) {
Some(id) => id,
None => return None,
};
/// Remove a set of requests from the stack.
pub fn remove(&mut self, req_id: ReqId, now: Instant) -> Option<Requests> {
let id = match self.ids.remove(&req_id) {
Some(id) => id,
None => return None,
};
let Entry(req, cost) = self.reqs.remove(&id).expect("entry in `ids` implies entry in `reqs`; qed");
let Entry(req, cost) = self
.reqs
.remove(&id)
.expect("entry in `ids` implies entry in `reqs`; qed");
match self.reqs.keys().next() {
Some(k) if *k > id => self.base = Some(now),
None => self.base = None,
_ => {}
}
match self.reqs.keys().next() {
Some(k) if *k > id => self.base = Some(now),
None => self.base = None,
_ => {}
}
self.cumulative_cost = self.cumulative_cost - cost;
Some(req)
}
self.cumulative_cost = self.cumulative_cost - cost;
Some(req)
}
/// Check for timeout against the given time. Returns true if
/// has timed out, false otherwise.
pub fn check_timeout(&self, now: Instant) -> bool {
let base = match self.base.as_ref().cloned() {
Some(base) => base,
None => return false,
};
/// Check for timeout against the given time. Returns true if
/// has timed out, false otherwise.
pub fn check_timeout(&self, now: Instant) -> bool {
let base = match self.base.as_ref().cloned() {
Some(base) => base,
None => return false,
};
let first_req = self.reqs.values().next()
.expect("base existing implies `reqs` non-empty; qed");
let first_req = self
.reqs
.values()
.next()
.expect("base existing implies `reqs` non-empty; qed");
base + compute_timeout(&first_req.0) <= now
}
base + compute_timeout(&first_req.0) <= now
}
/// Collect all pending request ids.
pub fn collect_ids<F>(&self) -> F where F: FromIterator<ReqId> {
self.ids.keys().cloned().collect()
}
/// Collect all pending request ids.
pub fn collect_ids<F>(&self) -> F
where
F: FromIterator<ReqId>,
{
self.ids.keys().cloned().collect()
}
/// Number of requests in the set.
pub fn len(&self) -> usize {
self.ids.len()
}
/// Number of requests in the set.
pub fn len(&self) -> usize {
self.ids.len()
}
/// Whether the set is empty.
pub fn is_empty(&self) -> bool { self.len() == 0 }
/// Whether the set is empty.
pub fn is_empty(&self) -> bool {
self.len() == 0
}
/// The cumulative cost of all requests in the set.
// this may be useful later for load balancing.
#[allow(dead_code)]
pub fn cumulative_cost(&self) -> U256 { self.cumulative_cost }
/// The cumulative cost of all requests in the set.
// this may be useful later for load balancing.
#[allow(dead_code)]
pub fn cumulative_cost(&self) -> U256 {
self.cumulative_cost
}
}
// helper to calculate timeout for a specific set of requests.
// it's a base amount + some amount per request.
fn compute_timeout(reqs: &Requests) -> Duration {
Duration::from_millis(reqs.requests().iter().fold(timeout::BASE, |tm, req| {
tm + match *req {
Request::Headers(_) => timeout::HEADERS,
Request::HeaderProof(_) => timeout::HEADER_PROOF,
Request::TransactionIndex(_) => timeout::TRANSACTION_INDEX,
Request::Receipts(_) => timeout::RECEIPT,
Request::Body(_) => timeout::BODY,
Request::Account(_) => timeout::PROOF,
Request::Storage(_) => timeout::PROOF,
Request::Code(_) => timeout::CONTRACT_CODE,
Request::Execution(_) => timeout::TRANSACTION_PROOF,
Request::Signal(_) => timeout::EPOCH_SIGNAL,
}
}))
Duration::from_millis(reqs.requests().iter().fold(timeout::BASE, |tm, req| {
tm + match *req {
Request::Headers(_) => timeout::HEADERS,
Request::HeaderProof(_) => timeout::HEADER_PROOF,
Request::TransactionIndex(_) => timeout::TRANSACTION_INDEX,
Request::Receipts(_) => timeout::RECEIPT,
Request::Body(_) => timeout::BODY,
Request::Account(_) => timeout::PROOF,
Request::Storage(_) => timeout::PROOF,
Request::Code(_) => timeout::CONTRACT_CODE,
Request::Execution(_) => timeout::TRANSACTION_PROOF,
Request::Signal(_) => timeout::EPOCH_SIGNAL,
}
}))
}
#[cfg(test)]
mod tests {
use net::ReqId;
use request::Builder;
use std::time::{Instant, Duration};
use super::{RequestSet, compute_timeout};
use super::{compute_timeout, RequestSet};
use net::ReqId;
use request::Builder;
use std::time::{Duration, Instant};
#[test]
fn multi_timeout() {
let test_begin = Instant::now();
let mut req_set = RequestSet::default();
#[test]
fn multi_timeout() {
let test_begin = Instant::now();
let mut req_set = RequestSet::default();
let the_req = Builder::default().build();
let req_time = compute_timeout(&the_req);
req_set.insert(ReqId(0), the_req.clone(), 0.into(), test_begin);
req_set.insert(ReqId(1), the_req, 0.into(), test_begin + Duration::from_secs(1));
let the_req = Builder::default().build();
let req_time = compute_timeout(&the_req);
req_set.insert(ReqId(0), the_req.clone(), 0.into(), test_begin);
req_set.insert(
ReqId(1),
the_req,
0.into(),
test_begin + Duration::from_secs(1),
);
assert_eq!(req_set.base, Some(test_begin));
assert_eq!(req_set.base, Some(test_begin));
let test_end = test_begin + req_time;
assert!(req_set.check_timeout(test_end));
let test_end = test_begin + req_time;
assert!(req_set.check_timeout(test_end));
req_set.remove(ReqId(0), test_begin + Duration::from_secs(1)).unwrap();
assert!(!req_set.check_timeout(test_end));
assert!(req_set.check_timeout(test_end + Duration::from_secs(1)));
}
req_set
.remove(ReqId(0), test_begin + Duration::from_secs(1))
.unwrap();
assert!(!req_set.check_timeout(test_end));
assert!(req_set.check_timeout(test_end + Duration::from_secs(1)));
}
#[test]
fn cumulative_cost() {
let the_req = Builder::default().build();
let test_begin = Instant::now();
let test_end = test_begin + Duration::from_secs(1);
let mut req_set = RequestSet::default();
#[test]
fn cumulative_cost() {
let the_req = Builder::default().build();
let test_begin = Instant::now();
let test_end = test_begin + Duration::from_secs(1);
let mut req_set = RequestSet::default();
for i in 0..5 {
req_set.insert(ReqId(i), the_req.clone(), 1.into(), test_begin);
assert_eq!(req_set.cumulative_cost, (i + 1).into());
}
for i in 0..5 {
req_set.insert(ReqId(i), the_req.clone(), 1.into(), test_begin);
assert_eq!(req_set.cumulative_cost, (i + 1).into());
}
for i in (0..5).rev() {
assert!(req_set.remove(ReqId(i), test_end).is_some());
assert_eq!(req_set.cumulative_cost, i.into());
}
}
for i in (0..5).rev() {
assert!(req_set.remove(ReqId(i), test_end).is_some());
assert_eq!(req_set.cumulative_cost, i.into());
}
}
}

View File

@@ -17,7 +17,7 @@
//! Peer status and capabilities.
use ethereum_types::{H256, U256};
use rlp::{DecoderError, Encodable, Decodable, RlpStream, Rlp};
use rlp::{Decodable, DecoderError, Encodable, Rlp, RlpStream};
use super::request_credits::FlowParams;
@@ -26,550 +26,544 @@ use super::request_credits::FlowParams;
// their string values are defined in the LES spec.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd)]
enum Key {
ProtocolVersion,
NetworkId,
HeadTD,
HeadHash,
HeadNum,
GenesisHash,
ServeHeaders,
ServeChainSince,
ServeStateSince,
TxRelay,
BufferLimit,
BufferCostTable,
BufferRechargeRate,
ProtocolVersion,
NetworkId,
HeadTD,
HeadHash,
HeadNum,
GenesisHash,
ServeHeaders,
ServeChainSince,
ServeStateSince,
TxRelay,
BufferLimit,
BufferCostTable,
BufferRechargeRate,
}
impl Key {
// get the string value of this key.
fn as_str(self) -> &'static str {
match self {
Key::ProtocolVersion => "protocolVersion",
Key::NetworkId => "networkId",
Key::HeadTD => "headTd",
Key::HeadHash => "headHash",
Key::HeadNum => "headNum",
Key::GenesisHash => "genesisHash",
Key::ServeHeaders => "serveHeaders",
Key::ServeChainSince => "serveChainSince",
Key::ServeStateSince => "serveStateSince",
Key::TxRelay => "txRelay",
Key::BufferLimit => "flowControl/BL",
Key::BufferCostTable => "flowControl/MRC",
Key::BufferRechargeRate => "flowControl/MRR",
}
}
// get the string value of this key.
fn as_str(self) -> &'static str {
match self {
Key::ProtocolVersion => "protocolVersion",
Key::NetworkId => "networkId",
Key::HeadTD => "headTd",
Key::HeadHash => "headHash",
Key::HeadNum => "headNum",
Key::GenesisHash => "genesisHash",
Key::ServeHeaders => "serveHeaders",
Key::ServeChainSince => "serveChainSince",
Key::ServeStateSince => "serveStateSince",
Key::TxRelay => "txRelay",
Key::BufferLimit => "flowControl/BL",
Key::BufferCostTable => "flowControl/MRC",
Key::BufferRechargeRate => "flowControl/MRR",
}
}
// try to parse the key value from a string.
fn from_str(s: &str) -> Option<Self> {
match s {
"protocolVersion" => Some(Key::ProtocolVersion),
"networkId" => Some(Key::NetworkId),
"headTd" => Some(Key::HeadTD),
"headHash" => Some(Key::HeadHash),
"headNum" => Some(Key::HeadNum),
"genesisHash" => Some(Key::GenesisHash),
"serveHeaders" => Some(Key::ServeHeaders),
"serveChainSince" => Some(Key::ServeChainSince),
"serveStateSince" => Some(Key::ServeStateSince),
"txRelay" => Some(Key::TxRelay),
"flowControl/BL" => Some(Key::BufferLimit),
"flowControl/MRC" => Some(Key::BufferCostTable),
"flowControl/MRR" => Some(Key::BufferRechargeRate),
_ => None
}
}
// try to parse the key value from a string.
fn from_str(s: &str) -> Option<Self> {
match s {
"protocolVersion" => Some(Key::ProtocolVersion),
"networkId" => Some(Key::NetworkId),
"headTd" => Some(Key::HeadTD),
"headHash" => Some(Key::HeadHash),
"headNum" => Some(Key::HeadNum),
"genesisHash" => Some(Key::GenesisHash),
"serveHeaders" => Some(Key::ServeHeaders),
"serveChainSince" => Some(Key::ServeChainSince),
"serveStateSince" => Some(Key::ServeStateSince),
"txRelay" => Some(Key::TxRelay),
"flowControl/BL" => Some(Key::BufferLimit),
"flowControl/MRC" => Some(Key::BufferCostTable),
"flowControl/MRR" => Some(Key::BufferRechargeRate),
_ => None,
}
}
}
// helper for decoding key-value pairs in the handshake or an announcement.
struct Parser<'a> {
pos: usize,
rlp: &'a Rlp<'a>,
pos: usize,
rlp: &'a Rlp<'a>,
}
impl<'a> Parser<'a> {
// expect a specific next key, and decode the value.
// error on unexpected key or invalid value.
fn expect<T: Decodable>(&mut self, key: Key) -> Result<T, DecoderError> {
self.expect_raw(key).and_then(|item| item.as_val())
}
// expect a specific next key, and decode the value.
// error on unexpected key or invalid value.
fn expect<T: Decodable>(&mut self, key: Key) -> Result<T, DecoderError> {
self.expect_raw(key).and_then(|item| item.as_val())
}
// expect a specific next key, and get the value's RLP.
// if the key isn't found, the position isn't advanced.
fn expect_raw(&mut self, key: Key) -> Result<Rlp<'a>, DecoderError> {
trace!(target: "les", "Expecting key {}", key.as_str());
let pre_pos = self.pos;
if let Some((k, val)) = self.get_next()? {
if k == key { return Ok(val) }
}
// expect a specific next key, and get the value's RLP.
// if the key isn't found, the position isn't advanced.
fn expect_raw(&mut self, key: Key) -> Result<Rlp<'a>, DecoderError> {
trace!(target: "les", "Expecting key {}", key.as_str());
let pre_pos = self.pos;
if let Some((k, val)) = self.get_next()? {
if k == key {
return Ok(val);
}
}
self.pos = pre_pos;
Err(DecoderError::Custom("Missing expected key"))
}
self.pos = pre_pos;
Err(DecoderError::Custom("Missing expected key"))
}
// get the next key and value RLP.
fn get_next(&mut self) -> Result<Option<(Key, Rlp<'a>)>, DecoderError> {
while self.pos < self.rlp.item_count()? {
let pair = self.rlp.at(self.pos)?;
let k: String = pair.val_at(0)?;
// get the next key and value RLP.
fn get_next(&mut self) -> Result<Option<(Key, Rlp<'a>)>, DecoderError> {
while self.pos < self.rlp.item_count()? {
let pair = self.rlp.at(self.pos)?;
let k: String = pair.val_at(0)?;
self.pos += 1;
match Key::from_str(&k) {
Some(key) => return Ok(Some((key , pair.at(1)?))),
None => continue,
}
}
self.pos += 1;
match Key::from_str(&k) {
Some(key) => return Ok(Some((key, pair.at(1)?))),
None => continue,
}
}
Ok(None)
}
Ok(None)
}
}
// Helper for encoding a key-value pair
fn encode_pair<T: Encodable>(key: Key, val: &T) -> Vec<u8> {
let mut s = RlpStream::new_list(2);
s.append(&key.as_str()).append(val);
s.out()
let mut s = RlpStream::new_list(2);
s.append(&key.as_str()).append(val);
s.out()
}
// Helper for encoding a flag.
fn encode_flag(key: Key) -> Vec<u8> {
let mut s = RlpStream::new_list(2);
s.append(&key.as_str()).append_empty_data();
s.out()
let mut s = RlpStream::new_list(2);
s.append(&key.as_str()).append_empty_data();
s.out()
}
/// A peer status message.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Status {
/// Protocol version.
pub protocol_version: u32,
/// Network id of this peer.
pub network_id: u64,
/// Total difficulty of the head of the chain.
pub head_td: U256,
/// Hash of the best block.
pub head_hash: H256,
/// Number of the best block.
pub head_num: u64,
/// Genesis hash
pub genesis_hash: H256,
/// Last announced chain head and reorg depth to common ancestor.
pub last_head: Option<(H256, u64)>,
/// Protocol version.
pub protocol_version: u32,
/// Network id of this peer.
pub network_id: u64,
/// Total difficulty of the head of the chain.
pub head_td: U256,
/// Hash of the best block.
pub head_hash: H256,
/// Number of the best block.
pub head_num: u64,
/// Genesis hash
pub genesis_hash: H256,
/// Last announced chain head and reorg depth to common ancestor.
pub last_head: Option<(H256, u64)>,
}
impl Status {
/// Update the status from an announcement.
pub fn update_from(&mut self, announcement: &Announcement) {
self.last_head = Some((self.head_hash, announcement.reorg_depth));
self.head_td = announcement.head_td;
self.head_hash = announcement.head_hash;
self.head_num = announcement.head_num;
}
/// Update the status from an announcement.
pub fn update_from(&mut self, announcement: &Announcement) {
self.last_head = Some((self.head_hash, announcement.reorg_depth));
self.head_td = announcement.head_td;
self.head_hash = announcement.head_hash;
self.head_num = announcement.head_num;
}
}
/// Peer capabilities.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Capabilities {
/// Whether this peer can serve headers
pub serve_headers: bool,
/// Earliest block number it can serve block/receipt requests for.
/// `None` means no requests will be servable.
pub serve_chain_since: Option<u64>,
/// Earliest block number it can serve state requests for.
/// `None` means no requests will be servable.
pub serve_state_since: Option<u64>,
/// Whether it can relay transactions to the eth network.
pub tx_relay: bool,
/// Whether this peer can serve headers
pub serve_headers: bool,
/// Earliest block number it can serve block/receipt requests for.
/// `None` means no requests will be servable.
pub serve_chain_since: Option<u64>,
/// Earliest block number it can serve state requests for.
/// `None` means no requests will be servable.
pub serve_state_since: Option<u64>,
/// Whether it can relay transactions to the eth network.
pub tx_relay: bool,
}
impl Default for Capabilities {
fn default() -> Self {
Capabilities {
serve_headers: true,
serve_chain_since: None,
serve_state_since: None,
tx_relay: false,
}
}
fn default() -> Self {
Capabilities {
serve_headers: true,
serve_chain_since: None,
serve_state_since: None,
tx_relay: false,
}
}
}
impl Capabilities {
/// Update the capabilities from an announcement.
pub fn update_from(&mut self, announcement: &Announcement) {
self.serve_headers = self.serve_headers || announcement.serve_headers;
self.serve_state_since = self.serve_state_since.or(announcement.serve_state_since);
self.serve_chain_since = self.serve_chain_since.or(announcement.serve_chain_since);
self.tx_relay = self.tx_relay || announcement.tx_relay;
}
/// Update the capabilities from an announcement.
pub fn update_from(&mut self, announcement: &Announcement) {
self.serve_headers = self.serve_headers || announcement.serve_headers;
self.serve_state_since = self.serve_state_since.or(announcement.serve_state_since);
self.serve_chain_since = self.serve_chain_since.or(announcement.serve_chain_since);
self.tx_relay = self.tx_relay || announcement.tx_relay;
}
}
/// Attempt to parse a handshake message into its three parts:
/// - chain status
/// - serving capabilities
/// - request credit parameters
pub fn parse_handshake(rlp: &Rlp) -> Result<(Status, Capabilities, Option<FlowParams>), DecoderError> {
let mut parser = Parser {
pos: 0,
rlp,
};
pub fn parse_handshake(
rlp: &Rlp,
) -> Result<(Status, Capabilities, Option<FlowParams>), DecoderError> {
let mut parser = Parser { pos: 0, rlp };
let status = Status {
protocol_version: parser.expect(Key::ProtocolVersion)?,
network_id: parser.expect(Key::NetworkId)?,
head_td: parser.expect(Key::HeadTD)?,
head_hash: parser.expect(Key::HeadHash)?,
head_num: parser.expect(Key::HeadNum)?,
genesis_hash: parser.expect(Key::GenesisHash)?,
last_head: None,
};
let status = Status {
protocol_version: parser.expect(Key::ProtocolVersion)?,
network_id: parser.expect(Key::NetworkId)?,
head_td: parser.expect(Key::HeadTD)?,
head_hash: parser.expect(Key::HeadHash)?,
head_num: parser.expect(Key::HeadNum)?,
genesis_hash: parser.expect(Key::GenesisHash)?,
last_head: None,
};
let capabilities = Capabilities {
serve_headers: parser.expect_raw(Key::ServeHeaders).is_ok(),
serve_chain_since: parser.expect(Key::ServeChainSince).ok(),
serve_state_since: parser.expect(Key::ServeStateSince).ok(),
tx_relay: parser.expect_raw(Key::TxRelay).is_ok(),
};
let capabilities = Capabilities {
serve_headers: parser.expect_raw(Key::ServeHeaders).is_ok(),
serve_chain_since: parser.expect(Key::ServeChainSince).ok(),
serve_state_since: parser.expect(Key::ServeStateSince).ok(),
tx_relay: parser.expect_raw(Key::TxRelay).is_ok(),
};
let flow_params = match (
parser.expect(Key::BufferLimit),
parser.expect(Key::BufferCostTable),
parser.expect(Key::BufferRechargeRate)
) {
(Ok(bl), Ok(bct), Ok(brr)) => Some(FlowParams::new(bl, bct, brr)),
_ => None,
};
let flow_params = match (
parser.expect(Key::BufferLimit),
parser.expect(Key::BufferCostTable),
parser.expect(Key::BufferRechargeRate),
) {
(Ok(bl), Ok(bct), Ok(brr)) => Some(FlowParams::new(bl, bct, brr)),
_ => None,
};
Ok((status, capabilities, flow_params))
Ok((status, capabilities, flow_params))
}
/// Write a handshake, given status, capabilities, and flow parameters.
pub fn write_handshake(status: &Status, capabilities: &Capabilities, flow_params: Option<&FlowParams>) -> Vec<u8> {
let mut pairs = Vec::new();
pairs.push(encode_pair(Key::ProtocolVersion, &status.protocol_version));
pairs.push(encode_pair(Key::NetworkId, &(status.network_id as u64)));
pairs.push(encode_pair(Key::HeadTD, &status.head_td));
pairs.push(encode_pair(Key::HeadHash, &status.head_hash));
pairs.push(encode_pair(Key::HeadNum, &status.head_num));
pairs.push(encode_pair(Key::GenesisHash, &status.genesis_hash));
pub fn write_handshake(
status: &Status,
capabilities: &Capabilities,
flow_params: Option<&FlowParams>,
) -> Vec<u8> {
let mut pairs = Vec::new();
pairs.push(encode_pair(Key::ProtocolVersion, &status.protocol_version));
pairs.push(encode_pair(Key::NetworkId, &(status.network_id as u64)));
pairs.push(encode_pair(Key::HeadTD, &status.head_td));
pairs.push(encode_pair(Key::HeadHash, &status.head_hash));
pairs.push(encode_pair(Key::HeadNum, &status.head_num));
pairs.push(encode_pair(Key::GenesisHash, &status.genesis_hash));
if capabilities.serve_headers {
pairs.push(encode_flag(Key::ServeHeaders));
}
if let Some(ref serve_chain_since) = capabilities.serve_chain_since {
pairs.push(encode_pair(Key::ServeChainSince, serve_chain_since));
}
if let Some(ref serve_state_since) = capabilities.serve_state_since {
pairs.push(encode_pair(Key::ServeStateSince, serve_state_since));
}
if capabilities.tx_relay {
pairs.push(encode_flag(Key::TxRelay));
}
if capabilities.serve_headers {
pairs.push(encode_flag(Key::ServeHeaders));
}
if let Some(ref serve_chain_since) = capabilities.serve_chain_since {
pairs.push(encode_pair(Key::ServeChainSince, serve_chain_since));
}
if let Some(ref serve_state_since) = capabilities.serve_state_since {
pairs.push(encode_pair(Key::ServeStateSince, serve_state_since));
}
if capabilities.tx_relay {
pairs.push(encode_flag(Key::TxRelay));
}
if let Some(flow_params) = flow_params {
pairs.push(encode_pair(Key::BufferLimit, flow_params.limit()));
pairs.push(encode_pair(Key::BufferCostTable, flow_params.cost_table()));
pairs.push(encode_pair(Key::BufferRechargeRate, flow_params.recharge_rate()));
}
if let Some(flow_params) = flow_params {
pairs.push(encode_pair(Key::BufferLimit, flow_params.limit()));
pairs.push(encode_pair(Key::BufferCostTable, flow_params.cost_table()));
pairs.push(encode_pair(
Key::BufferRechargeRate,
flow_params.recharge_rate(),
));
}
let mut stream = RlpStream::new_list(pairs.len());
let mut stream = RlpStream::new_list(pairs.len());
for pair in pairs {
stream.append_raw(&pair, 1);
}
for pair in pairs {
stream.append_raw(&pair, 1);
}
stream.out()
stream.out()
}
/// An announcement of new chain head or capabilities made by a peer.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Announcement {
/// Hash of the best block.
pub head_hash: H256,
/// Number of the best block.
pub head_num: u64,
/// Head total difficulty
pub head_td: U256,
/// reorg depth to common ancestor of last announced head.
pub reorg_depth: u64,
/// optional new header-serving capability. false means "no change"
pub serve_headers: bool,
/// optional new state-serving capability
pub serve_state_since: Option<u64>,
/// optional new chain-serving capability
pub serve_chain_since: Option<u64>,
/// optional new transaction-relay capability. false means "no change"
pub tx_relay: bool,
// TODO: changes in request credits.
/// Hash of the best block.
pub head_hash: H256,
/// Number of the best block.
pub head_num: u64,
/// Head total difficulty
pub head_td: U256,
/// reorg depth to common ancestor of last announced head.
pub reorg_depth: u64,
/// optional new header-serving capability. false means "no change"
pub serve_headers: bool,
/// optional new state-serving capability
pub serve_state_since: Option<u64>,
/// optional new chain-serving capability
pub serve_chain_since: Option<u64>,
/// optional new transaction-relay capability. false means "no change"
pub tx_relay: bool,
// TODO: changes in request credits.
}
/// Parse an announcement.
pub fn parse_announcement(rlp: &Rlp) -> Result<Announcement, DecoderError> {
let mut last_key = None;
let mut last_key = None;
let mut announcement = Announcement {
head_hash: rlp.val_at(0)?,
head_num: rlp.val_at(1)?,
head_td: rlp.val_at(2)?,
reorg_depth: rlp.val_at(3)?,
serve_headers: false,
serve_state_since: None,
serve_chain_since: None,
tx_relay: false,
};
let mut announcement = Announcement {
head_hash: rlp.val_at(0)?,
head_num: rlp.val_at(1)?,
head_td: rlp.val_at(2)?,
reorg_depth: rlp.val_at(3)?,
serve_headers: false,
serve_state_since: None,
serve_chain_since: None,
tx_relay: false,
};
let mut parser = Parser {
pos: 4,
rlp,
};
let mut parser = Parser { pos: 4, rlp };
while let Some((key, item)) = parser.get_next()? {
if Some(key) <= last_key { return Err(DecoderError::Custom("Invalid announcement key ordering")) }
last_key = Some(key);
while let Some((key, item)) = parser.get_next()? {
if Some(key) <= last_key {
return Err(DecoderError::Custom("Invalid announcement key ordering"));
}
last_key = Some(key);
match key {
Key::ServeHeaders => announcement.serve_headers = true,
Key::ServeStateSince => announcement.serve_state_since = Some(item.as_val()?),
Key::ServeChainSince => announcement.serve_chain_since = Some(item.as_val()?),
Key::TxRelay => announcement.tx_relay = true,
_ => return Err(DecoderError::Custom("Nonsensical key in announcement")),
}
}
match key {
Key::ServeHeaders => announcement.serve_headers = true,
Key::ServeStateSince => announcement.serve_state_since = Some(item.as_val()?),
Key::ServeChainSince => announcement.serve_chain_since = Some(item.as_val()?),
Key::TxRelay => announcement.tx_relay = true,
_ => return Err(DecoderError::Custom("Nonsensical key in announcement")),
}
}
Ok(announcement)
Ok(announcement)
}
/// Write an announcement out.
pub fn write_announcement(announcement: &Announcement) -> Vec<u8> {
let mut pairs = Vec::new();
if announcement.serve_headers {
pairs.push(encode_flag(Key::ServeHeaders));
}
if let Some(ref serve_chain_since) = announcement.serve_chain_since {
pairs.push(encode_pair(Key::ServeChainSince, serve_chain_since));
}
if let Some(ref serve_state_since) = announcement.serve_state_since {
pairs.push(encode_pair(Key::ServeStateSince, serve_state_since));
}
if announcement.tx_relay {
pairs.push(encode_flag(Key::TxRelay));
}
let mut pairs = Vec::new();
if announcement.serve_headers {
pairs.push(encode_flag(Key::ServeHeaders));
}
if let Some(ref serve_chain_since) = announcement.serve_chain_since {
pairs.push(encode_pair(Key::ServeChainSince, serve_chain_since));
}
if let Some(ref serve_state_since) = announcement.serve_state_since {
pairs.push(encode_pair(Key::ServeStateSince, serve_state_since));
}
if announcement.tx_relay {
pairs.push(encode_flag(Key::TxRelay));
}
let mut stream = RlpStream::new_list(4 + pairs.len());
stream
.append(&announcement.head_hash)
.append(&announcement.head_num)
.append(&announcement.head_td)
.append(&announcement.reorg_depth);
let mut stream = RlpStream::new_list(4 + pairs.len());
stream
.append(&announcement.head_hash)
.append(&announcement.head_num)
.append(&announcement.head_td)
.append(&announcement.reorg_depth);
for item in pairs {
stream.append_raw(&item, 1);
}
for item in pairs {
stream.append_raw(&item, 1);
}
stream.out()
stream.out()
}
#[cfg(test)]
mod tests {
use super::*;
use super::super::request_credits::FlowParams;
use ethereum_types::{U256, H256};
use rlp::{RlpStream, Rlp};
use super::{super::request_credits::FlowParams, *};
use ethereum_types::{H256, U256};
use rlp::{Rlp, RlpStream};
#[test]
fn full_handshake() {
let status = Status {
protocol_version: 1,
network_id: 1,
head_td: U256::default(),
head_hash: H256::default(),
head_num: 10,
genesis_hash: H256::zero(),
last_head: None,
};
#[test]
fn full_handshake() {
let status = Status {
protocol_version: 1,
network_id: 1,
head_td: U256::default(),
head_hash: H256::default(),
head_num: 10,
genesis_hash: H256::zero(),
last_head: None,
};
let capabilities = Capabilities {
serve_headers: true,
serve_chain_since: Some(5),
serve_state_since: Some(8),
tx_relay: true,
};
let capabilities = Capabilities {
serve_headers: true,
serve_chain_since: Some(5),
serve_state_since: Some(8),
tx_relay: true,
};
let flow_params = FlowParams::new(
1_000_000.into(),
Default::default(),
1000.into(),
);
let flow_params = FlowParams::new(1_000_000.into(), Default::default(), 1000.into());
let handshake = write_handshake(&status, &capabilities, Some(&flow_params));
let handshake = write_handshake(&status, &capabilities, Some(&flow_params));
let (read_status, read_capabilities, read_flow)
= parse_handshake(&Rlp::new(&handshake)).unwrap();
let (read_status, read_capabilities, read_flow) =
parse_handshake(&Rlp::new(&handshake)).unwrap();
assert_eq!(read_status, status);
assert_eq!(read_capabilities, capabilities);
assert_eq!(read_flow.unwrap(), flow_params);
}
assert_eq!(read_status, status);
assert_eq!(read_capabilities, capabilities);
assert_eq!(read_flow.unwrap(), flow_params);
}
#[test]
fn partial_handshake() {
let status = Status {
protocol_version: 1,
network_id: 1,
head_td: U256::default(),
head_hash: H256::default(),
head_num: 10,
genesis_hash: H256::zero(),
last_head: None,
};
#[test]
fn partial_handshake() {
let status = Status {
protocol_version: 1,
network_id: 1,
head_td: U256::default(),
head_hash: H256::default(),
head_num: 10,
genesis_hash: H256::zero(),
last_head: None,
};
let capabilities = Capabilities {
serve_headers: false,
serve_chain_since: Some(5),
serve_state_since: None,
tx_relay: true,
};
let capabilities = Capabilities {
serve_headers: false,
serve_chain_since: Some(5),
serve_state_since: None,
tx_relay: true,
};
let flow_params = FlowParams::new(
1_000_000.into(),
Default::default(),
1000.into(),
);
let flow_params = FlowParams::new(1_000_000.into(), Default::default(), 1000.into());
let handshake = write_handshake(&status, &capabilities, Some(&flow_params));
let handshake = write_handshake(&status, &capabilities, Some(&flow_params));
let (read_status, read_capabilities, read_flow)
= parse_handshake(&Rlp::new(&handshake)).unwrap();
let (read_status, read_capabilities, read_flow) =
parse_handshake(&Rlp::new(&handshake)).unwrap();
assert_eq!(read_status, status);
assert_eq!(read_capabilities, capabilities);
assert_eq!(read_flow.unwrap(), flow_params);
}
assert_eq!(read_status, status);
assert_eq!(read_capabilities, capabilities);
assert_eq!(read_flow.unwrap(), flow_params);
}
#[test]
fn skip_unknown_keys() {
let status = Status {
protocol_version: 1,
network_id: 1,
head_td: U256::default(),
head_hash: H256::default(),
head_num: 10,
genesis_hash: H256::zero(),
last_head: None,
};
#[test]
fn skip_unknown_keys() {
let status = Status {
protocol_version: 1,
network_id: 1,
head_td: U256::default(),
head_hash: H256::default(),
head_num: 10,
genesis_hash: H256::zero(),
last_head: None,
};
let capabilities = Capabilities {
serve_headers: false,
serve_chain_since: Some(5),
serve_state_since: None,
tx_relay: true,
};
let capabilities = Capabilities {
serve_headers: false,
serve_chain_since: Some(5),
serve_state_since: None,
tx_relay: true,
};
let flow_params = FlowParams::new(
1_000_000.into(),
Default::default(),
1000.into(),
);
let flow_params = FlowParams::new(1_000_000.into(), Default::default(), 1000.into());
let handshake = write_handshake(&status, &capabilities, Some(&flow_params));
let interleaved = {
let handshake = Rlp::new(&handshake);
let mut stream = RlpStream::new_list(handshake.item_count().unwrap_or(0) * 3);
let handshake = write_handshake(&status, &capabilities, Some(&flow_params));
let interleaved = {
let handshake = Rlp::new(&handshake);
let mut stream = RlpStream::new_list(handshake.item_count().unwrap_or(0) * 3);
for item in handshake.iter() {
stream.append_raw(item.as_raw(), 1);
let (mut s1, mut s2) = (RlpStream::new_list(2), RlpStream::new_list(2));
s1.append(&"foo").append_empty_data();
s2.append(&"bar").append_empty_data();
stream.append_raw(&s1.out(), 1);
stream.append_raw(&s2.out(), 1);
}
for item in handshake.iter() {
stream.append_raw(item.as_raw(), 1);
let (mut s1, mut s2) = (RlpStream::new_list(2), RlpStream::new_list(2));
s1.append(&"foo").append_empty_data();
s2.append(&"bar").append_empty_data();
stream.append_raw(&s1.out(), 1);
stream.append_raw(&s2.out(), 1);
}
stream.out()
};
stream.out()
};
let (read_status, read_capabilities, read_flow)
= parse_handshake(&Rlp::new(&interleaved)).unwrap();
let (read_status, read_capabilities, read_flow) =
parse_handshake(&Rlp::new(&interleaved)).unwrap();
assert_eq!(read_status, status);
assert_eq!(read_capabilities, capabilities);
assert_eq!(read_flow.unwrap(), flow_params);
}
assert_eq!(read_status, status);
assert_eq!(read_capabilities, capabilities);
assert_eq!(read_flow.unwrap(), flow_params);
}
#[test]
fn announcement_roundtrip() {
let announcement = Announcement {
head_hash: H256::random(),
head_num: 100_000,
head_td: 1_000_000.into(),
reorg_depth: 4,
serve_headers: false,
serve_state_since: Some(99_000),
serve_chain_since: Some(1),
tx_relay: true,
};
#[test]
fn announcement_roundtrip() {
let announcement = Announcement {
head_hash: H256::random(),
head_num: 100_000,
head_td: 1_000_000.into(),
reorg_depth: 4,
serve_headers: false,
serve_state_since: Some(99_000),
serve_chain_since: Some(1),
tx_relay: true,
};
let serialized = write_announcement(&announcement);
let read = parse_announcement(&Rlp::new(&serialized)).unwrap();
let serialized = write_announcement(&announcement);
let read = parse_announcement(&Rlp::new(&serialized)).unwrap();
assert_eq!(read, announcement);
}
assert_eq!(read, announcement);
}
#[test]
fn keys_out_of_order() {
use super::{Key, encode_pair, encode_flag};
#[test]
fn keys_out_of_order() {
use super::{encode_flag, encode_pair, Key};
let mut stream = RlpStream::new_list(6);
stream
.append(&H256::zero())
.append(&10_u64)
.append(&100_000_u64)
.append(&2_u64)
.append_raw(&encode_pair(Key::ServeStateSince, &44_u64), 1)
.append_raw(&encode_flag(Key::ServeHeaders), 1);
let mut stream = RlpStream::new_list(6);
stream
.append(&H256::zero())
.append(&10_u64)
.append(&100_000_u64)
.append(&2_u64)
.append_raw(&encode_pair(Key::ServeStateSince, &44_u64), 1)
.append_raw(&encode_flag(Key::ServeHeaders), 1);
let out = stream.drain();
assert!(parse_announcement(&Rlp::new(&out)).is_err());
let out = stream.drain();
assert!(parse_announcement(&Rlp::new(&out)).is_err());
let mut stream = RlpStream::new_list(6);
stream
.append(&H256::zero())
.append(&10_u64)
.append(&100_000_u64)
.append(&2_u64)
.append_raw(&encode_flag(Key::ServeHeaders), 1)
.append_raw(&encode_pair(Key::ServeStateSince, &44_u64), 1);
let mut stream = RlpStream::new_list(6);
stream
.append(&H256::zero())
.append(&10_u64)
.append(&100_000_u64)
.append(&2_u64)
.append_raw(&encode_flag(Key::ServeHeaders), 1)
.append_raw(&encode_pair(Key::ServeStateSince, &44_u64), 1);
let out = stream.drain();
assert!(parse_announcement(&Rlp::new(&out)).is_ok());
}
let out = stream.drain();
assert!(parse_announcement(&Rlp::new(&out)).is_ok());
}
#[test]
fn optional_flow() {
let status = Status {
protocol_version: 1,
network_id: 1,
head_td: U256::default(),
head_hash: H256::default(),
head_num: 10,
genesis_hash: H256::zero(),
last_head: None,
};
#[test]
fn optional_flow() {
let status = Status {
protocol_version: 1,
network_id: 1,
head_td: U256::default(),
head_hash: H256::default(),
head_num: 10,
genesis_hash: H256::zero(),
last_head: None,
};
let capabilities = Capabilities {
serve_headers: true,
serve_chain_since: Some(5),
serve_state_since: Some(8),
tx_relay: true,
};
let capabilities = Capabilities {
serve_headers: true,
serve_chain_since: Some(5),
serve_state_since: Some(8),
tx_relay: true,
};
let handshake = write_handshake(&status, &capabilities, None);
let handshake = write_handshake(&status, &capabilities, None);
let (read_status, read_capabilities, read_flow)
= parse_handshake(&Rlp::new(&handshake)).unwrap();
let (read_status, read_capabilities, read_flow) =
parse_handshake(&Rlp::new(&handshake)).unwrap();
assert_eq!(read_status, status);
assert_eq!(read_capabilities, capabilities);
assert!(read_flow.is_none());
}
assert_eq!(read_status, status);
assert_eq!(read_capabilities, capabilities);
assert!(read_flow.is_none());
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -22,102 +22,112 @@ type RequestPolicy = failsafe::failure_policy::ConsecutiveFailures<failsafe::bac
/// Error wrapped on-top of `FailsafeError`
#[derive(Debug, PartialEq)]
pub enum Error {
/// The call is let through
LetThrough,
/// The call rejected by the guard
Rejected,
/// The request reached the maximum of backoff iterations
ReachedLimit,
/// The call is let through
LetThrough,
/// The call rejected by the guard
Rejected,
/// The request reached the maximum of backoff iterations
ReachedLimit,
}
/// Handle and register requests that can fail
#[derive(Debug)]
pub struct RequestGuard {
backoff_round: usize,
max_backoff_rounds: usize,
state: failsafe::StateMachine<RequestPolicy, ()>,
backoff_round: usize,
max_backoff_rounds: usize,
state: failsafe::StateMachine<RequestPolicy, ()>,
}
impl RequestGuard {
/// Constructor
pub fn new(
consecutive_failures: u32,
max_backoff_rounds: usize,
start_backoff: Duration,
max_backoff: Duration,
) -> Self {
let backoff = failsafe::backoff::exponential(start_backoff, max_backoff);
// success_rate not used because only errors are registered
let policy = failsafe::failure_policy::consecutive_failures(consecutive_failures as u32, backoff);
/// Constructor
pub fn new(
consecutive_failures: u32,
max_backoff_rounds: usize,
start_backoff: Duration,
max_backoff: Duration,
) -> Self {
let backoff = failsafe::backoff::exponential(start_backoff, max_backoff);
// success_rate not used because only errors are registered
let policy =
failsafe::failure_policy::consecutive_failures(consecutive_failures as u32, backoff);
Self {
backoff_round: 0,
max_backoff_rounds,
state: failsafe::StateMachine::new(policy, ()),
}
}
Self {
backoff_round: 0,
max_backoff_rounds,
state: failsafe::StateMachine::new(policy, ()),
}
}
/// Update the state after a `faulty` call
pub fn register_error(&mut self) -> Error {
trace!(target: "circuit_breaker", "RequestGuard; backoff_round: {}/{}, state {:?}",
/// Update the state after a `faulty` call
pub fn register_error(&mut self) -> Error {
trace!(target: "circuit_breaker", "RequestGuard; backoff_round: {}/{}, state {:?}",
self.backoff_round, self.max_backoff_rounds, self.state);
if self.backoff_round >= self.max_backoff_rounds {
Error::ReachedLimit
} else if self.state.is_call_permitted() {
self.state.on_error();
if self.state.is_call_permitted() {
Error::LetThrough
} else {
self.backoff_round += 1;
Error::Rejected
}
} else {
Error::Rejected
}
}
if self.backoff_round >= self.max_backoff_rounds {
Error::ReachedLimit
} else if self.state.is_call_permitted() {
self.state.on_error();
if self.state.is_call_permitted() {
Error::LetThrough
} else {
self.backoff_round += 1;
Error::Rejected
}
} else {
Error::Rejected
}
}
/// Poll the circuit breaker, to check if the call is permitted
pub fn is_call_permitted(&self) -> bool {
self.state.is_call_permitted()
}
/// Poll the circuit breaker, to check if the call is permitted
pub fn is_call_permitted(&self) -> bool {
self.state.is_call_permitted()
}
}
#[cfg(test)]
mod tests {
use std::iter;
use std::time::Instant;
use super::*;
use super::*;
use std::{iter, time::Instant};
#[test]
fn one_consecutive_failure_with_10_backoffs() {
// 1, 2, 4, 5, 5 .... 5
let binary_exp_backoff = vec![1_u64, 2, 4].into_iter().chain(iter::repeat(5_u64).take(7));
let mut guard = RequestGuard::new(1, 10, Duration::from_secs(1), Duration::from_secs(5));
for backoff in binary_exp_backoff {
assert_eq!(guard.register_error(), Error::Rejected);
let now = Instant::now();
while now.elapsed() <= Duration::from_secs(backoff) {}
}
assert_eq!(guard.register_error(), Error::ReachedLimit, "10 backoffs should be error");
}
#[test]
fn one_consecutive_failure_with_10_backoffs() {
// 1, 2, 4, 5, 5 .... 5
let binary_exp_backoff = vec![1_u64, 2, 4]
.into_iter()
.chain(iter::repeat(5_u64).take(7));
let mut guard = RequestGuard::new(1, 10, Duration::from_secs(1), Duration::from_secs(5));
for backoff in binary_exp_backoff {
assert_eq!(guard.register_error(), Error::Rejected);
let now = Instant::now();
while now.elapsed() <= Duration::from_secs(backoff) {}
}
assert_eq!(
guard.register_error(),
Error::ReachedLimit,
"10 backoffs should be error"
);
}
#[test]
fn five_consecutive_failures_with_3_backoffs() {
let mut guard = RequestGuard::new(5, 3, Duration::from_secs(1), Duration::from_secs(30));
#[test]
fn five_consecutive_failures_with_3_backoffs() {
let mut guard = RequestGuard::new(5, 3, Duration::from_secs(1), Duration::from_secs(30));
// register five errors
for _ in 0..4 {
assert_eq!(guard.register_error(), Error::LetThrough);
}
// register five errors
for _ in 0..4 {
assert_eq!(guard.register_error(), Error::LetThrough);
}
let binary_exp_backoff = [1, 2, 4];
for backoff in &binary_exp_backoff {
assert_eq!(guard.register_error(), Error::Rejected);
let now = Instant::now();
while now.elapsed() <= Duration::from_secs(*backoff) {}
}
let binary_exp_backoff = [1, 2, 4];
for backoff in &binary_exp_backoff {
assert_eq!(guard.register_error(), Error::Rejected);
let now = Instant::now();
while now.elapsed() <= Duration::from_secs(*backoff) {}
}
assert_eq!(guard.register_error(), Error::ReachedLimit, "3 backoffs should be an error");
}
assert_eq!(
guard.register_error(),
Error::ReachedLimit,
"3 backoffs should be an error"
);
}
}

View File

@@ -20,154 +20,180 @@
//! 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::time::{Duration, Instant};
use std::collections::HashMap;
use std::fmt;
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),
/// 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)
}
}
}
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,
/// 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,
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,
}
}
/// 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,
}
}
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(())
}
}
/// 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 std::thread;
use super::*;
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));
#[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)));
}
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));
#[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)));
}
assert_eq!(
guard.register_error(&ResponseError::Validity(ValidityError::WrongKind)),
Err(Error::NoMajority(5))
);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -19,16 +19,17 @@
use std::sync::Arc;
use common_types::blockchain_info::BlockChainInfo;
use common_types::encoded;
use common_types::ids::BlockId;
use common_types::transaction::PendingTransaction;
use ethcore::client::{BlockChainClient, ProvingBlockChainClient, ChainInfo, BlockInfo as ClientBlockInfo};
use common_types::{
blockchain_info::BlockChainInfo, encoded, ids::BlockId, transaction::PendingTransaction,
};
use ethcore::client::{
BlockChainClient, BlockInfo as ClientBlockInfo, ChainInfo, ProvingBlockChainClient,
};
use ethereum_types::H256;
use parking_lot::RwLock;
use cht::{self, BlockInfo};
use client::{LightChainClient, AsLightClient};
use client::{AsLightClient, LightChainClient};
use transaction_queue::TransactionQueue;
use request;
@@ -38,373 +39,440 @@ pub const MAX_HEADERS_PER_REQUEST: u64 = 512;
/// Defines the operations that a provider for the light subprotocol must fulfill.
pub trait Provider: Send + Sync {
/// Provide current blockchain info.
fn chain_info(&self) -> BlockChainInfo;
/// Provide current blockchain info.
fn chain_info(&self) -> BlockChainInfo;
/// Find the depth of a common ancestor between two blocks.
/// If either block is unknown or an ancestor can't be found
/// then return `None`.
fn reorg_depth(&self, a: &H256, b: &H256) -> Option<u64>;
/// Find the depth of a common ancestor between two blocks.
/// If either block is unknown or an ancestor can't be found
/// then return `None`.
fn reorg_depth(&self, a: &H256, b: &H256) -> Option<u64>;
/// Earliest block where state queries are available.
/// If `None`, no state queries are servable.
fn earliest_state(&self) -> Option<u64>;
/// Earliest block where state queries are available.
/// If `None`, no state queries are servable.
fn earliest_state(&self) -> Option<u64>;
/// Provide a list of headers starting at the requested block,
/// possibly in reverse and skipping `skip` at a time.
///
/// The returned vector may have any length in the range [0, `max`], but the
/// results within must adhere to the `skip` and `reverse` parameters.
fn block_headers(&self, req: request::CompleteHeadersRequest) -> Option<request::HeadersResponse> {
use request::HashOrNumber;
/// Provide a list of headers starting at the requested block,
/// possibly in reverse and skipping `skip` at a time.
///
/// The returned vector may have any length in the range [0, `max`], but the
/// results within must adhere to the `skip` and `reverse` parameters.
fn block_headers(
&self,
req: request::CompleteHeadersRequest,
) -> Option<request::HeadersResponse> {
use request::HashOrNumber;
if req.max == 0 { return None }
if req.max == 0 {
return None;
}
let best_num = self.chain_info().best_block_number;
let start_num = match req.start {
HashOrNumber::Number(start_num) => start_num,
HashOrNumber::Hash(hash) => match self.block_header(BlockId::Hash(hash)) {
None => {
trace!(target: "pip_provider", "Unknown block hash {} requested", hash);
return None;
}
Some(header) => {
let num = header.number();
let canon_hash = self.block_header(BlockId::Number(num))
.map(|h| h.hash());
let best_num = self.chain_info().best_block_number;
let start_num = match req.start {
HashOrNumber::Number(start_num) => start_num,
HashOrNumber::Hash(hash) => match self.block_header(BlockId::Hash(hash)) {
None => {
trace!(target: "pip_provider", "Unknown block hash {} requested", hash);
return None;
}
Some(header) => {
let num = header.number();
let canon_hash = self.block_header(BlockId::Number(num)).map(|h| h.hash());
if req.max == 1 || canon_hash != Some(hash) {
// Non-canonical header or single header requested.
return Some(::request::HeadersResponse {
headers: vec![header],
})
}
if req.max == 1 || canon_hash != Some(hash) {
// Non-canonical header or single header requested.
return Some(::request::HeadersResponse {
headers: vec![header],
});
}
num
}
}
};
num
}
},
};
let max = ::std::cmp::min(MAX_HEADERS_PER_REQUEST, req.max);
let max = ::std::cmp::min(MAX_HEADERS_PER_REQUEST, req.max);
let headers: Vec<_> = (0_u64..max)
.map(|x: u64| x.saturating_mul(req.skip.saturating_add(1)))
.take_while(|&x| if req.reverse { x < start_num } else { best_num.saturating_sub(start_num) >= x })
.map(|x| if req.reverse { start_num.saturating_sub(x) } else { start_num.saturating_add(x) })
.map(|x| self.block_header(BlockId::Number(x)))
.take_while(|x| x.is_some())
.flat_map(|x| x)
.collect();
let headers: Vec<_> = (0_u64..max)
.map(|x: u64| x.saturating_mul(req.skip.saturating_add(1)))
.take_while(|&x| {
if req.reverse {
x < start_num
} else {
best_num.saturating_sub(start_num) >= x
}
})
.map(|x| {
if req.reverse {
start_num.saturating_sub(x)
} else {
start_num.saturating_add(x)
}
})
.map(|x| self.block_header(BlockId::Number(x)))
.take_while(|x| x.is_some())
.flat_map(|x| x)
.collect();
if headers.is_empty() {
None
} else {
Some(::request::HeadersResponse { headers })
}
}
if headers.is_empty() {
None
} else {
Some(::request::HeadersResponse { headers })
}
}
/// Get a block header by id.
fn block_header(&self, id: BlockId) -> Option<encoded::Header>;
/// Get a block header by id.
fn block_header(&self, id: BlockId) -> Option<encoded::Header>;
/// Get a transaction index by hash.
fn transaction_index(&self, req: request::CompleteTransactionIndexRequest)
-> Option<request::TransactionIndexResponse>;
/// Get a transaction index by hash.
fn transaction_index(
&self,
req: request::CompleteTransactionIndexRequest,
) -> Option<request::TransactionIndexResponse>;
/// Fulfill a block body request.
fn block_body(&self, req: request::CompleteBodyRequest) -> Option<request::BodyResponse>;
/// Fulfill a block body request.
fn block_body(&self, req: request::CompleteBodyRequest) -> Option<request::BodyResponse>;
/// Fulfill a request for block receipts.
fn block_receipts(&self, req: request::CompleteReceiptsRequest) -> Option<request::ReceiptsResponse>;
/// Fulfill a request for block receipts.
fn block_receipts(
&self,
req: request::CompleteReceiptsRequest,
) -> Option<request::ReceiptsResponse>;
/// Get an account proof.
fn account_proof(&self, req: request::CompleteAccountRequest) -> Option<request::AccountResponse>;
/// Get an account proof.
fn account_proof(
&self,
req: request::CompleteAccountRequest,
) -> Option<request::AccountResponse>;
/// Get a storage proof.
fn storage_proof(&self, req: request::CompleteStorageRequest) -> Option<request::StorageResponse>;
/// Get a storage proof.
fn storage_proof(
&self,
req: request::CompleteStorageRequest,
) -> Option<request::StorageResponse>;
/// Provide contract code for the specified (block_hash, code_hash) pair.
fn contract_code(&self, req: request::CompleteCodeRequest) -> Option<request::CodeResponse>;
/// Provide contract code for the specified (block_hash, code_hash) pair.
fn contract_code(&self, req: request::CompleteCodeRequest) -> Option<request::CodeResponse>;
/// Provide a header proof from a given Canonical Hash Trie as well as the
/// corresponding header.
fn header_proof(&self, req: request::CompleteHeaderProofRequest) -> Option<request::HeaderProofResponse>;
/// Provide a header proof from a given Canonical Hash Trie as well as the
/// corresponding header.
fn header_proof(
&self,
req: request::CompleteHeaderProofRequest,
) -> Option<request::HeaderProofResponse>;
/// Provide pending transactions.
fn transactions_to_propagate(&self) -> Vec<PendingTransaction>;
/// Provide pending transactions.
fn transactions_to_propagate(&self) -> Vec<PendingTransaction>;
/// Provide a proof-of-execution for the given transaction proof request.
/// Returns a vector of all state items necessary to execute the transaction.
fn transaction_proof(&self, req: request::CompleteExecutionRequest) -> Option<request::ExecutionResponse>;
/// Provide a proof-of-execution for the given transaction proof request.
/// Returns a vector of all state items necessary to execute the transaction.
fn transaction_proof(
&self,
req: request::CompleteExecutionRequest,
) -> Option<request::ExecutionResponse>;
/// Provide epoch signal data at given block hash. This should be just the
fn epoch_signal(&self, req: request::CompleteSignalRequest) -> Option<request::SignalResponse>;
/// Provide epoch signal data at given block hash. This should be just the
fn epoch_signal(&self, req: request::CompleteSignalRequest) -> Option<request::SignalResponse>;
}
// Implementation of a light client data provider for a client.
impl<T: ProvingBlockChainClient + ?Sized> Provider for T {
fn chain_info(&self) -> BlockChainInfo {
ChainInfo::chain_info(self)
}
fn chain_info(&self) -> BlockChainInfo {
ChainInfo::chain_info(self)
}
fn reorg_depth(&self, a: &H256, b: &H256) -> Option<u64> {
self.tree_route(a, b).map(|route| route.index as u64)
}
fn reorg_depth(&self, a: &H256, b: &H256) -> Option<u64> {
self.tree_route(a, b).map(|route| route.index as u64)
}
fn earliest_state(&self) -> Option<u64> {
Some(self.pruning_info().earliest_state)
}
fn earliest_state(&self) -> Option<u64> {
Some(self.pruning_info().earliest_state)
}
fn block_header(&self, id: BlockId) -> Option<encoded::Header> {
ClientBlockInfo::block_header(self, id)
}
fn block_header(&self, id: BlockId) -> Option<encoded::Header> {
ClientBlockInfo::block_header(self, id)
}
fn transaction_index(&self, req: request::CompleteTransactionIndexRequest)
-> Option<request::TransactionIndexResponse>
{
use common_types::ids::TransactionId;
fn transaction_index(
&self,
req: request::CompleteTransactionIndexRequest,
) -> Option<request::TransactionIndexResponse> {
use common_types::ids::TransactionId;
self.transaction_receipt(TransactionId::Hash(req.hash)).map(|receipt| request::TransactionIndexResponse {
num: receipt.block_number,
hash: receipt.block_hash,
index: receipt.transaction_index as u64,
})
}
self.transaction_receipt(TransactionId::Hash(req.hash))
.map(|receipt| request::TransactionIndexResponse {
num: receipt.block_number,
hash: receipt.block_hash,
index: receipt.transaction_index as u64,
})
}
fn block_body(&self, req: request::CompleteBodyRequest) -> Option<request::BodyResponse> {
BlockChainClient::block_body(self, BlockId::Hash(req.hash))
.map(|body| ::request::BodyResponse { body })
}
fn block_body(&self, req: request::CompleteBodyRequest) -> Option<request::BodyResponse> {
BlockChainClient::block_body(self, BlockId::Hash(req.hash))
.map(|body| ::request::BodyResponse { body })
}
fn block_receipts(&self, req: request::CompleteReceiptsRequest) -> Option<request::ReceiptsResponse> {
BlockChainClient::block_receipts(self, &req.hash)
.map(|x| ::request::ReceiptsResponse { receipts: x.receipts })
}
fn block_receipts(
&self,
req: request::CompleteReceiptsRequest,
) -> Option<request::ReceiptsResponse> {
BlockChainClient::block_receipts(self, &req.hash).map(|x| ::request::ReceiptsResponse {
receipts: x.receipts,
})
}
fn account_proof(&self, req: request::CompleteAccountRequest) -> Option<request::AccountResponse> {
self.prove_account(req.address_hash, BlockId::Hash(req.block_hash)).map(|(proof, acc)| {
::request::AccountResponse {
proof,
nonce: acc.nonce,
balance: acc.balance,
code_hash: acc.code_hash,
storage_root: acc.storage_root,
}
})
}
fn account_proof(
&self,
req: request::CompleteAccountRequest,
) -> Option<request::AccountResponse> {
self.prove_account(req.address_hash, BlockId::Hash(req.block_hash))
.map(|(proof, acc)| ::request::AccountResponse {
proof,
nonce: acc.nonce,
balance: acc.balance,
code_hash: acc.code_hash,
storage_root: acc.storage_root,
})
}
fn storage_proof(&self, req: request::CompleteStorageRequest) -> Option<request::StorageResponse> {
self.prove_storage(req.address_hash, req.key_hash, BlockId::Hash(req.block_hash)).map(|(proof, item) | {
::request::StorageResponse {
proof,
value: item,
}
})
}
fn storage_proof(
&self,
req: request::CompleteStorageRequest,
) -> Option<request::StorageResponse> {
self.prove_storage(
req.address_hash,
req.key_hash,
BlockId::Hash(req.block_hash),
)
.map(|(proof, item)| ::request::StorageResponse { proof, value: item })
}
fn contract_code(&self, req: request::CompleteCodeRequest) -> Option<request::CodeResponse> {
self.state_data(&req.code_hash)
.map(|code| ::request::CodeResponse { code })
}
fn contract_code(&self, req: request::CompleteCodeRequest) -> Option<request::CodeResponse> {
self.state_data(&req.code_hash)
.map(|code| ::request::CodeResponse { code })
}
fn header_proof(&self, req: request::CompleteHeaderProofRequest) -> Option<request::HeaderProofResponse> {
let cht_number = match cht::block_to_cht_number(req.num) {
Some(cht_num) => cht_num,
None => {
debug!(target: "pip_provider", "Requested CHT proof with invalid block number");
return None;
}
};
fn header_proof(
&self,
req: request::CompleteHeaderProofRequest,
) -> Option<request::HeaderProofResponse> {
let cht_number = match cht::block_to_cht_number(req.num) {
Some(cht_num) => cht_num,
None => {
debug!(target: "pip_provider", "Requested CHT proof with invalid block number");
return None;
}
};
let mut needed = None;
let mut needed = None;
// build the CHT, caching the requested header as we pass through it.
let cht = {
let block_info = |id| {
let hdr = self.block_header(id);
let td = self.block_total_difficulty(id);
// build the CHT, caching the requested header as we pass through it.
let cht = {
let block_info = |id| {
let hdr = self.block_header(id);
let td = self.block_total_difficulty(id);
match (hdr, td) {
(Some(hdr), Some(td)) => {
let info = BlockInfo {
hash: hdr.hash(),
parent_hash: hdr.parent_hash(),
total_difficulty: td,
};
match (hdr, td) {
(Some(hdr), Some(td)) => {
let info = BlockInfo {
hash: hdr.hash(),
parent_hash: hdr.parent_hash(),
total_difficulty: td,
};
if hdr.number() == req.num {
needed = Some((hdr, td));
}
if hdr.number() == req.num {
needed = Some((hdr, td));
}
Some(info)
}
_ => None,
}
};
Some(info)
}
_ => None,
}
};
match cht::build(cht_number, block_info) {
Some(cht) => cht,
None => return None, // incomplete CHT.
}
};
match cht::build(cht_number, block_info) {
Some(cht) => cht,
None => return None, // incomplete CHT.
}
};
let (needed_hdr, needed_td) = needed.expect("`needed` always set in loop, number checked before; qed");
let (needed_hdr, needed_td) =
needed.expect("`needed` always set in loop, number checked before; qed");
// prove our result.
match cht.prove(req.num, 0) {
Ok(Some(proof)) => Some(::request::HeaderProofResponse {
proof,
hash: needed_hdr.hash(),
td: needed_td,
}),
Ok(None) => None,
Err(e) => {
debug!(target: "pip_provider", "Error looking up number in freshly-created CHT: {}", e);
None
}
}
}
// prove our result.
match cht.prove(req.num, 0) {
Ok(Some(proof)) => Some(::request::HeaderProofResponse {
proof,
hash: needed_hdr.hash(),
td: needed_td,
}),
Ok(None) => None,
Err(e) => {
debug!(target: "pip_provider", "Error looking up number in freshly-created CHT: {}", e);
None
}
}
}
fn transaction_proof(&self, req: request::CompleteExecutionRequest) -> Option<request::ExecutionResponse> {
use common_types::transaction::Transaction;
fn transaction_proof(
&self,
req: request::CompleteExecutionRequest,
) -> Option<request::ExecutionResponse> {
use common_types::transaction::Transaction;
let id = BlockId::Hash(req.block_hash);
let nonce = match self.nonce(&req.from, id) {
Some(nonce) => nonce,
None => return None,
};
let transaction = Transaction {
nonce,
gas: req.gas,
gas_price: req.gas_price,
action: req.action,
value: req.value,
data: req.data,
}.fake_sign(req.from);
let id = BlockId::Hash(req.block_hash);
let nonce = match self.nonce(&req.from, id) {
Some(nonce) => nonce,
None => return None,
};
let transaction = Transaction {
nonce,
gas: req.gas,
gas_price: req.gas_price,
action: req.action,
value: req.value,
data: req.data,
}
.fake_sign(req.from);
self.prove_transaction(transaction, id)
.map(|(_, proof)| ::request::ExecutionResponse { items: proof })
}
self.prove_transaction(transaction, id)
.map(|(_, proof)| ::request::ExecutionResponse { items: proof })
}
fn transactions_to_propagate(&self) -> Vec<PendingTransaction> {
BlockChainClient::transactions_to_propagate(self)
.into_iter()
.map(|tx| tx.pending().clone())
.collect()
}
fn transactions_to_propagate(&self) -> Vec<PendingTransaction> {
BlockChainClient::transactions_to_propagate(self)
.into_iter()
.map(|tx| tx.pending().clone())
.collect()
}
fn epoch_signal(&self, req: request::CompleteSignalRequest) -> Option<request::SignalResponse> {
self.epoch_signal(req.block_hash).map(|signal| request::SignalResponse {
signal,
})
}
fn epoch_signal(&self, req: request::CompleteSignalRequest) -> Option<request::SignalResponse> {
self.epoch_signal(req.block_hash)
.map(|signal| request::SignalResponse { signal })
}
}
/// The light client "provider" implementation. This wraps a `LightClient` and
/// a light transaction queue.
pub struct LightProvider<L> {
client: Arc<L>,
txqueue: Arc<RwLock<TransactionQueue>>,
client: Arc<L>,
txqueue: Arc<RwLock<TransactionQueue>>,
}
impl<L> LightProvider<L> {
/// Create a new `LightProvider` from the given client and transaction queue.
pub fn new(client: Arc<L>, txqueue: Arc<RwLock<TransactionQueue>>) -> Self {
LightProvider {
client,
txqueue,
}
}
/// Create a new `LightProvider` from the given client and transaction queue.
pub fn new(client: Arc<L>, txqueue: Arc<RwLock<TransactionQueue>>) -> Self {
LightProvider { client, txqueue }
}
}
// TODO: draw from cache (shared between this and the RPC layer)
impl<L: AsLightClient + Send + Sync> Provider for LightProvider<L> {
fn chain_info(&self) -> BlockChainInfo {
self.client.as_light_client().chain_info()
}
fn chain_info(&self) -> BlockChainInfo {
self.client.as_light_client().chain_info()
}
fn reorg_depth(&self, _a: &H256, _b: &H256) -> Option<u64> {
None
}
fn reorg_depth(&self, _a: &H256, _b: &H256) -> Option<u64> {
None
}
fn earliest_state(&self) -> Option<u64> {
None
}
fn earliest_state(&self) -> Option<u64> {
None
}
fn block_header(&self, id: BlockId) -> Option<encoded::Header> {
self.client.as_light_client().block_header(id)
}
fn block_header(&self, id: BlockId) -> Option<encoded::Header> {
self.client.as_light_client().block_header(id)
}
fn transaction_index(&self, _req: request::CompleteTransactionIndexRequest)
-> Option<request::TransactionIndexResponse>
{
None
}
fn transaction_index(
&self,
_req: request::CompleteTransactionIndexRequest,
) -> Option<request::TransactionIndexResponse> {
None
}
fn block_body(&self, _req: request::CompleteBodyRequest) -> Option<request::BodyResponse> {
None
}
fn block_body(&self, _req: request::CompleteBodyRequest) -> Option<request::BodyResponse> {
None
}
fn block_receipts(&self, _req: request::CompleteReceiptsRequest) -> Option<request::ReceiptsResponse> {
None
}
fn block_receipts(
&self,
_req: request::CompleteReceiptsRequest,
) -> Option<request::ReceiptsResponse> {
None
}
fn account_proof(&self, _req: request::CompleteAccountRequest) -> Option<request::AccountResponse> {
None
}
fn account_proof(
&self,
_req: request::CompleteAccountRequest,
) -> Option<request::AccountResponse> {
None
}
fn storage_proof(&self, _req: request::CompleteStorageRequest) -> Option<request::StorageResponse> {
None
}
fn storage_proof(
&self,
_req: request::CompleteStorageRequest,
) -> Option<request::StorageResponse> {
None
}
fn contract_code(&self, _req: request::CompleteCodeRequest) -> Option<request::CodeResponse> {
None
}
fn contract_code(&self, _req: request::CompleteCodeRequest) -> Option<request::CodeResponse> {
None
}
fn header_proof(&self, _req: request::CompleteHeaderProofRequest) -> Option<request::HeaderProofResponse> {
None
}
fn header_proof(
&self,
_req: request::CompleteHeaderProofRequest,
) -> Option<request::HeaderProofResponse> {
None
}
fn transaction_proof(&self, _req: request::CompleteExecutionRequest) -> Option<request::ExecutionResponse> {
None
}
fn transaction_proof(
&self,
_req: request::CompleteExecutionRequest,
) -> Option<request::ExecutionResponse> {
None
}
fn epoch_signal(&self, _req: request::CompleteSignalRequest) -> Option<request::SignalResponse> {
None
}
fn epoch_signal(
&self,
_req: request::CompleteSignalRequest,
) -> Option<request::SignalResponse> {
None
}
fn transactions_to_propagate(&self) -> Vec<PendingTransaction> {
let chain_info = self.chain_info();
self.txqueue.read()
.ready_transactions(chain_info.best_block_number, chain_info.best_block_timestamp)
}
fn transactions_to_propagate(&self) -> Vec<PendingTransaction> {
let chain_info = self.chain_info();
self.txqueue.read().ready_transactions(
chain_info.best_block_number,
chain_info.best_block_timestamp,
)
}
}
impl<L: AsLightClient> AsLightClient for LightProvider<L> {
type Client = L::Client;
type Client = L::Client;
fn as_light_client(&self) -> &L::Client {
self.client.as_light_client()
}
fn as_light_client(&self) -> &L::Client {
self.client.as_light_client()
}
}
#[cfg(test)]
mod tests {
use ethcore::client::{EachBlockWith, TestBlockChainClient};
use super::Provider;
use super::Provider;
use ethcore::client::{EachBlockWith, TestBlockChainClient};
#[test]
fn cht_proof() {
let client = TestBlockChainClient::new();
client.add_blocks(2000, EachBlockWith::Nothing);
#[test]
fn cht_proof() {
let client = TestBlockChainClient::new();
client.add_blocks(2000, EachBlockWith::Nothing);
let req = ::request::CompleteHeaderProofRequest {
num: 1500,
};
let req = ::request::CompleteHeaderProofRequest { num: 1500 };
assert!(client.header_proof(req.clone()).is_none());
assert!(client.header_proof(req.clone()).is_none());
client.add_blocks(48, EachBlockWith::Nothing);
client.add_blocks(48, EachBlockWith::Nothing);
assert!(client.header_proof(req.clone()).is_some());
}
assert!(client.header_proof(req.clone()).is_some());
}
}

View File

@@ -23,107 +23,110 @@
//! accounts for which they create transactions, this queue is structured in an
//! address-wise manner.
use std::fmt;
use std::collections::{BTreeMap, HashMap};
use std::collections::hash_map::Entry;
use std::{
collections::{hash_map::Entry, BTreeMap, HashMap},
fmt,
};
use common_types::transaction::{self, Condition, PendingTransaction, SignedTransaction};
use ethereum_types::{H256, U256, Address};
use ethereum_types::{Address, H256, U256};
use fastmap::H256FastMap;
// Knowledge of an account's current nonce.
#[derive(Debug, Clone, PartialEq, Eq)]
enum CurrentNonce {
// Assumed current nonce.
Assumed(U256),
// Known current nonce.
Known(U256),
// Assumed current nonce.
Assumed(U256),
// Known current nonce.
Known(U256),
}
impl CurrentNonce {
// whether this nonce is assumed
fn is_assumed(&self) -> bool {
match *self {
CurrentNonce::Assumed(_) => true,
CurrentNonce::Known(_) => false,
}
}
// whether this nonce is assumed
fn is_assumed(&self) -> bool {
match *self {
CurrentNonce::Assumed(_) => true,
CurrentNonce::Known(_) => false,
}
}
// whether this nonce is known for certain from an external source.
fn is_known(&self) -> bool {
!self.is_assumed()
}
// whether this nonce is known for certain from an external source.
fn is_known(&self) -> bool {
!self.is_assumed()
}
// the current nonce's value.
fn value(&self) -> &U256 {
match *self {
CurrentNonce::Assumed(ref val) => val,
CurrentNonce::Known(ref val) => val,
}
}
// the current nonce's value.
fn value(&self) -> &U256 {
match *self {
CurrentNonce::Assumed(ref val) => val,
CurrentNonce::Known(ref val) => val,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct TransactionInfo {
hash: H256,
nonce: U256,
condition: Option<Condition>,
hash: H256,
nonce: U256,
condition: Option<Condition>,
}
impl<'a> From<&'a PendingTransaction> for TransactionInfo {
fn from(tx: &'a PendingTransaction) -> Self {
TransactionInfo {
hash: tx.hash(),
nonce: tx.nonce,
condition: tx.condition.clone(),
}
}
fn from(tx: &'a PendingTransaction) -> Self {
TransactionInfo {
hash: tx.hash(),
nonce: tx.nonce,
condition: tx.condition.clone(),
}
}
}
// transactions associated with a specific account.
#[derive(Debug, Clone, PartialEq, Eq)]
struct AccountTransactions {
// believed current nonce (gotten from initial given TX or `cull` calls).
cur_nonce: CurrentNonce,
current: Vec<TransactionInfo>, // ordered "current" transactions (cur_nonce onwards)
future: BTreeMap<U256, TransactionInfo>, // "future" transactions.
// believed current nonce (gotten from initial given TX or `cull` calls).
cur_nonce: CurrentNonce,
current: Vec<TransactionInfo>, // ordered "current" transactions (cur_nonce onwards)
future: BTreeMap<U256, TransactionInfo>, // "future" transactions.
}
impl AccountTransactions {
fn is_empty(&self) -> bool {
self.current.is_empty() && self.future.is_empty()
}
fn is_empty(&self) -> bool {
self.current.is_empty() && self.future.is_empty()
}
fn next_nonce(&self) -> U256 {
self.current.last().map(|last| last.nonce.saturating_add(1.into()))
.unwrap_or_else(|| *self.cur_nonce.value())
}
fn next_nonce(&self) -> U256 {
self.current
.last()
.map(|last| last.nonce.saturating_add(1.into()))
.unwrap_or_else(|| *self.cur_nonce.value())
}
// attempt to move transactions from the future queue into the current queue.
fn adjust_future(&mut self) -> Vec<H256> {
let mut promoted = Vec::new();
let mut next_nonce = self.next_nonce();
// attempt to move transactions from the future queue into the current queue.
fn adjust_future(&mut self) -> Vec<H256> {
let mut promoted = Vec::new();
let mut next_nonce = self.next_nonce();
while let Some(tx) = self.future.remove(&next_nonce) {
promoted.push(tx.hash);
self.current.push(tx);
next_nonce = next_nonce.saturating_add(1.into());
}
while let Some(tx) = self.future.remove(&next_nonce) {
promoted.push(tx.hash);
self.current.push(tx);
next_nonce = next_nonce.saturating_add(1.into());
}
promoted
}
promoted
}
}
/// Transaction import result.
pub enum ImportDestination {
/// Transaction has been imported to the current queue.
///
/// It's going to be propagated to peers.
Current,
/// Transaction has been imported to future queue.
///
/// It means it won't be propagated until the gap is filled.
Future,
/// Transaction has been imported to the current queue.
///
/// It's going to be propagated to peers.
Current,
/// Transaction has been imported to future queue.
///
/// It means it won't be propagated until the gap is filled.
Future,
}
type Listener = Box<Fn(&[H256]) + Send + Sync>;
@@ -131,129 +134,150 @@ type Listener = Box<Fn(&[H256]) + Send + Sync>;
/// Light transaction queue. See module docs for more details.
#[derive(Default)]
pub struct TransactionQueue {
by_account: HashMap<Address, AccountTransactions>,
by_hash: H256FastMap<PendingTransaction>,
listeners: Vec<Listener>,
by_account: HashMap<Address, AccountTransactions>,
by_hash: H256FastMap<PendingTransaction>,
listeners: Vec<Listener>,
}
impl fmt::Debug for TransactionQueue {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_struct("TransactionQueue")
.field("by_account", &self.by_account)
.field("by_hash", &self.by_hash)
.field("listeners", &self.listeners.len())
.finish()
}
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_struct("TransactionQueue")
.field("by_account", &self.by_account)
.field("by_hash", &self.by_hash)
.field("listeners", &self.listeners.len())
.finish()
}
}
impl TransactionQueue {
/// Import a pending transaction to be queued.
pub fn import(&mut self, tx: PendingTransaction) -> Result<ImportDestination, transaction::Error> {
let sender = tx.sender();
let hash = tx.hash();
let nonce = tx.nonce;
let tx_info = TransactionInfo::from(&tx);
/// Import a pending transaction to be queued.
pub fn import(
&mut self,
tx: PendingTransaction,
) -> Result<ImportDestination, transaction::Error> {
let sender = tx.sender();
let hash = tx.hash();
let nonce = tx.nonce;
let tx_info = TransactionInfo::from(&tx);
if self.by_hash.contains_key(&hash) { return Err(transaction::Error::AlreadyImported) }
if self.by_hash.contains_key(&hash) {
return Err(transaction::Error::AlreadyImported);
}
let (res, promoted) = match self.by_account.entry(sender) {
Entry::Vacant(entry) => {
entry.insert(AccountTransactions {
cur_nonce: CurrentNonce::Assumed(nonce),
current: vec![tx_info],
future: BTreeMap::new(),
});
let (res, promoted) = match self.by_account.entry(sender) {
Entry::Vacant(entry) => {
entry.insert(AccountTransactions {
cur_nonce: CurrentNonce::Assumed(nonce),
current: vec![tx_info],
future: BTreeMap::new(),
});
(ImportDestination::Current, vec![hash])
}
Entry::Occupied(mut entry) => {
let acct_txs = entry.get_mut();
if nonce < *acct_txs.cur_nonce.value() {
// don't accept txs from before known current nonce.
if acct_txs.cur_nonce.is_known() {
return Err(transaction::Error::Old)
}
(ImportDestination::Current, vec![hash])
}
Entry::Occupied(mut entry) => {
let acct_txs = entry.get_mut();
if nonce < *acct_txs.cur_nonce.value() {
// don't accept txs from before known current nonce.
if acct_txs.cur_nonce.is_known() {
return Err(transaction::Error::Old);
}
// lower our assumption until corrected later.
acct_txs.cur_nonce = CurrentNonce::Assumed(nonce);
}
// lower our assumption until corrected later.
acct_txs.cur_nonce = CurrentNonce::Assumed(nonce);
}
match acct_txs.current.binary_search_by(|x| x.nonce.cmp(&nonce)) {
Ok(idx) => {
trace!(target: "txqueue", "Replacing existing transaction from {} with nonce {}",
match acct_txs.current.binary_search_by(|x| x.nonce.cmp(&nonce)) {
Ok(idx) => {
trace!(target: "txqueue", "Replacing existing transaction from {} with nonce {}",
sender, nonce);
let old = ::std::mem::replace(&mut acct_txs.current[idx], tx_info);
self.by_hash.remove(&old.hash);
let old = ::std::mem::replace(&mut acct_txs.current[idx], tx_info);
self.by_hash.remove(&old.hash);
(ImportDestination::Current, vec![hash])
}
Err(idx) => {
let cur_len = acct_txs.current.len();
let incr_nonce = nonce + 1;
(ImportDestination::Current, vec![hash])
}
Err(idx) => {
let cur_len = acct_txs.current.len();
let incr_nonce = nonce + 1;
// current is sorted with one tx per nonce,
// so if a tx with given nonce wasn't found that means it is either
// earlier in nonce than all other "current" transactions or later.
assert!(idx == 0 || idx == cur_len);
// current is sorted with one tx per nonce,
// so if a tx with given nonce wasn't found that means it is either
// earlier in nonce than all other "current" transactions or later.
assert!(idx == 0 || idx == cur_len);
if idx == 0 && acct_txs.current.first().map_or(false, |f| f.nonce != incr_nonce) {
let old_cur = ::std::mem::replace(&mut acct_txs.current, vec![tx_info]);
if idx == 0
&& acct_txs
.current
.first()
.map_or(false, |f| f.nonce != incr_nonce)
{
let old_cur = ::std::mem::replace(&mut acct_txs.current, vec![tx_info]);
trace!(target: "txqueue", "Moving {} transactions with nonce > {} to future",
trace!(target: "txqueue", "Moving {} transactions with nonce > {} to future",
old_cur.len(), incr_nonce);
for future in old_cur {
let future_nonce = future.nonce;
acct_txs.future.insert(future_nonce, future);
}
for future in old_cur {
let future_nonce = future.nonce;
acct_txs.future.insert(future_nonce, future);
}
(ImportDestination::Current, vec![hash])
} else if idx == cur_len && acct_txs.current.last().map_or(false, |f| f.nonce + 1 != nonce) {
trace!(target: "txqueue", "Queued future transaction for {}, nonce={}", sender, nonce);
let future_nonce = nonce;
acct_txs.future.insert(future_nonce, tx_info);
(ImportDestination::Current, vec![hash])
} else if idx == cur_len
&& acct_txs
.current
.last()
.map_or(false, |f| f.nonce + 1 != nonce)
{
trace!(target: "txqueue", "Queued future transaction for {}, nonce={}", sender, nonce);
let future_nonce = nonce;
acct_txs.future.insert(future_nonce, tx_info);
(ImportDestination::Future, vec![])
} else {
trace!(target: "txqueue", "Queued current transaction for {}, nonce={}", sender, nonce);
(ImportDestination::Future, vec![])
} else {
trace!(target: "txqueue", "Queued current transaction for {}, nonce={}", sender, nonce);
// insert, then check if we've filled any gaps.
acct_txs.current.insert(idx, tx_info);
let mut promoted = acct_txs.adjust_future();
promoted.insert(0, hash);
// insert, then check if we've filled any gaps.
acct_txs.current.insert(idx, tx_info);
let mut promoted = acct_txs.adjust_future();
promoted.insert(0, hash);
(ImportDestination::Current, promoted)
}
}
}
}
};
(ImportDestination::Current, promoted)
}
}
}
}
};
self.by_hash.insert(hash, tx);
self.notify(&promoted);
Ok(res)
}
self.by_hash.insert(hash, tx);
self.notify(&promoted);
Ok(res)
}
/// Get pending transaction by hash.
pub fn transaction(&self, hash: &H256) -> Option<SignedTransaction> {
self.by_hash.get(hash).map(|tx| (&**tx).clone())
}
/// Get pending transaction by hash.
pub fn transaction(&self, hash: &H256) -> Option<SignedTransaction> {
self.by_hash.get(hash).map(|tx| (&**tx).clone())
}
/// Get the next nonce for a given address based on what's within the queue.
/// If the address has no queued transactions, then `None` will be returned
/// and the next nonce will have to be deduced via other means.
pub fn next_nonce(&self, address: &Address) -> Option<U256> {
self.by_account.get(address).map(AccountTransactions::next_nonce)
}
/// Get the next nonce for a given address based on what's within the queue.
/// If the address has no queued transactions, then `None` will be returned
/// and the next nonce will have to be deduced via other means.
pub fn next_nonce(&self, address: &Address) -> Option<U256> {
self.by_account
.get(address)
.map(AccountTransactions::next_nonce)
}
/// Get all transactions ready to be propagated.
/// `best_block_number` and `best_block_timestamp` are used to filter out conditionally
/// propagated transactions.
///
/// Returned transactions are batched by sender, in order of ascending nonce.
pub fn ready_transactions(&self, best_block_number: u64, best_block_timestamp: u64) -> Vec<PendingTransaction> {
self.by_account.values()
/// Get all transactions ready to be propagated.
/// `best_block_number` and `best_block_timestamp` are used to filter out conditionally
/// propagated transactions.
///
/// Returned transactions are batched by sender, in order of ascending nonce.
pub fn ready_transactions(
&self,
best_block_number: u64,
best_block_timestamp: u64,
) -> Vec<PendingTransaction> {
self.by_account.values()
.flat_map(|acct_txs| {
acct_txs.current.iter().take_while(|tx| match tx.condition {
None => true,
@@ -270,15 +294,19 @@ impl TransactionQueue {
}
})
.collect()
}
}
/// Get all transactions not ready to be propagated.
/// `best_block_number` and `best_block_timestamp` are used to filter out conditionally
/// propagated transactions.
///
/// Returned transactions are batched by sender, in order of ascending nonce.
pub fn future_transactions(&self, best_block_number: u64, best_block_timestamp: u64) -> Vec<PendingTransaction> {
self.by_account.values()
/// Get all transactions not ready to be propagated.
/// `best_block_number` and `best_block_timestamp` are used to filter out conditionally
/// propagated transactions.
///
/// Returned transactions are batched by sender, in order of ascending nonce.
pub fn future_transactions(
&self,
best_block_number: u64,
best_block_timestamp: u64,
) -> Vec<PendingTransaction> {
self.by_account.values()
.flat_map(|acct_txs| {
acct_txs.current.iter().skip_while(|tx| match tx.condition {
None => true,
@@ -295,267 +323,275 @@ impl TransactionQueue {
}
})
.collect()
}
}
/// Addresses for which we store transactions.
pub fn queued_senders(&self) -> Vec<Address> {
self.by_account.keys().cloned().collect()
}
/// Addresses for which we store transactions.
pub fn queued_senders(&self) -> Vec<Address> {
self.by_account.keys().cloned().collect()
}
/// Cull out all transactions by the given address which are invalidated by the given nonce.
pub fn cull(&mut self, address: Address, cur_nonce: U256) {
let mut removed_hashes = vec![];
if let Entry::Occupied(mut entry) = self.by_account.entry(address) {
{
let acct_txs = entry.get_mut();
acct_txs.cur_nonce = CurrentNonce::Known(cur_nonce);
/// Cull out all transactions by the given address which are invalidated by the given nonce.
pub fn cull(&mut self, address: Address, cur_nonce: U256) {
let mut removed_hashes = vec![];
if let Entry::Occupied(mut entry) = self.by_account.entry(address) {
{
let acct_txs = entry.get_mut();
acct_txs.cur_nonce = CurrentNonce::Known(cur_nonce);
// cull old "future" keys.
let old_future: Vec<_> = acct_txs.future.keys().take_while(|&&k| k < cur_nonce).cloned().collect();
// cull old "future" keys.
let old_future: Vec<_> = acct_txs
.future
.keys()
.take_while(|&&k| k < cur_nonce)
.cloned()
.collect();
for old in old_future {
let hash = acct_txs.future.remove(&old)
.expect("key extracted from keys iterator; known to exist; qed")
.hash;
removed_hashes.push(hash);
}
for old in old_future {
let hash = acct_txs
.future
.remove(&old)
.expect("key extracted from keys iterator; known to exist; qed")
.hash;
removed_hashes.push(hash);
}
// then cull from "current".
let valid_pos = acct_txs.current.iter().position(|tx| tx.nonce >= cur_nonce);
match valid_pos {
None =>
removed_hashes.extend(acct_txs.current.drain(..).map(|tx| tx.hash)),
Some(valid) =>
removed_hashes.extend(acct_txs.current.drain(..valid).map(|tx| tx.hash)),
}
// then cull from "current".
let valid_pos = acct_txs.current.iter().position(|tx| tx.nonce >= cur_nonce);
match valid_pos {
None => removed_hashes.extend(acct_txs.current.drain(..).map(|tx| tx.hash)),
Some(valid) => {
removed_hashes.extend(acct_txs.current.drain(..valid).map(|tx| tx.hash))
}
}
// now try and move stuff out of future into current.
acct_txs.adjust_future();
}
// now try and move stuff out of future into current.
acct_txs.adjust_future();
}
if entry.get_mut().is_empty() {
trace!(target: "txqueue", "No more queued transactions for {} after nonce {}",
if entry.get_mut().is_empty() {
trace!(target: "txqueue", "No more queued transactions for {} after nonce {}",
address, cur_nonce);
entry.remove();
}
}
entry.remove();
}
}
trace!(target: "txqueue", "Culled {} old transactions from sender {} (nonce={})",
trace!(target: "txqueue", "Culled {} old transactions from sender {} (nonce={})",
removed_hashes.len(), address, cur_nonce);
for hash in removed_hashes {
self.by_hash.remove(&hash);
}
}
for hash in removed_hashes {
self.by_hash.remove(&hash);
}
}
/// Get a transaction by hash.
pub fn get(&self, hash: &H256) -> Option<&PendingTransaction> {
self.by_hash.get(&hash)
}
/// Get a transaction by hash.
pub fn get(&self, hash: &H256) -> Option<&PendingTransaction> {
self.by_hash.get(&hash)
}
/// Add a transaction queue listener.
pub fn add_listener(&mut self, f: Listener) {
self.listeners.push(f);
}
/// Add a transaction queue listener.
pub fn add_listener(&mut self, f: Listener) {
self.listeners.push(f);
}
/// Notifies all listeners about new pending transaction.
fn notify(&self, hashes: &[H256]) {
for listener in &self.listeners {
listener(hashes)
}
}
/// Notifies all listeners about new pending transaction.
fn notify(&self, hashes: &[H256]) {
for listener in &self.listeners {
listener(hashes)
}
}
}
#[cfg(test)]
mod tests {
use super::TransactionQueue;
use ethereum_types::Address;
use common_types::transaction::{Transaction, PendingTransaction, Condition};
use super::TransactionQueue;
use common_types::transaction::{Condition, PendingTransaction, Transaction};
use ethereum_types::Address;
#[test]
fn queued_senders() {
let sender = Address::default();
let mut txq = TransactionQueue::default();
let tx = Transaction::default().fake_sign(sender);
#[test]
fn queued_senders() {
let sender = Address::default();
let mut txq = TransactionQueue::default();
let tx = Transaction::default().fake_sign(sender);
txq.import(tx.into()).unwrap();
txq.import(tx.into()).unwrap();
assert_eq!(txq.queued_senders(), vec![sender]);
assert_eq!(txq.queued_senders(), vec![sender]);
txq.cull(sender, 1.into());
txq.cull(sender, 1.into());
assert_eq!(txq.queued_senders(), vec![]);
assert!(txq.by_hash.is_empty());
}
assert_eq!(txq.queued_senders(), vec![]);
assert!(txq.by_hash.is_empty());
}
#[test]
fn next_nonce() {
let sender = Address::default();
let mut txq = TransactionQueue::default();
#[test]
fn next_nonce() {
let sender = Address::default();
let mut txq = TransactionQueue::default();
for i in (0..5).chain(10..15) {
let mut tx = Transaction::default();
tx.nonce = i.into();
for i in (0..5).chain(10..15) {
let mut tx = Transaction::default();
tx.nonce = i.into();
let tx = tx.fake_sign(sender);
let tx = tx.fake_sign(sender);
txq.import(tx.into()).unwrap();
}
txq.import(tx.into()).unwrap();
}
// current: 0..5, future: 10..15
assert_eq!(txq.ready_transactions(0, 0).len(), 5);
assert_eq!(txq.next_nonce(&sender).unwrap(), 5.into());
// current: 0..5, future: 10..15
assert_eq!(txq.ready_transactions(0, 0).len(), 5);
assert_eq!(txq.next_nonce(&sender).unwrap(), 5.into());
txq.cull(sender, 8.into());
txq.cull(sender, 8.into());
// current: empty, future: 10..15
assert_eq!(txq.ready_transactions(0, 0).len(), 0);
assert_eq!(txq.next_nonce(&sender).unwrap(), 8.into());
// current: empty, future: 10..15
assert_eq!(txq.ready_transactions(0, 0).len(), 0);
assert_eq!(txq.next_nonce(&sender).unwrap(), 8.into());
txq.cull(sender, 10.into());
txq.cull(sender, 10.into());
// current: 10..15, future: empty
assert_eq!(txq.ready_transactions(0, 0).len(), 5);
assert_eq!(txq.next_nonce(&sender).unwrap(), 15.into());
}
// current: 10..15, future: empty
assert_eq!(txq.ready_transactions(0, 0).len(), 5);
assert_eq!(txq.next_nonce(&sender).unwrap(), 15.into());
}
#[test]
fn current_to_future() {
let sender = Address::default();
let mut txq = TransactionQueue::default();
#[test]
fn current_to_future() {
let sender = Address::default();
let mut txq = TransactionQueue::default();
for i in 5..10 {
let mut tx = Transaction::default();
tx.nonce = i.into();
for i in 5..10 {
let mut tx = Transaction::default();
tx.nonce = i.into();
let tx = tx.fake_sign(sender);
let tx = tx.fake_sign(sender);
txq.import(tx.into()).unwrap();
}
txq.import(tx.into()).unwrap();
}
assert_eq!(txq.ready_transactions(0, 0).len(), 5);
assert_eq!(txq.next_nonce(&sender).unwrap(), 10.into());
assert_eq!(txq.ready_transactions(0, 0).len(), 5);
assert_eq!(txq.next_nonce(&sender).unwrap(), 10.into());
for i in 0..3 {
let mut tx = Transaction::default();
tx.nonce = i.into();
for i in 0..3 {
let mut tx = Transaction::default();
tx.nonce = i.into();
let tx = tx.fake_sign(sender);
let tx = tx.fake_sign(sender);
txq.import(tx.into()).unwrap();
}
txq.import(tx.into()).unwrap();
}
assert_eq!(txq.ready_transactions(0, 0).len(), 3);
assert_eq!(txq.next_nonce(&sender).unwrap(), 3.into());
assert_eq!(txq.ready_transactions(0, 0).len(), 3);
assert_eq!(txq.next_nonce(&sender).unwrap(), 3.into());
for i in 3..5 {
let mut tx = Transaction::default();
tx.nonce = i.into();
for i in 3..5 {
let mut tx = Transaction::default();
tx.nonce = i.into();
let tx = tx.fake_sign(sender);
let tx = tx.fake_sign(sender);
txq.import(tx.into()).unwrap();
}
txq.import(tx.into()).unwrap();
}
assert_eq!(txq.ready_transactions(0, 0).len(), 10);
assert_eq!(txq.next_nonce(&sender).unwrap(), 10.into());
}
assert_eq!(txq.ready_transactions(0, 0).len(), 10);
assert_eq!(txq.next_nonce(&sender).unwrap(), 10.into());
}
#[test]
fn conditional() {
let mut txq = TransactionQueue::default();
let sender = Address::default();
#[test]
fn conditional() {
let mut txq = TransactionQueue::default();
let sender = Address::default();
for i in 0..5 {
let mut tx = Transaction::default();
tx.nonce = i.into();
let tx = tx.fake_sign(sender);
for i in 0..5 {
let mut tx = Transaction::default();
tx.nonce = i.into();
let tx = tx.fake_sign(sender);
txq.import(match i {
3 => PendingTransaction::new(tx, Some(Condition::Number(100))),
4 => PendingTransaction::new(tx, Some(Condition::Timestamp(1234))),
_ => tx.into(),
}).unwrap();
}
txq.import(match i {
3 => PendingTransaction::new(tx, Some(Condition::Number(100))),
4 => PendingTransaction::new(tx, Some(Condition::Timestamp(1234))),
_ => tx.into(),
})
.unwrap();
}
assert_eq!(txq.ready_transactions(0, 0).len(), 3);
assert_eq!(txq.ready_transactions(0, 1234).len(), 3);
assert_eq!(txq.ready_transactions(100, 0).len(), 4);
assert_eq!(txq.ready_transactions(100, 1234).len(), 5);
}
assert_eq!(txq.ready_transactions(0, 0).len(), 3);
assert_eq!(txq.ready_transactions(0, 1234).len(), 3);
assert_eq!(txq.ready_transactions(100, 0).len(), 4);
assert_eq!(txq.ready_transactions(100, 1234).len(), 5);
}
#[test]
fn cull_from_future() {
let sender = Address::default();
let mut txq = TransactionQueue::default();
#[test]
fn cull_from_future() {
let sender = Address::default();
let mut txq = TransactionQueue::default();
for i in (0..1).chain(3..10) {
let mut tx = Transaction::default();
tx.nonce = i.into();
for i in (0..1).chain(3..10) {
let mut tx = Transaction::default();
tx.nonce = i.into();
let tx = tx.fake_sign(sender);
let tx = tx.fake_sign(sender);
txq.import(tx.into()).unwrap();
}
txq.import(tx.into()).unwrap();
}
txq.cull(sender, 6.into());
txq.cull(sender, 6.into());
assert_eq!(txq.ready_transactions(0, 0).len(), 4);
assert_eq!(txq.next_nonce(&sender).unwrap(), 10.into());
}
assert_eq!(txq.ready_transactions(0, 0).len(), 4);
assert_eq!(txq.next_nonce(&sender).unwrap(), 10.into());
}
#[test]
fn import_old() {
let sender = Address::default();
let mut txq = TransactionQueue::default();
#[test]
fn import_old() {
let sender = Address::default();
let mut txq = TransactionQueue::default();
let mut tx_a = Transaction::default();
tx_a.nonce = 3.into();
let mut tx_a = Transaction::default();
tx_a.nonce = 3.into();
let mut tx_b = Transaction::default();
tx_b.nonce = 2.into();
let mut tx_b = Transaction::default();
tx_b.nonce = 2.into();
txq.import(tx_a.fake_sign(sender).into()).unwrap();
txq.cull(sender, 3.into());
txq.import(tx_a.fake_sign(sender).into()).unwrap();
txq.cull(sender, 3.into());
assert!(txq.import(tx_b.fake_sign(sender).into()).is_err())
}
assert!(txq.import(tx_b.fake_sign(sender).into()).is_err())
}
#[test]
fn replace_is_removed() {
let sender = Address::default();
let mut txq = TransactionQueue::default();
#[test]
fn replace_is_removed() {
let sender = Address::default();
let mut txq = TransactionQueue::default();
let tx_b: PendingTransaction = Transaction::default().fake_sign(sender).into();
let tx_a: PendingTransaction = {
let mut tx_a = Transaction::default();
tx_a.gas_price = tx_b.gas_price + 1;
tx_a.fake_sign(sender).into()
};
let tx_b: PendingTransaction = Transaction::default().fake_sign(sender).into();
let tx_a: PendingTransaction = {
let mut tx_a = Transaction::default();
tx_a.gas_price = tx_b.gas_price + 1;
tx_a.fake_sign(sender).into()
};
let hash = tx_a.hash();
let hash = tx_a.hash();
txq.import(tx_a).unwrap();
txq.import(tx_b).unwrap();
txq.import(tx_a).unwrap();
txq.import(tx_b).unwrap();
assert!(txq.transaction(&hash).is_none());
}
assert!(txq.transaction(&hash).is_none());
}
#[test]
fn future_transactions() {
let sender = Address::default();
let mut txq = TransactionQueue::default();
#[test]
fn future_transactions() {
let sender = Address::default();
let mut txq = TransactionQueue::default();
for i in (0..1).chain(3..10) {
let mut tx = Transaction::default();
tx.nonce = i.into();
for i in (0..1).chain(3..10) {
let mut tx = Transaction::default();
tx.nonce = i.into();
let tx = tx.fake_sign(sender);
let tx = tx.fake_sign(sender);
txq.import(tx.into()).unwrap();
}
txq.import(tx.into()).unwrap();
}
assert_eq!(txq.future_transactions(0, 0).len(), 7);
assert_eq!(txq.next_nonce(&sender).unwrap(), 1.into());
}
assert_eq!(txq.future_transactions(0, 0).len(), 7);
assert_eq!(txq.next_nonce(&sender).unwrap(), 1.into());
}
}

View File

@@ -18,300 +18,358 @@
//! Push requests with `push`. Back-references and data required to verify responses must be
//! supplied as well.
use std::collections::HashMap;
use std::ops::{Deref, DerefMut};
use request::{
IncompleteRequest, OutputKind, Output, NoSuchOutput, ResponseError, ResponseLike,
use request::{IncompleteRequest, NoSuchOutput, Output, OutputKind, ResponseError, ResponseLike};
use std::{
collections::HashMap,
ops::{Deref, DerefMut},
};
/// Build chained requests. Push them onto the series with `push`,
/// and produce a `Batch` object with `build`. Outputs are checked for consistency.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Builder<T> {
output_kinds: HashMap<(usize, usize), OutputKind>,
requests: Vec<T>,
output_kinds: HashMap<(usize, usize), OutputKind>,
requests: Vec<T>,
}
impl<T> Default for Builder<T> {
fn default() -> Self {
Builder {
output_kinds: HashMap::new(),
requests: Vec::new(),
}
}
fn default() -> Self {
Builder {
output_kinds: HashMap::new(),
requests: Vec::new(),
}
}
}
impl<T: IncompleteRequest> Builder<T> {
/// Attempt to push a request onto the request chain. Fails if the request
/// references a non-existent output of a prior request.
pub fn push(&mut self, request: T) -> Result<(), NoSuchOutput> {
request.check_outputs(|req, idx, kind| {
match self.output_kinds.get(&(req, idx)) {
Some(k) if k == &kind => Ok(()),
_ => Err(NoSuchOutput),
}
})?;
let req_idx = self.requests.len();
request.note_outputs(|idx, kind| { self.output_kinds.insert((req_idx, idx), kind); });
self.requests.push(request);
Ok(())
}
/// Attempt to push a request onto the request chain. Fails if the request
/// references a non-existent output of a prior request.
pub fn push(&mut self, request: T) -> Result<(), NoSuchOutput> {
request.check_outputs(|req, idx, kind| match self.output_kinds.get(&(req, idx)) {
Some(k) if k == &kind => Ok(()),
_ => Err(NoSuchOutput),
})?;
let req_idx = self.requests.len();
request.note_outputs(|idx, kind| {
self.output_kinds.insert((req_idx, idx), kind);
});
self.requests.push(request);
Ok(())
}
/// Get a reference to the output kinds map.
pub fn output_kinds(&self) -> &HashMap<(usize, usize), OutputKind> {
&self.output_kinds
}
/// Get a reference to the output kinds map.
pub fn output_kinds(&self) -> &HashMap<(usize, usize), OutputKind> {
&self.output_kinds
}
/// Convert this into a "batch" object.
pub fn build(self) -> Batch<T> {
Batch {
outputs: HashMap::new(),
requests: self.requests,
answered: 0,
}
}
/// Convert this into a "batch" object.
pub fn build(self) -> Batch<T> {
Batch {
outputs: HashMap::new(),
requests: self.requests,
answered: 0,
}
}
}
/// Requests pending responses.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Batch<T> {
outputs: HashMap<(usize, usize), Output>,
requests: Vec<T>,
answered: usize,
outputs: HashMap<(usize, usize), Output>,
requests: Vec<T>,
answered: usize,
}
impl<T> Batch<T> {
/// Get access to the underlying slice of requests.
// TODO: unimplemented -> Vec<Request>, // do we _have to_ allocate?
pub fn requests(&self) -> &[T] { &self.requests }
/// Get access to the underlying slice of requests.
// TODO: unimplemented -> Vec<Request>, // do we _have to_ allocate?
pub fn requests(&self) -> &[T] {
&self.requests
}
/// Get the number of answered requests.
pub fn num_answered(&self) -> usize { self.answered }
/// Get the number of answered requests.
pub fn num_answered(&self) -> usize {
self.answered
}
/// Whether the batch is complete.
pub fn is_complete(&self) -> bool {
self.answered == self.requests.len()
}
/// Whether the batch is complete.
pub fn is_complete(&self) -> bool {
self.answered == self.requests.len()
}
/// Map requests from one type into another.
pub fn map_requests<F, U>(self, f: F) -> Batch<U>
where F: FnMut(T) -> U, U: IncompleteRequest
{
Batch {
outputs: self.outputs,
requests: self.requests.into_iter().map(f).collect(),
answered: self.answered,
}
}
/// Map requests from one type into another.
pub fn map_requests<F, U>(self, f: F) -> Batch<U>
where
F: FnMut(T) -> U,
U: IncompleteRequest,
{
Batch {
outputs: self.outputs,
requests: self.requests.into_iter().map(f).collect(),
answered: self.answered,
}
}
}
impl<T: IncompleteRequest + Clone> Batch<T> {
/// Get the next request as a filled request. Returns `None` when all requests answered.
pub fn next_complete(&self) -> Option<T::Complete> {
if self.is_complete() {
None
} else {
Some(self.requests[self.answered].clone()
.complete()
.expect("All outputs checked as invariant of `Batch` object; qed"))
}
}
/// Get the next request as a filled request. Returns `None` when all requests answered.
pub fn next_complete(&self) -> Option<T::Complete> {
if self.is_complete() {
None
} else {
Some(
self.requests[self.answered]
.clone()
.complete()
.expect("All outputs checked as invariant of `Batch` object; qed"),
)
}
}
/// Sweep through all unanswered requests, filling them as necessary.
pub fn fill_unanswered(&mut self) {
let outputs = &mut self.outputs;
/// Sweep through all unanswered requests, filling them as necessary.
pub fn fill_unanswered(&mut self) {
let outputs = &mut self.outputs;
for req in self.requests.iter_mut().skip(self.answered) {
req.fill(|req_idx, out_idx| outputs.get(&(req_idx, out_idx)).cloned().ok_or(NoSuchOutput))
}
}
for req in self.requests.iter_mut().skip(self.answered) {
req.fill(|req_idx, out_idx| {
outputs
.get(&(req_idx, out_idx))
.cloned()
.ok_or(NoSuchOutput)
})
}
}
/// Supply a response, asserting its correctness.
/// Fill outputs based upon it.
pub fn supply_response_unchecked<R: ResponseLike>(&mut self, response: &R) {
if self.is_complete() { return }
/// Supply a response, asserting its correctness.
/// Fill outputs based upon it.
pub fn supply_response_unchecked<R: ResponseLike>(&mut self, response: &R) {
if self.is_complete() {
return;
}
let outputs = &mut self.outputs;
let idx = self.answered;
response.fill_outputs(|out_idx, output| {
// we don't need to check output kinds here because all back-references
// are validated in the builder.
// TODO: optimization for only storing outputs we "care about"?
outputs.insert((idx, out_idx), output);
});
let outputs = &mut self.outputs;
let idx = self.answered;
response.fill_outputs(|out_idx, output| {
// we don't need to check output kinds here because all back-references
// are validated in the builder.
// TODO: optimization for only storing outputs we "care about"?
outputs.insert((idx, out_idx), output);
});
self.answered += 1;
self.answered += 1;
// fill as much of the next request as we can.
if let Some(ref mut req) = self.requests.get_mut(self.answered) {
req.fill(|req_idx, out_idx| outputs.get(&(req_idx, out_idx)).cloned().ok_or(NoSuchOutput))
}
}
// fill as much of the next request as we can.
if let Some(ref mut req) = self.requests.get_mut(self.answered) {
req.fill(|req_idx, out_idx| {
outputs
.get(&(req_idx, out_idx))
.cloned()
.ok_or(NoSuchOutput)
})
}
}
}
impl<T: super::CheckedRequest + Clone> Batch<T> {
/// Supply a response for the next request.
/// Fails on: wrong request kind, all requests answered already.
pub fn supply_response(&mut self, env: &T::Environment, response: &T::Response)
-> Result<T::Extract, ResponseError<T::Error>>
{
let idx = self.answered;
/// Supply a response for the next request.
/// Fails on: wrong request kind, all requests answered already.
pub fn supply_response(
&mut self,
env: &T::Environment,
response: &T::Response,
) -> Result<T::Extract, ResponseError<T::Error>> {
let idx = self.answered;
// check validity.
if idx == self.requests.len() { return Err(ResponseError::Unexpected) }
let completed = self.next_complete()
.expect("only fails when all requests have been answered; this just checked against; qed");
// check validity.
if idx == self.requests.len() {
return Err(ResponseError::Unexpected);
}
let completed = self.next_complete().expect(
"only fails when all requests have been answered; this just checked against; qed",
);
let extracted = self.requests[idx]
.check_response(&completed, env, response).map_err(ResponseError::Validity)?;
let extracted = self.requests[idx]
.check_response(&completed, env, response)
.map_err(ResponseError::Validity)?;
self.supply_response_unchecked(response);
Ok(extracted)
}
self.supply_response_unchecked(response);
Ok(extracted)
}
}
impl Batch<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();
/// 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;
}
}
}
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
}
responses
}
}
impl<T: IncompleteRequest> Deref for Batch<T> {
type Target = [T];
type Target = [T];
fn deref(&self) -> &[T] {
&self.requests[..]
}
fn deref(&self) -> &[T] {
&self.requests[..]
}
}
impl<T: IncompleteRequest> DerefMut for Batch<T> {
fn deref_mut(&mut self) -> &mut [T] {
&mut self.requests[..]
}
fn deref_mut(&mut self) -> &mut [T] {
&mut self.requests[..]
}
}
#[cfg(test)]
mod tests {
use request::*;
use super::Builder;
use ethereum_types::H256;
use super::Builder;
use ethereum_types::H256;
use request::*;
#[test]
fn all_scalar() {
let mut builder = Builder::default();
builder.push(Request::HeaderProof(IncompleteHeaderProofRequest {
num: 100.into(),
})).unwrap();
builder.push(Request::Receipts(IncompleteReceiptsRequest {
hash: H256::default().into(),
})).unwrap();
}
#[test]
fn all_scalar() {
let mut builder = Builder::default();
builder
.push(Request::HeaderProof(IncompleteHeaderProofRequest {
num: 100.into(),
}))
.unwrap();
builder
.push(Request::Receipts(IncompleteReceiptsRequest {
hash: H256::default().into(),
}))
.unwrap();
}
#[test]
#[should_panic]
fn missing_backref() {
let mut builder = Builder::default();
builder.push(Request::HeaderProof(IncompleteHeaderProofRequest {
num: Field::BackReference(100, 3),
})).unwrap();
}
#[test]
#[should_panic]
fn missing_backref() {
let mut builder = Builder::default();
builder
.push(Request::HeaderProof(IncompleteHeaderProofRequest {
num: Field::BackReference(100, 3),
}))
.unwrap();
}
#[test]
#[should_panic]
fn wrong_kind() {
let mut builder = Builder::default();
assert!(builder.push(Request::HeaderProof(IncompleteHeaderProofRequest {
num: 100.into(),
})).is_ok());
builder.push(Request::HeaderProof(IncompleteHeaderProofRequest {
num: Field::BackReference(0, 0),
})).unwrap();
}
#[test]
#[should_panic]
fn wrong_kind() {
let mut builder = Builder::default();
assert!(builder
.push(Request::HeaderProof(IncompleteHeaderProofRequest {
num: 100.into(),
}))
.is_ok());
builder
.push(Request::HeaderProof(IncompleteHeaderProofRequest {
num: Field::BackReference(0, 0),
}))
.unwrap();
}
#[test]
fn good_backreference() {
let mut builder = Builder::default();
builder.push(Request::HeaderProof(IncompleteHeaderProofRequest {
num: 100.into(), // header proof puts hash at output 0.
})).unwrap();
builder.push(Request::Receipts(IncompleteReceiptsRequest {
hash: Field::BackReference(0, 0),
})).unwrap();
}
#[test]
fn good_backreference() {
let mut builder = Builder::default();
builder
.push(Request::HeaderProof(IncompleteHeaderProofRequest {
num: 100.into(), // header proof puts hash at output 0.
}))
.unwrap();
builder
.push(Request::Receipts(IncompleteReceiptsRequest {
hash: Field::BackReference(0, 0),
}))
.unwrap();
}
#[test]
fn batch_tx_index_backreference() {
let mut builder = Builder::default();
builder.push(Request::HeaderProof(IncompleteHeaderProofRequest {
num: 100.into(), // header proof puts hash at output 0.
})).unwrap();
builder.push(Request::TransactionIndex(IncompleteTransactionIndexRequest {
hash: Field::BackReference(0, 0),
})).unwrap();
#[test]
fn batch_tx_index_backreference() {
let mut builder = Builder::default();
builder
.push(Request::HeaderProof(IncompleteHeaderProofRequest {
num: 100.into(), // header proof puts hash at output 0.
}))
.unwrap();
builder
.push(Request::TransactionIndex(
IncompleteTransactionIndexRequest {
hash: Field::BackReference(0, 0),
},
))
.unwrap();
let mut batch = builder.build();
batch.requests[1].fill(|_req_idx, _out_idx| Ok(Output::Hash(42.into())));
let mut batch = builder.build();
batch.requests[1].fill(|_req_idx, _out_idx| Ok(Output::Hash(42.into())));
assert!(batch.next_complete().is_some());
batch.answered += 1;
assert!(batch.next_complete().is_some());
}
assert!(batch.next_complete().is_some());
batch.answered += 1;
assert!(batch.next_complete().is_some());
}
#[test]
fn batch_tx_index_backreference_public_api() {
let mut builder = Builder::default();
builder.push(Request::HeaderProof(IncompleteHeaderProofRequest {
num: 100.into(), // header proof puts hash at output 0.
})).unwrap();
builder.push(Request::TransactionIndex(IncompleteTransactionIndexRequest {
hash: Field::BackReference(0, 0),
})).unwrap();
#[test]
fn batch_tx_index_backreference_public_api() {
let mut builder = Builder::default();
builder
.push(Request::HeaderProof(IncompleteHeaderProofRequest {
num: 100.into(), // header proof puts hash at output 0.
}))
.unwrap();
builder
.push(Request::TransactionIndex(
IncompleteTransactionIndexRequest {
hash: Field::BackReference(0, 0),
},
))
.unwrap();
let mut batch = builder.build();
let mut batch = builder.build();
assert!(batch.next_complete().is_some());
let hdr_proof_res = header_proof::Response {
proof: vec![],
hash: 12.into(),
td: 21.into(),
};
batch.supply_response_unchecked(&hdr_proof_res);
assert!(batch.next_complete().is_some());
let hdr_proof_res = header_proof::Response {
proof: vec![],
hash: 12.into(),
td: 21.into(),
};
batch.supply_response_unchecked(&hdr_proof_res);
assert!(batch.next_complete().is_some());
}
assert!(batch.next_complete().is_some());
}
#[test]
fn batch_receipts_backreference() {
let mut builder = Builder::default();
builder.push(Request::HeaderProof(IncompleteHeaderProofRequest {
num: 100.into(), // header proof puts hash at output 0.
})).unwrap();
builder.push(Request::Receipts(IncompleteReceiptsRequest {
hash: Field::BackReference(0, 0),
})).unwrap();
#[test]
fn batch_receipts_backreference() {
let mut builder = Builder::default();
builder
.push(Request::HeaderProof(IncompleteHeaderProofRequest {
num: 100.into(), // header proof puts hash at output 0.
}))
.unwrap();
builder
.push(Request::Receipts(IncompleteReceiptsRequest {
hash: Field::BackReference(0, 0),
}))
.unwrap();
let mut batch = builder.build();
batch.requests[1].fill(|_req_idx, _out_idx| Ok(Output::Hash(42.into())));
let mut batch = builder.build();
batch.requests[1].fill(|_req_idx, _out_idx| Ok(Output::Hash(42.into())));
assert!(batch.next_complete().is_some());
batch.answered += 1;
assert!(batch.next_complete().is_some());
}
assert!(batch.next_complete().is_some());
batch.answered += 1;
assert!(batch.next_complete().is_some());
}
}

File diff suppressed because it is too large Load Diff