difftreelog
Merge pull request #988 from UniqueNetwork/fix/collector-selection-tests-release-60
in: master
9 files changed
.baedeker/collator-selection.jsonnetdiffbeforeafterboth--- a/.baedeker/collator-selection.jsonnet
+++ b/.baedeker/collator-selection.jsonnet
@@ -31,7 +31,7 @@
bin: $.bin,
wantedKeys: 'para',
},
- for name in ['alice', 'bob']
+ for name in ['alpha', 'beta', 'gamma', 'delta']
},
};
.docker/docker-compose.gov.j2diffbeforeafterboth--- a/.docker/docker-compose.gov.j2
+++ b/.docker/docker-compose.gov.j2
@@ -21,4 +21,4 @@
options:
max-size: "1m"
max-file: "3"
- command: cargo run --release --features={{ NETWORK }}-runtime,{{ WASM_NAME }}-runtime/gov-test-timings -- --dev -linfo --rpc-cors=all --unsafe-rpc-external
+ command: cargo run --release --features={{ NETWORK }}-runtime,gov-test-timings -- --dev -linfo --rpc-cors=all --unsafe-rpc-external
.github/workflows/collator-selection.ymldiffbeforeafterboth--- a/.github/workflows/collator-selection.yml
+++ b/.github/workflows/collator-selection.yml
@@ -139,7 +139,7 @@
context: .
dockerfile: .docker/Dockerfile-unique
args: |
- --build-arg RUNTIME_FEATURES=${{ matrix.runtime_features }}
+ --build-arg RUNTIME_FEATURES=${{ matrix.runtime_features }},session-test-timings
--build-arg RUST_TOOLCHAIN=${{ env.RUST_TOOLCHAIN }}
dockerhub_username: ${{ secrets.CORE_DOCKERHUB_USERNAME }}
dockerhub_token: ${{ secrets.CORE_DOCKERHUB_TOKEN }}
@@ -161,7 +161,7 @@
jpath: |
.baedeker/vendor
tla-str: |
- relay_spec=westend-local
+ relay_spec=westend-local
inputs: |
.baedeker/collator-selection.jsonnet
snippet:(import 'baedeker-library/ops/rewrites.libsonnet').rewriteNodePaths({'bin/polkadot':{dockerImage:'${{ steps.polkadot.outputs.name }}'}})
node/cli/Cargo.tomldiffbeforeafterboth--- a/node/cli/Cargo.toml
+++ b/node/cli/Cargo.toml
@@ -108,3 +108,5 @@
runtime-benchmarks = ['opal-runtime/runtime-benchmarks', 'polkadot-cli/runtime-benchmarks', 'polkadot-service/runtime-benchmarks', 'quartz-runtime?/runtime-benchmarks', 'sc-service/runtime-benchmarks', 'unique-runtime?/runtime-benchmarks']
sapphire-runtime = ['quartz-runtime', 'quartz-runtime/become-sapphire']
try-runtime = ['opal-runtime?/try-runtime', 'quartz-runtime?/try-runtime', 'try-runtime-cli/try-runtime', 'unique-runtime?/try-runtime']
+session-test-timings = ['opal-runtime/session-test-timings', 'quartz-runtime?/session-test-timings', 'unique-runtime?/session-test-timings']
+gov-test-timings = ['opal-runtime/gov-test-timings', 'quartz-runtime?/gov-test-timings', 'unique-runtime?/gov-test-timings']
runtime/common/config/pallets/mod.rsdiffbeforeafterboth--- a/runtime/common/config/pallets/mod.rs
+++ b/runtime/common/config/pallets/mod.rs
@@ -125,10 +125,20 @@
pub AppPromotionDailyRate: Perbill = Perbill::from_rational(5u32, 10_000);
pub const MaxCollators: u32 = MAX_COLLATORS;
pub const LicenseBond: Balance = GENESIS_LICENSE_BOND;
+
+ pub const DayRelayBlocks: BlockNumber = RELAY_DAYS;
+}
+
+#[cfg(not(feature = "session-test-timings"))]
+parameter_types! {
pub const SessionPeriod: BlockNumber = SESSION_LENGTH;
- pub const DayRelayBlocks: BlockNumber = RELAY_DAYS;
}
+#[cfg(feature = "session-test-timings")]
+parameter_types! {
+ pub const SessionPeriod: BlockNumber = 5 * MINUTES;
+}
+
impl pallet_configuration::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type Balance = Balance;
runtime/opal/Cargo.tomldiffbeforeafterboth--- a/runtime/opal/Cargo.toml
+++ b/runtime/opal/Cargo.toml
@@ -229,6 +229,7 @@
preimage = []
refungible = []
unique-scheduler = []
+session-test-timings = []
################################################################################
# local dependencies
runtime/quartz/Cargo.tomldiffbeforeafterboth--- a/runtime/quartz/Cargo.toml
+++ b/runtime/quartz/Cargo.toml
@@ -218,6 +218,7 @@
refungible = []
unique-scheduler = []
gov-test-timings = []
+session-test-timings = []
################################################################################
# local dependencies
runtime/unique/Cargo.tomldiffbeforeafterboth--- a/runtime/unique/Cargo.toml
+++ b/runtime/unique/Cargo.toml
@@ -221,6 +221,7 @@
refungible = []
unique-scheduler = []
gov-test-timings = []
+session-test-timings = []
################################################################################
# local dependencies
tests/src/collator-selection/collatorSelection.seqtest.tsdiffbeforeafterboth1// 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);2930 let nonce = await helper.chain.getNonce(alice.address);31 // In case there are too many invulnerables already, remove some of them, leaving space for Alice and Bob.32 if(invulnerables.length + 2 >= helper.collatorSelection.maxCollators()) {33 await Promise.all([34 helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [invulnerables.pop()!], true, {nonce: nonce++}),35 helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [invulnerables.pop()!], true, {nonce: nonce++}),36 ]);37 }3839 nonce = await helper.chain.getNonce(alice.address);40 await Promise.all([41 helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [alice.address], true, {nonce: nonce++}),42 helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [bob.address], true, {nonce: nonce++}),43 ]);4445 nonce = await helper.chain.getNonce(alice.address);46 await Promise.all(invulnerables.map((invulnerable: any) => {47 if(invulnerable == alice.address || invulnerable == bob.address) return new Promise<void>(res => res());48 return helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [invulnerable], true, {nonce: nonce++});49 }));50 }51 });52}5354// todo:collator Most preferable to launch this test in parallel somehow -- or change the session period (1 hr).55describe('Integration Test: Collator Selection', () => {56 let superuser: IKeyringPair;57 let previousLicenseBond = 0n;58 let licenseBond = 0n;5960 before(async function() {61 if(!process.env.RUN_COLLATOR_TESTS) this.skip();6263 await usingPlaygrounds(async (helper, privateKey) => {64 requirePalletsOrSkip(this, helper, [Pallets.CollatorSelection]);65 superuser = await privateKey('//Alice');6667 previousLicenseBond = await helper.collatorSelection.getLicenseBond();68 licenseBond = 10n * helper.balance.getOneTokenNominal();69 await helper.getSudo().collatorSelection.setLicenseBond(superuser, licenseBond);70 });71 });7273 describe('Dynamic shuffling of collators', () => {74 // These two are the default invulnerables, and should return to be invulnerables after this suite.75 let alice: IKeyringPair;76 let bob: IKeyringPair;7778 let charlie: IKeyringPair;79 let dave: IKeyringPair;8081 before(async function() {82 await usingPlaygrounds(async (helper, privateKey) => {83 // todo:collator see again if blocks start to be finalized in dev mode84 // Skip the collator block production in dev mode, since the blocks are sealed automatically.85 if(await helper.arrange.isDevNode()) this.skip();8687 alice = await privateKey('//Alice');88 bob = await privateKey('//Bob');89 charlie = await privateKey('//Charlie');90 dave = await privateKey('//Dave');9192 expect((await helper.session.setOwnKeysFromAddress(charlie))93 .status.toLowerCase()).to.be.equal('success');94 expect((await helper.session.setOwnKeysFromAddress(dave))95 .status.toLowerCase()).to.be.equal('success');9697 const invulnerables = await helper.collatorSelection.getInvulnerables();98 if(!invulnerables.includes(alice.address) || !invulnerables.includes(bob.address) || invulnerables.length != 2) {99 console.warn('Alice and Bob are not the invulnerables! Reinstating them back. '100 + 'Current invulnerables\' size: ' + invulnerables.length);101102 let nonce = await helper.chain.getNonce(superuser.address);103 await Promise.all([104 helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [alice.address], true, {nonce: nonce++}),105 helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [bob.address], true, {nonce: nonce++}),106 ]);107108 nonce = await helper.chain.getNonce(superuser.address);109 await Promise.all(invulnerables.map((invulnerable: any) => {110 if(invulnerable == alice.address || invulnerable == bob.address) return new Promise((res) => res);111 return helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [invulnerable], true, {nonce: nonce++});112 }));113 }114 });115 });116117 itSub('Change invulnerables and make sure they start producing blocks', async ({helper}) => {118 let nonce = await helper.chain.getNonce(superuser.address);119 await expect(Promise.all([120 helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [charlie.address], true, {nonce: nonce++}),121 helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [dave.address], true, {nonce: nonce++}),122 ])).to.be.fulfilled;123124 nonce = await helper.chain.getNonce(superuser.address);125 await expect(Promise.all([126 helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [alice.address], true, {nonce: nonce++}),127 helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [bob.address], true, {nonce: nonce++}),128 ])).to.be.fulfilled;129130 const newInvulnerables = await helper.collatorSelection.getInvulnerables();131 expect(newInvulnerables).to.contain(charlie.address).and.contain(dave.address).and.be.length(2);132133 await helper.wait.newSessions(2);134135 const newValidators = await helper.callRpc('api.query.session.validators');136 expect(newValidators).to.contain(charlie.address).and.contain(dave.address).and.be.length(2);137138 const lastBlockNumber = await helper.chain.getLatestBlockNumber();139 await helper.wait.newBlocks(1);140 const lastCharlieBlock = (await helper.callRpc('api.query.collatorSelection.lastAuthoredBlock', [charlie.address])).toNumber();141 const lastDaveBlock = (await helper.callRpc('api.query.collatorSelection.lastAuthoredBlock', [dave.address])).toNumber();142 expect(lastCharlieBlock >= lastBlockNumber || lastDaveBlock >= lastBlockNumber).to.be.true;143 });144145 after(async () => {146 await usingPlaygrounds(async (helper) => {147 if(await helper.arrange.isDevNode()) return;148149 let nonce = await helper.chain.getNonce(superuser.address);150 await Promise.all([151 helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [alice.address], true, {nonce: nonce++}),152 helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [bob.address], true, {nonce: nonce++}),153 ]);154155 nonce = await helper.chain.getNonce(superuser.address);156 await Promise.all([157 await helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [charlie.address], true, {nonce: nonce++}),158 await helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [dave.address], true, {nonce: nonce++}),159 ]);160 });161 });162 });163164 describe('Getting and releasing licenses to collate', () => {165 let charlie: IKeyringPair;166 let dave: IKeyringPair;167 let crowd: IKeyringPair[];168169 before(async function() {170 await usingPlaygrounds(async (helper, privateKey) => {171 charlie = await privateKey('//Charlie');172 dave = await privateKey('//Dave');173 crowd = await helper.arrange.createCrowd(20, 100n, superuser);174175 // set session keys for everyone176 expect((await helper.session.setOwnKeysFromAddress(charlie))177 .status.toLowerCase()).to.be.equal('success');178 expect((await helper.session.setOwnKeysFromAddress(dave))179 .status.toLowerCase()).to.be.equal('success');180 await Promise.all(crowd.map(acc => helper.session.setOwnKeysFromAddress(acc)));181 });182 });183184 describe('Positive', () => {185 itSub('Can lease and release a license', async ({helper}) => {186 const account = crowd.pop()!;187188 // make sure it does not have any reserved funds189 expect((await helper.balance.getSubstrateFull(account.address)).reserved).to.be.equal(0n);190191 // getting a license reserves a license bond cost192 await helper.collatorSelection.obtainLicense(account);193 expect(await helper.collatorSelection.hasLicense(account.address)).to.be.equal(licenseBond);194 expect((await helper.balance.getSubstrateFull(account.address)).reserved).to.be.equal(licenseBond);195196 // releasing a license un-reserves the license bond cost197 await helper.collatorSelection.releaseLicense(account);198 expect(await helper.collatorSelection.hasLicense(account.address)).to.be.equal(0n);199200 const balance = await helper.balance.getSubstrateFull(account.address);201 expect(balance.reserved).to.be.equal(0n);202 expect(balance.free > 100n - licenseBond);203 });204205 itSub('Can force revoke a license', async ({helper}) => {206 const account = crowd.pop()!;207208 // getting a license reserves a license bond cost209 const previousBalance = await helper.balance.getSubstrateFull(account.address);210 await helper.collatorSelection.obtainLicense(account);211 expect(await helper.collatorSelection.hasLicense(account.address)).to.be.equal(licenseBond);212213 // force-releasing a license un-reserves the license bond cost as well214 await helper.getSudo().collatorSelection.forceReleaseLicense(superuser, account.address);215 expect(await helper.collatorSelection.hasLicense(account.address)).to.be.equal(previousBalance.reserved);216217 const balance = await helper.balance.getSubstrateFull(account.address);218 expect(balance.reserved).to.be.equal(previousBalance.reserved);219 expect(balance.free > previousBalance.free - licenseBond);220 });221 });222223 describe('Negative', () => {224 itSub('Cannot get a license without session keys set', async ({helper}) => {225 const [account] = await helper.arrange.createAccounts([100n], superuser);226 await expect(helper.collatorSelection.obtainLicense(account))227 .to.be.rejectedWith(/collatorSelection.ValidatorNotRegistered/);228 });229230 itSub('Cannot register a license twice', async ({helper}) => {231 const account = crowd.pop()!;232 await helper.collatorSelection.obtainLicense(account);233 await expect(helper.collatorSelection.obtainLicense(account))234 .to.be.rejectedWith(/collatorSelection.AlreadyHoldingLicense/);235 });236237 itSub('Cannot release a license twice', async ({helper}) => {238 const account = crowd.pop()!;239 await helper.collatorSelection.obtainLicense(account);240 await helper.collatorSelection.releaseLicense(account);241 await expect(helper.collatorSelection.releaseLicense(account))242 .to.be.rejectedWith(/collatorSelection.NoLicense/);243 });244245 itSub('Cannot force revoke a license as non-sudo', async ({helper}) => {246 const account = crowd.pop()!;247 await helper.collatorSelection.obtainLicense(account);248 await expect(helper.collatorSelection.forceReleaseLicense(superuser, account.address))249 .to.be.rejectedWith(/BadOrigin/);250 });251 });252 });253254 describe('Onboarding, collating, and offboarding as collator candidates', () => {255 // These two are the default invulnerables, and should return to be invulnerables after this suite.256 let charlie: IKeyringPair;257 let dave: IKeyringPair;258 let crowd: IKeyringPair[];259260 before(async function() {261 await usingPlaygrounds(async (helper, privateKey) => {262 charlie = await privateKey('//Charlie');263 dave = await privateKey('//Dave');264 crowd = await helper.arrange.createCrowd(20, 100n, superuser);265266 // set session keys for everyone267 expect((await helper.session.setOwnKeysFromAddress(charlie))268 .status.toLowerCase()).to.be.equal('success');269 expect((await helper.session.setOwnKeysFromAddress(dave))270 .status.toLowerCase()).to.be.equal('success');271 await Promise.all(crowd.map(acc => helper.session.setOwnKeysFromAddress(acc)));272 });273 });274275 describe('Positive', () => {276 itSub('Can onboard and offboard repeatedly', async ({helper}) => {277 const account = crowd.pop()!;278 await helper.collatorSelection.obtainLicense(account);279 await helper.collatorSelection.onboard(account);280 expect(await helper.collatorSelection.getCandidates()).to.be.deep.equal([account.address]);281282 await helper.collatorSelection.offboard(account);283 expect(await helper.collatorSelection.getCandidates()).to.be.deep.equal([]);284285 await helper.collatorSelection.onboard(account);286 expect(await helper.collatorSelection.getCandidates()).to.be.deep.equal([account.address]);287288 await helper.collatorSelection.offboard(account);289 expect(await helper.collatorSelection.getCandidates()).to.be.deep.equal([]);290 });291292 itSub('Penalizes and forfeits license from faulty collators', async ({helper}) => {293 // This one shouldn't even be able to produce blocks.294 const account = crowd.pop()!;295 await helper.collatorSelection.obtainLicense(account);296 await helper.collatorSelection.onboard(account);297 expect(await helper.collatorSelection.getCandidates()).to.contain(account.address);298299 // Wait for 3 new sessions before checking that the collator will be kicked:300 // one to get collator onboarded, and another two for the collator to fail301 await helper.wait.newSessions(3);302303 expect(await helper.collatorSelection.getCandidates()).to.not.contain(account.address);304 expect(await helper.collatorSelection.hasLicense(account.address)).to.be.equal(0n);305306 // The account's reserved funds get slashed as a penalty307 const balance = await helper.balance.getSubstrateFull(account.address);308 expect(balance.reserved).to.be.equal(0n);309 expect(balance.free < 100n - licenseBond);310 });311 });312313 describe('Negative', () => {314 itSub('Cannot onboard without a license', async ({helper}) => {315 const account = crowd.pop()!;316 await expect(helper.collatorSelection.onboard(account))317 .to.be.rejectedWith(/collatorSelection.NoLicense/);318 });319320 itSub('Cannot offboard without a license', async ({helper}) => {321 const account = crowd.pop()!;322 await expect(helper.collatorSelection.offboard(account))323 .to.be.rejectedWith(/collatorSelection.NotCandidate/);324 });325326 itSub('Cannot offboard while not onboarded', async ({helper}) => {327 const account = crowd.pop()!;328 await helper.collatorSelection.obtainLicense(account);329 await expect(helper.collatorSelection.offboard(account))330 .to.be.rejectedWith(/collatorSelection.NotCandidate/);331 });332333 itSub('Cannot onboard while already onboarded', async ({helper}) => {334 const account = crowd.pop()!;335 await helper.collatorSelection.obtainLicense(account);336 await helper.collatorSelection.onboard(account);337 await expect(helper.collatorSelection.onboard(account))338 .to.be.rejectedWith(/collatorSelection.AlreadyCandidate/);339 });340 });341 });342343 describe('Addition and removal of invulnerables', () => {344 before(async function() {345 await resetInvulnerables();346 });347348 describe('Positive', () => {349 itSub('Adds an invulnerable', async ({helper}) => {350 const [account] = await helper.arrange.createAccounts([10n], superuser);351 const invulnerables = await helper.collatorSelection.getInvulnerables();352353 await helper.session.setOwnKeysFromAddress(account);354 await helper.getSudo().collatorSelection.addInvulnerable(superuser, account.address);355356 const newInvulnerables = await helper.collatorSelection.getInvulnerables();357 expect(invulnerables.concat(account.address)).to.have.all.members(newInvulnerables);358 });359360 itSub('Removes an invulnerable', async ({helper}) => {361 const invulnerables = await helper.collatorSelection.getInvulnerables();362 const lastInvulnerable = invulnerables.pop()!;363364 await helper.getSudo().collatorSelection.removeInvulnerable(superuser, lastInvulnerable);365 const newInvulnerables = await helper.collatorSelection.getInvulnerables();366 // invulnerables had its last element removed, so they should be equal367 expect(newInvulnerables).to.have.all.members(invulnerables);368 });369 });370371 describe('Negative', () => {372 itSub('Does not duplicate an invulnerable', async ({helper}) => {373 const invulnerables = await helper.collatorSelection.getInvulnerables();374 // adding an already invulnerable should not fail, but should not duplicate it either375 await expect(helper.getSudo().collatorSelection.addInvulnerable(superuser, invulnerables[0]))376 .to.be.fulfilled;377 const newInvulnerables = await helper.collatorSelection.getInvulnerables();378 expect(newInvulnerables).to.have.all.members(invulnerables);379 });380381 itSub('Cannot remove a non-existent invulnerable', async ({helper}) => {382 const [account] = await helper.arrange.createAccounts([0n], superuser);383 await expect(helper.getSudo().collatorSelection.removeInvulnerable(superuser, account.address))384 .to.be.rejectedWith(/collatorSelection.NotInvulnerable/);385 });386387 itSub('Cannot allow invulnerables to be empty', async ({helper}) => {388 const invulnerables = await helper.collatorSelection.getInvulnerables();389 const lastInvulnerable = invulnerables.pop()!;390391 let nonce = await helper.chain.getNonce(superuser.address);392 await Promise.all(invulnerables.map((i: any) =>393 helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [i], true, {nonce: nonce++})));394395 await expect(helper.getSudo().collatorSelection.removeInvulnerable(superuser, lastInvulnerable))396 .to.be.rejectedWith(/collatorSelection.TooFewInvulnerables/);397398 const newInvulnerables = await helper.collatorSelection.getInvulnerables();399 expect(newInvulnerables).to.be.deep.equal([lastInvulnerable]);400401 // restore the invulnerables to the previous state402 nonce = await helper.chain.getNonce(superuser.address);403 await Promise.all(invulnerables.map((i: any) =>404 helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [i], true, {nonce: nonce++})));405 });406407 itSub('Cannot have too many invulnerables', async ({helper}) => {408 // todo:collator make sure that there is enough session time for a set of tests409 // 28 non-functioning collators, teehee.410411 const invulnerablesLength = (await helper.collatorSelection.getInvulnerables()).length;412 const invulnerablesUntilLimit = helper.collatorSelection.maxCollators() - invulnerablesLength;413 const newInvulnerables = await helper.arrange.createAccounts(Array(invulnerablesUntilLimit).fill(10n), superuser);414 const [lastInvulnerable] = await helper.arrange.createAccounts([10n], superuser);415416 await Promise.all(newInvulnerables.map((i: IKeyringPair) =>417 helper.session.setOwnKeysFromAddress(i)));418 await helper.session.setOwnKeysFromAddress(lastInvulnerable);419420 let nonce = await helper.chain.getNonce(superuser.address);421 await Promise.all(newInvulnerables.map((i: IKeyringPair) =>422 helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [i.address], true, {nonce: nonce++})));423424 await expect(helper.getSudo().collatorSelection.addInvulnerable(superuser, lastInvulnerable.address))425 .to.be.rejectedWith(/collatorSelection.TooManyInvulnerables/);426427 // restore the invulnerables to the previous state428 nonce = await helper.chain.getNonce(superuser.address);429 await Promise.all(newInvulnerables.map((i: IKeyringPair) =>430 helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [i.address], true, {nonce: nonce++})));431 });432433 itSub('Forbids a non-sudo to add an invulnerable', async ({helper}) => {434 const [account] = await helper.arrange.createAccounts([10n], superuser);435 const invulnerables = await helper.collatorSelection.getInvulnerables();436437 await helper.session.setOwnKeysFromAddress(account);438 await expect(helper.collatorSelection.addInvulnerable(superuser, account.address))439 .to.be.rejectedWith(/BadOrigin/);440441 const newInvulnerables = await helper.collatorSelection.getInvulnerables();442 expect(newInvulnerables).to.be.members(invulnerables);443 });444445 itSub('Forbids a non-sudo to remove an invulnerable', async ({helper}) => {446 const invulnerables = await helper.collatorSelection.getInvulnerables();447 await expect(helper.collatorSelection.removeInvulnerable(superuser, invulnerables[0]))448 .to.be.rejectedWith(/BadOrigin/);449 expect(await helper.collatorSelection.getInvulnerables()).to.have.all.members(invulnerables);450 });451 });452 });453454 after(async function() {455 if(!process.env.RUN_COLLATOR_TESTS) return;456457 await usingPlaygrounds(async (helper) => {458 if(helper.fetchMissingPalletNames([Pallets.CollatorSelection]).length != 0) return;459460 await helper.getSudo().collatorSelection.setLicenseBond(superuser, previousLicenseBond);461462 const candidates = await helper.collatorSelection.getCandidates();463 let nonce = await helper.chain.getNonce(superuser.address);464 await Promise.all(candidates.map(candidate =>465 helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.forceReleaseLicense', [candidate], true, {nonce: nonce++})));466 });467 });468});