From e3c469527489dc0ad2d8bf6afe80d2dad481daa0 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Mon, 19 Sep 2016 11:29:50 +0200 Subject: [PATCH 01/37] stub implementations of light client trait --- ethcore/src/light/client.rs | 95 +++++++++++++++++++++++++++++++++++++ ethcore/src/light/mod.rs | 18 +++++++ util/fetch/src/lib.rs | 2 +- 3 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 ethcore/src/light/client.rs create mode 100644 ethcore/src/light/mod.rs diff --git a/ethcore/src/light/client.rs b/ethcore/src/light/client.rs new file mode 100644 index 000000000..de79ad922 --- /dev/null +++ b/ethcore/src/light/client.rs @@ -0,0 +1,95 @@ +// Copyright 2015, 2016 Ethcore (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 . + +//! Light client implementation. Used for raw data queries as well as the header +//! sync. + +use std::sync::Arc; + +use engines::Engine; +use ids::BlockID; +use miner::TransactionQueue; +use service::ClientIoMessage; +use block_import_error::BlockImportError; +use block_status::BlockStatus; +use verification::queue::{Config as QueueConfig, HeaderQueue, QueueInfo, Status}; +use transaction::SignedTransaction; + +use super::provider::{CHTProofRequest, Provider, ProofRequest}; + +use io::IoChannel; +use util::hash::H256; +use util::Bytes; + +/// A light client. +pub struct Client { + engine: Arc, + header_queue: HeaderQueue, + message_channel: IoChannel, + transaction_queue: TransactionQueue, +} + +impl Client { + /// Import a header as rlp-encoded bytes. + fn import_header(&self, bytes: Bytes) -> Result { + 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) + fn is_known(&self, id: BlockID) -> bool { + false + } + + /// Fetch a vector of all pending transactions. + fn pending_transactions(&self) -> Vec { + self.transaction_queue.top_transactions() + } + + /// Inquire about the status of a given block. + fn status(&self, id: BlockID) -> BlockStatus { + BlockStatus::Unknown + } +} + +// stub implementation, can be partially implemented using LRU-caches +// but will almost definitely never be able to provide complete responses. +impl Provider for Client { + fn block_headers(&self, _block: H256, _skip: usize, _max: usize, _reverse: bool) -> Vec { + vec![] + } + + fn block_bodies(&self, _blocks: Vec) -> Vec { + vec![] + } + + fn receipts(&self, _blocks: Vec) -> Vec { + vec![] + } + + fn proofs(&self, _requests: Vec<(H256, ProofRequest)>) -> Vec { + vec![] + } + + fn code(&self, _accounts: Vec<(H256, H256)>) -> Vec { + vec![] + } + + fn header_proofs(&self, _requests: Vec) -> Vec { + vec![] + } +} \ No newline at end of file diff --git a/ethcore/src/light/mod.rs b/ethcore/src/light/mod.rs new file mode 100644 index 000000000..100548b46 --- /dev/null +++ b/ethcore/src/light/mod.rs @@ -0,0 +1,18 @@ +//! Light client logic and implementation. +//! +//! A "light" client stores very little chain-related data locally +//! unlike a full node, which stores all blocks, headers, receipts, and more. +//! +//! This enables the client to have a much lower resource footprint in +//! exchange for the cost of having to ask the network for state data +//! while responding to queries. This makes a light client unsuitable for +//! 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 every header in +//! the chain. + +mod provider; +mod client; + +pub use self::provider::{CHTProofRequest, ProofRequest, Provider}; \ No newline at end of file diff --git a/util/fetch/src/lib.rs b/util/fetch/src/lib.rs index 8ec9e0ddd..7ab38604b 100644 --- a/util/fetch/src/lib.rs +++ b/util/fetch/src/lib.rs @@ -26,4 +26,4 @@ extern crate rand; pub mod client; pub mod fetch_file; -pub use self::client::{Client, Fetch, FetchError, FetchResult}; +pub use self::client::{Client, Fetch, FetchError, FetchResult}; \ No newline at end of file From ed06572bd472219afc2f2f5bda9240eb4261a5ef Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Wed, 14 Sep 2016 19:23:29 +0200 Subject: [PATCH 02/37] Light provider trait --- ethcore/src/light/mod.rs | 2 +- ethcore/src/light/provider.rs | 57 ++++++++++++++++++++++++++++++ ethcore/src/types/mod.rs.in | 1 + ethcore/src/types/proof_request.rs | 44 +++++++++++++++++++++++ 4 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 ethcore/src/light/provider.rs create mode 100644 ethcore/src/types/proof_request.rs diff --git a/ethcore/src/light/mod.rs b/ethcore/src/light/mod.rs index 100548b46..32d4e9d8a 100644 --- a/ethcore/src/light/mod.rs +++ b/ethcore/src/light/mod.rs @@ -15,4 +15,4 @@ mod provider; mod client; -pub use self::provider::{CHTProofRequest, ProofRequest, Provider}; \ No newline at end of file +pub use self::provider::{CHTProofRequest, ProofRequest, Provider}; diff --git a/ethcore/src/light/provider.rs b/ethcore/src/light/provider.rs new file mode 100644 index 000000000..4833184eb --- /dev/null +++ b/ethcore/src/light/provider.rs @@ -0,0 +1,57 @@ +// Copyright 2015, 2016 Ethcore (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 . + +//! A provider for the LES protocol. This is typically a full node, who can +//! give as much data as necessary to its peers. + +pub use proof_request::{CHTProofRequest, ProofRequest}; + +use util::Bytes; +use util::hash::H256; + +/// Defines the operations that a provider for `LES` must fulfill. +/// +/// These are defined at [1], but may be subject to change. +/// +/// [1]: https://github.com/ethcore/parity/wiki/Light-Ethereum-Subprotocol-(LES) +pub trait Provider { + /// 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, block: H256, skip: usize, max: usize, reverse: bool) -> Vec; + + /// Provide as many as possible of the requested blocks (minus the headers) encoded + /// in RLP format. + fn block_bodies(&self, blocks: Vec) -> Vec; + + /// Provide the receipts as many as possible of the requested blocks. + /// Returns a vector of RLP-encoded lists of receipts. + fn receipts(&self, blocks: Vec) -> Vec; + + /// Provide a set of merkle proofs, as requested. Each request is a + /// block hash and request parameters. + /// + /// Returns a vector to RLP-encoded lists satisfying the requests. + fn proofs(&self, requests: Vec<(H256, ProofRequest)>) -> Vec; + + /// Provide contract code for the specified (block_hash, account_hash) pairs. + fn code(&self, accounts: Vec<(H256, H256)>) -> Vec; + + /// Provide header proofs from the Canonical Hash Tries. + fn header_proofs(&self, requests: Vec) -> Vec; +} \ No newline at end of file diff --git a/ethcore/src/types/mod.rs.in b/ethcore/src/types/mod.rs.in index 32c7faabe..c2537135d 100644 --- a/ethcore/src/types/mod.rs.in +++ b/ethcore/src/types/mod.rs.in @@ -33,3 +33,4 @@ pub mod transaction_import; pub mod block_import_error; pub mod restoration_status; pub mod snapshot_manifest; +pub mod proof_request; \ No newline at end of file diff --git a/ethcore/src/types/proof_request.rs b/ethcore/src/types/proof_request.rs new file mode 100644 index 000000000..20bddce21 --- /dev/null +++ b/ethcore/src/types/proof_request.rs @@ -0,0 +1,44 @@ +// Copyright 2015, 2016 Ethcore (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 . + +//! Merkle proof request. +use util::hash::H256; + +/// A request for a state merkle proof. +#[derive(Debug, Clone, PartialEq, Eq, Binary)] +pub enum ProofRequest { + /// Request a proof of the given account's (denoted by sha3(address)) + /// node in the state trie. Nodes with depth less than the second item + /// may be omitted. + Account(H256, usize), + + /// Request a proof for a key in the given account's storage trie. + /// Both values are hashes of their actual values. Nodes with depth + /// less than the third item may be omitted. + Storage(H256, H256, usize), +} + +/// A request for a Canonical Hash Trie proof for the given block number. +/// Nodes with depth less than the second item may be omitted. +#[derive(Debug, Clone, PartialEq, Eq, Binary)] +pub struct CHTProofRequest { + /// The number of the block the proof is requested for. + /// The CHT's number can be deduced from this (`number` / 4096) + pub number: u64, + + /// Nodes with depth less than this can be omitted from the proof. + pub depth: usize, +} \ No newline at end of file From 8d7244c09fe2c796b62f62863fab6627bca964df Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Wed, 5 Oct 2016 15:35:31 +0200 Subject: [PATCH 03/37] light client sync stubs --- ethcore/src/light/client.rs | 40 ++++++++----------------------------- ethcore/src/light/mod.rs | 1 + sync/src/lib.rs | 1 + sync/src/light/mod.rs | 27 +++++++++++++++++++++++++ sync/src/sync_io.rs | 4 ++-- 5 files changed, 39 insertions(+), 34 deletions(-) create mode 100644 sync/src/light/mod.rs diff --git a/ethcore/src/light/client.rs b/ethcore/src/light/client.rs index de79ad922..18635aa44 100644 --- a/ethcore/src/light/client.rs +++ b/ethcore/src/light/client.rs @@ -39,57 +39,33 @@ pub struct Client { engine: Arc, header_queue: HeaderQueue, message_channel: IoChannel, - transaction_queue: TransactionQueue, } impl Client { /// Import a header as rlp-encoded bytes. - fn import_header(&self, bytes: Bytes) -> Result { + pub fn import_header(&self, bytes: Bytes) -> Result { 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) - fn is_known(&self, id: BlockID) -> bool { + pub fn is_known(&self, id: BlockID) -> bool { false } /// Fetch a vector of all pending transactions. - fn pending_transactions(&self) -> Vec { - self.transaction_queue.top_transactions() + pub fn pending_transactions(&self) -> Vec { + vec![] } /// Inquire about the status of a given block. - fn status(&self, id: BlockID) -> BlockStatus { + pub fn status(&self, id: BlockID) -> BlockStatus { BlockStatus::Unknown } -} -// stub implementation, can be partially implemented using LRU-caches -// but will almost definitely never be able to provide complete responses. -impl Provider for Client { - fn block_headers(&self, _block: H256, _skip: usize, _max: usize, _reverse: bool) -> Vec { - vec![] - } - - fn block_bodies(&self, _blocks: Vec) -> Vec { - vec![] - } - - fn receipts(&self, _blocks: Vec) -> Vec { - vec![] - } - - fn proofs(&self, _requests: Vec<(H256, ProofRequest)>) -> Vec { - vec![] - } - - fn code(&self, _accounts: Vec<(H256, H256)>) -> Vec { - vec![] - } - - fn header_proofs(&self, _requests: Vec) -> Vec { - vec![] + /// Get the header queue info. + pub fn queue_info(&self) -> QueueInfo { + self.header_queue.queue_info() } } \ No newline at end of file diff --git a/ethcore/src/light/mod.rs b/ethcore/src/light/mod.rs index 32d4e9d8a..22b446074 100644 --- a/ethcore/src/light/mod.rs +++ b/ethcore/src/light/mod.rs @@ -15,4 +15,5 @@ mod provider; mod client; +pub use self::client::Client; pub use self::provider::{CHTProofRequest, ProofRequest, Provider}; diff --git a/sync/src/lib.rs b/sync/src/lib.rs index d2c6e2583..e13e3b8b1 100644 --- a/sync/src/lib.rs +++ b/sync/src/lib.rs @@ -50,6 +50,7 @@ mod chain; mod blocks; mod sync_io; mod snapshot; +mod light; #[cfg(test)] mod tests; diff --git a/sync/src/light/mod.rs b/sync/src/light/mod.rs new file mode 100644 index 000000000..13ba38c5a --- /dev/null +++ b/sync/src/light/mod.rs @@ -0,0 +1,27 @@ +// Copyright 2015, 2016 Ethcore (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 . + +//! LES Protocol Version 1 implementation. +//! +//! This uses a "Provider" to answer requests. + +use ethcore::light::{Client, Provider}; + +/// This handles synchronization of the header chain for a light client. +pub struct Chain { + client: Client, + peers: Vec, +} \ No newline at end of file diff --git a/sync/src/sync_io.rs b/sync/src/sync_io.rs index 445939399..867a75f38 100644 --- a/sync/src/sync_io.rs +++ b/sync/src/sync_io.rs @@ -18,7 +18,7 @@ use network::{NetworkContext, PeerId, PacketId, NetworkError}; use ethcore::client::BlockChainClient; use ethcore::snapshot::SnapshotService; -/// IO interface for the syning handler. +/// IO interface for the syncing handler. /// Provides peer connection management and an interface to the blockchain client. // TODO: ratings pub trait SyncIo { @@ -38,7 +38,7 @@ pub trait SyncIo { fn peer_info(&self, peer_id: PeerId) -> String { peer_id.to_string() } - /// Maximum mutuallt supported ETH protocol version + /// Maximum mutually supported ETH protocol version fn eth_protocol_version(&self, peer_id: PeerId) -> u8; /// Returns if the chain block queue empty fn is_chain_queue_empty(&self) -> bool { From 91f0c6896c7a1211163a1f75e2f3a0f7a082d8c9 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Mon, 10 Oct 2016 18:48:47 +0200 Subject: [PATCH 04/37] LES boilerplate --- sync/src/light/mod.rs | 294 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 292 insertions(+), 2 deletions(-) diff --git a/sync/src/light/mod.rs b/sync/src/light/mod.rs index 13ba38c5a..59ffceda6 100644 --- a/sync/src/light/mod.rs +++ b/sync/src/light/mod.rs @@ -16,12 +16,302 @@ //! LES Protocol Version 1 implementation. //! -//! This uses a "Provider" to answer requests. +//! This uses a "Provider" to answer requests and syncs to a `Client`. +//! See https://github.com/ethcore/parity/wiki/Light-Ethereum-Subprotocol-(LES) use ethcore::light::{Client, Provider}; +use io::TimerToken; +use network::{NetworkProtocolHandler, NetworkService, NetworkContext, NetworkError, PeerId}; +use rlp::{DecoderError, RlpStream, Stream, UntrustedRlp, View}; +use util::hash::H256; + +use std::collections::HashMap; + +const TIMEOUT: TimerToken = 0; +const TIMEOUT_INTERVAL_MS: u64 = 1000; + +// LPV1 +const PROTOCOL_VERSION: u32 = 1; + +// TODO [rob] make configurable. +const PROTOCOL_ID: [u8; 3] = *b"les"; + +// TODO [rob] Buffer flow. + +// packet ID definitions. +mod packet { + // the status packet. + pub const STATUS: u8 = 0x00; + + // broadcast that new block hashes have appeared. + pub const NEW_BLOCK_HASHES: u8 = 0x01; + + // request and response for block headers + pub const GET_BLOCK_HEADERS: u8 = 0x02; + pub const BLOCK_HEADERS: u8 = 0x03; + + // request and response for block bodies + pub const GET_BLOCK_BODIES: u8 = 0x04; + pub const BLOCK_BODIES: u8 = 0x05; + + // request and response for transaction receipts. + pub const GET_RECEIPTS: u8 = 0x06; + pub const RECEIPTS: u8 = 0x07; + + // request and response for merkle proofs. + pub const GET_PROOFS: u8 = 0x08; + pub const PROOFS: u8 = 0x09; + + // request and response for contract code. + pub const GET_CONTRACT_CODES: u8 = 0x0a; + pub const CONTRACT_CODES: u8 = 0x0b; + + // relay transactions to peers. + pub const SEND_TRANSACTIONS: u8 = 0x0c; + + // request and response for header proofs in a CHT. + pub const GET_HEADER_PROOFS: u8 = 0x0d; + pub const HEADER_PROOFS: u8 = 0x0e; + + // broadcast dynamic capabilities. + pub const CAPABILITIES: u8 = 0x0f; + + // request and response for block-level state deltas. + pub const GET_BLOCK_DELTAS: u8 = 0x10; + pub const BLOCK_DELTAS: u8 = 0x11; + + // request and response for transaction proofs. + pub const GET_TRANSACTION_PROOFS: u8 = 0x12; + pub const TRANSACTION_PROOFS: u8 = 0x13; +} +// which direction a request should go in. +enum Direction { + Forwards, + Reverse, +} + +// A request made to a peer. +enum Request { + // a request for headers: + // (number, hash), maximum, skip, direction. + Headers((u64, H256), u64, u64, Direction), + // a request for block bodies by hashes. + Bodies(Vec), + // a request for tx receipts by hashes. + Receipts(Vec), + // a request for contract code by (block hash, address hash). + Code(Vec<(H256, H256)>), +} + +// data about each peer. +struct Peer { + pending_requests: HashMap, // requests pending for peer. + buffer: u64, // remaining buffer value. +} /// This handles synchronization of the header chain for a light client. pub struct Chain { client: Client, - peers: Vec, + genesis_hash: H256, + mainnet: bool, + peers: RwLock>, +} + +impl Chain { + // called when a peer connects. + fn on_connect(&self, peer: &PeerId, io: &NetworkContext) { + let peer = *peer; + match self.send_status(peer, io) { + Ok(()) => { + self.peers.write().insert(peer, Peer); + } + Err(e) => { + trace!(target: "les", "Error while sending status: {}", e); + io.disable_peer(peer); + } + } + } + + // called when a peer disconnects. + fn on_disconnect(&self, peer: PeerId) { + self.peers.write().remove(peer); + } + + fn send_status(&self, peer: PeerId, io: &NetworkContext) -> Result<(), NetworkError> { + let chain_info = self.client.chain_info(); + + // TODO [rob] use optional keys too. + let mut stream = RlpStream::new_list(6); + stream + .new_list(2) + .append("protocolVersion") + .append(&PROTOCOL_VERSION) + .new_list(2) + .append("networkId") + .append(&(self.mainnet as u8)) + .new_list(2) + .append("headTd") + .append(&chain_info.total_difficulty) + .new_list(2) + .append("headHash") + .append(&chain_info.best_block_hash) + .new_list(2) + .append("headNum") + .append(&chain_info.best_block_number) + .new_list(2) + .append("genesisHash") + .append(&self.genesis_hash); + + io.send(peer, packet::STATUS, stream.out()) + } + + fn status(&self, peer: &PeerId, io: &NetworkContext) { + unimplemented!() + } + + // Handle a new block hashes message. + fn new_block_hashes(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { + const MAX_NEW_HASHES: usize = 256; + + unimplemented!() + } + + // Handle a request for block headers. + fn get_block_headers(&self, peers: &PeerId, io: &NetworkContext, data: UntrustedRlp) { + unimplemented!() + } + + // Receive a response for block headers. + fn block_headers(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { + unimplemented!() + } + + // Handle a request for block bodies. + fn get_block_bodies(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { + unimplemented!() + } + + // Receive a response for block bodies. + fn block_bodies(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { + unimplemented!() + } + + // Handle a request for proofs. + fn get_proofs(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { + unimplemented!() + } + + // Receive a response for proofs. + fn proofs(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { + unimplemented!() + } + + // Handle a request for contract code. + fn get_contract_code(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { + unimplemented!() + } + + // Receive a response for contract code. + fn contract_code(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { + unimplemented!() + } + + // Handle a request for header proofs + fn get_header_proofs(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { + unimplemented!() + } + + // Receive a response for header proofs + fn header_proofs(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { + unimplemented!() + } + + // Receive a set of transactions to relay. + fn relay_transactions(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { + unimplemented!() + } + + // Receive updated capabilities from a peer. + fn capabilities(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { + unimplemented!() + } + + // Handle a request for block deltas. + fn get_block_deltas(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { + unimplemented!() + } + + // Receive block deltas. + fn block_deltas(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { + unimplemented!() + } + + // Handle a request for transaction proofs. + fn get_transaction_proofs(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { + unimplemented!() + } + + // Receive transaction proofs. + fn transaction_proofs(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { + unimplemented!() + } +} + +impl NetworkProtocolHandler for Chain { + fn initialize(&self, io: &NetworkContext) { + io.register_timer(TIMEOUT, TIMEOUT_INTERVAL_MS).expect("Error registering sync timer."); + } + + fn read(&self, io: &NetworkContext, peer: &PeerId, packet_id: u8, data: &[u8]) { + let rlp = UntrustedRlp::new(data); + match packet_id { + packet::STATUS => self.status(peer, io, rlp), + packet::NEW_BLOCK_HASHES => self.new_block_hashes(peer, io, rlp), + + packet::GET_BLOCK_HEADERS => self.get_block_headers(peer, io, rlp), + packet::BLOCK_HEADERS => self.block_headers(peer, io, rlp), + + packet::GET_BLOCK_BODIES => self.get_block_bodies(peer, io, rlp), + packet::BLOCK_BODIES => self.block_bodies(peer, io, rlp), + + packet::GET_RECEIPTS => self.get_receipts(peer, io, rlp), + packet::RECEIPTS => self.receipt(peer, io, rlp), + + packet::GET_PROOFS => self.get_proofs(peer, io, rlp), + packet::PROOFS => self.proofs(peer, io, rlp), + + packet::GET_CONTRACT_CODES => self.get_contract_code(peer, io, rlp), + packet::CONTRACT_CODES => self.contract_code(peer, io, rlp), + + packet::SEND_TRANSACTIONS => self.relay_transactions(peer, io, rlp), + packet::CAPABILITIES => self.capabilities(peer, io, rlp), + + packet::GET_HEADER_PROOFS => self.get_header_proofs(peer, io, rlp), + packet::HEADER_PROOFS => self.header_proofs(peer, io, rlp), + + packet::GET_BLOCK_DELTAS => self.get_block_deltas(peer, io, rlp), + packet::BLOCK_DELTAS => self.block_deltas(peer, io, rlp), + + packet::GET_TRANSACTION_PROOFS => self.get_transaction_proofs(peer, io, rlp), + packet::TRANSACTION_PROOFS => self.transaction_proofs(peer, io, rlp), + } + } + + fn connected(&self, io: &NetworkContext, peer: &PeerId) { + self.on_connect(peer, io); + } + + fn disconnected(&self, io: &NetworkContext, peer: &PeerId) { + self.on_disconnect(peer, io); + } + + fn timeout(&self, io: &NetworkContext, timer: TimerToken) { + match timer { + TIMEOUT => { + // broadcast transactions to peers. + // update buffer flow. + } + _ => warn!(target: "les", "received timeout on unknown token {}", timer), + } + } } \ No newline at end of file From a7e420d9ecd088b49337de1f771df50f8af390ba Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Thu, 27 Oct 2016 15:09:17 +0200 Subject: [PATCH 05/37] stub implementation of provider for client --- ethcore/src/lib.rs | 1 + ethcore/src/light/client.rs | 32 +++++++++++++++++++++++++++++++- ethcore/src/light/mod.rs | 21 +++++++++++++++++++-- ethcore/src/light/provider.rs | 6 ++++++ 4 files changed, 57 insertions(+), 3 deletions(-) diff --git a/ethcore/src/lib.rs b/ethcore/src/lib.rs index 9985dc58e..cd32da999 100644 --- a/ethcore/src/lib.rs +++ b/ethcore/src/lib.rs @@ -136,6 +136,7 @@ pub mod miner; pub mod snapshot; pub mod action_params; pub mod db; +pub mod light; #[macro_use] pub mod evm; mod cache_manager; diff --git a/ethcore/src/light/client.rs b/ethcore/src/light/client.rs index 18635aa44..a4ff2531f 100644 --- a/ethcore/src/light/client.rs +++ b/ethcore/src/light/client.rs @@ -34,7 +34,7 @@ use io::IoChannel; use util::hash::H256; use util::Bytes; -/// A light client. +/// Light client implementation. pub struct Client { engine: Arc, header_queue: HeaderQueue, @@ -68,4 +68,34 @@ impl Client { pub fn queue_info(&self) -> QueueInfo { self.header_queue.queue_info() } +} + +impl Provider for Client { + fn block_headers(&self, block: H256, skip: usize, max: usize, reverse: bool) -> Vec { + Vec::new() + } + + fn block_bodies(&self, blocks: Vec) -> Vec { + Vec::new() + } + + fn receipts(&self, blocks: Vec) -> Vec { + Vec::new() + } + + fn proofs(&self, requests: Vec<(H256, ProofRequest)>) -> Vec { + Vec::new() + } + + fn code(&self, accounts: Vec<(H256, H256)>) -> Vec { + Vec::new() + } + + fn header_proofs(&self, requests: Vec) -> Vec { + Vec::new() + } + + fn pending_transactions(&self) -> Vec { + Vec::new() + } } \ No newline at end of file diff --git a/ethcore/src/light/mod.rs b/ethcore/src/light/mod.rs index 22b446074..863f35433 100644 --- a/ethcore/src/light/mod.rs +++ b/ethcore/src/light/mod.rs @@ -1,3 +1,19 @@ +// Copyright 2015, 2016 Ethcore (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 . + //! Light client logic and implementation. //! //! A "light" client stores very little chain-related data locally @@ -12,8 +28,9 @@ //! It starts by performing a header-only sync, verifying every header in //! the chain. -mod provider; mod client; +mod provider; +mod sync; pub use self::client::Client; -pub use self::provider::{CHTProofRequest, ProofRequest, Provider}; +pub use self::provider::{CHTProofRequest, ProofRequest, Provider}; \ No newline at end of file diff --git a/ethcore/src/light/provider.rs b/ethcore/src/light/provider.rs index 4833184eb..05859c78b 100644 --- a/ethcore/src/light/provider.rs +++ b/ethcore/src/light/provider.rs @@ -19,12 +19,15 @@ pub use proof_request::{CHTProofRequest, ProofRequest}; +use transaction::SignedTransaction; + use util::Bytes; use util::hash::H256; /// Defines the operations that a provider for `LES` must fulfill. /// /// These are defined at [1], but may be subject to change. +/// Requests which can't be fulfilled should return an empty RLP list. /// /// [1]: https://github.com/ethcore/parity/wiki/Light-Ethereum-Subprotocol-(LES) pub trait Provider { @@ -54,4 +57,7 @@ pub trait Provider { /// Provide header proofs from the Canonical Hash Tries. fn header_proofs(&self, requests: Vec) -> Vec; + + /// Provide pending transactions. + fn pending_transactions(&self) -> Vec; } \ No newline at end of file From 3a5843c40f208ab9715e3507ffcf30c46b5ec694 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Thu, 27 Oct 2016 15:45:59 +0200 Subject: [PATCH 06/37] skeleton and request traits --- ethcore/src/light/client.rs | 5 ++ ethcore/src/light/sync.rs | 33 +++++++++ sync/src/chain.rs | 12 ++-- sync/src/light/mod.rs | 53 ++++++++------ sync/src/light/request.rs | 138 ++++++++++++++++++++++++++++++++++++ 5 files changed, 213 insertions(+), 28 deletions(-) create mode 100644 ethcore/src/light/sync.rs create mode 100644 sync/src/light/request.rs diff --git a/ethcore/src/light/client.rs b/ethcore/src/light/client.rs index a4ff2531f..7f061e3ab 100644 --- a/ethcore/src/light/client.rs +++ b/ethcore/src/light/client.rs @@ -68,6 +68,11 @@ impl Client { pub fn queue_info(&self) -> QueueInfo { self.header_queue.queue_info() } + + /// Get the chain info. + pub fn chain_info(&self) -> ChainInfo { + + } } impl Provider for Client { diff --git a/ethcore/src/light/sync.rs b/ethcore/src/light/sync.rs new file mode 100644 index 000000000..15ed5673c --- /dev/null +++ b/ethcore/src/light/sync.rs @@ -0,0 +1,33 @@ +// Copyright 2015, 2016 Ethcore (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 . + +//! A light sync target. + +use block_import_error::BlockImportError; +use client::BlockChainInfo; + +use util::hash::H256; + +pub trait Sync { + /// Whether syncing is enabled. + fn enabled(&self) -> bool; + + /// Current chain info. + fn chain_info(&self) -> BlockChainInfo; + + /// Import a header. + fn import_header(&self, header_bytes: Vec) -> Result; +} \ No newline at end of file diff --git a/sync/src/chain.rs b/sync/src/chain.rs index 916e7424e..344ed8c41 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -209,8 +209,8 @@ pub struct SyncStatus { impl SyncStatus { /// Indicates if snapshot download is in progress pub fn is_snapshot_syncing(&self) -> bool { - self.state == SyncState::SnapshotManifest - || self.state == SyncState::SnapshotData + self.state == SyncState::SnapshotManifest + || self.state == SyncState::SnapshotData || self.state == SyncState::SnapshotWaiting } @@ -453,7 +453,7 @@ impl ChainSync { self.init_downloaders(io.chain()); self.reset_and_continue(io); } - + /// Restart sync after bad block has been detected. May end up re-downloading up to QUEUE_SIZE blocks fn init_downloaders(&mut self, chain: &BlockChainClient) { // Do not assume that the block queue/chain still has our last_imported_block @@ -1150,9 +1150,9 @@ impl ChainSync { } }, BlockSet::OldBlocks => { - if self.old_blocks.as_mut().map_or(false, |downloader| { downloader.collect_blocks(io, false) == Err(DownloaderImportError::Invalid) }) { - self.restart(io); - } else if self.old_blocks.as_ref().map_or(false, |downloader| { downloader.is_complete() }) { + if self.old_blocks.as_mut().map_or(false, |downloader| { downloader.collect_blocks(io, false) == Err(DownloaderImportError::Invalid) }) { + self.restart(io); + } else if self.old_blocks.as_ref().map_or(false, |downloader| { downloader.is_complete() }) { trace!(target: "sync", "Background block download is complete"); self.old_blocks = None; } diff --git a/sync/src/light/mod.rs b/sync/src/light/mod.rs index 59ffceda6..fe60c30e5 100644 --- a/sync/src/light/mod.rs +++ b/sync/src/light/mod.rs @@ -24,8 +24,10 @@ use io::TimerToken; use network::{NetworkProtocolHandler, NetworkService, NetworkContext, NetworkError, PeerId}; use rlp::{DecoderError, RlpStream, Stream, UntrustedRlp, View}; use util::hash::H256; +use parking_lot::{Mutex, RwLock}; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; +use std::sync::atomic::{AtomicUsize, Ordering}; const TIMEOUT: TimerToken = 0; const TIMEOUT_INTERVAL_MS: u64 = 1000; @@ -84,29 +86,18 @@ mod packet { pub const GET_TRANSACTION_PROOFS: u8 = 0x12; pub const TRANSACTION_PROOFS: u8 = 0x13; } -// which direction a request should go in. -enum Direction { - Forwards, - Reverse, -} -// A request made to a peer. -enum Request { - // a request for headers: - // (number, hash), maximum, skip, direction. - Headers((u64, H256), u64, u64, Direction), - // a request for block bodies by hashes. - Bodies(Vec), - // a request for tx receipts by hashes. - Receipts(Vec), - // a request for contract code by (block hash, address hash). - Code(Vec<(H256, H256)>), +struct Request; + +struct Requested { + timestamp: usize, + total: Request, } // data about each peer. struct Peer { - pending_requests: HashMap, // requests pending for peer. buffer: u64, // remaining buffer value. + current_asking: HashSet, // pending request ids. } /// This handles synchronization of the header chain for a light client. @@ -115,15 +106,25 @@ pub struct Chain { genesis_hash: H256, mainnet: bool, peers: RwLock>, + pending_requests: RwLock>, + req_id: AtomicUsize, } impl Chain { + // make a request to a given peer. + fn request_from(&self, peer: &PeerId, req: Request) { + unimplemented!() + } + // called when a peer connects. fn on_connect(&self, peer: &PeerId, io: &NetworkContext) { let peer = *peer; match self.send_status(peer, io) { Ok(()) => { - self.peers.write().insert(peer, Peer); + self.peers.write().insert(peer, Peer { + buffer: 0, + current_asking: HashSet::new(), + }); } Err(e) => { trace!(target: "les", "Error while sending status: {}", e); @@ -133,8 +134,9 @@ impl Chain { } // called when a peer disconnects. - fn on_disconnect(&self, peer: PeerId) { - self.peers.write().remove(peer); + fn on_disconnect(&self, peer: PeerId, io: &NetworkContext) { + // TODO: reassign all requests assigned to this peer. + self.peers.write().remove(&peer); } fn send_status(&self, peer: PeerId, io: &NetworkContext) -> Result<(), NetworkError> { @@ -165,6 +167,11 @@ impl Chain { io.send(peer, packet::STATUS, stream.out()) } + /// Check on the status of all pending requests. + fn check_pending_requests(&self) { + unimplemented!() + } + fn status(&self, peer: &PeerId, io: &NetworkContext) { unimplemented!() } @@ -177,7 +184,9 @@ impl Chain { } // Handle a request for block headers. - fn get_block_headers(&self, peers: &PeerId, io: &NetworkContext, data: UntrustedRlp) { + fn get_block_headers(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { + const MAX_HEADERS: usize = 512; + unimplemented!() } diff --git a/sync/src/light/request.rs b/sync/src/light/request.rs new file mode 100644 index 000000000..112cbcc42 --- /dev/null +++ b/sync/src/light/request.rs @@ -0,0 +1,138 @@ +// Copyright 2015, 2016 Ethcore (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 . + +//! LES request types. + +use util::bigint::prelude::*; +use rlp::*; + +/// An LES request. This defines its data format, and the format of its response type. +pub trait Request: Sized { + /// The response type of this request. + type Response: Response; + + /// The error type when decoding a response. + type Error; + + /// Whether this request is empty. + fn is_empty(&self) -> bool; + + /// The remainder of this request unfulfilled by the response. Required to return + /// an equivalent request when provided with an empty response. + fn remainder(&self, res: &Self::Response) -> Self; + + /// Attempt to parse raw data into a response object + /// or an error. Behavior undefined if the raw data didn't originate from + /// this request. + fn parse_response(&self, raw: &[u8]) -> Result; +} + +/// Request responses. These must have a combination operation used to fill the gaps +/// in one response with data from another. +pub trait Response: Sized { + /// Combine the two responses into one. This can only be relied on to behave correctly + /// if `other` is a response to a sub-request of the request this response was + /// produced from. + fn combine(&mut self, other: Self); +} + +/// A request for block bodies. +pub struct BlockBodies { + hashes: Vec, +} + +/// A response for block bodies. +pub struct BlockBodiesResponse { + bodies: Vec<(H256, Vec)>, +} + +impl Request for BlockBodies { + type Response = BlockBodiesResponse; + type Error = ::rlp::DecoderError; + + fn is_empty(&self) -> bool { self.hashes.is_empty() } + + fn remainder(&self, res: &Self::Response) -> Self { + let mut remaining = Vec::new(); + + let bodies = res.bodies.iter().map(|&(_, ref b) b).chain(::std::iter::repeat(&Vec::new())); + for (hash, body) in self.hashes.iter().zip(bodies) { + if body.is_empty() { + remaining.push(hash); + } + } + + BlockBodies { + hashes: remaining, + } + } + + fn parse_response(&self, raw: &[u8]) -> Result { + use ethcore::transaction::SignedTransaction; + use ethcore::header::Header; + + let rlp = UntrustedRlp::new(raw); + + let mut bodies = Vec::with_capacity(self.hashes.len()); + + let items = rlp.iter(); + for hash in self.hashes.iter().cloned() { + let res_bytes = match items.next() { + Some(rlp) => { + // perform basic block verification. + // TODO: custom error type? + try!(rlp.val_at::>(0) + .and_then(|_| rlp.val_at::>(1))); + + try!(rlp.data()).to_owned() + } + None => Vec::new(), + }; + + bodies.push((hash, res_bytes)); + } + + Ok(BlockBodiesResponse { + bodies: bodies, + }) + } +} + +impl Response for BlockBodiesResponse { + fn identity() -> Self { + BlockBodiesResponse { + bodies: Vec::new(), + } + } + + fn combine(&mut self, other: Self) { + let other_iter = other.bodies.into_iter(); + + 'a: + for &mut (ref my_hash, ref mut my_body) in self.bodies.iter_mut() { + loop { + match other_iter.next() { + Some((hash, body)) if hash == my_hash && !body.is_empty() => { + *my_body = body.to_owned(); + break + } + Some(_) => continue, + None => break 'a, + } + } + } + } +} \ No newline at end of file From eef9a355af9a69863a60e1e611837d1b4151e7fd Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Fri, 4 Nov 2016 17:19:01 +0100 Subject: [PATCH 07/37] request definitions --- ethcore/src/light/client.rs | 10 +- sync/src/light/mod.rs | 24 +++- sync/src/light/request.rs | 237 ++++++++++++++++++++---------------- 3 files changed, 156 insertions(+), 115 deletions(-) diff --git a/ethcore/src/light/client.rs b/ethcore/src/light/client.rs index 7f061e3ab..ae882b1a8 100644 --- a/ethcore/src/light/client.rs +++ b/ethcore/src/light/client.rs @@ -27,18 +27,19 @@ use block_import_error::BlockImportError; use block_status::BlockStatus; use verification::queue::{Config as QueueConfig, HeaderQueue, QueueInfo, Status}; use transaction::SignedTransaction; +use types::blockchain_info::BlockChainInfo; use super::provider::{CHTProofRequest, Provider, ProofRequest}; use io::IoChannel; use util::hash::H256; -use util::Bytes; +use util::{Bytes, Mutex}; /// Light client implementation. pub struct Client { engine: Arc, header_queue: HeaderQueue, - message_channel: IoChannel, + message_channel: Mutex>, } impl Client { @@ -70,11 +71,12 @@ impl Client { } /// Get the chain info. - pub fn chain_info(&self) -> ChainInfo { - + pub fn chain_info(&self) -> BlockChainInfo { + unimplemented!() } } +// dummy implementation -- may draw from canonical cache further on. impl Provider for Client { fn block_headers(&self, block: H256, skip: usize, max: usize, reverse: bool) -> Vec { Vec::new() diff --git a/sync/src/light/mod.rs b/sync/src/light/mod.rs index fe60c30e5..4c32d52ae 100644 --- a/sync/src/light/mod.rs +++ b/sync/src/light/mod.rs @@ -29,6 +29,10 @@ use parking_lot::{Mutex, RwLock}; use std::collections::{HashMap, HashSet}; use std::sync::atomic::{AtomicUsize, Ordering}; +use self::request::Request; + +mod request; + const TIMEOUT: TimerToken = 0; const TIMEOUT_INTERVAL_MS: u64 = 1000; @@ -87,11 +91,9 @@ mod packet { pub const TRANSACTION_PROOFS: u8 = 0x13; } -struct Request; - struct Requested { timestamp: usize, - total: Request, + req: Request, } // data about each peer. @@ -172,7 +174,7 @@ impl Chain { unimplemented!() } - fn status(&self, peer: &PeerId, io: &NetworkContext) { + fn status(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { unimplemented!() } @@ -205,6 +207,16 @@ impl Chain { unimplemented!() } + // Handle a request for receipts. + fn get_receipts(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { + unimplemented!() + } + + // Receive a response for receipts. + fn receipts(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { + unimplemented!() + } + // Handle a request for proofs. fn get_proofs(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { unimplemented!() @@ -284,7 +296,7 @@ impl NetworkProtocolHandler for Chain { packet::BLOCK_BODIES => self.block_bodies(peer, io, rlp), packet::GET_RECEIPTS => self.get_receipts(peer, io, rlp), - packet::RECEIPTS => self.receipt(peer, io, rlp), + packet::RECEIPTS => self.receipts(peer, io, rlp), packet::GET_PROOFS => self.get_proofs(peer, io, rlp), packet::PROOFS => self.proofs(peer, io, rlp), @@ -311,7 +323,7 @@ impl NetworkProtocolHandler for Chain { } fn disconnected(&self, io: &NetworkContext, peer: &PeerId) { - self.on_disconnect(peer, io); + self.on_disconnect(*peer, io); } fn timeout(&self, io: &NetworkContext, timer: TimerToken) { diff --git a/sync/src/light/request.rs b/sync/src/light/request.rs index 112cbcc42..bbb91e415 100644 --- a/sync/src/light/request.rs +++ b/sync/src/light/request.rs @@ -14,125 +14,152 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -//! LES request types. +/// LES request types. -use util::bigint::prelude::*; -use rlp::*; +use ethcore::transaction::Transaction; +use util::{Address, H256}; -/// An LES request. This defines its data format, and the format of its response type. -pub trait Request: Sized { - /// The response type of this request. - type Response: Response; - - /// The error type when decoding a response. - type Error; - - /// Whether this request is empty. - fn is_empty(&self) -> bool; - - /// The remainder of this request unfulfilled by the response. Required to return - /// an equivalent request when provided with an empty response. - fn remainder(&self, res: &Self::Response) -> Self; - - /// Attempt to parse raw data into a response object - /// or an error. Behavior undefined if the raw data didn't originate from - /// this request. - fn parse_response(&self, raw: &[u8]) -> Result; +/// A request for block headers. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Headers { + /// Block information for the request being made. + pub block: (u64, H256), + /// The maximum amount of headers which can be returned. + pub max: u64, + /// The amount of headers to skip between each response entry. + pub skip: u64, + /// Whether the headers should proceed in falling number from the initial block. + pub reverse: bool, } -/// Request responses. These must have a combination operation used to fill the gaps -/// in one response with data from another. -pub trait Response: Sized { - /// Combine the two responses into one. This can only be relied on to behave correctly - /// if `other` is a response to a sub-request of the request this response was - /// produced from. - fn combine(&mut self, other: Self); +/// A request for specific block bodies. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Bodies { + /// Hashes which bodies are being requested for. + pub block_hashes: Vec } -/// A request for block bodies. -pub struct BlockBodies { - hashes: Vec, +/// A request for transaction receipts. +/// +/// This request is answered with a list of transaction receipts for each block +/// requested. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Receipts { + /// Block hashes to return receipts for. + pub block_hashes: Vec, } -/// A response for block bodies. -pub struct BlockBodiesResponse { - bodies: Vec<(H256, Vec)>, +/// A request for state proofs. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct StateProofs { + /// Block hash to query state from. + pub block: H256, + /// Key of the state trie -- corresponds to account hash. + pub key1: H256, + /// Key in that account's storage trie; if empty, then the account RLP should be + /// returned. + pub key2: Option, + /// if greater than zero, trie nodes beyond this level may be omitted. + pub from_level: u32, // could even safely be u8; trie w/ 32-byte key can be at most 64-levels deep. } -impl Request for BlockBodies { - type Response = BlockBodiesResponse; - type Error = ::rlp::DecoderError; - - fn is_empty(&self) -> bool { self.hashes.is_empty() } - - fn remainder(&self, res: &Self::Response) -> Self { - let mut remaining = Vec::new(); - - let bodies = res.bodies.iter().map(|&(_, ref b) b).chain(::std::iter::repeat(&Vec::new())); - for (hash, body) in self.hashes.iter().zip(bodies) { - if body.is_empty() { - remaining.push(hash); - } - } - - BlockBodies { - hashes: remaining, - } - } - - fn parse_response(&self, raw: &[u8]) -> Result { - use ethcore::transaction::SignedTransaction; - use ethcore::header::Header; - - let rlp = UntrustedRlp::new(raw); - - let mut bodies = Vec::with_capacity(self.hashes.len()); - - let items = rlp.iter(); - for hash in self.hashes.iter().cloned() { - let res_bytes = match items.next() { - Some(rlp) => { - // perform basic block verification. - // TODO: custom error type? - try!(rlp.val_at::>(0) - .and_then(|_| rlp.val_at::>(1))); - - try!(rlp.data()).to_owned() - } - None => Vec::new(), - }; - - bodies.push((hash, res_bytes)); - } - - Ok(BlockBodiesResponse { - bodies: bodies, - }) - } +/// A request for contract code. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ContractCodes { + /// Block hash and account key (== sha3(address)) pairs to fetch code for. + pub code_requests: Vec<(H256, H256)>, } -impl Response for BlockBodiesResponse { - fn identity() -> Self { - BlockBodiesResponse { - bodies: Vec::new(), - } - } +/// A request for header proofs from the Canonical Hash Trie. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct HeaderProofs { + /// Number of the CHT. + pub cht_number: u64, + /// Block number requested. + pub block_number: u64, + /// If greater than zero, trie nodes beyond this level may be omitted. + pub from_level: u32, +} - fn combine(&mut self, other: Self) { - let other_iter = other.bodies.into_iter(); +/// A request for block deltas -- merkle proofs of all changed trie nodes and code. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct BlockDeltas { + /// Block hashes deltas are being requested for. + pub block_hashes: Vec, +} - 'a: - for &mut (ref my_hash, ref mut my_body) in self.bodies.iter_mut() { - loop { - match other_iter.next() { - Some((hash, body)) if hash == my_hash && !body.is_empty() => { - *my_body = body.to_owned(); - break - } - Some(_) => continue, - None => break 'a, - } - } +/// A request for a single transaction merkle proof. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TransactionProof { + /// The block hash to use the initial state from. + pub block_hash: H256, + /// The address to treat as the sender of the transaction. + pub sender: Address, + /// The raw transaction request itself. + pub transaction: Transaction, +} + +/// A request for transaction merkle proofs. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TransactionProofs { + /// Transaction proof requests. + pub tx_reqs: Vec, +} + +/// Kinds of requests. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Kind { + /// Requesting headers. + Headers, + /// Requesting block bodies. + Bodies, + /// Requesting transaction receipts. + Receipts, + /// Requesting proofs of state trie nodes. + StateProofs, + /// Requesting contract code by hash. + Codes, + /// Requesting header proofs (from the CHT). + HeaderProofs, + /// Requesting block deltas. + Deltas, + /// Requesting merkle proofs for transactions. + TransactionProofs, +} + +/// Encompasses all possible types of requests in a single structure. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Request { + /// Requesting headers. + Headers(Headers), + /// Requesting block bodies. + Bodies(Bodies), + /// Requesting transaction receipts. + Receipts(Receipts), + /// Requesting state proofs. + StateProofs(StateProofs), + /// Requesting contract codes. + Codes(ContractCodes), + /// Requesting header proofs. + HeaderProofs(HeaderProofs), + /// Requesting block deltas. + Deltas(BlockDeltas), + /// Requesting transaction proofs. + TransactionProofs(TransactionProofs), +} + +impl Request { + /// Get the kind of request this is. + pub fn kind(&self) -> Kind { + match *self { + Request::Headers(_) => Kind::Headers, + Request::Bodies(_) => Kind::Bodies, + Request::Receipts(_) => Kind::Receipts, + Request::StateProofs(_) => Kind::StateProofs, + Request::Codes(_) => Kind::Codes, + Request::HeaderProofs(_) => Kind::HeaderProofs, + Request::Deltas(_) => Kind::Deltas, + Request::TransactionProofs(_) => Kind::TransactionProofs, } } } \ No newline at end of file From 5d011fe57799cfb8fe7ade5d58ae34c5eabc85d1 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Fri, 4 Nov 2016 17:25:26 +0100 Subject: [PATCH 08/37] new_list -> begin_list --- sync/src/light/mod.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sync/src/light/mod.rs b/sync/src/light/mod.rs index 4c32d52ae..d7d1a84f7 100644 --- a/sync/src/light/mod.rs +++ b/sync/src/light/mod.rs @@ -147,22 +147,22 @@ impl Chain { // TODO [rob] use optional keys too. let mut stream = RlpStream::new_list(6); stream - .new_list(2) + .begin_list(2) .append("protocolVersion") .append(&PROTOCOL_VERSION) - .new_list(2) + .begin_list(2) .append("networkId") .append(&(self.mainnet as u8)) - .new_list(2) + .begin_list(2) .append("headTd") .append(&chain_info.total_difficulty) - .new_list(2) + .begin_list(2) .append("headHash") .append(&chain_info.best_block_hash) - .new_list(2) + .begin_list(2) .append("headNum") .append(&chain_info.best_block_number) - .new_list(2) + .begin_list(2) .append("genesisHash") .append(&self.genesis_hash); From 90a2c379779699d3e63ff92ae8faed9382b372fd Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Fri, 4 Nov 2016 17:35:31 +0100 Subject: [PATCH 09/37] handle unknown packet --- sync/src/light/mod.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/sync/src/light/mod.rs b/sync/src/light/mod.rs index d7d1a84f7..a4aa0dcf7 100644 --- a/sync/src/light/mod.rs +++ b/sync/src/light/mod.rs @@ -148,22 +148,22 @@ impl Chain { let mut stream = RlpStream::new_list(6); stream .begin_list(2) - .append("protocolVersion") + .append(&"protocolVersion") .append(&PROTOCOL_VERSION) .begin_list(2) - .append("networkId") + .append(&"networkId") .append(&(self.mainnet as u8)) .begin_list(2) - .append("headTd") + .append(&"headTd") .append(&chain_info.total_difficulty) .begin_list(2) - .append("headHash") + .append(&"headHash") .append(&chain_info.best_block_hash) .begin_list(2) - .append("headNum") + .append(&"headNum") .append(&chain_info.best_block_number) .begin_list(2) - .append("genesisHash") + .append(&"genesisHash") .append(&self.genesis_hash); io.send(peer, packet::STATUS, stream.out()) @@ -315,6 +315,11 @@ impl NetworkProtocolHandler for Chain { packet::GET_TRANSACTION_PROOFS => self.get_transaction_proofs(peer, io, rlp), packet::TRANSACTION_PROOFS => self.transaction_proofs(peer, io, rlp), + + other => { + debug!(target: "les", "Disconnecting peer {} on unexpected packet {}", peer, other); + io.disconnect_peer(*peer); + } } } @@ -330,7 +335,6 @@ impl NetworkProtocolHandler for Chain { match timer { TIMEOUT => { // broadcast transactions to peers. - // update buffer flow. } _ => warn!(target: "les", "received timeout on unknown token {}", timer), } From edf17d00c43a3ac38d33aadf91fd8ea5b4adc0af Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Fri, 4 Nov 2016 18:40:31 +0100 Subject: [PATCH 10/37] revise light implementation strategy --- ethcore/src/light/client.rs | 12 +++++----- ethcore/src/light/provider.rs | 8 +++++-- sync/src/light/mod.rs | 41 ++++++++++++++++++++++++++++++----- 3 files changed, 48 insertions(+), 13 deletions(-) diff --git a/ethcore/src/light/client.rs b/ethcore/src/light/client.rs index ae882b1a8..9f1b00e71 100644 --- a/ethcore/src/light/client.rs +++ b/ethcore/src/light/client.rs @@ -69,16 +69,16 @@ impl Client { pub fn queue_info(&self) -> QueueInfo { self.header_queue.queue_info() } - - /// Get the chain info. - pub fn chain_info(&self) -> BlockChainInfo { - unimplemented!() - } } // dummy implementation -- may draw from canonical cache further on. impl Provider for Client { - fn block_headers(&self, block: H256, skip: usize, max: usize, reverse: bool) -> Vec { + /// Get the chain info. + fn chain_info(&self) -> BlockChainInfo { + unimplemented!() + } + + fn block_headers(&self, block: (u64, H256), skip: usize, max: usize, reverse: bool) -> Vec { Vec::new() } diff --git a/ethcore/src/light/provider.rs b/ethcore/src/light/provider.rs index 05859c78b..10d3eed88 100644 --- a/ethcore/src/light/provider.rs +++ b/ethcore/src/light/provider.rs @@ -20,6 +20,7 @@ pub use proof_request::{CHTProofRequest, ProofRequest}; use transaction::SignedTransaction; +use blockchain_info::BlockChainInfo; use util::Bytes; use util::hash::H256; @@ -30,13 +31,16 @@ use util::hash::H256; /// Requests which can't be fulfilled should return an empty RLP list. /// /// [1]: https://github.com/ethcore/parity/wiki/Light-Ethereum-Subprotocol-(LES) -pub trait Provider { +pub trait Provider: Sync { + /// Provide current blockchain info. + fn chain_info(&self) -> BlockChainInfo; + /// 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, block: H256, skip: usize, max: usize, reverse: bool) -> Vec; + fn block_headers(&self, block: (u64, H256), skip: usize, max: usize, reverse: bool) -> Vec; /// Provide as many as possible of the requested blocks (minus the headers) encoded /// in RLP format. diff --git a/sync/src/light/mod.rs b/sync/src/light/mod.rs index a4aa0dcf7..19dac8561 100644 --- a/sync/src/light/mod.rs +++ b/sync/src/light/mod.rs @@ -91,6 +91,22 @@ mod packet { pub const TRANSACTION_PROOFS: u8 = 0x13; } +// helper macro for disconnecting peer on error while returning +// the value if ok. +// requires that error types are debug. +macro_rules! try_dc { + ($io: expr, $peer: expr, $e: expr) => { + match $e { + Ok(x) => x, + Err(e) => { + debug!(target: "les", "disconnecting peer {} due to error {:?}", $peer, e); + $io.disconnect_peer($peer); + return; + } + } + } +} + struct Requested { timestamp: usize, req: Request, @@ -102,9 +118,14 @@ struct Peer { current_asking: HashSet, // pending request ids. } -/// This handles synchronization of the header chain for a light client. -pub struct Chain { - client: Client, +/// This is an implementation of the light ethereum network protocol, abstracted +/// over a `Provider` of data and a p2p network. +/// +/// This is simply designed for request-response purposes. Higher level uses +/// of the protocol, such as synchronization, will function as wrappers around +/// this system. +pub struct LightProtocol { + provider: Box, genesis_hash: H256, mainnet: bool, peers: RwLock>, @@ -112,7 +133,7 @@ pub struct Chain { req_id: AtomicUsize, } -impl Chain { +impl LightProtocol { // make a request to a given peer. fn request_from(&self, peer: &PeerId, req: Request) { unimplemented!() @@ -189,6 +210,14 @@ impl Chain { fn get_block_headers(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { const MAX_HEADERS: usize = 512; + let req_id: u64 = try_dc!(io, peer, data.val_at(0)); + let block = try_dc!(io, peer, data.at(1).and_then(|block_list| { + (try!(block_list.val_at(0)), try!(block_list.val_at(1)) + })); + let max = ::std::cmp::min(MAX_HEADERS, try_dc!(io, peer, data.val_at(2))); + let reverse = try_dc!(io, peer, data.val_at(3)); + + let headers = self.provider.block_headers() unimplemented!() } @@ -199,6 +228,8 @@ impl Chain { // Handle a request for block bodies. fn get_block_bodies(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { + const MAX_BODIES: usize = 512; + unimplemented!() } @@ -278,7 +309,7 @@ impl Chain { } } -impl NetworkProtocolHandler for Chain { +impl NetworkProtocolHandler for LightProtocol { fn initialize(&self, io: &NetworkContext) { io.register_timer(TIMEOUT, TIMEOUT_INTERVAL_MS).expect("Error registering sync timer."); } From 5cabb3008f30e20d3105e2d761f4aca4304f8542 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Fri, 4 Nov 2016 19:21:48 +0100 Subject: [PATCH 11/37] make verification module public --- ethcore/src/lib.rs | 3 +-- ethcore/src/verification/canon_verifier.rs | 3 +++ ethcore/src/verification/mod.rs | 3 +++ ethcore/src/verification/noop_verifier.rs | 3 +++ ethcore/src/verification/verification.rs | 12 ++++++------ ethcore/src/verification/verifier.rs | 4 ++++ 6 files changed, 20 insertions(+), 8 deletions(-) diff --git a/ethcore/src/lib.rs b/ethcore/src/lib.rs index c42abd34b..bf3e59171 100644 --- a/ethcore/src/lib.rs +++ b/ethcore/src/lib.rs @@ -137,7 +137,7 @@ pub mod miner; pub mod snapshot; pub mod action_params; pub mod db; -pub mod light; +pub mod verification; #[macro_use] pub mod evm; mod cache_manager; @@ -151,7 +151,6 @@ mod account_db; mod builtin; mod executive; mod externalities; -mod verification; mod blockchain; mod types; mod factory; diff --git a/ethcore/src/verification/canon_verifier.rs b/ethcore/src/verification/canon_verifier.rs index cc6bc448a..b5b01279e 100644 --- a/ethcore/src/verification/canon_verifier.rs +++ b/ethcore/src/verification/canon_verifier.rs @@ -14,6 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +//! Canonical verifier. + use blockchain::BlockProvider; use engines::Engine; use error::Error; @@ -21,6 +23,7 @@ use header::Header; use super::Verifier; use super::verification; +/// A canonial verifier -- this does full verification. pub struct CanonVerifier; impl Verifier for CanonVerifier { diff --git a/ethcore/src/verification/mod.rs b/ethcore/src/verification/mod.rs index 239c88597..55663052b 100644 --- a/ethcore/src/verification/mod.rs +++ b/ethcore/src/verification/mod.rs @@ -14,6 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +//! Block verification utilities. + pub mod verification; pub mod verifier; pub mod queue; @@ -44,6 +46,7 @@ impl Default for VerifierType { } } +/// Create a new verifier based on type. pub fn new(v: VerifierType) -> Box { match v { VerifierType::Canon | VerifierType::CanonNoSeal => Box::new(CanonVerifier), diff --git a/ethcore/src/verification/noop_verifier.rs b/ethcore/src/verification/noop_verifier.rs index fb798be46..7db688a85 100644 --- a/ethcore/src/verification/noop_verifier.rs +++ b/ethcore/src/verification/noop_verifier.rs @@ -14,12 +14,15 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +//! No-op verifier. + use blockchain::BlockProvider; use engines::Engine; use error::Error; use header::Header; use super::Verifier; +/// A no-op verifier -- this will verify everything it's given immediately. #[allow(dead_code)] pub struct NoopVerifier; diff --git a/ethcore/src/verification/verification.rs b/ethcore/src/verification/verification.rs index 1b8eddfe8..4ebd8aebd 100644 --- a/ethcore/src/verification/verification.rs +++ b/ethcore/src/verification/verification.rs @@ -14,12 +14,12 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -/// Block and transaction verification functions -/// -/// Block verification is done in 3 steps -/// 1. Quick verification upon adding to the block queue -/// 2. Signatures verification done in the queue. -/// 3. Final verification against the blockchain done before enactment. +//! Block and transaction verification functions +//! +//! Block verification is done in 3 steps +//! 1. Quick verification upon adding to the block queue +//! 2. Signatures verification done in the queue. +//! 3. Final verification against the blockchain done before enactment. use util::*; use engines::Engine; diff --git a/ethcore/src/verification/verifier.rs b/ethcore/src/verification/verifier.rs index 7f57407f7..05d488f95 100644 --- a/ethcore/src/verification/verifier.rs +++ b/ethcore/src/verification/verifier.rs @@ -14,6 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +//! A generic verifier trait. + use blockchain::BlockProvider; use engines::Engine; use error::Error; @@ -21,6 +23,8 @@ use header::Header; /// Should be used to verify blocks. pub trait Verifier: Send + Sync { + /// Verify a block relative to its parent and uncles. fn verify_block_family(&self, header: &Header, bytes: &[u8], engine: &Engine, bc: &BlockProvider) -> Result<(), Error>; + /// Do a final verification check for an enacted header vs its expected counterpart. fn verify_block_final(&self, expected: &Header, got: &Header) -> Result<(), Error>; } From c1a6dbe75f617e2e0f98f16801a5a1c5b0ab1d67 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Fri, 4 Nov 2016 19:40:11 +0100 Subject: [PATCH 12/37] Move all light client work to own crate --- ethcore/light/Cargo.toml | 15 +++++++ ethcore/{src/light => light/src}/client.rs | 39 +++++++++------- .../{src/light/mod.rs => light/src/lib.rs} | 17 ++++--- .../light => ethcore/light/src/net}/mod.rs | 21 ++++----- ethcore/{src/light => light/src}/provider.rs | 26 ++++++----- .../light => ethcore/light/src}/request.rs | 4 +- ethcore/src/light/sync.rs | 33 -------------- ethcore/src/types/mod.rs.in | 1 - ethcore/src/types/proof_request.rs | 44 ------------------- sync/src/lib.rs | 1 - 10 files changed, 75 insertions(+), 126 deletions(-) create mode 100644 ethcore/light/Cargo.toml rename ethcore/{src/light => light/src}/client.rs (70%) rename ethcore/{src/light/mod.rs => light/src/lib.rs} (84%) rename {sync/src/light => ethcore/light/src/net}/mod.rs (95%) rename ethcore/{src/light => light/src}/provider.rs (78%) rename {sync/src/light => ethcore/light/src}/request.rs (98%) delete mode 100644 ethcore/src/light/sync.rs delete mode 100644 ethcore/src/types/proof_request.rs diff --git a/ethcore/light/Cargo.toml b/ethcore/light/Cargo.toml new file mode 100644 index 000000000..dcfb4760a --- /dev/null +++ b/ethcore/light/Cargo.toml @@ -0,0 +1,15 @@ +[package] +description = "Parity LES primitives" +homepage = "https://ethcore.io" +license = "GPL-3.0" +name = "ethcore-light" +version = "1.5.0" +authors = ["Ethcore "] + +[dependencies] +log = "0.3" +ethcore = { path = ".." } +ethcore-util = { path = "../../util" } +ethcore-network = { path = "../../util/network" } +ethcore-io = { path = "../../util/io" } +rlp = { path = "../../util/rlp" } \ No newline at end of file diff --git a/ethcore/src/light/client.rs b/ethcore/light/src/client.rs similarity index 70% rename from ethcore/src/light/client.rs rename to ethcore/light/src/client.rs index 9f1b00e71..2d9b643bc 100644 --- a/ethcore/src/light/client.rs +++ b/ethcore/light/src/client.rs @@ -19,22 +19,23 @@ use std::sync::Arc; -use engines::Engine; -use ids::BlockID; -use miner::TransactionQueue; -use service::ClientIoMessage; -use block_import_error::BlockImportError; -use block_status::BlockStatus; -use verification::queue::{Config as QueueConfig, HeaderQueue, QueueInfo, Status}; -use transaction::SignedTransaction; -use types::blockchain_info::BlockChainInfo; - -use super::provider::{CHTProofRequest, Provider, ProofRequest}; +use ethcore::engines::Engine; +use ethcore::ids::BlockID; +use ethcore::miner::TransactionQueue; +use ethcore::service::ClientIoMessage; +use ethcore::block_import_error::BlockImportError; +use ethcore::block_status::BlockStatus; +use ethcore::verification::queue::{Config as QueueConfig, HeaderQueue, QueueInfo, Status}; +use ethcore::transaction::SignedTransaction; +use ethcore::blockchain_info::BlockChainInfo; use io::IoChannel; use util::hash::H256; use util::{Bytes, Mutex}; +use provider::Provider; +use request; + /// Light client implementation. pub struct Client { engine: Arc, @@ -78,27 +79,31 @@ impl Provider for Client { unimplemented!() } - fn block_headers(&self, block: (u64, H256), skip: usize, max: usize, reverse: bool) -> Vec { + fn block_headers(&self, _req: request::Headers) -> Vec { Vec::new() } - fn block_bodies(&self, blocks: Vec) -> Vec { + fn block_bodies(&self, _req: request::Bodies) -> Vec { Vec::new() } - fn receipts(&self, blocks: Vec) -> Vec { + fn receipts(&self, _req: request::Receipts) -> Vec { Vec::new() } - fn proofs(&self, requests: Vec<(H256, ProofRequest)>) -> Vec { + fn proofs(&self, _req: request::StateProofs) -> Vec { Vec::new() } - fn code(&self, accounts: Vec<(H256, H256)>) -> Vec { + fn code(&self, _req: request::ContractCodes) -> Vec { Vec::new() } - fn header_proofs(&self, requests: Vec) -> Vec { + fn header_proofs(&self, _req: request::HeaderProofs) -> Vec { + Vec::new() + } + + fn block_deltas(&self, _req: request::BlockDeltas) -> Vec { Vec::new() } diff --git a/ethcore/src/light/mod.rs b/ethcore/light/src/lib.rs similarity index 84% rename from ethcore/src/light/mod.rs rename to ethcore/light/src/lib.rs index 863f35433..1bfb6569a 100644 --- a/ethcore/src/light/mod.rs +++ b/ethcore/light/src/lib.rs @@ -28,9 +28,16 @@ //! It starts by performing a header-only sync, verifying every header in //! the chain. -mod client; -mod provider; -mod sync; +pub mod client; +pub mod net; +pub mod provider; +pub mod request; -pub use self::client::Client; -pub use self::provider::{CHTProofRequest, ProofRequest, Provider}; \ No newline at end of file +extern crate ethcore_util as util; +extern crate ethcore_network as network; +extern crate ethcore_io as io; +extern crate ethcore; +extern crate rlp; + +#[macro_use] +extern crate log; \ No newline at end of file diff --git a/sync/src/light/mod.rs b/ethcore/light/src/net/mod.rs similarity index 95% rename from sync/src/light/mod.rs rename to ethcore/light/src/net/mod.rs index 19dac8561..ac0d1f98d 100644 --- a/sync/src/light/mod.rs +++ b/ethcore/light/src/net/mod.rs @@ -19,19 +19,17 @@ //! This uses a "Provider" to answer requests and syncs to a `Client`. //! See https://github.com/ethcore/parity/wiki/Light-Ethereum-Subprotocol-(LES) -use ethcore::light::{Client, Provider}; use io::TimerToken; use network::{NetworkProtocolHandler, NetworkService, NetworkContext, NetworkError, PeerId}; use rlp::{DecoderError, RlpStream, Stream, UntrustedRlp, View}; use util::hash::H256; -use parking_lot::{Mutex, RwLock}; +use util::{Mutex, RwLock}; use std::collections::{HashMap, HashSet}; use std::sync::atomic::{AtomicUsize, Ordering}; -use self::request::Request; - -mod request; +use provider::Provider; +use request::Request; const TIMEOUT: TimerToken = 0; const TIMEOUT_INTERVAL_MS: u64 = 1000; @@ -163,7 +161,7 @@ impl LightProtocol { } fn send_status(&self, peer: PeerId, io: &NetworkContext) -> Result<(), NetworkError> { - let chain_info = self.client.chain_info(); + let chain_info = self.provider.chain_info(); // TODO [rob] use optional keys too. let mut stream = RlpStream::new_list(6); @@ -210,14 +208,13 @@ impl LightProtocol { fn get_block_headers(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { const MAX_HEADERS: usize = 512; - let req_id: u64 = try_dc!(io, peer, data.val_at(0)); - let block = try_dc!(io, peer, data.at(1).and_then(|block_list| { - (try!(block_list.val_at(0)), try!(block_list.val_at(1)) + let req_id: u64 = try_dc!(io, *peer, data.val_at(0)); + let block: (u64, H256) = try_dc!(io, *peer, data.at(1).and_then(|block_list| { + Ok((try!(block_list.val_at(0)), try!(block_list.val_at(1)))) })); - let max = ::std::cmp::min(MAX_HEADERS, try_dc!(io, peer, data.val_at(2))); - let reverse = try_dc!(io, peer, data.val_at(3)); + let max = ::std::cmp::min(MAX_HEADERS, try_dc!(io, *peer, data.val_at(2))); + let reverse: bool = try_dc!(io, *peer, data.val_at(3)); - let headers = self.provider.block_headers() unimplemented!() } diff --git a/ethcore/src/light/provider.rs b/ethcore/light/src/provider.rs similarity index 78% rename from ethcore/src/light/provider.rs rename to ethcore/light/src/provider.rs index 10d3eed88..de0fe3e2e 100644 --- a/ethcore/src/light/provider.rs +++ b/ethcore/light/src/provider.rs @@ -17,21 +17,20 @@ //! A provider for the LES protocol. This is typically a full node, who can //! give as much data as necessary to its peers. -pub use proof_request::{CHTProofRequest, ProofRequest}; - -use transaction::SignedTransaction; -use blockchain_info::BlockChainInfo; - +use ethcore::transaction::SignedTransaction; +use ethcore::blockchain_info::BlockChainInfo; use util::Bytes; use util::hash::H256; +use request; + /// Defines the operations that a provider for `LES` must fulfill. /// /// These are defined at [1], but may be subject to change. /// Requests which can't be fulfilled should return an empty RLP list. /// /// [1]: https://github.com/ethcore/parity/wiki/Light-Ethereum-Subprotocol-(LES) -pub trait Provider: Sync { +pub trait Provider: Send + Sync { /// Provide current blockchain info. fn chain_info(&self) -> BlockChainInfo; @@ -40,27 +39,30 @@ pub trait Provider: Sync { /// /// 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, block: (u64, H256), skip: usize, max: usize, reverse: bool) -> Vec; + fn block_headers(&self, req: request::Headers) -> Vec; /// Provide as many as possible of the requested blocks (minus the headers) encoded /// in RLP format. - fn block_bodies(&self, blocks: Vec) -> Vec; + fn block_bodies(&self, req: request::Bodies) -> Vec; /// Provide the receipts as many as possible of the requested blocks. /// Returns a vector of RLP-encoded lists of receipts. - fn receipts(&self, blocks: Vec) -> Vec; + fn receipts(&self, req: request::Receipts) -> Vec; /// Provide a set of merkle proofs, as requested. Each request is a /// block hash and request parameters. /// /// Returns a vector to RLP-encoded lists satisfying the requests. - fn proofs(&self, requests: Vec<(H256, ProofRequest)>) -> Vec; + fn proofs(&self, req: request::StateProofs) -> Vec; /// Provide contract code for the specified (block_hash, account_hash) pairs. - fn code(&self, accounts: Vec<(H256, H256)>) -> Vec; + fn code(&self, req: request::ContractCodes) -> Vec; /// Provide header proofs from the Canonical Hash Tries. - fn header_proofs(&self, requests: Vec) -> Vec; + fn header_proofs(&self, req: request::HeaderProofs) -> Vec; + + /// Provide block deltas. + fn block_deltas(&self, req: request::BlockDeltas) -> Vec; /// Provide pending transactions. fn pending_transactions(&self) -> Vec; diff --git a/sync/src/light/request.rs b/ethcore/light/src/request.rs similarity index 98% rename from sync/src/light/request.rs rename to ethcore/light/src/request.rs index bbb91e415..63ec07068 100644 --- a/sync/src/light/request.rs +++ b/ethcore/light/src/request.rs @@ -14,7 +14,9 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -/// LES request types. +//! LES request types. + +// TODO: make IPC compatible. use ethcore::transaction::Transaction; use util::{Address, H256}; diff --git a/ethcore/src/light/sync.rs b/ethcore/src/light/sync.rs deleted file mode 100644 index 15ed5673c..000000000 --- a/ethcore/src/light/sync.rs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2015, 2016 Ethcore (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 . - -//! A light sync target. - -use block_import_error::BlockImportError; -use client::BlockChainInfo; - -use util::hash::H256; - -pub trait Sync { - /// Whether syncing is enabled. - fn enabled(&self) -> bool; - - /// Current chain info. - fn chain_info(&self) -> BlockChainInfo; - - /// Import a header. - fn import_header(&self, header_bytes: Vec) -> Result; -} \ No newline at end of file diff --git a/ethcore/src/types/mod.rs.in b/ethcore/src/types/mod.rs.in index b00a64f85..6ef67009a 100644 --- a/ethcore/src/types/mod.rs.in +++ b/ethcore/src/types/mod.rs.in @@ -33,5 +33,4 @@ pub mod transaction_import; pub mod block_import_error; pub mod restoration_status; pub mod snapshot_manifest; -pub mod proof_request; pub mod mode; diff --git a/ethcore/src/types/proof_request.rs b/ethcore/src/types/proof_request.rs deleted file mode 100644 index 20bddce21..000000000 --- a/ethcore/src/types/proof_request.rs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2015, 2016 Ethcore (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 . - -//! Merkle proof request. -use util::hash::H256; - -/// A request for a state merkle proof. -#[derive(Debug, Clone, PartialEq, Eq, Binary)] -pub enum ProofRequest { - /// Request a proof of the given account's (denoted by sha3(address)) - /// node in the state trie. Nodes with depth less than the second item - /// may be omitted. - Account(H256, usize), - - /// Request a proof for a key in the given account's storage trie. - /// Both values are hashes of their actual values. Nodes with depth - /// less than the third item may be omitted. - Storage(H256, H256, usize), -} - -/// A request for a Canonical Hash Trie proof for the given block number. -/// Nodes with depth less than the second item may be omitted. -#[derive(Debug, Clone, PartialEq, Eq, Binary)] -pub struct CHTProofRequest { - /// The number of the block the proof is requested for. - /// The CHT's number can be deduced from this (`number` / 4096) - pub number: u64, - - /// Nodes with depth less than this can be omitted from the proof. - pub depth: usize, -} \ No newline at end of file diff --git a/sync/src/lib.rs b/sync/src/lib.rs index 4983b3c90..532c05711 100644 --- a/sync/src/lib.rs +++ b/sync/src/lib.rs @@ -51,7 +51,6 @@ mod blocks; mod block_sync; mod sync_io; mod snapshot; -mod light; #[cfg(test)] mod tests; From 52abbc06435eec38578cf4bbf0742174c2aa2d36 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Fri, 4 Nov 2016 23:50:56 +0100 Subject: [PATCH 13/37] experiment with answering requests --- ethcore/light/src/net/mod.rs | 28 ++++++++++++++++++++-------- ethcore/light/src/provider.rs | 1 - 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/ethcore/light/src/net/mod.rs b/ethcore/light/src/net/mod.rs index ac0d1f98d..390c6a35b 100644 --- a/ethcore/light/src/net/mod.rs +++ b/ethcore/light/src/net/mod.rs @@ -29,7 +29,7 @@ use std::collections::{HashMap, HashSet}; use std::sync::atomic::{AtomicUsize, Ordering}; use provider::Provider; -use request::Request; +use request::{self, Request}; const TIMEOUT: TimerToken = 0; const TIMEOUT_INTERVAL_MS: u64 = 1000; @@ -206,16 +206,28 @@ impl LightProtocol { // Handle a request for block headers. fn get_block_headers(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { - const MAX_HEADERS: usize = 512; + const MAX_HEADERS: u64 = 512; let req_id: u64 = try_dc!(io, *peer, data.val_at(0)); - let block: (u64, H256) = try_dc!(io, *peer, data.at(1).and_then(|block_list| { - Ok((try!(block_list.val_at(0)), try!(block_list.val_at(1)))) - })); - let max = ::std::cmp::min(MAX_HEADERS, try_dc!(io, *peer, data.val_at(2))); - let reverse: bool = try_dc!(io, *peer, data.val_at(3)); + let req = request::Headers { + block: try_dc!(io, *peer, data.at(1).and_then(|block_list| { + Ok((try!(block_list.val_at(0)), try!(block_list.val_at(1)))) + })), + max: ::std::cmp::min(MAX_HEADERS, try_dc!(io, *peer, data.val_at(2))), + skip: try_dc!(io, *peer, data.val_at(3)), + reverse: try_dc!(io, *peer, data.val_at(4)), + }; - unimplemented!() + let res = self.provider.block_headers(req); + + let mut res_stream = RlpStream::new_list(2 + res.len()); + res_stream.append(&req_id); + res_stream.append(&0u64); // TODO: Buffer Flow. + for raw_header in res { + res_stream.append_raw(&raw_header, 1); + } + + try_dc!(io, *peer, io.respond(packet::BLOCK_HEADERS, res_stream.out())) } // Receive a response for block headers. diff --git a/ethcore/light/src/provider.rs b/ethcore/light/src/provider.rs index de0fe3e2e..6d4fb2ea0 100644 --- a/ethcore/light/src/provider.rs +++ b/ethcore/light/src/provider.rs @@ -20,7 +20,6 @@ use ethcore::transaction::SignedTransaction; use ethcore::blockchain_info::BlockChainInfo; use util::Bytes; -use util::hash::H256; use request; From 44e36596c9c0e8d606ffb89b6fa171b3ff04f53d Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Sun, 6 Nov 2016 19:04:30 +0100 Subject: [PATCH 14/37] buffer flow scaffolding --- ethcore/light/src/lib.rs | 4 +-- ethcore/light/src/net/buffer_flow.rs | 42 ++++++++++++++++++++++++++++ ethcore/light/src/net/mod.rs | 4 +-- 3 files changed, 46 insertions(+), 4 deletions(-) create mode 100644 ethcore/light/src/net/buffer_flow.rs diff --git a/ethcore/light/src/lib.rs b/ethcore/light/src/lib.rs index 1bfb6569a..63586f5a2 100644 --- a/ethcore/light/src/lib.rs +++ b/ethcore/light/src/lib.rs @@ -25,8 +25,8 @@ //! 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 every header in -//! the chain. +//! It starts by performing a header-only sync, verifying random samples +//! of members of the chain to varying degrees. pub mod client; pub mod net; diff --git a/ethcore/light/src/net/buffer_flow.rs b/ethcore/light/src/net/buffer_flow.rs new file mode 100644 index 000000000..ece9f7057 --- /dev/null +++ b/ethcore/light/src/net/buffer_flow.rs @@ -0,0 +1,42 @@ +// Copyright 2015, 2016 Ethcore (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 . + +//! LES buffer flow management. +//! +//! Every request in the LES protocol leads to a reduction +//! of the requester's buffer value as a rate-limiting mechanism. +//! This buffer value will recharge at a set rate. +//! +//! This module provides an interface for configuration of buffer +//! flow costs and recharge rates. + +use request::{self, Request}; + +/// Manages buffer flow costs for specific requests. +pub struct FlowManager; + +impl FlowManager { + /// Estimate the maximum cost of this request. + pub fn estimate_cost(&self, req: &request::Request) -> usize { + unimplemented!() + } + + /// Get an exact cost based on request kind and amount of requests fulfilled. + pub fn exact_cost(&self, kind: request::Kind, amount: usize) -> usize { + unimplemented!() + } +} + diff --git a/ethcore/light/src/net/mod.rs b/ethcore/light/src/net/mod.rs index 390c6a35b..efbc391e1 100644 --- a/ethcore/light/src/net/mod.rs +++ b/ethcore/light/src/net/mod.rs @@ -31,6 +31,8 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use provider::Provider; use request::{self, Request}; +mod buffer_flow; + const TIMEOUT: TimerToken = 0; const TIMEOUT_INTERVAL_MS: u64 = 1000; @@ -40,8 +42,6 @@ const PROTOCOL_VERSION: u32 = 1; // TODO [rob] make configurable. const PROTOCOL_ID: [u8; 3] = *b"les"; -// TODO [rob] Buffer flow. - // packet ID definitions. mod packet { // the status packet. From d573ef3cc27d00509ccc36ca731278fedb143f7b Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Sun, 6 Nov 2016 19:05:19 +0100 Subject: [PATCH 15/37] remove LESv2 requests --- ethcore/light/src/client.rs | 4 ---- ethcore/light/src/net/mod.rs | 13 +++++++------ ethcore/light/src/provider.rs | 3 --- ethcore/light/src/request.rs | 35 ----------------------------------- 4 files changed, 7 insertions(+), 48 deletions(-) diff --git a/ethcore/light/src/client.rs b/ethcore/light/src/client.rs index 2d9b643bc..81355bf0f 100644 --- a/ethcore/light/src/client.rs +++ b/ethcore/light/src/client.rs @@ -103,10 +103,6 @@ impl Provider for Client { Vec::new() } - fn block_deltas(&self, _req: request::BlockDeltas) -> Vec { - Vec::new() - } - fn pending_transactions(&self) -> Vec { Vec::new() } diff --git a/ethcore/light/src/net/mod.rs b/ethcore/light/src/net/mod.rs index efbc391e1..9b707db2f 100644 --- a/ethcore/light/src/net/mod.rs +++ b/ethcore/light/src/net/mod.rs @@ -23,13 +23,14 @@ use io::TimerToken; use network::{NetworkProtocolHandler, NetworkService, NetworkContext, NetworkError, PeerId}; use rlp::{DecoderError, RlpStream, Stream, UntrustedRlp, View}; use util::hash::H256; -use util::{Mutex, RwLock}; +use util::{U256, Mutex, RwLock}; use std::collections::{HashMap, HashSet}; use std::sync::atomic::{AtomicUsize, Ordering}; use provider::Provider; use request::{self, Request}; +use self::buffer_flow::FlowManager; mod buffer_flow; @@ -47,8 +48,8 @@ mod packet { // the status packet. pub const STATUS: u8 = 0x00; - // broadcast that new block hashes have appeared. - pub const NEW_BLOCK_HASHES: u8 = 0x01; + // announcement of new block hashes or capabilities. + pub const ANNOUNCE: u8 = 0x01; // request and response for block headers pub const GET_BLOCK_HEADERS: u8 = 0x02; @@ -197,8 +198,8 @@ impl LightProtocol { unimplemented!() } - // Handle a new block hashes message. - fn new_block_hashes(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { + // Handle an announcement. + fn announcement(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { const MAX_NEW_HASHES: usize = 256; unimplemented!() @@ -327,7 +328,7 @@ impl NetworkProtocolHandler for LightProtocol { let rlp = UntrustedRlp::new(data); match packet_id { packet::STATUS => self.status(peer, io, rlp), - packet::NEW_BLOCK_HASHES => self.new_block_hashes(peer, io, rlp), + packet::ANNOUNCE => self.announcement(peer, io, rlp), packet::GET_BLOCK_HEADERS => self.get_block_headers(peer, io, rlp), packet::BLOCK_HEADERS => self.block_headers(peer, io, rlp), diff --git a/ethcore/light/src/provider.rs b/ethcore/light/src/provider.rs index 6d4fb2ea0..d6458bedd 100644 --- a/ethcore/light/src/provider.rs +++ b/ethcore/light/src/provider.rs @@ -60,9 +60,6 @@ pub trait Provider: Send + Sync { /// Provide header proofs from the Canonical Hash Tries. fn header_proofs(&self, req: request::HeaderProofs) -> Vec; - /// Provide block deltas. - fn block_deltas(&self, req: request::BlockDeltas) -> Vec; - /// Provide pending transactions. fn pending_transactions(&self) -> Vec; } \ No newline at end of file diff --git a/ethcore/light/src/request.rs b/ethcore/light/src/request.rs index 63ec07068..f5c594970 100644 --- a/ethcore/light/src/request.rs +++ b/ethcore/light/src/request.rs @@ -83,31 +83,6 @@ pub struct HeaderProofs { pub from_level: u32, } -/// A request for block deltas -- merkle proofs of all changed trie nodes and code. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct BlockDeltas { - /// Block hashes deltas are being requested for. - pub block_hashes: Vec, -} - -/// A request for a single transaction merkle proof. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct TransactionProof { - /// The block hash to use the initial state from. - pub block_hash: H256, - /// The address to treat as the sender of the transaction. - pub sender: Address, - /// The raw transaction request itself. - pub transaction: Transaction, -} - -/// A request for transaction merkle proofs. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct TransactionProofs { - /// Transaction proof requests. - pub tx_reqs: Vec, -} - /// Kinds of requests. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Kind { @@ -123,10 +98,6 @@ pub enum Kind { Codes, /// Requesting header proofs (from the CHT). HeaderProofs, - /// Requesting block deltas. - Deltas, - /// Requesting merkle proofs for transactions. - TransactionProofs, } /// Encompasses all possible types of requests in a single structure. @@ -144,10 +115,6 @@ pub enum Request { Codes(ContractCodes), /// Requesting header proofs. HeaderProofs(HeaderProofs), - /// Requesting block deltas. - Deltas(BlockDeltas), - /// Requesting transaction proofs. - TransactionProofs(TransactionProofs), } impl Request { @@ -160,8 +127,6 @@ impl Request { Request::StateProofs(_) => Kind::StateProofs, Request::Codes(_) => Kind::Codes, Request::HeaderProofs(_) => Kind::HeaderProofs, - Request::Deltas(_) => Kind::Deltas, - Request::TransactionProofs(_) => Kind::TransactionProofs, } } } \ No newline at end of file From 051effe9f8ceca371eaf4ca7c9c7898c956d211a Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Mon, 7 Nov 2016 15:40:34 +0100 Subject: [PATCH 16/37] buffer flow basics, implement cost table --- ethcore/light/Cargo.toml | 3 +- ethcore/light/src/lib.rs | 1 + ethcore/light/src/net/buffer_flow.rs | 244 ++++++++++++++++++++++++++- ethcore/light/src/net/mod.rs | 85 +--------- ethcore/light/src/request.rs | 22 ++- 5 files changed, 258 insertions(+), 97 deletions(-) diff --git a/ethcore/light/Cargo.toml b/ethcore/light/Cargo.toml index dcfb4760a..daf141de7 100644 --- a/ethcore/light/Cargo.toml +++ b/ethcore/light/Cargo.toml @@ -12,4 +12,5 @@ ethcore = { path = ".." } ethcore-util = { path = "../../util" } ethcore-network = { path = "../../util/network" } ethcore-io = { path = "../../util/io" } -rlp = { path = "../../util/rlp" } \ No newline at end of file +rlp = { path = "../../util/rlp" } +time = "0.1" \ No newline at end of file diff --git a/ethcore/light/src/lib.rs b/ethcore/light/src/lib.rs index 63586f5a2..f6d05c65c 100644 --- a/ethcore/light/src/lib.rs +++ b/ethcore/light/src/lib.rs @@ -38,6 +38,7 @@ extern crate ethcore_network as network; extern crate ethcore_io as io; extern crate ethcore; extern crate rlp; +extern crate time; #[macro_use] extern crate log; \ No newline at end of file diff --git a/ethcore/light/src/net/buffer_flow.rs b/ethcore/light/src/net/buffer_flow.rs index ece9f7057..deed1cded 100644 --- a/ethcore/light/src/net/buffer_flow.rs +++ b/ethcore/light/src/net/buffer_flow.rs @@ -24,19 +24,245 @@ //! flow costs and recharge rates. use request::{self, Request}; +use super::packet; -/// Manages buffer flow costs for specific requests. -pub struct FlowManager; +use rlp::*; +use util::U256; +use time::{Duration, SteadyTime}; -impl FlowManager { - /// Estimate the maximum cost of this request. - pub fn estimate_cost(&self, req: &request::Request) -> usize { - unimplemented!() +/// A request cost specification. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Cost(pub U256, pub U256); + +/// An error: insufficient buffer. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct InsufficientBuffer; + +/// Buffer value. +/// +/// Produced and recharged using `FlowParams`. +/// Definitive updates can be made as well -- these will reset the recharge +/// point to the time of the update. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Buffer { + estimate: U256, + recharge_point: SteadyTime, +} + +impl Buffer { + /// 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 = SteadyTime::now(); } - /// Get an exact cost based on request kind and amount of requests fulfilled. - pub fn exact_cost(&self, kind: request::Kind, amount: usize) -> usize { - unimplemented!() + /// Attempt to apply the given cost to the buffer. + /// 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<(), InsufficientBuffer> { + match cost > self.estimate { + true => Err(InsufficientBuffer), + false => { + self.estimate = self.estimate - cost; + Ok(()) + } + } } } +/// A cost table, mapping requests to base and per-request costs. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct CostTable { + headers: Cost, + bodies: Cost, + receipts: Cost, + state_proofs: Cost, + contract_codes: Cost, + header_proofs: Cost, +} + +impl Default for CostTable { + fn default() -> Self { + // arbitrarily chosen constants. + CostTable { + headers: Cost(100000.into(), 10000.into()), + bodies: Cost(150000.into(), 15000.into()), + receipts: Cost(50000.into(), 5000.into()), + state_proofs: Cost(250000.into(), 25000.into()), + contract_codes: Cost(200000.into(), 20000.into()), + header_proofs: Cost(150000.into(), 15000.into()), + } + } +} + +impl RlpEncodable for CostTable { + fn rlp_append(&self, s: &mut RlpStream) { + fn append_cost(s: &mut RlpStream, msg_id: u8, cost: &Cost) { + s.begin_list(3) + .append(&msg_id) + .append(&cost.0) + .append(&cost.1); + } + + s.begin_list(6); + + append_cost(s, packet::GET_BLOCK_HEADERS, &self.headers); + append_cost(s, packet::GET_BLOCK_BODIES, &self.bodies); + append_cost(s, packet::GET_RECEIPTS, &self.receipts); + append_cost(s, packet::GET_PROOFS, &self.state_proofs); + append_cost(s, packet::GET_CONTRACT_CODES, &self.contract_codes); + append_cost(s, packet::GET_HEADER_PROOFS, &self.header_proofs); + } +} + +impl RlpDecodable for CostTable { + fn decode(decoder: &D) -> Result where D: Decoder { + let rlp = decoder.as_rlp(); + + let mut headers = None; + let mut bodies = None; + let mut receipts = None; + let mut state_proofs = None; + let mut contract_codes = None; + let mut header_proofs = None; + + for row in rlp.iter() { + let msg_id: u8 = try!(row.val_at(0)); + let cost = { + let base = try!(row.val_at(1)); + let per = try!(row.val_at(2)); + + Cost(base, per) + }; + + match msg_id { + packet::GET_BLOCK_HEADERS => headers = Some(cost), + packet::GET_BLOCK_BODIES => bodies = Some(cost), + packet::GET_RECEIPTS => receipts = Some(cost), + packet::GET_PROOFS => state_proofs = Some(cost), + packet::GET_CONTRACT_CODES => contract_codes = Some(cost), + packet::GET_HEADER_PROOFS => header_proofs = Some(cost), + _ => return Err(DecoderError::Custom("Unrecognized message in cost table")), + } + } + + Ok(CostTable { + headers: try!(headers.ok_or(DecoderError::Custom("No headers cost specified"))), + bodies: try!(bodies.ok_or(DecoderError::Custom("No bodies cost specified"))), + receipts: try!(receipts.ok_or(DecoderError::Custom("No receipts cost specified"))), + state_proofs: try!(state_proofs.ok_or(DecoderError::Custom("No proofs cost specified"))), + contract_codes: try!(contract_codes.ok_or(DecoderError::Custom("No contract codes specified"))), + header_proofs: try!(header_proofs.ok_or(DecoderError::Custom("No header proofs cost specified"))), + }) + } +} + +/// A buffer-flow manager handles costs, recharge, limits +#[derive(Debug, Clone, PartialEq)] +pub struct FlowParams { + costs: CostTable, + limit: U256, + recharge: U256, +} + +impl FlowParams { + /// Create new flow parameters from a request cost table, + /// buffer limit, and (minimum) rate of recharge. + pub fn new(costs: CostTable, limit: U256, recharge: U256) -> Self { + FlowParams { + costs: costs, + limit: limit, + recharge: recharge, + } + } + + /// Estimate the maximum cost of the request. + pub fn max_cost(&self, req: &Request) -> U256 { + let amount = match *req { + Request::Headers(ref req) => req.max as usize, + Request::Bodies(ref req) => req.block_hashes.len(), + Request::Receipts(ref req) => req.block_hashes.len(), + Request::StateProofs(ref req) => req.requests.len(), + Request::Codes(ref req) => req.code_requests.len(), + Request::HeaderProofs(ref req) => req.requests.len(), + }; + + self.actual_cost(req.kind(), amount) + } + + /// Compute the actual cost of a request, given the kind of request + /// and number of requests made. + pub fn actual_cost(&self, kind: request::Kind, amount: usize) -> U256 { + let cost = match kind { + request::Kind::Headers => &self.costs.headers, + request::Kind::Bodies => &self.costs.bodies, + request::Kind::Receipts => &self.costs.receipts, + request::Kind::StateProofs => &self.costs.state_proofs, + request::Kind::Codes => &self.costs.contract_codes, + request::Kind::HeaderProofs => &self.costs.header_proofs, + }; + + let amount: U256 = amount.into(); + cost.0 + (amount * cost.1) + } + + /// Create initial buffer parameter. + pub fn create_buffer(&self) -> Buffer { + Buffer { + estimate: self.limit, + recharge_point: SteadyTime::now(), + } + } + + /// Recharge the buffer based on time passed since last + /// update. + pub fn recharge(&self, buf: &mut Buffer) { + let now = SteadyTime::now(); + + // recompute and update only in terms of full seconds elapsed + // in order to keep the estimate as an underestimate. + let elapsed = (now - buf.recharge_point).num_seconds(); + buf.recharge_point = buf.recharge_point + Duration::seconds(elapsed); + + let elapsed: U256 = elapsed.into(); + + buf.estimate = ::std::cmp::min(self.limit, buf.estimate + (elapsed * self.recharge)); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use util::U256; + + #[test] + fn should_serialize_cost_table() { + let costs = CostTable::default(); + let serialized = ::rlp::encode(&costs); + + let new_costs: CostTable = ::rlp::decode(&*serialized); + + assert_eq!(costs, new_costs); + } + + #[test] + fn buffer_mechanism() { + use std::thread; + use std::time::Duration; + + let flow_params = FlowParams::new(Default::default(), 100.into(), 20.into()); + let mut buffer = flow_params.create_buffer(); + + assert!(buffer.deduct_cost(101.into()).is_err()); + assert!(buffer.deduct_cost(10.into()).is_ok()); + + thread::sleep(Duration::from_secs(1)); + + flow_params.recharge(&mut buffer); + + assert_eq!(buffer.estimate, 100.into()); + } +} \ No newline at end of file diff --git a/ethcore/light/src/net/mod.rs b/ethcore/light/src/net/mod.rs index 9b707db2f..34fe8acf2 100644 --- a/ethcore/light/src/net/mod.rs +++ b/ethcore/light/src/net/mod.rs @@ -30,7 +30,7 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use provider::Provider; use request::{self, Request}; -use self::buffer_flow::FlowManager; +use self::buffer_flow::FlowParams; mod buffer_flow; @@ -77,33 +77,6 @@ mod packet { // request and response for header proofs in a CHT. pub const GET_HEADER_PROOFS: u8 = 0x0d; pub const HEADER_PROOFS: u8 = 0x0e; - - // broadcast dynamic capabilities. - pub const CAPABILITIES: u8 = 0x0f; - - // request and response for block-level state deltas. - pub const GET_BLOCK_DELTAS: u8 = 0x10; - pub const BLOCK_DELTAS: u8 = 0x11; - - // request and response for transaction proofs. - pub const GET_TRANSACTION_PROOFS: u8 = 0x12; - pub const TRANSACTION_PROOFS: u8 = 0x13; -} - -// helper macro for disconnecting peer on error while returning -// the value if ok. -// requires that error types are debug. -macro_rules! try_dc { - ($io: expr, $peer: expr, $e: expr) => { - match $e { - Ok(x) => x, - Err(e) => { - debug!(target: "les", "disconnecting peer {} due to error {:?}", $peer, e); - $io.disconnect_peer($peer); - return; - } - } - } } struct Requested { @@ -209,26 +182,7 @@ impl LightProtocol { fn get_block_headers(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { const MAX_HEADERS: u64 = 512; - let req_id: u64 = try_dc!(io, *peer, data.val_at(0)); - let req = request::Headers { - block: try_dc!(io, *peer, data.at(1).and_then(|block_list| { - Ok((try!(block_list.val_at(0)), try!(block_list.val_at(1)))) - })), - max: ::std::cmp::min(MAX_HEADERS, try_dc!(io, *peer, data.val_at(2))), - skip: try_dc!(io, *peer, data.val_at(3)), - reverse: try_dc!(io, *peer, data.val_at(4)), - }; - - let res = self.provider.block_headers(req); - - let mut res_stream = RlpStream::new_list(2 + res.len()); - res_stream.append(&req_id); - res_stream.append(&0u64); // TODO: Buffer Flow. - for raw_header in res { - res_stream.append_raw(&raw_header, 1); - } - - try_dc!(io, *peer, io.respond(packet::BLOCK_HEADERS, res_stream.out())) + unimplemented!() } // Receive a response for block headers. @@ -292,31 +246,6 @@ impl LightProtocol { fn relay_transactions(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { unimplemented!() } - - // Receive updated capabilities from a peer. - fn capabilities(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { - unimplemented!() - } - - // Handle a request for block deltas. - fn get_block_deltas(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { - unimplemented!() - } - - // Receive block deltas. - fn block_deltas(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { - unimplemented!() - } - - // Handle a request for transaction proofs. - fn get_transaction_proofs(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { - unimplemented!() - } - - // Receive transaction proofs. - fn transaction_proofs(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { - unimplemented!() - } } impl NetworkProtocolHandler for LightProtocol { @@ -346,16 +275,6 @@ impl NetworkProtocolHandler for LightProtocol { packet::CONTRACT_CODES => self.contract_code(peer, io, rlp), packet::SEND_TRANSACTIONS => self.relay_transactions(peer, io, rlp), - packet::CAPABILITIES => self.capabilities(peer, io, rlp), - - packet::GET_HEADER_PROOFS => self.get_header_proofs(peer, io, rlp), - packet::HEADER_PROOFS => self.header_proofs(peer, io, rlp), - - packet::GET_BLOCK_DELTAS => self.get_block_deltas(peer, io, rlp), - packet::BLOCK_DELTAS => self.block_deltas(peer, io, rlp), - - packet::GET_TRANSACTION_PROOFS => self.get_transaction_proofs(peer, io, rlp), - packet::TRANSACTION_PROOFS => self.transaction_proofs(peer, io, rlp), other => { debug!(target: "les", "Disconnecting peer {} on unexpected packet {}", peer, other); diff --git a/ethcore/light/src/request.rs b/ethcore/light/src/request.rs index f5c594970..11b474ee5 100644 --- a/ethcore/light/src/request.rs +++ b/ethcore/light/src/request.rs @@ -51,9 +51,9 @@ pub struct Receipts { pub block_hashes: Vec, } -/// A request for state proofs. +/// A request for a state proof #[derive(Debug, Clone, PartialEq, Eq)] -pub struct StateProofs { +pub struct StateProof { /// Block hash to query state from. pub block: H256, /// Key of the state trie -- corresponds to account hash. @@ -65,6 +65,13 @@ pub struct StateProofs { pub from_level: u32, // could even safely be u8; trie w/ 32-byte key can be at most 64-levels deep. } +/// A request for state proofs. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct StateProofs { + /// All the proof requests. + pub requests: Vec, +} + /// A request for contract code. #[derive(Debug, Clone, PartialEq, Eq)] pub struct ContractCodes { @@ -72,9 +79,9 @@ pub struct ContractCodes { pub code_requests: Vec<(H256, H256)>, } -/// A request for header proofs from the Canonical Hash Trie. +/// A request for a header proof from the Canonical Hash Trie. #[derive(Debug, Clone, PartialEq, Eq)] -pub struct HeaderProofs { +pub struct HeaderProof { /// Number of the CHT. pub cht_number: u64, /// Block number requested. @@ -83,6 +90,13 @@ pub struct HeaderProofs { pub from_level: u32, } +/// A request for header proofs from the CHT. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct HeaderProofs { + /// All the proof requests. + pub requests: Vec, +} + /// Kinds of requests. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Kind { From 4ba486173472a9ade4ca9860d5a8252bd7d5bd83 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Mon, 7 Nov 2016 19:16:23 +0100 Subject: [PATCH 17/37] begin status module --- ethcore/light/src/net/mod.rs | 8 +-- ethcore/light/src/net/status.rs | 110 ++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+), 6 deletions(-) create mode 100644 ethcore/light/src/net/status.rs diff --git a/ethcore/light/src/net/mod.rs b/ethcore/light/src/net/mod.rs index 34fe8acf2..d7bcc4b39 100644 --- a/ethcore/light/src/net/mod.rs +++ b/ethcore/light/src/net/mod.rs @@ -16,7 +16,7 @@ //! LES Protocol Version 1 implementation. //! -//! This uses a "Provider" to answer requests and syncs to a `Client`. +//! This uses a "Provider" to answer requests. //! See https://github.com/ethcore/parity/wiki/Light-Ethereum-Subprotocol-(LES) use io::TimerToken; @@ -33,6 +33,7 @@ use request::{self, Request}; use self::buffer_flow::FlowParams; mod buffer_flow; +mod status; const TIMEOUT: TimerToken = 0; const TIMEOUT_INTERVAL_MS: u64 = 1000; @@ -79,11 +80,6 @@ mod packet { pub const HEADER_PROOFS: u8 = 0x0e; } -struct Requested { - timestamp: usize, - req: Request, -} - // data about each peer. struct Peer { buffer: u64, // remaining buffer value. diff --git a/ethcore/light/src/net/status.rs b/ethcore/light/src/net/status.rs new file mode 100644 index 000000000..d89c6ac79 --- /dev/null +++ b/ethcore/light/src/net/status.rs @@ -0,0 +1,110 @@ +// Copyright 2015, 2016 Ethcore (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 . + +//! Peer status and capabilities. + +use rlp::{RlpStream, Stream, UntrustedRlp, View}; +use util::{H256, U256}; + +/// Network ID structure. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u32)] +pub enum NetworkId { + /// ID for the mainnet + Mainnet = 1, + /// ID for the testnet + Testnet = 0, +} + +/// 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: NetworkId, + /// 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: Option, + /// Last announced chain head and reorg depth to common ancestor. + pub last_head: Option<(H256, u64)>, +} + +/// Peer capabilities. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Capabilities { + /// Whether this peer can serve headers + pub serve_headers: bool, + /// Earliest block number it can serve chain requests for. + pub serve_chain_since: Option, + /// Earliest block number it can serve state requests for. + pub serve_state_since: Option, + /// Whether it can relay transactions to the eth network. + pub tx_relay: bool, +} + +impl Default for Capabilities { + fn default() -> Self { + Capabilities { + serve_headers: false, + serve_chain_since: None, + serve_state_since: None, + tx_relay: false, + } + } +} + +impl Capabilities { + /// Decode capabilities from the given rlp stream, starting from the given + /// index. + fn decode_from(rlp: &UntrustedRlp, start_idx: usize) -> Result { + let mut caps = Capabilities::default(); + + for item in rlp.iter().skip(start_idx).take(4) { + let key: String = try!(item.val_at(0)); + + match &*key { + "serveHeaders" => caps.serve_headers = true, + "serveChainSince" => caps.serve_chain_since = Some(try!(item.val_at(1))), + "serveStateSince" => caps.serve_state_since = Some(try!(item.val_at(1))), + "txRelay" => caps.tx_relay = true, + _ => continue, + } + } + + Ok(caps) + } +} + +/// 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. + head_hash: H256, + /// Number of the best block. + head_num: u64, + /// Head total difficulty + head_td: U256, + /// reorg depth to common ancestor of last announced head. + reorg_depth: u64, + /// updated capabilities. + new_capabilities: Capabilities, +} \ No newline at end of file From 440f5e537f5d39b01cf9d7c8df6e86f640b92a11 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Tue, 8 Nov 2016 16:57:10 +0100 Subject: [PATCH 18/37] implement handshake parsing and creation --- ethcore/light/src/net/buffer_flow.rs | 13 +- ethcore/light/src/net/mod.rs | 2 +- ethcore/light/src/net/status.rs | 349 +++++++++++++++++++++++++-- 3 files changed, 342 insertions(+), 22 deletions(-) diff --git a/ethcore/light/src/net/buffer_flow.rs b/ethcore/light/src/net/buffer_flow.rs index deed1cded..7ee287533 100644 --- a/ethcore/light/src/net/buffer_flow.rs +++ b/ethcore/light/src/net/buffer_flow.rs @@ -171,7 +171,7 @@ pub struct FlowParams { impl FlowParams { /// Create new flow parameters from a request cost table, /// buffer limit, and (minimum) rate of recharge. - pub fn new(costs: CostTable, limit: U256, recharge: U256) -> Self { + pub fn new(limit: U256, costs: CostTable, recharge: U256) -> Self { FlowParams { costs: costs, limit: limit, @@ -179,6 +179,15 @@ impl FlowParams { } } + /// Get a reference to the buffer 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 recharge rate. + pub fn recharge_rate(&self) -> &U256 { &self.recharge } + /// Estimate the maximum cost of the request. pub fn max_cost(&self, req: &Request) -> U256 { let amount = match *req { @@ -253,7 +262,7 @@ mod tests { use std::thread; use std::time::Duration; - let flow_params = FlowParams::new(Default::default(), 100.into(), 20.into()); + let flow_params = FlowParams::new(100.into(), Default::default(), 20.into()); let mut buffer = flow_params.create_buffer(); assert!(buffer.deduct_cost(101.into()).is_err()); diff --git a/ethcore/light/src/net/mod.rs b/ethcore/light/src/net/mod.rs index d7bcc4b39..4f0543526 100644 --- a/ethcore/light/src/net/mod.rs +++ b/ethcore/light/src/net/mod.rs @@ -97,7 +97,7 @@ pub struct LightProtocol { genesis_hash: H256, mainnet: bool, peers: RwLock>, - pending_requests: RwLock>, + pending_requests: RwLock>, req_id: AtomicUsize, } diff --git a/ethcore/light/src/net/status.rs b/ethcore/light/src/net/status.rs index d89c6ac79..23b8b68c0 100644 --- a/ethcore/light/src/net/status.rs +++ b/ethcore/light/src/net/status.rs @@ -16,9 +16,75 @@ //! Peer status and capabilities. -use rlp::{RlpStream, Stream, UntrustedRlp, View}; +use rlp::{DecoderError, RlpDecodable, RlpEncodable, RlpStream, Stream, UntrustedRlp, View}; use util::{H256, U256}; +use super::buffer_flow::{CostTable, FlowParams}; + +// recognized handshake/announcement keys. +// unknown keys are to be skipped, known keys have a defined order. +// their string values are defined in the LES spec. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Key { + 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", + } + } + + fn from_str(s: &str) -> Option { + 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 + } + } + + fn is_recognized(s: &str) -> bool { + Key::from_str(s).is_some() + } +} + /// Network ID structure. #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u32)] @@ -29,6 +95,66 @@ pub enum NetworkId { Testnet = 0, } +impl NetworkId { + fn from_raw(raw: u32) -> Option { + match raw { + 0 => Some(NetworkId::Testnet), + 1 => Some(NetworkId::Mainnet), + _ => None, + } + } +} + +// helper for decoding key-value pairs in the handshake or an announcement. +struct Parser<'a> { + pos: usize, + rlp: UntrustedRlp<'a>, +} + +impl<'a> Parser<'a> { + // attempt to parse the next key, value pair, and decode the value to the given type. + fn expect(&mut self, key: Key) -> Result { + self.expect_raw(key).and_then(|item| item.as_val()) + } + + // attempt to parse the next key, value pair, and returns the value's RLP. + fn expect_raw(&mut self, key: Key) -> Result, DecoderError> { + loop { + let pair = try!(self.rlp.at(self.pos)); + let k: String = try!(pair.val_at(0)); + let k = match Key::from_str(&k) { + Some(k) => k, + None => { + // skip any unrecognized keys. + self.pos += 1; + continue; + } + }; + + if k == key { + self.pos += 1; + return pair.at(1) + } else { + return Err(DecoderError::Custom("Missing expected key")) + } + } + } +} + +// Helper for encoding a key-value pair +fn encode_pair(key: Key, val: &T) -> Vec { + 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 { + 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 { @@ -72,26 +198,80 @@ impl Default for Capabilities { } } -impl Capabilities { - /// Decode capabilities from the given rlp stream, starting from the given - /// index. - fn decode_from(rlp: &UntrustedRlp, start_idx: usize) -> Result { - let mut caps = Capabilities::default(); +/// Attempt to parse a handshake message into its three parts: +/// - chain status +/// - serving capabilities +/// - buffer flow parameters +pub fn parse_handshake(rlp: UntrustedRlp) -> Result<(Status, Capabilities, FlowParams), DecoderError> { + let mut parser = Parser { + pos: 0, + rlp: rlp, + }; - for item in rlp.iter().skip(start_idx).take(4) { - let key: String = try!(item.val_at(0)); + let status = Status { + protocol_version: try!(parser.expect(Key::ProtocolVersion)), + network_id: try!(parser.expect(Key::NetworkId) + .and_then(|id: u32| NetworkId::from_raw(id).ok_or(DecoderError::Custom("Invalid network ID")))), + head_td: try!(parser.expect(Key::HeadTD)), + head_hash: try!(parser.expect(Key::HeadHash)), + head_num: try!(parser.expect(Key::HeadNum)), + genesis_hash: parser.expect(Key::GenesisHash).ok(), + last_head: None, + }; - match &*key { - "serveHeaders" => caps.serve_headers = true, - "serveChainSince" => caps.serve_chain_since = Some(try!(item.val_at(1))), - "serveStateSince" => caps.serve_state_since = Some(try!(item.val_at(1))), - "txRelay" => caps.tx_relay = true, - _ => continue, - } - } + 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(), + }; - Ok(caps) + let flow_params = FlowParams::new( + try!(parser.expect(Key::BufferLimit)), + try!(parser.expect(Key::BufferCostTable)), + try!(parser.expect(Key::BufferRechargeRate)), + ); + + Ok((status, capabilities, flow_params)) +} + +/// Write a handshake, given status, capabilities, and flow parameters. +pub fn write_handshake(status: &Status, capabilities: &Capabilities, flow_params: &FlowParams) -> Vec { + let mut pairs = Vec::new(); + pairs.push(encode_pair(Key::ProtocolVersion, &status.protocol_version)); + pairs.push(encode_pair(Key::NetworkId, &(status.network_id as u32))); + 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)); + + if let Some(ref genesis_hash) = status.genesis_hash { + pairs.push(encode_pair(Key::GenesisHash, 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)); + } + + 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()); + + for pair in pairs { + stream.append_raw(&pair, 1); + } + + stream.out() } /// An announcement of new chain head or capabilities made by a peer. @@ -105,6 +285,137 @@ pub struct Announcement { head_td: U256, /// reorg depth to common ancestor of last announced head. reorg_depth: u64, - /// updated capabilities. - new_capabilities: Capabilities, + /// optional new state-serving capability + serve_state_since: Option, + /// optional new chain-serving capability + serve_chain_since: Option, + // TODO: changes in buffer flow? +} + +#[cfg(test)] +mod tests { + use super::*; + use super::super::buffer_flow::FlowParams; + use util::{U256, H256, FixedHash}; + use rlp::{RlpStream, Stream ,UntrustedRlp, View}; + + #[test] + fn full_handshake() { + let status = Status { + protocol_version: 1, + network_id: NetworkId::Mainnet, + head_td: U256::default(), + head_hash: H256::default(), + head_num: 10, + genesis_hash: Some(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 flow_params = FlowParams::new( + 1_000_000.into(), + Default::default(), + 1000.into(), + ); + + let handshake = write_handshake(&status, &capabilities, &flow_params); + + let (read_status, read_capabilities, read_flow) + = parse_handshake(UntrustedRlp::new(&handshake)).unwrap(); + + assert_eq!(read_status, status); + assert_eq!(read_capabilities, capabilities); + assert_eq!(read_flow, flow_params); + } + + #[test] + fn partial_handshake() { + let status = Status { + protocol_version: 1, + network_id: NetworkId::Mainnet, + head_td: U256::default(), + head_hash: H256::default(), + head_num: 10, + genesis_hash: None, + last_head: None, + }; + + 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 handshake = write_handshake(&status, &capabilities, &flow_params); + + let (read_status, read_capabilities, read_flow) + = parse_handshake(UntrustedRlp::new(&handshake)).unwrap(); + + assert_eq!(read_status, status); + assert_eq!(read_capabilities, capabilities); + assert_eq!(read_flow, flow_params); + } + + #[test] + fn skip_unknown_keys() { + let status = Status { + protocol_version: 1, + network_id: NetworkId::Mainnet, + head_td: U256::default(), + head_hash: H256::default(), + head_num: 10, + genesis_hash: None, + last_head: None, + }; + + 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 handshake = write_handshake(&status, &capabilities, &flow_params); + let interleaved = { + let handshake = UntrustedRlp::new(&handshake); + let mut stream = RlpStream::new_list(handshake.item_count() * 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); + } + + stream.out() + }; + + let (read_status, read_capabilities, read_flow) + = parse_handshake(UntrustedRlp::new(&interleaved)).unwrap(); + + assert_eq!(read_status, status); + assert_eq!(read_capabilities, capabilities); + assert_eq!(read_flow, flow_params); + } } \ No newline at end of file From ca25deb4e6e16cf03204ef6864d8e460371a93db Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Tue, 8 Nov 2016 19:00:37 +0100 Subject: [PATCH 19/37] implement announcement serialization --- ethcore/light/src/net/status.rs | 179 ++++++++++++++++++++++++++------ 1 file changed, 150 insertions(+), 29 deletions(-) diff --git a/ethcore/light/src/net/status.rs b/ethcore/light/src/net/status.rs index 23b8b68c0..c48b73234 100644 --- a/ethcore/light/src/net/status.rs +++ b/ethcore/light/src/net/status.rs @@ -24,7 +24,7 @@ use super::buffer_flow::{CostTable, FlowParams}; // recognized handshake/announcement keys. // unknown keys are to be skipped, known keys have a defined order. // their string values are defined in the LES spec. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd)] enum Key { ProtocolVersion, NetworkId, @@ -61,6 +61,7 @@ impl Key { } } + // try to parse the key value from a string. fn from_str(s: &str) -> Option { match s { "protocolVersion" => Some(Key::ProtocolVersion), @@ -79,10 +80,6 @@ impl Key { _ => None } } - - fn is_recognized(s: &str) -> bool { - Key::from_str(s).is_some() - } } /// Network ID structure. @@ -112,32 +109,38 @@ struct Parser<'a> { } impl<'a> Parser<'a> { - // attempt to parse the next key, value pair, and decode the value to the given type. + // expect a specific next key, and decode the value. + // error on unexpected key or invalid value. fn expect(&mut self, key: Key) -> Result { self.expect_raw(key).and_then(|item| item.as_val()) } - // attempt to parse the next key, value pair, and returns the value's RLP. + // 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, DecoderError> { - loop { - let pair = try!(self.rlp.at(self.pos)); - let k: String = try!(pair.val_at(0)); - let k = match Key::from_str(&k) { - Some(k) => k, - None => { - // skip any unrecognized keys. - self.pos += 1; - continue; - } - }; + let pre_pos = self.pos; + if let Some((k, val)) = try!(self.get_next()) { + if k == key { return Ok(val) } + } - if k == key { - self.pos += 1; - return pair.at(1) - } else { - return 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)>, DecoderError> { + while self.pos < self.rlp.item_count() { + let pair = try!(self.rlp.at(self.pos)); + let k: String = try!(pair.val_at(0)); + + self.pos += 1; + match Key::from_str(&k) { + Some(key) => return Ok(Some((key , try!(pair.at(1))))), + None => continue, } } + + Ok(None) } } @@ -278,20 +281,90 @@ pub fn write_handshake(status: &Status, capabilities: &Capabilities, flow_params #[derive(Debug, Clone, PartialEq, Eq)] pub struct Announcement { /// Hash of the best block. - head_hash: H256, + pub head_hash: H256, /// Number of the best block. - head_num: u64, + pub head_num: u64, /// Head total difficulty - head_td: U256, + pub head_td: U256, /// reorg depth to common ancestor of last announced head. - reorg_depth: u64, + pub reorg_depth: u64, + /// optional new header-serving capability. false means "no change" + pub serve_headers: bool, /// optional new state-serving capability - serve_state_since: Option, + pub serve_state_since: Option, /// optional new chain-serving capability - serve_chain_since: Option, + pub serve_chain_since: Option, + /// optional new transaction-relay capability. false means "no change" + pub tx_relay: bool, // TODO: changes in buffer flow? } +/// Parse an announcement. +pub fn parse_announcement(rlp: UntrustedRlp) -> Result { + let mut last_key = None; + + let mut announcement = Announcement { + head_hash: try!(rlp.val_at(0)), + head_num: try!(rlp.val_at(1)), + head_td: try!(rlp.val_at(2)), + reorg_depth: try!(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: rlp, + }; + + while let Some((key, item)) = try!(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(try!(item.as_val())), + Key::ServeChainSince => announcement.serve_chain_since = Some(try!(item.as_val())), + Key::TxRelay => announcement.tx_relay = true, + _ => return Err(DecoderError::Custom("Nonsensical key in announcement")), + } + } + + Ok(announcement) +} + +/// Write an announcement out. +pub fn write_announcement(announcement: &Announcement) -> Vec { + 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); + + for item in pairs { + stream.append_raw(&item, 1); + } + + stream.out() +} + #[cfg(test)] mod tests { use super::*; @@ -418,4 +491,52 @@ mod tests { assert_eq!(read_capabilities, capabilities); assert_eq!(read_flow, 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, + }; + + let serialized = write_announcement(&announcement); + let read = parse_announcement(UntrustedRlp::new(&serialized)).unwrap(); + + assert_eq!(read, announcement); + } + + #[test] + fn keys_out_of_order() { + use super::{Key, encode_pair, encode_flag}; + + let mut stream = RlpStream::new_list(6); + stream + .append(&H256::zero()) + .append(&10u64) + .append(&100_000u64) + .append(&2u64) + .append_raw(&encode_pair(Key::ServeStateSince, &44u64), 1) + .append_raw(&encode_flag(Key::ServeHeaders), 1); + + let out = stream.drain(); + assert!(parse_announcement(UntrustedRlp::new(&out)).is_err()); + + let mut stream = RlpStream::new_list(6); + stream + .append(&H256::zero()) + .append(&10u64) + .append(&100_000u64) + .append(&2u64) + .append_raw(&encode_flag(Key::ServeHeaders), 1) + .append_raw(&encode_pair(Key::ServeStateSince, &44u64), 1); + + let out = stream.drain(); + assert!(parse_announcement(UntrustedRlp::new(&out)).is_ok()); + } } \ No newline at end of file From ec1b982b528205dcabe3d75d810ee77322306e7f Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Wed, 9 Nov 2016 15:36:26 +0100 Subject: [PATCH 20/37] errors, punishment, and handshake --- ethcore/light/src/client.rs | 11 ++- ethcore/light/src/net/error.rs | 94 ++++++++++++++++++ ethcore/light/src/net/mod.rs | 164 +++++++++++++++++++++----------- ethcore/light/src/net/status.rs | 19 ++-- ethcore/light/src/provider.rs | 8 +- 5 files changed, 225 insertions(+), 71 deletions(-) create mode 100644 ethcore/light/src/net/error.rs diff --git a/ethcore/light/src/client.rs b/ethcore/light/src/client.rs index 81355bf0f..fb4cb251f 100644 --- a/ethcore/light/src/client.rs +++ b/ethcore/light/src/client.rs @@ -52,7 +52,7 @@ impl Client { } /// Whether the block is already known (but not necessarily part of the canonical chain) - pub fn is_known(&self, id: BlockID) -> bool { + pub fn is_known(&self, _id: BlockID) -> bool { false } @@ -74,11 +74,18 @@ impl Client { // dummy implementation -- may draw from canonical cache further on. impl Provider for Client { - /// Get the chain info. fn chain_info(&self) -> BlockChainInfo { unimplemented!() } + fn reorg_depth(&self, _a: &H256, _b: &H256) -> Option { + None + } + + fn earliest_state(&self) -> Option { + None + } + fn block_headers(&self, _req: request::Headers) -> Vec { Vec::new() } diff --git a/ethcore/light/src/net/error.rs b/ethcore/light/src/net/error.rs new file mode 100644 index 000000000..e15bd50d3 --- /dev/null +++ b/ethcore/light/src/net/error.rs @@ -0,0 +1,94 @@ +// Copyright 2015, 2016 Ethcore (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 . + +//! Defines error types and levels of punishment to use upon +//! encountering. + +use rlp::DecoderError; +use network::NetworkError; + +use std::fmt; + +/// Levels of punishment. +/// +/// Currently just encompasses two different kinds of disconnect and +/// no punishment, but this is where reputation systems might come into play. +// 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, +} + +/// Kinds of errors which can be encountered in the course of LES. +#[derive(Debug)] +pub enum Error { + /// An RLP decoding error. + Rlp(DecoderError), + /// A network error. + Network(NetworkError), + /// Out of buffer. + BufferEmpty, + /// Unrecognized packet code. + UnrecognizedPacket(u8), + /// Unexpected handshake. + UnexpectedHandshake, + /// Peer on wrong network (wrong NetworkId or genesis hash) + WrongNetwork, +} + +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::BufferEmpty => Punishment::Disable, + Error::UnrecognizedPacket(_) => Punishment::Disconnect, + Error::UnexpectedHandshake => Punishment::Disconnect, + Error::WrongNetwork => Punishment::Disable, + } + } +} + +impl From for Error { + fn from(err: DecoderError) -> Self { + Error::Rlp(err) + } +} + +impl From for Error { + fn from(err: NetworkError) -> 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::BufferEmpty => write!(f, "Out of buffer"), + Error::UnrecognizedPacket(code) => write!(f, "Unrecognized packet: 0x{:x}", code), + Error::UnexpectedHandshake => write!(f, "Unexpected handshake"), + Error::WrongNetwork => write!(f, "Wrong network"), + } + } +} \ No newline at end of file diff --git a/ethcore/light/src/net/mod.rs b/ethcore/light/src/net/mod.rs index 4f0543526..c0697407d 100644 --- a/ethcore/light/src/net/mod.rs +++ b/ethcore/light/src/net/mod.rs @@ -30,9 +30,13 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use provider::Provider; use request::{self, Request}; -use self::buffer_flow::FlowParams; + +use self::buffer_flow::{Buffer, FlowParams}; +use self::error::{Error, Punishment}; +use self::status::{Status, Capabilities}; mod buffer_flow; +mod error; mod status; const TIMEOUT: TimerToken = 0; @@ -80,10 +84,21 @@ mod packet { pub const HEADER_PROOFS: u8 = 0x0e; } +// A pending peer: one we've sent our status to but +// may not have received one for. +struct PendingPeer { + sent_head: H256, +} + // data about each peer. struct Peer { - buffer: u64, // remaining buffer value. + local_buffer: Buffer, // their buffer relative to us + remote_buffer: Buffer, // our buffer relative to them current_asking: HashSet, // pending request ids. + status: Status, + capabilities: Capabilities, + remote_flow: FlowParams, + sent_head: H256, // last head we've given them. } /// This is an implementation of the light ethereum network protocol, abstracted @@ -95,31 +110,32 @@ struct Peer { pub struct LightProtocol { provider: Box, genesis_hash: H256, - mainnet: bool, + network_id: status::NetworkId, + pending_peers: RwLock>, peers: RwLock>, pending_requests: RwLock>, + capabilities: RwLock, + flow_params: FlowParams, // assumed static and same for every peer. req_id: AtomicUsize, } impl LightProtocol { - // make a request to a given peer. - fn request_from(&self, peer: &PeerId, req: Request) { + // Check on the status of all pending requests. + fn check_pending_requests(&self) { unimplemented!() } // called when a peer connects. fn on_connect(&self, peer: &PeerId, io: &NetworkContext) { let peer = *peer; + match self.send_status(peer, io) { - Ok(()) => { - self.peers.write().insert(peer, Peer { - buffer: 0, - current_asking: HashSet::new(), - }); + Ok(pending_peer) => { + self.pending_peers.write().insert(peer, pending_peer); } Err(e) => { trace!(target: "les", "Error while sending status: {}", e); - io.disable_peer(peer); + io.disconnect_peer(peer); } } } @@ -127,119 +143,140 @@ impl LightProtocol { // called when a peer disconnects. fn on_disconnect(&self, peer: PeerId, io: &NetworkContext) { // TODO: reassign all requests assigned to this peer. + self.pending_peers.write().remove(&peer); self.peers.write().remove(&peer); } - fn send_status(&self, peer: PeerId, io: &NetworkContext) -> Result<(), NetworkError> { + // send status to a peer. + fn send_status(&self, peer: PeerId, io: &NetworkContext) -> Result { let chain_info = self.provider.chain_info(); - // TODO [rob] use optional keys too. - let mut stream = RlpStream::new_list(6); - stream - .begin_list(2) - .append(&"protocolVersion") - .append(&PROTOCOL_VERSION) - .begin_list(2) - .append(&"networkId") - .append(&(self.mainnet as u8)) - .begin_list(2) - .append(&"headTd") - .append(&chain_info.total_difficulty) - .begin_list(2) - .append(&"headHash") - .append(&chain_info.best_block_hash) - .begin_list(2) - .append(&"headNum") - .append(&chain_info.best_block_number) - .begin_list(2) - .append(&"genesisHash") - .append(&self.genesis_hash); + // TODO: could update capabilities here. - io.send(peer, packet::STATUS, stream.out()) + let status = Status { + head_td: chain_info.total_difficulty, + head_hash: chain_info.best_block_hash, + head_num: chain_info.best_block_number, + genesis_hash: chain_info.genesis_hash, + protocol_version: PROTOCOL_VERSION, + network_id: self.network_id, + last_head: None, + }; + + let capabilities = self.capabilities.read().clone(); + let status_packet = status::write_handshake(&status, &capabilities, &self.flow_params); + + try!(io.send(peer, packet::STATUS, status_packet)); + + Ok(PendingPeer { + sent_head: chain_info.best_block_hash, + }) } - /// Check on the status of all pending requests. - fn check_pending_requests(&self) { - unimplemented!() - } + // Handle status message from peer. + fn status(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { + let pending = match self.pending_peers.write().remove(peer) { + Some(pending) => pending, + None => { + return Err(Error::UnexpectedHandshake); + } + }; - fn status(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { - unimplemented!() + let (status, capabilities, flow_params) = try!(status::parse_handshake(data)); + + trace!(target: "les", "Connected peer with chain head {:?}", (status.head_hash, status.head_num)); + + if (status.network_id, status.genesis_hash) != (self.network_id, self.genesis_hash) { + return Err(Error::WrongNetwork); + } + + self.peers.write().insert(*peer, Peer { + local_buffer: self.flow_params.create_buffer(), + remote_buffer: flow_params.create_buffer(), + current_asking: HashSet::new(), + status: status, + capabilities: capabilities, + remote_flow: flow_params, + sent_head: pending.sent_head, + }); + + + Ok(()) } // Handle an announcement. - fn announcement(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { + fn announcement(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { const MAX_NEW_HASHES: usize = 256; unimplemented!() } // Handle a request for block headers. - fn get_block_headers(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { + fn get_block_headers(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { const MAX_HEADERS: u64 = 512; unimplemented!() } // Receive a response for block headers. - fn block_headers(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { + fn block_headers(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { unimplemented!() } // Handle a request for block bodies. - fn get_block_bodies(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { + fn get_block_bodies(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { const MAX_BODIES: usize = 512; unimplemented!() } // Receive a response for block bodies. - fn block_bodies(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { + fn block_bodies(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { unimplemented!() } // Handle a request for receipts. - fn get_receipts(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { + fn get_receipts(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { unimplemented!() } // Receive a response for receipts. - fn receipts(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { + fn receipts(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { unimplemented!() } // Handle a request for proofs. - fn get_proofs(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { + fn get_proofs(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { unimplemented!() } // Receive a response for proofs. - fn proofs(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { + fn proofs(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { unimplemented!() } // Handle a request for contract code. - fn get_contract_code(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { + fn get_contract_code(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { unimplemented!() } // Receive a response for contract code. - fn contract_code(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { + fn contract_code(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { unimplemented!() } // Handle a request for header proofs - fn get_header_proofs(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { + fn get_header_proofs(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { unimplemented!() } // Receive a response for header proofs - fn header_proofs(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { + fn header_proofs(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { unimplemented!() } // Receive a set of transactions to relay. - fn relay_transactions(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { + fn relay_transactions(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { unimplemented!() } } @@ -251,7 +288,7 @@ impl NetworkProtocolHandler for LightProtocol { fn read(&self, io: &NetworkContext, peer: &PeerId, packet_id: u8, data: &[u8]) { let rlp = UntrustedRlp::new(data); - match packet_id { + let res = match packet_id { packet::STATUS => self.status(peer, io, rlp), packet::ANNOUNCE => self.announcement(peer, io, rlp), @@ -273,8 +310,21 @@ impl NetworkProtocolHandler for LightProtocol { packet::SEND_TRANSACTIONS => self.relay_transactions(peer, io, rlp), other => { - debug!(target: "les", "Disconnecting peer {} on unexpected packet {}", peer, other); - io.disconnect_peer(*peer); + Err(Error::UnrecognizedPacket(other)) + } + }; + + if let Err(e) = res { + match e.punishment() { + Punishment::None => {} + Punishment::Disconnect => { + debug!(target: "les", "Disconnecting peer {}: {}", peer, e); + io.disconnect_peer(*peer) + } + Punishment::Disable => { + debug!(target: "les", "Disabling peer {}: {}", peer, e); + io.disable_peer(*peer) + } } } } diff --git a/ethcore/light/src/net/status.rs b/ethcore/light/src/net/status.rs index c48b73234..1cac14845 100644 --- a/ethcore/light/src/net/status.rs +++ b/ethcore/light/src/net/status.rs @@ -172,7 +172,7 @@ pub struct Status { /// Number of the best block. pub head_num: u64, /// Genesis hash - pub genesis_hash: Option, + pub genesis_hash: H256, /// Last announced chain head and reorg depth to common ancestor. pub last_head: Option<(H256, u64)>, } @@ -182,7 +182,7 @@ pub struct Status { pub struct Capabilities { /// Whether this peer can serve headers pub serve_headers: bool, - /// Earliest block number it can serve chain requests for. + /// Earliest block number it can serve block/receipt requests for. pub serve_chain_since: Option, /// Earliest block number it can serve state requests for. pub serve_state_since: Option, @@ -193,7 +193,7 @@ pub struct Capabilities { impl Default for Capabilities { fn default() -> Self { Capabilities { - serve_headers: false, + serve_headers: true, serve_chain_since: None, serve_state_since: None, tx_relay: false, @@ -218,7 +218,7 @@ pub fn parse_handshake(rlp: UntrustedRlp) -> Result<(Status, Capabilities, FlowP head_td: try!(parser.expect(Key::HeadTD)), head_hash: try!(parser.expect(Key::HeadHash)), head_num: try!(parser.expect(Key::HeadNum)), - genesis_hash: parser.expect(Key::GenesisHash).ok(), + genesis_hash: try!(parser.expect(Key::GenesisHash)), last_head: None, }; @@ -246,10 +246,7 @@ pub fn write_handshake(status: &Status, capabilities: &Capabilities, flow_params 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)); - - if let Some(ref genesis_hash) = status.genesis_hash { - pairs.push(encode_pair(Key::GenesisHash, genesis_hash)); - } + pairs.push(encode_pair(Key::GenesisHash, &status.genesis_hash)); if capabilities.serve_headers { pairs.push(encode_flag(Key::ServeHeaders)); @@ -380,7 +377,7 @@ mod tests { head_td: U256::default(), head_hash: H256::default(), head_num: 10, - genesis_hash: Some(H256::zero()), + genesis_hash: H256::zero(), last_head: None, }; @@ -415,7 +412,7 @@ mod tests { head_td: U256::default(), head_hash: H256::default(), head_num: 10, - genesis_hash: None, + genesis_hash: H256::zero(), last_head: None, }; @@ -450,7 +447,7 @@ mod tests { head_td: U256::default(), head_hash: H256::default(), head_num: 10, - genesis_hash: None, + genesis_hash: H256::zero(), last_head: None, }; diff --git a/ethcore/light/src/provider.rs b/ethcore/light/src/provider.rs index d6458bedd..b1625f95f 100644 --- a/ethcore/light/src/provider.rs +++ b/ethcore/light/src/provider.rs @@ -19,7 +19,7 @@ use ethcore::transaction::SignedTransaction; use ethcore::blockchain_info::BlockChainInfo; -use util::Bytes; +use util::{Bytes, H256}; use request; @@ -33,6 +33,12 @@ pub trait Provider: Send + Sync { /// Provide current blockchain info. fn chain_info(&self) -> BlockChainInfo; + /// Find the depth of a common ancestor between two blocks. + fn reorg_depth(&self, a: &H256, b: &H256) -> Option; + + /// Earliest state. + fn earliest_state(&self) -> Option; + /// Provide a list of headers starting at the requested block, /// possibly in reverse and skipping `skip` at a time. /// From c132775bb17c6047a19015979fb3b4822a06a2cd Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Wed, 9 Nov 2016 16:21:09 +0100 Subject: [PATCH 21/37] handle announcements --- ethcore/light/src/net/mod.rs | 38 +++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/ethcore/light/src/net/mod.rs b/ethcore/light/src/net/mod.rs index c0697407d..a721fde72 100644 --- a/ethcore/light/src/net/mod.rs +++ b/ethcore/light/src/net/mod.rs @@ -200,15 +200,47 @@ impl LightProtocol { sent_head: pending.sent_head, }); - Ok(()) } // Handle an announcement. fn announcement(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { - const MAX_NEW_HASHES: usize = 256; + if !self.peers.read().contains_key(peer) { + debug!(target: "les", "Ignoring announcement from unknown peer"); + return Ok(()) + } - unimplemented!() + let announcement = try!(status::parse_announcement(data)); + let mut peers = self.peers.write(); + + let peer_info = match peers.get_mut(peer) { + Some(info) => info, + None => return Ok(()), + }; + + // update status. + { + // TODO: punish peer if they've moved backwards. + let status = &mut peer_info.status; + let last_head = status.head_hash; + status.head_hash = announcement.head_hash; + status.head_td = announcement.head_td; + status.head_num = announcement.head_num; + status.last_head = Some((last_head, announcement.reorg_depth)); + } + + // update capabilities. + { + let caps = &mut peer_info.capabilities; + caps.serve_headers = caps.serve_headers || announcement.serve_headers; + caps.serve_state_since = caps.serve_state_since.or(announcement.serve_state_since); + caps.serve_chain_since = caps.serve_chain_since.or(announcement.serve_chain_since); + caps.tx_relay = caps.tx_relay || announcement.tx_relay; + } + + // TODO: notify listeners if new best block. + + Ok(()) } // Handle a request for block headers. From 25d5efac15142454386122ae3a97258bcad9da6d Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Wed, 9 Nov 2016 18:05:00 +0100 Subject: [PATCH 22/37] making announcements, clean up warnings --- ethcore/light/src/client.rs | 5 +- ethcore/light/src/net/buffer_flow.rs | 32 ++--- ethcore/light/src/net/mod.rs | 188 ++++++++++++++++++++++----- ethcore/light/src/net/status.rs | 2 +- ethcore/light/src/request.rs | 7 +- 5 files changed, 173 insertions(+), 61 deletions(-) diff --git a/ethcore/light/src/client.rs b/ethcore/light/src/client.rs index fb4cb251f..e3b5745b2 100644 --- a/ethcore/light/src/client.rs +++ b/ethcore/light/src/client.rs @@ -21,11 +21,10 @@ use std::sync::Arc; use ethcore::engines::Engine; use ethcore::ids::BlockID; -use ethcore::miner::TransactionQueue; use ethcore::service::ClientIoMessage; use ethcore::block_import_error::BlockImportError; use ethcore::block_status::BlockStatus; -use ethcore::verification::queue::{Config as QueueConfig, HeaderQueue, QueueInfo, Status}; +use ethcore::verification::queue::{HeaderQueue, QueueInfo}; use ethcore::transaction::SignedTransaction; use ethcore::blockchain_info::BlockChainInfo; @@ -62,7 +61,7 @@ impl Client { } /// Inquire about the status of a given block. - pub fn status(&self, id: BlockID) -> BlockStatus { + pub fn status(&self, _id: BlockID) -> BlockStatus { BlockStatus::Unknown } diff --git a/ethcore/light/src/net/buffer_flow.rs b/ethcore/light/src/net/buffer_flow.rs index 7ee287533..ff4c46f16 100644 --- a/ethcore/light/src/net/buffer_flow.rs +++ b/ethcore/light/src/net/buffer_flow.rs @@ -23,8 +23,9 @@ //! This module provides an interface for configuration of buffer //! flow costs and recharge rates. -use request::{self, Request}; +use request; use super::packet; +use super::error::Error; use rlp::*; use util::U256; @@ -34,10 +35,6 @@ use time::{Duration, SteadyTime}; #[derive(Debug, Clone, PartialEq, Eq)] pub struct Cost(pub U256, pub U256); -/// An error: insufficient buffer. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct InsufficientBuffer; - /// Buffer value. /// /// Produced and recharged using `FlowParams`. @@ -50,6 +47,9 @@ pub struct Buffer { } impl Buffer { + /// Get the current buffer value. + pub fn current(&self) -> U256 { self.estimate.clone() } + /// Make a definitive update. /// This will be the value obtained after receiving /// a response to a request. @@ -59,12 +59,14 @@ impl Buffer { } /// Attempt to apply the given cost to the buffer. + /// /// 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<(), InsufficientBuffer> { + pub fn deduct_cost(&mut self, cost: U256) -> Result<(), Error> { match cost > self.estimate { - true => Err(InsufficientBuffer), + true => Err(Error::BufferEmpty), false => { self.estimate = self.estimate - cost; Ok(()) @@ -188,23 +190,9 @@ impl FlowParams { /// Get a reference to the recharge rate. pub fn recharge_rate(&self) -> &U256 { &self.recharge } - /// Estimate the maximum cost of the request. - pub fn max_cost(&self, req: &Request) -> U256 { - let amount = match *req { - Request::Headers(ref req) => req.max as usize, - Request::Bodies(ref req) => req.block_hashes.len(), - Request::Receipts(ref req) => req.block_hashes.len(), - Request::StateProofs(ref req) => req.requests.len(), - Request::Codes(ref req) => req.code_requests.len(), - Request::HeaderProofs(ref req) => req.requests.len(), - }; - - self.actual_cost(req.kind(), amount) - } - /// Compute the actual cost of a request, given the kind of request /// and number of requests made. - pub fn actual_cost(&self, kind: request::Kind, amount: usize) -> U256 { + pub fn compute_cost(&self, kind: request::Kind, amount: usize) -> U256 { let cost = match kind { request::Kind::Headers => &self.costs.headers, request::Kind::Bodies => &self.costs.bodies, diff --git a/ethcore/light/src/net/mod.rs b/ethcore/light/src/net/mod.rs index a721fde72..e72ce4bb2 100644 --- a/ethcore/light/src/net/mod.rs +++ b/ethcore/light/src/net/mod.rs @@ -20,13 +20,13 @@ //! See https://github.com/ethcore/parity/wiki/Light-Ethereum-Subprotocol-(LES) use io::TimerToken; -use network::{NetworkProtocolHandler, NetworkService, NetworkContext, NetworkError, PeerId}; -use rlp::{DecoderError, RlpStream, Stream, UntrustedRlp, View}; +use network::{NetworkProtocolHandler, NetworkContext, NetworkError, PeerId}; +use rlp::{RlpStream, Stream, UntrustedRlp, View}; use util::hash::H256; -use util::{U256, Mutex, RwLock}; +use util::RwLock; use std::collections::{HashMap, HashSet}; -use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::atomic::AtomicUsize; use provider::Provider; use request::{self, Request}; @@ -39,6 +39,8 @@ mod buffer_flow; mod error; mod status; +pub use self::status::Announcement; + const TIMEOUT: TimerToken = 0; const TIMEOUT_INTERVAL_MS: u64 = 1000; @@ -120,11 +122,38 @@ pub struct LightProtocol { } impl LightProtocol { - // Check on the status of all pending requests. - fn check_pending_requests(&self) { - unimplemented!() - } + /// Make an announcement of new chain head and capabilities to all peers. + /// The announcement is expected to be valid. + pub fn make_announcement(&self, mut announcement: Announcement, io: &NetworkContext) { + let mut reorgs_map = HashMap::new(); + // calculate reorg info and send packets + for (peer_id, peer_info) in self.peers.write().iter_mut() { + let reorg_depth = reorgs_map.entry(peer_info.sent_head) + .or_insert_with(|| { + match self.provider.reorg_depth(&announcement.head_hash, &peer_info.sent_head) { + Some(depth) => depth, + None => { + // both values will always originate locally -- this means something + // has gone really wrong + debug!(target: "les", "couldn't compute reorganization depth between {:?} and {:?}", + &announcement.head_hash, &peer_info.sent_head); + 0 + } + } + }); + + peer_info.sent_head = announcement.head_hash; + announcement.reorg_depth = *reorg_depth; + + if let Err(e) = io.send(*peer_id, packet::ANNOUNCE, status::write_announcement(&announcement)) { + debug!(target: "les", "Error sending to peer {}: {}", peer_id, e); + } + } + } +} + +impl LightProtocol { // called when a peer connects. fn on_connect(&self, peer: &PeerId, io: &NetworkContext) { let peer = *peer; @@ -141,7 +170,7 @@ impl LightProtocol { } // called when a peer disconnects. - fn on_disconnect(&self, peer: PeerId, io: &NetworkContext) { + fn on_disconnect(&self, peer: PeerId) { // TODO: reassign all requests assigned to this peer. self.pending_peers.write().remove(&peer); self.peers.write().remove(&peer); @@ -174,7 +203,7 @@ impl LightProtocol { } // Handle status message from peer. - fn status(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { + fn status(&self, peer: &PeerId, data: UntrustedRlp) -> Result<(), Error> { let pending = match self.pending_peers.write().remove(peer) { Some(pending) => pending, None => { @@ -204,7 +233,7 @@ impl LightProtocol { } // Handle an announcement. - fn announcement(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { + fn announcement(&self, peer: &PeerId, data: UntrustedRlp) -> Result<(), Error> { if !self.peers.read().contains_key(peer) { debug!(target: "les", "Ignoring announcement from unknown peer"); return Ok(()) @@ -245,70 +274,161 @@ impl LightProtocol { // Handle a request for block headers. fn get_block_headers(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { - const MAX_HEADERS: u64 = 512; + const MAX_HEADERS: usize = 512; - unimplemented!() + let mut present_buffer = match self.peers.read().get(peer) { + Some(peer) => peer.local_buffer.clone(), + None => { + debug!(target: "les", "Ignoring announcement from unknown peer"); + return Ok(()) + } + }; + + self.flow_params.recharge(&mut present_buffer); + let req_id: u64 = try!(data.val_at(0)); + + let req = request::Headers { + block: { + let rlp = try!(data.at(1)); + (try!(rlp.val_at(0)), try!(rlp.val_at(1))) + }, + max: ::std::cmp::min(MAX_HEADERS, try!(data.val_at(2))), + skip: try!(data.val_at(3)), + reverse: try!(data.val_at(4)), + }; + + let max_cost = self.flow_params.compute_cost(request::Kind::Headers, req.max); + try!(present_buffer.deduct_cost(max_cost)); + + let response = self.provider.block_headers(req); + let actual_cost = self.flow_params.compute_cost(request::Kind::Headers, response.len()); + + let cur_buffer = match self.peers.write().get_mut(peer) { + Some(peer) => { + self.flow_params.recharge(&mut peer.local_buffer); + try!(peer.local_buffer.deduct_cost(actual_cost)); + peer.local_buffer.current() + } + None => { + debug!(target: "les", "peer disconnected during serving of request."); + return Ok(()) + } + }; + + io.respond(packet::BLOCK_HEADERS, { + let mut stream = RlpStream::new_list(response.len() + 2); + stream.append(&req_id).append(&cur_buffer); + + for header in response { + stream.append_raw(&header, 1); + } + + stream.out() + }).map_err(Into::into) } // Receive a response for block headers. - fn block_headers(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { + fn block_headers(&self, _: &PeerId, _: &NetworkContext, _: UntrustedRlp) -> Result<(), Error> { unimplemented!() } // Handle a request for block bodies. fn get_block_bodies(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { - const MAX_BODIES: usize = 512; + const MAX_BODIES: usize = 256; - unimplemented!() + let mut present_buffer = match self.peers.read().get(peer) { + Some(peer) => peer.local_buffer.clone(), + None => { + debug!(target: "les", "Ignoring announcement from unknown peer"); + return Ok(()) + } + }; + + self.flow_params.recharge(&mut present_buffer); + let req_id: u64 = try!(data.val_at(0)); + + let req = request::Bodies { + block_hashes: try!(data.iter().skip(1).take(MAX_BODIES).map(|x| x.as_val()).collect()) + }; + + let max_cost = self.flow_params.compute_cost(request::Kind::Bodies, req.block_hashes.len()); + try!(present_buffer.deduct_cost(max_cost)); + + let response = self.provider.block_bodies(req); + let response_len = response.iter().filter(|x| &x[..] != &::rlp::EMPTY_LIST_RLP).count(); + let actual_cost = self.flow_params.compute_cost(request::Kind::Bodies, response_len); + + let cur_buffer = match self.peers.write().get_mut(peer) { + Some(peer) => { + self.flow_params.recharge(&mut peer.local_buffer); + try!(peer.local_buffer.deduct_cost(actual_cost)); + peer.local_buffer.current() + } + None => { + debug!(target: "les", "peer disconnected during serving of request."); + return Ok(()) + } + }; + + io.respond(packet::BLOCK_BODIES, { + let mut stream = RlpStream::new_list(response.len() + 2); + stream.append(&req_id).append(&cur_buffer); + + for body in response { + stream.append_raw(&body, 1); + } + + stream.out() + }).map_err(Into::into) } // Receive a response for block bodies. - fn block_bodies(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { + fn block_bodies(&self, _: &PeerId, _: &NetworkContext, _: UntrustedRlp) -> Result<(), Error> { unimplemented!() } // Handle a request for receipts. - fn get_receipts(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { + fn get_receipts(&self, _: &PeerId, _: &NetworkContext, _: UntrustedRlp) -> Result<(), Error> { unimplemented!() } // Receive a response for receipts. - fn receipts(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { + fn receipts(&self, _: &PeerId, _: &NetworkContext, _: UntrustedRlp) -> Result<(), Error> { unimplemented!() } // Handle a request for proofs. - fn get_proofs(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { + fn get_proofs(&self, _: &PeerId, _: &NetworkContext, _: UntrustedRlp) -> Result<(), Error> { unimplemented!() } // Receive a response for proofs. - fn proofs(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { + fn proofs(&self, _: &PeerId, _: &NetworkContext, _: UntrustedRlp) -> Result<(), Error> { unimplemented!() } // Handle a request for contract code. - fn get_contract_code(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { + fn get_contract_code(&self, _: &PeerId, _: &NetworkContext, _: UntrustedRlp) -> Result<(), Error> { unimplemented!() } // Receive a response for contract code. - fn contract_code(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { + fn contract_code(&self, _: &PeerId, _: &NetworkContext, _: UntrustedRlp) -> Result<(), Error> { unimplemented!() } // Handle a request for header proofs - fn get_header_proofs(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { + fn get_header_proofs(&self, _: &PeerId, _: &NetworkContext, _: UntrustedRlp) -> Result<(), Error> { unimplemented!() } // Receive a response for header proofs - fn header_proofs(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { + fn header_proofs(&self, _: &PeerId, _: &NetworkContext, _: UntrustedRlp) -> Result<(), Error> { unimplemented!() } // Receive a set of transactions to relay. - fn relay_transactions(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { + fn relay_transactions(&self, _: &PeerId, _: &NetworkContext, _: UntrustedRlp) -> Result<(), Error> { unimplemented!() } } @@ -320,9 +440,11 @@ impl NetworkProtocolHandler for LightProtocol { fn read(&self, io: &NetworkContext, peer: &PeerId, packet_id: u8, data: &[u8]) { let rlp = UntrustedRlp::new(data); + + // handle the packet let res = match packet_id { - packet::STATUS => self.status(peer, io, rlp), - packet::ANNOUNCE => self.announcement(peer, io, rlp), + packet::STATUS => self.status(peer, rlp), + packet::ANNOUNCE => self.announcement(peer, rlp), packet::GET_BLOCK_HEADERS => self.get_block_headers(peer, io, rlp), packet::BLOCK_HEADERS => self.block_headers(peer, io, rlp), @@ -339,6 +461,9 @@ impl NetworkProtocolHandler for LightProtocol { packet::GET_CONTRACT_CODES => self.get_contract_code(peer, io, rlp), packet::CONTRACT_CODES => self.contract_code(peer, io, rlp), + packet::GET_HEADER_PROOFS => self.get_header_proofs(peer, io, rlp), + packet::HEADER_PROOFS => self.header_proofs(peer, io, rlp), + packet::SEND_TRANSACTIONS => self.relay_transactions(peer, io, rlp), other => { @@ -346,6 +471,7 @@ impl NetworkProtocolHandler for LightProtocol { } }; + // if something went wrong, figure out how much to punish the peer. if let Err(e) = res { match e.punishment() { Punishment::None => {} @@ -365,11 +491,11 @@ impl NetworkProtocolHandler for LightProtocol { self.on_connect(peer, io); } - fn disconnected(&self, io: &NetworkContext, peer: &PeerId) { - self.on_disconnect(*peer, io); + fn disconnected(&self, _io: &NetworkContext, peer: &PeerId) { + self.on_disconnect(*peer); } - fn timeout(&self, io: &NetworkContext, timer: TimerToken) { + fn timeout(&self, _io: &NetworkContext, timer: TimerToken) { match timer { TIMEOUT => { // broadcast transactions to peers. diff --git a/ethcore/light/src/net/status.rs b/ethcore/light/src/net/status.rs index 1cac14845..5aaea9f3a 100644 --- a/ethcore/light/src/net/status.rs +++ b/ethcore/light/src/net/status.rs @@ -19,7 +19,7 @@ use rlp::{DecoderError, RlpDecodable, RlpEncodable, RlpStream, Stream, UntrustedRlp, View}; use util::{H256, U256}; -use super::buffer_flow::{CostTable, FlowParams}; +use super::buffer_flow::FlowParams; // recognized handshake/announcement keys. // unknown keys are to be skipped, known keys have a defined order. diff --git a/ethcore/light/src/request.rs b/ethcore/light/src/request.rs index 11b474ee5..f043f0f25 100644 --- a/ethcore/light/src/request.rs +++ b/ethcore/light/src/request.rs @@ -18,8 +18,7 @@ // TODO: make IPC compatible. -use ethcore::transaction::Transaction; -use util::{Address, H256}; +use util::H256; /// A request for block headers. #[derive(Debug, Clone, PartialEq, Eq)] @@ -27,9 +26,9 @@ pub struct Headers { /// Block information for the request being made. pub block: (u64, H256), /// The maximum amount of headers which can be returned. - pub max: u64, + pub max: usize, /// The amount of headers to skip between each response entry. - pub skip: u64, + pub skip: usize, /// Whether the headers should proceed in falling number from the initial block. pub reverse: bool, } From 6c23d53f04f0f90ed3b9f62e8710f27b97a14ab5 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Wed, 9 Nov 2016 18:05:56 +0100 Subject: [PATCH 23/37] allow dead code temporarily --- ethcore/light/src/lib.rs | 3 +++ ethcore/light/src/net/buffer_flow.rs | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ethcore/light/src/lib.rs b/ethcore/light/src/lib.rs index f6d05c65c..07e6833a7 100644 --- a/ethcore/light/src/lib.rs +++ b/ethcore/light/src/lib.rs @@ -28,6 +28,9 @@ //! It starts by performing a header-only sync, verifying random samples //! of members of the chain to varying degrees. +// TODO: remove when integrating with parity. +#![allow(dead_code)] + pub mod client; pub mod net; pub mod provider; diff --git a/ethcore/light/src/net/buffer_flow.rs b/ethcore/light/src/net/buffer_flow.rs index ff4c46f16..b7bd30f82 100644 --- a/ethcore/light/src/net/buffer_flow.rs +++ b/ethcore/light/src/net/buffer_flow.rs @@ -233,7 +233,6 @@ impl FlowParams { #[cfg(test)] mod tests { use super::*; - use util::U256; #[test] fn should_serialize_cost_table() { From ebff010d16426ec7577a186f0d4c34c4b2eb96be Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Wed, 9 Nov 2016 23:25:54 +0100 Subject: [PATCH 24/37] partial implementation of provider for client types --- ethcore/light/src/provider.rs | 56 +++++++++++++++++++++++++++++-- ethcore/src/client/client.rs | 13 +++++-- ethcore/src/client/mod.rs | 11 +++--- ethcore/src/client/test_client.rs | 8 +++++ ethcore/src/client/traits.rs | 4 +++ ethcore/src/types/pruning_info.rs | 30 +++++++++++++++++ 6 files changed, 113 insertions(+), 9 deletions(-) create mode 100644 ethcore/src/types/pruning_info.rs diff --git a/ethcore/light/src/provider.rs b/ethcore/light/src/provider.rs index b1625f95f..3cabe3feb 100644 --- a/ethcore/light/src/provider.rs +++ b/ethcore/light/src/provider.rs @@ -17,8 +17,11 @@ //! A provider for the LES protocol. This is typically a full node, who can //! give as much data as necessary to its peers. +use ethcore::client::BlockChainClient; use ethcore::transaction::SignedTransaction; use ethcore::blockchain_info::BlockChainInfo; + +use rlp::EMPTY_LIST_RLP; use util::{Bytes, H256}; use request; @@ -36,8 +39,9 @@ pub trait Provider: Send + Sync { /// Find the depth of a common ancestor between two blocks. fn reorg_depth(&self, a: &H256, b: &H256) -> Option; - /// Earliest state. - fn earliest_state(&self) -> Option; + /// Earliest block where state queries are available. + /// All states between this value and + fn earliest_state(&self) -> u64; /// Provide a list of headers starting at the requested block, /// possibly in reverse and skipping `skip` at a time. @@ -67,5 +71,53 @@ pub trait Provider: Send + Sync { fn header_proofs(&self, req: request::HeaderProofs) -> Vec; /// Provide pending transactions. + fn pending_transactions(&self) -> Vec; +} + +// TODO [rob] move into trait definition file after ethcore crate +// is split up. ideally `ethcore-light` will be between `ethcore-blockchain` +// and `ethcore-client` +impl Provider for T { + fn chain_info(&self) -> BlockChainInfo { + BlockChainClient::chain_info(self) + } + + fn reorg_depth(&self, a: &H256, b: &H256) -> Option { + self.tree_route.map(|route| route.index as u64) + } + + fn earliest_state(&self) -> u64 { + self.pruning_info().earliest_state + } + + fn block_headers(&self, req: request::Headers) -> Vec { + unimplemented!() + } + + fn block_bodies(&self, req: request::Bodies) -> Vec { + req.block_hashes.into_iter() + .map(|hash| self.block_body(hash.into())) + .map(|body| body.unwrap_or_else(|| EMPTY_LIST_RLP.into())) + .collect() + } + + fn receipts(&self, req: request::Receipts) -> Vec { + req.block_hashes.into_iter() + .map(|hash| self.block_receipts(&hash) + .map(|receipts| receips.unwrap_or_else(|| EMPTY_LIST_RLP.into())) + .collect() + } + + fn proofs(&self, req: request::StateProofs) -> Vec { + unimplemented!() + } + + fn code(&self, req: request::ContractCodes) -> Vec; + + fn header_proofs(&self, req: request::HeaderProofs) -> Vec { + // TODO: [rob] implement CHT stuff on `ethcore` side. + req.requests.into_iter().map(|_| EMPTY_LIST_RLP.into()).collect() + } + fn pending_transactions(&self) -> Vec; } \ No newline at end of file diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index ec59e01cf..72356e91d 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -52,7 +52,7 @@ use blockchain::{BlockChain, BlockProvider, TreeRoute, ImportRoute}; use client::{ BlockID, TransactionID, UncleID, TraceId, ClientConfig, BlockChainClient, MiningBlockChainClient, TraceFilter, CallAnalytics, BlockImportError, Mode, - ChainNotify, + ChainNotify, PruningInfo, }; use client::Error as ClientError; use env_info::EnvInfo; @@ -262,7 +262,7 @@ impl Client { } } - /// Register an action to be done if a mode change happens. + /// Register an action to be done if a mode change happens. pub fn on_mode_change(&self, f: F) where F: 'static + FnMut(&Mode) + Send { *self.on_mode_change.lock() = Some(Box::new(f)); } @@ -890,7 +890,7 @@ impl BlockChainClient for Client { trace!(target: "mode", "Making callback..."); f(&*mode) }, - _ => {} + _ => {} } } match new_mode { @@ -1226,6 +1226,13 @@ impl BlockChainClient for Client { self.uncle(id) .map(|header| self.engine.extra_info(&decode(&header))) } + + fn pruning_info(&self) -> PruningInfo { + PruningInfo { + earliest_chain: self.chain.read().first_block().unwrap_or(1), + earliest_state: self.state_db.lock().journal_db().earliest_era().unwrap_or(0), + } + } } impl MiningBlockChainClient for Client { diff --git a/ethcore/src/client/mod.rs b/ethcore/src/client/mod.rs index 3898ab6cd..7eafbee36 100644 --- a/ethcore/src/client/mod.rs +++ b/ethcore/src/client/mod.rs @@ -25,18 +25,21 @@ mod client; pub use self::client::*; pub use self::config::{Mode, ClientConfig, DatabaseCompactionProfile, BlockChainConfig, VMType}; pub use self::error::Error; -pub use types::ids::*; pub use self::test_client::{TestBlockChainClient, EachBlockWith}; +pub use self::chain_notify::ChainNotify; +pub use self::traits::{BlockChainClient, MiningBlockChainClient}; + +pub use types::ids::*; pub use types::trace_filter::Filter as TraceFilter; +pub use types::pruning_info::PruningInfo; +pub use types::call_analytics::CallAnalytics; + pub use executive::{Executed, Executive, TransactOptions}; pub use env_info::{LastHashes, EnvInfo}; -pub use self::chain_notify::ChainNotify; -pub use types::call_analytics::CallAnalytics; pub use block_import_error::BlockImportError; pub use transaction_import::TransactionImportResult; pub use transaction_import::TransactionImportError; -pub use self::traits::{BlockChainClient, MiningBlockChainClient}; pub use verification::VerifierType; /// IPC interfaces diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index 434edd3e8..713c55056 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -38,6 +38,7 @@ use evm::{Factory as EvmFactory, VMType, Schedule}; use miner::{Miner, MinerService, TransactionImportResult}; use spec::Spec; use types::mode::Mode; +use types::pruning_info::PruningInfo; use views::BlockView; use verification::queue::QueueInfo; @@ -650,4 +651,11 @@ impl BlockChainClient for TestBlockChainClient { fn mode(&self) -> Mode { Mode::Active } fn set_mode(&self, _: Mode) { unimplemented!(); } + + fn pruning_info(&self) -> PruningInfo { + PruningInfo { + earliest_chain: 1, + earlest_state: 1, + } + } } diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index 67092e986..2cbf52f6c 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -39,6 +39,7 @@ use types::call_analytics::CallAnalytics; use types::blockchain_info::BlockChainInfo; use types::block_status::BlockStatus; use types::mode::Mode; +use types::pruning_info::PruningInfo; #[ipc(client_ident="RemoteClient")] /// Blockchain database client. Owns and manages a blockchain and a block queue. @@ -241,6 +242,9 @@ pub trait BlockChainClient : Sync + Send { /// Returns engine-related extra info for `UncleID`. fn uncle_extra_info(&self, id: UncleID) -> Option>; + + /// Returns information about pruning/data availability. + fn pruning_info(&self) -> PruningInfo; } /// Extended client interface used for mining diff --git a/ethcore/src/types/pruning_info.rs b/ethcore/src/types/pruning_info.rs new file mode 100644 index 000000000..c49b7825b --- /dev/null +++ b/ethcore/src/types/pruning_info.rs @@ -0,0 +1,30 @@ +// Copyright 2015, 2016 Ethcore (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 . + +//! Information about portions of the state and chain which the client may serve. +//! +//! Currently assumes that a client will store everything past a certain point +//! or everything. Will be extended in the future to support a definition +//! of which portions of the ancient chain and current state trie are stored as well. + +/// Client pruning info. See module-level docs for more details. +#[derive(Debug, Clone, Binary)] +pub struct PruningInfo { + /// The first block which everything can be served after. + pub earliest_chain: u64 + /// The first block where state requests may be served. + pub earliest_state: u64 +} \ No newline at end of file From 11e6b08f023f406baf355ce8e94dd1cb05f4a530 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Wed, 9 Nov 2016 23:39:56 +0100 Subject: [PATCH 25/37] Move ethcore-light crate into ethcore/light module --- ethcore/light/Cargo.toml | 16 ---------------- ethcore/{light/src => src/light}/client.rs | 0 ethcore/{light/src/lib.rs => src/light/mod.rs} | 9 +-------- .../{light/src => src/light}/net/buffer_flow.rs | 0 ethcore/{light/src => src/light}/net/error.rs | 0 ethcore/{light/src => src/light}/net/mod.rs | 0 ethcore/{light/src => src/light}/net/status.rs | 0 ethcore/{light/src => src/light}/provider.rs | 0 ethcore/{light/src => src/light}/request.rs | 0 9 files changed, 1 insertion(+), 24 deletions(-) delete mode 100644 ethcore/light/Cargo.toml rename ethcore/{light/src => src/light}/client.rs (100%) rename ethcore/{light/src/lib.rs => src/light/mod.rs} (87%) rename ethcore/{light/src => src/light}/net/buffer_flow.rs (100%) rename ethcore/{light/src => src/light}/net/error.rs (100%) rename ethcore/{light/src => src/light}/net/mod.rs (100%) rename ethcore/{light/src => src/light}/net/status.rs (100%) rename ethcore/{light/src => src/light}/provider.rs (100%) rename ethcore/{light/src => src/light}/request.rs (100%) diff --git a/ethcore/light/Cargo.toml b/ethcore/light/Cargo.toml deleted file mode 100644 index daf141de7..000000000 --- a/ethcore/light/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -description = "Parity LES primitives" -homepage = "https://ethcore.io" -license = "GPL-3.0" -name = "ethcore-light" -version = "1.5.0" -authors = ["Ethcore "] - -[dependencies] -log = "0.3" -ethcore = { path = ".." } -ethcore-util = { path = "../../util" } -ethcore-network = { path = "../../util/network" } -ethcore-io = { path = "../../util/io" } -rlp = { path = "../../util/rlp" } -time = "0.1" \ No newline at end of file diff --git a/ethcore/light/src/client.rs b/ethcore/src/light/client.rs similarity index 100% rename from ethcore/light/src/client.rs rename to ethcore/src/light/client.rs diff --git a/ethcore/light/src/lib.rs b/ethcore/src/light/mod.rs similarity index 87% rename from ethcore/light/src/lib.rs rename to ethcore/src/light/mod.rs index 07e6833a7..d96cedeaf 100644 --- a/ethcore/light/src/lib.rs +++ b/ethcore/src/light/mod.rs @@ -28,7 +28,7 @@ //! It starts by performing a header-only sync, verifying random samples //! of members of the chain to varying degrees. -// TODO: remove when integrating with parity. +// TODO: remove when integrating with the rest of parity. #![allow(dead_code)] pub mod client; @@ -36,12 +36,5 @@ pub mod net; pub mod provider; pub mod request; -extern crate ethcore_util as util; -extern crate ethcore_network as network; -extern crate ethcore_io as io; -extern crate ethcore; -extern crate rlp; -extern crate time; - #[macro_use] extern crate log; \ No newline at end of file diff --git a/ethcore/light/src/net/buffer_flow.rs b/ethcore/src/light/net/buffer_flow.rs similarity index 100% rename from ethcore/light/src/net/buffer_flow.rs rename to ethcore/src/light/net/buffer_flow.rs diff --git a/ethcore/light/src/net/error.rs b/ethcore/src/light/net/error.rs similarity index 100% rename from ethcore/light/src/net/error.rs rename to ethcore/src/light/net/error.rs diff --git a/ethcore/light/src/net/mod.rs b/ethcore/src/light/net/mod.rs similarity index 100% rename from ethcore/light/src/net/mod.rs rename to ethcore/src/light/net/mod.rs diff --git a/ethcore/light/src/net/status.rs b/ethcore/src/light/net/status.rs similarity index 100% rename from ethcore/light/src/net/status.rs rename to ethcore/src/light/net/status.rs diff --git a/ethcore/light/src/provider.rs b/ethcore/src/light/provider.rs similarity index 100% rename from ethcore/light/src/provider.rs rename to ethcore/src/light/provider.rs diff --git a/ethcore/light/src/request.rs b/ethcore/src/light/request.rs similarity index 100% rename from ethcore/light/src/request.rs rename to ethcore/src/light/request.rs From 8c2c04844471e2923db06a066cb58953e1587544 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Thu, 10 Nov 2016 14:05:47 +0100 Subject: [PATCH 26/37] clean up errors --- Cargo.lock | 1 + ethcore/Cargo.toml | 1 + ethcore/src/client/client.rs | 2 +- ethcore/src/client/test_client.rs | 2 +- ethcore/src/lib.rs | 2 + ethcore/src/light/client.rs | 20 +++++----- ethcore/src/light/mod.rs | 4 +- ethcore/src/light/net/buffer_flow.rs | 2 +- ethcore/src/light/net/mod.rs | 15 ++++--- ethcore/src/light/provider.rs | 38 ++++++++++-------- .../request.rs => types/les_request.rs} | 40 ++++++++++++------- ethcore/src/types/mod.rs.in | 2 + ethcore/src/types/pruning_info.rs | 4 +- 13 files changed, 78 insertions(+), 55 deletions(-) rename ethcore/src/{light/request.rs => types/les_request.rs} (80%) diff --git a/Cargo.lock b/Cargo.lock index 925535d8a..78f04a9ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -289,6 +289,7 @@ dependencies = [ "ethcore-ipc 1.4.0", "ethcore-ipc-codegen 1.4.0", "ethcore-ipc-nano 1.4.0", + "ethcore-network 1.5.0", "ethcore-util 1.5.0", "ethjson 0.1.0", "ethkey 0.2.0", diff --git a/ethcore/Cargo.toml b/ethcore/Cargo.toml index 667b40ace..825d9ffd1 100644 --- a/ethcore/Cargo.toml +++ b/ethcore/Cargo.toml @@ -41,6 +41,7 @@ ethcore-ipc-nano = { path = "../ipc/nano" } rlp = { path = "../util/rlp" } lru-cache = "0.1.0" ethcore-bloom-journal = { path = "../util/bloom" } +ethcore-network = { path = "../util/network" } [dependencies.hyper] git = "https://github.com/ethcore/hyper" diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 72356e91d..788596d86 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -1229,7 +1229,7 @@ impl BlockChainClient for Client { fn pruning_info(&self) -> PruningInfo { PruningInfo { - earliest_chain: self.chain.read().first_block().unwrap_or(1), + earliest_chain: self.chain.read().first_block_number().unwrap_or(1), earliest_state: self.state_db.lock().journal_db().earliest_era().unwrap_or(0), } } diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index 713c55056..91f0b18ff 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -655,7 +655,7 @@ impl BlockChainClient for TestBlockChainClient { fn pruning_info(&self) -> PruningInfo { PruningInfo { earliest_chain: 1, - earlest_state: 1, + earliest_state: 1, } } } diff --git a/ethcore/src/lib.rs b/ethcore/src/lib.rs index bf3e59171..2c07e7de6 100644 --- a/ethcore/src/lib.rs +++ b/ethcore/src/lib.rs @@ -102,6 +102,7 @@ extern crate rlp; extern crate ethcore_bloom_journal as bloom_journal; extern crate byteorder; extern crate transient_hashmap; +extern crate ethcore_network as network; #[macro_use] extern crate log; @@ -138,6 +139,7 @@ pub mod snapshot; pub mod action_params; pub mod db; pub mod verification; +pub mod light; #[macro_use] pub mod evm; mod cache_manager; diff --git a/ethcore/src/light/client.rs b/ethcore/src/light/client.rs index e3b5745b2..7b821d971 100644 --- a/ethcore/src/light/client.rs +++ b/ethcore/src/light/client.rs @@ -19,21 +19,21 @@ 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; -use ethcore::blockchain_info::BlockChainInfo; +use engines::Engine; +use ids::BlockID; +use service::ClientIoMessage; +use block_import_error::BlockImportError; +use block_status::BlockStatus; +use verification::queue::{HeaderQueue, QueueInfo}; +use transaction::SignedTransaction; +use blockchain_info::BlockChainInfo; use io::IoChannel; use util::hash::H256; use util::{Bytes, Mutex}; -use provider::Provider; -use request; +use light::provider::Provider; +use light::request; /// Light client implementation. pub struct Client { diff --git a/ethcore/src/light/mod.rs b/ethcore/src/light/mod.rs index d96cedeaf..a7b2d3922 100644 --- a/ethcore/src/light/mod.rs +++ b/ethcore/src/light/mod.rs @@ -34,7 +34,5 @@ pub mod client; pub mod net; pub mod provider; -pub mod request; -#[macro_use] -extern crate log; \ No newline at end of file +pub use types::les_request as request; \ No newline at end of file diff --git a/ethcore/src/light/net/buffer_flow.rs b/ethcore/src/light/net/buffer_flow.rs index b7bd30f82..62f351515 100644 --- a/ethcore/src/light/net/buffer_flow.rs +++ b/ethcore/src/light/net/buffer_flow.rs @@ -23,7 +23,7 @@ //! This module provides an interface for configuration of buffer //! flow costs and recharge rates. -use request; +use light::request; use super::packet; use super::error::Error; diff --git a/ethcore/src/light/net/mod.rs b/ethcore/src/light/net/mod.rs index e72ce4bb2..47146a2f9 100644 --- a/ethcore/src/light/net/mod.rs +++ b/ethcore/src/light/net/mod.rs @@ -28,8 +28,8 @@ use util::RwLock; use std::collections::{HashMap, HashSet}; use std::sync::atomic::AtomicUsize; -use provider::Provider; -use request::{self, Request}; +use light::provider::Provider; +use light::request::{self, Request}; use self::buffer_flow::{Buffer, FlowParams}; use self::error::{Error, Punishment}; @@ -287,11 +287,14 @@ impl LightProtocol { self.flow_params.recharge(&mut present_buffer); let req_id: u64 = try!(data.val_at(0)); + let block = { + let rlp = try!(data.at(1)); + (try!(rlp.val_at(0)), try!(rlp.val_at(1))) + }; + let req = request::Headers { - block: { - let rlp = try!(data.at(1)); - (try!(rlp.val_at(0)), try!(rlp.val_at(1))) - }, + block_num: block.0, + block_hash: block.1, max: ::std::cmp::min(MAX_HEADERS, try!(data.val_at(2))), skip: try!(data.val_at(3)), reverse: try!(data.val_at(4)), diff --git a/ethcore/src/light/provider.rs b/ethcore/src/light/provider.rs index 3cabe3feb..481865643 100644 --- a/ethcore/src/light/provider.rs +++ b/ethcore/src/light/provider.rs @@ -17,14 +17,14 @@ //! A provider for the LES protocol. This is typically a full node, who can //! give as much data as necessary to its peers. -use ethcore::client::BlockChainClient; -use ethcore::transaction::SignedTransaction; -use ethcore::blockchain_info::BlockChainInfo; +use client::BlockChainClient; +use transaction::SignedTransaction; +use blockchain_info::BlockChainInfo; use rlp::EMPTY_LIST_RLP; use util::{Bytes, H256}; -use request; +use light::request; /// Defines the operations that a provider for `LES` must fulfill. /// @@ -40,8 +40,8 @@ pub trait Provider: Send + Sync { fn reorg_depth(&self, a: &H256, b: &H256) -> Option; /// Earliest block where state queries are available. - /// All states between this value and - fn earliest_state(&self) -> u64; + /// If `None`, no state queries are servable. + fn earliest_state(&self) -> Option; /// Provide a list of headers starting at the requested block, /// possibly in reverse and skipping `skip` at a time. @@ -83,11 +83,11 @@ impl Provider for T { } fn reorg_depth(&self, a: &H256, b: &H256) -> Option { - self.tree_route.map(|route| route.index as u64) + self.tree_route(a, b).map(|route| route.index as u64) } - fn earliest_state(&self) -> u64 { - self.pruning_info().earliest_state + fn earliest_state(&self) -> Option { + Some(self.pruning_info().earliest_state) } fn block_headers(&self, req: request::Headers) -> Vec { @@ -95,16 +95,18 @@ impl Provider for T { } fn block_bodies(&self, req: request::Bodies) -> Vec { + use ids::BlockID; + req.block_hashes.into_iter() - .map(|hash| self.block_body(hash.into())) - .map(|body| body.unwrap_or_else(|| EMPTY_LIST_RLP.into())) + .map(|hash| self.block_body(BlockID::Hash(hash))) + .map(|body| body.unwrap_or_else(|| EMPTY_LIST_RLP.to_vec())) .collect() } fn receipts(&self, req: request::Receipts) -> Vec { req.block_hashes.into_iter() - .map(|hash| self.block_receipts(&hash) - .map(|receipts| receips.unwrap_or_else(|| EMPTY_LIST_RLP.into())) + .map(|hash| self.block_receipts(&hash)) + .map(|receipts| receipts.unwrap_or_else(|| EMPTY_LIST_RLP.to_vec())) .collect() } @@ -112,12 +114,16 @@ impl Provider for T { unimplemented!() } - fn code(&self, req: request::ContractCodes) -> Vec; + fn code(&self, req: request::ContractCodes) -> Vec { + unimplemented!() + } fn header_proofs(&self, req: request::HeaderProofs) -> Vec { // TODO: [rob] implement CHT stuff on `ethcore` side. - req.requests.into_iter().map(|_| EMPTY_LIST_RLP.into()).collect() + req.requests.into_iter().map(|_| EMPTY_LIST_RLP.to_vec()).collect() } - fn pending_transactions(&self) -> Vec; + fn pending_transactions(&self) -> Vec { + unimplemented!() + } } \ No newline at end of file diff --git a/ethcore/src/light/request.rs b/ethcore/src/types/les_request.rs similarity index 80% rename from ethcore/src/light/request.rs rename to ethcore/src/types/les_request.rs index f043f0f25..e947c654d 100644 --- a/ethcore/src/light/request.rs +++ b/ethcore/src/types/les_request.rs @@ -16,15 +16,16 @@ //! LES request types. -// TODO: make IPC compatible. - use util::H256; /// A request for block headers. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Binary)] pub struct Headers { - /// Block information for the request being made. - pub block: (u64, H256), + /// Starting block number + pub block_num: u64, + /// Starting block hash. This and number could be combined but IPC codegen is + /// not robust enough to support it. + pub block_hash: H256, /// The maximum amount of headers which can be returned. pub max: usize, /// The amount of headers to skip between each response entry. @@ -34,7 +35,7 @@ pub struct Headers { } /// A request for specific block bodies. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Binary)] pub struct Bodies { /// Hashes which bodies are being requested for. pub block_hashes: Vec @@ -44,14 +45,14 @@ pub struct Bodies { /// /// This request is answered with a list of transaction receipts for each block /// requested. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Binary)] pub struct Receipts { /// Block hashes to return receipts for. pub block_hashes: Vec, } /// A request for a state proof -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Binary)] pub struct StateProof { /// Block hash to query state from. pub block: H256, @@ -65,21 +66,30 @@ pub struct StateProof { } /// A request for state proofs. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Binary)] pub struct StateProofs { /// All the proof requests. pub requests: Vec, } /// A request for contract code. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Binary)] +pub struct ContractCode { + /// Block hash + pub block_hash: H256, + /// Account key (== sha3(address)) + pub account_key: H256, +} + +/// A request for contract code. +#[derive(Debug, Clone, PartialEq, Eq, Binary)] pub struct ContractCodes { /// Block hash and account key (== sha3(address)) pairs to fetch code for. - pub code_requests: Vec<(H256, H256)>, + pub code_requests: Vec, } /// A request for a header proof from the Canonical Hash Trie. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Binary)] pub struct HeaderProof { /// Number of the CHT. pub cht_number: u64, @@ -90,14 +100,14 @@ pub struct HeaderProof { } /// A request for header proofs from the CHT. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Binary)] pub struct HeaderProofs { /// All the proof requests. pub requests: Vec, } /// Kinds of requests. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Binary)] pub enum Kind { /// Requesting headers. Headers, @@ -114,7 +124,7 @@ pub enum Kind { } /// Encompasses all possible types of requests in a single structure. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Binary)] pub enum Request { /// Requesting headers. Headers(Headers), diff --git a/ethcore/src/types/mod.rs.in b/ethcore/src/types/mod.rs.in index 6ef67009a..05c2c4dba 100644 --- a/ethcore/src/types/mod.rs.in +++ b/ethcore/src/types/mod.rs.in @@ -34,3 +34,5 @@ pub mod block_import_error; pub mod restoration_status; pub mod snapshot_manifest; pub mod mode; +pub mod pruning_info; +pub mod les_request; \ No newline at end of file diff --git a/ethcore/src/types/pruning_info.rs b/ethcore/src/types/pruning_info.rs index c49b7825b..40564f488 100644 --- a/ethcore/src/types/pruning_info.rs +++ b/ethcore/src/types/pruning_info.rs @@ -24,7 +24,7 @@ #[derive(Debug, Clone, Binary)] pub struct PruningInfo { /// The first block which everything can be served after. - pub earliest_chain: u64 + pub earliest_chain: u64, /// The first block where state requests may be served. - pub earliest_state: u64 + pub earliest_state: u64, } \ No newline at end of file From abf39fde0a3339f6b190932784f6a125b85014cd Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Tue, 15 Nov 2016 14:53:30 +0100 Subject: [PATCH 27/37] implement provider for client --- ethcore/src/client/client.rs | 170 ++++++++++++++++++++++++++----- ethcore/src/light/client.rs | 2 +- ethcore/src/light/mod.rs | 1 + ethcore/src/light/provider.rs | 58 +---------- ethcore/src/state/account.rs | 21 ++++ ethcore/src/state/mod.rs | 52 +++++++++- ethcore/src/types/les_request.rs | 2 +- util/src/trie/mod.rs | 6 +- util/src/trie/recorder.rs | 2 +- 9 files changed, 227 insertions(+), 87 deletions(-) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 788596d86..1054f0614 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -68,6 +68,7 @@ use factory::Factories; use rlp::{decode, View, UntrustedRlp}; use state_db::StateDB; use rand::OsRng; +use light::{self, request}; // re-export pub use types::blockchain_info::BlockChainInfo; @@ -1317,32 +1318,155 @@ impl MayPanic for Client { } } +// Implementation of a light client data provider for a client. +impl light::Provider for Client { + fn chain_info(&self) -> BlockChainInfo { + BlockChainClient::chain_info(self) + } -#[test] -fn should_not_cache_details_before_commit() { - use tests::helpers::*; - use std::thread; - use std::time::Duration; - use std::sync::atomic::{AtomicBool, Ordering}; + fn reorg_depth(&self, a: &H256, b: &H256) -> Option { + self.tree_route(a, b).map(|route| route.index as u64) + } - let client = generate_dummy_client(0); - let genesis = client.chain_info().best_block_hash; - let (new_hash, new_block) = get_good_dummy_block_hash(); + fn earliest_state(&self) -> Option { + Some(self.pruning_info().earliest_state) + } - let go = { - // Separate thread uncommited transaction - let go = Arc::new(AtomicBool::new(false)); - let go_thread = go.clone(); - let another_client = client.reference().clone(); - thread::spawn(move || { - let mut batch = DBTransaction::new(&*another_client.chain.read().db().clone()); - another_client.chain.read().insert_block(&mut batch, &new_block, Vec::new()); - go_thread.store(true, Ordering::SeqCst); - }); - go - }; + fn block_headers(&self, req: request::Headers) -> Vec { + let best_num = self.chain.read().best_block_number(); + let start_num = req.block_num; - while !go.load(Ordering::SeqCst) { thread::park_timeout(Duration::from_millis(5)); } + match self.block_hash(BlockID::Number(req.block_num)) { + Some(hash) if hash == req.block_hash => {} + _=> { + trace!(target: "les_provider", "unknown/non-canonical start block in header request: {:?}", (req.block_num, req.block_hash)); + return vec![] + } + } - assert!(client.tree_route(&genesis, &new_hash).is_none()); + (0u64..req.max as u64) + .map(|x: u64| x.saturating_mul(req.skip)) + .take_while(|x| if req.reverse { x < &start_num } else { best_num - start_num < *x }) + .map(|x| if req.reverse { start_num - x } else { start_num + x }) + .map(|x| self.block_header(BlockID::Number(x))) + .flat_map(|x| x) + .fuse() // collect no more beyond the first `None` + .collect() + } + + fn block_bodies(&self, req: request::Bodies) -> Vec { + use ids::BlockID; + + req.block_hashes.into_iter() + .map(|hash| self.block_body(BlockID::Hash(hash))) + .map(|body| body.unwrap_or_else(|| ::rlp::EMPTY_LIST_RLP.to_vec())) + .collect() + } + + fn receipts(&self, req: request::Receipts) -> Vec { + req.block_hashes.into_iter() + .map(|hash| self.block_receipts(&hash)) + .map(|receipts| receipts.unwrap_or_else(|| ::rlp::EMPTY_LIST_RLP.to_vec())) + .collect() + } + + fn proofs(&self, req: request::StateProofs) -> Vec { + use rlp::{EMPTY_LIST_RLP, RlpStream, Stream}; + + let mut results = Vec::with_capacity(req.requests.len()); + + for request in req.requests { + let state = match self.state_at(BlockID::Hash(request.block)) { + Some(state) => state, + None => { + trace!(target: "light_provider", "state for {} not available", request.block); + results.push(EMPTY_LIST_RLP.to_vec()); + continue; + } + }; + + let res = match request.key2 { + Some(storage_key) => state.prove_storage(request.key1, storage_key, request.from_level), + None => state.prove_account(request.key1, request.from_level), + }; + + match res { + Ok(records) => { + let mut stream = RlpStream::new_list(records.len()); + for record in records { + stream.append_raw(&record, 1); + } + results.push(stream.out()) + } + Err(e) => { + debug!(target: "light_provider", "encountered error {} while forming proof of state at {}", e, request.block); + results.push(EMPTY_LIST_RLP.to_vec()); + } + } + } + + results + } + + fn contract_code(&self, req: request::ContractCodes) -> Vec { + req.code_requests.into_iter() + .map(|req| { + self.state_at(BlockID::Hash(req.block_hash)) + .map(|state| { + match state.code_by_address_hash(req.account_key) { + Ok(code) => code.unwrap_or_else(Vec::new), + Err(e) => { + debug!(target: "light_provider", "encountered error {} while fetching code.", e); + Vec::new() + } + } + }).unwrap_or_else(Vec::new) + }) + .collect() + } + + fn header_proofs(&self, req: request::HeaderProofs) -> Vec { + req.requests.into_iter().map(|_| ::rlp::EMPTY_LIST_RLP.to_vec()).collect() + } + + fn pending_transactions(&self) -> Vec { + BlockChainClient::pending_transactions(self) + } } + +#[cfg(test)] +mod tests { + + #[test] + fn should_not_cache_details_before_commit() { + use client::BlockChainClient; + use tests::helpers::*; + + use std::thread; + use std::time::Duration; + use std::sync::Arc; + use std::sync::atomic::{AtomicBool, Ordering}; + use util::kvdb::DBTransaction; + + let client = generate_dummy_client(0); + let genesis = client.chain_info().best_block_hash; + let (new_hash, new_block) = get_good_dummy_block_hash(); + + let go = { + // Separate thread uncommited transaction + let go = Arc::new(AtomicBool::new(false)); + let go_thread = go.clone(); + let another_client = client.reference().clone(); + thread::spawn(move || { + let mut batch = DBTransaction::new(&*another_client.chain.read().db().clone()); + another_client.chain.read().insert_block(&mut batch, &new_block, Vec::new()); + go_thread.store(true, Ordering::SeqCst); + }); + go + }; + + while !go.load(Ordering::SeqCst) { thread::park_timeout(Duration::from_millis(5)); } + + assert!(client.tree_route(&genesis, &new_hash).is_none()); + } +} \ No newline at end of file diff --git a/ethcore/src/light/client.rs b/ethcore/src/light/client.rs index 7b821d971..699db743a 100644 --- a/ethcore/src/light/client.rs +++ b/ethcore/src/light/client.rs @@ -101,7 +101,7 @@ impl Provider for Client { Vec::new() } - fn code(&self, _req: request::ContractCodes) -> Vec { + fn contract_code(&self, _req: request::ContractCodes) -> Vec { Vec::new() } diff --git a/ethcore/src/light/mod.rs b/ethcore/src/light/mod.rs index a7b2d3922..dca592453 100644 --- a/ethcore/src/light/mod.rs +++ b/ethcore/src/light/mod.rs @@ -35,4 +35,5 @@ pub mod client; pub mod net; pub mod provider; +pub use self::provider::Provider; pub use types::les_request as request; \ No newline at end of file diff --git a/ethcore/src/light/provider.rs b/ethcore/src/light/provider.rs index 481865643..bf65acd18 100644 --- a/ethcore/src/light/provider.rs +++ b/ethcore/src/light/provider.rs @@ -17,11 +17,9 @@ //! A provider for the LES protocol. This is typically a full node, who can //! give as much data as necessary to its peers. -use client::BlockChainClient; use transaction::SignedTransaction; use blockchain_info::BlockChainInfo; -use rlp::EMPTY_LIST_RLP; use util::{Bytes, H256}; use light::request; @@ -65,65 +63,11 @@ pub trait Provider: Send + Sync { fn proofs(&self, req: request::StateProofs) -> Vec; /// Provide contract code for the specified (block_hash, account_hash) pairs. - fn code(&self, req: request::ContractCodes) -> Vec; + fn contract_code(&self, req: request::ContractCodes) -> Vec; /// Provide header proofs from the Canonical Hash Tries. fn header_proofs(&self, req: request::HeaderProofs) -> Vec; /// Provide pending transactions. fn pending_transactions(&self) -> Vec; -} - -// TODO [rob] move into trait definition file after ethcore crate -// is split up. ideally `ethcore-light` will be between `ethcore-blockchain` -// and `ethcore-client` -impl Provider for T { - fn chain_info(&self) -> BlockChainInfo { - BlockChainClient::chain_info(self) - } - - fn reorg_depth(&self, a: &H256, b: &H256) -> Option { - self.tree_route(a, b).map(|route| route.index as u64) - } - - fn earliest_state(&self) -> Option { - Some(self.pruning_info().earliest_state) - } - - fn block_headers(&self, req: request::Headers) -> Vec { - unimplemented!() - } - - fn block_bodies(&self, req: request::Bodies) -> Vec { - use ids::BlockID; - - req.block_hashes.into_iter() - .map(|hash| self.block_body(BlockID::Hash(hash))) - .map(|body| body.unwrap_or_else(|| EMPTY_LIST_RLP.to_vec())) - .collect() - } - - fn receipts(&self, req: request::Receipts) -> Vec { - req.block_hashes.into_iter() - .map(|hash| self.block_receipts(&hash)) - .map(|receipts| receipts.unwrap_or_else(|| EMPTY_LIST_RLP.to_vec())) - .collect() - } - - fn proofs(&self, req: request::StateProofs) -> Vec { - unimplemented!() - } - - fn code(&self, req: request::ContractCodes) -> Vec { - unimplemented!() - } - - fn header_proofs(&self, req: request::HeaderProofs) -> Vec { - // TODO: [rob] implement CHT stuff on `ethcore` side. - req.requests.into_iter().map(|_| EMPTY_LIST_RLP.to_vec()).collect() - } - - fn pending_transactions(&self) -> Vec { - unimplemented!() - } } \ No newline at end of file diff --git a/ethcore/src/state/account.rs b/ethcore/src/state/account.rs index 76061f6a0..4cca0f645 100644 --- a/ethcore/src/state/account.rs +++ b/ethcore/src/state/account.rs @@ -437,6 +437,27 @@ impl Account { } } +// light client storage proof. +impl Account { + /// Prove a storage key's existence or nonexistence in the account's storage + /// trie. + /// `storage_key` is the hash of the desired storage key, meaning + /// this will only work correctly under a secure trie. + /// Returns a merkle proof of the storage trie node with all nodes before `from_level` + /// omitted. + pub fn prove_storage(&self, db: &HashDB, storage_key: H256, from_level: u32) -> Result, Box> { + use util::trie::{Trie, TrieDB}; + use util::trie::recorder::{Recorder, BasicRecorder as TrieRecorder}; + + let mut recorder = TrieRecorder::with_depth(from_level); + + let trie = try!(TrieDB::new(db, &self.storage_root)); + let _ = try!(trie.get_recorded(&storage_key, &mut recorder)); + + Ok(recorder.drain().into_iter().map(|r| r.data).collect()) + } +} + impl fmt::Debug for Account { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", PodAccount::from_account(self)) diff --git a/ethcore/src/state/mod.rs b/ethcore/src/state/mod.rs index 01a7e3b15..bedc46d9c 100644 --- a/ethcore/src/state/mod.rs +++ b/ethcore/src/state/mod.rs @@ -16,7 +16,7 @@ use std::cell::{RefCell, RefMut}; use std::collections::hash_map::Entry; -use util::*; + use receipt::Receipt; use engines::Engine; use env_info::EnvInfo; @@ -30,6 +30,9 @@ use types::state_diff::StateDiff; use transaction::SignedTransaction; use state_db::StateDB; +use util::*; +use util::trie::recorder::{Recorder, BasicRecorder as TrieRecorder}; + mod account; mod substate; @@ -751,6 +754,53 @@ impl State { } } +// LES state proof implementations. +impl State { + /// Prove an account's existence or nonexistence in the state trie. + /// Returns a merkle proof of the account's trie node with all nodes before `from_level` + /// omitted or an encountered trie error. + /// Requires a secure trie to be used for accurate results. + /// `account_key` == sha3(address) + pub fn prove_account(&self, account_key: H256, from_level: u32) -> Result, Box> { + let mut recorder = TrieRecorder::with_depth(from_level); + let trie = try!(TrieDB::new(self.db.as_hashdb(), &self.root)); + let _ = try!(trie.get_recorded(&account_key, &mut recorder)); + + Ok(recorder.drain().into_iter().map(|r| r.data).collect()) + } + + /// Prove an account's storage key's existence or nonexistence in the state. + /// Returns a merkle proof of the account's storage trie with all nodes before + /// `from_level` omitted. Requires a secure trie to be used for correctness. + /// `account_key` == sha3(address) + /// `storage_key` == sha3(key) + pub fn prove_storage(&self, account_key: H256, storage_key: H256, from_level: u32) -> Result, Box> { + // TODO: probably could look into cache somehow but it's keyed by + // address, not sha3(address). + let trie = try!(TrieDB::new(self.db.as_hashdb(), &self.root)); + let acc = match try!(trie.get(&account_key)) { + Some(rlp) => Account::from_rlp(&rlp), + None => return Ok(Vec::new()), + }; + + let account_db = self.factories.accountdb.readonly(self.db.as_hashdb(), account_key); + acc.prove_storage(account_db.as_hashdb(), storage_key, from_level) + } + + /// Get code by address hash. + /// Only works when backed by a secure trie. + pub fn code_by_address_hash(&self, account_key: H256) -> Result, Box> { + let trie = try!(TrieDB::new(self.db.as_hashdb(), &self.root)); + let mut acc = match try!(trie.get(&account_key)) { + Some(rlp) => Account::from_rlp(&rlp), + None => return Ok(None), + }; + + let account_db = self.factories.accountdb.readonly(self.db.as_hashdb(), account_key); + Ok(acc.cache_code(account_db.as_hashdb()).map(|c| (&*c).clone())) + } +} + impl fmt::Debug for State { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", self.cache.borrow()) diff --git a/ethcore/src/types/les_request.rs b/ethcore/src/types/les_request.rs index e947c654d..6e822dcdd 100644 --- a/ethcore/src/types/les_request.rs +++ b/ethcore/src/types/les_request.rs @@ -29,7 +29,7 @@ pub struct Headers { /// The maximum amount of headers which can be returned. pub max: usize, /// The amount of headers to skip between each response entry. - pub skip: usize, + pub skip: u64, /// Whether the headers should proceed in falling number from the initial block. pub reverse: bool, } diff --git a/util/src/trie/mod.rs b/util/src/trie/mod.rs index d4cc04962..5d71c2400 100644 --- a/util/src/trie/mod.rs +++ b/util/src/trie/mod.rs @@ -97,11 +97,11 @@ pub trait Trie { } /// Query the value of the given key in this trie while recording visited nodes - /// to the given recorder. If the query fails, the nodes passed to the recorder are unspecified. + /// to the given recorder. If the query encounters an error, the nodes passed to the recorder are unspecified. fn get_recorded<'a, 'b, R: 'b>(&'a self, key: &'b [u8], rec: &'b mut R) -> Result> where 'a: 'b, R: Recorder; - /// Returns an iterator over elements of trie. + /// Returns a depth-first iterator over the elements of the trie. fn iter<'a>(&'a self) -> Result + 'a>>; } @@ -235,5 +235,5 @@ impl TrieFactory { } /// Returns true iff the trie DB is a fat DB (allows enumeration of keys). - pub fn is_fat(&self) -> bool { self.spec == TrieSpec::Fat } + pub fn is_fat(&self) -> bool { self.spec == TrieSpec::Fat } } diff --git a/util/src/trie/recorder.rs b/util/src/trie/recorder.rs index 2f1d926f0..3930dd543 100644 --- a/util/src/trie/recorder.rs +++ b/util/src/trie/recorder.rs @@ -35,7 +35,6 @@ pub struct Record { /// These are used to record which nodes are visited during a trie query. /// Inline nodes are not to be recorded, as they are contained within their parent. pub trait Recorder { - /// Record that the given node has been visited. /// /// The depth parameter is the depth of the visited node, with the root node having depth 0. @@ -58,6 +57,7 @@ impl Recorder for NoOp { /// A simple recorder. Does nothing fancy but fulfills the `Recorder` interface /// properly. +#[derive(Debug)] pub struct BasicRecorder { nodes: Vec, min_depth: u32, From cb54152c2356e95c10cc8cae8cd3b7b99f3e005a Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Tue, 15 Nov 2016 15:47:08 +0100 Subject: [PATCH 28/37] cut off headers after first missing --- ethcore/src/client/client.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 1054f0614..58c8f4df6 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -1349,8 +1349,8 @@ impl light::Provider for Client { .take_while(|x| if req.reverse { x < &start_num } else { best_num - start_num < *x }) .map(|x| if req.reverse { start_num - x } else { start_num + x }) .map(|x| self.block_header(BlockID::Number(x))) + .take_while(|x| x.is_some()) .flat_map(|x| x) - .fuse() // collect no more beyond the first `None` .collect() } From 7bfb9e4003494fcedba1e65847d8e4abb8ef1c6c Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Tue, 15 Nov 2016 18:19:16 +0100 Subject: [PATCH 29/37] handle all LES requests --- ethcore/src/light/net/buffer_flow.rs | 10 + ethcore/src/light/net/mod.rs | 263 ++++++++++++++++++++++----- ethcore/src/light/provider.rs | 8 +- ethcore/src/types/les_request.rs | 2 +- 4 files changed, 234 insertions(+), 49 deletions(-) diff --git a/ethcore/src/light/net/buffer_flow.rs b/ethcore/src/light/net/buffer_flow.rs index 62f351515..8d6835db3 100644 --- a/ethcore/src/light/net/buffer_flow.rs +++ b/ethcore/src/light/net/buffer_flow.rs @@ -228,6 +228,16 @@ impl FlowParams { buf.estimate = ::std::cmp::min(self.limit, buf.estimate + (elapsed * self.recharge)); } + + /// Refund some buffer which was previously deducted. + /// Does not update the recharge timestamp. + pub fn refund(&self, buf: &mut Buffer, refund_amount: U256) { + buf.estimate = buf.estimate + refund_amount; + + if buf.estimate > self.limit { + buf.estimate = self.limit + } + } } #[cfg(test)] diff --git a/ethcore/src/light/net/mod.rs b/ethcore/src/light/net/mod.rs index 47146a2f9..1d7fc8f2d 100644 --- a/ethcore/src/light/net/mod.rs +++ b/ethcore/src/light/net/mod.rs @@ -23,7 +23,7 @@ use io::TimerToken; use network::{NetworkProtocolHandler, NetworkContext, NetworkError, PeerId}; use rlp::{RlpStream, Stream, UntrustedRlp, View}; use util::hash::H256; -use util::RwLock; +use util::{Mutex, RwLock, U256}; use std::collections::{HashMap, HashSet}; use std::sync::atomic::AtomicUsize; @@ -94,7 +94,7 @@ struct PendingPeer { // data about each peer. struct Peer { - local_buffer: Buffer, // their buffer relative to us + local_buffer: Mutex, // their buffer relative to us remote_buffer: Buffer, // our buffer relative to them current_asking: HashSet, // pending request ids. status: Status, @@ -103,6 +103,28 @@ struct Peer { sent_head: H256, // last head we've given them. } +impl Peer { + // check the maximum cost of a request, returning an error if there's + // not enough buffer left. + // returns the calculated maximum cost. + fn deduct_max(&self, flow_params: &FlowParams, kind: request::Kind, max: usize) -> Result { + let mut local_buffer = self.local_buffer.lock(); + flow_params.recharge(&mut local_buffer); + + let max_cost = flow_params.compute_cost(kind, max); + try!(local_buffer.deduct_cost(max_cost)); + Ok(max_cost) + } + + // refund buffer for a request. returns new buffer amount. + fn refund(&self, flow_params: &FlowParams, amount: U256) -> U256 { + let mut local_buffer = self.local_buffer.lock(); + flow_params.refund(&mut local_buffer, amount); + + local_buffer.current() + } +} + /// This is an implementation of the light ethereum network protocol, abstracted /// over a `Provider` of data and a p2p network. /// @@ -220,7 +242,7 @@ impl LightProtocol { } self.peers.write().insert(*peer, Peer { - local_buffer: self.flow_params.create_buffer(), + local_buffer: Mutex::new(self.flow_params.create_buffer()), remote_buffer: flow_params.create_buffer(), current_asking: HashSet::new(), status: status, @@ -276,15 +298,15 @@ impl LightProtocol { fn get_block_headers(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { const MAX_HEADERS: usize = 512; - let mut present_buffer = match self.peers.read().get(peer) { - Some(peer) => peer.local_buffer.clone(), + let peers = self.peers.read(); + let peer = match peers.get(peer) { + Some(peer) => peer, None => { - debug!(target: "les", "Ignoring announcement from unknown peer"); + debug!(target: "les", "Ignoring request from unknown peer"); return Ok(()) } }; - self.flow_params.recharge(&mut present_buffer); let req_id: u64 = try!(data.val_at(0)); let block = { @@ -300,24 +322,13 @@ impl LightProtocol { reverse: try!(data.val_at(4)), }; - let max_cost = self.flow_params.compute_cost(request::Kind::Headers, req.max); - try!(present_buffer.deduct_cost(max_cost)); + let max_cost = try!(peer.deduct_max(&self.flow_params, request::Kind::Headers, req.max)); let response = self.provider.block_headers(req); let actual_cost = self.flow_params.compute_cost(request::Kind::Headers, response.len()); + assert!(max_cost >= actual_cost, "Actual cost exceeded maximum computed cost."); - let cur_buffer = match self.peers.write().get_mut(peer) { - Some(peer) => { - self.flow_params.recharge(&mut peer.local_buffer); - try!(peer.local_buffer.deduct_cost(actual_cost)); - peer.local_buffer.current() - } - None => { - debug!(target: "les", "peer disconnected during serving of request."); - return Ok(()) - } - }; - + let cur_buffer = peer.refund(&self.flow_params, max_cost - actual_cost); io.respond(packet::BLOCK_HEADERS, { let mut stream = RlpStream::new_list(response.len() + 2); stream.append(&req_id).append(&cur_buffer); @@ -339,39 +350,29 @@ impl LightProtocol { fn get_block_bodies(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { const MAX_BODIES: usize = 256; - let mut present_buffer = match self.peers.read().get(peer) { - Some(peer) => peer.local_buffer.clone(), + let peers = self.peers.read(); + let peer = match peers.get(peer) { + Some(peer) => peer, None => { - debug!(target: "les", "Ignoring announcement from unknown peer"); + debug!(target: "les", "Ignoring request from unknown peer"); return Ok(()) } }; - self.flow_params.recharge(&mut present_buffer); let req_id: u64 = try!(data.val_at(0)); let req = request::Bodies { block_hashes: try!(data.iter().skip(1).take(MAX_BODIES).map(|x| x.as_val()).collect()) }; - let max_cost = self.flow_params.compute_cost(request::Kind::Bodies, req.block_hashes.len()); - try!(present_buffer.deduct_cost(max_cost)); + let max_cost = try!(peer.deduct_max(&self.flow_params, request::Kind::Bodies, req.block_hashes.len())); let response = self.provider.block_bodies(req); let response_len = response.iter().filter(|x| &x[..] != &::rlp::EMPTY_LIST_RLP).count(); let actual_cost = self.flow_params.compute_cost(request::Kind::Bodies, response_len); + assert!(max_cost >= actual_cost, "Actual cost exceeded maximum computed cost."); - let cur_buffer = match self.peers.write().get_mut(peer) { - Some(peer) => { - self.flow_params.recharge(&mut peer.local_buffer); - try!(peer.local_buffer.deduct_cost(actual_cost)); - peer.local_buffer.current() - } - None => { - debug!(target: "les", "peer disconnected during serving of request."); - return Ok(()) - } - }; + let cur_buffer = peer.refund(&self.flow_params, max_cost - actual_cost); io.respond(packet::BLOCK_BODIES, { let mut stream = RlpStream::new_list(response.len() + 2); @@ -391,8 +392,43 @@ impl LightProtocol { } // Handle a request for receipts. - fn get_receipts(&self, _: &PeerId, _: &NetworkContext, _: UntrustedRlp) -> Result<(), Error> { - unimplemented!() + fn get_receipts(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { + const MAX_RECEIPTS: usize = 256; + + let peers = self.peers.read(); + let peer = match peers.get(peer) { + Some(peer) => peer, + None => { + debug!(target: "les", "Ignoring request from unknown peer"); + return Ok(()) + } + }; + + let req_id: u64 = try!(data.val_at(0)); + + let req = request::Receipts { + block_hashes: try!(data.iter().skip(1).take(MAX_RECEIPTS).map(|x| x.as_val()).collect()) + }; + + let max_cost = try!(peer.deduct_max(&self.flow_params, request::Kind::Receipts, req.block_hashes.len())); + + let response = self.provider.receipts(req); + let response_len = response.iter().filter(|x| &x[..] != &::rlp::EMPTY_LIST_RLP).count(); + let actual_cost = self.flow_params.compute_cost(request::Kind::Receipts, response_len); + assert!(max_cost >= actual_cost, "Actual cost exceeded maximum computed cost."); + + let cur_buffer = peer.refund(&self.flow_params, max_cost - actual_cost); + + io.respond(packet::RECEIPTS, { + let mut stream = RlpStream::new_list(response.len() + 2); + stream.append(&req_id).append(&cur_buffer); + + for receipts in response { + stream.append_raw(&receipts, 1); + } + + stream.out() + }).map_err(Into::into) } // Receive a response for receipts. @@ -401,8 +437,54 @@ impl LightProtocol { } // Handle a request for proofs. - fn get_proofs(&self, _: &PeerId, _: &NetworkContext, _: UntrustedRlp) -> Result<(), Error> { - unimplemented!() + fn get_proofs(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { + const MAX_PROOFS: usize = 128; + + let peers = self.peers.read(); + let peer = match peers.get(peer) { + Some(peer) => peer, + None => { + debug!(target: "les", "Ignoring request from unknown peer"); + return Ok(()) + } + }; + + let req_id: u64 = try!(data.val_at(0)); + + let req = { + let requests: Result, Error> = data.iter().skip(1).take(MAX_PROOFS).map(|x| { + Ok(request::StateProof { + block: try!(x.val_at(0)), + key1: try!(x.val_at(1)), + key2: if try!(x.at(2)).is_empty() { None } else { Some(try!(x.val_at(2))) }, + from_level: try!(x.val_at(3)), + }) + }).collect(); + + request::StateProofs { + requests: try!(requests), + } + }; + + let max_cost = try!(peer.deduct_max(&self.flow_params, request::Kind::StateProofs, req.requests.len())); + + let response = self.provider.proofs(req); + let response_len = response.iter().filter(|x| &x[..] != &::rlp::EMPTY_LIST_RLP).count(); + let actual_cost = self.flow_params.compute_cost(request::Kind::StateProofs, response_len); + assert!(max_cost >= actual_cost, "Actual cost exceeded maximum computed cost."); + + let cur_buffer = peer.refund(&self.flow_params, max_cost - actual_cost); + + io.respond(packet::PROOFS, { + let mut stream = RlpStream::new_list(response.len() + 2); + stream.append(&req_id).append(&cur_buffer); + + for proof in response { + stream.append_raw(&proof, 1); + } + + stream.out() + }).map_err(Into::into) } // Receive a response for proofs. @@ -411,8 +493,52 @@ impl LightProtocol { } // Handle a request for contract code. - fn get_contract_code(&self, _: &PeerId, _: &NetworkContext, _: UntrustedRlp) -> Result<(), Error> { - unimplemented!() + fn get_contract_code(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { + const MAX_CODES: usize = 256; + + let peers = self.peers.read(); + let peer = match peers.get(peer) { + Some(peer) => peer, + None => { + debug!(target: "les", "Ignoring request from unknown peer"); + return Ok(()) + } + }; + + let req_id: u64 = try!(data.val_at(0)); + + let req = { + let requests: Result, Error> = data.iter().skip(1).take(MAX_CODES).map(|x| { + Ok(request::ContractCode { + block_hash: try!(x.val_at(0)), + account_key: try!(x.val_at(1)), + }) + }).collect(); + + request::ContractCodes { + code_requests: try!(requests), + } + }; + + let max_cost = try!(peer.deduct_max(&self.flow_params, request::Kind::Codes, req.code_requests.len())); + + let response = self.provider.contract_code(req); + let response_len = response.iter().filter(|x| !x.is_empty()).count(); + let actual_cost = self.flow_params.compute_cost(request::Kind::Codes, response_len); + assert!(max_cost >= actual_cost, "Actual cost exceeded maximum computed cost."); + + let cur_buffer = peer.refund(&self.flow_params, max_cost - actual_cost); + + io.respond(packet::CONTRACT_CODES, { + let mut stream = RlpStream::new_list(response.len() + 2); + stream.append(&req_id).append(&cur_buffer); + + for code in response { + stream.append_raw(&code, 1); + } + + stream.out() + }).map_err(Into::into) } // Receive a response for contract code. @@ -421,8 +547,53 @@ impl LightProtocol { } // Handle a request for header proofs - fn get_header_proofs(&self, _: &PeerId, _: &NetworkContext, _: UntrustedRlp) -> Result<(), Error> { - unimplemented!() + fn get_header_proofs(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { + const MAX_PROOFS: usize = 256; + + let peers = self.peers.read(); + let peer = match peers.get(peer) { + Some(peer) => peer, + None => { + debug!(target: "les", "Ignoring request from unknown peer"); + return Ok(()) + } + }; + + let req_id: u64 = try!(data.val_at(0)); + + let req = { + let requests: Result, Error> = data.iter().skip(1).take(MAX_PROOFS).map(|x| { + Ok(request::HeaderProof { + cht_number: try!(x.val_at(0)), + block_number: try!(x.val_at(1)), + from_level: try!(x.val_at(2)), + }) + }).collect(); + + request::HeaderProofs { + requests: try!(requests), + } + }; + + let max_cost = try!(peer.deduct_max(&self.flow_params, request::Kind::HeaderProofs, req.requests.len())); + + let response = self.provider.header_proofs(req); + let response_len = response.iter().filter(|x| &x[..] != ::rlp::EMPTY_LIST_RLP).count(); + let actual_cost = self.flow_params.compute_cost(request::Kind::HeaderProofs, response_len); + assert!(max_cost >= actual_cost, "Actual cost exceeded maximum computed cost."); + + let cur_buffer = peer.refund(&self.flow_params, max_cost - actual_cost); + + io.respond(packet::HEADER_PROOFS, { + let mut stream = RlpStream::new_list(response.len() + 2); + stream.append(&req_id).append(&cur_buffer); + + for proof in response { + stream.append_raw(&proof, 1); + } + + stream.out() + }).map_err(Into::into) } // Receive a response for header proofs diff --git a/ethcore/src/light/provider.rs b/ethcore/src/light/provider.rs index bf65acd18..5aa61828a 100644 --- a/ethcore/src/light/provider.rs +++ b/ethcore/src/light/provider.rs @@ -27,7 +27,8 @@ use light::request; /// Defines the operations that a provider for `LES` must fulfill. /// /// These are defined at [1], but may be subject to change. -/// Requests which can't be fulfilled should return an empty RLP list. +/// Requests which can't be fulfilled should return either an empty RLP list +/// or empty vector where appropriate. /// /// [1]: https://github.com/ethcore/parity/wiki/Light-Ethereum-Subprotocol-(LES) pub trait Provider: Send + Sync { @@ -35,6 +36,8 @@ pub trait Provider: Send + Sync { 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; /// Earliest block where state queries are available. @@ -59,10 +62,11 @@ pub trait Provider: Send + Sync { /// Provide a set of merkle proofs, as requested. Each request is a /// block hash and request parameters. /// - /// Returns a vector to RLP-encoded lists satisfying the requests. + /// Returns a vector of RLP-encoded lists satisfying the requests. fn proofs(&self, req: request::StateProofs) -> Vec; /// Provide contract code for the specified (block_hash, account_hash) pairs. + /// Each item in the resulting vector is either the raw bytecode or empty. fn contract_code(&self, req: request::ContractCodes) -> Vec; /// Provide header proofs from the Canonical Hash Tries. diff --git a/ethcore/src/types/les_request.rs b/ethcore/src/types/les_request.rs index 6e822dcdd..09d96db09 100644 --- a/ethcore/src/types/les_request.rs +++ b/ethcore/src/types/les_request.rs @@ -103,7 +103,7 @@ pub struct HeaderProof { #[derive(Debug, Clone, PartialEq, Eq, Binary)] pub struct HeaderProofs { /// All the proof requests. - pub requests: Vec, + pub requests: Vec, } /// Kinds of requests. From 3fabad5c0fb9eb0923b706bd38a78013a90106bd Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Fri, 18 Nov 2016 12:36:31 +0100 Subject: [PATCH 30/37] event struct types --- ethcore/src/light/net/event.rs | 40 ++++++++++++++++++++++++++++++++++ ethcore/src/light/net/mod.rs | 3 ++- 2 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 ethcore/src/light/net/event.rs diff --git a/ethcore/src/light/net/event.rs b/ethcore/src/light/net/event.rs new file mode 100644 index 000000000..f7dee217a --- /dev/null +++ b/ethcore/src/light/net/event.rs @@ -0,0 +1,40 @@ +// Copyright 2015, 2016 Ethcore (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 . + +//! Network events and event listeners. + +use network::PeerId; + +use super::{Status, Capabilities, Announcement}; + +use transaction::SignedTransaction; + +/// Peer connected +pub struct Connect(PeerId, Status, Capabilities); + +/// Peer disconnected +pub struct Disconnect(PeerId); + +/// Peer announces new capabilities. +pub struct Announcement(PeerId, Announcement); + +/// Transactions to be relayed. +pub struct RelayTransactions(Vec); + +/// An LES event handler. +pub trait Handler { + fn on_connect(&self, _event: Connect); +} \ No newline at end of file diff --git a/ethcore/src/light/net/mod.rs b/ethcore/src/light/net/mod.rs index 1d7fc8f2d..fc9ecbd18 100644 --- a/ethcore/src/light/net/mod.rs +++ b/ethcore/src/light/net/mod.rs @@ -39,7 +39,8 @@ mod buffer_flow; mod error; mod status; -pub use self::status::Announcement; +pub mod event; +pub use self::status::{Status, Capabilities, Announcement}; const TIMEOUT: TimerToken = 0; const TIMEOUT_INTERVAL_MS: u64 = 1000; From 63aa54cfc7b821625e7c421857ff8a33eaeec344 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Fri, 18 Nov 2016 15:30:06 +0100 Subject: [PATCH 31/37] trigger event handlers, update capabilities --- ethcore/src/light/net/event.rs | 40 ------------------- ethcore/src/light/net/mod.rs | 69 +++++++++++++++++++++++++-------- ethcore/src/light/net/status.rs | 10 +++++ 3 files changed, 63 insertions(+), 56 deletions(-) delete mode 100644 ethcore/src/light/net/event.rs diff --git a/ethcore/src/light/net/event.rs b/ethcore/src/light/net/event.rs deleted file mode 100644 index f7dee217a..000000000 --- a/ethcore/src/light/net/event.rs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2015, 2016 Ethcore (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 . - -//! Network events and event listeners. - -use network::PeerId; - -use super::{Status, Capabilities, Announcement}; - -use transaction::SignedTransaction; - -/// Peer connected -pub struct Connect(PeerId, Status, Capabilities); - -/// Peer disconnected -pub struct Disconnect(PeerId); - -/// Peer announces new capabilities. -pub struct Announcement(PeerId, Announcement); - -/// Transactions to be relayed. -pub struct RelayTransactions(Vec); - -/// An LES event handler. -pub trait Handler { - fn on_connect(&self, _event: Connect); -} \ No newline at end of file diff --git a/ethcore/src/light/net/mod.rs b/ethcore/src/light/net/mod.rs index fc9ecbd18..8d369e7bb 100644 --- a/ethcore/src/light/net/mod.rs +++ b/ethcore/src/light/net/mod.rs @@ -30,16 +30,15 @@ use std::sync::atomic::AtomicUsize; use light::provider::Provider; use light::request::{self, Request}; +use transaction::SignedTransaction; use self::buffer_flow::{Buffer, FlowParams}; use self::error::{Error, Punishment}; -use self::status::{Status, Capabilities}; mod buffer_flow; mod error; mod status; -pub mod event; pub use self::status::{Status, Capabilities, Announcement}; const TIMEOUT: TimerToken = 0; @@ -126,6 +125,18 @@ impl Peer { } } +/// An LES event handler. +pub trait Handler: Send + Sync { + /// Called when a peer connects. + fn on_connect(&self, _id: PeerId, _status: &Status, _capabilities: &Capabilities) { } + /// Called when a peer disconnects + fn on_disconnect(&self, _id: PeerId) { } + /// Called when a peer makes an announcement. + fn on_announcement(&self, _id: PeerId, _announcement: &Announcement) { } + /// Called when a peer requests relay of some transactions. + fn on_transactions(&self, _id: PeerId, _relay: &[SignedTransaction]) { } +} + /// This is an implementation of the light ethereum network protocol, abstracted /// over a `Provider` of data and a p2p network. /// @@ -141,6 +152,7 @@ pub struct LightProtocol { pending_requests: RwLock>, capabilities: RwLock, flow_params: FlowParams, // assumed static and same for every peer. + handlers: Vec>, req_id: AtomicUsize, } @@ -150,6 +162,9 @@ impl LightProtocol { pub fn make_announcement(&self, mut announcement: Announcement, io: &NetworkContext) { let mut reorgs_map = HashMap::new(); + // update stored capabilities + self.capabilities.write().update_from(&announcement); + // calculate reorg info and send packets for (peer_id, peer_info) in self.peers.write().iter_mut() { let reorg_depth = reorgs_map.entry(peer_info.sent_head) @@ -174,6 +189,14 @@ impl LightProtocol { } } } + + /// Add an event handler. + /// Ownership will be transferred to the protocol structure, + /// and the handler will be kept alive as long as it is. + /// These are intended to be added at the beginning of the + pub fn add_handler(&mut self, handler: Box) { + self.handlers.push(handler); + } } impl LightProtocol { @@ -196,7 +219,11 @@ impl LightProtocol { fn on_disconnect(&self, peer: PeerId) { // TODO: reassign all requests assigned to this peer. self.pending_peers.write().remove(&peer); - self.peers.write().remove(&peer); + if self.peers.write().remove(&peer).is_some() { + for handler in &self.handlers { + handler.on_disconnect(peer) + } + } } // send status to a peer. @@ -246,12 +273,16 @@ impl LightProtocol { local_buffer: Mutex::new(self.flow_params.create_buffer()), remote_buffer: flow_params.create_buffer(), current_asking: HashSet::new(), - status: status, - capabilities: capabilities, + status: status.clone(), + capabilities: capabilities.clone(), remote_flow: flow_params, sent_head: pending.sent_head, }); + for handler in &self.handlers { + handler.on_connect(*peer, &status, &capabilities) + } + Ok(()) } @@ -282,15 +313,11 @@ impl LightProtocol { } // update capabilities. - { - let caps = &mut peer_info.capabilities; - caps.serve_headers = caps.serve_headers || announcement.serve_headers; - caps.serve_state_since = caps.serve_state_since.or(announcement.serve_state_since); - caps.serve_chain_since = caps.serve_chain_since.or(announcement.serve_chain_since); - caps.tx_relay = caps.tx_relay || announcement.tx_relay; - } + peer_info.capabilities.update_from(&announcement); - // TODO: notify listeners if new best block. + for handler in &self.handlers { + handler.on_announcement(*peer, &announcement); + } Ok(()) } @@ -603,8 +630,18 @@ impl LightProtocol { } // Receive a set of transactions to relay. - fn relay_transactions(&self, _: &PeerId, _: &NetworkContext, _: UntrustedRlp) -> Result<(), Error> { - unimplemented!() + fn relay_transactions(&self, peer: &PeerId, data: UntrustedRlp) -> Result<(), Error> { + const MAX_TRANSACTIONS: usize = 256; + + let txs: Vec<_> = try!(data.iter().take(MAX_TRANSACTIONS).map(|x| x.as_val::()).collect()); + + debug!(target: "les", "Received {} transactions to relay from peer {}", txs.len(), peer); + + for handler in &self.handlers { + handler.on_transactions(*peer, &txs); + } + + Ok(()) } } @@ -639,7 +676,7 @@ impl NetworkProtocolHandler for LightProtocol { packet::GET_HEADER_PROOFS => self.get_header_proofs(peer, io, rlp), packet::HEADER_PROOFS => self.header_proofs(peer, io, rlp), - packet::SEND_TRANSACTIONS => self.relay_transactions(peer, io, rlp), + packet::SEND_TRANSACTIONS => self.relay_transactions(peer, rlp), other => { Err(Error::UnrecognizedPacket(other)) diff --git a/ethcore/src/light/net/status.rs b/ethcore/src/light/net/status.rs index 5aaea9f3a..e8f874621 100644 --- a/ethcore/src/light/net/status.rs +++ b/ethcore/src/light/net/status.rs @@ -201,6 +201,16 @@ impl Default for Capabilities { } } +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; + } +} + /// Attempt to parse a handshake message into its three parts: /// - chain status /// - serving capabilities From 4fd9670b332c0768935bdfb477ac3ae60eaee55e Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Fri, 18 Nov 2016 19:12:20 +0100 Subject: [PATCH 32/37] support request sending --- ethcore/src/light/net/buffer_flow.rs | 33 +++++ ethcore/src/light/net/error.rs | 4 + ethcore/src/light/net/mod.rs | 177 ++++++++++++++++++++++++++- ethcore/src/light/net/status.rs | 2 + ethcore/src/types/les_request.rs | 12 ++ 5 files changed, 223 insertions(+), 5 deletions(-) diff --git a/ethcore/src/light/net/buffer_flow.rs b/ethcore/src/light/net/buffer_flow.rs index 8d6835db3..7f653f826 100644 --- a/ethcore/src/light/net/buffer_flow.rs +++ b/ethcore/src/light/net/buffer_flow.rs @@ -206,6 +206,39 @@ impl FlowParams { cost.0 + (amount * cost.1) } + /// Compute the maximum number of costs of a specific kind which can be made + /// with the given buffer. + /// Saturates at `usize::max()`. This is not a problem in practice because + /// this amount of requests is already prohibitively large. + pub fn max_amount(&self, buffer: &Buffer, kind: request::Kind) -> usize { + use util::Uint; + use std::usize; + + let cost = match kind { + request::Kind::Headers => &self.costs.headers, + request::Kind::Bodies => &self.costs.bodies, + request::Kind::Receipts => &self.costs.receipts, + request::Kind::StateProofs => &self.costs.state_proofs, + request::Kind::Codes => &self.costs.contract_codes, + request::Kind::HeaderProofs => &self.costs.header_proofs, + }; + + let start = buffer.current(); + + if start <= cost.0 { + return 0; + } else if cost.1 == U256::zero() { + return usize::MAX; + } + + let max = (start - cost.0) / cost.1; + if max >= usize::MAX.into() { + usize::MAX + } else { + max.as_u64() as usize + } + } + /// Create initial buffer parameter. pub fn create_buffer(&self) -> Buffer { Buffer { diff --git a/ethcore/src/light/net/error.rs b/ethcore/src/light/net/error.rs index e15bd50d3..0855cdeb8 100644 --- a/ethcore/src/light/net/error.rs +++ b/ethcore/src/light/net/error.rs @@ -52,6 +52,8 @@ pub enum Error { UnexpectedHandshake, /// Peer on wrong network (wrong NetworkId or genesis hash) WrongNetwork, + /// Unknown peer. + UnknownPeer, } impl Error { @@ -64,6 +66,7 @@ impl Error { Error::UnrecognizedPacket(_) => Punishment::Disconnect, Error::UnexpectedHandshake => Punishment::Disconnect, Error::WrongNetwork => Punishment::Disable, + Error::UnknownPeer => Punishment::Disconnect, } } } @@ -89,6 +92,7 @@ impl fmt::Display for Error { 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"), } } } \ No newline at end of file diff --git a/ethcore/src/light/net/mod.rs b/ethcore/src/light/net/mod.rs index 8d369e7bb..a2202d460 100644 --- a/ethcore/src/light/net/mod.rs +++ b/ethcore/src/light/net/mod.rs @@ -24,9 +24,10 @@ use network::{NetworkProtocolHandler, NetworkContext, NetworkError, PeerId}; use rlp::{RlpStream, Stream, UntrustedRlp, View}; use util::hash::H256; use util::{Mutex, RwLock, U256}; +use time::SteadyTime; use std::collections::{HashMap, HashSet}; -use std::sync::atomic::AtomicUsize; +use std::sync::atomic::{AtomicUsize, Ordering}; use light::provider::Provider; use light::request::{self, Request}; @@ -39,7 +40,7 @@ mod buffer_flow; mod error; mod status; -pub use self::status::{Status, Capabilities, Announcement}; +pub use self::status::{Status, Capabilities, Announcement, NetworkId}; const TIMEOUT: TimerToken = 0; const TIMEOUT_INTERVAL_MS: u64 = 1000; @@ -86,6 +87,10 @@ mod packet { pub const HEADER_PROOFS: u8 = 0x0e; } +/// A request id. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct ReqId(usize); + // A pending peer: one we've sent our status to but // may not have received one for. struct PendingPeer { @@ -137,6 +142,24 @@ pub trait Handler: Send + Sync { fn on_transactions(&self, _id: PeerId, _relay: &[SignedTransaction]) { } } +// a request and the time it was made. +struct Requested { + request: Request, + timestamp: SteadyTime, +} + +/// Protocol parameters. +pub struct Params { + /// Genesis hash. + pub genesis_hash: H256, + /// Network id. + pub network_id: NetworkId, + /// Buffer flow parameters. + pub flow_params: FlowParams, + /// Initial capabilities. + pub capabilities: Capabilities, +} + /// This is an implementation of the light ethereum network protocol, abstracted /// over a `Provider` of data and a p2p network. /// @@ -146,10 +169,10 @@ pub trait Handler: Send + Sync { pub struct LightProtocol { provider: Box, genesis_hash: H256, - network_id: status::NetworkId, + network_id: NetworkId, pending_peers: RwLock>, peers: RwLock>, - pending_requests: RwLock>, + pending_requests: RwLock>, capabilities: RwLock, flow_params: FlowParams, // assumed static and same for every peer. handlers: Vec>, @@ -157,9 +180,71 @@ pub struct LightProtocol { } impl LightProtocol { + /// Create a new instance of the protocol manager. + pub fn new(provider: Box, params: Params) -> Self { + LightProtocol { + provider: provider, + genesis_hash: params.genesis_hash, + network_id: params.network_id, + pending_peers: RwLock::new(HashMap::new()), + peers: RwLock::new(HashMap::new()), + pending_requests: RwLock::new(HashMap::new()), + capabilities: RwLock::new(params.capabilities), + flow_params: params.flow_params, + handlers: Vec::new(), + req_id: AtomicUsize::new(0), + } + } + + /// 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 { + self.peers.write().get_mut(&peer).map(|peer| { + peer.remote_flow.recharge(&mut peer.remote_buffer); + peer.remote_flow.max_amount(&peer.remote_buffer, kind) + }) + } + + /// Make a request to a peer. + /// + /// Fails on: nonexistent peer, network error, + /// insufficient buffer. Does not check capabilities before sending. + /// On success, returns a request id which can later be coordinated + /// with an event. + pub fn request_from(&self, io: &NetworkContext, peer_id: &PeerId, request: Request) -> Result { + let mut peers = self.peers.write(); + let peer = try!(peers.get_mut(peer_id).ok_or_else(|| Error::UnknownPeer)); + peer.remote_flow.recharge(&mut peer.remote_buffer); + + let max = peer.remote_flow.compute_cost(request.kind(), request.amount()); + try!(peer.remote_buffer.deduct_cost(max)); + + let req_id = self.req_id.fetch_add(1, Ordering::SeqCst); + let packet_data = encode_request(&request, req_id); + + let packet_id = match request.kind() { + request::Kind::Headers => packet::GET_BLOCK_HEADERS, + request::Kind::Bodies => packet::GET_BLOCK_BODIES, + request::Kind::Receipts => packet::GET_RECEIPTS, + request::Kind::StateProofs => packet::GET_PROOFS, + request::Kind::Codes => packet::GET_CONTRACT_CODES, + request::Kind::HeaderProofs => packet::GET_HEADER_PROOFS, + }; + + try!(io.send(*peer_id, packet_id, packet_data)); + + peer.current_asking.insert(req_id); + self.pending_requests.write().insert(req_id, Requested { + request: request, + timestamp: SteadyTime::now(), + }); + + Ok(ReqId(req_id)) + } + /// Make an announcement of new chain head and capabilities to all peers. /// The announcement is expected to be valid. - pub fn make_announcement(&self, mut announcement: Announcement, io: &NetworkContext) { + pub fn make_announcement(&self, io: &NetworkContext, mut announcement: Announcement) { let mut reorgs_map = HashMap::new(); // update stored capabilities @@ -715,4 +800,86 @@ impl NetworkProtocolHandler for LightProtocol { _ => warn!(target: "les", "received timeout on unknown token {}", timer), } } +} + +// Helper for encoding the request to RLP with the given ID. +fn encode_request(req: &Request, req_id: usize) -> Vec { + match *req { + Request::Headers(ref headers) => { + let mut stream = RlpStream::new_list(5); + stream + .append(&req_id) + .begin_list(2) + .append(&headers.block_num) + .append(&headers.block_hash) + .append(&headers.max) + .append(&headers.skip) + .append(&headers.reverse); + stream.out() + } + Request::Bodies(ref request) => { + let mut stream = RlpStream::new_list(request.block_hashes.len() + 1); + stream.append(&req_id); + + for hash in &request.block_hashes { + stream.append(hash); + } + + stream.out() + } + Request::Receipts(ref request) => { + let mut stream = RlpStream::new_list(request.block_hashes.len() + 1); + stream.append(&req_id); + + for hash in &request.block_hashes { + stream.append(hash); + } + + stream.out() + } + Request::StateProofs(ref request) => { + let mut stream = RlpStream::new_list(request.requests.len() + 1); + stream.append(&req_id); + + for proof_req in &request.requests { + stream.begin_list(4) + .append(&proof_req.block) + .append(&proof_req.key1); + + match proof_req.key2 { + Some(ref key2) => stream.append(key2), + None => stream.append_empty_data(), + }; + + stream.append(&proof_req.from_level); + } + + stream.out() + } + Request::Codes(ref request) => { + let mut stream = RlpStream::new_list(request.code_requests.len() + 1); + stream.append(&req_id); + + for code_req in &request.code_requests { + stream.begin_list(2) + .append(&code_req.block_hash) + .append(&code_req.account_key); + } + + stream.out() + } + Request::HeaderProofs(ref request) => { + let mut stream = RlpStream::new_list(request.requests.len() + 1); + stream.append(&req_id); + + for proof_req in &request.requests { + stream.begin_list(3) + .append(&proof_req.cht_number) + .append(&proof_req.block_number) + .append(&proof_req.from_level); + } + + stream.out() + } + } } \ No newline at end of file diff --git a/ethcore/src/light/net/status.rs b/ethcore/src/light/net/status.rs index e8f874621..2c0c5f79a 100644 --- a/ethcore/src/light/net/status.rs +++ b/ethcore/src/light/net/status.rs @@ -183,8 +183,10 @@ 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, /// Earliest block number it can serve state requests for. + /// `None` means no requests will be servable. pub serve_state_since: Option, /// Whether it can relay transactions to the eth network. pub tx_relay: bool, diff --git a/ethcore/src/types/les_request.rs b/ethcore/src/types/les_request.rs index 09d96db09..d0de080ee 100644 --- a/ethcore/src/types/les_request.rs +++ b/ethcore/src/types/les_request.rs @@ -152,4 +152,16 @@ impl Request { Request::HeaderProofs(_) => Kind::HeaderProofs, } } + + /// Get the amount of requests being made. + pub fn amount(&self) -> usize { + match *self { + Request::Headers(ref req) => req.max, + Request::Bodies(ref req) => req.block_hashes.len(), + Request::Receipts(ref req) => req.block_hashes.len(), + Request::StateProofs(ref req) => req.requests.len(), + Request::Codes(ref req) => req.code_requests.len(), + Request::HeaderProofs(ref req) => req.requests.len(), + } + } } \ No newline at end of file From 48df2e12facf6265d44fb1828e2ee03abf2a0af4 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Fri, 18 Nov 2016 19:26:05 +0100 Subject: [PATCH 33/37] exclusive access to each peer at a time --- ethcore/src/light/net/mod.rs | 59 +++++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 21 deletions(-) diff --git a/ethcore/src/light/net/mod.rs b/ethcore/src/light/net/mod.rs index a2202d460..4b92e262e 100644 --- a/ethcore/src/light/net/mod.rs +++ b/ethcore/src/light/net/mod.rs @@ -99,7 +99,7 @@ struct PendingPeer { // data about each peer. struct Peer { - local_buffer: Mutex, // their buffer relative to us + local_buffer: Buffer, // their buffer relative to us remote_buffer: Buffer, // our buffer relative to them current_asking: HashSet, // pending request ids. status: Status, @@ -112,21 +112,25 @@ impl Peer { // check the maximum cost of a request, returning an error if there's // not enough buffer left. // returns the calculated maximum cost. - fn deduct_max(&self, flow_params: &FlowParams, kind: request::Kind, max: usize) -> Result { - let mut local_buffer = self.local_buffer.lock(); - flow_params.recharge(&mut local_buffer); + fn deduct_max(&mut self, flow_params: &FlowParams, kind: request::Kind, max: usize) -> Result { + flow_params.recharge(&mut self.local_buffer); let max_cost = flow_params.compute_cost(kind, max); - try!(local_buffer.deduct_cost(max_cost)); + try!(self.local_buffer.deduct_cost(max_cost)); Ok(max_cost) } // refund buffer for a request. returns new buffer amount. - fn refund(&self, flow_params: &FlowParams, amount: U256) -> U256 { - let mut local_buffer = self.local_buffer.lock(); - flow_params.refund(&mut local_buffer, amount); + fn refund(&mut self, flow_params: &FlowParams, amount: U256) -> U256 { + flow_params.refund(&mut self.local_buffer, amount); - local_buffer.current() + self.local_buffer.current() + } + + // recharge remote buffer with remote flow params. + fn recharge_remote(&mut self) { + let flow = &mut self.remote_flow; + flow.recharge(&mut self.remote_buffer); } } @@ -171,7 +175,7 @@ pub struct LightProtocol { genesis_hash: H256, network_id: NetworkId, pending_peers: RwLock>, - peers: RwLock>, + peers: RwLock>>, pending_requests: RwLock>, capabilities: RwLock, flow_params: FlowParams, // assumed static and same for every peer. @@ -199,8 +203,9 @@ 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 { - self.peers.write().get_mut(&peer).map(|peer| { - peer.remote_flow.recharge(&mut peer.remote_buffer); + self.peers.read().get(&peer).map(|peer| { + let mut peer = peer.lock(); + peer.recharge_remote(); peer.remote_flow.max_amount(&peer.remote_buffer, kind) }) } @@ -212,9 +217,11 @@ impl LightProtocol { /// On success, returns a request id which can later be coordinated /// with an event. pub fn request_from(&self, io: &NetworkContext, peer_id: &PeerId, request: Request) -> Result { - let mut peers = self.peers.write(); - let peer = try!(peers.get_mut(peer_id).ok_or_else(|| Error::UnknownPeer)); - peer.remote_flow.recharge(&mut peer.remote_buffer); + let peers = self.peers.read(); + let peer = try!(peers.get(peer_id).ok_or_else(|| Error::UnknownPeer)); + let mut peer = peer.lock(); + + peer.recharge_remote(); let max = peer.remote_flow.compute_cost(request.kind(), request.amount()); try!(peer.remote_buffer.deduct_cost(max)); @@ -251,7 +258,8 @@ impl LightProtocol { self.capabilities.write().update_from(&announcement); // calculate reorg info and send packets - for (peer_id, peer_info) in self.peers.write().iter_mut() { + for (peer_id, peer_info) in self.peers.read().iter() { + let mut peer_info = peer_info.lock(); let reorg_depth = reorgs_map.entry(peer_info.sent_head) .or_insert_with(|| { match self.provider.reorg_depth(&announcement.head_hash, &peer_info.sent_head) { @@ -354,15 +362,15 @@ impl LightProtocol { return Err(Error::WrongNetwork); } - self.peers.write().insert(*peer, Peer { - local_buffer: Mutex::new(self.flow_params.create_buffer()), + self.peers.write().insert(*peer, Mutex::new(Peer { + local_buffer: self.flow_params.create_buffer(), remote_buffer: flow_params.create_buffer(), current_asking: HashSet::new(), status: status.clone(), capabilities: capabilities.clone(), remote_flow: flow_params, sent_head: pending.sent_head, - }); + })); for handler in &self.handlers { handler.on_connect(*peer, &status, &capabilities) @@ -379,13 +387,15 @@ impl LightProtocol { } let announcement = try!(status::parse_announcement(data)); - let mut peers = self.peers.write(); + let peers = self.peers.read(); - let peer_info = match peers.get_mut(peer) { + let peer_info = match peers.get(peer) { Some(info) => info, None => return Ok(()), }; + let mut peer_info = peer_info.lock(); + // update status. { // TODO: punish peer if they've moved backwards. @@ -420,6 +430,8 @@ impl LightProtocol { } }; + let mut peer = peer.lock(); + let req_id: u64 = try!(data.val_at(0)); let block = { @@ -471,6 +483,7 @@ impl LightProtocol { return Ok(()) } }; + let mut peer = peer.lock(); let req_id: u64 = try!(data.val_at(0)); @@ -516,6 +529,7 @@ impl LightProtocol { return Ok(()) } }; + let mut peer = peer.lock(); let req_id: u64 = try!(data.val_at(0)); @@ -561,6 +575,7 @@ impl LightProtocol { return Ok(()) } }; + let mut peer = peer.lock(); let req_id: u64 = try!(data.val_at(0)); @@ -617,6 +632,7 @@ impl LightProtocol { return Ok(()) } }; + let mut peer = peer.lock(); let req_id: u64 = try!(data.val_at(0)); @@ -671,6 +687,7 @@ impl LightProtocol { return Ok(()) } }; + let mut peer = peer.lock(); let req_id: u64 = try!(data.val_at(0)); From 58ca93c123a73b264907a3f6565373e788201392 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Fri, 18 Nov 2016 19:27:32 +0100 Subject: [PATCH 34/37] document lock order --- ethcore/src/light/net/mod.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ethcore/src/light/net/mod.rs b/ethcore/src/light/net/mod.rs index 4b92e262e..2d38d4fd5 100644 --- a/ethcore/src/light/net/mod.rs +++ b/ethcore/src/light/net/mod.rs @@ -170,6 +170,10 @@ pub struct Params { /// This is simply designed for request-response purposes. Higher level uses /// of the protocol, such as synchronization, will function as wrappers around /// this system. +// +// LOCK ORDER: +// Locks must be acquired in the order declared, and when holding a read lock +// on the peers, only one peer may be held at a time. pub struct LightProtocol { provider: Box, genesis_hash: H256, From 61c3358447c7afc4e35e748a675acca5304bbf62 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Mon, 5 Dec 2016 16:55:33 +0100 Subject: [PATCH 35/37] move light to its own crate again --- ethcore/{src/light => light/src}/client.rs | 0 .../{src/light/mod.rs => light/src/lib.rs} | 14 +- .../light => light/src}/net/buffer_flow.rs | 0 ethcore/{src/light => light/src}/net/error.rs | 0 ethcore/{src/light => light/src}/net/mod.rs | 0 .../{src/light => light/src}/net/status.rs | 0 ethcore/light/src/provider.rs | 194 ++++++++++++++++++ ethcore/src/client/client.rs | 126 ++---------- ethcore/src/client/mod.rs | 2 +- ethcore/src/client/traits.rs | 25 ++- ethcore/src/lib.rs | 1 - ethcore/src/light/provider.rs | 77 ------- ethcore/src/types/les_request.rs | 167 --------------- ethcore/src/types/mod.rs.in | 3 +- 14 files changed, 247 insertions(+), 362 deletions(-) rename ethcore/{src/light => light/src}/client.rs (100%) rename ethcore/{src/light/mod.rs => light/src/lib.rs} (86%) rename ethcore/{src/light => light/src}/net/buffer_flow.rs (100%) rename ethcore/{src/light => light/src}/net/error.rs (100%) rename ethcore/{src/light => light/src}/net/mod.rs (100%) rename ethcore/{src/light => light/src}/net/status.rs (100%) create mode 100644 ethcore/light/src/provider.rs delete mode 100644 ethcore/src/light/provider.rs delete mode 100644 ethcore/src/types/les_request.rs diff --git a/ethcore/src/light/client.rs b/ethcore/light/src/client.rs similarity index 100% rename from ethcore/src/light/client.rs rename to ethcore/light/src/client.rs diff --git a/ethcore/src/light/mod.rs b/ethcore/light/src/lib.rs similarity index 86% rename from ethcore/src/light/mod.rs rename to ethcore/light/src/lib.rs index dca592453..e698905e8 100644 --- a/ethcore/src/light/mod.rs +++ b/ethcore/light/src/lib.rs @@ -35,5 +35,17 @@ pub mod client; pub mod net; pub mod provider; +mod types; + pub use self::provider::Provider; -pub use types::les_request as request; \ No newline at end of file +pub use types::les_request as request; + +#[macro_use] +extern crate log; + +extern crate ethcore; +extern crate ethcore_util as util; +extern crate ethcore_network as network; +extern crate ethcore_io as io; +extern crate rlp; +extern crate time; \ No newline at end of file diff --git a/ethcore/src/light/net/buffer_flow.rs b/ethcore/light/src/net/buffer_flow.rs similarity index 100% rename from ethcore/src/light/net/buffer_flow.rs rename to ethcore/light/src/net/buffer_flow.rs diff --git a/ethcore/src/light/net/error.rs b/ethcore/light/src/net/error.rs similarity index 100% rename from ethcore/src/light/net/error.rs rename to ethcore/light/src/net/error.rs diff --git a/ethcore/src/light/net/mod.rs b/ethcore/light/src/net/mod.rs similarity index 100% rename from ethcore/src/light/net/mod.rs rename to ethcore/light/src/net/mod.rs diff --git a/ethcore/src/light/net/status.rs b/ethcore/light/src/net/status.rs similarity index 100% rename from ethcore/src/light/net/status.rs rename to ethcore/light/src/net/status.rs diff --git a/ethcore/light/src/provider.rs b/ethcore/light/src/provider.rs new file mode 100644 index 000000000..64357e540 --- /dev/null +++ b/ethcore/light/src/provider.rs @@ -0,0 +1,194 @@ +// Copyright 2015, 2016 Ethcore (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 . + +//! A provider for the LES protocol. This is typically a full node, who can +//! give as much data as necessary to its peers. + +use ethcore::blockchain_info::BlockChainInfo; +use ethcore::client::{BlockChainClient, ProvingBlockChainClient}; +use ethcore::transaction::SignedTransaction; + +use util::{Bytes, H256}; + +use light::request; + +/// Defines the operations that a provider for `LES` must fulfill. +/// +/// These are defined at [1], but may be subject to change. +/// Requests which can't be fulfilled should return either an empty RLP list +/// or empty vector where appropriate. +/// +/// [1]: https://github.com/ethcore/parity/wiki/Light-Ethereum-Subprotocol-(LES) +pub trait Provider: Send + Sync { + /// 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; + + /// Earliest block where state queries are available. + /// If `None`, no state queries are servable. + fn earliest_state(&self) -> Option; + + /// 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::Headers) -> Vec; + + /// Provide as many as possible of the requested blocks (minus the headers) encoded + /// in RLP format. + fn block_bodies(&self, req: request::Bodies) -> Vec; + + /// Provide the receipts as many as possible of the requested blocks. + /// Returns a vector of RLP-encoded lists of receipts. + fn receipts(&self, req: request::Receipts) -> Vec; + + /// Provide a set of merkle proofs, as requested. Each request is a + /// block hash and request parameters. + /// + /// Returns a vector of RLP-encoded lists satisfying the requests. + fn proofs(&self, req: request::StateProofs) -> Vec; + + /// Provide contract code for the specified (block_hash, account_hash) pairs. + /// Each item in the resulting vector is either the raw bytecode or empty. + fn contract_code(&self, req: request::ContractCodes) -> Vec; + + /// Provide header proofs from the Canonical Hash Tries. + fn header_proofs(&self, req: request::HeaderProofs) -> Vec; + + /// Provide pending transactions. + fn pending_transactions(&self) -> Vec; +} + +// Implementation of a light client data provider for a client. +impl light::Provider for T { + fn chain_info(&self) -> BlockChainInfo { + BlockChainClient::chain_info(self) + } + + fn reorg_depth(&self, a: &H256, b: &H256) -> Option { + self.tree_route(a, b).map(|route| route.index as u64) + } + + fn earliest_state(&self) -> Option { + Some(self.pruning_info().earliest_state) + } + + fn block_headers(&self, req: request::Headers) -> Vec { + let best_num = self.chain.read().best_block_number(); + let start_num = req.block_num; + + match self.block_hash(BlockID::Number(req.block_num)) { + Some(hash) if hash == req.block_hash => {} + _=> { + trace!(target: "les_provider", "unknown/non-canonical start block in header request: {:?}", (req.block_num, req.block_hash)); + return vec![] + } + } + + (0u64..req.max as u64) + .map(|x: u64| x.saturating_mul(req.skip)) + .take_while(|x| if req.reverse { x < &start_num } else { best_num - start_num < *x }) + .map(|x| if req.reverse { start_num - x } else { start_num + x }) + .map(|x| self.block_header(BlockID::Number(x))) + .take_while(|x| x.is_some()) + .flat_map(|x| x) + .collect() + } + + fn block_bodies(&self, req: request::Bodies) -> Vec { + use ids::BlockID; + + req.block_hashes.into_iter() + .map(|hash| self.block_body(BlockID::Hash(hash))) + .map(|body| body.unwrap_or_else(|| ::rlp::EMPTY_LIST_RLP.to_vec())) + .collect() + } + + fn receipts(&self, req: request::Receipts) -> Vec { + req.block_hashes.into_iter() + .map(|hash| self.block_receipts(&hash)) + .map(|receipts| receipts.unwrap_or_else(|| ::rlp::EMPTY_LIST_RLP.to_vec())) + .collect() + } + + fn proofs(&self, req: request::StateProofs) -> Vec { + use rlp::{EMPTY_LIST_RLP, RlpStream, Stream}; + + let mut results = Vec::with_capacity(req.requests.len()); + + for request in req.requests { + let state = match self.state_at(BlockID::Hash(request.block)) { + Some(state) => state, + None => { + trace!(target: "light_provider", "state for {} not available", request.block); + results.push(EMPTY_LIST_RLP.to_vec()); + continue; + } + }; + + let res = match request.key2 { + Some(storage_key) => state.prove_storage(request.key1, storage_key, request.from_level), + None => state.prove_account(request.key1, request.from_level), + }; + + match res { + Ok(records) => { + let mut stream = RlpStream::new_list(records.len()); + for record in records { + stream.append_raw(&record, 1); + } + results.push(stream.out()) + } + Err(e) => { + debug!(target: "light_provider", "encountered error {} while forming proof of state at {}", e, request.block); + results.push(EMPTY_LIST_RLP.to_vec()); + } + } + } + + results + } + + fn contract_code(&self, req: request::ContractCodes) -> Vec { + req.code_requests.into_iter() + .map(|req| { + self.state_at(BlockID::Hash(req.block_hash)) + .map(|state| { + match state.code_by_address_hash(req.account_key) { + Ok(code) => code.unwrap_or_else(Vec::new), + Err(e) => { + debug!(target: "light_provider", "encountered error {} while fetching code.", e); + Vec::new() + } + } + }).unwrap_or_else(Vec::new) + }) + .collect() + } + + fn header_proofs(&self, req: request::HeaderProofs) -> Vec { + req.requests.into_iter().map(|_| ::rlp::EMPTY_LIST_RLP.to_vec()).collect() + } + + fn pending_transactions(&self) -> Vec { + BlockChainClient::pending_transactions(self) + } +} \ No newline at end of file diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 9204d3f93..dc61cdf3c 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -52,7 +52,7 @@ use blockchain::{BlockChain, BlockProvider, TreeRoute, ImportRoute}; use client::{ BlockID, TransactionID, UncleID, TraceId, ClientConfig, BlockChainClient, MiningBlockChainClient, TraceFilter, CallAnalytics, BlockImportError, Mode, - ChainNotify, PruningInfo, + ChainNotify, PruningInfo, ProvingBlockChainClient, }; use client::Error as ClientError; use env_info::EnvInfo; @@ -68,7 +68,6 @@ use factory::Factories; use rlp::{decode, View, UntrustedRlp}; use state_db::StateDB; use rand::OsRng; -use light::{self, request}; // re-export pub use types::blockchain_info::BlockChainInfo; @@ -1378,119 +1377,24 @@ impl MayPanic for Client { } } -// Implementation of a light client data provider for a client. -impl light::Provider for Client { - fn chain_info(&self) -> BlockChainInfo { - BlockChainClient::chain_info(self) +impl ProvingBlockChainClient for Client { + fn prove_storage(&self, key1: H256, key2: H256, from_level: u32, id: BlockID) -> Vec { + self.state_at(id) + .and_then(move |state| state.prove_storage(key1, key2, from_level).ok()) + .unwrap_or_else(Vec::new) } - fn reorg_depth(&self, a: &H256, b: &H256) -> Option { - self.tree_route(a, b).map(|route| route.index as u64) + fn prove_account(&self, key1: H256, from_level: u32, id: BlockID) -> Vec { + self.state_at(id) + .and_then(move |state| state.prove_account(key1, from_level).ok()) + .unwrap_or_else(Vec::new) } - fn earliest_state(&self) -> Option { - Some(self.pruning_info().earliest_state) - } - - fn block_headers(&self, req: request::Headers) -> Vec { - let best_num = self.chain.read().best_block_number(); - let start_num = req.block_num; - - match self.block_hash(BlockID::Number(req.block_num)) { - Some(hash) if hash == req.block_hash => {} - _=> { - trace!(target: "les_provider", "unknown/non-canonical start block in header request: {:?}", (req.block_num, req.block_hash)); - return vec![] - } - } - - (0u64..req.max as u64) - .map(|x: u64| x.saturating_mul(req.skip)) - .take_while(|x| if req.reverse { x < &start_num } else { best_num - start_num < *x }) - .map(|x| if req.reverse { start_num - x } else { start_num + x }) - .map(|x| self.block_header(BlockID::Number(x))) - .take_while(|x| x.is_some()) - .flat_map(|x| x) - .collect() - } - - fn block_bodies(&self, req: request::Bodies) -> Vec { - use ids::BlockID; - - req.block_hashes.into_iter() - .map(|hash| self.block_body(BlockID::Hash(hash))) - .map(|body| body.unwrap_or_else(|| ::rlp::EMPTY_LIST_RLP.to_vec())) - .collect() - } - - fn receipts(&self, req: request::Receipts) -> Vec { - req.block_hashes.into_iter() - .map(|hash| self.block_receipts(&hash)) - .map(|receipts| receipts.unwrap_or_else(|| ::rlp::EMPTY_LIST_RLP.to_vec())) - .collect() - } - - fn proofs(&self, req: request::StateProofs) -> Vec { - use rlp::{EMPTY_LIST_RLP, RlpStream, Stream}; - - let mut results = Vec::with_capacity(req.requests.len()); - - for request in req.requests { - let state = match self.state_at(BlockID::Hash(request.block)) { - Some(state) => state, - None => { - trace!(target: "light_provider", "state for {} not available", request.block); - results.push(EMPTY_LIST_RLP.to_vec()); - continue; - } - }; - - let res = match request.key2 { - Some(storage_key) => state.prove_storage(request.key1, storage_key, request.from_level), - None => state.prove_account(request.key1, request.from_level), - }; - - match res { - Ok(records) => { - let mut stream = RlpStream::new_list(records.len()); - for record in records { - stream.append_raw(&record, 1); - } - results.push(stream.out()) - } - Err(e) => { - debug!(target: "light_provider", "encountered error {} while forming proof of state at {}", e, request.block); - results.push(EMPTY_LIST_RLP.to_vec()); - } - } - } - - results - } - - fn contract_code(&self, req: request::ContractCodes) -> Vec { - req.code_requests.into_iter() - .map(|req| { - self.state_at(BlockID::Hash(req.block_hash)) - .map(|state| { - match state.code_by_address_hash(req.account_key) { - Ok(code) => code.unwrap_or_else(Vec::new), - Err(e) => { - debug!(target: "light_provider", "encountered error {} while fetching code.", e); - Vec::new() - } - } - }).unwrap_or_else(Vec::new) - }) - .collect() - } - - fn header_proofs(&self, req: request::HeaderProofs) -> Vec { - req.requests.into_iter().map(|_| ::rlp::EMPTY_LIST_RLP.to_vec()).collect() - } - - fn pending_transactions(&self) -> Vec { - BlockChainClient::pending_transactions(self) + fn code_by_hash(&self, account_key: H256, id: BlockID) -> Bytes { + self.state_at(id) + .and_then(move |state| state.code_by_address_hash(account_key).ok()) + .and_then(|x| x) + .unwrap_or_else(Vec::new) } } diff --git a/ethcore/src/client/mod.rs b/ethcore/src/client/mod.rs index 7eafbee36..af4daece6 100644 --- a/ethcore/src/client/mod.rs +++ b/ethcore/src/client/mod.rs @@ -27,7 +27,7 @@ pub use self::config::{Mode, ClientConfig, DatabaseCompactionProfile, BlockChain pub use self::error::Error; pub use self::test_client::{TestBlockChainClient, EachBlockWith}; pub use self::chain_notify::ChainNotify; -pub use self::traits::{BlockChainClient, MiningBlockChainClient}; +pub use self::traits::{BlockChainClient, MiningBlockChainClient, ProvingBlockChainClient}; pub use types::ids::*; pub use types::trace_filter::Filter as TraceFilter; diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index d2cafa592..85f6da0e6 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -256,8 +256,10 @@ pub trait BlockChainClient : Sync + Send { fn pruning_info(&self) -> PruningInfo; } +impl IpcConfig for BlockChainClient { } + /// Extended client interface used for mining -pub trait MiningBlockChainClient : BlockChainClient { +pub trait MiningBlockChainClient: BlockChainClient { /// Returns OpenBlock prepared for closing. fn prepare_open_block(&self, author: Address, @@ -275,4 +277,23 @@ pub trait MiningBlockChainClient : BlockChainClient { fn latest_schedule(&self) -> Schedule; } -impl IpcConfig for BlockChainClient { } +/// Extended client interface for providing proofs of the state. +pub trait ProvingBlockChainClient: BlockChainClient { + /// Prove account storage at a specific block id. + /// + /// Both provided keys assume a secure trie. + /// Returns a vector of raw trie nodes (in order from the root) proving the storage query. + /// Nodes after `from_level` may be omitted. + /// An empty vector indicates unservable query. + fn prove_storage(&self, key1: H256, key2: H256, from_level: u32, id: BlockID) -> Vec; + + /// Prove account existence at a specific block id. + /// The key is the keccak hash of the account's address. + /// Returns a vector of raw trie nodes (in order from the root) proving the query. + /// Nodes after `from_level` may be omitted. + /// An empty vector indicates unservable query. + fn prove_account(&self, key1: H256, from_level: u32, id: BlockID) -> Vec; + + /// Get code by address hash. + fn code_by_hash(&self, account_key: H256, id: BlockID) -> Bytes; +} \ No newline at end of file diff --git a/ethcore/src/lib.rs b/ethcore/src/lib.rs index f5994129b..de09013ce 100644 --- a/ethcore/src/lib.rs +++ b/ethcore/src/lib.rs @@ -140,7 +140,6 @@ pub mod snapshot; pub mod action_params; pub mod db; pub mod verification; -pub mod light; #[macro_use] pub mod evm; mod cache_manager; diff --git a/ethcore/src/light/provider.rs b/ethcore/src/light/provider.rs deleted file mode 100644 index 5aa61828a..000000000 --- a/ethcore/src/light/provider.rs +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2015, 2016 Ethcore (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 . - -//! A provider for the LES protocol. This is typically a full node, who can -//! give as much data as necessary to its peers. - -use transaction::SignedTransaction; -use blockchain_info::BlockChainInfo; - -use util::{Bytes, H256}; - -use light::request; - -/// Defines the operations that a provider for `LES` must fulfill. -/// -/// These are defined at [1], but may be subject to change. -/// Requests which can't be fulfilled should return either an empty RLP list -/// or empty vector where appropriate. -/// -/// [1]: https://github.com/ethcore/parity/wiki/Light-Ethereum-Subprotocol-(LES) -pub trait Provider: Send + Sync { - /// 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; - - /// Earliest block where state queries are available. - /// If `None`, no state queries are servable. - fn earliest_state(&self) -> Option; - - /// 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::Headers) -> Vec; - - /// Provide as many as possible of the requested blocks (minus the headers) encoded - /// in RLP format. - fn block_bodies(&self, req: request::Bodies) -> Vec; - - /// Provide the receipts as many as possible of the requested blocks. - /// Returns a vector of RLP-encoded lists of receipts. - fn receipts(&self, req: request::Receipts) -> Vec; - - /// Provide a set of merkle proofs, as requested. Each request is a - /// block hash and request parameters. - /// - /// Returns a vector of RLP-encoded lists satisfying the requests. - fn proofs(&self, req: request::StateProofs) -> Vec; - - /// Provide contract code for the specified (block_hash, account_hash) pairs. - /// Each item in the resulting vector is either the raw bytecode or empty. - fn contract_code(&self, req: request::ContractCodes) -> Vec; - - /// Provide header proofs from the Canonical Hash Tries. - fn header_proofs(&self, req: request::HeaderProofs) -> Vec; - - /// Provide pending transactions. - fn pending_transactions(&self) -> Vec; -} \ No newline at end of file diff --git a/ethcore/src/types/les_request.rs b/ethcore/src/types/les_request.rs deleted file mode 100644 index d0de080ee..000000000 --- a/ethcore/src/types/les_request.rs +++ /dev/null @@ -1,167 +0,0 @@ -// Copyright 2015, 2016 Ethcore (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 . - -//! LES request types. - -use util::H256; - -/// A request for block headers. -#[derive(Debug, Clone, PartialEq, Eq, Binary)] -pub struct Headers { - /// Starting block number - pub block_num: u64, - /// Starting block hash. This and number could be combined but IPC codegen is - /// not robust enough to support it. - pub block_hash: H256, - /// The maximum amount of headers which can be returned. - pub max: usize, - /// The amount of headers to skip between each response entry. - pub skip: u64, - /// Whether the headers should proceed in falling number from the initial block. - pub reverse: bool, -} - -/// A request for specific block bodies. -#[derive(Debug, Clone, PartialEq, Eq, Binary)] -pub struct Bodies { - /// Hashes which bodies are being requested for. - pub block_hashes: Vec -} - -/// A request for transaction receipts. -/// -/// This request is answered with a list of transaction receipts for each block -/// requested. -#[derive(Debug, Clone, PartialEq, Eq, Binary)] -pub struct Receipts { - /// Block hashes to return receipts for. - pub block_hashes: Vec, -} - -/// A request for a state proof -#[derive(Debug, Clone, PartialEq, Eq, Binary)] -pub struct StateProof { - /// Block hash to query state from. - pub block: H256, - /// Key of the state trie -- corresponds to account hash. - pub key1: H256, - /// Key in that account's storage trie; if empty, then the account RLP should be - /// returned. - pub key2: Option, - /// if greater than zero, trie nodes beyond this level may be omitted. - pub from_level: u32, // could even safely be u8; trie w/ 32-byte key can be at most 64-levels deep. -} - -/// A request for state proofs. -#[derive(Debug, Clone, PartialEq, Eq, Binary)] -pub struct StateProofs { - /// All the proof requests. - pub requests: Vec, -} - -/// A request for contract code. -#[derive(Debug, Clone, PartialEq, Eq, Binary)] -pub struct ContractCode { - /// Block hash - pub block_hash: H256, - /// Account key (== sha3(address)) - pub account_key: H256, -} - -/// A request for contract code. -#[derive(Debug, Clone, PartialEq, Eq, Binary)] -pub struct ContractCodes { - /// Block hash and account key (== sha3(address)) pairs to fetch code for. - pub code_requests: Vec, -} - -/// A request for a header proof from the Canonical Hash Trie. -#[derive(Debug, Clone, PartialEq, Eq, Binary)] -pub struct HeaderProof { - /// Number of the CHT. - pub cht_number: u64, - /// Block number requested. - pub block_number: u64, - /// If greater than zero, trie nodes beyond this level may be omitted. - pub from_level: u32, -} - -/// A request for header proofs from the CHT. -#[derive(Debug, Clone, PartialEq, Eq, Binary)] -pub struct HeaderProofs { - /// All the proof requests. - pub requests: Vec, -} - -/// Kinds of requests. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Binary)] -pub enum Kind { - /// Requesting headers. - Headers, - /// Requesting block bodies. - Bodies, - /// Requesting transaction receipts. - Receipts, - /// Requesting proofs of state trie nodes. - StateProofs, - /// Requesting contract code by hash. - Codes, - /// Requesting header proofs (from the CHT). - HeaderProofs, -} - -/// Encompasses all possible types of requests in a single structure. -#[derive(Debug, Clone, PartialEq, Eq, Binary)] -pub enum Request { - /// Requesting headers. - Headers(Headers), - /// Requesting block bodies. - Bodies(Bodies), - /// Requesting transaction receipts. - Receipts(Receipts), - /// Requesting state proofs. - StateProofs(StateProofs), - /// Requesting contract codes. - Codes(ContractCodes), - /// Requesting header proofs. - HeaderProofs(HeaderProofs), -} - -impl Request { - /// Get the kind of request this is. - pub fn kind(&self) -> Kind { - match *self { - Request::Headers(_) => Kind::Headers, - Request::Bodies(_) => Kind::Bodies, - Request::Receipts(_) => Kind::Receipts, - Request::StateProofs(_) => Kind::StateProofs, - Request::Codes(_) => Kind::Codes, - Request::HeaderProofs(_) => Kind::HeaderProofs, - } - } - - /// Get the amount of requests being made. - pub fn amount(&self) -> usize { - match *self { - Request::Headers(ref req) => req.max, - Request::Bodies(ref req) => req.block_hashes.len(), - Request::Receipts(ref req) => req.block_hashes.len(), - Request::StateProofs(ref req) => req.requests.len(), - Request::Codes(ref req) => req.code_requests.len(), - Request::HeaderProofs(ref req) => req.requests.len(), - } - } -} \ No newline at end of file diff --git a/ethcore/src/types/mod.rs.in b/ethcore/src/types/mod.rs.in index 4f996173e..d462d3cb9 100644 --- a/ethcore/src/types/mod.rs.in +++ b/ethcore/src/types/mod.rs.in @@ -34,5 +34,4 @@ pub mod block_import_error; pub mod restoration_status; pub mod snapshot_manifest; pub mod mode; -pub mod pruning_info; -pub mod les_request; +pub mod pruning_info; \ No newline at end of file From a6c2408562ef6da7abfaa6f2d5ec5dccd7e6349f Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Mon, 5 Dec 2016 16:56:21 +0100 Subject: [PATCH 36/37] IPC codegen in ethcore-light; remove network dependency --- ethcore/Cargo.toml | 1 - ethcore/light/Cargo.toml | 20 +++ ethcore/light/build.rs | 21 ++++ ethcore/light/src/types/les_request.rs | 167 +++++++++++++++++++++++++ ethcore/light/src/types/mod.rs | 20 +++ ethcore/light/src/types/mod.rs.in | 17 +++ ethcore/src/lib.rs | 1 - 7 files changed, 245 insertions(+), 2 deletions(-) create mode 100644 ethcore/light/Cargo.toml create mode 100644 ethcore/light/build.rs create mode 100644 ethcore/light/src/types/les_request.rs create mode 100644 ethcore/light/src/types/mod.rs create mode 100644 ethcore/light/src/types/mod.rs.in diff --git a/ethcore/Cargo.toml b/ethcore/Cargo.toml index 713cc1045..bd87c422f 100644 --- a/ethcore/Cargo.toml +++ b/ethcore/Cargo.toml @@ -42,7 +42,6 @@ ethcore-ipc-nano = { path = "../ipc/nano" } rlp = { path = "../util/rlp" } lru-cache = "0.1.0" ethcore-bloom-journal = { path = "../util/bloom" } -ethcore-network = { path = "../util/network" } [dependencies.hyper] git = "https://github.com/ethcore/hyper" diff --git a/ethcore/light/Cargo.toml b/ethcore/light/Cargo.toml new file mode 100644 index 000000000..3aca67c64 --- /dev/null +++ b/ethcore/light/Cargo.toml @@ -0,0 +1,20 @@ +[package] +description = "Parity LES primitives" +homepage = "https://ethcore.io" +license = "GPL-3.0" +name = "ethcore-light" +version = "1.5.0" +authors = ["Ethcore "] +build = "build.rs" + +[build-dependencies] +"ethcore-ipc-codegen" = { path = "../../ipc/codegen" } + +[dependencies] +log = "0.3" +ethcore = { path = ".." } +ethcore-util = { path = "../../util" } +ethcore-network = { path = "../../util/network" } +ethcore-io = { path = "../../util/io" } +rlp = { path = "../../util/rlp" } +time = "0.1" \ No newline at end of file diff --git a/ethcore/light/build.rs b/ethcore/light/build.rs new file mode 100644 index 000000000..cff92a011 --- /dev/null +++ b/ethcore/light/build.rs @@ -0,0 +1,21 @@ +// Copyright 2015, 2016 Ethcore (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 . + +extern crate ethcore_ipc_codegen; + +fn main() { + ethcore_ipc_codegen::derive_binary("src/types/mod.rs.in").unwrap(); +} diff --git a/ethcore/light/src/types/les_request.rs b/ethcore/light/src/types/les_request.rs new file mode 100644 index 000000000..d0de080ee --- /dev/null +++ b/ethcore/light/src/types/les_request.rs @@ -0,0 +1,167 @@ +// Copyright 2015, 2016 Ethcore (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 . + +//! LES request types. + +use util::H256; + +/// A request for block headers. +#[derive(Debug, Clone, PartialEq, Eq, Binary)] +pub struct Headers { + /// Starting block number + pub block_num: u64, + /// Starting block hash. This and number could be combined but IPC codegen is + /// not robust enough to support it. + pub block_hash: H256, + /// The maximum amount of headers which can be returned. + pub max: usize, + /// The amount of headers to skip between each response entry. + pub skip: u64, + /// Whether the headers should proceed in falling number from the initial block. + pub reverse: bool, +} + +/// A request for specific block bodies. +#[derive(Debug, Clone, PartialEq, Eq, Binary)] +pub struct Bodies { + /// Hashes which bodies are being requested for. + pub block_hashes: Vec +} + +/// A request for transaction receipts. +/// +/// This request is answered with a list of transaction receipts for each block +/// requested. +#[derive(Debug, Clone, PartialEq, Eq, Binary)] +pub struct Receipts { + /// Block hashes to return receipts for. + pub block_hashes: Vec, +} + +/// A request for a state proof +#[derive(Debug, Clone, PartialEq, Eq, Binary)] +pub struct StateProof { + /// Block hash to query state from. + pub block: H256, + /// Key of the state trie -- corresponds to account hash. + pub key1: H256, + /// Key in that account's storage trie; if empty, then the account RLP should be + /// returned. + pub key2: Option, + /// if greater than zero, trie nodes beyond this level may be omitted. + pub from_level: u32, // could even safely be u8; trie w/ 32-byte key can be at most 64-levels deep. +} + +/// A request for state proofs. +#[derive(Debug, Clone, PartialEq, Eq, Binary)] +pub struct StateProofs { + /// All the proof requests. + pub requests: Vec, +} + +/// A request for contract code. +#[derive(Debug, Clone, PartialEq, Eq, Binary)] +pub struct ContractCode { + /// Block hash + pub block_hash: H256, + /// Account key (== sha3(address)) + pub account_key: H256, +} + +/// A request for contract code. +#[derive(Debug, Clone, PartialEq, Eq, Binary)] +pub struct ContractCodes { + /// Block hash and account key (== sha3(address)) pairs to fetch code for. + pub code_requests: Vec, +} + +/// A request for a header proof from the Canonical Hash Trie. +#[derive(Debug, Clone, PartialEq, Eq, Binary)] +pub struct HeaderProof { + /// Number of the CHT. + pub cht_number: u64, + /// Block number requested. + pub block_number: u64, + /// If greater than zero, trie nodes beyond this level may be omitted. + pub from_level: u32, +} + +/// A request for header proofs from the CHT. +#[derive(Debug, Clone, PartialEq, Eq, Binary)] +pub struct HeaderProofs { + /// All the proof requests. + pub requests: Vec, +} + +/// Kinds of requests. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Binary)] +pub enum Kind { + /// Requesting headers. + Headers, + /// Requesting block bodies. + Bodies, + /// Requesting transaction receipts. + Receipts, + /// Requesting proofs of state trie nodes. + StateProofs, + /// Requesting contract code by hash. + Codes, + /// Requesting header proofs (from the CHT). + HeaderProofs, +} + +/// Encompasses all possible types of requests in a single structure. +#[derive(Debug, Clone, PartialEq, Eq, Binary)] +pub enum Request { + /// Requesting headers. + Headers(Headers), + /// Requesting block bodies. + Bodies(Bodies), + /// Requesting transaction receipts. + Receipts(Receipts), + /// Requesting state proofs. + StateProofs(StateProofs), + /// Requesting contract codes. + Codes(ContractCodes), + /// Requesting header proofs. + HeaderProofs(HeaderProofs), +} + +impl Request { + /// Get the kind of request this is. + pub fn kind(&self) -> Kind { + match *self { + Request::Headers(_) => Kind::Headers, + Request::Bodies(_) => Kind::Bodies, + Request::Receipts(_) => Kind::Receipts, + Request::StateProofs(_) => Kind::StateProofs, + Request::Codes(_) => Kind::Codes, + Request::HeaderProofs(_) => Kind::HeaderProofs, + } + } + + /// Get the amount of requests being made. + pub fn amount(&self) -> usize { + match *self { + Request::Headers(ref req) => req.max, + Request::Bodies(ref req) => req.block_hashes.len(), + Request::Receipts(ref req) => req.block_hashes.len(), + Request::StateProofs(ref req) => req.requests.len(), + Request::Codes(ref req) => req.code_requests.len(), + Request::HeaderProofs(ref req) => req.requests.len(), + } + } +} \ No newline at end of file diff --git a/ethcore/light/src/types/mod.rs b/ethcore/light/src/types/mod.rs new file mode 100644 index 000000000..2625358a3 --- /dev/null +++ b/ethcore/light/src/types/mod.rs @@ -0,0 +1,20 @@ +// Copyright 2015, 2016 Ethcore (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 . + +//! Types used in the public (IPC) api which require custom code generation. + +#![allow(dead_code, unused_assignments, unused_variables)] // codegen issues +include!(concat!(env!("OUT_DIR"), "/mod.rs.in")); diff --git a/ethcore/light/src/types/mod.rs.in b/ethcore/light/src/types/mod.rs.in new file mode 100644 index 000000000..2b7386b5b --- /dev/null +++ b/ethcore/light/src/types/mod.rs.in @@ -0,0 +1,17 @@ +// Copyright 2015, 2016 Ethcore (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 . + +pub mod les_request; \ No newline at end of file diff --git a/ethcore/src/lib.rs b/ethcore/src/lib.rs index de09013ce..ca343b1a7 100644 --- a/ethcore/src/lib.rs +++ b/ethcore/src/lib.rs @@ -102,7 +102,6 @@ extern crate rlp; extern crate ethcore_bloom_journal as bloom_journal; extern crate byteorder; extern crate transient_hashmap; -extern crate ethcore_network as network; extern crate linked_hash_map; #[macro_use] From 5db93cd4337f47527bb6d0bbd8df1bd167dee22f Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Mon, 5 Dec 2016 17:09:05 +0100 Subject: [PATCH 37/37] light: fix compile errors --- ethcore/light/Cargo.toml | 3 +- ethcore/light/src/client.rs | 20 +++++------ ethcore/light/src/lib.rs | 1 + ethcore/light/src/net/buffer_flow.rs | 2 +- ethcore/light/src/net/mod.rs | 6 ++-- ethcore/light/src/provider.rs | 54 ++++++++-------------------- 6 files changed, 31 insertions(+), 55 deletions(-) diff --git a/ethcore/light/Cargo.toml b/ethcore/light/Cargo.toml index 3aca67c64..74400d7ab 100644 --- a/ethcore/light/Cargo.toml +++ b/ethcore/light/Cargo.toml @@ -16,5 +16,6 @@ ethcore = { path = ".." } ethcore-util = { path = "../../util" } ethcore-network = { path = "../../util/network" } ethcore-io = { path = "../../util/io" } +ethcore-ipc = { path = "../../ipc/rpc" } rlp = { path = "../../util/rlp" } -time = "0.1" \ No newline at end of file +time = "0.1" \ No newline at end of file diff --git a/ethcore/light/src/client.rs b/ethcore/light/src/client.rs index 699db743a..8a5c43c48 100644 --- a/ethcore/light/src/client.rs +++ b/ethcore/light/src/client.rs @@ -19,21 +19,21 @@ use std::sync::Arc; -use engines::Engine; -use ids::BlockID; -use service::ClientIoMessage; -use block_import_error::BlockImportError; -use block_status::BlockStatus; -use verification::queue::{HeaderQueue, QueueInfo}; -use transaction::SignedTransaction; -use blockchain_info::BlockChainInfo; +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; +use ethcore::blockchain_info::BlockChainInfo; use io::IoChannel; use util::hash::H256; use util::{Bytes, Mutex}; -use light::provider::Provider; -use light::request; +use provider::Provider; +use request; /// Light client implementation. pub struct Client { diff --git a/ethcore/light/src/lib.rs b/ethcore/light/src/lib.rs index e698905e8..e150f4ee5 100644 --- a/ethcore/light/src/lib.rs +++ b/ethcore/light/src/lib.rs @@ -47,5 +47,6 @@ extern crate ethcore; extern crate ethcore_util as util; extern crate ethcore_network as network; extern crate ethcore_io as io; +extern crate ethcore_ipc as ipc; extern crate rlp; extern crate time; \ No newline at end of file diff --git a/ethcore/light/src/net/buffer_flow.rs b/ethcore/light/src/net/buffer_flow.rs index 7f653f826..6730c71a7 100644 --- a/ethcore/light/src/net/buffer_flow.rs +++ b/ethcore/light/src/net/buffer_flow.rs @@ -23,7 +23,7 @@ //! This module provides an interface for configuration of buffer //! flow costs and recharge rates. -use light::request; +use request; use super::packet; use super::error::Error; diff --git a/ethcore/light/src/net/mod.rs b/ethcore/light/src/net/mod.rs index 2d38d4fd5..a1b3b30b0 100644 --- a/ethcore/light/src/net/mod.rs +++ b/ethcore/light/src/net/mod.rs @@ -19,6 +19,7 @@ //! This uses a "Provider" to answer requests. //! See https://github.com/ethcore/parity/wiki/Light-Ethereum-Subprotocol-(LES) +use ethcore::transaction::SignedTransaction; use io::TimerToken; use network::{NetworkProtocolHandler, NetworkContext, NetworkError, PeerId}; use rlp::{RlpStream, Stream, UntrustedRlp, View}; @@ -29,9 +30,8 @@ use time::SteadyTime; use std::collections::{HashMap, HashSet}; use std::sync::atomic::{AtomicUsize, Ordering}; -use light::provider::Provider; -use light::request::{self, Request}; -use transaction::SignedTransaction; +use provider::Provider; +use request::{self, Request}; use self::buffer_flow::{Buffer, FlowParams}; use self::error::{Error, Punishment}; diff --git a/ethcore/light/src/provider.rs b/ethcore/light/src/provider.rs index 64357e540..264df0397 100644 --- a/ethcore/light/src/provider.rs +++ b/ethcore/light/src/provider.rs @@ -20,10 +20,11 @@ use ethcore::blockchain_info::BlockChainInfo; use ethcore::client::{BlockChainClient, ProvingBlockChainClient}; use ethcore::transaction::SignedTransaction; +use ethcore::ids::BlockID; use util::{Bytes, H256}; -use light::request; +use request; /// Defines the operations that a provider for `LES` must fulfill. /// @@ -78,7 +79,7 @@ pub trait Provider: Send + Sync { } // Implementation of a light client data provider for a client. -impl light::Provider for T { +impl Provider for T { fn chain_info(&self) -> BlockChainInfo { BlockChainClient::chain_info(self) } @@ -92,7 +93,7 @@ impl light::Provider for T { } fn block_headers(&self, req: request::Headers) -> Vec { - let best_num = self.chain.read().best_block_number(); + let best_num = self.chain_info().best_block_number; let start_num = req.block_num; match self.block_hash(BlockID::Number(req.block_num)) { @@ -114,8 +115,6 @@ impl light::Provider for T { } fn block_bodies(&self, req: request::Bodies) -> Vec { - use ids::BlockID; - req.block_hashes.into_iter() .map(|hash| self.block_body(BlockID::Hash(hash))) .map(|body| body.unwrap_or_else(|| ::rlp::EMPTY_LIST_RLP.to_vec())) @@ -130,38 +129,22 @@ impl light::Provider for T { } fn proofs(&self, req: request::StateProofs) -> Vec { - use rlp::{EMPTY_LIST_RLP, RlpStream, Stream}; + use rlp::{RlpStream, Stream}; let mut results = Vec::with_capacity(req.requests.len()); for request in req.requests { - let state = match self.state_at(BlockID::Hash(request.block)) { - Some(state) => state, - None => { - trace!(target: "light_provider", "state for {} not available", request.block); - results.push(EMPTY_LIST_RLP.to_vec()); - continue; - } + let proof = match request.key2 { + Some(key2) => self.prove_storage(request.key1, key2, request.from_level, BlockID::Hash(request.block)), + None => self.prove_account(request.key1, request.from_level, BlockID::Hash(request.block)), }; - let res = match request.key2 { - Some(storage_key) => state.prove_storage(request.key1, storage_key, request.from_level), - None => state.prove_account(request.key1, request.from_level), - }; - - match res { - Ok(records) => { - let mut stream = RlpStream::new_list(records.len()); - for record in records { - stream.append_raw(&record, 1); - } - results.push(stream.out()) - } - Err(e) => { - debug!(target: "light_provider", "encountered error {} while forming proof of state at {}", e, request.block); - results.push(EMPTY_LIST_RLP.to_vec()); - } + let mut stream = RlpStream::new_list(proof.len()); + for node in proof { + stream.append_raw(&node, 1); } + + results.push(stream.out()); } results @@ -170,16 +153,7 @@ impl light::Provider for T { fn contract_code(&self, req: request::ContractCodes) -> Vec { req.code_requests.into_iter() .map(|req| { - self.state_at(BlockID::Hash(req.block_hash)) - .map(|state| { - match state.code_by_address_hash(req.account_key) { - Ok(code) => code.unwrap_or_else(Vec::new), - Err(e) => { - debug!(target: "light_provider", "encountered error {} while fetching code.", e); - Vec::new() - } - } - }).unwrap_or_else(Vec::new) + self.code_by_hash(req.account_key, BlockID::Hash(req.block_hash)) }) .collect() }