From 3f24b015ab84ae4c0f648df87ef79515d05cca3a Mon Sep 17 00:00:00 2001 From: "Aratz M. Lasa" Date: Mon, 14 Oct 2019 00:29:28 +0200 Subject: [PATCH] Implemented Host that includes a routing system. Explicitly separating different Host types as in Go implementation --- libp2p/__init__.py | 9 +++++++-- libp2p/host/basic_host.py | 10 +++------- libp2p/host/routed_host.py | 36 ++++++++++++++++++++++++++++++++++++ libp2p/network/swarm.py | 12 +----------- 4 files changed, 47 insertions(+), 20 deletions(-) create mode 100644 libp2p/host/routed_host.py diff --git a/libp2p/__init__.py b/libp2p/__init__.py index 08caf256..b0571881 100644 --- a/libp2p/__init__.py +++ b/libp2p/__init__.py @@ -4,6 +4,7 @@ from typing import Sequence from libp2p.crypto.keys import KeyPair from libp2p.crypto.rsa import create_new_key_pair from libp2p.host.basic_host import BasicHost +from libp2p.host.routed_host import RoutedHost from libp2p.kademlia.network import KademliaServer from libp2p.kademlia.storage import IStorage from libp2p.network.network_interface import INetwork @@ -106,7 +107,7 @@ def initialize_default_swarm( peerstore = peerstore_opt or PeerStore() # TODO: Initialize discovery if not presented - return Swarm(id_opt, peerstore, upgrader, transport, disc_opt) + return Swarm(id_opt, peerstore, upgrader, transport) async def new_node( @@ -149,7 +150,11 @@ async def new_node( # TODO enable support for other host type # TODO routing unimplemented - host = BasicHost(swarm_opt) + if disc_opt: + host = RoutedHost(swarm_opt, disc_opt) + else: + host = BasicHost(swarm_opt) + # Kick off cleanup job asyncio.ensure_future(cleanup_done_tasks()) diff --git a/libp2p/host/basic_host.py b/libp2p/host/basic_host.py index 926efed6..95a21111 100644 --- a/libp2p/host/basic_host.py +++ b/libp2p/host/basic_host.py @@ -13,9 +13,7 @@ from libp2p.protocol_muxer.exceptions import MultiselectClientError, Multiselect from libp2p.protocol_muxer.multiselect import Multiselect from libp2p.protocol_muxer.multiselect_client import MultiselectClient from libp2p.protocol_muxer.multiselect_communicator import MultiselectCommunicator -from libp2p.routing.kademlia.kademlia_peer_router import KadmeliaPeerRouter from libp2p.typing import StreamHandlerFn, TProtocol - from .host_interface import IHost # Upon host creation, host takes in options, @@ -34,16 +32,14 @@ class BasicHost(IHost): """ _network: INetwork - _router: KadmeliaPeerRouter peerstore: IPeerStore multiselect: Multiselect multiselect_client: MultiselectClient - def __init__(self, network: INetwork, router: KadmeliaPeerRouter = None) -> None: + def __init__(self, network: INetwork) -> None: self._network = network self._network.set_stream_handler(self._swarm_stream_handler) - self._router = router self.peerstore = self._network.peerstore # Protocol muxing self.multiselect = Multiselect() @@ -87,7 +83,7 @@ class BasicHost(IHost): return addrs def set_stream_handler( - self, protocol_id: TProtocol, stream_handler: StreamHandlerFn + self, protocol_id: TProtocol, stream_handler: StreamHandlerFn ) -> None: """ set stream handler for given `protocol_id` @@ -97,7 +93,7 @@ class BasicHost(IHost): self.multiselect.add_handler(protocol_id, stream_handler) async def new_stream( - self, peer_id: ID, protocol_ids: Sequence[TProtocol] + self, peer_id: ID, protocol_ids: Sequence[TProtocol] ) -> INetStream: """ :param peer_id: peer_id that host is connecting diff --git a/libp2p/host/routed_host.py b/libp2p/host/routed_host.py new file mode 100644 index 00000000..2c59e721 --- /dev/null +++ b/libp2p/host/routed_host.py @@ -0,0 +1,36 @@ +from libp2p.host.basic_host import BasicHost +from libp2p.network.network_interface import INetwork +from libp2p.peer.peerinfo import PeerInfo +from libp2p.routing.interfaces import IPeerRouting + + +# RoutedHost is a p2p Host that includes a routing system. +# This allows the Host to find the addresses for peers when it does not have them. +class RoutedHost(BasicHost): + _router: IPeerRouting + + def __init__(self, network: INetwork, router: IPeerRouting): + super().__init__(network) + self._router = router + + async def connect(self, peer_info: PeerInfo) -> None: + """ + connect ensures there is a connection between this host and the peer with + given `peer_info.peer_id`. See (basic_host).connect for more information. + + RoutedHost's Connect differs in that if the host has no addresses for a + given peer, it will use its routing system to try to find some. + + :param peer_info: peer_info of the peer we want to connect to + :type peer_info: peer.peerinfo.PeerInfo + """ + # 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 + self.peerstore.add_addrs(peer_info.peer_id, peer_info.addrs, 10) + + # there is already a connection to this peer + if peer_info.peer_id in self._network.connections: + return + + await self._network.dial_peer(peer_info.peer_id) diff --git a/libp2p/network/swarm.py b/libp2p/network/swarm.py index b32e46f9..90c9475a 100644 --- a/libp2p/network/swarm.py +++ b/libp2p/network/swarm.py @@ -8,7 +8,6 @@ from libp2p.network.connection.net_connection_interface import INetConn from libp2p.peer.id import ID from libp2p.peer.peerstore import PeerStoreError from libp2p.peer.peerstore_interface import IPeerStore -from libp2p.routing.interfaces import IPeerRouting from libp2p.stream_muxer.abc import IMuxedConn from libp2p.transport.exceptions import ( MuxerUpgradeFailure, @@ -36,7 +35,6 @@ class Swarm(INetwork): peerstore: IPeerStore upgrader: TransportUpgrader transport: ITransport - router: IPeerRouting # TODO: Connection and `peer_id` are 1-1 mapping in our implementation, # whereas in Go one `peer_id` may point to multiple connections. connections: Dict[ID, INetConn] @@ -51,13 +49,11 @@ class Swarm(INetwork): peerstore: IPeerStore, upgrader: TransportUpgrader, transport: ITransport, - router: IPeerRouting, ): self.self_id = peer_id self.peerstore = peerstore self.upgrader = upgrader self.transport = transport - self.router = router self.connections = dict() self.listeners = dict() @@ -96,10 +92,7 @@ class Swarm(INetwork): if not addrs: raise SwarmException(f"No known addresses to peer {peer_id}") - if not self.router: - multiaddr = addrs[0] - else: - multiaddr = self.router.find_peer(peer_id) + multiaddr = addrs[0] # Dial peer (connection to peer does not yet exist) # Transport dials peer (gets back a raw conn) try: @@ -232,9 +225,6 @@ class Swarm(INetwork): # No maddr succeeded return False - def add_router(self, router: IPeerRouting) -> None: - self.router = router - async def close(self) -> None: # TODO: Prevent from new listeners and conns being added. # Reference: https://github.com/libp2p/go-libp2p-swarm/blob/8be680aef8dea0a4497283f2f98470c2aeae6b65/swarm.go#L124-L134 # noqa: E501