difftreelog
fix use plain node names instead of test account names
in: master
2 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', 'charlie', 'dave']
+ for name in ['alpha', 'beta', 'gamma', 'delta']
},
};
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 nodeAddress(name: string) {21 // eslint-disable-next-line require-await22 return await usingPlaygrounds(async (helper, _) => {23 const envNodeStash = `RELAY_UNIQUE_NODE_${name.toUpperCase()}_STASH`;2425 const nodeStash = process.env[envNodeStash];26 if(nodeStash) {27 return helper.address.normalizeSubstrateToChainFormat(nodeStash);28 } else {29 throw Error(`"${envNodeStash}" env var is not set`);30 }31 });32}3334async function getInitialInvulnerables() {35 return await Promise.all([36 nodeAddress('Alice'),37 nodeAddress('Bob'),38 nodeAddress('Charlie'),39 nodeAddress('Dave'),40 ]);41}4243async function resetInvulnerables() {44 await usingPlaygrounds(async (helper, privateKey) => {45 const superuser = await privateKey('//Alice');46 const initialInvulnerables = await getInitialInvulnerables();4748 const invulnerables = await helper.collatorSelection.getInvulnerables();4950 // Remove all invulnerables but the first one51 const firstInvulnerable = invulnerables[0];5253 let nonce = await helper.chain.getNonce(superuser.address);54 await Promise.all(invulnerables.slice(1).map(invulnerable => helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [invulnerable], true, {nonce: nonce++})));5556 // Add the initial invulnerables57 await Promise.all(initialInvulnerables.map(invulnerable => helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [invulnerable], true, {nonce: nonce++})));5859 // Remove the first invulnerable if it's not an initial one60 if(!initialInvulnerables.includes(firstInvulnerable)) {61 await helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [firstInvulnerable]);62 }63 });64}6566// todo:collator Most preferable to launch this test in parallel somehow -- or change the session period (1 hr).67describe('Integration Test: Collator Selection', () => {68 let superuser: IKeyringPair;69 let previousLicenseBond = 0n;70 let licenseBond = 0n;7172 before(async function() {73 if(!process.env.RUN_COLLATOR_TESTS) this.skip();7475 await usingPlaygrounds(async (helper, privateKey) => {76 requirePalletsOrSkip(this, helper, [Pallets.CollatorSelection]);77 superuser = await privateKey('//Alice');7879 previousLicenseBond = await helper.collatorSelection.getLicenseBond();80 licenseBond = 10n * helper.balance.getOneTokenNominal();81 await helper.getSudo().collatorSelection.setLicenseBond(superuser, licenseBond);82 });83 });8485 describe('Dynamic shuffling of collators', () => {86 // These two are the default invulnerables, and should return to be invulnerables after this suite.87 let aliceNode: string;88 let bobNode: string;8990 let charlieNode: string;91 let daveNode: string;9293 before(async function() {94 await usingPlaygrounds(async (helper, privateKey) => {95 // todo:collator see again if blocks start to be finalized in dev mode96 // Skip the collator block production in dev mode, since the blocks are sealed automatically.97 if(await helper.arrange.isDevNode()) this.skip();9899 [aliceNode, bobNode, charlieNode, daveNode] = await getInitialInvulnerables();100101 const invulnerables = await helper.collatorSelection.getInvulnerables();102 expect(invulnerables.length, 'Invalid initial invulnerables number').to.be.equal(4);103 expect(invulnerables, 'Invalid initial invulnerables').containSubset([aliceNode, bobNode, charlieNode, daveNode]);104 });105 });106107 itSub('Change invulnerables and make sure they start producing blocks', async ({helper}) => {108 let nonce = await helper.chain.getNonce(superuser.address);109110 nonce = await helper.chain.getNonce(superuser.address);111 await expect(Promise.all([112 helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [aliceNode], true, {nonce: nonce++}),113 helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [bobNode], true, {nonce: nonce++}),114 ])).to.be.fulfilled;115116 const newInvulnerables = await helper.collatorSelection.getInvulnerables();117 expect(newInvulnerables).to.contain(charlieNode).and.contain(daveNode).and.be.length(2);118119 await helper.wait.newSessions(2);120121 const newValidators = await helper.callRpc('api.query.session.validators');122 expect(newValidators).to.contain(charlieNode).and.contain(daveNode).and.be.length(2);123124 const lastBlockNumber = await helper.chain.getLatestBlockNumber();125 await helper.wait.newBlocks(1);126 const lastCharlieBlock = (await helper.callRpc('api.query.collatorSelection.lastAuthoredBlock', [charlieNode])).toNumber();127 const lastDaveBlock = (await helper.callRpc('api.query.collatorSelection.lastAuthoredBlock', [daveNode])).toNumber();128 expect(lastCharlieBlock >= lastBlockNumber || lastDaveBlock >= lastBlockNumber).to.be.true;129 });130131 after(async () => {132 await resetInvulnerables();133 });134 });135136 describe('Getting and releasing licenses to collate', () => {137 let crowd: IKeyringPair[];138139 before(async function() {140 await usingPlaygrounds(async (helper, privateKey) => {141 crowd = await helper.arrange.createCrowd(20, 100n, superuser);142143 // set session keys for everyone144 await Promise.all(crowd.map(acc => helper.session.setOwnKeysFromAddress(acc)));145 });146 });147148 describe('Positive', () => {149 itSub('Can lease and release a license', async ({helper}) => {150 const account = crowd.pop()!;151152 // make sure it does not have any reserved funds153 expect((await helper.balance.getSubstrateFull(account.address)).reserved).to.be.equal(0n);154155 // getting a license reserves a license bond cost156 await helper.collatorSelection.obtainLicense(account);157 expect(await helper.collatorSelection.hasLicense(account.address)).to.be.equal(licenseBond);158 expect((await helper.balance.getSubstrateFull(account.address)).reserved).to.be.equal(licenseBond);159160 // releasing a license un-reserves the license bond cost161 await helper.collatorSelection.releaseLicense(account);162 expect(await helper.collatorSelection.hasLicense(account.address)).to.be.equal(0n);163164 const balance = await helper.balance.getSubstrateFull(account.address);165 expect(balance.reserved).to.be.equal(0n);166 expect(balance.free > 100n - licenseBond);167 });168169 itSub('Can force revoke a license', async ({helper}) => {170 const account = crowd.pop()!;171172 // getting a license reserves a license bond cost173 const previousBalance = await helper.balance.getSubstrateFull(account.address);174 await helper.collatorSelection.obtainLicense(account);175 expect(await helper.collatorSelection.hasLicense(account.address)).to.be.equal(licenseBond);176177 // force-releasing a license un-reserves the license bond cost as well178 await helper.getSudo().collatorSelection.forceReleaseLicense(superuser, account.address);179 expect(await helper.collatorSelection.hasLicense(account.address)).to.be.equal(previousBalance.reserved);180181 const balance = await helper.balance.getSubstrateFull(account.address);182 expect(balance.reserved).to.be.equal(previousBalance.reserved);183 expect(balance.free > previousBalance.free - licenseBond);184 });185 });186187 describe('Negative', () => {188 itSub('Cannot get a license without session keys set', async ({helper}) => {189 const [account] = await helper.arrange.createAccounts([100n], superuser);190 await expect(helper.collatorSelection.obtainLicense(account))191 .to.be.rejectedWith(/collatorSelection.ValidatorNotRegistered/);192 });193194 itSub('Cannot register a license twice', async ({helper}) => {195 const account = crowd.pop()!;196 await helper.collatorSelection.obtainLicense(account);197 await expect(helper.collatorSelection.obtainLicense(account))198 .to.be.rejectedWith(/collatorSelection.AlreadyHoldingLicense/);199 });200201 itSub('Cannot release a license twice', async ({helper}) => {202 const account = crowd.pop()!;203 await helper.collatorSelection.obtainLicense(account);204 await helper.collatorSelection.releaseLicense(account);205 await expect(helper.collatorSelection.releaseLicense(account))206 .to.be.rejectedWith(/collatorSelection.NoLicense/);207 });208209 itSub('Cannot force revoke a license as non-sudo', async ({helper}) => {210 const account = crowd.pop()!;211 await helper.collatorSelection.obtainLicense(account);212 await expect(helper.collatorSelection.forceReleaseLicense(superuser, account.address))213 .to.be.rejectedWith(/BadOrigin/);214 });215 });216 });217218 describe('Onboarding, collating, and offboarding as collator candidates', () => {219 let crowd: IKeyringPair[];220221 before(async function() {222 await usingPlaygrounds(async (helper, privateKey) => {223 crowd = await helper.arrange.createCrowd(20, 100n, superuser);224225 // set session keys for everyone226 await Promise.all(crowd.map(acc => helper.session.setOwnKeysFromAddress(acc)));227 });228 });229230 describe('Positive', () => {231 itSub('Can onboard and offboard repeatedly', async ({helper}) => {232 const account = crowd.pop()!;233 await helper.collatorSelection.obtainLicense(account);234 await helper.collatorSelection.onboard(account);235 expect(await helper.collatorSelection.getCandidates()).to.be.deep.equal([account.address]);236237 await helper.collatorSelection.offboard(account);238 expect(await helper.collatorSelection.getCandidates()).to.be.deep.equal([]);239240 await helper.collatorSelection.onboard(account);241 expect(await helper.collatorSelection.getCandidates()).to.be.deep.equal([account.address]);242243 await helper.collatorSelection.offboard(account);244 expect(await helper.collatorSelection.getCandidates()).to.be.deep.equal([]);245 });246247 itSub('Penalizes and forfeits license from faulty collators', async ({helper}) => {248 // This one shouldn't even be able to produce blocks.249 const account = crowd.pop()!;250 await helper.collatorSelection.obtainLicense(account);251 await helper.collatorSelection.onboard(account);252 expect(await helper.collatorSelection.getCandidates()).to.contain(account.address);253254 // Wait for 3 new sessions before checking that the collator will be kicked:255 // one to get collator onboarded, and another two for the collator to fail256 await helper.wait.newSessions(3);257258 expect(await helper.collatorSelection.getCandidates()).to.not.contain(account.address);259 expect(await helper.collatorSelection.hasLicense(account.address)).to.be.equal(0n);260261 // The account's reserved funds get slashed as a penalty262 const balance = await helper.balance.getSubstrateFull(account.address);263 expect(balance.reserved).to.be.equal(0n);264 expect(balance.free < 100n - licenseBond);265 });266 });267268 describe('Negative', () => {269 itSub('Cannot onboard without a license', async ({helper}) => {270 const account = crowd.pop()!;271 await expect(helper.collatorSelection.onboard(account))272 .to.be.rejectedWith(/collatorSelection.NoLicense/);273 });274275 itSub('Cannot offboard without a license', async ({helper}) => {276 const account = crowd.pop()!;277 await expect(helper.collatorSelection.offboard(account))278 .to.be.rejectedWith(/collatorSelection.NotCandidate/);279 });280281 itSub('Cannot offboard while not onboarded', async ({helper}) => {282 const account = crowd.pop()!;283 await helper.collatorSelection.obtainLicense(account);284 await expect(helper.collatorSelection.offboard(account))285 .to.be.rejectedWith(/collatorSelection.NotCandidate/);286 });287288 itSub('Cannot onboard while already onboarded', async ({helper}) => {289 const account = crowd.pop()!;290 await helper.collatorSelection.obtainLicense(account);291 await helper.collatorSelection.onboard(account);292 await expect(helper.collatorSelection.onboard(account))293 .to.be.rejectedWith(/collatorSelection.AlreadyCandidate/);294 });295 });296 });297298 describe('Addition and removal of invulnerables', () => {299 describe('Positive', () => {300 itSub('Adds an invulnerable', async ({helper}) => {301 const [account] = await helper.arrange.createAccounts([10n], superuser);302 const invulnerables = await helper.collatorSelection.getInvulnerables();303304 await helper.session.setOwnKeysFromAddress(account);305 await helper.getSudo().collatorSelection.addInvulnerable(superuser, account.address);306307 const newInvulnerables = await helper.collatorSelection.getInvulnerables();308 expect(invulnerables.concat(account.address)).to.have.all.members(newInvulnerables);309 });310311 itSub('Removes an invulnerable', async ({helper}) => {312 const invulnerables = await helper.collatorSelection.getInvulnerables();313 const lastInvulnerable = invulnerables.pop()!;314315 await helper.getSudo().collatorSelection.removeInvulnerable(superuser, lastInvulnerable);316 const newInvulnerables = await helper.collatorSelection.getInvulnerables();317 // invulnerables had its last element removed, so they should be equal318 expect(newInvulnerables).to.have.all.members(invulnerables);319 });320 });321322 describe('Negative', () => {323 itSub('Does not duplicate an invulnerable', async ({helper}) => {324 const invulnerables = await helper.collatorSelection.getInvulnerables();325 // adding an already invulnerable should not fail, but should not duplicate it either326 await expect(helper.getSudo().collatorSelection.addInvulnerable(superuser, invulnerables[0]))327 .to.be.fulfilled;328 const newInvulnerables = await helper.collatorSelection.getInvulnerables();329 expect(newInvulnerables).to.have.all.members(invulnerables);330 });331332 itSub('Cannot remove a non-existent invulnerable', async ({helper}) => {333 const [account] = await helper.arrange.createAccounts([0n], superuser);334 await expect(helper.getSudo().collatorSelection.removeInvulnerable(superuser, account.address))335 .to.be.rejectedWith(/collatorSelection.NotInvulnerable/);336 });337338 itSub('Cannot allow invulnerables to be empty', async ({helper}) => {339 const invulnerables = await helper.collatorSelection.getInvulnerables();340 const lastInvulnerable = invulnerables.pop()!;341342 let nonce = await helper.chain.getNonce(superuser.address);343 await Promise.all(invulnerables.map((i: any) =>344 helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [i], true, {nonce: nonce++})));345346 await expect(helper.getSudo().collatorSelection.removeInvulnerable(superuser, lastInvulnerable))347 .to.be.rejectedWith(/collatorSelection.TooFewInvulnerables/);348349 const newInvulnerables = await helper.collatorSelection.getInvulnerables();350 expect(newInvulnerables).to.be.deep.equal([lastInvulnerable]);351352 // restore the invulnerables to the previous state353 nonce = await helper.chain.getNonce(superuser.address);354 await Promise.all(invulnerables.map((i: any) =>355 helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [i], true, {nonce: nonce++})));356 });357358 itSub('Cannot have too many invulnerables', async ({helper}) => {359 // todo:collator make sure that there is enough session time for a set of tests360 // 28 non-functioning collators, teehee.361362 const invulnerablesLength = (await helper.collatorSelection.getInvulnerables()).length;363 const invulnerablesUntilLimit = (await helper.collatorSelection.getDesiredCollators()) - invulnerablesLength;364 const newInvulnerables = await helper.arrange.createAccounts(Array(invulnerablesUntilLimit).fill(10n), superuser);365 const [lastInvulnerable] = await helper.arrange.createAccounts([10n], superuser);366367 await Promise.all(newInvulnerables.map((i: IKeyringPair) =>368 helper.session.setOwnKeysFromAddress(i)));369 await helper.session.setOwnKeysFromAddress(lastInvulnerable);370371 let nonce = await helper.chain.getNonce(superuser.address);372 await Promise.all(newInvulnerables.map((i: IKeyringPair) =>373 helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [i.address], true, {nonce: nonce++})));374375 await expect(helper.getSudo().collatorSelection.addInvulnerable(superuser, lastInvulnerable.address))376 .to.be.rejectedWith(/collatorSelection.TooManyInvulnerables/);377378 // restore the invulnerables to the previous state379 nonce = await helper.chain.getNonce(superuser.address);380 await Promise.all(newInvulnerables.map((i: IKeyringPair) =>381 helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [i.address], true, {nonce: nonce++})));382 });383384 itSub('Forbids a non-sudo to add an invulnerable', async ({helper}) => {385 const [account] = await helper.arrange.createAccounts([10n], superuser);386 const invulnerables = await helper.collatorSelection.getInvulnerables();387388 await helper.session.setOwnKeysFromAddress(account);389 await expect(helper.collatorSelection.addInvulnerable(superuser, account.address))390 .to.be.rejectedWith(/BadOrigin/);391392 const newInvulnerables = await helper.collatorSelection.getInvulnerables();393 expect(newInvulnerables).to.be.members(invulnerables);394 });395396 itSub('Forbids a non-sudo to remove an invulnerable', async ({helper}) => {397 const invulnerables = await helper.collatorSelection.getInvulnerables();398 await expect(helper.collatorSelection.removeInvulnerable(superuser, invulnerables[0]))399 .to.be.rejectedWith(/BadOrigin/);400 expect(await helper.collatorSelection.getInvulnerables()).to.have.all.members(invulnerables);401 });402403 after(async function() {404 await resetInvulnerables();405 });406 });407 });408409 after(async function() {410 if(!process.env.RUN_COLLATOR_TESTS) return;411412 await usingPlaygrounds(async (helper) => {413 if(helper.fetchMissingPalletNames([Pallets.CollatorSelection]).length != 0) return;414415 await helper.getSudo().collatorSelection.setLicenseBond(superuser, previousLicenseBond);416417 const candidates = await helper.collatorSelection.getCandidates();418 let nonce = await helper.chain.getNonce(superuser.address);419 await Promise.all(candidates.map(candidate =>420 helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.forceReleaseLicense', [candidate], true, {nonce: nonce++})));421 });422 });423});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 nodeAddress(name: string) {21 // eslint-disable-next-line require-await22 return await usingPlaygrounds(async (helper, _) => {23 const envNodeStash = `RELAY_UNIQUE_NODE_${name.toUpperCase()}_STASH`;2425 const nodeStash = process.env[envNodeStash];26 if(nodeStash) {27 return helper.address.normalizeSubstrateToChainFormat(nodeStash);28 } else {29 throw Error(`"${envNodeStash}" env var is not set`);30 }31 });32}3334async function getInitialInvulnerables() {35 return await Promise.all([36 nodeAddress('alpha'),37 nodeAddress('beta'),38 nodeAddress('gamma'),39 nodeAddress('delta'),40 ]);41}4243async function resetInvulnerables() {44 await usingPlaygrounds(async (helper, privateKey) => {45 const superuser = await privateKey('//Alice');46 const initialInvulnerables = await getInitialInvulnerables();4748 const invulnerables = await helper.collatorSelection.getInvulnerables();4950 // Remove all invulnerables but the first one51 const firstInvulnerable = invulnerables[0];5253 let nonce = await helper.chain.getNonce(superuser.address);54 await Promise.all(invulnerables.slice(1).map(invulnerable => helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [invulnerable], true, {nonce: nonce++})));5556 // Add the initial invulnerables57 await Promise.all(initialInvulnerables.map(invulnerable => helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [invulnerable], true, {nonce: nonce++})));5859 // Remove the first invulnerable if it's not an initial one60 if(!initialInvulnerables.includes(firstInvulnerable)) {61 await helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [firstInvulnerable]);62 }63 });64}6566// todo:collator Most preferable to launch this test in parallel somehow -- or change the session period (1 hr).67describe('Integration Test: Collator Selection', () => {68 let superuser: IKeyringPair;69 let previousLicenseBond = 0n;70 let licenseBond = 0n;7172 before(async function() {73 if(!process.env.RUN_COLLATOR_TESTS) this.skip();7475 await usingPlaygrounds(async (helper, privateKey) => {76 requirePalletsOrSkip(this, helper, [Pallets.CollatorSelection]);77 superuser = await privateKey('//Alice');7879 previousLicenseBond = await helper.collatorSelection.getLicenseBond();80 licenseBond = 10n * helper.balance.getOneTokenNominal();81 await helper.getSudo().collatorSelection.setLicenseBond(superuser, licenseBond);82 });83 });8485 describe('Dynamic shuffling of collators', () => {86 // These two are the default invulnerables, and should return to be invulnerables after this suite.87 let alphaNode: string;88 let betaNode: string;8990 let gammaNode: string;91 let deltaNode: string;9293 before(async function() {94 await usingPlaygrounds(async (helper, privateKey) => {95 // todo:collator see again if blocks start to be finalized in dev mode96 // Skip the collator block production in dev mode, since the blocks are sealed automatically.97 if(await helper.arrange.isDevNode()) this.skip();9899 [alphaNode, betaNode, gammaNode, deltaNode] = await getInitialInvulnerables();100101 const invulnerables = await helper.collatorSelection.getInvulnerables();102 expect(invulnerables.length, 'Invalid initial invulnerables number').to.be.equal(4);103 expect(invulnerables, 'Invalid initial invulnerables').containSubset([alphaNode, betaNode, gammaNode, deltaNode]);104 });105 });106107 itSub('Change invulnerables and make sure they start producing blocks', async ({helper}) => {108 let nonce = await helper.chain.getNonce(superuser.address);109110 nonce = await helper.chain.getNonce(superuser.address);111 await expect(Promise.all([112 helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [alphaNode], true, {nonce: nonce++}),113 helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [betaNode], true, {nonce: nonce++}),114 ])).to.be.fulfilled;115116 const newInvulnerables = await helper.collatorSelection.getInvulnerables();117 expect(newInvulnerables).to.contain(gammaNode).and.contain(deltaNode).and.be.length(2);118119 await helper.wait.newSessions(2);120121 const newValidators = await helper.callRpc('api.query.session.validators');122 expect(newValidators).to.contain(gammaNode).and.contain(deltaNode).and.be.length(2);123124 const lastBlockNumber = await helper.chain.getLatestBlockNumber();125 await helper.wait.newBlocks(1);126 const lastGammaBlock = (await helper.callRpc('api.query.collatorSelection.lastAuthoredBlock', [gammaNode])).toNumber();127 const lastDeltaBlock = (await helper.callRpc('api.query.collatorSelection.lastAuthoredBlock', [deltaNode])).toNumber();128 expect(lastGammaBlock >= lastBlockNumber || lastDeltaBlock >= lastBlockNumber).to.be.true;129 });130131 after(async () => {132 await resetInvulnerables();133 });134 });135136 describe('Getting and releasing licenses to collate', () => {137 let crowd: IKeyringPair[];138139 before(async function() {140 await usingPlaygrounds(async (helper, privateKey) => {141 crowd = await helper.arrange.createCrowd(20, 100n, superuser);142143 // set session keys for everyone144 await Promise.all(crowd.map(acc => helper.session.setOwnKeysFromAddress(acc)));145 });146 });147148 describe('Positive', () => {149 itSub('Can lease and release a license', async ({helper}) => {150 const account = crowd.pop()!;151152 // make sure it does not have any reserved funds153 expect((await helper.balance.getSubstrateFull(account.address)).reserved).to.be.equal(0n);154155 // getting a license reserves a license bond cost156 await helper.collatorSelection.obtainLicense(account);157 expect(await helper.collatorSelection.hasLicense(account.address)).to.be.equal(licenseBond);158 expect((await helper.balance.getSubstrateFull(account.address)).reserved).to.be.equal(licenseBond);159160 // releasing a license un-reserves the license bond cost161 await helper.collatorSelection.releaseLicense(account);162 expect(await helper.collatorSelection.hasLicense(account.address)).to.be.equal(0n);163164 const balance = await helper.balance.getSubstrateFull(account.address);165 expect(balance.reserved).to.be.equal(0n);166 expect(balance.free > 100n - licenseBond);167 });168169 itSub('Can force revoke a license', async ({helper}) => {170 const account = crowd.pop()!;171172 // getting a license reserves a license bond cost173 const previousBalance = await helper.balance.getSubstrateFull(account.address);174 await helper.collatorSelection.obtainLicense(account);175 expect(await helper.collatorSelection.hasLicense(account.address)).to.be.equal(licenseBond);176177 // force-releasing a license un-reserves the license bond cost as well178 await helper.getSudo().collatorSelection.forceReleaseLicense(superuser, account.address);179 expect(await helper.collatorSelection.hasLicense(account.address)).to.be.equal(previousBalance.reserved);180181 const balance = await helper.balance.getSubstrateFull(account.address);182 expect(balance.reserved).to.be.equal(previousBalance.reserved);183 expect(balance.free > previousBalance.free - licenseBond);184 });185 });186187 describe('Negative', () => {188 itSub('Cannot get a license without session keys set', async ({helper}) => {189 const [account] = await helper.arrange.createAccounts([100n], superuser);190 await expect(helper.collatorSelection.obtainLicense(account))191 .to.be.rejectedWith(/collatorSelection.ValidatorNotRegistered/);192 });193194 itSub('Cannot register a license twice', async ({helper}) => {195 const account = crowd.pop()!;196 await helper.collatorSelection.obtainLicense(account);197 await expect(helper.collatorSelection.obtainLicense(account))198 .to.be.rejectedWith(/collatorSelection.AlreadyHoldingLicense/);199 });200201 itSub('Cannot release a license twice', async ({helper}) => {202 const account = crowd.pop()!;203 await helper.collatorSelection.obtainLicense(account);204 await helper.collatorSelection.releaseLicense(account);205 await expect(helper.collatorSelection.releaseLicense(account))206 .to.be.rejectedWith(/collatorSelection.NoLicense/);207 });208209 itSub('Cannot force revoke a license as non-sudo', async ({helper}) => {210 const account = crowd.pop()!;211 await helper.collatorSelection.obtainLicense(account);212 await expect(helper.collatorSelection.forceReleaseLicense(superuser, account.address))213 .to.be.rejectedWith(/BadOrigin/);214 });215 });216 });217218 describe('Onboarding, collating, and offboarding as collator candidates', () => {219 let crowd: IKeyringPair[];220221 before(async function() {222 await usingPlaygrounds(async (helper, privateKey) => {223 crowd = await helper.arrange.createCrowd(20, 100n, superuser);224225 // set session keys for everyone226 await Promise.all(crowd.map(acc => helper.session.setOwnKeysFromAddress(acc)));227 });228 });229230 describe('Positive', () => {231 itSub('Can onboard and offboard repeatedly', async ({helper}) => {232 const account = crowd.pop()!;233 await helper.collatorSelection.obtainLicense(account);234 await helper.collatorSelection.onboard(account);235 expect(await helper.collatorSelection.getCandidates()).to.be.deep.equal([account.address]);236237 await helper.collatorSelection.offboard(account);238 expect(await helper.collatorSelection.getCandidates()).to.be.deep.equal([]);239240 await helper.collatorSelection.onboard(account);241 expect(await helper.collatorSelection.getCandidates()).to.be.deep.equal([account.address]);242243 await helper.collatorSelection.offboard(account);244 expect(await helper.collatorSelection.getCandidates()).to.be.deep.equal([]);245 });246247 itSub('Penalizes and forfeits license from faulty collators', async ({helper}) => {248 // This one shouldn't even be able to produce blocks.249 const account = crowd.pop()!;250 await helper.collatorSelection.obtainLicense(account);251 await helper.collatorSelection.onboard(account);252 expect(await helper.collatorSelection.getCandidates()).to.contain(account.address);253254 // Wait for 3 new sessions before checking that the collator will be kicked:255 // one to get collator onboarded, and another two for the collator to fail256 await helper.wait.newSessions(3);257258 expect(await helper.collatorSelection.getCandidates()).to.not.contain(account.address);259 expect(await helper.collatorSelection.hasLicense(account.address)).to.be.equal(0n);260261 // The account's reserved funds get slashed as a penalty262 const balance = await helper.balance.getSubstrateFull(account.address);263 expect(balance.reserved).to.be.equal(0n);264 expect(balance.free < 100n - licenseBond);265 });266 });267268 describe('Negative', () => {269 itSub('Cannot onboard without a license', async ({helper}) => {270 const account = crowd.pop()!;271 await expect(helper.collatorSelection.onboard(account))272 .to.be.rejectedWith(/collatorSelection.NoLicense/);273 });274275 itSub('Cannot offboard without a license', async ({helper}) => {276 const account = crowd.pop()!;277 await expect(helper.collatorSelection.offboard(account))278 .to.be.rejectedWith(/collatorSelection.NotCandidate/);279 });280281 itSub('Cannot offboard while not onboarded', async ({helper}) => {282 const account = crowd.pop()!;283 await helper.collatorSelection.obtainLicense(account);284 await expect(helper.collatorSelection.offboard(account))285 .to.be.rejectedWith(/collatorSelection.NotCandidate/);286 });287288 itSub('Cannot onboard while already onboarded', async ({helper}) => {289 const account = crowd.pop()!;290 await helper.collatorSelection.obtainLicense(account);291 await helper.collatorSelection.onboard(account);292 await expect(helper.collatorSelection.onboard(account))293 .to.be.rejectedWith(/collatorSelection.AlreadyCandidate/);294 });295 });296 });297298 describe('Addition and removal of invulnerables', () => {299 describe('Positive', () => {300 itSub('Adds an invulnerable', async ({helper}) => {301 const [account] = await helper.arrange.createAccounts([10n], superuser);302 const invulnerables = await helper.collatorSelection.getInvulnerables();303304 await helper.session.setOwnKeysFromAddress(account);305 await helper.getSudo().collatorSelection.addInvulnerable(superuser, account.address);306307 const newInvulnerables = await helper.collatorSelection.getInvulnerables();308 expect(invulnerables.concat(account.address)).to.have.all.members(newInvulnerables);309 });310311 itSub('Removes an invulnerable', async ({helper}) => {312 const invulnerables = await helper.collatorSelection.getInvulnerables();313 const lastInvulnerable = invulnerables.pop()!;314315 await helper.getSudo().collatorSelection.removeInvulnerable(superuser, lastInvulnerable);316 const newInvulnerables = await helper.collatorSelection.getInvulnerables();317 // invulnerables had its last element removed, so they should be equal318 expect(newInvulnerables).to.have.all.members(invulnerables);319 });320 });321322 describe('Negative', () => {323 itSub('Does not duplicate an invulnerable', async ({helper}) => {324 const invulnerables = await helper.collatorSelection.getInvulnerables();325 // adding an already invulnerable should not fail, but should not duplicate it either326 await expect(helper.getSudo().collatorSelection.addInvulnerable(superuser, invulnerables[0]))327 .to.be.fulfilled;328 const newInvulnerables = await helper.collatorSelection.getInvulnerables();329 expect(newInvulnerables).to.have.all.members(invulnerables);330 });331332 itSub('Cannot remove a non-existent invulnerable', async ({helper}) => {333 const [account] = await helper.arrange.createAccounts([0n], superuser);334 await expect(helper.getSudo().collatorSelection.removeInvulnerable(superuser, account.address))335 .to.be.rejectedWith(/collatorSelection.NotInvulnerable/);336 });337338 itSub('Cannot allow invulnerables to be empty', async ({helper}) => {339 const invulnerables = await helper.collatorSelection.getInvulnerables();340 const lastInvulnerable = invulnerables.pop()!;341342 let nonce = await helper.chain.getNonce(superuser.address);343 await Promise.all(invulnerables.map((i: any) =>344 helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [i], true, {nonce: nonce++})));345346 await expect(helper.getSudo().collatorSelection.removeInvulnerable(superuser, lastInvulnerable))347 .to.be.rejectedWith(/collatorSelection.TooFewInvulnerables/);348349 const newInvulnerables = await helper.collatorSelection.getInvulnerables();350 expect(newInvulnerables).to.be.deep.equal([lastInvulnerable]);351352 // restore the invulnerables to the previous state353 nonce = await helper.chain.getNonce(superuser.address);354 await Promise.all(invulnerables.map((i: any) =>355 helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [i], true, {nonce: nonce++})));356 });357358 itSub('Cannot have too many invulnerables', async ({helper}) => {359 // todo:collator make sure that there is enough session time for a set of tests360 // 28 non-functioning collators, teehee.361362 const invulnerablesLength = (await helper.collatorSelection.getInvulnerables()).length;363 const invulnerablesUntilLimit = (await helper.collatorSelection.getDesiredCollators()) - invulnerablesLength;364 const newInvulnerables = await helper.arrange.createAccounts(Array(invulnerablesUntilLimit).fill(10n), superuser);365 const [lastInvulnerable] = await helper.arrange.createAccounts([10n], superuser);366367 await Promise.all(newInvulnerables.map((i: IKeyringPair) =>368 helper.session.setOwnKeysFromAddress(i)));369 await helper.session.setOwnKeysFromAddress(lastInvulnerable);370371 let nonce = await helper.chain.getNonce(superuser.address);372 await Promise.all(newInvulnerables.map((i: IKeyringPair) =>373 helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [i.address], true, {nonce: nonce++})));374375 await expect(helper.getSudo().collatorSelection.addInvulnerable(superuser, lastInvulnerable.address))376 .to.be.rejectedWith(/collatorSelection.TooManyInvulnerables/);377378 // restore the invulnerables to the previous state379 nonce = await helper.chain.getNonce(superuser.address);380 await Promise.all(newInvulnerables.map((i: IKeyringPair) =>381 helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [i.address], true, {nonce: nonce++})));382 });383384 itSub('Forbids a non-sudo to add an invulnerable', async ({helper}) => {385 const [account] = await helper.arrange.createAccounts([10n], superuser);386 const invulnerables = await helper.collatorSelection.getInvulnerables();387388 await helper.session.setOwnKeysFromAddress(account);389 await expect(helper.collatorSelection.addInvulnerable(superuser, account.address))390 .to.be.rejectedWith(/BadOrigin/);391392 const newInvulnerables = await helper.collatorSelection.getInvulnerables();393 expect(newInvulnerables).to.be.members(invulnerables);394 });395396 itSub('Forbids a non-sudo to remove an invulnerable', async ({helper}) => {397 const invulnerables = await helper.collatorSelection.getInvulnerables();398 await expect(helper.collatorSelection.removeInvulnerable(superuser, invulnerables[0]))399 .to.be.rejectedWith(/BadOrigin/);400 expect(await helper.collatorSelection.getInvulnerables()).to.have.all.members(invulnerables);401 });402403 after(async function() {404 await resetInvulnerables();405 });406 });407 });408409 after(async function() {410 if(!process.env.RUN_COLLATOR_TESTS) return;411412 await usingPlaygrounds(async (helper) => {413 if(helper.fetchMissingPalletNames([Pallets.CollatorSelection]).length != 0) return;414415 await helper.getSudo().collatorSelection.setLicenseBond(superuser, previousLicenseBond);416417 const candidates = await helper.collatorSelection.getCandidates();418 let nonce = await helper.chain.getNonce(superuser.address);419 await Promise.all(candidates.map(candidate =>420 helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.forceReleaseLicense', [candidate], true, {nonce: nonce++})));421 });422 });423});