Exemple #1
0
async def test_network_find_nodes_api(alice, bob):
    distances = {0}

    async with alice.network() as alice_network:
        async with bob.network() as bob_network:
            for _ in range(200):
                enr = ENRFactory()
                bob.enr_db.set_enr(enr)
                bob_network.routing_table.update(enr.node_id)
                distances.add(compute_log_distance(enr.node_id, bob.node_id))
                if distances.issuperset({0, 256, 255}):
                    break
            else:
                raise Exception("failed")

            with trio.fail_after(2):
                enrs = await alice_network.find_nodes(bob.node_id, 0, 255, 256)

    assert any(enr.node_id == bob.node_id for enr in enrs)
    response_distances = {
        compute_log_distance(enr.node_id, bob.node_id)
        for enr in enrs
        if enr.node_id != bob.node_id
    }
    assert response_distances == {256, 255}
Exemple #2
0
async def test_network_responds_to_find_node_requests(alice, bob):
    distances = {0}

    async with alice.network() as alice_network:
        async with bob.network() as bob_network:
            for _ in range(200):
                enr = ENRFactory()
                bob.enr_db.set_enr(enr)
                bob_network.routing_table.update(enr.node_id)
                distances.add(compute_log_distance(enr.node_id, bob.node_id))
                if distances.issuperset({0, 256, 255}):
                    break
            else:
                raise Exception("failed")

            with trio.fail_after(2):
                responses = await alice_network.client.find_nodes(
                    bob.endpoint, bob.node_id, distances=(0, 255, 256),
                )

    assert all(
        isinstance(response.message, FoundNodesMessage) for response in responses
    )
    response_enrs = tuple(
        enr for response in responses for enr in response.message.enrs
    )
    response_distances = {
        compute_log_distance(enr.node_id, bob.node_id)
        if enr.node_id != bob.node_id
        else 0
        for enr in response_enrs
    }
    assert response_distances.issuperset({0, 255, 256})
Exemple #3
0
    def _get_nodes_for_exploration(self) -> Iterator[Tuple[NodeID, int]]:
        candidates = self._get_ordered_candidates()
        candidate_triplets = sliding_window(
            3, caboose(cons(None, candidates), None))

        for left_id, node_id, right_id in candidate_triplets:
            # Filter out nodes that have already been queried
            if node_id in self.queried:
                continue
            elif node_id in self.in_flight:
                continue

            # By looking at the two closest *sibling* nodes we can determine
            # how much of their routing table we need to query.  We consider
            # the maximum logarithmic distance to either neighbor which
            # guarantees that we look up the region of the network that this
            # node knows the most about, but avoid querying buckets for which
            # other nodes are going to have a more complete view.
            if left_id is None:
                left_distance = 256
            else:
                left_distance = compute_log_distance(node_id, left_id)

            if right_id is None:
                right_distance = 256
            else:
                right_distance = compute_log_distance(node_id, right_id)

            # We use the maximum distance to ensure that we cover every part of
            # the address space.
            yield node_id, max(left_distance, right_distance)
