text: from logging import getLogger
  from typing import Any, Dict, List, Set
  
  from gnosis.eth import EthereumClient
  
  from ..models import InternalTx
  from .transaction_scan_service import TransactionScanService
  
  logger = getLogger(__name__)
  
  
  class InternalTxServiceProvider:
      def __new__(cls):
          if not hasattr(cls, 'instance'):
              from django.conf import settings
              cls.instance = InternalTxService(EthereumClient(settings.ETHEREUM_TRACING_NODE_URL),
                                               block_process_limit=settings.INTERNAL_TXS_BLOCK_PROCESS_LIMIT)
          return cls.instance
  
      @classmethod
      def del_singleton(cls):
          if hasattr(cls, "instance"):
              del cls.instance
  
  
  class InternalTxService(TransactionScanService):
      @property
      def database_field(self):
          return 'tx_block_number'
  
      def find_relevant_tx_hashes(self, safe_addresses: List[str], from_block_number: int,
                                  to_block_number: int) -> Set[str]:
          """
          Search for tx hashes with internal txs (in and out) of a `safe_address`
          :param safe_addresses:
          :param from_block_number: Starting block number
          :param to_block_number: Ending block number
          :return: Tx hashes of txs with internal txs relevant for the `safe_addresses`
          """
          logger.debug('Searching for internal txs from block-number=%d to block-number=%d - Safes=%s',
                       from_block_number, to_block_number, safe_addresses)
  
          to_traces = self.ethereum_client.parity.trace_filter(from_block=from_block_number,
                                                               to_block=to_block_number,
                                                               to_address=safe_addresses)
  
          from_traces = self.ethereum_client.parity.trace_filter(from_block=from_block_number,
                                                                 to_block=to_block_number,
                                                                 from_address=safe_addresses)
  
          # Log INFO if traces found, DEBUG if not
          log_fn = logger.info if to_traces + from_traces else logger.debug
          log_fn('Found %d relevant txs between block-number=%d and block-number=%d. Safes=%s',
                 len(to_traces + from_traces), from_block_number, to_block_number, safe_addresses)
  
          return set([trace['transactionHash'] for trace in (to_traces + from_traces)])
  
      def process_tx_hash(self, tx_hash: str) -> List[InternalTx]:
          """
          Search on Ethereum and store internal txs for provided `tx_hash`
          :param tx_hash:
          :return: List of `InternalTx` already stored in database
          """
          return self._process_traces(self.ethereum_client.parity.trace_transaction(tx_hash))
  
      def _process_trace(self, trace: Dict[str, Any]) -> InternalTx:
          ethereum_tx = self.create_or_update_ethereum_tx(trace['transactionHash'])
          return InternalTx.objects.get_or_create_from_trace(trace, ethereum_tx)
  
      def _process_traces(self, traces: List[Dict[str, Any]]) -> List[InternalTx]:
          return [self._process_trace(trace) for trace in traces]
  