//! The Discovery v5 protocol. See `lib.rs` for further details. //! //! Note: Discovered ENR's are not automatically added to the routing table. Only established //! sessions get added, ensuring only valid ENRs are added. Manual additions can be made using the //! `add_enr()` function. //! //! Response to queries return `PeerId`. Only the trusted (a session has been established with) //! `PeerId`'s are returned, as ENR's for these `PeerId`'s are stored in the routing table and as //! such should have an address to connect to. Untrusted `PeerId`'s can be obtained from the //! `Service::Discovered` event, which is fired as peers get discovered. //! //! Note that although the ENR crate does support Ed25519 keys, these are currently not //! supported as the ECDH procedure isn't specified in the specification. Therefore, only //! secp256k1 keys are supported currently. use self::{ ip_vote::IpVote, query_info::{QueryInfo, QueryType}, }; use crate::{ error::{RequestError, ResponseError}, handler::{Handler, HandlerRequest, HandlerResponse}, kbucket::{ self, ConnectionDirection, ConnectionState, FailureReason, InsertResult, KBucketsTable, NodeStatus, UpdateResult, }, node_info::{NodeAddress, NodeContact}, packet::MAX_PACKET_SIZE, query_pool::{ FindNodeQueryConfig, PredicateQueryConfig, QueryId, QueryPool, QueryPoolState, TargetKey, }, rpc, Discv5Config, Discv5Event, Enr, }; use enr::{CombinedKey, NodeId}; use fnv::FnvHashMap; use futures::prelude::*; use hashset_delay::HashSetDelay; use parking_lot::RwLock; use rpc::*; use std::{collections::HashMap, net::SocketAddr, sync::Arc, task::Poll, time::Instant}; use tokio::sync::{mpsc, oneshot}; use tracing::{debug, error, info, trace, warn}; mod hashset_delay; mod ip_vote; mod query_info; mod test; /// Request type for Protocols using `TalkReq` message. /// /// Automatically responds with an empty body on drop if /// [`TalkRequest::respond`] is not called. #[derive(Debug)] pub struct TalkRequest { id: RequestId, node_address: NodeAddress, protocol: Vec, body: Vec, sender: Option>, } impl Drop for TalkRequest { fn drop(&mut self) { let sender = match self.sender.take() { Some(s) => s, None => return, }; let response = Response { id: self.id.clone(), body: ResponseBody::Talk { response: vec![] }, }; debug!("Sending empty TALK response to {}", self.node_address); let _ = sender.send(HandlerRequest::Response( self.node_address.clone(), Box::new(response), )); } } impl TalkRequest { pub fn id(&self) -> &RequestId { &self.id } pub fn node_id(&self) -> &NodeId { &self.node_address.node_id } pub fn protocol(&self) -> &[u8] { &self.protocol } pub fn body(&self) -> &[u8] { &self.body } pub fn respond(mut self, response: Vec) -> Result<(), ResponseError> { debug!("Sending TALK response to {}", self.node_address); let response = Response { id: self.id.clone(), body: ResponseBody::Talk { response }, }; self.sender .take() .unwrap() .send(HandlerRequest::Response( self.node_address.clone(), Box::new(response), )) .map_err(|_| ResponseError::ChannelClosed)?; Ok(()) } } /// The number of distances (buckets) we simultaneously request from each peer. pub(crate) const DISTANCES_TO_REQUEST_PER_PEER: usize = 3; /// The types of requests to send to the Discv5 service. pub enum ServiceRequest { /// A request to start a query. There are two types of queries: /// - A FindNode Query - Searches for peers using a random target. /// - A Predicate Query - Searches for peers closest to a random target that match a specified /// predicate. StartQuery(QueryKind, oneshot::Sender>), /// Find the ENR of a node given its multiaddr. FindEnr(NodeContact, oneshot::Sender>), /// The TALK discv5 RPC function. Talk( NodeContact, Vec, Vec, oneshot::Sender, RequestError>>, ), /// Sets up an event stream where the discv5 server will return various events such as /// discovered nodes as it traverses the DHT. RequestEventStream(oneshot::Sender>), } use crate::discv5::PERMIT_BAN_LIST; pub struct Service { /// Configuration parameters. config: Discv5Config, /// The local ENR of the server. local_enr: Arc>, /// The key associated with the local ENR. enr_key: Arc>, /// Storage of the ENR record for each node. kbuckets: Arc>>, /// All the iterative queries we are currently performing. queries: QueryPool, /// RPC requests that have been sent and are awaiting a response. Some requests are linked to a /// query. active_requests: FnvHashMap, /// Keeps track of the number of responses received from a NODES response. active_nodes_responses: HashMap, /// A map of votes nodes have made about our external IP address. We accept the majority. ip_votes: Option, /// The channel to send messages to the handler. handler_send: mpsc::UnboundedSender, /// The channel to receive messages from the handler. handler_recv: mpsc::Receiver, /// The exit channel to shutdown the handler. handler_exit: Option>, /// The channel of messages sent by the controlling discv5 wrapper. discv5_recv: mpsc::Receiver, /// The exit channel for the service. exit: oneshot::Receiver<()>, /// A queue of peers that require regular ping to check connectivity. peers_to_ping: HashSetDelay, /// A channel that the service emits events on. event_stream: Option>, } /// Active RPC request awaiting a response from the handler. struct ActiveRequest { /// The address the request was sent to. pub contact: NodeContact, /// The request that was sent. pub request_body: RequestBody, /// The query ID if the request was related to a query. pub query_id: Option, /// Channel callback if this request was from a user level request. pub callback: Option, } /// The kinds of responses we can send back to the discv5 layer. pub enum CallbackResponse { /// A response to a requested ENR. Enr(oneshot::Sender>), /// A response from a TALK request Talk(oneshot::Sender, RequestError>>), } /// For multiple responses to a FindNodes request, this keeps track of the request count /// and the nodes that have been received. struct NodesResponse { /// The response count. count: usize, /// The filtered nodes that have been received. received_nodes: Vec, } impl Default for NodesResponse { fn default() -> Self { NodesResponse { count: 1, received_nodes: Vec::new(), } } } impl Service { /// Builds the `Service` main struct. /// /// `local_enr` is the `ENR` representing the local node. This contains node identifying information, such /// as IP addresses and ports which we wish to broadcast to other nodes via this discovery /// mechanism. pub async fn spawn( local_enr: Arc>, enr_key: Arc>, kbuckets: Arc>>, config: Discv5Config, listen_socket: SocketAddr, ) -> Result<(oneshot::Sender<()>, mpsc::Sender), std::io::Error> { // process behaviour-level configuration parameters let ip_votes = if config.enr_update { Some(IpVote::new( config.enr_peer_update_min, config.vote_duration, )) } else { None }; // build the session service let (handler_exit, handler_send, handler_recv) = Handler::spawn( local_enr.clone(), enr_key.clone(), listen_socket, config.clone(), ) .await?; // create the required channels let (discv5_send, discv5_recv) = mpsc::channel(30); let (exit_send, exit) = oneshot::channel(); config .executor .clone() .expect("Executor must be present") .spawn(Box::pin(async move { let mut service = Service { local_enr, enr_key, kbuckets, queries: QueryPool::new(config.query_timeout), active_requests: Default::default(), active_nodes_responses: HashMap::new(), ip_votes, handler_send, handler_recv, handler_exit: Some(handler_exit), peers_to_ping: HashSetDelay::new(config.ping_interval), discv5_recv, event_stream: None, exit, config: config.clone(), }; info!("Discv5 Service started"); service.start().await; })); Ok((exit_send, discv5_send)) } /// The main execution loop of the discv5 serviced. async fn start(&mut self) { loop { tokio::select! { _ = &mut self.exit => { if let Some(exit) = self.handler_exit.take() { let _ = exit.send(()); info!("Discv5 Service shutdown"); } return; } Some(service_request) = self.discv5_recv.recv() => { match service_request { ServiceRequest::StartQuery(query, callback) => { match query { QueryKind::FindNode { target_node } => { self.start_findnode_query(target_node, callback); } QueryKind::Predicate { target_node, target_peer_no, predicate } => { self.start_predicate_query(target_node, target_peer_no, predicate, callback); } } } ServiceRequest::FindEnr(node_contact, callback) => { self.request_enr(node_contact, Some(callback)); } ServiceRequest::Talk(node_contact, protocol, request, callback) => { self.talk_request(node_contact, protocol, request, callback); } ServiceRequest::RequestEventStream(callback) => { // the channel size needs to be large to handle many discovered peers // if we are reporting them on the event stream. let channel_size = if self.config.report_discovered_peers { 100 } else { 30 }; let (event_stream, event_stream_recv) = mpsc::channel(channel_size); self.event_stream = Some(event_stream); if callback.send(event_stream_recv).is_err() { error!("Failed to return the event stream channel"); } } } } Some(event) = self.handler_recv.recv() => { match event { HandlerResponse::Established(enr, direction) => { self.inject_session_established(enr,direction); } HandlerResponse::Request(node_address, request) => { self.handle_rpc_request(node_address, *request); } HandlerResponse::Response(node_address, response) => { self.handle_rpc_response(node_address, *response); } HandlerResponse::WhoAreYou(whoareyou_ref) => { // check what our latest known ENR is for this node. if let Some(known_enr) = self.find_enr(&whoareyou_ref.0.node_id) { let _ = self.handler_send.send(HandlerRequest::WhoAreYou(whoareyou_ref, Some(known_enr))); } else { // do not know of this peer debug!("NodeId unknown, requesting ENR. {}", whoareyou_ref.0); let _ = self.handler_send.send(HandlerRequest::WhoAreYou(whoareyou_ref, None)); } } HandlerResponse::RequestFailed(request_id, error) => { if let RequestError::Timeout = error { debug!("RPC Request timed out. id: {}", request_id); } else { warn!("RPC Request failed: id: {}, error {:?}", request_id, error); } self.rpc_failure(request_id, error); } } } event = Service::bucket_maintenance_poll(&self.kbuckets) => { self.send_event(event); } query_event = Service::query_event_poll(&mut self.queries) => { match query_event { QueryEvent::Waiting(query_id, node_id, request_body) => { self.send_rpc_query(query_id, node_id, request_body); } // Note: Currently the distinction between a timed-out query and a finished // query is superfluous, however it may be useful in future versions. QueryEvent::Finished(query) | QueryEvent::TimedOut(query) => { let id = query.id(); let mut result = query.into_result(); // obtain the ENR's for the resulting nodes let mut found_enrs = Vec::new(); for node_id in result.closest_peers.into_iter() { if let Some(position) = result.target.untrusted_enrs.iter().position(|enr| enr.node_id() == node_id) { let enr = result.target.untrusted_enrs.swap_remove(position); found_enrs.push(enr); } else if let Some(enr) = self.find_enr(&node_id) { // look up from the routing table found_enrs.push(enr); } else { warn!("ENR not present in queries results"); } } if result.target.callback.send(found_enrs).is_err() { warn!("Callback dropped for query {}. Results dropped", *id); } } } } Some(Ok(node_id)) = self.peers_to_ping.next() => { // If the node is in the routing table, Ping it and re-queue the node. let key = kbucket::Key::from(node_id); let enr = { if let kbucket::Entry::Present(entry, _) = self.kbuckets.write().entry(&key) { // The peer is in the routing table, ping it and re-queue the ping self.peers_to_ping.insert(node_id); Some(entry.value().clone()) } else { None } }; if let Some(enr) = enr { self.send_ping(enr); } } } } } /// Internal function that starts a query. fn start_findnode_query(&mut self, target_node: NodeId, callback: oneshot::Sender>) { let mut target = QueryInfo { query_type: QueryType::FindNode(target_node), untrusted_enrs: Default::default(), distances_to_request: DISTANCES_TO_REQUEST_PER_PEER, callback, }; let target_key: kbucket::Key = target.key(); let mut known_closest_peers = Vec::new(); { let mut kbuckets = self.kbuckets.write(); for closest in kbuckets.closest_values(&target_key) { // Add the known ENR's to the untrusted list target.untrusted_enrs.push(closest.value); // Add the key to the list for the query known_closest_peers.push(closest.key); } } if known_closest_peers.is_empty() { warn!("No known_closest_peers found. Return empty result without sending query."); if target.callback.send(vec![]).is_err() { warn!("Failed to callback"); } } else { let query_config = FindNodeQueryConfig::new_from_config(&self.config); self.queries .add_findnode_query(query_config, target, known_closest_peers); } } /// Internal function that starts a query. fn start_predicate_query( &mut self, target_node: NodeId, num_nodes: usize, predicate: Box bool + Send>, callback: oneshot::Sender>, ) { let mut target = QueryInfo { query_type: QueryType::FindNode(target_node), untrusted_enrs: Default::default(), distances_to_request: DISTANCES_TO_REQUEST_PER_PEER, callback, }; let target_key: kbucket::Key = target.key(); // Map the TableEntry to an ENR. let kbucket_predicate = |e: &Enr| predicate(e); let mut known_closest_peers = Vec::>::new(); { let mut kbuckets = self.kbuckets.write(); for closest in kbuckets.closest_values_predicate(&target_key, &kbucket_predicate) { // Add the known ENR's to the untrusted list target.untrusted_enrs.push(closest.value.clone()); // Add the key to the list for the query known_closest_peers.push(closest.into()); } }; if known_closest_peers.is_empty() { warn!("No known_closest_peers found. Return empty result without sending query."); if target.callback.send(vec![]).is_err() { warn!("Failed to callback"); } } else { let mut query_config = PredicateQueryConfig::new_from_config(&self.config); query_config.num_results = num_nodes; self.queries .add_predicate_query(query_config, target, known_closest_peers, predicate); } } /// Returns an ENR if one is known for the given NodeId. pub fn find_enr(&self, node_id: &NodeId) -> Option { // check if we know this node id in our routing table let key = kbucket::Key::from(*node_id); if let kbucket::Entry::Present(entry, _) = self.kbuckets.write().entry(&key) { return Some(entry.value().clone()); } // check the untrusted addresses for ongoing queries for query in self.queries.iter() { if let Some(enr) = query .target() .untrusted_enrs .iter() .find(|v| v.node_id() == *node_id) { return Some(enr.clone()); } } None } /// Processes an RPC request from a peer. Requests respond to the received socket address, /// rather than the IP of the known ENR. fn handle_rpc_request(&mut self, node_address: NodeAddress, req: Request) { let id = req.id; match req.body { RequestBody::FindNode { distances } => { self.send_nodes_response(node_address, id, distances); } RequestBody::Ping { enr_seq } => { // check if we need to update the known ENR let mut to_request_enr = None; match self.kbuckets.write().entry(&node_address.node_id.into()) { kbucket::Entry::Present(ref mut entry, _) => { if entry.value().seq() < enr_seq { let enr = entry.value().clone(); to_request_enr = Some(enr.into()); } } kbucket::Entry::Pending(ref mut entry, _) => { if entry.value().seq() < enr_seq { let enr = entry.value().clone(); to_request_enr = Some(enr.into()); } } // don't know of the ENR, request the update _ => {} } if let Some(enr) = to_request_enr { self.request_enr(enr, None); } // build the PONG response let src = node_address.socket_addr; let response = Response { id, body: ResponseBody::Pong { enr_seq: self.local_enr.read().seq(), ip: src.ip(), port: src.port(), }, }; debug!("Sending PONG response to {}", node_address); let _ = self .handler_send .send(HandlerRequest::Response(node_address, Box::new(response))); } RequestBody::Talk { protocol, request } => { let req = TalkRequest { id, node_address, protocol, body: request, sender: Some(self.handler_send.clone()), }; self.send_event(Discv5Event::TalkRequest(req)); } RequestBody::RegisterTopic { .. } => { debug!("Received RegisterTopic request which is unimplemented"); } RequestBody::TopicQuery { .. } => { debug!("Received TopicQuery request which is unimplemented"); } } } /// Processes an RPC response from a peer. fn handle_rpc_response(&mut self, node_address: NodeAddress, response: Response) { // verify we know of the rpc_id let id = response.id.clone(); if let Some(mut active_request) = self.active_requests.remove(&id) { debug!( "Received RPC response: {} to request: {} from: {}", response.body, active_request.request_body, active_request.contact ); // Check that the responder matches the expected request if let Ok(request_node_address) = active_request.contact.node_address() { if request_node_address != node_address { warn!("Received a response from an unexpected address. Expected {}, received {}, request_id {}", request_node_address, node_address, id); return; } } let node_id = active_request.contact.node_id(); if !response.match_request(&active_request.request_body) { warn!( "Node gave an incorrect response type. Ignoring response from: {}", active_request.contact ); return; } match response.body { ResponseBody::Nodes { total, mut nodes } => { // Currently a maximum of DISTANCES_TO_REQUEST_PER_PEER*BUCKET_SIZE peers can be returned. Datagrams have a max // size of 1280 and ENR's have a max size of 300 bytes. // // Bucket sizes should be 16. In this case, there should be no more than 5*DISTANCES_TO_REQUEST_PER_PEER responses, to return all required peers. if total > 5 * DISTANCES_TO_REQUEST_PER_PEER as u64 { warn!( "NodesResponse has a total larger than {}, nodes will be truncated", DISTANCES_TO_REQUEST_PER_PEER * 5 ); } // These are sanitized and ordered let distances_requested = match &active_request.request_body { RequestBody::FindNode { distances } => distances, _ => unreachable!(), }; // This could be an ENR request from the outer service. If so respond to the // callback and End. if let Some(CallbackResponse::Enr(callback)) = active_request.callback.take() { // Currently only support requesting for ENR's. Verify this is the case. if !distances_requested.is_empty() && distances_requested[0] != 0 { error!("Retrieved a callback request that wasn't for a peer's ENR"); return; } // This must be for asking for an ENR if nodes.len() > 1 { warn!( "Peer returned more than one ENR for itself. {}", active_request.contact ); } let response = nodes.pop().ok_or_else(|| { RequestError::InvalidEnr("Peer did not return an ENR".into()) }); let _ = callback.send(response); return; } // Filter out any nodes that are not of the correct distance let peer_key: kbucket::Key = node_id.into(); // The distances we send are sanitized an ordered. // We never send an ENR request in combination of other requests. if distances_requested.len() == 1 && distances_requested[0] == 0 { // we requested an ENR update if nodes.len() > 1 { warn!( "Peer returned more than one ENR for itself. Blacklisting {}", active_request.contact ); } let ban_timeout = self.config.ban_duration.map(|v| Instant::now() + v); PERMIT_BAN_LIST.write().ban( active_request .contact .node_address() .expect("Sanitized request"), ban_timeout, ); nodes.retain(|enr| peer_key.log2_distance(&enr.node_id().into()).is_none()); } else { let before_len = nodes.len(); nodes.retain(|enr| { peer_key .log2_distance(&enr.node_id().into()) .map(|distance| distances_requested.contains(&distance)) .unwrap_or_else(|| false) }); if nodes.len() < before_len { // Peer sent invalid ENRs. Blacklist the Node warn!( "Peer sent invalid ENR. Blacklisting {}", active_request.contact ); let ban_timeout = self.config.ban_duration.map(|v| Instant::now() + v); PERMIT_BAN_LIST.write().ban( active_request .contact .node_address() .expect("Sanitized request"), ban_timeout, ); } } // handle the case that there is more than one response if total > 1 { let mut current_response = self .active_nodes_responses .remove(&node_id) .unwrap_or_default(); debug!( "Nodes Response: {} of {} received", current_response.count, total ); // if there are more requests coming, store the nodes and wait for // another response // We allow for implementations to send at a minimum 3 nodes per response. // We allow for the number of nodes to be returned as the maximum we emit. if current_response.count < self.config.max_nodes_response / 3 + 1 && (current_response.count as u64) < total { current_response.count += 1; current_response.received_nodes.append(&mut nodes); self.active_nodes_responses .insert(node_id, current_response); self.active_requests.insert(id, active_request); return; } // have received all the Nodes responses we are willing to accept // ignore duplicates here as they will be handled when adding // to the DHT current_response.received_nodes.append(&mut nodes); nodes = current_response.received_nodes; } debug!( "Received a nodes response of len: {}, total: {}, from: {}", nodes.len(), total, active_request.contact ); // note: If a peer sends an initial NODES response with a total > 1 then // in a later response sends a response with a total of 1, all previous nodes // will be ignored. // ensure any mapping is removed in this rare case self.active_nodes_responses.remove(&node_id); self.discovered(&node_id, nodes, active_request.query_id); } ResponseBody::Pong { enr_seq, ip, port } => { let socket = SocketAddr::new(ip, port); // perform ENR majority-based update if required. // // only attempt the majority-update if the peer supplies an ipv4 address to // mitigate https://github.com/sigp/lighthouse/issues/2215 // // Only count votes that from peers we have contacted. let key: kbucket::Key = node_id.into(); let should_count = match self.kbuckets.write().entry(&key) { kbucket::Entry::Present(_, status) if status.is_connected() && !status.is_incoming() => { true } _ => false, }; if should_count && socket.is_ipv4() { let local_socket = self.local_enr.read().udp_socket(); if let Some(ref mut ip_votes) = self.ip_votes { ip_votes.insert(node_id, socket); if let Some(majority_socket) = ip_votes.majority() { if Some(majority_socket) != local_socket { info!("Local UDP socket updated to: {}", majority_socket); self.send_event(Discv5Event::SocketUpdated(majority_socket)); // Update the UDP socket if self .local_enr .write() .set_udp_socket(majority_socket, &self.enr_key.read()) .is_ok() { self.ping_connected_peers(); } } } } } // check if we need to request a new ENR if let Some(enr) = self.find_enr(&node_id) { if enr.seq() < enr_seq { // request an ENR update debug!("Requesting an ENR update from: {}", active_request.contact); let request_body = RequestBody::FindNode { distances: vec![0] }; let active_request = ActiveRequest { contact: active_request.contact, request_body, query_id: None, callback: None, }; self.send_rpc_request(active_request); } self.connection_updated(node_id, ConnectionStatus::PongReceived(enr)); } } ResponseBody::Talk { response } => { // Send the response to the user match active_request.callback { Some(CallbackResponse::Talk(callback)) => { let _ = callback.send(Ok(response)); } _ => error!("Invalid callback for response"), } } ResponseBody::Ticket { .. } => { error!("Received a TICKET response. This is unimplemented and should be unreachable."); } ResponseBody::RegisterConfirmation { .. } => { error!("Received a RegisterConfirmation response. This is unimplemented and should be unreachable."); } } } else { warn!( "Received an RPC response which doesn't match a request. Id: {}", id ); } } // Send RPC Requests // /// Sends a PING request to a node. fn send_ping(&mut self, enr: Enr) { let request_body = RequestBody::Ping { enr_seq: self.local_enr.read().seq(), }; let active_request = ActiveRequest { contact: enr.into(), request_body, query_id: None, callback: None, }; self.send_rpc_request(active_request); } /// Ping all peers that are connected in the routing table. fn ping_connected_peers(&mut self) { // maintain the ping interval let connected_peers = { let mut kbuckets = self.kbuckets.write(); kbuckets .iter() .filter_map(|entry| { if entry.status.is_connected() { Some(entry.node.value.clone()) } else { None } }) .collect::>() }; for enr in connected_peers { self.send_ping(enr.clone()); } } /// Request an external node's ENR. fn request_enr( &mut self, contact: NodeContact, callback: Option>>, ) { let request_body = RequestBody::FindNode { distances: vec![0] }; let active_request = ActiveRequest { contact, request_body, query_id: None, callback: callback.map(CallbackResponse::Enr), }; self.send_rpc_request(active_request); } /// Requests a TALK message from the peer. fn talk_request( &mut self, contact: NodeContact, protocol: Vec, request: Vec, callback: oneshot::Sender, RequestError>>, ) { let request_body = RequestBody::Talk { protocol, request }; let active_request = ActiveRequest { contact, request_body, query_id: None, callback: Some(CallbackResponse::Talk(callback)), }; self.send_rpc_request(active_request); } /// Sends a NODES response, given a list of found ENR's. This function splits the nodes up /// into multiple responses to ensure the response stays below the maximum packet size. fn send_nodes_response( &mut self, node_address: NodeAddress, rpc_id: RequestId, mut distances: Vec, ) { // NOTE: At most we only allow 5 distances to be sent (see the decoder). If each of these // buckets are full, that equates to 80 ENR's to respond with. let mut nodes_to_send = Vec::new(); distances.sort_unstable(); distances.dedup(); if let Some(0) = distances.first() { // if the distance is 0 send our local ENR nodes_to_send.push(self.local_enr.read().clone()); debug!("Sending our ENR to node: {}", node_address); distances.remove(0); } if !distances.is_empty() { let mut kbuckets = self.kbuckets.write(); for node in kbuckets .nodes_by_distances(distances.as_slice(), self.config.max_nodes_response) .into_iter() .filter_map(|entry| { if entry.node.key.preimage() != &node_address.node_id { Some(entry.node.value.clone()) } else { None } }) { nodes_to_send.push(node); } } // if there are no nodes, send an empty response if nodes_to_send.is_empty() { let response = Response { id: rpc_id, body: ResponseBody::Nodes { total: 1u64, nodes: Vec::new(), }, }; trace!( "Sending empty FINDNODES response to: {}", node_address.node_id ); let _ = self .handler_send .send(HandlerRequest::Response(node_address, Box::new(response))); } else { // build the NODES response let mut to_send_nodes: Vec> = Vec::new(); let mut total_size = 0; let mut rpc_index = 0; to_send_nodes.push(Vec::new()); for enr in nodes_to_send.into_iter() { let entry_size = rlp::encode(&enr).len(); // Responses assume that a session is established. Thus, on top of the encoded // ENR's the packet should be a regular message. A regular message has an IV (16 // bytes), and a header of 55 bytes. The find-nodes RPC requires 16 bytes for the ID and the // `total` field. Also there is a 16 byte HMAC for encryption and an extra byte for // RLP encoding. // // We could also be responding via an autheader which can take up to 282 bytes in its // header. // As most messages will be normal messages we will try and pack as many ENR's we // can in and drop the response packet if a user requests an auth message of a very // packed response. // // The estimated total overhead for a regular message is therefore 104 bytes. if entry_size + total_size < MAX_PACKET_SIZE - 104 { total_size += entry_size; trace!( "Adding ENR {}, size {}, total size {}", enr, entry_size, total_size ); to_send_nodes[rpc_index].push(enr); } else { total_size = entry_size; to_send_nodes.push(vec![enr]); rpc_index += 1; } } let responses: Vec = to_send_nodes .into_iter() .map(|nodes| Response { id: rpc_id.clone(), body: ResponseBody::Nodes { total: (rpc_index + 1) as u64, nodes, }, }) .collect(); for response in responses { trace!( "Sending FINDNODES response to: {}. Response: {} ", node_address, response ); let _ = self.handler_send.send(HandlerRequest::Response( node_address.clone(), Box::new(response), )); } } } /// Constructs and sends a request RPC to the session service given a `QueryInfo`. fn send_rpc_query( &mut self, query_id: QueryId, return_peer: NodeId, request_body: RequestBody, ) { // find the ENR associated with the query if let Some(enr) = self.find_enr(&return_peer) { let active_request = ActiveRequest { contact: enr.into(), request_body, query_id: Some(query_id), callback: None, }; self.send_rpc_request(active_request); } else { error!("Query {} requested an unknown ENR", *query_id); } } /// Sends generic RPC requests. Each request gets added to known outputs, awaiting a response. fn send_rpc_request(&mut self, active_request: ActiveRequest) { // Generate a random rpc_id which is matched per node id let id = RequestId::random(); let request: Request = Request { id: id.clone(), body: active_request.request_body.clone(), }; let contact = active_request.contact.clone(); self.active_requests.insert(id, active_request); debug!("Sending RPC {} to node: {}", request, contact); let _ = self .handler_send .send(HandlerRequest::Request(contact, Box::new(request))); } fn send_event(&mut self, event: Discv5Event) { if let Some(stream) = self.event_stream.as_mut() { if let Err(mpsc::error::TrySendError::Closed(_)) = stream.try_send(event) { // If the stream has been dropped prevent future attempts to send events self.event_stream = None; } } } /// Processes discovered peers from a query. fn discovered(&mut self, source: &NodeId, mut enrs: Vec, query_id: Option) { let local_id = self.local_enr.read().node_id(); enrs.retain(|enr| { if enr.node_id() == local_id { return false; } // If any of the discovered nodes are in the routing table, and there contains an older ENR, update it. // If there is an event stream send the Discovered event if self.config.report_discovered_peers { self.send_event(Discv5Event::Discovered(enr.clone())); } // ignore peers that don't pass the table filter if (self.config.table_filter)(enr) { let key = kbucket::Key::from(enr.node_id()); // If the ENR exists in the routing table and the discovered ENR has a greater // sequence number, perform some filter checks before updating the enr. let must_update_enr = match self.kbuckets.write().entry(&key) { kbucket::Entry::Present(entry, _) => entry.value().seq() < enr.seq(), kbucket::Entry::Pending(mut entry, _) => entry.value().seq() < enr.seq(), _ => false, }; if must_update_enr { if let UpdateResult::Failed(reason) = self.kbuckets.write().update_node(&key, enr.clone(), None) { self.peers_to_ping.remove(&enr.node_id()); debug!( "Failed to update discovered ENR. Node: {}, Reason: {:?}", source, reason ); false // Remove this peer from the discovered list } else { true // Keep this peer in the list } } else { true // We don't need to update ENR } } else { false // Didn't pass the table filter } }); // if this is part of a query, update the query if let Some(query_id) = query_id { if let Some(query) = self.queries.get_mut(query_id) { let mut peer_count = 0; for enr_ref in enrs.iter() { if !query .target_mut() .untrusted_enrs .iter() .any(|e| e.node_id() == enr_ref.node_id()) { query.target_mut().untrusted_enrs.push(enr_ref.clone()); } peer_count += 1; } debug!("{} peers found for query id {:?}", peer_count, query_id); query.on_success(source, &enrs) } else { debug!("Response returned for ended query {:?}", query_id) } } } /// Update the connection status of a node in the routing table. /// This tracks whether or not we should be pinging peers. Disconnected peers are removed from /// the queue and newly added peers to the routing table are added to the queue. fn connection_updated(&mut self, node_id: NodeId, new_status: ConnectionStatus) { // Variables to that may require post-processing let mut ping_peer = None; let mut event_to_send = None; let key = kbucket::Key::from(node_id); match new_status { ConnectionStatus::Connected(enr, direction) => { // attempt to update or insert the new ENR. let status = NodeStatus { state: ConnectionState::Connected, direction, }; match self.kbuckets.write().insert_or_update(&key, enr, status) { InsertResult::Inserted => { // We added this peer to the table debug!("New connected node added to routing table: {}", node_id); self.peers_to_ping.insert(node_id); let event = Discv5Event::NodeInserted { node_id, replaced: None, }; event_to_send = Some(event); } InsertResult::Pending { disconnected } => { ping_peer = Some(disconnected); } InsertResult::StatusUpdated { promoted_to_connected, } | InsertResult::Updated { promoted_to_connected, } => { // The node was updated if promoted_to_connected { debug!("Node promoted to connected: {}", node_id); self.peers_to_ping.insert(node_id); } } InsertResult::ValueUpdated | InsertResult::UpdatedPending => {} InsertResult::Failed(reason) => { self.peers_to_ping.remove(&node_id); trace!("Could not insert node: {}, reason: {:?}", node_id, reason); } } } ConnectionStatus::PongReceived(enr) => { match self .kbuckets .write() .update_node(&key, enr, Some(ConnectionState::Connected)) { UpdateResult::Failed(reason) => { self.peers_to_ping.remove(&node_id); debug!( "Could not update ENR from pong. Node: {}, reason: {:?}", node_id, reason ); } update => { debug!("Updated {:?}", update) } // Updated ENR successfully. } } ConnectionStatus::Disconnected => { // If the node has disconnected, remove any ping timer for the node. match self.kbuckets.write().update_node_status( &key, ConnectionState::Disconnected, None, ) { UpdateResult::Failed(reason) => match reason { FailureReason::KeyNonExistant => {} others => { warn!( "Could not update node to disconnected. Node: {}, Reason: {:?}", node_id, others ); } }, _ => { debug!("Node set to disconnected: {}", node_id) } } self.peers_to_ping.remove(&node_id); } }; // Post processing if let Some(event) = event_to_send { self.send_event(event); } if let Some(node_key) = ping_peer { let optional_enr = { if let kbucket::Entry::Present(entry, _status) = self.kbuckets.write().entry(&node_key) { // NOTE: We don't check the status of this peer. We try and ping outdated peers. Some(entry.value().clone()) } else { None } }; if let Some(enr) = optional_enr { self.send_ping(enr) } } } /// The equivalent of libp2p `inject_connected()` for a udp session. We have no stream, but a /// session key-pair has been negotiated. fn inject_session_established(&mut self, enr: Enr, direction: ConnectionDirection) { // Ignore sessions with non-contactable ENRs if enr.udp_socket().is_none() { return; } let node_id = enr.node_id(); debug!( "Session established with Node: {}, direction: {}", node_id, direction ); self.connection_updated(node_id, ConnectionStatus::Connected(enr, direction)); } /// A session could not be established or an RPC request timed-out (after a few retries, if /// specified). fn rpc_failure(&mut self, id: RequestId, error: RequestError) { trace!("RPC Error removing request. Reason: {:?}, id {}", error, id); if let Some(active_request) = self.active_requests.remove(&id) { // If this is initiated by the user, return an error on the callback. All callbacks // support a request error. match active_request.callback { Some(CallbackResponse::Enr(callback)) => { callback .send(Err(error)) .unwrap_or_else(|_| debug!("Couldn't send TALK error response to user")); return; } Some(CallbackResponse::Talk(callback)) => { // return the error callback .send(Err(error)) .unwrap_or_else(|_| debug!("Couldn't send TALK error response to user")); return; } None => { // no callback to send too } } let node_id = active_request.contact.node_id(); match active_request.request_body { // if a failed FindNodes request, ensure we haven't partially received packets. If // so, process the partially found nodes RequestBody::FindNode { .. } => { if let Some(nodes_response) = self.active_nodes_responses.remove(&node_id) { if !nodes_response.received_nodes.is_empty() { warn!( "NODES Response failed, but was partially processed from: {}", active_request.contact ); // if it's a query mark it as success, to process the partial // collection of peers self.discovered( &node_id, nodes_response.received_nodes, active_request.query_id, ); } } else { // there was no partially downloaded nodes inform the query of the failure // if it's part of a query if let Some(query_id) = active_request.query_id { if let Some(query) = self.queries.get_mut(query_id) { query.on_failure(&node_id); } } else { debug!( "Failed RPC request: {}: {} ", active_request.request_body, active_request.contact ); } } } // for all other requests, if any are queries, mark them as failures. _ => { if let Some(query_id) = active_request.query_id { if let Some(query) = self.queries.get_mut(query_id) { debug!( "Failed query request: {} for query: {} and {} ", active_request.request_body, *query_id, active_request.contact ); query.on_failure(&node_id); } } else { debug!( "Failed RPC request: {} for node: {}, reason {:?} ", active_request.request_body, active_request.contact, error ); } } } self.connection_updated(node_id, ConnectionStatus::Disconnected); } } /// A future that maintains the routing table and inserts nodes when required. This returns the /// `Discv5Event::NodeInserted` variant if a new node has been inserted into the routing table. async fn bucket_maintenance_poll( kbuckets: &Arc>>, ) -> Discv5Event { future::poll_fn(move |_cx| { // Drain applied pending entries from the routing table. if let Some(entry) = kbuckets.write().take_applied_pending() { let event = Discv5Event::NodeInserted { node_id: entry.inserted.into_preimage(), replaced: entry.evicted.map(|n| n.key.into_preimage()), }; return Poll::Ready(event); } Poll::Pending }) .await } /// A future the maintains active queries. This returns completed and timed out queries, as /// well as queries which need to be driven further with extra requests. async fn query_event_poll(queries: &mut QueryPool) -> QueryEvent { future::poll_fn(move |_cx| match queries.poll() { QueryPoolState::Finished(query) => Poll::Ready(QueryEvent::Finished(Box::new(query))), QueryPoolState::Waiting(Some((query, return_peer))) => { let node_id = return_peer; let request_body = match query.target().rpc_request(return_peer) { Ok(r) => r, Err(e) => { // dst node is local_key, report failure error!("Send RPC failed: {}", e); query.on_failure(&node_id); return Poll::Pending; } }; Poll::Ready(QueryEvent::Waiting(query.id(), node_id, request_body)) } QueryPoolState::Timeout(query) => { warn!("Query id: {:?} timed out", query.id()); Poll::Ready(QueryEvent::TimedOut(Box::new(query))) } QueryPoolState::Waiting(None) | QueryPoolState::Idle => Poll::Pending, }) .await } } /// The result of the `query_event_poll` indicating an action is required to further progress an /// active query. enum QueryEvent { /// The query is waiting for a peer to be contacted. Waiting(QueryId, NodeId, RequestBody), /// The query has timed out, possible returning peers. TimedOut(Box>), /// The query has completed successfully. Finished(Box>), } /// The types of queries that can be made. pub enum QueryKind { /// A FindNode query. Searches for peers that are closest to a particular target. FindNode { target_node: NodeId }, /// A predicate query. Searches for peers that are close to a target but filtered by a specific /// predicate and limited by a target peer count. Predicate { target_node: NodeId, target_peer_no: usize, predicate: Box bool + Send>, }, } /// Reporting the connection status of a node. enum ConnectionStatus { /// A node has started a new connection with us. Connected(Enr, ConnectionDirection), /// We received a Pong from a new node. Do not have the connection direction. PongReceived(Enr), /// The node has disconnected Disconnected, }