def test_closest_nodes_only_additional(empty_routing_table): target = NodeIDFactory() nodes = [NodeIDFactory() for _ in range(10)] closest_nodes = list(iter_closest_nodes(target, empty_routing_table, nodes)) assert closest_nodes == sorted( nodes, key=lambda node: compute_distance(target, node))
def test_closest_nodes_only_routing(empty_routing_table): target = NodeIDFactory() nodes = [NodeIDFactory() for _ in range(10)] for node in nodes: empty_routing_table.update(node) closest_nodes = list(iter_closest_nodes(target, empty_routing_table, [])) assert closest_nodes == sorted( nodes, key=lambda node: compute_distance(target, node))
def test_lookup_generator_mixed(empty_routing_table): target = NodeIDFactory() nodes = sorted( [NodeIDFactory() for _ in range(10)], key=lambda node: compute_distance(node, target), ) nodes_in_routing_table = nodes[:3] + nodes[6:8] nodes_in_additional = nodes[3:6] + nodes[8:] for node in nodes_in_routing_table: empty_routing_table.update(node) closest_nodes = list( iter_closest_nodes(target, empty_routing_table, nodes_in_additional)) assert closest_nodes == nodes
async def _collate( self, enr_send: trio.abc.SendChannel[ENRAPI], enr_receive: trio.abc.ReceiveChannel[ENRAPI], ) -> None: enr_buffer: List[ENRAPI] = [] yielded_node_ids: Set[NodeID] = {self._network.local_node_id} while self.manager.is_running: # First wait for at least one ENR to be available... enr = await enr_receive.receive() if enr.node_id in yielded_node_ids: continue enr_buffer.append(enr) # Next, empty any additional values that are in the channel buffer. while True: try: enr = enr_receive.receive_nowait() # type: ignore except trio.WouldBlock: break else: if enr.node_id in yielded_node_ids: continue enr_buffer.append(enr) # deduplicate enr_buffer = list(reduce_enrs(enr_buffer)) # sort and pull off closest closest_enr, *enr_buffer = list( sorted( enr_buffer, key=lambda enr: compute_distance( self._network.local_node_id, enr.node_id), )) yielded_node_ids.add(closest_enr.node_id) await enr_send.send(closest_enr)
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), ))
def test_distance(left_node_id, right_node_id, distance): assert compute_distance(left_node_id, right_node_id) == distance assert compute_distance(right_node_id, left_node_id) == distance
async def test_alexandria_network_broadcast_api( tester, alice, alice_alexandria_network, autojump_clock, ): async with AsyncExitStack() as stack: network_group = await stack.enter_async_context( tester.alexandria.network_group(10)) furthest_network = max( network_group, key=lambda network: compute_distance(alice.node_id, network. local_node_id), ) closest_network = min( network_group, key=lambda network: compute_distance(alice.node_id, network. local_node_id), ) furthest_node_distance_from_alice = compute_distance( furthest_network.local_node_id, alice.node_id, ) furthest_ad = AdvertisementFactory() for _ in range(100): advertisement = AdvertisementFactory() distance_from_alice = compute_content_distance( alice.node_id, advertisement.content_id) distance_from_furthest = compute_content_distance( alice.node_id, furthest_ad.content_id) if distance_from_alice > distance_from_furthest: furthest_ad = advertisement if distance_from_furthest >= furthest_node_distance_from_alice: break async with trio.open_nursery() as nursery: async def _respond(network, subscription): request = await subscription.receive() await network.client.send_ack( request.sender_node_id, request.sender_endpoint, advertisement_radius=network.local_advertisement_radius, acked=(True, ) * len(request.message.payload), request_id=request.request_id, ) for network in network_group: subscription = await stack.enter_async_context( network.client.subscribe(AdvertiseMessage)) nursery.start_soon(_respond, network, subscription) alice_alexandria_network.enr_db.set_enr( closest_network.enr_manager.enr) await alice_alexandria_network.bond(closest_network.local_node_id) for _ in range(10000): await trio.lowlevel.checkpoint() with trio.fail_after(30): result = await alice_alexandria_network.broadcast(advertisement ) assert len(result) > 0 nursery.cancel_scope.cancel()