// Copyright 2017-2020 Parity Technologies (UK) Ltd. // This file is part of Substrate. // Substrate 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. // Substrate 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 Substrate. If not, see . //! Light client requests service. //! //! Handles requests for data coming from our local light client and that must be answered by //! nodes on the network. use std::collections::{HashMap, VecDeque}; use std::sync::Arc; use std::time::Duration; use wasm_timer::Instant; use log::{trace, info}; use futures::channel::oneshot::{Sender as OneShotSender}; use linked_hash_map::{Entry, LinkedHashMap}; use sp_blockchain::Error as ClientError; use sc_client_api::{FetchChecker, RemoteHeaderRequest, RemoteCallRequest, RemoteReadRequest, RemoteChangesRequest, ChangesProof, RemoteReadChildRequest, RemoteBodyRequest, StorageProof}; use crate::message::{self, BlockAttributes, Direction, FromBlock, RequestId}; use libp2p::PeerId; use crate::config::Roles; use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor}; use sc_peerset::ReputationChange; /// Remote request timeout. const REQUEST_TIMEOUT: Duration = Duration::from_secs(15); /// Default request retry count. const RETRY_COUNT: usize = 1; /// Reputation change for a peer when a request timed out. pub(crate) const TIMEOUT_REPUTATION_CHANGE: i32 = -(1 << 8); /// Trait used by the `LightDispatch` service to communicate messages back to the network. pub trait LightDispatchNetwork { /// Adjusts the reputation of the given peer. fn report_peer(&mut self, who: &PeerId, reputation_change: ReputationChange); /// Disconnect from the given peer. Used in case of misbehaviour. fn disconnect_peer(&mut self, who: &PeerId); /// Send to `who` a request for a header. fn send_header_request(&mut self, who: &PeerId, id: RequestId, block: <::Header as HeaderT>::Number); /// Send to `who` a read request. fn send_read_request( &mut self, who: &PeerId, id: RequestId, block: ::Hash, keys: Vec>, ); /// Send to `who` a child read request. fn send_read_child_request( &mut self, who: &PeerId, id: RequestId, block: ::Hash, storage_key: Vec, child_info: Vec, child_type: u32, keys: Vec>, ); /// Send to `who` a call request. fn send_call_request( &mut self, who: &PeerId, id: RequestId, block: ::Hash, method: String, data: Vec ); /// Send to `who` a changes request. fn send_changes_request( &mut self, who: &PeerId, id: RequestId, first: ::Hash, last: ::Hash, min: ::Hash, max: ::Hash, storage_key: Option>, key: Vec, ); /// Send to `who` a body request. fn send_body_request( &mut self, who: &PeerId, id: RequestId, fields: BlockAttributes, from: FromBlock<::Hash, <::Header as HeaderT>::Number>, to: Option, direction: Direction, max: Option ); } /// Light client requests service. Dispatches requests to appropriate peers. pub struct LightDispatch { /// Verifies that responses are correct. Passed at initialization. checker: Arc>, /// Numeric ID to assign to the next outgoing request. Used to assign responses to their /// corresponding request. next_request_id: u64, /// Requests that we have yet to send out on the network. pending_requests: VecDeque>, /// List of nodes to which we have sent a request and that are yet to answer. active_peers: LinkedHashMap>, /// List of nodes that we know of that aren't doing anything and that are available for new /// requests. idle_peers: VecDeque, /// Best known block for each node in `active_peers` and `idle_peers`. best_blocks: HashMap>, } struct Request { id: u64, /// When the request got created or sent out to the network. timestamp: Instant, /// Number of remaining attempts to fulfill this request. If it reaches 0, we interrupt the /// attempt. retry_count: usize, data: RequestData, } /// One request for data made by the `Client`. /// /// Contains a `Sender` where to send the result. pub(crate) enum RequestData { RemoteBody(RemoteBodyRequest, OneShotSender, ClientError>>), RemoteHeader(RemoteHeaderRequest, OneShotSender>), RemoteRead( RemoteReadRequest, OneShotSender, Option>>, ClientError>>, ), RemoteReadChild( RemoteReadChildRequest, OneShotSender, Option>>, ClientError>> ), RemoteCall(RemoteCallRequest, OneShotSender, ClientError>>), RemoteChanges( RemoteChangesRequest, OneShotSender, u32)>, ClientError>> ), } enum Accept { Ok, CheckFailed(ClientError, RequestData), Unexpected(RequestData), } /// Dummy implementation of `FetchChecker` that always assumes that responses are bad. /// /// Considering that it is the responsibility of the client to build the fetcher, it can use this /// implementation if it knows that it will never perform any request. #[derive(Default, Clone)] pub struct AlwaysBadChecker; impl FetchChecker for AlwaysBadChecker { fn check_header_proof( &self, _request: &RemoteHeaderRequest, _remote_header: Option, _remote_proof: StorageProof, ) -> Result { Err(ClientError::Msg("AlwaysBadChecker".into())) } fn check_read_proof( &self, _request: &RemoteReadRequest, _remote_proof: StorageProof, ) -> Result,Option>>, ClientError> { Err(ClientError::Msg("AlwaysBadChecker".into())) } fn check_read_child_proof( &self, _request: &RemoteReadChildRequest, _remote_proof: StorageProof, ) -> Result, Option>>, ClientError> { Err(ClientError::Msg("AlwaysBadChecker".into())) } fn check_execution_proof( &self, _request: &RemoteCallRequest, _remote_proof: StorageProof, ) -> Result, ClientError> { Err(ClientError::Msg("AlwaysBadChecker".into())) } fn check_changes_proof( &self, _request: &RemoteChangesRequest, _remote_proof: ChangesProof ) -> Result, u32)>, ClientError> { Err(ClientError::Msg("AlwaysBadChecker".into())) } fn check_body_proof( &self, _request: &RemoteBodyRequest, _body: Vec ) -> Result, ClientError> { Err(ClientError::Msg("AlwaysBadChecker".into())) } } impl LightDispatch where B::Header: HeaderT, { /// Creates new light client requests processer. pub fn new(checker: Arc>) -> Self { LightDispatch { checker, next_request_id: 0, pending_requests: VecDeque::new(), active_peers: LinkedHashMap::new(), idle_peers: VecDeque::new(), best_blocks: HashMap::new(), } } /// Inserts a new request in the list of requests to execute. pub(crate) fn add_request(&mut self, network: impl LightDispatchNetwork, data: RequestData) { self.insert(RETRY_COUNT, data); self.dispatch(network); } /// Inserts a new request in the list of requests to execute. fn insert(&mut self, retry_count: usize, data: RequestData) { let request_id = self.next_request_id; self.next_request_id += 1; self.pending_requests.push_back(Request { id: request_id, timestamp: Instant::now(), retry_count, data, }); } /// Try to accept response from given peer. fn accept_response( &mut self, rtype: &str, mut network: impl LightDispatchNetwork, peer: PeerId, request_id: u64, try_accept: impl FnOnce(Request, &Arc>) -> Accept ) { let request = match self.remove(peer.clone(), request_id) { Some(request) => request, None => { info!("Invalid remote {} response from peer {}", rtype, peer); network.report_peer(&peer, ReputationChange::new_fatal("Invalid remote response")); network.disconnect_peer(&peer); self.remove_peer(peer); return; }, }; let retry_count = request.retry_count; let (retry_count, retry_request_data) = match try_accept(request, &self.checker) { Accept::Ok => (retry_count, None), Accept::CheckFailed(error, retry_request_data) => { info!("Failed to check remote {} response from peer {}: {}", rtype, peer, error); network.report_peer(&peer, ReputationChange::new_fatal("Failed remote response check")); network.disconnect_peer(&peer); self.remove_peer(peer); if retry_count > 0 { (retry_count - 1, Some(retry_request_data)) } else { trace!(target: "sync", "Failed to get remote {} response for given number of retries", rtype); retry_request_data.fail(ClientError::RemoteFetchFailed.into()); (0, None) } }, Accept::Unexpected(retry_request_data) => { info!("Unexpected response to remote {} from peer", rtype); network.report_peer(&peer, ReputationChange::new_fatal("Unexpected remote response")); network.disconnect_peer(&peer); self.remove_peer(peer); (retry_count, Some(retry_request_data)) }, }; if let Some(request_data) = retry_request_data { self.insert(retry_count, request_data); } self.dispatch(network); } /// Call this when we connect to a node on the network. pub fn on_connect( &mut self, network: impl LightDispatchNetwork, peer: PeerId, role: Roles, best_number: NumberFor ) { if !role.is_full() { return; } self.idle_peers.push_back(peer.clone()); self.best_blocks.insert(peer, best_number); self.dispatch(network); } /// Sets the best seen block for the given node. pub fn update_best_number(&mut self, network: impl LightDispatchNetwork, peer: PeerId, best_number: NumberFor) { self.best_blocks.insert(peer, best_number); self.dispatch(network); } /// Call this when we disconnect from a node. pub fn on_disconnect(&mut self, network: impl LightDispatchNetwork, peer: PeerId) { self.remove_peer(peer); self.dispatch(network); } /// Must be called periodically in order to perform maintenance. pub fn maintain_peers(&mut self, mut network: impl LightDispatchNetwork) { let now = Instant::now(); loop { match self.active_peers.front() { Some((_, request)) if now - request.timestamp >= REQUEST_TIMEOUT => (), _ => break, } let (bad_peer, request) = self.active_peers.pop_front().expect("front() is Some as checked above"); self.pending_requests.push_front(request); network.report_peer(&bad_peer, ReputationChange::new(TIMEOUT_REPUTATION_CHANGE, "Light request timeout")); network.disconnect_peer(&bad_peer); } self.dispatch(network); } /// Handles a remote header response message from on the network. pub fn on_remote_header_response( &mut self, network: impl LightDispatchNetwork, peer: PeerId, response: message::RemoteHeaderResponse ) { self.accept_response("header", network, peer, response.id, |request, checker| match request.data { RequestData::RemoteHeader(request, sender) => match checker.check_header_proof( &request, response.header, response.proof ) { Ok(response) => { // we do not bother if receiver has been dropped already let _ = sender.send(Ok(response)); Accept::Ok }, Err(error) => Accept::CheckFailed(error, RequestData::RemoteHeader(request, sender)), }, data => Accept::Unexpected(data), }) } /// Handles a remote read response message from on the network. pub fn on_remote_read_response( &mut self, network: impl LightDispatchNetwork, peer: PeerId, response: message::RemoteReadResponse ) { self.accept_response("read", network, peer, response.id, |request, checker| match request.data { RequestData::RemoteRead(request, sender) => { match checker.check_read_proof(&request, response.proof) { Ok(response) => { // we do not bother if receiver has been dropped already let _ = sender.send(Ok(response)); Accept::Ok }, Err(error) => Accept::CheckFailed( error, RequestData::RemoteRead(request, sender) ), }}, RequestData::RemoteReadChild(request, sender) => { match checker.check_read_child_proof(&request, response.proof) { Ok(response) => { // we do not bother if receiver has been dropped already let _ = sender.send(Ok(response)); Accept::Ok }, Err(error) => Accept::CheckFailed( error, RequestData::RemoteReadChild(request, sender) ), }}, data => Accept::Unexpected(data), }) } /// Handles a remote call response message from on the network. pub fn on_remote_call_response( &mut self, network: impl LightDispatchNetwork, peer: PeerId, response: message::RemoteCallResponse ) { self.accept_response("call", network, peer, response.id, |request, checker| match request.data { RequestData::RemoteCall(request, sender) => match checker.check_execution_proof(&request, response.proof) { Ok(response) => { // we do not bother if receiver has been dropped already let _ = sender.send(Ok(response)); Accept::Ok }, Err(error) => Accept::CheckFailed(error, RequestData::RemoteCall(request, sender)), }, data => Accept::Unexpected(data), }) } /// Handles a remote changes response message from on the network. pub fn on_remote_changes_response( &mut self, network: impl LightDispatchNetwork, peer: PeerId, response: message::RemoteChangesResponse, B::Hash> ) { self.accept_response("changes", network, peer, response.id, |request, checker| match request.data { RequestData::RemoteChanges(request, sender) => match checker.check_changes_proof( &request, ChangesProof { max_block: response.max, proof: response.proof, roots: response.roots.into_iter().collect(), roots_proof: response.roots_proof, }) { Ok(response) => { // we do not bother if receiver has been dropped already let _ = sender.send(Ok(response)); Accept::Ok }, Err(error) => Accept::CheckFailed(error, RequestData::RemoteChanges(request, sender)), }, data => Accept::Unexpected(data), }) } /// Handles a remote body response message from on the network. pub fn on_remote_body_response( &mut self, network: impl LightDispatchNetwork, peer: PeerId, response: message::BlockResponse ) { self.accept_response("body", network, peer, response.id, |request, checker| match request.data { RequestData::RemoteBody(request, sender) => { let mut bodies: Vec<_> = response .blocks .into_iter() .filter_map(|b| b.body) .collect(); // Number of bodies are hardcoded to 1 for valid `RemoteBodyResponses` if bodies.len() != 1 { return Accept::CheckFailed( "RemoteBodyResponse: invalid number of blocks".into(), RequestData::RemoteBody(request, sender), ) } let body = bodies.remove(0); match checker.check_body_proof(&request, body) { Ok(body) => { let _ = sender.send(Ok(body)); Accept::Ok } Err(error) => Accept::CheckFailed(error, RequestData::RemoteBody(request, sender)), } } other => Accept::Unexpected(other), }) } pub fn is_light_response(&self, peer: &PeerId, request_id: message::RequestId) -> bool { self.active_peers.get(&peer).map_or(false, |r| r.id == request_id) } fn remove(&mut self, peer: PeerId, id: u64) -> Option> { match self.active_peers.entry(peer.clone()) { Entry::Occupied(entry) => match entry.get().id == id { true => { self.idle_peers.push_back(peer); Some(entry.remove()) }, false => None, }, Entry::Vacant(_) => None, } } /// Removes a peer from the list of known peers. /// /// Puts back the active request that this node was performing into `pending_requests`. fn remove_peer(&mut self, peer: PeerId) { self.best_blocks.remove(&peer); if let Some(request) = self.active_peers.remove(&peer) { self.pending_requests.push_front(request); return; } if let Some(idle_index) = self.idle_peers.iter().position(|i| *i == peer) { self.idle_peers.swap_remove_back(idle_index); } } /// Dispatches pending requests. fn dispatch(&mut self, mut network: impl LightDispatchNetwork) { let mut last_peer = self.idle_peers.back().cloned(); let mut unhandled_requests = VecDeque::new(); loop { let peer = match self.idle_peers.pop_front() { Some(peer) => peer, None => break, }; // check if request can (optimistically) be processed by the peer let can_be_processed_by_peer = { let request = match self.pending_requests.front() { Some(r) => r, None => { self.idle_peers.push_front(peer); break; }, }; let peer_best_block = self.best_blocks.get(&peer) .expect("entries are inserted into best_blocks when peer is connected; entries are removed from best_blocks when peer is disconnected; peer is in idle_peers and thus connected; qed"); request.required_block() <= *peer_best_block }; if !can_be_processed_by_peer { // return peer to the back of the queue self.idle_peers.push_back(peer.clone()); // we have enumerated all peers and noone can handle request if Some(peer) == last_peer { let request = self.pending_requests.pop_front().expect("checked in loop condition; qed"); unhandled_requests.push_back(request); last_peer = self.idle_peers.back().cloned(); } continue; } last_peer = self.idle_peers.back().cloned(); let mut request = self.pending_requests.pop_front().expect("checked in loop condition; qed"); request.timestamp = Instant::now(); trace!(target: "sync", "Dispatching remote request {} to peer {}", request.id, peer); request.send_to(&mut network, &peer); self.active_peers.insert(peer, request); } self.pending_requests.append(&mut unhandled_requests); } } impl Request { /// Returns the block that the remote needs to have in order to be able to fulfill /// this request. fn required_block(&self) -> NumberFor { match self.data { RequestData::RemoteHeader(ref data, _) => data.block, RequestData::RemoteRead(ref data, _) => *data.header.number(), RequestData::RemoteReadChild(ref data, _) => *data.header.number(), RequestData::RemoteCall(ref data, _) => *data.header.number(), RequestData::RemoteChanges(ref data, _) => data.max_block.0, RequestData::RemoteBody(ref data, _) => *data.header.number(), } } fn send_to(&self, out: &mut impl LightDispatchNetwork, peer: &PeerId) { match self.data { RequestData::RemoteHeader(ref data, _) => out.send_header_request( peer, self.id, data.block, ), RequestData::RemoteRead(ref data, _) => out.send_read_request( peer, self.id, data.block, data.keys.clone(), ), RequestData::RemoteReadChild(ref data, _) => out.send_read_child_request( peer, self.id, data.block, data.storage_key.clone(), data.child_info.clone(), data.child_type, data.keys.clone(), ), RequestData::RemoteCall(ref data, _) => out.send_call_request( peer, self.id, data.block, data.method.clone(), data.call_data.clone(), ), RequestData::RemoteChanges(ref data, _) => out.send_changes_request( peer, self.id, data.first_block.1.clone(), data.last_block.1.clone(), data.tries_roots.1.clone(), data.max_block.1.clone(), data.storage_key.clone(), data.key.clone(), ), RequestData::RemoteBody(ref data, _) => out.send_body_request( peer, self.id, message::BlockAttributes::BODY, message::FromBlock::Hash(data.header.hash()), None, message::Direction::Ascending, Some(1) ), } } } impl RequestData { fn fail(self, error: ClientError) { // don't care if anyone is listening match self { RequestData::RemoteHeader(_, sender) => { let _ = sender.send(Err(error)); }, RequestData::RemoteCall(_, sender) => { let _ = sender.send(Err(error)); }, RequestData::RemoteRead(_, sender) => { let _ = sender.send(Err(error)); }, RequestData::RemoteReadChild(_, sender) => { let _ = sender.send(Err(error)); }, RequestData::RemoteChanges(_, sender) => { let _ = sender.send(Err(error)); }, RequestData::RemoteBody(_, sender) => { let _ = sender.send(Err(error)); }, } } } #[cfg(test)] pub mod tests { use std::collections::{HashMap, HashSet}; use std::sync::Arc; use std::time::Instant; use futures::channel::oneshot; use sp_core::storage::ChildInfo; use sp_runtime::traits::{Block as BlockT, NumberFor, Header as HeaderT}; use sp_blockchain::{Error as ClientError, Result as ClientResult}; use sc_client_api::{FetchChecker, RemoteHeaderRequest, ChangesProof, RemoteCallRequest, RemoteReadRequest, RemoteReadChildRequest, RemoteChangesRequest, RemoteBodyRequest}; use crate::config::Roles; use crate::message::{self, BlockAttributes, Direction, FromBlock, RequestId}; use libp2p::PeerId; use super::{REQUEST_TIMEOUT, LightDispatch, LightDispatchNetwork, RequestData, StorageProof}; use sp_test_primitives::{Block, Header}; pub(crate) struct DummyFetchChecker { pub(crate) ok: bool, _mark: std::marker::PhantomData } impl DummyFetchChecker { pub(crate) fn new(ok: bool) -> Self { DummyFetchChecker { ok, _mark: std::marker::PhantomData } } } impl FetchChecker for DummyFetchChecker { fn check_header_proof( &self, _request: &RemoteHeaderRequest, header: Option, _remote_proof: StorageProof, ) -> ClientResult { match self.ok { true if header.is_some() => Ok(header.unwrap()), _ => Err(ClientError::Backend("Test error".into())), } } fn check_read_proof( &self, request: &RemoteReadRequest, _: StorageProof, ) -> ClientResult, Option>>> { match self.ok { true => Ok(request.keys .iter() .cloned() .map(|k| (k, Some(vec![42]))) .collect() ), false => Err(ClientError::Backend("Test error".into())), } } fn check_read_child_proof( &self, request: &RemoteReadChildRequest, _: StorageProof, ) -> ClientResult, Option>>> { match self.ok { true => Ok(request.keys .iter() .cloned() .map(|k| (k, Some(vec![42]))) .collect() ), false => Err(ClientError::Backend("Test error".into())), } } fn check_execution_proof(&self, _: &RemoteCallRequest, _: StorageProof) -> ClientResult> { match self.ok { true => Ok(vec![42]), false => Err(ClientError::Backend("Test error".into())), } } fn check_changes_proof( &self, _: &RemoteChangesRequest, _: ChangesProof ) -> ClientResult, u32)>> { match self.ok { true => Ok(vec![(100.into(), 2)]), false => Err(ClientError::Backend("Test error".into())), } } fn check_body_proof( &self, _: &RemoteBodyRequest, body: Vec ) -> ClientResult> { match self.ok { true => Ok(body), false => Err(ClientError::Backend("Test error".into())), } } } fn dummy(ok: bool) -> LightDispatch { LightDispatch::new(Arc::new(DummyFetchChecker::new(ok))) } fn total_peers(light_dispatch: &LightDispatch) -> usize { light_dispatch.idle_peers.len() + light_dispatch.active_peers.len() } fn receive_call_response( network_interface: impl LightDispatchNetwork, light_dispatch: &mut LightDispatch, peer: PeerId, id: message::RequestId ) { light_dispatch.on_remote_call_response(network_interface, peer, message::RemoteCallResponse { id: id, proof: StorageProof::empty(), }); } pub(crate) fn dummy_header() -> Header { Header { parent_hash: Default::default(), number: 0, state_root: Default::default(), extrinsics_root: Default::default(), digest: Default::default(), } } #[derive(Default)] struct DummyNetwork { disconnected_peers: HashSet, } impl<'a, B: BlockT> LightDispatchNetwork for &'a mut DummyNetwork { fn report_peer(&mut self, _: &PeerId, _: crate::ReputationChange) {} fn disconnect_peer(&mut self, who: &PeerId) { self.disconnected_peers.insert(who.clone()); } fn send_header_request(&mut self, _: &PeerId, _: RequestId, _: <::Header as HeaderT>::Number) {} fn send_read_request(&mut self, _: &PeerId, _: RequestId, _: ::Hash, _: Vec>) {} fn send_read_child_request(&mut self, _: &PeerId, _: RequestId, _: ::Hash, _: Vec, _: Vec, _: u32, _: Vec>) {} fn send_call_request(&mut self, _: &PeerId, _: RequestId, _: ::Hash, _: String, _: Vec) {} fn send_changes_request(&mut self, _: &PeerId, _: RequestId, _: ::Hash, _: ::Hash, _: ::Hash, _: ::Hash, _: Option>, _: Vec) {} fn send_body_request(&mut self, _: &PeerId, _: RequestId, _: BlockAttributes, _: FromBlock<::Hash, <::Header as HeaderT>::Number>, _: Option, _: Direction, _: Option) {} } fn assert_disconnected_peer(dummy: &DummyNetwork) { assert_eq!(dummy.disconnected_peers.len(), 1); } #[test] fn knows_about_peers_roles() { let mut network_interface = DummyNetwork::default(); let mut light_dispatch = dummy(true); let peer0 = PeerId::random(); let peer1 = PeerId::random(); let peer2 = PeerId::random(); light_dispatch.on_connect(&mut network_interface, peer0, Roles::LIGHT, 1000); light_dispatch.on_connect(&mut network_interface, peer1.clone(), Roles::FULL, 2000); light_dispatch.on_connect(&mut network_interface, peer2.clone(), Roles::AUTHORITY, 3000); assert_eq!(vec![peer1.clone(), peer2.clone()], light_dispatch.idle_peers.iter().cloned().collect::>()); assert_eq!(light_dispatch.best_blocks.get(&peer1), Some(&2000)); assert_eq!(light_dispatch.best_blocks.get(&peer2), Some(&3000)); } #[test] fn disconnects_from_idle_peer() { let peer0 = PeerId::random(); let mut network_interface = DummyNetwork::default(); let mut light_dispatch = dummy(true); light_dispatch.on_connect(&mut network_interface, peer0.clone(), Roles::FULL, 100); assert_eq!(1, total_peers(&light_dispatch)); assert!(!light_dispatch.best_blocks.is_empty()); light_dispatch.on_disconnect(&mut network_interface, peer0); assert_eq!(0, total_peers(&light_dispatch)); assert!(light_dispatch.best_blocks.is_empty()); } #[test] fn disconnects_from_timeouted_peer() { let mut light_dispatch = dummy(true); let mut network_interface = DummyNetwork::default(); let peer0 = PeerId::random(); let peer1 = PeerId::random(); light_dispatch.on_connect(&mut network_interface, peer0.clone(), Roles::FULL, 1000); light_dispatch.on_connect(&mut network_interface, peer1.clone(), Roles::FULL, 1000); assert_eq!(vec![peer0.clone(), peer1.clone()], light_dispatch.idle_peers.iter().cloned().collect::>()); assert!(light_dispatch.active_peers.is_empty()); light_dispatch.add_request(&mut network_interface, RequestData::RemoteCall(RemoteCallRequest { block: Default::default(), header: dummy_header(), method: "test".into(), call_data: vec![], retry_count: None, }, oneshot::channel().0)); assert_eq!(vec![peer1.clone()], light_dispatch.idle_peers.iter().cloned().collect::>()); assert_eq!(vec![peer0.clone()], light_dispatch.active_peers.keys().cloned().collect::>()); light_dispatch.active_peers[&peer0].timestamp = Instant::now() - REQUEST_TIMEOUT - REQUEST_TIMEOUT; light_dispatch.maintain_peers(&mut network_interface); assert!(light_dispatch.idle_peers.is_empty()); assert_eq!(vec![peer1.clone()], light_dispatch.active_peers.keys().cloned().collect::>()); assert_disconnected_peer(&network_interface); } #[test] fn disconnects_from_peer_on_response_with_wrong_id() { let mut light_dispatch = dummy(true); let peer0 = PeerId::random(); let mut network_interface = DummyNetwork::default(); light_dispatch.on_connect(&mut network_interface, peer0.clone(), Roles::FULL, 1000); light_dispatch.add_request(&mut network_interface, RequestData::RemoteCall(RemoteCallRequest { block: Default::default(), header: dummy_header(), method: "test".into(), call_data: vec![], retry_count: None, }, oneshot::channel().0)); receive_call_response(&mut network_interface, &mut light_dispatch, peer0, 1); assert_disconnected_peer(&network_interface); assert_eq!(light_dispatch.pending_requests.len(), 1); } #[test] fn disconnects_from_peer_on_incorrect_response() { let mut light_dispatch = dummy(false); let mut network_interface = DummyNetwork::default(); let peer0 = PeerId::random(); light_dispatch.add_request(&mut network_interface, RequestData::RemoteCall(RemoteCallRequest { block: Default::default(), header: dummy_header(), method: "test".into(), call_data: vec![], retry_count: Some(1), }, oneshot::channel().0)); light_dispatch.on_connect(&mut network_interface, peer0.clone(), Roles::FULL, 1000); receive_call_response(&mut network_interface, &mut light_dispatch, peer0.clone(), 0); assert_disconnected_peer(&network_interface); assert_eq!(light_dispatch.pending_requests.len(), 1); } #[test] fn disconnects_from_peer_on_unexpected_response() { let mut light_dispatch = dummy(true); let mut network_interface = DummyNetwork::default(); let peer0 = PeerId::random(); light_dispatch.on_connect(&mut network_interface, peer0.clone(), Roles::FULL, 1000); receive_call_response(&mut network_interface, &mut light_dispatch, peer0, 0); assert_disconnected_peer(&network_interface); } #[test] fn disconnects_from_peer_on_wrong_response_type() { let mut light_dispatch = dummy(false); let peer0 = PeerId::random(); let mut network_interface = DummyNetwork::default(); light_dispatch.on_connect(&mut network_interface, peer0.clone(), Roles::FULL, 1000); light_dispatch.add_request(&mut network_interface, RequestData::RemoteCall(RemoteCallRequest { block: Default::default(), header: dummy_header(), method: "test".into(), call_data: vec![], retry_count: Some(1), }, oneshot::channel().0)); light_dispatch.on_remote_read_response(&mut network_interface, peer0.clone(), message::RemoteReadResponse { id: 0, proof: StorageProof::empty(), }); assert_disconnected_peer(&network_interface); assert_eq!(light_dispatch.pending_requests.len(), 1); } #[test] fn receives_remote_failure_after_retry_count_failures() { let retry_count = 2; let peer_ids = (0 .. retry_count + 1).map(|_| PeerId::random()).collect::>(); let mut light_dispatch = dummy(false); let mut network_interface = DummyNetwork::default(); for i in 0..retry_count+1 { light_dispatch.on_connect(&mut network_interface, peer_ids[i].clone(), Roles::FULL, 1000); } let (tx, mut response) = oneshot::channel(); light_dispatch.add_request(&mut network_interface, RequestData::RemoteCall(RemoteCallRequest { block: Default::default(), header: dummy_header(), method: "test".into(), call_data: vec![], retry_count: Some(retry_count) }, tx)); for i in 0..retry_count { assert!(response.try_recv().unwrap().is_none()); receive_call_response(&mut network_interface, &mut light_dispatch, peer_ids[i].clone(), i as u64); } assert!(response.try_recv().unwrap().unwrap().is_err()); } #[test] fn receives_remote_call_response() { let mut light_dispatch = dummy(true); let mut network_interface = DummyNetwork::default(); let peer0 = PeerId::random(); light_dispatch.on_connect(&mut network_interface, peer0.clone(), Roles::FULL, 1000); let (tx, response) = oneshot::channel(); light_dispatch.add_request(&mut network_interface, RequestData::RemoteCall(RemoteCallRequest { block: Default::default(), header: dummy_header(), method: "test".into(), call_data: vec![], retry_count: None, }, tx)); receive_call_response(&mut network_interface, &mut light_dispatch, peer0.clone(), 0); assert_eq!(futures::executor::block_on(response).unwrap().unwrap(), vec![42]); } #[test] fn receives_remote_read_response() { let mut light_dispatch = dummy(true); let mut network_interface = DummyNetwork::default(); let peer0 = PeerId::random(); light_dispatch.on_connect(&mut network_interface, peer0.clone(), Roles::FULL, 1000); let (tx, response) = oneshot::channel(); light_dispatch.add_request(&mut network_interface, RequestData::RemoteRead(RemoteReadRequest { header: dummy_header(), block: Default::default(), keys: vec![b":key".to_vec()], retry_count: None, }, tx)); light_dispatch.on_remote_read_response(&mut network_interface, peer0.clone(), message::RemoteReadResponse { id: 0, proof: StorageProof::empty(), }); assert_eq!( futures::executor::block_on(response).unwrap().unwrap().remove(b":key".as_ref()).unwrap(), Some(vec![42]) ); } #[test] fn receives_remote_read_child_response() { let mut light_dispatch = dummy(true); let mut network_interface = DummyNetwork::default(); let peer0 = PeerId::random(); light_dispatch.on_connect(&mut network_interface, peer0.clone(), Roles::FULL, 1000); let (tx, response) = oneshot::channel(); let child_info = ChildInfo::new_default(b"unique_id_1"); let (child_info, child_type) = child_info.info(); light_dispatch.add_request(&mut network_interface, RequestData::RemoteReadChild(RemoteReadChildRequest { header: dummy_header(), block: Default::default(), storage_key: b":child_storage:sub".to_vec(), child_info: child_info.to_vec(), child_type, keys: vec![b":key".to_vec()], retry_count: None, }, tx)); light_dispatch.on_remote_read_response(&mut network_interface, peer0.clone(), message::RemoteReadResponse { id: 0, proof: StorageProof::empty(), }); assert_eq!(futures::executor::block_on(response).unwrap().unwrap().remove(b":key".as_ref()).unwrap(), Some(vec![42])); } #[test] fn receives_remote_header_response() { let mut light_dispatch = dummy(true); let mut network_interface = DummyNetwork::default(); let peer0 = PeerId::random(); light_dispatch.on_connect(&mut network_interface, peer0.clone(), Roles::FULL, 1000); let (tx, response) = oneshot::channel(); light_dispatch.add_request(&mut network_interface, RequestData::RemoteHeader(RemoteHeaderRequest { cht_root: Default::default(), block: 1, retry_count: None, }, tx)); light_dispatch.on_remote_header_response(&mut network_interface, peer0.clone(), message::RemoteHeaderResponse { id: 0, header: Some(Header { parent_hash: Default::default(), number: 1, state_root: Default::default(), extrinsics_root: Default::default(), digest: Default::default(), }), proof: StorageProof::empty(), }); assert_eq!( futures::executor::block_on(response).unwrap().unwrap().hash(), "6443a0b46e0412e626363028115a9f2cf963eeed526b8b33e5316f08b50d0dc3".parse().unwrap(), ); } #[test] fn receives_remote_changes_response() { let mut light_dispatch = dummy(true); let mut network_interface = DummyNetwork::default(); let peer0 = PeerId::random(); light_dispatch.on_connect(&mut network_interface, peer0.clone(), Roles::FULL, 1000); let (tx, response) = oneshot::channel(); light_dispatch.add_request(&mut network_interface, RequestData::RemoteChanges(RemoteChangesRequest { changes_trie_configs: vec![sp_core::ChangesTrieConfigurationRange { zero: (0, Default::default()), end: None, config: Some(sp_core::ChangesTrieConfiguration::new(4, 2)), }], first_block: (1, Default::default()), last_block: (100, Default::default()), max_block: (100, Default::default()), tries_roots: (1, Default::default(), vec![]), key: vec![], storage_key: None, retry_count: None, }, tx)); light_dispatch.on_remote_changes_response(&mut network_interface, peer0.clone(), message::RemoteChangesResponse { id: 0, max: 1000, proof: vec![vec![2]], roots: vec![], roots_proof: StorageProof::empty(), }); assert_eq!(futures::executor::block_on(response).unwrap().unwrap(), vec![(100, 2)]); } #[test] fn does_not_sends_request_to_peer_who_has_no_required_block() { let mut light_dispatch = dummy(true); let mut network_interface = DummyNetwork::default(); let peer1 = PeerId::random(); let peer2 = PeerId::random(); light_dispatch.on_connect(&mut network_interface, peer1.clone(), Roles::FULL, 100); light_dispatch.add_request(&mut network_interface, RequestData::RemoteHeader(RemoteHeaderRequest { cht_root: Default::default(), block: 200, retry_count: None, }, oneshot::channel().0)); light_dispatch.add_request(&mut network_interface, RequestData::RemoteHeader(RemoteHeaderRequest { cht_root: Default::default(), block: 250, retry_count: None, }, oneshot::channel().0)); light_dispatch.add_request(&mut network_interface, RequestData::RemoteHeader(RemoteHeaderRequest { cht_root: Default::default(), block: 250, retry_count: None, }, oneshot::channel().0)); light_dispatch.on_connect(&mut network_interface, peer2.clone(), Roles::FULL, 150); assert_eq!(vec![peer1.clone(), peer2.clone()], light_dispatch.idle_peers.iter().cloned().collect::>()); assert_eq!(light_dispatch.pending_requests.len(), 3); light_dispatch.update_best_number(&mut network_interface, peer1.clone(), 250); assert_eq!(vec![peer2.clone()], light_dispatch.idle_peers.iter().cloned().collect::>()); assert_eq!(light_dispatch.pending_requests.len(), 2); light_dispatch.update_best_number(&mut network_interface, peer2.clone(), 250); assert!(!light_dispatch.idle_peers.iter().any(|_| true)); assert_eq!(light_dispatch.pending_requests.len(), 1); light_dispatch.on_remote_header_response(&mut network_interface, peer1.clone(), message::RemoteHeaderResponse { id: 0, header: Some(dummy_header()), proof: StorageProof::empty(), }); assert!(!light_dispatch.idle_peers.iter().any(|_| true)); assert_eq!(light_dispatch.pending_requests.len(), 0); } #[test] fn does_not_loop_forever_after_dispatching_request_to_last_peer() { // this test is a regression for a bug where the dispatch function would // loop forever after dispatching a request to the last peer, since the // last peer was not updated let mut light_dispatch = dummy(true); let mut network_interface = DummyNetwork::default(); let peer1 = PeerId::random(); let peer2 = PeerId::random(); let peer3 = PeerId::random(); light_dispatch.add_request(&mut network_interface, RequestData::RemoteHeader(RemoteHeaderRequest { cht_root: Default::default(), block: 250, retry_count: None, }, oneshot::channel().0)); light_dispatch.add_request(&mut network_interface, RequestData::RemoteHeader(RemoteHeaderRequest { cht_root: Default::default(), block: 250, retry_count: None, }, oneshot::channel().0)); light_dispatch.on_connect(&mut network_interface, peer1.clone(), Roles::FULL, 200); light_dispatch.on_connect(&mut network_interface, peer2.clone(), Roles::FULL, 200); light_dispatch.on_connect(&mut network_interface, peer3.clone(), Roles::FULL, 250); assert_eq!(vec![peer1.clone(), peer2.clone()], light_dispatch.idle_peers.iter().cloned().collect::>()); assert_eq!(light_dispatch.pending_requests.len(), 1); } #[test] fn tries_to_send_all_pending_requests() { let mut light_dispatch = dummy(true); let mut network_interface = DummyNetwork::default(); let peer1 = PeerId::random(); light_dispatch.add_request(&mut network_interface, RequestData::RemoteHeader(RemoteHeaderRequest { cht_root: Default::default(), block: 300, retry_count: None, }, oneshot::channel().0)); light_dispatch.add_request(&mut network_interface, RequestData::RemoteHeader(RemoteHeaderRequest { cht_root: Default::default(), block: 250, retry_count: None, }, oneshot::channel().0)); light_dispatch.on_connect(&mut network_interface, peer1.clone(), Roles::FULL, 250); assert!(light_dispatch.idle_peers.iter().cloned().collect::>().is_empty()); assert_eq!(light_dispatch.pending_requests.len(), 1); } #[test] fn remote_body_with_one_block_body_should_succeed() { let mut light_dispatch = dummy(true); let mut network_interface = DummyNetwork::default(); let peer1 = PeerId::random(); let header = dummy_header(); light_dispatch.on_connect(&mut network_interface, peer1.clone(), Roles::FULL, 250); light_dispatch.add_request(&mut network_interface, RequestData::RemoteBody(RemoteBodyRequest { header: header.clone(), retry_count: None, }, oneshot::channel().0)); assert!(light_dispatch.pending_requests.is_empty()); assert_eq!(light_dispatch.active_peers.len(), 1); let block = message::BlockData:: { hash: sp_core::H256::random(), header: None, body: Some(Vec::new()), message_queue: None, receipt: None, justification: None, }; let response = message::generic::BlockResponse { id: 0, blocks: vec![block], }; light_dispatch.on_remote_body_response(&mut network_interface, peer1.clone(), response); assert!(light_dispatch.active_peers.is_empty()); assert_eq!(light_dispatch.idle_peers.len(), 1); } #[test] fn remote_body_with_three_bodies_should_fail() { let mut light_dispatch = dummy(true); let mut network_interface = DummyNetwork::default(); let peer1 = PeerId::random(); let header = dummy_header(); light_dispatch.on_connect(&mut network_interface, peer1.clone(), Roles::FULL, 250); light_dispatch.add_request(&mut network_interface, RequestData::RemoteBody(RemoteBodyRequest { header: header.clone(), retry_count: None, }, oneshot::channel().0)); assert!(light_dispatch.pending_requests.is_empty()); assert_eq!(light_dispatch.active_peers.len(), 1); let response = { let blocks: Vec<_> = (0..3).map(|_| message::BlockData:: { hash: sp_core::H256::random(), header: None, body: Some(Vec::new()), message_queue: None, receipt: None, justification: None, }).collect(); message::generic::BlockResponse { id: 0, blocks, } }; light_dispatch.on_remote_body_response(&mut network_interface, peer1.clone(), response); assert!(light_dispatch.active_peers.is_empty()); assert!(light_dispatch.idle_peers.is_empty(), "peer should be disconnected after bad response"); } }