@@ -93,7 +93,8 @@ class GossipSub(IPubsubRouter, Service):
93
93
direct_connect_interval : int
94
94
95
95
do_px : bool
96
- back_off : int
96
+ back_off : dict [str , dict [ID , int ]]
97
+ prune_back_off : int
97
98
unsubscribe_back_off : int
98
99
99
100
def __init__ (
@@ -111,7 +112,7 @@ def __init__(
111
112
direct_connect_initial_delay : float = 0.1 ,
112
113
direct_connect_interval : int = 300 ,
113
114
do_px : bool = False ,
114
- back_off : int = 60 ,
115
+ prune_back_off : int = 60 ,
115
116
unsubscribe_back_off : int = 10 ,
116
117
) -> None :
117
118
self .protocols = list (protocols )
@@ -148,7 +149,8 @@ def __init__(
148
149
self .time_since_last_publish = {}
149
150
150
151
self .do_px = do_px
151
- self .back_off = back_off
152
+ self .back_off = dict ()
153
+ self .prune_back_off = prune_back_off
152
154
self .unsubscribe_back_off = unsubscribe_back_off
153
155
154
156
async def run (self ) -> None :
@@ -345,15 +347,21 @@ async def join(self, topic: str) -> None:
345
347
self .mesh [topic ] = set ()
346
348
347
349
topic_in_fanout : bool = topic in self .fanout
348
- fanout_peers : set [ID ] = self .fanout [topic ] if topic_in_fanout else set ()
350
+ fanout_peers : set [ID ] = set ()
351
+
352
+ for peer in self .fanout [topic ]:
353
+ if self ._check_back_off (peer , topic ):
354
+ continue
355
+ fanout_peers .add (peer )
356
+
349
357
fanout_size = len (fanout_peers )
350
358
if not topic_in_fanout or (topic_in_fanout and fanout_size < self .degree ):
351
359
# There are less than D peers (let this number be x)
352
360
# in the fanout for a topic (or the topic is not in the fanout).
353
361
# Selects the remaining number of peers (D-x) from peers.gossipsub[topic].
354
- if topic in self .pubsub .peer_topics :
362
+ if self . pubsub is not None and topic in self .pubsub .peer_topics :
355
363
selected_peers = self ._get_in_topic_gossipsub_peers_from_minus (
356
- topic , self .degree - fanout_size , fanout_peers
364
+ topic , self .degree - fanout_size , fanout_peers , True
357
365
)
358
366
# Combine fanout peers with selected peers
359
367
fanout_peers .update (selected_peers )
@@ -380,7 +388,8 @@ async def leave(self, topic: str) -> None:
380
388
return
381
389
# Notify the peers in mesh[topic] with a PRUNE(topic) message
382
390
for peer in self .mesh [topic ]:
383
- await self .emit_prune (topic , peer , do_px = self .do_px , is_unsubscribe = True )
391
+ await self .emit_prune (topic , peer , self .do_px , True )
392
+ self ._add_back_off (peer , topic , True )
384
393
385
394
# Forget mesh[topic]
386
395
self .mesh .pop (topic , None )
@@ -516,7 +525,7 @@ def mesh_heartbeat(
516
525
if num_mesh_peers_in_topic < self .degree_low :
517
526
# Select D - |mesh[topic]| peers from peers.gossipsub[topic] - mesh[topic] # noqa: E501
518
527
selected_peers = self ._get_in_topic_gossipsub_peers_from_minus (
519
- topic , self .degree - num_mesh_peers_in_topic , self .mesh [topic ]
528
+ topic , self .degree - num_mesh_peers_in_topic , self .mesh [topic ], True
520
529
)
521
530
522
531
for peer in selected_peers :
@@ -579,9 +588,7 @@ def _handle_topic_heartbeat(
579
588
if len (in_topic_peers ) < self .degree :
580
589
# Select additional peers from peers.gossipsub[topic]
581
590
selected_peers = self ._get_in_topic_gossipsub_peers_from_minus (
582
- topic ,
583
- self .degree - len (in_topic_peers ),
584
- in_topic_peers ,
591
+ topic , self .degree - len (in_topic_peers ), in_topic_peers , True
585
592
)
586
593
# Add the selected peers
587
594
in_topic_peers .update (selected_peers )
@@ -592,7 +599,7 @@ def _handle_topic_heartbeat(
592
599
if msg_ids :
593
600
# Select D peers from peers.gossipsub[topic] excluding current peers
594
601
peers_to_emit_ihave_to = self ._get_in_topic_gossipsub_peers_from_minus (
595
- topic , self .degree , current_peers
602
+ topic , self .degree , current_peers , True
596
603
)
597
604
msg_id_strs = [str (msg_id ) for msg_id in msg_ids ]
598
605
for peer in peers_to_emit_ihave_to :
@@ -666,7 +673,11 @@ def select_from_minus(
666
673
return selection
667
674
668
675
def _get_in_topic_gossipsub_peers_from_minus (
669
- self , topic : str , num_to_select : int , minus : Iterable [ID ]
676
+ self ,
677
+ topic : str ,
678
+ num_to_select : int ,
679
+ minus : Iterable [ID ],
680
+ backoff_check : bool = False ,
670
681
) -> list [ID ]:
671
682
if self .pubsub is None :
672
683
raise NoPubsubAttached
@@ -675,8 +686,57 @@ def _get_in_topic_gossipsub_peers_from_minus(
675
686
for peer_id in self .pubsub .peer_topics [topic ]
676
687
if self .peer_protocol [peer_id ] == PROTOCOL_ID
677
688
}
689
+ if backoff_check :
690
+ # filter out peers that are in back off for this topic
691
+ gossipsub_peers_in_topic = {
692
+ peer_id
693
+ for peer_id in gossipsub_peers_in_topic
694
+ if self ._check_back_off (peer_id , topic ) is False
695
+ }
678
696
return self .select_from_minus (num_to_select , gossipsub_peers_in_topic , minus )
679
697
698
+ def _add_back_off (
699
+ self , peer : ID , topic : str , is_unsubscribe : bool , backoff_duration : int = 0
700
+ ) -> None :
701
+ """
702
+ Add back off for a peer in a topic.
703
+ :param peer: peer to add back off for
704
+ :param topic: topic to add back off for
705
+ :param is_unsubscribe: whether this is an unsubscribe operation
706
+ :param backoff_duration: duration of back off in seconds, if 0, use default
707
+ """
708
+ if topic not in self .back_off :
709
+ self .back_off [topic ] = dict ()
710
+
711
+ backoff_till = int (time .time ())
712
+ if backoff_duration > 0 :
713
+ backoff_till += backoff_duration
714
+ else :
715
+ if is_unsubscribe :
716
+ backoff_till += self .unsubscribe_back_off
717
+ else :
718
+ backoff_till += self .prune_back_off
719
+
720
+ if peer not in self .back_off [topic ]:
721
+ self .back_off [topic ][peer ] = backoff_till
722
+ else :
723
+ self .back_off [topic ][peer ] = max (self .back_off [topic ][peer ], backoff_till )
724
+
725
+ def _check_back_off (self , peer : ID , topic : str ) -> bool :
726
+ """
727
+ Check if a peer is in back off for a topic and cleanup expired back off entries.
728
+ :param peer: peer to check
729
+ :param topic: topic to check
730
+ :return: True if the peer is in back off, False otherwise
731
+ """
732
+ if topic not in self .back_off :
733
+ return False
734
+ if self .back_off [topic ].get (peer , 0 ) > int (time .time ()):
735
+ return True
736
+ else :
737
+ del self .back_off [topic ][peer ]
738
+ return False
739
+
680
740
# RPC handlers
681
741
682
742
async def handle_ihave (
@@ -762,36 +822,45 @@ async def handle_graft(
762
822
) -> None :
763
823
topic : str = graft_msg .topicID
764
824
765
- # TODO: complete the remaining logic
766
- self .do_px
767
-
768
825
# Add peer to mesh for topic
769
826
if topic in self .mesh :
770
827
for direct_peer in self .direct_peers :
771
828
if direct_peer == sender_peer_id :
772
829
logger .warning (
773
830
"GRAFT: ignoring request from direct peer %s" , sender_peer_id
774
831
)
775
- await self .emit_prune (
776
- topic , sender_peer_id , do_px = self .do_px , is_unsubscribe = False
777
- )
832
+ await self .emit_prune (topic , sender_peer_id , False , False )
778
833
return
779
834
835
+ if self ._check_back_off (sender_peer_id , topic ):
836
+ logger .warning (
837
+ "GRAFT: ignoring request from %s, back off until %d" ,
838
+ sender_peer_id ,
839
+ self .back_off [topic ][sender_peer_id ],
840
+ )
841
+ self ._add_back_off (sender_peer_id , topic , False )
842
+ await self .emit_prune (topic , sender_peer_id , False , False )
843
+ return
844
+
780
845
if sender_peer_id not in self .mesh [topic ]:
781
846
self .mesh [topic ].add (sender_peer_id )
782
847
else :
783
848
# Respond with PRUNE if not subscribed to the topic
784
- await self .emit_prune (
785
- topic , sender_peer_id , do_px = self .do_px , is_unsubscribe = False
786
- )
849
+ await self .emit_prune (topic , sender_peer_id , self .do_px , False )
787
850
788
851
async def handle_prune (
789
852
self , prune_msg : rpc_pb2 .ControlPrune , sender_peer_id : ID
790
853
) -> None :
791
854
topic : str = prune_msg .topicID
855
+ backoff_till : int = prune_msg .backoff
792
856
793
857
# Remove peer from mesh for topic
794
858
if topic in self .mesh :
859
+ if backoff_till > 0 :
860
+ self ._add_back_off (sender_peer_id , topic , False , backoff_till )
861
+ else :
862
+ self ._add_back_off (sender_peer_id , topic , False )
863
+
795
864
self .mesh [topic ].discard (sender_peer_id )
796
865
797
866
# RPC emitters
@@ -845,12 +914,11 @@ async def emit_graft(self, topic: str, id: ID) -> None:
845
914
async def emit_prune (
846
915
self , topic : str , to_peer : ID , do_px : bool , is_unsubscribe : bool
847
916
) -> None :
848
- async def emit_prune (self , topic : str , id : ID ) -> None :
849
917
"""Emit graft message, sent to to_peer, for topic."""
850
918
prune_msg : rpc_pb2 .ControlPrune = rpc_pb2 .ControlPrune ()
851
919
prune_msg .topicID = topic
852
920
853
- back_off_duration = self .back_off
921
+ back_off_duration = self .prune_back_off
854
922
if is_unsubscribe :
855
923
back_off_duration = self .unsubscribe_back_off
856
924
@@ -862,7 +930,7 @@ async def emit_prune(self, topic: str, id: ID) -> None:
862
930
control_msg : rpc_pb2 .ControlMessage = rpc_pb2 .ControlMessage ()
863
931
control_msg .prune .extend ([prune_msg ])
864
932
865
- await self .emit_control_message (control_msg , id )
933
+ await self .emit_control_message (control_msg , to_peer )
866
934
867
935
async def emit_control_message (
868
936
self , control_msg : rpc_pb2 .ControlMessage , to_peer : ID
0 commit comments