From 31aae4ed784119cd71c48a5f931e4456dde29073 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Mon, 16 Jan 2017 16:55:23 +0100 Subject: [PATCH 1/3] move cht module up a level --- ethcore/light/src/{client => }/cht.rs | 5 +++++ ethcore/light/src/client/header_chain.rs | 2 +- ethcore/light/src/client/mod.rs | 2 -- ethcore/light/src/lib.rs | 1 + 4 files changed, 7 insertions(+), 3 deletions(-) rename ethcore/light/src/{client => }/cht.rs (87%) diff --git a/ethcore/light/src/client/cht.rs b/ethcore/light/src/cht.rs similarity index 87% rename from ethcore/light/src/client/cht.rs rename to ethcore/light/src/cht.rs index 8424e0c31..537a3ed6a 100644 --- a/ethcore/light/src/client/cht.rs +++ b/ethcore/light/src/cht.rs @@ -20,3 +20,8 @@ pub const SIZE: u64 = 2048; pub fn block_to_cht_number(block_num: u64) -> u64 { (block_num + 1) / SIZE } + +/// Get the starting block of a given CHT. +pub fn start_number(cht_num: u64) -> u64 { + (cht_num * SIZE) + 1 +} diff --git a/ethcore/light/src/client/header_chain.rs b/ethcore/light/src/client/header_chain.rs index fdb075e04..a2a953653 100644 --- a/ethcore/light/src/client/header_chain.rs +++ b/ethcore/light/src/client/header_chain.rs @@ -28,7 +28,7 @@ use std::collections::{BTreeMap, HashMap}; -use client::cht; +use cht; use ethcore::block_status::BlockStatus; use ethcore::error::BlockError; diff --git a/ethcore/light/src/client/mod.rs b/ethcore/light/src/client/mod.rs index 7d4cb0fe8..1d7821e78 100644 --- a/ethcore/light/src/client/mod.rs +++ b/ethcore/light/src/client/mod.rs @@ -39,8 +39,6 @@ use self::header_chain::HeaderChain; pub use self::service::Service; -pub mod cht; - mod header_chain; mod service; diff --git a/ethcore/light/src/lib.rs b/ethcore/light/src/lib.rs index 0e650f992..a23fd879a 100644 --- a/ethcore/light/src/lib.rs +++ b/ethcore/light/src/lib.rs @@ -33,6 +33,7 @@ #![deny(missing_docs)] pub mod client; +pub mod cht; pub mod net; pub mod on_demand; From b960152d5b099c08367b2933cc52c5a42a258d07 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Mon, 16 Jan 2017 17:10:30 +0100 Subject: [PATCH 2/3] CHT calculations for full nodes --- ethcore/light/src/on_demand/mod.rs | 2 +- ethcore/light/src/provider.rs | 45 ++++++++++++++++++++++++++++-- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/ethcore/light/src/on_demand/mod.rs b/ethcore/light/src/on_demand/mod.rs index aadb43c0e..dfc78a629 100644 --- a/ethcore/light/src/on_demand/mod.rs +++ b/ethcore/light/src/on_demand/mod.rs @@ -112,7 +112,7 @@ impl OnDemand { // dispatch the request, completing the request if no peers available. fn dispatch_header_by_number(&self, ctx: &BasicContext, req: request::HeaderByNumber, sender: Sender) { let num = req.num; - let cht_num = ::client::cht::block_to_cht_number(req.num); + let cht_num = ::cht::block_to_cht_number(req.num); let les_req = LesRequest::HeaderProofs(les_request::HeaderProofs { requests: vec![les_request::HeaderProof { cht_number: cht_num, diff --git a/ethcore/light/src/provider.rs b/ethcore/light/src/provider.rs index 9a05d3499..14f8a148d 100644 --- a/ethcore/light/src/provider.rs +++ b/ethcore/light/src/provider.rs @@ -226,8 +226,49 @@ impl Provider for T { self.code_by_hash(req.account_key, BlockId::Hash(req.block_hash)) } - fn header_proof(&self, _req: request::HeaderProof) -> Option<(encoded::Header, Vec)> { - None + fn header_proof(&self, req: request::HeaderProof) -> Option<(encoded::Header, Vec)> { + use util::MemoryDB; + use util::trie::{Trie, TrieMut, TrieDB, TrieDBMut, Recorder}; + + if req.cht_number != ::cht::block_to_cht_number(req.block_number) { + debug!(target: "les_provider", "Requested CHT number mismatch with block number."); + return None; + } + + let mut memdb = MemoryDB::new(); + let mut root = H256::default(); + let mut needed_hdr = None; + { + let mut t = TrieDBMut::new(&mut memdb, &mut root); + let start_num = ::cht::start_number(req.cht_number); + for i in (0..::cht::SIZE).map(|x| x + start_num) { + match self.block_header(BlockId::Number(i)) { + None => return None, + Some(hdr) => { + t.insert( + &*::rlp::encode(&i), + &*::rlp::encode(&hdr.hash()), + ).expect("fresh in-memory database is infallible; qed"); + + if i == req.block_number { needed_hdr = Some(hdr) } + } + } + } + } + let needed_hdr = needed_hdr.expect("`needed_hdr` always set in loop, number checked before; qed"); + + let mut recorder = Recorder::new(); + let t = TrieDB::new(&memdb, &root) + .expect("Same DB and root as just produced by TrieDBMut; qed"); + + if let Err(e) = t.get_with(&*::rlp::encode(&req.block_number), &mut recorder) { + debug!(target: "les_provider", "Error looking up number in freshly-created CHT: {}", e); + return None; + } + + // TODO: cache calculated CHT if possible. + let proof = recorder.drain().into_iter().map(|x| x.data).collect(); + Some((needed_hdr, proof)) } fn ready_transactions(&self) -> Vec { From 536df809c657da02b728af40647b6e482377cc07 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Mon, 16 Jan 2017 17:42:39 +0100 Subject: [PATCH 3/3] tests + documentation --- ethcore/light/src/cht.rs | 31 ++++++++++++++++++++++++-- ethcore/light/src/on_demand/mod.rs | 12 +++++++++- ethcore/light/src/provider.rs | 28 +++++++++++++++++++++-- ethcore/light/src/types/les_request.rs | 2 +- 4 files changed, 67 insertions(+), 6 deletions(-) diff --git a/ethcore/light/src/cht.rs b/ethcore/light/src/cht.rs index 537a3ed6a..13edadee1 100644 --- a/ethcore/light/src/cht.rs +++ b/ethcore/light/src/cht.rs @@ -17,11 +17,38 @@ pub const SIZE: u64 = 2048; /// Convert a block number to a CHT number. -pub fn block_to_cht_number(block_num: u64) -> u64 { - (block_num + 1) / SIZE +/// Returns `None` for `block_num` == 0, `Some` otherwise. +pub fn block_to_cht_number(block_num: u64) -> Option { + match block_num { + 0 => None, + n => Some((n - 1) / SIZE), + } } /// Get the starting block of a given CHT. +/// CHT 0 includes block 1...SIZE, +/// CHT 1 includes block SIZE + 1 ... 2*SIZE +/// More generally: CHT N includes block (1 + N*SIZE)...((N+1)*SIZE). +/// This is because the genesis hash is assumed to be known +/// and including it would be redundant. pub fn start_number(cht_num: u64) -> u64 { (cht_num * SIZE) + 1 } + +#[cfg(test)] +mod tests { + #[test] + fn block_to_cht_number() { + assert!(::cht::block_to_cht_number(0).is_none()); + assert_eq!(::cht::block_to_cht_number(1).unwrap(), 0); + assert_eq!(::cht::block_to_cht_number(::cht::SIZE + 1).unwrap(), 1); + assert_eq!(::cht::block_to_cht_number(::cht::SIZE).unwrap(), 0); + } + + #[test] + fn start_number() { + assert_eq!(::cht::start_number(0), 1); + assert_eq!(::cht::start_number(1), ::cht::SIZE + 1); + assert_eq!(::cht::start_number(2), ::cht::SIZE * 2 + 1); + } +} diff --git a/ethcore/light/src/on_demand/mod.rs b/ethcore/light/src/on_demand/mod.rs index dfc78a629..a01de7598 100644 --- a/ethcore/light/src/on_demand/mod.rs +++ b/ethcore/light/src/on_demand/mod.rs @@ -41,6 +41,8 @@ pub enum Error { Canceled, /// No suitable peers available to serve the request. NoPeersAvailable, + /// Invalid request. + InvalidRequest, /// Request timed out. TimedOut, } @@ -112,7 +114,15 @@ impl OnDemand { // dispatch the request, completing the request if no peers available. fn dispatch_header_by_number(&self, ctx: &BasicContext, req: request::HeaderByNumber, sender: Sender) { let num = req.num; - let cht_num = ::cht::block_to_cht_number(req.num); + let cht_num = match ::cht::block_to_cht_number(req.num) { + Some(cht_num) => cht_num, + None => { + warn!(target: "on_demand", "Attempted to dispatch invalid header proof: req.num == 0"); + sender.complete(Err(Error::InvalidRequest)); + return; + } + }; + let les_req = LesRequest::HeaderProofs(les_request::HeaderProofs { requests: vec![les_request::HeaderProof { cht_number: cht_num, diff --git a/ethcore/light/src/provider.rs b/ethcore/light/src/provider.rs index 14f8a148d..a2ee36f82 100644 --- a/ethcore/light/src/provider.rs +++ b/ethcore/light/src/provider.rs @@ -230,7 +230,7 @@ impl Provider for T { use util::MemoryDB; use util::trie::{Trie, TrieMut, TrieDB, TrieDBMut, Recorder}; - if req.cht_number != ::cht::block_to_cht_number(req.block_number) { + if Some(req.cht_number) != ::cht::block_to_cht_number(req.block_number) { debug!(target: "les_provider", "Requested CHT number mismatch with block number."); return None; } @@ -257,7 +257,7 @@ impl Provider for T { } let needed_hdr = needed_hdr.expect("`needed_hdr` always set in loop, number checked before; qed"); - let mut recorder = Recorder::new(); + let mut recorder = Recorder::with_depth(req.from_level); let t = TrieDB::new(&memdb, &root) .expect("Same DB and root as just produced by TrieDBMut; qed"); @@ -275,3 +275,27 @@ impl Provider for T { BlockChainClient::ready_transactions(self) } } + +#[cfg(test)] +mod tests { + use ethcore::client::{EachBlockWith, TestBlockChainClient}; + use super::Provider; + + #[test] + fn cht_proof() { + let client = TestBlockChainClient::new(); + client.add_blocks(2000, EachBlockWith::Nothing); + + let req = ::request::HeaderProof { + cht_number: 0, + block_number: 1500, + from_level: 0, + }; + + assert!(client.header_proof(req.clone()).is_none()); + + client.add_blocks(48, EachBlockWith::Nothing); + + assert!(client.header_proof(req.clone()).is_some()); + } +} diff --git a/ethcore/light/src/types/les_request.rs b/ethcore/light/src/types/les_request.rs index dde8ac2f8..6dced2731 100644 --- a/ethcore/light/src/types/les_request.rs +++ b/ethcore/light/src/types/les_request.rs @@ -120,7 +120,7 @@ pub struct ContractCodes { pub struct HeaderProof { /// Number of the CHT. pub cht_number: u64, - /// Block number requested. + /// Block number requested. May not be 0: genesis isn't included in any CHT. pub block_number: u64, /// If greater than zero, trie nodes beyond this level may be omitted. pub from_level: u32,