From 53db128f6984d6d4f38dd8a9195b66a475f9b9f8 Mon Sep 17 00:00:00 2001 From: lla-dane Date: Tue, 12 Aug 2025 13:57:16 +0530 Subject: [PATCH 01/21] fix typos --- libp2p/identity/identify/identify.py | 13 +- libp2p/kad_dht/kad_dht.py | 223 +++++++++++++++++++ libp2p/kad_dht/pb/kademlia.proto | 3 + libp2p/kad_dht/pb/kademlia_pb2.py | 31 +-- libp2p/kad_dht/pb/kademlia_pb2.pyi | 197 ++++++---------- libp2p/kad_dht/peer_routing.py | 91 ++++++++ libp2p/kad_dht/provider_store.py | 89 +++++++- libp2p/kad_dht/value_store.py | 60 ++++- libp2p/peer/peerstore.py | 14 +- tests/core/kad_dht/test_unit_peer_routing.py | 5 +- 10 files changed, 569 insertions(+), 157 deletions(-) diff --git a/libp2p/identity/identify/identify.py b/libp2p/identity/identify/identify.py index b2811ff9..4e8931ba 100644 --- a/libp2p/identity/identify/identify.py +++ b/libp2p/identity/identify/identify.py @@ -15,8 +15,7 @@ from libp2p.custom_types import ( from libp2p.network.stream.exceptions import ( StreamClosed, ) -from libp2p.peer.envelope import seal_record -from libp2p.peer.peer_record import PeerRecord +from libp2p.peer.peerstore import create_signed_peer_record from libp2p.utils import ( decode_varint_with_size, get_agent_version, @@ -66,9 +65,11 @@ def _mk_identify_protobuf( protocols = tuple(str(p) for p in host.get_mux().get_protocols() if p is not None) # Create a signed peer-record for the remote peer - record = PeerRecord(host.get_id(), host.get_addrs()) - envelope = seal_record(record, host.get_private_key()) - protobuf = envelope.marshal_envelope() + envelope = create_signed_peer_record( + host.get_id(), + host.get_addrs(), + host.get_private_key(), + ) observed_addr = observed_multiaddr.to_bytes() if observed_multiaddr else b"" return Identify( @@ -78,7 +79,7 @@ def _mk_identify_protobuf( listen_addrs=map(_multiaddr_to_bytes, laddrs), observed_addr=observed_addr, protocols=protocols, - signedPeerRecord=protobuf, + signedPeerRecord=envelope.marshal_envelope(), ) diff --git a/libp2p/kad_dht/kad_dht.py b/libp2p/kad_dht/kad_dht.py index 097b6c48..2fb42662 100644 --- a/libp2p/kad_dht/kad_dht.py +++ b/libp2p/kad_dht/kad_dht.py @@ -25,12 +25,14 @@ from libp2p.discovery.random_walk.rt_refresh_manager import RTRefreshManager from libp2p.network.stream.net_stream import ( INetStream, ) +from libp2p.peer.envelope import Envelope, consume_envelope from libp2p.peer.id import ( ID, ) from libp2p.peer.peerinfo import ( PeerInfo, ) +from libp2p.peer.peerstore import create_signed_peer_record from libp2p.tools.async_service import ( Service, ) @@ -234,6 +236,9 @@ class KadDHT(Service): await self.add_peer(peer_id) logger.debug(f"Added peer {peer_id} to routing table") + closer_peer_envelope: Envelope | None = None + provider_peer_envelope: Envelope | None = None + try: # Read varint-prefixed length for the message length_prefix = b"" @@ -266,6 +271,7 @@ class KadDHT(Service): # Handle FIND_NODE message if message.type == Message.MessageType.FIND_NODE: # Get target key directly from protobuf + print("FIND NODE RECEIVED") target_key = message.key # Find closest peers to the target key @@ -274,6 +280,26 @@ class KadDHT(Service): ) logger.debug(f"Found {len(closest_peers)} peers close to target") + # Consume the source signed_peer_record if sent + if message.HasField("senderRecord"): + try: + # Convert the signed-peer-record(Envelope) from + # protobuf bytes + envelope, _ = consume_envelope( + message.senderRecord, "libp2p-peer-record" + ) + # Use the defualt TTL of 2 hours (7200 seconds) + if not self.host.get_peerstore().consume_peer_record( + envelope, 7200 + ): + logger.error( + "Updating the Certified-Addr-Book was unsuccessful" + ) + except Exception as e: + logger.error( + "Error updating the certified addr book for peer: %s", e + ) + # Build response message with protobuf response = Message() response.type = Message.MessageType.FIND_NODE @@ -298,6 +324,25 @@ class KadDHT(Service): except Exception: pass + # Add the signed-peer-record for each peer in the peer-proto + # if cached in the peerstore + closer_peer_envelope = ( + self.host.get_peerstore().get_peer_record(peer) + ) + + if closer_peer_envelope is not None: + peer_proto.signedRecord = ( + closer_peer_envelope.marshal_envelope() + ) + + # Create sender_signed_peer_record + envelope = create_signed_peer_record( + self.host.get_id(), + self.host.get_addrs(), + self.host.get_private_key(), + ) + response.senderRecord = envelope.marshal_envelope() + # Serialize and send response response_bytes = response.SerializeToString() await stream.write(varint.encode(len(response_bytes))) @@ -312,6 +357,26 @@ class KadDHT(Service): key = message.key logger.debug(f"Received ADD_PROVIDER for key {key.hex()}") + # Consume the source signed-peer-record if sent + if message.HasField("senderRecord"): + try: + # Convert the signed-peer-record(Envelope) from + # protobuf bytes + envelope, _ = consume_envelope( + message.senderRecord, "libp2p-peer-record" + ) + # Use the default TTL of 2 hours (72000 seconds) + if not self.host.get_peerstore().consume_peer_record( + envelope, 7200 + ): + logger.error( + "Updating the Certified-Addr-Book was unsuccessful" + ) + except Exception as e: + logger.error( + "Error updating the certified addr book for peer: %s", e + ) + # Extract provider information for provider_proto in message.providerPeers: try: @@ -341,11 +406,42 @@ class KadDHT(Service): except Exception as e: logger.warning(f"Failed to process provider info: {e}") + # Process the signed-records of provider if sent + if provider_proto.HasField("signedRecord"): + try: + # Convert the signed-peer-record(Envelope) from + # protobuf bytes + envelope, _ = consume_envelope( + provider_proto.signedRecord, + "libp2p-peer-record", + ) + # Use the default TTL of 2 hours (7200 seconds) + if not self.host.get_peerstore().consume_peer_record( # noqa + envelope, 7200 + ): + logger.error( + "Failed to update the Certified-Addr-Book" + ) + except Exception as e: + logger.error( + "Error updating the certified-addr-book for peer %s: %s", # noqa + provider_id, + e, + ) + # Send acknowledgement response = Message() response.type = Message.MessageType.ADD_PROVIDER response.key = key + # Add sender's signed-peer-record + envelope = create_signed_peer_record( + self.host.get_id(), + self.host.get_addrs(), + self.host.get_private_key(), + ) + response.senderRecord = envelope.marshal_envelope() + response_bytes = response.SerializeToString() await stream.write(varint.encode(len(response_bytes))) await stream.write(response_bytes) @@ -357,6 +453,26 @@ class KadDHT(Service): key = message.key logger.debug(f"Received GET_PROVIDERS request for key {key.hex()}") + # Consume the source signed_peer_record if sent + if message.HasField("senderRecord"): + try: + # Convert the signed-peer-record(Envelope) from + # protobuf bytes + envelope, _ = consume_envelope( + message.senderRecord, "libp2p-peer-record" + ) + # Use the defualt TTL of 2 hours (7200 seconds) + if not self.host.get_peerstore().consume_peer_record( + envelope, 7200 + ): + logger.error( + "Updating the Certified-Addr-Book was unsuccessful" + ) + except Exception as e: + logger.error( + "Error updating the certified addr book for peer: %s", e + ) + # Find providers for the key providers = self.provider_store.get_providers(key) logger.debug( @@ -368,12 +484,32 @@ class KadDHT(Service): response.type = Message.MessageType.GET_PROVIDERS response.key = key + # Create sender_signed_peer_record for the response + envelope = create_signed_peer_record( + self.host.get_id(), + self.host.get_addrs(), + self.host.get_private_key(), + ) + response.senderRecord = envelope.marshal_envelope() + # Add provider information to response for provider_info in providers: provider_proto = response.providerPeers.add() provider_proto.id = provider_info.peer_id.to_bytes() provider_proto.connection = Message.ConnectionType.CAN_CONNECT + # Add provider signed-records if cached + provider_peer_envelope = ( + self.host.get_peerstore().get_peer_record( + provider_info.peer_id + ) + ) + + if provider_peer_envelope is not None: + provider_proto.signedRecord = ( + provider_peer_envelope.marshal_envelope() + ) + # Add addresses if available for addr in provider_info.addrs: provider_proto.addrs.append(addr.to_bytes()) @@ -397,6 +533,16 @@ class KadDHT(Service): peer_proto.id = peer.to_bytes() peer_proto.connection = Message.ConnectionType.CAN_CONNECT + # Add the signed-records of closest_peers if cached + closer_peer_envelope = ( + self.host.get_peerstore().get_peer_record(peer) + ) + + if closer_peer_envelope is not None: + peer_proto.signedRecord = ( + closer_peer_envelope.marshal_envelope() + ) + # Add addresses if available try: addrs = self.host.get_peerstore().addrs(peer) @@ -417,6 +563,26 @@ class KadDHT(Service): key = message.key logger.debug(f"Received GET_VALUE request for key {key.hex()}") + # Consume the sender_signed_peer_record + if message.HasField("senderRecord"): + try: + # Convert the signed-peer-record(Envelope) from + # protobuf bytes + envelope, _ = consume_envelope( + message.senderRecord, "libp2p-peer-record" + ) + # Use the default TTL of 2 hours (7200 seconds) + if not self.host.get_peerstore().consume_peer_record( + envelope, 7200 + ): + logger.error( + "Updating teh Certified-Addr-Book was unsuccessful" + ) + except Exception as e: + logger.error( + "Error updating the certified addr book for peer: %s", e + ) + value = self.value_store.get(key) if value: logger.debug(f"Found value for key {key.hex()}") @@ -431,6 +597,14 @@ class KadDHT(Service): response.record.value = value response.record.timeReceived = str(time.time()) + # Create sender_signed_peer_record + envelope = create_signed_peer_record( + self.host.get_id(), + self.host.get_addrs(), + self.host.get_private_key(), + ) + response.senderRecord = envelope.marshal_envelope() + # Serialize and send response response_bytes = response.SerializeToString() await stream.write(varint.encode(len(response_bytes))) @@ -444,6 +618,14 @@ class KadDHT(Service): response.type = Message.MessageType.GET_VALUE response.key = key + # Create sender_signed_peer_record for the response + envelope = create_signed_peer_record( + self.host.get_id(), + self.host.get_addrs(), + self.host.get_private_key(), + ) + response.senderRecord = envelope.marshal_envelope() + # Add closest peers to key closest_peers = self.routing_table.find_local_closest_peers( key, 20 @@ -462,6 +644,16 @@ class KadDHT(Service): peer_proto.id = peer.to_bytes() peer_proto.connection = Message.ConnectionType.CAN_CONNECT + # Add signed-records of closer-peers if cached + closer_peer_envelope = ( + self.host.get_peerstore().get_peer_record(peer) + ) + + if closer_peer_envelope is not None: + peer_proto.signedRecord = ( + closer_peer_envelope.marshal_envelope() + ) + # Add addresses if available try: addrs = self.host.get_peerstore().addrs(peer) @@ -484,6 +676,27 @@ class KadDHT(Service): key = message.record.key value = message.record.value success = False + + # Consume the source signed_peer_record if sent + if message.HasField("senderRecord"): + try: + # Convert the signed-peer-record(Envelope) from + # protobuf bytes + envelope, _ = consume_envelope( + message.senderRecord, "libp2p-peer-record" + ) + # Use the default TTL of 2 hours (7200 seconds) + if not self.host.get_peerstore().consume_peer_record( + envelope, 7200 + ): + logger.error( + "Updating the certified-addr-book was unsuccessful" + ) + except Exception as e: + logger.error( + "Error updating the certified addr book for peer: %s", e + ) + try: if not (key and value): raise ValueError( @@ -504,6 +717,16 @@ class KadDHT(Service): response.type = Message.MessageType.PUT_VALUE if success: response.key = key + + # Create sender_signed_peer_record for the response + envelope = create_signed_peer_record( + self.host.get_id(), + self.host.get_addrs(), + self.host.get_private_key(), + ) + response.senderRecord = envelope.marshal_envelope() + + # Serialize and send response response_bytes = response.SerializeToString() await stream.write(varint.encode(len(response_bytes))) await stream.write(response_bytes) diff --git a/libp2p/kad_dht/pb/kademlia.proto b/libp2p/kad_dht/pb/kademlia.proto index fd198d28..7c3e5bad 100644 --- a/libp2p/kad_dht/pb/kademlia.proto +++ b/libp2p/kad_dht/pb/kademlia.proto @@ -27,6 +27,7 @@ message Message { bytes id = 1; repeated bytes addrs = 2; ConnectionType connection = 3; + optional bytes signedRecord = 4; // Envelope(PeerRecord) encoded } MessageType type = 1; @@ -35,4 +36,6 @@ message Message { Record record = 3; repeated Peer closerPeers = 8; repeated Peer providerPeers = 9; + + optional bytes senderRecord = 11; // Envelope(PeerRecord) encoded } diff --git a/libp2p/kad_dht/pb/kademlia_pb2.py b/libp2p/kad_dht/pb/kademlia_pb2.py index 781333bf..ac23169c 100644 --- a/libp2p/kad_dht/pb/kademlia_pb2.py +++ b/libp2p/kad_dht/pb/kademlia_pb2.py @@ -1,11 +1,12 @@ # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! # source: libp2p/kad_dht/pb/kademlia.proto +# Protobuf Python Version: 4.25.3 """Generated protocol buffer code.""" -from google.protobuf.internal import builder as _builder from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() @@ -13,21 +14,21 @@ _sym_db = _symbol_database.Default() -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n libp2p/kad_dht/pb/kademlia.proto\":\n\x06Record\x12\x0b\n\x03key\x18\x01 \x01(\x0c\x12\r\n\x05value\x18\x02 \x01(\x0c\x12\x14\n\x0ctimeReceived\x18\x05 \x01(\t\"\xca\x03\n\x07Message\x12\"\n\x04type\x18\x01 \x01(\x0e\x32\x14.Message.MessageType\x12\x17\n\x0f\x63lusterLevelRaw\x18\n \x01(\x05\x12\x0b\n\x03key\x18\x02 \x01(\x0c\x12\x17\n\x06record\x18\x03 \x01(\x0b\x32\x07.Record\x12\"\n\x0b\x63loserPeers\x18\x08 \x03(\x0b\x32\r.Message.Peer\x12$\n\rproviderPeers\x18\t \x03(\x0b\x32\r.Message.Peer\x1aN\n\x04Peer\x12\n\n\x02id\x18\x01 \x01(\x0c\x12\r\n\x05\x61\x64\x64rs\x18\x02 \x03(\x0c\x12+\n\nconnection\x18\x03 \x01(\x0e\x32\x17.Message.ConnectionType\"i\n\x0bMessageType\x12\r\n\tPUT_VALUE\x10\x00\x12\r\n\tGET_VALUE\x10\x01\x12\x10\n\x0c\x41\x44\x44_PROVIDER\x10\x02\x12\x11\n\rGET_PROVIDERS\x10\x03\x12\r\n\tFIND_NODE\x10\x04\x12\x08\n\x04PING\x10\x05\"W\n\x0e\x43onnectionType\x12\x11\n\rNOT_CONNECTED\x10\x00\x12\r\n\tCONNECTED\x10\x01\x12\x0f\n\x0b\x43\x41N_CONNECT\x10\x02\x12\x12\n\x0e\x43\x41NNOT_CONNECT\x10\x03\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n libp2p/kad_dht/pb/kademlia.proto\":\n\x06Record\x12\x0b\n\x03key\x18\x01 \x01(\x0c\x12\r\n\x05value\x18\x02 \x01(\x0c\x12\x14\n\x0ctimeReceived\x18\x05 \x01(\t\"\xa2\x04\n\x07Message\x12\"\n\x04type\x18\x01 \x01(\x0e\x32\x14.Message.MessageType\x12\x17\n\x0f\x63lusterLevelRaw\x18\n \x01(\x05\x12\x0b\n\x03key\x18\x02 \x01(\x0c\x12\x17\n\x06record\x18\x03 \x01(\x0b\x32\x07.Record\x12\"\n\x0b\x63loserPeers\x18\x08 \x03(\x0b\x32\r.Message.Peer\x12$\n\rproviderPeers\x18\t \x03(\x0b\x32\r.Message.Peer\x12\x19\n\x0csenderRecord\x18\x0b \x01(\x0cH\x00\x88\x01\x01\x1az\n\x04Peer\x12\n\n\x02id\x18\x01 \x01(\x0c\x12\r\n\x05\x61\x64\x64rs\x18\x02 \x03(\x0c\x12+\n\nconnection\x18\x03 \x01(\x0e\x32\x17.Message.ConnectionType\x12\x19\n\x0csignedRecord\x18\x04 \x01(\x0cH\x00\x88\x01\x01\x42\x0f\n\r_signedRecord\"i\n\x0bMessageType\x12\r\n\tPUT_VALUE\x10\x00\x12\r\n\tGET_VALUE\x10\x01\x12\x10\n\x0c\x41\x44\x44_PROVIDER\x10\x02\x12\x11\n\rGET_PROVIDERS\x10\x03\x12\r\n\tFIND_NODE\x10\x04\x12\x08\n\x04PING\x10\x05\"W\n\x0e\x43onnectionType\x12\x11\n\rNOT_CONNECTED\x10\x00\x12\r\n\tCONNECTED\x10\x01\x12\x0f\n\x0b\x43\x41N_CONNECT\x10\x02\x12\x12\n\x0e\x43\x41NNOT_CONNECT\x10\x03\x42\x0f\n\r_senderRecordb\x06proto3') -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'libp2p.kad_dht.pb.kademlia_pb2', globals()) +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'libp2p.kad_dht.pb.kademlia_pb2', _globals) if _descriptor._USE_C_DESCRIPTORS == False: - DESCRIPTOR._options = None - _RECORD._serialized_start=36 - _RECORD._serialized_end=94 - _MESSAGE._serialized_start=97 - _MESSAGE._serialized_end=555 - _MESSAGE_PEER._serialized_start=281 - _MESSAGE_PEER._serialized_end=359 - _MESSAGE_MESSAGETYPE._serialized_start=361 - _MESSAGE_MESSAGETYPE._serialized_end=466 - _MESSAGE_CONNECTIONTYPE._serialized_start=468 - _MESSAGE_CONNECTIONTYPE._serialized_end=555 + _globals['_RECORD']._serialized_start=36 + _globals['_RECORD']._serialized_end=94 + _globals['_MESSAGE']._serialized_start=97 + _globals['_MESSAGE']._serialized_end=643 + _globals['_MESSAGE_PEER']._serialized_start=308 + _globals['_MESSAGE_PEER']._serialized_end=430 + _globals['_MESSAGE_MESSAGETYPE']._serialized_start=432 + _globals['_MESSAGE_MESSAGETYPE']._serialized_end=537 + _globals['_MESSAGE_CONNECTIONTYPE']._serialized_start=539 + _globals['_MESSAGE_CONNECTIONTYPE']._serialized_end=626 # @@protoc_insertion_point(module_scope) diff --git a/libp2p/kad_dht/pb/kademlia_pb2.pyi b/libp2p/kad_dht/pb/kademlia_pb2.pyi index c8f16db2..6d80d77d 100644 --- a/libp2p/kad_dht/pb/kademlia_pb2.pyi +++ b/libp2p/kad_dht/pb/kademlia_pb2.pyi @@ -1,133 +1,70 @@ -""" -@generated by mypy-protobuf. Do not edit manually! -isort:skip_file -""" +from google.protobuf.internal import containers as _containers +from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union -import builtins -import collections.abc -import google.protobuf.descriptor -import google.protobuf.internal.containers -import google.protobuf.internal.enum_type_wrapper -import google.protobuf.message -import sys -import typing +DESCRIPTOR: _descriptor.FileDescriptor -if sys.version_info >= (3, 10): - import typing as typing_extensions -else: - import typing_extensions +class Record(_message.Message): + __slots__ = ("key", "value", "timeReceived") + KEY_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + TIMERECEIVED_FIELD_NUMBER: _ClassVar[int] + key: bytes + value: bytes + timeReceived: str + def __init__(self, key: _Optional[bytes] = ..., value: _Optional[bytes] = ..., timeReceived: _Optional[str] = ...) -> None: ... -DESCRIPTOR: google.protobuf.descriptor.FileDescriptor - -@typing.final -class Record(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - KEY_FIELD_NUMBER: builtins.int - VALUE_FIELD_NUMBER: builtins.int - TIMERECEIVED_FIELD_NUMBER: builtins.int - key: builtins.bytes - value: builtins.bytes - timeReceived: builtins.str - def __init__( - self, - *, - key: builtins.bytes = ..., - value: builtins.bytes = ..., - timeReceived: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing.Literal["key", b"key", "timeReceived", b"timeReceived", "value", b"value"]) -> None: ... - -global___Record = Record - -@typing.final -class Message(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - class _MessageType: - ValueType = typing.NewType("ValueType", builtins.int) - V: typing_extensions.TypeAlias = ValueType - - class _MessageTypeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[Message._MessageType.ValueType], builtins.type): - DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor - PUT_VALUE: Message._MessageType.ValueType # 0 - GET_VALUE: Message._MessageType.ValueType # 1 - ADD_PROVIDER: Message._MessageType.ValueType # 2 - GET_PROVIDERS: Message._MessageType.ValueType # 3 - FIND_NODE: Message._MessageType.ValueType # 4 - PING: Message._MessageType.ValueType # 5 - - class MessageType(_MessageType, metaclass=_MessageTypeEnumTypeWrapper): ... - PUT_VALUE: Message.MessageType.ValueType # 0 - GET_VALUE: Message.MessageType.ValueType # 1 - ADD_PROVIDER: Message.MessageType.ValueType # 2 - GET_PROVIDERS: Message.MessageType.ValueType # 3 - FIND_NODE: Message.MessageType.ValueType # 4 - PING: Message.MessageType.ValueType # 5 - - class _ConnectionType: - ValueType = typing.NewType("ValueType", builtins.int) - V: typing_extensions.TypeAlias = ValueType - - class _ConnectionTypeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[Message._ConnectionType.ValueType], builtins.type): - DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor - NOT_CONNECTED: Message._ConnectionType.ValueType # 0 - CONNECTED: Message._ConnectionType.ValueType # 1 - CAN_CONNECT: Message._ConnectionType.ValueType # 2 - CANNOT_CONNECT: Message._ConnectionType.ValueType # 3 - - class ConnectionType(_ConnectionType, metaclass=_ConnectionTypeEnumTypeWrapper): ... - NOT_CONNECTED: Message.ConnectionType.ValueType # 0 - CONNECTED: Message.ConnectionType.ValueType # 1 - CAN_CONNECT: Message.ConnectionType.ValueType # 2 - CANNOT_CONNECT: Message.ConnectionType.ValueType # 3 - - @typing.final - class Peer(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - ID_FIELD_NUMBER: builtins.int - ADDRS_FIELD_NUMBER: builtins.int - CONNECTION_FIELD_NUMBER: builtins.int - id: builtins.bytes - connection: global___Message.ConnectionType.ValueType - @property - def addrs(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.bytes]: ... - def __init__( - self, - *, - id: builtins.bytes = ..., - addrs: collections.abc.Iterable[builtins.bytes] | None = ..., - connection: global___Message.ConnectionType.ValueType = ..., - ) -> None: ... - def ClearField(self, field_name: typing.Literal["addrs", b"addrs", "connection", b"connection", "id", b"id"]) -> None: ... - - TYPE_FIELD_NUMBER: builtins.int - CLUSTERLEVELRAW_FIELD_NUMBER: builtins.int - KEY_FIELD_NUMBER: builtins.int - RECORD_FIELD_NUMBER: builtins.int - CLOSERPEERS_FIELD_NUMBER: builtins.int - PROVIDERPEERS_FIELD_NUMBER: builtins.int - type: global___Message.MessageType.ValueType - clusterLevelRaw: builtins.int - key: builtins.bytes - @property - def record(self) -> global___Record: ... - @property - def closerPeers(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___Message.Peer]: ... - @property - def providerPeers(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___Message.Peer]: ... - def __init__( - self, - *, - type: global___Message.MessageType.ValueType = ..., - clusterLevelRaw: builtins.int = ..., - key: builtins.bytes = ..., - record: global___Record | None = ..., - closerPeers: collections.abc.Iterable[global___Message.Peer] | None = ..., - providerPeers: collections.abc.Iterable[global___Message.Peer] | None = ..., - ) -> None: ... - def HasField(self, field_name: typing.Literal["record", b"record"]) -> builtins.bool: ... - def ClearField(self, field_name: typing.Literal["closerPeers", b"closerPeers", "clusterLevelRaw", b"clusterLevelRaw", "key", b"key", "providerPeers", b"providerPeers", "record", b"record", "type", b"type"]) -> None: ... - -global___Message = Message +class Message(_message.Message): + __slots__ = ("type", "clusterLevelRaw", "key", "record", "closerPeers", "providerPeers", "senderRecord") + class MessageType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + PUT_VALUE: _ClassVar[Message.MessageType] + GET_VALUE: _ClassVar[Message.MessageType] + ADD_PROVIDER: _ClassVar[Message.MessageType] + GET_PROVIDERS: _ClassVar[Message.MessageType] + FIND_NODE: _ClassVar[Message.MessageType] + PING: _ClassVar[Message.MessageType] + PUT_VALUE: Message.MessageType + GET_VALUE: Message.MessageType + ADD_PROVIDER: Message.MessageType + GET_PROVIDERS: Message.MessageType + FIND_NODE: Message.MessageType + PING: Message.MessageType + class ConnectionType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + NOT_CONNECTED: _ClassVar[Message.ConnectionType] + CONNECTED: _ClassVar[Message.ConnectionType] + CAN_CONNECT: _ClassVar[Message.ConnectionType] + CANNOT_CONNECT: _ClassVar[Message.ConnectionType] + NOT_CONNECTED: Message.ConnectionType + CONNECTED: Message.ConnectionType + CAN_CONNECT: Message.ConnectionType + CANNOT_CONNECT: Message.ConnectionType + class Peer(_message.Message): + __slots__ = ("id", "addrs", "connection", "signedRecord") + ID_FIELD_NUMBER: _ClassVar[int] + ADDRS_FIELD_NUMBER: _ClassVar[int] + CONNECTION_FIELD_NUMBER: _ClassVar[int] + SIGNEDRECORD_FIELD_NUMBER: _ClassVar[int] + id: bytes + addrs: _containers.RepeatedScalarFieldContainer[bytes] + connection: Message.ConnectionType + signedRecord: bytes + def __init__(self, id: _Optional[bytes] = ..., addrs: _Optional[_Iterable[bytes]] = ..., connection: _Optional[_Union[Message.ConnectionType, str]] = ..., signedRecord: _Optional[bytes] = ...) -> None: ... + TYPE_FIELD_NUMBER: _ClassVar[int] + CLUSTERLEVELRAW_FIELD_NUMBER: _ClassVar[int] + KEY_FIELD_NUMBER: _ClassVar[int] + RECORD_FIELD_NUMBER: _ClassVar[int] + CLOSERPEERS_FIELD_NUMBER: _ClassVar[int] + PROVIDERPEERS_FIELD_NUMBER: _ClassVar[int] + SENDERRECORD_FIELD_NUMBER: _ClassVar[int] + type: Message.MessageType + clusterLevelRaw: int + key: bytes + record: Record + closerPeers: _containers.RepeatedCompositeFieldContainer[Message.Peer] + providerPeers: _containers.RepeatedCompositeFieldContainer[Message.Peer] + senderRecord: bytes + def __init__(self, type: _Optional[_Union[Message.MessageType, str]] = ..., clusterLevelRaw: _Optional[int] = ..., key: _Optional[bytes] = ..., record: _Optional[_Union[Record, _Mapping]] = ..., closerPeers: _Optional[_Iterable[_Union[Message.Peer, _Mapping]]] = ..., providerPeers: _Optional[_Iterable[_Union[Message.Peer, _Mapping]]] = ..., senderRecord: _Optional[bytes] = ...) -> None: ... # type: ignore diff --git a/libp2p/kad_dht/peer_routing.py b/libp2p/kad_dht/peer_routing.py index c4a066f7..dc3190a5 100644 --- a/libp2p/kad_dht/peer_routing.py +++ b/libp2p/kad_dht/peer_routing.py @@ -15,12 +15,14 @@ from libp2p.abc import ( INetStream, IPeerRouting, ) +from libp2p.peer.envelope import Envelope, consume_envelope from libp2p.peer.id import ( ID, ) from libp2p.peer.peerinfo import ( PeerInfo, ) +from libp2p.peer.peerstore import create_signed_peer_record from .common import ( ALPHA, @@ -255,6 +257,14 @@ class PeerRouting(IPeerRouting): find_node_msg.type = Message.MessageType.FIND_NODE find_node_msg.key = target_key # Set target key directly as bytes + print("MESSAGE GOING TO BE CREATED") + + # Create sender_signed_peer_record + envelope = create_signed_peer_record( + self.host.get_id(), self.host.get_addrs(), self.host.get_private_key() + ) + find_node_msg.senderRecord = envelope.marshal_envelope() + # Serialize and send the protobuf message with varint length prefix proto_bytes = find_node_msg.SerializeToString() logger.debug( @@ -299,6 +309,26 @@ class PeerRouting(IPeerRouting): # Process closest peers from response if response_msg.type == Message.MessageType.FIND_NODE: + # Consume the sender_signed_peer_record + if response_msg.HasField("senderRecord"): + try: + # Convert the signed-peer-record(Envelope) from + # protobuf bytes + envelope, _ = consume_envelope( + response_msg.senderRecord, "libp2p-peer-record" + ) + # Use the default TTL of 2 hours (7200 seconds) + if not self.host.get_peerstore().consume_peer_record( + envelope, 7200 + ): + logger.error( + "Updating teh Certified-Addr-Book was unsuccessful" + ) + except Exception as e: + logger.error( + "Error updating the certified addr book for peer: %s", e + ) + for peer_data in response_msg.closerPeers: new_peer_id = ID(peer_data.id) if new_peer_id not in results: @@ -311,7 +341,29 @@ class PeerRouting(IPeerRouting): addrs = [Multiaddr(addr) for addr in peer_data.addrs] self.host.get_peerstore().add_addrs(new_peer_id, addrs, 3600) + # Consume the received closer_peers signed-records + if peer_data.HasField("signedRecord"): + try: + # Convert the signed-peer-record(Envelope) from + # protobuf bytes + envelope, _ = consume_envelope( + peer_data.signedRecord, + "libp2p-peer-record", + ) + # Use the default TTL of 2 hours (7200 seconds) + if not self.host.get_peerstore().consume_peer_record( + envelope, 7200 + ): + logger.error("Failed to update certified-addr-book") + except Exception as e: + logger.error( + "Error updating the certified-addr-book for peer %s: %s", # noqa + new_peer_id, + e, + ) + except Exception as e: + print("EXCEPTION CAME") logger.debug(f"Error querying peer {peer} for closest: {e}") finally: @@ -345,10 +397,31 @@ class PeerRouting(IPeerRouting): # Parse protobuf message kad_message = Message() + closer_peer_envelope: Envelope | None = None try: kad_message.ParseFromString(message_bytes) if kad_message.type == Message.MessageType.FIND_NODE: + # Consume the sender's signed-peer-record if sent + if kad_message.HasField("senderRecord"): + try: + # Convert the signed-peer-record(Envelope) from + # protobuf bytes + envelope, _ = consume_envelope( + kad_message.senderRecord, "libp2p-peer-record" + ) + # Use the default TTL of 2 hours (7200 seconds) + if not self.host.get_peerstore().consume_peer_record( + envelope, 7200 + ): + logger.error( + "Updating the Certified-Addr-Book was unsuccessful" + ) + except Exception as e: + logger.error( + "Error updating the certified addr book for peer: %s", e + ) + # Get target key directly from protobuf message target_key = kad_message.key @@ -361,12 +434,30 @@ class PeerRouting(IPeerRouting): response = Message() response.type = Message.MessageType.FIND_NODE + # Create sender_signed_peer_record for the response + envelope = create_signed_peer_record( + self.host.get_id(), + self.host.get_addrs(), + self.host.get_private_key(), + ) + response.senderRecord = envelope.marshal_envelope() + # Add peer information to response for peer_id in closest_peers: peer_proto = response.closerPeers.add() peer_proto.id = peer_id.to_bytes() peer_proto.connection = Message.ConnectionType.CAN_CONNECT + # Add the signed-records of closest_peers if cached + closer_peer_envelope = ( + self.host.get_peerstore().get_peer_record(peer_id) + ) + + if isinstance(closer_peer_envelope, Envelope): + peer_proto.signedRecord = ( + closer_peer_envelope.marshal_envelope() + ) + # Add addresses if available try: addrs = self.host.get_peerstore().addrs(peer_id) diff --git a/libp2p/kad_dht/provider_store.py b/libp2p/kad_dht/provider_store.py index 5c34f0c7..c5800914 100644 --- a/libp2p/kad_dht/provider_store.py +++ b/libp2p/kad_dht/provider_store.py @@ -22,12 +22,14 @@ from libp2p.abc import ( from libp2p.custom_types import ( TProtocol, ) +from libp2p.peer.envelope import consume_envelope from libp2p.peer.id import ( ID, ) from libp2p.peer.peerinfo import ( PeerInfo, ) +from libp2p.peer.peerstore import create_signed_peer_record from .common import ( ALPHA, @@ -240,11 +242,22 @@ class ProviderStore: message.type = Message.MessageType.ADD_PROVIDER message.key = key + # Create sender's signed-peer-record + envelope = create_signed_peer_record( + self.host.get_id(), + self.host.get_addrs(), + self.host.get_private_key(), + ) + message.senderRecord = envelope.marshal_envelope() + # Add our provider info provider = message.providerPeers.add() provider.id = self.local_peer_id.to_bytes() provider.addrs.extend(addrs) + # Add the provider's signed-peer-record + provider.signedRecord = envelope.marshal_envelope() + # Serialize and send the message proto_bytes = message.SerializeToString() await stream.write(varint.encode(len(proto_bytes))) @@ -276,9 +289,27 @@ class ProviderStore: response = Message() response.ParseFromString(response_bytes) - # Check response type - response.type == Message.MessageType.ADD_PROVIDER - if response.type: + if response.type == Message.MessageType.ADD_PROVIDER: + # Consume the sender's signed-peer-record if sent + if response.HasField("senderRecord"): + try: + # Convert the signed-peer-record(Envelope) from + # protobuf bytes + envelope, _ = consume_envelope( + response.senderRecord, "libp2p-peer-record" + ) + # Use the defualt TTL of 2 hours (7200 seconds) + if not self.host.get_peerstore().consume_peer_record( + envelope, 7200 + ): + logger.error( + "Updating the Certified-Addr-Book was unsuccessful" + ) + except Exception as e: + logger.error( + "Error updating the certified addr book for peer: %s", e + ) + result = True except Exception as e: @@ -380,6 +411,14 @@ class ProviderStore: message.type = Message.MessageType.GET_PROVIDERS message.key = key + # Create sender's signed-peer-record + envelope = create_signed_peer_record( + self.host.get_id(), + self.host.get_addrs(), + self.host.get_private_key(), + ) + message.senderRecord = envelope.marshal_envelope() + # Serialize and send the message proto_bytes = message.SerializeToString() await stream.write(varint.encode(len(proto_bytes))) @@ -414,6 +453,26 @@ class ProviderStore: if response.type != Message.MessageType.GET_PROVIDERS: return [] + # Consume the sender's signed-peer-record if sent + if response.HasField("senderRecord"): + try: + # Convert the signed-peer-record(Envelope) from + # protobuf bytes + envelope, _ = consume_envelope( + response.senderRecord, "libp2p-peer-record" + ) + # Use the defualt TTL of 2 hours (7200 seconds) + if not self.host.get_peerstore().consume_peer_record( + envelope, 7200 + ): + logger.error( + "Updating the Certified-Addr-Book was unsuccessful" + ) + except Exception as e: + logger.error( + "Error updating the certified addr book for peer: %s", e + ) + # Extract provider information providers = [] for provider_proto in response.providerPeers: @@ -431,6 +490,30 @@ class ProviderStore: # Create PeerInfo and add to result providers.append(PeerInfo(provider_id, addrs)) + + # Consume the provider's signed-peer-record if sent + if provider_proto.HasField("signedRecord"): + try: + # Convert the signed-peer-record(Envelope) from + # protobuf bytes + envelope, _ = consume_envelope( + provider_proto.signedRecord, + "libp2p-peer-record", + ) + # Use the default TTL of 2 hours (7200 seconds) + if not self.host.get_peerstore().consume_peer_record( # noqa + envelope, 7200 + ): + logger.error( + "Failed to update the Certified-Addr-Book" + ) + except Exception as e: + logger.error( + "Error updating the certified-addr-book for peer %s: %s", # noqa + provider_id, + e, + ) + except Exception as e: logger.warning(f"Failed to parse provider info: {e}") diff --git a/libp2p/kad_dht/value_store.py b/libp2p/kad_dht/value_store.py index b79425fd..28cc6d8c 100644 --- a/libp2p/kad_dht/value_store.py +++ b/libp2p/kad_dht/value_store.py @@ -15,9 +15,11 @@ from libp2p.abc import ( from libp2p.custom_types import ( TProtocol, ) +from libp2p.peer.envelope import consume_envelope from libp2p.peer.id import ( ID, ) +from libp2p.peer.peerstore import create_signed_peer_record from .common import ( DEFAULT_TTL, @@ -110,6 +112,14 @@ class ValueStore: message = Message() message.type = Message.MessageType.PUT_VALUE + # Create sender's signed-peer-record + envelope = create_signed_peer_record( + self.host.get_id(), + self.host.get_addrs(), + self.host.get_private_key(), + ) + message.senderRecord = envelope.marshal_envelope() + # Set message fields message.key = key message.record.key = key @@ -155,7 +165,27 @@ class ValueStore: # Check if response is valid if response.type == Message.MessageType.PUT_VALUE: - if response.key: + # Consume the sender's signed-peer-record if sent + if response.HasField("senderRecord"): + try: + # Convert the signed-peer-record(Envelope) from + # protobuf bytes + envelope, _ = consume_envelope( + response.senderRecord, "libp2p-peer-record" + ) + # Use the default TTL of 2 hours (7200 seconds) + if not self.host.get_peerstore().consume_peer_record( + envelope, 7200 + ): + logger.error( + "Updating the certified-addr-book was unsuccessful" + ) + except Exception as e: + logger.error( + "Error updating the certified addr book for peer: %s", e + ) + + if response.key == key: result = True return result @@ -231,6 +261,14 @@ class ValueStore: message.type = Message.MessageType.GET_VALUE message.key = key + # Create sender's signed-peer-record + envelope = create_signed_peer_record( + self.host.get_id(), + self.host.get_addrs(), + self.host.get_private_key(), + ) + message.senderRecord = envelope.marshal_envelope() + # Serialize and send the protobuf message proto_bytes = message.SerializeToString() await stream.write(varint.encode(len(proto_bytes))) @@ -275,6 +313,26 @@ class ValueStore: and response.HasField("record") and response.record.value ): + # Consume the sender's signed-peer-record + if response.HasField("senderRecord"): + try: + # Convert the signed-peer-record(Envelope) from + # protobuf bytes + envelope, _ = consume_envelope( + response.senderRecord, "libp2p-peer-record" + ) + # Use the default TTL of 2 hours (7200 seconds) + if not self.host.get_peerstore().consume_peer_record( + envelope, 7200 + ): + logger.error( + "Updating the certified-addr-book was unsuccessful" + ) + except Exception as e: + logger.error( + "Error updating the certified addr book for peer: %s", e + ) + logger.debug( f"Received value for key {key.hex()} from peer {peer_id}" ) diff --git a/libp2p/peer/peerstore.py b/libp2p/peer/peerstore.py index 043aaf0d..4669e9ec 100644 --- a/libp2p/peer/peerstore.py +++ b/libp2p/peer/peerstore.py @@ -23,7 +23,8 @@ from libp2p.crypto.keys import ( PrivateKey, PublicKey, ) -from libp2p.peer.envelope import Envelope +from libp2p.peer.envelope import Envelope, seal_record +from libp2p.peer.peer_record import PeerRecord from .id import ( ID, @@ -39,6 +40,17 @@ from .peerinfo import ( PERMANENT_ADDR_TTL = 0 +def create_signed_peer_record( + peer_id: ID, addrs: list[Multiaddr], pvt_key: PrivateKey +) -> Envelope: + """Creates a signed_peer_record wrapped in an Envelope""" + record = PeerRecord(peer_id, addrs) + envelope = seal_record(record, pvt_key) + + print(envelope) + return envelope + + class PeerRecordState: envelope: Envelope seq: int diff --git a/tests/core/kad_dht/test_unit_peer_routing.py b/tests/core/kad_dht/test_unit_peer_routing.py index ffe20655..6e15ce7e 100644 --- a/tests/core/kad_dht/test_unit_peer_routing.py +++ b/tests/core/kad_dht/test_unit_peer_routing.py @@ -57,7 +57,10 @@ class TestPeerRouting: def mock_host(self): """Create a mock host for testing.""" host = Mock() - host.get_id.return_value = create_valid_peer_id("local") + key_pair = create_new_key_pair() + host.get_id.return_value = ID.from_pubkey(key_pair.public_key) + host.get_public_key.return_value = key_pair.public_key + host.get_private_key.return_value = key_pair.private_key host.get_addrs.return_value = [Multiaddr("/ip4/127.0.0.1/tcp/8000")] host.get_peerstore.return_value = Mock() host.new_stream = AsyncMock() From d1792588f9fcfb074bdbb873447d726358087d97 Mon Sep 17 00:00:00 2001 From: lla-dane Date: Sun, 10 Aug 2025 14:59:55 +0530 Subject: [PATCH 02/21] added tests for signed-peee-record transfer in kad-dht --- libp2p/kad_dht/kad_dht.py | 4 +- libp2p/kad_dht/peer_routing.py | 2 - libp2p/peer/peerstore.py | 2 - tests/core/kad_dht/test_kad_dht.py | 135 ++++++++++++++++++++++++++++- 4 files changed, 137 insertions(+), 6 deletions(-) diff --git a/libp2p/kad_dht/kad_dht.py b/libp2p/kad_dht/kad_dht.py index 2fb42662..f510390d 100644 --- a/libp2p/kad_dht/kad_dht.py +++ b/libp2p/kad_dht/kad_dht.py @@ -271,7 +271,6 @@ class KadDHT(Service): # Handle FIND_NODE message if message.type == Message.MessageType.FIND_NODE: # Get target key directly from protobuf - print("FIND NODE RECEIVED") target_key = message.key # Find closest peers to the target key @@ -353,6 +352,7 @@ class KadDHT(Service): # Handle ADD_PROVIDER message elif message.type == Message.MessageType.ADD_PROVIDER: + print("ADD_PROVIDER REQ RECEIVED") # Process ADD_PROVIDER key = message.key logger.debug(f"Received ADD_PROVIDER for key {key.hex()}") @@ -449,6 +449,7 @@ class KadDHT(Service): # Handle GET_PROVIDERS message elif message.type == Message.MessageType.GET_PROVIDERS: + print("GET_PROVIDERS REQ RECIEVED") # Process GET_PROVIDERS key = message.key logger.debug(f"Received GET_PROVIDERS request for key {key.hex()}") @@ -559,6 +560,7 @@ class KadDHT(Service): # Handle GET_VALUE message elif message.type == Message.MessageType.GET_VALUE: + print("GET VALUE REQ RECEIVED") # Process GET_VALUE key = message.key logger.debug(f"Received GET_VALUE request for key {key.hex()}") diff --git a/libp2p/kad_dht/peer_routing.py b/libp2p/kad_dht/peer_routing.py index dc3190a5..a2f3d193 100644 --- a/libp2p/kad_dht/peer_routing.py +++ b/libp2p/kad_dht/peer_routing.py @@ -257,8 +257,6 @@ class PeerRouting(IPeerRouting): find_node_msg.type = Message.MessageType.FIND_NODE find_node_msg.key = target_key # Set target key directly as bytes - print("MESSAGE GOING TO BE CREATED") - # Create sender_signed_peer_record envelope = create_signed_peer_record( self.host.get_id(), self.host.get_addrs(), self.host.get_private_key() diff --git a/libp2p/peer/peerstore.py b/libp2p/peer/peerstore.py index 4669e9ec..0faccb45 100644 --- a/libp2p/peer/peerstore.py +++ b/libp2p/peer/peerstore.py @@ -46,8 +46,6 @@ def create_signed_peer_record( """Creates a signed_peer_record wrapped in an Envelope""" record = PeerRecord(peer_id, addrs) envelope = seal_record(record, pvt_key) - - print(envelope) return envelope diff --git a/tests/core/kad_dht/test_kad_dht.py b/tests/core/kad_dht/test_kad_dht.py index a6f73074..eaf9a956 100644 --- a/tests/core/kad_dht/test_kad_dht.py +++ b/tests/core/kad_dht/test_kad_dht.py @@ -21,6 +21,7 @@ from libp2p.kad_dht.kad_dht import ( from libp2p.kad_dht.utils import ( create_key_from_binary, ) +from libp2p.peer.envelope import Envelope from libp2p.peer.peerinfo import ( PeerInfo, ) @@ -80,6 +81,16 @@ async def test_find_node(dht_pair: tuple[KadDHT, KadDHT]): with trio.fail_after(TEST_TIMEOUT): found_info = await dht_a.find_peer(dht_b.host.get_id()) + # Verifies if the senderRecord in the FIND_NODE request is correctly processed + assert isinstance( + dht_b.host.get_peerstore().get_peer_record(dht_a.host.get_id()), Envelope + ) + + # Verifies if the senderRecord in the FIND_NODE response is correctly proccessed + assert isinstance( + dht_a.host.get_peerstore().get_peer_record(dht_b.host.get_id()), Envelope + ) + # Verify that the found peer has the correct peer ID assert found_info is not None, "Failed to find the target peer" assert found_info.peer_id == dht_b.host.get_id(), "Found incorrect peer ID" @@ -104,14 +115,44 @@ async def test_put_and_get_value(dht_pair: tuple[KadDHT, KadDHT]): await dht_a.routing_table.add_peer(peer_b_info) print("Routing table of a has ", dht_a.routing_table.get_peer_ids()) + # An extra FIND_NODE req is sent between the 2 nodes while dht creation, + # so both the nodes will have records of each other before PUT_VALUE req is sent + envelope_a = dht_a.host.get_peerstore().get_peer_record(dht_b.host.get_id()) + envelope_b = dht_b.host.get_peerstore().get_peer_record(dht_a.host.get_id()) + + assert isinstance(envelope_a, Envelope) + assert isinstance(envelope_b, Envelope) + + record_a = envelope_a.record() + record_b = envelope_b.record() + # Store the value using the first node (this will also store locally) with trio.fail_after(TEST_TIMEOUT): await dht_a.put_value(key, value) + # These are the records that were sent betweeen the peers during the PUT_VALUE req + envelope_a_put_value = dht_a.host.get_peerstore().get_peer_record( + dht_b.host.get_id() + ) + envelope_b_put_value = dht_b.host.get_peerstore().get_peer_record( + dht_a.host.get_id() + ) + + assert isinstance(envelope_a_put_value, Envelope) + assert isinstance(envelope_b_put_value, Envelope) + + record_a_put_value = envelope_a_put_value.record() + record_b_put_value = envelope_b_put_value.record() + + # This proves that both the records are different, and a new signed record + # was passed between the peers during PUT_VALUE exceution, which proves the + # signed-record transfer works correctly in PUT_VALUE executions. + assert record_a.seq < record_a_put_value.seq + assert record_b.seq < record_b_put_value.seq + # # Log debugging information logger.debug("Put value with key %s...", key.hex()[:10]) logger.debug("Node A value store: %s", dht_a.value_store.store) - print("hello test") # # Allow more time for the value to propagate await trio.sleep(0.5) @@ -126,6 +167,26 @@ async def test_put_and_get_value(dht_pair: tuple[KadDHT, KadDHT]): print("the value stored in node b is", dht_b.get_value_store_size()) logger.debug("Retrieved value: %s", retrieved_value) + # These are the records that were sent betweeen the peers during the PUT_VALUE req + envelope_a_get_value = dht_a.host.get_peerstore().get_peer_record( + dht_b.host.get_id() + ) + envelope_b_get_value = dht_b.host.get_peerstore().get_peer_record( + dht_a.host.get_id() + ) + + assert isinstance(envelope_a_get_value, Envelope) + assert isinstance(envelope_b_get_value, Envelope) + + record_a_get_value = envelope_a_get_value.record() + record_b_get_value = envelope_b_get_value.record() + + # This proves that there was no record exchange between the nodes during GET_VALUE + # execution, as dht_b already had the key/value pair stored locally after the + # PUT_VALUE execution. + assert record_a_get_value.seq == record_a_put_value.seq + assert record_b_get_value.seq == record_b_put_value.seq + # Verify that the retrieved value matches the original assert retrieved_value == value, "Retrieved value does not match the stored value" @@ -142,11 +203,43 @@ async def test_provide_and_find_providers(dht_pair: tuple[KadDHT, KadDHT]): # Store content on the first node dht_a.value_store.put(content_id, content) + # An extra FIND_NODE req is sent between the 2 nodes while dht creation, + # so both the nodes will have records of each other before PUT_VALUE req is sent + envelope_a = dht_a.host.get_peerstore().get_peer_record(dht_b.host.get_id()) + envelope_b = dht_b.host.get_peerstore().get_peer_record(dht_a.host.get_id()) + + assert isinstance(envelope_a, Envelope) + assert isinstance(envelope_b, Envelope) + + record_a = envelope_a.record() + record_b = envelope_b.record() + # Advertise the first node as a provider with trio.fail_after(TEST_TIMEOUT): success = await dht_a.provide(content_id) assert success, "Failed to advertise as provider" + # These are the records that were sent betweeen the peers during + # the ADD_PROVIDER req + envelope_a_add_prov = dht_a.host.get_peerstore().get_peer_record( + dht_b.host.get_id() + ) + envelope_b_add_prov = dht_b.host.get_peerstore().get_peer_record( + dht_a.host.get_id() + ) + + assert isinstance(envelope_a_add_prov, Envelope) + assert isinstance(envelope_b_add_prov, Envelope) + + record_a_add_prov = envelope_a_add_prov.record() + record_b_add_prov = envelope_b_add_prov.record() + + # This proves that both the records are different, and a new signed record + # was passed between the peers during ADD_PROVIDER exceution, which proves the + # signed-record transfer works correctly in ADD_PROVIDER executions. + assert record_a.seq < record_a_add_prov.seq + assert record_b.seq < record_b_add_prov.seq + # Allow time for the provider record to propagate await trio.sleep(0.1) @@ -154,6 +247,26 @@ async def test_provide_and_find_providers(dht_pair: tuple[KadDHT, KadDHT]): with trio.fail_after(TEST_TIMEOUT): providers = await dht_b.find_providers(content_id) + # These are the records in each peer after the find_provider execution + envelope_a_find_prov = dht_a.host.get_peerstore().get_peer_record( + dht_b.host.get_id() + ) + envelope_b_find_prov = dht_b.host.get_peerstore().get_peer_record( + dht_a.host.get_id() + ) + + assert isinstance(envelope_a_find_prov, Envelope) + assert isinstance(envelope_b_find_prov, Envelope) + + record_a_find_prov = envelope_a_find_prov.record() + record_b_find_prov = envelope_b_find_prov.record() + + # This proves that both the records are same, as the dht_b already + # has the provider record for the content_id, after the ADD_PROVIDER + # advertisement by dht_a + assert record_a_find_prov.seq == record_a_add_prov.seq + assert record_b_find_prov.seq == record_b_add_prov.seq + # Verify that we found the first node as a provider assert providers, "No providers found" assert any(p.peer_id == dht_a.local_peer_id for p in providers), ( @@ -166,3 +279,23 @@ async def test_provide_and_find_providers(dht_pair: tuple[KadDHT, KadDHT]): assert retrieved_value == content, ( "Retrieved content does not match the original" ) + + # These are the record state of each peer aftet the GET_VALUE execution + envelope_a_get_value = dht_a.host.get_peerstore().get_peer_record( + dht_b.host.get_id() + ) + envelope_b_get_value = dht_b.host.get_peerstore().get_peer_record( + dht_a.host.get_id() + ) + + assert isinstance(envelope_a_get_value, Envelope) + assert isinstance(envelope_b_get_value, Envelope) + + record_a_get_value = envelope_a_get_value.record() + record_b_get_value = envelope_b_get_value.record() + + # This proves that both the records are different, meaning that there was + # a new signed-record tranfer during the GET_VALUE execution by dht_b, which means + # the signed-record transfer works correctly in GET_VALUE executions. + assert record_a_find_prov.seq < record_a_get_value.seq + assert record_b_find_prov.seq < record_b_get_value.seq From 5ab68026d639be4617ebe4411897b20b68965762 Mon Sep 17 00:00:00 2001 From: lla-dane Date: Sun, 10 Aug 2025 15:02:39 +0530 Subject: [PATCH 03/21] removed redundant logs --- libp2p/kad_dht/kad_dht.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/libp2p/kad_dht/kad_dht.py b/libp2p/kad_dht/kad_dht.py index f510390d..2a3a2b1a 100644 --- a/libp2p/kad_dht/kad_dht.py +++ b/libp2p/kad_dht/kad_dht.py @@ -352,7 +352,6 @@ class KadDHT(Service): # Handle ADD_PROVIDER message elif message.type == Message.MessageType.ADD_PROVIDER: - print("ADD_PROVIDER REQ RECEIVED") # Process ADD_PROVIDER key = message.key logger.debug(f"Received ADD_PROVIDER for key {key.hex()}") @@ -449,7 +448,6 @@ class KadDHT(Service): # Handle GET_PROVIDERS message elif message.type == Message.MessageType.GET_PROVIDERS: - print("GET_PROVIDERS REQ RECIEVED") # Process GET_PROVIDERS key = message.key logger.debug(f"Received GET_PROVIDERS request for key {key.hex()}") @@ -560,7 +558,6 @@ class KadDHT(Service): # Handle GET_VALUE message elif message.type == Message.MessageType.GET_VALUE: - print("GET VALUE REQ RECEIVED") # Process GET_VALUE key = message.key logger.debug(f"Received GET_VALUE request for key {key.hex()}") From a21d9e878bc97f0f51ea756438c129df5f057e38 Mon Sep 17 00:00:00 2001 From: lla-dane Date: Mon, 11 Aug 2025 09:48:45 +0530 Subject: [PATCH 04/21] recompile protobuf schema and remove typos --- libp2p/kad_dht/kad_dht.py | 2 +- libp2p/kad_dht/peer_routing.py | 1 - tests/core/kad_dht/test_kad_dht.py | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/libp2p/kad_dht/kad_dht.py b/libp2p/kad_dht/kad_dht.py index 2a3a2b1a..78cf50e2 100644 --- a/libp2p/kad_dht/kad_dht.py +++ b/libp2p/kad_dht/kad_dht.py @@ -364,7 +364,7 @@ class KadDHT(Service): envelope, _ = consume_envelope( message.senderRecord, "libp2p-peer-record" ) - # Use the default TTL of 2 hours (72000 seconds) + # Use the default TTL of 2 hours (7200 seconds) if not self.host.get_peerstore().consume_peer_record( envelope, 7200 ): diff --git a/libp2p/kad_dht/peer_routing.py b/libp2p/kad_dht/peer_routing.py index a2f3d193..58406f05 100644 --- a/libp2p/kad_dht/peer_routing.py +++ b/libp2p/kad_dht/peer_routing.py @@ -361,7 +361,6 @@ class PeerRouting(IPeerRouting): ) except Exception as e: - print("EXCEPTION CAME") logger.debug(f"Error querying peer {peer} for closest: {e}") finally: diff --git a/tests/core/kad_dht/test_kad_dht.py b/tests/core/kad_dht/test_kad_dht.py index eaf9a956..70d9a5e9 100644 --- a/tests/core/kad_dht/test_kad_dht.py +++ b/tests/core/kad_dht/test_kad_dht.py @@ -86,7 +86,7 @@ async def test_find_node(dht_pair: tuple[KadDHT, KadDHT]): dht_b.host.get_peerstore().get_peer_record(dht_a.host.get_id()), Envelope ) - # Verifies if the senderRecord in the FIND_NODE response is correctly proccessed + # Verifies if the senderRecord in the FIND_NODE response is correctly processed assert isinstance( dht_a.host.get_peerstore().get_peer_record(dht_b.host.get_id()), Envelope ) From 702ad4876e8c925284b6e4faf4e63342a03f8b4a Mon Sep 17 00:00:00 2001 From: lla-dane Date: Tue, 12 Aug 2025 13:53:40 +0530 Subject: [PATCH 05/21] remove too much repeatitive code --- libp2p/kad_dht/kad_dht.py | 126 +++---------------------------- libp2p/kad_dht/peer_routing.py | 61 ++------------- libp2p/kad_dht/provider_store.py | 63 +--------------- libp2p/kad_dht/utils.py | 44 +++++++++++ libp2p/kad_dht/value_store.py | 40 +--------- 5 files changed, 68 insertions(+), 266 deletions(-) diff --git a/libp2p/kad_dht/kad_dht.py b/libp2p/kad_dht/kad_dht.py index 78cf50e2..db0e635e 100644 --- a/libp2p/kad_dht/kad_dht.py +++ b/libp2p/kad_dht/kad_dht.py @@ -22,10 +22,11 @@ from libp2p.abc import ( IHost, ) from libp2p.discovery.random_walk.rt_refresh_manager import RTRefreshManager +from libp2p.kad_dht.utils import maybe_consume_signed_record from libp2p.network.stream.net_stream import ( INetStream, ) -from libp2p.peer.envelope import Envelope, consume_envelope +from libp2p.peer.envelope import Envelope from libp2p.peer.id import ( ID, ) @@ -280,24 +281,7 @@ class KadDHT(Service): logger.debug(f"Found {len(closest_peers)} peers close to target") # Consume the source signed_peer_record if sent - if message.HasField("senderRecord"): - try: - # Convert the signed-peer-record(Envelope) from - # protobuf bytes - envelope, _ = consume_envelope( - message.senderRecord, "libp2p-peer-record" - ) - # Use the defualt TTL of 2 hours (7200 seconds) - if not self.host.get_peerstore().consume_peer_record( - envelope, 7200 - ): - logger.error( - "Updating the Certified-Addr-Book was unsuccessful" - ) - except Exception as e: - logger.error( - "Error updating the certified addr book for peer: %s", e - ) + success = maybe_consume_signed_record(message, self.host) # Build response message with protobuf response = Message() @@ -357,24 +341,7 @@ class KadDHT(Service): logger.debug(f"Received ADD_PROVIDER for key {key.hex()}") # Consume the source signed-peer-record if sent - if message.HasField("senderRecord"): - try: - # Convert the signed-peer-record(Envelope) from - # protobuf bytes - envelope, _ = consume_envelope( - message.senderRecord, "libp2p-peer-record" - ) - # Use the default TTL of 2 hours (7200 seconds) - if not self.host.get_peerstore().consume_peer_record( - envelope, 7200 - ): - logger.error( - "Updating the Certified-Addr-Book was unsuccessful" - ) - except Exception as e: - logger.error( - "Error updating the certified addr book for peer: %s", e - ) + success = maybe_consume_signed_record(message, self.host) # Extract provider information for provider_proto in message.providerPeers: @@ -402,31 +369,13 @@ class KadDHT(Service): logger.debug( f"Added provider {provider_id} for key {key.hex()}" ) - except Exception as e: - logger.warning(f"Failed to process provider info: {e}") # Process the signed-records of provider if sent - if provider_proto.HasField("signedRecord"): - try: - # Convert the signed-peer-record(Envelope) from - # protobuf bytes - envelope, _ = consume_envelope( - provider_proto.signedRecord, - "libp2p-peer-record", - ) - # Use the default TTL of 2 hours (7200 seconds) - if not self.host.get_peerstore().consume_peer_record( # noqa - envelope, 7200 - ): - logger.error( - "Failed to update the Certified-Addr-Book" - ) - except Exception as e: - logger.error( - "Error updating the certified-addr-book for peer %s: %s", # noqa - provider_id, - e, - ) + success = maybe_consume_signed_record( + provider_proto, self.host + ) + except Exception as e: + logger.warning(f"Failed to process provider info: {e}") # Send acknowledgement response = Message() @@ -453,24 +402,7 @@ class KadDHT(Service): logger.debug(f"Received GET_PROVIDERS request for key {key.hex()}") # Consume the source signed_peer_record if sent - if message.HasField("senderRecord"): - try: - # Convert the signed-peer-record(Envelope) from - # protobuf bytes - envelope, _ = consume_envelope( - message.senderRecord, "libp2p-peer-record" - ) - # Use the defualt TTL of 2 hours (7200 seconds) - if not self.host.get_peerstore().consume_peer_record( - envelope, 7200 - ): - logger.error( - "Updating the Certified-Addr-Book was unsuccessful" - ) - except Exception as e: - logger.error( - "Error updating the certified addr book for peer: %s", e - ) + success = maybe_consume_signed_record(message, self.host) # Find providers for the key providers = self.provider_store.get_providers(key) @@ -563,24 +495,7 @@ class KadDHT(Service): logger.debug(f"Received GET_VALUE request for key {key.hex()}") # Consume the sender_signed_peer_record - if message.HasField("senderRecord"): - try: - # Convert the signed-peer-record(Envelope) from - # protobuf bytes - envelope, _ = consume_envelope( - message.senderRecord, "libp2p-peer-record" - ) - # Use the default TTL of 2 hours (7200 seconds) - if not self.host.get_peerstore().consume_peer_record( - envelope, 7200 - ): - logger.error( - "Updating teh Certified-Addr-Book was unsuccessful" - ) - except Exception as e: - logger.error( - "Error updating the certified addr book for peer: %s", e - ) + success = maybe_consume_signed_record(message, self.host) value = self.value_store.get(key) if value: @@ -677,24 +592,7 @@ class KadDHT(Service): success = False # Consume the source signed_peer_record if sent - if message.HasField("senderRecord"): - try: - # Convert the signed-peer-record(Envelope) from - # protobuf bytes - envelope, _ = consume_envelope( - message.senderRecord, "libp2p-peer-record" - ) - # Use the default TTL of 2 hours (7200 seconds) - if not self.host.get_peerstore().consume_peer_record( - envelope, 7200 - ): - logger.error( - "Updating the certified-addr-book was unsuccessful" - ) - except Exception as e: - logger.error( - "Error updating the certified addr book for peer: %s", e - ) + success = maybe_consume_signed_record(message, self.host) try: if not (key and value): diff --git a/libp2p/kad_dht/peer_routing.py b/libp2p/kad_dht/peer_routing.py index 58406f05..e36f7caf 100644 --- a/libp2p/kad_dht/peer_routing.py +++ b/libp2p/kad_dht/peer_routing.py @@ -15,7 +15,7 @@ from libp2p.abc import ( INetStream, IPeerRouting, ) -from libp2p.peer.envelope import Envelope, consume_envelope +from libp2p.peer.envelope import Envelope from libp2p.peer.id import ( ID, ) @@ -35,6 +35,7 @@ from .routing_table import ( RoutingTable, ) from .utils import ( + maybe_consume_signed_record, sort_peer_ids_by_distance, ) @@ -308,24 +309,7 @@ class PeerRouting(IPeerRouting): # Process closest peers from response if response_msg.type == Message.MessageType.FIND_NODE: # Consume the sender_signed_peer_record - if response_msg.HasField("senderRecord"): - try: - # Convert the signed-peer-record(Envelope) from - # protobuf bytes - envelope, _ = consume_envelope( - response_msg.senderRecord, "libp2p-peer-record" - ) - # Use the default TTL of 2 hours (7200 seconds) - if not self.host.get_peerstore().consume_peer_record( - envelope, 7200 - ): - logger.error( - "Updating teh Certified-Addr-Book was unsuccessful" - ) - except Exception as e: - logger.error( - "Error updating the certified addr book for peer: %s", e - ) + _ = maybe_consume_signed_record(response_msg, self.host) for peer_data in response_msg.closerPeers: new_peer_id = ID(peer_data.id) @@ -340,25 +324,7 @@ class PeerRouting(IPeerRouting): self.host.get_peerstore().add_addrs(new_peer_id, addrs, 3600) # Consume the received closer_peers signed-records - if peer_data.HasField("signedRecord"): - try: - # Convert the signed-peer-record(Envelope) from - # protobuf bytes - envelope, _ = consume_envelope( - peer_data.signedRecord, - "libp2p-peer-record", - ) - # Use the default TTL of 2 hours (7200 seconds) - if not self.host.get_peerstore().consume_peer_record( - envelope, 7200 - ): - logger.error("Failed to update certified-addr-book") - except Exception as e: - logger.error( - "Error updating the certified-addr-book for peer %s: %s", # noqa - new_peer_id, - e, - ) + _ = maybe_consume_signed_record(peer_data, self.host) except Exception as e: logger.debug(f"Error querying peer {peer} for closest: {e}") @@ -400,24 +366,7 @@ class PeerRouting(IPeerRouting): if kad_message.type == Message.MessageType.FIND_NODE: # Consume the sender's signed-peer-record if sent - if kad_message.HasField("senderRecord"): - try: - # Convert the signed-peer-record(Envelope) from - # protobuf bytes - envelope, _ = consume_envelope( - kad_message.senderRecord, "libp2p-peer-record" - ) - # Use the default TTL of 2 hours (7200 seconds) - if not self.host.get_peerstore().consume_peer_record( - envelope, 7200 - ): - logger.error( - "Updating the Certified-Addr-Book was unsuccessful" - ) - except Exception as e: - logger.error( - "Error updating the certified addr book for peer: %s", e - ) + _ = maybe_consume_signed_record(kad_message, self.host) # Get target key directly from protobuf message target_key = kad_message.key diff --git a/libp2p/kad_dht/provider_store.py b/libp2p/kad_dht/provider_store.py index c5800914..21bd1c80 100644 --- a/libp2p/kad_dht/provider_store.py +++ b/libp2p/kad_dht/provider_store.py @@ -22,7 +22,7 @@ from libp2p.abc import ( from libp2p.custom_types import ( TProtocol, ) -from libp2p.peer.envelope import consume_envelope +from libp2p.kad_dht.utils import maybe_consume_signed_record from libp2p.peer.id import ( ID, ) @@ -291,25 +291,7 @@ class ProviderStore: if response.type == Message.MessageType.ADD_PROVIDER: # Consume the sender's signed-peer-record if sent - if response.HasField("senderRecord"): - try: - # Convert the signed-peer-record(Envelope) from - # protobuf bytes - envelope, _ = consume_envelope( - response.senderRecord, "libp2p-peer-record" - ) - # Use the defualt TTL of 2 hours (7200 seconds) - if not self.host.get_peerstore().consume_peer_record( - envelope, 7200 - ): - logger.error( - "Updating the Certified-Addr-Book was unsuccessful" - ) - except Exception as e: - logger.error( - "Error updating the certified addr book for peer: %s", e - ) - + _ = maybe_consume_signed_record(response, self.host) result = True except Exception as e: @@ -454,24 +436,7 @@ class ProviderStore: return [] # Consume the sender's signed-peer-record if sent - if response.HasField("senderRecord"): - try: - # Convert the signed-peer-record(Envelope) from - # protobuf bytes - envelope, _ = consume_envelope( - response.senderRecord, "libp2p-peer-record" - ) - # Use the defualt TTL of 2 hours (7200 seconds) - if not self.host.get_peerstore().consume_peer_record( - envelope, 7200 - ): - logger.error( - "Updating the Certified-Addr-Book was unsuccessful" - ) - except Exception as e: - logger.error( - "Error updating the certified addr book for peer: %s", e - ) + _ = maybe_consume_signed_record(response, self.host) # Extract provider information providers = [] @@ -492,27 +457,7 @@ class ProviderStore: providers.append(PeerInfo(provider_id, addrs)) # Consume the provider's signed-peer-record if sent - if provider_proto.HasField("signedRecord"): - try: - # Convert the signed-peer-record(Envelope) from - # protobuf bytes - envelope, _ = consume_envelope( - provider_proto.signedRecord, - "libp2p-peer-record", - ) - # Use the default TTL of 2 hours (7200 seconds) - if not self.host.get_peerstore().consume_peer_record( # noqa - envelope, 7200 - ): - logger.error( - "Failed to update the Certified-Addr-Book" - ) - except Exception as e: - logger.error( - "Error updating the certified-addr-book for peer %s: %s", # noqa - provider_id, - e, - ) + _ = maybe_consume_signed_record(provider_proto, self.host) except Exception as e: logger.warning(f"Failed to parse provider info: {e}") diff --git a/libp2p/kad_dht/utils.py b/libp2p/kad_dht/utils.py index 61158320..64976cb3 100644 --- a/libp2p/kad_dht/utils.py +++ b/libp2p/kad_dht/utils.py @@ -2,13 +2,57 @@ Utility functions for Kademlia DHT implementation. """ +import logging + import base58 import multihash +from libp2p.abc import IHost +from libp2p.peer.envelope import consume_envelope from libp2p.peer.id import ( ID, ) +from .pb.kademlia_pb2 import ( + Message, +) + +logger = logging.getLogger("kademlia-example.utils") + + +def maybe_consume_signed_record(msg: Message | Message.Peer, host: IHost) -> bool: + if isinstance(msg, Message): + if msg.HasField("senderRecord"): + try: + # Convert the signed-peer-record(Envelope) from + # protobuf bytes + envelope, _ = consume_envelope(msg.senderRecord, "libp2p-peer-record") + # Use the default TTL of 2 hours (7200 seconds) + if not host.get_peerstore().consume_peer_record(envelope, 7200): + logger.error("Updating the certified-addr-book was unsuccessful") + except Exception as e: + logger.error("Error updating teh certified addr book for peer: %s", e) + return False + else: + if msg.HasField("signedRecord"): + try: + # Convert the signed-peer-record(Envelope) from + # protobuf bytes + envelope, _ = consume_envelope( + msg.signedRecord, + "libp2p-peer-record", + ) + # Use the default TTL of 2 hours (7200 seconds) + if not host.get_peerstore().consume_peer_record(envelope, 7200): + logger.error("Failed to update the Certified-Addr-Book") + except Exception as e: + logger.error( + "Error updating the certified-addr-book: %s", + e, + ) + + return True + def create_key_from_binary(binary_data: bytes) -> bytes: """ diff --git a/libp2p/kad_dht/value_store.py b/libp2p/kad_dht/value_store.py index 28cc6d8c..adc37b72 100644 --- a/libp2p/kad_dht/value_store.py +++ b/libp2p/kad_dht/value_store.py @@ -15,7 +15,7 @@ from libp2p.abc import ( from libp2p.custom_types import ( TProtocol, ) -from libp2p.peer.envelope import consume_envelope +from libp2p.kad_dht.utils import maybe_consume_signed_record from libp2p.peer.id import ( ID, ) @@ -166,24 +166,7 @@ class ValueStore: # Check if response is valid if response.type == Message.MessageType.PUT_VALUE: # Consume the sender's signed-peer-record if sent - if response.HasField("senderRecord"): - try: - # Convert the signed-peer-record(Envelope) from - # protobuf bytes - envelope, _ = consume_envelope( - response.senderRecord, "libp2p-peer-record" - ) - # Use the default TTL of 2 hours (7200 seconds) - if not self.host.get_peerstore().consume_peer_record( - envelope, 7200 - ): - logger.error( - "Updating the certified-addr-book was unsuccessful" - ) - except Exception as e: - logger.error( - "Error updating the certified addr book for peer: %s", e - ) + _ = maybe_consume_signed_record(response, self.host) if response.key == key: result = True @@ -314,24 +297,7 @@ class ValueStore: and response.record.value ): # Consume the sender's signed-peer-record - if response.HasField("senderRecord"): - try: - # Convert the signed-peer-record(Envelope) from - # protobuf bytes - envelope, _ = consume_envelope( - response.senderRecord, "libp2p-peer-record" - ) - # Use the default TTL of 2 hours (7200 seconds) - if not self.host.get_peerstore().consume_peer_record( - envelope, 7200 - ): - logger.error( - "Updating the certified-addr-book was unsuccessful" - ) - except Exception as e: - logger.error( - "Error updating the certified addr book for peer: %s", e - ) + _ = maybe_consume_signed_record(response, self.host) logger.debug( f"Received value for key {key.hex()} from peer {peer_id}" From cea1985c5c7b8aed6ea2b202b775adc949ad682b Mon Sep 17 00:00:00 2001 From: lla-dane Date: Thu, 14 Aug 2025 10:39:48 +0530 Subject: [PATCH 06/21] add reissuing mechanism of records if addrs dont change --- libp2p/abc.py | 7 +++ libp2p/host/basic_host.py | 9 +++ libp2p/kad_dht/kad_dht.py | 51 ++++------------ libp2p/kad_dht/peer_routing.py | 16 ++--- libp2p/kad_dht/provider_store.py | 21 ++----- libp2p/kad_dht/utils.py | 29 +++++++++ libp2p/kad_dht/value_store.py | 19 ++---- libp2p/peer/envelope.py | 5 ++ libp2p/peer/peerstore.py | 9 +++ tests/core/kad_dht/test_kad_dht.py | 95 ++++++++++++++++++++++++++---- 10 files changed, 170 insertions(+), 91 deletions(-) diff --git a/libp2p/abc.py b/libp2p/abc.py index 90ad6a45..614af8bf 100644 --- a/libp2p/abc.py +++ b/libp2p/abc.py @@ -970,6 +970,13 @@ class IPeerStore( # --------CERTIFIED-ADDR-BOOK---------- + @abstractmethod + def get_local_record(self) -> Optional["Envelope"]: + """Get the local-peer-record wrapped in Envelope""" + + 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: """ diff --git a/libp2p/host/basic_host.py b/libp2p/host/basic_host.py index b40b0128..a0311bd8 100644 --- a/libp2p/host/basic_host.py +++ b/libp2p/host/basic_host.py @@ -43,6 +43,7 @@ from libp2p.peer.id import ( from libp2p.peer.peerinfo import ( PeerInfo, ) +from libp2p.peer.peerstore import create_signed_peer_record from libp2p.protocol_muxer.exceptions import ( MultiselectClientError, MultiselectError, @@ -110,6 +111,14 @@ class BasicHost(IHost): if bootstrap: self.bootstrap = BootstrapDiscovery(network, bootstrap) + # Cache a signed-record if the local-node in the PeerStore + envelope = create_signed_peer_record( + self.get_id(), + self.get_addrs(), + self.get_private_key(), + ) + self.get_peerstore().set_local_record(envelope) + def get_id(self) -> ID: """ :return: peer_id of host diff --git a/libp2p/kad_dht/kad_dht.py b/libp2p/kad_dht/kad_dht.py index db0e635e..f93aa75e 100644 --- a/libp2p/kad_dht/kad_dht.py +++ b/libp2p/kad_dht/kad_dht.py @@ -22,7 +22,7 @@ from libp2p.abc import ( IHost, ) from libp2p.discovery.random_walk.rt_refresh_manager import RTRefreshManager -from libp2p.kad_dht.utils import maybe_consume_signed_record +from libp2p.kad_dht.utils import env_to_send_in_RPC, maybe_consume_signed_record from libp2p.network.stream.net_stream import ( INetStream, ) @@ -33,7 +33,6 @@ from libp2p.peer.id import ( from libp2p.peer.peerinfo import ( PeerInfo, ) -from libp2p.peer.peerstore import create_signed_peer_record from libp2p.tools.async_service import ( Service, ) @@ -319,12 +318,8 @@ class KadDHT(Service): ) # Create sender_signed_peer_record - envelope = create_signed_peer_record( - self.host.get_id(), - self.host.get_addrs(), - self.host.get_private_key(), - ) - response.senderRecord = envelope.marshal_envelope() + envelope_bytes, bool = env_to_send_in_RPC(self.host) + response.senderRecord = envelope_bytes # Serialize and send response response_bytes = response.SerializeToString() @@ -383,12 +378,8 @@ class KadDHT(Service): response.key = key # Add sender's signed-peer-record - envelope = create_signed_peer_record( - self.host.get_id(), - self.host.get_addrs(), - self.host.get_private_key(), - ) - response.senderRecord = envelope.marshal_envelope() + envelope_bytes, bool = env_to_send_in_RPC(self.host) + response.senderRecord = envelope_bytes response_bytes = response.SerializeToString() await stream.write(varint.encode(len(response_bytes))) @@ -416,12 +407,8 @@ class KadDHT(Service): response.key = key # Create sender_signed_peer_record for the response - envelope = create_signed_peer_record( - self.host.get_id(), - self.host.get_addrs(), - self.host.get_private_key(), - ) - response.senderRecord = envelope.marshal_envelope() + envelope_bytes, bool = env_to_send_in_RPC(self.host) + response.senderRecord = envelope_bytes # Add provider information to response for provider_info in providers: @@ -512,12 +499,8 @@ class KadDHT(Service): response.record.timeReceived = str(time.time()) # Create sender_signed_peer_record - envelope = create_signed_peer_record( - self.host.get_id(), - self.host.get_addrs(), - self.host.get_private_key(), - ) - response.senderRecord = envelope.marshal_envelope() + envelope_bytes, bool = env_to_send_in_RPC(self.host) + response.senderRecord = envelope_bytes # Serialize and send response response_bytes = response.SerializeToString() @@ -533,12 +516,8 @@ class KadDHT(Service): response.key = key # Create sender_signed_peer_record for the response - envelope = create_signed_peer_record( - self.host.get_id(), - self.host.get_addrs(), - self.host.get_private_key(), - ) - response.senderRecord = envelope.marshal_envelope() + envelope_bytes, bool = env_to_send_in_RPC(self.host) + response.senderRecord = envelope_bytes # Add closest peers to key closest_peers = self.routing_table.find_local_closest_peers( @@ -616,12 +595,8 @@ class KadDHT(Service): response.key = key # Create sender_signed_peer_record for the response - envelope = create_signed_peer_record( - self.host.get_id(), - self.host.get_addrs(), - self.host.get_private_key(), - ) - response.senderRecord = envelope.marshal_envelope() + envelope_bytes, bool = env_to_send_in_RPC(self.host) + response.senderRecord = envelope_bytes # Serialize and send response response_bytes = response.SerializeToString() diff --git a/libp2p/kad_dht/peer_routing.py b/libp2p/kad_dht/peer_routing.py index e36f7caf..4362ffea 100644 --- a/libp2p/kad_dht/peer_routing.py +++ b/libp2p/kad_dht/peer_routing.py @@ -22,7 +22,6 @@ from libp2p.peer.id import ( from libp2p.peer.peerinfo import ( PeerInfo, ) -from libp2p.peer.peerstore import create_signed_peer_record from .common import ( ALPHA, @@ -35,6 +34,7 @@ from .routing_table import ( RoutingTable, ) from .utils import ( + env_to_send_in_RPC, maybe_consume_signed_record, sort_peer_ids_by_distance, ) @@ -259,10 +259,8 @@ class PeerRouting(IPeerRouting): find_node_msg.key = target_key # Set target key directly as bytes # Create sender_signed_peer_record - envelope = create_signed_peer_record( - self.host.get_id(), self.host.get_addrs(), self.host.get_private_key() - ) - find_node_msg.senderRecord = envelope.marshal_envelope() + envelope_bytes, bool = env_to_send_in_RPC(self.host) + find_node_msg.senderRecord = envelope_bytes # Serialize and send the protobuf message with varint length prefix proto_bytes = find_node_msg.SerializeToString() @@ -381,12 +379,8 @@ class PeerRouting(IPeerRouting): response.type = Message.MessageType.FIND_NODE # Create sender_signed_peer_record for the response - envelope = create_signed_peer_record( - self.host.get_id(), - self.host.get_addrs(), - self.host.get_private_key(), - ) - response.senderRecord = envelope.marshal_envelope() + envelope_bytes, bool = env_to_send_in_RPC(self.host) + response.senderRecord = envelope_bytes # Add peer information to response for peer_id in closest_peers: diff --git a/libp2p/kad_dht/provider_store.py b/libp2p/kad_dht/provider_store.py index 21bd1c80..4c6a8e06 100644 --- a/libp2p/kad_dht/provider_store.py +++ b/libp2p/kad_dht/provider_store.py @@ -22,14 +22,13 @@ from libp2p.abc import ( from libp2p.custom_types import ( TProtocol, ) -from libp2p.kad_dht.utils import maybe_consume_signed_record +from libp2p.kad_dht.utils import env_to_send_in_RPC, maybe_consume_signed_record from libp2p.peer.id import ( ID, ) from libp2p.peer.peerinfo import ( PeerInfo, ) -from libp2p.peer.peerstore import create_signed_peer_record from .common import ( ALPHA, @@ -243,12 +242,8 @@ class ProviderStore: message.key = key # Create sender's signed-peer-record - envelope = create_signed_peer_record( - self.host.get_id(), - self.host.get_addrs(), - self.host.get_private_key(), - ) - message.senderRecord = envelope.marshal_envelope() + envelope_bytes, bool = env_to_send_in_RPC(self.host) + message.senderRecord = envelope_bytes # Add our provider info provider = message.providerPeers.add() @@ -256,7 +251,7 @@ class ProviderStore: provider.addrs.extend(addrs) # Add the provider's signed-peer-record - provider.signedRecord = envelope.marshal_envelope() + provider.signedRecord = envelope_bytes # Serialize and send the message proto_bytes = message.SerializeToString() @@ -394,12 +389,8 @@ class ProviderStore: message.key = key # Create sender's signed-peer-record - envelope = create_signed_peer_record( - self.host.get_id(), - self.host.get_addrs(), - self.host.get_private_key(), - ) - message.senderRecord = envelope.marshal_envelope() + envelope_bytes, bool = env_to_send_in_RPC(self.host) + message.senderRecord = envelope_bytes # Serialize and send the message proto_bytes = message.SerializeToString() diff --git a/libp2p/kad_dht/utils.py b/libp2p/kad_dht/utils.py index 64976cb3..3cf79efd 100644 --- a/libp2p/kad_dht/utils.py +++ b/libp2p/kad_dht/utils.py @@ -12,6 +12,7 @@ from libp2p.peer.envelope import consume_envelope from libp2p.peer.id import ( ID, ) +from libp2p.peer.peerstore import create_signed_peer_record from .pb.kademlia_pb2 import ( Message, @@ -54,6 +55,34 @@ def maybe_consume_signed_record(msg: Message | Message.Peer, host: IHost) -> boo return True +def env_to_send_in_RPC(host: IHost) -> tuple[bytes, bool]: + listen_addrs_set = {addr for addr in host.get_addrs()} + local_env = host.get_peerstore().get_local_record() + + if local_env is None: + # No cached SPR yet -> create one + return issue_and_cache_local_record(host), True + else: + record_addrs_set = local_env._env_addrs_set() + if record_addrs_set == listen_addrs_set: + # Perfect match -> reuse cached envelope + return local_env.marshal_envelope(), False + else: + # Addresses changed -> issue a new SPR and cache it + return issue_and_cache_local_record(host), True + + +def issue_and_cache_local_record(host: IHost) -> bytes: + env = create_signed_peer_record( + host.get_id(), + host.get_addrs(), + host.get_private_key(), + ) + # Cache it for nexxt time use + host.get_peerstore().set_local_record(env) + return env.marshal_envelope() + + def create_key_from_binary(binary_data: bytes) -> bytes: """ Creates a key for the DHT by hashing binary data with SHA-256. diff --git a/libp2p/kad_dht/value_store.py b/libp2p/kad_dht/value_store.py index adc37b72..bb143dcd 100644 --- a/libp2p/kad_dht/value_store.py +++ b/libp2p/kad_dht/value_store.py @@ -15,11 +15,10 @@ from libp2p.abc import ( from libp2p.custom_types import ( TProtocol, ) -from libp2p.kad_dht.utils import maybe_consume_signed_record +from libp2p.kad_dht.utils import env_to_send_in_RPC, maybe_consume_signed_record from libp2p.peer.id import ( ID, ) -from libp2p.peer.peerstore import create_signed_peer_record from .common import ( DEFAULT_TTL, @@ -113,12 +112,8 @@ class ValueStore: message.type = Message.MessageType.PUT_VALUE # Create sender's signed-peer-record - envelope = create_signed_peer_record( - self.host.get_id(), - self.host.get_addrs(), - self.host.get_private_key(), - ) - message.senderRecord = envelope.marshal_envelope() + envelope_bytes, bool = env_to_send_in_RPC(self.host) + message.senderRecord = envelope_bytes # Set message fields message.key = key @@ -245,12 +240,8 @@ class ValueStore: message.key = key # Create sender's signed-peer-record - envelope = create_signed_peer_record( - self.host.get_id(), - self.host.get_addrs(), - self.host.get_private_key(), - ) - message.senderRecord = envelope.marshal_envelope() + envelope_bytes, bool = env_to_send_in_RPC(self.host) + message.senderRecord = envelope_bytes # Serialize and send the protobuf message proto_bytes = message.SerializeToString() diff --git a/libp2p/peer/envelope.py b/libp2p/peer/envelope.py index e93a8280..f8bf9f43 100644 --- a/libp2p/peer/envelope.py +++ b/libp2p/peer/envelope.py @@ -1,5 +1,7 @@ from typing import Any, cast +import multiaddr + from libp2p.crypto.ed25519 import Ed25519PublicKey from libp2p.crypto.keys import PrivateKey, PublicKey from libp2p.crypto.rsa import RSAPublicKey @@ -131,6 +133,9 @@ class Envelope: ) return False + def _env_addrs_set(self) -> set[multiaddr.Multiaddr]: + return {b for b in self.record().addrs} + def pub_key_to_protobuf(pub_key: PublicKey) -> cryto_pb.PublicKey: """ diff --git a/libp2p/peer/peerstore.py b/libp2p/peer/peerstore.py index 0faccb45..ad6f08db 100644 --- a/libp2p/peer/peerstore.py +++ b/libp2p/peer/peerstore.py @@ -65,8 +65,17 @@ class PeerStore(IPeerStore): self.peer_data_map = defaultdict(PeerData) self.addr_update_channels: dict[ID, MemorySendChannel[Multiaddr]] = {} self.peer_record_map: dict[ID, PeerRecordState] = {} + self.local_peer_record: Envelope | None = None self.max_records = max_records + def get_local_record(self) -> Envelope | None: + """Get the local-signed-record wrapped in Envelope""" + return self.local_peer_record + + def set_local_record(self, envelope: Envelope) -> None: + """Set the local-signed-record wrapped in Envelope""" + self.local_peer_record = envelope + def peer_info(self, peer_id: ID) -> PeerInfo: """ :param peer_id: peer ID to get info for diff --git a/tests/core/kad_dht/test_kad_dht.py b/tests/core/kad_dht/test_kad_dht.py index 70d9a5e9..a2e9ec4c 100644 --- a/tests/core/kad_dht/test_kad_dht.py +++ b/tests/core/kad_dht/test_kad_dht.py @@ -9,9 +9,12 @@ This module tests core functionality of the Kademlia DHT including: import hashlib import logging +import os +from unittest.mock import patch import uuid import pytest +import multiaddr import trio from libp2p.kad_dht.kad_dht import ( @@ -77,6 +80,18 @@ async def test_find_node(dht_pair: tuple[KadDHT, KadDHT]): """Test that nodes can find each other in the DHT.""" dht_a, dht_b = dht_pair + # An extra FIND_NODE req is sent between the 2 nodes while dht creation, + # so both the nodes will have records of each other before the next FIND_NODE + # req is sent + envelope_a = dht_a.host.get_peerstore().get_peer_record(dht_b.host.get_id()) + envelope_b = dht_b.host.get_peerstore().get_peer_record(dht_a.host.get_id()) + + assert isinstance(envelope_a, Envelope) + assert isinstance(envelope_b, Envelope) + + record_a = envelope_a.record() + record_b = envelope_b.record() + # Node A should be able to find Node B with trio.fail_after(TEST_TIMEOUT): found_info = await dht_a.find_peer(dht_b.host.get_id()) @@ -91,6 +106,26 @@ async def test_find_node(dht_pair: tuple[KadDHT, KadDHT]): dht_a.host.get_peerstore().get_peer_record(dht_b.host.get_id()), Envelope ) + # These are the records that were sent betweeen the peers during the FIND_NODE req + envelope_a_find_peer = dht_a.host.get_peerstore().get_peer_record( + dht_b.host.get_id() + ) + envelope_b_find_peer = dht_b.host.get_peerstore().get_peer_record( + dht_a.host.get_id() + ) + + assert isinstance(envelope_a_find_peer, Envelope) + assert isinstance(envelope_b_find_peer, Envelope) + + record_a_find_peer = envelope_a_find_peer.record() + record_b_find_peer = envelope_b_find_peer.record() + + # This proves that both the records are same, and a latest cached signed record + # was passed between the peers during FIND_NODE exceution, which proves the + # signed-record transfer/re-issuing works correctly in FIND_NODE executions. + assert record_a.seq == record_a_find_peer.seq + assert record_b.seq == record_b_find_peer.seq + # Verify that the found peer has the correct peer ID assert found_info is not None, "Failed to find the target peer" assert found_info.peer_id == dht_b.host.get_id(), "Found incorrect peer ID" @@ -144,11 +179,11 @@ async def test_put_and_get_value(dht_pair: tuple[KadDHT, KadDHT]): record_a_put_value = envelope_a_put_value.record() record_b_put_value = envelope_b_put_value.record() - # This proves that both the records are different, and a new signed record + # This proves that both the records are same, and a latest cached signed record # was passed between the peers during PUT_VALUE exceution, which proves the - # signed-record transfer works correctly in PUT_VALUE executions. - assert record_a.seq < record_a_put_value.seq - assert record_b.seq < record_b_put_value.seq + # signed-record transfer/re-issuing works correctly in PUT_VALUE executions. + assert record_a.seq == record_a_put_value.seq + assert record_b.seq == record_b_put_value.seq # # Log debugging information logger.debug("Put value with key %s...", key.hex()[:10]) @@ -234,11 +269,12 @@ async def test_provide_and_find_providers(dht_pair: tuple[KadDHT, KadDHT]): record_a_add_prov = envelope_a_add_prov.record() record_b_add_prov = envelope_b_add_prov.record() - # This proves that both the records are different, and a new signed record + # This proves that both the records are same, the latest cached signed record # was passed between the peers during ADD_PROVIDER exceution, which proves the - # signed-record transfer works correctly in ADD_PROVIDER executions. - assert record_a.seq < record_a_add_prov.seq - assert record_b.seq < record_b_add_prov.seq + # signed-record transfer/re-issuing of the latest record works correctly in + # ADD_PROVIDER executions. + assert record_a.seq == record_a_add_prov.seq + assert record_b.seq == record_b_add_prov.seq # Allow time for the provider record to propagate await trio.sleep(0.1) @@ -294,8 +330,41 @@ async def test_provide_and_find_providers(dht_pair: tuple[KadDHT, KadDHT]): record_a_get_value = envelope_a_get_value.record() record_b_get_value = envelope_b_get_value.record() - # This proves that both the records are different, meaning that there was - # a new signed-record tranfer during the GET_VALUE execution by dht_b, which means - # the signed-record transfer works correctly in GET_VALUE executions. - assert record_a_find_prov.seq < record_a_get_value.seq - assert record_b_find_prov.seq < record_b_get_value.seq + # This proves that both the records are same, meaning that the latest cached + # signed-record tranfer happened during the GET_VALUE execution by dht_b, + # which means the signed-record transfer/re-issuing works correctly + # in GET_VALUE executions. + assert record_a_find_prov.seq == record_a_get_value.seq + assert record_b_find_prov.seq == record_b_get_value.seq + + +@pytest.mark.trio +async def test_reissue_when_listen_addrs_change(dht_pair: tuple[KadDHT, KadDHT]): + dht_a, dht_b = dht_pair + + # Warm-up: A stores B's current record + with trio.fail_after(10): + await dht_a.find_peer(dht_b.host.get_id()) + + env0 = dht_a.host.get_peerstore().get_peer_record(dht_b.host.get_id()) + assert isinstance(env0, Envelope) + seq0 = env0.record().seq + + # Simulate B's listen addrs changing (different port) + new_addr = multiaddr.Multiaddr("/ip4/127.0.0.1/tcp/123") + + # Patch just for the duration we force B to respond: + with patch.object(dht_b.host, "get_addrs", return_value=[new_addr]): + # Force B to send a response (which should include a fresh SPR) + with trio.fail_after(10): + await dht_a.peer_routing._query_peer_for_closest( + dht_b.host.get_id(), os.urandom(32) + ) + + # A should now hold B's new record with a bumped seq + env1 = dht_a.host.get_peerstore().get_peer_record(dht_b.host.get_id()) + assert isinstance(env1, Envelope) + seq1 = env1.record().seq + + # This proves that upon the change in listen_addrs, we issue new records + assert seq1 > seq0, f"Expected seq to bump after addr change, got {seq0} -> {seq1}" From efc899e8725f9bdc4b7c27d0aad66c4ff2b214d5 Mon Sep 17 00:00:00 2001 From: lla-dane Date: Thu, 14 Aug 2025 11:34:40 +0530 Subject: [PATCH 07/21] fix abc.py file --- libp2p/abc.py | 1 + 1 file changed, 1 insertion(+) diff --git a/libp2p/abc.py b/libp2p/abc.py index 614af8bf..a9748339 100644 --- a/libp2p/abc.py +++ b/libp2p/abc.py @@ -974,6 +974,7 @@ class IPeerStore( 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""" From 57d1c9d80784e229ade251c58aa30fbdedc5fbf9 Mon Sep 17 00:00:00 2001 From: lla-dane Date: Fri, 15 Aug 2025 16:11:27 +0530 Subject: [PATCH 08/21] reject dht-msgs upon receiving invalid records --- libp2p/kad_dht/kad_dht.py | 45 ++++++++++++++++++++++++++------ libp2p/kad_dht/peer_routing.py | 22 ++++++++++++---- libp2p/kad_dht/provider_store.py | 26 +++++++++++++----- libp2p/kad_dht/utils.py | 7 +++-- libp2p/kad_dht/value_store.py | 13 ++++++--- 5 files changed, 89 insertions(+), 24 deletions(-) diff --git a/libp2p/kad_dht/kad_dht.py b/libp2p/kad_dht/kad_dht.py index f93aa75e..adfd7400 100644 --- a/libp2p/kad_dht/kad_dht.py +++ b/libp2p/kad_dht/kad_dht.py @@ -280,7 +280,12 @@ class KadDHT(Service): logger.debug(f"Found {len(closest_peers)} peers close to target") # Consume the source signed_peer_record if sent - success = maybe_consume_signed_record(message, self.host) + if not maybe_consume_signed_record(message, self.host): + logger.error( + "Received an invalid-signed-record, dropping the stream" + ) + await stream.close() + return # Build response message with protobuf response = Message() @@ -336,7 +341,12 @@ class KadDHT(Service): logger.debug(f"Received ADD_PROVIDER for key {key.hex()}") # Consume the source signed-peer-record if sent - success = maybe_consume_signed_record(message, self.host) + if not maybe_consume_signed_record(message, self.host): + logger.error( + "Received an invalid-signed-record, dropping the stream" + ) + await stream.close() + return # Extract provider information for provider_proto in message.providerPeers: @@ -366,9 +376,13 @@ class KadDHT(Service): ) # Process the signed-records of provider if sent - success = maybe_consume_signed_record( - provider_proto, self.host - ) + if not maybe_consume_signed_record(message, self.host): + logger.error( + "Received an invalid-signed-record," + "dropping the stream" + ) + await stream.close() + return except Exception as e: logger.warning(f"Failed to process provider info: {e}") @@ -393,7 +407,12 @@ class KadDHT(Service): logger.debug(f"Received GET_PROVIDERS request for key {key.hex()}") # Consume the source signed_peer_record if sent - success = maybe_consume_signed_record(message, self.host) + if not maybe_consume_signed_record(message, self.host): + logger.error( + "Received an invalid-signed-record, dropping the stream" + ) + await stream.close() + return # Find providers for the key providers = self.provider_store.get_providers(key) @@ -482,7 +501,12 @@ class KadDHT(Service): logger.debug(f"Received GET_VALUE request for key {key.hex()}") # Consume the sender_signed_peer_record - success = maybe_consume_signed_record(message, self.host) + if not maybe_consume_signed_record(message, self.host): + logger.error( + "Received an invalid-signed-record, dropping the stream" + ) + await stream.close() + return value = self.value_store.get(key) if value: @@ -571,7 +595,12 @@ class KadDHT(Service): success = False # Consume the source signed_peer_record if sent - success = maybe_consume_signed_record(message, self.host) + if not maybe_consume_signed_record(message, self.host): + logger.error( + "Received an invalid-signed-record, dropping the stream" + ) + await stream.close() + return try: if not (key and value): diff --git a/libp2p/kad_dht/peer_routing.py b/libp2p/kad_dht/peer_routing.py index 4362ffea..cd1611ed 100644 --- a/libp2p/kad_dht/peer_routing.py +++ b/libp2p/kad_dht/peer_routing.py @@ -307,9 +307,20 @@ class PeerRouting(IPeerRouting): # Process closest peers from response if response_msg.type == Message.MessageType.FIND_NODE: # Consume the sender_signed_peer_record - _ = maybe_consume_signed_record(response_msg, self.host) + if not maybe_consume_signed_record(response_msg, self.host): + logger.error( + "Received an invalid-signed-record,ignoring the response" + ) + return [] for peer_data in response_msg.closerPeers: + # Consume the received closer_peers signed-records + if not maybe_consume_signed_record(peer_data, self.host): + logger.error( + "Received an invalid-signed-record,ignoring the response" + ) + return [] + new_peer_id = ID(peer_data.id) if new_peer_id not in results: results.append(new_peer_id) @@ -321,9 +332,6 @@ class PeerRouting(IPeerRouting): addrs = [Multiaddr(addr) for addr in peer_data.addrs] self.host.get_peerstore().add_addrs(new_peer_id, addrs, 3600) - # Consume the received closer_peers signed-records - _ = maybe_consume_signed_record(peer_data, self.host) - except Exception as e: logger.debug(f"Error querying peer {peer} for closest: {e}") @@ -364,7 +372,11 @@ class PeerRouting(IPeerRouting): if kad_message.type == Message.MessageType.FIND_NODE: # Consume the sender's signed-peer-record if sent - _ = maybe_consume_signed_record(kad_message, self.host) + if not maybe_consume_signed_record(kad_message, self.host): + logger.error( + "Receivedf an invalid-signed-record, dropping the stream" + ) + return # Get target key directly from protobuf message target_key = kad_message.key diff --git a/libp2p/kad_dht/provider_store.py b/libp2p/kad_dht/provider_store.py index 4c6a8e06..ee7adfe8 100644 --- a/libp2p/kad_dht/provider_store.py +++ b/libp2p/kad_dht/provider_store.py @@ -286,8 +286,13 @@ class ProviderStore: if response.type == Message.MessageType.ADD_PROVIDER: # Consume the sender's signed-peer-record if sent - _ = maybe_consume_signed_record(response, self.host) - result = True + if not maybe_consume_signed_record(response, self.host): + logger.error( + "Received an invalid-signed-record, ignoring the response" + ) + result = False + else: + result = True except Exception as e: logger.warning(f"Error sending ADD_PROVIDER to {peer_id}: {e}") @@ -427,12 +432,24 @@ class ProviderStore: return [] # Consume the sender's signed-peer-record if sent - _ = maybe_consume_signed_record(response, self.host) + if not maybe_consume_signed_record(response, self.host): + logger.error( + "Recieved an invalid-signed-record, ignoring the response" + ) + return [] # Extract provider information providers = [] for provider_proto in response.providerPeers: try: + # Consume the provider's signed-peer-record if sent + if not maybe_consume_signed_record(provider_proto, self.host): + logger.error( + "Recieved an invalid-signed-record, " + "ignoring the response" + ) + return [] + # Create peer ID from bytes provider_id = ID(provider_proto.id) @@ -447,9 +464,6 @@ class ProviderStore: # Create PeerInfo and add to result providers.append(PeerInfo(provider_id, addrs)) - # Consume the provider's signed-peer-record if sent - _ = maybe_consume_signed_record(provider_proto, self.host) - except Exception as e: logger.warning(f"Failed to parse provider info: {e}") diff --git a/libp2p/kad_dht/utils.py b/libp2p/kad_dht/utils.py index 3cf79efd..6d65d1af 100644 --- a/libp2p/kad_dht/utils.py +++ b/libp2p/kad_dht/utils.py @@ -27,7 +27,10 @@ def maybe_consume_signed_record(msg: Message | Message.Peer, host: IHost) -> boo try: # Convert the signed-peer-record(Envelope) from # protobuf bytes - envelope, _ = consume_envelope(msg.senderRecord, "libp2p-peer-record") + envelope, _ = consume_envelope( + msg.senderRecord, + "libp2p-peer-record", + ) # Use the default TTL of 2 hours (7200 seconds) if not host.get_peerstore().consume_peer_record(envelope, 7200): logger.error("Updating the certified-addr-book was unsuccessful") @@ -51,7 +54,7 @@ def maybe_consume_signed_record(msg: Message | Message.Peer, host: IHost) -> boo "Error updating the certified-addr-book: %s", e, ) - + return False return True diff --git a/libp2p/kad_dht/value_store.py b/libp2p/kad_dht/value_store.py index bb143dcd..aa545797 100644 --- a/libp2p/kad_dht/value_store.py +++ b/libp2p/kad_dht/value_store.py @@ -161,8 +161,11 @@ class ValueStore: # Check if response is valid if response.type == Message.MessageType.PUT_VALUE: # Consume the sender's signed-peer-record if sent - _ = maybe_consume_signed_record(response, self.host) - + if not maybe_consume_signed_record(response, self.host): + logger.error( + "Received an invalid-signed-record, ignoring the response" + ) + return False if response.key == key: result = True return result @@ -288,7 +291,11 @@ class ValueStore: and response.record.value ): # Consume the sender's signed-peer-record - _ = maybe_consume_signed_record(response, self.host) + if not maybe_consume_signed_record(response, self.host): + logger.error( + "Received an invalid-signed-record, ignoring the response" + ) + return None logger.debug( f"Received value for key {key.hex()} from peer {peer_id}" From ba39e91a2ee6f63b6a122d11334a32459732b260 Mon Sep 17 00:00:00 2001 From: lla-dane Date: Sun, 17 Aug 2025 12:10:08 +0530 Subject: [PATCH 09/21] added test for req rejection upon invalid record transfer --- tests/core/kad_dht/test_kad_dht.py | 40 ++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/tests/core/kad_dht/test_kad_dht.py b/tests/core/kad_dht/test_kad_dht.py index a2e9ec4c..05a31468 100644 --- a/tests/core/kad_dht/test_kad_dht.py +++ b/tests/core/kad_dht/test_kad_dht.py @@ -17,6 +17,7 @@ import pytest import multiaddr import trio +from libp2p.crypto.rsa import create_new_key_pair from libp2p.kad_dht.kad_dht import ( DHTMode, KadDHT, @@ -368,3 +369,42 @@ async def test_reissue_when_listen_addrs_change(dht_pair: tuple[KadDHT, KadDHT]) # This proves that upon the change in listen_addrs, we issue new records assert seq1 > seq0, f"Expected seq to bump after addr change, got {seq0} -> {seq1}" + + +@pytest.mark.trio +async def test_dht_req_fail_with_invalid_record_transfer( + dht_pair: tuple[KadDHT, KadDHT], +): + """ + Testing showing failure of storing and retrieving values in the DHT, + if invalid signed-records are sent. + """ + dht_a, dht_b = dht_pair + peer_b_info = PeerInfo(dht_b.host.get_id(), dht_b.host.get_addrs()) + + # Generate a random key and value + key = create_key_from_binary(b"test-key") + value = b"test-value" + + # First add the value directly to node A's store to verify storage works + dht_a.value_store.put(key, value) + local_value = dht_a.value_store.get(key) + assert local_value == value, "Local value storage failed" + await dht_a.routing_table.add_peer(peer_b_info) + + # Corrupt dht_a's local peer_record + envelope = dht_a.host.get_peerstore().get_local_record() + key_pair = create_new_key_pair() + + if envelope is not None: + envelope.public_key = key_pair.public_key + dht_a.host.get_peerstore().set_local_record(envelope) + + with trio.fail_after(TEST_TIMEOUT): + await dht_a.put_value(key, value) + + value = dht_b.value_store.get(key) + + # This proves that DHT_B rejected DHT_A PUT_RECORD req upon receiving + # the corrupted invalid record + assert value is None From 3aacb3a391015e710cef24b3973c195b69c4ff25 Mon Sep 17 00:00:00 2001 From: lla-dane Date: Sun, 17 Aug 2025 12:21:39 +0530 Subject: [PATCH 10/21] remove the timeout bound from the kad-dht test --- tests/core/kad_dht/test_kad_dht.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/core/kad_dht/test_kad_dht.py b/tests/core/kad_dht/test_kad_dht.py index 05a31468..37730308 100644 --- a/tests/core/kad_dht/test_kad_dht.py +++ b/tests/core/kad_dht/test_kad_dht.py @@ -400,8 +400,7 @@ async def test_dht_req_fail_with_invalid_record_transfer( envelope.public_key = key_pair.public_key dht_a.host.get_peerstore().set_local_record(envelope) - with trio.fail_after(TEST_TIMEOUT): - await dht_a.put_value(key, value) + await dht_a.put_value(key, value) value = dht_b.value_store.get(key) From 3917d7b5967bae22655bd1054a7e0a5175b42e35 Mon Sep 17 00:00:00 2001 From: lla-dane Date: Wed, 20 Aug 2025 18:07:32 +0530 Subject: [PATCH 11/21] verify peer_id in signed-record matches authenticated sender --- libp2p/kad_dht/kad_dht.py | 14 ++++++++------ libp2p/kad_dht/peer_routing.py | 8 +++++--- libp2p/kad_dht/provider_store.py | 7 ++++--- libp2p/kad_dht/utils.py | 13 ++++++++++--- libp2p/kad_dht/value_store.py | 4 ++-- tests/core/kad_dht/test_kad_dht.py | 24 ++++++++++++++++++++---- 6 files changed, 49 insertions(+), 21 deletions(-) diff --git a/libp2p/kad_dht/kad_dht.py b/libp2p/kad_dht/kad_dht.py index adfd7400..44787690 100644 --- a/libp2p/kad_dht/kad_dht.py +++ b/libp2p/kad_dht/kad_dht.py @@ -280,7 +280,7 @@ class KadDHT(Service): logger.debug(f"Found {len(closest_peers)} peers close to target") # Consume the source signed_peer_record if sent - if not maybe_consume_signed_record(message, self.host): + if not maybe_consume_signed_record(message, self.host, peer_id): logger.error( "Received an invalid-signed-record, dropping the stream" ) @@ -341,7 +341,7 @@ class KadDHT(Service): logger.debug(f"Received ADD_PROVIDER for key {key.hex()}") # Consume the source signed-peer-record if sent - if not maybe_consume_signed_record(message, self.host): + if not maybe_consume_signed_record(message, self.host, peer_id): logger.error( "Received an invalid-signed-record, dropping the stream" ) @@ -376,7 +376,9 @@ class KadDHT(Service): ) # Process the signed-records of provider if sent - if not maybe_consume_signed_record(message, self.host): + if not maybe_consume_signed_record( + message, self.host, peer_id + ): logger.error( "Received an invalid-signed-record," "dropping the stream" @@ -407,7 +409,7 @@ class KadDHT(Service): logger.debug(f"Received GET_PROVIDERS request for key {key.hex()}") # Consume the source signed_peer_record if sent - if not maybe_consume_signed_record(message, self.host): + if not maybe_consume_signed_record(message, self.host, peer_id): logger.error( "Received an invalid-signed-record, dropping the stream" ) @@ -501,7 +503,7 @@ class KadDHT(Service): logger.debug(f"Received GET_VALUE request for key {key.hex()}") # Consume the sender_signed_peer_record - if not maybe_consume_signed_record(message, self.host): + if not maybe_consume_signed_record(message, self.host, peer_id): logger.error( "Received an invalid-signed-record, dropping the stream" ) @@ -595,7 +597,7 @@ class KadDHT(Service): success = False # Consume the source signed_peer_record if sent - if not maybe_consume_signed_record(message, self.host): + if not maybe_consume_signed_record(message, self.host, peer_id): logger.error( "Received an invalid-signed-record, dropping the stream" ) diff --git a/libp2p/kad_dht/peer_routing.py b/libp2p/kad_dht/peer_routing.py index cd1611ed..34b95902 100644 --- a/libp2p/kad_dht/peer_routing.py +++ b/libp2p/kad_dht/peer_routing.py @@ -307,14 +307,15 @@ class PeerRouting(IPeerRouting): # Process closest peers from response if response_msg.type == Message.MessageType.FIND_NODE: # Consume the sender_signed_peer_record - if not maybe_consume_signed_record(response_msg, self.host): + if not maybe_consume_signed_record(response_msg, self.host, peer): logger.error( "Received an invalid-signed-record,ignoring the response" ) return [] for peer_data in response_msg.closerPeers: - # Consume the received closer_peers signed-records + # Consume the received closer_peers signed-records, peer-id is + # sent with the peer-data if not maybe_consume_signed_record(peer_data, self.host): logger.error( "Received an invalid-signed-record,ignoring the response" @@ -353,6 +354,7 @@ class PeerRouting(IPeerRouting): """ try: # Read message length + peer_id = stream.muxed_conn.peer_id length_bytes = await stream.read(4) if not length_bytes: return @@ -372,7 +374,7 @@ class PeerRouting(IPeerRouting): if kad_message.type == Message.MessageType.FIND_NODE: # Consume the sender's signed-peer-record if sent - if not maybe_consume_signed_record(kad_message, self.host): + if not maybe_consume_signed_record(kad_message, self.host, peer_id): logger.error( "Receivedf an invalid-signed-record, dropping the stream" ) diff --git a/libp2p/kad_dht/provider_store.py b/libp2p/kad_dht/provider_store.py index ee7adfe8..1aae23f7 100644 --- a/libp2p/kad_dht/provider_store.py +++ b/libp2p/kad_dht/provider_store.py @@ -286,7 +286,7 @@ class ProviderStore: if response.type == Message.MessageType.ADD_PROVIDER: # Consume the sender's signed-peer-record if sent - if not maybe_consume_signed_record(response, self.host): + if not maybe_consume_signed_record(response, self.host, peer_id): logger.error( "Received an invalid-signed-record, ignoring the response" ) @@ -432,7 +432,7 @@ class ProviderStore: return [] # Consume the sender's signed-peer-record if sent - if not maybe_consume_signed_record(response, self.host): + if not maybe_consume_signed_record(response, self.host, peer_id): logger.error( "Recieved an invalid-signed-record, ignoring the response" ) @@ -442,7 +442,8 @@ class ProviderStore: providers = [] for provider_proto in response.providerPeers: try: - # Consume the provider's signed-peer-record if sent + # Consume the provider's signed-peer-record if sent, peer-id + # already sent with the provider-proto if not maybe_consume_signed_record(provider_proto, self.host): logger.error( "Recieved an invalid-signed-record, " diff --git a/libp2p/kad_dht/utils.py b/libp2p/kad_dht/utils.py index 6d65d1af..6c406587 100644 --- a/libp2p/kad_dht/utils.py +++ b/libp2p/kad_dht/utils.py @@ -21,16 +21,20 @@ from .pb.kademlia_pb2 import ( logger = logging.getLogger("kademlia-example.utils") -def maybe_consume_signed_record(msg: Message | Message.Peer, host: IHost) -> bool: +def maybe_consume_signed_record( + msg: Message | Message.Peer, host: IHost, peer_id: ID | None = None +) -> bool: if isinstance(msg, Message): if msg.HasField("senderRecord"): try: # Convert the signed-peer-record(Envelope) from # protobuf bytes - envelope, _ = consume_envelope( + envelope, record = consume_envelope( msg.senderRecord, "libp2p-peer-record", ) + if not (isinstance(peer_id, ID) and record.peer_id == peer_id): + return False # Use the default TTL of 2 hours (7200 seconds) if not host.get_peerstore().consume_peer_record(envelope, 7200): logger.error("Updating the certified-addr-book was unsuccessful") @@ -39,13 +43,16 @@ def maybe_consume_signed_record(msg: Message | Message.Peer, host: IHost) -> boo return False else: if msg.HasField("signedRecord"): + # TODO: Check in with the Message.Peer id with the record's id try: # Convert the signed-peer-record(Envelope) from # protobuf bytes - envelope, _ = consume_envelope( + envelope, record = consume_envelope( msg.signedRecord, "libp2p-peer-record", ) + if not record.peer_id.to_bytes() == msg.id: + return False # Use the default TTL of 2 hours (7200 seconds) if not host.get_peerstore().consume_peer_record(envelope, 7200): logger.error("Failed to update the Certified-Addr-Book") diff --git a/libp2p/kad_dht/value_store.py b/libp2p/kad_dht/value_store.py index aa545797..c0241528 100644 --- a/libp2p/kad_dht/value_store.py +++ b/libp2p/kad_dht/value_store.py @@ -161,7 +161,7 @@ class ValueStore: # Check if response is valid if response.type == Message.MessageType.PUT_VALUE: # Consume the sender's signed-peer-record if sent - if not maybe_consume_signed_record(response, self.host): + if not maybe_consume_signed_record(response, self.host, peer_id): logger.error( "Received an invalid-signed-record, ignoring the response" ) @@ -291,7 +291,7 @@ class ValueStore: and response.record.value ): # Consume the sender's signed-peer-record - if not maybe_consume_signed_record(response, self.host): + if not maybe_consume_signed_record(response, self.host, peer_id): logger.error( "Received an invalid-signed-record, ignoring the response" ) diff --git a/tests/core/kad_dht/test_kad_dht.py b/tests/core/kad_dht/test_kad_dht.py index 37730308..0d9a29f7 100644 --- a/tests/core/kad_dht/test_kad_dht.py +++ b/tests/core/kad_dht/test_kad_dht.py @@ -25,7 +25,9 @@ from libp2p.kad_dht.kad_dht import ( from libp2p.kad_dht.utils import ( create_key_from_binary, ) -from libp2p.peer.envelope import Envelope +from libp2p.peer.envelope import Envelope, seal_record +from libp2p.peer.id import ID +from libp2p.peer.peer_record import PeerRecord from libp2p.peer.peerinfo import ( PeerInfo, ) @@ -394,6 +396,8 @@ async def test_dht_req_fail_with_invalid_record_transfer( # Corrupt dht_a's local peer_record envelope = dht_a.host.get_peerstore().get_local_record() + if envelope is not None: + true_record = envelope.record() key_pair = create_new_key_pair() if envelope is not None: @@ -401,9 +405,21 @@ async def test_dht_req_fail_with_invalid_record_transfer( dht_a.host.get_peerstore().set_local_record(envelope) await dht_a.put_value(key, value) - - value = dht_b.value_store.get(key) + retrieved_value = dht_b.value_store.get(key) # This proves that DHT_B rejected DHT_A PUT_RECORD req upon receiving # the corrupted invalid record - assert value is None + assert retrieved_value is None + + # Create a corrupt envelope with correct signature but false peer_id + false_record = PeerRecord(ID.from_pubkey(key_pair.public_key), true_record.addrs) + false_envelope = seal_record(false_record, dht_a.host.get_private_key()) + + dht_a.host.get_peerstore().set_local_record(false_envelope) + + await dht_a.put_value(key, value) + retrieved_value = dht_b.value_store.get(key) + + # This proves that DHT_B rejected DHT_A PUT_RECORD req upon receving + # the record with a different peer_id regardless of a valid signature + assert retrieved_value is None From 15f4a399ec3ba5b955b0fdca2e80887bf02b6b1c Mon Sep 17 00:00:00 2001 From: lla-dane Date: Sat, 23 Aug 2025 15:36:57 +0530 Subject: [PATCH 12/21] Added and docstrings and removed typos --- libp2p/kad_dht/provider_store.py | 4 ++-- libp2p/kad_dht/utils.py | 34 ++++++++++++++++++++++++++++---- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/libp2p/kad_dht/provider_store.py b/libp2p/kad_dht/provider_store.py index 1aae23f7..3f912ace 100644 --- a/libp2p/kad_dht/provider_store.py +++ b/libp2p/kad_dht/provider_store.py @@ -434,7 +434,7 @@ class ProviderStore: # Consume the sender's signed-peer-record if sent if not maybe_consume_signed_record(response, self.host, peer_id): logger.error( - "Recieved an invalid-signed-record, ignoring the response" + "Received an invalid-signed-record, ignoring the response" ) return [] @@ -446,7 +446,7 @@ class ProviderStore: # already sent with the provider-proto if not maybe_consume_signed_record(provider_proto, self.host): logger.error( - "Recieved an invalid-signed-record, " + "Received an invalid-signed-record, " "ignoring the response" ) return [] diff --git a/libp2p/kad_dht/utils.py b/libp2p/kad_dht/utils.py index 6c406587..839efb10 100644 --- a/libp2p/kad_dht/utils.py +++ b/libp2p/kad_dht/utils.py @@ -24,6 +24,31 @@ logger = logging.getLogger("kademlia-example.utils") def maybe_consume_signed_record( msg: Message | Message.Peer, host: IHost, peer_id: ID | None = None ) -> bool: + """ + Attempt to parse and store a signed-peer-record (Envelope) received during + DHT communication. If the record is invalid, the peer-id does not match, or + updating the peerstore fails, the function logs an error and returns False. + + Parameters + ---------- + msg : Message | Message.Peer + The protobuf message received during DHT communication. Can either be a + top-level `Message` containing `senderRecord` or a `Message.Peer` + containing `signedRecord`. + host : IHost + The local host instance, providing access to the peerstore for storing + verified peer records. + peer_id : ID | None, optional + The expected peer ID for record validation. If provided, the peer ID + inside the record must match this value. + + Returns + ------- + bool + True if a valid signed peer record was successfully consumed and stored, + False otherwise. + + """ if isinstance(msg, Message): if msg.HasField("senderRecord"): try: @@ -37,13 +62,13 @@ def maybe_consume_signed_record( return False # Use the default TTL of 2 hours (7200 seconds) if not host.get_peerstore().consume_peer_record(envelope, 7200): - logger.error("Updating the certified-addr-book was unsuccessful") + logger.error("Failed to update the Certified-Addr-Book") + return False except Exception as e: - logger.error("Error updating teh certified addr book for peer: %s", e) + logger.error("Failed to update the Certified-Addr-Book: %s", e) return False else: if msg.HasField("signedRecord"): - # TODO: Check in with the Message.Peer id with the record's id try: # Convert the signed-peer-record(Envelope) from # protobuf bytes @@ -56,9 +81,10 @@ def maybe_consume_signed_record( # Use the default TTL of 2 hours (7200 seconds) if not host.get_peerstore().consume_peer_record(envelope, 7200): logger.error("Failed to update the Certified-Addr-Book") + return False except Exception as e: logger.error( - "Error updating the certified-addr-book: %s", + "Failed to update the Certified-Addr-Book: %s", e, ) return False From 091ac082b9e61d77214b10570086885f83ecdde4 Mon Sep 17 00:00:00 2001 From: lla-dane Date: Sat, 23 Aug 2025 15:52:43 +0530 Subject: [PATCH 13/21] Commented out the bool variable from env_to_send_in_RPC() at places --- libp2p/kad_dht/kad_dht.py | 12 ++++++------ libp2p/kad_dht/peer_routing.py | 4 ++-- libp2p/kad_dht/provider_store.py | 4 ++-- libp2p/kad_dht/value_store.py | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/libp2p/kad_dht/kad_dht.py b/libp2p/kad_dht/kad_dht.py index 44787690..701d8415 100644 --- a/libp2p/kad_dht/kad_dht.py +++ b/libp2p/kad_dht/kad_dht.py @@ -323,7 +323,7 @@ class KadDHT(Service): ) # Create sender_signed_peer_record - envelope_bytes, bool = env_to_send_in_RPC(self.host) + envelope_bytes, _ = env_to_send_in_RPC(self.host) response.senderRecord = envelope_bytes # Serialize and send response @@ -394,7 +394,7 @@ class KadDHT(Service): response.key = key # Add sender's signed-peer-record - envelope_bytes, bool = env_to_send_in_RPC(self.host) + envelope_bytes, _ = env_to_send_in_RPC(self.host) response.senderRecord = envelope_bytes response_bytes = response.SerializeToString() @@ -428,7 +428,7 @@ class KadDHT(Service): response.key = key # Create sender_signed_peer_record for the response - envelope_bytes, bool = env_to_send_in_RPC(self.host) + envelope_bytes, _ = env_to_send_in_RPC(self.host) response.senderRecord = envelope_bytes # Add provider information to response @@ -525,7 +525,7 @@ class KadDHT(Service): response.record.timeReceived = str(time.time()) # Create sender_signed_peer_record - envelope_bytes, bool = env_to_send_in_RPC(self.host) + envelope_bytes, _ = env_to_send_in_RPC(self.host) response.senderRecord = envelope_bytes # Serialize and send response @@ -542,7 +542,7 @@ class KadDHT(Service): response.key = key # Create sender_signed_peer_record for the response - envelope_bytes, bool = env_to_send_in_RPC(self.host) + envelope_bytes, _ = env_to_send_in_RPC(self.host) response.senderRecord = envelope_bytes # Add closest peers to key @@ -626,7 +626,7 @@ class KadDHT(Service): response.key = key # Create sender_signed_peer_record for the response - envelope_bytes, bool = env_to_send_in_RPC(self.host) + envelope_bytes, _ = env_to_send_in_RPC(self.host) response.senderRecord = envelope_bytes # Serialize and send response diff --git a/libp2p/kad_dht/peer_routing.py b/libp2p/kad_dht/peer_routing.py index 34b95902..cf96dd7b 100644 --- a/libp2p/kad_dht/peer_routing.py +++ b/libp2p/kad_dht/peer_routing.py @@ -259,7 +259,7 @@ class PeerRouting(IPeerRouting): find_node_msg.key = target_key # Set target key directly as bytes # Create sender_signed_peer_record - envelope_bytes, bool = env_to_send_in_RPC(self.host) + envelope_bytes, _ = env_to_send_in_RPC(self.host) find_node_msg.senderRecord = envelope_bytes # Serialize and send the protobuf message with varint length prefix @@ -393,7 +393,7 @@ class PeerRouting(IPeerRouting): response.type = Message.MessageType.FIND_NODE # Create sender_signed_peer_record for the response - envelope_bytes, bool = env_to_send_in_RPC(self.host) + envelope_bytes, _ = env_to_send_in_RPC(self.host) response.senderRecord = envelope_bytes # Add peer information to response diff --git a/libp2p/kad_dht/provider_store.py b/libp2p/kad_dht/provider_store.py index 3f912ace..fd780840 100644 --- a/libp2p/kad_dht/provider_store.py +++ b/libp2p/kad_dht/provider_store.py @@ -242,7 +242,7 @@ class ProviderStore: message.key = key # Create sender's signed-peer-record - envelope_bytes, bool = env_to_send_in_RPC(self.host) + envelope_bytes, _ = env_to_send_in_RPC(self.host) message.senderRecord = envelope_bytes # Add our provider info @@ -394,7 +394,7 @@ class ProviderStore: message.key = key # Create sender's signed-peer-record - envelope_bytes, bool = env_to_send_in_RPC(self.host) + envelope_bytes, _ = env_to_send_in_RPC(self.host) message.senderRecord = envelope_bytes # Serialize and send the message diff --git a/libp2p/kad_dht/value_store.py b/libp2p/kad_dht/value_store.py index c0241528..7ada100f 100644 --- a/libp2p/kad_dht/value_store.py +++ b/libp2p/kad_dht/value_store.py @@ -112,7 +112,7 @@ class ValueStore: message.type = Message.MessageType.PUT_VALUE # Create sender's signed-peer-record - envelope_bytes, bool = env_to_send_in_RPC(self.host) + envelope_bytes, _ = env_to_send_in_RPC(self.host) message.senderRecord = envelope_bytes # Set message fields @@ -243,7 +243,7 @@ class ValueStore: message.key = key # Create sender's signed-peer-record - envelope_bytes, bool = env_to_send_in_RPC(self.host) + envelope_bytes, _ = env_to_send_in_RPC(self.host) message.senderRecord = envelope_bytes # Serialize and send the protobuf message From 8958c0fac39421a58fc3fe99f1d40a7db2aa1c7d Mon Sep 17 00:00:00 2001 From: lla-dane Date: Sat, 23 Aug 2025 16:05:08 +0530 Subject: [PATCH 14/21] Moved env_to_send_in_RPC function to libp2p/init.py --- libp2p/__init__.py | 70 ++++++++++++++++++++++++++++++++ libp2p/kad_dht/kad_dht.py | 3 +- libp2p/kad_dht/peer_routing.py | 2 +- libp2p/kad_dht/provider_store.py | 3 +- libp2p/kad_dht/utils.py | 29 ------------- libp2p/kad_dht/value_store.py | 3 +- 6 files changed, 77 insertions(+), 33 deletions(-) diff --git a/libp2p/__init__.py b/libp2p/__init__.py index d2ce122a..5942cd2e 100644 --- a/libp2p/__init__.py +++ b/libp2p/__init__.py @@ -49,6 +49,7 @@ from libp2p.peer.id import ( ) from libp2p.peer.peerstore import ( PeerStore, + create_signed_peer_record, ) from libp2p.security.insecure.transport import ( PLAINTEXT_PROTOCOL_ID, @@ -155,6 +156,75 @@ def get_default_muxer_options() -> TMuxerOptions: else: # YAMUX is default return create_yamux_muxer_option() +def env_to_send_in_RPC(host: IHost) -> tuple[bytes, bool]: + """ + Returns the signed peer record (Envelope) to be sent in an RPC, + by checking whether the host already has a cached signed peer record. + If one exists and its addresses match the host's current listen addresses, + the cached envelope is reused. Otherwise, a new signed peer record is created, + cached, and returned. + + Parameters + ---------- + host : IHost + The local host instance, providing access to peer ID, listen addresses, + private key, and the peerstore. + + Returns + ------- + tuple[bytes, bool] + A tuple containing: + - The serialized envelope (bytes) for the signed peer record. + - A boolean flag indicating whether a new record was created (True) + or an existing cached one was reused (False). + + """ + + listen_addrs_set = {addr for addr in host.get_addrs()} + local_env = host.get_peerstore().get_local_record() + + if local_env is None: + # No cached SPR yet -> create one + return issue_and_cache_local_record(host), True + else: + record_addrs_set = local_env._env_addrs_set() + if record_addrs_set == listen_addrs_set: + # Perfect match -> reuse cached envelope + return local_env.marshal_envelope(), False + else: + # Addresses changed -> issue a new SPR and cache it + return issue_and_cache_local_record(host), True + + +def issue_and_cache_local_record(host: IHost) -> bytes: + """ + Create and cache a new signed peer record (Envelope) for the host. + + This function generates a new signed peer record from the host’s peer ID, + listen addresses, and private key. The resulting envelope is stored in + the peerstore as the local record for future reuse. + + Parameters + ---------- + host : IHost + The local host instance, providing access to peer ID, listen addresses, + private key, and the peerstore. + + Returns + ------- + bytes + The serialized envelope (bytes) representing the newly created signed + peer record. + """ + env = create_signed_peer_record( + host.get_id(), + host.get_addrs(), + host.get_private_key(), + ) + # Cache it for nexxt time use + host.get_peerstore().set_local_record(env) + return env.marshal_envelope() + def new_swarm( key_pair: KeyPair | None = None, diff --git a/libp2p/kad_dht/kad_dht.py b/libp2p/kad_dht/kad_dht.py index 701d8415..39de7cc0 100644 --- a/libp2p/kad_dht/kad_dht.py +++ b/libp2p/kad_dht/kad_dht.py @@ -18,11 +18,12 @@ from multiaddr import ( import trio import varint +from libp2p import env_to_send_in_RPC from libp2p.abc import ( IHost, ) from libp2p.discovery.random_walk.rt_refresh_manager import RTRefreshManager -from libp2p.kad_dht.utils import env_to_send_in_RPC, maybe_consume_signed_record +from libp2p.kad_dht.utils import maybe_consume_signed_record from libp2p.network.stream.net_stream import ( INetStream, ) diff --git a/libp2p/kad_dht/peer_routing.py b/libp2p/kad_dht/peer_routing.py index cf96dd7b..9dc18c83 100644 --- a/libp2p/kad_dht/peer_routing.py +++ b/libp2p/kad_dht/peer_routing.py @@ -10,6 +10,7 @@ import logging import trio import varint +from libp2p import env_to_send_in_RPC from libp2p.abc import ( IHost, INetStream, @@ -34,7 +35,6 @@ from .routing_table import ( RoutingTable, ) from .utils import ( - env_to_send_in_RPC, maybe_consume_signed_record, sort_peer_ids_by_distance, ) diff --git a/libp2p/kad_dht/provider_store.py b/libp2p/kad_dht/provider_store.py index fd780840..45be2dba 100644 --- a/libp2p/kad_dht/provider_store.py +++ b/libp2p/kad_dht/provider_store.py @@ -16,13 +16,14 @@ from multiaddr import ( import trio import varint +from libp2p import env_to_send_in_RPC from libp2p.abc import ( IHost, ) from libp2p.custom_types import ( TProtocol, ) -from libp2p.kad_dht.utils import env_to_send_in_RPC, maybe_consume_signed_record +from libp2p.kad_dht.utils import maybe_consume_signed_record from libp2p.peer.id import ( ID, ) diff --git a/libp2p/kad_dht/utils.py b/libp2p/kad_dht/utils.py index 839efb10..fe768723 100644 --- a/libp2p/kad_dht/utils.py +++ b/libp2p/kad_dht/utils.py @@ -12,7 +12,6 @@ from libp2p.peer.envelope import consume_envelope from libp2p.peer.id import ( ID, ) -from libp2p.peer.peerstore import create_signed_peer_record from .pb.kademlia_pb2 import ( Message, @@ -91,34 +90,6 @@ def maybe_consume_signed_record( return True -def env_to_send_in_RPC(host: IHost) -> tuple[bytes, bool]: - listen_addrs_set = {addr for addr in host.get_addrs()} - local_env = host.get_peerstore().get_local_record() - - if local_env is None: - # No cached SPR yet -> create one - return issue_and_cache_local_record(host), True - else: - record_addrs_set = local_env._env_addrs_set() - if record_addrs_set == listen_addrs_set: - # Perfect match -> reuse cached envelope - return local_env.marshal_envelope(), False - else: - # Addresses changed -> issue a new SPR and cache it - return issue_and_cache_local_record(host), True - - -def issue_and_cache_local_record(host: IHost) -> bytes: - env = create_signed_peer_record( - host.get_id(), - host.get_addrs(), - host.get_private_key(), - ) - # Cache it for nexxt time use - host.get_peerstore().set_local_record(env) - return env.marshal_envelope() - - def create_key_from_binary(binary_data: bytes) -> bytes: """ Creates a key for the DHT by hashing binary data with SHA-256. diff --git a/libp2p/kad_dht/value_store.py b/libp2p/kad_dht/value_store.py index 7ada100f..39223f02 100644 --- a/libp2p/kad_dht/value_store.py +++ b/libp2p/kad_dht/value_store.py @@ -9,13 +9,14 @@ import time import varint +from libp2p import env_to_send_in_RPC from libp2p.abc import ( IHost, ) from libp2p.custom_types import ( TProtocol, ) -from libp2p.kad_dht.utils import env_to_send_in_RPC, maybe_consume_signed_record +from libp2p.kad_dht.utils import maybe_consume_signed_record from libp2p.peer.id import ( ID, ) From 5bf9c7b5379357da21b0d900855d7f81c1197dbf Mon Sep 17 00:00:00 2001 From: lla-dane Date: Sat, 23 Aug 2025 16:07:10 +0530 Subject: [PATCH 15/21] Fix spinx error --- libp2p/__init__.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/libp2p/__init__.py b/libp2p/__init__.py index 5942cd2e..e95bacc0 100644 --- a/libp2p/__init__.py +++ b/libp2p/__init__.py @@ -158,11 +158,12 @@ def get_default_muxer_options() -> TMuxerOptions: def env_to_send_in_RPC(host: IHost) -> tuple[bytes, bool]: """ - Returns the signed peer record (Envelope) to be sent in an RPC, - by checking whether the host already has a cached signed peer record. - If one exists and its addresses match the host's current listen addresses, - the cached envelope is reused. Otherwise, a new signed peer record is created, - cached, and returned. + Return the signed peer record (Envelope) to be sent in an RPC. + + This function checks whether the host already has a cached signed peer record + (SPR). If one exists and its addresses match the host's current listen + addresses, the cached envelope is reused. Otherwise, a new signed peer record + is created, cached, and returned. Parameters ---------- @@ -173,13 +174,11 @@ def env_to_send_in_RPC(host: IHost) -> tuple[bytes, bool]: Returns ------- tuple[bytes, bool] - A tuple containing: - - The serialized envelope (bytes) for the signed peer record. - - A boolean flag indicating whether a new record was created (True) - or an existing cached one was reused (False). - + A 2-tuple where the first element is the serialized envelope (bytes) + for the signed peer record, and the second element is a boolean flag + indicating whether a new record was created (True) or an existing cached + one was reused (False). """ - listen_addrs_set = {addr for addr in host.get_addrs()} local_env = host.get_peerstore().get_local_record() From 91bee9df8915817f742f61bac3ff1da04f929167 Mon Sep 17 00:00:00 2001 From: lla-dane Date: Sat, 23 Aug 2025 16:20:24 +0530 Subject: [PATCH 16/21] Moved env_to_send_in_RPC function to libp2p/peer/peerstore.py --- libp2p/__init__.py | 69 ------------------------------ libp2p/kad_dht/kad_dht.py | 2 +- libp2p/kad_dht/peer_routing.py | 2 +- libp2p/kad_dht/provider_store.py | 2 +- libp2p/kad_dht/value_store.py | 2 +- libp2p/peer/peerstore.py | 72 ++++++++++++++++++++++++++++++++ 6 files changed, 76 insertions(+), 73 deletions(-) diff --git a/libp2p/__init__.py b/libp2p/__init__.py index e95bacc0..350ae46b 100644 --- a/libp2p/__init__.py +++ b/libp2p/__init__.py @@ -156,75 +156,6 @@ def get_default_muxer_options() -> TMuxerOptions: else: # YAMUX is default return create_yamux_muxer_option() -def env_to_send_in_RPC(host: IHost) -> tuple[bytes, bool]: - """ - Return the signed peer record (Envelope) to be sent in an RPC. - - This function checks whether the host already has a cached signed peer record - (SPR). If one exists and its addresses match the host's current listen - addresses, the cached envelope is reused. Otherwise, a new signed peer record - is created, cached, and returned. - - Parameters - ---------- - host : IHost - The local host instance, providing access to peer ID, listen addresses, - private key, and the peerstore. - - Returns - ------- - tuple[bytes, bool] - A 2-tuple where the first element is the serialized envelope (bytes) - for the signed peer record, and the second element is a boolean flag - indicating whether a new record was created (True) or an existing cached - one was reused (False). - """ - listen_addrs_set = {addr for addr in host.get_addrs()} - local_env = host.get_peerstore().get_local_record() - - if local_env is None: - # No cached SPR yet -> create one - return issue_and_cache_local_record(host), True - else: - record_addrs_set = local_env._env_addrs_set() - if record_addrs_set == listen_addrs_set: - # Perfect match -> reuse cached envelope - return local_env.marshal_envelope(), False - else: - # Addresses changed -> issue a new SPR and cache it - return issue_and_cache_local_record(host), True - - -def issue_and_cache_local_record(host: IHost) -> bytes: - """ - Create and cache a new signed peer record (Envelope) for the host. - - This function generates a new signed peer record from the host’s peer ID, - listen addresses, and private key. The resulting envelope is stored in - the peerstore as the local record for future reuse. - - Parameters - ---------- - host : IHost - The local host instance, providing access to peer ID, listen addresses, - private key, and the peerstore. - - Returns - ------- - bytes - The serialized envelope (bytes) representing the newly created signed - peer record. - """ - env = create_signed_peer_record( - host.get_id(), - host.get_addrs(), - host.get_private_key(), - ) - # Cache it for nexxt time use - host.get_peerstore().set_local_record(env) - return env.marshal_envelope() - - def new_swarm( key_pair: KeyPair | None = None, muxer_opt: TMuxerOptions | None = None, diff --git a/libp2p/kad_dht/kad_dht.py b/libp2p/kad_dht/kad_dht.py index 39de7cc0..b5064e23 100644 --- a/libp2p/kad_dht/kad_dht.py +++ b/libp2p/kad_dht/kad_dht.py @@ -18,7 +18,6 @@ from multiaddr import ( import trio import varint -from libp2p import env_to_send_in_RPC from libp2p.abc import ( IHost, ) @@ -34,6 +33,7 @@ from libp2p.peer.id import ( from libp2p.peer.peerinfo import ( PeerInfo, ) +from libp2p.peer.peerstore import env_to_send_in_RPC from libp2p.tools.async_service import ( Service, ) diff --git a/libp2p/kad_dht/peer_routing.py b/libp2p/kad_dht/peer_routing.py index 9dc18c83..195209a2 100644 --- a/libp2p/kad_dht/peer_routing.py +++ b/libp2p/kad_dht/peer_routing.py @@ -10,7 +10,6 @@ import logging import trio import varint -from libp2p import env_to_send_in_RPC from libp2p.abc import ( IHost, INetStream, @@ -23,6 +22,7 @@ from libp2p.peer.id import ( from libp2p.peer.peerinfo import ( PeerInfo, ) +from libp2p.peer.peerstore import env_to_send_in_RPC from .common import ( ALPHA, diff --git a/libp2p/kad_dht/provider_store.py b/libp2p/kad_dht/provider_store.py index 45be2dba..77bb464f 100644 --- a/libp2p/kad_dht/provider_store.py +++ b/libp2p/kad_dht/provider_store.py @@ -16,7 +16,6 @@ from multiaddr import ( import trio import varint -from libp2p import env_to_send_in_RPC from libp2p.abc import ( IHost, ) @@ -30,6 +29,7 @@ from libp2p.peer.id import ( from libp2p.peer.peerinfo import ( PeerInfo, ) +from libp2p.peer.peerstore import env_to_send_in_RPC from .common import ( ALPHA, diff --git a/libp2p/kad_dht/value_store.py b/libp2p/kad_dht/value_store.py index 39223f02..2002965f 100644 --- a/libp2p/kad_dht/value_store.py +++ b/libp2p/kad_dht/value_store.py @@ -9,7 +9,6 @@ import time import varint -from libp2p import env_to_send_in_RPC from libp2p.abc import ( IHost, ) @@ -20,6 +19,7 @@ from libp2p.kad_dht.utils import maybe_consume_signed_record from libp2p.peer.id import ( ID, ) +from libp2p.peer.peerstore import env_to_send_in_RPC from .common import ( DEFAULT_TTL, diff --git a/libp2p/peer/peerstore.py b/libp2p/peer/peerstore.py index ad6f08db..993a8523 100644 --- a/libp2p/peer/peerstore.py +++ b/libp2p/peer/peerstore.py @@ -16,6 +16,7 @@ import trio from trio import MemoryReceiveChannel, MemorySendChannel from libp2p.abc import ( + IHost, IPeerStore, ) from libp2p.crypto.keys import ( @@ -49,6 +50,77 @@ def create_signed_peer_record( return envelope +def env_to_send_in_RPC(host: IHost) -> tuple[bytes, bool]: + """ + Return the signed peer record (Envelope) to be sent in an RPC. + + This function checks whether the host already has a cached signed peer record + (SPR). If one exists and its addresses match the host's current listen + addresses, the cached envelope is reused. Otherwise, a new signed peer record + is created, cached, and returned. + + Parameters + ---------- + host : IHost + The local host instance, providing access to peer ID, listen addresses, + private key, and the peerstore. + + Returns + ------- + tuple[bytes, bool] + A 2-tuple where the first element is the serialized envelope (bytes) + for the signed peer record, and the second element is a boolean flag + indicating whether a new record was created (True) or an existing cached + one was reused (False). + + """ + listen_addrs_set = {addr for addr in host.get_addrs()} + local_env = host.get_peerstore().get_local_record() + + if local_env is None: + # No cached SPR yet -> create one + return issue_and_cache_local_record(host), True + else: + record_addrs_set = local_env._env_addrs_set() + if record_addrs_set == listen_addrs_set: + # Perfect match -> reuse cached envelope + return local_env.marshal_envelope(), False + else: + # Addresses changed -> issue a new SPR and cache it + return issue_and_cache_local_record(host), True + + +def issue_and_cache_local_record(host: IHost) -> bytes: + """ + Create and cache a new signed peer record (Envelope) for the host. + + This function generates a new signed peer record from the host’s peer ID, + listen addresses, and private key. The resulting envelope is stored in + the peerstore as the local record for future reuse. + + Parameters + ---------- + host : IHost + The local host instance, providing access to peer ID, listen addresses, + private key, and the peerstore. + + Returns + ------- + bytes + The serialized envelope (bytes) representing the newly created signed + peer record. + + """ + env = create_signed_peer_record( + host.get_id(), + host.get_addrs(), + host.get_private_key(), + ) + # Cache it for nexxt time use + host.get_peerstore().set_local_record(env) + return env.marshal_envelope() + + class PeerRecordState: envelope: Envelope seq: int From 7b2d637382d34be4e1ba6621f080965fba5eb5aa Mon Sep 17 00:00:00 2001 From: lla-dane Date: Sat, 23 Aug 2025 16:30:34 +0530 Subject: [PATCH 17/21] Now using env_to_send_in_RPC for issuing records in Identify rpc messages --- libp2p/identity/identify/identify.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/libp2p/identity/identify/identify.py b/libp2p/identity/identify/identify.py index 4e8931ba..146fbd2d 100644 --- a/libp2p/identity/identify/identify.py +++ b/libp2p/identity/identify/identify.py @@ -15,7 +15,7 @@ from libp2p.custom_types import ( from libp2p.network.stream.exceptions import ( StreamClosed, ) -from libp2p.peer.peerstore import create_signed_peer_record +from libp2p.peer.peerstore import env_to_send_in_RPC from libp2p.utils import ( decode_varint_with_size, get_agent_version, @@ -65,11 +65,7 @@ def _mk_identify_protobuf( protocols = tuple(str(p) for p in host.get_mux().get_protocols() if p is not None) # Create a signed peer-record for the remote peer - envelope = create_signed_peer_record( - host.get_id(), - host.get_addrs(), - host.get_private_key(), - ) + envelope_bytes, _ = env_to_send_in_RPC(host) observed_addr = observed_multiaddr.to_bytes() if observed_multiaddr else b"" return Identify( @@ -79,7 +75,7 @@ def _mk_identify_protobuf( listen_addrs=map(_multiaddr_to_bytes, laddrs), observed_addr=observed_addr, protocols=protocols, - signedPeerRecord=envelope.marshal_envelope(), + signedPeerRecord=envelope_bytes, ) From fe3f7adc1b66f0e97a45f257ff353259354e1147 Mon Sep 17 00:00:00 2001 From: lla-dane Date: Tue, 26 Aug 2025 12:49:33 +0530 Subject: [PATCH 18/21] fix typos --- libp2p/kad_dht/peer_routing.py | 2 +- libp2p/peer/peerstore.py | 2 +- tests/core/kad_dht/test_kad_dht.py | 14 +++++++------- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/libp2p/kad_dht/peer_routing.py b/libp2p/kad_dht/peer_routing.py index 195209a2..f5313cb6 100644 --- a/libp2p/kad_dht/peer_routing.py +++ b/libp2p/kad_dht/peer_routing.py @@ -376,7 +376,7 @@ class PeerRouting(IPeerRouting): # Consume the sender's signed-peer-record if sent if not maybe_consume_signed_record(kad_message, self.host, peer_id): logger.error( - "Receivedf an invalid-signed-record, dropping the stream" + "Received an invalid-signed-record, dropping the stream" ) return diff --git a/libp2p/peer/peerstore.py b/libp2p/peer/peerstore.py index 993a8523..ddf1af1f 100644 --- a/libp2p/peer/peerstore.py +++ b/libp2p/peer/peerstore.py @@ -116,7 +116,7 @@ def issue_and_cache_local_record(host: IHost) -> bytes: host.get_addrs(), host.get_private_key(), ) - # Cache it for nexxt time use + # Cache it for next time use host.get_peerstore().set_local_record(env) return env.marshal_envelope() diff --git a/tests/core/kad_dht/test_kad_dht.py b/tests/core/kad_dht/test_kad_dht.py index 0d9a29f7..285268d9 100644 --- a/tests/core/kad_dht/test_kad_dht.py +++ b/tests/core/kad_dht/test_kad_dht.py @@ -109,7 +109,7 @@ async def test_find_node(dht_pair: tuple[KadDHT, KadDHT]): dht_a.host.get_peerstore().get_peer_record(dht_b.host.get_id()), Envelope ) - # These are the records that were sent betweeen the peers during the FIND_NODE req + # These are the records that were sent between the peers during the FIND_NODE req envelope_a_find_peer = dht_a.host.get_peerstore().get_peer_record( dht_b.host.get_id() ) @@ -124,7 +124,7 @@ async def test_find_node(dht_pair: tuple[KadDHT, KadDHT]): record_b_find_peer = envelope_b_find_peer.record() # This proves that both the records are same, and a latest cached signed record - # was passed between the peers during FIND_NODE exceution, which proves the + # was passed between the peers during FIND_NODE execution, which proves the # signed-record transfer/re-issuing works correctly in FIND_NODE executions. assert record_a.seq == record_a_find_peer.seq assert record_b.seq == record_b_find_peer.seq @@ -168,7 +168,7 @@ async def test_put_and_get_value(dht_pair: tuple[KadDHT, KadDHT]): with trio.fail_after(TEST_TIMEOUT): await dht_a.put_value(key, value) - # These are the records that were sent betweeen the peers during the PUT_VALUE req + # These are the records that were sent between the peers during the PUT_VALUE req envelope_a_put_value = dht_a.host.get_peerstore().get_peer_record( dht_b.host.get_id() ) @@ -183,7 +183,7 @@ async def test_put_and_get_value(dht_pair: tuple[KadDHT, KadDHT]): record_b_put_value = envelope_b_put_value.record() # This proves that both the records are same, and a latest cached signed record - # was passed between the peers during PUT_VALUE exceution, which proves the + # was passed between the peers during PUT_VALUE execution, which proves the # signed-record transfer/re-issuing works correctly in PUT_VALUE executions. assert record_a.seq == record_a_put_value.seq assert record_b.seq == record_b_put_value.seq @@ -205,7 +205,7 @@ async def test_put_and_get_value(dht_pair: tuple[KadDHT, KadDHT]): print("the value stored in node b is", dht_b.get_value_store_size()) logger.debug("Retrieved value: %s", retrieved_value) - # These are the records that were sent betweeen the peers during the PUT_VALUE req + # These are the records that were sent between the peers during the PUT_VALUE req envelope_a_get_value = dht_a.host.get_peerstore().get_peer_record( dht_b.host.get_id() ) @@ -257,7 +257,7 @@ async def test_provide_and_find_providers(dht_pair: tuple[KadDHT, KadDHT]): success = await dht_a.provide(content_id) assert success, "Failed to advertise as provider" - # These are the records that were sent betweeen the peers during + # These are the records that were sent between the peers during # the ADD_PROVIDER req envelope_a_add_prov = dht_a.host.get_peerstore().get_peer_record( dht_b.host.get_id() @@ -273,7 +273,7 @@ async def test_provide_and_find_providers(dht_pair: tuple[KadDHT, KadDHT]): record_b_add_prov = envelope_b_add_prov.record() # This proves that both the records are same, the latest cached signed record - # was passed between the peers during ADD_PROVIDER exceution, which proves the + # was passed between the peers during ADD_PROVIDER execution, which proves the # signed-record transfer/re-issuing of the latest record works correctly in # ADD_PROVIDER executions. assert record_a.seq == record_a_add_prov.seq From 2006b2c92cf24263336e50d4e2ccb6b056716f75 Mon Sep 17 00:00:00 2001 From: lla-dane Date: Tue, 26 Aug 2025 12:59:18 +0530 Subject: [PATCH 19/21] added newsfragment --- newsfragments/815.feature.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 newsfragments/815.feature.rst diff --git a/newsfragments/815.feature.rst b/newsfragments/815.feature.rst new file mode 100644 index 00000000..8fcf6fea --- /dev/null +++ b/newsfragments/815.feature.rst @@ -0,0 +1 @@ +KAD-DHT now include signed-peer-records in its protobuf message schema, for more secure peer-discovery. From 943bcc4d36455026e08152b06f967eafe4df2e6f Mon Sep 17 00:00:00 2001 From: lla-dane Date: Wed, 27 Aug 2025 10:17:40 +0530 Subject: [PATCH 20/21] fix the logic error in add_provider handling --- libp2p/kad_dht/kad_dht.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libp2p/kad_dht/kad_dht.py b/libp2p/kad_dht/kad_dht.py index b5064e23..0d05aaf8 100644 --- a/libp2p/kad_dht/kad_dht.py +++ b/libp2p/kad_dht/kad_dht.py @@ -378,7 +378,7 @@ class KadDHT(Service): # Process the signed-records of provider if sent if not maybe_consume_signed_record( - message, self.host, peer_id + provider_proto, self.host ): logger.error( "Received an invalid-signed-record," From c2c4228591eadac7bb0d1c3bdd5aa0a697fe7d7f Mon Sep 17 00:00:00 2001 From: lla-dane Date: Wed, 27 Aug 2025 13:02:32 +0530 Subject: [PATCH 21/21] added test for ADD_PROVIDER record processing --- tests/core/kad_dht/test_kad_dht.py | 36 ++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/core/kad_dht/test_kad_dht.py b/tests/core/kad_dht/test_kad_dht.py index 285268d9..5bf4f3e8 100644 --- a/tests/core/kad_dht/test_kad_dht.py +++ b/tests/core/kad_dht/test_kad_dht.py @@ -31,6 +31,7 @@ from libp2p.peer.peer_record import PeerRecord from libp2p.peer.peerinfo import ( PeerInfo, ) +from libp2p.peer.peerstore import create_signed_peer_record from libp2p.tools.async_service import ( background_trio_service, ) @@ -340,6 +341,41 @@ async def test_provide_and_find_providers(dht_pair: tuple[KadDHT, KadDHT]): assert record_a_find_prov.seq == record_a_get_value.seq assert record_b_find_prov.seq == record_b_get_value.seq + # Create a new provider record in dht_a + provider_key_pair = create_new_key_pair() + provider_peer_id = ID.from_pubkey(provider_key_pair.public_key) + provider_addr = multiaddr.Multiaddr("/ip4/127.0.0.1/tcp/123") + provider_peer_info = PeerInfo(peer_id=provider_peer_id, addrs=[provider_addr]) + + # Generate a random content ID + content_2 = f"random-content-{uuid.uuid4()}".encode() + content_id_2 = hashlib.sha256(content_2).digest() + + provider_signed_envelope = create_signed_peer_record( + provider_peer_id, [provider_addr], provider_key_pair.private_key + ) + assert ( + dht_a.host.get_peerstore().consume_peer_record(provider_signed_envelope, 7200) + is True + ) + + # Store this provider record in dht_a + dht_a.provider_store.add_provider(content_id_2, provider_peer_info) + + # Fetch the provider-record via peer-discovery at dht_b's end + peerinfo = await dht_b.provider_store.find_providers(content_id_2) + + assert len(peerinfo) == 1 + assert peerinfo[0].peer_id == provider_peer_id + provider_envelope = dht_b.host.get_peerstore().get_peer_record(provider_peer_id) + + # This proves that the signed-envelope of provider is consumed on dht_b's end + assert provider_envelope is not None + assert ( + provider_signed_envelope.marshal_envelope() + == provider_envelope.marshal_envelope() + ) + @pytest.mark.trio async def test_reissue_when_listen_addrs_change(dht_pair: tuple[KadDHT, KadDHT]):