From 3cbe24caab851eb1f1ebdf2691445a34425a53a1 Mon Sep 17 00:00:00 2001 From: Jonathan de Jong Date: Tue, 17 Dec 2019 12:00:11 +0100 Subject: [PATCH 1/6] fixes #384 also adds MultiError to libp2p/exceptions.py and an additional fixme I have noticed --- libp2p/exceptions.py | 6 ++++++ libp2p/network/swarm.py | 44 +++++++++++++++++++++++++++++++++++++---- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/libp2p/exceptions.py b/libp2p/exceptions.py index bdecfad..5b78114 100644 --- a/libp2p/exceptions.py +++ b/libp2p/exceptions.py @@ -8,3 +8,9 @@ class ValidationError(BaseLibp2pError): class ParseError(BaseLibp2pError): pass + + +class MultiError(BaseLibp2pError): + """Raised with multiple exceptions.""" + + # todo: find some way for this to fancy-print all encapsulated errors diff --git a/libp2p/network/swarm.py b/libp2p/network/swarm.py index 0c40510..5099a5d 100644 --- a/libp2p/network/swarm.py +++ b/libp2p/network/swarm.py @@ -19,6 +19,7 @@ from libp2p.transport.transport_interface import ITransport from libp2p.transport.upgrader import TransportUpgrader from libp2p.typing import StreamHandlerFn +from ..exceptions import MultiError from .connection.raw_connection import RawConnection from .connection.swarm_connection import SwarmConn from .exceptions import SwarmException @@ -90,14 +91,49 @@ class Swarm(INetwork): except PeerStoreError: raise SwarmException(f"No known addresses to peer {peer_id}") - if not addrs: + if len(addrs) == 0: raise SwarmException(f"No known addresses to peer {peer_id}") - multiaddr = addrs[0] + exceptions: List[SwarmException] = [] + + # Try all known addresses + for multiaddr in addrs: + try: + return await self.dial_addr(multiaddr, peer_id) + except SwarmException as e: + exceptions.append(e) + logger.debug( + "encountered swarm exception when trying to connect to %s, " + "trying next address...", + multiaddr, + exc_info=e, + ) + + # Tried all addresses, raising exception. + if len(exceptions) > 0: + raise SwarmException( + "unable to connect to %s, all addresses failed to dial (with exceptions)", + peer_id, + ) from MultiError(exceptions) + else: + raise SwarmException( + "unable to connect to %s, all addresses failed to dial", peer_id + ) + + async def dial_addr(self, addr: Multiaddr, peer_id: ID) -> INetConn: + """ + dial_addr try to create a connection to peer_id with addr. + + :param addr: the address we want to connect with + :param peer_id: the peer we want to connect to + :raises SwarmException: raised when an error occurs + :return: muxed connection + """ + # Dial peer (connection to peer does not yet exist) # Transport dials peer (gets back a raw conn) try: - raw_conn = await self.transport.dial(multiaddr) + raw_conn = await self.transport.dial(addr) except OpenConnectionError as error: logger.debug("fail to dial peer %s over base transport", peer_id) raise SwarmException( @@ -137,7 +173,7 @@ class Swarm(INetwork): async def new_stream(self, peer_id: ID) -> INetStream: """ :param peer_id: peer_id of destination - :param protocol_id: protocol id + :param protocol_id: protocol id fixme: protocol_id not in parameters :raises SwarmException: raised when an error occurs :return: net stream instance """ From 6b759012438773067e1642a4a7a7e158d54eeec5 Mon Sep 17 00:00:00 2001 From: Jonathan de Jong Date: Tue, 17 Dec 2019 20:20:09 +0100 Subject: [PATCH 2/6] apply PR feedback (remote len == 0 block, remove redundant fixme comment + docstring line) change wording of final SwarmException to include possible case of no addresses in returned address set add `from error` in except clause --- libp2p/network/swarm.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/libp2p/network/swarm.py b/libp2p/network/swarm.py index 5099a5d..2e36ddd 100644 --- a/libp2p/network/swarm.py +++ b/libp2p/network/swarm.py @@ -88,11 +88,8 @@ class Swarm(INetwork): try: # Get peer info from peer store addrs = self.peerstore.addrs(peer_id) - except PeerStoreError: - raise SwarmException(f"No known addresses to peer {peer_id}") - - if len(addrs) == 0: - raise SwarmException(f"No known addresses to peer {peer_id}") + except PeerStoreError as error: + raise SwarmException(f"No known addresses to peer {peer_id}") from error exceptions: List[SwarmException] = [] @@ -112,12 +109,16 @@ class Swarm(INetwork): # Tried all addresses, raising exception. if len(exceptions) > 0: raise SwarmException( - "unable to connect to %s, all addresses failed to dial (with exceptions)", + "unable to connect to %s, no addresses established a successful connection " + "(with exceptions)", peer_id, ) from MultiError(exceptions) else: raise SwarmException( - "unable to connect to %s, all addresses failed to dial", peer_id + "unable to connect to %s, no addresses established a successful connection " + "(tried %d addresses)", + peer_id, + len(addrs), ) async def dial_addr(self, addr: Multiaddr, peer_id: ID) -> INetConn: @@ -173,7 +174,6 @@ class Swarm(INetwork): async def new_stream(self, peer_id: ID) -> INetStream: """ :param peer_id: peer_id of destination - :param protocol_id: protocol id fixme: protocol_id not in parameters :raises SwarmException: raised when an error occurs :return: net stream instance """ From 4e4d91b2e20a76f90cd23245a69d3c1e7bf73666 Mon Sep 17 00:00:00 2001 From: Jonathan de Jong Date: Wed, 18 Dec 2019 10:54:52 +0100 Subject: [PATCH 3/6] Apply PR review suggestion (change "muxed" to "network" in docstrings) Co-Authored-By: Kevin Mai-Husan Chia --- libp2p/network/swarm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libp2p/network/swarm.py b/libp2p/network/swarm.py index 2e36ddd..4c36ab4 100644 --- a/libp2p/network/swarm.py +++ b/libp2p/network/swarm.py @@ -128,7 +128,7 @@ class Swarm(INetwork): :param addr: the address we want to connect with :param peer_id: the peer we want to connect to :raises SwarmException: raised when an error occurs - :return: muxed connection + :return: network connection """ # Dial peer (connection to peer does not yet exist) From 81fe4049cf038d098f8b3f714e65471a4a998dfb Mon Sep 17 00:00:00 2001 From: Jonathan de Jong Date: Wed, 18 Dec 2019 18:47:03 +0100 Subject: [PATCH 4/6] Apply PR review feedback > add `if not addr` clause back > use f-strings for exceptions instead of %s --- libp2p/network/swarm.py | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/libp2p/network/swarm.py b/libp2p/network/swarm.py index 4c36ab4..eb7e306 100644 --- a/libp2p/network/swarm.py +++ b/libp2p/network/swarm.py @@ -91,6 +91,9 @@ class Swarm(INetwork): except PeerStoreError as error: raise SwarmException(f"No known addresses to peer {peer_id}") from error + if not addrs: + raise SwarmException(f"No known addresses to peer {peer_id}") + exceptions: List[SwarmException] = [] # Try all known addresses @@ -107,19 +110,10 @@ class Swarm(INetwork): ) # Tried all addresses, raising exception. - if len(exceptions) > 0: - raise SwarmException( - "unable to connect to %s, no addresses established a successful connection " - "(with exceptions)", - peer_id, - ) from MultiError(exceptions) - else: - raise SwarmException( - "unable to connect to %s, no addresses established a successful connection " - "(tried %d addresses)", - peer_id, - len(addrs), - ) + raise SwarmException( + f"unable to connect to {peer_id}, no addresses established a successful connection " + "(with exceptions)", + ) from MultiError(exceptions) async def dial_addr(self, addr: Multiaddr, peer_id: ID) -> INetConn: """ @@ -138,7 +132,7 @@ class Swarm(INetwork): except OpenConnectionError as error: logger.debug("fail to dial peer %s over base transport", peer_id) raise SwarmException( - "fail to open connection to peer %s", peer_id + f"fail to open connection to peer {peer_id}" ) from error logger.debug("dialed peer %s over base transport", peer_id) From f54bc9d1afcad7b0ac883f597277ab1db1e3ea78 Mon Sep 17 00:00:00 2001 From: Jonathan de Jong Date: Wed, 18 Dec 2019 19:05:22 +0100 Subject: [PATCH 5/6] Make linter happy --- libp2p/network/swarm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libp2p/network/swarm.py b/libp2p/network/swarm.py index eb7e306..c861877 100644 --- a/libp2p/network/swarm.py +++ b/libp2p/network/swarm.py @@ -112,7 +112,7 @@ class Swarm(INetwork): # Tried all addresses, raising exception. raise SwarmException( f"unable to connect to {peer_id}, no addresses established a successful connection " - "(with exceptions)", + "(with exceptions)" ) from MultiError(exceptions) async def dial_addr(self, addr: Multiaddr, peer_id: ID) -> INetConn: From 17074dded0a48b2fb942e2da55ec02c3f4a56292 Mon Sep 17 00:00:00 2001 From: Jonathan de Jong Date: Sat, 21 Dec 2019 10:35:34 +0100 Subject: [PATCH 6/6] add tests to new multiple multiaddr change --- tests/network/test_swarm.py | 57 +++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/tests/network/test_swarm.py b/tests/network/test_swarm.py index 6fe2543..3cc9375 100644 --- a/tests/network/test_swarm.py +++ b/tests/network/test_swarm.py @@ -1,5 +1,6 @@ import asyncio +from multiaddr import Multiaddr import pytest from libp2p.network.exceptions import SwarmException @@ -91,3 +92,59 @@ async def test_swarm_remove_conn(swarm_pair): # Test: Remove twice. There should not be errors. swarm_0.remove_conn(conn_0) assert swarm_1.get_peer_id() not in swarm_0.connections + + +@pytest.mark.asyncio +async def test_swarm_multiaddr(is_host_secure): + swarms = await SwarmFactory.create_batch_and_listen(is_host_secure, 3) + + def clear(): + swarms[0].peerstore.clear_addrs(swarms[1].get_peer_id()) + + clear() + # No addresses + with pytest.raises(SwarmException): + await swarms[0].dial_peer(swarms[1].get_peer_id()) + + clear() + # Wrong addresses + swarms[0].peerstore.add_addrs( + swarms[1].get_peer_id(), [Multiaddr("/ip4/0.0.0.0/tcp/9999")], 10000 + ) + + with pytest.raises(SwarmException): + await swarms[0].dial_peer(swarms[1].get_peer_id()) + + clear() + # Multiple wrong addresses + swarms[0].peerstore.add_addrs( + swarms[1].get_peer_id(), + [Multiaddr("/ip4/0.0.0.0/tcp/9999"), Multiaddr("/ip4/0.0.0.0/tcp/9998")], + 10000, + ) + + with pytest.raises(SwarmException): + await swarms[0].dial_peer(swarms[1].get_peer_id()) + + # Test one address + addrs = tuple( + addr + for transport in swarms[1].listeners.values() + for addr in transport.get_addrs() + ) + + swarms[0].peerstore.add_addrs(swarms[1].get_peer_id(), addrs[:1], 10000) + await swarms[0].dial_peer(swarms[1].get_peer_id()) + + # Test multiple addresses + addrs = tuple( + addr + for transport in swarms[1].listeners.values() + for addr in transport.get_addrs() + ) + + swarms[0].peerstore.add_addrs(swarms[1].get_peer_id(), addrs + addrs, 10000) + await swarms[0].dial_peer(swarms[1].get_peer_id()) + + for swarm in swarms: + await swarm.close()