def test_at_log_distance(): for i in range(10000): node = NodeIDFactory() distance = random.randint(1, 256) other = at_log_distance(node, distance) actual = compute_log_distance(node, other) assert actual == distance
async def test_network_explore(tester, alice): async with AsyncExitStack() as stack: networks = await stack.enter_async_context( tester.alexandria.network_group(8)) # give the the network some time to interconnect. with trio.fail_after(20): for _ in range(1000): await trio.lowlevel.checkpoint() bootnodes = tuple(network.enr_manager.enr for network in networks) alice_network = await stack.enter_async_context( alice.alexandria.network(bootnodes=bootnodes)) # give alice a little time to connect to the network as well with trio.fail_after(20): for _ in range(1000): await trio.lowlevel.checkpoint() assert len(set(alice_network.routing_table.iter_all_random())) == 8 target_node_id = at_log_distance(alice.node_id, 256) node_ids_by_distance = tuple( sorted( tuple(network.local_node_id for network in networks), key=lambda node_id: compute_log_distance( target_node_id, node_id), )) best_node_ids_by_distance = set(node_ids_by_distance[:3]) async with alice_network.explore(target_node_id) as enr_aiter: with trio.fail_after(60): found_enrs = tuple([enr async for enr in enr_aiter]) found_node_ids = tuple(enr.node_id for enr in found_enrs) assert len(found_node_ids) == len(networks) + 1 # Ensure that one of the three closest node ids was in the returned node ids assert best_node_ids_by_distance.intersection(found_node_ids)
async def _manage_routing_table(self) -> None: # First load all the bootnode ENRs into our database for enr in self._bootnodes: try: self.enr_db.set_enr(enr) except OldSequenceNumber: pass # Now repeatedly try to bond with each bootnode until one succeeds. while self.manager.is_running: with trio.move_on_after(20): async with trio.open_nursery() as nursery: for enr in self._bootnodes: if enr.node_id == self.local_node_id: continue endpoint = Endpoint.from_enr(enr) nursery.start_soon(self._bond, enr.node_id, endpoint) await self._routing_table_ready.wait() break # Now we enter into an infinite loop that continually probes the # network to beep the routing table fresh. We both perform completely # random lookups, as well as targeted lookups on the outermost routing # table buckets which are not full. # # The `TokenBucket` allows us to burst at the beginning, making quick # successive probes, then slowing down once the # # TokenBucket starts with 10 tokens, refilling at 1 token every 30 # seconds. token_bucket = TokenBucket(1 / 30, 10) async with trio.open_nursery() as nursery: while self.manager.is_running: await token_bucket.take() # Get the logarithmic distance to the "largest" buckets # that are not full. non_full_bucket_distances = tuple( idx + 1 for idx, bucket in enumerate(self.routing_table.buckets) if len(bucket) < self.routing_table.bucket_size # noqa: E501 )[-16:] # Probe one of the not-full-buckets with a weighted preference # towards the largest buckets. distance_to_probe = weighted_choice(non_full_bucket_distances) target_node_id = at_log_distance(self.local_node_id, distance_to_probe) async with self.recursive_find_nodes(target_node_id) as enr_aiter: async for enr in enr_aiter: if enr.node_id == self.local_node_id: continue try: self.enr_db.set_enr(enr) except OldSequenceNumber: pass nursery.start_soon(self._bond, enr.node_id)
def __init__(self, network: NetworkProtocol, concurrency: int = 32) -> None: target = at_log_distance(network.local_node_id, 256) self._explorer = CrawlerExplorer(network, target, concurrency)