@@ -218,6 +218,10 @@ def __init__(self, loop: asyncio.AbstractEventLoop, protocol: 'KademliaProtocol'
218218 def running (self ):
219219 return self ._running
220220
221+ @property
222+ def busy (self ):
223+ return self ._running and (any (self ._running_pings ) or any (self ._pending_contacts ))
224+
221225 def enqueue_maybe_ping (self , * peers : 'KademliaPeer' , delay : typing .Optional [float ] = None ):
222226 delay = delay if delay is not None else self ._default_delay
223227 now = self ._loop .time ()
@@ -229,7 +233,7 @@ def maybe_ping(self, peer: 'KademliaPeer'):
229233 async def ping_task ():
230234 try :
231235 if self ._protocol .peer_manager .peer_is_good (peer ):
232- if peer not in self ._protocol .routing_table .get_peers ( ):
236+ if not self ._protocol .routing_table .get_peer ( peer . node_id ):
233237 self ._protocol .add_peer (peer )
234238 return
235239 await self ._protocol .get_rpc_peer (peer ).ping ()
@@ -294,7 +298,7 @@ class KademliaProtocol(DatagramProtocol):
294298
295299 def __init__ (self , loop : asyncio .AbstractEventLoop , peer_manager : 'PeerManager' , node_id : bytes , external_ip : str ,
296300 udp_port : int , peer_port : int , rpc_timeout : float = constants .RPC_TIMEOUT ,
297- split_buckets_under_index : int = constants .SPLIT_BUCKETS_UNDER_INDEX ):
301+ split_buckets_under_index : int = constants .SPLIT_BUCKETS_UNDER_INDEX , is_boostrap_node : bool = False ):
298302 self .peer_manager = peer_manager
299303 self .loop = loop
300304 self .node_id = node_id
@@ -309,7 +313,8 @@ def __init__(self, loop: asyncio.AbstractEventLoop, peer_manager: 'PeerManager',
309313 self .transport : DatagramTransport = None
310314 self .old_token_secret = constants .generate_id ()
311315 self .token_secret = constants .generate_id ()
312- self .routing_table = TreeRoutingTable (self .loop , self .peer_manager , self .node_id , split_buckets_under_index )
316+ self .routing_table = TreeRoutingTable (
317+ self .loop , self .peer_manager , self .node_id , split_buckets_under_index , is_bootstrap_node = is_boostrap_node )
313318 self .data_store = DictDataStore (self .loop , self .peer_manager )
314319 self .ping_queue = PingQueue (self .loop , self )
315320 self .node_rpc = KademliaRPC (self , self .loop , self .peer_port )
@@ -356,72 +361,10 @@ def _migrate_incoming_rpc_args(peer: 'KademliaPeer', method: bytes, *args) -> ty
356361 return args , {}
357362
358363 async def _add_peer (self , peer : 'KademliaPeer' ):
359- if not peer .node_id :
360- log .warning ("Tried adding a peer with no node id!" )
361- return False
362- for my_peer in self .routing_table .get_peers ():
363- if (my_peer .address , my_peer .udp_port ) == (peer .address , peer .udp_port ) and my_peer .node_id != peer .node_id :
364- self .routing_table .remove_peer (my_peer )
365- self .routing_table .join_buckets ()
366- bucket_index = self .routing_table .kbucket_index (peer .node_id )
367- if self .routing_table .buckets [bucket_index ].add_peer (peer ):
368- return True
369-
370- # The bucket is full; see if it can be split (by checking if its range includes the host node's node_id)
371- if self .routing_table .should_split (bucket_index , peer .node_id ):
372- self .routing_table .split_bucket (bucket_index )
373- # Retry the insertion attempt
374- result = await self ._add_peer (peer )
375- self .routing_table .join_buckets ()
376- return result
377- else :
378- # We can't split the k-bucket
379- #
380- # The 13 page kademlia paper specifies that the least recently contacted node in the bucket
381- # shall be pinged. If it fails to reply it is replaced with the new contact. If the ping is successful
382- # the new contact is ignored and not added to the bucket (sections 2.2 and 2.4).
383- #
384- # A reasonable extension to this is BEP 0005, which extends the above:
385- #
386- # Not all nodes that we learn about are equal. Some are "good" and some are not.
387- # Many nodes using the DHT are able to send queries and receive responses,
388- # but are not able to respond to queries from other nodes. It is important that
389- # each node's routing table must contain only known good nodes. A good node is
390- # a node has responded to one of our queries within the last 15 minutes. A node
391- # is also good if it has ever responded to one of our queries and has sent us a
392- # query within the last 15 minutes. After 15 minutes of inactivity, a node becomes
393- # questionable. Nodes become bad when they fail to respond to multiple queries
394- # in a row. Nodes that we know are good are given priority over nodes with unknown status.
395- #
396- # When there are bad or questionable nodes in the bucket, the least recent is selected for
397- # potential replacement (BEP 0005). When all nodes in the bucket are fresh, the head (least recent)
398- # contact is selected as described in section 2.2 of the kademlia paper. In both cases the new contact
399- # is ignored if the pinged node replies.
400-
401- not_good_contacts = self .routing_table .buckets [bucket_index ].get_bad_or_unknown_peers ()
402- not_recently_replied = []
403- for my_peer in not_good_contacts :
404- last_replied = self .peer_manager .get_last_replied (my_peer .address , my_peer .udp_port )
405- if not last_replied or last_replied + 60 < self .loop .time ():
406- not_recently_replied .append (my_peer )
407- if not_recently_replied :
408- to_replace = not_recently_replied [0 ]
409- else :
410- to_replace = self .routing_table .buckets [bucket_index ].peers [0 ]
411- last_replied = self .peer_manager .get_last_replied (to_replace .address , to_replace .udp_port )
412- if last_replied and last_replied + 60 > self .loop .time ():
413- return False
414- log .debug ("pinging %s:%s" , to_replace .address , to_replace .udp_port )
415- try :
416- to_replace_rpc = self .get_rpc_peer (to_replace )
417- await to_replace_rpc .ping ()
418- return False
419- except asyncio .TimeoutError :
420- log .debug ("Replacing dead contact in bucket %i: %s:%i with %s:%i " , bucket_index ,
421- to_replace .address , to_replace .udp_port , peer .address , peer .udp_port )
422- if to_replace in self .routing_table .buckets [bucket_index ]:
423- self .routing_table .buckets [bucket_index ].remove_peer (to_replace )
424- return await self ._add_peer (peer )
364+ async def probe (some_peer : 'KademliaPeer' ):
365+ rpc_peer = self .get_rpc_peer (some_peer )
366+ await rpc_peer .ping ()
367+ return await self .routing_table .add_peer (peer , probe )
425368
426369 def add_peer (self , peer : 'KademliaPeer' ):
427370 if peer .node_id == self .node_id :
@@ -439,7 +382,6 @@ async def routing_table_task(self):
439382 async with self ._split_lock :
440383 peer = self ._to_remove .pop ()
441384 self .routing_table .remove_peer (peer )
442- self .routing_table .join_buckets ()
443385 while self ._to_add :
444386 async with self ._split_lock :
445387 await self ._add_peer (self ._to_add .pop ())
@@ -482,9 +424,8 @@ def handle_request_datagram(self, address: typing.Tuple[str, int], request_datag
482424 # This is an RPC method request
483425 self .received_request_metric .labels (method = request_datagram .method ).inc ()
484426 self .peer_manager .report_last_requested (address [0 ], address [1 ])
485- try :
486- peer = self .routing_table .get_peer (request_datagram .node_id )
487- except IndexError :
427+ peer = self .routing_table .get_peer (request_datagram .node_id )
428+ if not peer :
488429 try :
489430 peer = make_kademlia_peer (request_datagram .node_id , address [0 ], address [1 ])
490431 except ValueError as err :
0 commit comments