commit
085b8ad553
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -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",
|
||||||
|
@ -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
21
ethcore/light/build.rs
Normal 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();
|
||||||
|
}
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
|
@ -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)]
|
||||||
|
@ -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"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
@ -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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
20
ethcore/light/src/types/mod.rs
Normal file
20
ethcore/light/src/types/mod.rs
Normal 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"));
|
17
ethcore/light/src/types/mod.rs.in
Normal file
17
ethcore/light/src/types/mod.rs.in
Normal 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;
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
@ -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))
|
||||||
|
@ -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())
|
||||||
|
@ -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;
|
30
ethcore/src/types/pruning_info.rs
Normal file
30
ethcore/src/types/pruning_info.rs
Normal 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,
|
||||||
|
}
|
@ -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 }
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
Loading…
Reference in New Issue
Block a user