Example #1
0
    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,
            )
Example #2
0
    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)