git.delta.rocks / unique-network / refs/commits / 9296d13cc7af

difftreelog

Merge pull request #840 from UniqueNetwork/feature/collator-workflow

Yaroslav Bolyukin2023-01-18parents: #9505eb6 #eb6dfa3.patch.diff
in: master

12 files changed

added.docker/Dockerfile-collatorsdiffbeforeafterboth
--- /dev/null
+++ b/.docker/Dockerfile-collators
@@ -0,0 +1,74 @@
+ARG POLKADOT_BUILD_BRANCH
+FROM uniquenetwork/builder-polkadot:${POLKADOT_BUILD_BRANCH} as polkadot
+
+# ===== Rust builder =====
+FROM uniquenetwork/services:latest as rust-builder
+ARG RUST_TOOLCHAIN=
+
+ENV RUST_TOOLCHAIN $RUST_TOOLCHAIN
+ENV CARGO_HOME="/cargo-home"
+ENV PATH="/cargo-home/bin:$PATH"
+ENV TZ=UTC
+
+RUN rustup toolchain uninstall $(rustup toolchain list) && \
+    rustup toolchain install $RUST_TOOLCHAIN && \
+    rustup default $RUST_TOOLCHAIN && \
+    rustup target list --installed && \
+    rustup show
+RUN rustup target add wasm32-unknown-unknown --toolchain $RUST_TOOLCHAIN
+
+RUN mkdir /unique_parachain
+WORKDIR /unique_parachain
+
+# ===== BUILD ======
+FROM rust-builder as builder-unique
+
+ARG PROFILE=release
+ARG NETWORK
+ARG POLKADOT_LAUNCH_BRANCH
+ARG BRANCH
+
+ENV POLKADOT_LAUNCH_BRANCH $POLKADOT_LAUNCH_BRANCH
+
+WORKDIR /unique_parachain
+
+RUN git clone -b $BRANCH https://github.com/UniqueNetwork/unique-chain.git && \
+    cd unique-chain && \
+    cargo build --features=$NETWORK-runtime --$PROFILE
+
+# ===== RUN ======
+
+FROM ubuntu:20.04
+
+RUN apt-get -y update && \
+      apt-get -y install curl git && \
+      curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash && \
+      export NVM_DIR="$HOME/.nvm" && \
+      [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" && \
+      nvm install v16.16.0 && \
+      nvm use v16.16.0
+
+RUN git clone https://github.com/uniquenetwork/polkadot-launch -b ${POLKADOT_LAUNCH_BRANCH}
+
+RUN export NVM_DIR="$HOME/.nvm" && \
+    [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" && \
+    cd /polkadot-launch && \
+    npm install --global yarn && \
+    yarn install
+
+COPY --from=builder-unique /unique_parachain/unique-chain/.docker/testnet-config/launch-config.json /polkadot-launch/launch-config.json
+COPY --from=builder-unique /unique_parachain/unique-chain/target/release/unique-collator /unique-chain/target/release/
+
+COPY --from=polkadot /unique_parachain/polkadot/target/release/polkadot /polkadot/target/release/
+
+EXPOSE 9844
+EXPOSE 9944
+EXPOSE 9933
+EXPOSE 9833
+EXPOSE 40333
+EXPOSE 30333
+
+CMD export NVM_DIR="$HOME/.nvm" && \
+    [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" && \
+    cd /polkadot-launch && \
+    yarn start launch-config.json
modified.docker/Dockerfile-parachaindiffbeforeafterboth
--- a/.docker/Dockerfile-parachain
+++ b/.docker/Dockerfile-parachain
@@ -32,7 +32,7 @@
 
 WORKDIR /unique_parachain
 
-RUN git clone $REPO_URL -b $BRANCH . && \
+RUN git clone https://github.com/UniqueNetwork/unique-chain.git -b $BRANCH . && \
     cargo build --features=${NETWORK}-runtime --$PROFILE
 
 # ===== RUN ======
added.docker/docker-compose.tmp-collators.j2diffbeforeafterboth
--- /dev/null
+++ b/.docker/docker-compose.tmp-collators.j2
@@ -0,0 +1,25 @@
+version: "3.5"
+
+services:
+  parachain-collators:
+    build:
+      args:
+        - "RUST_TOOLCHAIN={{ RUST_TOOLCHAIN }}"
+        - "POLKADOT_BUILD_BRANCH={{ POLKADOT_BUILD_BRANCH }}"
+        - "POLKADOT_LAUNCH_BRANCH={{ POLKADOT_LAUNCH_BRANCH }}"
+        - "NETWORK={{ NETWORK }}"
+        - "BRANCH={{ BRANCH }}"
+      context: ../
+      dockerfile: .docker/Dockerfile-parachain
+    image: parachain-collators
+    container_name: parachain-collators
+    expose:
+      - 9944
+      - 9933
+    ports:
+      - 127.0.0.1:9944:9944
+      - 127.0.0.1:9933:9933
+    logging:
+      options:
+        max-size: "1m"
+        max-file: "3"
modified.docker/testnet-config/launch-config.jsondiffbeforeafterboth
--- a/.docker/testnet-config/launch-config.json
+++ b/.docker/testnet-config/launch-config.json
@@ -111,6 +111,42 @@
                         "--unsafe-ws-external",
                         "-lxcm=trace"
                     ]
+                },
+                {
+                    "port": 31202,
+                    "wsPort": 9946,
+                    "rpcPort": 9935,
+                    "name": "charlie",
+                    "flags": [
+                        "--rpc-cors=all",
+                        "--unsafe-rpc-external",
+                        "--unsafe-ws-external",
+                        "-lxcm=trace"
+                    ]
+                },
+                {
+                    "port": 31203,
+                    "wsPort": 9947,
+                    "rpcPort": 9936,
+                    "name": "dave",
+                    "flags": [
+                        "--rpc-cors=all",
+                        "--unsafe-rpc-external",
+                        "--unsafe-ws-external",
+                        "-lxcm=trace"
+                    ]
+                },
+                {
+                    "port": 31204,
+                    "wsPort": 9948,
+                    "rpcPort": 9937,
+                    "name": "eve",
+                    "flags": [
+                        "--rpc-cors=all",
+                        "--unsafe-rpc-external",
+                        "--unsafe-ws-external",
+                        "-lxcm=trace"
+                    ]
                 }
             ]
         }
modified.github/workflows/ci-develop.ymldiffbeforeafterboth
before · .github/workflows/ci-develop.yml
1# https://cryptousetech.atlassian.net/wiki/spaces/CI/pages/2586869783/CI+Develop2# Workflow which controls starts nested workflows.3name: develop45# Triger: PR at 'develop' branch with following types of events.6on:7  pull_request:8    branches: [ 'develop' ]9    types: [ opened, reopened, synchronize, ready_for_review, converted_to_draft ]1011#Concurency group for control execution queue over github runners.12concurrency:13  group: ${{ github.workflow }}-${{ github.head_ref }}14  cancel-in-progress: true1516# List of a jobs included into Workflow.17jobs:1819  yarn-dev:20    if: github.event.pull_request.draft == false                                                                             # Conditional check for draft per job.21    uses: ./.github/workflows/yarn-dev.yml2223  unit-test:24    if: github.event.pull_request.draft == false                                                                             # Conditional check for draft per job.25    uses: ./.github/workflows/unit-test.yml2627  canary:28    if: ${{ (github.event.pull_request.draft == false && contains( github.event.pull_request.labels.*.name, 'canary')) }}    # Conditional check for draft & labels per job.29    uses: ./.github/workflows/canary.yml30    secrets: inherit # pass all secrets from initial workflow to nested3132  xcm:33    if: ${{ (github.event.pull_request.draft == false && contains( github.event.pull_request.labels.*.name, 'xcm')) }}       # Conditional check for draft & labels per job.34    uses: ./.github/workflows/xcm.yml35    secrets: inherit # pass all secrets from initial workflow to nested36    37  forkless:38    if: ${{ (github.event.pull_request.draft == false && contains( github.event.pull_request.labels.*.name, 'forkless')) }}  # Conditional check for draft & labels per job.39    uses: ./.github/workflows/forkless.yml40  41  node-only-update:42    if: ${{ (github.event.pull_request.draft == false && contains( github.event.pull_request.labels.*.name, 'node-only-update')) }}  # Conditional check for draft & labels per job.43    uses: ./.github/workflows/node-only-update.yml44    45  # integration:46  #   if: ${{ (github.event.pull_request.draft == false && contains( github.event.pull_request.labels.*.name, 'integration')) }}      # Conditional check for draft & labels per job.47  #   uses: ./.github/workflows/integration-tests.yml4849  codestyle:50    if: github.event.pull_request.draft == false                                                                             # Conditional check for draft per job.51    uses: ./.github/workflows/codestyle.yml
modified.github/workflows/ci-master.ymldiffbeforeafterboth
--- a/.github/workflows/ci-master.yml
+++ b/.github/workflows/ci-master.yml
@@ -35,6 +35,9 @@
   xcm:
     uses: ./.github/workflows/xcm.yml
     secrets: inherit # pass all secrets from initial workflow to nested
