git.delta.rocks / unique-network / refs/commits / 33d3cd09a241

difftreelog

test(collator-selection) intermediate changes and fixes to tests + minor refactor

Fahrrader2022-12-21parent: #1e14fe8.patch.diff
in: master

6 files changed

modifiedpallets/collator-selection/src/lib.rsdiffbeforeafterboth
--- a/pallets/collator-selection/src/lib.rs
+++ b/pallets/collator-selection/src/lib.rs
@@ -555,7 +555,7 @@
 
 						T::Currency::resolve_creating(&T::TreasuryAccountId::get(), imbalance);
 
-						// Self::deposit_event(Event::CandidateSlashed(who.clone()));
+					// Self::deposit_event(Event::CandidateSlashed(who.clone()));
 					} else {
 						//T::Currency::unreserve(who, deposit);
 						deposit_returned = deposit;
modifiedpallets/collator-selection/src/tests.rsdiffbeforeafterboth
--- a/pallets/collator-selection/src/tests.rs
+++ b/pallets/collator-selection/src/tests.rs
@@ -50,28 +50,32 @@
 	});
 }
 
+// todo:collator add more tests later
+
 #[test]
-fn it_should_set_invulnerables() {
+fn it_should_add_invulnerables() {
 	new_test_ext().execute_with(|| {
-		let new_set = vec![1, 2, 3, 4];
-		assert_ok!(CollatorSelection::set_invulnerables(
+		assert_ok!(CollatorSelection::add_invulnerable(
 			RuntimeOrigin::signed(RootAccount::get()),
-			new_set.clone()
+			1
 		));
-		assert_eq!(CollatorSelection::invulnerables(), new_set);
+		assert_ok!(CollatorSelection::add_invulnerable(
+			RuntimeOrigin::signed(RootAccount::get()),
+			2
+		));
+		assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]);
 
 		// cannot set with non-root.
 		assert_noop!(
-			CollatorSelection::set_invulnerables(RuntimeOrigin::signed(1), new_set.clone()),
+			CollatorSelection::add_invulnerable(RuntimeOrigin::signed(1), 3),
 			BadOrigin
 		);
 
 		// cannot set invulnerables without associated validator keys
-		let invulnerables = vec![7];
 		assert_noop!(
-			CollatorSelection::set_invulnerables(
+			CollatorSelection::add_invulnerable(
 				RuntimeOrigin::signed(RootAccount::get()),
-				invulnerables.clone()
+				7
 			),
 			Error::<Test>::ValidatorNotRegistered
 		);
@@ -79,6 +83,41 @@
 }
 
 #[test]
+fn it_should_remove_invulnerables() {
+	new_test_ext().execute_with(|| {
+		assert_ok!(CollatorSelection::add_invulnerable(
+			RuntimeOrigin::signed(RootAccount::get()),
+			1
+		));
+		assert_ok!(CollatorSelection::add_invulnerable(
+			RuntimeOrigin::signed(RootAccount::get()),
+			2
+		));
+
+		// cannot remove with non-root.
+		assert_noop!(
+			CollatorSelection::remove_invulnerable(RuntimeOrigin::signed(1), 3),
+			BadOrigin
+		);
+
+		assert_ok!(CollatorSelection::remove_invulnerable(
+			RuntimeOrigin::signed(RootAccount::get()),
+			2
+		));
+		assert_eq!(CollatorSelection::invulnerables(), vec![1]);
+
+		// cannot remove an invulnerable if there would be 0 invulnerables.
+		assert_noop!(
+			CollatorSelection::add_invulnerable(
+				RuntimeOrigin::signed(RootAccount::get()), 
+				1
+			),
+			Error::<Test>::NotInvulnerable
+		);
+	});
+}
+
+#[test]
 fn set_desired_candidates_works() {
 	new_test_ext().execute_with(|| {
 		// given
@@ -404,6 +443,7 @@
 			deposit: 10,
 		};
 		assert_eq!(CollatorSelection::candidates(), vec![collator]);
+		assert_eq!(CollatorSelection::kick_threshold(), 1);
 		assert_eq!(CollatorSelection::last_authored_block(4), 20);
 		initialize_to_block(30);
 		// 3 gets kicked after 1 session delay
@@ -457,6 +497,7 @@
 	let collator_selection = collator_selection::GenesisConfig::<Test> {
 		desired_candidates: 2,
 		candidacy_bond: 10,
+		kick_threshold: 1,
 		invulnerables,
 	};
 	// collator selection must be initialized before session.
modifiedprimitives/common/src/constants.rsdiffbeforeafterboth
--- a/primitives/common/src/constants.rs
+++ b/primitives/common/src/constants.rs
@@ -47,7 +47,7 @@
 /// Amount of Balance reserved for candidate registration.
 pub const GENESIS_CANDIDACY_BOND: u128 = EXISTENTIAL_DEPOSIT;
 /// How long a periodic session lasts in blocks.
-pub const SESSION_LENGTH: BlockNumber = MINUTES;
+pub const SESSION_LENGTH: BlockNumber = HOURS;
 
 // Targeting 0.1 UNQ per transfer
 pub const WEIGHT_TO_FEE_COEFF: u32 = /*<weight2fee>*/175_199_920/*</weight2fee>*/;
modifiedtests/package.jsondiffbeforeafterboth
--- a/tests/package.json
+++ b/tests/package.json
@@ -85,7 +85,7 @@
     "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",
+    "testCollatorSelection": "mocha --timeout 9999999 -r ts-node/register ./**/collatorSelection.*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/collatorSelection.seqtest.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/collatorSelection.seqtest.ts
@@ -0,0 +1,267 @@
+// 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);
+      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).
+// + 18 tests: 5 (1+4) on session change
+describe('Integration Test: Collator Selection', () => {
+  let superuser: IKeyringPair;
+
+  before(async function() {  
+    await usingPlaygrounds(async (helper, privateKey) => {
+      requirePalletsOrSkip(this, helper, [Pallets.CollatorSelection]);
+      superuser = await privateKey('//Alice');
+    });
+  });
+
+  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) => {
+        alice = await privateKey('//Alice');
+        bob = await privateKey('//Bob');
+        charlie = await privateKey('//Charlie');
+        dave = await privateKey('//Dave');
+
+        expect((await helper.collatorSelection.setOwnKeys(charlie))
+          .status.toLowerCase()).to.be.equal('success');
+        expect((await helper.collatorSelection.setOwnKeys(dave))
+          .status.toLowerCase()).to.be.equal('success');
+  
+        // todo:collator check necessity + add RPC for invulnerables / just improve in general
+        // validators = await helper.callRpc('api.query.session.validators');
+        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);
+  
+      const expectedSessionIndex = (await helper.callRpc('api.query.session.currentIndex')).toNumber() + 2;
+      let currentSessionIndex = -1;
+      console.log('Waiting for the session after the next.' 
+        + ' This might take a while -- check SessionPeriod in pallet_session::Config for session time.');
+  
+      while (currentSessionIndex < expectedSessionIndex) {
+        // eslint-disable-next-line no-async-promise-executor
+        currentSessionIndex = await expect(helper.wait.withTimeout(new Promise(async (resolve) => {
+          await helper.wait.newBlocks(1);
+          const res = (await helper.callRpc('api.query.session.currentIndex')).toNumber();
+          resolve(res);
+        }), 24000, 'The chain has stopped producing blocks!')).to.be.fulfilled;
+      }
+  
+      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;
+    });
+  
+    // todo:collator keyless invulnerables? will hang, so, a breaking test, eh
+    // register candidate without sudos and the like
+  
+    after(async () => {
+      await usingPlaygrounds(async (helper) => {
+        if (helper.fetchMissingPalletNames([Pallets.CollatorSelection]).length != 0) 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++}),
+        ]);
+      });
+    });
+  });
+
+  // todo:collator make sure that there is enough session time for a set of tests
+  // 28 non-functioning collators, teehee.
+
+  describe.skip('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.collatorSelection.setOwnKeys(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 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.rejected;//todo:collator With(/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}) => {
+        const invulnerablesLength = (await helper.collatorSelection.getInvulnerables()).length;
+        const invulnerablesUntilLimit = 30 - 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.collatorSelection.setOwnKeys(i)));
+        await helper.collatorSelection.setOwnKeys(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.rejected; // todo:collator With(/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.collatorSelection.setOwnKeys(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);
+      });
+    });
+    
+    // todo:collator after
+  });
+});
\ No newline at end of file
deletedtests/src/collatorSelection.test.tsdiffbeforeafterboth
before · tests/src/collatorSelection.test.ts
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import {IKeyringPair} from '@polkadot/types/types';18import {usingPlaygrounds, expect, itSub, Pallets, requirePalletsOrSkip} from './util';1920async function resetInvulnerables() {21  await usingPlaygrounds(async (helper, privateKey) => {22    const superuser = await privateKey('//Alice');23    const alice = await privateKey('//Alice');24    const bob = await privateKey('//Bob');25    const invulnerables = await helper.collatorSelection.getInvulnerables();26    if (!invulnerables.includes(alice.address) || !invulnerables.includes(bob.address) || invulnerables.length != 2) {27      console.warn('Alice and Bob are not the invulnerables! Reinstating them back. ' 28        + 'Current invulnerables\' size: ' + invulnerables.length);29      30      let nonce = await helper.chain.getNonce(alice.address);31      await Promise.all([32        helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [alice.address], true, {nonce: nonce++}),33        helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [bob.address], true, {nonce: nonce++}),34      ]);3536      nonce = await helper.chain.getNonce(alice.address);37      await Promise.all(invulnerables.map((invulnerable: any) => {38        if (invulnerable == alice.address || invulnerable == bob.address) return new Promise<void>(res => res());39        return helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [invulnerable], true, {nonce: nonce++});40      }));41    }42  });43}4445// todo:collator Most preferable to launch this test in parallel somehow -- or change the session period (1 hr).46// + 18 tests: 5 (1+4) on session change47describe('Integration Test: Collator Selection', () => {48  let superuser: IKeyringPair;4950  // These are the default invulnerables, and should return to be invulnerables after this suite.51  let alice: IKeyringPair;52  let bob: IKeyringPair;5354  let charlie: IKeyringPair;55  let dave: IKeyringPair;56  //let eve: IKeyringPair;5758  before(async function() {  59    await usingPlaygrounds(async (helper, privateKey) => {60      requirePalletsOrSkip(this, helper, [Pallets.CollatorSelection]);6162      //todo:collator63      //const donor = await privateKey({filename: __filename});64      //[charlie, dave] = await helper.arrange.createAccounts([100n, 100n], donor);65      alice = await privateKey('//Alice');66      bob = await privateKey('//Bob');67      charlie = await privateKey('//Charlie');68      dave = await privateKey('//Dave');6970      superuser = await privateKey('//Alice');71    });72  });7374  describe('Dynamic shuffling of collators', () => {75    before(async function() {  76      await usingPlaygrounds(async (helper) => {77        expect((await helper.collatorSelection.setOwnKeys(charlie))78          .status.toLowerCase()).to.be.equal('success');79        expect((await helper.collatorSelection.setOwnKeys(dave))80          .status.toLowerCase()).to.be.equal('success');81  82        // todo:collator check necessity + add RPC for invulnerables / just improve in general83        // validators = await helper.callRpc('api.query.session.validators');84        const invulnerables = await helper.callRpc('api.query.collatorSelection.invulnerables');85        if (!invulnerables.includes(alice.address) || !invulnerables.includes(bob.address) || invulnerables.length != 2) {86          console.warn('Alice and Bob are not the invulnerables! Reinstating them back. ' 87            + 'Current invulnerables\' size: ' + invulnerables.length);88          89          await Promise.all([90            helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [alice.address], true, {nonce: 0}),91            helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [bob.address], true, {nonce: 1}),92          ]);93  94          let nonce = 0;95          await Promise.all(invulnerables.map((invulnerable: any) => {96            if (invulnerable == alice.address || invulnerable == bob.address) return new Promise((res) => res);97            return helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [invulnerable], true, {nonce: nonce++});98          }));99        }100      });101    });102  103    itSub('Change invulnerables and make sure they start producing blocks', async ({helper}) => {104      await expect(Promise.all([105        helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [charlie.address], true, {nonce: 0}),106        helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [dave.address], true, {nonce: 1}),107      ])).to.be.fulfilled;108  109      await expect(Promise.all([110        helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [alice.address], true, {nonce: 0}),111        helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [bob.address], true, {nonce: 1}),112      ])).to.be.fulfilled;113  114      const newInvulnerables = await helper.callRpc('api.query.collatorSelection.invulnerables');115      expect(newInvulnerables).to.contain(charlie.address).and.contain(dave.address).and.be.length(2);116  117      const expectedSessionIndex = (await helper.callRpc('api.query.session.currentIndex')).toNumber() + 2;118      let currentSessionIndex = -1;119      console.log('Waiting for the session after the next.' 120        + ' This might take a while -- check SessionPeriod in pallet_session::Config for session time.');121  122      while (currentSessionIndex < expectedSessionIndex) {123        // eslint-disable-next-line no-async-promise-executor124        currentSessionIndex = await expect(helper.wait.withTimeout(new Promise(async (resolve) => {125          //todo:collator126          console.log('starting wait...');127          console.time('ein');128          await helper.wait.newBlocks(1);129          console.timeLog('ein');130          const res = (await helper.callRpc('api.query.session.currentIndex')).toNumber();131          console.timeEnd('ein');132          resolve(res);133        }), 24000, 'The chain has stopped producing blocks!')).to.be.fulfilled;134      }135  136      const newValidators = await helper.callRpc('api.query.session.validators');137      expect(newValidators).to.contain(charlie.address).and.contain(dave.address).and.be.length(2);138  139      const lastBlockNumber = await helper.chain.getLatestBlockNumber();140      await helper.wait.newBlocks(1);141      const lastCharlieBlock = (await helper.callRpc('api.query.collatorSelection.lastAuthoredBlock', [charlie.address])).toNumber();142      const lastDaveBlock = (await helper.callRpc('api.query.collatorSelection.lastAuthoredBlock', [dave.address])).toNumber();143      expect(lastCharlieBlock >= lastBlockNumber || lastDaveBlock >= lastBlockNumber).to.be.true;144    });145  146    // todo:collator keyless invulnerables? will hang, so, a breaking test, eh147    // register candidate without sudos and the like148  149    after(async () => {150      await usingPlaygrounds(async (helper) => {151        if (helper.fetchMissingPalletNames([Pallets.CollatorSelection]).length != 0) return;152153        await Promise.all([154          helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [alice.address], true, {nonce: 0}),155          helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [bob.address], true, {nonce: 1}),156        ]);157  158        await Promise.all([159          await helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [charlie.address], true, {nonce: 0}),160          await helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [dave.address], true, {nonce: 1}),161        ]);162      });163    });164  });165166  // todo:collator make sure that there is enough session time for a set of tests167  // 28 non-functioning collators, teehee.168169  describe('Addition and removal of invulnerables', () => {170    before(async function() {171      await resetInvulnerables();172    });173174    describe('Positive', () => {175      itSub('Adds an invulnerable', async ({helper}) => {176        const [account] = await helper.arrange.createAccounts([10n], superuser);177        const invulnerables = await helper.collatorSelection.getInvulnerables();178179        await helper.collatorSelection.setOwnKeys(account);180        await helper.getSudo().collatorSelection.addInvulnerable(superuser, account.address);181        182        const newInvulnerables = await helper.collatorSelection.getInvulnerables();183        expect(invulnerables.concat(account.address)).to.have.all.members(newInvulnerables);184      });185186      itSub('Removes an invulnerable', async ({helper}) => {187        const invulnerables = await helper.collatorSelection.getInvulnerables();188        const lastInvulnerable = invulnerables.pop();189190        await helper.getSudo().collatorSelection.removeInvulnerable(superuser, lastInvulnerable);191        const newInvulnerables = await helper.collatorSelection.getInvulnerables();192        // invulnerables had its last element removed, so they should be equal193        expect(newInvulnerables).to.have.all.members(invulnerables);194      });195    });196197    describe('Negative', () => {198      itSub('Does not duplicate an invulnerable', async ({helper}) => {199        const invulnerables = await helper.collatorSelection.getInvulnerables();200        // adding an already invulnerable should not fail, but should not duplicate it either201        await expect(helper.getSudo().collatorSelection.addInvulnerable(superuser, invulnerables[0]))202          .to.be.fulfilled;203        const newInvulnerables = await helper.collatorSelection.getInvulnerables();204        expect(newInvulnerables).to.have.all.members(invulnerables);205      });206207      itSub('Cannot allow invulnerables to be empty', async ({helper}) => {208        const invulnerables = await helper.collatorSelection.getInvulnerables();209        const lastInvulnerable = invulnerables.pop();210211        let nonce = await helper.chain.getNonce(superuser.address);212        await Promise.all(invulnerables.map((i: any) => 213          helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [i], true, {nonce: nonce++})));214215        await expect(helper.getSudo().collatorSelection.removeInvulnerable(superuser, lastInvulnerable))216          .to.be.rejected;//todo:collator With(/collatorSelection.TooFewInvulnerables/);217218        const newInvulnerables = await helper.collatorSelection.getInvulnerables();219        expect(newInvulnerables).to.be.deep.equal([lastInvulnerable]);220        221        // restore the invulnerables to the previous state222        nonce = await helper.chain.getNonce(superuser.address);223        await Promise.all(invulnerables.map((i: any) => 224          helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [i], true, {nonce: nonce++})));225      });226227      itSub('Cannot have too many invulnerables', async ({helper}) => {228        const invulnerablesLength = (await helper.collatorSelection.getInvulnerables()).length;229        const invulnerablesUntilLimit = 30 - invulnerablesLength;230        const newInvulnerables = await helper.arrange.createAccounts(Array(invulnerablesUntilLimit).fill(10n), superuser);231        const [lastInvulnerable] = await helper.arrange.createAccounts([10n], superuser);232233        await Promise.all(newInvulnerables.map((i: IKeyringPair) => 234          helper.collatorSelection.setOwnKeys(i)));235        await helper.collatorSelection.setOwnKeys(lastInvulnerable);236237        let nonce = await helper.chain.getNonce(superuser.address);238        await Promise.all(newInvulnerables.map((i: IKeyringPair) => 239          helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [i.address], true, {nonce: nonce++})));240241        await expect(helper.getSudo().collatorSelection.addInvulnerable(superuser, lastInvulnerable.address))242          .to.be.rejected; // todo:collator With(/collatorSelection.TooManyInvulnerables/);243        244        // restore the invulnerables to the previous state245        nonce = await helper.chain.getNonce(superuser.address);246        await Promise.all(newInvulnerables.map((i: IKeyringPair) => 247          helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [i.address], true, {nonce: nonce++})));248      });249250      itSub('Forbids a non-sudo to add an invulnerable', async ({helper}) => {251        const [account] = await helper.arrange.createAccounts([10n], bob);252        const invulnerables = await helper.collatorSelection.getInvulnerables();253254        await helper.collatorSelection.setOwnKeys(account);255        await expect(helper.collatorSelection.addInvulnerable(bob, account.address))256          .to.be.rejectedWith(/BadOrigin/);257258        const newInvulnerables = await helper.collatorSelection.getInvulnerables();259        expect(newInvulnerables).to.be.members(invulnerables);260      });261262      itSub('Forbids a non-sudo to remove an invulnerable', async ({helper}) => {263        const invulnerables = await helper.collatorSelection.getInvulnerables();264        await expect(helper.collatorSelection.removeInvulnerable(superuser, invulnerables[0]))265          .to.be.rejectedWith(/BadOrigin/);266        expect(await helper.collatorSelection.getInvulnerables()).to.have.all.members(invulnerables);267      });268    });269    270    // todo:collator after271  });272});