From bf5b88fcb256034633ea6f4c411ed7cedc6a58c0 Mon Sep 17 00:00:00 2001
From: Lorenz Herzberger <64837895+LaurentDeMontBlanc@users.noreply.github.com>
Date: Tue, 8 Mar 2022 14:38:40 +0100
Subject: [PATCH] Scalable integration test (#57)

* updated Dockerfile-all-in-one

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

* adjusted all-in-one.bash and monit conf to work with dockerized setup

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

* integration tests pass inconsistently

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

* added timeout for integration test pass

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

* add startup control logic, adjusted tests to wait for transactions

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

* added pre-config for docker-compose approach, removed remnants of old integration tests

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

* reverted changes to pkg, split pre-config, added clean-shared service

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

* fixed path in all-in-one.bash

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

* added ipdb copyright notice

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

* chmod planetmint-monit-config

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

* removed entrypoint from Dockerfile-all-in-one

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

* added integration stage to travis matrix

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

* removed unused secret

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

* changed pre-config and docker-compose.integration.yml to support scaling

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

* using env var to control number of nodes

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

* make test-integration now scalable

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

* added make docs-integration, added .gitignore to python integration tests, updated readme and removed clutter

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

* fixed linter errors

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

* disable planetmint for test purpose

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

* test docker-compose down

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

* disable every job except integration test

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

* need more logs

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

* name collision?

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

* reverted changes to debug ci

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

* added TODO for ci optimization

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>
---
 .ci/travis_script.sh                         |  1 +
 Makefile                                     |  4 ++
 docker-compose.integration.yml               | 40 ++---------
 docker-compose.yml                           |  1 +
 integration/README.md                        | 11 +--
 integration/python/.gitignore                |  1 +
 integration/python/Dockerfile                |  4 +-
 integration/python/src/test_basic.py         | 65 +++++++++---------
 integration/python/src/test_multisig.py      | 72 +++++++++-----------
 integration/scripts/clean-shared.sh          |  6 +-
 integration/scripts/genesis.py               | 33 ++++-----
 integration/scripts/pre-config-planetmint.sh | 40 ++++++++---
 integration/scripts/test.sh                  | 16 ++++-
 integration/scripts/wait-for-planetmint.sh   | 27 ++++----
 14 files changed, 163 insertions(+), 158 deletions(-)
 create mode 100644 integration/python/.gitignore

diff --git a/.ci/travis_script.sh b/.ci/travis_script.sh
index 9d475b6..68398d6 100755
--- a/.ci/travis_script.sh
+++ b/.ci/travis_script.sh
@@ -14,6 +14,7 @@ elif [[ ${PLANETMINT_CI_ABCI} == 'enable' ]]; then
 elif [[ ${PLANETMINT_ACCEPTANCE_TEST} == 'enable' ]]; then
     ./scripts/run-acceptance-test.sh
 elif [[ ${PLANETMINT_INTEGRATION_TEST} == 'enable' ]]; then
+    docker-compose down # TODO: remove after ci optimization
     ./scripts/run-integration-test.sh
 else
   docker-compose exec planetmint pytest -v --cov=planetmint --cov-report xml:htmlcov/coverage.xml
diff --git a/Makefile b/Makefile
index 8fbeb29..b29ea0f 100644
--- a/Makefile
+++ b/Makefile
@@ -100,6 +100,10 @@ docs-acceptance: check-deps ## Create documentation for acceptance tests
 	@$(DC) run --rm python-acceptance pycco -i -s /src -d /docs
 	$(BROWSER) acceptance/python/docs/index.html
 
+docs-integration: check-deps ## Create documentation for integration tests
+	@$(DC) run --rm python-integration pycco -i -s /src -d /docs
+	$(BROWSER) integration/python/docs/index.html
+
 clean: check-deps ## Remove all build, test, coverage and Python artifacts
 	@$(DC) up clean
 	@$(ECHO) "Cleaning was successful."
diff --git a/docker-compose.integration.yml b/docker-compose.integration.yml
index 2da2980..b1cbdaf 100644
--- a/docker-compose.integration.yml
+++ b/docker-compose.integration.yml
@@ -13,7 +13,7 @@ services:
       - ./integration/scripts/clean-shared.sh:/scripts/clean-shared.sh
       - shared:/shared
   
-  planetmint_1:
+  planetmint-all-in-one:
     build:
       context: .
       dockerfile: Dockerfile-all-in-one
@@ -26,53 +26,27 @@ services:
       - "26656"
       - "26657"
       - "26658"
-    environment:
-      ME: "planetmint_1"
-      OTHER: "planetmint_2"
-    command: ["/usr/src/app/scripts/pre-config-planetmint.sh", "/usr/src/app/scripts/all-in-one.bash"]
-    volumes:
-      - ./integration/scripts:/usr/src/app/scripts
-      - shared:/shared
-
-  planetmint_2:
-    build:
-      context: .
-      dockerfile: Dockerfile-all-in-one
-    depends_on:
-      - clean-shared
-    expose:
-      - "22"
-      - "9984"
-      - "9985"
-      - "26656"
-      - "26657"
-      - "26658"
-    environment:
-      ME: "planetmint_2"
-      OTHER: "planetmint_1"
     command: ["/usr/src/app/scripts/pre-config-planetmint.sh", "/usr/src/app/scripts/all-in-one.bash"]
+    environment:
+      SCALE: ${SCALE:-4}
     volumes:
       - ./integration/scripts:/usr/src/app/scripts
       - shared:/shared
+    scale: ${SCALE:-4}
 
   test:
     build:
       context: .
       dockerfile: integration/python/Dockerfile
     depends_on:
-      - planetmint_1
-      - planetmint_2
-    environment:
-      ME: "test"
-      PLANETMINT_ENDPOINT_1: planetmint_1
-      PLANETMINT_ENDPOINT_2: planetmint_2
+      - planetmint-all-in-one
     command: ["/scripts/pre-config-test.sh", "/scripts/wait-for-planetmint.sh", "/scripts/test.sh", "pytest", "/src"]
+    environment:
+      SCALE: ${SCALE:-4}
     volumes:
       - ./integration/python/src:/src
       - ./integration/scripts:/scripts
       - shared:/shared
 
-
-
 volumes:
   shared:
diff --git a/docker-compose.yml b/docker-compose.yml
index f6aff4c..39005e9 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -89,6 +89,7 @@ services:
       context: .
       dockerfile: ./integration/python/Dockerfile
     volumes:
+      - ./integration/python/docs:/docs
       - ./integration/python/src:/src
     environment:
       - PLANETMINT_ENDPOINT_1=https://itest1.planetmint.io
diff --git a/integration/README.md b/integration/README.md
index 0efa4f8..ba1e204 100644
--- a/integration/README.md
+++ b/integration/README.md
@@ -8,15 +8,16 @@ Code is Apache-2.0 and docs are CC-BY-4.0
 # Integration test suite
 This directory contains the integration test suite for Planetmint.
 
-The suite uses Docker Compose to run all tests.
+The suite uses Docker Compose to spin up multiple Planetmint nodes, run tests with `pytest` as well as cli tests and teardown. 
 
 ## Running the tests
 Run `make test-integration` in the project root directory.
 
-During development you can run single test use `pytest` inside the `python-integration` container with:
+By default the integration test suite spins up four planetmint nodes. If you desire to run a different configuration you can pass `SCALE=<number of nodes>` as an environmental variable.
+
+## Writing and documenting the tests
+Tests are sometimes difficult to read. For integration tests, we try to be really explicit on what the test is doing, so please write code that is *simple* and easy to understand. We decided to use literate-programming documentation. To generate the documentation for python tests run:
 
 ```bash
-docker-compose run --rm python-integration pytest <use whatever option you need>
+make docs-integration
 ```
-
-Note: The `/src` directory contains all the test within the container.
diff --git a/integration/python/.gitignore b/integration/python/.gitignore
new file mode 100644
index 0000000..5c457d7
--- /dev/null
+++ b/integration/python/.gitignore
@@ -0,0 +1 @@
+docs
\ No newline at end of file
diff --git a/integration/python/Dockerfile b/integration/python/Dockerfile
index 9d73f6f..c0e47f1 100644
--- a/integration/python/Dockerfile
+++ b/integration/python/Dockerfile
@@ -3,5 +3,7 @@ FROM python:3.9
 RUN mkdir -p /src
 RUN pip install --upgrade \
     pytest~=6.2.5 \
-    planetmint-driver~=0.9.0
+    planetmint-driver~=0.9.0 \
+    pycco
+
 RUN apt-get update && apt-get install -y openssh-client openssh-server
\ No newline at end of file
diff --git a/integration/python/src/test_basic.py b/integration/python/src/test_basic.py
index 643f3d9..4932638 100644
--- a/integration/python/src/test_basic.py
+++ b/integration/python/src/test_basic.py
@@ -7,18 +7,21 @@
 from planetmint_driver import Planetmint
 from planetmint_driver.crypto import generate_keypair
 import time
-import os
 
 
 def test_basic():
     # Setup up connection to Planetmint integration test nodes
-    pm_itest1_url = os.environ.get('PLANETMINT_ENDPOINT_1')
-    pm_itest2_url = os.environ.get('PLANETMINT_ENDPOINT_1')
-    pm_itest1 = Planetmint(pm_itest1_url)
-    pm_itest2 = Planetmint(pm_itest2_url)
+    hosts = []
+    with open('/shared/hostnames') as f:
+        hosts = f.readlines()
+
+    pm_hosts = list(map(lambda x: Planetmint(x), hosts))
+
+    pm_alpha = pm_hosts[0]
+    pm_betas = pm_hosts[1:]
 
     # genarate a keypair
-    alice, bob = generate_keypair(), generate_keypair()
+    alice = generate_keypair()
 
     # create a digital asset for Alice
     game_boy_token = {
@@ -29,30 +32,31 @@ def test_basic():
     }
 
     # prepare the transaction with the digital asset and issue 10 tokens to bob
-    prepared_creation_tx = pm_itest1.transactions.prepare(
+    prepared_creation_tx = pm_alpha.transactions.prepare(
         operation='CREATE',
         metadata={
             'hash': '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF',
-            'storageID': '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF',},
+            'storageID': '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', },
         signers=alice.public_key,
         recipients=[([alice.public_key], 10)],
         asset=game_boy_token)
 
     # fulfill and send the transaction
-    fulfilled_creation_tx = pm_itest1.transactions.fulfill(
+    fulfilled_creation_tx = pm_alpha.transactions.fulfill(
         prepared_creation_tx,
         private_keys=alice.private_key)
-    pm_itest1.transactions.send_commit(fulfilled_creation_tx)
-    time.sleep(4)
+    pm_alpha.transactions.send_commit(fulfilled_creation_tx)
+    time.sleep(1)
 
     creation_tx_id = fulfilled_creation_tx['id']
 
-    # retrieve transactions from both planetmint nodes
-    creation_tx_itest1 = pm_itest1.transactions.retrieve(creation_tx_id)
-    creation_tx_itest2 = pm_itest2.transactions.retrieve(creation_tx_id)
+    # retrieve transactions from all planetmint nodes
+    creation_tx_alpha = pm_alpha.transactions.retrieve(creation_tx_id)
+    creation_tx_betas = list(map(lambda beta: beta.transactions.retrieve(creation_tx_id), pm_betas))
 
-    # Assert that transaction is stored on both planetmint nodes
-    assert creation_tx_itest1 == creation_tx_itest2
+    # Assert that transaction is stored on all planetmint nodes
+    for tx in creation_tx_betas:
+        assert creation_tx_alpha == tx
 
     # Transfer
     # create the output and inout for the transaction
@@ -65,35 +69,28 @@ def test_basic():
                       'owners_before': output['public_keys']}
 
     # prepare the transaction and use 3 tokens
-    prepared_transfer_tx = pm_itest1.transactions.prepare(
+    prepared_transfer_tx = pm_alpha.transactions.prepare(
         operation='TRANSFER',
         asset=transfer_asset,
         inputs=transfer_input,
         metadata={'hash': '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF',
-                'storageID': '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', },
+                  'storageID': '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', },
         recipients=[([alice.public_key], 10)])
 
     # fulfill and send the transaction
-    fulfilled_transfer_tx = pm_itest1.transactions.fulfill(
+    fulfilled_transfer_tx = pm_alpha.transactions.fulfill(
         prepared_transfer_tx,
         private_keys=alice.private_key)
-    sent_transfer_tx = pm_itest1.transactions.send_commit(fulfilled_transfer_tx)
+    sent_transfer_tx = pm_alpha.transactions.send_commit(fulfilled_transfer_tx)
 
-    transfer_tx_id = fulfilled_transfer_tx['id']
+    time.sleep(1)
+
+    transfer_tx_id = sent_transfer_tx['id']
 
     # retrieve transactions from both planetmint nodes
-    transfer_tx_itest1 = pm_itest1.transactions.retrieve(transfer_tx_id)
-    transfer_tx_itest2 = pm_itest2.transactions.retrieve(transfer_tx_id)
+    transfer_tx_alpha = pm_alpha.transactions.retrieve(transfer_tx_id)
+    transfer_tx_betas = list(map(lambda beta: beta.transactions.retrieve(transfer_tx_id), pm_betas))
 
     # Assert that transaction is stored on both planetmint nodes
-    assert transfer_tx_itest1 == transfer_tx_itest2
-
-
-
-
-    
-    
-
-
-
-
+    for tx in transfer_tx_betas:
+        assert transfer_tx_alpha == tx
diff --git a/integration/python/src/test_multisig.py b/integration/python/src/test_multisig.py
index 12dcc08..94ce9dc 100644
--- a/integration/python/src/test_multisig.py
+++ b/integration/python/src/test_multisig.py
@@ -18,21 +18,24 @@
 #
 # This integration test is a rip-off of our mutliple signature acceptance tests.
 
-# ## Imports
-# We need some utils from the `os` package, we will interact with
-# env variables.
-import os
+# # Imports
 import time
 
 # For this test case we need import and use the Python driver
 from planetmint_driver import Planetmint
 from planetmint_driver.crypto import generate_keypair
-from planetmint_driver.exceptions import NotFoundError
+
 
 def test_multiple_owners():
-    # ## Set up a connection to the Planetmint integration test nodes
-    pm_itest1 = Planetmint(os.environ.get('PLANETMINT_ENDPOINT_1'))
-    pm_itest2 = Planetmint(os.environ.get('PLANETMINT_ENDPOINT_2'))
+    # Setup up connection to Planetmint integration test nodes
+    hosts = []
+    with open('/shared/hostnames') as f:
+        hosts = f.readlines()
+
+    pm_hosts = list(map(lambda x: Planetmint(x), hosts))
+
+    pm_alpha = pm_hosts[0]
+    pm_betas = pm_hosts[1:]
 
     # Generate Keypairs for Alice and Bob!
     alice, bob = generate_keypair(), generate_keypair()
@@ -52,7 +55,7 @@ def test_multiple_owners():
 
     # They prepare a `CREATE` transaction. To have multiple owners, both
     # Bob and Alice need to be the recipients.
-    prepared_dw_tx = pm_itest1.transactions.prepare(
+    prepared_dw_tx = pm_alpha.transactions.prepare(
         operation='CREATE',
         signers=alice.public_key,
         recipients=(alice.public_key, bob.public_key),
@@ -60,36 +63,31 @@ def test_multiple_owners():
 
     # Now they both sign the transaction by providing their private keys.
     # And send it afterwards.
-    fulfilled_dw_tx = pm_itest1.transactions.fulfill(
+    fulfilled_dw_tx = pm_alpha.transactions.fulfill(
         prepared_dw_tx,
         private_keys=[alice.private_key, bob.private_key])
 
-    pm_itest1.transactions.send_commit(fulfilled_dw_tx)
+    pm_alpha.transactions.send_commit(fulfilled_dw_tx)
 
     # We store the `id` of the transaction to use it later on.
     dw_id = fulfilled_dw_tx['id']
 
+    time.sleep(1)
     # Let's retrieve the transaction from both nodes
-    pm_itest1_tx = pm_itest1.transactions.retrieve(dw_id)
-    pm_itest2_tx = {}
-    # TODO: REPLACE WITH ASYNC OR POLL
-    try:
-        pm_itest2_tx = pm_itest2.transactions.retrieve(dw_id)
-    except NotFoundError:
-        print('TOO FAST')
-        time.sleep(3)
-        pm_itest2_tx = pm_itest2.transactions.retrieve(dw_id)
+    pm_alpha_tx = pm_alpha.transactions.retrieve(dw_id)
+    pm_betas_tx = list(map(lambda beta: beta.transactions.retrieve(dw_id), pm_betas))
 
     # Both retrieved transactions should be the same
-    assert pm_itest1_tx == pm_itest2_tx
+    for tx in pm_betas_tx:
+        assert pm_alpha_tx == tx
 
     # Let's check if the transaction was successful.
-    assert pm_itest1.transactions.retrieve(dw_id), \
+    assert pm_alpha.transactions.retrieve(dw_id), \
         'Cannot find transaction {}'.format(dw_id)
 
     # The transaction should have two public keys in the outputs.
     assert len(
-        pm_itest1.transactions.retrieve(dw_id)['outputs'][0]['public_keys']) == 2
+        pm_alpha.transactions.retrieve(dw_id)['outputs'][0]['public_keys']) == 2
 
     # ## Alice and Bob transfer a transaction to Carol.
     # Alice and Bob save a lot of money living together. They often go out
@@ -112,43 +110,37 @@ def test_multiple_owners():
                       'owners_before': output['public_keys']}
 
     # Now they create the transaction...
-    prepared_transfer_tx = pm_itest1.transactions.prepare(
+    prepared_transfer_tx = pm_alpha.transactions.prepare(
         operation='TRANSFER',
         asset=transfer_asset,
         inputs=transfer_input,
         recipients=carol.public_key)
 
     # ... and sign it with their private keys, then send it.
-    fulfilled_transfer_tx = pm_itest1.transactions.fulfill(
+    fulfilled_transfer_tx = pm_alpha.transactions.fulfill(
         prepared_transfer_tx,
         private_keys=[alice.private_key, bob.private_key])
 
-    sent_transfer_tx = pm_itest1.transactions.send_commit(fulfilled_transfer_tx)
+    sent_transfer_tx = pm_alpha.transactions.send_commit(fulfilled_transfer_tx)
+    time.sleep(1)
 
     # Retrieve the fulfilled transaction from both nodes
-    pm_itest1_tx = pm_itest1.transactions.retrieve(fulfilled_transfer_tx['id'])
-    pm_itest2_tx
-    # TODO: REPLACE WITH ASYNC OR POLL
-    try:
-        pm_itest2_tx = pm_itest2.transactions.retrieve(fulfilled_transfer_tx['id'])
-    except NotFoundError:
-        print('TOO FAST')
-        time.sleep(3)
-        pm_itest2_tx = pm_itest2.transactions.retrieve(fulfilled_transfer_tx['id'])
+    pm_alpha_tx = pm_alpha.transactions.retrieve(fulfilled_transfer_tx['id'])
+    pm_betas_tx = list(map(lambda beta: beta.transactions.retrieve(fulfilled_transfer_tx['id']), pm_betas))
 
     # Now compare if both nodes returned the same transaction
-    assert pm_itest1_tx == pm_itest2_tx
+    for tx in pm_betas_tx:
+        assert pm_alpha_tx == tx
 
     # They check if the transaction was successful.
-    assert pm_itest1.transactions.retrieve(
+    assert pm_alpha.transactions.retrieve(
         fulfilled_transfer_tx['id']) == sent_transfer_tx
 
     # The owners before should include both Alice and Bob.
     assert len(
-        pm_itest1.transactions.retrieve(fulfilled_transfer_tx['id'])['inputs'][0][
+        pm_alpha.transactions.retrieve(fulfilled_transfer_tx['id'])['inputs'][0][
             'owners_before']) == 2
 
     # While the new owner is Carol.
-    assert pm_itest1.transactions.retrieve(fulfilled_transfer_tx['id'])[
+    assert pm_alpha.transactions.retrieve(fulfilled_transfer_tx['id'])[
            'outputs'][0]['public_keys'][0] == carol.public_key
-    
\ No newline at end of file
diff --git a/integration/scripts/clean-shared.sh b/integration/scripts/clean-shared.sh
index b303cff..7ba481e 100755
--- a/integration/scripts/clean-shared.sh
+++ b/integration/scripts/clean-shared.sh
@@ -4,6 +4,8 @@
 # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
 # Code is Apache-2.0 and docs are CC-BY-4.0
 
-rm /shared/planetmint*
-rm /shared/genesis.json
+rm /shared/hostnames
+rm /shared/lock
+rm /shared/*node_id
+rm /shared/*.json
 rm /shared/id_rsa.pub
\ No newline at end of file
diff --git a/integration/scripts/genesis.py b/integration/scripts/genesis.py
index b46b5ba..3593f34 100755
--- a/integration/scripts/genesis.py
+++ b/integration/scripts/genesis.py
@@ -5,32 +5,29 @@
 # Code is Apache-2.0 and docs are CC-BY-4.0
 
 import json
-import os
+import sys
+
 
-# TODO: CHANGE ME/OTHER VARIABLES
 def edit_genesis() -> None:
-    ME = os.getenv('ME')
-    OTHER = os.getenv('OTHER')
-
-    if ME == 'planetmint_1':
-        file_name = '{}_genesis.json'.format(ME)
-        other_file_name = '{}_genesis.json'.format(OTHER)
-
-        file = open(os.path.join('/shared', file_name))
-        other_file = open(os.path.join('/shared', other_file_name))
+    file_names = sys.argv[1:]
 
+    validators = []
+    for file_name in file_names:
+        file = open(file_name)
         genesis = json.load(file)
-        other_genesis = json.load(other_file)
-
-        genesis['validators'] = genesis['validators'] + other_genesis['validators']
-
+        validators.extend(genesis['validators'])
         file.close()
-        other_file.close()
 
-        with open(os.path.join('/shared', 'genesis.json'), 'w') as f:
-            json.dump(genesis, f, indent=True)
+    genesis_file = open(file_names[0])
+    genesis_json = json.load(genesis_file)
+    genesis_json['validators'] = validators
+    genesis_file.close()
+
+    with open('/shared/genesis.json', 'w') as f:
+        json.dump(genesis_json, f, indent=True)
 
     return None
 
+
 if __name__ == '__main__':
     edit_genesis()
diff --git a/integration/scripts/pre-config-planetmint.sh b/integration/scripts/pre-config-planetmint.sh
index d2f1b0a..ea15ea7 100755
--- a/integration/scripts/pre-config-planetmint.sh
+++ b/integration/scripts/pre-config-planetmint.sh
@@ -4,6 +4,9 @@
 # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
 # Code is Apache-2.0 and docs are CC-BY-4.0
 
+# Write hostname to list
+echo $(hostname) >> /shared/hostnames
+
 # Create ssh folder
 mkdir ~/.ssh
 
@@ -26,31 +29,48 @@ service ssh restart
 tendermint init
 
 # Write node id to shared folder
+HOSTNAME=$(hostname)
 NODE_ID=$(tendermint show_node_id | tail -n 1)
-echo $NODE_ID > /shared/${ME}_node_id
+echo $NODE_ID > /shared/${HOSTNAME}_node_id
 
-# Wait for other node id
-while [ ! -f "/shared/${OTHER}_node_id" ]; do
-    echo "WAIT FOR NODE ID"
+# Wait for other node ids
+FILES=()
+while [ ! ${#FILES[@]} == $SCALE ]; do
+    echo "WAIT FOR NODE IDS"
     sleep 1
+    FILES=(/shared/*node_id)
 done
 
 # Write node ids to persistent peers
-OTHER_NODE_ID=$(cat /shared/${OTHER}_node_id)
-PEERS=$(echo "persistent_peers = \"${NODE_ID}@${ME}:26656, ${OTHER_NODE_ID}@${OTHER}:26656\"")
+PEERS="persistent_peers = \""
+for f in ${FILES[@]}; do
+    ID=$(cat $f)
+    HOST=$(echo $f | cut -c 9-20)
+    if [ ! $HOST == $HOSTNAME ]; then
+        PEERS+="${ID}@${HOST}:26656, "
+    fi
+done
+PEERS=$(echo $PEERS | rev | cut -c 2- | rev)
+PEERS+="\""
 sed -i "/persistent_peers = \"\"/c\\${PEERS}" /tendermint/config/config.toml
 
 # Copy genesis.json to shared folder
-cp /tendermint/config/genesis.json /shared/${ME}_genesis.json
+cp /tendermint/config/genesis.json /shared/${HOSTNAME}_genesis.json
 
 # Await config file of all services to be present
-while [ ! -f /shared/${OTHER}_genesis.json ]; do
-    echo "WAIT FOR OTHER GENESIS"
+FILES=()
+while [ ! ${#FILES[@]} == $SCALE ]; do
+    echo "WAIT FOR GENESIS FILES"
     sleep 1
+    FILES=(/shared/*_genesis.json)
 done
 
 # Create genesis.json for nodes
-/usr/src/app/scripts/genesis.py
+if [ ! -f /shared/lock ]; then
+    echo LOCKING
+    touch /shared/lock
+    /usr/src/app/scripts/genesis.py ${FILES[@]}
+fi
 
 while [ ! -f /shared/genesis.json ]; do
     echo "WAIT FOR GENESIS"
diff --git a/integration/scripts/test.sh b/integration/scripts/test.sh
index 1626526..de9271d 100755
--- a/integration/scripts/test.sh
+++ b/integration/scripts/test.sh
@@ -4,7 +4,19 @@
 # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
 # Code is Apache-2.0 and docs are CC-BY-4.0
 
-result=$(ssh -o "StrictHostKeyChecking=no" -i \~/.ssh/id_rsa root@planetmint_1 'bash -s' < scripts/election.sh elect 2)
-ssh -o "StrictHostKeyChecking=no" -i ~/.ssh/id_rsa root@planetmint_2 'bash -s' < scripts/election.sh approve $result
+# Read host names from shared
+readarray -t HOSTNAMES < /shared/hostnames
+
+# Split into proposer and approvers
+ALPHA=${HOSTNAMES[0]}
+BETAS=${HOSTNAMES[@]:1}
+
+# Propose validator upsert
+result=$(ssh -o "StrictHostKeyChecking=no" -i \~/.ssh/id_rsa root@${ALPHA} 'bash -s' < scripts/election.sh elect 2)
+
+# Approve validator upsert
+for BETA in ${BETAS[@]}; do
+    ssh -o "StrictHostKeyChecking=no" -i ~/.ssh/id_rsa root@${BETA} 'bash -s' < scripts/election.sh approve $result
+done
 
 exec "$@"
\ No newline at end of file
diff --git a/integration/scripts/wait-for-planetmint.sh b/integration/scripts/wait-for-planetmint.sh
index 1864c54..36c7794 100755
--- a/integration/scripts/wait-for-planetmint.sh
+++ b/integration/scripts/wait-for-planetmint.sh
@@ -5,24 +5,25 @@
 # Code is Apache-2.0 and docs are CC-BY-4.0
 
 # Only continue if all services are ready
-while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' planetmint_1:9984/api/v1)" != "200" ]]; do
-    echo "WAIT FOR PLANETMINT"
+HOSTNAMES=()
+while [ ! ${#HOSTNAMES[@]} == $SCALE ]; do
+    echo "WAIT FOR HOSTNAMES"
     sleep 1
+    readarray -t HOSTNAMES < /shared/hostnames
 done
 
-while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' planetmint_1:26657)" != "200" ]]; do
-    echo "WAIT FOR TENDERMINT"
-    sleep 1
+for host in ${HOSTNAMES[@]}; do
+    while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' $host:9984)" != "200" ]]; do
+        echo "WAIT FOR PLANETMINT $host"
+        sleep 1
+    done
 done
 
-while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' planetmint_2:9984/api/v1)" != "200" ]]; do
-    echo "WAIT FOR PLANETMINT"
-    sleep 1
-done
-
-while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' planetmint_2:26657)" != "200" ]]; do
-    echo "WAIT FOR TENDERMINT"
-    sleep 1
+for host in ${HOSTNAMES[@]}; do
+    while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' $host:26657)" != "200" ]]; do
+        echo "WAIT FOR TENDERMINT $host"
+        sleep 1
+    done
 done
 
 exec "$@"
\ No newline at end of file