+    
+  collator-selection:
+    uses: ./.github/workflows/collator-selection.yml
 
   # testnet:
   #   uses: ./.github/workflows/testnet-build.yml
added.github/workflows/collator-selection.ymldiffbeforeafterboth
--- /dev/null
+++ b/.github/workflows/collator-selection.yml
@@ -0,0 +1,165 @@
+name: collator-selection-test-run
+
+# Triger: only call from main workflow(re-usable workflows)
+on:
+  workflow_call:
+
+# A workflow run is made up of one or more jobs that can run sequentially or in parallel
+jobs:
+  execution-marix:
+    name: Prepare execution matrix
+
+    runs-on: self-hosted-ci
+    outputs:
+      matrix: ${{ steps.create_matrix.outputs.matrix }}
+
+    steps:
+
+      - name: Clean Workspace
+        uses: AutoModality/action-clean@v1.1.0
+
+      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
+      - uses: actions/checkout@v3
+        with:
+          ref: ${{ github.head_ref }}  #Checking out head commit
+
+      - name: Read .env file
+        uses: xom9ikk/dotenv@v2
+
+      - name: Create Execution matrix
+        uses: CertainLach/create-matrix-action@v3
+        id: create_matrix
+        with:
+          matrix: |
+            network {opal}, wasm_name {opal}, mainnet_branch {${{ env.OPAL_MAINNET_BRANCH }}}, replica_from_address {${{ env.OPAL_REPLICA_FROM }}}, relay_branch {${{ env.UNIQUEWEST_MAINNET_BRANCH }}}
+            network {quartz}, wasm_name {quartz}, mainnet_branch {${{ env.QUARTZ_MAINNET_BRANCH }}}, replica_from_address {${{ env.QUARTZ_REPLICA_FROM }}}, relay_branch {${{ env.KUSAMA_MAINNET_BRANCH }}}
+            network {sapphire}, wasm_name {quartz}, mainnet_branch {${{ env.SAPPHIRE_MAINNET_BRANCH }}}, replica_from_address {${{ env.SAPPHIRE_REPLICA_FROM }}}, relay_branch {${{ env.UNIQUEEAST_MAINNET_BRANCH }}}
+
+  collator-selection:
+    needs: execution-marix
+    # The type of runner that the job will run on
+    runs-on: [self-hosted-ci,large]
+    timeout-minutes: 1380
+
+    name: ${{ matrix.network }}
+    strategy:
+      matrix:
+        include: ${{fromJson(needs.execution-marix.outputs.matrix)}}
+
+    continue-on-error: true         #Do not stop testing of matrix runs failed.  As it decided during PR review - it required 50/50& Let's check it with false.
+
+    steps:
+      - name: Clean Workspace
+        uses: AutoModality/action-clean@v1.1.0
+
+      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
+      - uses: actions/checkout@v3
+        with:
+          ref: ${{ github.head_ref }}  #Checking out head commit
+
+      - name: Read .env file
+        uses: xom9ikk/dotenv@v2
+
+      - name: Generate ENV related extend file for docker-compose
+        uses: cuchi/jinja2-action@v1.2.0
+        with:
+          template: .docker/docker-compose.tmp-collators.j2
+          output_file: .docker/docker-compose.collators.${{ matrix.network }}.yml
+          variables: |
+            RUST_TOOLCHAIN=${{ env.RUST_TOOLCHAIN }}
+            POLKADOT_BUILD_BRANCH=${{ env.POLKADOT_BUILD_BRANCH }}
+            POLKADOT_LAUNCH_BRANCH=${{ env.POLKADOT_LAUNCH_BRANCH }}
+            NETWORK=${{ matrix.network }}
+            BRANCH=${{ github.head_ref }}
+
+      - name: Show build configuration
+        run: cat .docker/docker-compose.collators.${{ matrix.network }}.yml
+
+      - name: Show launch-config configuration
+        run: cat launch-config.json
+
+      - name: Build the stack
+        run: docker-compose -f ".docker/docker-compose.collators.${{ matrix.network }}.yml" up -d --remove-orphans --force-recreate --timeout 300
+
+      #  🚀 POLKADOT LAUNCH COMPLETE 🚀
+      - name: Check if docker logs consist messages related to testing of collator tests 
+        if: success()
+        run: |
+          counter=160
+          function check_container_status {
+                docker inspect -f {{.State.Running}} parachain-collators
+          }
+          function do_docker_logs {
+                docker logs --details parachain-collators  2>&1
+          }
+          function is_started {
+                if [ "$(check_container_status)" == "true" ]; then
+                        echo "Container: collator-${{ matrix.network }}-testnet-local RUNNING";
+                        echo "Check Docker logs"
+                        DOCKER_LOGS=$(do_docker_logs)
+                        if [[ ${DOCKER_LOGS} = *"POLKADOT LAUNCH COMPLETE"* ]];then
+                                echo "🚀 POLKADOT LAUNCH COMPLETE 🚀"
+                                return 0
+                        else
+                                echo "Message not found in logs output, repeating..."
+                                return 1
+                        fi
+                else
+                        echo "Container collator-${{ matrix.network }}-testnet-local NOT RUNNING"
+                        echo "Halting all future checks"
+                        exit 1
+                fi
+          echo "something goes wrong"
+          exit 1
+          }
+          while ! is_started; do
+                echo "Waiting for special message in log files "
+                sleep 30s
+                counter=$(( $counter - 1 ))
+                echo "Counter: $counter"
+                if [ "$counter" -gt "0" ]; then
+                         continue
+                else
+                         break
+                fi
+          done
+          echo "Halting script"
+          exit 0
+        shell: bash
+
+      - uses: actions/setup-node@v3
+        with:
+          node-version: 16
+
+      - name: Run Collator tests
+        working-directory: tests
+        if: success() || failure()
+        run: |
+          yarn install
+          yarn add mochawesome
+          node scripts/readyness.js
+          echo "Ready to start tests"
+          yarn polkadot-types
+          NOW=$(date +%s) && yarn testCollators --reporter mochawesome --reporter-options reportFilename=test-collators-${NOW}
+        env:
+          RPC_URL: http://127.0.0.1:9933/
+
+      - name: Test Report Collator
+        uses: phoenix-actions/test-reporting@v8
+        id: test-report-collator
+        if: success() || failure()    # run this step even if previous step failed
+        with:
+          name: Report Collator tests results - ${{ matrix.network }}            # Name of the check run which will be created
+          path: tests/mochawesome-report/test-collators-*.json    # Path to test results
+          reporter: mochawesome-json
+          fail-on-error: 'false'
+
+      - name: Stop running containers
+        if: always()                   # run this step always
+        run: docker-compose -f ".docker/docker-compose.collators.${{ matrix.network }}.yml" down
+
+      - name: Remove builder cache
+        if: always()                   # run this step always
+        run: |
+          docker builder prune -f -a
+          docker system prune -f
modifiedtests/package.jsondiffbeforeafterboth
--- a/tests/package.json
+++ b/tests/package.json
@@ -88,8 +88,9 @@
     "testSchedulingEVM": "mocha --timeout 9999999 -r ts-node/register ./**/eth/scheduling.test.ts",
     "testPalletPresence": "mocha --timeout 9999999 -r ts-node/register ./**/pallet-presence.test.ts",
     "testBlockProduction": "mocha --timeout 9999999 -r ts-node/register ./**/block-production.test.ts",
-    "testCollatorSelection": "mocha --timeout 9999999 -r ts-node/register ./**/collatorSelection.*test.ts",
-    "testIdentity": "mocha --timeout 9999999 -r ts-node/register ./**/identity.*test.ts",
+    "testCollators": "RUN_COLLATOR_TESTS=1 mocha --timeout 49999999 -r ts-node/register ./**/collator-selection/**.*test.ts",
+    "testCollatorSelection": "RUN_COLLATOR_TESTS=1 mocha --timeout 49999999 -r ts-node/register ./**/collatorSelection.*test.ts",
+    "testIdentity": "RUN_COLLATOR_TESTS=1 mocha --timeout 49999999 -r ts-node/register ./**/identity.*test.ts",
     "testEnableDisableTransfers": "mocha --timeout 9999999 -r ts-node/register ./**/enableDisableTransfer.test.ts",
     "testLimits": "mocha --timeout 9999999 -r ts-node/register ./**/limits.test.ts",
     "testEthCreateNFTCollection": "mocha --timeout 9999999 -r ts-node/register ./**/eth/createNFTCollection.test.ts",
