def process_block_broadcast(self, msg, connection: AbstractRelayConnection) -> None: """ Handle broadcast message receive from bloXroute. This is typically an encrypted block. """ # TODO handle the situation where txs that received from relays while syncing are in the blocks that were # ignored while syncing, so these txs won't be cleaned for 3 days if not self._node.should_process_block_hash(msg.block_hash()): return block_stats.add_block_event( msg, BlockStatEventType.ENC_BLOCK_RECEIVED_BY_GATEWAY_FROM_NETWORK, network_num=connection.network_num, more_info=stats_format.connection(connection)) block_hash = msg.block_hash() is_encrypted = msg.is_encrypted() self._node.track_block_from_bdn_handling_started( block_hash, connection.peer_desc) if not is_encrypted: block = msg.blob() self._handle_decrypted_block(block, connection) return cipherblob = msg.blob() expected_hash = Sha256Hash(crypto.double_sha256(cipherblob)) if block_hash != expected_hash: connection.log_warning(log_messages.BLOCK_WITH_INCONSISTENT_HASHES, expected_hash, block_hash) return if self._node.in_progress_blocks.has_encryption_key_for_hash( block_hash): connection.log_trace( "Already had key for received block. Sending block to node.") decrypt_start_timestamp = time.time() decrypt_start_datetime = datetime.datetime.utcnow() block = self._node.in_progress_blocks.decrypt_ciphertext( block_hash, cipherblob) if block is not None: block_stats.add_block_event( msg, BlockStatEventType.ENC_BLOCK_DECRYPTED_SUCCESS, start_date_time=decrypt_start_datetime, end_date_time=datetime.datetime.utcnow(), network_num=connection.network_num, more_info=stats_format.timespan(decrypt_start_timestamp, time.time())) self._handle_decrypted_block( block, connection, encrypted_block_hash_hex=convert.bytes_to_hex( block_hash.binary)) else: block_stats.add_block_event( msg, BlockStatEventType.ENC_BLOCK_DECRYPTION_ERROR, network_num=connection.network_num) else: connection.log_trace("Received encrypted block. Storing.") self._node.in_progress_blocks.add_ciphertext( block_hash, cipherblob) block_received_message = BlockReceivedMessage(block_hash) conns = self._node.broadcast( block_received_message, connection, connection_types=(ConnectionType.GATEWAY, )) block_stats.add_block_event_by_block_hash( block_hash, BlockStatEventType.ENC_BLOCK_SENT_BLOCK_RECEIPT, network_num=connection.network_num, peers=conns, )
def _handle_decrypted_block( self, bx_block: memoryview, connection: AbstractRelayConnection, encrypted_block_hash_hex: Optional[str] = None, recovered: bool = False, recovered_txs_source: Optional[RecoveredTxsSource] = None) -> None: transaction_service = self._node.get_tx_service() message_converter = self._node.message_converter assert message_converter is not None valid_block = self._validate_compressed_block_header(bx_block) if not valid_block.is_valid: reason = valid_block.reason assert reason is not None block_stats.add_block_event_by_block_hash( valid_block.block_hash, BlockStatEventType.BLOCK_DECOMPRESSED_FAILED_VALIDATION, connection.network_num, more_info=reason) return # TODO: determine if a real block or test block. Discard if test block. if self._node.remote_node_conn or self._node.has_active_blockchain_peer( ): try: (block_message, block_info, unknown_sids, unknown_hashes) = message_converter.bx_block_to_block( bx_block, transaction_service) block_content_debug_utils.log_compressed_block_debug_info( transaction_service, bx_block) except MessageConversionError as e: block_stats.add_block_event_by_block_hash( e.msg_hash, BlockStatEventType.BLOCK_CONVERSION_FAILED, network_num=connection.network_num, conversion_type=e.conversion_type.value) transaction_service.on_block_cleaned_up(e.msg_hash) connection.log_warning(log_messages.FAILED_TO_DECOMPRESS_BLOCK, e.msg_hash, e) return else: connection.log_warning(log_messages.LACK_BLOCKCHAIN_CONNECTION) return block_hash = block_info.block_hash all_sids = block_info.short_ids if encrypted_block_hash_hex is not None: block_stats.add_block_event_by_block_hash( block_hash, BlockStatEventType.BLOCK_TO_ENC_BLOCK_MATCH, matching_block_hash=encrypted_block_hash_hex, matching_block_type=StatBlockType.ENCRYPTED.value, network_num=connection.network_num) self.cancel_hold_timeout(block_hash, connection) if recovered: block_stats.add_block_event_by_block_hash( block_hash, BlockStatEventType.BLOCK_RECOVERY_COMPLETED, network_num=connection.network_num, more_info=str(recovered_txs_source)) if block_hash in self._node.blocks_seen.contents: block_stats.add_block_event_by_block_hash( block_hash, BlockStatEventType.BLOCK_DECOMPRESSED_IGNORE_SEEN, start_date_time=block_info.start_datetime, end_date_time=block_info.end_datetime, network_num=connection.network_num, prev_block_hash=block_info.prev_block_hash, original_size=block_info.original_size, compressed_size=block_info.compressed_size, txs_count=block_info.txn_count, blockchain_network=self._node.opts.blockchain_network, blockchain_protocol=self._node.opts.blockchain_protocol, matching_block_hash=block_info.compressed_block_hash, matching_block_type=StatBlockType.COMPRESSED.value, more_info=stats_format.duration(block_info.duration_ms)) self._node.track_block_from_bdn_handling_ended(block_hash) transaction_service.track_seen_short_ids(block_hash, all_sids) connection.log_info("Discarding duplicate block {} from the BDN.", block_hash) if block_message is not None: self._node.on_block_received_from_bdn(block_hash, block_message) if self._node.block_queuing_service_manager.get_block_data( block_hash) is None: self._node.block_queuing_service_manager.store_block_data( block_hash, block_message) return if not recovered: connection.log_info("Received block {} from the BDN.", block_hash) else: connection.log_info("Successfully recovered block {}.", block_hash) if block_message is not None: compression_rate = block_info.compression_rate assert compression_rate is not None block_stats.add_block_event_by_block_hash( block_hash, BlockStatEventType.BLOCK_DECOMPRESSED_SUCCESS, start_date_time=block_info.start_datetime, end_date_time=block_info.end_datetime, network_num=connection.network_num, prev_block_hash=block_info.prev_block_hash, original_size=block_info.original_size, compressed_size=block_info.compressed_size, txs_count=block_info.txn_count, blockchain_network=self._node.opts.blockchain_network, blockchain_protocol=self._node.opts.blockchain_protocol, matching_block_hash=block_info.compressed_block_hash, matching_block_type=StatBlockType.COMPRESSED.value, more_info="Compression rate {}, Decompression time {}, " "Queued behind {} blocks".format( stats_format.percentage(compression_rate), stats_format.duration(block_info.duration_ms), self._node.block_queuing_service_manager. get_length_of_each_queuing_service_stats_format())) self._on_block_decompressed(block_message) if recovered or self._node.block_queuing_service_manager.is_in_any_queuing_service( block_hash): self._node.block_queuing_service_manager.update_recovered_block( block_hash, block_message) else: self._node.block_queuing_service_manager.push( block_hash, block_message) gateway_bdn_performance_stats_service.log_block_from_bdn() self._node.on_block_received_from_bdn(block_hash, block_message) transaction_service.track_seen_short_ids(block_hash, all_sids) self._node.publish_block(None, block_hash, block_message, FeedSource.BDN_SOCKET) self._node.log_blocks_network_content(self._node.network_num, block_message) else: if self._node.block_queuing_service_manager.is_in_any_queuing_service( block_hash) and not recovered: connection.log_trace( "Handling already queued block again. Ignoring.") return self._node.block_recovery_service.add_block( bx_block, block_hash, unknown_sids, unknown_hashes) block_stats.add_block_event_by_block_hash( block_hash, BlockStatEventType.BLOCK_DECOMPRESSED_WITH_UNKNOWN_TXS, start_date_time=block_info.start_datetime, end_date_time=block_info.end_datetime, network_num=connection.network_num, prev_block_hash=block_info.prev_block_hash, original_size=block_info.original_size, compressed_size=block_info.compressed_size, txs_count=block_info.txn_count, blockchain_network=self._node.opts.blockchain_network, blockchain_protocol=self._node.opts.blockchain_protocol, matching_block_hash=block_info.compressed_block_hash, matching_block_type=StatBlockType.COMPRESSED.value, more_info="{} sids, {} hashes, [{},...]".format( len(unknown_sids), len(unknown_hashes), unknown_sids[:5])) connection.log_info( "Block {} requires short id recovery. Querying BDN...", block_hash) self.start_transaction_recovery(unknown_sids, unknown_hashes, block_hash, connection) if recovered: # should never happen –– this should not be called on blocks that have not recovered connection.log_error(log_messages.BLOCK_DECOMPRESSION_FAILURE, block_hash) else: self._node.block_queuing_service_manager.push( block_hash, waiting_for_recovery=True)
def _handle_decrypted_consensus_block( self, bx_block: memoryview, connection: AbstractRelayConnection, encrypted_block_hash_hex: Optional[str] = None, recovered: bool = False, recovered_txs_source: Optional[RecoveredTxsSource] = None ): transaction_service = self._node.get_tx_service() if self._node.has_active_blockchain_peer() or self._node.remote_node_conn: try: block_message, block_info, unknown_sids, unknown_hashes = \ self._node.consensus_message_converter.bx_block_to_block(bx_block, transaction_service) except MessageConversionError as e: block_stats.add_block_event_by_block_hash( e.msg_hash, BlockStatEventType.BLOCK_CONVERSION_FAILED, network_num=connection.network_num, broadcast_type=BroadcastMessageType.CONSENSUS, conversion_type=e.conversion_type.value ) transaction_service.on_block_cleaned_up(e.msg_hash) connection.log_warning(log_messages.FAILED_TO_DECOMPRESS_BLOCK_ONT_CONSENSUS, e.msg_hash, e) return else: connection.log_warning(log_messages.LACK_BLOCKCHAIN_CONNECTION_ONT_CONSENSUS) return block_hash = block_info.block_hash all_sids = block_info.short_ids if encrypted_block_hash_hex is not None: block_stats.add_block_event_by_block_hash(block_hash, BlockStatEventType.BLOCK_TO_ENC_BLOCK_MATCH, matching_block_hash=encrypted_block_hash_hex, matching_block_type=StatBlockType.ENCRYPTED.value, network_num=connection.network_num, broadcast_type=BroadcastMessageType.CONSENSUS) self.cancel_hold_timeout(block_hash, connection) if recovered: block_stats.add_block_event_by_block_hash(block_hash, BlockStatEventType.BLOCK_RECOVERY_COMPLETED, network_num=connection.network_num, broadcast_type=BroadcastMessageType.CONSENSUS, more_info=str(recovered_txs_source)) if block_hash in self._node.blocks_seen.contents: block_stats.add_block_event_by_block_hash(block_hash, BlockStatEventType.BLOCK_DECOMPRESSED_IGNORE_SEEN, start_date_time=block_info.start_datetime, end_date_time=block_info.end_datetime, network_num=connection.network_num, broadcast_type=BroadcastMessageType.CONSENSUS, prev_block_hash=block_info.prev_block_hash, original_size=block_info.original_size, compressed_size=block_info.compressed_size, txs_count=block_info.txn_count, blockchain_network=self._node.opts.blockchain_protocol, blockchain_protocol=self._node.opts.blockchain_network, matching_block_hash=block_info.compressed_block_hash, matching_block_type=StatBlockType.COMPRESSED.value, more_info=stats_format.duration(block_info.duration_ms)) self._node.track_block_from_bdn_handling_ended(block_hash) transaction_service.track_seen_short_ids(block_hash, all_sids) connection.log_info( "Discarding duplicate consensus block {} from the BDN.", block_hash ) return if not recovered: connection.log_info("Received consensus block {} from the BDN.", block_hash) else: connection.log_info("Successfully recovered consensus block {}.", block_hash) if block_message is not None: block_stats.add_block_event_by_block_hash(block_hash, BlockStatEventType.BLOCK_DECOMPRESSED_SUCCESS, start_date_time=block_info.start_datetime, end_date_time=block_info.end_datetime, network_num=connection.network_num, broadcast_type=BroadcastMessageType.CONSENSUS, prev_block_hash=block_info.prev_block_hash, original_size=block_info.original_size, compressed_size=block_info.compressed_size, txs_count=block_info.txn_count, blockchain_network=self._node.opts.blockchain_protocol, blockchain_protocol=self._node.opts.blockchain_network, matching_block_hash=block_info.compressed_block_hash, matching_block_type=StatBlockType.COMPRESSED.value, peer=connection.peer_desc, more_info="Consensus compression rate {}, Decompression time {}, " "Queued behind {} blocks".format( stats_format.percentage(block_info.compression_rate), stats_format.duration(block_info.duration_ms), self._node.block_queueing_service_manager.get_length_of_each_queuing_service_stats_format())) if recovered or block_hash in self._node.block_queueing_service_manager: self._node.block_queueing_service_manager.update_recovered_block(block_hash, block_message) else: self._node.block_queueing_service_manager.push(block_hash, block_message) self._node.block_recovery_service.cancel_recovery_for_block(block_hash) # self._node.blocks_seen.add(block_hash) transaction_service.track_seen_short_ids(block_hash, all_sids) else: if block_hash in self._node.block_queueing_service_manager and not recovered: connection.log_trace("Handling already queued consensus block again. Ignoring.") return self._node.block_recovery_service.add_block(bx_block, block_hash, unknown_sids, unknown_hashes) block_stats.add_block_event_by_block_hash(block_hash, BlockStatEventType.BLOCK_DECOMPRESSED_WITH_UNKNOWN_TXS, start_date_time=block_info.start_datetime, end_date_time=block_info.end_datetime, network_num=connection.network_num, broadcast_type=BroadcastMessageType.CONSENSUS, prev_block_hash=block_info.prev_block_hash, original_size=block_info.original_size, compressed_size=block_info.compressed_size, txs_count=block_info.txn_count, blockchain_network=self._node.opts.blockchain_protocol, blockchain_protocol=self._node.opts.blockchain_network, matching_block_hash=block_info.compressed_block_hash, matching_block_type=StatBlockType.COMPRESSED.value, more_info="{} sids, {} hashes".format( len(unknown_sids), len(unknown_hashes))) connection.log_info("Consensus block {} requires short id recovery. Querying BDN...", block_hash) self.start_transaction_recovery(unknown_sids, unknown_hashes, block_hash, connection) if recovered: # should never happen –– this should not be called on blocks that have not recovered connection.log_error(log_messages.BLOCK_DECOMPRESSION_FAILURE_ONT_CONSENSUS, block_hash) else: self._node.block_queueing_service_manager.push(block_hash, waiting_for_recovery=True)