Exemple #4
0
async def test_alexandria_network_find_nodes_api(alice, bob,
                                                 alice_alexandria_network,
                                                 bob_alexandria_client):
    distances = {0}

    bob_alexandria_routing_table = KademliaRoutingTable(
        bob.enr.node_id, ROUTING_TABLE_BUCKET_SIZE)

    for _ in range(200):
        enr = ENRFactory()
        bob.enr_db.set_enr(enr)
        bob_alexandria_routing_table.update(enr.node_id)
        distances.add(compute_log_distance(enr.node_id, bob.node_id))
        if distances.issuperset({0, 256, 255}):
            break
    else:
        raise Exception("failed")

    async with bob_alexandria_client.subscribe(
            FindNodesMessage) as subscription:
        async with trio.open_nursery() as nursery:

            async def _respond():
                request = await subscription.receive()
                response_enrs = []
                for distance in request.message.payload.distances:
                    if distance == 0:
                        response_enrs.append(bob.enr)
                    else:
                        for (
                                node_id
                        ) in bob_alexandria_routing_table.get_nodes_at_log_distance(
                                distance):
                            response_enrs.append(bob.enr_db.get_enr(node_id))
                await bob_alexandria_client.send_found_nodes(
                    request.sender_node_id,
                    request.sender_endpoint,
                    enrs=response_enrs,
                    request_id=request.request_id,
                )

            nursery.start_soon(_respond)

            with trio.fail_after(2):
                enrs = await alice_alexandria_network.find_nodes(
                    bob.node_id, 0, 255, 256)

    assert any(enr.node_id == bob.node_id for enr in enrs)
    response_distances = {
        compute_log_distance(enr.node_id, bob.node_id)
        for enr in enrs if enr.node_id != bob.node_id
    }
    assert response_distances == {256, 255}
 async def lookup_at_peer(
     self, peer: NodeID, target: NodeID
 ) -> Optional[Tuple[ENRAPI, ...]]:
     self.logger.debug(
         "Looking up %s at node %s", encode_hex(target), encode_hex(peer)
     )
     distance = compute_log_distance(peer, target)
     first_attempt = await self.request_nodes(peer, target, distance)
     if first_attempt is None:
         self.logger.debug("Lookup with node %s failed", encode_hex(peer))
         return None
     elif len(first_attempt) >= LOOKUP_RETRY_THRESHOLD:
         self.logger.debug(
             "Node %s responded with %d nodes with single attempt",
             encode_hex(peer),
             len(first_attempt),
         )
         return first_attempt
     else:
         second_attempt = await self.request_nodes(peer, target, distance)
         both_attempts = first_attempt + (second_attempt or ())
         self.logger.debug(
             "Node %s responded with %d nodes in two attempts",
             encode_hex(peer),
             len(both_attempts),
         )
         return both_attempts
async def test_v51_rpc_sendFindNodes_web3(bob_node_id_param_w3, bob,
                                          bob_network, 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)

    async with bob_network.client.dispatcher.subscribe(
            FindNodeMessage) as subscription:
        first_response = await trio.to_thread.run_sync(
            w3.discv5.send_find_nodes, bob_node_id_param_w3, 0)
        with trio.fail_after(2):
            first_receipt = await subscription.receive()

        assert encode_hex(
            first_receipt.message.request_id) == first_response.value
        assert first_receipt.message.distances == (0, )

        # request with multiple distances
        second_response = await trio.to_thread.run_sync(
            w3.discv5.send_find_nodes, bob_node_id_param_w3, tuple(distances))

        with trio.fail_after(2):
            second_receipt = await subscription.receive()
        assert encode_hex(
            second_receipt.message.request_id) == second_response.value
        assert second_receipt.message.distances == tuple(distances)
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
Exemple #8
0
    def at_log_distance(cls, reference: NodeID, log_distance: int) -> NodeID:
        num_bits = len(reference) * 8

        if log_distance > num_bits:
            raise ValueError(
                "Log distance must not be greater than number of bits in the node id"
            )
        elif log_distance < 0:
            raise ValueError("Log distance cannot be negative")

        num_common_bits = num_bits - log_distance
        flipped_bit_index = num_common_bits
        num_random_bits = num_bits - num_common_bits - 1

        reference_bits = bytes_to_bits(reference)

        shared_bits = reference_bits[:num_common_bits]
        flipped_bit = not reference_bits[flipped_bit_index]
        random_bits = [
            bool(random.randint(0, 1))
            for _ in range(flipped_bit_index + 1, flipped_bit_index + 1 +
                           num_random_bits)
        ]

        result_bits = tuple(list(shared_bits) + [flipped_bit] + random_bits)
        result = NodeID(bits_to_bytes(result_bits))

        assert compute_log_distance(result, reference) == log_distance
        return result
