def msg_txs(self, msg: TxsMessage):
        if ConnectionType.RELAY_TRANSACTION not in self.CONNECTION_TYPE:
            self.log_error(log_messages.UNEXPECTED_TXS_ON_NON_RELAY_CONN, msg)
            return

        transactions = msg.get_txs()
        tx_service = self.node.get_tx_service()

        tx_stats.add_txs_by_short_ids_event(
            map(lambda x: x.short_id, transactions),
            TransactionStatEventType.
            TX_UNKNOWN_SHORT_IDS_REPLY_RECEIVED_BY_GATEWAY_FROM_RELAY,
            network_num=self.node.network_num,
            peer=stats_format.connection(self),
            found_tx_hashes=map(lambda x: convert.bytes_to_hex(x.hash.binary),
                                transactions))

        for transaction in transactions:
            tx_hash, transaction_contents, short_id = transaction

            assert tx_hash is not None
            assert short_id is not None

            self.node.block_recovery_service.check_missing_sid(short_id)

            if not tx_service.has_short_id(short_id):
                tx_service.assign_short_id(tx_hash, short_id)

            self.node.block_recovery_service.check_missing_tx_hash(tx_hash)

            if not tx_service.has_transaction_contents(tx_hash):
                assert transaction_contents is not None
                tx_service.set_transaction_contents(tx_hash,
                                                    transaction_contents)

            tx_stats.add_tx_by_hash_event(
                tx_hash,
                TransactionStatEventType.
                TX_UNKNOWN_TRANSACTION_RECEIVED_BY_GATEWAY_FROM_RELAY,
                self.node.network_num,
                short_id,
                peer=stats_format.connection(self))

        self.node.block_processing_service.retry_broadcast_recovered_blocks(
            self)

        for block_awaiting_recovery in self.node.block_recovery_service.get_blocks_awaiting_recovery(
        ):
            self.node.block_processing_service.schedule_recovery_retry(
                block_awaiting_recovery)
Пример #2
0
    def record_block_receipt(self, cipher_hash, connection):
        """
        Records a receipt of a block hash. Releases key if threshold reached.
        :param cipher_hash encrypted block ObjectHash
        :param connection posting block received receipt
        """
        if cipher_hash in self._receipt_tracker:
            self._receipt_tracker[cipher_hash] += 1
            block_stats.add_block_event_by_block_hash(
                cipher_hash,
                BlockStatEventType.ENC_BLOCK_RECEIVED_BLOCK_RECEIPT,
                network_num=self._node.network_num,
                peers=[connection],
                more_info="{}, {} receipts".format(
                    stats_format.connection(connection),
                    self._receipt_tracker[cipher_hash]))

            if self._are_enough_receipts_received(cipher_hash):
                logger.debug(
                    "Received enough block receipt messages. Releasing key for block with hash: {}",
                    convert.bytes_to_hex(cipher_hash.binary))
                self._send_key(cipher_hash)
                self._node.alarm_queue.unregister_alarm(
                    self._alarms[cipher_hash])
                del self._receipt_tracker[cipher_hash]
                del self._alarms[cipher_hash]
Пример #3
0
    def place_hold(self, block_hash, connection):
        """
        Places hold on block hash and propagates message.
        :param block_hash: ObjectHash
        :param connection:
        """
        block_stats.add_block_event_by_block_hash(
            block_hash,
            BlockStatEventType.BLOCK_HOLD_REQUESTED,
            network_num=connection.network_num,
            more_info=stats_format.connection(connection))

        if block_hash in self._node.blocks_seen.contents:
            return

        if block_hash not in self._holds.contents:
            self._holds.add(block_hash, BlockHold(time.time(), connection))
            conns = self._node.broadcast(BlockHoldingMessage(
                block_hash, self._node.network_num),
                                         broadcasting_conn=connection,
                                         connection_types=[
                                             ConnectionType.RELAY_BLOCK,
                                             ConnectionType.GATEWAY
                                         ])
            if len(conns) > 0:
                block_stats.add_block_event_by_block_hash(
                    block_hash,
                    BlockStatEventType.BLOCK_HOLD_SENT_BY_GATEWAY_TO_PEERS,
                    network_num=self._node.network_num,
                    more_info=stats_format.connections(conns))
