From 12ad2dcdf49220e4284fd523b4fdcbd80e1bf0f7 Mon Sep 17 00:00:00 2001 From: "sumanjeet0012@gmail.com" Date: Sun, 29 Jun 2025 16:10:27 +0530 Subject: [PATCH 01/14] Added bootstrap module --- .gitignore | 4 + examples/bootstrap/bootstrap.py | 121 ++++++++++++++++++ libp2p/__init__.py | 6 +- libp2p/discovery/bootstrap/__init__.py | 5 + libp2p/discovery/bootstrap/bootstrap.py | 69 ++++++++++ libp2p/discovery/bootstrap/utils.py | 51 ++++++++ libp2p/host/basic_host.py | 9 ++ libp2p/host/routed_host.py | 8 +- tests/discovery/__init__.py | 1 + tests/discovery/bootstrap/__init__.py | 1 + .../bootstrap/test_bootstrap_script.py | 70 ++++++++++ tests/discovery/bootstrap/test_integration.py | 78 +++++++++++ 12 files changed, 419 insertions(+), 4 deletions(-) create mode 100644 examples/bootstrap/bootstrap.py create mode 100644 libp2p/discovery/bootstrap/__init__.py create mode 100644 libp2p/discovery/bootstrap/bootstrap.py create mode 100644 libp2p/discovery/bootstrap/utils.py create mode 100644 tests/discovery/bootstrap/__init__.py create mode 100644 tests/discovery/bootstrap/test_bootstrap_script.py create mode 100644 tests/discovery/bootstrap/test_integration.py diff --git a/.gitignore b/.gitignore index e46cc8aa6..85c044678 100644 --- a/.gitignore +++ b/.gitignore @@ -178,3 +178,7 @@ env.bak/ #lockfiles uv.lock poetry.lock + +bootstrap_instructions.txt +.gitignore +README.md diff --git a/examples/bootstrap/bootstrap.py b/examples/bootstrap/bootstrap.py new file mode 100644 index 000000000..415b7a81a --- /dev/null +++ b/examples/bootstrap/bootstrap.py @@ -0,0 +1,121 @@ +import argparse +import logging +import secrets + +import multiaddr +import trio + +from libp2p import new_host +from libp2p.abc import PeerInfo +from libp2p.crypto.secp256k1 import create_new_key_pair +from libp2p.discovery.events.peerDiscovery import peerDiscovery + +# Configure logging +logger = logging.getLogger("libp2p.discovery.bootstrap") +logger.setLevel(logging.INFO) +handler = logging.StreamHandler() +handler.setFormatter( + logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") +) +logger.addHandler(handler) + +# Set root logger to DEBUG to capture all logs +logging.getLogger().setLevel(logging.DEBUG) + + +def on_peer_discovery(peer_info: PeerInfo) -> None: + """Handler for peer discovery events.""" + logger.info(f"๐Ÿ” Discovered peer: {peer_info.peer_id}") + logger.info(f" Addresses: {[str(addr) for addr in peer_info.addrs]}") + + +# Example bootstrap peers (you can replace with real bootstrap nodes) +BOOTSTRAP_PEERS = [ + # IPFS bootstrap nodes (examples - replace with actual working nodes) + "/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SznbYGzPwp8qDrq", + "/ip6/2604:a880:1:20::203:d001/tcp/4001/p2p/QmSoLPppuBtQSGwKDZT2M73ULpjvfd3aZ6ha4oFGL1KrGM", +] + + +async def run(port: int, bootstrap_addrs: list[str]) -> None: + """Run the bootstrap discovery example.""" + # Generate key pair + secret = secrets.token_bytes(32) + key_pair = create_new_key_pair(secret) + + # Create listen address + listen_addr = multiaddr.Multiaddr(f"/ip4/0.0.0.0/tcp/{port}") + + # Register peer discovery handler + peerDiscovery.register_peer_discovered_handler(on_peer_discovery) + + logger.info("๐Ÿš€ Starting Bootstrap Discovery Example") + logger.info(f"๐Ÿ“ Listening on: {listen_addr}") + logger.info(f"๐ŸŒ Bootstrap peers: {len(bootstrap_addrs)}") + + print("\n" + "=" * 60) + print("Bootstrap Discovery Example") + print("=" * 60) + print("This example demonstrates connecting to bootstrap peers.") + print("Watch the logs for peer discovery events!") + print("Press Ctrl+C to exit.") + print("=" * 60) + + # Create and run host with bootstrap discovery + host = new_host(key_pair=key_pair, bootstrap=bootstrap_addrs) + + try: + async with host.run(listen_addrs=[listen_addr]): + # Keep running and log peer discovery events + await trio.sleep_forever() + except KeyboardInterrupt: + logger.info("๐Ÿ‘‹ Shutting down...") + + +def main() -> None: + """Main entry point.""" + description = """ + Bootstrap Discovery Example for py-libp2p + + This example demonstrates how to use bootstrap peers for peer discovery. + Bootstrap peers are predefined peers that help new nodes join the network. + + Usage: + python bootstrap.py -p 8000 + python bootstrap.py -p 8001 --custom-bootstrap \\ + "/ip4/127.0.0.1/tcp/8000/p2p/QmYourPeerID" + """ + + parser = argparse.ArgumentParser( + description=description, formatter_class=argparse.RawDescriptionHelpFormatter + ) + parser.add_argument( + "-p", "--port", default=0, type=int, help="Port to listen on (default: random)" + ) + parser.add_argument( + "--custom-bootstrap", + nargs="*", + help="Custom bootstrap addresses (space-separated)", + ) + parser.add_argument( + "-v", "--verbose", action="store_true", help="Enable verbose output" + ) + + args = parser.parse_args() + + if args.verbose: + logger.setLevel(logging.DEBUG) + + # Use custom bootstrap addresses if provided, otherwise use defaults + bootstrap_addrs = ( + args.custom_bootstrap if args.custom_bootstrap else BOOTSTRAP_PEERS + ) + + try: + trio.run(run, args.port, bootstrap_addrs) + except KeyboardInterrupt: + logger.info("Exiting...") + + +if __name__ == "__main__": + main() diff --git a/libp2p/__init__.py b/libp2p/__init__.py index fa7ebefd2..744099b70 100644 --- a/libp2p/__init__.py +++ b/libp2p/__init__.py @@ -249,6 +249,7 @@ def new_host( muxer_preference: Literal["YAMUX", "MPLEX"] | None = None, listen_addrs: Sequence[multiaddr.Multiaddr] | None = None, enable_mDNS: bool = False, + bootstrap: list[str] | None = None, ) -> IHost: """ Create a new libp2p host based on the given parameters. @@ -261,6 +262,7 @@ def new_host( :param muxer_preference: optional explicit muxer preference :param listen_addrs: optional list of multiaddrs to listen on :param enable_mDNS: whether to enable mDNS discovery + :param bootstrap: optional list of bootstrap peer addresses as strings :return: return a host instance """ swarm = new_swarm( @@ -273,7 +275,7 @@ def new_host( ) if disc_opt is not None: - return RoutedHost(swarm, disc_opt, enable_mDNS) - return BasicHost(swarm, enable_mDNS) + return RoutedHost(swarm, disc_opt, enable_mDNS, bootstrap) + return BasicHost(swarm, enable_mDNS, bootstrap) __version__ = __version("libp2p") diff --git a/libp2p/discovery/bootstrap/__init__.py b/libp2p/discovery/bootstrap/__init__.py new file mode 100644 index 000000000..bad6ff74e --- /dev/null +++ b/libp2p/discovery/bootstrap/__init__.py @@ -0,0 +1,5 @@ +"""Bootstrap peer discovery module for py-libp2p.""" + +from .bootstrap import BootstrapDiscovery + +__all__ = ["BootstrapDiscovery"] diff --git a/libp2p/discovery/bootstrap/bootstrap.py b/libp2p/discovery/bootstrap/bootstrap.py new file mode 100644 index 000000000..49e070e5a --- /dev/null +++ b/libp2p/discovery/bootstrap/bootstrap.py @@ -0,0 +1,69 @@ +import logging + +from multiaddr import Multiaddr + +from libp2p.abc import INetworkService +from libp2p.discovery.events.peerDiscovery import peerDiscovery +from libp2p.peer.peerinfo import info_from_p2p_addr + +logger = logging.getLogger("libp2p.discovery.bootstrap") + + +class BootstrapDiscovery: + """ + Bootstrap-based peer discovery for py-libp2p. + Connects to predefined bootstrap peers and adds them to peerstore. + """ + + def __init__(self, swarm: INetworkService, bootstrap_addrs: list[str]): + self.swarm = swarm + self.peerstore = swarm.peerstore + self.bootstrap_addrs = bootstrap_addrs or [] + self.discovered_peers: set[str] = set() + + def start(self) -> None: + """Process bootstrap addresses and emit peer discovery events.""" + logger.debug( + f"Starting bootstrap discovery with " + f"{len(self.bootstrap_addrs)} bootstrap addresses" + ) + + for addr_str in self.bootstrap_addrs: + try: + self._process_bootstrap_addr(addr_str) + except Exception as e: + logger.warning(f"Failed to process bootstrap address {addr_str}: {e}") + + def stop(self) -> None: + """Clean up bootstrap discovery resources.""" + logger.debug("Stopping bootstrap discovery") + self.discovered_peers.clear() + + def _process_bootstrap_addr(self, addr_str: str) -> None: + """Convert string address to PeerInfo and add to peerstore.""" + # Convert string to Multiaddr + multiaddr = Multiaddr(addr_str) + + # Extract peer info from multiaddr + peer_info = info_from_p2p_addr(multiaddr) + + # Skip if it's our own peer + if peer_info.peer_id == self.swarm.get_peer_id(): + logger.debug(f"Skipping own peer ID: {peer_info.peer_id}") + return + + # Skip if already discovered + if str(peer_info.peer_id) in self.discovered_peers: + logger.debug(f"Peer already discovered: {peer_info.peer_id}") + return + + # Add to peerstore with TTL (using same pattern as mDNS) + self.peerstore.add_addrs(peer_info.peer_id, peer_info.addrs, 10) + + # Track discovered peer + self.discovered_peers.add(str(peer_info.peer_id)) + + # Emit peer discovery event + peerDiscovery.emit_peer_discovered(peer_info) + + logger.info(f"Discovered bootstrap peer: {peer_info.peer_id}") diff --git a/libp2p/discovery/bootstrap/utils.py b/libp2p/discovery/bootstrap/utils.py new file mode 100644 index 000000000..c88dfd87d --- /dev/null +++ b/libp2p/discovery/bootstrap/utils.py @@ -0,0 +1,51 @@ +"""Utility functions for bootstrap discovery.""" + +import logging + +from multiaddr import Multiaddr + +from libp2p.peer.peerinfo import InvalidAddrError, PeerInfo, info_from_p2p_addr + +logger = logging.getLogger("libp2p.discovery.bootstrap.utils") + + +def validate_bootstrap_addresses(addrs: list[str]) -> list[str]: + """ + Validate and filter bootstrap addresses. + + :param addrs: List of bootstrap address strings + :return: List of valid bootstrap addresses + """ + valid_addrs = [] + + for addr_str in addrs: + try: + # Try to parse as multiaddr + multiaddr = Multiaddr(addr_str) + + # Try to extract peer info (this validates the p2p component) + info_from_p2p_addr(multiaddr) + + valid_addrs.append(addr_str) + logger.debug(f"Valid bootstrap address: {addr_str}") + + except (InvalidAddrError, ValueError, Exception) as e: + logger.warning(f"Invalid bootstrap address '{addr_str}': {e}") + continue + + return valid_addrs + + +def parse_bootstrap_peer_info(addr_str: str) -> PeerInfo | None: + """ + Parse bootstrap address string into PeerInfo. + + :param addr_str: Bootstrap address string + :return: PeerInfo object or None if parsing fails + """ + try: + multiaddr = Multiaddr(addr_str) + return info_from_p2p_addr(multiaddr) + except Exception as e: + logger.error(f"Failed to parse bootstrap address '{addr_str}': {e}") + return None diff --git a/libp2p/host/basic_host.py b/libp2p/host/basic_host.py index 798186cfe..ff17effdc 100644 --- a/libp2p/host/basic_host.py +++ b/libp2p/host/basic_host.py @@ -29,6 +29,7 @@ StreamHandlerFn, TProtocol, ) +from libp2p.discovery.bootstrap.bootstrap import BootstrapDiscovery from libp2p.discovery.mdns.mdns import MDNSDiscovery from libp2p.host.defaults import ( get_default_protocols, @@ -91,6 +92,7 @@ def __init__( self, network: INetworkService, enable_mDNS: bool = False, + bootstrap: list[str] | None = None, default_protocols: Optional["OrderedDict[TProtocol, StreamHandlerFn]"] = None, ) -> None: self._network = network @@ -102,6 +104,8 @@ def __init__( self.multiselect_client = MultiselectClient() if enable_mDNS: self.mDNS = MDNSDiscovery(network) + if bootstrap: + self.bootstrap = BootstrapDiscovery(network, bootstrap) def get_id(self) -> ID: """ @@ -169,11 +173,16 @@ async def _run() -> AsyncIterator[None]: if hasattr(self, "mDNS") and self.mDNS is not None: logger.debug("Starting mDNS Discovery") self.mDNS.start() + if hasattr(self, "bootstrap") and self.bootstrap is not None: + logger.debug("Starting Bootstrap Discovery") + self.bootstrap.start() try: yield finally: if hasattr(self, "mDNS") and self.mDNS is not None: self.mDNS.stop() + if hasattr(self, "bootstrap") and self.bootstrap is not None: + self.bootstrap.stop() return _run() diff --git a/libp2p/host/routed_host.py b/libp2p/host/routed_host.py index 166a15ec0..e103c9e57 100644 --- a/libp2p/host/routed_host.py +++ b/libp2p/host/routed_host.py @@ -19,9 +19,13 @@ class RoutedHost(BasicHost): _router: IPeerRouting def __init__( - self, network: INetworkService, router: IPeerRouting, enable_mDNS: bool = False + self, + network: INetworkService, + router: IPeerRouting, + enable_mDNS: bool = False, + bootstrap: list[str] | None = None, ): - super().__init__(network, enable_mDNS) + super().__init__(network, enable_mDNS, bootstrap) self._router = router async def connect(self, peer_info: PeerInfo) -> None: diff --git a/tests/discovery/__init__.py b/tests/discovery/__init__.py index e69de29bb..297d7bd25 100644 --- a/tests/discovery/__init__.py +++ b/tests/discovery/__init__.py @@ -0,0 +1 @@ +"""Discovery tests for py-libp2p.""" diff --git a/tests/discovery/bootstrap/__init__.py b/tests/discovery/bootstrap/__init__.py new file mode 100644 index 000000000..4bb10e8a3 --- /dev/null +++ b/tests/discovery/bootstrap/__init__.py @@ -0,0 +1 @@ +"""Bootstrap discovery tests for py-libp2p.""" diff --git a/tests/discovery/bootstrap/test_bootstrap_script.py b/tests/discovery/bootstrap/test_bootstrap_script.py new file mode 100644 index 000000000..f22fa9701 --- /dev/null +++ b/tests/discovery/bootstrap/test_bootstrap_script.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +""" +Simple test script to verify bootstrap functionality +""" + +import os +import sys + +# Add the parent directory to sys.path so we can import libp2p +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "../../")) + +from libp2p.discovery.bootstrap.utils import ( + parse_bootstrap_peer_info, + validate_bootstrap_addresses, +) + + +def test_bootstrap_validation(): + """Test bootstrap address validation""" + print("๐Ÿงช Testing Bootstrap Address Validation") + + # Test addresses + test_addresses = [ + "/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SznbYGzPwp8qDrq", + "/ip4/127.0.0.1/tcp/8000/p2p/QmTest123", # This might be invalid peer ID format + "invalid-address", + "/ip4/192.168.1.1/tcp/4001", # Missing p2p component + "/ip6/2604:a880:1:20::203:d001/tcp/4001/p2p/QmSoLPppuBtQSGwKDZT2M73ULpjvfd3aZ6ha4oFGL1KrGM", + ] + + print(f"๐Ÿ“‹ Testing {len(test_addresses)} addresses:") + for addr in test_addresses: + print(f" โ€ข {addr}") + + # Validate addresses + valid_addresses = validate_bootstrap_addresses(test_addresses) + + print(f"\nโœ… Valid addresses ({len(valid_addresses)}):") + for addr in valid_addresses: + print(f" โ€ข {addr}") + + # Try to parse peer info + peer_info = parse_bootstrap_peer_info(addr) + if peer_info: + print(f" โ†’ Peer ID: {peer_info.peer_id}") + print(f" โ†’ Addresses: {[str(a) for a in peer_info.addrs]}") + else: + print(" โ†’ Failed to parse peer info") + + return len(valid_addresses) > 0 + + +if __name__ == "__main__": + print("=" * 60) + print("Bootstrap Module Test") + print("=" * 60) + + try: + success = test_bootstrap_validation() + if success: + print("\n๐ŸŽ‰ Bootstrap module test completed successfully!") + else: + print("\nโŒ No valid bootstrap addresses found") + sys.exit(1) + except Exception as e: + print(f"\n๐Ÿ’ฅ Test failed with error: {e}") + import traceback + + traceback.print_exc() + sys.exit(1) diff --git a/tests/discovery/bootstrap/test_integration.py b/tests/discovery/bootstrap/test_integration.py new file mode 100644 index 000000000..dc77aab24 --- /dev/null +++ b/tests/discovery/bootstrap/test_integration.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +""" +Test the full bootstrap discovery integration +""" + +import logging +import secrets + +import pytest + +from libp2p import new_host +from libp2p.abc import PeerInfo +from libp2p.crypto.secp256k1 import create_new_key_pair +from libp2p.discovery.events.peerDiscovery import peerDiscovery +from libp2p.host.basic_host import BasicHost + +# Configure logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger("bootstrap_test") + + +def on_peer_discovery(peer_info: PeerInfo) -> None: + """Handler for peer discovery events.""" + logger.info(f"๐Ÿ” Discovered peer: {peer_info.peer_id}") + logger.info(f" Addresses: {[str(addr) for addr in peer_info.addrs]}") + + +@pytest.mark.trio +async def test_bootstrap_integration(): + """Test bootstrap integration with new_host""" + print("๐Ÿงช Testing Bootstrap Integration") + + # Test bootstrap addresses + bootstrap_addrs = [ + "/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SznbYGzPwp8qDrq", + "/ip4/104.236.179.241/tcp/4001/p2p/QmSoLPppuBtQSGwKDZT2M73ULpjvfd3aZ6ha4oFGL1KrGM", + ] + + # Generate key pair + secret = secrets.token_bytes(32) + key_pair = create_new_key_pair(secret) + + # Register peer discovery handler + peerDiscovery.register_peer_discovered_handler(on_peer_discovery) + + print(f"๐ŸŒ Testing with {len(bootstrap_addrs)} bootstrap peers") + + # Create host with bootstrap + host = new_host(key_pair=key_pair, bootstrap=bootstrap_addrs) + + print("โœ… Successfully created host with bootstrap") + print(f"๐Ÿ“ Host peer ID: {host.get_id()}") + print("๐Ÿ”— Bootstrap discovery should process peers when host starts") + + # Verify bootstrap discovery is set up (cast to BasicHost for type checking) + assert isinstance(host, BasicHost), "Host should be a BasicHost instance" + assert hasattr(host, "bootstrap"), "Host should have bootstrap attribute" + assert host.bootstrap is not None, "Bootstrap discovery should be initialized" + assert len(host.bootstrap.bootstrap_addrs) == len(bootstrap_addrs), ( + "Bootstrap addresses should match" + ) + + print("๐ŸŽ‰ Bootstrap integration test completed successfully!") + + +def test_bootstrap_no_addresses(): + """Test that bootstrap is not initialized when no addresses provided""" + secret = secrets.token_bytes(32) + key_pair = create_new_key_pair(secret) + + # Create host without bootstrap + host = new_host(key_pair=key_pair) + + # Verify bootstrap is not initialized + assert isinstance(host, BasicHost) + assert not hasattr(host, "bootstrap") or host.bootstrap is None, ( + "Bootstrap should not be initialized when no addresses provided" + ) From befb2d31dbd93629e5c8e3a3830bb40ad6e67a73 Mon Sep 17 00:00:00 2001 From: "sumanjeet0012@gmail.com" Date: Sun, 29 Jun 2025 16:32:38 +0530 Subject: [PATCH 02/14] added newsfragments --- newsfragments/711.feature.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 newsfragments/711.feature.rst diff --git a/newsfragments/711.feature.rst b/newsfragments/711.feature.rst new file mode 100644 index 000000000..4be4bec2c --- /dev/null +++ b/newsfragments/711.feature.rst @@ -0,0 +1 @@ +Added `Bootstrap` peer discovery module that allows nodes to connect to predefined bootstrap peers for network discovery. \ No newline at end of file From 36be4c354bc175fb7a76407e17916a51d679c2f8 Mon Sep 17 00:00:00 2001 From: "sumanjeet0012@gmail.com" Date: Sun, 29 Jun 2025 16:45:48 +0530 Subject: [PATCH 03/14] fix: ensure newline at end of file in Bootstrap peer discovery module documentation --- newsfragments/711.feature.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/newsfragments/711.feature.rst b/newsfragments/711.feature.rst index 4be4bec2c..a4c4c5fff 100644 --- a/newsfragments/711.feature.rst +++ b/newsfragments/711.feature.rst @@ -1 +1 @@ -Added `Bootstrap` peer discovery module that allows nodes to connect to predefined bootstrap peers for network discovery. \ No newline at end of file +Added `Bootstrap` peer discovery module that allows nodes to connect to predefined bootstrap peers for network discovery. From ddbd19099319208ba0df79bb273894302f63ca16 Mon Sep 17 00:00:00 2001 From: "sumanjeet0012@gmail.com" Date: Sun, 29 Jun 2025 17:09:54 +0530 Subject: [PATCH 04/14] docs: added bootstrap docs in doctree --- docs/libp2p.discovery.bootstrap.rst | 13 +++++++++++++ docs/libp2p.discovery.rst | 1 + 2 files changed, 14 insertions(+) create mode 100644 docs/libp2p.discovery.bootstrap.rst diff --git a/docs/libp2p.discovery.bootstrap.rst b/docs/libp2p.discovery.bootstrap.rst new file mode 100644 index 000000000..d99e80d98 --- /dev/null +++ b/docs/libp2p.discovery.bootstrap.rst @@ -0,0 +1,13 @@ +libp2p.discovery.bootstrap package +================================== + +Submodules +---------- + +Module contents +--------------- + +.. automodule:: libp2p.discovery.bootstrap + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/libp2p.discovery.rst b/docs/libp2p.discovery.rst index cb8859a40..508ca059d 100644 --- a/docs/libp2p.discovery.rst +++ b/docs/libp2p.discovery.rst @@ -7,6 +7,7 @@ Subpackages .. toctree:: :maxdepth: 4 + libp2p.discovery.bootstrap libp2p.discovery.events libp2p.discovery.mdns From ec20ca81ddac64d747c8d513fc7e50a15015709b Mon Sep 17 00:00:00 2001 From: "sumanjeet0012@gmail.com" Date: Mon, 30 Jun 2025 22:46:19 +0530 Subject: [PATCH 05/14] remove unnecessary files from .gitignore --- .gitignore | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.gitignore b/.gitignore index 85c044678..e46cc8aa6 100644 --- a/.gitignore +++ b/.gitignore @@ -178,7 +178,3 @@ env.bak/ #lockfiles uv.lock poetry.lock - -bootstrap_instructions.txt -.gitignore -README.md From 69a2cb00ba27253fd5d5d6f0117342e6a1923701 Mon Sep 17 00:00:00 2001 From: "sumanjeet0012@gmail.com" Date: Mon, 30 Jun 2025 23:12:48 +0530 Subject: [PATCH 06/14] remove obsolete test script and add comprehensive validation tests for bootstrap addresses --- .../bootstrap/test_bootstrap_script.py | 70 ------------------- tests/discovery/bootstrap/test_integration.py | 26 ------- tests/discovery/bootstrap/test_utils.py | 35 ++++++++++ 3 files changed, 35 insertions(+), 96 deletions(-) delete mode 100644 tests/discovery/bootstrap/test_bootstrap_script.py create mode 100644 tests/discovery/bootstrap/test_utils.py diff --git a/tests/discovery/bootstrap/test_bootstrap_script.py b/tests/discovery/bootstrap/test_bootstrap_script.py deleted file mode 100644 index f22fa9701..000000000 --- a/tests/discovery/bootstrap/test_bootstrap_script.py +++ /dev/null @@ -1,70 +0,0 @@ -#!/usr/bin/env python3 -""" -Simple test script to verify bootstrap functionality -""" - -import os -import sys - -# Add the parent directory to sys.path so we can import libp2p -sys.path.insert(0, os.path.join(os.path.dirname(__file__), "../../")) - -from libp2p.discovery.bootstrap.utils import ( - parse_bootstrap_peer_info, - validate_bootstrap_addresses, -) - - -def test_bootstrap_validation(): - """Test bootstrap address validation""" - print("๐Ÿงช Testing Bootstrap Address Validation") - - # Test addresses - test_addresses = [ - "/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SznbYGzPwp8qDrq", - "/ip4/127.0.0.1/tcp/8000/p2p/QmTest123", # This might be invalid peer ID format - "invalid-address", - "/ip4/192.168.1.1/tcp/4001", # Missing p2p component - "/ip6/2604:a880:1:20::203:d001/tcp/4001/p2p/QmSoLPppuBtQSGwKDZT2M73ULpjvfd3aZ6ha4oFGL1KrGM", - ] - - print(f"๐Ÿ“‹ Testing {len(test_addresses)} addresses:") - for addr in test_addresses: - print(f" โ€ข {addr}") - - # Validate addresses - valid_addresses = validate_bootstrap_addresses(test_addresses) - - print(f"\nโœ… Valid addresses ({len(valid_addresses)}):") - for addr in valid_addresses: - print(f" โ€ข {addr}") - - # Try to parse peer info - peer_info = parse_bootstrap_peer_info(addr) - if peer_info: - print(f" โ†’ Peer ID: {peer_info.peer_id}") - print(f" โ†’ Addresses: {[str(a) for a in peer_info.addrs]}") - else: - print(" โ†’ Failed to parse peer info") - - return len(valid_addresses) > 0 - - -if __name__ == "__main__": - print("=" * 60) - print("Bootstrap Module Test") - print("=" * 60) - - try: - success = test_bootstrap_validation() - if success: - print("\n๐ŸŽ‰ Bootstrap module test completed successfully!") - else: - print("\nโŒ No valid bootstrap addresses found") - sys.exit(1) - except Exception as e: - print(f"\n๐Ÿ’ฅ Test failed with error: {e}") - import traceback - - traceback.print_exc() - sys.exit(1) diff --git a/tests/discovery/bootstrap/test_integration.py b/tests/discovery/bootstrap/test_integration.py index dc77aab24..06fba0f61 100644 --- a/tests/discovery/bootstrap/test_integration.py +++ b/tests/discovery/bootstrap/test_integration.py @@ -3,33 +3,18 @@ Test the full bootstrap discovery integration """ -import logging import secrets import pytest from libp2p import new_host -from libp2p.abc import PeerInfo from libp2p.crypto.secp256k1 import create_new_key_pair -from libp2p.discovery.events.peerDiscovery import peerDiscovery from libp2p.host.basic_host import BasicHost -# Configure logging -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger("bootstrap_test") - - -def on_peer_discovery(peer_info: PeerInfo) -> None: - """Handler for peer discovery events.""" - logger.info(f"๐Ÿ” Discovered peer: {peer_info.peer_id}") - logger.info(f" Addresses: {[str(addr) for addr in peer_info.addrs]}") - @pytest.mark.trio async def test_bootstrap_integration(): """Test bootstrap integration with new_host""" - print("๐Ÿงช Testing Bootstrap Integration") - # Test bootstrap addresses bootstrap_addrs = [ "/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SznbYGzPwp8qDrq", @@ -40,18 +25,9 @@ async def test_bootstrap_integration(): secret = secrets.token_bytes(32) key_pair = create_new_key_pair(secret) - # Register peer discovery handler - peerDiscovery.register_peer_discovered_handler(on_peer_discovery) - - print(f"๐ŸŒ Testing with {len(bootstrap_addrs)} bootstrap peers") - # Create host with bootstrap host = new_host(key_pair=key_pair, bootstrap=bootstrap_addrs) - print("โœ… Successfully created host with bootstrap") - print(f"๐Ÿ“ Host peer ID: {host.get_id()}") - print("๐Ÿ”— Bootstrap discovery should process peers when host starts") - # Verify bootstrap discovery is set up (cast to BasicHost for type checking) assert isinstance(host, BasicHost), "Host should be a BasicHost instance" assert hasattr(host, "bootstrap"), "Host should have bootstrap attribute" @@ -60,8 +36,6 @@ async def test_bootstrap_integration(): "Bootstrap addresses should match" ) - print("๐ŸŽ‰ Bootstrap integration test completed successfully!") - def test_bootstrap_no_addresses(): """Test that bootstrap is not initialized when no addresses provided""" diff --git a/tests/discovery/bootstrap/test_utils.py b/tests/discovery/bootstrap/test_utils.py new file mode 100644 index 000000000..ed5e9f430 --- /dev/null +++ b/tests/discovery/bootstrap/test_utils.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +""" +Test bootstrap address validation +""" + +import pytest +from libp2p.discovery.bootstrap.utils import ( + parse_bootstrap_peer_info, + validate_bootstrap_addresses, +) + +def test_validate_addresses(): + """Test validation with a mix of valid and invalid addresses in one list.""" + addresses = [ + # Valid + "/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SznbYGzPwp8qDrq", + "/ip4/104.236.179.241/tcp/4001/p2p/QmSoLPppuBtQSGwKDZT2M73ULpjvfd3aZ6ha4oFGL1KrGM", + # Invalid + "invalid-address", + "/ip4/192.168.1.1/tcp/4001", # Missing p2p part + "", # Empty + "/ip4/127.0.0.1/tcp/4001/p2p/InvalidPeerID", # Bad peer ID + ] + valid_expected = [ + "/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SznbYGzPwp8qDrq", + "/ip4/104.236.179.241/tcp/4001/p2p/QmSoLPppuBtQSGwKDZT2M73ULpjvfd3aZ6ha4oFGL1KrGM", + ] + validated = validate_bootstrap_addresses(addresses) + assert validated == valid_expected, f"Expected only valid addresses, got: {validated}" + for addr in addresses: + peer_info = parse_bootstrap_peer_info(addr) + if addr in valid_expected: + assert peer_info is not None and peer_info.peer_id is not None, f"Should parse valid address: {addr}" + else: + assert peer_info is None, f"Should not parse invalid address: {addr}" From cbb1e26a4f14128817eeeb6d684e75c57420d8ed Mon Sep 17 00:00:00 2001 From: "sumanjeet0012@gmail.com" Date: Mon, 30 Jun 2025 23:19:03 +0530 Subject: [PATCH 07/14] refactor fixed some lint issues --- tests/discovery/bootstrap/test_utils.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/discovery/bootstrap/test_utils.py b/tests/discovery/bootstrap/test_utils.py index ed5e9f430..8ccd8ae71 100644 --- a/tests/discovery/bootstrap/test_utils.py +++ b/tests/discovery/bootstrap/test_utils.py @@ -3,12 +3,12 @@ Test bootstrap address validation """ -import pytest from libp2p.discovery.bootstrap.utils import ( parse_bootstrap_peer_info, validate_bootstrap_addresses, ) + def test_validate_addresses(): """Test validation with a mix of valid and invalid addresses in one list.""" addresses = [ @@ -26,10 +26,14 @@ def test_validate_addresses(): "/ip4/104.236.179.241/tcp/4001/p2p/QmSoLPppuBtQSGwKDZT2M73ULpjvfd3aZ6ha4oFGL1KrGM", ] validated = validate_bootstrap_addresses(addresses) - assert validated == valid_expected, f"Expected only valid addresses, got: {validated}" + assert validated == valid_expected, ( + f"Expected only valid addresses, got: {validated}" + ) for addr in addresses: peer_info = parse_bootstrap_peer_info(addr) if addr in valid_expected: - assert peer_info is not None and peer_info.peer_id is not None, f"Should parse valid address: {addr}" + assert peer_info is not None and peer_info.peer_id is not None, ( + f"Should parse valid address: {addr}" + ) else: assert peer_info is None, f"Should not parse invalid address: {addr}" From 2965b4e3641307add66bef8fe5bf4707e25eface Mon Sep 17 00:00:00 2001 From: "sumanjeet0012@gmail.com" Date: Wed, 9 Jul 2025 01:45:15 +0530 Subject: [PATCH 08/14] DNS resolution working --- examples/bootstrap/bootstrap.py | 11 ++++++++--- libp2p/discovery/bootstrap/bootstrap.py | 17 +++++++++++------ 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/examples/bootstrap/bootstrap.py b/examples/bootstrap/bootstrap.py index 415b7a81a..2ab24d540 100644 --- a/examples/bootstrap/bootstrap.py +++ b/examples/bootstrap/bootstrap.py @@ -29,11 +29,16 @@ def on_peer_discovery(peer_info: PeerInfo) -> None: logger.info(f" Addresses: {[str(addr) for addr in peer_info.addrs]}") -# Example bootstrap peers (you can replace with real bootstrap nodes) +# Example bootstrap peers ( valid IPFS bootstrap nodes) BOOTSTRAP_PEERS = [ - # IPFS bootstrap nodes (examples - replace with actual working nodes) - "/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SznbYGzPwp8qDrq", + "/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ", "/ip6/2604:a880:1:20::203:d001/tcp/4001/p2p/QmSoLPppuBtQSGwKDZT2M73ULpjvfd3aZ6ha4oFGL1KrGM", + "/ip4/128.199.219.111/tcp/4001/p2p/QmSoLV4Bbm51jM9C4gDYZQ9Cy3U6aXMJDAbzgu2fzaDs64", + "/ip4/104.236.76.40/tcp/4001/p2p/QmSoLV4Bbm51jM9C4gDYZQ9Cy3U6aXMJDAbzgu2fzaDs64", + "/ip4/178.62.158.247/tcp/4001/p2p/QmSoLer265NRgSp2LA3dPaeykiS1J6DifTC88f5uVQKNAd", + "/ip6/2604:a880:1:20::203:d001/tcp/4001/p2p/QmSoLPppuBtQSGwKDZT2M73ULpjvfd3aZ6ha4oFGL1KrGM", + "/ip6/2400:6180:0:d0::151:6001/tcp/4001/p2p/QmSoLSafTMBsPKadTEgaXctDQVcqN88CNLHXMkTNwMKPnu", + "/ip6/2a03:b0c0:0:1010::23:1001/tcp/4001/p2p/QmSoLueR4xBeUbY9WZ9xGUUxunbKWcrNFTDAadQJmocnWm", ] diff --git a/libp2p/discovery/bootstrap/bootstrap.py b/libp2p/discovery/bootstrap/bootstrap.py index 49e070e5a..0f9a0b9bb 100644 --- a/libp2p/discovery/bootstrap/bootstrap.py +++ b/libp2p/discovery/bootstrap/bootstrap.py @@ -32,7 +32,7 @@ def start(self) -> None: try: self._process_bootstrap_addr(addr_str) except Exception as e: - logger.warning(f"Failed to process bootstrap address {addr_str}: {e}") + logger.debug(f"Failed to process bootstrap address {addr_str}: {e}") def stop(self) -> None: """Clean up bootstrap discovery resources.""" @@ -41,11 +41,18 @@ def stop(self) -> None: def _process_bootstrap_addr(self, addr_str: str) -> None: """Convert string address to PeerInfo and add to peerstore.""" - # Convert string to Multiaddr - multiaddr = Multiaddr(addr_str) + try: + multiaddr = Multiaddr(addr_str) + except Exception as e: + logger.debug(f"Invalid multiaddr format '{addr_str}': {e}") + return # Extract peer info from multiaddr - peer_info = info_from_p2p_addr(multiaddr) + try: + peer_info = info_from_p2p_addr(multiaddr) + except Exception as e: + logger.debug(f"Failed to extract peer info from '{addr_str}': {e}") + return # Skip if it's our own peer if peer_info.peer_id == self.swarm.get_peer_id(): @@ -65,5 +72,3 @@ def _process_bootstrap_addr(self, addr_str: str) -> None: # Emit peer discovery event peerDiscovery.emit_peer_discovered(peer_info) - - logger.info(f"Discovered bootstrap peer: {peer_info.peer_id}") From 198208aef3c044a99f9c7d4bac9215917de4cace Mon Sep 17 00:00:00 2001 From: "sumanjeet0012@gmail.com" Date: Wed, 9 Jul 2025 20:23:47 +0530 Subject: [PATCH 09/14] validate and filter bootstrap addresses during discovery initialization --- libp2p/discovery/bootstrap/bootstrap.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libp2p/discovery/bootstrap/bootstrap.py b/libp2p/discovery/bootstrap/bootstrap.py index 0f9a0b9bb..5844eb536 100644 --- a/libp2p/discovery/bootstrap/bootstrap.py +++ b/libp2p/discovery/bootstrap/bootstrap.py @@ -3,6 +3,7 @@ from multiaddr import Multiaddr from libp2p.abc import INetworkService +from libp2p.discovery.bootstrap.utils import validate_bootstrap_addresses from libp2p.discovery.events.peerDiscovery import peerDiscovery from libp2p.peer.peerinfo import info_from_p2p_addr @@ -28,6 +29,9 @@ def start(self) -> None: f"{len(self.bootstrap_addrs)} bootstrap addresses" ) + # Validate and filter bootstrap addresses + self.bootstrap_addrs = validate_bootstrap_addresses(self.bootstrap_addrs) + for addr_str in self.bootstrap_addrs: try: self._process_bootstrap_addr(addr_str) From 2dfee68f20fadd32ebc85bfeca4fdcc2d5aa3a4e Mon Sep 17 00:00:00 2001 From: "sumanjeet0012@gmail.com" Date: Thu, 10 Jul 2025 19:24:09 +0530 Subject: [PATCH 10/14] Refactor bootstrap discovery to use async methods and update bootstrap peers list --- examples/bootstrap/bootstrap.py | 7 ++++++- libp2p/discovery/bootstrap/bootstrap.py | 28 ++++++++++++++++++------- libp2p/host/basic_host.py | 2 +- pyproject.toml | 2 +- 4 files changed, 29 insertions(+), 10 deletions(-) diff --git a/examples/bootstrap/bootstrap.py b/examples/bootstrap/bootstrap.py index 2ab24d540..dce8d4a3c 100644 --- a/examples/bootstrap/bootstrap.py +++ b/examples/bootstrap/bootstrap.py @@ -29,8 +29,13 @@ def on_peer_discovery(peer_info: PeerInfo) -> None: logger.info(f" Addresses: {[str(addr) for addr in peer_info.addrs]}") -# Example bootstrap peers ( valid IPFS bootstrap nodes) +# Example bootstrap peers BOOTSTRAP_PEERS = [ + "/dnsaddr/github.com/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", + "/dnsaddr/cloudflare.com/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", + "/dnsaddr/google.com/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", + "/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", + "/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb", "/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ", "/ip6/2604:a880:1:20::203:d001/tcp/4001/p2p/QmSoLPppuBtQSGwKDZT2M73ULpjvfd3aZ6ha4oFGL1KrGM", "/ip4/128.199.219.111/tcp/4001/p2p/QmSoLV4Bbm51jM9C4gDYZQ9Cy3U6aXMJDAbzgu2fzaDs64", diff --git a/libp2p/discovery/bootstrap/bootstrap.py b/libp2p/discovery/bootstrap/bootstrap.py index 5844eb536..ef09e3b52 100644 --- a/libp2p/discovery/bootstrap/bootstrap.py +++ b/libp2p/discovery/bootstrap/bootstrap.py @@ -22,7 +22,7 @@ def __init__(self, swarm: INetworkService, bootstrap_addrs: list[str]): self.bootstrap_addrs = bootstrap_addrs or [] self.discovered_peers: set[str] = set() - def start(self) -> None: + async def start(self) -> None: """Process bootstrap addresses and emit peer discovery events.""" logger.debug( f"Starting bootstrap discovery with " @@ -34,7 +34,7 @@ def start(self) -> None: for addr_str in self.bootstrap_addrs: try: - self._process_bootstrap_addr(addr_str) + await self._process_bootstrap_addr(addr_str) except Exception as e: logger.debug(f"Failed to process bootstrap address {addr_str}: {e}") @@ -43,19 +43,33 @@ def stop(self) -> None: logger.debug("Stopping bootstrap discovery") self.discovered_peers.clear() - def _process_bootstrap_addr(self, addr_str: str) -> None: + async def _process_bootstrap_addr(self, addr_str: str) -> None: """Convert string address to PeerInfo and add to peerstore.""" try: multiaddr = Multiaddr(addr_str) except Exception as e: logger.debug(f"Invalid multiaddr format '{addr_str}': {e}") return - + if (self.is_dns_addr(multiaddr)): + resolved_addrs = await multiaddr.resolve() + for resolved_addr in resolved_addrs: + if resolved_addr == multiaddr: + return + self.add_addr(Multiaddr(resolved_addr)) + + self.add_addr(multiaddr) + + def is_dns_addr(self, addr: Multiaddr) -> bool: + """Check if the address is a DNS address.""" + return any(protocol.name == "dnsaddr" for protocol in addr.protocols()) + + def add_addr(self, addr: Multiaddr) -> None: + """Add a peer to the peerstore and emit discovery event.""" # Extract peer info from multiaddr try: - peer_info = info_from_p2p_addr(multiaddr) + peer_info = info_from_p2p_addr(addr) except Exception as e: - logger.debug(f"Failed to extract peer info from '{addr_str}': {e}") + logger.debug(f"Failed to extract peer info from '{addr}': {e}") return # Skip if it's our own peer @@ -75,4 +89,4 @@ def _process_bootstrap_addr(self, addr_str: str) -> None: self.discovered_peers.add(str(peer_info.peer_id)) # Emit peer discovery event - peerDiscovery.emit_peer_discovered(peer_info) + peerDiscovery.emit_peer_discovered(peer_info) \ No newline at end of file diff --git a/libp2p/host/basic_host.py b/libp2p/host/basic_host.py index b3b0ecc7d..70e419532 100644 --- a/libp2p/host/basic_host.py +++ b/libp2p/host/basic_host.py @@ -178,7 +178,7 @@ async def _run() -> AsyncIterator[None]: self.mDNS.start() if hasattr(self, "bootstrap") and self.bootstrap is not None: logger.debug("Starting Bootstrap Discovery") - self.bootstrap.start() + await self.bootstrap.start() try: yield finally: diff --git a/pyproject.toml b/pyproject.toml index 604949fb2..1cc9bfc8b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,7 @@ dependencies = [ "exceptiongroup>=1.2.0; python_version < '3.11'", "grpcio>=1.41.0", "lru-dict>=1.1.6", - "multiaddr>=0.0.9", + "multiaddr @ git+https://github.com/multiformats/py-multiaddr.git", "mypy-protobuf>=3.0.0", "noiseprotocol>=0.3.0", "protobuf>=4.21.0,<5.0.0", From 9669a92976378260db1bc4693cfdcf3e41f0010d Mon Sep 17 00:00:00 2001 From: "sumanjeet0012@gmail.com" Date: Thu, 10 Jul 2025 19:25:58 +0530 Subject: [PATCH 11/14] Fix formatting and linting issues --- libp2p/discovery/bootstrap/bootstrap.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/libp2p/discovery/bootstrap/bootstrap.py b/libp2p/discovery/bootstrap/bootstrap.py index ef09e3b52..43d7a16c7 100644 --- a/libp2p/discovery/bootstrap/bootstrap.py +++ b/libp2p/discovery/bootstrap/bootstrap.py @@ -50,19 +50,19 @@ async def _process_bootstrap_addr(self, addr_str: str) -> None: except Exception as e: logger.debug(f"Invalid multiaddr format '{addr_str}': {e}") return - if (self.is_dns_addr(multiaddr)): + if self.is_dns_addr(multiaddr): resolved_addrs = await multiaddr.resolve() for resolved_addr in resolved_addrs: if resolved_addr == multiaddr: return - self.add_addr(Multiaddr(resolved_addr)) - + self.add_addr(Multiaddr(resolved_addr)) + self.add_addr(multiaddr) def is_dns_addr(self, addr: Multiaddr) -> bool: """Check if the address is a DNS address.""" return any(protocol.name == "dnsaddr" for protocol in addr.protocols()) - + def add_addr(self, addr: Multiaddr) -> None: """Add a peer to the peerstore and emit discovery event.""" # Extract peer info from multiaddr @@ -89,4 +89,4 @@ def add_addr(self, addr: Multiaddr) -> None: self.discovered_peers.add(str(peer_info.peer_id)) # Emit peer discovery event - peerDiscovery.emit_peer_discovered(peer_info) \ No newline at end of file + peerDiscovery.emit_peer_discovered(peer_info) From 9e76940e75328e0e73b00016474cd355f11ccf85 Mon Sep 17 00:00:00 2001 From: "sumanjeet0012@gmail.com" Date: Mon, 14 Jul 2025 01:38:15 +0530 Subject: [PATCH 12/14] Refactor logging configuration to reduce verbosity and improve peer discovery events --- examples/bootstrap/bootstrap.py | 11 ++++++--- libp2p/discovery/bootstrap/bootstrap.py | 31 ++++++++++++------------- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/examples/bootstrap/bootstrap.py b/examples/bootstrap/bootstrap.py index dce8d4a3c..af7d08cc9 100644 --- a/examples/bootstrap/bootstrap.py +++ b/examples/bootstrap/bootstrap.py @@ -19,14 +19,19 @@ ) logger.addHandler(handler) -# Set root logger to DEBUG to capture all logs -logging.getLogger().setLevel(logging.DEBUG) +# Configure root logger to only show warnings and above to reduce noise +# This prevents verbose DEBUG messages from multiaddr, DNS, etc. +logging.getLogger().setLevel(logging.WARNING) + +# Specifically silence noisy libraries +logging.getLogger("multiaddr").setLevel(logging.WARNING) +logging.getLogger("root").setLevel(logging.WARNING) def on_peer_discovery(peer_info: PeerInfo) -> None: """Handler for peer discovery events.""" logger.info(f"๐Ÿ” Discovered peer: {peer_info.peer_id}") - logger.info(f" Addresses: {[str(addr) for addr in peer_info.addrs]}") + logger.debug(f" Addresses: {[str(addr) for addr in peer_info.addrs]}") # Example bootstrap peers diff --git a/libp2p/discovery/bootstrap/bootstrap.py b/libp2p/discovery/bootstrap/bootstrap.py index 43d7a16c7..94402b8c3 100644 --- a/libp2p/discovery/bootstrap/bootstrap.py +++ b/libp2p/discovery/bootstrap/bootstrap.py @@ -1,13 +1,15 @@ import logging from multiaddr import Multiaddr +from multiaddr.resolvers import DNSResolver -from libp2p.abc import INetworkService +from libp2p.abc import ID, INetworkService, PeerInfo from libp2p.discovery.bootstrap.utils import validate_bootstrap_addresses from libp2p.discovery.events.peerDiscovery import peerDiscovery from libp2p.peer.peerinfo import info_from_p2p_addr logger = logging.getLogger("libp2p.discovery.bootstrap") +resolver = DNSResolver() class BootstrapDiscovery: @@ -51,27 +53,24 @@ async def _process_bootstrap_addr(self, addr_str: str) -> None: logger.debug(f"Invalid multiaddr format '{addr_str}': {e}") return if self.is_dns_addr(multiaddr): - resolved_addrs = await multiaddr.resolve() - for resolved_addr in resolved_addrs: - if resolved_addr == multiaddr: - return - self.add_addr(Multiaddr(resolved_addr)) - - self.add_addr(multiaddr) + resolved_addrs = await resolver.resolve(multiaddr) + peer_id_str = multiaddr.get_peer_id() + if peer_id_str is None: + logger.warning(f"Missing peer ID in DNS address: {addr_str}") + return + peer_id = ID.from_base58(peer_id_str) + addrs = [addr for addr in resolved_addrs] + peer_info = PeerInfo(peer_id, addrs) + self.add_addr(peer_info) + else: + self.add_addr(info_from_p2p_addr(multiaddr)) def is_dns_addr(self, addr: Multiaddr) -> bool: """Check if the address is a DNS address.""" return any(protocol.name == "dnsaddr" for protocol in addr.protocols()) - def add_addr(self, addr: Multiaddr) -> None: + def add_addr(self, peer_info: PeerInfo) -> None: """Add a peer to the peerstore and emit discovery event.""" - # Extract peer info from multiaddr - try: - peer_info = info_from_p2p_addr(addr) - except Exception as e: - logger.debug(f"Failed to extract peer info from '{addr}': {e}") - return - # Skip if it's our own peer if peer_info.peer_id == self.swarm.get_peer_id(): logger.debug(f"Skipping own peer ID: {peer_info.peer_id}") From 9f38d48e2618edd1c3fbe5c77f169b3c20acb128 Mon Sep 17 00:00:00 2001 From: "sumanjeet0012@gmail.com" Date: Mon, 14 Jul 2025 01:45:12 +0530 Subject: [PATCH 13/14] Fix valid bootstrap address in test case --- tests/discovery/bootstrap/test_utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/discovery/bootstrap/test_utils.py b/tests/discovery/bootstrap/test_utils.py index 8ccd8ae71..b99e948f7 100644 --- a/tests/discovery/bootstrap/test_utils.py +++ b/tests/discovery/bootstrap/test_utils.py @@ -12,8 +12,8 @@ def test_validate_addresses(): """Test validation with a mix of valid and invalid addresses in one list.""" addresses = [ - # Valid - "/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SznbYGzPwp8qDrq", + # Valid - using proper peer IDs + "/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ", "/ip4/104.236.179.241/tcp/4001/p2p/QmSoLPppuBtQSGwKDZT2M73ULpjvfd3aZ6ha4oFGL1KrGM", # Invalid "invalid-address", @@ -22,7 +22,7 @@ def test_validate_addresses(): "/ip4/127.0.0.1/tcp/4001/p2p/InvalidPeerID", # Bad peer ID ] valid_expected = [ - "/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SznbYGzPwp8qDrq", + "/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ", "/ip4/104.236.179.241/tcp/4001/p2p/QmSoLPppuBtQSGwKDZT2M73ULpjvfd3aZ6ha4oFGL1KrGM", ] validated = validate_bootstrap_addresses(addresses) From e91f458446996045aeb84b45c6ec3be742b5b6ea Mon Sep 17 00:00:00 2001 From: "sumanjeet0012@gmail.com" Date: Thu, 24 Jul 2025 00:11:05 +0530 Subject: [PATCH 14/14] Enhance peer discovery logging and address resolution handling in BootstrapDiscovery --- libp2p/discovery/bootstrap/bootstrap.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/libp2p/discovery/bootstrap/bootstrap.py b/libp2p/discovery/bootstrap/bootstrap.py index 94402b8c3..222a88a14 100644 --- a/libp2p/discovery/bootstrap/bootstrap.py +++ b/libp2p/discovery/bootstrap/bootstrap.py @@ -60,6 +60,9 @@ async def _process_bootstrap_addr(self, addr_str: str) -> None: return peer_id = ID.from_base58(peer_id_str) addrs = [addr for addr in resolved_addrs] + if not addrs: + logger.warning(f"No addresses resolved for DNS address: {addr_str}") + return peer_info = PeerInfo(peer_id, addrs) self.add_addr(peer_info) else: @@ -76,16 +79,16 @@ def add_addr(self, peer_info: PeerInfo) -> None: logger.debug(f"Skipping own peer ID: {peer_info.peer_id}") return - # Skip if already discovered - if str(peer_info.peer_id) in self.discovered_peers: - logger.debug(f"Peer already discovered: {peer_info.peer_id}") - return - - # Add to peerstore with TTL (using same pattern as mDNS) + # Always add addresses to peerstore (allows multiple addresses for same peer) self.peerstore.add_addrs(peer_info.peer_id, peer_info.addrs, 10) - # Track discovered peer - self.discovered_peers.add(str(peer_info.peer_id)) - - # Emit peer discovery event - peerDiscovery.emit_peer_discovered(peer_info) + # Only emit discovery event if this is the first time we see this peer + peer_id_str = str(peer_info.peer_id) + if peer_id_str not in self.discovered_peers: + # Track discovered peer + self.discovered_peers.add(peer_id_str) + # Emit peer discovery event + peerDiscovery.emit_peer_discovered(peer_info) + logger.debug(f"Peer discovered: {peer_info.peer_id}") + else: + logger.debug(f"Additional addresses added for peer: {peer_info.peer_id}")