async def respond_signature(self, response: harvester_protocol.RespondSignature): """ Receives a signature on a block header hash, which is required for submitting a block to the blockchain. """ header_hash = response.message proof_of_space: bytes32 = self.header_hash_to_pos[header_hash] validates: bool = False for sk in self._get_private_keys(): pk = sk.get_g1() if pk == response.farmer_pk: agg_pk = ProofOfSpace.generate_plot_public_key(response.local_pk, pk) assert agg_pk == proof_of_space.plot_public_key farmer_share = AugSchemeMPL.sign(sk, header_hash, agg_pk) agg_sig = AugSchemeMPL.aggregate( [response.message_signature, farmer_share] ) validates = AugSchemeMPL.verify(agg_pk, header_hash, agg_sig) if validates: break assert validates pos_hash: bytes32 = proof_of_space.get_hash() request = farmer_protocol.HeaderSignature(pos_hash, header_hash, agg_sig) yield OutboundMessage( NodeType.FULL_NODE, Message("header_signature", request), Delivery.BROADCAST )
async def respond_header_signature( self, response: harvester_protocol.RespondHeaderSignature ): """ Receives a signature on a block header hash, which is required for submitting a block to the blockchain. """ header_hash: bytes32 = self.harvester_responses_header_hash[ response.quality_string ] proof_of_space: bytes32 = self.harvester_responses_proofs[ response.quality_string ] plot_pubkey = self.harvester_responses_proofs[ response.quality_string ].plot_pubkey assert response.header_hash_signature.verify( [Util.hash256(header_hash)], [plot_pubkey] ) pos_hash: bytes32 = proof_of_space.get_hash() request = farmer_protocol.HeaderSignature( pos_hash, header_hash, response.header_hash_signature ) yield OutboundMessage( NodeType.FULL_NODE, Message("header_signature", request), Delivery.BROADCAST )
async def request_peers( self, request: RequestPeers, peer: WSChiaConnection, ) -> Optional[Message]: max_peers = self.introducer.max_peers_to_send if self.introducer.server is None or self.introducer.server.introducer_peers is None: return None rawpeers = self.introducer.server.introducer_peers.get_peers( max_peers * 5, True, self.introducer.recent_peer_threshold) peers = [] for r_peer in rawpeers: if r_peer.get_hash() not in self.introducer.vetted: continue if self.introducer.vetted[r_peer.get_hash()]: if r_peer.host == peer.peer_host and r_peer.port == peer.peer_server_port: continue peer_without_timestamp = TimestampedPeerInfo( r_peer.host, r_peer.port, uint64(0), ) peers.append(peer_without_timestamp) if len(peers) >= max_peers: break self.introducer.log.info(f"Sending vetted {peers}") msg = Message("respond_peers", RespondPeers(peers)) return msg
async def challenge_response( self, challenge_response: harvester_protocol.ChallengeResponse ): """ This is a response from the harvester, for a NewChallenge. Here we check if the proof of space is sufficiently good, and if so, we ask for the whole proof. """ if challenge_response.quality_string in self.harvester_responses_challenge: log.warning( f"Have already seen quality string {challenge_response.quality_string}" ) return height: uint32 = self.challenge_to_height[challenge_response.challenge_hash] number_iters = await self._get_required_iters( challenge_response.challenge_hash, challenge_response.quality_string, challenge_response.plot_size, ) if height < 1000: # As the difficulty adjusts, don't fetch all qualities if challenge_response.challenge_hash not in self.challenge_to_best_iters: self.challenge_to_best_iters[ challenge_response.challenge_hash ] = number_iters elif ( number_iters < self.challenge_to_best_iters[challenge_response.challenge_hash] ): self.challenge_to_best_iters[ challenge_response.challenge_hash ] = number_iters else: return estimate_secs: float = number_iters / self.proof_of_time_estimate_ips if challenge_response.challenge_hash not in self.challenge_to_estimates: self.challenge_to_estimates[challenge_response.challenge_hash] = [] self.challenge_to_estimates[challenge_response.challenge_hash].append( estimate_secs ) log.info(f"Estimate: {estimate_secs}, rate: {self.proof_of_time_estimate_ips}") if ( estimate_secs < self.config["pool_share_threshold"] or estimate_secs < self.config["propagate_threshold"] ): self.harvester_responses_challenge[ challenge_response.quality_string ] = challenge_response.challenge_hash request = harvester_protocol.RequestProofOfSpace( challenge_response.quality_string ) yield OutboundMessage( NodeType.HARVESTER, Message("request_proof_of_space", request), Delivery.RESPOND, ) self._state_changed("challenge")
async def request_header_signature( self, request: harvester_protocol.RequestHeaderSignature): """ The farmer requests a signature on the header hash, for one of the proofs that we found. A signature is created on the header hash using the plot private key. """ if request.quality_string not in self.challenge_hashes: return _, filename, _ = self.challenge_hashes[request.quality_string] plot_sk = PrivateKey.from_bytes( bytes.fromhex(self.plot_config["plots"][filename]["sk"])) header_hash_signature: PrependSignature = plot_sk.sign_prepend( request.header_hash) assert header_hash_signature.verify( [Util.hash256(request.header_hash)], [plot_sk.get_public_key()]) response: harvester_protocol.RespondHeaderSignature = harvester_protocol.RespondHeaderSignature( request.quality_string, header_hash_signature, ) yield OutboundMessage( NodeType.FARMER, Message("respond_header_signature", response), Delivery.RESPOND, )
async def api_call(full_message: Message, connection: WSChiaConnection, task_id): start_time = time.time() try: if self.received_message_callback is not None: await self.received_message_callback(connection) connection.log.info( f"<- {ProtocolMessageTypes(full_message.type).name} from peer " f"{connection.peer_node_id} {connection.peer_host}") message_type: str = ProtocolMessageTypes( full_message.type).name f = getattr(self.api, message_type, None) if f is None: self.log.error( f"Non existing function: {message_type}") raise ProtocolError(Err.INVALID_PROTOCOL_MESSAGE, [message_type]) if not hasattr(f, "api_function"): self.log.error( f"Peer trying to call non api function {message_type}" ) raise ProtocolError(Err.INVALID_PROTOCOL_MESSAGE, [message_type]) if hasattr(f, "peer_required"): coroutine = f(full_message.data, connection) else: coroutine = f(full_message.data) response: Optional[Message] = await asyncio.wait_for( coroutine, timeout=300) connection.log.debug( f"Time taken to process {message_type} from {connection.peer_node_id} is " f"{time.time() - start_time} seconds") if response is not None: response_message = Message(response.type, response.data, full_message.id) await connection.reply_to_request(response_message) except Exception as e: if self.connection_close_task is None: tb = traceback.format_exc() connection.log.error( f"Exception: {e}, closing connection {connection.get_peer_info()}. {tb}" ) else: connection.log.debug( f"Exception: {e} while closing connection") pass await connection.close() finally: if task_id in self.api_tasks: self.api_tasks.pop(task_id) if task_id in self.tasks_from_peer[ connection.peer_node_id]: self.tasks_from_peer[connection.peer_node_id].remove( task_id)
async def _on_connect(self): # Sends a handshake to the harvester msg = harvester_protocol.HarvesterHandshake( self._get_public_keys(), self.pool_public_keys ) yield OutboundMessage( NodeType.HARVESTER, Message("harvester_handshake", msg), Delivery.RESPOND ) if self.current_weight in self.challenges: for posf in self.challenges[self.current_weight]: message = harvester_protocol.NewChallenge(posf.challenge_hash) yield OutboundMessage( NodeType.HARVESTER, Message("new_challenge", message), Delivery.BROADCAST, )
async def respond_peers( self, request: introducer_protocol.RespondPeers ) -> OutboundMessageGenerator: """ We have received a list of full node peers that we can connect to. """ if self.server is None or self.wallet_state_manager is None: return conns = self.global_connections for peer in request.peer_list: conns.peers.add(peer) # Pseudo-message to close the connection yield OutboundMessage(NodeType.INTRODUCER, Message("", None), Delivery.CLOSE) unconnected = conns.get_unconnected_peers( recent_threshold=self.config["recent_peer_threshold"]) to_connect = unconnected[:self._num_needed_peers()] if not len(to_connect): return self.log.info(f"Trying to connect to peers: {to_connect}") tasks = [] for peer in to_connect: tasks.append( asyncio.create_task( self.server.start_client(peer, self._on_connect))) await asyncio.gather(*tasks)
async def new_proof_of_time( self, new_proof_of_time: peer_protocol.NewProofOfTime ) -> OutboundMessageGenerator: """ A proof of time, received by a peer full node. If we have the rest of the block, we can complete it. Otherwise, we just verify and propagate the proof. """ finish_block: bool = False propagate_proof: bool = False if self.store.get_unfinished_block( ( new_proof_of_time.proof.challenge_hash, new_proof_of_time.proof.number_of_iterations, ) ): finish_block = True elif new_proof_of_time.proof.is_valid(constants["DISCRIMINANT_SIZE_BITS"]): propagate_proof = True if finish_block: request = timelord_protocol.ProofOfTimeFinished(new_proof_of_time.proof) async for msg in self.proof_of_time_finished(request): yield msg if propagate_proof: yield OutboundMessage( NodeType.FULL_NODE, Message("new_proof_of_time", new_proof_of_time), Delivery.BROADCAST_TO_OTHERS, )
async def successful_handshake(self, connection): if connection.connection_type == NodeType.FULL_NODE: if connection.is_outbound: if self.full_node_peers_callback is not None: self.full_node_peers_callback( "mark_tried", connection.get_peer_info(), ) if self.wallet_callback is not None: self.wallet_callback( "make_tried", connection.get_peer_info(), ) if connection.is_feeler: connection.close() self.close(connection) return # Request peers after handshake. if connection.local_type == NodeType.FULL_NODE: await connection.send(Message("request_peers", "")) else: if self.full_node_peers_callback is not None: self.full_node_peers_callback( "new_inbound_connection", connection.get_peer_info(), ) yield connection
async def challenge_response( self, challenge_response: harvester_protocol.ChallengeResponse): """ This is a response from the harvester, for a NewChallenge. Here we check if the proof of space is sufficiently good, and if so, we ask for the whole proof. """ if challenge_response.quality_string in self.harvester_responses_challenge: log.warning( f"Have already seen quality string {challenge_response.quality_string}" ) return weight: uint128 = self.challenge_to_weight[ challenge_response.challenge_hash] height: uint32 = self.challenge_to_height[ challenge_response.challenge_hash] difficulty: uint64 = uint64(0) for posf in self.challenges[weight]: if posf.challenge_hash == challenge_response.challenge_hash: difficulty = posf.difficulty if difficulty == 0: raise RuntimeError("Did not find challenge") estimate_min = (self.proof_of_time_estimate_ips * self.constants["BLOCK_TIME_TARGET"] / self.constants["MIN_ITERS_PROPORTION"]) number_iters: uint64 = calculate_iterations_quality( challenge_response.quality_string, challenge_response.plot_size, difficulty, estimate_min, ) if height < 1000: # As the difficulty adjusts, don't fetch all qualities if challenge_response.challenge_hash not in self.challenge_to_best_iters: self.challenge_to_best_iters[ challenge_response.challenge_hash] = number_iters elif (number_iters < self.challenge_to_best_iters[ challenge_response.challenge_hash]): self.challenge_to_best_iters[ challenge_response.challenge_hash] = number_iters else: return estimate_secs: float = number_iters / self.proof_of_time_estimate_ips log.info( f"Estimate: {estimate_secs}, rate: {self.proof_of_time_estimate_ips}" ) if (estimate_secs < self.config["pool_share_threshold"] or estimate_secs < self.config["propagate_threshold"]): self.harvester_responses_challenge[ challenge_response. quality_string] = challenge_response.challenge_hash request = harvester_protocol.RequestProofOfSpace( challenge_response.quality_string) yield OutboundMessage( NodeType.HARVESTER, Message("request_proof_of_space", request), Delivery.RESPOND, )
async def invoke(*args, **kwargs): timeout = 60 if "timeout" in kwargs: timeout = kwargs["timeout"] attribute = getattr(class_for_type(self.connection_type), attr_name, None) if attribute is None: raise AttributeError(f"Node type {self.connection_type} does not have method {attr_name}") msg = Message(uint8(getattr(ProtocolMessageTypes, attr_name).value), args[0]) request_start_t = time.time() result = await self.create_request(msg, timeout) self.log.debug( f"Time for request {attr_name}: {self.get_peer_info()} = {time.time() - request_start_t}, " f"None? {result is None}" ) if result is not None: ret_attr = getattr(class_for_type(self.local_type), ProtocolMessageTypes(result.type).name, None) req_annotations = ret_attr.__annotations__ req = None for key in req_annotations: if key == "return" or key == "peer": continue else: req = req_annotations[key] assert req is not None result = req.from_bytes(result.data) return result
async def request_peers_with_peer_info( self, request: RequestPeers, peer_info: PeerInfo, ) -> AsyncGenerator[OutboundMessage, None]: max_peers = self.max_peers_to_send if self.global_connections.introducer_peers is None: return rawpeers = self.global_connections.introducer_peers.get_peers( max_peers * 5, True, self.recent_peer_threshold ) peers = [] for peer in rawpeers: if peer.get_hash() not in self.vetted: continue if self.vetted[peer.get_hash()]: if peer.host == peer_info.host and peer.port == peer_info.port: continue peer_without_timestamp = TimestampedPeerInfo( peer.host, peer.port, uint64(0), ) peers.append(peer_without_timestamp) if len(peers) >= max_peers: break log.info(f"Sending vetted {peers}") msg = Message("respond_peers", RespondPeers(peers)) yield OutboundMessage(NodeType.FULL_NODE, msg, Delivery.RESPOND) yield OutboundMessage(NodeType.WALLET, msg, Delivery.RESPOND)
async def test_blocks_load(self, two_nodes): num_blocks = 100 full_node_1, full_node_2, server_1, server_2 = two_nodes blocks = bt.get_consecutive_blocks(test_constants, num_blocks, [], 10) await server_2.start_client( PeerInfo("localhost", uint16(server_1._port)), None) await asyncio.sleep(2) # Allow connections to get made start_unf = time.time() for i in range(1, num_blocks): msg = Message("respond_block", full_node_protocol.RespondBlock(blocks[i])) server_1.push_message( OutboundMessage(NodeType.FULL_NODE, msg, Delivery.BROADCAST)) while time.time() - start_unf < 100: if (max( [h.height for h in full_node_2.blockchain.get_current_tips() ]) == num_blocks - 1): print( f"Time taken to process {num_blocks} is {time.time() - start_unf}" ) return await asyncio.sleep(0.1) raise Exception("Took too long to process blocks")
async def request_header_blocks( self, request: peer_protocol.RequestHeaderBlocks ) -> OutboundMessageGenerator: """ A peer requests a list of header blocks, by height. Used for syncing or light clients. """ start = time.time() if len(request.heights) > self.config["max_headers_to_send"]: raise errors.TooManyheadersRequested( f"The max number of headers is {self.config['max_headers_to_send']},\ but requested {len(request.heights)}" ) try: header_hashes: List[ HeaderBlock ] = self.blockchain.get_header_hashes_by_height( request.heights, request.tip_header_hash ) header_blocks: List[ HeaderBlock ] = await self.store.get_header_blocks_by_hash(header_hashes) log.info(f"Got header blocks by height {time.time() - start}") except KeyError: return except BlockNotInBlockchain as e: log.info(f"{e}") return response = peer_protocol.HeaderBlocks(request.tip_header_hash, header_blocks) yield OutboundMessage( NodeType.FULL_NODE, Message("header_blocks", response), Delivery.RESPOND )
async def farm_block(self, request): raw_puzzle_hash = decode_puzzle_hash(request["address"]) request = FarmNewBlockProtocol(raw_puzzle_hash) msg = Message("farm_new_block", request) await self.service.server.send_to_all([msg], NodeType.FULL_NODE) return {}
async def proof_of_space_finalized( self, proof_of_space_finalized: farmer_protocol.ProofOfSpaceFinalized): """ Full node notifies farmer that a proof of space has been completed. It gets added to the challenges list at that weight, and weight is updated if necessary """ get_proofs: bool = False if (proof_of_space_finalized.weight >= self.current_weight and proof_of_space_finalized.challenge_hash not in self.seen_challenges): # Only get proofs for new challenges, at a current or new weight get_proofs = True if proof_of_space_finalized.weight > self.current_weight: self.current_weight = proof_of_space_finalized.weight # TODO: ask the pool for this information coinbase: CoinbaseInfo = CoinbaseInfo( uint32(proof_of_space_finalized.height + 1), calculate_block_reward(proof_of_space_finalized.height), bytes.fromhex(self.key_config["pool_target"]), ) pool_sks: List[PrivateKey] = [ PrivateKey.from_bytes(bytes.fromhex(ce)) for ce in self.key_config["pool_sks"] ] coinbase_signature: PrependSignature = pool_sks[0].sign_prepend( bytes(coinbase)) self.coinbase_rewards[uint32(proof_of_space_finalized.height + 1)] = ( coinbase, coinbase_signature, ) log.info(f"\tCurrent weight set to {self.current_weight}") self.seen_challenges.add(proof_of_space_finalized.challenge_hash) if proof_of_space_finalized.weight not in self.challenges: self.challenges[proof_of_space_finalized.weight] = [ proof_of_space_finalized ] else: self.challenges[proof_of_space_finalized.weight].append( proof_of_space_finalized) self.challenge_to_weight[ proof_of_space_finalized. challenge_hash] = proof_of_space_finalized.weight self.challenge_to_height[ proof_of_space_finalized. challenge_hash] = proof_of_space_finalized.height if get_proofs: message = harvester_protocol.NewChallenge( proof_of_space_finalized.challenge_hash) yield OutboundMessage( NodeType.HARVESTER, Message("new_challenge", message), Delivery.BROADCAST, )
async def handle_message( triple: Tuple[ChiaConnection, Message, PeerConnections], api: Any ) -> AsyncGenerator[Tuple[ChiaConnection, OutboundMessage, PeerConnections], None]: """ Async generator which takes messages, parses, them, executes the right api function, and yields responses (to same connection, propagated, etc). """ connection, full_message, global_connections = triple try: if len(full_message.function) == 0 or full_message.function.startswith( "_"): # This prevents remote calling of private methods that start with "_" raise ProtocolError(Err.INVALID_PROTOCOL_MESSAGE, [full_message.function]) connection.log.info( f"<- {full_message.function} from peer {connection.get_peername()}" ) if full_message.function == "ping": ping_msg = Ping(full_message.data["nonce"]) assert connection.connection_type outbound_message = OutboundMessage( connection.connection_type, Message("pong", Pong(ping_msg.nonce)), Delivery.RESPOND, ) yield connection, outbound_message, global_connections return elif full_message.function == "pong": return f_with_peer_name = getattr(api, full_message.function + "_with_peer_name", None) if f_with_peer_name is not None: result = f_with_peer_name(full_message.data, connection.get_peername()) else: f = getattr(api, full_message.function, None) if f is None: raise ProtocolError(Err.INVALID_PROTOCOL_MESSAGE, [full_message.function]) result = f(full_message.data) if isinstance(result, AsyncGenerator): async for outbound_message in result: yield connection, outbound_message, global_connections else: await result except Exception: tb = traceback.format_exc() connection.log.error(f"Error, closing connection {connection}. {tb}") # TODO: Exception means peer gave us invalid information, so ban this peer. global_connections.close(connection)
async def read_one_message(self) -> Message: size = await self.reader.readexactly(LENGTH_BYTES) full_message_length = int.from_bytes(size, "big") full_message: bytes = await self.reader.readexactly(full_message_length) full_message_loaded: Any = cbor.loads(full_message) self.bytes_read += LENGTH_BYTES + full_message_length self.last_message_time = time.time() return Message(full_message_loaded["f"], full_message_loaded["d"])
async def farm_block(self, websocket, request, response_api): puzzle_hash = bytes.fromhex(request["puzzle_hash"]) request = FarmNewBlockProtocol(puzzle_hash) msg = OutboundMessage( NodeType.FULL_NODE, Message("farm_new_block", request), Delivery.BROADCAST, ) self.wallet_node.server.push_message(msg)
async def request_peers( self, request: RequestPeers ) -> AsyncGenerator[OutboundMessage, None]: max_peers = self.config["max_peers_to_send"] peers = self.server.global_connections.peers.get_peers( max_peers, True, self.config["recent_peer_threshold"] ) msg = Message("peers", Peers(peers)) yield OutboundMessage(NodeType.FULL_NODE, msg, Delivery.RESPOND)
async def on_connect(self, peer: WSChiaConnection): # Sends a handshake to the harvester msg = harvester_protocol.HarvesterHandshake( self.get_public_keys(), self.pool_public_keys, ) if peer.connection_type is NodeType.HARVESTER: msg = Message("harvester_handshake", msg) await peer.send_message(msg)
async def request_proof_of_space( self, request: harvester_protocol.RequestProofOfSpace): """ The farmer requests a proof of space, for one of the plots. We look up the correct plot based on the plot id and response number, lookup the proof, and return it. """ response: Optional[harvester_protocol.RespondProofOfSpace] = None challenge_hash = request.challenge_hash filename = Path(request.plot_id).resolve() index = request.response_number proof_xs: bytes plot_info = self.provers[filename] try: try: proof_xs = plot_info.prover.get_full_proof( challenge_hash, index) except RuntimeError: prover = DiskProver(str(filename)) self.provers[filename] = PlotInfo( prover, plot_info.pool_public_key, plot_info.farmer_public_key, plot_info.plot_public_key, plot_info.local_sk, plot_info.file_size, plot_info.time_modified, ) proof_xs = self.provers[filename].prover.get_full_proof( challenge_hash, index) except KeyError: log.warning(f"KeyError plot {filename} does not exist.") plot_info = self.provers[filename] plot_public_key = ProofOfSpace.generate_plot_public_key( plot_info.local_sk.get_g1(), plot_info.farmer_public_key) proof_of_space: ProofOfSpace = ProofOfSpace( challenge_hash, plot_info.pool_public_key, plot_public_key, uint8(self.provers[filename].prover.get_size()), proof_xs, ) response = harvester_protocol.RespondProofOfSpace( request.plot_id, request.response_number, proof_of_space, ) if response: yield OutboundMessage( NodeType.FARMER, Message("respond_proof_of_space", response), Delivery.RESPOND, )
async def farm_block(self, request): puzzle_hash = request["puzzle_hash"] raw_puzzle_hash = decode_puzzle_hash(puzzle_hash) request = FarmNewBlockProtocol(raw_puzzle_hash) msg = OutboundMessage( NodeType.FULL_NODE, Message("farm_new_block", request), Delivery.BROADCAST, ) self.service.server.push_message(msg) return {"success": True}
async def on_connect(): # Sends a handshake to the harvester pool_sks: List[PrivateKey] = [ PrivateKey.from_bytes(bytes.fromhex(ce)) for ce in key_config["pool_sks"] ] msg = HarvesterHandshake([sk.get_public_key() for sk in pool_sks]) yield OutboundMessage(NodeType.HARVESTER, Message("harvester_handshake", msg), Delivery.BROADCAST)
async def reject_block(self, header_hash: bytes32, node_id: bytes32) -> OutboundMessageGenerator: """ A rejection was received from a peer, so we remove this peer and close the connection, since we assume this peer cannot help us sync up. All blocks are removed from the request set. """ self.current_outbound_sets.pop(node_id, None) yield OutboundMessage(NodeType.FULL_NODE, Message("", None), Delivery.CLOSE)
async def request_all_header_hashes( self, request: peer_protocol.RequestAllHeaderHashes ) -> OutboundMessageGenerator: try: header_hashes = self.blockchain.get_header_hashes(request.tip_header_hash) message = Message( "all_header_hashes", peer_protocol.AllHeaderHashes(header_hashes) ) yield OutboundMessage(NodeType.FULL_NODE, message, Delivery.RESPOND) except ValueError: log.info("Do not have requested header hashes.")
async def request_block( self, request_block: peer_protocol.RequestBlock ) -> OutboundMessageGenerator: block: Optional[FullBlock] = await self.store.get_block( request_block.header_hash) if block is not None: yield OutboundMessage( NodeType.FULL_NODE, Message("block", peer_protocol.Block(block)), Delivery.RESPOND, )
async def test_unfinished_blocks_load(self, two_nodes): num_blocks = 10 full_node_1, full_node_2, server_1, server_2 = two_nodes blocks = bt.get_consecutive_blocks(test_constants, num_blocks, [], 10) for i in range(1, num_blocks - 1): async for _ in full_node_1.block(peer_protocol.Block(blocks[i])): pass await server_2.start_client( PeerInfo(server_1._host, uint16(server_1._port)), None ) await asyncio.sleep(2) # Allow connections to get made num_unfinished_blocks = 1000 start_unf = time.time() for i in range(num_unfinished_blocks): msg = Message("unfinished_block", peer_protocol.UnfinishedBlock(blocks[9])) server_1.push_message( OutboundMessage(NodeType.FULL_NODE, msg, Delivery.BROADCAST) ) # Send the whole block ast the end so we can detect when the node is done block_msg = Message("block", peer_protocol.Block(blocks[9])) server_1.push_message( OutboundMessage(NodeType.FULL_NODE, block_msg, Delivery.BROADCAST) ) while time.time() - start_unf < 100: if ( max([h.height for h in full_node_2.blockchain.get_current_tips()]) == num_blocks - 1 ): print( f"Time taken to process {num_unfinished_blocks} is {time.time() - start_unf}" ) return await asyncio.sleep(0.1) raise Exception("Took too long to process blocks")
async def _read_one_message(self) -> Optional[Message]: try: message: WSMessage = await self.ws.receive(30) except asyncio.TimeoutError: # self.ws._closed if we didn't receive a ping / pong if self.ws._closed: asyncio.create_task(self.close()) await asyncio.sleep(3) return None return None if self.connection_type is not None: connection_type_str = NodeType(self.connection_type).name.lower() else: connection_type_str = "" if message.type == WSMsgType.CLOSING: self.log.debug( f"Closing connection to {connection_type_str} {self.peer_host}:" f"{self.peer_server_port}/" f"{self.peer_port}") asyncio.create_task(self.close()) await asyncio.sleep(3) elif message.type == WSMsgType.CLOSE: self.log.debug( f"Peer closed connection {connection_type_str} {self.peer_host}:" f"{self.peer_server_port}/" f"{self.peer_port}") asyncio.create_task(self.close()) await asyncio.sleep(3) elif message.type == WSMsgType.CLOSED: if not self.closed: asyncio.create_task(self.close()) await asyncio.sleep(3) return None elif message.type == WSMsgType.BINARY: data = message.data full_message_loaded: Message = Message.from_bytes(data) self.bytes_read += len(data) self.last_message_time = time.time() return full_message_loaded elif message.type == WSMsgType.ERROR: self.log.error(f"WebSocket Error: {message}") if message.data.code == WSCloseCode.MESSAGE_TOO_BIG: asyncio.create_task(self.close(300)) else: asyncio.create_task(self.close()) await asyncio.sleep(3) else: self.log.error(f"Unexpected WebSocket message type: {message}") asyncio.create_task(self.close()) await asyncio.sleep(3) return None