datastructures.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. """
  2. Originally taken from: https://github.com/mhchia/py-libp2p-daemon-bindings
  3. Licence: MIT
  4. Author: Kevin Mai-Husan Chia
  5. """
  6. import hashlib
  7. from typing import Any, Sequence, Union
  8. import base58
  9. import multihash
  10. from multiaddr import Multiaddr, protocols
  11. from hivemind.proto import p2pd_pb2
  12. # NOTE: On inlining...
  13. # See: https://github.com/libp2p/specs/issues/138
  14. # NOTE: enabling to be interoperable w/ the Go implementation
  15. ENABLE_INLINING = True
  16. MAX_INLINE_KEY_LENGTH = 42
  17. IDENTITY_MULTIHASH_CODE = 0x00
  18. if ENABLE_INLINING:
  19. class IdentityHash:
  20. def __init__(self) -> None:
  21. self._digest = bytearray()
  22. def update(self, input: bytes) -> None:
  23. self._digest += input
  24. def digest(self) -> bytes:
  25. return self._digest
  26. multihash.FuncReg.register(IDENTITY_MULTIHASH_CODE, "identity", hash_new=IdentityHash)
  27. class PeerID:
  28. def __init__(self, peer_id_bytes: bytes) -> None:
  29. self._bytes = peer_id_bytes
  30. self._xor_id = int(sha256_digest(self._bytes).hex(), 16)
  31. self._b58_str = base58.b58encode(self._bytes).decode()
  32. @property
  33. def xor_id(self) -> int:
  34. return self._xor_id
  35. def to_bytes(self) -> bytes:
  36. return self._bytes
  37. def to_base58(self) -> str:
  38. return self._b58_str
  39. def __repr__(self) -> str:
  40. return f"<libp2p.peer.id.ID ({self.to_base58()})>"
  41. def __str__(self):
  42. return self.to_base58()
  43. def pretty(self):
  44. return self.to_base58()
  45. def to_string(self):
  46. return self.to_base58()
  47. def __eq__(self, other: object) -> bool:
  48. if isinstance(other, str):
  49. return self.to_base58() == other
  50. elif isinstance(other, bytes):
  51. return self._bytes == other
  52. elif isinstance(other, PeerID):
  53. return self._bytes == other._bytes
  54. else:
  55. return False
  56. def __lt__(self, other: object) -> bool:
  57. if not isinstance(other, PeerID):
  58. raise TypeError(f"'<' not supported between instances of 'PeerID' and '{type(other)}'")
  59. return self.to_base58() < other.to_base58()
  60. def __hash__(self) -> int:
  61. return hash(self._bytes)
  62. @classmethod
  63. def from_base58(cls, base58_id: str) -> "PeerID":
  64. peer_id_bytes = base58.b58decode(base58_id)
  65. return cls(peer_id_bytes)
  66. def sha256_digest(data: Union[str, bytes]) -> bytes:
  67. if isinstance(data, str):
  68. data = data.encode("utf8")
  69. return hashlib.sha256(data).digest()
  70. class StreamInfo:
  71. def __init__(self, peer_id: PeerID, addr: Multiaddr, proto: str) -> None:
  72. self.peer_id = peer_id
  73. self.addr = addr
  74. self.proto = proto
  75. def __repr__(self) -> str:
  76. return f"<StreamInfo peer_id={self.peer_id} addr={self.addr} proto={self.proto}>"
  77. def to_protobuf(self) -> p2pd_pb2.StreamInfo:
  78. pb_msg = p2pd_pb2.StreamInfo(peer=self.peer_id.to_bytes(), addr=self.addr.to_bytes(), proto=self.proto)
  79. return pb_msg
  80. @classmethod
  81. def from_protobuf(cls, pb_msg: p2pd_pb2.StreamInfo) -> "StreamInfo":
  82. stream_info = cls(peer_id=PeerID(pb_msg.peer), addr=Multiaddr(pb_msg.addr), proto=pb_msg.proto)
  83. return stream_info
  84. class PeerInfo:
  85. def __init__(self, peer_id: PeerID, addrs: Sequence[Multiaddr]) -> None:
  86. self.peer_id = peer_id
  87. self.addrs = list(addrs)
  88. def __eq__(self, other: Any) -> bool:
  89. return isinstance(other, PeerInfo) and self.peer_id == other.peer_id and self.addrs == other.addrs
  90. @classmethod
  91. def from_protobuf(cls, peer_info_pb: p2pd_pb2.PeerInfo) -> "PeerInfo":
  92. peer_id = PeerID(peer_info_pb.id)
  93. addrs = [Multiaddr(addr) for addr in peer_info_pb.addrs]
  94. return PeerInfo(peer_id, addrs)
  95. def __str__(self):
  96. return f"{self.peer_id.pretty()} {','.join(str(a) for a in self.addrs)}"
  97. def __repr__(self):
  98. return f"PeerInfo(peer_id={repr(self.peer_id)}, addrs={repr(self.addrs)})"
  99. class InvalidAddrError(ValueError):
  100. pass
  101. def info_from_p2p_addr(addr: Multiaddr) -> PeerInfo:
  102. if addr is None:
  103. raise InvalidAddrError("`addr` should not be `None`")
  104. parts = addr.split()
  105. if not parts:
  106. raise InvalidAddrError(f"`parts`={parts} should at least have a protocol `P_P2P`")
  107. p2p_part = parts[-1]
  108. last_protocol_code = p2p_part.protocols()[0].code
  109. if last_protocol_code != protocols.P_P2P:
  110. raise InvalidAddrError(f"The last protocol should be `P_P2P` instead of `{last_protocol_code}`")
  111. # make sure the /p2p value parses as a peer.ID
  112. peer_id_str: str = p2p_part.value_for_protocol(protocols.P_P2P)
  113. peer_id = PeerID.from_base58(peer_id_str)
  114. # we might have received just an / p2p part, which means there's no addr.
  115. if len(parts) > 1:
  116. addr = Multiaddr.join(*parts[:-1])
  117. return PeerInfo(peer_id, [addr])