from abc import ( ABC, abstractmethod, ) from collections.abc import ( AsyncIterable, Iterable, KeysView, Sequence, ) from contextlib import AbstractAsyncContextManager from types import ( TracebackType, ) from typing import ( TYPE_CHECKING, Any, AsyncContextManager, Optional, ) from multiaddr import ( Multiaddr, ) import trio from libp2p.crypto.keys import ( KeyPair, PrivateKey, PublicKey, ) from libp2p.custom_types import ( StreamHandlerFn, THandler, TProtocol, ValidatorFn, ) from libp2p.io.abc import ( Closer, ReadWriteCloser, ) from libp2p.peer.id import ( ID, ) import libp2p.peer.pb.peer_record_pb2 as pb from libp2p.peer.peerinfo import ( PeerInfo, ) if TYPE_CHECKING: from libp2p.peer.envelope import Envelope from libp2p.peer.peer_record import PeerRecord from libp2p.protocol_muxer.multiselect import Multiselect from libp2p.pubsub.pubsub import ( Pubsub, ) from libp2p.pubsub.pb import ( rpc_pb2, ) from libp2p.tools.async_service import ( ServiceAPI, ) # -------------------------- raw_connection interface.py -------------------------- class IRawConnection(ReadWriteCloser): """ Interface for a raw connection. This interface provides a basic reader/writer connection abstraction. Attributes ---------- is_initiator (bool): True if the local endpoint initiated the connection. """ is_initiator: bool # -------------------------- secure_conn interface.py -------------------------- """ Relevant go repo: https://github.com/libp2p/go-conn-security/blob/master/interface.go """ class AbstractSecureConn(ABC): """ Abstract interface for secure connections. Represents a secured connection object, including details about the security involved in the connection. """ @abstractmethod def get_local_peer(self) -> ID: """ Retrieve the local peer's identifier. :return: The local peer ID. """ @abstractmethod def get_local_private_key(self) -> PrivateKey: """ Retrieve the local peer's private key. :return: The private key of the local peer. """ @abstractmethod def get_remote_peer(self) -> ID: """ Retrieve the remote peer's identifier. :return: The remote peer ID. """ @abstractmethod def get_remote_public_key(self) -> PublicKey: """ Retrieve the remote peer's public key. :return: The public key of the remote peer. """ class ISecureConn(AbstractSecureConn, IRawConnection): """ Interface for a secure connection. Combines secure connection functionalities with raw I/O operations. """ # -------------------------- stream_muxer abc.py -------------------------- class IMuxedConn(ABC): """ Interface for a multiplexed connection. References ---------- https://github.com/libp2p/go-stream-muxer/blob/master/muxer.go Attributes ---------- peer_id (ID): The identifier of the connected peer. event_started (trio.Event): An event that signals when the multiplexer has started. """ peer_id: ID event_started: trio.Event @abstractmethod def __init__( self, conn: ISecureConn, peer_id: ID, ) -> None: """ Initialize a new multiplexed connection. :param conn: An instance of a secure connection used for new multiplexed streams. :param peer_id: The peer ID associated with the connection. """ @property @abstractmethod def is_initiator(self) -> bool: """ Determine if this connection is the initiator. :return: True if this connection initiated the connection, otherwise False. """ @abstractmethod async def start(self) -> None: """ Start the multiplexer. """ @abstractmethod async def close(self) -> None: """ Close the multiplexed connection. """ @property @abstractmethod def is_closed(self) -> bool: """ Check if the connection is fully closed. :return: True if the connection is closed, otherwise False. """ @abstractmethod async def open_stream(self) -> "IMuxedStream": """ Create and open a new multiplexed stream. :return: A new instance of IMuxedStream. """ @abstractmethod async def accept_stream(self) -> "IMuxedStream": """ Accept a new multiplexed stream initiated by the remote peer. :return: A new instance of IMuxedStream. """ class IMuxedStream(ReadWriteCloser, AsyncContextManager["IMuxedStream"]): """ Interface for a multiplexed stream. Represents a stream multiplexed over a single connection. Attributes ---------- muxed_conn (IMuxedConn): The underlying multiplexed connection. """ muxed_conn: IMuxedConn @abstractmethod async def reset(self) -> None: """ Reset the stream. This method closes both ends of the stream, instructing the remote side to hang up. """ @abstractmethod def set_deadline(self, ttl: int) -> bool: """ Set a deadline for the stream. :param ttl: Time-to-live for the stream in seconds. :return: True if the deadline was set successfully, otherwise False. """ @abstractmethod async def __aenter__(self) -> "IMuxedStream": """Enter the async context manager.""" return self async def __aexit__( self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None, ) -> None: """Exit the async context manager and close the stream.""" await self.close() # -------------------------- net_stream interface.py -------------------------- class INetStream(ReadWriteCloser): """ Interface for a network stream. Represents a network stream operating over a multiplexed connection. Attributes ---------- muxed_conn (IMuxedConn): The multiplexed connection that this stream belongs to. """ muxed_conn: IMuxedConn @abstractmethod def get_protocol(self) -> TProtocol | None: """ Retrieve the protocol identifier for the stream. :return: The protocol ID associated with the stream. """ @abstractmethod def set_protocol(self, protocol_id: TProtocol) -> None: """ Set the protocol identifier for the stream. :param protocol_id: The protocol ID to assign to the stream. """ @abstractmethod async def reset(self) -> None: """ Reset the network stream. This method closes both ends of the stream. """ # -------------------------- net_connection interface.py -------------------------- class INetConn(Closer): """ Interface for a network connection. Defines a network connection capable of creating and managing streams. Attributes ---------- muxed_conn (IMuxedConn): The underlying multiplexed connection. event_started (trio.Event): Event signaling when the connection has started. """ muxed_conn: IMuxedConn event_started: trio.Event @abstractmethod async def new_stream(self) -> INetStream: """ Create a new network stream over the connection. :return: A new instance of INetStream. """ @abstractmethod def get_streams(self) -> tuple[INetStream, ...]: """ Retrieve all active streams associated with this connection. :return: A tuple containing instances of INetStream. """ @abstractmethod def get_transport_addresses(self) -> list[Multiaddr]: """ Retrieve the transport addresses used by this connection. :return: A list of multiaddresses used by the transport. """ # -------------------------- peermetadata interface.py -------------------------- class IPeerMetadata(ABC): """ Interface for managing peer metadata. Provides methods for storing and retrieving metadata associated with peers. """ @abstractmethod def get(self, peer_id: ID, key: str) -> Any: """ Retrieve metadata for a specified peer. :param peer_id: The ID of the peer. :param key: The key for the metadata to retrieve. :return: The metadata value associated with the key. :raises Exception: If the peer ID is not found. """ @abstractmethod def put(self, peer_id: ID, key: str, val: Any) -> None: """ Store metadata for a specified peer. :param peer_id: The ID of the peer. :param key: The key for the metadata. :param val: The value to store. :raises Exception: If the operation is unsuccessful. """ @abstractmethod def clear_metadata(self, peer_id: ID) -> None: """ Remove all stored metadata for the specified peer. Parameters ---------- peer_id : ID The peer identifier whose metadata are to be removed. """ # -------------------------- addrbook interface.py -------------------------- class IAddrBook(ABC): """ Interface for an address book. Provides methods for managing peer addresses. """ @abstractmethod def add_addr(self, peer_id: ID, addr: Multiaddr, ttl: int) -> None: """ Add a single address for a given peer. This method calls ``add_addrs(peer_id, [addr], ttl)``. Parameters ---------- peer_id : ID The peer identifier for which to add the address. addr : Multiaddr The multiaddress of the peer. ttl : int The time-to-live for the address, after which it is no longer valid. """ @abstractmethod def add_addrs(self, peer_id: ID, addrs: Sequence[Multiaddr], ttl: int) -> None: """ Add multiple addresses for a given peer, all with the same TTL. If an address already exists with a longer TTL, no action should be taken. If an address exists with a shorter TTL, its TTL should be extended to match the provided TTL. Parameters ---------- peer_id : ID The peer identifier for which to add addresses. addrs : Sequence[Multiaddr] A sequence of multiaddresses to add. ttl : int The time-to-live for the addresses, after which they become invalid. """ @abstractmethod def addrs(self, peer_id: ID) -> list[Multiaddr]: """ Retrieve all known and valid addresses for the specified peer. Parameters ---------- peer_id : ID The peer identifier whose addresses are requested. Returns ------- list[Multiaddr] A list of valid multiaddresses for the given peer. """ @abstractmethod def clear_addrs(self, peer_id: ID) -> None: """ Remove all stored addresses for the specified peer. Parameters ---------- peer_id : ID The peer identifier whose addresses are to be removed. """ @abstractmethod def peers_with_addrs(self) -> list[ID]: """ Retrieve all peer identifiers that have stored addresses. Returns ------- list[ID] A list of peer IDs with stored addresses. """ # ------------------ certified-addr-book interface.py --------------------- class ICertifiedAddrBook(ABC): """ Interface for a certified address book. Provides methods for managing signed peer records """ @abstractmethod def consume_peer_record(self, envelope: "Envelope", ttl: int) -> bool: """ Accept and store a signed PeerRecord, unless it's older than the one already stored. This function: - Extracts the peer ID and sequence number from the envelope - Rejects the record if it's older (lower seq) - Updates the stored peer record and replaces associated addresses if accepted Parameters ---------- envelope: Signed envelope containing a PeerRecord. ttl: Time-to-live for the included multiaddrs (in seconds). """ @abstractmethod def get_peer_record(self, peer_id: ID) -> Optional["Envelope"]: """ Retrieve the most recent signed PeerRecord `Envelope` for a peer, if it exists and is still relevant. First, it runs cleanup via `maybe_delete_peer_record` to purge stale data. Then it checks whether the peer has valid, unexpired addresses before returning the associated envelope. Parameters ---------- peer_id : ID The peer to look up. """ @abstractmethod def maybe_delete_peer_record(self, peer_id: ID) -> None: """ Delete the signed peer record for a peer if it has no know (non-expired) addresses. This is a garbage collection mechanism: if all addresses for a peer have expired or been cleared, there's no point holding onto its signed `Envelope` Parameters ---------- peer_id : ID The peer whose record we may delete. """ # -------------------------- keybook interface.py -------------------------- class IKeyBook(ABC): """ Interface for an key book. Provides methods for managing cryptographic keys. """ @abstractmethod def pubkey(self, peer_id: ID) -> PublicKey: """ Returns the public key of the specified peer Parameters ---------- peer_id : ID The peer identifier whose public key is to be returned. """ @abstractmethod def privkey(self, peer_id: ID) -> PrivateKey: """ Returns the private key of the specified peer Parameters ---------- peer_id : ID The peer identifier whose private key is to be returned. """ @abstractmethod def add_pubkey(self, peer_id: ID, pubkey: PublicKey) -> None: """ Adds the public key for a specified peer Parameters ---------- peer_id : ID The peer identifier whose public key is to be added pubkey: PublicKey The public key of the peer """ @abstractmethod def add_privkey(self, peer_id: ID, privkey: PrivateKey) -> None: """ Adds the private key for a specified peer Parameters ---------- peer_id : ID The peer identifier whose private key is to be added privkey: PrivateKey The private key of the peer """ @abstractmethod def add_key_pair(self, peer_id: ID, key_pair: KeyPair) -> None: """ Adds the key pair for a specified peer Parameters ---------- peer_id : ID The peer identifier whose key pair is to be added key_pair: KeyPair The key pair of the peer """ @abstractmethod def peer_with_keys(self) -> list[ID]: """Returns all the peer IDs stored in the AddrBook""" @abstractmethod def clear_keydata(self, peer_id: ID) -> None: """ Remove all stored keydata for the specified peer. Parameters ---------- peer_id : ID The peer identifier whose keys are to be removed. """ # -------------------------- metrics interface.py -------------------------- class IMetrics(ABC): """ Interface for metrics of peer interaction. Provides methods for managing the metrics. """ @abstractmethod def record_latency(self, peer_id: ID, RTT: float) -> None: """ Records a new round-trip time (RTT) latency value for the specified peer using Exponentially Weighted Moving Average (EWMA). Parameters ---------- peer_id : ID The identifier of the peer for which latency is being recorded. RTT : float The round-trip time latency value to record. """ @abstractmethod def latency_EWMA(self, peer_id: ID) -> float: """ Returns the current latency value for the specified peer using Exponentially Weighted Moving Average (EWMA). Parameters ---------- peer_id : ID The identifier of the peer whose latency EWMA is to be returned. """ @abstractmethod def clear_metrics(self, peer_id: ID) -> None: """ Clears the stored latency metrics for the specified peer. Parameters ---------- peer_id : ID The identifier of the peer whose latency metrics are to be cleared. """ # -------------------------- protobook interface.py -------------------------- class IProtoBook(ABC): """ Interface for a protocol book. Provides methods for managing the list of supported protocols. """ @abstractmethod def get_protocols(self, peer_id: ID) -> list[str]: """ Returns the list of protocols associated with the specified peer. Parameters ---------- peer_id : ID The identifier of the peer whose supported protocols are to be returned. """ @abstractmethod def add_protocols(self, peer_id: ID, protocols: Sequence[str]) -> None: """ Adds the given protocols to the specified peer's protocol list. Parameters ---------- peer_id : ID The identifier of the peer to which protocols will be added. protocols : Sequence[str] A sequence of protocol strings to add. """ @abstractmethod def set_protocols(self, peer_id: ID, protocols: Sequence[str]) -> None: """ Replaces the existing protocols of the specified peer with the given list. Parameters ---------- peer_id : ID The identifier of the peer whose protocols are to be set. protocols : Sequence[str] A sequence of protocol strings to assign. """ @abstractmethod def remove_protocols(self, peer_id: ID, protocols: Sequence[str]) -> None: """ Removes the specified protocols from the peer's protocol list. Parameters ---------- peer_id : ID The identifier of the peer from which protocols will be removed. protocols : Sequence[str] A sequence of protocol strings to remove. """ @abstractmethod def supports_protocols(self, peer_id: ID, protocols: Sequence[str]) -> list[str]: """ Returns the list of protocols from the input sequence that the peer supports. Parameters ---------- peer_id : ID The identifier of the peer to check for protocol support. protocols : Sequence[str] A sequence of protocol strings to check against the peer's supported protocols. """ @abstractmethod def first_supported_protocol(self, peer_id: ID, protocols: Sequence[str]) -> str: """ Returns the first protocol from the input list that the peer supports. Parameters ---------- peer_id : ID The identifier of the peer to check for supported protocols. protocols : Sequence[str] A sequence of protocol strings to check. Returns ------- str The first matching protocol string, or an empty string if none are supported. """ @abstractmethod def clear_protocol_data(self, peer_id: ID) -> None: """ Clears all protocol data associated with the specified peer. Parameters ---------- peer_id : ID The identifier of the peer whose protocol data will be cleared. """ # -------------------------- peerstore interface.py -------------------------- class IPeerStore( IPeerMetadata, IAddrBook, ICertifiedAddrBook, IKeyBook, IMetrics, IProtoBook ): """ Interface for a peer store. Provides methods for managing peer information including address management, protocol handling, and key storage. """ # -------METADATA--------- @abstractmethod def get(self, peer_id: ID, key: str) -> Any: """ Retrieve the value associated with a key for a specified peer. Parameters ---------- peer_id : ID The identifier of the peer. key : str The key to look up. Returns ------- Any The value corresponding to the specified key. Raises ------ PeerStoreError If the peer ID or value is not found. """ @abstractmethod def put(self, peer_id: ID, key: str, val: Any) -> None: """ Store a key-value pair for the specified peer. Parameters ---------- peer_id : ID The identifier of the peer. key : str The key for the data. val : Any The value to store. """ @abstractmethod def clear_metadata(self, peer_id: ID) -> None: """ Clears the stored latency metrics for the specified peer. Parameters ---------- peer_id : ID The identifier of the peer whose latency metrics are to be cleared. """ # --------ADDR-BOOK--------- @abstractmethod def add_addr(self, peer_id: ID, addr: Multiaddr, ttl: int) -> None: """ Add an address for the specified peer. Parameters ---------- peer_id : ID The identifier of the peer. addr : Multiaddr The multiaddress to add. ttl : int The time-to-live for the record. """ @abstractmethod def add_addrs(self, peer_id: ID, addrs: Sequence[Multiaddr], ttl: int) -> None: """ Add multiple addresses for the specified peer. Parameters ---------- peer_id : ID The identifier of the peer. addrs : Sequence[Multiaddr] A sequence of multiaddresses to add. ttl : int The time-to-live for the record. """ @abstractmethod def addrs(self, peer_id: ID) -> list[Multiaddr]: """ Retrieve the addresses for the specified peer. Parameters ---------- peer_id : ID The identifier of the peer. Returns ------- list[Multiaddr] A list of multiaddresses. """ @abstractmethod def clear_addrs(self, peer_id: ID) -> None: """ Clear all addresses for the specified peer. Parameters ---------- peer_id : ID The identifier of the peer. """ @abstractmethod def peers_with_addrs(self) -> list[ID]: """ Retrieve all peer identifiers with stored addresses. Returns ------- list[ID] A list of peer IDs. """ # --------CERTIFIED-ADDR-BOOK---------- @abstractmethod def get_local_record(self) -> Optional["Envelope"]: """Get the local-peer-record wrapped in Envelope""" @abstractmethod def set_local_record(self, envelope: "Envelope") -> None: """Set the local-peer-record wrapped in Envelope""" @abstractmethod def consume_peer_record(self, envelope: "Envelope", ttl: int) -> bool: """ Accept and store a signed PeerRecord, unless it's older than the one already stored. This function: - Extracts the peer ID and sequence number from the envelope - Rejects the record if it's older (lower seq) - Updates the stored peer record and replaces associated addresses if accepted Parameters ---------- envelope: Signed envelope containing a PeerRecord. ttl: Time-to-live for the included multiaddrs (in seconds). """ @abstractmethod def get_peer_record(self, peer_id: ID) -> Optional["Envelope"]: """ Retrieve the most recent signed PeerRecord `Envelope` for a peer, if it exists and is still relevant. First, it runs cleanup via `maybe_delete_peer_record` to purge stale data. Then it checks whether the peer has valid, unexpired addresses before returning the associated envelope. Parameters ---------- peer_id : ID The peer to look up. """ @abstractmethod def maybe_delete_peer_record(self, peer_id: ID) -> None: """ Delete the signed peer record for a peer if it has no know (non-expired) addresses. This is a garbage collection mechanism: if all addresses for a peer have expired or been cleared, there's no point holding onto its signed `Envelope` Parameters ---------- peer_id : ID The peer whose record we may delete. """ # --------KEY-BOOK---------- @abstractmethod def pubkey(self, peer_id: ID) -> PublicKey: """ Retrieve the public key for the specified peer. Parameters ---------- peer_id : ID The identifier of the peer. Returns ------- PublicKey The public key of the peer. Raises ------ PeerStoreError If the peer ID is not found. """ @abstractmethod def privkey(self, peer_id: ID) -> PrivateKey: """ Retrieve the private key for the specified peer. Parameters ---------- peer_id : ID The identifier of the peer. Returns ------- PrivateKey The private key of the peer. Raises ------ PeerStoreError If the peer ID is not found. """ @abstractmethod def add_pubkey(self, peer_id: ID, pubkey: PublicKey) -> None: """ Add a public key for the specified peer. Parameters ---------- peer_id : ID The identifier of the peer. pubkey : PublicKey The public key to add. Raises ------ PeerStoreError If the peer already has a public key set. """ @abstractmethod def add_privkey(self, peer_id: ID, privkey: PrivateKey) -> None: """ Add a private key for the specified peer. Parameters ---------- peer_id : ID The identifier of the peer. privkey : PrivateKey The private key to add. Raises ------ PeerStoreError If the peer already has a private key set. """ @abstractmethod def add_key_pair(self, peer_id: ID, key_pair: KeyPair) -> None: """ Add a key pair for the specified peer. Parameters ---------- peer_id : ID The identifier of the peer. key_pair : KeyPair The key pair to add. Raises ------ PeerStoreError If the peer already has a public or private key set. """ @abstractmethod def peer_with_keys(self) -> list[ID]: """Returns all the peer IDs stored in the AddrBook""" @abstractmethod def clear_keydata(self, peer_id: ID) -> None: """ Remove all stored keydata for the specified peer. Parameters ---------- peer_id : ID The peer identifier whose keys are to be removed. """ # -------METRICS--------- @abstractmethod def record_latency(self, peer_id: ID, RTT: float) -> None: """ Records a new round-trip time (RTT) latency value for the specified peer using Exponentially Weighted Moving Average (EWMA). Parameters ---------- peer_id : ID The identifier of the peer for which latency is being recorded. RTT : float The round-trip time latency value to record. """ @abstractmethod def latency_EWMA(self, peer_id: ID) -> float: """ Returns the current latency value for the specified peer using Exponentially Weighted Moving Average (EWMA). Parameters ---------- peer_id : ID The identifier of the peer whose latency EWMA is to be returned. """ @abstractmethod def clear_metrics(self, peer_id: ID) -> None: """ Clears the stored latency metrics for the specified peer. Parameters ---------- peer_id : ID The identifier of the peer whose latency metrics are to be cleared. """ # --------PROTO-BOOK---------- @abstractmethod def get_protocols(self, peer_id: ID) -> list[str]: """ Retrieve the protocols associated with the specified peer. Parameters ---------- peer_id : ID The identifier of the peer. Returns ------- list[str] A list of protocol identifiers. Raises ------ PeerStoreError If the peer ID is not found. """ @abstractmethod def add_protocols(self, peer_id: ID, protocols: Sequence[str]) -> None: """ Add additional protocols for the specified peer. Parameters ---------- peer_id : ID The identifier of the peer. protocols : Sequence[str] The protocols to add. """ @abstractmethod def set_protocols(self, peer_id: ID, protocols: Sequence[str]) -> None: """ Set the protocols for the specified peer. Parameters ---------- peer_id : ID The identifier of the peer. protocols : Sequence[str] The protocols to set. """ @abstractmethod def remove_protocols(self, peer_id: ID, protocols: Sequence[str]) -> None: """ Removes the specified protocols from the peer's protocol list. Parameters ---------- peer_id : ID The identifier of the peer from which protocols will be removed. protocols : Sequence[str] A sequence of protocol strings to remove. """ @abstractmethod def supports_protocols(self, peer_id: ID, protocols: Sequence[str]) -> list[str]: """ Returns the list of protocols from the input sequence that the peer supports. Parameters ---------- peer_id : ID The identifier of the peer to check for protocol support. protocols : Sequence[str] A sequence of protocol strings to check against the peer's supported protocols. """ @abstractmethod def first_supported_protocol(self, peer_id: ID, protocols: Sequence[str]) -> str: """ Returns the first protocol from the input list that the peer supports. Parameters ---------- peer_id : ID The identifier of the peer to check for supported protocols. protocols : Sequence[str] A sequence of protocol strings to check. Returns ------- str The first matching protocol string, or an empty string if none are supported. """ @abstractmethod def clear_protocol_data(self, peer_id: ID) -> None: """ Clears all protocol data associated with the specified peer. Parameters ---------- peer_id : ID The identifier of the peer whose protocol data will be cleared. """ # --------PEER-STORE-------- @abstractmethod def peer_info(self, peer_id: ID) -> PeerInfo: """ Retrieve the peer information for the specified peer. Parameters ---------- peer_id : ID The identifier of the peer. Returns ------- PeerInfo The peer information object for the given peer. """ @abstractmethod def peer_ids(self) -> list[ID]: """ Retrieve all peer identifiers stored in the peer store. Returns ------- list[ID] A list of all peer IDs in the store. """ @abstractmethod def clear_peerdata(self, peer_id: ID) -> None: """clear_peerdata""" @abstractmethod async def start_cleanup_task(self, cleanup_interval: int = 3600) -> None: """Start periodic cleanup of expired peer records and addresses.""" # -------------------------- listener interface.py -------------------------- class IListener(ABC): """ Interface for a network listener. Provides methods for starting a listener, retrieving its addresses, and closing it. """ @abstractmethod async def listen(self, maddr: Multiaddr, nursery: trio.Nursery) -> bool: """ Start listening on the specified multiaddress. Parameters ---------- maddr : Multiaddr The multiaddress on which to listen. nursery : trio.Nursery The nursery for spawning listening tasks. Returns ------- bool True if the listener started successfully, otherwise False. """ @abstractmethod def get_addrs(self) -> tuple[Multiaddr, ...]: """ Retrieve the list of addresses on which the listener is active. Returns ------- tuple[Multiaddr, ...] A tuple of multiaddresses. """ @abstractmethod async def close(self) -> None: """ Close the listener. """ ... # -------------------------- network interface.py -------------------------- class INetwork(ABC): """ Interface for the network. Provides methods for managing connections, streams, and listeners. Attributes ---------- peerstore : IPeerStore The peer store for managing peer information. connections : dict[ID, list[INetConn]] A mapping of peer IDs to lists of network connections (multiple connections per peer). listeners : dict[str, IListener] A mapping of listener identifiers to listener instances. """ peerstore: IPeerStore connections: dict[ID, list[INetConn]] listeners: dict[str, IListener] @abstractmethod def get_peer_id(self) -> ID: """ Retrieve the peer identifier for this network. Returns ------- ID The identifier of this peer. """ @abstractmethod def get_connections(self, peer_id: ID | None = None) -> list[INetConn]: """ Get connections for peer (like JS getConnections, Go ConnsToPeer). Parameters ---------- peer_id : ID | None The peer ID to get connections for. If None, returns all connections. Returns ------- list[INetConn] List of connections to the specified peer, or all connections if peer_id is None. """ @abstractmethod def get_connections_map(self) -> dict[ID, list[INetConn]]: """ Get all connections map (like JS getConnectionsMap). Returns ------- dict[ID, list[INetConn]] The complete mapping of peer IDs to their connection lists. """ @abstractmethod def get_connection(self, peer_id: ID) -> INetConn | None: """ Get single connection for backward compatibility. Parameters ---------- peer_id : ID The peer ID to get a connection for. Returns ------- INetConn | None The first available connection, or None if no connections exist. """ @abstractmethod async def dial_peer(self, peer_id: ID) -> list[INetConn]: """ Create connections to the specified peer with load balancing. Parameters ---------- peer_id : ID The identifier of the peer to dial. Returns ------- list[INetConn] List of established connections to the peer. Raises ------ SwarmException If an error occurs during dialing. """ @abstractmethod async def new_stream(self, peer_id: ID) -> INetStream: """ Create a new network stream to the specified peer. Parameters ---------- peer_id : ID The identifier of the destination peer. Returns ------- INetStream The newly created network stream. """ @abstractmethod def set_stream_handler(self, stream_handler: StreamHandlerFn) -> None: """ Set the stream handler for incoming streams. Parameters ---------- stream_handler : StreamHandlerFn The handler function to process incoming streams. """ @abstractmethod async def listen(self, *multiaddrs: Multiaddr) -> bool: """ Start listening on one or more multiaddresses. Parameters ---------- multiaddrs : Sequence[Multiaddr] One or more multiaddresses on which to start listening. Returns ------- bool True if at least one listener started successfully, otherwise False. """ @abstractmethod def register_notifee(self, notifee: "INotifee") -> None: """ Register a notifee instance to receive network events. Parameters ---------- notifee : INotifee An object implementing the INotifee interface. """ @abstractmethod async def close(self) -> None: """ Close the network and all associated connections and listeners. """ @abstractmethod async def close_peer(self, peer_id: ID) -> None: """ Close the connection to the specified peer. Parameters ---------- peer_id : ID The identifier of the peer whose connection should be closed. """ class INetworkService(INetwork, ServiceAPI): pass # -------------------------- notifee interface.py -------------------------- class INotifee(ABC): """ Interface for a network service. Extends the INetwork interface with additional service management capabilities. """ @abstractmethod async def opened_stream(self, network: "INetwork", stream: INetStream) -> None: """ Called when a new stream is opened. Parameters ---------- network : INetwork The network instance on which the stream was opened. stream : INetStream The stream that was opened. """ @abstractmethod async def closed_stream(self, network: "INetwork", stream: INetStream) -> None: """ Called when a stream is closed. Parameters ---------- network : INetwork The network instance on which the stream was closed. stream : INetStream The stream that was closed. """ @abstractmethod async def connected(self, network: "INetwork", conn: INetConn) -> None: """ Called when a new connection is established. Parameters ---------- network : INetwork The network instance where the connection was established. conn : INetConn The connection that was opened. """ @abstractmethod async def disconnected(self, network: "INetwork", conn: INetConn) -> None: """ Called when a connection is closed. Parameters ---------- network : INetwork The network instance where the connection was closed. conn : INetConn The connection that was closed. """ @abstractmethod async def listen(self, network: "INetwork", multiaddr: Multiaddr) -> None: """ Called when a listener starts on a multiaddress. Parameters ---------- network : INetwork The network instance where the listener is active. multiaddr : Multiaddr The multiaddress on which the listener is listening. """ @abstractmethod async def listen_close(self, network: "INetwork", multiaddr: Multiaddr) -> None: """ Called when a listener stops listening on a multiaddress. Parameters ---------- network : INetwork The network instance where the listener was active. multiaddr : Multiaddr The multiaddress that is no longer being listened on. """ # -------------------------- host interface.py -------------------------- class IHost(ABC): """ Interface for the host. Provides methods for retrieving host information, managing connections and streams, and running the host. """ @abstractmethod def get_id(self) -> ID: """ Retrieve the host's peer identifier. Returns ------- ID The host's peer identifier. """ @abstractmethod def get_public_key(self) -> PublicKey: """ Retrieve the public key of the host. Returns ------- PublicKey The public key belonging to the host. """ @abstractmethod def get_private_key(self) -> PrivateKey: """ Retrieve the private key of the host. Returns ------- PrivateKey The private key belonging to the host. """ @abstractmethod def get_network(self) -> INetworkService: """ Retrieve the network service instance associated with the host. Returns ------- INetworkService The network instance of the host. """ @abstractmethod def get_mux(self) -> "Multiselect": """ Retrieve the muxer instance for the host. Returns ------- Any The muxer instance of the host. """ @abstractmethod def get_addrs(self) -> list[Multiaddr]: """ Retrieve all multiaddresses on which the host is listening. Returns ------- list[Multiaddr] A list of multiaddresses. """ @abstractmethod def get_peerstore(self) -> IPeerStore: """ :return: the peerstore of the host """ @abstractmethod def get_connected_peers(self) -> list[ID]: """ Retrieve the identifiers of peers currently connected to the host. Returns ------- list[ID] A list of peer identifiers. """ @abstractmethod def get_live_peers(self) -> list[ID]: """ :return: List of peer IDs that have active connections """ @abstractmethod def run( self, listen_addrs: Sequence[Multiaddr] ) -> AbstractAsyncContextManager[None]: """ Run the host and start listening on the specified multiaddresses. Parameters ---------- listen_addrs : Sequence[Multiaddr] A sequence of multiaddresses on which the host should listen. """ @abstractmethod def set_stream_handler( self, protocol_id: TProtocol, stream_handler: StreamHandlerFn ) -> None: """ Set the stream handler for the specified protocol. Parameters ---------- protocol_id : TProtocol The protocol identifier used on the stream. stream_handler : StreamHandlerFn The stream handler function to be set. """ # protocol_id can be a list of protocol_ids # stream will decide which protocol_id to run on @abstractmethod async def new_stream( self, peer_id: ID, protocol_ids: Sequence[TProtocol] ) -> INetStream: """ Create a new stream to the specified peer. Parameters ---------- peer_id : ID The identifier of the peer to connect. protocol_ids : Sequence[TProtocol] A sequence of available protocol identifiers to use for the stream. Returns ------- INetStream The newly created network stream. """ @abstractmethod async def connect(self, peer_info: PeerInfo) -> None: """ Establish a connection to the specified peer. This method ensures there is a connection between the host and the peer represented by the provided peer information. It also absorbs the addresses from ``peer_info`` into the host's internal peerstore. If no active connection exists, the host will dial the peer and block until a connection is established or an error occurs. Parameters ---------- peer_info : PeerInfo The peer information of the peer to connect to. """ @abstractmethod async def disconnect(self, peer_id: ID) -> None: """ Disconnect from the specified peer. Parameters ---------- peer_id : ID The identifier of the peer to disconnect from. """ @abstractmethod async def close(self) -> None: """ Close the host and all underlying connections and services. """ # -------------------------- peer-record interface.py -------------------------- class IPeerRecord(ABC): """ Interface for a libp2p PeerRecord object. A PeerRecord contains metadata about a peer such as its ID, public addresses, and a strictly increasing sequence number for versioning. PeerRecords are used in signed routing Envelopes for secure peer data propagation. """ @abstractmethod def domain(self) -> str: """ Return the domain string for this record type. Used in envelope validation to distinguish different record types. """ @abstractmethod def codec(self) -> bytes: """ Return a binary codec prefix that identifies the PeerRecord type. This is prepended in signed envelopes to allow type-safe decoding. """ @abstractmethod def to_protobuf(self) -> pb.PeerRecord: """ Convert this PeerRecord into its Protobuf representation. :raises ValueError: if serialization fails (e.g., invalid peer ID). :return: A populated protobuf `PeerRecord` message. """ @abstractmethod def marshal_record(self) -> bytes: """ Serialize this PeerRecord into a byte string. Used when signing or sealing the record in an envelope. :raises ValueError: if protobuf serialization fails. :return: Byte-encoded PeerRecord. """ @abstractmethod def equal(self, other: object) -> bool: """ Compare this PeerRecord with another for equality. Two PeerRecords are considered equal if: - They have the same `peer_id` - Their `seq` numbers match - Their address lists are identical and ordered :param other: Object to compare with. :return: True if equal, False otherwise. """ # -------------------------- envelope interface.py -------------------------- class IEnvelope(ABC): @abstractmethod def marshal_envelope(self) -> bytes: """ Serialize this Envelope into its protobuf wire format. Converts all envelope fields into a `pb.Envelope` protobuf message and returns the serialized bytes. :return: Serialized envelope as bytes. """ @abstractmethod def validate(self, domain: str) -> None: """ Verify the envelope's signature within the given domain scope. This ensures that the envelope has not been tampered with and was signed under the correct usage context. :param domain: Domain string that contextualizes the signature. :raises ValueError: If the signature is invalid. """ @abstractmethod def record(self) -> "PeerRecord": """ Lazily decode and return the embedded PeerRecord. This method unmarshals the payload bytes into a `PeerRecord` instance, using the registered codec to identify the type. The decoded result is cached for future use. :return: Decoded PeerRecord object. :raises Exception: If decoding fails or payload type is unsupported. """ @abstractmethod def equal(self, other: Any) -> bool: """ Compare this Envelope with another for structural equality. Two envelopes are considered equal if: - They have the same public key - The payload type and payload bytes match - Their signatures are identical :param other: Another object to compare. :return: True if equal, False otherwise. """ # -------------------------- peerdata interface.py -------------------------- class IPeerData(ABC): """ Interface for managing peer data. Provides methods for handling protocols, addresses, metadata, and keys associated with a peer. """ @abstractmethod def get_protocols(self) -> list[str]: """ Retrieve all protocols associated with the peer. Returns ------- list[str] A list of protocols associated with the peer. """ @abstractmethod def add_protocols(self, protocols: Sequence[str]) -> None: """ Add one or more protocols to the peer's data. Parameters ---------- protocols : Sequence[str] A sequence of protocols to add. """ @abstractmethod def set_protocols(self, protocols: Sequence[str]) -> None: """ Set the protocols for the peer. Parameters ---------- protocols : Sequence[str] A sequence of protocols to set. """ @abstractmethod def remove_protocols(self, protocols: Sequence[str]) -> None: """ Removes the specified protocols from this peer's list of supported protocols. Parameters ---------- protocols : Sequence[str] A sequence of protocol strings to be removed. """ @abstractmethod def supports_protocols(self, protocols: Sequence[str]) -> list[str]: """ Returns the list of protocols from the input sequence that are supported by this peer. Parameters ---------- protocols : Sequence[str] A sequence of protocol strings to check against this peer's supported protocols. Returns ------- list[str] A list of protocol strings that are supported. """ @abstractmethod def first_supported_protocol(self, protocols: Sequence[str]) -> str: """ Returns the first protocol from the input list that this peer supports. Parameters ---------- protocols : Sequence[str] A sequence of protocol strings to check for support. Returns ------- str The first matching protocol, or an empty string if none are supported. """ @abstractmethod def clear_protocol_data(self) -> None: """ Clears all protocol data associated with this peer. """ @abstractmethod def add_addrs(self, addrs: Sequence[Multiaddr]) -> None: """ Add multiple multiaddresses to the peer's data. Parameters ---------- addrs : Sequence[Multiaddr] A sequence of multiaddresses to add. ttl: inr Time to live for the peer record """ @abstractmethod def get_addrs(self) -> list[Multiaddr]: """ Retrieve all multiaddresses associated with the peer. Returns ------- list[Multiaddr] A list of multiaddresses. """ @abstractmethod def clear_addrs(self) -> None: """ Clear all addresses associated with the peer. """ @abstractmethod def put_metadata(self, key: str, val: Any) -> None: """ Store a metadata key-value pair for the peer. Parameters ---------- key : str The metadata key. val : Any The value to associate with the key. """ @abstractmethod def get_metadata(self, key: str) -> IPeerMetadata: """ Retrieve metadata for a given key. Parameters ---------- key : str The metadata key. Returns ------- IPeerMetadata The metadata value for the given key. Raises ------ PeerDataError If the key is not found. """ @abstractmethod def clear_metadata(self) -> None: """ Clears all metadata entries associated with this peer. """ @abstractmethod def add_pubkey(self, pubkey: PublicKey) -> None: """ Add a public key to the peer's data. Parameters ---------- pubkey : PublicKey The public key to add. """ @abstractmethod def get_pubkey(self) -> PublicKey: """ Retrieve the public key for the peer. Returns ------- PublicKey The public key of the peer. Raises ------ PeerDataError If the public key is not found. """ @abstractmethod def add_privkey(self, privkey: PrivateKey) -> None: """ Add a private key to the peer's data. Parameters ---------- privkey : PrivateKey The private key to add. """ @abstractmethod def get_privkey(self) -> PrivateKey: """ Retrieve the private key for the peer. Returns ------- PrivateKey The private key of the peer. Raises ------ PeerDataError If the private key is not found. """ @abstractmethod def clear_keydata(self) -> None: """ Clears all cryptographic key data associated with this peer, including both public and private keys. """ @abstractmethod def record_latency(self, new_latency: float) -> None: """ Records a new latency measurement using Exponentially Weighted Moving Average (EWMA). Parameters ---------- new_latency : float The new round-trip time (RTT) latency value to incorporate into the EWMA calculation. """ @abstractmethod def latency_EWMA(self) -> float: """ Returns the current EWMA value of the recorded latency. Returns ------- float The current latency estimate based on EWMA. """ @abstractmethod def clear_metrics(self) -> None: """ Clears all latency-related metrics and resets the internal state. """ @abstractmethod def update_last_identified(self) -> None: """ Updates timestamp to current time. """ @abstractmethod def get_last_identified(self) -> int: """ Fetch the last identified timestamp Returns ------- last_identified_timestamp The lastIdentified time of peer. """ @abstractmethod def get_ttl(self) -> int: """ Get ttl value for the peer for validity check Returns ------- int The ttl of the peer. """ @abstractmethod def set_ttl(self, ttl: int) -> None: """ Set ttl value for the peer for validity check Parameters ---------- ttl : int The ttl for the peer. """ @abstractmethod def is_expired(self) -> bool: """ Check if the peer is expired based on last_identified and ttl Returns ------- bool True, if last_identified + ttl > current_time """ # ------------------ multiselect_communicator interface.py ------------------ class IMultiselectCommunicator(ABC): """ Communicator helper for multiselect. Ensures that both the client and multistream module follow the same multistream protocol. """ @abstractmethod async def write(self, msg_str: str) -> None: """ Write a message to the stream. Parameters ---------- msg_str : str The message string to write. """ @abstractmethod async def read(self) -> str: """ Read a message from the stream until EOF. Returns ------- str The message read from the stream. """ # -------------------------- multiselect_client interface.py -------------------------- class IMultiselectClient(ABC): """ Client for multiselect negotiation. Communicates with the receiver's multiselect module to select a protocol for communication. """ @abstractmethod async def handshake(self, communicator: IMultiselectCommunicator) -> None: """ Ensure that the client and multiselect module are using the same multiselect protocol. Parameters ---------- communicator : IMultiselectCommunicator The communicator used for negotiating the multiselect protocol. Raises ------ Exception If there is a multiselect protocol ID mismatch. """ @abstractmethod async def select_one_of( self, protocols: Sequence[TProtocol], communicator: IMultiselectCommunicator ) -> TProtocol: """ Select one protocol from a sequence by communicating with the multiselect module. For each protocol in the provided sequence, the client sends a selection message and expects the multiselect module to confirm the protocol. The first confirmed protocol is returned. Parameters ---------- protocols : Sequence[TProtocol] The protocols to attempt selection. communicator : IMultiselectCommunicator The communicator used for negotiating the protocol. Returns ------- TProtocol The protocol selected by the multiselect module. """ @abstractmethod async def try_select( self, communicator: IMultiselectCommunicator, protocol: TProtocol ) -> TProtocol: """ Attempt to select the given protocol. Parameters ---------- communicator : IMultiselectCommunicator The communicator used to interact with the counterparty. protocol : TProtocol The protocol to select. Returns ------- TProtocol The protocol if successfully selected. Raises ------ Exception If protocol selection fails. """ # -------------------------- multiselect_muxer interface.py -------------------------- class IMultiselectMuxer(ABC): """ Multiselect module for protocol negotiation. Responsible for responding to a multiselect client by selecting a protocol and its corresponding handler for communication. """ handlers: dict[TProtocol | None, StreamHandlerFn | None] @abstractmethod def add_handler(self, protocol: TProtocol, handler: StreamHandlerFn) -> None: """ Store a handler for the specified protocol. Parameters ---------- protocol : TProtocol The protocol name. handler : StreamHandlerFn The handler function associated with the protocol. """ @abstractmethod def get_protocols(self) -> tuple[TProtocol | None, ...]: """ Retrieve the protocols for which handlers have been registered. Returns ------- tuple[TProtocol, ...] A tuple of registered protocol names. """ @abstractmethod async def negotiate( self, communicator: IMultiselectCommunicator ) -> tuple[TProtocol | None, StreamHandlerFn | None]: """ Negotiate a protocol selection with a multiselect client. Parameters ---------- communicator : IMultiselectCommunicator The communicator used to negotiate the protocol. Returns ------- tuple[TProtocol, StreamHandlerFn] A tuple containing the selected protocol and its handler. Raises ------ Exception If negotiation fails. """ # -------------------------- routing interface.py -------------------------- class IContentRouting(ABC): """ Interface for content routing. Provides methods to advertise and search for content providers. """ @abstractmethod def provide(self, cid: bytes, announce: bool = True) -> None: """ Advertise that the host can provide content identified by the given CID. If ``announce`` is True, the content is announced; otherwise, it is only recorded locally. Parameters ---------- cid : bytes The content identifier. announce : bool, optional Whether to announce the provided content (default is True). """ @abstractmethod def find_provider_iter(self, cid: bytes, count: int) -> Iterable[PeerInfo]: """ Search for peers that can provide the content identified by the given CID. Parameters ---------- cid : bytes The content identifier. count : int The maximum number of providers to return. Returns ------- Iterable[PeerInfo] An iterator of PeerInfo objects for peers that provide the content. """ class IPeerRouting(ABC): """ Interface for peer routing. Provides methods to search for a specific peer. """ @abstractmethod async def find_peer(self, peer_id: ID) -> PeerInfo | None: """ Search for a peer with the specified peer ID. Parameters ---------- peer_id : ID The identifier of the peer to search for. Returns ------- PeerInfo The peer information containing relevant addresses. """ # -------------------------- security_transport interface.py -------------------------- class ISecureTransport(ABC): """ Interface for a security transport. Used to secure connections by performing handshakes and negotiating secure channels between peers. References ---------- https://github.com/libp2p/go-conn-security/blob/master/interface.go """ @abstractmethod async def secure_inbound(self, conn: IRawConnection) -> ISecureConn: """ Secure an inbound connection (when we are not the initiator). This method secures the connection by either performing local operations or communicating with the opposing node. Parameters ---------- conn : IRawConnection The raw connection to secure. Returns ------- ISecureConn The secured connection instance. """ @abstractmethod async def secure_outbound(self, conn: IRawConnection, peer_id: ID) -> ISecureConn: """ Secure an outbound connection (when we are the initiator). This method secures the connection by either performing local operations or communicating with the opposing node. Parameters ---------- conn : IRawConnection The raw connection to secure. peer_id : ID The identifier of the remote peer. Returns ------- ISecureConn The secured connection instance. """ # -------------------------- transport interface.py -------------------------- class ITransport(ABC): """ Interface for a transport. Provides methods for dialing peers and creating listeners on a transport. """ @abstractmethod async def dial(self, maddr: Multiaddr) -> IRawConnection: """ Dial a peer on the specified multiaddress. Parameters ---------- maddr : Multiaddr The multiaddress of the peer to dial. Returns ------- IRawConnection The raw connection established to the peer. """ @abstractmethod def create_listener(self, handler_function: THandler) -> IListener: """ Create a listener on the transport. Parameters ---------- handler_function : THandler A function that is called when a new connection is received. The function should accept a connection (that implements the connection interface) as its argument. Returns ------- IListener A listener instance. """ # -------------------------- pubsub abc.py -------------------------- class ISubscriptionAPI( AsyncContextManager["ISubscriptionAPI"], AsyncIterable[rpc_pb2.Message] ): """ Interface for a subscription in pubsub. Combines asynchronous context management and iteration over messages. """ @abstractmethod async def unsubscribe(self) -> None: """ Unsubscribe from the current topic. """ ... @abstractmethod async def get(self) -> rpc_pb2.Message: """ Retrieve the next message from the subscription. Returns ------- rpc_pb2.Message The next pubsub message. """ ... class IPubsubRouter(ABC): """ Interface for a pubsub router. Provides methods to manage protocol support, peer attachments, and message handling for pubsub. """ mesh: dict[str, set[ID]] fanout: dict[str, set[ID]] peer_protocol: dict[ID, TProtocol] degree: int @abstractmethod def get_protocols(self) -> list[TProtocol]: """ Retrieve the list of protocols supported by the router. Returns ------- list[TProtocol] A list of supported protocol identifiers. """ @abstractmethod def attach(self, pubsub: "Pubsub") -> None: """ Attach the router to a newly initialized PubSub instance. Parameters ---------- pubsub : Pubsub The PubSub instance to attach to. """ @abstractmethod def add_peer(self, peer_id: ID, protocol_id: TProtocol | None) -> None: """ Notify the router that a new peer has connected. Parameters ---------- peer_id : ID The identifier of the peer. protocol_id : TProtocol The protocol the peer supports (e.g., floodsub, gossipsub). """ @abstractmethod def remove_peer(self, peer_id: ID) -> None: """ Notify the router that a peer has disconnected. Parameters ---------- peer_id : ID The identifier of the peer to remove. """ @abstractmethod async def handle_rpc(self, rpc: rpc_pb2.RPC, sender_peer_id: ID) -> None: """ Process an RPC message received from a peer. Parameters ---------- rpc : rpc_pb2.RPC The RPC message to process. sender_peer_id : ID The identifier of the peer that sent the message. """ @abstractmethod async def publish(self, msg_forwarder: ID, pubsub_msg: rpc_pb2.Message) -> None: """ Forward a validated pubsub message. Parameters ---------- msg_forwarder : ID The identifier of the message sender. pubsub_msg : rpc_pb2.Message The pubsub message to forward. """ @abstractmethod async def join(self, topic: str) -> None: """ Join a topic to receive and forward messages. Parameters ---------- topic : str The topic to join. """ @abstractmethod async def leave(self, topic: str) -> None: """ Leave a topic, stopping message forwarding for that topic. Parameters ---------- topic : str The topic to leave. """ class IPubsub(ServiceAPI): """ Interface for the pubsub system. Provides properties and methods to manage topics, subscriptions, and message publishing. """ @property @abstractmethod def my_id(self) -> ID: """ Retrieve the identifier for this pubsub instance. Returns ------- ID The pubsub identifier. """ ... @property @abstractmethod def protocols(self) -> tuple[TProtocol, ...]: """ Retrieve the protocols used by the pubsub system. Returns ------- tuple[TProtocol, ...] A tuple of protocol identifiers. """ ... @property @abstractmethod def topic_ids(self) -> KeysView[str]: """ Retrieve the set of topic identifiers. Returns ------- KeysView[str] A view of the topic identifiers. """ ... @abstractmethod def set_topic_validator( self, topic: str, validator: ValidatorFn, is_async_validator: bool ) -> None: """ Set a validator for a specific topic. Parameters ---------- topic : str The topic for which to set the validator. validator : ValidatorFn The validator function. is_async_validator : bool Whether the validator is asynchronous. """ ... @abstractmethod def remove_topic_validator(self, topic: str) -> None: """ Remove the validator for a specific topic. Parameters ---------- topic : str The topic whose validator should be removed. """ ... @abstractmethod async def wait_until_ready(self) -> None: """ Wait until the pubsub system is fully initialized and ready. """ ... @abstractmethod async def subscribe(self, topic_id: str) -> ISubscriptionAPI: """ Subscribe to a topic. Parameters ---------- topic_id : str The identifier of the topic to subscribe to. Returns ------- ISubscriptionAPI An object representing the subscription. """ ... @abstractmethod async def unsubscribe(self, topic_id: str) -> None: """ Unsubscribe from a topic. Parameters ---------- topic_id : str The identifier of the topic to unsubscribe from. """ ... @abstractmethod async def publish(self, topic_id: str | list[str], data: bytes) -> None: """ Publish a message to a topic or multiple topics. Parameters ---------- topic_id : str | list[str] The identifier of the topic (str) or topics (list[str]). data : bytes The data to publish. """ ...