Merge pull request #3892 from ethcore/lightsync
Naive light client synchronization
This commit is contained in:
@@ -19,7 +19,8 @@ ethcore-io = { path = "../../util/io" }
|
||||
ethcore-ipc = { path = "../../ipc/rpc", optional = true }
|
||||
rlp = { path = "../../util/rlp" }
|
||||
time = "0.1"
|
||||
smallvec = "0.3.1"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
ipc = ["ethcore-ipc", "ethcore-ipc-codegen"]
|
||||
ipc = ["ethcore-ipc", "ethcore-ipc-codegen"]
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Light client implementation. Stores data from light sync
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use ethcore::engines::Engine;
|
||||
use ethcore::ids::BlockId;
|
||||
use ethcore::service::ClientIoMessage;
|
||||
use ethcore::block_import_error::BlockImportError;
|
||||
use ethcore::block_status::BlockStatus;
|
||||
use ethcore::verification::queue::{HeaderQueue, QueueInfo};
|
||||
use ethcore::transaction::{SignedTransaction, PendingTransaction};
|
||||
use ethcore::blockchain_info::BlockChainInfo;
|
||||
use ethcore::encoded;
|
||||
|
||||
use io::IoChannel;
|
||||
use util::hash::{H256, H256FastMap};
|
||||
use util::{Bytes, Mutex};
|
||||
|
||||
use provider::Provider;
|
||||
use request;
|
||||
|
||||
/// Light client implementation.
|
||||
pub struct Client {
|
||||
_engine: Arc<Engine>,
|
||||
header_queue: HeaderQueue,
|
||||
_message_channel: Mutex<IoChannel<ClientIoMessage>>,
|
||||
tx_pool: Mutex<H256FastMap<SignedTransaction>>,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
/// Import a header as rlp-encoded bytes.
|
||||
pub fn import_header(&self, bytes: Bytes) -> Result<H256, BlockImportError> {
|
||||
let header = ::rlp::decode(&bytes);
|
||||
|
||||
self.header_queue.import(header).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Whether the block is already known (but not necessarily part of the canonical chain)
|
||||
pub fn is_known(&self, _id: BlockId) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Import a local transaction.
|
||||
pub fn import_own_transaction(&self, tx: SignedTransaction) {
|
||||
self.tx_pool.lock().insert(tx.hash(), tx);
|
||||
}
|
||||
|
||||
/// Fetch a vector of all pending transactions.
|
||||
pub fn pending_transactions(&self) -> Vec<SignedTransaction> {
|
||||
self.tx_pool.lock().values().cloned().collect()
|
||||
}
|
||||
|
||||
/// Inquire about the status of a given block (or header).
|
||||
pub fn status(&self, _id: BlockId) -> BlockStatus {
|
||||
BlockStatus::Unknown
|
||||
}
|
||||
|
||||
/// Get the header queue info.
|
||||
pub fn queue_info(&self) -> QueueInfo {
|
||||
self.header_queue.queue_info()
|
||||
}
|
||||
}
|
||||
|
||||
// dummy implementation -- may draw from canonical cache further on.
|
||||
impl Provider for Client {
|
||||
fn chain_info(&self) -> BlockChainInfo {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn reorg_depth(&self, _a: &H256, _b: &H256) -> Option<u64> {
|
||||
None
|
||||
}
|
||||
|
||||
fn earliest_state(&self) -> Option<u64> {
|
||||
None
|
||||
}
|
||||
|
||||
fn block_header(&self, _id: BlockId) -> Option<encoded::Header> {
|
||||
None
|
||||
}
|
||||
|
||||
fn block_body(&self, _id: BlockId) -> Option<encoded::Body> {
|
||||
None
|
||||
}
|
||||
|
||||
fn block_receipts(&self, _hash: &H256) -> Option<Bytes> {
|
||||
None
|
||||
}
|
||||
|
||||
fn state_proof(&self, _req: request::StateProof) -> Vec<Bytes> {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
fn contract_code(&self, _req: request::ContractCode) -> Bytes {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
fn header_proof(&self, _req: request::HeaderProof) -> Option<(encoded::Header, Vec<Bytes>)> {
|
||||
None
|
||||
}
|
||||
|
||||
fn ready_transactions(&self) -> Vec<PendingTransaction> {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
22
ethcore/light/src/client/cht.rs
Normal file
22
ethcore/light/src/client/cht.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
//! Canonical hash trie definitions and helper functions.
|
||||
|
||||
/// The size of each CHT.
|
||||
pub const SIZE: u64 = 2048;
|
||||
|
||||
/// Convert a block number to a CHT number.
|
||||
pub fn block_to_cht_number(block_num: u64) -> u64 {
|
||||
(block_num + 1) / SIZE
|
||||
}
|
||||
365
ethcore/light/src/client/header_chain.rs
Normal file
365
ethcore/light/src/client/header_chain.rs
Normal file
@@ -0,0 +1,365 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Light client header chain.
|
||||
//!
|
||||
//! Unlike a full node's `BlockChain` this doesn't store much in the database.
|
||||
//! It stores candidates for the last 2048-4096 blocks as well as CHT roots for
|
||||
//! historical blocks all the way to the genesis.
|
||||
//!
|
||||
//! This is separate from the `BlockChain` for two reasons:
|
||||
//! - It stores only headers (and a pruned subset of them)
|
||||
//! - To allow for flexibility in the database layout once that's incorporated.
|
||||
// TODO: use DB instead of memory. DB Layout: just the contents of `candidates`/`headers`
|
||||
//
|
||||
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
|
||||
use client::cht;
|
||||
|
||||
use ethcore::block_status::BlockStatus;
|
||||
use ethcore::error::BlockError;
|
||||
use ethcore::ids::BlockId;
|
||||
use ethcore::views::HeaderView;
|
||||
use util::{Bytes, H256, U256, HeapSizeOf, Mutex, RwLock};
|
||||
|
||||
use smallvec::SmallVec;
|
||||
|
||||
/// Store at least this many candidate headers at all times.
|
||||
/// Also functions as the delay for computing CHTs as they aren't
|
||||
/// relevant to any blocks we've got in memory.
|
||||
const HISTORY: u64 = 2048;
|
||||
|
||||
/// Information about a block.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BlockDescriptor {
|
||||
/// The block's hash
|
||||
pub hash: H256,
|
||||
/// The block's number
|
||||
pub number: u64,
|
||||
/// The block's total difficulty.
|
||||
pub total_difficulty: U256,
|
||||
}
|
||||
|
||||
// candidate block description.
|
||||
struct Candidate {
|
||||
hash: H256,
|
||||
parent_hash: H256,
|
||||
total_difficulty: U256,
|
||||
}
|
||||
|
||||
struct Entry {
|
||||
candidates: SmallVec<[Candidate; 3]>, // 3 arbitrarily chosen
|
||||
canonical_hash: H256,
|
||||
}
|
||||
|
||||
impl HeapSizeOf for Entry {
|
||||
fn heap_size_of_children(&self) -> usize {
|
||||
match self.candidates.spilled() {
|
||||
false => 0,
|
||||
true => self.candidates.capacity() * ::std::mem::size_of::<Candidate>(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Header chain. See module docs for more details.
|
||||
pub struct HeaderChain {
|
||||
genesis_header: Bytes, // special-case the genesis.
|
||||
candidates: RwLock<BTreeMap<u64, Entry>>,
|
||||
headers: RwLock<HashMap<H256, Bytes>>,
|
||||
best_block: RwLock<BlockDescriptor>,
|
||||
cht_roots: Mutex<Vec<H256>>,
|
||||
}
|
||||
|
||||
impl HeaderChain {
|
||||
/// Create a new header chain given this genesis block.
|
||||
pub fn new(genesis: &[u8]) -> Self {
|
||||
let g_view = HeaderView::new(genesis);
|
||||
|
||||
HeaderChain {
|
||||
genesis_header: genesis.to_owned(),
|
||||
best_block: RwLock::new(BlockDescriptor {
|
||||
hash: g_view.hash(),
|
||||
number: 0,
|
||||
total_difficulty: g_view.difficulty(),
|
||||
}),
|
||||
candidates: RwLock::new(BTreeMap::new()),
|
||||
headers: RwLock::new(HashMap::new()),
|
||||
cht_roots: Mutex::new(Vec::new()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert a pre-verified header.
|
||||
///
|
||||
/// This blindly trusts that the data given to it is
|
||||
/// a) valid RLP encoding of a header and
|
||||
/// b) has sensible data contained within it.
|
||||
pub fn insert(&self, header: Bytes) -> Result<(), BlockError> {
|
||||
let view = HeaderView::new(&header);
|
||||
let hash = view.hash();
|
||||
let number = view.number();
|
||||
let parent_hash = view.parent_hash();
|
||||
|
||||
// hold candidates the whole time to guard import order.
|
||||
let mut candidates = self.candidates.write();
|
||||
|
||||
// find parent details.
|
||||
let parent_td =
|
||||
if number == 1 {
|
||||
let g_view = HeaderView::new(&self.genesis_header);
|
||||
g_view.difficulty()
|
||||
} else {
|
||||
candidates.get(&(number - 1))
|
||||
.and_then(|entry| entry.candidates.iter().find(|c| c.hash == parent_hash))
|
||||
.map(|c| c.total_difficulty)
|
||||
.ok_or_else(|| BlockError::UnknownParent(parent_hash))?
|
||||
};
|
||||
|
||||
let total_difficulty = parent_td + view.difficulty();
|
||||
|
||||
// insert headers and candidates entries.
|
||||
candidates.entry(number).or_insert_with(|| Entry { candidates: SmallVec::new(), canonical_hash: hash})
|
||||
.candidates.push(Candidate {
|
||||
hash: hash,
|
||||
parent_hash: parent_hash,
|
||||
total_difficulty: total_difficulty,
|
||||
});
|
||||
|
||||
self.headers.write().insert(hash, header.clone());
|
||||
|
||||
// reorganize ancestors so canonical entries are first in their
|
||||
// respective candidates vectors.
|
||||
if self.best_block.read().total_difficulty < total_difficulty {
|
||||
let mut canon_hash = hash;
|
||||
for (_, entry) in candidates.iter_mut().rev().skip_while(|&(height, _)| *height > number) {
|
||||
if entry.canonical_hash == canon_hash { break; }
|
||||
|
||||
let canon = entry.candidates.iter().find(|x| x.hash == canon_hash)
|
||||
.expect("blocks are only inserted if parent is present; or this is the block we just added; qed");
|
||||
|
||||
// what about reorgs > cht::SIZE + HISTORY?
|
||||
// resetting to the last block of a given CHT should be possible.
|
||||
canon_hash = canon.parent_hash;
|
||||
}
|
||||
|
||||
*self.best_block.write() = BlockDescriptor {
|
||||
hash: hash,
|
||||
number: number,
|
||||
total_difficulty: total_difficulty,
|
||||
};
|
||||
|
||||
// produce next CHT root if it's time.
|
||||
let earliest_era = *candidates.keys().next().expect("at least one era just created; qed");
|
||||
if earliest_era + HISTORY + cht::SIZE <= number {
|
||||
let mut values = Vec::with_capacity(cht::SIZE as usize);
|
||||
{
|
||||
let mut headers = self.headers.write();
|
||||
for i in (0..cht::SIZE).map(|x| x + earliest_era) {
|
||||
let era_entry = candidates.remove(&i)
|
||||
.expect("all eras are sequential with no gaps; qed");
|
||||
|
||||
for ancient in &era_entry.candidates {
|
||||
headers.remove(&ancient.hash);
|
||||
}
|
||||
|
||||
values.push((
|
||||
::rlp::encode(&i).to_vec(),
|
||||
::rlp::encode(&era_entry.canonical_hash).to_vec(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let cht_root = ::util::triehash::trie_root(values);
|
||||
debug!(target: "chain", "Produced CHT {} root: {:?}", (earliest_era - 1) % cht::SIZE, cht_root);
|
||||
|
||||
self.cht_roots.lock().push(cht_root);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get a block header. In the case of query by number, only canonical blocks
|
||||
/// will be returned.
|
||||
pub fn get_header(&self, id: BlockId) -> Option<Bytes> {
|
||||
match id {
|
||||
BlockId::Earliest | BlockId::Number(0) => Some(self.genesis_header.clone()),
|
||||
BlockId::Hash(hash) => self.headers.read().get(&hash).map(|x| x.to_vec()),
|
||||
BlockId::Number(num) => {
|
||||
if self.best_block.read().number < num { return None }
|
||||
|
||||
self.candidates.read().get(&num).map(|entry| entry.canonical_hash)
|
||||
.and_then(|hash| self.headers.read().get(&hash).map(|x| x.to_vec()))
|
||||
}
|
||||
BlockId::Latest | BlockId::Pending => {
|
||||
let hash = self.best_block.read().hash;
|
||||
self.headers.read().get(&hash).map(|x| x.to_vec())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the nth CHT root, if it's been computed.
|
||||
///
|
||||
/// CHT root 0 is from block `1..2048`.
|
||||
/// CHT root 1 is from block `2049..4096`
|
||||
/// and so on.
|
||||
///
|
||||
/// This is because it's assumed that the genesis hash is known,
|
||||
/// so including it within a CHT would be redundant.
|
||||
pub fn cht_root(&self, n: usize) -> Option<H256> {
|
||||
self.cht_roots.lock().get(n).map(|h| h.clone())
|
||||
}
|
||||
|
||||
/// Get the genesis hash.
|
||||
pub fn genesis_hash(&self) -> H256 {
|
||||
::util::Hashable::sha3(&self.genesis_header)
|
||||
}
|
||||
|
||||
/// Get the best block's data.
|
||||
pub fn best_block(&self) -> BlockDescriptor {
|
||||
self.best_block.read().clone()
|
||||
}
|
||||
|
||||
/// If there is a gap between the genesis and the rest
|
||||
/// of the stored blocks, return the first post-gap block.
|
||||
pub fn first_block(&self) -> Option<BlockDescriptor> {
|
||||
let candidates = self.candidates.read();
|
||||
match candidates.iter().next() {
|
||||
None | Some((&1, _)) => None,
|
||||
Some((&height, entry)) => Some(BlockDescriptor {
|
||||
number: height,
|
||||
hash: entry.canonical_hash,
|
||||
total_difficulty: entry.candidates.iter().find(|x| x.hash == entry.canonical_hash)
|
||||
.expect("entry always stores canonical candidate; qed").total_difficulty,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Get block status.
|
||||
pub fn status(&self, hash: &H256) -> BlockStatus {
|
||||
match self.headers.read().contains_key(hash) {
|
||||
true => BlockStatus::InChain,
|
||||
false => BlockStatus::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HeapSizeOf for HeaderChain {
|
||||
fn heap_size_of_children(&self) -> usize {
|
||||
self.candidates.read().heap_size_of_children() +
|
||||
self.headers.read().heap_size_of_children() +
|
||||
self.cht_roots.lock().heap_size_of_children()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::HeaderChain;
|
||||
use ethcore::ids::BlockId;
|
||||
use ethcore::header::Header;
|
||||
use ethcore::spec::Spec;
|
||||
|
||||
#[test]
|
||||
fn basic_chain() {
|
||||
let spec = Spec::new_test();
|
||||
let genesis_header = spec.genesis_header();
|
||||
|
||||
let chain = HeaderChain::new(&::rlp::encode(&genesis_header));
|
||||
|
||||
let mut parent_hash = genesis_header.hash();
|
||||
let mut rolling_timestamp = genesis_header.timestamp();
|
||||
for i in 1..10000 {
|
||||
let mut header = Header::new();
|
||||
header.set_parent_hash(parent_hash);
|
||||
header.set_number(i);
|
||||
header.set_timestamp(rolling_timestamp);
|
||||
header.set_difficulty(*genesis_header.difficulty() * i.into());
|
||||
|
||||
chain.insert(::rlp::encode(&header).to_vec()).unwrap();
|
||||
|
||||
parent_hash = header.hash();
|
||||
rolling_timestamp += 10;
|
||||
}
|
||||
|
||||
assert!(chain.get_header(BlockId::Number(10)).is_none());
|
||||
assert!(chain.get_header(BlockId::Number(9000)).is_some());
|
||||
assert!(chain.cht_root(2).is_some());
|
||||
assert!(chain.cht_root(3).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reorganize() {
|
||||
let spec = Spec::new_test();
|
||||
let genesis_header = spec.genesis_header();
|
||||
|
||||
let chain = HeaderChain::new(&::rlp::encode(&genesis_header));
|
||||
|
||||
let mut parent_hash = genesis_header.hash();
|
||||
let mut rolling_timestamp = genesis_header.timestamp();
|
||||
for i in 1..6 {
|
||||
let mut header = Header::new();
|
||||
header.set_parent_hash(parent_hash);
|
||||
header.set_number(i);
|
||||
header.set_timestamp(rolling_timestamp);
|
||||
header.set_difficulty(*genesis_header.difficulty() * i.into());
|
||||
|
||||
chain.insert(::rlp::encode(&header).to_vec()).unwrap();
|
||||
|
||||
parent_hash = header.hash();
|
||||
rolling_timestamp += 10;
|
||||
}
|
||||
|
||||
{
|
||||
let mut rolling_timestamp = rolling_timestamp;
|
||||
let mut parent_hash = parent_hash;
|
||||
for i in 6..16 {
|
||||
let mut header = Header::new();
|
||||
header.set_parent_hash(parent_hash);
|
||||
header.set_number(i);
|
||||
header.set_timestamp(rolling_timestamp);
|
||||
header.set_difficulty(*genesis_header.difficulty() * i.into());
|
||||
|
||||
chain.insert(::rlp::encode(&header).to_vec()).unwrap();
|
||||
|
||||
parent_hash = header.hash();
|
||||
rolling_timestamp += 10;
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(chain.best_block().number, 15);
|
||||
|
||||
{
|
||||
let mut rolling_timestamp = rolling_timestamp;
|
||||
let mut parent_hash = parent_hash;
|
||||
|
||||
// import a shorter chain which has better TD.
|
||||
for i in 6..13 {
|
||||
let mut header = Header::new();
|
||||
header.set_parent_hash(parent_hash);
|
||||
header.set_number(i);
|
||||
header.set_timestamp(rolling_timestamp);
|
||||
header.set_difficulty(*genesis_header.difficulty() * (i * i).into());
|
||||
|
||||
chain.insert(::rlp::encode(&header).to_vec()).unwrap();
|
||||
|
||||
parent_hash = header.hash();
|
||||
rolling_timestamp += 11;
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(chain.best_block().number, 12);
|
||||
}
|
||||
}
|
||||
264
ethcore/light/src/client/mod.rs
Normal file
264
ethcore/light/src/client/mod.rs
Normal file
@@ -0,0 +1,264 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Light client implementation. Stores data from light sync
|
||||
|
||||
use ethcore::block_import_error::BlockImportError;
|
||||
use ethcore::block_status::BlockStatus;
|
||||
use ethcore::client::ClientReport;
|
||||
use ethcore::ids::BlockId;
|
||||
use ethcore::header::Header;
|
||||
use ethcore::verification::queue::{self, HeaderQueue};
|
||||
use ethcore::transaction::PendingTransaction;
|
||||
use ethcore::blockchain_info::BlockChainInfo;
|
||||
use ethcore::spec::Spec;
|
||||
use ethcore::service::ClientIoMessage;
|
||||
use ethcore::encoded;
|
||||
use io::IoChannel;
|
||||
|
||||
use util::hash::{H256, H256FastMap};
|
||||
use util::{Bytes, Mutex, RwLock};
|
||||
|
||||
use provider::Provider;
|
||||
use request;
|
||||
|
||||
use self::header_chain::HeaderChain;
|
||||
|
||||
pub use self::service::Service;
|
||||
|
||||
pub mod cht;
|
||||
|
||||
mod header_chain;
|
||||
mod service;
|
||||
|
||||
/// Configuration for the light client.
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct Config {
|
||||
/// Verification queue config.
|
||||
pub queue: queue::Config,
|
||||
}
|
||||
|
||||
/// Trait for interacting with the header chain abstractly.
|
||||
pub trait LightChainClient: Send + Sync {
|
||||
/// Get chain info.
|
||||
fn chain_info(&self) -> BlockChainInfo;
|
||||
|
||||
/// Queue header to be verified. Required that all headers queued have their
|
||||
/// parent queued prior.
|
||||
fn queue_header(&self, header: Header) -> Result<H256, BlockImportError>;
|
||||
|
||||
/// Query whether a block is known.
|
||||
fn is_known(&self, hash: &H256) -> bool;
|
||||
|
||||
/// Clear the queue.
|
||||
fn clear_queue(&self);
|
||||
|
||||
/// Get queue info.
|
||||
fn queue_info(&self) -> queue::QueueInfo;
|
||||
|
||||
/// Get the `i`th CHT root.
|
||||
fn cht_root(&self, i: usize) -> Option<H256>;
|
||||
}
|
||||
|
||||
/// Light client implementation.
|
||||
pub struct Client {
|
||||
queue: HeaderQueue,
|
||||
chain: HeaderChain,
|
||||
tx_pool: Mutex<H256FastMap<PendingTransaction>>,
|
||||
report: RwLock<ClientReport>,
|
||||
import_lock: Mutex<()>,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
/// Create a new `Client`.
|
||||
pub fn new(config: Config, spec: &Spec, io_channel: IoChannel<ClientIoMessage>) -> Self {
|
||||
Client {
|
||||
queue: HeaderQueue::new(config.queue, spec.engine.clone(), io_channel, true),
|
||||
chain: HeaderChain::new(&::rlp::encode(&spec.genesis_header())),
|
||||
tx_pool: Mutex::new(Default::default()),
|
||||
report: RwLock::new(ClientReport::default()),
|
||||
import_lock: Mutex::new(()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Import a header to the queue for additional verification.
|
||||
pub fn import_header(&self, header: Header) -> Result<H256, BlockImportError> {
|
||||
self.queue.import(header).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Import a local transaction.
|
||||
pub fn import_own_transaction(&self, tx: PendingTransaction) {
|
||||
self.tx_pool.lock().insert(tx.transaction.hash(), tx);
|
||||
}
|
||||
|
||||
/// Fetch a vector of all pending transactions.
|
||||
pub fn ready_transactions(&self) -> Vec<PendingTransaction> {
|
||||
let best_num = self.chain.best_block().number;
|
||||
self.tx_pool.lock()
|
||||
.values()
|
||||
.filter(|t| t.min_block.as_ref().map_or(true, |x| x <= &best_num))
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Inquire about the status of a given header.
|
||||
pub fn status(&self, hash: &H256) -> BlockStatus {
|
||||
match self.queue.status(hash) {
|
||||
queue::Status::Unknown => self.chain.status(hash),
|
||||
other => other.into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the chain info.
|
||||
pub fn chain_info(&self) -> BlockChainInfo {
|
||||
let best_block = self.chain.best_block();
|
||||
let first_block = self.chain.first_block();
|
||||
let genesis_hash = self.chain.genesis_hash();
|
||||
|
||||
BlockChainInfo {
|
||||
total_difficulty: best_block.total_difficulty,
|
||||
pending_total_difficulty: best_block.total_difficulty,
|
||||
genesis_hash: genesis_hash,
|
||||
best_block_hash: best_block.hash,
|
||||
best_block_number: best_block.number,
|
||||
ancient_block_hash: if first_block.is_some() { Some(genesis_hash) } else { None },
|
||||
ancient_block_number: if first_block.is_some() { Some(0) } else { None },
|
||||
first_block_hash: first_block.as_ref().map(|first| first.hash),
|
||||
first_block_number: first_block.as_ref().map(|first| first.number),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the header queue info.
|
||||
pub fn queue_info(&self) -> queue::QueueInfo {
|
||||
self.queue.queue_info()
|
||||
}
|
||||
|
||||
/// Get a block header by Id.
|
||||
pub fn get_header(&self, id: BlockId) -> Option<Bytes> {
|
||||
self.chain.get_header(id)
|
||||
}
|
||||
|
||||
/// Get the `i`th CHT root.
|
||||
pub fn cht_root(&self, i: usize) -> Option<H256> {
|
||||
self.chain.cht_root(i)
|
||||
}
|
||||
|
||||
/// Import a set of pre-verified headers from the queue.
|
||||
pub fn import_verified(&self) {
|
||||
const MAX: usize = 256;
|
||||
|
||||
let _lock = self.import_lock.lock();
|
||||
|
||||
let mut bad = Vec::new();
|
||||
let mut good = Vec::new();
|
||||
for verified_header in self.queue.drain(MAX) {
|
||||
let (num, hash) = (verified_header.number(), verified_header.hash());
|
||||
|
||||
match self.chain.insert(::rlp::encode(&verified_header).to_vec()) {
|
||||
Ok(()) => {
|
||||
good.push(hash);
|
||||
self.report.write().blocks_imported += 1;
|
||||
}
|
||||
Err(e) => {
|
||||
debug!(target: "client", "Error importing header {:?}: {}", (num, hash), e);
|
||||
bad.push(hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.queue.mark_as_bad(&bad);
|
||||
self.queue.mark_as_good(&good);
|
||||
}
|
||||
|
||||
/// Get a report about blocks imported.
|
||||
pub fn report(&self) -> ClientReport {
|
||||
::std::mem::replace(&mut *self.report.write(), ClientReport::default())
|
||||
}
|
||||
|
||||
/// Get blockchain mem usage in bytes.
|
||||
pub fn chain_mem_used(&self) -> usize {
|
||||
use util::HeapSizeOf;
|
||||
|
||||
self.chain.heap_size_of_children()
|
||||
}
|
||||
}
|
||||
|
||||
impl LightChainClient for Client {
|
||||
fn chain_info(&self) -> BlockChainInfo { Client::chain_info(self) }
|
||||
|
||||
fn queue_header(&self, header: Header) -> Result<H256, BlockImportError> {
|
||||
self.import_header(header)
|
||||
}
|
||||
|
||||
fn is_known(&self, hash: &H256) -> bool {
|
||||
self.status(hash) == BlockStatus::InChain
|
||||
}
|
||||
|
||||
fn clear_queue(&self) {
|
||||
self.queue.clear()
|
||||
}
|
||||
|
||||
fn queue_info(&self) -> queue::QueueInfo {
|
||||
self.queue.queue_info()
|
||||
}
|
||||
|
||||
fn cht_root(&self, i: usize) -> Option<H256> {
|
||||
Client::cht_root(self, i)
|
||||
}
|
||||
}
|
||||
|
||||
// dummy implementation -- may draw from canonical cache further on.
|
||||
impl Provider for Client {
|
||||
fn chain_info(&self) -> BlockChainInfo {
|
||||
Client::chain_info(self)
|
||||
}
|
||||
|
||||
fn reorg_depth(&self, _a: &H256, _b: &H256) -> Option<u64> {
|
||||
None
|
||||
}
|
||||
|
||||
fn earliest_state(&self) -> Option<u64> {
|
||||
None
|
||||
}
|
||||
|
||||
fn block_header(&self, id: BlockId) -> Option<encoded::Header> {
|
||||
self.chain.get_header(id).map(encoded::Header::new)
|
||||
}
|
||||
|
||||
fn block_body(&self, _id: BlockId) -> Option<encoded::Body> {
|
||||
None
|
||||
}
|
||||
|
||||
fn block_receipts(&self, _hash: &H256) -> Option<Bytes> {
|
||||
None
|
||||
}
|
||||
|
||||
fn state_proof(&self, _req: request::StateProof) -> Vec<Bytes> {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
fn contract_code(&self, _req: request::ContractCode) -> Bytes {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
fn header_proof(&self, _req: request::HeaderProof) -> Option<(encoded::Header, Vec<Bytes>)> {
|
||||
None
|
||||
}
|
||||
|
||||
fn ready_transactions(&self) -> Vec<PendingTransaction> {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
73
ethcore/light/src/client/service.rs
Normal file
73
ethcore/light/src/client/service.rs
Normal file
@@ -0,0 +1,73 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Minimal IO service for light client.
|
||||
//! Just handles block import messages and passes them to the client.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use ethcore::service::ClientIoMessage;
|
||||
use ethcore::spec::Spec;
|
||||
use io::{IoContext, IoError, IoHandler, IoService};
|
||||
|
||||
use super::{Client, Config as ClientConfig};
|
||||
|
||||
/// Light client service.
|
||||
pub struct Service {
|
||||
client: Arc<Client>,
|
||||
_io_service: IoService<ClientIoMessage>,
|
||||
}
|
||||
|
||||
impl Service {
|
||||
/// Start the service: initialize I/O workers and client itself.
|
||||
pub fn start(config: ClientConfig, spec: &Spec) -> Result<Self, IoError> {
|
||||
let io_service = try!(IoService::<ClientIoMessage>::start());
|
||||
let client = Arc::new(Client::new(config, spec, io_service.channel()));
|
||||
try!(io_service.register_handler(Arc::new(ImportBlocks(client.clone()))));
|
||||
|
||||
Ok(Service {
|
||||
client: client,
|
||||
_io_service: io_service,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get a handle to the client.
|
||||
pub fn client(&self) -> &Arc<Client> {
|
||||
&self.client
|
||||
}
|
||||
}
|
||||
|
||||
struct ImportBlocks(Arc<Client>);
|
||||
|
||||
impl IoHandler<ClientIoMessage> for ImportBlocks {
|
||||
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;
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let spec = Spec::new_test();
|
||||
Service::start(Default::default(), &spec).unwrap();
|
||||
}
|
||||
}
|
||||
@@ -25,8 +25,10 @@
|
||||
//! low-latency applications, but perfectly suitable for simple everyday
|
||||
//! use-cases like sending transactions from a personal account.
|
||||
//!
|
||||
//! It starts by performing a header-only sync, verifying random samples
|
||||
//! of members of the chain to varying degrees.
|
||||
//! The light client performs a header-only sync, doing verification and pruning
|
||||
//! historical blocks. Upon pruning, batches of 2048 blocks have a number => hash
|
||||
//! mapping sealed into "canonical hash tries" which can later be used to verify
|
||||
//! historical block queries from peers.
|
||||
|
||||
#![deny(missing_docs)]
|
||||
|
||||
@@ -60,7 +62,8 @@ extern crate ethcore_util as util;
|
||||
extern crate ethcore_network as network;
|
||||
extern crate ethcore_io as io;
|
||||
extern crate rlp;
|
||||
extern crate smallvec;
|
||||
extern crate time;
|
||||
|
||||
#[cfg(feature = "ipc")]
|
||||
extern crate ethcore_ipc as ipc;
|
||||
extern crate ethcore_ipc as ipc;
|
||||
|
||||
@@ -20,7 +20,7 @@ use network::{NetworkContext, PeerId, NodeId};
|
||||
|
||||
use super::{Announcement, LightProtocol, ReqId};
|
||||
use super::error::Error;
|
||||
use request::Request;
|
||||
use request::{self, Request};
|
||||
|
||||
/// An I/O context which allows sending and receiving packets as well as
|
||||
/// disconnecting peers. This is used as a generalization of the portions
|
||||
@@ -77,12 +77,8 @@ impl<'a> IoContext for NetworkContext<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Context for a protocol event.
|
||||
pub trait EventContext {
|
||||
/// Get the peer relevant to the event e.g. message sender,
|
||||
/// disconnected/connected peer.
|
||||
fn peer(&self) -> PeerId;
|
||||
|
||||
/// Basic context for a the protocol.
|
||||
pub trait BasicContext {
|
||||
/// Returns the relevant's peer persistent Id (aka NodeId).
|
||||
fn persistent_peer_id(&self, peer: PeerId) -> Option<NodeId>;
|
||||
|
||||
@@ -93,6 +89,10 @@ pub trait EventContext {
|
||||
// TODO: maybe just put this on a timer in LightProtocol?
|
||||
fn make_announcement(&self, announcement: Announcement);
|
||||
|
||||
/// Find the maximum number of requests of a specific type which can be made from
|
||||
/// supplied peer.
|
||||
fn max_requests(&self, peer: PeerId, kind: request::Kind) -> usize;
|
||||
|
||||
/// Disconnect a peer.
|
||||
fn disconnect_peer(&self, peer: PeerId);
|
||||
|
||||
@@ -100,6 +100,50 @@ pub trait EventContext {
|
||||
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;
|
||||
|
||||
/// 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,
|
||||
}
|
||||
|
||||
impl<'a> BasicContext for TickCtx<'a> {
|
||||
fn persistent_peer_id(&self, id: PeerId) -> Option<NodeId> {
|
||||
self.io.persistent_peer_id(id)
|
||||
}
|
||||
|
||||
fn request_from(&self, peer: PeerId, request: Request) -> Result<ReqId, Error> {
|
||||
self.proto.request_from(self.io, &peer, request)
|
||||
}
|
||||
|
||||
fn make_announcement(&self, announcement: Announcement) {
|
||||
self.proto.make_announcement(self.io, announcement);
|
||||
}
|
||||
|
||||
fn max_requests(&self, peer: PeerId, kind: request::Kind) -> usize {
|
||||
self.proto.max_requests(peer, kind)
|
||||
}
|
||||
|
||||
fn disconnect_peer(&self, peer: PeerId) {
|
||||
self.io.disconnect_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> {
|
||||
@@ -111,11 +155,7 @@ pub struct Ctx<'a> {
|
||||
pub peer: PeerId,
|
||||
}
|
||||
|
||||
impl<'a> EventContext for Ctx<'a> {
|
||||
fn peer(&self) -> PeerId {
|
||||
self.peer
|
||||
}
|
||||
|
||||
impl<'a> BasicContext for Ctx<'a> {
|
||||
fn persistent_peer_id(&self, id: PeerId) -> Option<NodeId> {
|
||||
self.io.persistent_peer_id(id)
|
||||
}
|
||||
@@ -128,6 +168,10 @@ impl<'a> EventContext for Ctx<'a> {
|
||||
self.proto.make_announcement(self.io, announcement);
|
||||
}
|
||||
|
||||
fn max_requests(&self, peer: PeerId, kind: request::Kind) -> usize {
|
||||
self.proto.max_requests(peer, kind)
|
||||
}
|
||||
|
||||
fn disconnect_peer(&self, peer: PeerId) {
|
||||
self.io.disconnect_peer(peer);
|
||||
}
|
||||
@@ -136,3 +180,13 @@ impl<'a> EventContext for Ctx<'a> {
|
||||
self.io.disable_peer(peer);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> EventContext for Ctx<'a> {
|
||||
fn peer(&self) -> PeerId {
|
||||
self.peer
|
||||
}
|
||||
|
||||
fn as_basic(&self) -> &BasicContext {
|
||||
&*self
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +62,8 @@ pub enum Error {
|
||||
UnsupportedProtocolVersion(u8),
|
||||
/// Bad protocol version.
|
||||
BadProtocolVersion,
|
||||
/// Peer is overburdened.
|
||||
Overburdened,
|
||||
}
|
||||
|
||||
impl Error {
|
||||
@@ -79,6 +81,7 @@ impl Error {
|
||||
Error::NotServer => Punishment::Disable,
|
||||
Error::UnsupportedProtocolVersion(_) => Punishment::Disable,
|
||||
Error::BadProtocolVersion => Punishment::Disable,
|
||||
Error::Overburdened => Punishment::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -107,8 +110,9 @@ impl fmt::Display for Error {
|
||||
Error::UnknownPeer => write!(f, "Unknown peer"),
|
||||
Error::UnsolicitedResponse => write!(f, "Peer provided unsolicited data"),
|
||||
Error::NotServer => write!(f, "Peer not a server."),
|
||||
Error::UnsupportedProtocolVersion(pv) => write!(f, "Unsupported protocol version: {}", pv),
|
||||
Error::UnsupportedProtocolVersion(pv) => write!(f, "Unsupported protocol version: {}", pv),
|
||||
Error::BadProtocolVersion => write!(f, "Bad protocol version in handshake"),
|
||||
Error::Overburdened => write!(f, "Peer overburdened"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,8 +37,8 @@ use provider::Provider;
|
||||
use request::{self, HashOrNumber, Request};
|
||||
|
||||
use self::buffer_flow::{Buffer, FlowParams};
|
||||
use self::context::Ctx;
|
||||
use self::error::{Error, Punishment};
|
||||
use self::context::{Ctx, TickCtx};
|
||||
use self::error::Punishment;
|
||||
|
||||
mod buffer_flow;
|
||||
mod context;
|
||||
@@ -48,12 +48,16 @@ mod status;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub use self::context::{EventContext, IoContext};
|
||||
pub use self::error::Error;
|
||||
pub use self::context::{BasicContext, EventContext, IoContext};
|
||||
pub use self::status::{Status, Capabilities, Announcement};
|
||||
|
||||
const TIMEOUT: TimerToken = 0;
|
||||
const TIMEOUT_INTERVAL_MS: u64 = 1000;
|
||||
|
||||
const TICK_TIMEOUT: TimerToken = 1;
|
||||
const TICK_TIMEOUT_INTERVAL_MS: u64 = 5000;
|
||||
|
||||
// minimum interval between updates.
|
||||
const UPDATE_INTERVAL_MS: i64 = 5000;
|
||||
|
||||
@@ -131,8 +135,9 @@ struct Peer {
|
||||
status: Status,
|
||||
capabilities: Capabilities,
|
||||
remote_flow: Option<(Buffer, FlowParams)>,
|
||||
sent_head: H256, // last head we've given them.
|
||||
sent_head: H256, // last chain head we've given them.
|
||||
last_update: SteadyTime,
|
||||
idle: bool, // make into a current percentage of max buffer being requested?
|
||||
}
|
||||
|
||||
impl Peer {
|
||||
@@ -188,7 +193,11 @@ pub trait Handler: Send + Sync {
|
||||
/// Called when a peer responds with header proofs. Each proof is a block header coupled
|
||||
/// with a series of trie nodes is ascending order by distance from the root.
|
||||
fn on_header_proofs(&self, _ctx: &EventContext, _req_id: ReqId, _proofs: &[(Bytes, Vec<Bytes>)]) { }
|
||||
/// Called on abort.
|
||||
/// Called to "tick" the handler periodically.
|
||||
fn tick(&self, _ctx: &BasicContext) { }
|
||||
/// Called on abort. This signals to handlers that they should clean up
|
||||
/// and ignore peers.
|
||||
// TODO: coreresponding `on_activate`?
|
||||
fn on_abort(&self) { }
|
||||
}
|
||||
|
||||
@@ -253,18 +262,25 @@ impl LightProtocol {
|
||||
}
|
||||
|
||||
/// Check the maximum amount of requests of a specific type
|
||||
/// which a peer would be able to serve.
|
||||
pub fn max_requests(&self, peer: PeerId, kind: request::Kind) -> Option<usize> {
|
||||
/// which a peer would be able to serve. Returns zero if the
|
||||
/// peer is unknown or has no buffer flow parameters.
|
||||
fn max_requests(&self, peer: PeerId, kind: request::Kind) -> usize {
|
||||
self.peers.read().get(&peer).and_then(|peer| {
|
||||
let mut peer = peer.lock();
|
||||
match peer.remote_flow.as_mut() {
|
||||
Some(&mut (ref mut buf, ref flow)) => {
|
||||
let idle = peer.idle;
|
||||
match peer.remote_flow {
|
||||
Some((ref mut buf, ref flow)) => {
|
||||
flow.recharge(buf);
|
||||
Some(flow.max_amount(&*buf, kind))
|
||||
|
||||
if !idle {
|
||||
Some(0)
|
||||
} else {
|
||||
Some(flow.max_amount(&*buf, kind))
|
||||
}
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
})
|
||||
}).unwrap_or(0)
|
||||
}
|
||||
|
||||
/// Make a request to a peer.
|
||||
@@ -278,8 +294,10 @@ impl LightProtocol {
|
||||
let peer = peers.get(peer_id).ok_or_else(|| Error::UnknownPeer)?;
|
||||
let mut peer = peer.lock();
|
||||
|
||||
match peer.remote_flow.as_mut() {
|
||||
Some(&mut (ref mut buf, ref flow)) => {
|
||||
if !peer.idle { return Err(Error::Overburdened) }
|
||||
|
||||
match peer.remote_flow {
|
||||
Some((ref mut buf, ref flow)) => {
|
||||
flow.recharge(buf);
|
||||
let max = flow.compute_cost(request.kind(), request.amount());
|
||||
buf.deduct_cost(max)?;
|
||||
@@ -290,6 +308,8 @@ impl LightProtocol {
|
||||
let req_id = self.req_id.fetch_add(1, Ordering::SeqCst);
|
||||
let packet_data = encode_request(&request, req_id);
|
||||
|
||||
trace!(target: "les", "Dispatching request {} to peer {}", req_id, peer_id);
|
||||
|
||||
let packet_id = match request.kind() {
|
||||
request::Kind::Headers => packet::GET_BLOCK_HEADERS,
|
||||
request::Kind::Bodies => packet::GET_BLOCK_BODIES,
|
||||
@@ -301,6 +321,7 @@ impl LightProtocol {
|
||||
|
||||
io.send(*peer_id, packet_id, packet_data);
|
||||
|
||||
peer.idle = false;
|
||||
self.pending_requests.write().insert(req_id, Requested {
|
||||
request: request,
|
||||
timestamp: SteadyTime::now(),
|
||||
@@ -404,6 +425,8 @@ impl LightProtocol {
|
||||
match peers.get(peer) {
|
||||
Some(peer_info) => {
|
||||
let mut peer_info = peer_info.lock();
|
||||
peer_info.idle = true;
|
||||
|
||||
match peer_info.remote_flow.as_mut() {
|
||||
Some(&mut (ref mut buf, ref mut flow)) => {
|
||||
let actual_buffer = ::std::cmp::min(cur_buffer, *flow.limit());
|
||||
@@ -505,6 +528,15 @@ impl LightProtocol {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn tick_handlers(&self, io: &IoContext) {
|
||||
for handler in &self.handlers {
|
||||
handler.tick(&TickCtx {
|
||||
io: io,
|
||||
proto: self,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LightProtocol {
|
||||
@@ -603,6 +635,7 @@ impl LightProtocol {
|
||||
remote_flow: remote_flow,
|
||||
sent_head: pending.sent_head,
|
||||
last_update: pending.last_update,
|
||||
idle: true,
|
||||
}));
|
||||
|
||||
for handler in &self.handlers {
|
||||
@@ -1123,7 +1156,10 @@ fn punish(peer: PeerId, io: &IoContext, e: Error) {
|
||||
|
||||
impl NetworkProtocolHandler for LightProtocol {
|
||||
fn initialize(&self, io: &NetworkContext) {
|
||||
io.register_timer(TIMEOUT, TIMEOUT_INTERVAL_MS).expect("Error registering sync timer.");
|
||||
io.register_timer(TIMEOUT, TIMEOUT_INTERVAL_MS)
|
||||
.expect("Error registering sync timer.");
|
||||
io.register_timer(TICK_TIMEOUT, TICK_TIMEOUT_INTERVAL_MS)
|
||||
.expect("Error registering sync timer.");
|
||||
}
|
||||
|
||||
fn read(&self, io: &NetworkContext, peer: &PeerId, packet_id: u8, data: &[u8]) {
|
||||
@@ -1141,6 +1177,7 @@ impl NetworkProtocolHandler for LightProtocol {
|
||||
fn timeout(&self, io: &NetworkContext, timer: TimerToken) {
|
||||
match timer {
|
||||
TIMEOUT => self.timeout_check(io),
|
||||
TICK_TIMEOUT => self.tick_handlers(io),
|
||||
_ => warn!(target: "les", "received timeout on unknown token {}", timer),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -562,4 +562,4 @@ mod tests {
|
||||
assert_eq!(read_capabilities, capabilities);
|
||||
assert!(read_flow.is_none());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user