Exemplo n.º 1
0
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,
    )
Exemplo n.º 2
0
def test_auth_header_preparation(
    tag, auth_tag, id_nonce, initiator_key, auth_response_key, ephemeral_public_key
):
    enr = ENR(
        sequence_number=1,
        signature=b"",
        kv_pairs={b"id": b"v4", b"secp256k1": b"\x02" * 33},
    )
    message = PingMessage(request_id=5, enr_seq=enr.sequence_number)
    id_nonce_signature = b"\x00" * 32

    packet = AuthHeaderPacket.prepare(
        tag=tag,
        auth_tag=auth_tag,
        id_nonce=id_nonce,
        message=message,
        initiator_key=initiator_key,
        id_nonce_signature=id_nonce_signature,
        auth_response_key=auth_response_key,
        enr=enr,
        ephemeral_public_key=ephemeral_public_key,
    )

    assert packet.tag == tag
    assert packet.auth_header.auth_tag == auth_tag
    assert packet.auth_header.id_nonce == id_nonce
    assert packet.auth_header.auth_scheme_name == AUTH_SCHEME_NAME
    assert packet.auth_header.ephemeral_public_key == ephemeral_public_key

    decrypted_auth_response = aesgcm_decrypt(
        key=auth_response_key,
        nonce=ZERO_NONCE,
        cipher_text=packet.auth_header.encrypted_auth_response,
        authenticated_data=b"",
    )
    decoded_auth_response = rlp.decode(decrypted_auth_response)
    assert is_list_like(decoded_auth_response) and len(decoded_auth_response) == 3
    assert decoded_auth_response[0] == int_to_big_endian(AUTH_RESPONSE_VERSION)
    assert decoded_auth_response[1] == id_nonce_signature
    assert ENR.deserialize(decoded_auth_response[2]) == enr

    decrypted_message = aesgcm_decrypt(
        key=initiator_key,
        nonce=auth_tag,
        cipher_text=packet.encrypted_message,
        authenticated_data=tag,
    )
    assert decrypted_message[0] == message.message_type
    assert rlp.decode(decrypted_message[1:], PingMessage) == message
Exemplo n.º 3
0
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
Exemplo n.º 4
0
    def decrypt_auth_response(
        self, auth_response_key: AES128Key
    ) -> Tuple[bytes, Optional[ENRAPI]]:
        """Extract id nonce signature and optional ENR from auth header packet."""
        plain_text = aesgcm_decrypt(
            key=auth_response_key,
            nonce=ZERO_NONCE,
            cipher_text=self.auth_header.encrypted_auth_response,
            authenticated_data=b"",
        )

        try:
            decoded_rlp = rlp.decode(plain_text)
        except DecodingError:
            raise ValidationError(
                f"Auth response does not contain valid RLP: {encode_hex(plain_text)}"
            )

        if not is_list_like(decoded_rlp):
            raise ValidationError(
                f"Auth response contains bytes instead of list: {encode_hex(decoded_rlp)}"
            )

        if len(decoded_rlp) != 3:
            raise ValidationError(
                f"Auth response is a list of {len(decoded_rlp)} instead of three elements"
            )
        version_bytes, id_nonce_signature, serialized_enr = decoded_rlp

        if not is_bytes(version_bytes):
            raise ValidationError(
                f"Version is a list instead of big endian encoded integer: {version_bytes}"
            )
        version_int = big_endian_to_int(version_bytes)
        if version_int != AUTH_RESPONSE_VERSION:
            raise ValidationError(
                f"Expected auth response version {AUTH_RESPONSE_VERSION}, but got {version_int}"
            )

        if not is_bytes(id_nonce_signature):
            raise ValidationError(
                f"Id nonce signature is a list instead of bytes: {id_nonce_signature}"
            )

        if not is_list_like(serialized_enr):
            raise ValidationError(
                f"ENR is bytes instead of list: {encode_hex(serialized_enr)}"
            )

        if len(serialized_enr) == 0:
            enr = None
        else:
            try:
                enr = ENR.deserialize(serialized_enr)
            except DeserializationError as error:
                raise ValidationError(
                    "ENR in auth response is not properly encoded"
                ) from error

        return id_nonce_signature, enr
Exemplo n.º 5
0
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)
Exemplo n.º 6
0
def enr():
    return ENR(
        sequence_number=1,
        signature=b"",
        kv_pairs={
            b"id": b"v4",
            b"secp256k1": PrivateKey(b"\x01" * 32).public_key.to_compressed_bytes(),
        },
    )
Exemplo n.º 7
0
async def test_enr_response_handler_does_not_crash_on_invalid_responses():
    discovery = MockDiscoveryService([])
    token = b''
    invalid_enr = b'garbage'
    payload = [token, invalid_enr]
    await discovery.recv_enr_response(discovery.this_node, payload, b'')

    enr = ENRFactory()
    enr._kv_pairs.pop(b'secp256k1')
    with pytest.raises(ValidationError):
        ENR.deserialize(ENR.serialize(enr))
    payload = [token, ENR.serialize(enr)]
    await discovery.recv_enr_response(discovery.this_node, payload, b'')

    enr = ENRFactory()
    enr._signature = b'garbage'
    with pytest.raises(eth_keys.exceptions.ValidationError):
        enr.validate_signature()
    payload = [token, ENR.serialize(enr)]
    await discovery.recv_enr_response(discovery.this_node, payload, b'')
Exemplo n.º 8
0
def create_stub_enr(pubkey: datatypes.PublicKey, address: AddressAPI) -> ENRAPI:
    return ENR(
        0,
        {
            IDENTITY_SCHEME_ENR_KEY: V4CompatIdentityScheme.id,
            V4CompatIdentityScheme.public_key_enr_key: pubkey.to_compressed_bytes(),
            IP_V4_ADDRESS_ENR_KEY: address.ip_packed,
            UDP_PORT_ENR_KEY: address.udp_port,
            TCP_PORT_ENR_KEY: address.tcp_port,
        },
        signature=b''
    )
Exemplo n.º 9
0
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)
Exemplo n.º 10
0
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,
    )
Exemplo n.º 11
0
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
Exemplo n.º 12
0
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)
Exemplo n.º 13
0
    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)
Exemplo n.º 14
0
    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
Exemplo n.º 15
0
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
Exemplo n.º 16
0
         "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"
     ),
Exemplo n.º 17
0
 def __setstate__(self, state: Dict[Any, Any]) -> None:
     self._init(ENR.from_repr(state.pop('enr')))
Exemplo n.º 18
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
Exemplo n.º 19
0
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
Exemplo n.º 20
0
 def from_rpc_response(cls, response: GetENRResponse) -> "GetENRPayload":
     return cls(enr=ENR.from_repr(response["enr_repr"]))
Exemplo n.º 21
0
def find_nodes_response_formatter(
        enr_reprs: Sequence[str]) -> Tuple[ENRAPI, ...]:
    return tuple(ENR.from_repr(enr_repr) for enr_repr in enr_reprs)
Exemplo n.º 22
0
 def from_rpc_response(cls,
                       response: NodeInfoResponse) -> "UpdateENRPayload":
     return cls(enr=ENR.from_repr(response["enr"]))
Exemplo n.º 23
0
 def from_rpc_response(cls, response: NodeInfoResponse) -> "NodeInfo":
     return cls(
         node_id=NodeID(decode_hex(response["node_id"])),
         enr=ENR.from_repr(response["enr"]),
     )
Exemplo n.º 24
0
 def from_enr_repr(cls: Type[TNode], uri: str) -> TNode:
     return cls(ENR.from_repr(uri))