Exemple #9
0
async def test_network_stream_find_nodes(alice, bob, alice_network,
                                         bob_client):
    enrs = tuple(ENRFactory() for _ in range(FOUND_NODES_MAX_PAYLOAD_SIZE + 1))
    distances = set(
        [compute_log_distance(enr.node_id, bob.node_id) for enr in enrs])

    async with trio.open_nursery() as nursery:
        async with bob.events.find_nodes_received.subscribe() as subscription:

            async def _send_response():
                find_nodes = await subscription.receive()
                await bob_client.send_found_nodes(
                    alice.node_id,
                    alice.endpoint,
                    enrs=enrs,
                    request_id=find_nodes.message.request_id,
                )

            nursery.start_soon(_send_response)

            with trio.fail_after(2):
                async with alice_network.stream_find_nodes(
                        bob.node_id, bob.endpoint,
                        distances=distances) as resp_aiter:
                    actual_enrs = tuple([resp async for resp in resp_aiter])
            assert actual_enrs == enrs

            nursery.cancel_scope.cancel()
Exemple #10
0
async def test_network_stream_find_nodes_api_validates_response_distances(
        alice, bob, bob_client, alice_network, response_enr):
    if response_enr == "own":
        enr_for_response = bob.enr
    elif response_enr == "wrong":
        for _ in range(200):
            enr_for_response = ENRFactory()
            if compute_log_distance(enr_for_response.node_id,
                                    bob.node_id) == 256:
                break
        else:
            raise Exception("failed")
    else:
        raise Exception(f"unsupported param: {response_enr}")

    async with bob.events.find_nodes_received.subscribe() as subscription:
        async with trio.open_nursery() as nursery:

            async def _respond():
                request = await subscription.receive()
                await bob_client.send_found_nodes(
                    alice.node_id,
                    alice.endpoint,
                    enrs=(enr_for_response, ),
                    request_id=request.request_id,
                )

            nursery.start_soon(_respond)
            with trio.fail_after(REQUEST_RESPONSE_TIMEOUT):
                with pytest.raises(ValidationError,
                                   match="Invalid response: distance="):
                    async with alice_network.stream_find_nodes(
                            bob.node_id, bob.endpoint,
                            distances=[255]) as resp_aiter:
                        tuple([resp async for resp in resp_aiter])
Exemple #11
0
async def test_ping_handler_updates_routing_table(
    ping_handler_service,
    inbound_message_channels,
    outbound_message_channels,
    local_enr,
    remote_enr,
    routing_table,
):
    distance = compute_log_distance(remote_enr.node_id, local_enr.node_id)
    other_node_id = NodeIDFactory.at_log_distance(local_enr.node_id, distance)
    routing_table.update(other_node_id)
    assert routing_table.get_nodes_at_log_distance(distance) == (
        other_node_id,
        remote_enr.node_id,
    )

    ping = PingMessageFactory()
    inbound_message = InboundMessageFactory(
        message=ping,
        sender_node_id=remote_enr.node_id,
    )
    await inbound_message_channels[0].send(inbound_message)
    await wait_all_tasks_blocked()

    assert routing_table.get_nodes_at_log_distance(distance) == (
        remote_enr.node_id,
        other_node_id,
    )
async def test_v51_rpc_sendFindNodes(make_request, bob_node_id_param, bob,
                                     bob_network):
    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)

    async with bob_network.client.dispatcher.subscribe(
            FindNodeMessage) as subscription:
        single_response = await make_request("discv5_sendFindNodes",
                                             [bob_node_id_param, 0])
        with trio.fail_after(2):
            first_receipt = await subscription.receive()

        assert encode_hex(first_receipt.message.request_id) == single_response
        assert first_receipt.message.distances == (0, )

        # request with multiple distances
        multiple_response = await make_request(
            "discv5_sendFindNodes",
            [bob_node_id_param, tuple(distances)],
        )

        with trio.fail_after(2):
            second_receipt = await subscription.receive()
        assert encode_hex(
            second_receipt.message.request_id) == multiple_response
        assert second_receipt.message.distances == tuple(distances)
