async def _monitor_handshake_completions(self) -> None: """ Ensure that we only ever have one fully handshaked session for any given endpoint/node-id. Anytime we find a duplicate sessions exists we discard them, preferring the newly handshaked session. """ async with self._events.session_handshake_complete.subscribe() as subscription: async for session in subscription: self.logger.debug( "Session established: %s@%s (%s) id=%s", humanize_node_id(session.remote_node_id), session.remote_endpoint, "outbound" if session.is_initiator else "inbound", session.id, ) for other in self._pool.get_sessions_for_endpoint( session.remote_endpoint ): if not other.is_after_handshake: continue elif other.id == session.id: continue elif other.remote_node_id != session.remote_node_id: continue else: self.logger.debug( "Newly handshaked session %s triggered discard of previous session %s", session, other, ) self._pool.remove_session(other.id)
async def _serve_find_nodes(self) -> None: async with self.dispatcher.subscribe(FindNodeMessage) as subscription: async for request in subscription: response_enrs: List[ENRAPI] = [] distances = set(request.message.distances) if len(distances) != len(request.message.distances): self.logger.debug( "Ignoring invalid FindNodeMessage from %s@%s: duplicate distances", humanize_node_id(request.sender_node_id), request.sender_endpoint, ) continue elif not distances: self.logger.debug( "Ignoring invalid FindNodeMessage from %s@%s: empty distances", humanize_node_id(request.sender_node_id), request.sender_endpoint, ) continue elif any(distance > self.routing_table.num_buckets for distance in distances): self.logger.debug( "Ignoring invalid FindNodeMessage from %s@%s: distances: %s", humanize_node_id(request.sender_node_id), request.sender_endpoint, distances, ) continue for distance in distances: if distance == 0: response_enrs.append(self.enr_manager.enr) elif distance <= self.routing_table.num_buckets: node_ids_at_distance = self.routing_table.get_nodes_at_log_distance( distance, ) for node_id in node_ids_at_distance: response_enrs.append(self.enr_db.get_enr(node_id)) else: raise Exception("Should be unreachable") await self.client.send_found_nodes( request.sender_endpoint, request.sender_node_id, enrs=response_enrs, request_id=request.message.request_id, )
async def bond(self, node_id: NodeID, *, endpoint: Optional[Endpoint] = None) -> bool: try: pong = await self.ping(node_id, endpoint=endpoint) except trio.TooSlowError: self.logger.debug("Bonding with %s timed out during ping", humanize_node_id(node_id)) return False try: enr = self.enr_db.get_enr(node_id) except KeyError: try: enr = await self.get_enr(node_id, endpoint=endpoint) except trio.TooSlowError: self.logger.debug( "Bonding with %s timed out during ENR retrieval", humanize_node_id(node_id), ) return False else: if pong.enr_seq > enr.sequence_number: try: enr = await self.get_enr(node_id, endpoint=endpoint) except trio.TooSlowError: self.logger.debug( "Bonding with %s timed out during ENR retrieval", humanize_node_id(node_id), ) else: self.enr_db.set_enr(enr) self.routing_table.update(enr.node_id) self._routing_table_ready.set() return True
async def recursive_find_nodes(self, target: NodeID) -> Tuple[ENRAPI, ...]: self.logger.debug("Recursive find nodes: %s", humanize_node_id(target)) queried_node_ids = set() unresponsive_node_ids = set() received_enrs: List[ENRAPI] = [] received_node_ids: Set[NodeID] = set() async def do_lookup(node_id: NodeID) -> None: queried_node_ids.add(node_id) distance = compute_log_distance(node_id, target) try: enrs = await self.find_nodes(node_id, distance) except trio.TooSlowError: unresponsive_node_ids.add(node_id) return for enr in enrs: received_node_ids.add(enr.node_id) try: self.enr_db.set_enr(enr) except OldSequenceNumber: received_enrs.append(self.enr_db.get_enr(enr.node_id)) else: received_enrs.append(enr) for lookup_round_counter in itertools.count(): candidates = iter_closest_nodes(target, self.routing_table, received_node_ids) responsive_candidates = itertools.dropwhile( lambda node: node in unresponsive_node_ids, candidates) closest_k_candidates = take(self.routing_table.bucket_size, responsive_candidates) closest_k_unqueried_candidates = ( candidate for candidate in closest_k_candidates if candidate not in queried_node_ids) nodes_to_query = tuple(take(3, closest_k_unqueried_candidates)) if nodes_to_query: self.logger.debug( "Starting lookup round %d for %s", lookup_round_counter + 1, humanize_node_id(target), ) async with trio.open_nursery() as nursery: for peer in nodes_to_query: nursery.start_soon(do_lookup, peer) else: self.logger.debug( "Lookup for %s finished in %d rounds", humanize_node_id(target), lookup_round_counter, ) break # now sort and return the ENR records in order of closesness to the target. return tuple( sorted( _reduce_enrs(received_enrs), key=lambda enr: compute_distance(enr.node_id, target), ))