Merge pull request #3527 from ethcore/client-provider

LES Part 2
This commit is contained in:
Robert Habermeier 2016-12-07 09:31:50 +01:00 committed by GitHub
commit 085b8ad553
23 changed files with 962 additions and 142 deletions

1
Cargo.lock generated
View File

@ -298,6 +298,7 @@ dependencies = [
"ethcore-ipc 1.4.0", "ethcore-ipc 1.4.0",
"ethcore-ipc-codegen 1.4.0", "ethcore-ipc-codegen 1.4.0",
"ethcore-ipc-nano 1.4.0", "ethcore-ipc-nano 1.4.0",
"ethcore-network 1.5.0",
"ethcore-util 1.5.0", "ethcore-util 1.5.0",
"ethjson 0.1.0", "ethjson 0.1.0",
"ethkey 0.2.0", "ethkey 0.2.0",

View File

@ -5,6 +5,10 @@ license = "GPL-3.0"
name = "ethcore-light" name = "ethcore-light"
version = "1.5.0" version = "1.5.0"
authors = ["Ethcore <admin@ethcore.io>"] authors = ["Ethcore <admin@ethcore.io>"]
build = "build.rs"
[build-dependencies]
"ethcore-ipc-codegen" = { path = "../../ipc/codegen" }
[dependencies] [dependencies]
log = "0.3" log = "0.3"
@ -12,5 +16,6 @@ ethcore = { path = ".." }
ethcore-util = { path = "../../util" } ethcore-util = { path = "../../util" }
ethcore-network = { path = "../../util/network" } ethcore-network = { path = "../../util/network" }
ethcore-io = { path = "../../util/io" } ethcore-io = { path = "../../util/io" }
ethcore-ipc = { path = "../../ipc/rpc" }
rlp = { path = "../../util/rlp" } rlp = { path = "../../util/rlp" }
time = "0.1" time = "0.1"

21
ethcore/light/build.rs Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
extern crate ethcore_ipc_codegen;
fn main() {
ethcore_ipc_codegen::derive_binary("src/types/mod.rs.in").unwrap();
}

View File

@ -101,7 +101,7 @@ impl Provider for Client {
Vec::new() Vec::new()
} }
fn code(&self, _req: request::ContractCodes) -> Vec<Bytes> { fn contract_code(&self, _req: request::ContractCodes) -> Vec<Bytes> {
Vec::new() Vec::new()
} }

View File

@ -28,20 +28,25 @@
//! It starts by performing a header-only sync, verifying random samples //! It starts by performing a header-only sync, verifying random samples
//! of members of the chain to varying degrees. //! 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)] #![allow(dead_code)]
pub mod client; pub mod client;
pub mod net; pub mod net;
pub mod provider; pub mod provider;
pub mod request;
mod types;
pub use self::provider::Provider;
pub use types::les_request as request;
#[macro_use]
extern crate log;
extern crate ethcore;
extern crate ethcore_util as util; extern crate ethcore_util as util;
extern crate ethcore_network as network; extern crate ethcore_network as network;
extern crate ethcore_io as io; extern crate ethcore_io as io;
extern crate ethcore; extern crate ethcore_ipc as ipc;
extern crate rlp; extern crate rlp;
extern crate time; extern crate time;
#[macro_use]
extern crate log;

View File

@ -206,6 +206,39 @@ impl FlowParams {
cost.0 + (amount * cost.1) 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. /// Create initial buffer parameter.
pub fn create_buffer(&self) -> Buffer { pub fn create_buffer(&self) -> Buffer {
Buffer { Buffer {
@ -228,6 +261,16 @@ impl FlowParams {
buf.estimate = ::std::cmp::min(self.limit, buf.estimate + (elapsed * self.recharge)); 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)] #[cfg(test)]

View File

@ -52,6 +52,8 @@ pub enum Error {
UnexpectedHandshake, UnexpectedHandshake,
/// Peer on wrong network (wrong NetworkId or genesis hash) /// Peer on wrong network (wrong NetworkId or genesis hash)
WrongNetwork, WrongNetwork,
/// Unknown peer.
UnknownPeer,
} }
impl Error { impl Error {
@ -64,6 +66,7 @@ impl Error {
Error::UnrecognizedPacket(_) => Punishment::Disconnect, Error::UnrecognizedPacket(_) => Punishment::Disconnect,
Error::UnexpectedHandshake => Punishment::Disconnect, Error::UnexpectedHandshake => Punishment::Disconnect,
Error::WrongNetwork => Punishment::Disable, 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::UnrecognizedPacket(code) => write!(f, "Unrecognized packet: 0x{:x}", code),
Error::UnexpectedHandshake => write!(f, "Unexpected handshake"), Error::UnexpectedHandshake => write!(f, "Unexpected handshake"),
Error::WrongNetwork => write!(f, "Wrong network"), Error::WrongNetwork => write!(f, "Wrong network"),
Error::UnknownPeer => write!(f, "unknown peer"),
} }
} }
} }

