From 8c16b316ac6a74fa5c2eaefd54fa7fc682e3855e Mon Sep 17 00:00:00 2001 From: Winter-Soren Date: Wed, 18 Jun 2025 23:32:48 +0530 Subject: [PATCH] added newsfragement and tests that would fail without these changes but pass with them --- newsfragments/631.feature.rst | 3 + .../test_insecure_peerstore_integration.py | 147 ++++++++++++++++++ 2 files changed, 150 insertions(+) create mode 100644 newsfragments/631.feature.rst create mode 100644 tests/core/security/test_insecure_peerstore_integration.py diff --git a/newsfragments/631.feature.rst b/newsfragments/631.feature.rst new file mode 100644 index 00000000..f8844ebd --- /dev/null +++ b/newsfragments/631.feature.rst @@ -0,0 +1,3 @@ +Store public key and peer ID in peerstore during handshake + +Modified the InsecureTransport class to accept an optional peerstore parameter and updated the handshake process to store the received public key and peer ID in the peerstore when available. diff --git a/tests/core/security/test_insecure_peerstore_integration.py b/tests/core/security/test_insecure_peerstore_integration.py new file mode 100644 index 00000000..302c9470 --- /dev/null +++ b/tests/core/security/test_insecure_peerstore_integration.py @@ -0,0 +1,147 @@ +import pytest +import trio +from trio.testing import memory_stream_pair + +from libp2p.abc import IRawConnection +from libp2p.crypto.rsa import create_new_key_pair +from libp2p.peer.id import ID +from libp2p.peer.peerstore import PeerStore +from libp2p.security.insecure.transport import InsecureTransport + + +# Adapter class to bridge between trio streams and libp2p raw connections +class TrioStreamAdapter(IRawConnection): + def __init__(self, send_stream, receive_stream, is_initiator: bool = False): + self.send_stream = send_stream + self.receive_stream = receive_stream + self.is_initiator = is_initiator + + async def write(self, data: bytes) -> None: + await self.send_stream.send_all(data) + + async def read(self, n: int | None = None) -> bytes: + if n is None or n == -1: + raise ValueError("Reading unbounded not supported") + return await self.receive_stream.receive_some(n) + + async def close(self) -> None: + await self.send_stream.aclose() + await self.receive_stream.aclose() + + def get_remote_address(self) -> tuple[str, int] | None: + # Return None since this is a test adapter without real network info + return None + + +@pytest.mark.trio +async def test_insecure_transport_stores_pubkey_in_peerstore(): + """ + Test that InsecureTransport stores the pubkey and peerid in + peerstore during handshake. + """ + # Create key pairs for both sides + local_key_pair = create_new_key_pair() + remote_key_pair = create_new_key_pair() + + # Create peer IDs + remote_peer_id = ID.from_pubkey(remote_key_pair.public_key) + + # Create peerstore + peerstore = PeerStore() + + # Create memory streams for communication + local_send, remote_receive = memory_stream_pair() + remote_send, local_receive = memory_stream_pair() + + # Create adapters + local_stream = TrioStreamAdapter(local_send, local_receive, is_initiator=True) + remote_stream = TrioStreamAdapter(remote_send, remote_receive, is_initiator=False) + + # Create transports + local_transport = InsecureTransport(local_key_pair, peerstore=peerstore) + remote_transport = InsecureTransport(remote_key_pair, peerstore=None) + + # Run handshake + async def run_local_handshake(nursery_results): + with trio.move_on_after(5): + local_conn = await local_transport.secure_outbound( + local_stream, remote_peer_id + ) + nursery_results["local"] = local_conn + + async def run_remote_handshake(nursery_results): + with trio.move_on_after(5): + remote_conn = await remote_transport.secure_inbound(remote_stream) + nursery_results["remote"] = remote_conn + + nursery_results = {} + async with trio.open_nursery() as nursery: + nursery.start_soon(run_local_handshake, nursery_results) + nursery.start_soon(run_remote_handshake, nursery_results) + await trio.sleep(0.1) # Give tasks a chance to finish + + local_conn = nursery_results.get("local") + remote_conn = nursery_results.get("remote") + + assert local_conn is not None, "Local handshake failed" + assert remote_conn is not None, "Remote handshake failed" + + # Verify that the remote peer ID is in the peerstore + assert remote_peer_id in peerstore.peer_ids() + + # Verify that the public key was stored and matches + stored_pubkey = peerstore.pubkey(remote_peer_id) + assert stored_pubkey is not None + assert stored_pubkey.serialize() == remote_key_pair.public_key.serialize() + + +@pytest.mark.trio +async def test_insecure_transport_without_peerstore(): + """ + Test that InsecureTransport works correctly + without a peerstore. + """ + # Create key pairs for both sides + local_key_pair = create_new_key_pair() + remote_key_pair = create_new_key_pair() + + # Create peer IDs + remote_peer_id = ID.from_pubkey(remote_key_pair.public_key) + + # Create memory streams for communication + local_send, remote_receive = memory_stream_pair() + remote_send, local_receive = memory_stream_pair() + + # Create adapters + local_stream = TrioStreamAdapter(local_send, local_receive, is_initiator=True) + remote_stream = TrioStreamAdapter(remote_send, remote_receive, is_initiator=False) + + # Create transports without peerstore + local_transport = InsecureTransport(local_key_pair, peerstore=None) + remote_transport = InsecureTransport(remote_key_pair, peerstore=None) + + # Run handshake + async def run_local_handshake(nursery_results): + with trio.move_on_after(5): + local_conn = await local_transport.secure_outbound( + local_stream, remote_peer_id + ) + nursery_results["local"] = local_conn + + async def run_remote_handshake(nursery_results): + with trio.move_on_after(5): + remote_conn = await remote_transport.secure_inbound(remote_stream) + nursery_results["remote"] = remote_conn + + nursery_results = {} + async with trio.open_nursery() as nursery: + nursery.start_soon(run_local_handshake, nursery_results) + nursery.start_soon(run_remote_handshake, nursery_results) + await trio.sleep(0.1) # Give tasks a chance to finish + + local_conn = nursery_results.get("local") + remote_conn = nursery_results.get("remote") + + # Verify that handshake still works without a peerstore + assert local_conn is not None, "Local handshake failed" + assert remote_conn is not None, "Remote handshake failed"