Example #1
0
    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)
Example #2
0
    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,
                )
Example #3
0
    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
Example #4
0
    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),
            ))