Exemple #13
0
    async def find_nodes(
        self,
        node_id: NodeID,
        endpoint: Endpoint,
        distances: Collection[int],
        *,
        request_id: Optional[bytes] = None,
    ) -> Tuple[InboundMessage[FoundNodesMessage], ...]:
        request = FindNodesMessage(FindNodesPayload(tuple(distances)))

        subscription: trio.abc.ReceiveChannel[
            InboundMessage[FoundNodesMessage]]
        # unclear why `subscribe_request` isn't properly carrying the type information
        async with self.subscribe_request(  # type: ignore
            node_id,
            endpoint,
            request,
            FoundNodesMessage,
            request_id=request_id,
        ) as subscription:
            head_response = await subscription.receive()
            total = head_response.message.payload.total
            responses: Tuple[InboundMessage[FoundNodesMessage], ...]
            if total == 1:
                responses = (head_response, )
            elif total > 1:
                tail_responses: List[InboundMessage[FoundNodesMessage]] = []
                for _ in range(total - 1):
                    tail_responses.append(await subscription.receive())
                responses = (head_response, ) + tuple(tail_responses)
            else:
                # TODO: this code path needs to be excercised and
                # probably replaced with some sort of
                # `SessionTerminated` exception.
                raise Exception("Invalid `total` counter in response")

            # Validate that all responses are indeed at one of the
            # specified distances.
            for response in responses:
                for enr in response.message.payload.enrs:
                    if enr.node_id == node_id:
                        if 0 not in distances:
                            raise ValidationError(
                                f"Invalid response: distance=0  expected={distances}"
                            )
                    else:
                        distance = compute_log_distance(enr.node_id, node_id)
                        if distance not in distances:
                            raise ValidationError(
                                f"Invalid response: distance={distance}  expected={distances}"
                            )

            return responses
Exemple #14
0
    async def find_nodes(
        self,
        endpoint: Endpoint,
        node_id: NodeID,
        distances: Collection[int],
    ) -> Tuple[InboundMessage[FoundNodesMessage], ...]:
        with self._get_request_id(node_id) as request_id:
            request = AnyOutboundMessage(
                FindNodeMessage(request_id, tuple(distances)),
                endpoint,
                node_id,
            )
            async with self.dispatcher.subscribe_request(
                    request, FoundNodesMessage) as subscription:
                with trio.fail_after(REQUEST_RESPONSE_TIMEOUT):
                    head_response = await subscription.receive()
                    total = head_response.message.total
                    responses: Tuple[InboundMessage[FoundNodesMessage], ...]
                    if total == 1:
                        responses = (head_response, )
                    elif total > 1:
                        tail_responses: List[
                            InboundMessage[FoundNodesMessage]] = []
                        for _ in range(total - 1):
                            tail_responses.append(await subscription.receive())
                        responses = (head_response, ) + tuple(tail_responses)
                    else:
                        # TODO: this code path needs to be excercised and
                        # probably replaced with some sort of
                        # `SessionTerminated` exception.
                        raise Exception("Invalid `total` counter in response")

                # Validate that all responses are indeed at one of the
                # specified distances.
                for response in responses:
                    for enr in response.message.enrs:
                        if enr.node_id == node_id:
                            if 0 not in distances:
                                raise ValidationError(
                                    f"Invalid response: distance=0  expected={distances}"
                                )
                        else:
                            distance = compute_log_distance(
                                enr.node_id, node_id)
                            if distance not in distances:
                                raise ValidationError(
                                    f"Invalid response: distance={distance}  expected={distances}"
                                )

                return responses
Exemple #15
0
def validate_found_nodes_distances(
    enrs: Collection[ENRAPI],
    local_node_id: NodeID,
    distances: Collection[int],
) -> None:
    for enr in enrs:
        if enr.node_id == local_node_id:
            if 0 not in distances:
                raise ValidationError(
                    f"Invalid response: distance=0  expected={distances}")
        else:
            distance = compute_log_distance(enr.node_id, local_node_id)
            if distance not in distances:
                raise ValidationError(
                    f"Invalid response: distance={distance}  expected={distances}"
                )
