From abf39fde0a3339f6b190932784f6a125b85014cd Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Tue, 15 Nov 2016 14:53:30 +0100 Subject: [PATCH] implement provider for client --- ethcore/src/client/client.rs | 170 ++++++++++++++++++++++++++----- ethcore/src/light/client.rs | 2 +- ethcore/src/light/mod.rs | 1 + ethcore/src/light/provider.rs | 58 +---------- ethcore/src/state/account.rs | 21 ++++ ethcore/src/state/mod.rs | 52 +++++++++- ethcore/src/types/les_request.rs | 2 +- util/src/trie/mod.rs | 6 +- util/src/trie/recorder.rs | 2 +- 9 files changed, 227 insertions(+), 87 deletions(-) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 788596d86..1054f0614 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -68,6 +68,7 @@ use factory::Factories; use rlp::{decode, View, UntrustedRlp}; use state_db::StateDB; use rand::OsRng; +use light::{self, request}; // re-export pub use types::blockchain_info::BlockChainInfo; @@ -1317,32 +1318,155 @@ impl MayPanic for Client { } } +// Implementation of a light client data provider for a client. +impl light::Provider for Client { + fn chain_info(&self) -> BlockChainInfo { + BlockChainClient::chain_info(self) + } -#[test] -fn should_not_cache_details_before_commit() { - use tests::helpers::*; - use std::thread; - use std::time::Duration; - use std::sync::atomic::{AtomicBool, Ordering}; + fn reorg_depth(&self, a: &H256, b: &H256) -> Option { + self.tree_route(a, b).map(|route| route.index as u64) + } - let client = generate_dummy_client(0); - let genesis = client.chain_info().best_block_hash; - let (new_hash, new_block) = get_good_dummy_block_hash(); + fn earliest_state(&self) -> Option { + Some(self.pruning_info().earliest_state) + } - let go = { - // Separate thread uncommited transaction - let go = Arc::new(AtomicBool::new(false)); - let go_thread = go.clone(); - let another_client = client.reference().clone(); - thread::spawn(move || { - let mut batch = DBTransaction::new(&*another_client.chain.read().db().clone()); - another_client.chain.read().insert_block(&mut batch, &new_block, Vec::new()); - go_thread.store(true, Ordering::SeqCst); - }); - go - }; + fn block_headers(&self, req: request::Headers) -> Vec { + let best_num = self.chain.read().best_block_number(); + let start_num = req.block_num; - while !go.load(Ordering::SeqCst) { thread::park_timeout(Duration::from_millis(5)); } + match self.block_hash(BlockID::Number(req.block_num)) { + Some(hash) if hash == req.block_hash => {} + _=> { + trace!(target: "les_provider", "unknown/non-canonical start block in header request: {:?}", (req.block_num, req.block_hash)); + return vec![] + } + } - assert!(client.tree_route(&genesis, &new_hash).is_none()); + (0u64..req.max as u64) + .map(|x: u64| x.saturating_mul(req.skip)) + .take_while(|x| if req.reverse { x < &start_num } else { best_num - start_num < *x }) + .map(|x| if req.reverse { start_num - x } else { start_num + x }) + .map(|x| self.block_header(BlockID::Number(x))) + .flat_map(|x| x) + .fuse() // collect no more beyond the first `None` + .collect() + } + + fn block_bodies(&self, req: request::Bodies) -> Vec { + use ids::BlockID; + + req.block_hashes.into_iter() + .map(|hash| self.block_body(BlockID::Hash(hash))) + .map(|body| body.unwrap_or_else(|| ::rlp::EMPTY_LIST_RLP.to_vec())) + .collect() + } + + fn receipts(&self, req: request::Receipts) -> Vec { + req.block_hashes.into_iter() + .map(|hash| self.block_receipts(&hash)) + .map(|receipts| receipts.unwrap_or_else(|| ::rlp::EMPTY_LIST_RLP.to_vec())) + .collect() + } + + fn proofs(&self, req: request::StateProofs) -> Vec { + use rlp::{EMPTY_LIST_RLP, RlpStream, Stream}; + + let mut results = Vec::with_capacity(req.requests.len()); + + for request in req.requests { + let state = match self.state_at(BlockID::Hash(request.block)) { + Some(state) => state, + None => { + trace!(target: "light_provider", "state for {} not available", request.block); + results.push(EMPTY_LIST_RLP.to_vec()); + continue; + } + }; + + let res = match request.key2 { + Some(storage_key) => state.prove_storage(request.key1, storage_key, request.from_level), + None => state.prove_account(request.key1, request.from_level), + }; + + match res { + Ok(records) => { + let mut stream = RlpStream::new_list(records.len()); + for record in records { + stream.append_raw(&record, 1); + } + results.push(stream.out()) + } + Err(e) => { + debug!(target: "light_provider", "encountered error {} while forming proof of state at {}", e, request.block); + results.push(EMPTY_LIST_RLP.to_vec()); + } + } + } + + results + } + + fn contract_code(&self, req: request::ContractCodes) -> Vec { + req.code_requests.into_iter() + .map(|req| { + self.state_at(BlockID::Hash(req.block_hash)) + .map(|state| { + match state.code_by_address_hash(req.account_key) { + Ok(code) => code.unwrap_or_else(Vec::new), + Err(e) => { + debug!(target: "light_provider", "encountered error {} while fetching code.", e); + Vec::new() + } + } + }).unwrap_or_else(Vec::new) + }) + .collect() + } + + fn header_proofs(&self, req: request::HeaderProofs) -> Vec { + req.requests.into_iter().map(|_| ::rlp::EMPTY_LIST_RLP.to_vec()).collect() + } + + fn pending_transactions(&self) -> Vec { + BlockChainClient::pending_transactions(self) + } } + +#[cfg(test)] +mod tests { + + #[test] + fn should_not_cache_details_before_commit() { + use client::BlockChainClient; + use tests::helpers::*; + + use std::thread; + use std::time::Duration; + use std::sync::Arc; + use std::sync::atomic::{AtomicBool, Ordering}; + use util::kvdb::DBTransaction; + + let client = generate_dummy_client(0); + let genesis = client.chain_info().best_block_hash; + let (new_hash, new_block) = get_good_dummy_block_hash(); + + let go = { + // Separate thread uncommited transaction + let go = Arc::new(AtomicBool::new(false)); + let go_thread = go.clone(); + let another_client = client.reference().clone(); + thread::spawn(move || { + let mut batch = DBTransaction::new(&*another_client.chain.read().db().clone()); + another_client.chain.read().insert_block(&mut batch, &new_block, Vec::new()); + go_thread.store(true, Ordering::SeqCst); + }); + go + }; + + while !go.load(Ordering::SeqCst) { thread::park_timeout(Duration::from_millis(5)); } + + assert!(client.tree_route(&genesis, &new_hash).is_none()); + } +} \ No newline at end of file diff --git a/ethcore/src/light/client.rs b/ethcore/src/light/client.rs index 7b821d971..699db743a 100644 --- a/ethcore/src/light/client.rs +++ b/ethcore/src/light/client.rs @@ -101,7 +101,7 @@ impl Provider for Client { Vec::new() } - fn code(&self, _req: request::ContractCodes) -> Vec { + fn contract_code(&self, _req: request::ContractCodes) -> Vec { Vec::new() } diff --git a/ethcore/src/light/mod.rs b/ethcore/src/light/mod.rs index a7b2d3922..dca592453 100644 --- a/ethcore/src/light/mod.rs +++ b/ethcore/src/light/mod.rs @@ -35,4 +35,5 @@ pub mod client; pub mod net; pub mod provider; +pub use self::provider::Provider; pub use types::les_request as request; \ No newline at end of file diff --git a/ethcore/src/light/provider.rs b/ethcore/src/light/provider.rs index 481865643..bf65acd18 100644 --- a/ethcore/src/light/provider.rs +++ b/ethcore/src/light/provider.rs @@ -17,11 +17,9 @@ //! A provider for the LES protocol. This is typically a full node, who can //! give as much data as necessary to its peers. -use client::BlockChainClient; use transaction::SignedTransaction; use blockchain_info::BlockChainInfo; -use rlp::EMPTY_LIST_RLP; use util::{Bytes, H256}; use light::request; @@ -65,65 +63,11 @@ pub trait Provider: Send + Sync { fn proofs(&self, req: request::StateProofs) -> Vec; /// Provide contract code for the specified (block_hash, account_hash) pairs. - fn code(&self, req: request::ContractCodes) -> Vec; + fn contract_code(&self, req: request::ContractCodes) -> Vec; /// Provide header proofs from the Canonical Hash Tries. fn header_proofs(&self, req: request::HeaderProofs) -> Vec; /// Provide pending transactions. fn pending_transactions(&self) -> Vec; -} - -// TODO [rob] move into trait definition file after ethcore crate -// is split up. ideally `ethcore-light` will be between `ethcore-blockchain` -// and `ethcore-client` -impl Provider for T { - fn chain_info(&self) -> BlockChainInfo { - BlockChainClient::chain_info(self) - } - - fn reorg_depth(&self, a: &H256, b: &H256) -> Option { - self.tree_route(a, b).map(|route| route.index as u64) - } - - fn earliest_state(&self) -> Option { - Some(self.pruning_info().earliest_state) - } - - fn block_headers(&self, req: request::Headers) -> Vec { - unimplemented!() - } - - fn block_bodies(&self, req: request::Bodies) -> Vec { - use ids::BlockID; - - req.block_hashes.into_iter() - .map(|hash| self.block_body(BlockID::Hash(hash))) - .map(|body| body.unwrap_or_else(|| EMPTY_LIST_RLP.to_vec())) - .collect() - } - - fn receipts(&self, req: request::Receipts) -> Vec { - req.block_hashes.into_iter() - .map(|hash| self.block_receipts(&hash)) - .map(|receipts| receipts.unwrap_or_else(|| EMPTY_LIST_RLP.to_vec())) - .collect() - } - - fn proofs(&self, req: request::StateProofs) -> Vec { - unimplemented!() - } - - fn code(&self, req: request::ContractCodes) -> Vec { - unimplemented!() - } - - fn header_proofs(&self, req: request::HeaderProofs) -> Vec { - // TODO: [rob] implement CHT stuff on `ethcore` side. - req.requests.into_iter().map(|_| EMPTY_LIST_RLP.to_vec()).collect() - } - - fn pending_transactions(&self) -> Vec { - unimplemented!() - } } \ No newline at end of file diff --git a/ethcore/src/state/account.rs b/ethcore/src/state/account.rs index 76061f6a0..4cca0f645 100644 --- a/ethcore/src/state/account.rs +++ b/ethcore/src/state/account.rs @@ -437,6 +437,27 @@ impl Account { } } +// light client storage proof. +impl Account { + /// Prove a storage key's existence or nonexistence in the account's storage + /// trie. + /// `storage_key` is the hash of the desired storage key, meaning + /// this will only work correctly under a secure trie. + /// Returns a merkle proof of the storage trie node with all nodes before `from_level` + /// omitted. + pub fn prove_storage(&self, db: &HashDB, storage_key: H256, from_level: u32) -> Result, Box> { + use util::trie::{Trie, TrieDB}; + use util::trie::recorder::{Recorder, BasicRecorder as TrieRecorder}; + + let mut recorder = TrieRecorder::with_depth(from_level); + + let trie = try!(TrieDB::new(db, &self.storage_root)); + let _ = try!(trie.get_recorded(&storage_key, &mut recorder)); + + Ok(recorder.drain().into_iter().map(|r| r.data).collect()) + } +} + impl fmt::Debug for Account { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", PodAccount::from_account(self)) diff --git a/ethcore/src/state/mod.rs b/ethcore/src/state/mod.rs index 01a7e3b15..bedc46d9c 100644 --- a/ethcore/src/state/mod.rs +++ b/ethcore/src/state/mod.rs @@ -16,7 +16,7 @@ use std::cell::{RefCell, RefMut}; use std::collections::hash_map::Entry; -use util::*; + use receipt::Receipt; use engines::Engine; use env_info::EnvInfo; @@ -30,6 +30,9 @@ use types::state_diff::StateDiff; use transaction::SignedTransaction; use state_db::StateDB; +use util::*; +use util::trie::recorder::{Recorder, BasicRecorder as TrieRecorder}; + mod account; mod substate; @@ -751,6 +754,53 @@ impl State { } } +// LES state proof implementations. +impl State { + /// Prove an account's existence or nonexistence in the state trie. + /// Returns a merkle proof of the account's trie node with all nodes before `from_level` + /// omitted or an encountered trie error. + /// Requires a secure trie to be used for accurate results. + /// `account_key` == sha3(address) + pub fn prove_account(&self, account_key: H256, from_level: u32) -> Result, Box> { + let mut recorder = TrieRecorder::with_depth(from_level); + let trie = try!(TrieDB::new(self.db.as_hashdb(), &self.root)); + let _ = try!(trie.get_recorded(&account_key, &mut recorder)); + + Ok(recorder.drain().into_iter().map(|r| r.data).collect()) + } + + /// Prove an account's storage key's existence or nonexistence in the state. + /// Returns a merkle proof of the account's storage trie with all nodes before + /// `from_level` omitted. Requires a secure trie to be used for correctness. + /// `account_key` == sha3(address) + /// `storage_key` == sha3(key) + pub fn prove_storage(&self, account_key: H256, storage_key: H256, from_level: u32) -> Result, Box> { + // TODO: probably could look into cache somehow but it's keyed by + // address, not sha3(address). + let trie = try!(TrieDB::new(self.db.as_hashdb(), &self.root)); + let acc = match try!(trie.get(&account_key)) { + Some(rlp) => Account::from_rlp(&rlp), + None => return Ok(Vec::new()), + }; + + let account_db = self.factories.accountdb.readonly(self.db.as_hashdb(), account_key); + acc.prove_storage(account_db.as_hashdb(), storage_key, from_level) + } + + /// Get code by address hash. + /// Only works when backed by a secure trie. + pub fn code_by_address_hash(&self, account_key: H256) -> Result, Box> { + let trie = try!(TrieDB::new(self.db.as_hashdb(), &self.root)); + let mut acc = match try!(trie.get(&account_key)) { + Some(rlp) => Account::from_rlp(&rlp), + None => return Ok(None), + }; + + let account_db = self.factories.accountdb.readonly(self.db.as_hashdb(), account_key); + Ok(acc.cache_code(account_db.as_hashdb()).map(|c| (&*c).clone())) + } +} + impl fmt::Debug for State { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", self.cache.borrow()) diff --git a/ethcore/src/types/les_request.rs b/ethcore/src/types/les_request.rs index e947c654d..6e822dcdd 100644 --- a/ethcore/src/types/les_request.rs +++ b/ethcore/src/types/les_request.rs @@ -29,7 +29,7 @@ pub struct Headers { /// The maximum amount of headers which can be returned. pub max: usize, /// The amount of headers to skip between each response entry. - pub skip: usize, + pub skip: u64, /// Whether the headers should proceed in falling number from the initial block. pub reverse: bool, } diff --git a/util/src/trie/mod.rs b/util/src/trie/mod.rs index d4cc04962..5d71c2400 100644 --- a/util/src/trie/mod.rs +++ b/util/src/trie/mod.rs @@ -97,11 +97,11 @@ pub trait Trie { } /// Query the value of the given key in this trie while recording visited nodes - /// to the given recorder. If the query fails, the nodes passed to the recorder are unspecified. + /// to the given recorder. If the query encounters an error, the nodes passed to the recorder are unspecified. fn get_recorded<'a, 'b, R: 'b>(&'a self, key: &'b [u8], rec: &'b mut R) -> Result> where 'a: 'b, R: Recorder; - /// Returns an iterator over elements of trie. + /// Returns a depth-first iterator over the elements of the trie. fn iter<'a>(&'a self) -> Result + 'a>>; } @@ -235,5 +235,5 @@ impl TrieFactory { } /// Returns true iff the trie DB is a fat DB (allows enumeration of keys). - pub fn is_fat(&self) -> bool { self.spec == TrieSpec::Fat } + pub fn is_fat(&self) -> bool { self.spec == TrieSpec::Fat } } diff --git a/util/src/trie/recorder.rs b/util/src/trie/recorder.rs index 2f1d926f0..3930dd543 100644 --- a/util/src/trie/recorder.rs +++ b/util/src/trie/recorder.rs @@ -35,7 +35,6 @@ pub struct Record { /// These are used to record which nodes are visited during a trie query. /// Inline nodes are not to be recorded, as they are contained within their parent. pub trait Recorder { - /// Record that the given node has been visited. /// /// The depth parameter is the depth of the visited node, with the root node having depth 0. @@ -58,6 +57,7 @@ impl Recorder for NoOp { /// A simple recorder. Does nothing fancy but fulfills the `Recorder` interface /// properly. +#[derive(Debug)] pub struct BasicRecorder { nodes: Vec, min_depth: u32,