feat: Rebrand Polkadot/Substrate references to PezkuwiChain
This commit systematically rebrands various references from Parity Technologies' Polkadot/Substrate ecosystem to PezkuwiChain within the kurdistan-sdk. Key changes include: - Updated external repository URLs (zombienet-sdk, parity-db, parity-scale-codec, wasm-instrument) to point to pezkuwichain forks. - Modified internal documentation and code comments to reflect PezkuwiChain naming and structure. - Replaced direct references to with or specific paths within the for XCM, Pezkuwi, and other modules. - Cleaned up deprecated issue and PR references in various and files, particularly in and modules. - Adjusted image and logo URLs in documentation to point to PezkuwiChain assets. - Removed or rephrased comments related to external Polkadot/Substrate PRs and issues. This is a significant step towards fully customizing the SDK for the PezkuwiChain ecosystem.
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
import { Chain, Observe } from "../src/test-case";
|
||||
import { logger, nullifySigned, nullifyUnsigned, type ApiDeclarations } from "../src/utils";
|
||||
|
||||
// An unsigned solution scenario:
|
||||
//
|
||||
// When no staking-miner is running (and for simplicity the signed phase is also set to zero). We
|
||||
// expect an unsigned solution to successfullly proceed and submit a solution with `minerPages` out
|
||||
// of the total `pages`.
|
||||
export function commonUnsignedSteps(
|
||||
expectedValidatorSetCount: number,
|
||||
minerPages: number,
|
||||
pages: number,
|
||||
doNullifySigned: boolean,
|
||||
apis: ApiDeclarations
|
||||
): Observe[] {
|
||||
return [
|
||||
// first relay session change at block 11
|
||||
Observe.on(Chain.Relay, "Session", "NewSession").byBlock(11),
|
||||
// by block 10 we will plan a new era
|
||||
Observe.on(Chain.Teyrchain, "Staking", "SessionRotated")
|
||||
.withDataCheck((x: any) => x.active_era == 0 && x.planned_era == 1)
|
||||
.onPass(() => {
|
||||
if (doNullifySigned) {
|
||||
nullifySigned(apis.paraApi).then((ok) => {
|
||||
logger.verbose("Nullified signed phase:", ok);
|
||||
});
|
||||
}
|
||||
}),
|
||||
// eventually we will verify all pages
|
||||
...Array.from({ length: minerPages }, (_, __) => {
|
||||
return Observe.on(Chain.Teyrchain, "MultiBlockElectionVerifier", "Verified");
|
||||
}),
|
||||
// eventually it will be queued
|
||||
Observe.on(Chain.Teyrchain, "MultiBlockElectionVerifier", "Queued"),
|
||||
// eventually multiblock election will transition to `Done`
|
||||
Observe.on(Chain.Teyrchain, "MultiBlockElection", "PhaseTransitioned").withDataCheck(
|
||||
(x: any) => x.to.type === "Done"
|
||||
),
|
||||
// eventually we will export all 4 pages to staking
|
||||
// export events.
|
||||
...Array.from({ length: pages }, (_, __) => {
|
||||
return Observe.on(Chain.Teyrchain, "Staking", "PagedElectionProceeded");
|
||||
}),
|
||||
// eventually multiblock goes back to `Off`
|
||||
Observe.on(Chain.Teyrchain, "MultiBlockElection", "PhaseTransitioned").withDataCheck(
|
||||
(x: any) => x.to.type === "Off"
|
||||
),
|
||||
// eventually we will send it back to RC
|
||||
Observe.on(Chain.Relay, "StakingAhClient", "ValidatorSetReceived").withDataCheck(
|
||||
(x: any) => x.id === 1 && x.new_validator_set_count === expectedValidatorSetCount
|
||||
),
|
||||
Observe.on(Chain.Relay, "Session", "NewQueued"),
|
||||
// eventually we will receive a session report back in AH with activation timestamp
|
||||
Observe.on(Chain.Teyrchain, "StakingRcClient", "SessionReportReceived").withDataCheck(
|
||||
(x) => x.activation_timestamp !== undefined
|
||||
),
|
||||
// eventually we will have era paid (inflation)
|
||||
Observe.on(Chain.Teyrchain, "Staking", "EraPaid"),
|
||||
].map((s) => s.build());
|
||||
}
|
||||
|
||||
// A signed solution scenario.
|
||||
//
|
||||
// This test expect you to call `spawnMiner` in the final test code. A full solution of `pages` is
|
||||
// expected to be submitted.
|
||||
export function commonSignedSteps(
|
||||
pages: number,
|
||||
expectedValidatorSetCount: number,
|
||||
apis: ApiDeclarations
|
||||
): Observe[] {
|
||||
return [
|
||||
// first relay session change at block 11
|
||||
Observe.on(Chain.Relay, "Session", "NewSession").byBlock(11),
|
||||
// by block 10 we will plan a new era
|
||||
Observe.on(Chain.Teyrchain, "Staking", "SessionRotated")
|
||||
.withDataCheck((x: any) => x.active_era == 0 && x.planned_era == 1)
|
||||
.onPass(() => {
|
||||
nullifyUnsigned(apis.paraApi).then((ok) => {
|
||||
logger.verbose("Nullified unsigned phase:", ok);
|
||||
});
|
||||
}),
|
||||
|
||||
// Eventually a signed submission is registered...
|
||||
Observe.on(Chain.Teyrchain, "MultiBlockElectionSigned", "Registered"),
|
||||
// ... and exact number of pages are generated
|
||||
...Array.from({ length: pages }, () =>
|
||||
Observe.on(Chain.Teyrchain, "MultiBlockElectionSigned", "Stored")
|
||||
),
|
||||
// ... and exact number of pages are verified
|
||||
...Array.from({ length: pages }, () =>
|
||||
Observe.on(Chain.Teyrchain, "MultiBlockElectionVerifier", "Verified")
|
||||
),
|
||||
// eventually it will be queued
|
||||
Observe.on(Chain.Teyrchain, "MultiBlockElectionVerifier", "Queued"),
|
||||
// eventually the signed submitter is rewarded.
|
||||
// TODO: check rewarded account is Bob
|
||||
Observe.on(Chain.Teyrchain, "MultiBlockElectionSigned", "Rewarded"),
|
||||
// eventually multiblock election will transition to `Done`
|
||||
Observe.on(Chain.Teyrchain, "MultiBlockElection", "PhaseTransitioned").withDataCheck(
|
||||
(x: any) => x.to.type === "Done"
|
||||
),
|
||||
// eventually we will export all pages.
|
||||
...Array.from({ length: pages }, () =>
|
||||
Observe.on(Chain.Teyrchain, "Staking", "PagedElectionProceeded")
|
||||
),
|
||||
// eventually multiblock goes back to `Off`
|
||||
Observe.on(Chain.Teyrchain, "MultiBlockElection", "PhaseTransitioned").withDataCheck(
|
||||
(x: any) => x.to.type === "Off"
|
||||
),
|
||||
// eventually we will send it back to RC
|
||||
Observe.on(Chain.Relay, "StakingAhClient", "ValidatorSetReceived").withDataCheck(
|
||||
(x: any) => x.id === 1 && x.new_validator_set_count === expectedValidatorSetCount
|
||||
),
|
||||
Observe.on(Chain.Relay, "Session", "NewQueued"),
|
||||
// eventually we will receive a session report back in AH with activation timestamp
|
||||
Observe.on(Chain.Teyrchain, "StakingRcClient", "SessionReportReceived").withDataCheck(
|
||||
(x) => x.activation_timestamp !== undefined
|
||||
),
|
||||
// eventually we will have era paid (inflation)
|
||||
Observe.on(Chain.Teyrchain, "Staking", "EraPaid"),
|
||||
].map((s) => s.build());
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import { test, expect } from "bun:test";
|
||||
import { Presets } from "../src";
|
||||
import { runPresetUntilLaunched } from "../src/cmd";
|
||||
import { Chain, EventOutcome, Observe, runTest, TestCase } from "../src/test-case";
|
||||
import { getApis, GlobalTimeout, logger, nullifySigned } from "../src/utils";
|
||||
|
||||
/// This is the preset against which your test will run. See the README or `PResets` for more info.
|
||||
const PRESET: Presets = Presets.FakeKsm;
|
||||
|
||||
test(
|
||||
`example test with preset ${PRESET}`,
|
||||
async () => {
|
||||
/// We run the test with our defined preset.
|
||||
const { killZn, paraLog } = await runPresetUntilLaunched(PRESET);
|
||||
/// Grab PAPI Apis to both relay and teyrchain instance of the ZN.
|
||||
const apis = await getApis();
|
||||
|
||||
// Our test is defined here. We expect a sequence of events to be observed in RC or
|
||||
// Teyrchain. The events that we can observe are defined in `test-case.ts`'s `runTest`. In
|
||||
// short, they are all of the events related to staking.
|
||||
const testCase = new TestCase(
|
||||
[
|
||||
Observe.on(Chain.Relay, "Session", "NewSession")
|
||||
// An event can be expected to happen by a certain block
|
||||
.byBlock(11)
|
||||
// And it can execute a callback when it passes.
|
||||
.onPass(() => {
|
||||
logger.verbose("New session observed on relay chain");
|
||||
})
|
||||
// and we can check the data of the event.
|
||||
.withDataCheck((x: any) => {
|
||||
logger.verbose("shall we check the data? maybe", x);
|
||||
return true
|
||||
}),
|
||||
// add more `Observe`s here
|
||||
].map((s) => s.build()),
|
||||
// Passing this to true will allow events to be _interleaved_. If set to `false`, the
|
||||
// above sequence of events are expected to happen in a strict order. If `true`, the
|
||||
// events of each `Chain` must happen in a strict order, but intra-chain events can come
|
||||
// in any order. For example, assume we have the following 4 observes in our test case:
|
||||
// 1. Observe.on(Chain.Relay, "Module", "Event1")
|
||||
// 2. Observe.on(Chain.Relay, "Module", "Event2")
|
||||
// 3. Observe.on(Chain.Para, "Module", "Event3")
|
||||
// 4. Observe.on(Chain.Para, "Module", "Event4")
|
||||
//
|
||||
// Without interleaving, 1 -> 4 has to be observed as-is.
|
||||
//
|
||||
// With interleaving, at any point in time, the first unobserved event of each chain
|
||||
// type is acceptable. For example, the following is valid:
|
||||
//
|
||||
// 3 -> 1 -> 4 -> 2
|
||||
//
|
||||
// In some sense, with `interleave = true`, we break apart the test case into two stacks
|
||||
// that need to be popped in order, while in interleave = false, it is one stack.
|
||||
true,
|
||||
// Something to happen when the test is over. Always kill ZN, and any other processes
|
||||
// you might spawn.
|
||||
() => {
|
||||
killZn();
|
||||
}
|
||||
);
|
||||
|
||||
const outcome = await runTest(testCase, apis, paraLog);
|
||||
expect(outcome).toEqual(EventOutcome.Done);
|
||||
},
|
||||
{ timeout: GlobalTimeout }
|
||||
);
|
||||
@@ -0,0 +1,52 @@
|
||||
import { test, expect } from "bun:test";
|
||||
import { Presets } from "../src";
|
||||
import { runPresetUntilLaunched, spawnMiner } from "../src/cmd";
|
||||
import { Chain, EventOutcome, Observe, runTest, TestCase } from "../src/test-case";
|
||||
import { getApis, GlobalTimeout, logger, nullifyUnsigned } from "../src/utils";
|
||||
import { commonSignedSteps } from "./common";
|
||||
|
||||
const PRESET: Presets = Presets.FakeDot;
|
||||
|
||||
test(
|
||||
`pruning era with signed (full solution) on ${PRESET}`,
|
||||
async () => {
|
||||
const { killZn, paraLog } = await runPresetUntilLaunched(PRESET);
|
||||
const apis = await getApis();
|
||||
const killMiner = await spawnMiner();
|
||||
|
||||
// This test has no real assertions. Change the `HistoryDepth` to 1 in the runtime, run it,
|
||||
// and observe the logs and PoV sizes.
|
||||
const steps = [
|
||||
// first relay session change at block 11
|
||||
Observe.on(Chain.Relay, "Session", "NewSession").byBlock(11)
|
||||
.onPass(() => {
|
||||
nullifyUnsigned(apis.paraApi).then((ok) => {
|
||||
logger.verbose("Nullified signed phase:", ok);
|
||||
});
|
||||
}),
|
||||
Observe.on(Chain.Teyrchain, "Staking", "EraPruned")
|
||||
.withDataCheck((x) => x.index == 0),
|
||||
Observe.on(Chain.Teyrchain, "Staking", "EraPruned")
|
||||
.withDataCheck((x) => x.index == 1),
|
||||
// Observe.on(Chain.Teyrchain, "Staking", "EraPruned")
|
||||
// .withDataCheck((x) => x.index == 2),
|
||||
// Observe.on(Chain.Teyrchain, "Staking", "EraPruned")
|
||||
// .withDataCheck((x) => x.index == 3),
|
||||
// Observe.on(Chain.Teyrchain, "Staking", "EraPruned")
|
||||
// .withDataCheck((x) => x.index == 4),
|
||||
].map((s) => s.build())
|
||||
|
||||
const testCase = new TestCase(
|
||||
steps,
|
||||
true,
|
||||
() => {
|
||||
killMiner();
|
||||
killZn();
|
||||
}
|
||||
);
|
||||
|
||||
const outcome = await runTest(testCase, apis, paraLog);
|
||||
expect(outcome).toEqual(EventOutcome.Done);
|
||||
},
|
||||
{ timeout: GlobalTimeout * 10 }
|
||||
);
|
||||
@@ -0,0 +1,32 @@
|
||||
import { test, expect } from "bun:test";
|
||||
import { Presets } from "../src";
|
||||
import { runPresetUntilLaunched, spawnMiner } from "../src/cmd";
|
||||
import { EventOutcome, runTest, TestCase } from "../src/test-case";
|
||||
import {
|
||||
getApis,
|
||||
GlobalTimeout,
|
||||
} from "../src/utils";
|
||||
import { commonSignedSteps } from "./common";
|
||||
|
||||
const PRESET: Presets = Presets.FakeDev;
|
||||
test(
|
||||
`signed solution on ${PRESET}`,
|
||||
async () => {
|
||||
const { killZn, paraLog } = await runPresetUntilLaunched(PRESET);
|
||||
const apis = await getApis();
|
||||
const killMiner = await spawnMiner();
|
||||
|
||||
const testCase = new TestCase(
|
||||
commonSignedSteps(4, 10, apis),
|
||||
true,
|
||||
() => {
|
||||
killMiner();
|
||||
killZn();
|
||||
}
|
||||
);
|
||||
|
||||
const outcome = await runTest(testCase, apis, paraLog);
|
||||
expect(outcome).toEqual(EventOutcome.Done);
|
||||
},
|
||||
{ timeout: GlobalTimeout }
|
||||
);
|
||||
@@ -0,0 +1,30 @@
|
||||
import { test, expect } from "bun:test";
|
||||
import { Presets } from "../src";
|
||||
import { runPresetUntilLaunched, spawnMiner } from "../src/cmd";
|
||||
import { EventOutcome, runTest, TestCase } from "../src/test-case";
|
||||
import { getApis, GlobalTimeout} from "../src/utils";
|
||||
import { commonSignedSteps } from "./common";
|
||||
|
||||
const PRESET: Presets = Presets.FakeDot;
|
||||
|
||||
test(
|
||||
`signed solution on ${PRESET}`,
|
||||
async () => {
|
||||
const { killZn, paraLog } = await runPresetUntilLaunched(PRESET);
|
||||
const apis = await getApis();
|
||||
const killMiner = await spawnMiner();
|
||||
|
||||
const testCase = new TestCase(
|
||||
commonSignedSteps(32, 500, apis),
|
||||
true,
|
||||
() => {
|
||||
killMiner();
|
||||
killZn();
|
||||
}
|
||||
);
|
||||
|
||||
const outcome = await runTest(testCase, apis, paraLog);
|
||||
expect(outcome).toEqual(EventOutcome.Done);
|
||||
},
|
||||
{ timeout: GlobalTimeout }
|
||||
);
|
||||
@@ -0,0 +1,30 @@
|
||||
import { test, expect } from "bun:test";
|
||||
import { Presets } from "../src";
|
||||
import { runPresetUntilLaunched, spawnMiner } from "../src/cmd";
|
||||
import { EventOutcome, runTest, TestCase } from "../src/test-case";
|
||||
import { getApis, GlobalTimeout } from "../src/utils";
|
||||
import { commonSignedSteps } from "./common";
|
||||
|
||||
const PRESET: Presets = Presets.FakeKsm;
|
||||
|
||||
test(
|
||||
`signed solution on ${PRESET}`,
|
||||
async () => {
|
||||
const { killZn, paraLog } = await runPresetUntilLaunched(PRESET);
|
||||
const apis = await getApis();
|
||||
const killMiner = await spawnMiner();
|
||||
|
||||
const testCase = new TestCase(
|
||||
commonSignedSteps(16, 1000, apis),
|
||||
true,
|
||||
() => {
|
||||
killMiner();
|
||||
killZn();
|
||||
}
|
||||
);
|
||||
|
||||
const outcome = await runTest(testCase, apis, paraLog);
|
||||
expect(outcome).toEqual(EventOutcome.Done);
|
||||
},
|
||||
{ timeout: GlobalTimeout }
|
||||
);
|
||||
@@ -0,0 +1,103 @@
|
||||
import { test, expect } from "bun:test";
|
||||
import { Presets } from "../src";
|
||||
import { runPresetUntilLaunched } from "../src/cmd";
|
||||
import { Chain, EventOutcome, Observe, runTest, TestCase } from "../src/test-case";
|
||||
import { alice, getApis, GlobalTimeout, logger, nullifySigned, aliceStash } from "../src/utils";
|
||||
|
||||
// Note the `RealM` preset as this test MUST run with enough validators such that we can disable one.
|
||||
const PRESET: Presets = Presets.RealM;
|
||||
|
||||
test(
|
||||
`slashing with disabling on ${PRESET}`,
|
||||
async () => {
|
||||
const { killZn, paraLog } = await runPresetUntilLaunched(PRESET);
|
||||
const apis = await getApis();
|
||||
let aliceExposedNominators = 0;
|
||||
let pages = 0;
|
||||
|
||||
const steps = [
|
||||
// first relay session change at block 11
|
||||
Observe.on(Chain.Relay, "Session", "NewSession").byBlock(11),
|
||||
// eventually AH will will be instructed to plan a new session.
|
||||
Observe.on(Chain.Teyrchain, "Staking", "SessionRotated")
|
||||
.withDataCheck((x: any) => x.active_era == 0 && x.planned_era == 1)
|
||||
.onPass(() => {
|
||||
nullifySigned(apis.paraApi);
|
||||
}),
|
||||
// Eventually we will receive an activation timestamp in AH, meaning the first era was complete.
|
||||
Observe.on(Chain.Teyrchain, "StakingRcClient", "SessionReportReceived")
|
||||
.withDataCheck((x) => x.activation_timestamp !== undefined)
|
||||
.onPass(() => {
|
||||
// upon completion, submit a slash to rc
|
||||
logger.info("Submitting slash to RC");
|
||||
const call = apis.rcApi.tx.RootOffences.create_offence({
|
||||
offenders: [
|
||||
// alice//Stash, 50%, which will cause any disabling. See `DisablingStrategy` in `./runtimes/teyrchain`.
|
||||
[aliceStash, 500000000],
|
||||
],
|
||||
maybe_identifications: undefined,
|
||||
maybe_session_index: undefined,
|
||||
}).decodedCall;
|
||||
apis.rcApi.tx.Sudo.sudo({ call })
|
||||
.signAndSubmit(alice)
|
||||
.then((res: any) => {
|
||||
logger.verbose("Slash submission result:", res.ok);
|
||||
});
|
||||
}),
|
||||
// we will receive the root offence event
|
||||
Observe.on(Chain.Relay, "RootOffences", "OffenceCreated"),
|
||||
// Session pallet has insta-disabled this validator.
|
||||
Observe.on(Chain.Relay, "Session", "ValidatorDisabled").withDataCheck(
|
||||
(x: any) => x.validator === aliceStash
|
||||
),
|
||||
|
||||
// Rest of the events are the same as in the non-disabling test.
|
||||
|
||||
// eventually we will receive an offence in the teyrchain, first the rc-client
|
||||
Observe.on(Chain.Teyrchain, "StakingRcClient", "OffenceReceived").withDataCheck(
|
||||
(x: any) => x.offences_count === 1
|
||||
),
|
||||
// then staking
|
||||
Observe.on(Chain.Teyrchain, "Staking", "OffenceReported")
|
||||
.withDataCheck((x: any) => x.offence_era === 1 && x.fraction === 500000000)
|
||||
.onPass(async () => {
|
||||
// let's calculate how many pages of exposure alice has -- this will impact the number of next events.
|
||||
const overview = await apis.paraApi.query.Staking.ErasStakersOverview.getValue(
|
||||
1,
|
||||
aliceStash
|
||||
);
|
||||
pages = overview?.page_count || 0;
|
||||
aliceExposedNominators = overview?.nominator_count || 0;
|
||||
// TODO: lazily create the `Slashed` and `SlashComputed` based on this
|
||||
logger.verbose(
|
||||
`Alice has ${aliceExposedNominators} exposed nominators (${pages}) whom we expect to slash later`
|
||||
);
|
||||
}),
|
||||
|
||||
// then staking will calculate the slashes, we only check 1 page
|
||||
Observe.on(Chain.Teyrchain, "Staking", "SlashComputed").withDataCheck(
|
||||
(x: any) => x.page === 0
|
||||
),
|
||||
|
||||
// staking will eventually bump to active era 2, where slashes will be applied.
|
||||
Observe.on(Chain.Teyrchain, "Staking", "EraPaid"),
|
||||
Observe.on(Chain.Teyrchain, "Staking", "SessionRotated").withDataCheck(
|
||||
(x: any) => x.active_era === 2
|
||||
),
|
||||
|
||||
// staking will apply slashes, we only check one slash.
|
||||
Observe.on(Chain.Teyrchain, "Staking", "Slashed"),
|
||||
];
|
||||
const testCase = new TestCase(
|
||||
steps.map((s) => s.build()),
|
||||
true,
|
||||
() => {
|
||||
killZn();
|
||||
}
|
||||
);
|
||||
|
||||
const outcome = await runTest(testCase, apis, paraLog);
|
||||
expect(outcome).toEqual(EventOutcome.Done);
|
||||
},
|
||||
{ timeout: GlobalTimeout }
|
||||
);
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
import { test, expect } from "bun:test";
|
||||
import { Presets } from "../src";
|
||||
import { runPresetUntilLaunched } from "../src/cmd";
|
||||
import { Chain, EventOutcome, Observe, runTest, TestCase } from "../src/test-case";
|
||||
import { alice, getApis, GlobalTimeout, logger, nullifySigned, aliceStash } from "../src/utils";
|
||||
|
||||
const PRESET: Presets = Presets.RealS;
|
||||
|
||||
test(
|
||||
`slashing without disabling on ${PRESET}`,
|
||||
async () => {
|
||||
const { killZn, paraLog } = await runPresetUntilLaunched(PRESET);
|
||||
const apis = await getApis();
|
||||
let aliceExposedNominators = 0;
|
||||
let pages = 0;
|
||||
|
||||
const steps = [
|
||||
// first relay session change at block 11
|
||||
Observe.on(Chain.Relay, "Session", "NewSession").byBlock(11),
|
||||
// eventually AH will will be instructed to plan a new session.
|
||||
Observe.on(Chain.Teyrchain, "Staking", "SessionRotated")
|
||||
.withDataCheck((x: any) => x.active_era == 0 && x.planned_era == 1)
|
||||
.onPass(() => {
|
||||
nullifySigned(apis.paraApi);
|
||||
}),
|
||||
// Eventually we will receive an activation timestamp in AH, meaning the first era was complete.
|
||||
Observe.on(Chain.Teyrchain, "StakingRcClient", "SessionReportReceived")
|
||||
.withDataCheck((x) => x.activation_timestamp !== undefined)
|
||||
.onPass(() => {
|
||||
// upon completion, submit a slash to rc
|
||||
logger.info("Submitting slash to RC");
|
||||
const call = apis.rcApi.tx.RootOffences.create_offence({
|
||||
offenders: [
|
||||
// alice//Stash, 10%, which will NOT cause any disabling. See `DisablingStrategy` in `./runtimes/teyrchain`.
|
||||
[aliceStash, 100000000],
|
||||
],
|
||||
maybe_identifications: undefined,
|
||||
maybe_session_index: undefined,
|
||||
}).decodedCall;
|
||||
apis.rcApi.tx.Sudo.sudo({ call })
|
||||
.signAndSubmit(alice)
|
||||
.then((res: any) => {
|
||||
logger.verbose("Slash submission result:", res.ok);
|
||||
});
|
||||
}),
|
||||
// we will receive the root offence event
|
||||
Observe.on(Chain.Relay, "RootOffences", "OffenceCreated"),
|
||||
|
||||
// eventually we will receive an offence in the teyrchain, first the rc-client
|
||||
Observe.on(Chain.Teyrchain, "StakingRcClient", "OffenceReceived").withDataCheck(
|
||||
(x: any) => x.offences_count === 1
|
||||
),
|
||||
// then staking
|
||||
Observe.on(Chain.Teyrchain, "Staking", "OffenceReported")
|
||||
.withDataCheck((x: any) => x.offence_era === 1 && x.fraction === 100000000)
|
||||
.onPass(async () => {
|
||||
// let's calculate how many pages of exposure alice has -- this will impact the number of next events.
|
||||
const overview = await apis.paraApi.query.Staking.ErasStakersOverview.getValue(
|
||||
1,
|
||||
aliceStash
|
||||
);
|
||||
pages = overview?.page_count || 0;
|
||||
aliceExposedNominators = overview?.nominator_count || 0;
|
||||
// TODO: lazily create the `Slashed` and `SlashComputed` based on this
|
||||
logger.verbose(
|
||||
`Alice has ${aliceExposedNominators} exposed nominators (${pages}) whom we expect to slash later`
|
||||
);
|
||||
}),
|
||||
|
||||
// then staking will calculate the slasheh, we only check 1 page
|
||||
Observe.on(Chain.Teyrchain, "Staking", "SlashComputed").withDataCheck(
|
||||
(x: any) => x.page === 0
|
||||
),
|
||||
|
||||
// staking will eventually bump to active era 2, where slashes will be applied.
|
||||
Observe.on(Chain.Teyrchain, "Staking", "EraPaid"),
|
||||
Observe.on(Chain.Teyrchain, "Staking", "SessionRotated").withDataCheck(
|
||||
(x: any) => x.active_era === 2
|
||||
),
|
||||
|
||||
// staking will apply slashes, we only check one slash.
|
||||
Observe.on(Chain.Teyrchain, "Staking", "Slashed"),
|
||||
];
|
||||
const testCase = new TestCase(
|
||||
steps.map((s) => s.build()),
|
||||
true,
|
||||
() => {
|
||||
killZn();
|
||||
}
|
||||
);
|
||||
|
||||
const outcome = await runTest(testCase, apis, paraLog);
|
||||
expect(outcome).toEqual(EventOutcome.Done);
|
||||
},
|
||||
{ timeout: GlobalTimeout }
|
||||
);
|
||||
@@ -0,0 +1,97 @@
|
||||
import { test, expect } from "bun:test";
|
||||
import { Presets } from "../src";
|
||||
import { runPresetUntilLaunched } from "../src/cmd";
|
||||
import { Chain, EventOutcome, Observe, runTest, TestCase } from "../src/test-case";
|
||||
import { alice, getApis, GlobalTimeout, logger, nullifySigned, aliceStash, derivePubkeyFrom, ss58 } from "../src/utils";
|
||||
|
||||
const PRESET: Presets = Presets.RealS;
|
||||
|
||||
test(
|
||||
`slashing spam test on ${PRESET}`,
|
||||
async () => {
|
||||
const { killZn, paraLog } = await runPresetUntilLaunched(PRESET);
|
||||
const apis = await getApis();
|
||||
// total number of offences to send.
|
||||
const target = 1000;
|
||||
// the size of each batch.
|
||||
const batchSize = 100;
|
||||
const numBatches = Math.ceil(target / batchSize);
|
||||
let sent = 0;
|
||||
let received = 0;
|
||||
// onchain-page-size for offence queueing in RC is 50, so we expect 20 pages for 1000 offences.
|
||||
|
||||
const steps = [
|
||||
// first relay session change at block 11, just a sanity check
|
||||
Observe.on(Chain.Relay, "Session", "NewSession")
|
||||
.byBlock(11)
|
||||
.onPass(async () => {
|
||||
logger.info(`Submitting ${target} offences in batches of ${batchSize}`);
|
||||
|
||||
// Calculate number of batches needed
|
||||
|
||||
let nonce = await apis.rcApi.apis.AccountNonceApi.account_nonce(ss58(alice.publicKey));
|
||||
logger.info(`Alice nonce at start: ${nonce}`);
|
||||
|
||||
for (let batchIndex = 0; batchIndex < numBatches; batchIndex++) {
|
||||
const start = batchIndex * batchSize;
|
||||
const end = Math.min(start + batchSize, target);
|
||||
const currentBatchSize = end - start;
|
||||
|
||||
logger.info(`Processing batch ${batchIndex + 1}/${numBatches}: offences ${start} to ${end - 1}`);
|
||||
|
||||
// Create batch of offence calls
|
||||
const offenceCalls = Array.from({ length: currentBatchSize }, (_, i) => {
|
||||
const offenceIndex = start + i;
|
||||
logger.debug(`Preparing offence ${offenceIndex}: ${derivePubkeyFrom(`//${offenceIndex}`)}`);
|
||||
|
||||
return apis.rcApi.tx.RootOffences.report_offence({
|
||||
offences: [[
|
||||
[derivePubkeyFrom(`//${offenceIndex}`), { total: BigInt(0), own: BigInt(0), others: [] }],
|
||||
0, // session index
|
||||
BigInt(offenceIndex), // time slot, each being unique
|
||||
100000000 // slash ppm
|
||||
]]
|
||||
}).decodedCall;
|
||||
});
|
||||
|
||||
// Submit this batch as a single transaction
|
||||
try {
|
||||
const batchCall = apis.rcApi.tx.Utility.force_batch({ calls: offenceCalls }).decodedCall;
|
||||
const result = apis.rcApi.tx.Sudo.sudo({ call: batchCall })
|
||||
.signAndSubmit(alice, { at: "best", nonce: nonce });
|
||||
|
||||
logger.info(`Batch ${batchIndex + 1} submitted`);
|
||||
nonce += 1;
|
||||
sent += currentBatchSize;
|
||||
|
||||
} catch (error) {
|
||||
logger.error(`Batch ${batchIndex + 1} failed:`, error);
|
||||
// Continue with next batch even if this one fails
|
||||
}
|
||||
}
|
||||
}),
|
||||
|
||||
// in the meantime, we expect to see on the AH side:
|
||||
...Array.from({ length: 20 }, (_, __) =>
|
||||
Observe.on(Chain.Teyrchain, "StakingRcClient", "OffenceReceived").withDataCheck((x) => {
|
||||
received += x.offences_count;
|
||||
return true
|
||||
}),
|
||||
),
|
||||
];
|
||||
|
||||
const testCase = new TestCase(
|
||||
steps.map((s) => s.build()),
|
||||
true,
|
||||
() => {
|
||||
logger.info(`Test completed. Created ${sent} offences, processed ${received} in teyrchain`);
|
||||
killZn();
|
||||
}
|
||||
);
|
||||
|
||||
const outcome = await runTest(testCase, apis, paraLog);
|
||||
expect(outcome).toEqual(EventOutcome.Done);
|
||||
expect(sent).toEqual(received);
|
||||
},
|
||||
{ timeout: GlobalTimeout * 2 } // Double timeout for this complex test
|
||||
);
|
||||
@@ -0,0 +1,26 @@
|
||||
import { test, expect } from "bun:test";
|
||||
import { Presets } from "../src";
|
||||
import { runPresetUntilLaunched } from "../src/cmd";
|
||||
import { EventOutcome, runTest, TestCase } from "../src/test-case";
|
||||
import { getApis, GlobalTimeout} from "../src/utils";
|
||||
import { commonUnsignedSteps } from "./common";
|
||||
|
||||
const PRESET: Presets = Presets.FakeDev;
|
||||
|
||||
test(
|
||||
`unsigned solution on ${PRESET}`,
|
||||
async () => {
|
||||
const { killZn, paraLog } = await runPresetUntilLaunched(PRESET);
|
||||
|
||||
const apis = await getApis();
|
||||
const steps = commonUnsignedSteps(10, 4, 4, false, apis);
|
||||
|
||||
const testCase = new TestCase(steps, true, () => {
|
||||
killZn();
|
||||
});
|
||||
|
||||
const outcome = await runTest(testCase, apis, paraLog);
|
||||
expect(outcome).toEqual(EventOutcome.Done);
|
||||
},
|
||||
{ timeout: GlobalTimeout }
|
||||
);
|
||||
@@ -0,0 +1,33 @@
|
||||
import { test, expect } from "bun:test";
|
||||
import { Presets } from "../src";
|
||||
import { runPresetUntilLaunched } from "../src/cmd";
|
||||
import { EventOutcome, runTest, TestCase } from "../src/test-case";
|
||||
import { getApis, GlobalTimeout } from "../src/utils";
|
||||
import { commonUnsignedSteps } from "./common";
|
||||
|
||||
const PRESET: Presets = Presets.FakeDot;
|
||||
|
||||
test(
|
||||
`unsigned solution on ${PRESET}`,
|
||||
async () => {
|
||||
const { killZn, paraLog } = await runPresetUntilLaunched(PRESET);
|
||||
const apis = await getApis();
|
||||
|
||||
const expectedValidatorSetCount = await apis.paraApi.query.Staking.ValidatorCount.getValue();
|
||||
const pages = 32;
|
||||
const minerPages = 4;
|
||||
const steps = commonUnsignedSteps(expectedValidatorSetCount, minerPages, pages, true, apis);
|
||||
|
||||
const testCase = new TestCase(
|
||||
steps,
|
||||
true,
|
||||
() => {
|
||||
killZn();
|
||||
}
|
||||
);
|
||||
|
||||
const outcome = await runTest(testCase, apis, paraLog);
|
||||
expect(outcome).toEqual(EventOutcome.Done);
|
||||
},
|
||||
{ timeout: GlobalTimeout }
|
||||
);
|
||||
@@ -0,0 +1,33 @@
|
||||
import { test, expect } from "bun:test";
|
||||
import { Presets } from "../src";
|
||||
import { runPresetUntilLaunched } from "../src/cmd";
|
||||
import { EventOutcome, runTest, TestCase } from "../src/test-case";
|
||||
import { getApis, GlobalTimeout } from "../src/utils";
|
||||
import { commonUnsignedSteps } from "./common";
|
||||
|
||||
const PRESET: Presets = Presets.FakeKsm;
|
||||
|
||||
test(
|
||||
`unsigned solution on ${PRESET}`,
|
||||
async () => {
|
||||
const { killZn, paraLog } = await runPresetUntilLaunched(PRESET);
|
||||
const apis = await getApis();
|
||||
|
||||
const expectedValidatorSetCount = await apis.paraApi.query.Staking.ValidatorCount.getValue();
|
||||
const pages = 16;
|
||||
const minerPages = 4;
|
||||
const steps = commonUnsignedSteps(expectedValidatorSetCount, minerPages, pages, true, apis);
|
||||
|
||||
const testCase = new TestCase(
|
||||
steps,
|
||||
true,
|
||||
() => {
|
||||
killZn();
|
||||
}
|
||||
);
|
||||
|
||||
const outcome = await runTest(testCase, apis, paraLog);
|
||||
expect(outcome).toEqual(EventOutcome.Done);
|
||||
},
|
||||
{ timeout: GlobalTimeout }
|
||||
);
|
||||
@@ -0,0 +1,243 @@
|
||||
import { test, expect } from "bun:test";
|
||||
import { Presets } from "../src";
|
||||
import { runPresetUntilLaunched } from "../src/cmd";
|
||||
import { Chain, EventOutcome, Observe, runTest, TestCase } from "../src/test-case";
|
||||
import { alice, aliceStash, deriveFrom, getApis, GlobalTimeout, logger, safeJsonStringify, ss58, type ApiDeclarations } from "../src/utils";
|
||||
import { DEV_PHRASE } from "@polkadot-labs/hdkd-helpers";
|
||||
import { FixedSizeBinary, type PolkadotSigner, type TxCall, type TxCallData, type TypedApi } from "polkadot-api";
|
||||
import { teyrchain, rc } from "@polkadot-api/descriptors";
|
||||
|
||||
const PRESET: Presets = Presets.FakeDev;
|
||||
|
||||
async function sendUp(api: TypedApi<typeof teyrchain>, count: number) {
|
||||
const calls: TxCallData[] = [];
|
||||
const ed = await api.constants.Balances.ExistentialDeposit();
|
||||
for (let i = 0; i < count; i++) {
|
||||
const account = deriveFrom(DEV_PHRASE, `//up_${i}`);
|
||||
const endowment = BigInt(1000) * ed;
|
||||
const teleport = endowment / BigInt(10);
|
||||
|
||||
const forceSetBalance = api.tx.Balances.force_set_balance({
|
||||
new_free: endowment,
|
||||
who: { type: "Id", value: ss58(account.publicKey) }
|
||||
});
|
||||
|
||||
const xcm = api.tx.PolkadotXcm.teleport_assets({
|
||||
dest: {
|
||||
type: "V5",
|
||||
value: {
|
||||
parents: 1,
|
||||
interior: {
|
||||
type: "Here",
|
||||
value: undefined
|
||||
}
|
||||
}
|
||||
},
|
||||
beneficiary: {
|
||||
type: "V5",
|
||||
value: {
|
||||
parents: 0,
|
||||
interior: {
|
||||
type: "X1",
|
||||
value: {
|
||||
type: "AccountId32",
|
||||
value: { id: new FixedSizeBinary(account.publicKey) }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
assets: {
|
||||
type: "V5",
|
||||
value: [
|
||||
{
|
||||
id: {
|
||||
parents: 1,
|
||||
interior: {
|
||||
type: "Here",
|
||||
value: undefined
|
||||
}
|
||||
},
|
||||
fun: {
|
||||
type: "Fungible",
|
||||
"value": teleport
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
fee_asset_id: {
|
||||
type: "V5",
|
||||
value: {
|
||||
parents: 1,
|
||||
interior: {
|
||||
type: "Here",
|
||||
value: undefined
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
const dispatchAs = api.tx.Utility.dispatch_as({
|
||||
as_origin: { type: "system", value: { type: "Signed", value: ss58(account.publicKey) } },
|
||||
call: xcm.decodedCall
|
||||
});
|
||||
|
||||
calls.push(forceSetBalance.decodedCall);
|
||||
calls.push(dispatchAs.decodedCall);
|
||||
}
|
||||
|
||||
const finalBatch = api.tx.Utility.batch_all({ calls });
|
||||
const finalSudo = api.tx.Sudo.sudo({ call: finalBatch.decodedCall });
|
||||
try {
|
||||
const res = await finalSudo.signAndSubmit(alice, { at: "best" });
|
||||
let success = 0;
|
||||
let failure = 0;
|
||||
res.events.forEach((e) => {
|
||||
logger.debug(safeJsonStringify(e.value));
|
||||
if (e.value.type === "DispatchedAs") {
|
||||
// @ts-ignore
|
||||
if (e.value.value.result.success) {
|
||||
success += 1
|
||||
} else {
|
||||
failure += 1
|
||||
}
|
||||
}
|
||||
});
|
||||
logger.info(`Sent ${count} upward messages, intercepted ${success + failure} events, ${success} succeeded, ${failure} failed`);
|
||||
} catch(e) {
|
||||
logger.warn(`Error sending upward messages: ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function sendDown(api: TypedApi<typeof rc>, count: number) {
|
||||
const calls: TxCallData[] = [];
|
||||
const ed = await api.constants.Balances.ExistentialDeposit();
|
||||
for (let i = 0; i < count; i++) {
|
||||
const account = deriveFrom(DEV_PHRASE, `//down_${i}`)
|
||||
const endowment = BigInt(1000) * ed;
|
||||
const teleport = endowment / BigInt(10);
|
||||
|
||||
const forceSetBalance = api.tx.Balances.force_set_balance({
|
||||
new_free: endowment,
|
||||
who: { type: "Id", value: ss58(account.publicKey) }
|
||||
})
|
||||
|
||||
const xcm = api.tx.XcmPallet.teleport_assets({
|
||||
dest: {
|
||||
type: "V5",
|
||||
value: {
|
||||
parents: 0,
|
||||
interior: {
|
||||
type: "X1",
|
||||
value: { type: "Teyrchain", value: 1100 }
|
||||
}
|
||||
}
|
||||
},
|
||||
beneficiary: {
|
||||
type: "V5",
|
||||
value: {
|
||||
parents: 0,
|
||||
interior: {
|
||||
type: "X1",
|
||||
value: {
|
||||
type: "AccountId32",
|
||||
value: { id: new FixedSizeBinary(account.publicKey) }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
assets: {
|
||||
type: "V5",
|
||||
value: [
|
||||
{
|
||||
id: {
|
||||
parents: 0,
|
||||
interior: {
|
||||
type: "Here",
|
||||
value: undefined
|
||||
}
|
||||
},
|
||||
fun: {
|
||||
type: "Fungible",
|
||||
value: teleport
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
fee_asset_id: {
|
||||
type: "V5",
|
||||
value: {
|
||||
parents: 0,
|
||||
interior: {
|
||||
type: "Here",
|
||||
value: undefined
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
const dispatchAs = api.tx.Utility.dispatch_as({
|
||||
as_origin: { type: "system", value: { type: "Signed", value: ss58(account.publicKey) } },
|
||||
call: xcm.decodedCall
|
||||
});
|
||||
calls.push(forceSetBalance.decodedCall);
|
||||
calls.push(dispatchAs.decodedCall);
|
||||
}
|
||||
|
||||
const finalBatch = api.tx.Utility.batch_all({ calls });
|
||||
const finalSudo = api.tx.Sudo.sudo({ call: finalBatch.decodedCall });
|
||||
try {
|
||||
const res = await finalSudo.signAndSubmit(alice, { at: "best" });
|
||||
let success = 0;
|
||||
let failure = 0;
|
||||
res.events.forEach((e) => {
|
||||
logger.verbose(safeJsonStringify(e.value));
|
||||
if (e.value.type === "DispatchedAs") {
|
||||
// @ts-ignore
|
||||
if (e.value.value.result.success) {
|
||||
success += 1
|
||||
} else {
|
||||
failure += 1
|
||||
}
|
||||
}
|
||||
});
|
||||
logger.info(`Sent ${count} downward messages, intercepted ${success + failure} events, ${success} succeeded, ${failure} failed`);
|
||||
} catch(e) {
|
||||
logger.warn(`Error sending downward messages: ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
test(
|
||||
`${PRESET} preset with vmp queues being spammed af`,
|
||||
async () => {
|
||||
const { killZn, paraLog } = await runPresetUntilLaunched(PRESET);
|
||||
|
||||
const apis = await getApis();
|
||||
// This test is meant to not run automatically, so most things are commented out.
|
||||
|
||||
// const downSub = apis.rcClient.blocks$.subscribe((block) => {
|
||||
// if (block.number > 10) {
|
||||
// logger.verbose(`spammer::down spamming at height ${block.number}`);
|
||||
// sendDown(apis.rcApi, (block.number * 10) + 50);
|
||||
// }
|
||||
// });
|
||||
// const upSub = apis.paraClient.blocks$.subscribe((block) => {
|
||||
// if (block.number > 0) {
|
||||
// logger.verbose(`spammer::up spamming at height ${block.number}`);
|
||||
// sendUp(apis.paraApi, 40);
|
||||
// }
|
||||
// });
|
||||
const steps: Observe[] = [
|
||||
Observe.on(Chain.Relay, "Session", "NewSession")
|
||||
.byBlock(11),
|
||||
// Observe.on(Chain.Relay, "WontReach", "WontReach")
|
||||
].map((s) => s.build());
|
||||
|
||||
const testCase = new TestCase(steps, true, () => {
|
||||
killZn();
|
||||
// downSub.unsubscribe();
|
||||
// upSub.unsubscribe()
|
||||
});
|
||||
|
||||
const outcome = await runTest(testCase, apis, paraLog);
|
||||
expect(outcome).toEqual(EventOutcome.Done);
|
||||
},
|
||||
{ timeout: GlobalTimeout }
|
||||
);
|
||||
Reference in New Issue
Block a user