Exemple #16
0
async def test_client_request_response_stream_find_nodes_handles_premature_exit(
    alice, bob, alice_client, bob_client, autojump_clock
):
    enrs = tuple(ENRFactory() for _ in range(FOUND_NODES_MAX_PAYLOAD_SIZE + 1))

    async with trio.open_nursery() as nursery:
        async with bob.events.find_nodes_received.subscribe() as subscription:
            enr_batches = partition_enrs(
                enrs, max_payload_size=FOUND_NODES_MAX_PAYLOAD_SIZE
            )
            first_enr_batch = enr_batches[0]
            distances = set(
                [
                    compute_log_distance(enr.node_id, bob.node_id)
                    for enr in first_enr_batch
                ]
            )
            num_batches = len(enr_batches)

            async def _respond():
                find_nodes = await subscription.receive()
                assert num_batches > 1
                message = AnyOutboundMessage(
                    FoundNodesMessage(
                        find_nodes.message.request_id, num_batches, first_enr_batch,
                    ),
                    alice.endpoint,
                    alice.node_id,
                )
                await bob_client.dispatcher.send_message(message)

            nursery.start_soon(_respond)

            found_nodes_messages = []
            with trio.fail_after(REQUEST_RESPONSE_TIMEOUT + 1):
                async with alice_client.stream_find_nodes(
                    bob.node_id, bob.endpoint, distances=distances
                ) as resp_aiter:
                    async for resp in resp_aiter:
                        found_nodes_messages.append(resp)
                        # prematurely close the context after first response
                        await resp_aiter.aclose()

            assert len(found_nodes_messages) == 1

            nursery.cancel_scope.cancel()
Exemple #17
0
async def test_client_request_response_stream_find_nodes_timeout_with_incomplete_response(
    alice, bob, alice_client, bob_client, autojump_clock
):
    enrs = tuple(ENRFactory() for _ in range(FOUND_NODES_MAX_PAYLOAD_SIZE + 1))

    async with trio.open_nursery() as nursery:
        async with bob.events.find_nodes_received.subscribe() as subscription:
            enr_batches = partition_enrs(
                enrs, max_payload_size=FOUND_NODES_MAX_PAYLOAD_SIZE
            )
            first_enr_batch = enr_batches[0]
            distances = set(
                [
                    compute_log_distance(enr.node_id, bob.node_id)
                    for enr in first_enr_batch
                ]
            )
            num_batches = len(enr_batches)

            async def _respond():
                find_nodes = await subscription.receive()
                assert num_batches > 1
                message = AnyOutboundMessage(
                    FoundNodesMessage(
                        find_nodes.message.request_id, num_batches, first_enr_batch,
                    ),
                    alice.endpoint,
                    alice.node_id,
                )
                # only send first batch of responses, then nothing
                await bob_client.dispatcher.send_message(message)

            nursery.start_soon(_respond)

            with trio.fail_after(REQUEST_RESPONSE_TIMEOUT + 1):
                with pytest.raises(trio.TooSlowError):
                    async with alice_client.stream_find_nodes(
                        bob.node_id, bob.endpoint, distances=distances
                    ) as resp_aiter:
                        found_nodes_messages = tuple(
                            [resp async for resp in resp_aiter]
                        )

            assert len(found_nodes_messages) == 1

            nursery.cancel_scope.cancel()
Exemple #18
0
async def test_find_node_handler_sends_remote_enrs(
    find_node_handler_service,
    inbound_message_channels,
    outbound_message_channels,
    local_enr,
    remote_enr,
):
    distance = compute_log_distance(local_enr.node_id, remote_enr.node_id)
    find_node = FindNodeMessageFactory(distance=distance)
    inbound_message = InboundMessageFactory(message=find_node)
    await inbound_message_channels[0].send(inbound_message)
    await wait_all_tasks_blocked()

    outbound_message = outbound_message_channels[1].receive_nowait()
    assert isinstance(outbound_message.message, NodesMessage)
    assert outbound_message.message.request_id == find_node.request_id
    assert outbound_message.message.total == 1
    assert outbound_message.message.enrs == (remote_enr, )
Exemple #19
0
        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)
