async def peer_action(peer: ws.WSChiaConnection): peer_info = peer.get_peer_info() version = peer.get_version() if peer_info is not None and version is not None: self.version_cache.append((peer_info.host, version)) # Ask peer for peers response = await peer.request_peers( full_node_protocol.RequestPeers(), timeout=3) # Add peers to DB if isinstance(response, full_node_protocol.RespondPeers): self.peers_retrieved.append(response) peer_info = peer.get_peer_info() tries = 0 got_peak = False while tries < 25: tries += 1 if peer_info is None: break if peer_info in self.with_peak: got_peak = True break await asyncio.sleep(0.1) if not got_peak and peer_info is not None and self.crawl_store is not None: await self.crawl_store.peer_connected_hostname( peer_info.host, False) await peer.close()
async def on_connect(self, peer: ws.WSChiaConnection): if ( peer.is_outbound is False and peer.peer_server_port is not None and peer.connection_type is NodeType.FULL_NODE and self.server._local_type is NodeType.FULL_NODE and self.address_manager is not None ): timestamped_peer_info = TimestampedPeerInfo( peer.peer_host, peer.peer_server_port, uint64(int(time.time())), ) await self.address_manager.add_to_new_table([timestamped_peer_info], peer.get_peer_info(), 0) if self.relay_queue is not None: self.relay_queue.put_nowait((timestamped_peer_info, 1)) if ( peer.is_outbound and peer.peer_server_port is not None and peer.connection_type is NodeType.FULL_NODE and (self.server._local_type is NodeType.FULL_NODE or self.server._local_type is NodeType.WALLET) and self.address_manager is not None ): msg = make_msg(ProtocolMessageTypes.request_peers, full_node_protocol.RequestPeers()) await peer.send_message(msg)
async def establish_connection(server: ChiaServer, dummy_port: int, ssl_context) -> bool: timeout = aiohttp.ClientTimeout(total=10) session = aiohttp.ClientSession(timeout=timeout) try: incoming_queue: asyncio.Queue = asyncio.Queue() url = f"wss://{self_hostname}:{server._port}/ws" ws = await session.ws_connect(url, autoclose=False, autoping=True, ssl=ssl_context) wsc = WSChiaConnection( NodeType.FULL_NODE, ws, server._port, server.log, True, False, self_hostname, incoming_queue, lambda x, y: x, None, 100, 30, ) handshake = await wsc.perform_handshake(server._network_id, protocol_version, dummy_port, NodeType.FULL_NODE) await session.close() return handshake except Exception: await session.close() return False
async def new_peak(self, request: full_node_protocol.NewPeak, peer: ws.WSChiaConnection): try: peer_info = peer.get_peer_info() tls_version = peer.get_tls_version() if tls_version is None: tls_version = "unknown" if peer_info is None: return if request.height >= self.minimum_height: if self.crawl_store is not None: await self.crawl_store.peer_connected_hostname( peer_info.host, True, tls_version) self.with_peak.add(peer_info) except Exception as e: self.log.error( f"Exception: {e}. Traceback: {traceback.format_exc()}.")
async def respond_peers(self, request: full_node_protocol.RespondPeers, peer: WSChiaConnection): if not self.wallet_node.has_full_node(): self.log.info(f"Wallet received {len(request.peer_list)} peers.") await self.wallet_node.wallet_peers.respond_peers(request, peer.get_peer_info(), True) else: self.log.info(f"Wallet received {len(request.peer_list)} peers, but ignoring, since we have a full node.") await self.wallet_node.wallet_peers.ensure_is_closed() return None
async def respond_peers_introducer( self, request: introducer_protocol.RespondPeersIntroducer, peer: WSChiaConnection): if self.wallet_node.wallet_peers is not None: await self.wallet_node.wallet_peers.respond_peers( request, peer.get_peer_info(), False) if peer is not None and peer.connection_type is NodeType.INTRODUCER: await peer.close()
async def respond_peers(self, request: full_node_protocol.RespondPeers, peer: WSChiaConnection): if self.wallet_node.wallet_peers is None: return None self.log.info(f"Wallet received {len(request.peer_list)} peers.") await self.wallet_node.wallet_peers.respond_peers( request, peer.get_peer_info(), True) return None
async def update_peer_timestamp_on_message(self, peer: ws.WSChiaConnection): if (peer.is_outbound and peer.peer_server_port is not None and peer.connection_type is NodeType.FULL_NODE and self.server._local_type is NodeType.FULL_NODE and self.address_manager is not None): peer_info = peer.get_peer_info() if peer_info is None: return None if peer_info.host not in self.connection_time_pretest: self.connection_time_pretest[peer_info.host] = time.time() if time.time() - self.connection_time_pretest[ peer_info.host] > 600: self.connection_time_pretest[peer_info.host] = time.time() await self.address_manager.connect(peer_info)
async def add_dummy_connection( server: ChiaServer, dummy_port: int, type: NodeType = NodeType.FULL_NODE) -> Tuple[asyncio.Queue, bytes32]: timeout = aiohttp.ClientTimeout(total=10) session = aiohttp.ClientSession(timeout=timeout) incoming_queue: asyncio.Queue = asyncio.Queue() dummy_crt_path = server._private_key_path.parent / "dummy.crt" dummy_key_path = server._private_key_path.parent / "dummy.key" generate_ca_signed_cert(server.chia_ca_crt_path.read_bytes(), server.chia_ca_key_path.read_bytes(), dummy_crt_path, dummy_key_path) ssl_context = ssl_context_for_client(server.chia_ca_crt_path, server.chia_ca_key_path, dummy_crt_path, dummy_key_path) pem_cert = x509.load_pem_x509_certificate(dummy_crt_path.read_bytes(), default_backend()) der_cert = x509.load_der_x509_certificate( pem_cert.public_bytes(serialization.Encoding.DER), default_backend()) peer_id = bytes32(der_cert.fingerprint(hashes.SHA256())) url = f"wss://{self_hostname}:{server._port}/ws" ws = await session.ws_connect(url, autoclose=True, autoping=True, ssl=ssl_context) wsc = WSChiaConnection( type, ws, server._port, log, True, False, self_hostname, incoming_queue, lambda x, y: x, peer_id, 100, 30, ) handshake = await wsc.perform_handshake(server._network_id, protocol_version, dummy_port, NodeType.FULL_NODE) assert handshake is True return incoming_queue, peer_id
async def start_client( self, target_node: PeerInfo, on_connect: Callable = None, auth: bool = False, is_feeler: bool = False, ) -> bool: """ Tries to connect to the target node, adding one connection into the pipeline, if successful. An on connect method can also be specified, and this will be saved into the instance variables. """ if self.is_duplicate_or_self_connection(target_node): return False if target_node.host in self.banned_peers and time.time( ) < self.banned_peers[target_node.host]: self.log.warning( f"Peer {target_node.host} is still banned, not connecting to it" ) return False if auth: ssl_context = ssl_context_for_client(self.ca_private_crt_path, self.ca_private_key_path, self._private_cert_path, self._private_key_path) else: ssl_context = ssl_context_for_client(self.chia_ca_crt_path, self.chia_ca_key_path, self.p2p_crt_path, self.p2p_key_path) session = None connection: Optional[WSChiaConnection] = None try: timeout = ClientTimeout(total=30) session = ClientSession(timeout=timeout) try: if type(ip_address(target_node.host)) is IPv6Address: target_node = PeerInfo(f"[{target_node.host}]", target_node.port) except ValueError: pass url = f"wss://{target_node.host}:{target_node.port}/ws" self.log.debug(f"Connecting: {url}, Peer info: {target_node}") try: ws = await session.ws_connect(url, autoclose=True, autoping=True, heartbeat=60, ssl=ssl_context, max_msg_size=50 * 1024 * 1024) except ServerDisconnectedError: self.log.debug( f"Server disconnected error connecting to {url}. Perhaps we are banned by the peer." ) return False except asyncio.TimeoutError: self.log.debug(f"Timeout error connecting to {url}") return False if ws is None: return False assert ws._response.connection is not None and ws._response.connection.transport is not None transport = ws._response.connection.transport # type: ignore cert_bytes = transport._ssl_protocol._extra[ "ssl_object"].getpeercert(True) # type: ignore der_cert = x509.load_der_x509_certificate(cert_bytes, default_backend()) peer_id = bytes32(der_cert.fingerprint(hashes.SHA256())) if peer_id == self.node_id: raise RuntimeError( f"Trying to connect to a peer ({target_node}) with the same peer_id: {peer_id}" ) connection = WSChiaConnection( self._local_type, ws, self._port, self.log, True, False, target_node.host, self.incoming_messages, self.connection_closed, peer_id, self._inbound_rate_limit_percent, self._outbound_rate_limit_percent, session=session, ) handshake = await connection.perform_handshake( self._network_id, protocol_version, self._port, self._local_type, ) assert handshake is True await self.connection_added(connection, on_connect) # the session has been adopted by the connection, don't close it at # the end of the function session = None connection_type_str = "" if connection.connection_type is not None: connection_type_str = connection.connection_type.name.lower() self.log.info( f"Connected with {connection_type_str} {target_node}") if is_feeler: asyncio.create_task(connection.close()) return True except client_exceptions.ClientConnectorError as e: self.log.info(f"{e}") except ProtocolError as e: if connection is not None: await connection.close(self.invalid_protocol_ban_seconds, WSCloseCode.PROTOCOL_ERROR, e.code) if e.code == Err.INVALID_HANDSHAKE: self.log.warning( f"Invalid handshake with peer {target_node}. Maybe the peer is running old software." ) elif e.code == Err.INCOMPATIBLE_NETWORK_ID: self.log.warning( "Incompatible network ID. Maybe the peer is on another network" ) elif e.code == Err.SELF_CONNECTION: pass else: error_stack = traceback.format_exc() self.log.error( f"Exception {e}, exception Stack: {error_stack}") except Exception as e: if connection is not None: await connection.close(self.invalid_protocol_ban_seconds, WSCloseCode.PROTOCOL_ERROR, Err.UNKNOWN) error_stack = traceback.format_exc() self.log.error(f"Exception {e}, exception Stack: {error_stack}") finally: if session is not None: await session.close() return False
async def incoming_connection(self, request): if request.remote in self.banned_peers and time.time( ) < self.banned_peers[request.remote]: self.log.warning( f"Peer {request.remote} is banned, refusing connection") return None ws = web.WebSocketResponse(max_msg_size=50 * 1024 * 1024) await ws.prepare(request) close_event = asyncio.Event() cert_bytes = request.transport._ssl_protocol._extra[ "ssl_object"].getpeercert(True) der_cert = x509.load_der_x509_certificate(cert_bytes) peer_id = bytes32(der_cert.fingerprint(hashes.SHA256())) if peer_id == self.node_id: return ws connection: Optional[WSChiaConnection] = None try: connection = WSChiaConnection( self._local_type, ws, self._port, self.log, False, False, request.remote, self.incoming_messages, self.connection_closed, peer_id, self._inbound_rate_limit_percent, self._outbound_rate_limit_percent, close_event, ) handshake = await connection.perform_handshake( self._network_id, protocol_version, self._port, self._local_type, ) assert handshake is True # Limit inbound connections to config's specifications. if not self.accept_inbound_connections( connection.connection_type) and not is_in_network( connection.peer_host, self.exempt_peer_networks): self.log.info( f"Not accepting inbound connection: {connection.get_peer_info()}.Inbound limit reached." ) await connection.close() close_event.set() else: await self.connection_added(connection, self.on_connect) if self._local_type is NodeType.INTRODUCER and connection.connection_type is NodeType.FULL_NODE: self.introducer_peers.add(connection.get_peer_info()) except ProtocolError as e: if connection is not None: await connection.close(self.invalid_protocol_ban_seconds, WSCloseCode.PROTOCOL_ERROR, e.code) if e.code == Err.INVALID_HANDSHAKE: self.log.warning( "Invalid handshake with peer. Maybe the peer is running old software." ) close_event.set() elif e.code == Err.INCOMPATIBLE_NETWORK_ID: self.log.warning( "Incompatible network ID. Maybe the peer is on another network" ) close_event.set() elif e.code == Err.SELF_CONNECTION: close_event.set() else: error_stack = traceback.format_exc() self.log.error( f"Exception {e}, exception Stack: {error_stack}") close_event.set() except Exception as e: if connection is not None: await connection.close( ws_close_code=WSCloseCode.PROTOCOL_ERROR, error=Err.UNKNOWN) error_stack = traceback.format_exc() self.log.error(f"Exception {e}, exception Stack: {error_stack}") close_event.set() await close_event.wait() return ws