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. async with trio.open_nursery() as nursery: while self.manager.is_running: 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) with trio.move_on_after(10): await self._routing_table_ready.wait() break # TODO: Need better logic here for more quickly populating the # routing table. Should start off aggressively filling in the # table, only backing off once the table contains some minimum # number of records **or** searching for new records fails to find # new nodes. Maybe use a TokenBucket async for _ in every(30): async with trio.open_nursery() as nursery: target_node_id = NodeID(secrets.token_bytes(32)) found_enrs = await self.recursive_find_nodes(target_node_id) for enr in found_enrs: endpoint = Endpoint.from_enr(enr) nursery.start_soon(self._bond, enr.node_id, endpoint)
def validate_and_extract_destination( value: Any) -> Tuple[NodeID, Optional[Endpoint]]: node_id: NodeID endpoint: Optional[Endpoint] if is_hex_node_id(value): node_id = NodeID(decode_hex(value)) endpoint = None elif value.startswith("enode://"): raw_node_id, _, raw_endpoint = value[8:].partition("@") validate_hex_node_id(raw_node_id) validate_endpoint(raw_endpoint) node_id = NodeID(decode_hex(raw_node_id)) raw_ip_address, _, raw_port = raw_endpoint.partition(":") ip_address = ipaddress.ip_address(raw_ip_address) port = int(raw_port) endpoint = Endpoint(ip_address.packed, port) elif value.startswith("enr:"): enr = ENR.from_repr(value) node_id = enr.node_id endpoint = Endpoint.from_enr(enr) else: raise RPCError(f"Unrecognized node identifier: {value}") return node_id, endpoint
async def endpoint_for_node_id(self, node_id: NodeID) -> Endpoint: try: enr = self.enr_db.get_enr(node_id) except KeyError: enr = await self.lookup_enr(node_id) return Endpoint.from_enr(enr)
async def lookup_enr( self, node_id: NodeID, *, enr_seq: int = 0, endpoint: Optional[Endpoint] = None ) -> ENRAPI: if node_id == self.local_node_id: raise Exception(f"Cannot lookup local ENR: node_id={node_id.hex()}") try: enr = self.enr_db.get_enr(node_id) except KeyError: if endpoint is None: # Try to use a recursive network lookup to find the desired # node. async with self.recursive_find_nodes(node_id) as enr_aiter: async for found_enr in enr_aiter: if found_enr.node_id == node_id: endpoint = Endpoint.from_enr(found_enr) break else: # we weren't given an endpoint and we don't have an enr which would give # us an endpoint, there's no way to reach this node. raise KeyError(f"Could not find ENR: node_id={node_id.hex()}") else: if enr.sequence_number >= enr_seq: return enr enr = await self._fetch_enr(node_id, endpoint=endpoint) try: self.enr_db.set_enr(enr) except OldSequenceNumber: pass return enr
def extract_params( self, request: RPCRequest) -> Tuple[NodeID, Optional[Endpoint]]: try: raw_params = request["params"] except KeyError as err: raise RPCError(f"Missiing call params: {err}") if len(raw_params) != 1: raise RPCError(f"`ddht_ping` endpoint expects a single parameter: " f"Got {len(raw_params)} params: {raw_params}") value = raw_params[0] node_id: NodeID endpoint: Optional[Endpoint] if is_hex_node_id(value): node_id = NodeID(decode_hex(value)) endpoint = None elif value.startswith("enode://"): raw_node_id, _, raw_endpoint = value[8:].partition("@") validate_hex_node_id(raw_node_id) validate_endpoint(raw_endpoint) node_id = NodeID(decode_hex(raw_node_id)) raw_ip_address, _, raw_port = raw_endpoint.partition(":") ip_address = ipaddress.ip_address(raw_ip_address) port = int(raw_port) endpoint = Endpoint(ip_address.packed, port) elif value.startswith("enr:"): enr = ENR.from_repr(value) node_id = enr.node_id endpoint = Endpoint.from_enr(enr) else: raise RPCError(f"Unrecognized node identifier: {value}") return node_id, endpoint
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 _endpoint_for_node_id(self, node_id: NodeID) -> Endpoint: enr = self.enr_db.get_enr(node_id) return Endpoint.from_enr(enr)