diff --git a/.github/workflows/tox.yml b/.github/workflows/tox.yml index e90c3688..6f2a7b6f 100644 --- a/.github/workflows/tox.yml +++ b/.github/workflows/tox.yml @@ -37,7 +37,6 @@ jobs: with: python-version: ${{ matrix.python }} - # Add Nim installation for interop tests - name: Install Nim for interop testing if: matrix.toxenv == 'interop' run: | @@ -46,7 +45,6 @@ jobs: echo "$HOME/.nimble/bin" >> $GITHUB_PATH echo "$HOME/.choosenim/toolchains/nim-stable/bin" >> $GITHUB_PATH - # Cache nimble packages - ADD THIS - name: Cache nimble packages if: matrix.toxenv == 'interop' uses: actions/cache@v4 diff --git a/libp2p/transport/quic/connection.py b/libp2p/transport/quic/connection.py index ccba3c3d..6165d2dc 100644 --- a/libp2p/transport/quic/connection.py +++ b/libp2p/transport/quic/connection.py @@ -1,12 +1,11 @@ """ QUIC Connection implementation. -Uses aioquic's sans-IO core with trio for async operations. +Manages bidirectional QUIC connections with integrated stream multiplexing. """ from collections.abc import Awaitable, Callable import logging import socket -from sys import stdout import time from typing import TYPE_CHECKING, Any, Optional @@ -37,14 +36,7 @@ if TYPE_CHECKING: from .security import QUICTLSConfigManager from .transport import QUICTransport -logging.root.handlers = [] -logging.basicConfig( - level=logging.DEBUG, - format="%(asctime)s [%(levelname)s] [%(name)s] %(message)s", - handlers=[logging.StreamHandler(stdout)], -) logger = logging.getLogger(__name__) -logger.setLevel(logging.DEBUG) class QUICConnection(IRawConnection, IMuxedConn): @@ -66,11 +58,11 @@ class QUICConnection(IRawConnection, IMuxedConn): - COMPLETE connection ID management (fixes the original issue) """ - MAX_CONCURRENT_STREAMS = 100 + MAX_CONCURRENT_STREAMS = 256 MAX_INCOMING_STREAMS = 1000 MAX_OUTGOING_STREAMS = 1000 - STREAM_ACCEPT_TIMEOUT = 30.0 - CONNECTION_HANDSHAKE_TIMEOUT = 30.0 + STREAM_ACCEPT_TIMEOUT = 60.0 + CONNECTION_HANDSHAKE_TIMEOUT = 60.0 CONNECTION_CLOSE_TIMEOUT = 10.0 def __init__( @@ -107,7 +99,7 @@ class QUICConnection(IRawConnection, IMuxedConn): self._remote_peer_id = remote_peer_id self._local_peer_id = local_peer_id self.peer_id = remote_peer_id or local_peer_id - self.__is_initiator = is_initiator + self._is_initiator = is_initiator self._maddr = maddr self._transport = transport self._security_manager = security_manager @@ -198,7 +190,7 @@ class QUICConnection(IRawConnection, IMuxedConn): For libp2p, we primarily use bidirectional streams. """ - if self.__is_initiator: + if self._is_initiator: return 0 # Client starts with 0, then 4, 8, 12... else: return 1 # Server starts with 1, then 5, 9, 13... @@ -208,7 +200,7 @@ class QUICConnection(IRawConnection, IMuxedConn): @property def is_initiator(self) -> bool: # type: ignore """Check if this connection is the initiator.""" - return self.__is_initiator + return self._is_initiator @property def is_closed(self) -> bool: @@ -283,7 +275,7 @@ class QUICConnection(IRawConnection, IMuxedConn): try: # If this is a client connection, we need to establish the connection - if self.__is_initiator: + if self._is_initiator: await self._initiate_connection() else: # For server connections, we're already connected via the listener @@ -383,7 +375,7 @@ class QUICConnection(IRawConnection, IMuxedConn): self._background_tasks_started = True - if self.__is_initiator: + if self._is_initiator: self._nursery.start_soon(async_fn=self._client_packet_receiver) self._nursery.start_soon(async_fn=self._event_processing_loop) @@ -616,7 +608,7 @@ class QUICConnection(IRawConnection, IMuxedConn): "handshake_complete": self._handshake_completed, "peer_id": str(self._remote_peer_id) if self._remote_peer_id else None, "local_peer_id": str(self._local_peer_id), - "is_initiator": self.__is_initiator, + "is_initiator": self._is_initiator, "has_certificate": self._peer_certificate is not None, "security_manager_available": self._security_manager is not None, } @@ -808,8 +800,6 @@ class QUICConnection(IRawConnection, IMuxedConn): logger.debug(f"Removed stream {stream_id} from connection") - # *** UPDATED: Complete QUIC event handling - FIXES THE ORIGINAL ISSUE *** - async def _process_quic_events(self) -> None: """Process all pending QUIC events.""" if self._event_processing_active: @@ -868,8 +858,6 @@ class QUICConnection(IRawConnection, IMuxedConn): except Exception as e: logger.error(f"Error handling QUIC event {type(event).__name__}: {e}") - # *** NEW: Connection ID event handlers - THE MAIN FIX *** - async def _handle_connection_id_issued( self, event: events.ConnectionIdIssued ) -> None: @@ -919,10 +907,15 @@ class QUICConnection(IRawConnection, IMuxedConn): if self._current_connection_id == event.connection_id: if self._available_connection_ids: self._current_connection_id = next(iter(self._available_connection_ids)) - logger.debug( - f"Switching new connection ID: {self._current_connection_id.hex()}" - ) - self._stats["connection_id_changes"] += 1 + if self._current_connection_id: + logger.debug( + "Switching to new connection ID: " + f"{self._current_connection_id.hex()}" + ) + self._stats["connection_id_changes"] += 1 + else: + logger.warning("⚠️ No available connection IDs after retirement!") + logger.debug("⚠️ No available connection IDs after retirement!") else: self._current_connection_id = None logger.warning("⚠️ No available connection IDs after retirement!") @@ -931,8 +924,6 @@ class QUICConnection(IRawConnection, IMuxedConn): # Update statistics self._stats["connection_ids_retired"] += 1 - # *** NEW: Additional event handlers for completeness *** - async def _handle_ping_acknowledged(self, event: events.PingAcknowledged) -> None: """Handle ping acknowledgment.""" logger.debug(f"Ping acknowledged: uid={event.uid}") @@ -957,8 +948,6 @@ class QUICConnection(IRawConnection, IMuxedConn): # Handle stop sending on the stream if method exists await stream.handle_stop_sending(event.error_code) - # *** EXISTING event handlers (unchanged) *** - async def _handle_handshake_completed( self, event: events.HandshakeCompleted ) -> None: @@ -1108,7 +1097,7 @@ class QUICConnection(IRawConnection, IMuxedConn): - Even IDs are client-initiated - Odd IDs are server-initiated """ - if self.__is_initiator: + if self._is_initiator: # We're the client, so odd stream IDs are incoming return stream_id % 2 == 1 else: @@ -1336,7 +1325,6 @@ class QUICConnection(IRawConnection, IMuxedConn): QUICStreamTimeoutError: If read timeout occurs. """ - # This method doesn't make sense for a muxed connection # It's here for interface compatibility but should not be used raise NotImplementedError( "Use streams for reading data from QUIC connections. " @@ -1399,7 +1387,7 @@ class QUICConnection(IRawConnection, IMuxedConn): return ( f"QUICConnection(peer={self._remote_peer_id}, " f"addr={self._remote_addr}, " - f"initiator={self.__is_initiator}, " + f"initiator={self._is_initiator}, " f"verified={self._peer_verified}, " f"established={self._established}, " f"streams={len(self._streams)}, " diff --git a/libp2p/transport/quic/security.py b/libp2p/transport/quic/security.py index e7a85b7f..2deabd69 100644 --- a/libp2p/transport/quic/security.py +++ b/libp2p/transport/quic/security.py @@ -778,6 +778,16 @@ class PeerAuthenticator: """ try: + from datetime import datetime, timezone + + now = datetime.now(timezone.utc) + + if certificate.not_valid_after_utc < now: + raise QUICPeerVerificationError("Certificate has expired") + + if certificate.not_valid_before_utc > now: + raise QUICPeerVerificationError("Certificate not yet valid") + # Extract libp2p extension libp2p_extension = None for extension in certificate.extensions: diff --git a/libp2p/transport/quic/stream.py b/libp2p/transport/quic/stream.py index 5b8d6bf9..dac8925e 100644 --- a/libp2p/transport/quic/stream.py +++ b/libp2p/transport/quic/stream.py @@ -1,7 +1,6 @@ """ -QUIC Stream implementation for py-libp2p Module 3. -Based on patterns from go-libp2p and js-libp2p QUIC implementations. -Uses aioquic's native stream capabilities with libp2p interface compliance. +QUIC Stream implementation +Provides stream interface over QUIC's native multiplexing. """ from enum import Enum diff --git a/libp2p/transport/quic/transport.py b/libp2p/transport/quic/transport.py index fe13e07b..ef0df368 100644 --- a/libp2p/transport/quic/transport.py +++ b/libp2p/transport/quic/transport.py @@ -5,7 +5,6 @@ QUIC Transport implementation import copy import logging import ssl -import sys from typing import TYPE_CHECKING, cast from aioquic.quic.configuration import ( @@ -66,11 +65,6 @@ from .security import ( QUIC_V1_PROTOCOL = QUICTransportConfig.PROTOCOL_QUIC_V1 QUIC_DRAFT29_PROTOCOL = QUICTransportConfig.PROTOCOL_QUIC_DRAFT29 -logging.basicConfig( - level=logging.DEBUG, - format="%(asctime)s [%(levelname)s] [%(name)s] %(message)s", - handlers=[logging.StreamHandler(sys.stdout)], -) logger = logging.getLogger(__name__) diff --git a/libp2p/transport/quic/utils.py b/libp2p/transport/quic/utils.py index 1aa812bf..f57f92a7 100644 --- a/libp2p/transport/quic/utils.py +++ b/libp2p/transport/quic/utils.py @@ -27,25 +27,26 @@ IP4_PROTOCOL = "ip4" IP6_PROTOCOL = "ip6" SERVER_CONFIG_PROTOCOL_V1 = f"{QUIC_V1_PROTOCOL}_server" -SERVER_CONFIG_PROTOCOL_DRAFT_29 = f"{QUIC_V1_PROTOCOL}_server" -CLIENT_CONFIG_PROTCOL_V1 = f"{QUIC_DRAFT29_PROTOCOL}_client" +CLIENT_CONFIG_PROTCOL_V1 = f"{QUIC_V1_PROTOCOL}_client" + +SERVER_CONFIG_PROTOCOL_DRAFT_29 = f"{QUIC_DRAFT29_PROTOCOL}_server" CLIENT_CONFIG_PROTOCOL_DRAFT_29 = f"{QUIC_DRAFT29_PROTOCOL}_client" -CUSTOM_QUIC_VERSION_MAPPING = { +CUSTOM_QUIC_VERSION_MAPPING: dict[str, int] = { SERVER_CONFIG_PROTOCOL_V1: 0x00000001, # RFC 9000 CLIENT_CONFIG_PROTCOL_V1: 0x00000001, # RFC 9000 - SERVER_CONFIG_PROTOCOL_DRAFT_29: 0x00000001, # draft-29 - CLIENT_CONFIG_PROTOCOL_DRAFT_29: 0x00000001, # draft-29 + SERVER_CONFIG_PROTOCOL_DRAFT_29: 0xFF00001D, # draft-29 + CLIENT_CONFIG_PROTOCOL_DRAFT_29: 0xFF00001D, # draft-29 } # QUIC version to wire format mappings (required for aioquic) -QUIC_VERSION_MAPPINGS = { +QUIC_VERSION_MAPPINGS: dict[TProtocol, int] = { QUIC_V1_PROTOCOL: 0x00000001, # RFC 9000 - QUIC_DRAFT29_PROTOCOL: 0x00000001, # draft-29 + QUIC_DRAFT29_PROTOCOL: 0xFF00001D, # draft-29 } # ALPN protocols for libp2p over QUIC -LIBP2P_ALPN_PROTOCOLS = ["libp2p"] +LIBP2P_ALPN_PROTOCOLS: list[str] = ["libp2p"] def is_quic_multiaddr(maddr: multiaddr.Multiaddr) -> bool: