def compact_block_to_bx_block(
        self, compact_block: CompactBlockBtcMessage,
        transaction_service: TransactionService
    ) -> CompactBlockCompressionResult:
        """
         Handle decompression of Bitcoin compact block.
         Decompression converts compact block message to full block message.
         """
        compress_start_datetime = datetime.utcnow()
        block_header = compact_block.block_header()
        sha256_hash = hashlib.sha256()
        sha256_hash.update(block_header)
        sha256_hash.update(compact_block.short_nonce_buf())
        hex_digest = sha256_hash.digest()
        key = hex_digest[0:16]

        short_ids = compact_block.short_ids()

        short_id_to_tx_contents = {}

        for tx_hash in transaction_service.iter_transaction_hashes():
            tx_hash_binary = tx_hash.binary[::-1]
            tx_short_id = compute_short_id(key, tx_hash_binary)
            if tx_short_id in short_ids:
                tx_content = transaction_service.get_transaction_by_hash(
                    tx_hash)
                if tx_content is None:
                    logger.debug(
                        "Hash {} is known by transactions service but content is missing.",
                        tx_hash)
                else:
                    short_id_to_tx_contents[tx_short_id] = tx_content
            if len(short_id_to_tx_contents) == len(short_ids):
                break

        block_transactions = []
        missing_transactions_indices = []
        pre_filled_transactions = compact_block.pre_filled_transactions()
        total_txs_count = len(pre_filled_transactions) + len(short_ids)

        size = 0
        block_msg_parts = deque()

        block_msg_parts.append(block_header)
        size += len(block_header)

        tx_count_size = btc_messages_util.get_sizeof_btc_varint(
            total_txs_count)
        tx_count_buf = bytearray(tx_count_size)
        btc_messages_util.pack_int_to_btc_varint(total_txs_count, tx_count_buf,
                                                 0)
        block_msg_parts.append(tx_count_buf)
        size += tx_count_size

        short_ids_iter = iter(short_ids.keys())

        for index in range(total_txs_count):
            if index not in pre_filled_transactions:
                short_id = next(short_ids_iter)

                if short_id in short_id_to_tx_contents:
                    short_tx = short_id_to_tx_contents[short_id]
                    block_msg_parts.append(short_tx)
                    block_transactions.append(short_tx)
                    size += len(short_tx)
                else:
                    missing_transactions_indices.append(index)
                    block_transactions.append(None)
            else:
                pre_filled_transaction = pre_filled_transactions[index]
                block_msg_parts.append(pre_filled_transaction)
                block_transactions.append(pre_filled_transaction)
                size += len(pre_filled_transaction)

        recovered_item = CompactBlockRecoveryData(block_transactions,
                                                  block_header,
                                                  compact_block.magic(),
                                                  transaction_service)

        block_info = BlockInfo(compact_block.block_hash(), [],
                               compress_start_datetime,
                               compress_start_datetime, 0, None, None, None,
                               len(compact_block.rawbytes()), None, None, [])

        if len(missing_transactions_indices) > 0:
            recovery_index = self._last_recovery_idx
            self._last_recovery_idx += 1
            self._recovery_items[
                recovery_index] = recovered_item  # pyre-ignore
            return CompactBlockCompressionResult(False, block_info, None,
                                                 recovery_index,
                                                 missing_transactions_indices,
                                                 [])
        result = CompactBlockCompressionResult(False, block_info, None, None,
                                               [], [])
        return self._recovered_compact_block_to_bx_block(
            result, recovered_item)
Ejemplo n.º 2
0
    def msg_compact_block(self, msg: CompactBlockBtcMessage) -> None:
        """
        Handle COMPACT BLOCK message from Bitcoin node
        :param msg: COMPACT BLOCK message
        """

        block_hash = msg.block_hash()
        if not self.node.should_process_block_hash(block_hash):
            return

        short_ids_count = len(msg.short_ids())
        block_stats.add_block_event_by_block_hash(
            block_hash,
            BlockStatEventType.COMPACT_BLOCK_RECEIVED_FROM_BLOCKCHAIN_NODE,
            network_num=self.connection.network_num,
            peer=self.connection.peer_desc,
            more_info="{} short ids".format(short_ids_count))

        if block_hash in self.node.blocks_seen.contents:
            self.node.on_block_seen_by_blockchain_node(block_hash)
            block_stats.add_block_event_by_block_hash(
                block_hash,
                BlockStatEventType.
                COMPACT_BLOCK_RECEIVED_FROM_BLOCKCHAIN_NODE_IGNORE_SEEN,
                network_num=self.connection.network_num,
                peer=self.connection.peer_desc)
            self.connection.log_info(
                "Discarding duplicate block {} from local blockchain node.",
                block_hash)
            return

        max_time_offset = self.node.opts.blockchain_block_interval * self.node.opts.blockchain_ignore_block_interval_count
        if time.time() - msg.timestamp() >= max_time_offset:
            self.connection.log_trace(
                "Received block {} more than {} seconds after it was created ({}). Ignoring.",
                block_hash, max_time_offset, msg.timestamp())
            return

        self.node.track_block_from_node_handling_started(block_hash)

        if short_ids_count < self.node.opts.compact_block_min_tx_count:
            self.connection.log_debug(
                "Compact block {} contains {} short transactions, less than limit {}. Requesting full block.",
                convert.bytes_to_hex(msg.block_hash().binary), short_ids_count,
                btc_constants.BTC_COMPACT_BLOCK_DECOMPRESS_MIN_TX_COUNT)
            get_data_msg = GetDataBtcMessage(magic=self.magic,
                                             inv_vects=[
                                                 (InventoryType.MSG_BLOCK,
                                                  msg.block_hash())
                                             ])
            self.node.send_msg_to_node(get_data_msg)
            block_stats.add_block_event_by_block_hash(
                block_hash,
                BlockStatEventType.COMPACT_BLOCK_REQUEST_FULL,
                network_num=self.connection.network_num)
            return

        self.node.block_cleanup_service.on_new_block_received(
            msg.block_hash(), msg.prev_block_hash())
        self.node.on_block_seen_by_blockchain_node(block_hash)

        self.connection.log_info(
            "Processing compact block {} from local Bitcoin node.", block_hash)

        try:
            parse_result = self.node.block_processing_service.process_compact_block(
                msg, self.connection)
        except MessageConversionError as e:
            block_stats.add_block_event_by_block_hash(
                e.msg_hash,
                BlockStatEventType.BLOCK_CONVERSION_FAILED,
                network_num=self.connection.network_num,
                conversion_type=e.conversion_type.value)
            self.connection.log_warning(log_messages.PROCESS_BLOCK_FAILURE,
                                        e.msg_hash, e)
            get_data_msg = GetDataBtcMessage(magic=self.magic,
                                             inv_vects=[
                                                 (InventoryType.MSG_BLOCK,
                                                  msg.block_hash())
                                             ])
            self.node.send_msg_to_node(get_data_msg)
            return

        if not parse_result.success:
            self._recovery_compact_blocks.add(block_hash, parse_result)

            get_block_txs_msg = GetBlockTransactionsBtcMessage(
                magic=self.magic,
                block_hash=block_hash,
                indices=parse_result.missing_indices)
            self.node.send_msg_to_node(get_block_txs_msg)