code: #!/usr/bin/env python3
  
  import argparse
  import json
  import numpy
  import os
  import random
  import re
  import subprocess
  import sys
  import time
  
  args = None
  logFile = None
  
  unlockTimeout = 999999999
  fastUnstakeSystem = './fast.refund/dccio.system/dccio.system.wasm'
  
  systemAccounts = [
      'dccio.bpay',
      'dccio.msig',
      'dccio.names',
      'dccio.ram',
      'dccio.ramfee',
      'dccio.saving',
      'dccio.stake',
      'dccio.token',
      'dccio.vpay',
  ]
  
  def jsonArg(a):
      return " '" + json.dumps(a) + "' "
  
  def run(args):
      print('bios-boot-tutorial.py:', args)
      logFile.write(args + '\n')
      if subprocess.call(args, shell=True):
          print('bios-boot-tutorial.py: exiting because of error')
          sys.exit(1)
  
  def retry(args):
      while True:
          print('bios-boot-tutorial.py:', args)
          logFile.write(args + '\n')
          if subprocess.call(args, shell=True):
              print('*** Retry')
          else:
              break
  
  def background(args):
      print('bios-boot-tutorial.py:', args)
      logFile.write(args + '\n')
      return subprocess.Popen(args, shell=True)
  
  def getOutput(args):
      print('bios-boot-tutorial.py:', args)
      logFile.write(args + '\n')
      proc = subprocess.Popen(args, shell=True, stdout=subprocess.PIPE)
      return proc.communicate()[0].decode('utf-8')
  
  def getJsonOutput(args):
      print('bios-boot-tutorial.py:', args)
      logFile.write(args + '\n')
      proc = subprocess.Popen(args, shell=True, stdout=subprocess.PIPE)
      return json.loads(proc.communicate()[0])
  
  def sleep(t):
      print('sleep', t, '...')
      time.sleep(t)
      print('resume')
  
  def startWallet():
      run('rm -rf ' + os.path.abspath(args.wallet_dir))
      run('mkdir -p ' + os.path.abspath(args.wallet_dir))
      background(args.kdccd + ' --unlock-timeout %d --http-server-address 127.0.0.1:6666 --wallet-dir %s' % (unlockTimeout, os.path.abspath(args.wallet_dir)))
      sleep(.4)
      run(args.cldcc + 'wallet create --to-console')
  
  def importKeys():
      run(args.cldcc + 'wallet import --private-key ' + args.private_key)
      keys = {}
      for a in accounts:
          key = a['pvt']
          if not key in keys:
              if len(keys) >= args.max_user_keys:
                  break
              keys[key] = True
              run(args.cldcc + 'wallet import --private-key ' + key)
      for i in range(firstProducer, firstProducer + numProducers):
          a = accounts[i]
          key = a['pvt']
          if not key in keys:
              keys[key] = True
              run(args.cldcc + 'wallet import --private-key ' + key)
  
  def startNode(nodeIndex, account):
      dir = args.nodes_dir + ('%02d-' % nodeIndex) + account['name'] + '/'
      run('rm -rf ' + dir)
      run('mkdir -p ' + dir)
      otherOpts = ''.join(list(map(lambda i: '    --p2p-peer-address localhost:' + str(9000 + i), range(nodeIndex))))
      if not nodeIndex: otherOpts += (
          '    --plugin dccio::history_plugin'
          '    --plugin dccio::history_api_plugin'
      )
      cmd = (
          args.noddcc +
          '    --max-irreversible-block-age -1'
          '    --contracts-console'
          '    --genesis-json ' + os.path.abspath(args.genesis) +
          '    --blocks-dir ' + os.path.abspath(dir) + '/blocks'
          '    --config-dir ' + os.path.abspath(dir) +
          '    --data-dir ' + os.path.abspath(dir) +
          '    --chain-state-db-size-mb 1024'
          '    --http-server-address 127.0.0.1:' + str(8000 + nodeIndex) +
          '    --p2p-listen-endpoint 127.0.0.1:' + str(9000 + nodeIndex) +
          '    --max-clients ' + str(maxClients) +
          '    --p2p-max-nodes-per-host ' + str(maxClients) +
          '    --enable-stale-production'
          '    --producer-name ' + account['name'] +
          '    --private-key \'["' + account['pub'] + '","' + account['pvt'] + '"]\''
          '    --plugin dccio::http_plugin'
          '    --plugin dccio::chain_api_plugin'
          '    --plugin dccio::producer_plugin' +
          otherOpts)
      with open(dir + 'stderr', mode='w') as f:
          f.write(cmd + '\n\n')
      background(cmd + '    2>>' + dir + 'stderr')
  
  def startProducers(b, e):
      for i in range(b, e):
          startNode(i - b + 1, accounts[i])
  
  def createSystemAccounts():
      for a in systemAccounts:
          run(args.cldcc + 'create account dccio ' + a + ' ' + args.public_key)
  
  def intToCurrency(i):
      return '%d.%04d %s' % (i // 10000, i % 10000, args.symbol)
  
  def allocateFunds(b, e):
      dist = numpy.random.pareto(1.161, e - b).tolist() # 1.161 = 80/20 rule
      dist.sort()
      dist.reverse()
      factor = 1_000_000_000 / sum(dist)
      total = 0
      for i in range(b, e):
          funds = round(factor * dist[i - b] * 10000)
          if i >= firstProducer and i < firstProducer + numProducers:
              funds = max(funds, round(args.min_producer_funds * 10000))
          total += funds
          accounts[i]['funds'] = funds
      return total
  
  def createStakedAccounts(b, e):
      ramFunds = round(args.ram_funds * 10000)
      configuredMinStake = round(args.min_stake * 10000)
      maxUnstaked = round(args.max_unstaked * 10000)
      for i in range(b, e):
          a = accounts[i]
          funds = a['funds']
          print('#' * 80)
          print('# %d/%d %s %s' % (i, e, a['name'], intToCurrency(funds)))
          print('#' * 80)
          if funds < ramFunds:
              print('skipping %s: not enough funds to cover ram' % a['name'])
              continue
          minStake = min(funds - ramFunds, configuredMinStake)
          unstaked = min(funds - ramFunds - minStake, maxUnstaked)
          stake = funds - ramFunds - unstaked
          stakeNet = round(stake / 2)
          stakeCpu = stake - stakeNet
          print('%s: total funds=%s, ram=%s, net=%s, cpu=%s, unstaked=%s' % (a['name'], intToCurrency(a['funds']), intToCurrency(ramFunds), intToCurrency(stakeNet), intToCurrency(stakeCpu), intToCurrency(unstaked)))
          assert(funds == ramFunds + stakeNet + stakeCpu + unstaked)
          retry(args.cldcc + 'system newaccount --transfer dccio %s %s --stake-net "%s" --stake-cpu "%s" --buy-ram "%s"   ' % 
              (a['name'], a['pub'], intToCurrency(stakeNet), intToCurrency(stakeCpu), intToCurrency(ramFunds)))
          if unstaked:
              retry(args.cldcc + 'transfer dccio %s "%s"' % (a['name'], intToCurrency(unstaked)))
  
  def regProducers(b, e):
      for i in range(b, e):
          a = accounts[i]
          retry(args.cldcc + 'system regproducer ' + a['name'] + ' ' + a['pub'] + ' https://' + a['name'] + '.com' + '/' + a['pub'])
  
  def listProducers():
      run(args.cldcc + 'system listproducers')
  
  def vote(b, e):
      for i in range(b, e):
          voter = accounts[i]['name']
          prods = random.sample(range(firstProducer, firstProducer + numProducers), args.num_producers_vote)
          prods = ' '.join(map(lambda x: accounts[x]['name'], prods))
          retry(args.cldcc + 'system voteproducer prods ' + voter + ' ' + prods)
  
  def claimRewards():
      table = getJsonOutput(args.cldcc + 'get table dccio dccio producers -l 100')
      times = []
      for row in table['rows']:
          if row['unpaid_blocks'] and not row['last_claim_time']:
              times.append(getJsonOutput(args.cldcc + 'system claimrewards -j ' + row['owner'])['processed']['elapsed'])
      print('Elapsed time for claimrewards:', times)
  
  def proxyVotes(b, e):
      vote(firstProducer, firstProducer + 1)
      proxy = accounts[firstProducer]['name']
      retry(args.cldcc + 'system regproxy ' + proxy)
      sleep(1.0)
      for i in range(b, e):
          voter = accounts[i]['name']
          retry(args.cldcc + 'system voteproducer proxy ' + voter + ' ' + proxy)
  
  def updateAuth(account, permission, parent, controller):
      run(args.cldcc + 'push action dccio updateauth' + jsonArg({
          'account': account,
          'permission': permission,
          'parent': parent,
          'auth': {
              'threshold': 1, 'keys': [], 'waits': [],
              'accounts': [{
                  'weight': 1,
                  'permission': {'actor': controller, 'permission': 'active'}
              }]
          }
      }) + '-p ' + account + '@' + permission)
  
  def resign(account, controller):
      updateAuth(account, 'owner', '', controller)
      updateAuth(account, 'active', 'owner', controller)
      sleep(1)
      run(args.cldcc + 'get account ' + account)
  
  def randomTransfer(b, e):
      for j in range(20):
          src = accounts[random.randint(b, e - 1)]['name']
          dest = src
          while dest == src:
              dest = accounts[random.randint(b, e - 1)]['name']
          run(args.cldcc + 'transfer -f ' + src + ' ' + dest + ' "0.0001 ' + args.symbol + '"' + ' || true')
  
  def msigProposeReplaceSystem(proposer, proposalName):
      requestedPermissions = []
      for i in range(firstProducer, firstProducer + numProducers):
          requestedPermissions.append({'actor': accounts[i]['name'], 'permission': 'active'})
      trxPermissions = [{'actor': 'dccio', 'permission': 'active'}]
      with open(fastUnstakeSystem, mode='rb') as f:
          setcode = {'account': 'dccio', 'vmtype': 0, 'vmversion': 0, 'code': f.read().hex()}
      run(args.cldcc + 'multisig propose ' + proposalName + jsonArg(requestedPermissions) + 
          jsonArg(trxPermissions) + 'dccio setcode' + jsonArg(setcode) + ' -p ' + proposer)
  
  def msigApproveReplaceSystem(proposer, proposalName):
      for i in range(firstProducer, firstProducer + numProducers):
          run(args.cldcc + 'multisig approve ' + proposer + ' ' + proposalName +
              jsonArg({'actor': accounts[i]['name'], 'permission': 'active'}) +
              '-p ' + accounts[i]['name'])
  
  def msigExecReplaceSystem(proposer, proposalName):
      retry(args.cldcc + 'multisig exec ' + proposer + ' ' + proposalName + ' -p ' + proposer)
  
  def msigReplaceSystem():
      run(args.cldcc + 'push action dccio buyrambytes' + jsonArg(['dccio', accounts[0]['name'], 200000]) + '-p dccio')
      sleep(1)
      msigProposeReplaceSystem(accounts[0]['name'], 'fast.unstake')
      sleep(1)
      msigApproveReplaceSystem(accounts[0]['name'], 'fast.unstake')
      msigExecReplaceSystem(accounts[0]['name'], 'fast.unstake')
  
  def produceNewAccounts():
      with open('newusers', 'w') as f:
          for i in range(120_000, 200_000):
              x = getOutput(args.cldcc + 'create key --to-console')
              r = re.match('Private key: *([^ \n]*)\nPublic key: *([^ \n]*)', x, re.DOTALL | re.MULTILINE)
              name = 'user'
              for j in range(7, -1, -1):
                  name += chr(ord('a') + ((i >> (j * 4)) & 15))
              print(i, name)
              f.write('        {"name":"%s", "pvt":"%s", "pub":"%s"},\n' % (name, r[1], r[2]))
  
  def stepKillAll():
      run('killall kdccd noddcc || true')
      sleep(1.5)
  def stepStartWallet():
      startWallet()
      importKeys()
  def stepStartBoot():
      startNode(0, {'name': 'dccio', 'pvt': args.private_key, 'pub': args.public_key})
      sleep(1.5)
  def stepInstallSystemContracts():
      run(args.cldcc + 'set contract dccio.token ' + args.contracts_dir + 'dccio.token/')
      run(args.cldcc + 'set contract dccio.msig ' + args.contracts_dir + 'dccio.msig/')
  def stepCreateTokens():
      run(args.cldcc + 'push action dccio.token create \'["dccio", "10000000000.0000 %s"]\' -p dccio.token' % (args.symbol))
      totalAllocation = allocateFunds(0, len(accounts))
      run(args.cldcc + 'push action dccio.token issue \'["dccio", "%s", "memo"]\' -p dccio' % intToCurrency(totalAllocation))
      sleep(1)
  def stepSetSystemContract():
      retry(args.cldcc + 'set contract dccio ' + args.contracts_dir + 'dccio.system/')
      sleep(1)
      run(args.cldcc + 'push action dccio setpriv' + jsonArg(['dccio.msig', 1]) + '-p dccio@active')
  def stepCreateStakedAccounts():
      createStakedAccounts(0, len(accounts))
  def stepRegProducers():
      regProducers(firstProducer, firstProducer + numProducers)
      sleep(1)
      listProducers()
  def stepStartProducers():
      startProducers(firstProducer, firstProducer + numProducers)
      sleep(args.producer_sync_delay)
  def stepVote():
      vote(0, 0 + args.num_voters)
      sleep(1)
      listProducers()
      sleep(5)
  def stepProxyVotes():
      proxyVotes(0, 0 + args.num_voters)
  def stepResign():
      resign('dccio', 'dccio.prods')
      for a in systemAccounts:
          resign(a, 'dccio')
  def stepTransfer():
      while True:
          randomTransfer(0, args.num_senders)
  def stepLog():
      run('tail -n 60 ' + args.nodes_dir + '00-dccio/stderr')
  
  # Command Line Arguments
  
  parser = argparse.ArgumentParser()
  
  commands = [
      ('k', 'kill',           stepKillAll,                True,    "Kill all noddcc and kdccd processes"),
      ('w', 'wallet',         stepStartWallet,            True,    "Start kdccd, create wallet, fill with keys"),
      ('b', 'boot',           stepStartBoot,              True,    "Start boot node"),
      ('s', 'sys',            createSystemAccounts,       True,    "Create system accounts (dccio.*)"),
      ('c', 'contracts',      stepInstallSystemContracts, True,    "Install system contracts (token, msig)"),
      ('t', 'tokens',         stepCreateTokens,           True,    "Create tokens"),
      ('S', 'sys-contract',   stepSetSystemContract,      True,    "Set system contract"),
      ('T', 'stake',          stepCreateStakedAccounts,   True,    "Create staked accounts"),
      ('p', 'reg-prod',       stepRegProducers,           True,    "Register producers"),
      ('P', 'start-prod',     stepStartProducers,         True,    "Start producers"),
      ('v', 'vote',           stepVote,                   True,    "Vote for producers"),
      ('R', 'claim',          claimRewards,               True,    "Claim rewards"),
      ('x', 'proxy',          stepProxyVotes,             True,    "Proxy votes"),
      ('q', 'resign',         stepResign,                 True,    "Resign dccio"),
      ('m', 'msg-replace',    msigReplaceSystem,          False,   "Replace system contract using msig"),
      ('X', 'xfer',           stepTransfer,               False,   "Random transfer tokens (infinite loop)"),
      ('l', 'log',            stepLog,                    True,    "Show tail of node's log"),
  ]
  
  parser.add_argument('--public-key', metavar='', help="dccIO Public Key", default='dcc8Znrtgwt8TfpmbVpTKvA2oB8Nqey625CLN8bCN3TEbgx86Dsvr', dest="public_key")
  parser.add_argument('--private-Key', metavar='', help="dccIO Private Key", default='5K463ynhZoCDDa4RDcr63cUwWLTnKqmdcoTKTHBjqoKfv4u5V7p', dest="private_key")
  parser.add_argument('--cldcc', metavar='', help="Cldcc command", default='../../build/programs/cldcc/cldcc --wallet-url http://127.0.0.1:6666 ')
  parser.add_argument('--noddcc', metavar='', help="Path to noddcc binary", default='../../build/programs/noddcc/noddcc')
  parser.add_argument('--kdccd', metavar='', help="Path to kdccd binary", default='../../build/programs/kdccd/kdccd')
  parser.add_argument('--contracts-dir', metavar='', help="Path to contracts directory", default='../../build/contracts/')
  parser.add_argument('--nodes-dir', metavar='', help="Path to nodes directory", default='./nodes/')
  parser.add_argument('--genesis', metavar='', help="Path to genesis.json", default="./genesis.json")
  parser.add_argument('--wallet-dir', metavar='', help="Path to wallet directory", default='./wallet/')
  parser.add_argument('--log-path', metavar='', help="Path to log file", default='./output.log')
  parser.add_argument('--symbol', metavar='', help="The dccio.system symbol", default='SYS')
  parser.add_argument('--user-limit', metavar='', help="Max number of users. (0 = no limit)", type=int, default=3000)
  parser.add_argument('--max-user-keys', metavar='', help="Maximum user keys to import into wallet", type=int, default=10)
  parser.add_argument('--ram-funds', metavar='', help="How much funds for each user to spend on ram", type=float, default=0.1)
  parser.add_argument('--min-stake', metavar='', help="Minimum stake before allocating unstaked funds", type=float, default=0.9)
  parser.add_argument('--max-unstaked', metavar='', help="Maximum unstaked funds", type=float, default=10)
  parser.add_argument('--producer-limit', metavar='', help="Maximum number of producers. (0 = no limit)", type=int, default=0)
  parser.add_argument('--min-producer-funds', metavar='', help="Minimum producer funds", type=float, default=1000.0000)
  parser.add_argument('--num-producers-vote', metavar='', help="Number of producers for which each user votes", type=int, default=20)
  parser.add_argument('--num-voters', metavar='', help="Number of voters", type=int, default=10)
  parser.add_argument('--num-senders', metavar='', help="Number of users to transfer funds randomly", type=int, default=10)
  parser.add_argument('--producer-sync-delay', metavar='', help="Time (s) to sleep to allow producers to sync", type=int, default=80)
  parser.add_argument('-a', '--all', action='store_true', help="Do everything marked with (*)")
  parser.add_argument('-H', '--http-port', type=int, default=8000, metavar='', help='HTTP port for cldcc')
  
  for (flag, command, function, inAll, help) in commands:
      prefix = ''
      if inAll: prefix += '*'
      if prefix: help = '(' + prefix + ') ' + help
      if flag:
          parser.add_argument('-' + flag, '--' + command, action='store_true', help=help, dest=command)
      else:
          parser.add_argument('--' + command, action='store_true', help=help, dest=command)
          
  args = parser.parse_args()
  
  args.cldcc += '--url http://127.0.0.1:%d ' % args.http_port
  
  logFile = open(args.log_path, 'a')
  
  logFile.write('\n\n' + '*' * 80 + '\n\n\n')
  
  with open('accounts.json') as f:
      a = json.load(f)
      if args.user_limit:
          del a['users'][args.user_limit:]
      if args.producer_limit:
          del a['producers'][args.producer_limit:]
      firstProducer = len(a['users'])
      numProducers = len(a['producers'])
      accounts = a['users'] + a['producers']
  
  maxClients = numProducers + 10
  
  haveCommand = False
  for (flag, command, function, inAll, help) in commands:
      if getattr(args, command) or inAll and args.all:
          if function:
              haveCommand = True
              function()
  if not haveCommand:
      print('bios-boot-tutorial.py: Tell me what to do. -a does almost everything. -h shows options.')
  
apis: numpy.random.pareto