Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/agustin-map-slashes' into jeremy…
Browse files Browse the repository at this point in the history
…-bench-slashes
  • Loading branch information
nanocryk committed Jan 10, 2025
2 parents 87103ab + 018dd75 commit 9dd47bb
Show file tree
Hide file tree
Showing 6 changed files with 300 additions and 1 deletion.
110 changes: 110 additions & 0 deletions chains/orchestrator-relays/runtime/dancelight/src/tests/slashes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,116 @@ fn test_slashes_are_sent_to_ethereum_accumulatedly() {
});
}

#[test]
fn test_slashes_are_sent_to_ethereum_accumulate_until_next_era() {
sp_tracing::try_init_simple();
ExtBuilder::default()
.with_balances(vec![
// Alice gets 10k extra tokens for her mapping deposit
(AccountId::from(ALICE), 210_000 * UNIT),
(AccountId::from(BOB), 100_000 * UNIT),
(AccountId::from(CHARLIE), 100_000 * UNIT),
(AccountId::from(DAVE), 100_000 * UNIT),
])
.build()
.execute_with(|| {
run_to_block(2);
let channel_id = PRIMARY_GOVERNANCE_CHANNEL.encode();

// Insert PRIMARY_GOVERNANCE_CHANNEL channel id into storage.
let mut combined_channel_id_key = Vec::new();
let hashed_key = twox_64(&channel_id);

combined_channel_id_key.extend_from_slice(&hashed_key);
combined_channel_id_key.extend_from_slice(PRIMARY_GOVERNANCE_CHANNEL.as_ref());

let mut full_storage_key = Vec::new();
full_storage_key.extend_from_slice(&frame_support::storage::storage_prefix(
b"EthereumSystem",
b"Channels",
));
full_storage_key.extend_from_slice(&combined_channel_id_key);

let channel = Channel {
agent_id: H256::default(),
para_id: 1000u32.into(),
};

frame_support::storage::unhashed::put(&full_storage_key, &channel);

// We can inject arbitraqry slashes for arbitary accounts with root
let page_limit: u32 = <Runtime as pallet_external_validator_slashes::Config>::QueuedSlashesProcessedPerBlock::get();

let blocks_in_era = crate::EpochDurationInBlocks::get() * SessionsPerEra::get();
let total_slashes_to_inject = blocks_in_era*page_limit +1;
for i in 0..total_slashes_to_inject {
assert_ok!(ExternalValidatorSlashes::force_inject_slash(
RuntimeOrigin::root(),
0,
AccountId::new(H256::from_low_u64_be(i as u64).to_fixed_bytes()),
Perbill::from_percent(75)
));
}

let deferred_era = ExternalValidators::current_era().unwrap() + SlashDeferDuration::get() + 1;

let slashes = ExternalValidatorSlashes::slashes(deferred_era);
assert_eq!(slashes.len() as u32, total_slashes_to_inject);

let session_in_which_slashes_are_sent =
(ExternalValidators::current_era().unwrap() + SlashDeferDuration::get() + 1)
* SessionsPerEra::get();
run_to_session(session_in_which_slashes_are_sent);

let outbound_msg_queue_event = System::events()
.iter()
.filter(|r| match r.event {
RuntimeEvent::EthereumOutboundQueue(
snowbridge_pallet_outbound_queue::Event::MessageQueued { .. },
) => true,
_ => false,
})
.count();

// We have two reasons for sending messages:
// 1, because on_era_end sends rewards
// 2, because on_era_start sends slashes
// Both session ends and session starts are done on_initialize of frame-sesssion
assert_eq!(
outbound_msg_queue_event, 1,
"MessageQueued event should be emitted"
);

// We still have all slashes as unprocessed
let unprocessed_slashes = ExternalValidatorSlashes::unreported_slashes();
assert_eq!(unprocessed_slashes.len() as u32, total_slashes_to_inject);

// Running to the next era, but we should still have unprocessed
run_to_session((ExternalValidators::current_era().unwrap() +1)*SessionsPerEra::get());

let unprocessed_slashes = ExternalValidatorSlashes::unreported_slashes();

// We still should have one pending unprocessed slash, to be sent in the next block
assert_eq!(unprocessed_slashes.len() as u32, 1);

// And in this case, we have 2 events
// the rewards one plus the one where we sent remaining slashes
let outbound_msg_queue_event = System::events()
.iter()
.filter(|r| match r.event {
RuntimeEvent::EthereumOutboundQueue(
snowbridge_pallet_outbound_queue::Event::MessageQueued { .. },
) => true,
_ => false,
})
.count();
assert_eq!(
outbound_msg_queue_event, 2,
"MessageQueued event should be emitted"
);
});
}