Exemple #20
0
    async def do_lookup(
        node_id: NodeID, send_channel: trio.abc.SendChannel[ENRAPI]
    ) -> None:
        """
        Perform an individual lookup on the target part of the network from the
        given `node_id`
        """
        if node_id == target:
            distance = 0
        else:
            distance = compute_log_distance(node_id, target)

        try:
            found_enrs = await network.find_nodes(node_id, distance)
        except (trio.TooSlowError, MissingEndpointFields, ValidationError):
            unresponsive_node_ids.add(node_id)
            unresponsive_cache[node_id] = trio.current_time()
            return
        except trio.Cancelled:
            # We don't add these to the unresponsive cache since they didn't
            # necessarily exceed the fulle 10s request/response timeout.
            unresponsive_node_ids.add(node_id)
            raise

        for enr in found_enrs:
            try:
                network.enr_db.set_enr(enr)
            except OldSequenceNumber:
                pass

        async with condition:
            new_enrs = tuple(
                enr for enr in found_enrs if enr.node_id not in received_node_ids
            )
            received_node_ids.update(enr.node_id for enr in new_enrs)

        for enr in new_enrs:
            try:
                await send_channel.send(enr)
            except (trio.BrokenResourceError, trio.ClosedResourceError):
                # In the event that the consumer of `recursive_find_nodes`
                # exits early before the lookup has completed we can end up
                # operating on a closed channel.
                return
Exemple #21
0
async def test_client_request_response_stream_find_nodes_inconsistent_message_total(
    alice, bob, alice_client, bob_client
):
    enrs = tuple(ENRFactory() for _ in range(FOUND_NODES_MAX_PAYLOAD_SIZE + 1))
    distances = set([compute_log_distance(enr.node_id, bob.node_id) for enr in enrs])

    async with trio.open_nursery() as nursery:
        async with bob_client.dispatcher.subscribe(FindNodeMessage) as subscription:
            enr_batches = partition_enrs(
                enrs, max_payload_size=FOUND_NODES_MAX_PAYLOAD_SIZE
            )
            num_batches = len(enr_batches)

            async def _respond():
                request = await subscription.receive()
                assert len(enr_batches) > 1

                head_message = AnyOutboundMessage(
                    FoundNodesMessage(
                        request.message.request_id, num_batches, enr_batches[0],
                    ),
                    alice.endpoint,
                    alice.node_id,
                )
                await bob_client.dispatcher.send_message(head_message)

                invalid_message = AnyOutboundMessage(
                    FoundNodesMessage(
                        request.message.request_id, num_batches + 1, enr_batches[1],
                    ),
                    alice.endpoint,
                    alice.node_id,
                )
                await bob_client.dispatcher.send_message(invalid_message)

            nursery.start_soon(_respond)

            with pytest.raises(ValidationError, match="Inconsistent message total"):
                async with alice_client.stream_find_nodes(
                    bob.node_id, bob.endpoint, distances=distances
                ) as resp_aiter:
                    tuple([resp async for resp in resp_aiter])

            nursery.cancel_scope.cancel()
Exemple #22
0
async def test_alexandria_network_responds_to_find_nodes(
    alice,
    bob,
    alice_alexandria_network,
    bob_alexandria_network,
):
    enr = ENRFactory()
    bob.enr_db.set_enr(enr)
    bob_alexandria_network.routing_table.update(enr.node_id)
    enr_distance = compute_log_distance(enr.node_id, bob.node_id)

    with trio.fail_after(2):
        enrs = await alice_alexandria_network.find_nodes(
            bob.node_id,
            enr_distance,
        )

    assert len(enrs) >= 1
    assert any(enr.node_id == enr.node_id for enr in enrs)
