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 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 respond_peers(self, request: introducer_protocol.RespondPeers, peer: WSChiaConnection): if not self.wallet_node.has_full_node(): await self.wallet_node.wallet_peers.respond_peers(request, peer.get_peer_info(), False) else: await self.wallet_node.wallet_peers.ensure_is_closed() 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 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 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.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) -> 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( NodeType.FULL_NODE, ws, server._port, log, True, False, self_hostname, incoming_queue, lambda x: x, peer_id, ) 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=10) 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." ) await session.close() return False except asyncio.TimeoutError: self.log.debug(f"Timeout error connecting to {url}") await session.close() return False if ws is not None: 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) 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 else: await session.close() return False 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}") 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): 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