From b41acd5be4df0b68d468748851e1159d167db0ee Mon Sep 17 00:00:00 2001 From: girazoki Date: Wed, 30 Oct 2024 09:02:02 +0100 Subject: [PATCH] test cleanup --- ...lashes_are_confirmed_after_defer_period.ts | 124 +++++----------- ...hes_are_not_applicable_to_invulnerables.ts | 98 +++---------- ...lashes_are_removed_after_bonding_period.ts | 132 +++++------------- .../slashes/test_slashes_babe.ts | 100 +++---------- .../slashes/test_slashes_can_be_cancelled.ts | 113 ++++----------- .../slashes/test_slashes_grandpa.ts | 96 +++---------- test/util/slashes.ts | 118 ++++++++++++++++ 7 files changed, 283 insertions(+), 498 deletions(-) create mode 100644 test/util/slashes.ts diff --git a/test/suites/dev-tanssi-relay/slashes/test_slashes_are_confirmed_after_defer_period.ts b/test/suites/dev-tanssi-relay/slashes/test_slashes_are_confirmed_after_defer_period.ts index c01221f93..6c216f394 100644 --- a/test/suites/dev-tanssi-relay/slashes/test_slashes_are_confirmed_after_defer_period.ts +++ b/test/suites/dev-tanssi-relay/slashes/test_slashes_are_confirmed_after_defer_period.ts @@ -2,18 +2,14 @@ import "@tanssi/api-augment"; import { describeSuite, expect, beforeAll } from "@moonwall/cli"; import { ApiPromise } from "@polkadot/api"; import { KeyringPair } from "@moonwall/util"; -import { fetchCollatorAssignmentTip, jumpSessions } from "util/block"; import { Keyring } from "@polkadot/keyring"; -import { Header, BabeEquivocationProof } from "@polkadot/types/interfaces"; -import { SpRuntimeHeader } from '@polkadot/types/lookup'; -import { extrinsics } from "@polkadot/types/interfaces/definitions"; -import { u8aToHex, hexToU8a, stringToHex, numberToHex, stringToU8a } from "@polkadot/util"; -import { blake2AsHex } from "@polkadot/util-crypto"; +import { u8aToHex } from "@polkadot/util"; import { jumpToSession } from "../../../util/block"; +import { generateBabeEquivocationProof } from "../../../util/slashes"; describeSuite({ id: "DTR1304", - title: "Babe offences should trigger a slash", + title: "Babe slashes defer period confirmation", foundationMethods: "dev", testCases: ({ it, context }) => { let polkadotJs: ApiPromise; @@ -29,92 +25,40 @@ describeSuite({ }); it({ id: "E01", - title: "Babe offences trigger a slash+", + title: "Babe offences should be confirmed after defer period", test: async function () { // we crate one block so that we at least have one seal. await jumpToSession(context, 1); // Remove alice from invulnerables (just for the slash) - const removeAliceFromInvulnerables = await polkadotJs.tx.sudo.sudo( - polkadotJs.tx.externalValidators.removeWhitelisted(aliceStash.address) - ).signAsync(alice) + const removeAliceFromInvulnerables = await polkadotJs.tx.sudo + .sudo(polkadotJs.tx.externalValidators.removeWhitelisted(aliceStash.address)) + .signAsync(alice); await context.createBlock([removeAliceFromInvulnerables]); - let baseHeader = await polkadotJs.rpc.chain.getHeader(); - let baseHeader2 = await polkadotJs.rpc.chain.getHeader(); - - const header1: SpRuntimeHeader = polkadotJs.createType("SpRuntimeHeader", { - digest: baseHeader.digest, - extrinsicsRoot: baseHeader.extrinsicsRoot, - stateRoot: baseHeader.stateRoot, - parentHash: baseHeader.parentHash, - number: 1, - }); - - // we just change the block number - const header2: SpRuntimeHeader = polkadotJs.createType("SpRuntimeHeader", { - digest: baseHeader2.digest, - extrinsicsRoot: baseHeader2.extrinsicsRoot, - stateRoot: baseHeader2.stateRoot, - parentHash: baseHeader2.parentHash, - number: 2, - }); - - const sig1 = aliceBabePair.sign(blake2AsHex(header1.toU8a())); - const sig2 = aliceBabePair.sign(blake2AsHex(header2.toU8a())); - - const slot = await polkadotJs.query.babe.currentSlot(); - // let's inject the equivocation proof + const doubleVotingProof = await generateBabeEquivocationProof(polkadotJs, aliceBabePair); - const keyOwnershipProof = (await polkadotJs.call.babeApi.generateKeyOwnershipProof( - slot, - u8aToHex(aliceBabePair.publicKey) - )).unwrap(); - - // We don't care about the first 8 characters of the proof, as they - // correspond to SCALE encoded wrapping stuff we don't need. - //const keyOwnershipProofHex = `0x${keyOwnershipProof.toHuman().toString().slice(8)}`; - - const digestItemSeal1: SpRuntimeDigestDigestItem = polkadotJs.createType( - "SpRuntimeDigestDigestItem", - { Seal: [ - stringToHex('BABE'), - u8aToHex(sig1) - ] - } - ); - - const digestItemSeal2: SpRuntimeDigestDigestItem = polkadotJs.createType( - "SpRuntimeDigestDigestItem", - { Seal: [ - stringToHex('BABE'), - u8aToHex(sig2) - ] - } - ); - - header1.digest.logs.push(digestItemSeal1); - header2.digest.logs.push(digestItemSeal2); + // generate key ownership proof + const keyOwnershipProof = ( + await polkadotJs.call.babeApi.generateKeyOwnershipProof( + doubleVotingProof.slotNumber, + u8aToHex(aliceBabePair.publicKey) + ) + ).unwrap(); - const doubleVotingProof: BabeEquivocationProof = polkadotJs.createType( - "BabeEquivocationProof", - { - offender: aliceBabePair.publicKey, - slotNumber: slot, - firstHeader: header1, - secondHeader: header2 - } - ); const tx = polkadotJs.tx.sudo.sudoUncheckedWeight( polkadotJs.tx.utility.dispatchAs( { system: { Signed: alice.address }, } as any, - polkadotJs.tx.babe.reportEquivocation(doubleVotingProof, keyOwnershipProof)), { - refTime: 1n, - proofSize: 1n - }) + polkadotJs.tx.babe.reportEquivocation(doubleVotingProof, keyOwnershipProof) + ), + { + refTime: 1n, + proofSize: 1n, + } + ); const signedTx = await tx.signAsync(alice); await context.createBlock(signedTx); @@ -123,26 +67,28 @@ describeSuite({ const DeferPeriod = 2; // scheduled slashes - const expectedSlashes = await polkadotJs.query.externalValidatorSlashes.slashes(DeferPeriod +1); + const expectedSlashes = await polkadotJs.query.externalValidatorSlashes.slashes(DeferPeriod + 1); expect(expectedSlashes.length).to.be.eq(1); expect(u8aToHex(expectedSlashes[0].validator)).to.be.eq(u8aToHex(aliceStash.addressRaw)); - - // Put alice back to invulnerables - const addAliceFromInvulnerables = await polkadotJs.tx.sudo.sudo( - polkadotJs.tx.externalValidators.addWhitelisted(aliceStash.address) - ).signAsync(alice) + + // Put alice back to invulnerables + const addAliceFromInvulnerables = await polkadotJs.tx.sudo + .sudo(polkadotJs.tx.externalValidators.addWhitelisted(aliceStash.address)) + .signAsync(alice); await context.createBlock([addAliceFromInvulnerables]); - let sessionsPerEra = await polkadotJs.consts.externalValidators.sessionsPerEra; + const sessionsPerEra = await polkadotJs.consts.externalValidators.sessionsPerEra; - let currentIndex = await polkadotJs.query.session.currentIndex(); + const currentIndex = await polkadotJs.query.session.currentIndex(); - let targetSession = currentIndex*sessionsPerEra*(DeferPeriod +1); + const targetSession = currentIndex * sessionsPerEra * (DeferPeriod + 1); await jumpToSession(context, targetSession); - + // scheduled slashes - const expectedSlashesAfterDefer = await polkadotJs.query.externalValidatorSlashes.slashes(DeferPeriod +1); + const expectedSlashesAfterDefer = await polkadotJs.query.externalValidatorSlashes.slashes( + DeferPeriod + 1 + ); expect(expectedSlashesAfterDefer.length).to.be.eq(1); expect(expectedSlashesAfterDefer[0].confirmed.toHuman()).to.be.true; }, diff --git a/test/suites/dev-tanssi-relay/slashes/test_slashes_are_not_applicable_to_invulnerables.ts b/test/suites/dev-tanssi-relay/slashes/test_slashes_are_not_applicable_to_invulnerables.ts index 2bffb60a0..88b99743c 100644 --- a/test/suites/dev-tanssi-relay/slashes/test_slashes_are_not_applicable_to_invulnerables.ts +++ b/test/suites/dev-tanssi-relay/slashes/test_slashes_are_not_applicable_to_invulnerables.ts @@ -2,113 +2,55 @@ import "@tanssi/api-augment"; import { describeSuite, expect, beforeAll } from "@moonwall/cli"; import { ApiPromise } from "@polkadot/api"; import { KeyringPair } from "@moonwall/util"; -import { fetchCollatorAssignmentTip, jumpSessions } from "util/block"; import { Keyring } from "@polkadot/keyring"; -import { Header, BabeEquivocationProof } from "@polkadot/types/interfaces"; -import { SpRuntimeHeader } from '@polkadot/types/lookup'; -import { extrinsics } from "@polkadot/types/interfaces/definitions"; -import { u8aToHex, hexToU8a, stringToHex, numberToHex, stringToU8a } from "@polkadot/util"; -import { blake2AsHex } from "@polkadot/util-crypto"; +import { u8aToHex } from "@polkadot/util"; import { jumpToSession } from "../../../util/block"; +import { generateBabeEquivocationProof } from "../../../util/slashes"; describeSuite({ id: "DTR1302", - title: "Babe offences should trigger a slash", + title: "Babe offences invulnerables", foundationMethods: "dev", testCases: ({ it, context }) => { let polkadotJs: ApiPromise; let alice: KeyringPair; let aliceBabePair: KeyringPair; - let aliceStash: KeyringPair; beforeAll(async () => { const keyringBabe = new Keyring({ type: "sr25519" }); aliceBabePair = keyringBabe.addFromUri("//Alice"); polkadotJs = context.polkadotJs(); alice = context.keyring.alice; - aliceStash = keyringBabe.addFromUri("//Alice//stash"); }); it({ id: "E01", - title: "Babe offences trigger a slash+", + title: "Babe offences do not trigger a slash to invulnerables", test: async function () { // we crate one block so that we at least have one seal. await jumpToSession(context, 1); - let baseHeader = await polkadotJs.rpc.chain.getHeader(); - let baseHeader2 = await polkadotJs.rpc.chain.getHeader(); - - const header1: SpRuntimeHeader = polkadotJs.createType("SpRuntimeHeader", { - digest: baseHeader.digest, - extrinsicsRoot: baseHeader.extrinsicsRoot, - stateRoot: baseHeader.stateRoot, - parentHash: baseHeader.parentHash, - number: 1, - }); - - // we just change the block number - const header2: SpRuntimeHeader = polkadotJs.createType("SpRuntimeHeader", { - digest: baseHeader2.digest, - extrinsicsRoot: baseHeader2.extrinsicsRoot, - stateRoot: baseHeader2.stateRoot, - parentHash: baseHeader2.parentHash, - number: 2, - }); - - const sig1 = aliceBabePair.sign(blake2AsHex(header1.toU8a())); - const sig2 = aliceBabePair.sign(blake2AsHex(header2.toU8a())); - - const slot = await polkadotJs.query.babe.currentSlot(); - // let's inject the equivocation proof + const doubleVotingProof = await generateBabeEquivocationProof(polkadotJs, aliceBabePair); - const keyOwnershipProof = (await polkadotJs.call.babeApi.generateKeyOwnershipProof( - slot, - u8aToHex(aliceBabePair.publicKey) - )).unwrap(); + // generate key ownership proof + const keyOwnershipProof = ( + await polkadotJs.call.babeApi.generateKeyOwnershipProof( + doubleVotingProof.slotNumber, + u8aToHex(aliceBabePair.publicKey) + ) + ).unwrap(); - // We don't care about the first 8 characters of the proof, as they - // correspond to SCALE encoded wrapping stuff we don't need. - //const keyOwnershipProofHex = `0x${keyOwnershipProof.toHuman().toString().slice(8)}`; - - const digestItemSeal1: SpRuntimeDigestDigestItem = polkadotJs.createType( - "SpRuntimeDigestDigestItem", - { Seal: [ - stringToHex('BABE'), - u8aToHex(sig1) - ] - } - ); - - const digestItemSeal2: SpRuntimeDigestDigestItem = polkadotJs.createType( - "SpRuntimeDigestDigestItem", - { Seal: [ - stringToHex('BABE'), - u8aToHex(sig2) - ] - } - ); - - header1.digest.logs.push(digestItemSeal1); - header2.digest.logs.push(digestItemSeal2); - - const doubleVotingProof: BabeEquivocationProof = polkadotJs.createType( - "BabeEquivocationProof", - { - offender: aliceBabePair.publicKey, - slotNumber: slot, - firstHeader: header1, - secondHeader: header2 - } - ); const tx = polkadotJs.tx.sudo.sudoUncheckedWeight( polkadotJs.tx.utility.dispatchAs( { system: { Signed: alice.address }, } as any, - polkadotJs.tx.babe.reportEquivocation(doubleVotingProof, keyOwnershipProof)), { - refTime: 1n, - proofSize: 1n - }) + polkadotJs.tx.babe.reportEquivocation(doubleVotingProof, keyOwnershipProof) + ), + { + refTime: 1n, + proofSize: 1n, + } + ); const signedTx = await tx.signAsync(alice); await context.createBlock(signedTx); @@ -117,7 +59,7 @@ describeSuite({ const DeferPeriod = 2; // Alice is an invulnerable, therefore she should not be slashed - const expectedSlashes = await polkadotJs.query.externalValidatorSlashes.slashes(DeferPeriod +1); + const expectedSlashes = await polkadotJs.query.externalValidatorSlashes.slashes(DeferPeriod + 1); expect(expectedSlashes.length).to.be.eq(0); }, }); diff --git a/test/suites/dev-tanssi-relay/slashes/test_slashes_are_removed_after_bonding_period.ts b/test/suites/dev-tanssi-relay/slashes/test_slashes_are_removed_after_bonding_period.ts index e3c384389..9ce814674 100644 --- a/test/suites/dev-tanssi-relay/slashes/test_slashes_are_removed_after_bonding_period.ts +++ b/test/suites/dev-tanssi-relay/slashes/test_slashes_are_removed_after_bonding_period.ts @@ -2,18 +2,14 @@ import "@tanssi/api-augment"; import { describeSuite, expect, beforeAll } from "@moonwall/cli"; import { ApiPromise } from "@polkadot/api"; import { KeyringPair } from "@moonwall/util"; -import { fetchCollatorAssignmentTip, jumpSessions } from "util/block"; import { Keyring } from "@polkadot/keyring"; -import { Header, BabeEquivocationProof } from "@polkadot/types/interfaces"; -import { SpRuntimeHeader } from '@polkadot/types/lookup'; -import { extrinsics } from "@polkadot/types/interfaces/definitions"; -import { u8aToHex, hexToU8a, stringToHex, numberToHex, stringToU8a } from "@polkadot/util"; -import { blake2AsHex } from "@polkadot/util-crypto"; +import { u8aToHex } from "@polkadot/util"; import { jumpToSession } from "../../../util/block"; +import { generateBabeEquivocationProof } from "../../../util/slashes"; describeSuite({ id: "DTR1305", - title: "Babe offences should trigger a slash", + title: "Babe offences bonding period", foundationMethods: "dev", testCases: ({ it, context }) => { let polkadotJs: ApiPromise; @@ -29,92 +25,40 @@ describeSuite({ }); it({ id: "E01", - title: "Babe offences trigger a slash+", + title: "Babe offences should be removed after bonding period", test: async function () { // we crate one block so that we at least have one seal. await jumpToSession(context, 1); // Remove alice from invulnerables (just for the slash) - const removeAliceFromInvulnerables = await polkadotJs.tx.sudo.sudo( - polkadotJs.tx.externalValidators.removeWhitelisted(aliceStash.address) - ).signAsync(alice) + const removeAliceFromInvulnerables = await polkadotJs.tx.sudo + .sudo(polkadotJs.tx.externalValidators.removeWhitelisted(aliceStash.address)) + .signAsync(alice); await context.createBlock([removeAliceFromInvulnerables]); - let baseHeader = await polkadotJs.rpc.chain.getHeader(); - let baseHeader2 = await polkadotJs.rpc.chain.getHeader(); - - const header1: SpRuntimeHeader = polkadotJs.createType("SpRuntimeHeader", { - digest: baseHeader.digest, - extrinsicsRoot: baseHeader.extrinsicsRoot, - stateRoot: baseHeader.stateRoot, - parentHash: baseHeader.parentHash, - number: 1, - }); - - // we just change the block number - const header2: SpRuntimeHeader = polkadotJs.createType("SpRuntimeHeader", { - digest: baseHeader2.digest, - extrinsicsRoot: baseHeader2.extrinsicsRoot, - stateRoot: baseHeader2.stateRoot, - parentHash: baseHeader2.parentHash, - number: 2, - }); - - const sig1 = aliceBabePair.sign(blake2AsHex(header1.toU8a())); - const sig2 = aliceBabePair.sign(blake2AsHex(header2.toU8a())); - - const slot = await polkadotJs.query.babe.currentSlot(); - // let's inject the equivocation proof + const doubleVotingProof = await generateBabeEquivocationProof(polkadotJs, aliceBabePair); - const keyOwnershipProof = (await polkadotJs.call.babeApi.generateKeyOwnershipProof( - slot, - u8aToHex(aliceBabePair.publicKey) - )).unwrap(); - - // We don't care about the first 8 characters of the proof, as they - // correspond to SCALE encoded wrapping stuff we don't need. - //const keyOwnershipProofHex = `0x${keyOwnershipProof.toHuman().toString().slice(8)}`; - - const digestItemSeal1: SpRuntimeDigestDigestItem = polkadotJs.createType( - "SpRuntimeDigestDigestItem", - { Seal: [ - stringToHex('BABE'), - u8aToHex(sig1) - ] - } - ); - - const digestItemSeal2: SpRuntimeDigestDigestItem = polkadotJs.createType( - "SpRuntimeDigestDigestItem", - { Seal: [ - stringToHex('BABE'), - u8aToHex(sig2) - ] - } - ); - - header1.digest.logs.push(digestItemSeal1); - header2.digest.logs.push(digestItemSeal2); + // generate key ownership proof + const keyOwnershipProof = ( + await polkadotJs.call.babeApi.generateKeyOwnershipProof( + doubleVotingProof.slotNumber, + u8aToHex(aliceBabePair.publicKey) + ) + ).unwrap(); - const doubleVotingProof: BabeEquivocationProof = polkadotJs.createType( - "BabeEquivocationProof", - { - offender: aliceBabePair.publicKey, - slotNumber: slot, - firstHeader: header1, - secondHeader: header2 - } - ); const tx = polkadotJs.tx.sudo.sudoUncheckedWeight( polkadotJs.tx.utility.dispatchAs( { system: { Signed: alice.address }, } as any, - polkadotJs.tx.babe.reportEquivocation(doubleVotingProof, keyOwnershipProof)), { - refTime: 1n, - proofSize: 1n - }) + polkadotJs.tx.babe.reportEquivocation(doubleVotingProof, keyOwnershipProof) + ), + { + refTime: 1n, + proofSize: 1n, + } + ); const signedTx = await tx.signAsync(alice); await context.createBlock(signedTx); @@ -123,30 +67,30 @@ describeSuite({ const DeferPeriod = 2; // scheduled slashes - const expectedSlashes = await polkadotJs.query.externalValidatorSlashes.slashes(DeferPeriod +1); + const expectedSlashes = await polkadotJs.query.externalValidatorSlashes.slashes(DeferPeriod + 1); expect(expectedSlashes.length).to.be.eq(1); expect(u8aToHex(expectedSlashes[0].validator)).to.be.eq(u8aToHex(aliceStash.addressRaw)); - - // Put alice back to invulnerables - const addAliceFromInvulnerables = await polkadotJs.tx.sudo.sudo( - polkadotJs.tx.externalValidators.addWhitelisted(aliceStash.address) - ).signAsync(alice) + + // Put alice back to invulnerables + const addAliceFromInvulnerables = await polkadotJs.tx.sudo + .sudo(polkadotJs.tx.externalValidators.addWhitelisted(aliceStash.address)) + .signAsync(alice); await context.createBlock([addAliceFromInvulnerables]); - let sessionsPerEra = await polkadotJs.consts.externalValidators.sessionsPerEra; - let bondingPeriod = await polkadotJs.consts.externalValidatorSlashes.bondingDuration; + const sessionsPerEra = await polkadotJs.consts.externalValidators.sessionsPerEra; + const bondingPeriod = await polkadotJs.consts.externalValidatorSlashes.bondingDuration; - let currentIndex = await polkadotJs.query.session.currentIndex(); - console.log("bondingPeriod is ", bondingPeriod); - console.log("currentIndex is ", currentIndex); - console.log("sessionsPerEra is ", sessionsPerEra); + const currentIndex = await polkadotJs.query.session.currentIndex(); - let targetSession = currentIndex.toNumber() + sessionsPerEra*(DeferPeriod +1) + sessionsPerEra*bondingPeriod; + const targetSession = + currentIndex.toNumber() + sessionsPerEra * (DeferPeriod + 1) + sessionsPerEra * bondingPeriod; // TODO: check this - await jumpToSession(context, targetSession +10); - + await jumpToSession(context, targetSession + 10); + // scheduled slashes - const expectedSlashesAfterDefer = await polkadotJs.query.externalValidatorSlashes.slashes(DeferPeriod +1); + const expectedSlashesAfterDefer = await polkadotJs.query.externalValidatorSlashes.slashes( + DeferPeriod + 1 + ); expect(expectedSlashesAfterDefer.length).to.be.eq(0); }, }); diff --git a/test/suites/dev-tanssi-relay/slashes/test_slashes_babe.ts b/test/suites/dev-tanssi-relay/slashes/test_slashes_babe.ts index 8117baf48..8b8c4c297 100644 --- a/test/suites/dev-tanssi-relay/slashes/test_slashes_babe.ts +++ b/test/suites/dev-tanssi-relay/slashes/test_slashes_babe.ts @@ -2,14 +2,10 @@ import "@tanssi/api-augment"; import { describeSuite, expect, beforeAll } from "@moonwall/cli"; import { ApiPromise } from "@polkadot/api"; import { KeyringPair } from "@moonwall/util"; -import { fetchCollatorAssignmentTip, jumpSessions } from "util/block"; import { Keyring } from "@polkadot/keyring"; -import { Header, BabeEquivocationProof } from "@polkadot/types/interfaces"; -import { SpRuntimeHeader } from '@polkadot/types/lookup'; -import { extrinsics } from "@polkadot/types/interfaces/definitions"; -import { u8aToHex, hexToU8a, stringToHex, numberToHex, stringToU8a } from "@polkadot/util"; -import { blake2AsHex } from "@polkadot/util-crypto"; +import { u8aToHex } from "@polkadot/util"; import { jumpToSession } from "../../../util/block"; +import { generateBabeEquivocationProof } from "../../../util/slashes"; describeSuite({ id: "DTR1301", @@ -29,92 +25,40 @@ describeSuite({ }); it({ id: "E01", - title: "Babe offences do not trigger a slash against invulnerables", + title: "Babe offences trigger a slash", test: async function () { // we crate one block so that we at least have one seal. await jumpToSession(context, 1); // Remove alice from invulnerables (just for the slash) - const removeAliceFromInvulnerables = await polkadotJs.tx.sudo.sudo( - polkadotJs.tx.externalValidators.removeWhitelisted(aliceStash.address) - ).signAsync(alice) + const removeAliceFromInvulnerables = await polkadotJs.tx.sudo + .sudo(polkadotJs.tx.externalValidators.removeWhitelisted(aliceStash.address)) + .signAsync(alice); await context.createBlock([removeAliceFromInvulnerables]); - let baseHeader = await polkadotJs.rpc.chain.getHeader(); - let baseHeader2 = await polkadotJs.rpc.chain.getHeader(); - - const header1: SpRuntimeHeader = polkadotJs.createType("SpRuntimeHeader", { - digest: baseHeader.digest, - extrinsicsRoot: baseHeader.extrinsicsRoot, - stateRoot: baseHeader.stateRoot, - parentHash: baseHeader.parentHash, - number: 1, - }); - - // we just change the block number - const header2: SpRuntimeHeader = polkadotJs.createType("SpRuntimeHeader", { - digest: baseHeader2.digest, - extrinsicsRoot: baseHeader2.extrinsicsRoot, - stateRoot: baseHeader2.stateRoot, - parentHash: baseHeader2.parentHash, - number: 2, - }); - - const sig1 = aliceBabePair.sign(blake2AsHex(header1.toU8a())); - const sig2 = aliceBabePair.sign(blake2AsHex(header2.toU8a())); - - const slot = await polkadotJs.query.babe.currentSlot(); - // let's inject the equivocation proof + const doubleVotingProof = await generateBabeEquivocationProof(polkadotJs, aliceBabePair); - const keyOwnershipProof = (await polkadotJs.call.babeApi.generateKeyOwnershipProof( - slot, - u8aToHex(aliceBabePair.publicKey) - )).unwrap(); + // generate key ownership proof + const keyOwnershipProof = ( + await polkadotJs.call.babeApi.generateKeyOwnershipProof( + doubleVotingProof.slotNumber, + u8aToHex(aliceBabePair.publicKey) + ) + ).unwrap(); - // We don't care about the first 8 characters of the proof, as they - // correspond to SCALE encoded wrapping stuff we don't need. - //const keyOwnershipProofHex = `0x${keyOwnershipProof.toHuman().toString().slice(8)}`; - - const digestItemSeal1: SpRuntimeDigestDigestItem = polkadotJs.createType( - "SpRuntimeDigestDigestItem", - { Seal: [ - stringToHex('BABE'), - u8aToHex(sig1) - ] - } - ); - - const digestItemSeal2: SpRuntimeDigestDigestItem = polkadotJs.createType( - "SpRuntimeDigestDigestItem", - { Seal: [ - stringToHex('BABE'), - u8aToHex(sig2) - ] - } - ); - - header1.digest.logs.push(digestItemSeal1); - header2.digest.logs.push(digestItemSeal2); - - const doubleVotingProof: BabeEquivocationProof = polkadotJs.createType( - "BabeEquivocationProof", - { - offender: aliceBabePair.publicKey, - slotNumber: slot, - firstHeader: header1, - secondHeader: header2 - } - ); const tx = polkadotJs.tx.sudo.sudoUncheckedWeight( polkadotJs.tx.utility.dispatchAs( { system: { Signed: alice.address }, } as any, - polkadotJs.tx.babe.reportEquivocation(doubleVotingProof, keyOwnershipProof)), { - refTime: 1n, - proofSize: 1n - }) + polkadotJs.tx.babe.reportEquivocation(doubleVotingProof, keyOwnershipProof) + ), + { + refTime: 1n, + proofSize: 1n, + } + ); const signedTx = await tx.signAsync(alice); await context.createBlock(signedTx); @@ -123,7 +67,7 @@ describeSuite({ const DeferPeriod = 2; // scheduled slashes - const expectedSlashes = await polkadotJs.query.externalValidatorSlashes.slashes(DeferPeriod +1); + const expectedSlashes = await polkadotJs.query.externalValidatorSlashes.slashes(DeferPeriod + 1); expect(expectedSlashes.length).to.be.eq(1); expect(u8aToHex(expectedSlashes[0].validator)).to.be.eq(u8aToHex(aliceStash.addressRaw)); }, diff --git a/test/suites/dev-tanssi-relay/slashes/test_slashes_can_be_cancelled.ts b/test/suites/dev-tanssi-relay/slashes/test_slashes_can_be_cancelled.ts index 98637bec2..e2a802254 100644 --- a/test/suites/dev-tanssi-relay/slashes/test_slashes_can_be_cancelled.ts +++ b/test/suites/dev-tanssi-relay/slashes/test_slashes_can_be_cancelled.ts @@ -2,18 +2,14 @@ import "@tanssi/api-augment"; import { describeSuite, expect, beforeAll } from "@moonwall/cli"; import { ApiPromise } from "@polkadot/api"; import { KeyringPair } from "@moonwall/util"; -import { fetchCollatorAssignmentTip, jumpSessions } from "util/block"; import { Keyring } from "@polkadot/keyring"; -import { Header, BabeEquivocationProof } from "@polkadot/types/interfaces"; -import { SpRuntimeHeader } from '@polkadot/types/lookup'; -import { extrinsics } from "@polkadot/types/interfaces/definitions"; -import { u8aToHex, hexToU8a, stringToHex, numberToHex, stringToU8a } from "@polkadot/util"; -import { blake2AsHex } from "@polkadot/util-crypto"; +import { u8aToHex } from "@polkadot/util"; import { jumpToSession } from "../../../util/block"; +import { generateBabeEquivocationProof } from "../../../util/slashes"; describeSuite({ id: "DTR1303", - title: "Babe offences should trigger a slash", + title: "Babe offences should be cancellable", foundationMethods: "dev", testCases: ({ it, context }) => { let polkadotJs: ApiPromise; @@ -29,92 +25,40 @@ describeSuite({ }); it({ id: "E01", - title: "Babe offences trigger a slash+", + title: "Babe offences are cancellable during the defer period", test: async function () { // we crate one block so that we at least have one seal. await jumpToSession(context, 1); // Remove alice from invulnerables (just for the slash) - const removeAliceFromInvulnerables = await polkadotJs.tx.sudo.sudo( - polkadotJs.tx.externalValidators.removeWhitelisted(aliceStash.address) - ).signAsync(alice) + const removeAliceFromInvulnerables = await polkadotJs.tx.sudo + .sudo(polkadotJs.tx.externalValidators.removeWhitelisted(aliceStash.address)) + .signAsync(alice); await context.createBlock([removeAliceFromInvulnerables]); - let baseHeader = await polkadotJs.rpc.chain.getHeader(); - let baseHeader2 = await polkadotJs.rpc.chain.getHeader(); - - const header1: SpRuntimeHeader = polkadotJs.createType("SpRuntimeHeader", { - digest: baseHeader.digest, - extrinsicsRoot: baseHeader.extrinsicsRoot, - stateRoot: baseHeader.stateRoot, - parentHash: baseHeader.parentHash, - number: 1, - }); - - // we just change the block number - const header2: SpRuntimeHeader = polkadotJs.createType("SpRuntimeHeader", { - digest: baseHeader2.digest, - extrinsicsRoot: baseHeader2.extrinsicsRoot, - stateRoot: baseHeader2.stateRoot, - parentHash: baseHeader2.parentHash, - number: 2, - }); - - const sig1 = aliceBabePair.sign(blake2AsHex(header1.toU8a())); - const sig2 = aliceBabePair.sign(blake2AsHex(header2.toU8a())); - - const slot = await polkadotJs.query.babe.currentSlot(); - // let's inject the equivocation proof + const doubleVotingProof = await generateBabeEquivocationProof(polkadotJs, aliceBabePair); - const keyOwnershipProof = (await polkadotJs.call.babeApi.generateKeyOwnershipProof( - slot, - u8aToHex(aliceBabePair.publicKey) - )).unwrap(); - - // We don't care about the first 8 characters of the proof, as they - // correspond to SCALE encoded wrapping stuff we don't need. - //const keyOwnershipProofHex = `0x${keyOwnershipProof.toHuman().toString().slice(8)}`; + // generate key ownership proof + const keyOwnershipProof = ( + await polkadotJs.call.babeApi.generateKeyOwnershipProof( + doubleVotingProof.slotNumber, + u8aToHex(aliceBabePair.publicKey) + ) + ).unwrap(); - const digestItemSeal1: SpRuntimeDigestDigestItem = polkadotJs.createType( - "SpRuntimeDigestDigestItem", - { Seal: [ - stringToHex('BABE'), - u8aToHex(sig1) - ] - } - ); - - const digestItemSeal2: SpRuntimeDigestDigestItem = polkadotJs.createType( - "SpRuntimeDigestDigestItem", - { Seal: [ - stringToHex('BABE'), - u8aToHex(sig2) - ] - } - ); - - header1.digest.logs.push(digestItemSeal1); - header2.digest.logs.push(digestItemSeal2); - - const doubleVotingProof: BabeEquivocationProof = polkadotJs.createType( - "BabeEquivocationProof", - { - offender: aliceBabePair.publicKey, - slotNumber: slot, - firstHeader: header1, - secondHeader: header2 - } - ); const tx = polkadotJs.tx.sudo.sudoUncheckedWeight( polkadotJs.tx.utility.dispatchAs( { system: { Signed: alice.address }, } as any, - polkadotJs.tx.babe.reportEquivocation(doubleVotingProof, keyOwnershipProof)), { - refTime: 1n, - proofSize: 1n - }) + polkadotJs.tx.babe.reportEquivocation(doubleVotingProof, keyOwnershipProof) + ), + { + refTime: 1n, + proofSize: 1n, + } + ); const signedTx = await tx.signAsync(alice); await context.createBlock(signedTx); @@ -123,20 +67,21 @@ describeSuite({ const DeferPeriod = 2; // scheduled slashes - const expectedSlashes = await polkadotJs.query.externalValidatorSlashes.slashes(DeferPeriod +1); + const expectedSlashes = await polkadotJs.query.externalValidatorSlashes.slashes(DeferPeriod + 1); expect(expectedSlashes.length).to.be.eq(1); expect(u8aToHex(expectedSlashes[0].validator)).to.be.eq(u8aToHex(aliceStash.addressRaw)); // Remove alice from invulnerables (just for the slash) - const cancelSlash = await polkadotJs.tx.sudo.sudo( - polkadotJs.tx.externalValidatorSlashes.cancelDeferredSlash(DeferPeriod +1, [0]) - ).signAsync(alice) + const cancelSlash = await polkadotJs.tx.sudo + .sudo(polkadotJs.tx.externalValidatorSlashes.cancelDeferredSlash(DeferPeriod + 1, [0])) + .signAsync(alice); await context.createBlock([cancelSlash]); // alashes have dissapeared - const expectedSlashesAfterCancel = await polkadotJs.query.externalValidatorSlashes.slashes(DeferPeriod +1); + const expectedSlashesAfterCancel = await polkadotJs.query.externalValidatorSlashes.slashes( + DeferPeriod + 1 + ); expect(expectedSlashesAfterCancel.length).to.be.eq(0); - }, }); }, diff --git a/test/suites/dev-tanssi-relay/slashes/test_slashes_grandpa.ts b/test/suites/dev-tanssi-relay/slashes/test_slashes_grandpa.ts index 7e8c4ec81..6ec531e5b 100644 --- a/test/suites/dev-tanssi-relay/slashes/test_slashes_grandpa.ts +++ b/test/suites/dev-tanssi-relay/slashes/test_slashes_grandpa.ts @@ -2,14 +2,10 @@ import "@tanssi/api-augment"; import { describeSuite, expect, beforeAll } from "@moonwall/cli"; import { ApiPromise } from "@polkadot/api"; import { KeyringPair } from "@moonwall/util"; -import { fetchCollatorAssignmentTip, jumpSessions } from "util/block"; import { Keyring } from "@polkadot/keyring"; -import { Header, GrandpaEquivocationProof, GrandpaEquivocation, GrandpaEquivocationValue } from "@polkadot/types/interfaces"; -import { SpRuntimeHeader, FinalityGrandpaPrevote } from '@polkadot/types/lookup'; -import { extrinsics } from "@polkadot/types/interfaces/definitions"; -import { u8aToHex, hexToU8a, stringToHex, numberToHex, stringToU8a, identity } from "@polkadot/util"; -import { blake2AsHex } from "@polkadot/util-crypto"; +import { u8aToHex } from "@polkadot/util"; import { jumpToSession } from "../../../util/block"; +import { generateGrandpaEquivocationProof } from "../../../util/slashes"; describeSuite({ id: "DTR1306", @@ -30,87 +26,38 @@ describeSuite({ }); it({ id: "E01", - title: "Grandpa offences do not trigger a slash against invulnerables", + title: "Grandpa offences trigger a slashing event", test: async function () { // we crate one block so that we at least have one seal. await jumpToSession(context, 1); // Remove alice from invulnerables (just for the slash) - const removeAliceFromInvulnerables = await polkadotJs.tx.sudo.sudo( - polkadotJs.tx.externalValidators.removeWhitelisted(aliceStash.address) - ).signAsync(alice) + const removeAliceFromInvulnerables = await polkadotJs.tx.sudo + .sudo(polkadotJs.tx.externalValidators.removeWhitelisted(aliceStash.address)) + .signAsync(alice); await context.createBlock([removeAliceFromInvulnerables]); - let prevote1: FinalityGrandpaPrevote = polkadotJs.createType("FinalityGrandpaPrevote", { - targetHash: "0x0000000000000000000000000000000000000000000000000000000000000000", - targetNumber: 1 - }); + const doubleVotingProof = await generateGrandpaEquivocationProof(polkadotJs, aliceGrandpaPair); - let prevote2: FinalityGrandpaPrevote = polkadotJs.createType("FinalityGrandpaPrevote", { - targetHash: "0x0000000000000000000000000000000000000000000000000000000000000000", - targetNumber: 2 - }); - - let roundNumber = polkadotJs.createType("u64", 1); - let setId = await polkadotJs.query.grandpa.currentSetId(); - - // I could not find the proper struct that holds all this into a singl message - // ergo I need to construct the signing payload myself - // the first 0 is because of this enum variant - // https://github.com/paritytech/finality-grandpa/blob/8c45a664c05657f0c71057158d3ba555ba7d20de/src/lib.rs#L228 - // then we have the prevote message - // then the round number - // then the set id - const toSign1 = new Uint8Array([ - ...hexToU8a("0x00"), - ...prevote1.toU8a(), - ...roundNumber.toU8a(), - ...setId.toU8a() - ]); - - const toSign2 = new Uint8Array([ - ...hexToU8a("0x00"), - ...prevote2.toU8a(), - ...roundNumber.toU8a(), - ...setId.toU8a() - ]); - const sig1 = aliceGrandpaPair.sign(toSign1); - const sig2 = aliceGrandpaPair.sign(toSign2); - - const equivocationValue: GrandpaEquivocationValue = polkadotJs.createType("GrandpaEquivocationValue", { - roundNumber, - identity: aliceGrandpaPair.address, - first: [prevote1, sig1], - second: [prevote2, sig2] - }); - - const equivocation: GrandpaEquivocation = polkadotJs.createType("GrandpaEquivocation", - { - 'Prevote': equivocationValue - }); - - const doubleVotingProof: GrandpaEquivocationProof = polkadotJs.createType( - "GrandpaEquivocationProof", - { - setId, - equivocation - } - ); - - const keyOwnershipProof = (await polkadotJs.call.grandpaApi.generateKeyOwnershipProof( - setId, - u8aToHex(aliceGrandpaPair.publicKey) - )).unwrap(); + const keyOwnershipProof = ( + await polkadotJs.call.grandpaApi.generateKeyOwnershipProof( + doubleVotingProof.setId, + u8aToHex(aliceGrandpaPair.publicKey) + ) + ).unwrap(); const tx = polkadotJs.tx.sudo.sudoUncheckedWeight( polkadotJs.tx.utility.dispatchAs( { system: { Signed: alice.address }, } as any, - polkadotJs.tx.grandpa.reportEquivocation(doubleVotingProof, keyOwnershipProof)), { - refTime: 1n, - proofSize: 1n - }) + polkadotJs.tx.grandpa.reportEquivocation(doubleVotingProof, keyOwnershipProof) + ), + { + refTime: 1n, + proofSize: 1n, + } + ); const signedTx = await tx.signAsync(alice); await context.createBlock(signedTx); @@ -119,10 +66,9 @@ describeSuite({ const DeferPeriod = 2; // scheduled slashes - const expectedSlashes = await polkadotJs.query.externalValidatorSlashes.slashes(DeferPeriod +1); + const expectedSlashes = await polkadotJs.query.externalValidatorSlashes.slashes(DeferPeriod + 1); expect(expectedSlashes.length).to.be.eq(1); expect(u8aToHex(expectedSlashes[0].validator)).to.be.eq(u8aToHex(aliceStash.addressRaw)); - }, }); }, diff --git a/test/util/slashes.ts b/test/util/slashes.ts new file mode 100644 index 000000000..483dbaf3f --- /dev/null +++ b/test/util/slashes.ts @@ -0,0 +1,118 @@ +import { ApiPromise } from "@polkadot/api"; +import { + BabeEquivocationProof, + GrandpaEquivocationProof, + GrandpaEquivocation, + GrandpaEquivocationValue, +} from "@polkadot/types/interfaces"; +import { SpRuntimeHeader, SpRuntimeDigestDigestItem, FinalityGrandpaPrevote } from "@polkadot/types/lookup"; +import { KeyringPair } from "@moonwall/util"; +import { blake2AsHex } from "@polkadot/util-crypto"; +import { u8aToHex, stringToHex, hexToU8a } from "@polkadot/util"; + +export async function generateBabeEquivocationProof( + api: ApiPromise, + pair: KeyringPair +): Promise { + const baseHeader = await api.rpc.chain.getHeader(); + const baseHeader2 = await api.rpc.chain.getHeader(); + + const header1: SpRuntimeHeader = api.createType("SpRuntimeHeader", { + digest: baseHeader.digest, + extrinsicsRoot: baseHeader.extrinsicsRoot, + stateRoot: baseHeader.stateRoot, + parentHash: baseHeader.parentHash, + number: 1, + }); + + // we just change the block number + const header2: SpRuntimeHeader = api.createType("SpRuntimeHeader", { + digest: baseHeader2.digest, + extrinsicsRoot: baseHeader2.extrinsicsRoot, + stateRoot: baseHeader2.stateRoot, + parentHash: baseHeader2.parentHash, + number: 2, + }); + + const sig1 = pair.sign(blake2AsHex(header1.toU8a())); + const sig2 = pair.sign(blake2AsHex(header2.toU8a())); + + const slot = await api.query.babe.currentSlot(); + + const digestItemSeal1: SpRuntimeDigestDigestItem = api.createType("SpRuntimeDigestDigestItem", { + Seal: [stringToHex("BABE"), u8aToHex(sig1)], + }); + + const digestItemSeal2: SpRuntimeDigestDigestItem = api.createType("SpRuntimeDigestDigestItem", { + Seal: [stringToHex("BABE"), u8aToHex(sig2)], + }); + + header1.digest.logs.push(digestItemSeal1); + header2.digest.logs.push(digestItemSeal2); + + const doubleVotingProof: BabeEquivocationProof = api.createType("BabeEquivocationProof", { + offender: pair.publicKey, + slotNumber: slot, + firstHeader: header1, + secondHeader: header2, + }); + return doubleVotingProof; +} + +export async function generateGrandpaEquivocationProof( + api: ApiPromise, + pair: KeyringPair +): Promise { + const prevote1: FinalityGrandpaPrevote = api.createType("FinalityGrandpaPrevote", { + targetHash: "0x0000000000000000000000000000000000000000000000000000000000000000", + targetNumber: 1, + }); + + const prevote2: FinalityGrandpaPrevote = api.createType("FinalityGrandpaPrevote", { + targetHash: "0x0000000000000000000000000000000000000000000000000000000000000000", + targetNumber: 2, + }); + + const roundNumber = api.createType("u64", 1); + const setId = await api.query.grandpa.currentSetId(); + + // I could not find the proper struct that holds all this into a singl message + // ergo I need to construct the signing payload myself + // the first 0 is because of this enum variant + // https://github.com/paritytech/finality-grandpa/blob/8c45a664c05657f0c71057158d3ba555ba7d20de/src/lib.rs#L228 + // then we have the prevote message + // then the round number + // then the set id + const toSign1 = new Uint8Array([ + ...hexToU8a("0x00"), + ...prevote1.toU8a(), + ...roundNumber.toU8a(), + ...setId.toU8a(), + ]); + + const toSign2 = new Uint8Array([ + ...hexToU8a("0x00"), + ...prevote2.toU8a(), + ...roundNumber.toU8a(), + ...setId.toU8a(), + ]); + const sig1 = pair.sign(toSign1); + const sig2 = pair.sign(toSign2); + + const equivocationValue: GrandpaEquivocationValue = api.createType("GrandpaEquivocationValue", { + roundNumber, + identity: pair.address, + first: [prevote1, sig1], + second: [prevote2, sig2], + }); + + const equivocation: GrandpaEquivocation = api.createType("GrandpaEquivocation", { + Prevote: equivocationValue, + }); + + const doubleVotingProof: GrandpaEquivocationProof = api.createType("GrandpaEquivocationProof", { + setId, + equivocation, + }); + return doubleVotingProof; +}