Пример #4
0
 def _holding_timeout(self, block_hash, hold):
     block_stats.add_block_event_by_block_hash(
         block_hash,
         BlockStatEventType.BLOCK_HOLD_TIMED_OUT,
         network_num=hold.connection.network_num,
         more_info=stats_format.connection(hold.connection))
     self._process_and_broadcast_block(hold.block_message, hold.connection)
    def msg_compressed_block_txs(self, msg: CompressedBlockTxsMessage):
        start_time = time.time()

        self.msg_txs(msg.to_txs_message(),
                     RecoveredTxsSource.COMPRESSED_BLOCK_TXS_RECEIVED)

        block_stats.add_block_event_by_block_hash(
            msg.block_hash(),
            BlockStatEventType.ENC_BLOCK_COMPRESSED_TXS_RECEIVED_BY_GATEWAY,
            network_num=msg.network_num(),
            peers=[self],
            more_info="{}. processing time: {}".format(
                stats_format.connection(self),
                stats_format.timespan(start_time, time.time())))
Пример #6
0
    def cancel_hold_timeout(self, block_hash, connection):
        """
        Lifts hold on block hash and cancels timeout.
        :param block_hash: ObjectHash
        :param connection: connection cancelling hold
        """
        if block_hash in self._holds.contents:
            block_stats.add_block_event_by_block_hash(
                block_hash,
                BlockStatEventType.BLOCK_HOLD_LIFTED,
                network_num=connection.network_num,
                more_info=stats_format.connection(connection))

            hold = self._holds.contents[block_hash]
            if hold.alarm is not None:
                self._node.alarm_queue.unregister_alarm(hold.alarm)
            del self._holds.contents[block_hash]
Пример #7
0
    def queue_block_for_processing(self, block_message, connection):
        """
        Queues up block for processing on timeout if hold message received.
        If no hold exists, compress and broadcast block immediately.
        :param block_message: block message to process
        :param connection: receiving connection (AbstractBlockchainConnection)
        """

        block_hash = block_message.block_hash()
        connection.log_info("Processing block {} from local blockchain node.",
                            block_hash)

        if block_hash in self._holds.contents:
            hold: BlockHold = self._holds.contents[block_hash]
            block_stats.add_block_event_by_block_hash(
                block_hash,
                BlockStatEventType.BLOCK_HOLD_HELD_BLOCK,
                network_num=connection.network_num,
                more_info=stats_format.connection(hold.holding_connection))
            if hold.alarm is None:
                hold.alarm = self._node.alarm_queue.register_alarm(
                    self._node.opts.blockchain_block_hold_timeout_s,
                    self._holding_timeout, block_hash, hold)
                hold.block_message = block_message
                hold.connection = connection
        else:
            # Broadcast BlockHoldingMessage through relays and gateways
            conns = self._node.broadcast(BlockHoldingMessage(
                block_hash, self._node.network_num),
                                         broadcasting_conn=connection,
                                         prepend_to_queue=True,
                                         connection_types=[
                                             ConnectionType.RELAY_BLOCK,
                                             ConnectionType.GATEWAY
                                         ])
            if len(conns) > 0:
                block_stats.add_block_event_by_block_hash(
                    block_hash,
                    BlockStatEventType.BLOCK_HOLD_SENT_BY_GATEWAY_TO_PEERS,
                    network_num=self._node.network_num,
                    more_info=stats_format.connections(conns))
            self._process_and_broadcast_block(block_message, connection)
Пример #8
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,
            )
