diff --git a/ethcore/light/src/client/mod.rs b/ethcore/light/src/client/mod.rs index 2872e0eec..34f7ed990 100644 --- a/ethcore/light/src/client/mod.rs +++ b/ethcore/light/src/client/mod.rs @@ -315,50 +315,3 @@ impl LightChainClient for Client { Client::cht_root(self, i) } } - -// dummy implementation, should be removed when a `TestClient` is added. -impl ::provider::Provider for Client { - fn chain_info(&self) -> BlockChainInfo { - Client::chain_info(self) - } - - fn reorg_depth(&self, _a: &H256, _b: &H256) -> Option { - None - } - - fn earliest_state(&self) -> Option { - None - } - - fn block_header(&self, id: BlockId) -> Option { - Client::block_header(self, id) - } - - fn block_body(&self, _id: BlockId) -> Option { - None - } - - fn block_receipts(&self, _hash: &H256) -> Option { - None - } - - fn state_proof(&self, _req: ::request::StateProof) -> Vec { - Vec::new() - } - - fn contract_code(&self, _req: ::request::ContractCode) -> Bytes { - Vec::new() - } - - fn header_proof(&self, _req: ::request::HeaderProof) -> Option<(encoded::Header, Vec)> { - None - } - - fn transaction_proof(&self, _req: ::request::TransactionProof) -> Option> { - None - } - - fn ready_transactions(&self) -> Vec<::ethcore::transaction::PendingTransaction> { - Vec::new() - } -} diff --git a/ethcore/light/src/provider.rs b/ethcore/light/src/provider.rs index 3f55a6b99..4e43296ab 100644 --- a/ethcore/light/src/provider.rs +++ b/ethcore/light/src/provider.rs @@ -59,10 +59,10 @@ pub trait Provider: Send + Sync { /// /// The returned vector may have any length in the range [0, `max`], but the /// results within must adhere to the `skip` and `reverse` parameters. - fn block_headers(&self, req: request::Headers) -> Vec { + fn block_headers(&self, req: request::CompleteHeadersRequest) -> Option { use request::HashOrNumber; - if req.max == 0 { return Vec::new() } + if req.max == 0 { return None } let best_num = self.chain_info().best_block_number; let start_num = match req.start { @@ -70,7 +70,7 @@ pub trait Provider: Send + Sync { HashOrNumber::Hash(hash) => match self.block_header(BlockId::Hash(hash)) { None => { trace!(target: "les_provider", "Unknown block hash {} requested", hash); - return Vec::new(); + return None; } Some(header) => { let num = header.number(); @@ -79,7 +79,9 @@ pub trait Provider: Send + Sync { if req.max == 1 || canon_hash != Some(hash) { // Non-canonical header or single header requested. - return vec![header]; + return Some(::request::HeadersResponse { + headers: vec![header], + }) } num @@ -87,109 +89,39 @@ pub trait Provider: Send + Sync { } }; - (0u64..req.max as u64) + let headers = (0u64..req.max as u64) .map(|x: u64| x.saturating_mul(req.skip + 1)) .take_while(|x| if req.reverse { x < &start_num } else { best_num.saturating_sub(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() + .collect(); + + Some(::request::HeadersResponse { headers: headers }) } /// Get a block header by id. fn block_header(&self, id: BlockId) -> Option; - /// Provide as many as possible of the requested blocks (minus the headers) encoded - /// in RLP format. - fn block_bodies(&self, req: request::Bodies) -> Vec> { - req.block_hashes.into_iter() - .map(|hash| self.block_body(BlockId::Hash(hash))) - .collect() - } + /// Fulfill a block body request. + fn block_body(&self, req: request::CompleteBodyRequest) -> Option; - /// Get a block body by id. - fn block_body(&self, id: BlockId) -> Option; + /// Fulfill a request for block receipts. + fn block_receipts(&self, req: request::CompleteReceiptsRequest) -> Option; - /// Provide the receipts as many as possible of the requested blocks. - /// Returns a vector of RLP-encoded lists of receipts. - fn receipts(&self, req: request::Receipts) -> Vec { - req.block_hashes.into_iter() - .map(|hash| self.block_receipts(&hash)) - .map(|receipts| receipts.unwrap_or_else(|| ::rlp::EMPTY_LIST_RLP.to_vec())) - .collect() - } + /// Get an account proof. + fn account_proof(&self, req: request::CompleteAccountRequest) -> Option; - /// Get a block's receipts as an RLP-encoded list by block hash. - fn block_receipts(&self, hash: &H256) -> Option; + /// Get a storage proof. + fn storage_proof(&self, req: request::CompleteStorageRequest) -> Option; - /// Provide a set of merkle proofs, as requested. Each request is a - /// block hash and request parameters. - /// - /// Returns a vector of RLP-encoded lists satisfying the requests. - fn proofs(&self, req: request::StateProofs) -> Vec { - use rlp::{RlpStream, Stream}; - - let mut results = Vec::with_capacity(req.requests.len()); - - for request in req.requests { - let proof = self.state_proof(request); - - let mut stream = RlpStream::new_list(proof.len()); - for node in proof { - stream.append_raw(&node, 1); - } - - results.push(stream.out()); - } - - results - } - - /// Get a state proof from a request. Each proof should be a vector - /// of rlp-encoded trie nodes, in ascending order by distance from the root. - fn state_proof(&self, req: request::StateProof) -> Vec; - - /// Provide contract code for the specified (block_hash, account_hash) pairs. - /// Each item in the resulting vector is either the raw bytecode or empty. - fn contract_codes(&self, req: request::ContractCodes) -> Vec { - req.code_requests.into_iter() - .map(|req| self.contract_code(req)) - .collect() - } - - /// Get contract code by request. Either the raw bytecode or empty. - fn contract_code(&self, req: request::ContractCode) -> Bytes; - - /// Provide header proofs from the Canonical Hash Tries as well as the headers - /// they correspond to -- each element in the returned vector is a 2-tuple. - /// The first element is a block header and the second a merkle proof of - /// the header in a requested CHT. - fn header_proofs(&self, req: request::HeaderProofs) -> Vec { - use rlp::{self, RlpStream, Stream}; - - req.requests.into_iter() - .map(|req| self.header_proof(req)) - .map(|maybe_proof| match maybe_proof { - None => rlp::EMPTY_LIST_RLP.to_vec(), - Some((header, proof)) => { - let mut stream = RlpStream::new_list(2); - stream.append_raw(&header.into_inner(), 1).begin_list(proof.len()); - - for node in proof { - stream.append_raw(&node, 1); - } - - stream.out() - } - }) - .collect() - } + /// Provide contract code for the specified (block_hash, code_hash) pair. + fn contract_code(&self, req: request::CompleteCodeRequest) -> Option; /// Provide a header proof from a given Canonical Hash Trie as well as the - /// corresponding header. The first element is the block header and the - /// second is a merkle proof of the CHT. - fn header_proof(&self, req: request::HeaderProof) -> Option<(encoded::Header, Vec)>; + /// corresponding header. + fn header_proof(&self, req: request::CompleteHeaderProofRequest) -> Option; /// Provide pending transactions. fn ready_transactions(&self) -> Vec; @@ -217,32 +149,52 @@ impl Provider for T { BlockChainClient::block_header(self, id) } - fn block_body(&self, id: BlockId) -> Option { + fn block_body(&self, req: request::CompleteBodyRequest) -> Option; BlockChainClient::block_body(self, id) + .map(|body| ::request::BodyResponse { body: body }) } - fn block_receipts(&self, hash: &H256) -> Option { + fn block_receipts(&self, req: request::CompleteReceiptsRequest) -> Option; BlockChainClient::block_receipts(self, hash) + .map(|x| ::request::ReceiptsResponse { receipts: ::rlp::decode(&x) }) } - fn state_proof(&self, req: request::StateProof) -> Vec { - match req.key2 { - Some(key2) => self.prove_storage(req.key1, key2, req.from_level, BlockId::Hash(req.block)), - None => self.prove_account(req.key1, req.from_level, BlockId::Hash(req.block)), - } + fn account_proof(&self, req: request::CompleteAccountRequest) -> Option { + self.prove_account(req.address_hash, BlockId::Hash(req.block_hash)).map(|(proof, acc)| { + ::request::AccountResponse { + proof: proof, + nonce: acc.nonce, + balance: acc.balance, + code_hash: acc.code_hash, + storage_root: acc.storage_root, + } + })) } - fn contract_code(&self, req: request::ContractCode) -> Bytes { - self.code_by_hash(req.account_key, BlockId::Hash(req.block_hash)) + fn storage_proof(&self, req: request::CompleteStorageRequest) -> Option { + self.prove_account(req.address_hash, req.key_hash, BlockId::Hash(req.block_hash)).map(|(proof, item) | { + ::request::StorageResponse { + proof: proof, + value: item, + } + })) } - fn header_proof(&self, req: request::HeaderProof) -> Option<(encoded::Header, Vec)> { - 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; - } + fn contract_code(&self, req: request::ContractCode) -> Option { + self.state_data(&req.code_hash) + .map(|code| ::request::CodeResponse { code: code }) + } - let mut needed_hdr = None; + fn header_proof(&self, req: request::CompleteHeaderProofRequest) -> Option; + let cht_number = match cht::block_to_cht_number(req.num) { + Some(cht_num) => cht_num, + None => { + debug!(target: "les_provider", "Requested CHT proof with invalid block number"); + return None; + } + }; + + let mut needed = None; // build the CHT, caching the requested header as we pass through it. let cht = { @@ -258,8 +210,8 @@ impl Provider for T { total_difficulty: td, }; - if hdr.number() == req.block_number { - needed_hdr = Some(hdr); + if hdr.number() == req.num { + needed = Some((hdr, td)); } Some(info) @@ -268,17 +220,21 @@ impl Provider for T { } }; - match cht::build(req.cht_number, block_info) { + match cht::build(cht_number, block_info) { Some(cht) => cht, None => return None, // incomplete CHT. } }; - let needed_hdr = needed_hdr.expect("`needed_hdr` always set in loop, number checked before; qed"); + let (needed_hdr, needed_td) = needed.expect("`needed` always set in loop, number checked before; qed"); // prove our result. - match cht.prove(req.block_number, req.from_level) { - Ok(Some(proof)) => Some((needed_hdr, proof)), + match cht.prove(req.num, 0) { + Ok(Some(proof)) => Some(::request::HeaderProofResponse { + proof: proof, + hash: needed_hdr.hash(), + td: needed_td, + }), Ok(None) => None, Err(e) => { debug!(target: "les_provider", "Error looking up number in freshly-created CHT: {}", e); @@ -347,23 +303,27 @@ impl Provider for LightProvider { self.client.as_light_client().block_header(id) } - fn block_body(&self, _id: BlockId) -> Option { + fn block_body(&self, req: request::CompleteBodyRequest) -> Option { None } - fn block_receipts(&self, _hash: &H256) -> Option { + fn block_receipts(&self, req: request::CompleteReceiptsRequest) -> Option { None } - fn state_proof(&self, _req: request::StateProof) -> Vec { - Vec::new() + fn account_proof(&self, req: request::CompleteAccountRequest) -> Option { + None } - fn contract_code(&self, _req: request::ContractCode) -> Bytes { - Vec::new() + fn storage_proof(&self, req: request::CompleteStorageRequest) -> Option { + None } - fn header_proof(&self, _req: request::HeaderProof) -> Option<(encoded::Header, Vec)> { + fn contract_code(&self, req: request::CompleteCodeRequest) -> Option { + None + } + + fn header_proof(&self, req: request::CompleteHeaderProofRequest) -> Option { None } diff --git a/ethcore/light/src/types/request.rs b/ethcore/light/src/types/request.rs index 259f3def7..2b23d0380 100644 --- a/ethcore/light/src/types/request.rs +++ b/ethcore/light/src/types/request.rs @@ -113,6 +113,15 @@ pub enum Output { Number(u64), } +impl Output { + fn kind(&self) -> OutputKind { + match *self { + Output::Hash(_) => OutputKind::Hash, + Output::Number(_) => OutputKind::Number, + } + } +} + /// Response output kinds which can be used as back-references. #[derive(Debug, Clone, PartialEq, Eq)] pub enum OutputKind { @@ -145,6 +154,7 @@ impl From for HashOrNumber { } /// All request types, as they're sent over the network. +#[derive(Debug, Clone, PartialEq, Eq)] pub enum Request { /// A request for block headers. Headers(IncompleteHeadersRequest), @@ -164,6 +174,27 @@ pub enum Request { // Transaction proof. } +/// All request types, as they're sent over the network. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum CompleteRequest { + /// A request for block headers. + Headers(CompleteHeadersRequest), + /// A request for a header proof (from a CHT) + HeaderProof(CompleteHeaderProofRequest), + // TransactionIndex, + /// A request for a block's receipts. + Receipts(CompleteReceiptsRequest), + /// A request for a block body. + Body(CompleteBodyRequest), + /// A request for a merkle proof of an account. + Account(CompleteAccountRequest), + /// A request for a merkle proof of contract storage. + Storage(CompleteStorageRequest), + /// A request for contract code. + Code(CompleteCodeRequest), + // Transaction proof. +} + impl Request { fn kind(&self) -> RequestKind { match *self { @@ -210,10 +241,54 @@ impl Encodable for Request { } } +impl IncompleteRequest for Request { + type Complete = CompleteRequest; + + fn check_outputs(&self, f: F) -> Result<(), NoSuchOutput> + where F: FnMut(usize, usize, OutputKind) -> Result<(), NoSuchOutput> + { + match *self { + Request::Headers(ref req) => req.check_outputs(f), + Request::HeaderProof(ref req) => req.check_outputs(f), + Request::Receipts(ref req) => req.check_outputs(f), + Request::Body(ref req) => req.check_outputs(f), + Request::Account(ref req) => req.check_outputs(f), + Request::Storage(ref req) => req.check_outputs(f), + Request::Code(ref req) => req.check_outputs(f), + } + } + + fn note_outputs(&self, f: F) where F: FnMut(usize, OutputKind) { + match *self { + Request::Headers(ref req) => req.note_outputs(f), + Request::HeaderProof(ref req) => req.note_outputs(f), + Request::Receipts(ref req) => req.note_outputs(f), + Request::Body(ref req) => req.note_outputs(f), + Request::Account(ref req) => req.note_outputs(f), + Request::Storage(ref req) => req.note_outputs(f), + Request::Code(ref req) => req.note_outputs(f), + } + } + + fn fill(self, oracle: F) -> Result + where F: Fn(usize, usize) -> Result + { + match self { + Request::Headers(req) => CompleteRequest::Headers(req.fill(oracle)), + Request::HeaderProof(req) => CompleteRequest::HeaderProof(req.fill(oracle)), + Request::Receipts(req) => CompleteRequest::Receipts(req.fill(oracle)), + Request::Body(req) => CompleteRequest::Body(req.fill(oracle)), + Request::Account(req) => CompleteRequest::Account(req.fill(oracle)), + Request::Storage(req) => CompleteRequest::Storage(req.fill(oracle)), + Request::Code(req) => CompleteRequest::Code(req.fill(oracle)), + } + } +} /// Kinds of requests. /// Doubles as the "ID" field of the request. #[repr(u8)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum RequestKind { /// A request for headers. Headers = 0, @@ -252,6 +327,42 @@ impl Encodable for RequestKind { } } +/// All response types. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Response { + /// A response for block headers. + Headers(HeadersResponse), + /// A response for a header proof (from a CHT) + HeaderProof(HeaderProofResponse), + // TransactionIndex, + /// A response for a block's receipts. + Receipts(ReceiptsResponse), + /// A response for a block body. + Body(BodyResponse), + /// A response for a merkle proof of an account. + Account(AccountResponse), + /// A response for a merkle proof of contract storage. + Storage(StorageResponse), + /// A response for contract code. + Code(CodeResponse), + // Transaction proof. +} + +impl Response { + /// Fill reusable outputs by writing them into the function. + pub fn fill_outputs(&self, mut f: F) where F: FnMut(usize, Output) { + match *self { + Response::Headers(res) => res.fill_outputs(f) + Response::HeaderProof(res) => res.fill_outputs(f) + Response::Receipts(res) => res.fill_outputs(f) + Response::Body(res) => res.fill_outputs(f) + Response::Account(res) => res.fill_outputs(f) + Response::Storage(res) => res.fill_outputs(f) + Response::Code(res) => res.fill_outputs(f) + } + } +} + /// A potentially incomplete request. pub trait IncompleteRequest: Sized { type Complete; @@ -369,7 +480,8 @@ pub mod header { /// The output of a request for headers. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Response { - header: Vec, + /// The headers requested. + pub headers: Vec, } impl Response { @@ -523,7 +635,6 @@ pub mod block_receipts { hash: hash, }) } - } /// A complete block receipts request.