3232from hathor .p2p .peer_endpoint import PeerAddress , PeerEndpoint
3333from hathor .p2p .peer_id import PeerId
3434from hathor .p2p .peer_storage import VerifiedPeerStorage
35+ from hathor .p2p .peers_whitelist import PeersWhitelist
3536from hathor .p2p .protocol import HathorProtocol
3637from hathor .p2p .rate_limiter import RateLimiter
3738from hathor .p2p .states .ready import ReadyState
3839from hathor .p2p .sync_factory import SyncAgentFactory
3940from hathor .p2p .sync_version import SyncVersion
40- from hathor .p2p .utils import parse_whitelist
4141from hathor .pubsub import HathorEvents , PubSubManager
4242from hathor .reactor import ReactorProtocol as Reactor
4343from hathor .transaction import BaseTransaction
4848
4949logger = get_logger ()
5050
51- # The timeout in seconds for the whitelist GET request
52- WHITELIST_REQUEST_TIMEOUT = 45
53-
5451
5552class _SyncRotateInfo (NamedTuple ):
5653 candidates : list [PeerId ]
@@ -85,10 +82,11 @@ class GlobalRateLimiter:
8582 new_connection_from_queue : deque [PeerId ]
8683 connecting_peers : dict [IStreamClientEndpoint , _ConnectingPeer ]
8784 handshaking_peers : set [HathorProtocol ]
88- whitelist_only : bool
8985 verified_peer_storage : VerifiedPeerStorage
9086 _sync_factories : dict [SyncVersion , SyncAgentFactory ]
9187 _enabled_sync_versions : set [SyncVersion ]
88+ p2p_whitelist : Optional [PeersWhitelist ]
89+ only_whitelist : bool
9290
9391 rate_limiter : RateLimiter
9492
@@ -100,7 +98,7 @@ def __init__(
10098 pubsub : PubSubManager ,
10199 ssl : bool ,
102100 rng : Random ,
103- whitelist_only : bool ,
101+ peers_whitelist : PeersWhitelist | None ,
104102 enable_ipv6 : bool ,
105103 disable_ipv4 : bool ,
106104 ) -> None :
@@ -187,17 +185,16 @@ def __init__(
187185 self .lc_connect .clock = self .reactor
188186 self .lc_connect_interval = 0.2 # seconds
189187
190- # A timer to try to reconnect to the disconnect known peers.
191- if self ._settings .ENABLE_PEER_WHITELIST :
192- self .wl_reconnect = LoopingCall (self .update_whitelist )
193- self .wl_reconnect .clock = self .reactor
188+ # Whitelisted peers.
189+ self .peers_whitelist : PeersWhitelist | None = peers_whitelist
190+
191+ # One may chose whether to follow the whitelist or not. Alterable by sysctl.
192+ # This is a p2p_manager copy of the wl_object status "_following_wl".
193+ self .only_whitelist = self .peers_whitelist .following_wl () if self .peers_whitelist else False
194194
195195 # Pubsub object to publish events
196196 self .pubsub = pubsub
197197
198- # Parameter to explicitly enable whitelist-only mode, when False it will still check the whitelist for sync-v1
199- self .whitelist_only = whitelist_only
200-
201198 # Parameter to enable IPv6 connections
202199 self .enable_ipv6 = enable_ipv6
203200
@@ -303,29 +300,14 @@ def start(self) -> None:
303300 self .lc_reconnect .start (5 , now = False )
304301 self .lc_sync_update .start (self .lc_sync_update_interval , now = False )
305302
306- if self ._settings . ENABLE_PEER_WHITELIST :
307- self ._start_whitelist_reconnect ( )
303+ if self .peers_whitelist :
304+ self .peers_whitelist . start ( self . drop_connection_by_peer_id )
308305
309306 for description in self .listen_address_descriptions :
310307 self .listen (description )
311308
312309 self .do_discovery ()
313310
314- def _start_whitelist_reconnect (self ) -> None :
315- # The deferred returned by the LoopingCall start method
316- # executes when the looping call stops running
317- # https://docs.twistedmatrix.com/en/stable/api/twisted.internet.task.LoopingCall.html
318- d = self .wl_reconnect .start (30 )
319- d .addErrback (self ._handle_whitelist_reconnect_err )
320-
321- def _handle_whitelist_reconnect_err (self , * args : Any , ** kwargs : Any ) -> None :
322- """ This method will be called when an exception happens inside the whitelist update
323- and ends up stopping the looping call.
324- We log the error and start the looping call again.
325- """
326- self .log .error ('whitelist reconnect had an exception. Start looping call again.' , args = args , kwargs = kwargs )
327- self .reactor .callLater (30 , self ._start_whitelist_reconnect )
328-
329311 def _start_peer_connect_loop (self ) -> None :
330312 # The deferred returned by the LoopingCall start method
331313 # executes when the looping call stops running
@@ -354,6 +336,9 @@ def stop(self) -> None:
354336 if self .lc_sync_update .running :
355337 self .lc_sync_update .stop ()
356338
339+ if self .peers_whitelist :
340+ self .peers_whitelist .stop ()
341+
357342 def _get_peers_count (self ) -> PeerConnectionsMetrics :
358343 """Get a dict containing the count of peers in each state"""
359344
@@ -421,9 +406,9 @@ def on_peer_connect(self, protocol: HathorProtocol) -> None:
421406 self .log .warn ('reached maximum number of connections' , max_connections = self .max_connections )
422407 protocol .disconnect (force = True )
423408 return
409+
424410 self .connections .add (protocol )
425411 self .handshaking_peers .add (protocol )
426-
427412 self .pubsub .publish (
428413 HathorEvents .NETWORK_PEER_CONNECTED ,
429414 protocol = protocol ,
@@ -602,47 +587,6 @@ def reconnect_to_all(self) -> None:
602587 for peer in list (self .verified_peer_storage .values ()):
603588 self .connect_to_peer (peer , int (now ))
604589
605- def update_whitelist (self ) -> Deferred [None ]:
606- from twisted .web .client import readBody
607- from twisted .web .http_headers import Headers
608- assert self ._settings .WHITELIST_URL is not None
609- self .log .info ('update whitelist' )
610- d = self ._http_agent .request (
611- b'GET' ,
612- self ._settings .WHITELIST_URL .encode (),
613- Headers ({'User-Agent' : ['hathor-core' ]}),
614- None )
615- d .addCallback (readBody )
616- d .addTimeout (WHITELIST_REQUEST_TIMEOUT , self .reactor )
617- d .addCallback (self ._update_whitelist_cb )
618- d .addErrback (self ._update_whitelist_err )
619-
620- return d
621-
622- def _update_whitelist_err (self , * args : Any , ** kwargs : Any ) -> None :
623- self .log .error ('update whitelist failed' , args = args , kwargs = kwargs )
624-
625- def _update_whitelist_cb (self , body : bytes ) -> None :
626- assert self .manager is not None
627- self .log .info ('update whitelist got response' )
628- try :
629- text = body .decode ()
630- new_whitelist = parse_whitelist (text )
631- except Exception :
632- self .log .exception ('failed to parse whitelist' )
633- return
634- current_whitelist = set (self .manager .peers_whitelist )
635- peers_to_add = new_whitelist - current_whitelist
636- if peers_to_add :
637- self .log .info ('add new peers to whitelist' , peers = peers_to_add )
638- peers_to_remove = current_whitelist - new_whitelist
639- if peers_to_remove :
640- self .log .info ('remove peers peers from whitelist' , peers = peers_to_remove )
641- for peer_id in peers_to_add :
642- self .manager .add_peer_to_whitelist (peer_id )
643- for peer_id in peers_to_remove :
644- self .manager .remove_peer_from_whitelist_and_disconnect (peer_id )
645-
646590 def connect_to_peer (self , peer : UnverifiedPeer | PublicPeer , now : int ) -> None :
647591 """ Attempts to connect if it is not connected to the peer.
648592 """
@@ -940,3 +884,44 @@ def reload_entrypoints_and_connections(self) -> None:
940884 self .log .warn ('Killing all connections and resetting entrypoints...' )
941885 self .disconnect_all_peers (force = True )
942886 self .my_peer .reload_entrypoints_from_source_file ()
887+
888+ def whitelist_swap (self , wl_object : PeersWhitelist ) -> None :
889+ """
890+ Altering whitelist (URL/PATH) during full-node runtime.
891+ """
892+
893+ if not wl_object :
894+ return
895+
896+ if self .peers_whitelist :
897+ self .peers_whitelist .stop ()
898+
899+ # Sysctl may only update to another URL or Path, not None.
900+ self .log .warn ("Swapping whitelists... " )
901+ self .peers_whitelist = wl_object
902+
903+ self .whitelist_toggle (wl_object .following_wl ())
904+
905+ def whitelist_toggle (self , wl_toggle : bool ) -> None :
906+ """
907+ Called if whitelist is turned "on" or "off".
908+ Called via sysctl methods.
909+ """
910+
911+ if not self .peers_whitelist :
912+ return
913+
914+ # Pass toggle option to p2p local copy.
915+ self .only_whitelist = wl_toggle
916+ self .peers_whitelist ._following_wl = wl_toggle
917+
918+ if wl_toggle :
919+ self .log .warn ('Whitelist ON: Node starts following it...' )
920+ # All connections not in the whitelist are severed.
921+ for conn in self .connections :
922+ if conn .get_peer_id ():
923+ if conn .get_peer_id () not in self .peers_whitelist ._current :
924+ conn .disconnect (reason = 'Whitelist turned on' , force = True )
925+ return
926+
927+ self .log .warn ("Whitelist OFF - Node starts ignoring it..." )
0 commit comments