From 814356383160fb30d376850710d240d9360cea91 Mon Sep 17 00:00:00 2001 From: "Aratz M. Lasa" Date: Tue, 15 Oct 2019 01:01:16 +0200 Subject: [PATCH] Added tests for 'RoutedHost' and modified 'FindPeer' --- libp2p/host/routed_host.py | 6 ++- libp2p/peer/peerinfo.py | 12 +++++ .../routing/kademlia/kademlia_peer_router.py | 27 ++-------- tests/host/__init__.py | 0 tests/host/test_routed_host.py | 53 +++++++++++++++++++ tests/routing/test_kad_peer_router.py | 12 ++--- tests/utils.py | 25 +++++++++ 7 files changed, 105 insertions(+), 30 deletions(-) create mode 100644 tests/host/__init__.py create mode 100644 tests/host/test_routed_host.py diff --git a/libp2p/host/routed_host.py b/libp2p/host/routed_host.py index 2c59e72..1b63848 100644 --- a/libp2p/host/routed_host.py +++ b/libp2p/host/routed_host.py @@ -1,4 +1,5 @@ from libp2p.host.basic_host import BasicHost +from libp2p.host.exceptions import ConnectionFailure from libp2p.network.network_interface import INetwork from libp2p.peer.peerinfo import PeerInfo from libp2p.routing.interfaces import IPeerRouting @@ -26,7 +27,10 @@ class RoutedHost(BasicHost): """ # check if we were given some addresses, otherwise, find some with the routing system. if not peer_info.addrs: - peer_info.addrs = (await self._router.find_peer(peer_info.peer_id)).addrs + found_peer_info = await self._router.find_peer(peer_info.peer_id) + if not found_peer_info: + raise ConnectionFailure("Unable to find Peer address") + peer_info.addrs = found_peer_info.addrs self.peerstore.add_addrs(peer_info.peer_id, peer_info.addrs, 10) # there is already a connection to this peer diff --git a/libp2p/peer/peerinfo.py b/libp2p/peer/peerinfo.py index 069a67d..d744c0a 100644 --- a/libp2p/peer/peerinfo.py +++ b/libp2p/peer/peerinfo.py @@ -1,3 +1,4 @@ +import json from typing import List, Sequence import multiaddr @@ -14,6 +15,17 @@ class PeerInfo: self.peer_id = peer_id self.addrs = list(addrs) + def to_string(self) -> str: + return json.dumps([self.peer_id.to_string(), list(map(lambda a: str(a), self.addrs))]) + + def __eq__(self, other): + return isinstance(other, PeerInfo) and self.peer_id == other.peer_id and self.addrs == other.addrs + + @classmethod + def info_from_string(cls, info: str) -> "PeerInfo": + peer_id, raw_addrs = json.loads(info) + return PeerInfo(ID.from_base58(peer_id), list(map(lambda a: multiaddr.Multiaddr(a), raw_addrs))) + def info_from_p2p_addr(addr: multiaddr.Multiaddr) -> PeerInfo: if not addr: diff --git a/libp2p/routing/kademlia/kademlia_peer_router.py b/libp2p/routing/kademlia/kademlia_peer_router.py index 59eaa1e..9710d70 100644 --- a/libp2p/routing/kademlia/kademlia_peer_router.py +++ b/libp2p/routing/kademlia/kademlia_peer_router.py @@ -1,42 +1,23 @@ -import ast -from typing import Union - -from libp2p.kademlia.kad_peerinfo import KadPeerInfo, create_kad_peerinfo from libp2p.kademlia.network import KademliaServer from libp2p.peer.id import ID +from libp2p.peer.peerinfo import PeerInfo from libp2p.routing.interfaces import IPeerRouting class KadmeliaPeerRouter(IPeerRouting): - server: KademliaServer def __init__(self, dht_server: KademliaServer) -> None: self.server = dht_server - async def find_peer(self, peer_id: ID) -> KadPeerInfo: + async def find_peer(self, peer_id: ID) -> PeerInfo: """ Find a specific peer :param peer_id: peer to search for - :return: KadPeerInfo of specified peer + :return: PeerInfo of specified peer """ # switching peer_id to xor_id used by kademlia as node_id xor_id = peer_id.xor_id # ignore type for kad value = await self.server.get(xor_id) # type: ignore - return decode_peerinfo(value) - - -def decode_peerinfo(encoded: Union[bytes, str]) -> KadPeerInfo: - if isinstance(encoded, bytes): - encoded = encoded.decode() - try: - lines = ast.literal_eval(encoded) - except SyntaxError: - return None - ip = lines[1] - port = lines[2] - peer_id = lines[3] - # ignore typing for kad - peer_info = create_kad_peerinfo(peer_id, ip, port) # type: ignore - return peer_info + return PeerInfo.info_from_string(value) if value else None # TODO: should raise error if None? diff --git a/tests/host/__init__.py b/tests/host/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/host/test_routed_host.py b/tests/host/test_routed_host.py new file mode 100644 index 0000000..bcc53ca --- /dev/null +++ b/tests/host/test_routed_host.py @@ -0,0 +1,53 @@ +import asyncio + +import pytest + +from libp2p.host.exceptions import ConnectionFailure +from libp2p.peer.peerinfo import PeerInfo +from tests.utils import set_up_routers, set_up_nodes_by_transport_opt, set_up_nodes_by_transport_and_disc_opt + + +@pytest.mark.asyncio +async def test_host_routing_success(): + routers = await set_up_routers([5678, 5679]) + transports = [["/ip4/127.0.0.1/tcp/0"], ["/ip4/127.0.0.1/tcp/0"]] + transport_disc_opt_list = zip(transports, routers) + (host_a, host_b) = await set_up_nodes_by_transport_and_disc_opt(transport_disc_opt_list) + + # Set routing info + await routers[0].server.set(host_a.get_id().xor_id, PeerInfo(host_a.get_id(), host_a.get_addrs()).to_string()) + await routers[1].server.set(host_b.get_id().xor_id, PeerInfo(host_b.get_id(), host_b.get_addrs()).to_string()) + + # forces to use routing as no addrs are provided + await host_a.connect(PeerInfo(host_b.get_id(), [])) + await host_b.connect(PeerInfo(host_a.get_id(), [])) + + # Clean up + await asyncio.gather(*[host_a.close(), host_b.close()]) + routers[0].server.stop() + routers[1].server.stop() + + +@pytest.mark.asyncio +async def test_host_routing_fail(): + routers = await set_up_routers([5678, 5679]) + transports = [["/ip4/127.0.0.1/tcp/0"], ["/ip4/127.0.0.1/tcp/0"]] + transport_disc_opt_list = zip(transports, routers) + (host_a, host_b) = await set_up_nodes_by_transport_and_disc_opt(transport_disc_opt_list) + + host_c = (await set_up_nodes_by_transport_opt([["/ip4/127.0.0.1/tcp/0"]]))[0] + + # Set routing info + await routers[0].server.set(host_a.get_id().xor_id, PeerInfo(host_a.get_id(), host_a.get_addrs()).to_string()) + await routers[1].server.set(host_b.get_id().xor_id, PeerInfo(host_b.get_id(), host_b.get_addrs()).to_string()) + + # routing fails because host_c does not use routing + with pytest.raises(ConnectionFailure): + await host_a.connect(PeerInfo(host_c.get_id(), [])) + with pytest.raises(ConnectionFailure): + await host_b.connect(PeerInfo(host_c.get_id(), [])) + + # Clean up + await asyncio.gather(*[host_a.close(), host_b.close(), host_c.close()]) + routers[0].server.stop() + routers[1].server.stop() diff --git a/tests/routing/test_kad_peer_router.py b/tests/routing/test_kad_peer_router.py index 69d374c..e2ff260 100644 --- a/tests/routing/test_kad_peer_router.py +++ b/tests/routing/test_kad_peer_router.py @@ -15,11 +15,11 @@ async def test_simple_two_nodes(): node_a_value = await node_b.bootstrap([("127.0.0.1", 5678)]) node_a_kad_peerinfo = node_a_value[0] - await node_a.set(node_a_kad_peerinfo.xor_id, repr(node_a_kad_peerinfo)) + await node_a.set(node_a_kad_peerinfo.xor_id, node_a_kad_peerinfo.to_string()) router = KadmeliaPeerRouter(node_b) returned_info = await router.find_peer(ID(node_a_kad_peerinfo.peer_id_bytes)) - assert repr(returned_info) == repr(node_a_kad_peerinfo) + assert returned_info == node_a_kad_peerinfo @pytest.mark.asyncio @@ -37,11 +37,11 @@ async def test_simple_three_nodes(): node_a_kad_peerinfo = node_a_value[0] await node_c.bootstrap([("127.0.0.1", 5702)]) - await node_a.set(node_a_kad_peerinfo.xor_id, repr(node_a_kad_peerinfo)) + await node_a.set(node_a_kad_peerinfo.xor_id, node_a_kad_peerinfo.to_string()) router = KadmeliaPeerRouter(node_c) returned_info = await router.find_peer(ID(node_a_kad_peerinfo.peer_id_bytes)) - assert str(returned_info) == str(node_a_kad_peerinfo) + assert returned_info == node_a_kad_peerinfo @pytest.mark.asyncio @@ -65,8 +65,8 @@ async def test_simple_four_nodes(): await node_d.bootstrap([("127.0.0.1", 5803)]) - await node_b.set(node_a_kad_peerinfo.xor_id, repr(node_a_kad_peerinfo)) + await node_b.set(node_a_kad_peerinfo.xor_id, node_a_kad_peerinfo.to_string()) router = KadmeliaPeerRouter(node_d) returned_info = await router.find_peer(ID(node_a_kad_peerinfo.peer_id_bytes)) - assert str(returned_info) == str(node_a_kad_peerinfo) + assert returned_info == node_a_kad_peerinfo diff --git a/tests/utils.py b/tests/utils.py index 4b4357d..6c1fa47 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,7 +1,9 @@ import multiaddr from libp2p import new_node +from libp2p.kademlia.network import KademliaServer from libp2p.peer.peerinfo import info_from_p2p_addr +from libp2p.routing.kademlia.kademlia_peer_router import KadmeliaPeerRouter from tests.constants import MAX_READ_LEN @@ -36,6 +38,29 @@ async def set_up_nodes_by_transport_opt(transport_opt_list): return tuple(nodes_list) +async def set_up_nodes_by_transport_and_disc_opt(transport_disc_opt_list): + nodes_list = [] + for transport_opt, disc_opt in transport_disc_opt_list: + node = await new_node(transport_opt=transport_opt, disc_opt=disc_opt) + await node.get_network().listen(multiaddr.Multiaddr(transport_opt[0])) + nodes_list.append(node) + return tuple(nodes_list) + + +async def set_up_routers(router_confs): + bootstrap_node = KademliaServer() + await bootstrap_node.listen(router_confs[0]) + + routers = [KadmeliaPeerRouter(bootstrap_node)] + for port in router_confs[1:]: + node = KademliaServer() + await node.listen(port) + + await node.bootstrap_node(("127.0.0.1", router_confs[0])) + routers.append(KadmeliaPeerRouter(node)) + return routers + + async def echo_stream_handler(stream): while True: read_string = (await stream.read(MAX_READ_LEN)).decode()