addedtests/src/collator-selection/collatorSelection.seqtest.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/collator-selection/collatorSelection.seqtest.ts
@@ -0,0 +1,468 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+import {IKeyringPair} from '@polkadot/types/types';
+import {usingPlaygrounds, expect, itSub, Pallets, requirePalletsOrSkip} from '../util';
+
+async function resetInvulnerables() {
+  await usingPlaygrounds(async (helper, privateKey) => {
+    const superuser = await privateKey('//Alice');
+    const alice = await privateKey('//Alice');
+    const bob = await privateKey('//Bob');
+    const invulnerables = await helper.collatorSelection.getInvulnerables();
+    if (!invulnerables.includes(alice.address) || !invulnerables.includes(bob.address) || invulnerables.length != 2) {
+      console.warn('Alice and Bob are not the invulnerables! Reinstating them back. '
+        + 'Current invulnerables\' size: ' + invulnerables.length);
+
+      let nonce = await helper.chain.getNonce(alice.address);
+      // In case there are too many invulnerables already, remove some of them, leaving space for Alice and Bob.
+      if (invulnerables.length + 2 >= helper.collatorSelection.maxCollators()) {
+        await Promise.all([
+          helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [invulnerables.pop()], true, {nonce: nonce++}),
+          helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [invulnerables.pop()], true, {nonce: nonce++}),
+        ]);
+      }
+
+      nonce = await helper.chain.getNonce(alice.address);
+      await Promise.all([
+        helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [alice.address], true, {nonce: nonce++}),
+        helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [bob.address], true, {nonce: nonce++}),
+      ]);
+
+      nonce = await helper.chain.getNonce(alice.address);
+      await Promise.all(invulnerables.map((invulnerable: any) => {
+        if (invulnerable == alice.address || invulnerable == bob.address) return new Promise<void>(res => res());
+        return helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [invulnerable], true, {nonce: nonce++});
+      }));
+    }
+  });
+}
+
+// todo:collator Most preferable to launch this test in parallel somehow -- or change the session period (1 hr).
+describe('Integration Test: Collator Selection', () => {
+  let superuser: IKeyringPair;
+  let previousLicenseBond = 0n;
+  let licenseBond = 0n;
+
+  before(async function() {
+    if (!process.env.RUN_COLLATOR_TESTS) this.skip();
+
+    await usingPlaygrounds(async (helper, privateKey) => {
+      requirePalletsOrSkip(this, helper, [Pallets.CollatorSelection]);
+      superuser = await privateKey('//Alice');
+
+      previousLicenseBond = await helper.collatorSelection.getLicenseBond();
+      licenseBond = 10n * helper.balance.getOneTokenNominal();
+      await helper.getSudo().collatorSelection.setLicenseBond(superuser, licenseBond);
+    });
+  });
+
+  describe('Dynamic shuffling of collators', () => {
+    // These two are the default invulnerables, and should return to be invulnerables after this suite.
+    let alice: IKeyringPair;
+    let bob: IKeyringPair;
+
+    let charlie: IKeyringPair;
+    let dave: IKeyringPair;
+
+    before(async function() {
+      await usingPlaygrounds(async (helper, privateKey) => {
+        // todo:collator see again if blocks start to be finalized in dev mode
+        // Skip the collator block production in dev mode, since the blocks are sealed automatically.
+        if (await helper.arrange.isDevNode()) this.skip();
+
+        alice = await privateKey('//Alice');
+        bob = await privateKey('//Bob');
+        charlie = await privateKey('//Charlie');
+        dave = await privateKey('//Dave');
+
+        expect((await helper.session.setOwnKeysFromAddress(charlie))
+          .status.toLowerCase()).to.be.equal('success');
+        expect((await helper.session.setOwnKeysFromAddress(dave))
+          .status.toLowerCase()).to.be.equal('success');
+
+        const invulnerables = await helper.collatorSelection.getInvulnerables();
+        if (!invulnerables.includes(alice.address) || !invulnerables.includes(bob.address) || invulnerables.length != 2) {
+          console.warn('Alice and Bob are not the invulnerables! Reinstating them back. '
+            + 'Current invulnerables\' size: ' + invulnerables.length);
+
+          let nonce = await helper.chain.getNonce(superuser.address);
+          await Promise.all([
+            helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [alice.address], true, {nonce: nonce++}),
+            helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [bob.address], true, {nonce: nonce++}),
+          ]);
+
+          nonce = await helper.chain.getNonce(superuser.address);
+          await Promise.all(invulnerables.map((invulnerable: any) => {
+            if (invulnerable == alice.address || invulnerable == bob.address) return new Promise((res) => res);
+            return helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [invulnerable], true, {nonce: nonce++});
+          }));
+        }
+      });
+    });
+
+    itSub('Change invulnerables and make sure they start producing blocks', async ({helper}) => {
+      let nonce = await helper.chain.getNonce(superuser.address);
+      await expect(Promise.all([
+        helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [charlie.address], true, {nonce: nonce++}),
+        helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [dave.address], true, {nonce: nonce++}),
+      ])).to.be.fulfilled;
+
+      nonce = await helper.chain.getNonce(superuser.address);
+      await expect(Promise.all([
+        helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [alice.address], true, {nonce: nonce++}),
+        helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [bob.address], true, {nonce: nonce++}),
+      ])).to.be.fulfilled;
+
+      const newInvulnerables = await helper.collatorSelection.getInvulnerables();
+      expect(newInvulnerables).to.contain(charlie.address).and.contain(dave.address).and.be.length(2);
+
+      await helper.wait.newSessions(2);
+
+      const newValidators = await helper.callRpc('api.query.session.validators');
+      expect(newValidators).to.contain(charlie.address).and.contain(dave.address).and.be.length(2);
+
+      const lastBlockNumber = await helper.chain.getLatestBlockNumber();
+      await helper.wait.newBlocks(1);
+      const lastCharlieBlock = (await helper.callRpc('api.query.collatorSelection.lastAuthoredBlock', [charlie.address])).toNumber();
+      const lastDaveBlock = (await helper.callRpc('api.query.collatorSelection.lastAuthoredBlock', [dave.address])).toNumber();
+      expect(lastCharlieBlock >= lastBlockNumber || lastDaveBlock >= lastBlockNumber).to.be.true;
+    });
+
+    after(async () => {
+      await usingPlaygrounds(async (helper) => {
+        if (await helper.arrange.isDevNode()) return;
+
+        let nonce = await helper.chain.getNonce(superuser.address);
+        await Promise.all([
+          helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [alice.address], true, {nonce: nonce++}),
+          helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [bob.address], true, {nonce: nonce++}),
+        ]);
+
+        nonce = await helper.chain.getNonce(superuser.address);
+        await Promise.all([
+          await helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [charlie.address], true, {nonce: nonce++}),
+          await helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [dave.address], true, {nonce: nonce++}),
+        ]);
+      });
+    });
+  });
+
+  describe('Getting and releasing licenses to collate', () => {
+    let charlie: IKeyringPair;
+    let dave: IKeyringPair;
+    let crowd: IKeyringPair[];
+
+    before(async function() {
+      await usingPlaygrounds(async (helper, privateKey) => {
+        charlie = await privateKey('//Charlie');
+        dave = await privateKey('//Dave');
+        crowd = await helper.arrange.createCrowd(20, 100n, superuser);
+
+        // set session keys for everyone
+        expect((await helper.session.setOwnKeysFromAddress(charlie))
+          .status.toLowerCase()).to.be.equal('success');
+        expect((await helper.session.setOwnKeysFromAddress(dave))
+          .status.toLowerCase()).to.be.equal('success');
+        await Promise.all(crowd.map(acc => helper.session.setOwnKeysFromAddress(acc)));
+      });
+    });
+
+    describe('Positive', () => {
+      itSub('Can lease and release a license', async ({helper}) => {
+        const account = crowd.pop()!;
+
+        // make sure it does not have any reserved funds
+        expect((await helper.balance.getSubstrateFull(account.address)).reserved).to.be.equal(0n);
+
+        // getting a license reserves a license bond cost
+        await helper.collatorSelection.obtainLicense(account);
+        expect(await helper.collatorSelection.hasLicense(account.address)).to.be.equal(licenseBond);
+        expect((await helper.balance.getSubstrateFull(account.address)).reserved).to.be.equal(licenseBond);
+
+        // releasing a license un-reserves the license bond cost
+        await helper.collatorSelection.releaseLicense(account);
+        expect(await helper.collatorSelection.hasLicense(account.address)).to.be.equal(0n);
+
+        const balance = await helper.balance.getSubstrateFull(account.address);
+        expect(balance.reserved).to.be.equal(0n);
+        expect(balance.free > 100n - licenseBond);
+      });
+
+      itSub('Can force revoke a license', async ({helper}) => {
+        const account = crowd.pop()!;
+
+        // getting a license reserves a license bond cost
+        const previousBalance = await helper.balance.getSubstrateFull(account.address);
+        await helper.collatorSelection.obtainLicense(account);
+        expect(await helper.collatorSelection.hasLicense(account.address)).to.be.equal(licenseBond);
+
+        // force-releasing a license un-reserves the license bond cost as well
+        await helper.getSudo().collatorSelection.forceReleaseLicense(superuser, account.address);
+        expect(await helper.collatorSelection.hasLicense(account.address)).to.be.equal(previousBalance.reserved);
+
+        const balance = await helper.balance.getSubstrateFull(account.address);
+        expect(balance.reserved).to.be.equal(previousBalance.reserved);
+        expect(balance.free > previousBalance.free - licenseBond);
+      });
+    });
+
+    describe('Negative', () => {
+      itSub('Cannot get a license without session keys set', async ({helper}) => {
+        const [account] = await helper.arrange.createAccounts([100n], superuser);
+        await expect(helper.collatorSelection.obtainLicense(account))
+          .to.be.rejectedWith(/collatorSelection.ValidatorNotRegistered/);
+      });
+
+      itSub('Cannot register a license twice', async ({helper}) => {
+        const account = crowd.pop()!;
+        await helper.collatorSelection.obtainLicense(account);
+        await expect(helper.collatorSelection.obtainLicense(account))
+          .to.be.rejectedWith(/collatorSelection.AlreadyHoldingLicense/);
+      });
+
+      itSub('Cannot release a license twice', async ({helper}) => {
+        const account = crowd.pop()!;
+        await helper.collatorSelection.obtainLicense(account);
+        await helper.collatorSelection.releaseLicense(account);
+        await expect(helper.collatorSelection.releaseLicense(account))
+          .to.be.rejectedWith(/collatorSelection.NoLicense/);
+      });
+
+      itSub('Cannot force revoke a license as non-sudo', async ({helper}) => {
+        const account = crowd.pop()!;
+        await helper.collatorSelection.obtainLicense(account);
+        await expect(helper.collatorSelection.forceReleaseLicense(superuser, account.address))
+          .to.be.rejectedWith(/BadOrigin/);
+      });
+    });
+  });
+
+  describe('Onboarding, collating, and offboarding as collator candidates', () => {
+    // These two are the default invulnerables, and should return to be invulnerables after this suite.
+    let charlie: IKeyringPair;
+    let dave: IKeyringPair;
+    let crowd: IKeyringPair[];
+
+    before(async function() {
+      await usingPlaygrounds(async (helper, privateKey) => {
+        charlie = await privateKey('//Charlie');
+        dave = await privateKey('//Dave');
+        crowd = await helper.arrange.createCrowd(20, 100n, superuser);
+
+        // set session keys for everyone
+        expect((await helper.session.setOwnKeysFromAddress(charlie))
+          .status.toLowerCase()).to.be.equal('success');
+        expect((await helper.session.setOwnKeysFromAddress(dave))
+          .status.toLowerCase()).to.be.equal('success');
+        await Promise.all(crowd.map(acc => helper.session.setOwnKeysFromAddress(acc)));
+      });
+    });
+
+    describe('Positive', () => {
+      itSub('Can onboard and offboard repeatedly', async ({helper}) => {
+        const account = crowd.pop()!;
+        await helper.collatorSelection.obtainLicense(account);
+        await helper.collatorSelection.onboard(account);
+        expect(await helper.collatorSelection.getCandidates()).to.be.deep.equal([account.address]);
+
+        await helper.collatorSelection.offboard(account);
+        expect(await helper.collatorSelection.getCandidates()).to.be.deep.equal([]);
+
+        await helper.collatorSelection.onboard(account);
+        expect(await helper.collatorSelection.getCandidates()).to.be.deep.equal([account.address]);
+
+        await helper.collatorSelection.offboard(account);
+        expect(await helper.collatorSelection.getCandidates()).to.be.deep.equal([]);
+      });
+
+      itSub('Penalizes and forfeits license from faulty collators', async ({helper}) => {
+        // This one shouldn't even be able to produce blocks.
+        const account = crowd.pop()!;
+        await helper.collatorSelection.obtainLicense(account);
+        await helper.collatorSelection.onboard(account);
+        expect(await helper.collatorSelection.getCandidates()).to.contain(account.address);
+
+        // Wait for 3 new sessions before checking that the collator will be kicked:
+        // one to get collator onboarded, and another two for the collator to fail
+        await helper.wait.newSessions(3);
+
+        expect(await helper.collatorSelection.getCandidates()).to.not.contain(account.address);
+        expect(await helper.collatorSelection.hasLicense(account.address)).to.be.equal(0n);
+
+        // The account's reserved funds get slashed as a penalty
+        const balance = await helper.balance.getSubstrateFull(account.address);
+        expect(balance.reserved).to.be.equal(0n);
+        expect(balance.free < 100n - licenseBond);
+      });
+    });
+
+    describe('Negative', () => {
+      itSub('Cannot onboard without a license', async ({helper}) => {
+        const account = crowd.pop()!;
+        await expect(helper.collatorSelection.onboard(account))
+          .to.be.rejectedWith(/collatorSelection.NoLicense/);
+      });
+
+      itSub('Cannot offboard without a license', async ({helper}) => {
+        const account = crowd.pop()!;
+        await expect(helper.collatorSelection.offboard(account))
+          .to.be.rejectedWith(/collatorSelection.NotCandidate/);
+      });
+
+      itSub('Cannot offboard while not onboarded', async ({helper}) => {
+        const account = crowd.pop()!;
+        await helper.collatorSelection.obtainLicense(account);
+        await expect(helper.collatorSelection.offboard(account))
+          .to.be.rejectedWith(/collatorSelection.NotCandidate/);
+      });
+
+      itSub('Cannot onboard while already onboarded', async ({helper}) => {
+        const account = crowd.pop()!;
+        await helper.collatorSelection.obtainLicense(account);
+        await helper.collatorSelection.onboard(account);
+        await expect(helper.collatorSelection.onboard(account))
+          .to.be.rejectedWith(/collatorSelection.AlreadyCandidate/);
+      });
+    });
+  });
+
+  describe('Addition and removal of invulnerables', () => {
+    before(async function() {
+      await resetInvulnerables();
+    });
+
+    describe('Positive', () => {
+      itSub('Adds an invulnerable', async ({helper}) => {
+        const [account] = await helper.arrange.createAccounts([10n], superuser);
+        const invulnerables = await helper.collatorSelection.getInvulnerables();
+
+        await helper.session.setOwnKeysFromAddress(account);
+        await helper.getSudo().collatorSelection.addInvulnerable(superuser, account.address);
+
+        const newInvulnerables = await helper.collatorSelection.getInvulnerables();
+        expect(invulnerables.concat(account.address)).to.have.all.members(newInvulnerables);
+      });
+
+      itSub('Removes an invulnerable', async ({helper}) => {
+        const invulnerables = await helper.collatorSelection.getInvulnerables();
+        const lastInvulnerable = invulnerables.pop()!;
+
+        await helper.getSudo().collatorSelection.removeInvulnerable(superuser, lastInvulnerable);
+        const newInvulnerables = await helper.collatorSelection.getInvulnerables();
+        // invulnerables had its last element removed, so they should be equal
+        expect(newInvulnerables).to.have.all.members(invulnerables);
+      });
+    });
+
+    describe('Negative', () => {
+      itSub('Does not duplicate an invulnerable', async ({helper}) => {
+        const invulnerables = await helper.collatorSelection.getInvulnerables();
+        // adding an already invulnerable should not fail, but should not duplicate it either
+        await expect(helper.getSudo().collatorSelection.addInvulnerable(superuser, invulnerables[0]))
+          .to.be.fulfilled;
+        const newInvulnerables = await helper.collatorSelection.getInvulnerables();
+        expect(newInvulnerables).to.have.all.members(invulnerables);
+      });
+
+      itSub('Cannot remove a non-existent invulnerable', async ({helper}) => {
+        const [account] = await helper.arrange.createAccounts([0n], superuser);
+        await expect(helper.getSudo().collatorSelection.removeInvulnerable(superuser, account.address))
+          .to.be.rejectedWith(/collatorSelection.NotInvulnerable/);
+      });
+
+      itSub('Cannot allow invulnerables to be empty', async ({helper}) => {
+        const invulnerables = await helper.collatorSelection.getInvulnerables();
+        const lastInvulnerable = invulnerables.pop()!;
+
+        let nonce = await helper.chain.getNonce(superuser.address);
+        await Promise.all(invulnerables.map((i: any) =>
+          helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [i], true, {nonce: nonce++})));
+
+        await expect(helper.getSudo().collatorSelection.removeInvulnerable(superuser, lastInvulnerable))
+          .to.be.rejectedWith(/collatorSelection.TooFewInvulnerables/);
+
+        const newInvulnerables = await helper.collatorSelection.getInvulnerables();
+        expect(newInvulnerables).to.be.deep.equal([lastInvulnerable]);
+
+        // restore the invulnerables to the previous state
+        nonce = await helper.chain.getNonce(superuser.address);
+        await Promise.all(invulnerables.map((i: any) =>
+          helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [i], true, {nonce: nonce++})));
+      });
+
+      itSub('Cannot have too many invulnerables', async ({helper}) => {
+        // todo:collator make sure that there is enough session time for a set of tests
+        // 28 non-functioning collators, teehee.
+
+        const invulnerablesLength = (await helper.collatorSelection.getInvulnerables()).length;
+        const invulnerablesUntilLimit = helper.collatorSelection.maxCollators() - invulnerablesLength;
+        const newInvulnerables = await helper.arrange.createAccounts(Array(invulnerablesUntilLimit).fill(10n), superuser);
+        const [lastInvulnerable] = await helper.arrange.createAccounts([10n], superuser);
+
+        await Promise.all(newInvulnerables.map((i: IKeyringPair) =>
+          helper.session.setOwnKeysFromAddress(i)));
+        await helper.session.setOwnKeysFromAddress(lastInvulnerable);
+
+        let nonce = await helper.chain.getNonce(superuser.address);
+        await Promise.all(newInvulnerables.map((i: IKeyringPair) =>
+          helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [i.address], true, {nonce: nonce++})));
+
+        await expect(helper.getSudo().collatorSelection.addInvulnerable(superuser, lastInvulnerable.address))
+          .to.be.rejectedWith(/collatorSelection.TooManyInvulnerables/);
+
+        // restore the invulnerables to the previous state
+        nonce = await helper.chain.getNonce(superuser.address);
+        await Promise.all(newInvulnerables.map((i: IKeyringPair) =>
+          helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [i.address], true, {nonce: nonce++})));
+      });
+
+      itSub('Forbids a non-sudo to add an invulnerable', async ({helper}) => {
+        const [account] = await helper.arrange.createAccounts([10n], superuser);
+        const invulnerables = await helper.collatorSelection.getInvulnerables();
+
+        await helper.session.setOwnKeysFromAddress(account);
+        await expect(helper.collatorSelection.addInvulnerable(superuser, account.address))
+          .to.be.rejectedWith(/BadOrigin/);
+
+        const newInvulnerables = await helper.collatorSelection.getInvulnerables();
+        expect(newInvulnerables).to.be.members(invulnerables);
+      });
+
+      itSub('Forbids a non-sudo to remove an invulnerable', async ({helper}) => {
+        const invulnerables = await helper.collatorSelection.getInvulnerables();
+        await expect(helper.collatorSelection.removeInvulnerable(superuser, invulnerables[0]))
+          .to.be.rejectedWith(/BadOrigin/);
+        expect(await helper.collatorSelection.getInvulnerables()).to.have.all.members(invulnerables);
+      });
+    });
+  });
+
+  after(async function() {
+    if (!process.env.RUN_COLLATOR_TESTS) return;
+
+    await usingPlaygrounds(async (helper) => {
+      if (helper.fetchMissingPalletNames([Pallets.CollatorSelection]).length != 0) return;
+
+      await helper.getSudo().collatorSelection.setLicenseBond(superuser, previousLicenseBond);
+
+      const candidates = await helper.collatorSelection.getCandidates();
+      let nonce = await helper.chain.getNonce(superuser.address);
+      await Promise.all(candidates.map(candidate =>
+        helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.forceReleaseLicense', [candidate], true, {nonce: nonce++})));
+    });
+  });
+});
\ No newline at end of file
addedtests/src/collator-selection/identity.seqtest.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/collator-selection/identity.seqtest.ts
@@ -0,0 +1,105 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+import {IKeyringPair} from '@polkadot/types/types';
+import {usingPlaygrounds, expect, itSub, Pallets, requirePalletsOrSkip} from '../util';
+import {UniqueHelper} from '../util/playgrounds/unique';
+
+async function getIdentities(helper: UniqueHelper) {
+  const identities: [string, any][] = [];
+  for(const [key, value] of await helper.getApi().query.identity.identityOf.entries())
+    identities.push([(key as any).toHuman(), (value as any).unwrap()]);
+  return identities;
+}
+
+async function getIdentityAccounts(helper: UniqueHelper) {
+  return (await getIdentities(helper)).flatMap(([key, _value]) => key);
+}
+
+describe('Integration Test: Identities Manipulation', () => {
+  let superuser: IKeyringPair;
+
+  before(async function() {
+    if (!process.env.RUN_COLLATOR_TESTS) this.skip();
+
+    await usingPlaygrounds(async (helper, privateKey) => {
+      requirePalletsOrSkip(this, helper, [Pallets.Identity]);
+      superuser = await privateKey('//Alice');
+    });
+  });
+
+  itSub('Normal calls do not work', async ({helper}) => {
+    // console.error = () => {};
+    await expect(helper.executeExtrinsic(superuser, 'api.tx.identity.setIdentity', [{info: {display: {Raw: 'Meowser'}}}]))
+      .to.be.rejectedWith(/Transaction call is not expected/);
+  });
+
+  itSub('Sets identities', async ({helper}) => {
+    const oldIdentitiesCount = (await getIdentityAccounts(helper)).length;
+
+    const crowdSize = 10;
+    const crowd = await helper.arrange.createCrowd(crowdSize, 0n, superuser);
+    const identities = crowd.map((acc, i) => [acc.address, {info: {display: {Raw: `accounter #${i}`}}}]);
+    await helper.getSudo().executeExtrinsic(superuser, 'api.tx.identity.forceInsertIdentities', [identities]);
+
+    expect((await getIdentityAccounts(helper)).length).to.be.equal(oldIdentitiesCount + crowdSize);
+  });
+
+  itSub('Setting identities does not delete existing but does overwrite', async ({helper}) => {
+    const crowd = await helper.arrange.createCrowd(10, 0n, superuser);
+    const identities = crowd.map((acc, i) => [acc.address, {info: {display: {Raw: `accounter #${i}`}}}]);
+
+    // insert a single identity
+    let singleIdentity = identities.pop()!;
+    await helper.getSudo().executeExtrinsic(superuser, 'api.tx.identity.forceInsertIdentities', [[singleIdentity]]);
+
+    const oldIdentitiesCount = (await getIdentityAccounts(helper)).length;
+
+    // change an identity and push it with a few new others
+    singleIdentity = [singleIdentity[0], {info: {display: {Raw: 'something special'}}}];
+    identities.push(singleIdentity);
+    await helper.getSudo().executeExtrinsic(superuser, 'api.tx.identity.forceInsertIdentities', [identities]);
+
+    // oldIdentitiesCount + 9 because one identity is overwritten, not inserted on top
+    expect((await getIdentityAccounts(helper)).length).to.be.equal(oldIdentitiesCount + 9);
+    expect((await helper.callRpc('api.query.identity.identityOf', [singleIdentity[0]])).toHuman().info.display)
+      .to.be.deep.equal({Raw: 'something special'});
+  });
+
+  itSub('Removes identities', async ({helper}) => {
+    const crowd = await helper.arrange.createCrowd(10, 0n, superuser);
+    const identities = crowd.map((acc, i) => [acc.address, {info: {display: {Raw: `accounter #${i}`}}}]);
+    await helper.getSudo().executeExtrinsic(superuser, 'api.tx.identity.forceInsertIdentities', [identities]);
+    const oldIdentities = await getIdentityAccounts(helper);
+
+    // delete a couple, check that they are no longer there
+    const scapegoats = [crowd.pop()!.address, crowd.pop()!.address];
+    await helper.getSudo().executeExtrinsic(superuser, 'api.tx.identity.forceRemoveIdentities', [scapegoats]);
+    const newIdentities = await getIdentityAccounts(helper);
+    expect(newIdentities.concat(scapegoats)).to.be.have.members(oldIdentities);
+  });
+
+  after(async function() {
+    if (!process.env.RUN_COLLATOR_TESTS) return;
+
+    await usingPlaygrounds(async helper => {
+      if (helper.fetchMissingPalletNames([Pallets.Identity]).length != 0) return;
+
+      const identitiesToRemove: string[] = await getIdentityAccounts(helper);
+      await helper.getSudo().executeExtrinsic(superuser, 'api.tx.identity.forceRemoveIdentities', [identitiesToRemove]);
+    });
+  });
+});
deletedtests/src/collatorSelection.seqtest.tsdiffbeforeafterboth
--- a/tests/src/collatorSelection.seqtest.ts
+++ /dev/null
@@ -1,465 +0,0 @@
-// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
-// This file is part of Unique Network.
-
-// Unique Network is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-
-// Unique Network is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-
-// You should have received a copy of the GNU General Public License
-// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
-
-import {IKeyringPair} from '@polkadot/types/types';
-import {usingPlaygrounds, expect, itSub, Pallets, requirePalletsOrSkip} from './util';
-
-async function resetInvulnerables() {
-  await usingPlaygrounds(async (helper, privateKey) => {
-    const superuser = await privateKey('//Alice');
-    const alice = await privateKey('//Alice');
-    const bob = await privateKey('//Bob');
-    const invulnerables = await helper.collatorSelection.getInvulnerables();
-    if (!invulnerables.includes(alice.address) || !invulnerables.includes(bob.address) || invulnerables.length != 2) {
-      console.warn('Alice and Bob are not the invulnerables! Reinstating them back. '
-        + 'Current invulnerables\' size: ' + invulnerables.length);
-
-      let nonce = await helper.chain.getNonce(alice.address);
-      // In case there are too many invulnerables already, remove some of them, leaving space for Alice and Bob.
-      if (invulnerables.length + 2 >= helper.collatorSelection.maxCollators()) {
-        await Promise.all([
-          helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [invulnerables.pop()], true, {nonce: nonce++}),
-          helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [invulnerables.pop()], true, {nonce: nonce++}),
-        ]);
-      }
-
-      nonce = await helper.chain.getNonce(alice.address);
-      await Promise.all([
-        helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [alice.address], true, {nonce: nonce++}),
-        helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [bob.address], true, {nonce: nonce++}),
-      ]);
-
-      nonce = await helper.chain.getNonce(alice.address);
-      await Promise.all(invulnerables.map((invulnerable: any) => {
-        if (invulnerable == alice.address || invulnerable == bob.address) return new Promise<void>(res => res());
-        return helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [invulnerable], true, {nonce: nonce++});
-      }));
-    }
-  });
-}
-
-// todo:collator Most preferable to launch this test in parallel somehow -- or change the session period (1 hr).
-describe('Integration Test: Collator Selection', () => {
-  let superuser: IKeyringPair;
-  let previousLicenseBond = 0n;
-  let licenseBond = 0n;
-
-  before(async function() {
-    await usingPlaygrounds(async (helper, privateKey) => {
-      requirePalletsOrSkip(this, helper, [Pallets.CollatorSelection]);
-      superuser = await privateKey('//Alice');
-
-      previousLicenseBond = await helper.collatorSelection.getLicenseBond();
-      licenseBond = 10n * helper.balance.getOneTokenNominal();
-      await helper.getSudo().collatorSelection.setLicenseBond(superuser, licenseBond);
-    });
-  });
-
-  describe('Dynamic shuffling of collators', () => {
-    // These two are the default invulnerables, and should return to be invulnerables after this suite.
-    let alice: IKeyringPair;
-    let bob: IKeyringPair;
-
-    let charlie: IKeyringPair;
-    let dave: IKeyringPair;
-
-    before(async function() {
-      await usingPlaygrounds(async (helper, privateKey) => {
-        // todo:collator see again if blocks start to be finalized in dev mode
-        // Skip the collator block production in dev mode, since the blocks are sealed automatically.
-        if (await helper.arrange.isDevNode()) this.skip();
-
-        alice = await privateKey('//Alice');
-        bob = await privateKey('//Bob');
-        charlie = await privateKey('//Charlie');
-        dave = await privateKey('//Dave');
-
-        expect((await helper.session.setOwnKeysFromAddress(charlie))
-          .status.toLowerCase()).to.be.equal('success');
-        expect((await helper.session.setOwnKeysFromAddress(dave))
-          .status.toLowerCase()).to.be.equal('success');
-
-        const invulnerables = await helper.collatorSelection.getInvulnerables();
-        if (!invulnerables.includes(alice.address) || !invulnerables.includes(bob.address) || invulnerables.length != 2) {
-          console.warn('Alice and Bob are not the invulnerables! Reinstating them back. '
-            + 'Current invulnerables\' size: ' + invulnerables.length);
-
-          let nonce = await helper.chain.getNonce(superuser.address);
-          await Promise.all([
-            helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [alice.address], true, {nonce: nonce++}),
-            helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [bob.address], true, {nonce: nonce++}),
-          ]);
-
-          nonce = await helper.chain.getNonce(superuser.address);
-          await Promise.all(invulnerables.map((invulnerable: any) => {
-            if (invulnerable == alice.address || invulnerable == bob.address) return new Promise((res) => res);
-            return helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [invulnerable], true, {nonce: nonce++});
-          }));
-        }
-      });
-    });
-
-    itSub('Change invulnerables and make sure they start producing blocks', async ({helper}) => {
-      let nonce = await helper.chain.getNonce(superuser.address);
-      await expect(Promise.all([
-        helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [charlie.address], true, {nonce: nonce++}),
-        helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [dave.address], true, {nonce: nonce++}),
-      ])).to.be.fulfilled;
-
-      nonce = await helper.chain.getNonce(superuser.address);
-      await expect(Promise.all([
-        helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [alice.address], true, {nonce: nonce++}),
-        helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [bob.address], true, {nonce: nonce++}),
-      ])).to.be.fulfilled;
-
-      const newInvulnerables = await helper.collatorSelection.getInvulnerables();
-      expect(newInvulnerables).to.contain(charlie.address).and.contain(dave.address).and.be.length(2);
-
-      await helper.wait.newSessions(2);
-
-      const newValidators = await helper.callRpc('api.query.session.validators');
-      expect(newValidators).to.contain(charlie.address).and.contain(dave.address).and.be.length(2);
-
-      const lastBlockNumber = await helper.chain.getLatestBlockNumber();
-      await helper.wait.newBlocks(1);
-      const lastCharlieBlock = (await helper.callRpc('api.query.collatorSelection.lastAuthoredBlock', [charlie.address])).toNumber();
-      const lastDaveBlock = (await helper.callRpc('api.query.collatorSelection.lastAuthoredBlock', [dave.address])).toNumber();
-      expect(lastCharlieBlock >= lastBlockNumber || lastDaveBlock >= lastBlockNumber).to.be.true;
-    });
-
-    after(async () => {
-      await usingPlaygrounds(async (helper) => {
-        if (await helper.arrange.isDevNode()) return;
-
-        let nonce = await helper.chain.getNonce(superuser.address);
-        await Promise.all([
-          helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [alice.address], true, {nonce: nonce++}),
-          helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [bob.address], true, {nonce: nonce++}),
-        ]);
-
-        nonce = await helper.chain.getNonce(superuser.address);
-        await Promise.all([
-          await helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [charlie.address], true, {nonce: nonce++}),
-          await helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [dave.address], true, {nonce: nonce++}),
-        ]);
-      });
-    });
-  });
-
-  describe('Getting and releasing licenses to collate', () => {
-    let charlie: IKeyringPair;
-    let dave: IKeyringPair;
-    let crowd: IKeyringPair[];
-
-    before(async function() {
-      await usingPlaygrounds(async (helper, privateKey) => {
-        charlie = await privateKey('//Charlie');
-        dave = await privateKey('//Dave');
-        crowd = await helper.arrange.createCrowd(20, 100n, superuser);
-
-        // set session keys for everyone
-        expect((await helper.session.setOwnKeysFromAddress(charlie))
-          .status.toLowerCase()).to.be.equal('success');
-        expect((await helper.session.setOwnKeysFromAddress(dave))
-          .status.toLowerCase()).to.be.equal('success');
-        await Promise.all(crowd.map(acc => helper.session.setOwnKeysFromAddress(acc)));
-      });
-    });
-
-    describe('Positive', () => {
-      itSub('Can lease and release a license', async ({helper}) => {
-        const account = crowd.pop()!;
-
-        // make sure it does not have any reserved funds
-        expect((await helper.balance.getSubstrateFull(account.address)).reserved).to.be.equal(0n);
-
-        // getting a license reserves a license bond cost
-        await helper.collatorSelection.obtainLicense(account);
-        expect(await helper.collatorSelection.hasLicense(account.address)).to.be.equal(licenseBond);
-        expect((await helper.balance.getSubstrateFull(account.address)).reserved).to.be.equal(licenseBond);
-
-        // releasing a license un-reserves the license bond cost
-        await helper.collatorSelection.releaseLicense(account);
-        expect(await helper.collatorSelection.hasLicense(account.address)).to.be.equal(0n);
-
-        const balance = await helper.balance.getSubstrateFull(account.address);
-        expect(balance.reserved).to.be.equal(0n);
-        expect(balance.free > 100n - licenseBond);
-      });
-
-      itSub('Can force revoke a license', async ({helper}) => {
-        const account = crowd.pop()!;
-
-        // getting a license reserves a license bond cost
-        const previousBalance = await helper.balance.getSubstrateFull(account.address);
-        await helper.collatorSelection.obtainLicense(account);
-        expect(await helper.collatorSelection.hasLicense(account.address)).to.be.equal(licenseBond);
-
-        // force-releasing a license un-reserves the license bond cost as well
-        await helper.getSudo().collatorSelection.forceReleaseLicense(superuser, account.address);
-        expect(await helper.collatorSelection.hasLicense(account.address)).to.be.equal(previousBalance.reserved);
-
-        const balance = await helper.balance.getSubstrateFull(account.address);
-        expect(balance.reserved).to.be.equal(previousBalance.reserved);
-        expect(balance.free > previousBalance.free - licenseBond);
-      });
-    });
-
-    describe('Negative', () => {
-      itSub('Cannot get a license without session keys set', async ({helper}) => {
-        const [account] = await helper.arrange.createAccounts([100n], superuser);
-        await expect(helper.collatorSelection.obtainLicense(account))
-          .to.be.rejectedWith(/collatorSelection.ValidatorNotRegistered/);
-      });
-
-      itSub('Cannot register a license twice', async ({helper}) => {
-        const account = crowd.pop()!;
-        await helper.collatorSelection.obtainLicense(account);
-        await expect(helper.collatorSelection.obtainLicense(account))
-          .to.be.rejectedWith(/collatorSelection.AlreadyHoldingLicense/);
-      });
-
-      itSub('Cannot release a license twice', async ({helper}) => {
-        const account = crowd.pop()!;
-        await helper.collatorSelection.obtainLicense(account);
-        await helper.collatorSelection.releaseLicense(account);
-        await expect(helper.collatorSelection.releaseLicense(account))
-          .to.be.rejectedWith(/collatorSelection.NoLicense/);
-      });
-
-      itSub('Cannot force revoke a license as non-sudo', async ({helper}) => {
-        const account = crowd.pop()!;
-        await helper.collatorSelection.obtainLicense(account);
-        await expect(helper.collatorSelection.forceReleaseLicense(superuser, account.address))
-          .to.be.rejectedWith(/BadOrigin/);
-      });
-    });
-  });
-
-  describe('Onboarding, collating, and offboarding as collator candidates', () => {
-    // These two are the default invulnerables, and should return to be invulnerables after this suite.
-    let charlie: IKeyringPair;
-    let dave: IKeyringPair;
-    let crowd: IKeyringPair[];
-
-    before(async function() {
-      await usingPlaygrounds(async (helper, privateKey) => {
-        charlie = await privateKey('//Charlie');
-        dave = await privateKey('//Dave');
-        crowd = await helper.arrange.createCrowd(20, 100n, superuser);
-
-        // set session keys for everyone
-        expect((await helper.session.setOwnKeysFromAddress(charlie))
-          .status.toLowerCase()).to.be.equal('success');
-        expect((await helper.session.setOwnKeysFromAddress(dave))
-          .status.toLowerCase()).to.be.equal('success');
-        await Promise.all(crowd.map(acc => helper.session.setOwnKeysFromAddress(acc)));
-      });
-    });
-
-    describe('Positive', () => {
-      itSub('Can onboard and offboard repeatedly', async ({helper}) => {
-        const account = crowd.pop()!;
-        await helper.collatorSelection.obtainLicense(account);
-        await helper.collatorSelection.onboard(account);
-        expect(await helper.collatorSelection.getCandidates()).to.be.deep.equal([account.address]);
-
-        await helper.collatorSelection.offboard(account);
-        expect(await helper.collatorSelection.getCandidates()).to.be.deep.equal([]);
-
-        await helper.collatorSelection.onboard(account);
-        expect(await helper.collatorSelection.getCandidates()).to.be.deep.equal([account.address]);
-
-        await helper.collatorSelection.offboard(account);
-        expect(await helper.collatorSelection.getCandidates()).to.be.deep.equal([]);
-      });
-
-      itSub('Penalizes and forfeits license from faulty collators', async ({helper}) => {
-        // This one shouldn't even be able to produce blocks.
-        const account = crowd.pop()!;
-        await helper.collatorSelection.obtainLicense(account);
-        await helper.collatorSelection.onboard(account);
-        expect(await helper.collatorSelection.getCandidates()).to.contain(account.address);
-
-        // Wait for 3 new sessions before checking that the collator will be kicked:
-        // one to get collator onboarded, and another two for the collator to fail
-        await helper.wait.newSessions(3);
-
-        expect(await helper.collatorSelection.getCandidates()).to.not.contain(account.address);
-        expect(await helper.collatorSelection.hasLicense(account.address)).to.be.equal(0n);
-
-        // The account's reserved funds get slashed as a penalty
-        const balance = await helper.balance.getSubstrateFull(account.address);
-        expect(balance.reserved).to.be.equal(0n);
-        expect(balance.free < 100n - licenseBond);
-      });
-    });
-
-    describe('Negative', () => {
-      itSub('Cannot onboard without a license', async ({helper}) => {
-        const account = crowd.pop()!;
-        await expect(helper.collatorSelection.onboard(account))
-          .to.be.rejectedWith(/collatorSelection.NoLicense/);
-      });
-
-      itSub('Cannot offboard without a license', async ({helper}) => {
-        const account = crowd.pop()!;
-        await expect(helper.collatorSelection.offboard(account))
-          .to.be.rejectedWith(/collatorSelection.NotCandidate/);
-      });
-
-      itSub('Cannot offboard while not onboarded', async ({helper}) => {
-        const account = crowd.pop()!;
-        await helper.collatorSelection.obtainLicense(account);
-        await expect(helper.collatorSelection.offboard(account))
-          .to.be.rejectedWith(/collatorSelection.NotCandidate/);
-      });
-
-      itSub('Cannot onboard while already onboarded', async ({helper}) => {
-        const account = crowd.pop()!;
-        await helper.collatorSelection.obtainLicense(account);
-        await helper.collatorSelection.onboard(account);
-        await expect(helper.collatorSelection.onboard(account))
-          .to.be.rejectedWith(/collatorSelection.AlreadyCandidate/);
-      });
-    });
-  });
-
-  describe('Addition and removal of invulnerables', () => {
-    before(async function() {
-      await resetInvulnerables();
-    });
-
-    describe('Positive', () => {
-      itSub('Adds an invulnerable', async ({helper}) => {
-        const [account] = await helper.arrange.createAccounts([10n], superuser);
-        const invulnerables = await helper.collatorSelection.getInvulnerables();
-
-        await helper.session.setOwnKeysFromAddress(account);
-        await helper.getSudo().collatorSelection.addInvulnerable(superuser, account.address);
-
-        const newInvulnerables = await helper.collatorSelection.getInvulnerables();
-        expect(invulnerables.concat(account.address)).to.have.all.members(newInvulnerables);
-      });
-
-      itSub('Removes an invulnerable', async ({helper}) => {
-        const invulnerables = await helper.collatorSelection.getInvulnerables();
-        const lastInvulnerable = invulnerables.pop()!;
-
-        await helper.getSudo().collatorSelection.removeInvulnerable(superuser, lastInvulnerable);
-        const newInvulnerables = await helper.collatorSelection.getInvulnerables();
-        // invulnerables had its last element removed, so they should be equal
-        expect(newInvulnerables).to.have.all.members(invulnerables);
-      });
-    });
-
-    describe('Negative', () => {
-      itSub('Does not duplicate an invulnerable', async ({helper}) => {
-        const invulnerables = await helper.collatorSelection.getInvulnerables();
-        // adding an already invulnerable should not fail, but should not duplicate it either
-        await expect(helper.getSudo().collatorSelection.addInvulnerable(superuser, invulnerables[0]))
-          .to.be.fulfilled;
-        const newInvulnerables = await helper.collatorSelection.getInvulnerables();
-        expect(newInvulnerables).to.have.all.members(invulnerables);
-      });
-
-      itSub('Cannot remove a non-existent invulnerable', async ({helper}) => {
-        const [account] = await helper.arrange.createAccounts([0n], superuser);
-        await expect(helper.getSudo().collatorSelection.removeInvulnerable(superuser, account.address))
-          .to.be.rejectedWith(/collatorSelection.NotInvulnerable/);
-      });
-
-      itSub('Cannot allow invulnerables to be empty', async ({helper}) => {
-        const invulnerables = await helper.collatorSelection.getInvulnerables();
-        const lastInvulnerable = invulnerables.pop()!;
-
-        let nonce = await helper.chain.getNonce(superuser.address);
-        await Promise.all(invulnerables.map((i: any) =>
-          helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [i], true, {nonce: nonce++})));
-
-        await expect(helper.getSudo().collatorSelection.removeInvulnerable(superuser, lastInvulnerable))
-          .to.be.rejectedWith(/collatorSelection.TooFewInvulnerables/);
-
-        const newInvulnerables = await helper.collatorSelection.getInvulnerables();
-        expect(newInvulnerables).to.be.deep.equal([lastInvulnerable]);
-
-        // restore the invulnerables to the previous state
-        nonce = await helper.chain.getNonce(superuser.address);
-        await Promise.all(invulnerables.map((i: any) =>
-          helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [i], true, {nonce: nonce++})));
-      });
-
-      itSub('Cannot have too many invulnerables', async ({helper}) => {
-        // todo:collator make sure that there is enough session time for a set of tests
-        // 28 non-functioning collators, teehee.
-
-        const invulnerablesLength = (await helper.collatorSelection.getInvulnerables()).length;
-        const invulnerablesUntilLimit = helper.collatorSelection.maxCollators() - invulnerablesLength;
-        const newInvulnerables = await helper.arrange.createAccounts(Array(invulnerablesUntilLimit).fill(10n), superuser);
-        const [lastInvulnerable] = await helper.arrange.createAccounts([10n], superuser);
-
-        await Promise.all(newInvulnerables.map((i: IKeyringPair) =>
-          helper.session.setOwnKeysFromAddress(i)));
-        await helper.session.setOwnKeysFromAddress(lastInvulnerable);
-
-        let nonce = await helper.chain.getNonce(superuser.address);
-        await Promise.all(newInvulnerables.map((i: IKeyringPair) =>
-          helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [i.address], true, {nonce: nonce++})));
-
-        await expect(helper.getSudo().collatorSelection.addInvulnerable(superuser, lastInvulnerable.address))
-          .to.be.rejectedWith(/collatorSelection.TooManyInvulnerables/);
-
-        // restore the invulnerables to the previous state
-        nonce = await helper.chain.getNonce(superuser.address);
-        await Promise.all(newInvulnerables.map((i: IKeyringPair) =>
-          helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [i.address], true, {nonce: nonce++})));
-      });
-
-      itSub('Forbids a non-sudo to add an invulnerable', async ({helper}) => {
-        const [account] = await helper.arrange.createAccounts([10n], superuser);
-        const invulnerables = await helper.collatorSelection.getInvulnerables();
-
-        await helper.session.setOwnKeysFromAddress(account);
-        await expect(helper.collatorSelection.addInvulnerable(superuser, account.address))
-          .to.be.rejectedWith(/BadOrigin/);
-
-        const newInvulnerables = await helper.collatorSelection.getInvulnerables();
-        expect(newInvulnerables).to.be.members(invulnerables);
-      });
-
-      itSub('Forbids a non-sudo to remove an invulnerable', async ({helper}) => {
-        const invulnerables = await helper.collatorSelection.getInvulnerables();
-        await expect(helper.collatorSelection.removeInvulnerable(superuser, invulnerables[0]))
-          .to.be.rejectedWith(/BadOrigin/);
-        expect(await helper.collatorSelection.getInvulnerables()).to.have.all.members(invulnerables);
-      });
-    });
-  });
-
-  after(async () => {
-    // eslint-disable-next-line require-await
-    await usingPlaygrounds(async (helper) => {
-      if (helper.fetchMissingPalletNames([Pallets.CollatorSelection]).length != 0) return;
-
-      await helper.getSudo().collatorSelection.setLicenseBond(superuser, previousLicenseBond);
-
-      const candidates = await helper.collatorSelection.getCandidates();
-      let nonce = await helper.chain.getNonce(superuser.address);
-      await Promise.all(candidates.map(candidate =>
-        helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.forceReleaseLicense', [candidate], true, {nonce: nonce++})));
-    });
-  });
-});
\ No newline at end of file
deletedtests/src/identity.seqtest.tsdiffbeforeafterboth
--- a/tests/src/identity.seqtest.ts
+++ /dev/null
@@ -1,101 +0,0 @@
-// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
-// This file is part of Unique Network.
-
-// Unique Network is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-
-// Unique Network is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-
-// You should have received a copy of the GNU General Public License
-// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
-
-import {IKeyringPair} from '@polkadot/types/types';
-import {usingPlaygrounds, expect, itSub, Pallets, requirePalletsOrSkip} from './util';
-import {UniqueHelper} from './util/playgrounds/unique';
-
-async function getIdentities(helper: UniqueHelper) {
-  const identities: [string, any][] = [];
-  for(const [key, value] of await helper.getApi().query.identity.identityOf.entries())
-    identities.push([(key as any).toHuman(), (value as any).unwrap()]);
-  return identities;
-}
-
-async function getIdentityAccounts(helper: UniqueHelper) {
-  return (await getIdentities(helper)).flatMap(([key, _value]) => key);
-}
-
-describe('Integration Test: Identities Manipulation', () => {
-  let superuser: IKeyringPair;
-
-  before(async function() {
-    await usingPlaygrounds(async (helper, privateKey) => {
-      requirePalletsOrSkip(this, helper, [Pallets.Identity]);
-      superuser = await privateKey('//Alice');
-    });
-  });
-
-  itSub('Normal calls do not work', async ({helper}) => {
-    // console.error = () => {};
-    await expect(helper.executeExtrinsic(superuser, 'api.tx.identity.setIdentity', [{info: {display: {Raw: 'Meowser'}}}]))
-      .to.be.rejectedWith(/Transaction call is not expected/);
-  });
-
-  itSub('Sets identities', async ({helper}) => {
-    const oldIdentitiesCount = (await getIdentityAccounts(helper)).length;
-
-    const crowdSize = 10;
-    const crowd = await helper.arrange.createCrowd(crowdSize, 0n, superuser);
-    const identities = crowd.map((acc, i) => [acc.address, {info: {display: {Raw: `accounter #${i}`}}}]);
-    await helper.getSudo().executeExtrinsic(superuser, 'api.tx.identity.forceInsertIdentities', [identities]);
-
-    expect((await getIdentityAccounts(helper)).length).to.be.equal(oldIdentitiesCount + crowdSize);
-  });
-
-  itSub('Setting identities does not delete existing but does overwrite', async ({helper}) => {
-    const crowd = await helper.arrange.createCrowd(10, 0n, superuser);
-    const identities = crowd.map((acc, i) => [acc.address, {info: {display: {Raw: `accounter #${i}`}}}]);
-
-    // insert a single identity
-    let singleIdentity = identities.pop()!;
-    await helper.getSudo().executeExtrinsic(superuser, 'api.tx.identity.forceInsertIdentities', [[singleIdentity]]);
-
-    const oldIdentitiesCount = (await getIdentityAccounts(helper)).length;
-
-    // change an identity and push it with a few new others
-    singleIdentity = [singleIdentity[0], {info: {display: {Raw: 'something special'}}}];
-    identities.push(singleIdentity);
-    await helper.getSudo().executeExtrinsic(superuser, 'api.tx.identity.forceInsertIdentities', [identities]);
-
-    // oldIdentitiesCount + 9 because one identity is overwritten, not inserted on top
-    expect((await getIdentityAccounts(helper)).length).to.be.equal(oldIdentitiesCount + 9);
-    expect((await helper.callRpc('api.query.identity.identityOf', [singleIdentity[0]])).toHuman().info.display)
-      .to.be.deep.equal({Raw: 'something special'});
-  });
-
-  itSub('Removes identities', async ({helper}) => {
-    const crowd = await helper.arrange.createCrowd(10, 0n, superuser);
-    const identities = crowd.map((acc, i) => [acc.address, {info: {display: {Raw: `accounter #${i}`}}}]);
-    await helper.getSudo().executeExtrinsic(superuser, 'api.tx.identity.forceInsertIdentities', [identities]);
-    const oldIdentities = await getIdentityAccounts(helper);
-
-    // delete a couple, check that they are no longer there
-    const scapegoats = [crowd.pop()!.address, crowd.pop()!.address];
-    await helper.getSudo().executeExtrinsic(superuser, 'api.tx.identity.forceRemoveIdentities', [scapegoats]);
-    const newIdentities = await getIdentityAccounts(helper);
-    expect(newIdentities.concat(scapegoats)).to.be.have.members(oldIdentities);
-  });
-
-  after(async function() {
-    await usingPlaygrounds(async helper => {
-      if (helper.fetchMissingPalletNames([Pallets.Identity]).length != 0) return;
-
-      const identitiesToRemove: string[] = await getIdentityAccounts(helper);
-      await helper.getSudo().executeExtrinsic(superuser, 'api.tx.identity.forceRemoveIdentities', [identitiesToRemove]);
-    });
-  });
-});