text: # Python3
  #
  # Returns list of live tested nodes sorted by latency
  #
  
  # (BTS) litepresence1
  
  from multiprocessing import Process, Value, Array
  from bitshares.blockchain import Blockchain
  from bitshares import BitShares
  from datetime import datetime
  import requests
  import time
  import sys
  import os
  
  ID = '4018d7844c78f6a6c41c6a552b898022310fc5dec06da467ee7905a8dad512c8'
  
  def nodes(timeout=20, pings=999999, crop=99, noprint=False, write=False,
            include=False, exclude=False, suffix=True, master=False):
  
      # timeout : seconds to ping until abort per node
      # pings   : number of good nodes to find until satisfied (0 none, 999 all)
      # suffix  : checks each node for no suffix plus with /ws or /wss
      # noprint : disables printing, only returns list of good nodes
      # master  : check only nodes listed in bitshares/ui/master
      # crop    : return only best nodes
      # write   : maintains an output file nodes.txt with list of best nodes
  
      # include and exclude custom nodes
      included, excluded = [], []
      if include:
          included = ['api.bts.mobi']
  
      if exclude:
          excluded = []
  
      # web scraping methods
      def clean(raw):
          return ((str(raw).replace('"', " "))
                  .replace("'", " ")).replace(',', ' ')
  
      def parse(cleaned):
          return [t for t in cleaned.split() if t.startswith('wss')]
  
      def validate(parsed):
          v = parsed
          for i in range(len(v)):
              if v[i].endswith('/'):
                  v[i] = v[i][:-1]
          for i in range(len(v)):
              if v[i].endswith('/ws'):
                  v[i] = v[i][:-3]
          for i in range(len(v)):
              if v[i].endswith('/wss'):
                  v[i] = v[i][:-4]
          # these are known to require /ws extension
          ws = ['wss://relinked.com',
                'wss://bitshares.crypto.fans',
                'wss://this.uptick.rocks']
          if suffix:
              wss = [(i + '/wss') for i in v]
              ws = [(i + '/ws') for i in v]
              v = v + wss + ws
          else:
              for i in range(len(v)):
                  if v[i] in ws:
                      v[i] += '/ws'
                  else:
                      v[i] += '/wss'
          return v
  
      # ping the blockchain and return latency
              
      def ping(n, num, arr):
  
          try:
              start = time.time()
              chain = Blockchain(
                  bitshares_instance=BitShares(n, num_retries=0), mode='head')
  
              # print(n,chain.rpc.chain_params["chain_id"])
              ping_latency = time.time() - start
              current_block = chain.get_current_block_num()
              blocktimestamp = abs(
                  chain.block_timestamp(current_block))  # + utc_offset)
              block_latency = time.time() - blocktimestamp
              # print (blocktimestamp)
              # print (time.time())
              # print (block_latency)
              # print (ping_latency)
              # print (time.ctime())
              # print (utc_offset)
              # print (chain.get_network())
              if chain.get_network()['chain_id'] != ID:
                  num.value = 333333
              elif block_latency < (ping_latency + 4):
                  num.value = ping_latency
              else:
                  num.value = 111111
          except:
              num.value = 222222
              pass
  
      # Disable / Enable printing
      def blockPrint():
          if noprint:
              sys.stdout = open(os.devnull, 'w')
  
      def enablePrint():
          if noprint:
              sys.stdout = sys.__stdout__
  
      # gather list of nodes from github
      blockPrint()
      begin = time.time()
      utc_offset = (datetime.fromtimestamp(begin) -
                    datetime.utcfromtimestamp(begin)).total_seconds()
      print ('=====================================')
      print(('found %s nodes stored in script' % len(included)))
      urls = []
      # scrape from github
      git = 'https://raw.githubusercontent.com'
      url = git + '/bitshares/bitshares-ui/master/app/api/apiConfig.js'
      urls.append(url)
      if not master:
          url = git + '/bitshares/bitshares-ui/staging/app/api/apiConfig.js'
          urls.append(url)
          url = git + '/CryptoBridge/cryptobridge-ui/'
          url += 'e5214ad63a41bd6de1333fd98d717b37e1a52f77/app/api/apiConfig.js'
          urls.append(url)
          url = git + '/litepresence/extinction-event/master/bitshares-nodes.py'
          urls.append(url)
  
      # searched selected sites for Bitshares nodes
      validated = [] + included
  
  
  
      for u in urls:
          attempts = 3
          while attempts > 0:
              try:
                  raw = requests.get(u).text
                  v = validate(parse(clean(raw)))
                  print(('found %s nodes at %s' % (len(v), u[:65])))
                  validated += v
                  attempts = 0
              except:
                  print(('failed to connect to %s' % u))
                  attempts -= 1
                  pass
  
      # remove known bad nodes from test
      if len(excluded):
          excluded = sorted(excluded)
          print(('remove %s known bad nodes' % len(excluded)))
          validated = [i for i in validated if i not in excluded]
  
      #validated = []
  
      validated = sorted(list(set(validate(parse(clean(validated))))))
  
      # attempt to contact each websocket
      print ('=====================================')
      print(('found %s total nodes - no duplicates' % len(validated)))
      print ('=====================================')
      print (validated)
      pinging = min(pings, len(validated))
      if pinging:
          print ('=====================================')
          enablePrint()
          print(('%s pinging %s nodes; timeout %s sec; est %.1f minutes' % (
              time.ctime(), pinging, timeout, timeout * len(validated) / 60.0)))
          blockPrint()
          print ('=====================================')
          pinged, timed, down, stale, expired, testnet = [], [], [], [], [], []
          for n in validated:
              if len(pinged) < pinging:
                  # use multiprocessing module to enforce timeout
                  num = Value('d', 999999)
                  arr = Array('i', list(range(0)))
                  p = Process(target=ping, args=(n, num, arr))
                  p.start()
                  p.join(timeout)
                  if p.is_alive() or (num.value > timeout):
                      p.terminate()
                      p.join()
                      if num.value == 111111:  # head block is stale
                          stale.append(n)
                      elif num.value == 222222:  # connect failed
                          down.append(n)
                      elif num.value == 333333:  # connect failed
                          testnet.append(n)
                      elif num.value == 999999:  # timeout reached
                          expired.append(n)
                  else:
                      pinged.append(n)        # connect success
                      timed.append(num.value)  # connect success time
                  print(('ping:', ('%.2f' % num.value), n))
  
          # sort websockets by latency
          pinged = [x for _, x in sorted(zip(timed, pinged))]
          timed = sorted(timed)
          unknown = sorted(
              list(set(validated).difference(
                  pinged + down + stale + expired + testnet)))
  
          # report outcome
          print('')
          print((len(pinged), 'of', len(validated),
                 'nodes are active with latency less than', timeout))
          print('')
          print(('fastest node', pinged[0], 'with latency', ('%.2f' % timed[0])))
          if len(excluded):
              for i in range(len(excluded)):
                  print(('EXCLUDED', excluded[i]))
          if len(unknown):
              for i in range(len(unknown)):
                  print(('UNTESTED', unknown[i]))
          if len(testnet):
              for i in range(len(testnet)):
                  print(('TESTNET', testnet[i]))
          if len(expired):
              for i in range(len(expired)):
                  print(('TIMEOUT', expired[i]))
          if len(stale):
              for i in range(len(stale)):
                  print(('STALE', stale[i]))
          if len(down):
              for i in range(len(down)):
                  print(('DOWN', down[i]))
          if len(pinged):
              print ('')
              print ('GOOD nodes:')
              print ('')
              for i in range(len(pinged)):
                  print((('%.2f' % timed[i]), pinged[i]))
  
          ret = pinged[:crop]
          #print (pinged[0])
          #print (ret[0])
          #print (timed[0])
      else:
          ret = validated[:crop]
  
      print ('')
      enablePrint()
      elapsed = time.time()-begin
      print ('elapsed:', ('%.1f' % elapsed), 
              'fastest:', ('%.3f' % timed[0]), ret[0])
      print (ret)
  
      if write and (len(ret) == crop):
          opened = 0
          while not opened:
              try:
                  with open('nodes.txt', 'w+') as file:
                      file.write(str(ret))
  
                  opened = 1
              except:
                  pass
      return (ret)
  
  def loop():
  
      while 1:
          try:
              nodes(timeout=5, pings=999, crop=10, noprint=True, write=True,
                  include=True, exclude=False, suffix=False, master=False)
              time.sleep(30)
  
          # no matter what happens just keep verifying book
          except Exception as e:
              print (type(e).__name__, e.args, e)
              pass
  
  def update():
  
      print('Acquiring low latency connection to Bitshares DEX'+
              ', this may take a few minutes...')
      updated = 0
      try:
          while not updated:
              nodes(timeout=10, pings=999, crop=10, noprint=False, write=True,
                  include=True, exclude=False, suffix=True, master=False)
              updated = 1
  
      # not satisfied until verified once
      except Exception as e:
          print (type(e).__name__, e.args, e)
          pass
  
      print('')
      print('DEX CONNECTION ESTABLISHED - will refresh every 10 minutes')
      print('')
  
  if __name__ == '__main__':
  
  
          update()
  