Better support for eth_getLogs in light mode (#9186)
* Light client on-demand request for headers range. * Cache headers in HeaderWithAncestors response. Also fulfills request locally if all headers are in cache. * LightFetch::logs fetches missing headers on demand. * LightFetch::logs limit the number of headers requested at a time. * LightFetch::logs refactor header fetching logic. * Enforce limit on header range length in light client logs request. * Fix light request tests after struct change. * Respond to review comments.
This commit is contained in:
		
							parent
							
								
									7abe9ec4cc
								
							
						
					
					
						commit
						9ed43230ca
					
				
							
								
								
									
										1
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							@ -2146,6 +2146,7 @@ dependencies = [
 | 
			
		||||
 "ethstore 0.2.0",
 | 
			
		||||
 "fake-fetch 0.0.1",
 | 
			
		||||
 "fake-hardware-wallet 0.0.1",
 | 
			
		||||
 "fastmap 0.1.0",
 | 
			
		||||
 "fetch 0.1.0",
 | 
			
		||||
 "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)",
 | 
			
		||||
 "futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
 | 
			
		||||
 | 
			
		||||
@ -43,7 +43,7 @@ pub mod provider;
 | 
			
		||||
mod types;
 | 
			
		||||
 | 
			
		||||
pub use self::cache::Cache;
 | 
			
		||||
pub use self::provider::Provider;
 | 
			
		||||
pub use self::provider::{Provider, MAX_HEADERS_PER_REQUEST};
 | 
			
		||||
pub use self::transaction_queue::TransactionQueue;
 | 
			
		||||
pub use types::request as request;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -204,6 +204,8 @@ fn guess_capabilities(requests: &[CheckedRequest]) -> Capabilities {
 | 
			
		||||
				caps.serve_headers = true,
 | 
			
		||||
			CheckedRequest::HeaderByHash(_, _) =>
 | 
			
		||||
				caps.serve_headers = true,
 | 
			
		||||
			CheckedRequest::HeaderWithAncestors(_, _) =>
 | 
			
		||||
				caps.serve_headers = true,
 | 
			
		||||
			CheckedRequest::TransactionIndex(_, _) => {} // hashes yield no info.
 | 
			
		||||
			CheckedRequest::Signal(_, _) =>
 | 
			
		||||
				caps.serve_headers = true,
 | 
			
		||||
 | 
			
		||||
@ -16,6 +16,7 @@
 | 
			
		||||
 | 
			
		||||
//! Request types, verification, and verification errors.
 | 
			
		||||
 | 
			
		||||
use std::cmp;
 | 
			
		||||
use std::sync::Arc;
 | 
			
		||||
 | 
			
		||||
use bytes::Bytes;
 | 
			
		||||
@ -47,6 +48,8 @@ pub enum Request {
 | 
			
		||||
	HeaderProof(HeaderProof),
 | 
			
		||||
	/// A request for a header by hash.
 | 
			
		||||
	HeaderByHash(HeaderByHash),
 | 
			
		||||
	/// A request for a header by hash with a range of its ancestors.
 | 
			
		||||
	HeaderWithAncestors(HeaderWithAncestors),
 | 
			
		||||
	/// A request for the index of a transaction.
 | 
			
		||||
	TransactionIndex(TransactionIndex),
 | 
			
		||||
	/// A request for block receipts.
 | 
			
		||||
@ -136,6 +139,7 @@ macro_rules! impl_single {
 | 
			
		||||
// implement traits for each kind of request.
 | 
			
		||||
impl_single!(HeaderProof, HeaderProof, (H256, U256));
 | 
			
		||||
impl_single!(HeaderByHash, HeaderByHash, encoded::Header);
 | 
			
		||||
impl_single!(HeaderWithAncestors, HeaderWithAncestors, Vec<encoded::Header>);
 | 
			
		||||
impl_single!(TransactionIndex, TransactionIndex, net_request::TransactionIndexResponse);
 | 
			
		||||
impl_single!(Receipts, BlockReceipts, Vec<Receipt>);
 | 
			
		||||
impl_single!(Body, Body, encoded::Block);
 | 
			
		||||
@ -246,6 +250,7 @@ impl From<encoded::Header> for HeaderRef {
 | 
			
		||||
pub enum CheckedRequest {
 | 
			
		||||
	HeaderProof(HeaderProof, net_request::IncompleteHeaderProofRequest),
 | 
			
		||||
	HeaderByHash(HeaderByHash, net_request::IncompleteHeadersRequest),
 | 
			
		||||
	HeaderWithAncestors(HeaderWithAncestors, net_request::IncompleteHeadersRequest),
 | 
			
		||||
	TransactionIndex(TransactionIndex, net_request::IncompleteTransactionIndexRequest),
 | 
			
		||||
	Receipts(BlockReceipts, net_request::IncompleteReceiptsRequest),
 | 
			
		||||
	Body(Body, net_request::IncompleteBodyRequest),
 | 
			
		||||
@ -268,6 +273,16 @@ impl From<Request> for CheckedRequest {
 | 
			
		||||
				trace!(target: "on_demand", "HeaderByHash Request, {:?}", net_req);
 | 
			
		||||
				CheckedRequest::HeaderByHash(req, net_req)
 | 
			
		||||
			}
 | 
			
		||||
			Request::HeaderWithAncestors(req) => {
 | 
			
		||||
				let net_req = net_request::IncompleteHeadersRequest {
 | 
			
		||||
					start: req.block_hash.map(Into::into),
 | 
			
		||||
					skip: 0,
 | 
			
		||||
					max: req.ancestor_count + 1,
 | 
			
		||||
					reverse: true,
 | 
			
		||||
				};
 | 
			
		||||
				trace!(target: "on_demand", "HeaderWithAncestors Request, {:?}", net_req);
 | 
			
		||||
				CheckedRequest::HeaderWithAncestors(req, net_req)
 | 
			
		||||
			}
 | 
			
		||||
			Request::HeaderProof(req) => {
 | 
			
		||||
				let net_req = net_request::IncompleteHeaderProofRequest {
 | 
			
		||||
					num: req.num().into(),
 | 
			
		||||
@ -344,6 +359,7 @@ impl CheckedRequest {
 | 
			
		||||
		match self {
 | 
			
		||||
			CheckedRequest::HeaderProof(_, req) => NetRequest::HeaderProof(req),
 | 
			
		||||
			CheckedRequest::HeaderByHash(_, req) => NetRequest::Headers(req),
 | 
			
		||||
			CheckedRequest::HeaderWithAncestors(_, req) => NetRequest::Headers(req),
 | 
			
		||||
			CheckedRequest::TransactionIndex(_, req) => NetRequest::TransactionIndex(req),
 | 
			
		||||
			CheckedRequest::Receipts(_, req) => NetRequest::Receipts(req),
 | 
			
		||||
			CheckedRequest::Body(_, req) => NetRequest::Body(req),
 | 
			
		||||
@ -399,6 +415,27 @@ impl CheckedRequest {
 | 
			
		||||
 | 
			
		||||
				None
 | 
			
		||||
			}
 | 
			
		||||
			CheckedRequest::HeaderWithAncestors(_, ref req) => {
 | 
			
		||||
				if req.skip != 1 || !req.reverse {
 | 
			
		||||
					return None;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if let Some(&net_request::HashOrNumber::Hash(start)) = req.start.as_ref() {
 | 
			
		||||
					let mut result = Vec::with_capacity(req.max as usize);
 | 
			
		||||
					let mut hash = start;
 | 
			
		||||
					let mut cache = cache.lock();
 | 
			
		||||
					for _ in 0..req.max {
 | 
			
		||||
						match cache.block_header(&hash) {
 | 
			
		||||
							Some(header) => {
 | 
			
		||||
								hash = header.parent_hash();
 | 
			
		||||
								result.push(header);
 | 
			
		||||
							}
 | 
			
		||||
							None => return None,
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
					Some(Response::HeaderWithAncestors(result))
 | 
			
		||||
				} else { None }
 | 
			
		||||
			}
 | 
			
		||||
			CheckedRequest::Receipts(ref check, ref req) => {
 | 
			
		||||
				// empty transactions -> no receipts
 | 
			
		||||
				if check.0.as_ref().ok().map_or(false, |hdr| hdr.receipts_root() == KECCAK_NULL_RLP) {
 | 
			
		||||
@ -467,6 +504,7 @@ macro_rules! match_me {
 | 
			
		||||
		match $me {
 | 
			
		||||
			CheckedRequest::HeaderProof($check, $req) => $e,
 | 
			
		||||
			CheckedRequest::HeaderByHash($check, $req) => $e,
 | 
			
		||||
			CheckedRequest::HeaderWithAncestors($check, $req) => $e,
 | 
			
		||||
			CheckedRequest::TransactionIndex($check, $req) => $e,
 | 
			
		||||
			CheckedRequest::Receipts($check, $req) => $e,
 | 
			
		||||
			CheckedRequest::Body($check, $req) => $e,
 | 
			
		||||
@ -496,6 +534,15 @@ impl IncompleteRequest for CheckedRequest {
 | 
			
		||||
					_ => Ok(()),
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			CheckedRequest::HeaderWithAncestors(ref check, ref req) => {
 | 
			
		||||
				req.check_outputs(&mut f)?;
 | 
			
		||||
 | 
			
		||||
				// make sure the output given is definitively a hash.
 | 
			
		||||
				match check.block_hash {
 | 
			
		||||
					Field::BackReference(r, idx) => f(r, idx, OutputKind::Hash),
 | 
			
		||||
					_ => Ok(()),
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			CheckedRequest::TransactionIndex(_, ref req) => req.check_outputs(f),
 | 
			
		||||
			CheckedRequest::Receipts(_, ref req) => req.check_outputs(f),
 | 
			
		||||
			CheckedRequest::Body(_, ref req) => req.check_outputs(f),
 | 
			
		||||
@ -524,6 +571,10 @@ impl IncompleteRequest for CheckedRequest {
 | 
			
		||||
				trace!(target: "on_demand", "HeaderByHash request completed {:?}", req);
 | 
			
		||||
				req.complete().map(CompleteRequest::Headers)
 | 
			
		||||
			}
 | 
			
		||||
			CheckedRequest::HeaderWithAncestors(_, req) => {
 | 
			
		||||
				trace!(target: "on_demand", "HeaderWithAncestors request completed {:?}", req);
 | 
			
		||||
				req.complete().map(CompleteRequest::Headers)
 | 
			
		||||
			}
 | 
			
		||||
			CheckedRequest::TransactionIndex(_, req) => {
 | 
			
		||||
				trace!(target: "on_demand", "TransactionIndex request completed {:?}", req);
 | 
			
		||||
				req.complete().map(CompleteRequest::TransactionIndex)
 | 
			
		||||
@ -587,6 +638,9 @@ impl net_request::CheckedRequest for CheckedRequest {
 | 
			
		||||
			CheckedRequest::HeaderByHash(ref prover, _) =>
 | 
			
		||||
				expect!((&NetResponse::Headers(ref res), &CompleteRequest::Headers(ref req)) =>
 | 
			
		||||
					prover.check_response(cache, &req.start, &res.headers).map(Response::HeaderByHash)),
 | 
			
		||||
			CheckedRequest::HeaderWithAncestors(ref prover, _) =>
 | 
			
		||||
				expect!((&NetResponse::Headers(ref res), &CompleteRequest::Headers(ref req)) =>
 | 
			
		||||
					prover.check_response(cache, &req.start, &res.headers).map(Response::HeaderWithAncestors)),
 | 
			
		||||
			CheckedRequest::TransactionIndex(ref prover, _) =>
 | 
			
		||||
				expect!((&NetResponse::TransactionIndex(ref res), _) =>
 | 
			
		||||
					prover.check_response(cache, res).map(Response::TransactionIndex)),
 | 
			
		||||
@ -620,6 +674,8 @@ pub enum Response {
 | 
			
		||||
	HeaderProof((H256, U256)),
 | 
			
		||||
	/// Response to a header-by-hash request.
 | 
			
		||||
	HeaderByHash(encoded::Header),
 | 
			
		||||
	/// Response to a header-by-hash with ancestors request.
 | 
			
		||||
	HeaderWithAncestors(Vec<encoded::Header>),
 | 
			
		||||
	/// Response to a transaction-index request.
 | 
			
		||||
	TransactionIndex(net_request::TransactionIndexResponse),
 | 
			
		||||
	/// Response to a receipts request.
 | 
			
		||||
@ -661,6 +717,10 @@ pub enum Error {
 | 
			
		||||
	Decoder(::rlp::DecoderError),
 | 
			
		||||
	/// Empty response.
 | 
			
		||||
	Empty,
 | 
			
		||||
	/// Response data length exceeds request max.
 | 
			
		||||
	TooManyResults(u64, u64),
 | 
			
		||||
	/// Response data is incomplete.
 | 
			
		||||
	TooFewResults(u64, u64),
 | 
			
		||||
	/// Trie lookup error (result of bad proof)
 | 
			
		||||
	Trie(TrieError),
 | 
			
		||||
	/// Bad inclusion proof
 | 
			
		||||
@ -677,6 +737,8 @@ pub enum Error {
 | 
			
		||||
	WrongTrieRoot(H256, H256),
 | 
			
		||||
	/// Wrong response kind.
 | 
			
		||||
	WrongKind,
 | 
			
		||||
	/// Wrong sequence of headers.
 | 
			
		||||
	WrongHeaderSequence,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<::rlp::DecoderError> for Error {
 | 
			
		||||
@ -737,6 +799,65 @@ impl HeaderProof {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Request for a header by hash with a range of ancestors.
 | 
			
		||||
#[derive(Debug, Clone, PartialEq, Eq)]
 | 
			
		||||
pub struct HeaderWithAncestors {
 | 
			
		||||
	/// Hash of the last block in the range to fetch.
 | 
			
		||||
	pub block_hash: Field<H256>,
 | 
			
		||||
	/// Number of headers before the last block to fetch in addition.
 | 
			
		||||
	pub ancestor_count: u64,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl HeaderWithAncestors {
 | 
			
		||||
	/// Check a response for the headers.
 | 
			
		||||
	pub fn check_response(
 | 
			
		||||
		&self,
 | 
			
		||||
		cache: &Mutex<::cache::Cache>,
 | 
			
		||||
		start: &net_request::HashOrNumber,
 | 
			
		||||
		headers: &[encoded::Header]
 | 
			
		||||
	) -> Result<Vec<encoded::Header>, Error> {
 | 
			
		||||
		let expected_hash = match (self.block_hash, start) {
 | 
			
		||||
			(Field::Scalar(ref h), &net_request::HashOrNumber::Hash(ref h2)) => {
 | 
			
		||||
				if h != h2 { return Err(Error::WrongHash(*h, *h2)) }
 | 
			
		||||
				*h
 | 
			
		||||
			}
 | 
			
		||||
			(_, &net_request::HashOrNumber::Hash(h2)) => h2,
 | 
			
		||||
			_ => return Err(Error::HeaderByNumber),
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		let start_header = headers.first().ok_or(Error::Empty)?;
 | 
			
		||||
		let start_hash = start_header.hash();
 | 
			
		||||
		if start_hash != expected_hash {
 | 
			
		||||
			return Err(Error::WrongHash(expected_hash, start_hash));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		let expected_len = 1 + cmp::min(self.ancestor_count, start_header.number());
 | 
			
		||||
		let actual_len = headers.len() as u64;
 | 
			
		||||
		match actual_len.cmp(&expected_len) {
 | 
			
		||||
			cmp::Ordering::Less =>
 | 
			
		||||
				return Err(Error::TooFewResults(expected_len, actual_len)),
 | 
			
		||||
			cmp::Ordering::Greater =>
 | 
			
		||||
				return Err(Error::TooManyResults(expected_len, actual_len)),
 | 
			
		||||
			cmp::Ordering::Equal => (),
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		for (header, prev_header) in headers.iter().zip(headers[1..].iter()) {
 | 
			
		||||
			if header.number() != prev_header.number() + 1 ||
 | 
			
		||||
				header.parent_hash() != prev_header.hash()
 | 
			
		||||
			{
 | 
			
		||||
				return Err(Error::WrongHeaderSequence)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		let mut cache = cache.lock();
 | 
			
		||||
		for header in headers {
 | 
			
		||||
			cache.insert_block_header(header.hash(), header.clone());
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		Ok(headers.to_vec())
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Request for a header by hash.
 | 
			
		||||
#[derive(Debug, Clone, PartialEq, Eq)]
 | 
			
		||||
pub struct HeaderByHash(pub Field<H256>);
 | 
			
		||||
@ -1045,6 +1166,83 @@ mod tests {
 | 
			
		||||
		assert!(HeaderByHash(hash.into()).check_response(&cache, &hash.into(), &[raw_header]).is_ok())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	#[test]
 | 
			
		||||
	fn check_header_with_ancestors() {
 | 
			
		||||
		let mut last_header_hash = H256::default();
 | 
			
		||||
		let mut headers = (0..11).map(|num| {
 | 
			
		||||
			let mut header = Header::new();
 | 
			
		||||
			header.set_number(num);
 | 
			
		||||
			header.set_parent_hash(last_header_hash);
 | 
			
		||||
 | 
			
		||||
			last_header_hash = header.hash();
 | 
			
		||||
			header
 | 
			
		||||
		}).collect::<Vec<_>>();
 | 
			
		||||
 | 
			
		||||
		headers.reverse();  // because responses are in reverse order
 | 
			
		||||
 | 
			
		||||
		let raw_headers = headers.iter()
 | 
			
		||||
			.map(|hdr| encoded::Header::new(::rlp::encode(hdr).into_vec()))
 | 
			
		||||
			.collect::<Vec<_>>();
 | 
			
		||||
 | 
			
		||||
		let mut invalid_successor = Header::new();
 | 
			
		||||
		invalid_successor.set_number(11);
 | 
			
		||||
		invalid_successor.set_parent_hash(headers[1].hash());
 | 
			
		||||
 | 
			
		||||
		let raw_invalid_successor = encoded::Header::new(::rlp::encode(&invalid_successor).into_vec());
 | 
			
		||||
 | 
			
		||||
		let cache = Mutex::new(make_cache());
 | 
			
		||||
 | 
			
		||||
		let header_with_ancestors = |hash, count| {
 | 
			
		||||
			HeaderWithAncestors {
 | 
			
		||||
				block_hash: hash,
 | 
			
		||||
				ancestor_count: count
 | 
			
		||||
			}
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		// Correct responses
 | 
			
		||||
		assert!(header_with_ancestors(headers[0].hash().into(), 0)
 | 
			
		||||
				.check_response(&cache, &headers[0].hash().into(), &raw_headers[0..1]).is_ok());
 | 
			
		||||
		assert!(header_with_ancestors(headers[0].hash().into(), 2)
 | 
			
		||||
				.check_response(&cache, &headers[0].hash().into(), &raw_headers[0..3]).is_ok());
 | 
			
		||||
		assert!(header_with_ancestors(headers[0].hash().into(), 10)
 | 
			
		||||
				.check_response(&cache, &headers[0].hash().into(), &raw_headers[0..11]).is_ok());
 | 
			
		||||
		assert!(header_with_ancestors(headers[2].hash().into(), 2)
 | 
			
		||||
				.check_response(&cache, &headers[2].hash().into(), &raw_headers[2..5]).is_ok());
 | 
			
		||||
		assert!(header_with_ancestors(headers[2].hash().into(), 10)
 | 
			
		||||
				.check_response(&cache, &headers[2].hash().into(), &raw_headers[2..11]).is_ok());
 | 
			
		||||
		assert!(header_with_ancestors(invalid_successor.hash().into(), 0)
 | 
			
		||||
				.check_response(&cache, &invalid_successor.hash().into(), &[raw_invalid_successor.clone()]).is_ok());
 | 
			
		||||
 | 
			
		||||
		// Incorrect responses
 | 
			
		||||
		assert_eq!(header_with_ancestors(invalid_successor.hash().into(), 0)
 | 
			
		||||
				   .check_response(&cache, &headers[0].hash().into(), &raw_headers[0..1]),
 | 
			
		||||
				   Err(Error::WrongHash(invalid_successor.hash(), headers[0].hash())));
 | 
			
		||||
		assert_eq!(header_with_ancestors(headers[0].hash().into(), 0)
 | 
			
		||||
				   .check_response(&cache, &headers[0].hash().into(), &[]),
 | 
			
		||||
				   Err(Error::Empty));
 | 
			
		||||
		assert_eq!(header_with_ancestors(headers[0].hash().into(), 10)
 | 
			
		||||
				   .check_response(&cache, &headers[0].hash().into(), &raw_headers[0..10]),
 | 
			
		||||
				   Err(Error::TooFewResults(11, 10)));
 | 
			
		||||
		assert_eq!(header_with_ancestors(headers[0].hash().into(), 9)
 | 
			
		||||
				   .check_response(&cache, &headers[0].hash().into(), &raw_headers[0..11]),
 | 
			
		||||
				   Err(Error::TooManyResults(10, 11)));
 | 
			
		||||
 | 
			
		||||
		let response = &[raw_headers[0].clone(), raw_headers[2].clone()];
 | 
			
		||||
		assert_eq!(header_with_ancestors(headers[0].hash().into(), 1)
 | 
			
		||||
				   .check_response(&cache, &headers[0].hash().into(), response),
 | 
			
		||||
				   Err(Error::WrongHeaderSequence));
 | 
			
		||||
 | 
			
		||||
		let response = &[raw_invalid_successor.clone(), raw_headers[0].clone()];
 | 
			
		||||
		assert_eq!(header_with_ancestors(invalid_successor.hash().into(), 1)
 | 
			
		||||
				   .check_response(&cache, &invalid_successor.hash().into(), response),
 | 
			
		||||
				   Err(Error::WrongHeaderSequence));
 | 
			
		||||
 | 
			
		||||
		let response = &[raw_invalid_successor.clone(), raw_headers[1].clone()];
 | 
			
		||||
		assert_eq!(header_with_ancestors(invalid_successor.hash().into(), 1)
 | 
			
		||||
				   .check_response(&cache, &invalid_successor.hash().into(), response),
 | 
			
		||||
				   Err(Error::WrongHeaderSequence));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	#[test]
 | 
			
		||||
	fn check_body() {
 | 
			
		||||
		use rlp::RlpStream;
 | 
			
		||||
 | 
			
		||||
@ -33,6 +33,9 @@ use transaction_queue::TransactionQueue;
 | 
			
		||||
 | 
			
		||||
use request;
 | 
			
		||||
 | 
			
		||||
/// Maximum allowed size of a headers request.
 | 
			
		||||
pub const MAX_HEADERS_PER_REQUEST: u64 = 512;
 | 
			
		||||
 | 
			
		||||
/// Defines the operations that a provider for the light subprotocol must fulfill.
 | 
			
		||||
pub trait Provider: Send + Sync {
 | 
			
		||||
	/// Provide current blockchain info.
 | 
			
		||||
@ -54,7 +57,6 @@ pub trait Provider: Send + Sync {
 | 
			
		||||
	/// results within must adhere to the `skip` and `reverse` parameters.
 | 
			
		||||
	fn block_headers(&self, req: request::CompleteHeadersRequest) -> Option<request::HeadersResponse> {
 | 
			
		||||
		use request::HashOrNumber;
 | 
			
		||||
		const MAX_HEADERS_TO_SEND: u64 = 512;
 | 
			
		||||
 | 
			
		||||
		if req.max == 0 { return None }
 | 
			
		||||
 | 
			
		||||
@ -83,7 +85,7 @@ pub trait Provider: Send + Sync {
 | 
			
		||||
			}
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		let max = ::std::cmp::min(MAX_HEADERS_TO_SEND, req.max);
 | 
			
		||||
		let max = ::std::cmp::min(MAX_HEADERS_PER_REQUEST, req.max);
 | 
			
		||||
 | 
			
		||||
		let headers: Vec<_> = (0u64..max)
 | 
			
		||||
			.map(|x: u64| x.saturating_mul(req.skip.saturating_add(1)))
 | 
			
		||||
 | 
			
		||||
@ -37,6 +37,7 @@ jsonrpc-pubsub = { git = "https://github.com/paritytech/jsonrpc.git", branch = "
 | 
			
		||||
 | 
			
		||||
ethash = { path = "../ethash" }
 | 
			
		||||
ethcore = { path = "../ethcore", features = ["test-helpers"] }
 | 
			
		||||
fastmap = { path = "../util/fastmap" }
 | 
			
		||||
parity-bytes = { git = "https://github.com/paritytech/parity-common" }
 | 
			
		||||
parity-crypto = { git = "https://github.com/paritytech/parity-common" }
 | 
			
		||||
ethcore-devtools = { path = "../devtools" }
 | 
			
		||||
 | 
			
		||||
@ -44,6 +44,7 @@ extern crate jsonrpc_pubsub;
 | 
			
		||||
 | 
			
		||||
extern crate ethash;
 | 
			
		||||
extern crate ethcore;
 | 
			
		||||
extern crate fastmap;
 | 
			
		||||
extern crate parity_bytes as bytes;
 | 
			
		||||
extern crate parity_crypto as crypto;
 | 
			
		||||
extern crate ethcore_devtools as devtools;
 | 
			
		||||
 | 
			
		||||
@ -101,6 +101,14 @@ pub fn request_rejected_limit() -> Error {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn request_rejected_param_limit(limit: u64, items_desc: &str) -> Error {
 | 
			
		||||
	Error {
 | 
			
		||||
		code: ErrorCode::ServerError(codes::REQUEST_REJECTED_LIMIT),
 | 
			
		||||
		message: format!("Requested data size exceeds limit of {} {}.", limit, items_desc),
 | 
			
		||||
		data: None,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn account<T: fmt::Debug>(error: &str, details: T) -> Error {
 | 
			
		||||
	Error {
 | 
			
		||||
		code: ErrorCode::ServerError(codes::ACCOUNT_ERROR),
 | 
			
		||||
 | 
			
		||||
@ -16,6 +16,7 @@
 | 
			
		||||
 | 
			
		||||
//! Helpers for fetching blockchain data either from the light client or the network.
 | 
			
		||||
 | 
			
		||||
use std::cmp;
 | 
			
		||||
use std::sync::Arc;
 | 
			
		||||
 | 
			
		||||
use ethcore::basic_account::BasicAccount;
 | 
			
		||||
@ -31,7 +32,7 @@ use jsonrpc_macros::Trailing;
 | 
			
		||||
 | 
			
		||||
use light::cache::Cache;
 | 
			
		||||
use light::client::LightChainClient;
 | 
			
		||||
use light::cht;
 | 
			
		||||
use light::{cht, MAX_HEADERS_PER_REQUEST};
 | 
			
		||||
use light::on_demand::{
 | 
			
		||||
	request, OnDemand, HeaderRef, Request as OnDemandRequest,
 | 
			
		||||
	Response as OnDemandResponse, ExecutionResult,
 | 
			
		||||
@ -42,6 +43,7 @@ use sync::LightSync;
 | 
			
		||||
use ethereum_types::{U256, Address};
 | 
			
		||||
use hash::H256;
 | 
			
		||||
use parking_lot::Mutex;
 | 
			
		||||
use fastmap::H256FastMap;
 | 
			
		||||
use transaction::{Action, Transaction as EthTransaction, SignedTransaction, LocalizedTransaction};
 | 
			
		||||
 | 
			
		||||
use v1::helpers::{CallRequest as CallRequestHelper, errors, dispatch};
 | 
			
		||||
@ -299,78 +301,67 @@ impl LightFetch {
 | 
			
		||||
		use std::collections::BTreeMap;
 | 
			
		||||
		use jsonrpc_core::futures::stream::{self, Stream};
 | 
			
		||||
 | 
			
		||||
		// early exit for "to" block before "from" block.
 | 
			
		||||
		let best_number = self.client.chain_info().best_block_number;
 | 
			
		||||
		let block_number = |id| match id {
 | 
			
		||||
			BlockId::Earliest => Some(0),
 | 
			
		||||
			BlockId::Latest => Some(best_number),
 | 
			
		||||
			BlockId::Hash(h) => self.client.block_header(BlockId::Hash(h)).map(|hdr| hdr.number()),
 | 
			
		||||
			BlockId::Number(x) => Some(x),
 | 
			
		||||
		};
 | 
			
		||||
		const MAX_BLOCK_RANGE: u64 = 1000;
 | 
			
		||||
 | 
			
		||||
		let (from_block_number, from_block_header) = match self.client.block_header(filter.from_block) {
 | 
			
		||||
			Some(from) => (from.number(), from),
 | 
			
		||||
			None => return Either::A(future::err(errors::unknown_block())),
 | 
			
		||||
		};
 | 
			
		||||
		let fetcher = self.clone();
 | 
			
		||||
		self.headers_range_by_block_id(filter.from_block, filter.to_block, MAX_BLOCK_RANGE)
 | 
			
		||||
			.and_then(move |mut headers| {
 | 
			
		||||
				if headers.is_empty() {
 | 
			
		||||
					return Either::A(future::ok(Vec::new()));
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
		match block_number(filter.to_block) {
 | 
			
		||||
			Some(to) if to < from_block_number || from_block_number > best_number 
 | 
			
		||||
				=> return Either::A(future::ok(Vec::new())),
 | 
			
		||||
			Some(_) => (),
 | 
			
		||||
			_ => return Either::A(future::err(errors::unknown_block())),
 | 
			
		||||
		}
 | 
			
		||||
				let on_demand = &fetcher.on_demand;
 | 
			
		||||
 | 
			
		||||
		let maybe_future = self.sync.with_context(move |ctx| {
 | 
			
		||||
			// find all headers which match the filter, and fetch the receipts for each one.
 | 
			
		||||
			// match them with their numbers for easy sorting later.
 | 
			
		||||
			let bit_combos = filter.bloom_possibilities();
 | 
			
		||||
			let receipts_futures: Vec<_> = self.client.ancestry_iter(filter.to_block)
 | 
			
		||||
				.take_while(|ref hdr| hdr.number() != from_block_number)
 | 
			
		||||
				.chain(Some(from_block_header))
 | 
			
		||||
				.filter(|ref hdr| {
 | 
			
		||||
					let hdr_bloom = hdr.log_bloom();
 | 
			
		||||
					bit_combos.iter().any(|bloom| hdr_bloom.contains_bloom(bloom))
 | 
			
		||||
				})
 | 
			
		||||
				.map(|hdr| (hdr.number(), hdr.hash(), request::BlockReceipts(hdr.into())))
 | 
			
		||||
				.map(|(num, hash, req)| self.on_demand.request(ctx, req).expect(NO_INVALID_BACK_REFS).map(move |x| (num, hash, x)))
 | 
			
		||||
				.collect();
 | 
			
		||||
				let maybe_future = fetcher.sync.with_context(move |ctx| {
 | 
			
		||||
					// find all headers which match the filter, and fetch the receipts for each one.
 | 
			
		||||
					// match them with their numbers for easy sorting later.
 | 
			
		||||
					let bit_combos = filter.bloom_possibilities();
 | 
			
		||||
					let receipts_futures: Vec<_> = headers.drain(..)
 | 
			
		||||
						.filter(|ref hdr| {
 | 
			
		||||
							let hdr_bloom = hdr.log_bloom();
 | 
			
		||||
							bit_combos.iter().any(|bloom| hdr_bloom.contains_bloom(bloom))
 | 
			
		||||
						})
 | 
			
		||||
						.map(|hdr| (hdr.number(), hdr.hash(), request::BlockReceipts(hdr.into())))
 | 
			
		||||
						.map(|(num, hash, req)| on_demand.request(ctx, req).expect(NO_INVALID_BACK_REFS).map(move |x| (num, hash, x)))
 | 
			
		||||
						.collect();
 | 
			
		||||
 | 
			
		||||
			// as the receipts come in, find logs within them which match the filter.
 | 
			
		||||
			// insert them into a BTreeMap to maintain order by number and block index.
 | 
			
		||||
			stream::futures_unordered(receipts_futures)
 | 
			
		||||
				.fold(BTreeMap::new(), move |mut matches, (num, hash, receipts)| {
 | 
			
		||||
					let mut block_index = 0;
 | 
			
		||||
					for (transaction_index, receipt) in receipts.into_iter().enumerate() {
 | 
			
		||||
						for (transaction_log_index, log) in receipt.logs.into_iter().enumerate() {
 | 
			
		||||
							if filter.matches(&log) {
 | 
			
		||||
								matches.insert((num, block_index), Log {
 | 
			
		||||
									address: log.address.into(),
 | 
			
		||||
									topics: log.topics.into_iter().map(Into::into).collect(),
 | 
			
		||||
									data: log.data.into(),
 | 
			
		||||
									block_hash: Some(hash.into()),
 | 
			
		||||
									block_number: Some(num.into()),
 | 
			
		||||
									// No way to easily retrieve transaction hash, so let's just skip it.
 | 
			
		||||
									transaction_hash: None,
 | 
			
		||||
									transaction_index: Some(transaction_index.into()),
 | 
			
		||||
									log_index: Some(block_index.into()),
 | 
			
		||||
									transaction_log_index: Some(transaction_log_index.into()),
 | 
			
		||||
									log_type: "mined".into(),
 | 
			
		||||
									removed: false,
 | 
			
		||||
								});
 | 
			
		||||
					// as the receipts come in, find logs within them which match the filter.
 | 
			
		||||
					// insert them into a BTreeMap to maintain order by number and block index.
 | 
			
		||||
					stream::futures_unordered(receipts_futures)
 | 
			
		||||
						.fold(BTreeMap::new(), move |mut matches, (num, hash, receipts)| {
 | 
			
		||||
							let mut block_index = 0;
 | 
			
		||||
							for (transaction_index, receipt) in receipts.into_iter().enumerate() {
 | 
			
		||||
								for (transaction_log_index, log) in receipt.logs.into_iter().enumerate() {
 | 
			
		||||
									if filter.matches(&log) {
 | 
			
		||||
										matches.insert((num, block_index), Log {
 | 
			
		||||
											address: log.address.into(),
 | 
			
		||||
											topics: log.topics.into_iter().map(Into::into).collect(),
 | 
			
		||||
											data: log.data.into(),
 | 
			
		||||
											block_hash: Some(hash.into()),
 | 
			
		||||
											block_number: Some(num.into()),
 | 
			
		||||
											// No way to easily retrieve transaction hash, so let's just skip it.
 | 
			
		||||
											transaction_hash: None,
 | 
			
		||||
											transaction_index: Some(transaction_index.into()),
 | 
			
		||||
											log_index: Some(block_index.into()),
 | 
			
		||||
											transaction_log_index: Some(transaction_log_index.into()),
 | 
			
		||||
											log_type: "mined".into(),
 | 
			
		||||
											removed: false,
 | 
			
		||||
										});
 | 
			
		||||
									}
 | 
			
		||||
									block_index += 1;
 | 
			
		||||
								}
 | 
			
		||||
							}
 | 
			
		||||
							block_index += 1;
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
					future::ok(matches)
 | 
			
		||||
				}) // and then collect them into a vector.
 | 
			
		||||
				.map(|matches| matches.into_iter().map(|(_, v)| v).collect())
 | 
			
		||||
				.map_err(errors::on_demand_cancel)
 | 
			
		||||
		});
 | 
			
		||||
							future::ok(matches)
 | 
			
		||||
						}) // and then collect them into a vector.
 | 
			
		||||
						.map(|matches| matches.into_iter().map(|(_, v)| v).collect())
 | 
			
		||||
						.map_err(errors::on_demand_cancel)
 | 
			
		||||
				});
 | 
			
		||||
 | 
			
		||||
		match maybe_future {
 | 
			
		||||
			Some(fut) => Either::B(Either::A(fut)),
 | 
			
		||||
			None => Either::B(Either::B(future::err(errors::network_disabled()))),
 | 
			
		||||
		}
 | 
			
		||||
				match maybe_future {
 | 
			
		||||
					Some(fut) => Either::B(Either::A(fut)),
 | 
			
		||||
					None => Either::B(Either::B(future::err(errors::network_disabled()))),
 | 
			
		||||
				}
 | 
			
		||||
			})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Get a transaction by hash. also returns the index in the block.
 | 
			
		||||
@ -448,6 +439,150 @@ impl LightFetch {
 | 
			
		||||
			None => Box::new(future::err(errors::network_disabled())) as Box<Future<Item = _, Error = _> + Send>
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fn headers_range_by_block_id(
 | 
			
		||||
		&self,
 | 
			
		||||
		from_block: BlockId,
 | 
			
		||||
		to_block: BlockId,
 | 
			
		||||
		max: u64
 | 
			
		||||
	) -> impl Future<Item = Vec<encoded::Header>, Error = Error> {
 | 
			
		||||
		let fetch_hashes = [from_block, to_block].iter()
 | 
			
		||||
			.filter_map(|block_id| match block_id {
 | 
			
		||||
				BlockId::Hash(hash) => Some(hash.clone()),
 | 
			
		||||
				_ => None,
 | 
			
		||||
			})
 | 
			
		||||
			.collect::<Vec<_>>();
 | 
			
		||||
 | 
			
		||||
		let best_number = self.client.chain_info().best_block_number;
 | 
			
		||||
 | 
			
		||||
		let fetcher = self.clone();
 | 
			
		||||
		self.headers_by_hash(&fetch_hashes[..]).and_then(move |mut header_map| {
 | 
			
		||||
			let (from_block_num, to_block_num) = {
 | 
			
		||||
				let block_number = |id| match id {
 | 
			
		||||
					&BlockId::Earliest => 0,
 | 
			
		||||
					&BlockId::Latest => best_number,
 | 
			
		||||
					&BlockId::Hash(ref h) =>
 | 
			
		||||
						header_map.get(h).map(|hdr| hdr.number())
 | 
			
		||||
						.expect("from_block and to_block headers are fetched by hash; this closure is only called on from_block and to_block; qed"),
 | 
			
		||||
					&BlockId::Number(x) => x,
 | 
			
		||||
				};
 | 
			
		||||
				(block_number(&from_block), block_number(&to_block))
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			if to_block_num < from_block_num {
 | 
			
		||||
				// early exit for "to" block before "from" block.
 | 
			
		||||
				return Either::A(future::err(errors::filter_block_not_found(to_block)));
 | 
			
		||||
			} else if to_block_num - from_block_num >= max {
 | 
			
		||||
				return Either::A(future::err(errors::request_rejected_param_limit(max, "blocks")));
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			let to_header_hint = match to_block {
 | 
			
		||||
				BlockId::Hash(ref h) => header_map.remove(h),
 | 
			
		||||
				_ => None,
 | 
			
		||||
			};
 | 
			
		||||
			let headers_fut = fetcher.headers_range(from_block_num, to_block_num, to_header_hint);
 | 
			
		||||
			Either::B(headers_fut.map(move |headers| {
 | 
			
		||||
				// Validate from_block if it's a hash
 | 
			
		||||
				let last_hash = headers.last().map(|hdr| hdr.hash());
 | 
			
		||||
				match (last_hash, from_block) {
 | 
			
		||||
					(Some(h1), BlockId::Hash(h2)) if h1 != h2 => Vec::new(),
 | 
			
		||||
					_ => headers,
 | 
			
		||||
				}
 | 
			
		||||
			}))
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fn headers_by_hash(&self, hashes: &[H256]) -> impl Future<Item = H256FastMap<encoded::Header>, Error = Error> {
 | 
			
		||||
		let mut refs = H256FastMap::with_capacity_and_hasher(hashes.len(), Default::default());
 | 
			
		||||
		let mut reqs = Vec::with_capacity(hashes.len());
 | 
			
		||||
 | 
			
		||||
		for hash in hashes {
 | 
			
		||||
			refs.entry(*hash).or_insert_with(|| {
 | 
			
		||||
				self.make_header_requests(BlockId::Hash(*hash), &mut reqs)
 | 
			
		||||
					.expect("make_header_requests never fails for BlockId::Hash; qed")
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		self.send_requests(reqs, move |res| {
 | 
			
		||||
			let headers = refs.drain()
 | 
			
		||||
				.map(|(hash, header_ref)| {
 | 
			
		||||
					let hdr = extract_header(&res, header_ref)
 | 
			
		||||
						.expect("these responses correspond to requests that header_ref belongs to; \
 | 
			
		||||
								qed");
 | 
			
		||||
					(hash, hdr)
 | 
			
		||||
				})
 | 
			
		||||
				.collect();
 | 
			
		||||
			headers
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fn headers_range(
 | 
			
		||||
		&self,
 | 
			
		||||
		from_number: u64,
 | 
			
		||||
		to_number: u64,
 | 
			
		||||
		to_header_hint: Option<encoded::Header>
 | 
			
		||||
	) -> impl Future<Item = Vec<encoded::Header>, Error = Error> {
 | 
			
		||||
		let range_length = (to_number - from_number + 1) as usize;
 | 
			
		||||
		let mut headers: Vec<encoded::Header> = Vec::with_capacity(range_length);
 | 
			
		||||
 | 
			
		||||
		let iter_start = match to_header_hint {
 | 
			
		||||
			Some(hdr) => {
 | 
			
		||||
				let block_id = BlockId::Hash(hdr.parent_hash());
 | 
			
		||||
				headers.push(hdr);
 | 
			
		||||
				block_id
 | 
			
		||||
			}
 | 
			
		||||
			None => BlockId::Number(to_number),
 | 
			
		||||
		};
 | 
			
		||||
		headers.extend(self.client.ancestry_iter(iter_start)
 | 
			
		||||
				.take_while(|hdr| hdr.number() >= from_number));
 | 
			
		||||
 | 
			
		||||
		let fetcher = self.clone();
 | 
			
		||||
		future::loop_fn(headers, move |mut headers| {
 | 
			
		||||
			let remaining = range_length - headers.len();
 | 
			
		||||
			if remaining == 0 {
 | 
			
		||||
				return Either::A(future::ok(future::Loop::Break(headers)));
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			let mut reqs: Vec<request::Request> = Vec::with_capacity(2);
 | 
			
		||||
 | 
			
		||||
			let start_hash = if let Some(hdr) = headers.last() {
 | 
			
		||||
				hdr.parent_hash().into()
 | 
			
		||||
			} else {
 | 
			
		||||
				let cht_root = cht::block_to_cht_number(to_number)
 | 
			
		||||
					.and_then(|cht_num| fetcher.client.cht_root(cht_num as usize));
 | 
			
		||||
 | 
			
		||||
				let cht_root = match cht_root {
 | 
			
		||||
					Some(cht_root) => cht_root,
 | 
			
		||||
					None => return Either::A(future::err(errors::unknown_block())),
 | 
			
		||||
				};
 | 
			
		||||
 | 
			
		||||
				let header_proof = request::HeaderProof::new(to_number, cht_root)
 | 
			
		||||
					.expect("HeaderProof::new is Some(_) if cht::block_to_cht_number() is Some(_); \
 | 
			
		||||
							this would return above if block_to_cht_number returned None; qed");
 | 
			
		||||
 | 
			
		||||
				let idx = reqs.len();
 | 
			
		||||
				let hash_ref = Field::back_ref(idx, 0);
 | 
			
		||||
				reqs.push(header_proof.into());
 | 
			
		||||
 | 
			
		||||
				hash_ref
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			let max = cmp::min(remaining as u64, MAX_HEADERS_PER_REQUEST);
 | 
			
		||||
			reqs.push(request::HeaderWithAncestors {
 | 
			
		||||
				block_hash: start_hash,
 | 
			
		||||
				ancestor_count: max - 1,
 | 
			
		||||
			}.into());
 | 
			
		||||
 | 
			
		||||
			Either::B(fetcher.send_requests(reqs, |mut res| {
 | 
			
		||||
				match res.last_mut() {
 | 
			
		||||
					Some(&mut OnDemandResponse::HeaderWithAncestors(ref mut res_headers)) =>
 | 
			
		||||
						headers.extend(res_headers.drain(..)),
 | 
			
		||||
					_ => panic!("reqs has at least one entry; each request maps to a response; qed"),
 | 
			
		||||
				};
 | 
			
		||||
				future::Loop::Continue(headers)
 | 
			
		||||
			}))
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Clone)]
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user