View File

@ -19,27 +19,28 @@
//! This uses a "Provider" to answer requests. //! This uses a "Provider" to answer requests.
//! See https://github.com/ethcore/parity/wiki/Light-Ethereum-Subprotocol-(LES) //! See https://github.com/ethcore/parity/wiki/Light-Ethereum-Subprotocol-(LES)
use ethcore::transaction::SignedTransaction;
use io::TimerToken; use io::TimerToken;
use network::{NetworkProtocolHandler, NetworkContext, NetworkError, PeerId}; use network::{NetworkProtocolHandler, NetworkContext, NetworkError, PeerId};
use rlp::{RlpStream, Stream, UntrustedRlp, View}; use rlp::{RlpStream, Stream, UntrustedRlp, View};
use util::hash::H256; use util::hash::H256;
use util::RwLock; use util::{Mutex, RwLock, U256};
use time::SteadyTime;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::sync::atomic::AtomicUsize; use std::sync::atomic::{AtomicUsize, Ordering};
use provider::Provider; use provider::Provider;
use request::{self, Request}; use request::{self, Request};
use self::buffer_flow::{Buffer, FlowParams}; use self::buffer_flow::{Buffer, FlowParams};
use self::error::{Error, Punishment}; use self::error::{Error, Punishment};
use self::status::{Status, Capabilities};
mod buffer_flow; mod buffer_flow;
mod error; mod error;
mod status; mod status;
pub use self::status::Announcement; pub use self::status::{Status, Capabilities, Announcement, NetworkId};
const TIMEOUT: TimerToken = 0; const TIMEOUT: TimerToken = 0;
const TIMEOUT_INTERVAL_MS: u64 = 1000; const TIMEOUT_INTERVAL_MS: u64 = 1000;
@ -86,6 +87,10 @@ mod packet {
pub const HEADER_PROOFS: u8 = 0x0e; 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 // A pending peer: one we've sent our status to but
// may not have received one for. // may not have received one for.
struct PendingPeer { struct PendingPeer {
@ -103,32 +108,162 @@ struct Peer {
sent_head: H256, // last head we've given them. 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(&mut self, flow_params: &FlowParams, kind: request::Kind, max: usize) -> Result<U256, Error> {
flow_params.recharge(&mut self.local_buffer);
let max_cost = flow_params.compute_cost(kind, max);
try!(self.local_buffer.deduct_cost(max_cost));
Ok(max_cost)
}
// refund buffer for a request. returns new buffer amount.
fn refund(&mut self, flow_params: &FlowParams, amount: U256) -> U256 {
flow_params.refund(&mut self.local_buffer, amount);
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);
}
}
/// 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]) { }
}
// 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 /// This is an implementation of the light ethereum network protocol, abstracted
/// over a `Provider` of data and a p2p network. /// over a `Provider` of data and a p2p network.
/// ///
/// This is simply designed for request-response purposes. Higher level uses /// This is simply designed for request-response purposes. Higher level uses
/// of the protocol, such as synchronization, will function as wrappers around /// of the protocol, such as synchronization, will function as wrappers around
/// this system. /// 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 { pub struct LightProtocol {
provider: Box<Provider>, provider: Box<Provider>,
genesis_hash: H256, genesis_hash: H256,
network_id: status::NetworkId, network_id: NetworkId,
pending_peers: RwLock<HashMap<PeerId, PendingPeer>>, pending_peers: RwLock<HashMap<PeerId, PendingPeer>>,
peers: RwLock<HashMap<PeerId, Peer>>, peers: RwLock<HashMap<PeerId, Mutex<Peer>>>,
pending_requests: RwLock<HashMap<usize, Request>>, pending_requests: RwLock<HashMap<usize, Requested>>,
capabilities: RwLock<Capabilities>, capabilities: RwLock<Capabilities>,
flow_params: FlowParams, // assumed static and same for every peer. flow_params: FlowParams, // assumed static and same for every peer.
handlers: Vec<Box<Handler>>,
req_id: AtomicUsize, req_id: AtomicUsize,
} }
impl LightProtocol { impl LightProtocol {
/// Create a new instance of the protocol manager.
pub fn new(provider: Box<Provider>, 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<usize> {
self.peers.read().get(&peer).map(|peer| {
let mut peer = peer.lock();
peer.recharge_remote();
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<ReqId, Error> {
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));
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. /// Make an announcement of new chain head and capabilities to all peers.
/// The announcement is expected to be valid. /// 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(); let mut reorgs_map = HashMap::new();
// update stored capabilities
self.capabilities.write().update_from(&announcement);
// calculate reorg info and send packets // 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) let reorg_depth = reorgs_map.entry(peer_info.sent_head)
.or_insert_with(|| { .or_insert_with(|| {
match self.provider.reorg_depth(&announcement.head_hash, &peer_info.sent_head) { match self.provider.reorg_depth(&announcement.head_hash, &peer_info.sent_head) {
@ -151,6 +286,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<Handler>) {
self.handlers.push(handler);
}
} }
impl LightProtocol { impl LightProtocol {
@ -173,7 +316,11 @@ impl LightProtocol {
fn on_disconnect(&self, peer: PeerId) { fn on_disconnect(&self, peer: PeerId) {
// TODO: reassign all requests assigned to this peer. // TODO: reassign all requests assigned to this peer.
self.pending_peers.write().remove(&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. // send status to a peer.
@ -219,15 +366,19 @@ impl LightProtocol {
return Err(Error::WrongNetwork); return Err(Error::WrongNetwork);
} }
self.peers.write().insert(*peer, Peer { self.peers.write().insert(*peer, Mutex::new(Peer {
local_buffer: self.flow_params.create_buffer(), local_buffer: self.flow_params.create_buffer(),
remote_buffer: flow_params.create_buffer(), remote_buffer: flow_params.create_buffer(),
current_asking: HashSet::new(), current_asking: HashSet::new(),
status: status, status: status.clone(),
capabilities: capabilities, capabilities: capabilities.clone(),
remote_flow: flow_params, remote_flow: flow_params,
sent_head: pending.sent_head, sent_head: pending.sent_head,
}); }));
for handler in &self.handlers {
handler.on_connect(*peer, &status, &capabilities)
}
Ok(()) Ok(())
} }
@ -240,13 +391,15 @@ impl LightProtocol {
} }
let announcement = try!(status::parse_announcement(data)); 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, Some(info) => info,
None => return Ok(()), None => return Ok(()),
}; };
let mut peer_info = peer_info.lock();
// update status. // update status.
{ {
// TODO: punish peer if they've moved backwards. // TODO: punish peer if they've moved backwards.
@ -259,15 +412,11 @@ impl LightProtocol {
} }
// update capabilities. // update capabilities.
{ peer_info.capabilities.update_from(&announcement);
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. for handler in &self.handlers {
handler.on_announcement(*peer, &announcement);
}
Ok(()) Ok(())
} }
@ -276,45 +425,39 @@ impl LightProtocol {
fn get_block_headers(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { fn get_block_headers(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> {
const MAX_HEADERS: usize = 512; const MAX_HEADERS: usize = 512;
let mut present_buffer = match self.peers.read().get(peer) { let peers = self.peers.read();
Some(peer) => peer.local_buffer.clone(), let peer = match peers.get(peer) {
Some(peer) => peer,
None => { None => {
debug!(target: "les", "Ignoring announcement from unknown peer"); debug!(target: "les", "Ignoring request from unknown peer");
return Ok(()) return Ok(())
} }
}; };
self.flow_params.recharge(&mut present_buffer); let mut peer = peer.lock();
let req_id: u64 = try!(data.val_at(0)); 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 { let req = request::Headers {
block: { block_num: block.0,
let rlp = try!(data.at(1)); block_hash: block.1,
(try!(rlp.val_at(0)), try!(rlp.val_at(1)))
},
max: ::std::cmp::min(MAX_HEADERS, try!(data.val_at(2))), max: ::std::cmp::min(MAX_HEADERS, try!(data.val_at(2))),
skip: try!(data.val_at(3)), skip: try!(data.val_at(3)),
reverse: try!(data.val_at(4)), reverse: try!(data.val_at(4)),
}; };
let max_cost = self.flow_params.compute_cost(request::Kind::Headers, req.max); let max_cost = try!(peer.deduct_max(&self.flow_params, request::Kind::Headers, req.max));
try!(present_buffer.deduct_cost(max_cost));
let response = self.provider.block_headers(req); let response = self.provider.block_headers(req);
let actual_cost = self.flow_params.compute_cost(request::Kind::Headers, response.len()); 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) { let cur_buffer = peer.refund(&self.flow_params, max_cost - actual_cost);
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, { io.respond(packet::BLOCK_HEADERS, {
let mut stream = RlpStream::new_list(response.len() + 2); let mut stream = RlpStream::new_list(response.len() + 2);
stream.append(&req_id).append(&cur_buffer); stream.append(&req_id).append(&cur_buffer);
@ -336,39 +479,30 @@ impl LightProtocol {
fn get_block_bodies(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> { fn get_block_bodies(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> {
const MAX_BODIES: usize = 256; const MAX_BODIES: usize = 256;
let mut present_buffer = match self.peers.read().get(peer) { let peers = self.peers.read();
Some(peer) => peer.local_buffer.clone(), let peer = match peers.get(peer) {
Some(peer) => peer,
None => { None => {
debug!(target: "les", "Ignoring announcement from unknown peer"); debug!(target: "les", "Ignoring request from unknown peer");
return Ok(()) return Ok(())
} }
}; };
let mut peer = peer.lock();
self.flow_params.recharge(&mut present_buffer);
let req_id: u64 = try!(data.val_at(0)); let req_id: u64 = try!(data.val_at(0));
let req = request::Bodies { let req = request::Bodies {
block_hashes: try!(data.iter().skip(1).take(MAX_BODIES).map(|x| x.as_val()).collect()) 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()); let max_cost = try!(peer.deduct_max(&self.flow_params, request::Kind::Bodies, req.block_hashes.len()));
try!(present_buffer.deduct_cost(max_cost));
let response = self.provider.block_bodies(req); let response = self.provider.block_bodies(req);
let response_len = response.iter().filter(|x| &x[..] != &::rlp::EMPTY_LIST_RLP).count(); 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 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) { let cur_buffer = peer.refund(&self.flow_params, max_cost - actual_cost);
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, { io.respond(packet::BLOCK_BODIES, {
let mut stream = RlpStream::new_list(response.len() + 2); let mut stream = RlpStream::new_list(response.len() + 2);
@ -388,8 +522,44 @@ impl LightProtocol {
} }
// Handle a request for receipts. // Handle a request for receipts.
fn get_receipts(&self, _: &PeerId, _: &NetworkContext, _: UntrustedRlp) -> Result<(), Error> { fn get_receipts(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> {
unimplemented!() 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 mut peer = peer.lock();
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. // Receive a response for receipts.
@ -398,8 +568,55 @@ impl LightProtocol {
} }
// Handle a request for proofs. // Handle a request for proofs.
fn get_proofs(&self, _: &PeerId, _: &NetworkContext, _: UntrustedRlp) -> Result<(), Error> { fn get_proofs(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> {
unimplemented!() 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 mut peer = peer.lock();
let req_id: u64 = try!(data.val_at(0));
let req = {
let requests: Result<Vec<_>, 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. // Receive a response for proofs.
@ -408,8 +625,53 @@ impl LightProtocol {
} }
// Handle a request for contract code. // Handle a request for contract code.
fn get_contract_code(&self, _: &PeerId, _: &NetworkContext, _: UntrustedRlp) -> Result<(), Error> { fn get_contract_code(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> {
unimplemented!() 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 mut peer = peer.lock();
let req_id: u64 = try!(data.val_at(0));
let req = {
let requests: Result<Vec<_>, 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. // Receive a response for contract code.
@ -418,8 +680,54 @@ impl LightProtocol {
} }
// Handle a request for header proofs // Handle a request for header proofs
fn get_header_proofs(&self, _: &PeerId, _: &NetworkContext, _: UntrustedRlp) -> Result<(), Error> { fn get_header_proofs(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) -> Result<(), Error> {
unimplemented!() 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 mut peer = peer.lock();
let req_id: u64 = try!(data.val_at(0));
let req = {
let requests: Result<Vec<_>, 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 // Receive a response for header proofs
@ -428,8 +736,18 @@ impl LightProtocol {
} }
// Receive a set of transactions to relay. // Receive a set of transactions to relay.
fn relay_transactions(&self, _: &PeerId, _: &NetworkContext, _: UntrustedRlp) -> Result<(), Error> { fn relay_transactions(&self, peer: &PeerId, data: UntrustedRlp) -> Result<(), Error> {
unimplemented!() const MAX_TRANSACTIONS: usize = 256;
let txs: Vec<_> = try!(data.iter().take(MAX_TRANSACTIONS).map(|x| x.as_val::<SignedTransaction>()).collect());
debug!(target: "les", "Received {} transactions to relay from peer {}", txs.len(), peer);
for handler in &self.handlers {
handler.on_transactions(*peer, &txs);
}
Ok(())
} }
} }
@ -464,7 +782,7 @@ impl NetworkProtocolHandler for LightProtocol {
packet::GET_HEADER_PROOFS => self.get_header_proofs(peer, io, rlp), packet::GET_HEADER_PROOFS => self.get_header_proofs(peer, io, rlp),
packet::HEADER_PROOFS => self.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 => { other => {
Err(Error::UnrecognizedPacket(other)) Err(Error::UnrecognizedPacket(other))
@ -503,4 +821,86 @@ impl NetworkProtocolHandler for LightProtocol {
_ => warn!(target: "les", "received timeout on unknown token {}", timer), _ => 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<u8> {
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()
}
}
} }

View File

@ -183,8 +183,10 @@ pub struct Capabilities {
/// Whether this peer can serve headers /// Whether this peer can serve headers
pub serve_headers: bool, pub serve_headers: bool,
/// Earliest block number it can serve block/receipt requests for. /// Earliest block number it can serve block/receipt requests for.
/// `None` means no requests will be servable.
pub serve_chain_since: Option<u64>, pub serve_chain_since: Option<u64>,
/// Earliest block number it can serve state requests for. /// Earliest block number it can serve state requests for.
/// `None` means no requests will be servable.
pub serve_state_since: Option<u64>, pub serve_state_since: Option<u64>,
/// Whether it can relay transactions to the eth network. /// Whether it can relay transactions to the eth network.
pub tx_relay: bool, pub tx_relay: bool,
@ -201,6 +203,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: /// Attempt to parse a handshake message into its three parts:
/// - chain status /// - chain status
/// - serving capabilities /// - serving capabilities

View File

@ -17,8 +17,11 @@
//! A provider for the LES protocol. This is typically a full node, who can //! A provider for the LES protocol. This is typically a full node, who can
//! give as much data as necessary to its peers. //! give as much data as necessary to its peers.
use ethcore::transaction::SignedTransaction;
use ethcore::blockchain_info::BlockChainInfo; use ethcore::blockchain_info::BlockChainInfo;
use ethcore::client::{BlockChainClient, ProvingBlockChainClient};
use ethcore::transaction::SignedTransaction;
use ethcore::ids::BlockID;
use util::{Bytes, H256}; use util::{Bytes, H256};
use request; use request;
@ -26,7 +29,8 @@ use request;
/// Defines the operations that a provider for `LES` must fulfill. /// Defines the operations that a provider for `LES` must fulfill.
/// ///
/// These are defined at [1], but may be subject to change. /// 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) /// [1]: https://github.com/ethcore/parity/wiki/Light-Ethereum-Subprotocol-(LES)
pub trait Provider: Send + Sync { pub trait Provider: Send + Sync {
@ -34,9 +38,12 @@ pub trait Provider: Send + Sync {
fn chain_info(&self) -> BlockChainInfo; fn chain_info(&self) -> BlockChainInfo;
/// Find the depth of a common ancestor between two blocks. /// Find the depth of a common ancestor between two blocks.
/// If either block is unknown or an ancestor can't be found
/// then return `None`.
fn reorg_depth(&self, a: &H256, b: &H256) -> Option<u64>; fn reorg_depth(&self, a: &H256, b: &H256) -> Option<u64>;
/// Earliest state. /// Earliest block where state queries are available.
/// If `None`, no state queries are servable.
fn earliest_state(&self) -> Option<u64>; fn earliest_state(&self) -> Option<u64>;
/// Provide a list of headers starting at the requested block, /// Provide a list of headers starting at the requested block,
@ -57,15 +64,105 @@ pub trait Provider: Send + Sync {
/// Provide a set of merkle proofs, as requested. Each request is a /// Provide a set of merkle proofs, as requested. Each request is a
/// block hash and request parameters. /// 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<Bytes>; fn proofs(&self, req: request::StateProofs) -> Vec<Bytes>;
/// Provide contract code for the specified (block_hash, account_hash) pairs. /// Provide contract code for the specified (block_hash, account_hash) pairs.
fn code(&self, req: request::ContractCodes) -> Vec<Bytes>; /// Each item in the resulting vector is either the raw bytecode or empty.
fn contract_code(&self, req: request::ContractCodes) -> Vec<Bytes>;
/// Provide header proofs from the Canonical Hash Tries. /// Provide header proofs from the Canonical Hash Tries.
fn header_proofs(&self, req: request::HeaderProofs) -> Vec<Bytes>; fn header_proofs(&self, req: request::HeaderProofs) -> Vec<Bytes>;
/// Provide pending transactions. /// Provide pending transactions.
fn pending_transactions(&self) -> Vec<SignedTransaction>; fn pending_transactions(&self) -> Vec<SignedTransaction>;
}
// Implementation of a light client data provider for a client.
impl<T: ProvingBlockChainClient + ?Sized> Provider for T {
fn chain_info(&self) -> BlockChainInfo {
BlockChainClient::chain_info(self)
}
fn reorg_depth(&self, a: &H256, b: &H256) -> Option<u64> {
self.tree_route(a, b).map(|route| route.index as u64)
}
fn earliest_state(&self) -> Option<u64> {
Some(self.pruning_info().earliest_state)
}
fn block_headers(&self, req: request::Headers) -> Vec<Bytes> {
let best_num = self.chain_info().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<Bytes> {
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<Bytes> {
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<Bytes> {
use rlp::{RlpStream, Stream};
let mut results = Vec::with_capacity(req.requests.len());
for request in req.requests {
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 mut stream = RlpStream::new_list(proof.len());
for node in proof {
stream.append_raw(&node, 1);
}
results.push(stream.out());
}
results
}
fn contract_code(&self, req: request::ContractCodes) -> Vec<Bytes> {
req.code_requests.into_iter()
.map(|req| {
self.code_by_hash(req.account_key, BlockID::Hash(req.block_hash))
})
.collect()
}
fn header_proofs(&self, req: request::HeaderProofs) -> Vec<Bytes> {
req.requests.into_iter().map(|_| ::rlp::EMPTY_LIST_RLP.to_vec()).collect()
}
fn pending_transactions(&self) -> Vec<SignedTransaction> {
BlockChainClient::pending_transactions(self)
}
} }

View File

@ -16,25 +16,26 @@
//! LES request types. //! LES request types.
// TODO: make IPC compatible.
use util::H256; use util::H256;
/// A request for block headers. /// A request for block headers.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq, Binary)]
pub struct Headers { pub struct Headers {
/// Block information for the request being made. /// Starting block number
pub block: (u64, H256), 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. /// The maximum amount of headers which can be returned.
pub max: usize, pub max: usize,
/// The amount of headers to skip between each response entry. /// 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. /// Whether the headers should proceed in falling number from the initial block.
pub reverse: bool, pub reverse: bool,
} }
/// A request for specific block bodies. /// A request for specific block bodies.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq, Binary)]
pub struct Bodies { pub struct Bodies {
/// Hashes which bodies are being requested for. /// Hashes which bodies are being requested for.
pub block_hashes: Vec<H256> pub block_hashes: Vec<H256>
@ -44,14 +45,14 @@ pub struct Bodies {
/// ///
/// This request is answered with a list of transaction receipts for each block /// This request is answered with a list of transaction receipts for each block
/// requested. /// requested.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq, Binary)]
pub struct Receipts { pub struct Receipts {
/// Block hashes to return receipts for. /// Block hashes to return receipts for.
pub block_hashes: Vec<H256>, pub block_hashes: Vec<H256>,
} }
/// A request for a state proof /// A request for a state proof
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq, Binary)]
pub struct StateProof { pub struct StateProof {
/// Block hash to query state from. /// Block hash to query state from.
pub block: H256, pub block: H256,
@ -65,21 +66,30 @@ pub struct StateProof {
} }
/// A request for state proofs. /// A request for state proofs.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq, Binary)]
pub struct StateProofs { pub struct StateProofs {
/// All the proof requests. /// All the proof requests.
pub requests: Vec<StateProof>, pub requests: Vec<StateProof>,
} }
/// A request for contract code. /// 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 { pub struct ContractCodes {
/// Block hash and account key (== sha3(address)) pairs to fetch code for. /// Block hash and account key (== sha3(address)) pairs to fetch code for.
pub code_requests: Vec<(H256, H256)>, pub code_requests: Vec<ContractCode>,
} }
/// A request for a header proof from the Canonical Hash Trie. /// 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 { pub struct HeaderProof {
/// Number of the CHT. /// Number of the CHT.
pub cht_number: u64, pub cht_number: u64,
@ -90,14 +100,14 @@ pub struct HeaderProof {
} }
/// A request for header proofs from the CHT. /// A request for header proofs from the CHT.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq, Binary)]
pub struct HeaderProofs { pub struct HeaderProofs {
/// All the proof requests. /// All the proof requests.
pub requests: Vec<HeaderProofs>, pub requests: Vec<HeaderProof>,
} }
/// Kinds of requests. /// Kinds of requests.
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Binary)]
pub enum Kind { pub enum Kind {
/// Requesting headers. /// Requesting headers.
Headers, Headers,
@ -114,7 +124,7 @@ pub enum Kind {
} }
/// Encompasses all possible types of requests in a single structure. /// Encompasses all possible types of requests in a single structure.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq, Binary)]
pub enum Request { pub enum Request {
/// Requesting headers. /// Requesting headers.
Headers(Headers), Headers(Headers),
@ -142,4 +152,16 @@ impl Request {
Request::HeaderProofs(_) => Kind::HeaderProofs, 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(),
}
}
} }

View File

@ -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 <http://www.gnu.org/licenses/>.
//! 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"));

View File

@ -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 <http://www.gnu.org/licenses/>.
pub mod les_request;

View File

@ -52,7 +52,7 @@ use blockchain::{BlockChain, BlockProvider, TreeRoute, ImportRoute};
use client::{ use client::{
BlockID, TransactionID, UncleID, TraceId, ClientConfig, BlockChainClient, BlockID, TransactionID, UncleID, TraceId, ClientConfig, BlockChainClient,
MiningBlockChainClient, TraceFilter, CallAnalytics, BlockImportError, Mode, MiningBlockChainClient, TraceFilter, CallAnalytics, BlockImportError, Mode,
ChainNotify, ChainNotify, PruningInfo, ProvingBlockChainClient,
}; };
use client::Error as ClientError; use client::Error as ClientError;
use env_info::EnvInfo; use env_info::EnvInfo;
@ -1286,6 +1286,13 @@ impl BlockChainClient for Client {
self.uncle(id) self.uncle(id)
.map(|header| self.engine.extra_info(&decode(&header))) .map(|header| self.engine.extra_info(&decode(&header)))
} }
fn pruning_info(&self) -> PruningInfo {
PruningInfo {
earliest_chain: self.chain.read().first_block_number().unwrap_or(1),
earliest_state: self.state_db.lock().journal_db().earliest_era().unwrap_or(0),
}
}
} }
impl MiningBlockChainClient for Client { impl MiningBlockChainClient for Client {
@ -1370,32 +1377,60 @@ impl MayPanic for Client {
} }
} }
impl ProvingBlockChainClient for Client {
fn prove_storage(&self, key1: H256, key2: H256, from_level: u32, id: BlockID) -> Vec<Bytes> {
self.state_at(id)
.and_then(move |state| state.prove_storage(key1, key2, from_level).ok())
.unwrap_or_else(Vec::new)
}
#[test] fn prove_account(&self, key1: H256, from_level: u32, id: BlockID) -> Vec<Bytes> {
fn should_not_cache_details_before_commit() { self.state_at(id)
use tests::helpers::*; .and_then(move |state| state.prove_account(key1, from_level).ok())
use std::thread; .unwrap_or_else(Vec::new)
use std::time::Duration; }
use std::sync::atomic::{AtomicBool, Ordering};
let client = generate_dummy_client(0); fn code_by_hash(&self, account_key: H256, id: BlockID) -> Bytes {
let genesis = client.chain_info().best_block_hash; self.state_at(id)
let (new_hash, new_block) = get_good_dummy_block_hash(); .and_then(move |state| state.code_by_address_hash(account_key).ok())
.and_then(|x| x)
let go = { .unwrap_or_else(Vec::new)
// 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());
} }
#[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());
}
}

View File

@ -25,18 +25,21 @@ mod client;
pub use self::client::*; pub use self::client::*;
pub use self::config::{Mode, ClientConfig, DatabaseCompactionProfile, BlockChainConfig, VMType}; pub use self::config::{Mode, ClientConfig, DatabaseCompactionProfile, BlockChainConfig, VMType};
pub use self::error::Error; pub use self::error::Error;
pub use types::ids::*;
pub use self::test_client::{TestBlockChainClient, EachBlockWith}; pub use self::test_client::{TestBlockChainClient, EachBlockWith};
pub use self::chain_notify::ChainNotify;
pub use self::traits::{BlockChainClient, MiningBlockChainClient, ProvingBlockChainClient};
pub use types::ids::*;
pub use types::trace_filter::Filter as TraceFilter; 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 executive::{Executed, Executive, TransactOptions};
pub use env_info::{LastHashes, EnvInfo}; 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 block_import_error::BlockImportError;
pub use transaction_import::TransactionImportResult; pub use transaction_import::TransactionImportResult;
pub use transaction_import::TransactionImportError; pub use transaction_import::TransactionImportError;
pub use self::traits::{BlockChainClient, MiningBlockChainClient};
pub use verification::VerifierType; pub use verification::VerifierType;
/// IPC interfaces /// IPC interfaces

View File

@ -38,6 +38,7 @@ use evm::{Factory as EvmFactory, VMType, Schedule};
use miner::{Miner, MinerService, TransactionImportResult}; use miner::{Miner, MinerService, TransactionImportResult};
use spec::Spec; use spec::Spec;
use types::mode::Mode; use types::mode::Mode;
use types::pruning_info::PruningInfo;
use views::BlockView; use views::BlockView;
use verification::queue::QueueInfo; use verification::queue::QueueInfo;
@ -667,4 +668,11 @@ impl BlockChainClient for TestBlockChainClient {
fn mode(&self) -> Mode { Mode::Active } fn mode(&self) -> Mode { Mode::Active }
fn set_mode(&self, _: Mode) { unimplemented!(); } fn set_mode(&self, _: Mode) { unimplemented!(); }
fn pruning_info(&self) -> PruningInfo {
PruningInfo {
earliest_chain: 1,
earliest_state: 1,
}
}
} }

View File

@ -39,6 +39,7 @@ use types::call_analytics::CallAnalytics;
use types::blockchain_info::BlockChainInfo; use types::blockchain_info::BlockChainInfo;
use types::block_status::BlockStatus; use types::block_status::BlockStatus;
use types::mode::Mode; use types::mode::Mode;
use types::pruning_info::PruningInfo;
#[ipc(client_ident="RemoteClient")] #[ipc(client_ident="RemoteClient")]
/// Blockchain database client. Owns and manages a blockchain and a block queue. /// Blockchain database client. Owns and manages a blockchain and a block queue.
@ -250,10 +251,15 @@ pub trait BlockChainClient : Sync + Send {
/// Returns engine-related extra info for `UncleID`. /// Returns engine-related extra info for `UncleID`.
fn uncle_extra_info(&self, id: UncleID) -> Option<BTreeMap<String, String>>; fn uncle_extra_info(&self, id: UncleID) -> Option<BTreeMap<String, String>>;
/// Returns information about pruning/data availability.
fn pruning_info(&self) -> PruningInfo;
} }
impl IpcConfig for BlockChainClient { }
/// Extended client interface used for mining /// Extended client interface used for mining
pub trait MiningBlockChainClient : BlockChainClient { pub trait MiningBlockChainClient: BlockChainClient {
/// Returns OpenBlock prepared for closing. /// Returns OpenBlock prepared for closing.
fn prepare_open_block(&self, fn prepare_open_block(&self,
author: Address, author: Address,
@ -271,4 +277,23 @@ pub trait MiningBlockChainClient : BlockChainClient {
fn latest_schedule(&self) -> Schedule; 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<Bytes>;
/// 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<Bytes>;
/// Get code by address hash.
fn code_by_hash(&self, account_key: H256, id: BlockID) -> Bytes;
}

View File

@ -436,6 +436,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<Vec<Bytes>, Box<TrieError>> {
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 { impl fmt::Debug for Account {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", PodAccount::from_account(self)) write!(f, "{:?}", PodAccount::from_account(self))

View File

@ -16,7 +16,7 @@
use std::cell::{RefCell, RefMut}; use std::cell::{RefCell, RefMut};
use std::collections::hash_map::Entry; use std::collections::hash_map::Entry;
use util::*;
use receipt::Receipt; use receipt::Receipt;
use engines::Engine; use engines::Engine;
use env_info::EnvInfo; use env_info::EnvInfo;
@ -30,6 +30,9 @@ use types::state_diff::StateDiff;
use transaction::SignedTransaction; use transaction::SignedTransaction;
use state_db::StateDB; use state_db::StateDB;
use util::*;
use util::trie::recorder::{Recorder, BasicRecorder as TrieRecorder};
mod account; mod account;
mod substate; mod substate;
@ -758,6 +761,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<Vec<Bytes>, Box<TrieError>> {
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<Vec<Bytes>, Box<TrieError>> {
// 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<Option<Bytes>, Box<TrieError>> {
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 { impl fmt::Debug for State {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self.cache.borrow()) write!(f, "{:?}", self.cache.borrow())

View File

@ -34,3 +34,4 @@ pub mod block_import_error;
pub mod restoration_status; pub mod restoration_status;
pub mod snapshot_manifest; pub mod snapshot_manifest;
pub mod mode; pub mod mode;
pub mod pruning_info;

View File

@ -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 <http://www.gnu.org/licenses/>.
//! 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,
}

View File

@ -97,11 +97,11 @@ pub trait Trie {
} }
/// Query the value of the given key in this trie while recording visited nodes /// 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<Option<DBValue>> fn get_recorded<'a, 'b, R: 'b>(&'a self, key: &'b [u8], rec: &'b mut R) -> Result<Option<DBValue>>
where 'a: 'b, R: Recorder; where 'a: 'b, R: Recorder;
/// Returns an iterator over elements of trie. /// Returns a depth-first iterator over the elements of trie.
fn iter<'a>(&'a self) -> Result<Box<TrieIterator<Item = TrieItem> + 'a>>; fn iter<'a>(&'a self) -> Result<Box<TrieIterator<Item = TrieItem> + 'a>>;
} }
@ -241,5 +241,5 @@ impl TrieFactory {
} }
/// Returns true iff the trie DB is a fat DB (allows enumeration of keys). /// 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 }
} }

View File

@ -35,7 +35,6 @@ pub struct Record {
/// These are used to record which nodes are visited during a trie query. /// 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. /// Inline nodes are not to be recorded, as they are contained within their parent.
pub trait Recorder { pub trait Recorder {
/// Record that the given node has been visited. /// 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. /// 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 /// A simple recorder. Does nothing fancy but fulfills the `Recorder` interface
/// properly. /// properly.
#[derive(Debug)]
pub struct BasicRecorder { pub struct BasicRecorder {
nodes: Vec<Record>, nodes: Vec<Record>,
min_depth: u32, min_depth: u32,