def _cli_args_to_boot_info_kwargs(args: argparse.Namespace) -> BootInfoKwargs: protocol_version = args.protocol_version is_ephemeral = args.ephemeral is True is_upnp_enabled = not args.disable_upnp if args.base_dir is not None: base_dir = args.base_dir.expanduser().resolve() elif is_ephemeral: base_dir = pathlib.Path(tempfile.TemporaryDirectory().name) else: base_dir = get_xdg_ddht_root() if args.port is not None: port = args.port elif is_ephemeral: port = get_open_port() else: port = DEFAULT_PORT listen_on: Optional[AnyIPAddress] if args.listen_address is None: listen_on = None else: listen_on = args.listen_address if args.bootnodes is None: if protocol_version is ProtocolVersion.v5: bootnodes = tuple( ENR.from_repr(enr_repr) for enr_repr in DEFAULT_V5_BOOTNODES) elif protocol_version is ProtocolVersion.v5_1: bootnodes = tuple( ENR.from_repr(enr_repr) for enr_repr in DEFAULT_V51_BOOTNODES) else: raise Exception( f"Unsupported protocol version: {protocol_version}") else: bootnodes = args.bootnodes private_key: Optional[keys.PrivateKey] if args.private_key is not None: private_key = keys.PrivateKey(decode_hex(args.private_key)) else: private_key = None return BootInfoKwargs( protocol_version=protocol_version, base_dir=base_dir, port=port, listen_on=listen_on, bootnodes=bootnodes, private_key=private_key, is_ephemeral=is_ephemeral, is_upnp_enabled=is_upnp_enabled, )
def test_extract_forkid(): enr = ENR.from_repr( "enr:-Jq4QO5zEyIBU5lSa9iaen0A2xUB5_IVrCi1DbyASTTnLV5RJan6aGPr8kU0p0MYKU5YezZgdSUE" "-GOBEio6Ultyf1Aog2V0aMrJhGN2AZCDGfCggmlkgnY0gmlwhF4_wLuJc2VjcDI1NmsxoQOt7cA_B_Kg" "nQ5RmwyA6ji8M1Y0jfINItRGbOOwy7XgbIN0Y3CCdl-DdWRwgnZf") assert extract_forkid(enr) == ForkID(hash=to_bytes(hexstr='0x63760190'), next=1700000)
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 test_v51_rpc_findNodes_w3(bob_node_id_param, bob, w3): distances = set() for _ in range(10): enr = ENRFactory() distances.add(compute_log_distance(bob.node_id, enr.node_id)) bob.enr_db.set_enr(enr) # request with positional single distance enrs_at_0 = await trio.to_thread.run_sync(w3.discv5.find_nodes, bob_node_id_param, 0) assert all(isinstance(enr, ENR) for enr in enrs_at_0) # request with multiple distances enrs_at_some_distance = await trio.to_thread.run_sync( w3.discv5.find_nodes, bob_node_id_param, tuple(distances), ) # verify that all of the returned ENR records can be parsed as valid ENRs for enr_repr in enrs_at_some_distance: ENR.from_repr(enr_repr)
def _cli_args_to_boot_info_kwargs( args: argparse.Namespace) -> AlexandriaBootInfoKwargs: if args.alexandria_bootnodes is None: bootnodes = tuple( ENR.from_repr(enr_repr) for enr_repr in DEFAULT_BOOTNODES) else: bootnodes = args.alexandria_bootnodes max_advertisement_count: int if args.alexandria_max_advertisement_count is None: max_advertisement_count = DEFAULT_MAX_ADVERTISEMENTS else: max_advertisement_count = args.alexandria_max_advertisement_count commons_storage_size: int if args.alexandria_commons_storage_size is None: commons_storage_size = DEFAULT_COMMONS_STORAGE_SIZE else: commons_storage_size = args.alexandria_commons_storage_size commons_storage: Optional[Union[Literal[":memory:"], pathlib.Path]] if args.alexandria_commons_storage == ":memory:": commons_storage = ":memory:" elif args.alexandria_commons_storage is not None: commons_storage = (pathlib.Path( args.alexandria_commons_storage).expanduser().resolve()) else: commons_storage = None pinned_storage: Optional[Union[Literal[":memory:"], pathlib.Path]] if args.alexandria_pinned_storage == ":memory:": pinned_storage = ":memory:" elif args.alexandria_pinned_storage is not None: pinned_storage = (pathlib.Path( args.alexandria_pinned_storage).expanduser().resolve()) else: pinned_storage = None return AlexandriaBootInfoKwargs( bootnodes=bootnodes, max_advertisement_count=max_advertisement_count, commons_storage_size=commons_storage_size, commons_storage=commons_storage, pinned_storage=pinned_storage, )
async def test_v51_rpc_findNodes(make_request, bob_node_id_param, bob): distances = set() for _ in range(10): enr = ENRFactory() distances.add(compute_log_distance(bob.node_id, enr.node_id)) bob.enr_db.set_enr(enr) # request with positional single distance enrs_at_0 = await make_request("discv5_findNodes", [bob_node_id_param, 0]) # verify that all of the returned ENR records can be parsed as valid ENRs for enr_repr in enrs_at_0: ENR.from_repr(enr_repr) # request with multiple distances enrs_at_some_distance = await make_request( "discv5_findNodes", [bob_node_id_param, tuple(distances)], ) # verify that all of the returned ENR records can be parsed as valid ENRs for enr_repr in enrs_at_some_distance: ENR.from_repr(enr_repr)
async def test_rpc_updateNodeInfo(make_request, enr_manager): # add a kv pair first_response = await make_request("discv5_updateNodeInfo", [("0xabcd", "0x1234")]) first_enr = ENR.from_repr(first_response["enr"]) assert first_enr.sequence_number == 2 assert enr_manager.enr.sequence_number == 2 assert first_enr._kv_pairs[b"\xab\xcd"] == b"\x124" assert enr_manager.enr._kv_pairs[b"\xab\xcd"] == b"\x124" # update a kv pair second_response = await make_request("discv5_updateNodeInfo", [("0xabcd", "0x6789")]) second_enr = ENR.from_repr(second_response["enr"]) assert second_enr._kv_pairs[b"\xab\xcd"] == b"g\x89" assert second_enr.sequence_number == 3 # test with multiple kv_pairs, and remove 'foo' key third_response = await make_request("discv5_updateNodeInfo", [("0xabcd", None), ("0xdef1", 123)]) third_enr = ENR.from_repr(third_response["enr"]) assert b"\xab\xcd" not in third_enr._kv_pairs assert third_enr._kv_pairs[b"\xde\xf1"] == to_bytes(123) assert third_enr.sequence_number == 4
def __call__( self, parser: argparse.ArgumentParser, namespace: argparse.Namespace, value: Any, option_string: str = None, ) -> None: if value is None: return enr = ENR.from_repr(value) if getattr(namespace, self.dest) is None: setattr(namespace, self.dest, ()) enr_list = getattr(namespace, self.dest) enr_list += (enr, ) setattr(namespace, self.dest, enr_list)
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 test_v51_rpc_recursiveFindNodes(tester, bob, make_request): async with AsyncExitStack() as stack: await stack.enter_async_context(bob.network()) bootnodes = collections.deque((bob.enr, ), maxlen=4) nodes = [bob] target_node_id = None for _ in range(8): node = tester.node() nodes.append(node) await stack.enter_async_context(node.network(bootnodes=bootnodes)) bootnodes.append(node.enr) if (not target_node_id and compute_log_distance(node.node_id, bob.node_id) < 256): target_node_id = node.node_id # give the the network some time to interconnect. with trio.fail_after(60): for _ in range(1000): await trio.lowlevel.checkpoint() await make_request("discv5_bond", [bob.node_id.hex()]) try: with trio.fail_after(60): found_enrs = await make_request("discv5_recursiveFindNodes", [target_node_id.hex()]) except trio.TooSlowError: # These tests are flakey. Timeouts are expected in the testing # environment so we silently pass on timeouts. This still allows # this test to provide some value in the case that a non-timeout # based error shows up. return found_enrs = tuple( ENR.from_repr(enr_repr).node_id for enr_repr in found_enrs) assert len(found_enrs) > 0
def test_default_bootnodes_valid(enr_repr): enr = ENR.from_repr(enr_repr) assert b"ip" in enr or b"ip6" in enr assert b"udp" in enr
from typing import Dict, Tuple from eth_enr import ENR import factory from ddht.boot_info import BootInfo from ddht.constants import DEFAULT_PORT, ProtocolVersion from ddht.v5.constants import DEFAULT_BOOTNODES as DEFAULT_V5_BOOTNODES from ddht.v5_1.constants import DEFAULT_BOOTNODES as DEFAULT_V51_BOOTNODES from ddht.xdg import get_xdg_ddht_root BOOTNODES_V5 = tuple( ENR.from_repr(enr_repr) for enr_repr in DEFAULT_V5_BOOTNODES) BOOTNODES_V5_1 = tuple( ENR.from_repr(enr_repr) for enr_repr in DEFAULT_V51_BOOTNODES) BOOTNODES: Dict[ProtocolVersion, Tuple[ENR, ...]] = { ProtocolVersion.v5: BOOTNODES_V5, ProtocolVersion.v5_1: BOOTNODES_V5_1, } class BootInfoFactory(factory.Factory): # type: ignore class Meta: model = BootInfo protocol_version = ProtocolVersion.v5 private_key = None base_dir = factory.LazyFunction(get_xdg_ddht_root) port = DEFAULT_PORT listen_on = None
def __setstate__(self, state: Dict[Any, Any]) -> None: self._init(ENR.from_repr(state.pop('enr')))
"request_id": 0x01, "total": 0x01, "enrs": [] }, decode_hex("0x04c30101c0"), ], [ NodesMessage, { "request_id": 0x01, "total": 0x01, "enrs": [ ENR.from_repr( "enr:-HW4QBzimRxkmT18hMKaAL3IcZF1UcfTMPyi3Q1pxwZZbcZVRI8DC5infUAB_UauARLOJtYTxa" # noqa: E501 "agKoGmIjzQxO2qUygBgmlkgnY0iXNlY3AyNTZrMaEDymNMrg1JrLQB2KTGtv6MVbcNEVv0AHacwUAP" # noqa: E501 "MljNMTg"), ENR.from_repr( "enr:-HW4QNfxw543Ypf4HXKXdYxkyzfcxcO-6p9X986WldfVpnVTQX1xlTnWrktEWUbeTZnmgOuAY_" # noqa: E501 "KUhbVV1Ft98WoYUBMBgmlkgnY0iXNlY3AyNTZrMaEDDiy3QkHAxPyOgWbxp5oF1bDdlYE6dLCUUp8x" # noqa: E501 "fVw50jU"), ], }, decode_hex( "0x04f8f20101f8eef875b8401ce2991c64993d7c84c29a00bdc871917551c7d330fca2dd0d69c706596dc6" # noqa: E501 "55448f030b98a77d4001fd46ae0112ce26d613c5a6a02a81a6223cd0c4edaa532801826964827634897365" # noqa: E501 "63703235366b31a103ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd3138f875" # noqa: E501 "b840d7f1c39e376297f81d7297758c64cb37dcc5c3beea9f57f7ce9695d7d5a67553417d719539d6ae4b44" # noqa: E501 "5946de4d99e680eb8063f29485b555d45b7df16a1850130182696482763489736563703235366b31a1030e" # noqa: E501 "2cb74241c0c4fc8e8166f1a79a05d5b0dd95813a74b094529f317d5c39d235" ),
def from_rpc_response(cls, response: GetENRResponse) -> "GetENRPayload": return cls(enr=ENR.from_repr(response["enr_repr"]))
def from_rpc_response(cls, response: NodeInfoResponse) -> "NodeInfo": return cls( node_id=NodeID(decode_hex(response["node_id"])), enr=ENR.from_repr(response["enr"]), )
def from_rpc_response(cls, response: NodeInfoResponse) -> "UpdateENRPayload": return cls(enr=ENR.from_repr(response["enr"]))
def from_enr_repr(cls: Type[TNode], uri: str) -> TNode: return cls(ENR.from_repr(uri))
def find_nodes_response_formatter( enr_reprs: Sequence[str]) -> Tuple[ENRAPI, ...]: return tuple(ENR.from_repr(enr_repr) for enr_repr in enr_reprs)