Пример #9
0
    def process_block_key(self, msg, connection: AbstractRelayConnection):
        """
        Handles key message receive from bloXroute.
        Looks for the encrypted block and decrypts; otherwise stores for later.
        """
        key = msg.key()
        block_hash = msg.block_hash()

        if not self._node.should_process_block_hash(block_hash):
            return

        block_stats.add_block_event_by_block_hash(
            block_hash,
            BlockStatEventType.ENC_BLOCK_KEY_RECEIVED_BY_GATEWAY_FROM_NETWORK,
            network_num=connection.network_num,
            connection_type=connection.CONNECTION_TYPE,
            more_info=stats_format.connection(connection))

        if self._node.in_progress_blocks.has_encryption_key_for_hash(
                block_hash):
            return

        if self._node.in_progress_blocks.has_ciphertext_for_hash(block_hash):
            connection.log_trace(
                "Cipher text found. Decrypting and sending to node.")
            decrypt_start_timestamp = time.time()
            decrypt_start_datetime = datetime.datetime.utcnow()
            block = self._node.in_progress_blocks.decrypt_and_get_payload(
                block_hash, key)

            if block is not None:
                block_stats.add_block_event_by_block_hash(
                    block_hash,
                    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_by_block_hash(
                    block_hash,
                    BlockStatEventType.ENC_BLOCK_DECRYPTION_ERROR,
                    network_num=connection.network_num)
        else:
            connection.log_trace(
                "No cipher text found on key message. Storing.")
            self._node.in_progress_blocks.add_key(block_hash, key)

        conns = self._node.broadcast(msg,
                                     connection,
                                     connection_types=[ConnectionType.GATEWAY])
        if len(conns) > 0:
            block_stats.add_block_event_by_block_hash(
                block_hash,
                BlockStatEventType.ENC_BLOCK_KEY_SENT_BY_GATEWAY_TO_PEERS,
                network_num=self._node.network_num,
                more_info=stats_format.connections(conns))
Пример #10
0
    def msg_tx(self, msg):
        """
        Handle a TX message by broadcasting to the entire network
        """
        start_time = time.time()
        txn_count = 0
        broadcast_txs_count = 0

        process_tx_msg_result = self.tx_service.process_transactions_message_from_node(
            msg, self.node.get_network_min_transaction_fee(),
            self.node.opts.transaction_validation)

        if not self.node.opts.has_fully_updated_tx_service:
            logger.debug(
                "Gateway received {} transaction messages while syncing, skipping..",
                len(process_tx_msg_result))
            return

        broadcast_start_time = time.time()

        for tx_result in process_tx_msg_result:
            txn_count += 1
            if TxValidationStatus.INVALID_FORMAT in tx_result.tx_validation_status:
                gateway_transaction_stats_service.log_tx_validation_failed_structure(
                )
                logger.warning(
                    log_messages.NODE_RECEIVED_TX_WITH_INVALID_FORMAT,
                    self.node.NODE_TYPE, tx_result.transaction_hash,
                    stats_format.connection(self.connection))

                tx_stats.add_tx_by_hash_event(
                    tx_result.transaction_hash,
                    TransactionStatEventType.TX_VALIDATION_FAILED_STRUCTURE,
                    self.connection.network_num,
                    peers=[self.connection])
                continue

            if TxValidationStatus.INVALID_SIGNATURE in tx_result.tx_validation_status:
                gateway_transaction_stats_service.log_tx_validation_failed_signature(
                )
                logger.warning(log_messages.NODE_RECEIVED_TX_WITH_INVALID_SIG,
                               self.node.NODE_TYPE, tx_result.transaction_hash,
                               stats_format.connection(self.connection))

                tx_stats.add_tx_by_hash_event(
                    tx_result.transaction_hash,
                    TransactionStatEventType.TX_VALIDATION_FAILED_SIGNATURE,
                    self.connection.network_num,
                    peers=[self.connection])
                continue

            if TxValidationStatus.LOW_FEE in tx_result.tx_validation_status:
                gateway_transaction_stats_service.log_tx_validation_failed_gas_price(
                )
                # log low fee transaction here for bdn_performance
                gateway_bdn_performance_stats_service.log_tx_from_blockchain_node(
                    True)

                logger.trace(
                    "transaction {} has gas price lower then the setting {}",
                    tx_result.transaction_hash,
                    self.node.get_network_min_transaction_fee())

                tx_stats.add_tx_by_hash_event(
                    tx_result.transaction_hash,
                    TransactionStatEventType.TX_VALIDATION_FAILED_GAS_PRICE,
                    self.connection.network_num,
                    peers=[self.connection])
                continue

            if tx_result.seen:
                tx_stats.add_tx_by_hash_event(
                    tx_result.transaction_hash,
                    TransactionStatEventType.
                    TX_RECEIVED_FROM_BLOCKCHAIN_NODE_IGNORE_SEEN,
                    self.connection.network_num,
                    peers=[self.connection])
                gateway_transaction_stats_service.log_duplicate_transaction_from_blockchain(
                )
                continue

            broadcast_txs_count += 1

            tx_stats.add_tx_by_hash_event(
                tx_result.transaction_hash,
                TransactionStatEventType.TX_RECEIVED_FROM_BLOCKCHAIN_NODE,
                self.connection.network_num,
                peers=[self.connection])
            gateway_transaction_stats_service.log_transaction_from_blockchain(
                tx_result.transaction_hash)

            # log transactions that passed validation, according to fee
            gateway_bdn_performance_stats_service.log_tx_from_blockchain_node(
                not self.node.is_gas_price_above_min_network_fee(
                    tx_result.transaction_contents))

            # All connections outside of this one is a bloXroute server
            broadcast_peers = self.node.broadcast(
                tx_result.bdn_transaction_message,
                self.connection,
                connection_types=[ConnectionType.RELAY_TRANSACTION])
            self.node.broadcast(
                msg,
                self.connection,
                connection_types=[ConnectionType.BLOCKCHAIN_NODE])

            if self.node.opts.ws:
                self.publish_transaction(
                    tx_result.transaction_hash,
                    memoryview(tx_result.transaction_contents))

            if broadcast_peers:
                tx_stats.add_tx_by_hash_event(
                    tx_result.transaction_hash,
                    TransactionStatEventType.TX_SENT_FROM_GATEWAY_TO_PEERS,
                    self.connection.network_num,
                    peers=broadcast_peers)
            else:
                logger.trace(
                    "Tx Message: {} from BlockchainNode was dropped, no upstream relay connection available",
                    tx_result.transaction_hash)

        set_content_start_time = time.time()
        end_time = time.time()

        total_duration_ms = (end_time - start_time) * 1000
        duration_before_broadcast_ms = (broadcast_start_time -
                                        start_time) * 1000
        duration_broadcast_ms = (set_content_start_time -
                                 broadcast_start_time) * 1000
        duration_set_content_ms = (end_time - set_content_start_time) * 1000

        gateway_transaction_stats_service.log_processed_node_transaction(
            total_duration_ms, duration_before_broadcast_ms,
            duration_broadcast_ms, duration_set_content_ms,
            broadcast_txs_count)

        performance_utils.log_operation_duration(
            msg_handling_logger,
            "Process single transaction from Blockchain",
            start_time,
            gateway_constants.
            BLOCKCHAIN_TX_PROCESSING_TIME_WARNING_THRESHOLD_S,
            connection=self,
            message=msg,
            total_duration_ms=total_duration_ms,
            duration_before_broadcast_ms=duration_before_broadcast_ms,
            duration_broadcast_ms=duration_broadcast_ms,
            duration_set_content_ms=duration_set_content_ms)
Пример #11
0
    def msg_tx(self, msg):
        """
        Handle transactions receive from bloXroute network.
        """

        start_time = time.time()

        if ConnectionType.RELAY_TRANSACTION not in self.CONNECTION_TYPE:
            self.log_error(log_messages.UNEXPECTED_TX_MESSAGE, msg)
            return

        tx_service = self.node.get_tx_service()

        tx_hash = msg.tx_hash()
        short_id = msg.short_id()
        tx_contents = msg.tx_val()
        is_compact = msg.is_compact()
        network_num = msg.network_num()
        attempt_recovery = False

        ext_start_time = time.time()

        processing_result = tx_service.process_gateway_transaction_from_bdn(
            tx_hash, short_id, tx_contents, is_compact)

        ext_end_time = time.time()

        if processing_result.ignore_seen:
            gateway_transaction_stats_service.log_duplicate_transaction_from_relay(
            )
            tx_stats.add_tx_by_hash_event(
                tx_hash,
                TransactionStatEventType.
                TX_RECEIVED_BY_GATEWAY_FROM_PEER_IGNORE_SEEN,
                network_num,
                short_id,
                peer=stats_format.connection(self),
                is_compact_transaction=False)
            self.log_trace("Transaction has already been seen: {}", tx_hash)
            return

        if processing_result.existing_short_id:
            gateway_transaction_stats_service.log_duplicate_transaction_from_relay(
                is_compact)
            tx_stats.add_tx_by_hash_event(
                tx_hash,
                TransactionStatEventType.
                TX_RECEIVED_BY_GATEWAY_FROM_PEER_IGNORE_SEEN,
                network_num,
                short_id,
                peer=stats_format.connection(self),
                is_compact_transaction=is_compact)
            return

        tx_stats.add_tx_by_hash_event(
            tx_hash,
            TransactionStatEventType.TX_RECEIVED_BY_GATEWAY_FROM_PEER,
            network_num,
            short_id,
            peer=stats_format.connection(self),
            is_compact_transaction=msg.is_compact())
        gateway_transaction_stats_service.log_transaction_from_relay(
            tx_hash, short_id is not None, msg.is_compact())

        if processing_result.assigned_short_id:
            was_missing = self.node.block_recovery_service.check_missing_sid(
                short_id)
            attempt_recovery |= was_missing
            tx_stats.add_tx_by_hash_event(
                tx_hash,
                TransactionStatEventType.TX_SHORT_ID_STORED_BY_GATEWAY,
                network_num,
                short_id,
                was_missing=was_missing)
            gateway_transaction_stats_service.log_short_id_assignment_processed(
            )
        elif not short_id:
            tx_stats.add_tx_by_hash_event(
                tx_hash,
                TransactionStatEventType.TX_SHORT_ID_EMPTY_IN_MSG_FROM_RELAY,
                network_num,
                short_id,
                peer=stats_format.connection(self))

        if not is_compact and processing_result.existing_contents:
            gateway_transaction_stats_service.log_redundant_transaction_content(
            )

        if processing_result.set_content:
            self.log_trace(
                "Adding hash value to tx service and forwarding it to node")
            gateway_bdn_performance_stats_service.log_tx_from_bdn()
            attempt_recovery |= self.node.block_recovery_service.check_missing_tx_hash(
                tx_hash)

            if self.node.node_conn is not None:
                blockchain_tx_message = self.node.message_converter.bx_tx_to_tx(
                    msg)
                self.publish_new_transaction(tx_hash, tx_contents)
                transaction_feed_stats_service.log_new_transaction(tx_hash)
                self.node.send_msg_to_node(blockchain_tx_message)

                tx_stats.add_tx_by_hash_event(
                    tx_hash, TransactionStatEventType.
                    TX_SENT_FROM_GATEWAY_TO_BLOCKCHAIN_NODE, network_num,
                    short_id)

        if attempt_recovery:
            self.node.block_processing_service.retry_broadcast_recovered_blocks(
                self)

        end_time = time.time()

        total_duration_ms = (end_time - start_time) * 1000
        duration_before_ext_ms = (ext_start_time - start_time) * 1000
        duration_ext_ms = (ext_end_time - ext_start_time) * 1000
        duration_after_ext_ms = (end_time - ext_end_time) * 1000

        gateway_transaction_stats_service.log_processed_bdn_transaction(
            total_duration_ms, duration_before_ext_ms, duration_ext_ms,
            duration_after_ext_ms)

        performance_utils.log_operation_duration(
            msg_handling_logger,
            "Process single transaction from BDN",
            start_time,
            gateway_constants.BDN_TX_PROCESSING_TIME_WARNING_THRESHOLD_S,
            connection=self,
            message=msg,
            total_duration_ms=total_duration_ms,
            duration_before_ext_ms=duration_before_ext_ms,
            duration_ext_ms=duration_ext_ms,
            duration_after_ext_ms=duration_after_ext_ms)
Пример #12
0
    def msg_tx(self, msg):
        """
        Handle a TX message by broadcasting to the entire network
        """
        start_time = time.time()
        txn_count = 0
        broadcast_txs_count = 0

        tx_service = self.connection.node.get_tx_service()

        process_tx_msg_result = tx_service.process_transactions_message_from_node(msg)

        broadcast_start_time = time.time()

        for tx_result in process_tx_msg_result:
            txn_count += 1

            if tx_result.seen:
                tx_stats.add_tx_by_hash_event(tx_result.transaction_hash,
                                              TransactionStatEventType.TX_RECEIVED_FROM_BLOCKCHAIN_NODE_IGNORE_SEEN,
                                              self.connection.network_num,
                                              peer=stats_format.connection(self.connection))
                gateway_transaction_stats_service.log_duplicate_transaction_from_blockchain()
                continue

            broadcast_txs_count += 1

            tx_stats.add_tx_by_hash_event(
                tx_result.transaction_hash,
                TransactionStatEventType.TX_RECEIVED_FROM_BLOCKCHAIN_NODE,
                self.connection.network_num,
                peer=stats_format.connection(self.connection)
            )
            gateway_transaction_stats_service.log_transaction_from_blockchain(tx_result.transaction_hash)
            gateway_bdn_performance_stats_service.log_tx_from_blockchain_node()

            # All connections outside of this one is a bloXroute server
            broadcast_peers = self.connection.node.broadcast(
                tx_result.bdn_transaction_message,
                self.connection,
                connection_types=[ConnectionType.RELAY_TRANSACTION]
            )
            if self.connection.node.opts.ws:
                self.publish_transaction(
                    tx_result.transaction_hash, memoryview(tx_result.transaction_contents)
                )

            if broadcast_peers:
                tx_stats.add_tx_by_hash_event(
                    tx_result.transaction_hash,
                    TransactionStatEventType.TX_SENT_FROM_GATEWAY_TO_PEERS,
                    self.connection.network_num,
                    peers=map(lambda conn: (stats_format.connection(conn)), broadcast_peers)
                )
            else:
                logger.trace(
                    "Tx Message: {} from BlockchainNode was dropped, no upstream relay connection available",
                    tx_result.transaction_hash
                )

        set_content_start_time = time.time()
        end_time = time.time()

        total_duration_ms = (end_time - start_time) * 1000
        duration_before_broadcast_ms = (broadcast_start_time - start_time) * 1000
        duration_broadcast_ms = (set_content_start_time - broadcast_start_time) * 1000
        duration_set_content_ms = (end_time - set_content_start_time) * 1000

        gateway_transaction_stats_service.log_processed_node_transaction(
            total_duration_ms,
            duration_before_broadcast_ms,
            duration_broadcast_ms,
            duration_set_content_ms,
            broadcast_txs_count
        )

        performance_utils.log_operation_duration(
            msg_handling_logger,
            "Process single transaction from Blockchain",
            start_time,
            gateway_constants.BLOCKCHAIN_TX_PROCESSING_TIME_WARNING_THRESHOLD_S,
            connection=self,
            message=msg,
            total_duration_ms=total_duration_ms,
            duration_before_broadcast_ms=duration_before_broadcast_ms,
            duration_broadcast_ms=duration_broadcast_ms,
            duration_set_content_ms=duration_set_content_ms
        )