Exemple #23
0
async def test_client_request_response_stream_find_nodes_found_nodes(
    alice, bob, alice_client, bob_client
):
    enrs = tuple(ENRFactory() for _ in range(FOUND_NODES_MAX_PAYLOAD_SIZE + 1))
    distances = set([compute_log_distance(enr.node_id, bob.node_id) for enr in enrs])

    async with trio.open_nursery() as nursery:
        async with bob.events.find_nodes_received.subscribe() as subscription:

            async def _send_response():
                find_nodes = await subscription.receive()
                await bob_client.send_found_nodes(
                    alice.node_id,
                    alice.endpoint,
                    enrs=enrs,
                    request_id=find_nodes.message.request_id,
                )

            nursery.start_soon(_send_response)

            with trio.fail_after(2):
                async with alice_client.stream_find_nodes(
                    bob.node_id, bob.endpoint, distances=distances
                ) as resp_aiter:
                    found_nodes_messages = tuple([resp async for resp in resp_aiter])
            found_node_ids = {
                enr.node_id
                for message in found_nodes_messages
                for enr in message.message.enrs
            }
            expected_node_ids = {enr.node_id for enr in enrs}
            assert found_node_ids == expected_node_ids

            expected_total = len(
                partition_enrs(enrs, max_payload_size=FOUND_NODES_MAX_PAYLOAD_SIZE)
            )
            assert set((msg.message.total) for msg in found_nodes_messages) == set(
                [expected_total]
            )

            nursery.cancel_scope.cancel()
Exemple #24
0
async def test_network_recursive_find_nodes(tester, alice, bob):
    async with AsyncExitStack() as stack:
        await stack.enter_async_context(bob.network())
        bootnodes = collections.deque((bob.enr,), maxlen=4)
        nodes = [bob, alice]
        for _ in range(20):
            node = tester.node()
            nodes.append(node)
            await stack.enter_async_context(node.network(bootnodes=bootnodes))
            bootnodes.append(node.enr)

        # give the the network some time to interconnect.
        with trio.fail_after(5):
            for _ in range(1000):
                await trio.lowlevel.checkpoint()

        alice_network = await stack.enter_async_context(
            alice.network(bootnodes=bootnodes)
        )

        # give alice a little time to connect to the network as well
        with trio.fail_after(5):
            for _ in range(1000):
                await trio.lowlevel.checkpoint()

        target_node_id = secrets.token_bytes(32)
        node_ids_by_distance = tuple(
            sorted(
                tuple(node.enr.node_id for node in nodes),
                key=lambda node_id: compute_log_distance(target_node_id, node_id),
            )
        )
        best_node_ids_by_distance = set(node_ids_by_distance[:3])

        with trio.fail_after(10):
            found_enrs = await alice_network.recursive_find_nodes(target_node_id)
        found_node_ids = tuple(enr.node_id for enr in found_enrs)

        # 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)
Exemple #25
0
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 test_v51_rpc_recursiveFindNodes_web3(tester, bob, w3):
    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 trio.to_thread.run_sync(
            w3.discv5.bond,
            bob.node_id.hex(),
        )

        try:
            with trio.fail_after(60):
                found_enrs = await trio.to_thread.run_sync(
                    w3.discv5.recursive_find_nodes, target_node_id)
        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

        # Ensure that one of the three closest node ids was in the returned node ids
        assert len(found_enrs) > 0
        assert isinstance(found_enrs[0], ENR)
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)
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_v51_rpc_sendFoundNodes_web3(bob_node_id_param_w3, bob,
                                           bob_network, w3):
    distances = set()
    enrs = set()

    for _ in range(10):
        enr = ENRFactory()
        distances.add(compute_log_distance(bob.node_id, enr.node_id))
        bob.enr_db.set_enr(enr)
        enrs.add(repr(enr))

    request_id = encode_hex(secrets.token_bytes(4))
    single_enr = next(iter(enrs))

    async with bob_network.client.dispatcher.subscribe(
            FoundNodesMessage) as subscription:

        first_response = await trio.to_thread.run_sync(
            w3.discv5.send_found_nodes, bob_node_id_param_w3, (single_enr, ),
            request_id)
        with trio.fail_after(2):
            first_receipt = await subscription.receive()

        assert first_receipt.message.total == first_response.value
        assert encode_hex(first_receipt.message.request_id) == request_id

        # request with multiple enrs
        second_response = await trio.to_thread.run_sync(
            w3.discv5.send_found_nodes, bob_node_id_param_w3, tuple(enrs),
            request_id)

        with trio.fail_after(2):
            second_receipt = await subscription.receive()

        assert second_receipt.message.total == second_response.value
        assert encode_hex(second_receipt.message.request_id) == request_id
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