github.com/muhammedhassanm/blockchain@v0.0.0-20200120143007-697261defd4d/sawtooth-core-master/validator/sawtooth_validator/gossip/gossip.py (about) 1 # Copyright 2017 Intel Corporation 2 # 3 # Licensed under the Apache License, Version 2.0 (the "License"); 4 # you may not use this file except in compliance with the License. 5 # You may obtain a copy of the License at 6 # 7 # http://www.apache.org/licenses/LICENSE-2.0 8 # 9 # Unless required by applicable law or agreed to in writing, software 10 # distributed under the License is distributed on an "AS IS" BASIS, 11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 # See the License for the specific language governing permissions and 13 # limitations under the License. 14 # ------------------------------------------------------------------------------ 15 import logging 16 import copy 17 import time 18 import random 19 import os 20 import binascii 21 from threading import Lock 22 from functools import partial 23 from collections import namedtuple 24 from enum import Enum 25 26 from sawtooth_validator.concurrent.thread import InstrumentedThread 27 from sawtooth_validator.protobuf.network_pb2 import DisconnectMessage 28 from sawtooth_validator.protobuf.network_pb2 import GossipMessage 29 from sawtooth_validator.protobuf.network_pb2 import GossipConsensusMessage 30 from sawtooth_validator.protobuf.network_pb2 import GossipBatchByBatchIdRequest 31 from sawtooth_validator.protobuf.network_pb2 import \ 32 GossipBatchByTransactionIdRequest 33 from sawtooth_validator.protobuf.network_pb2 import GossipBlockRequest 34 from sawtooth_validator.protobuf import validator_pb2 35 from sawtooth_validator.protobuf.network_pb2 import PeerRegisterRequest 36 from sawtooth_validator.protobuf.network_pb2 import PeerUnregisterRequest 37 from sawtooth_validator.protobuf.network_pb2 import GetPeersRequest 38 from sawtooth_validator.protobuf.network_pb2 import GetPeersResponse 39 from sawtooth_validator.protobuf.network_pb2 import NetworkAcknowledgement 40 from sawtooth_validator.exceptions import PeeringException 41 42 LOGGER = logging.getLogger(__name__) 43 44 45 class PeerStatus(Enum): 46 CLOSED = 1 47 TEMP = 2 48 PEER = 3 49 50 51 class EndpointStatus(Enum): 52 # Endpoint will be used for peering 53 PEERING = 1 54 # Endpoint will be used to request peers 55 TOPOLOGY = 2 56 57 58 EndpointInfo = namedtuple('EndpointInfo', 59 ['status', 'time', "retry_threshold"]) 60 61 StaticPeerInfo = namedtuple('StaticPeerInfo', 62 ['time', 'retry_threshold', 'count']) 63 64 INITIAL_RETRY_FREQUENCY = 10 65 MAXIMUM_RETRY_FREQUENCY = 300 66 67 MAXIMUM_STATIC_RETRY_FREQUENCY = 3600 68 MAXIMUM_STATIC_RETRIES = 24 69 70 TIME_TO_LIVE = 3 71 72 # This is the protocol version number. It should only be incremented when 73 # there are changes to the network protocols, as well as only once per 74 # release. 75 NETWORK_PROTOCOL_VERSION = 1 76 77 78 class Gossip(object): 79 def __init__(self, network, 80 settings_cache, 81 current_chain_head_func, 82 current_root_func, 83 endpoint=None, 84 peering_mode='static', 85 initial_seed_endpoints=None, 86 initial_peer_endpoints=None, 87 minimum_peer_connectivity=3, 88 maximum_peer_connectivity=10, 89 topology_check_frequency=1 90 ): 91 """Constructor for the Gossip object. Gossip defines the 92 overlay network above the lower level networking classes. 93 94 Args: 95 network (networking.Interconnect): Provides inbound and 96 outbound network connections. 97 settings_cache (state.SettingsCache): A cache for on chain 98 settings. 99 current_chain_head_func (function): returns the current chain head. 100 current_root_func (function): returns the current state root hash 101 for the current chain root. 102 endpoint (str): The publically accessible zmq-style uri 103 endpoint for this validator. 104 peering_mode (str): The type of peering approach. Either 'static' 105 or 'dynamic'. In 'static' mode, no attempted topology 106 buildout occurs -- the validator only attempts to initiate 107 peering connections with endpoints specified in the 108 peer_list. In 'dynamic' mode, the validator will first 109 attempt to initiate peering connections with endpoints 110 specified in the peer_list and then attempt to do a 111 topology buildout starting with peer lists obtained from 112 endpoints in the seeds_list. In either mode, the validator 113 will accept incoming peer requests up to max_peers. 114 initial_seed_endpoints ([str]): A list of initial endpoints 115 to attempt to connect and gather initial topology buildout 116 information from. These are specified as zmq-compatible 117 URIs (e.g. tcp://hostname:port). 118 initial_peer_endpoints ([str]): A list of initial peer endpoints 119 to attempt to connect and peer with. These are specified 120 as zmq-compatible URIs (e.g. tcp://hostname:port). 121 minimum_peer_connectivity (int): If the number of connected 122 peers is below this threshold, the topology builder will 123 continue to attempt to identify new candidate peers to 124 connect with. 125 maximum_peer_connectivity (int): The validator will reject 126 new peer requests if the number of connected peers 127 reaches this threshold. 128 topology_check_frequency (int): The time in seconds between 129 topology update checks. 130 """ 131 self._peering_mode = peering_mode 132 self._lock = Lock() 133 self._network = network 134 self._endpoint = endpoint 135 self._initial_seed_endpoints = initial_seed_endpoints \ 136 if initial_seed_endpoints else [] 137 self._initial_peer_endpoints = initial_peer_endpoints \ 138 if initial_peer_endpoints else [] 139 self._minimum_peer_connectivity = minimum_peer_connectivity 140 self._maximum_peer_connectivity = maximum_peer_connectivity 141 self._topology_check_frequency = topology_check_frequency 142 self._settings_cache = settings_cache 143 144 self._current_chain_head_func = current_chain_head_func 145 self._current_root_func = current_root_func 146 147 self._topology = None 148 self._peers = {} 149 150 def send_peers(self, connection_id): 151 """Sends a message containing our peers to the 152 connection identified by connection_id. 153 154 Args: 155 connection_id (str): A unique identifier which identifies an 156 connection on the network server socket. 157 """ 158 with self._lock: 159 # Needs to actually be the list of advertised endpoints of 160 # our peers 161 peer_endpoints = list(self._peers.values()) 162 if self._endpoint: 163 peer_endpoints.append(self._endpoint) 164 peers_response = GetPeersResponse(peer_endpoints=peer_endpoints) 165 try: 166 # Send a one_way message because the connection will be closed 167 # if this is a temp connection. 168 self._network.send( 169 validator_pb2.Message.GOSSIP_GET_PEERS_RESPONSE, 170 peers_response.SerializeToString(), 171 connection_id, 172 one_way=True) 173 except ValueError: 174 LOGGER.debug("Connection disconnected: %s", connection_id) 175 176 def add_candidate_peer_endpoints(self, peer_endpoints): 177 """Adds candidate endpoints to the list of endpoints to 178 attempt to peer with. 179 180 Args: 181 peer_endpoints ([str]): A list of public uri's which the 182 validator can attempt to peer with. 183 """ 184 if self._topology: 185 self._topology.add_candidate_peer_endpoints(peer_endpoints) 186 else: 187 LOGGER.debug("Could not add peer endpoints to topology. " 188 "ConnectionManager does not exist.") 189 190 def get_peers(self): 191 """Returns a copy of the gossip peers. 192 """ 193 with self._lock: 194 return copy.copy(self._peers) 195 196 @property 197 def endpoint(self): 198 """Returns the validator's public endpoint. 199 """ 200 return self._endpoint 201 202 def register_peer(self, connection_id, endpoint): 203 """Registers a connected connection_id. 204 205 Args: 206 connection_id (str): A unique identifier which identifies an 207 connection on the network server socket. 208 endpoint (str): The publically reachable endpoint of the new 209 peer 210 """ 211 with self._lock: 212 if len(self._peers) < self._maximum_peer_connectivity: 213 self._peers[connection_id] = endpoint 214 self._topology.set_connection_status(connection_id, 215 PeerStatus.PEER) 216 LOGGER.debug("Added connection_id %s with endpoint %s, " 217 "connected identities are now %s", 218 connection_id, endpoint, self._peers) 219 else: 220 raise PeeringException( 221 "At maximum configured number of peers: {} " 222 "Rejecting peering request from {}.".format( 223 self._maximum_peer_connectivity, 224 endpoint)) 225 226 def unregister_peer(self, connection_id): 227 """Removes a connection_id from the registry. 228 229 Args: 230 connection_id (str): A unique identifier which identifies an 231 connection on the network server socket. 232 """ 233 with self._lock: 234 if connection_id in self._peers: 235 del self._peers[connection_id] 236 LOGGER.debug("Removed connection_id %s, " 237 "connected identities are now %s", 238 connection_id, self._peers) 239 self._topology.set_connection_status(connection_id, 240 PeerStatus.TEMP) 241 else: 242 LOGGER.warning("Connection unregister failed as connection " 243 "was not registered: %s", 244 connection_id) 245 246 def get_time_to_live(self): 247 time_to_live = \ 248 self._settings_cache.get_setting( 249 "sawtooth.gossip.time_to_live", 250 self._current_root_func(), 251 default_value=TIME_TO_LIVE 252 ) 253 return int(time_to_live) 254 255 def broadcast_block(self, block, exclude=None, time_to_live=None): 256 if time_to_live is None: 257 time_to_live = self.get_time_to_live() 258 gossip_message = GossipMessage( 259 content_type=GossipMessage.BLOCK, 260 content=block.SerializeToString(), 261 time_to_live=time_to_live) 262 263 self.broadcast( 264 gossip_message, validator_pb2.Message.GOSSIP_MESSAGE, exclude) 265 266 def broadcast_block_request(self, block_id): 267 time_to_live = self.get_time_to_live() 268 block_request = GossipBlockRequest( 269 block_id=block_id, 270 nonce=binascii.b2a_hex(os.urandom(16)), 271 time_to_live=time_to_live) 272 self.broadcast(block_request, 273 validator_pb2.Message.GOSSIP_BLOCK_REQUEST) 274 275 def send_block_request(self, block_id, connection_id): 276 time_to_live = self.get_time_to_live() 277 block_request = GossipBlockRequest( 278 block_id=block_id, 279 nonce=binascii.b2a_hex(os.urandom(16)), 280 time_to_live=time_to_live) 281 self.send(validator_pb2.Message.GOSSIP_BLOCK_REQUEST, 282 block_request.SerializeToString(), 283 connection_id, 284 one_way=True) 285 286 def broadcast_batch(self, batch, exclude=None, time_to_live=None): 287 if time_to_live is None: 288 time_to_live = self.get_time_to_live() 289 gossip_message = GossipMessage( 290 content_type=GossipMessage.BATCH, 291 content=batch.SerializeToString(), 292 time_to_live=time_to_live) 293 294 self.broadcast( 295 gossip_message, validator_pb2.Message.GOSSIP_MESSAGE, exclude) 296 297 def broadcast_batch_by_transaction_id_request(self, transaction_ids): 298 time_to_live = self.get_time_to_live() 299 batch_request = GossipBatchByTransactionIdRequest( 300 ids=transaction_ids, 301 nonce=binascii.b2a_hex(os.urandom(16)), 302 time_to_live=time_to_live) 303 self.broadcast( 304 batch_request, 305 validator_pb2.Message.GOSSIP_BATCH_BY_TRANSACTION_ID_REQUEST) 306 307 def broadcast_batch_by_batch_id_request(self, batch_id): 308 time_to_live = self.get_time_to_live() 309 batch_request = GossipBatchByBatchIdRequest( 310 id=batch_id, 311 nonce=binascii.b2a_hex(os.urandom(16)), 312 time_to_live=time_to_live) 313 self.broadcast( 314 batch_request, 315 validator_pb2.Message.GOSSIP_BATCH_BY_BATCH_ID_REQUEST) 316 317 def send_consensus_message(self, peer_id, message, public_key): 318 connection_id = self._network.public_key_to_connection_id(peer_id) 319 320 self.send( 321 validator_pb2.Message.GOSSIP_CONSENSUS_MESSAGE, 322 GossipConsensusMessage( 323 message=message, 324 sender_id=public_key, 325 time_to_live=0).SerializeToString(), 326 connection_id) 327 328 def broadcast_consensus_message(self, message, public_key): 329 self.broadcast( 330 GossipConsensusMessage( 331 message=message, 332 time_to_live=self.get_time_to_live()), 333 validator_pb2.Message.GOSSIP_CONSENSUS_MESSAGE) 334 335 def send(self, message_type, message, connection_id, one_way=False): 336 """Sends a message via the network. 337 338 Args: 339 message_type (str): The type of the message. 340 message (bytes): The message to be sent. 341 connection_id (str): The connection to send it to. 342 """ 343 try: 344 self._network.send(message_type, message, connection_id, 345 one_way=one_way) 346 except ValueError: 347 LOGGER.debug("Connection %s is no longer valid. " 348 "Removing from list of peers.", 349 connection_id) 350 if connection_id in self._peers: 351 del self._peers[connection_id] 352 353 def broadcast(self, gossip_message, message_type, exclude=None): 354 """Broadcast gossip messages. 355 356 Broadcast the message to all peers unless they are in the excluded 357 list. 358 359 Args: 360 gossip_message: The message to be broadcast. 361 message_type: Type of the message. 362 exclude: A list of connection_ids that should be excluded from this 363 broadcast. 364 """ 365 with self._lock: 366 if exclude is None: 367 exclude = [] 368 for connection_id in self._peers.copy(): 369 if connection_id not in exclude and \ 370 self._network.is_connection_handshake_complete( 371 connection_id): 372 self.send( 373 message_type, 374 gossip_message.SerializeToString(), 375 connection_id, 376 one_way=True) 377 378 def connect_success(self, connection_id): 379 """ 380 Notify topology that a connection has been properly authorized 381 382 Args: 383 connection_id: The connection id for the authorized connection. 384 385 """ 386 if self._topology: 387 self._topology.connect_success(connection_id) 388 389 def remove_temp_endpoint(self, endpoint): 390 """ 391 Remove temporary endpoints that never finished authorization. 392 393 Args: 394 endpoint: The endpoint that is not authorized to connect to the 395 network. 396 """ 397 if self._topology: 398 self._topology.remove_temp_endpoint(endpoint) 399 400 def start(self): 401 self._topology = ConnectionManager( 402 gossip=self, 403 network=self._network, 404 endpoint=self._endpoint, 405 current_chain_head_func=self._current_chain_head_func, 406 initial_peer_endpoints=self._initial_peer_endpoints, 407 initial_seed_endpoints=self._initial_seed_endpoints, 408 peering_mode=self._peering_mode, 409 min_peers=self._minimum_peer_connectivity, 410 max_peers=self._maximum_peer_connectivity, 411 check_frequency=self._topology_check_frequency) 412 413 self._topology.start() 414 415 def stop(self): 416 for peer in self.get_peers(): 417 request = PeerUnregisterRequest() 418 try: 419 self._network.send(validator_pb2.Message.GOSSIP_UNREGISTER, 420 request.SerializeToString(), 421 peer) 422 except ValueError: 423 pass 424 if self._topology: 425 self._topology.stop() 426 427 428 class ConnectionManager(InstrumentedThread): 429 def __init__(self, gossip, network, endpoint, 430 current_chain_head_func, 431 initial_peer_endpoints, initial_seed_endpoints, 432 peering_mode, min_peers=3, max_peers=10, 433 check_frequency=1): 434 """Constructor for the ConnectionManager class. 435 436 Args: 437 gossip (gossip.Gossip): The gossip overlay network. 438 network (network.Interconnect): The underlying network. 439 endpoint (str): A zmq-style endpoint uri representing 440 this validator's publically reachable endpoint. 441 current_chain_head_func (function): Returns the current chain head. 442 initial_peer_endpoints ([str]): A list of static peers 443 to attempt to connect and peer with. 444 initial_seed_endpoints ([str]): A list of endpoints to 445 connect to and get candidate peer lists to attempt 446 to reach min_peers threshold. 447 peering_mode (str): Either 'static' or 'dynamic'. 'static' 448 only connects to peers in initial_peer_endpoints. 449 'dynamic' connects to peers in initial_peer_endpoints 450 and gets candidate peer lists from initial_seed_endpoints. 451 min_peers (int): The minimum number of peers required to stop 452 attempting candidate connections. 453 max_peers (int): The maximum number of active peer connections 454 to allow. 455 check_frequency (int): How often to attempt dynamic connectivity. 456 """ 457 super().__init__(name="ConnectionManager") 458 self._lock = Lock() 459 self._stopped = False 460 self._gossip = gossip 461 self._network = network 462 self._endpoint = endpoint 463 self._current_chain_head_func = current_chain_head_func 464 self._initial_peer_endpoints = initial_peer_endpoints 465 self._initial_seed_endpoints = initial_seed_endpoints 466 self._peering_mode = peering_mode 467 self._min_peers = min_peers 468 self._max_peers = max_peers 469 self._check_frequency = check_frequency 470 471 self._candidate_peer_endpoints = [] 472 # Seconds to wait for messages to arrive 473 self._response_duration = 2 474 self._connection_statuses = {} 475 self._temp_endpoints = {} 476 self._static_peer_status = {} 477 478 def start(self): 479 # First, attempt to connect to explicit peers 480 for endpoint in self._initial_peer_endpoints: 481 self._static_peer_status[endpoint] = \ 482 StaticPeerInfo( 483 time=0, 484 retry_threshold=INITIAL_RETRY_FREQUENCY, 485 count=0) 486 487 super().start() 488 489 def run(self): 490 has_chain_head = self._current_chain_head_func() is not None 491 while not self._stopped: 492 try: 493 if self._peering_mode == 'dynamic': 494 self.retry_dynamic_peering() 495 elif self._peering_mode == 'static': 496 self.retry_static_peering() 497 498 # This tests for a degenerate case where the node is connected 499 # to peers, but at first connection no peer had a valid chain 500 # head. Keep querying connected peers until a valid chain head 501 # is received. 502 has_chain_head = has_chain_head or \ 503 self._current_chain_head_func() is not None 504 if not has_chain_head: 505 peered_connections = self._get_peered_connections() 506 if peered_connections: 507 LOGGER.debug( 508 'Have not received a chain head from peers. ' 509 'Requesting from %s', 510 peered_connections) 511 512 self._request_chain_head(peered_connections) 513 514 time.sleep(self._check_frequency) 515 except Exception: # pylint: disable=broad-except 516 LOGGER.exception("Unhandled exception during peer refresh") 517 518 def stop(self): 519 self._stopped = True 520 for connection_id in self._connection_statuses: 521 try: 522 if self._connection_statuses[connection_id] == \ 523 PeerStatus.CLOSED: 524 continue 525 526 msg = DisconnectMessage() 527 self._network.send( 528 validator_pb2.Message.NETWORK_DISCONNECT, 529 msg.SerializeToString(), 530 connection_id) 531 self._connection_statuses[connection_id] = PeerStatus.CLOSED 532 except ValueError: 533 # Connection has already been disconnected. 534 pass 535 536 def _get_peered_connections(self): 537 peers = self._gossip.get_peers() 538 539 return [conn_id for conn_id in peers 540 if self._connection_statuses[conn_id] == PeerStatus.PEER] 541 542 def _request_chain_head(self, peered_connections): 543 """Request chain head from the given peer ids. 544 545 Args: 546 peered_connecions (:list:str): a list of peer connection ids where 547 the requests will be sent. 548 """ 549 for conn_id in peered_connections: 550 self._gossip.send_block_request("HEAD", conn_id) 551 552 def retry_dynamic_peering(self): 553 self._refresh_peer_list(self._gossip.get_peers()) 554 peers = self._gossip.get_peers() 555 peer_count = len(peers) 556 if peer_count < self._min_peers: 557 LOGGER.debug( 558 "Number of peers (%s) below " 559 "minimum peer threshold (%s). " 560 "Doing topology search.", 561 peer_count, 562 self._min_peers) 563 564 self._reset_candidate_peer_endpoints() 565 self._refresh_peer_list(peers) 566 # Cleans out any old connections that have disconnected 567 self._refresh_connection_list() 568 self._check_temp_endpoints() 569 570 peers = self._gossip.get_peers() 571 572 self._get_peers_of_peers(peers) 573 self._get_peers_of_endpoints( 574 peers, 575 self._initial_seed_endpoints) 576 577 # Wait for GOSSIP_GET_PEER_RESPONSE messages to arrive 578 time.sleep(self._response_duration) 579 580 peered_endpoints = list(peers.values()) 581 582 with self._lock: 583 unpeered_candidates = list( 584 set(self._candidate_peer_endpoints) 585 - set(peered_endpoints) 586 - set([self._endpoint])) 587 588 LOGGER.debug( 589 "Peers are: %s. " 590 "Unpeered candidates are: %s", 591 peered_endpoints, 592 unpeered_candidates) 593 594 if unpeered_candidates: 595 self._attempt_to_peer_with_endpoint( 596 random.choice(unpeered_candidates)) 597 598 def retry_static_peering(self): 599 with self._lock: 600 # Endpoints that have reached their retry count and should be 601 # removed 602 to_remove = [] 603 for endpoint in self._initial_peer_endpoints: 604 connection_id = None 605 try: 606 connection_id = \ 607 self._network.get_connection_id_by_endpoint(endpoint) 608 except KeyError: 609 pass 610 611 static_peer_info = self._static_peer_status[endpoint] 612 if connection_id is not None: 613 if connection_id in self._connection_statuses: 614 # Endpoint is already a Peer 615 if self._connection_statuses[connection_id] == \ 616 PeerStatus.PEER: 617 # reset static peering info 618 self._static_peer_status[endpoint] = \ 619 StaticPeerInfo( 620 time=0, 621 retry_threshold=INITIAL_RETRY_FREQUENCY, 622 count=0) 623 continue 624 625 if (time.time() - static_peer_info.time) > \ 626 static_peer_info.retry_threshold: 627 LOGGER.debug("Endpoint has not completed authorization in " 628 "%s seconds: %s", 629 static_peer_info.retry_threshold, 630 endpoint) 631 if connection_id is not None: 632 # If the connection exists remove it before retrying to 633 # authorize. 634 try: 635 self._network.remove_connection(connection_id) 636 except KeyError: 637 pass 638 639 if static_peer_info.retry_threshold == \ 640 MAXIMUM_STATIC_RETRY_FREQUENCY: 641 if static_peer_info.count >= MAXIMUM_STATIC_RETRIES: 642 # Unable to peer with endpoint 643 to_remove.append(endpoint) 644 continue 645 else: 646 # At maximum retry threashold, increment count 647 self._static_peer_status[endpoint] = \ 648 StaticPeerInfo( 649 time=time.time(), 650 retry_threshold=min( 651 static_peer_info.retry_threshold * 2, 652 MAXIMUM_STATIC_RETRY_FREQUENCY), 653 count=static_peer_info.count + 1) 654 else: 655 self._static_peer_status[endpoint] = \ 656 StaticPeerInfo( 657 time=time.time(), 658 retry_threshold=min( 659 static_peer_info.retry_threshold * 2, 660 MAXIMUM_STATIC_RETRY_FREQUENCY), 661 count=0) 662 663 LOGGER.debug("attempting to peer with %s", endpoint) 664 self._network.add_outbound_connection(endpoint) 665 self._temp_endpoints[endpoint] = EndpointInfo( 666 EndpointStatus.PEERING, 667 time.time(), 668 INITIAL_RETRY_FREQUENCY) 669 670 for endpoint in to_remove: 671 # Endpoints that have reached their retry count and should be 672 # removed 673 self._initial_peer_endpoints.remove(endpoint) 674 del self._static_peer_status[endpoint] 675 676 def add_candidate_peer_endpoints(self, peer_endpoints): 677 """Adds candidate endpoints to the list of endpoints to 678 attempt to peer with. 679 680 Args: 681 peer_endpoints ([str]): A list of public uri's which the 682 validator can attempt to peer with. 683 """ 684 with self._lock: 685 for endpoint in peer_endpoints: 686 if endpoint not in self._candidate_peer_endpoints: 687 self._candidate_peer_endpoints.append(endpoint) 688 689 def set_connection_status(self, connection_id, status): 690 self._connection_statuses[connection_id] = status 691 692 def remove_temp_endpoint(self, endpoint): 693 with self._lock: 694 if endpoint in self._temp_endpoints: 695 del self._temp_endpoints[endpoint] 696 697 def _check_temp_endpoints(self): 698 with self._lock: 699 for endpoint in self._temp_endpoints: 700 endpoint_info = self._temp_endpoints[endpoint] 701 if (time.time() - endpoint_info.time) > \ 702 endpoint_info.retry_threshold: 703 LOGGER.debug("Endpoint has not completed authorization in " 704 "%s seconds: %s", 705 endpoint_info.retry_threshold, 706 endpoint) 707 try: 708 # If the connection exists remove it before retrying to 709 # authorize. If the connection does not exist, a 710 # KeyError will be thrown. 711 conn_id = \ 712 self._network.get_connection_id_by_endpoint( 713 endpoint) 714 self._network.remove_connection(conn_id) 715 except KeyError: 716 pass 717 718 self._network.add_outbound_connection(endpoint) 719 self._temp_endpoints[endpoint] = EndpointInfo( 720 endpoint_info.status, 721 time.time(), 722 min(endpoint_info.retry_threshold * 2, 723 MAXIMUM_RETRY_FREQUENCY)) 724 725 def _refresh_peer_list(self, peers): 726 for conn_id in peers: 727 try: 728 self._network.get_connection_id_by_endpoint( 729 peers[conn_id]) 730 except KeyError: 731 LOGGER.debug("removing peer %s because " 732 "connection went away", 733 peers[conn_id]) 734 735 self._gossip.unregister_peer(conn_id) 736 if conn_id in self._connection_statuses: 737 del self._connection_statuses[conn_id] 738 739 def _refresh_connection_list(self): 740 with self._lock: 741 closed_connections = [] 742 for connection_id in self._connection_statuses: 743 if not self._network.has_connection(connection_id): 744 closed_connections.append(connection_id) 745 746 for connection_id in closed_connections: 747 del self._connection_statuses[connection_id] 748 749 def _get_peers_of_peers(self, peers): 750 get_peers_request = GetPeersRequest() 751 752 for conn_id in peers: 753 try: 754 self._network.send( 755 validator_pb2.Message.GOSSIP_GET_PEERS_REQUEST, 756 get_peers_request.SerializeToString(), 757 conn_id) 758 except ValueError: 759 LOGGER.debug("Peer disconnected: %s", conn_id) 760 761 def _get_peers_of_endpoints(self, peers, endpoints): 762 get_peers_request = GetPeersRequest() 763 764 for endpoint in endpoints: 765 conn_id = None 766 try: 767 conn_id = self._network.get_connection_id_by_endpoint( 768 endpoint) 769 770 except KeyError: 771 # If the connection does not exist, send a connection request 772 with self._lock: 773 if endpoint in self._temp_endpoints: 774 del self._temp_endpoints[endpoint] 775 776 self._temp_endpoints[endpoint] = EndpointInfo( 777 EndpointStatus.TOPOLOGY, 778 time.time(), 779 INITIAL_RETRY_FREQUENCY) 780 781 self._network.add_outbound_connection(endpoint) 782 783 # If the connection does exist, request peers. 784 if conn_id is not None: 785 if not self._network.is_connection_handshake_complete(conn_id): 786 # has not finished the authorization (trust/challenge) 787 # process yet. 788 continue 789 elif conn_id in peers: 790 # connected and peered - we've already sent peer request 791 continue 792 else: 793 # connected but not peered 794 if endpoint in self._temp_endpoints: 795 # Endpoint is not yet authorized, do not request peers 796 continue 797 798 try: 799 self._network.send( 800 validator_pb2.Message.GOSSIP_GET_PEERS_REQUEST, 801 get_peers_request.SerializeToString(), 802 conn_id) 803 except ValueError: 804 LOGGER.debug("Connection disconnected: %s", conn_id) 805 806 def _attempt_to_peer_with_endpoint(self, endpoint): 807 LOGGER.debug("Attempting to connect/peer with %s", endpoint) 808 809 # check if the connection exists, if it does - send, 810 # otherwise create it 811 try: 812 connection_id = \ 813 self._network.get_connection_id_by_endpoint( 814 endpoint) 815 816 register_request = PeerRegisterRequest( 817 endpoint=self._endpoint, 818 protocol_version=NETWORK_PROTOCOL_VERSION) 819 820 self._network.send( 821 validator_pb2.Message.GOSSIP_REGISTER, 822 register_request.SerializeToString(), 823 connection_id, 824 callback=partial( 825 self._peer_callback, 826 endpoint=endpoint, 827 connection_id=connection_id)) 828 except KeyError: 829 # if the connection uri wasn't found in the network's 830 # connections, it raises a KeyError and we need to add 831 # a new outbound connection 832 with self._lock: 833 self._temp_endpoints[endpoint] = EndpointInfo( 834 EndpointStatus.PEERING, 835 time.time(), 836 INITIAL_RETRY_FREQUENCY) 837 self._network.add_outbound_connection(endpoint) 838 839 def _reset_candidate_peer_endpoints(self): 840 with self._lock: 841 self._candidate_peer_endpoints = [] 842 843 def _peer_callback(self, request, result, connection_id, endpoint=None): 844 with self._lock: 845 ack = NetworkAcknowledgement() 846 ack.ParseFromString(result.content) 847 848 if ack.status == ack.ERROR: 849 LOGGER.debug("Peering request to %s was NOT successful", 850 connection_id) 851 self._remove_temporary_connection(connection_id) 852 elif ack.status == ack.OK: 853 LOGGER.debug("Peering request to %s was successful", 854 connection_id) 855 if endpoint: 856 try: 857 self._gossip.register_peer(connection_id, endpoint) 858 self._connection_statuses[connection_id] = \ 859 PeerStatus.PEER 860 self._gossip.send_block_request("HEAD", connection_id) 861 except PeeringException as e: 862 # Remove unsuccessful peer 863 LOGGER.warning('Unable to successfully peer with ' 864 'connection_id: %s, due to %s', 865 connection_id, str(e)) 866 867 self._remove_temporary_connection(connection_id) 868 else: 869 LOGGER.debug("Cannot register peer with no endpoint for " 870 "connection_id: %s", 871 connection_id) 872 self._remove_temporary_connection(connection_id) 873 874 def _remove_temporary_connection(self, connection_id): 875 status = self._connection_statuses.get(connection_id) 876 if status == PeerStatus.TEMP: 877 LOGGER.debug("Closing connection to %s", connection_id) 878 msg = DisconnectMessage() 879 try: 880 self._network.send(validator_pb2.Message.NETWORK_DISCONNECT, 881 msg.SerializeToString(), 882 connection_id) 883 except ValueError: 884 pass 885 del self._connection_statuses[connection_id] 886 self._network.remove_connection(connection_id) 887 elif status == PeerStatus.PEER: 888 LOGGER.debug("Connection close request for peer ignored: %s", 889 connection_id) 890 elif status is None: 891 LOGGER.debug("Connection close request for unknown connection " 892 "ignored: %s", 893 connection_id) 894 895 def connect_success(self, connection_id): 896 """ 897 Check to see if the successful connection is meant to be peered with. 898 If not, it should be used to get the peers from the endpoint. 899 """ 900 endpoint = self._network.connection_id_to_endpoint(connection_id) 901 endpoint_info = self._temp_endpoints.get(endpoint) 902 903 LOGGER.debug("Endpoint has completed authorization: %s (id: %s)", 904 endpoint, 905 connection_id) 906 if endpoint_info is None: 907 LOGGER.debug("Received unknown endpoint: %s", endpoint) 908 909 elif endpoint_info.status == EndpointStatus.PEERING: 910 self._connect_success_peering(connection_id, endpoint) 911 912 elif endpoint_info.status == EndpointStatus.TOPOLOGY: 913 self._connect_success_topology(connection_id) 914 915 else: 916 LOGGER.debug("Endpoint has unknown status: %s", endpoint) 917 918 with self._lock: 919 if endpoint in self._temp_endpoints: 920 del self._temp_endpoints[endpoint] 921 922 def _connect_success_peering(self, connection_id, endpoint): 923 LOGGER.debug("Connection to %s succeeded", connection_id) 924 925 register_request = PeerRegisterRequest( 926 endpoint=self._endpoint, 927 protocol_version=NETWORK_PROTOCOL_VERSION) 928 self._connection_statuses[connection_id] = PeerStatus.TEMP 929 try: 930 self._network.send( 931 validator_pb2.Message.GOSSIP_REGISTER, 932 register_request.SerializeToString(), 933 connection_id, 934 callback=partial( 935 self._peer_callback, 936 connection_id=connection_id, 937 endpoint=endpoint)) 938 except ValueError: 939 LOGGER.debug("Connection disconnected: %s", connection_id) 940 941 def _connect_success_topology(self, connection_id): 942 LOGGER.debug("Connection to %s succeeded for topology request", 943 connection_id) 944 self._connection_statuses[connection_id] = PeerStatus.TEMP 945 get_peers_request = GetPeersRequest() 946 947 def callback(request, result): 948 # request, result are ignored, but required by the callback 949 self._remove_temporary_connection(connection_id) 950 951 try: 952 self._network.send( 953 validator_pb2.Message.GOSSIP_GET_PEERS_REQUEST, 954 get_peers_request.SerializeToString(), 955 connection_id, 956 callback=callback) 957 except ValueError: 958 LOGGER.debug("Connection disconnected: %s", connection_id)