fn inject_babe_slash(seed: &str) {
let babe_key = get_pair_from_seed::<babe_primitives::AuthorityId>(seed);
let equivocation_proof = generate_babe_equivocation_proof(&babe_key);
Expand Down
2 changes: 1 addition & 1 deletion pallets/external-validator-slashes/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ pub mod pallet {
type TimestampProvider: Get<u64>;

/// How many queued slashes are being processed per block.
#[pallet::constant]
type QueuedSlashesProcessedPerBlock: Get<u32>;

/// The weight information of this pallet.
Expand Down Expand Up @@ -475,7 +476,6 @@ impl<T: Config> OnEraStart for Pallet<T> {
// let's put 1000 as a conservative measure
const REMOVE_LIMIT: u32 = 1000;

log::info!("on era start");
let bonding_duration = T::BondingDuration::get();

BondedEras::<T>::mutate(|bonded| {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import "@tanssi/api-augment";
import { describeSuite, expect, beforeAll } from "@moonwall/cli";
import { ApiPromise } from "@polkadot/api";
import { KeyringPair, generateKeyringPair } from "@moonwall/util";
import { Keyring } from "@polkadot/keyring";
import { jumpToSession } from "../../../util/block";
import { PRIMARY_GOVERNANCE_CHANNEL_ID } from "../../../util/constants";

describeSuite({
id: "DTR1307",
title: "Slashes are accumulated based on max slashes sent per block",
foundationMethods: "dev",
testCases: ({ it, context }) => {
let polkadotJs: ApiPromise;
let alice: KeyringPair;
let aliceBabePair: KeyringPair;

Check failure on line 16 in test/suites/dev-tanssi-relay/slashes/test_slashes_are_acummulated_across_blocks.ts

View workflow job for this annotation

GitHub Actions / typescript-linting

'aliceBabePair' is assigned a value but never used
let aliceStash: KeyringPair;

Check failure on line 17 in test/suites/dev-tanssi-relay/slashes/test_slashes_are_acummulated_across_blocks.ts

View workflow job for this annotation

GitHub Actions / typescript-linting

'aliceStash' is assigned a value but never used
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: "Slashes are accumulated across blocks",
test: async function () {
// we need to start at least one sesssion to start eras
await jumpToSession(context, 1);
// Let's inject slashes N+1 slashes, where N is the max slashes to send per block
// With force inject slash, we can inject a slash for any account
const maxSlashesPerMessage = (await polkadotJs.consts.externalValidatorSlashes.queuedSlashesProcessedPerBlock).toNumber();
const slashesToInject = maxSlashesPerMessage +1;
let aliceNonce = (await polkadotJs.query.system.account(alice.address)).nonce.toNumber();

for (let i = 0; i < slashesToInject; i++) {
const randomAccount = generateKeyringPair("sr25519");
await polkadotJs.tx.sudo
.sudo(polkadotJs.tx.externalValidatorSlashes.forceInjectSlash(0, randomAccount.address, 1000))
.signAndSend(alice, { nonce: aliceNonce++ });
}
await context.createBlock();

// Slash item should be there
const DeferPeriod = (await polkadotJs.consts.externalValidatorSlashes.slashDeferDuration).toNumber();

// scheduled slashes
const expectedSlashes = await polkadotJs.query.externalValidatorSlashes.slashes(DeferPeriod + 1);
expect(expectedSlashes.length).to.be.eq(slashesToInject);

const sessionsPerEra = await polkadotJs.consts.externalValidators.sessionsPerEra;

const currentIndex = await polkadotJs.query.session.currentIndex();

const targetSession = currentIndex * (sessionsPerEra * (DeferPeriod + 1));

await jumpToSession(context, targetSession);

// scheduled slashes
const expectedSlashesAfterDefer = await polkadotJs.query.externalValidatorSlashes.slashes(
DeferPeriod + 1
);
// We should have unprocessed messages
const expectedUnprocessedMessages = await polkadotJs.query.externalValidatorSlashes.unreportedSlashesQueue();
expect (expectedUnprocessedMessages.length).to.be.eq(slashesToInject);
expect(expectedSlashesAfterDefer.length).to.be.eq(slashesToInject);
expect(expectedSlashesAfterDefer[0].confirmed.toHuman()).to.be.true;

// In the next block we should send the slashes. For this we will confirm:
// A: that the unprocessed slashes decrease
// B: that the nonce of the primary channel increases
const primaryChannelNonceBefore = await polkadotJs.query.ethereumOutboundQueue.nonce(PRIMARY_GOVERNANCE_CHANNEL_ID)

await context.createBlock();
const expectedUnprocessedMessagesAfterOneBlock = await polkadotJs.query.externalValidatorSlashes.unreportedSlashesQueue();
const primaryChannelNonceAfter = await polkadotJs.query.ethereumOutboundQueue.nonce(PRIMARY_GOVERNANCE_CHANNEL_ID);
expect (primaryChannelNonceAfter.toBigInt()).toBe(primaryChannelNonceBefore.toBigInt()+ 1n);
// However we stil should have one unprocessed message
expect (expectedUnprocessedMessagesAfterOneBlock.length).to.be.eq(1);
},
});
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import "@tanssi/api-augment";
import { describeSuite, expect, beforeAll } from "@moonwall/cli";
import { ApiPromise } from "@polkadot/api";
import { KeyringPair, generateKeyringPair } from "@moonwall/util";
import { Keyring } from "@polkadot/keyring";
import { jumpToSession } from "../../../util/block";
import { PRIMARY_GOVERNANCE_CHANNEL_ID } from "../../../util/constants";

Check failure on line 7 in test/suites/dev-tanssi-relay/slashes/test_slashes_are_acummulated_across_eras.ts

View workflow job for this annotation

GitHub Actions / typescript-linting

'PRIMARY_GOVERNANCE_CHANNEL_ID' is defined but never used

describeSuite({
id: "DTR1308",
title: "Slashes are accumulated across eras based on max slashes sent per block",
foundationMethods: "dev",
testCases: ({ it, context }) => {
let polkadotJs: ApiPromise;
let alice: KeyringPair;
let aliceBabePair: KeyringPair;

Check failure on line 16 in test/suites/dev-tanssi-relay/slashes/test_slashes_are_acummulated_across_eras.ts

View workflow job for this annotation

GitHub Actions / typescript-linting

'aliceBabePair' is assigned a value but never used
let aliceStash: KeyringPair;

Check failure on line 17 in test/suites/dev-tanssi-relay/slashes/test_slashes_are_acummulated_across_eras.ts

View workflow job for this annotation

GitHub Actions / typescript-linting

'aliceStash' is assigned a value but never used
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: "Slashes are accumulated across eras",
test: async function () {
// we need to start at least one sesssion to start eras
await jumpToSession(context, 1);
// Let's inject slashes N+1 slashes, where N is the max slashes to send per block
// With force inject slash, we can inject a slash for any account
const maxSlashesPerMessage = (await polkadotJs.consts.externalValidatorSlashes.queuedSlashesProcessedPerBlock).toNumber();
const epochDuration = (await polkadotJs.consts.babe.epochDuration).toNumber();
const sessionsPerEra = await polkadotJs.consts.externalValidators.sessionsPerEra;

const slashesToInject = (maxSlashesPerMessage*epochDuration*sessionsPerEra.toNumber()) +1;
let aliceNonce = (await polkadotJs.query.system.account(alice.address)).nonce.toNumber();

for (let i = 0; i < slashesToInject; i++) {
const randomAccount = generateKeyringPair("sr25519");
await polkadotJs.tx.sudo
.sudo(polkadotJs.tx.externalValidatorSlashes.forceInjectSlash(0, randomAccount.address, 1000))
.signAndSend(alice, { nonce: aliceNonce++ });
}
await context.createBlock();

// Slash item should be there
const DeferPeriod = (await polkadotJs.consts.externalValidatorSlashes.slashDeferDuration).toNumber();

// scheduled slashes
const expectedSlashes = await polkadotJs.query.externalValidatorSlashes.slashes(DeferPeriod + 1);
expect(expectedSlashes.length).to.be.eq(slashesToInject);


const currentIndex = await polkadotJs.query.session.currentIndex();

const targetSession = currentIndex * (sessionsPerEra * (DeferPeriod + 1));

await jumpToSession(context, targetSession);

// scheduled slashes
const expectedSlashesAfterDefer = await polkadotJs.query.externalValidatorSlashes.slashes(
DeferPeriod + 1
);
// We should have unprocessed messages
const expectedUnprocessedMessages = await polkadotJs.query.externalValidatorSlashes.unreportedSlashesQueue();
expect (expectedUnprocessedMessages.length).to.be.eq(slashesToInject);
expect(expectedSlashesAfterDefer.length).to.be.eq(slashesToInject);
expect(expectedSlashesAfterDefer[0].confirmed.toHuman()).to.be.true;

// Now we will jump one entire era
// After one era, we should still have one slash to send
const currentIndexAfterSlashes = await polkadotJs.query.session.currentIndex();
const targetSessionToNextEra = currentIndexAfterSlashes.toNumber() + sessionsPerEra.toNumber();
console.log(currentIndexAfterSlashes.toBigInt())
console.log(sessionsPerEra.toBigInt())
console.log(targetSessionToNextEra)

await jumpToSession(context, targetSessionToNextEra);

// However we stil should have one unprocessed message
const expectedUnprocessedMessagesAfterOneEra = await polkadotJs.query.externalValidatorSlashes.unreportedSlashesQueue();
expect (expectedUnprocessedMessagesAfterOneEra.length).to.be.eq(1);
},
});
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Keyring } from "@polkadot/keyring";
import { u8aToHex } from "@polkadot/util";
import { jumpToSession } from "../../../util/block";
import { generateBabeEquivocationProof } from "../../../util/slashes";
import { PRIMARY_GOVERNANCE_CHANNEL_ID } from "../../../util/constants";

describeSuite({
id: "DTR1304",
Expand Down Expand Up @@ -90,8 +91,22 @@ describeSuite({
const expectedSlashesAfterDefer = await polkadotJs.query.externalValidatorSlashes.slashes(
DeferPeriod + 1
);
// We should have unprocessed messages
const expectedUnprocessedMessages = await polkadotJs.query.externalValidatorSlashes.unreportedSlashesQueue();
expect (expectedUnprocessedMessages.length).to.be.eq(1);
expect(expectedSlashesAfterDefer.length).to.be.eq(1);
expect(expectedSlashesAfterDefer[0].confirmed.toHuman()).to.be.true;

// In the next block we should send the slashes. For this we will confirm:
// A: that the unprocessed slashes decrease
// B: that the nonce of the primary channel increases
const primaryChannelNonceBefore = await polkadotJs.query.ethereumOutboundQueue.nonce(PRIMARY_GOVERNANCE_CHANNEL_ID)

await context.createBlock();
const expectedUnprocessedMessagesAfterOneBlock = await polkadotJs.query.externalValidatorSlashes.unreportedSlashesQueue();
const primaryChannelNonceAfter = await polkadotJs.query.ethereumOutboundQueue.nonce(PRIMARY_GOVERNANCE_CHANNEL_ID);
expect (primaryChannelNonceAfter.toBigInt()).toBe(primaryChannelNonceBefore.toBigInt()+ 1n);
expect (expectedUnprocessedMessagesAfterOneBlock.length).to.be.eq(0);
},
});
},
Expand Down
2 changes: 2 additions & 0 deletions test/util/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ export const STATEMINT_LOCATION_EXAMPLE = {
X3: [{ Parachain: 1000 }, { PalletInstance: 50 }, { GeneralIndex: 0n }],
},
};

export const PRIMARY_GOVERNANCE_CHANNEL_ID="0x0000000000000000000000000000000000000000000000000000000000000001"

0 comments on commit 9dd47bb

Please sign in to comment.