2019-04-19 07:21:43 +08:00
|
|
|
import heapq
|
2019-04-20 08:44:17 +08:00
|
|
|
import random
|
2019-04-20 08:00:23 +08:00
|
|
|
|
2019-04-19 07:21:43 +08:00
|
|
|
from operator import itemgetter
|
2019-04-20 08:44:17 +08:00
|
|
|
from multiaddr import Multiaddr
|
2019-04-19 07:21:43 +08:00
|
|
|
from libp2p.peer.peerinfo import PeerInfo
|
2019-04-20 08:44:17 +08:00
|
|
|
from libp2p.peer.id import ID
|
|
|
|
from libp2p.peer.peerdata import PeerData
|
2019-04-20 08:00:23 +08:00
|
|
|
from .utils import digest
|
2019-04-19 07:21:43 +08:00
|
|
|
|
2019-04-20 08:00:23 +08:00
|
|
|
P_IP = "ip4"
|
|
|
|
P_UDP = "udp"
|
2019-04-19 07:21:43 +08:00
|
|
|
|
2019-08-01 06:00:12 +08:00
|
|
|
|
2019-04-19 07:21:43 +08:00
|
|
|
class KadPeerInfo(PeerInfo):
|
2019-04-20 04:29:15 +08:00
|
|
|
def __init__(self, peer_id, peer_data=None):
|
2019-04-19 07:21:43 +08:00
|
|
|
super(KadPeerInfo, self).__init__(peer_id, peer_data)
|
2019-04-20 08:00:23 +08:00
|
|
|
|
2019-07-31 23:50:53 +08:00
|
|
|
self.peer_id_bytes = peer_id.to_bytes()
|
2019-07-31 19:26:13 +08:00
|
|
|
self.xor_id = peer_id.xor_id
|
2019-04-20 08:00:23 +08:00
|
|
|
|
|
|
|
self.addrs = peer_data.get_addrs() if peer_data else None
|
|
|
|
|
|
|
|
# pylint: disable=invalid-name
|
2019-08-01 06:00:12 +08:00
|
|
|
self.ip = self.addrs[0].value_for_protocol(P_IP) if peer_data else None
|
|
|
|
self.port = int(self.addrs[0].value_for_protocol(P_UDP)) if peer_data else None
|
2019-04-19 07:21:43 +08:00
|
|
|
|
|
|
|
def same_home_as(self, node):
|
2019-04-20 09:00:55 +08:00
|
|
|
return sorted(self.addrs) == sorted(node.addrs)
|
2019-04-19 07:21:43 +08:00
|
|
|
|
|
|
|
def distance_to(self, node):
|
|
|
|
"""
|
|
|
|
Get the distance between this node and another.
|
|
|
|
"""
|
2019-04-25 10:11:54 +08:00
|
|
|
return self.xor_id ^ node.xor_id
|
2019-04-19 07:21:43 +08:00
|
|
|
|
|
|
|
def __iter__(self):
|
|
|
|
"""
|
|
|
|
Enables use of Node as a tuple - i.e., tuple(node) works.
|
|
|
|
"""
|
2019-07-31 23:50:53 +08:00
|
|
|
return iter([self.peer_id_bytes, self.ip, self.port])
|
2019-04-19 07:21:43 +08:00
|
|
|
|
|
|
|
def __repr__(self):
|
2019-07-31 23:50:53 +08:00
|
|
|
return repr([self.xor_id, self.ip, self.port, self.peer_id_bytes])
|
2019-04-19 07:21:43 +08:00
|
|
|
|
|
|
|
def __str__(self):
|
2019-04-20 08:00:23 +08:00
|
|
|
return "%s:%s" % (self.ip, str(self.port))
|
2019-04-19 07:21:43 +08:00
|
|
|
|
2019-04-29 05:38:40 +08:00
|
|
|
def encode(self):
|
2019-08-01 06:00:12 +08:00
|
|
|
return (
|
2019-07-31 23:50:53 +08:00
|
|
|
str(self.peer_id_bytes)
|
2019-08-01 06:00:12 +08:00
|
|
|
+ "\n"
|
|
|
|
+ str("/ip4/" + str(self.ip) + "/udp/" + str(self.port))
|
|
|
|
)
|
|
|
|
|
2019-04-29 05:38:40 +08:00
|
|
|
|
2019-04-19 07:21:43 +08:00
|
|
|
class KadPeerHeap:
|
|
|
|
"""
|
|
|
|
A heap of peers ordered by distance to a given node.
|
|
|
|
"""
|
2019-08-01 06:00:12 +08:00
|
|
|
|
2019-04-19 07:21:43 +08:00
|
|
|
def __init__(self, node, maxsize):
|
|
|
|
"""
|
|
|
|
Constructor.
|
|
|
|
|
|
|
|
@param node: The node to measure all distnaces from.
|
|
|
|
@param maxsize: The maximum size that this heap can grow to.
|
|
|
|
"""
|
|
|
|
self.node = node
|
|
|
|
self.heap = []
|
|
|
|
self.contacted = set()
|
|
|
|
self.maxsize = maxsize
|
|
|
|
|
|
|
|
def remove(self, peers):
|
|
|
|
"""
|
|
|
|
Remove a list of peer ids from this heap. Note that while this
|
|
|
|
heap retains a constant visible size (based on the iterator), it's
|
|
|
|
actual size may be quite a bit larger than what's exposed. Therefore,
|
|
|
|
removal of nodes may not change the visible size as previously added
|
|
|
|
nodes suddenly become visible.
|
|
|
|
"""
|
|
|
|
peers = set(peers)
|
|
|
|
if not peers:
|
|
|
|
return
|
|
|
|
nheap = []
|
|
|
|
for distance, node in self.heap:
|
2019-07-31 23:50:53 +08:00
|
|
|
if node.peer_id_bytes not in peers:
|
2019-04-19 07:21:43 +08:00
|
|
|
heapq.heappush(nheap, (distance, node))
|
|
|
|
self.heap = nheap
|
|
|
|
|
|
|
|
def get_node(self, node_id):
|
|
|
|
for _, node in self.heap:
|
2019-07-31 23:50:53 +08:00
|
|
|
if node.peer_id_bytes == node_id:
|
2019-04-19 07:21:43 +08:00
|
|
|
return node
|
|
|
|
return None
|
|
|
|
|
|
|
|
def have_contacted_all(self):
|
|
|
|
return len(self.get_uncontacted()) == 0
|
|
|
|
|
|
|
|
def get_ids(self):
|
2019-07-31 23:50:53 +08:00
|
|
|
return [n.peer_id_bytes for n in self]
|
2019-04-19 07:21:43 +08:00
|
|
|
|
|
|
|
def mark_contacted(self, node):
|
2019-07-31 23:50:53 +08:00
|
|
|
self.contacted.add(node.peer_id_bytes)
|
2019-04-19 07:21:43 +08:00
|
|
|
|
|
|
|
def popleft(self):
|
|
|
|
return heapq.heappop(self.heap)[1] if self else None
|
|
|
|
|
|
|
|
def push(self, nodes):
|
|
|
|
"""
|
|
|
|
Push nodes onto heap.
|
|
|
|
|
|
|
|
@param nodes: This can be a single item or a C{list}.
|
|
|
|
"""
|
|
|
|
if not isinstance(nodes, list):
|
|
|
|
nodes = [nodes]
|
|
|
|
|
|
|
|
for node in nodes:
|
|
|
|
if node not in self:
|
|
|
|
distance = self.node.distance_to(node)
|
|
|
|
heapq.heappush(self.heap, (distance, node))
|
|
|
|
|
|
|
|
def __len__(self):
|
|
|
|
return min(len(self.heap), self.maxsize)
|
|
|
|
|
|
|
|
def __iter__(self):
|
|
|
|
nodes = heapq.nsmallest(self.maxsize, self.heap)
|
|
|
|
return iter(map(itemgetter(1), nodes))
|
|
|
|
|
|
|
|
def __contains__(self, node):
|
|
|
|
for _, other in self.heap:
|
2019-07-31 23:50:53 +08:00
|
|
|
if node.peer_id_bytes == other.peer_id_bytes:
|
2019-04-19 07:21:43 +08:00
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
|
|
def get_uncontacted(self):
|
2019-07-31 23:50:53 +08:00
|
|
|
return [n for n in self if n.peer_id_bytes not in self.contacted]
|
2019-04-20 08:44:17 +08:00
|
|
|
|
2019-08-01 13:25:20 +08:00
|
|
|
|
2019-07-31 21:35:50 +08:00
|
|
|
def create_kad_peerinfo(node_id_bytes=None, sender_ip=None, sender_port=None):
|
2019-08-01 13:25:20 +08:00
|
|
|
node_id = (
|
|
|
|
ID(node_id_bytes) if node_id_bytes else ID(digest(random.getrandbits(255)))
|
|
|
|
)
|
2019-04-20 08:44:17 +08:00
|
|
|
peer_data = None
|
|
|
|
if sender_ip and sender_port:
|
2019-08-01 06:00:12 +08:00
|
|
|
peer_data = PeerData() # pylint: disable=no-value-for-parameter
|
|
|
|
addr = [
|
|
|
|
Multiaddr(
|
|
|
|
"/" + P_IP + "/" + str(sender_ip) + "/" + P_UDP + "/" + str(sender_port)
|
|
|
|
)
|
|
|
|
]
|
2019-04-20 08:44:17 +08:00
|
|
|
peer_data.add_addrs(addr)
|
|
|
|
|
|
|
|
return KadPeerInfo(node_id, peer_data)
|