referrerpolicy=no-referrer-when-downgrade

pallet_ahm_test/
lib.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18#[cfg(test)]
19pub mod ah;
20#[cfg(test)]
21pub mod rc;
22
23#[cfg(test)]
24pub mod shared;
25
26// shared tests.
27#[cfg(test)]
28mod tests {
29	use super::*;
30	use crate::{
31		ah::{rc_client_events_since_last_call, staking_events_since_last_call},
32		rc::RootOffences,
33	};
34	use ah_client::OperatingMode;
35	use frame::testing_prelude::*;
36	use frame_support::traits::Get;
37	use pallet_election_provider_multi_block as multi_block;
38	use pallet_staking as staking_classic;
39	use pallet_staking_async::{ActiveEra, ActiveEraInfo, Forcing};
40	use pallet_staking_async_ah_client::{self as ah_client, OffenceSendQueue};
41	use pallet_staking_async_rc_client as rc_client;
42
43	#[test]
44	fn rc_session_change_reported_to_ah() {
45		// sets up AH chain with current and active era.
46		shared::put_ah_state(ah::ExtBuilder::default().build());
47		shared::put_rc_state(rc::ExtBuilder::default().build());
48		// shared::RC_STATE.with(|state| *state.get_mut() = rc::ExtBuilder::default().build());
49
50		// initial state of ah
51		shared::in_ah(|| {
52			assert_eq!(frame_system::Pallet::<ah::Runtime>::block_number(), 1);
53			assert_eq!(pallet_staking_async::CurrentEra::<ah::Runtime>::get(), Some(0));
54			assert_eq!(
55				ActiveEra::<ah::Runtime>::get(),
56				Some(ActiveEraInfo { index: 0, start: Some(0) })
57			);
58		});
59
60		shared::in_rc(|| {
61			// initial state of rc
62			assert_eq!(ah_client::Mode::<rc::Runtime>::get(), OperatingMode::Active);
63			// go to session 1 in RC and test.
64			// when
65			assert!(frame_system::Pallet::<rc::Runtime>::block_number() == 1);
66
67			// given end session 0, start session 1, plan 2
68			rc::roll_until_matches(
69				|| pallet_session::CurrentIndex::<rc::Runtime>::get() == 1,
70				true,
71			);
72
73			// then
74			assert_eq!(frame_system::Pallet::<rc::Runtime>::block_number(), rc::Period::get());
75		});
76
77		shared::in_rc(|| {
78			// roll a few more sessions
79			rc::roll_until_matches(
80				|| pallet_session::CurrentIndex::<rc::Runtime>::get() == 4,
81				true,
82			);
83		});
84
85		shared::in_ah(|| {
86			// ah's rc-client has also progressed some blocks, equal to 4 sessions
87			assert_eq!(frame_system::Pallet::<ah::Runtime>::block_number(), 120);
88			// election is ongoing, and has just started
89			assert!(matches!(
90				multi_block::CurrentPhase::<ah::Runtime>::get(),
91				multi_block::Phase::Snapshot(_)
92			));
93		});
94
95		// go to session 5 in rc, and forward AH too.
96		shared::in_rc(|| {
97			rc::roll_until_matches(
98				|| pallet_session::CurrentIndex::<rc::Runtime>::get() == 5,
99				true,
100			);
101		});
102
103		// ah has bumped the current era, but not the active era
104		shared::in_ah(|| {
105			assert_eq!(pallet_staking_async::CurrentEra::<ah::Runtime>::get(), Some(1));
106			assert_eq!(
107				ActiveEra::<ah::Runtime>::get(),
108				Some(ActiveEraInfo { index: 0, start: Some(0) })
109			);
110		});
111
112		// go to session 6 in rc, and forward AH too.
113		shared::in_rc(|| {
114			rc::roll_until_matches(
115				|| pallet_session::CurrentIndex::<rc::Runtime>::get() == 6,
116				true,
117			);
118		});
119	}
120
121	#[test]
122	fn ah_takes_over_staking_post_migration() {
123		// SCENE (1): Pre AHM Migration
124		shared::put_rc_state(
125			rc::ExtBuilder::default()
126				.pre_migration()
127				// set session keys for all "potential" validators
128				.session_keys(vec![1, 2, 3, 4, 5, 6, 7, 8])
129				// set a very low `MaxOffenceBatchSize` to test batching behavior
130				.max_offence_batch_size(2)
131				.build(),
132		);
133		shared::put_ah_state(ah::ExtBuilder::default().build());
134
135		shared::in_rc(|| {
136			assert!(staking_classic::ActiveEra::<rc::Runtime>::get().is_none());
137
138			// - staking-classic is active on RC.
139			rc::roll_until_matches(
140				|| {
141					staking_classic::ActiveEra::<rc::Runtime>::get().map(|a| a.index).unwrap_or(0) ==
142						1
143				},
144				true,
145			);
146
147			// No offence exist so far
148			assert!(staking_classic::UnappliedSlashes::<rc::Runtime>::get(4).is_empty());
149
150			assert_ok!(RootOffences::create_offence(
151				rc::RuntimeOrigin::root(),
152				vec![(2, Perbill::from_percent(100))],
153				None,
154				None
155			));
156
157			// offence is expected to be deferred to era 1 + 3 = 4
158			assert_eq!(staking_classic::UnappliedSlashes::<rc::Runtime>::get(4).len(), 1);
159		});
160
161		// nothing happened in ah-staking so far
162		shared::in_ah(|| {
163			// Ensure AH does not receive any
164			// - offences
165			// - session change reports.
166			assert_eq!(shared::CounterRCAHNewOffence::get(), 0);
167			assert_eq!(shared::CounterRCAHSessionReport::get(), 0);
168
169			assert_eq!(ah::mock::staking_events_since_last_call(), vec![]);
170		});
171
172		// SCENE (2): AHM migration begins
173		let mut pre_migration_block_number = 0;
174		shared::in_rc(|| {
175			rc::roll_next();
176
177			let pre_migration_era_points =
178				staking_classic::ErasRewardPoints::<rc::Runtime>::get(1).total;
179
180			ah_client::Pallet::<rc::Runtime>::on_migration_start();
181			assert_eq!(ah_client::Mode::<rc::Runtime>::get(), OperatingMode::Buffered);
182
183			// get current session
184			let current_session = pallet_session::CurrentIndex::<rc::Runtime>::get();
185			pre_migration_block_number = frame_system::Pallet::<rc::Runtime>::block_number();
186
187			// assume migration takes at least one era
188			// go forward by more than `SessionsPerEra` sessions -- staking will not rotate a new
189			// era.
190			rc::roll_until_matches(
191				|| {
192					pallet_session::CurrentIndex::<rc::Runtime>::get() ==
193						current_session + ah::SessionsPerEra::get() + 1
194				},
195				true,
196			);
197			let migration_start_block_number = frame_system::Pallet::<rc::Runtime>::block_number();
198
199			// ensure era is still 1 on RC.
200			// (Session events are received by AHClient and never passed on to staking-classic once
201			// migration starts)
202			assert_eq!(staking_classic::ActiveEra::<rc::Runtime>::get().unwrap().index, 1);
203			// no new era is planned
204			assert_eq!(staking_classic::CurrentEra::<rc::Runtime>::get().unwrap(), 1);
205
206			// no new block author points accumulated
207			assert_eq!(
208				staking_classic::ErasRewardPoints::<rc::Runtime>::get(1).total,
209				pre_migration_era_points
210			);
211
212			// some validator points have been recorded in ah-client
213			assert_eq!(
214				ah_client::ValidatorPoints::<rc::Runtime>::iter().count(),
215				1,
216				"only 11 has authored blocks in rc"
217			);
218			assert_eq!(
219				ah_client::ValidatorPoints::<rc::Runtime>::get(&11),
220				(migration_start_block_number - pre_migration_block_number) as u32 *
221					<<rc::Runtime as ah_client::Config>::PointsPerBlock as Get<u32>>::get()
222			);
223
224			// Verify buffered mode doesn't send anything to AH
225			let offence_counter_before = shared::CounterRCAHNewOffence::get();
226
227			// ---------- Offences in session 12 (6 total) ----------
228			assert_eq!(pallet_session::CurrentIndex::<rc::Runtime>::get(), 12);
229
230			// 3 for validator 2
231			assert_ok!(RootOffences::create_offence(
232				rc::RuntimeOrigin::root(),
233				vec![(2, Perbill::from_percent(50))],
234				None,
235				None,
236			));
237			assert_ok!(RootOffences::create_offence(
238				rc::RuntimeOrigin::root(),
239				vec![(2, Perbill::from_percent(100))],
240				None,
241				None,
242			));
243			assert_ok!(RootOffences::create_offence(
244				rc::RuntimeOrigin::root(),
245				vec![(2, Perbill::from_percent(25))],
246				None,
247				None,
248			));
249
250			// 2 for validator 1
251			assert_ok!(RootOffences::create_offence(
252				rc::RuntimeOrigin::root(),
253				vec![(1, Perbill::from_percent(75))],
254				None,
255				None,
256			));
257			assert_ok!(RootOffences::create_offence(
258				rc::RuntimeOrigin::root(),
259				vec![(1, Perbill::from_percent(60))],
260				None,
261				None,
262			));
263
264			// one for validator 4
265			assert_ok!(RootOffences::create_offence(
266				rc::RuntimeOrigin::root(),
267				vec![(5, Perbill::from_percent(55))],
268				None,
269				None,
270			));
271
272			// Move to the next session to create offences in different sessions for batching test
273			rc::roll_to_next_session(false);
274
275			// ---------- Offences in session 13 (4 total) ----------
276			assert_eq!(pallet_session::CurrentIndex::<rc::Runtime>::get(), 13);
277
278			// 2 for validator 2
279			assert_ok!(RootOffences::create_offence(
280				rc::RuntimeOrigin::root(),
281				vec![(2, Perbill::from_percent(90))],
282				None,
283				None,
284			));
285			assert_ok!(RootOffences::create_offence(
286				rc::RuntimeOrigin::root(),
287				vec![(2, Perbill::from_percent(80))],
288				None,
289				None,
290			));
291			// 1 for validator 1
292			assert_ok!(RootOffences::create_offence(
293				rc::RuntimeOrigin::root(),
294				vec![(1, Perbill::from_percent(85))],
295				None,
296				None,
297			));
298			// 1 for validator 5
299			assert_ok!(RootOffences::create_offence(
300				rc::RuntimeOrigin::root(),
301				vec![(5, Perbill::from_percent(45))],
302				None,
303				None,
304			));
305
306			// Move to another session and create more offences
307			rc::roll_to_next_session(false);
308
309			// ---------- Offences in session 14 (3 total) ----------
310			assert_eq!(pallet_session::CurrentIndex::<rc::Runtime>::get(), 14);
311
312			// 1 for validator 2
313			assert_ok!(RootOffences::create_offence(
314				rc::RuntimeOrigin::root(),
315				vec![(2, Perbill::from_percent(70))],
316				None,
317				None,
318			));
319			// 1 for validator 1
320			assert_ok!(RootOffences::create_offence(
321				rc::RuntimeOrigin::root(),
322				vec![(1, Perbill::from_percent(65))],
323				None,
324				None,
325			));
326			// 1 for validator 5
327			assert_ok!(RootOffences::create_offence(
328				rc::RuntimeOrigin::root(),
329				vec![(5, Perbill::from_percent(40))],
330				None,
331				None,
332			));
333
334			// ---------- End of offences created so far (13 total) ----------
335
336			// Verify nothing was sent to AH in buffered mode
337			assert_eq!(
338				shared::CounterRCAHNewOffence::get(),
339				offence_counter_before,
340				"No offences should be sent to AH in buffered mode"
341			);
342
343			// no new unapplied slashes are created in staking-classic (other than the previously
344			// created).
345			assert_eq!(staking_classic::UnappliedSlashes::<rc::Runtime>::get(4).len(), 1);
346
347			// we have stored a total of 13 offences, in 7 pages
348			assert_eq!(OffenceSendQueue::<rc::Runtime>::count(), 13);
349			assert_eq!(OffenceSendQueue::<rc::Runtime>::pages(), 7);
350		});
351
352		// Ensure AH still does not receive any offence while migration is ongoing.
353		shared::in_ah(|| {
354			assert_eq!(shared::CounterRCAHNewOffence::get(), 0);
355			assert_eq!(shared::CounterRCAHSessionReport::get(), 0);
356
357			assert_eq!(ah::mock::staking_events_since_last_call(), vec![]);
358		});
359
360		// let's migrate state from RC::staking-classic to AH::staking-async
361		shared::migrate_state();
362
363		// SCENE (3): AHM migration ends.
364		shared::in_rc(|| {
365			// Before migration ends, verify we have 9 buffered offences across multiple sessions
366			assert_eq!(OffenceSendQueue::<rc::Runtime>::count(), 13);
367
368			ah_client::Pallet::<rc::Runtime>::on_migration_end();
369			assert_eq!(ah_client::Mode::<rc::Runtime>::get(), OperatingMode::Active);
370
371			// `MaxOffenceBatchSize` is set to 2 in this test, so we will send over the 13 offences
372			// in 7 next blocks.
373			assert_eq!(crate::rc::MaxOffenceBatchSize::get(), 2);
374
375			// in the first block we process the half finished page.
376			rc::roll_next();
377			assert_eq!(OffenceSendQueue::<rc::Runtime>::count(), 12);
378			assert_eq!(shared::CounterRCAHNewOffence::get(), 1);
379
380			// the rest are full pages of 2 offences each.
381			rc::roll_next();
382			assert_eq!(OffenceSendQueue::<rc::Runtime>::count(), 10);
383			assert_eq!(shared::CounterRCAHNewOffence::get(), 3);
384			rc::roll_next();
385			assert_eq!(OffenceSendQueue::<rc::Runtime>::count(), 8);
386			assert_eq!(shared::CounterRCAHNewOffence::get(), 5);
387			rc::roll_next();
388			assert_eq!(OffenceSendQueue::<rc::Runtime>::count(), 6);
389			assert_eq!(shared::CounterRCAHNewOffence::get(), 7);
390			rc::roll_next();
391			assert_eq!(OffenceSendQueue::<rc::Runtime>::count(), 4);
392			assert_eq!(shared::CounterRCAHNewOffence::get(), 9);
393			rc::roll_next();
394			assert_eq!(OffenceSendQueue::<rc::Runtime>::count(), 2);
395			assert_eq!(shared::CounterRCAHNewOffence::get(), 11);
396			rc::roll_next();
397			assert_eq!(OffenceSendQueue::<rc::Runtime>::count(), 0);
398			assert_eq!(shared::CounterRCAHNewOffence::get(), 13);
399		});
400
401		let mut post_migration_era_reward_points = 0;
402		shared::in_ah(|| {
403			// all offences are received by rc-client. The count of these events are not 13, because
404			// in each batch we group offenders by session. Note that the sum of offenders count is
405			// indeed 13.
406			assert_eq!(
407				rc_client_events_since_last_call(),
408				vec![
409					rc_client::Event::OffenceReceived { slash_session: 14, offences_count: 1 },
410					rc_client::Event::OffenceReceived { slash_session: 14, offences_count: 2 },
411					rc_client::Event::OffenceReceived { slash_session: 13, offences_count: 2 },
412					rc_client::Event::OffenceReceived { slash_session: 13, offences_count: 2 },
413					rc_client::Event::OffenceReceived { slash_session: 12, offences_count: 2 },
414					rc_client::Event::OffenceReceived { slash_session: 12, offences_count: 2 },
415					rc_client::Event::OffenceReceived { slash_session: 12, offences_count: 2 }
416				]
417			);
418
419			post_migration_era_reward_points =
420				pallet_staking_async::ErasRewardPoints::<ah::Runtime>::get(1).total;
421			// staking async has always been in NotForcing, not doing anything since no session
422			// reports come in
423			assert_eq!(pallet_staking_async::ForceEra::<ah::Runtime>::get(), Forcing::NotForcing);
424
425			// Verify all offences were properly queued in staking-async.
426			// Should have offences for validators 1, 2, and 5 from different sessions (all map to
427			// era 1)
428			assert!(pallet_staking_async::OffenceQueue::<ah::Runtime>::get(1, 1).is_some());
429			assert!(pallet_staking_async::OffenceQueue::<ah::Runtime>::get(1, 2).is_some());
430			assert!(pallet_staking_async::OffenceQueue::<ah::Runtime>::get(1, 5).is_some());
431
432			// Verify specific OffenceRecord structure for all three validators
433			let offence_record_v1 =
434				pallet_staking_async::OffenceQueue::<ah::Runtime>::get(1, 1).unwrap();
435			assert_eq!(
436				offence_record_v1,
437				pallet_staking_async::slashing::OffenceRecord {
438					reporter: None,
439					reported_era: 1,
440					exposure_page: 0,
441					slash_fraction: Perbill::from_percent(85), /* Should be the highest slash
442					                                            * fraction for validator 1 */
443					prior_slash_fraction: Perbill::from_percent(0),
444				}
445			);
446
447			let offence_record_v2 =
448				pallet_staking_async::OffenceQueue::<ah::Runtime>::get(1, 2).unwrap();
449			assert_eq!(
450				offence_record_v2,
451				pallet_staking_async::slashing::OffenceRecord {
452					reporter: None,
453					reported_era: 1,
454					exposure_page: 0,
455					slash_fraction: Perbill::from_percent(100), /* Should be the highest slash
456					                                             * fraction for validator 2 */
457					prior_slash_fraction: Perbill::from_percent(0),
458				}
459			);
460
461			let offence_record_v5 =
462				pallet_staking_async::OffenceQueue::<ah::Runtime>::get(1, 5).unwrap();
463			assert_eq!(
464				offence_record_v5,
465				pallet_staking_async::slashing::OffenceRecord {
466					reporter: None,
467					reported_era: 1,
468					exposure_page: 0,
469					slash_fraction: Perbill::from_percent(55), /* Should be the highest slash
470					                                            * fraction for validator 5 */
471					prior_slash_fraction: Perbill::from_percent(0),
472				}
473			);
474
475			// NOTE:
476			// - We sent 13 total offences across 3 sessions and 7 messages (3 offences per session)
477			// - Each session's offences trigger OffenceReported events when received
478			// - But only the highest slash fraction per validator per era gets queued for
479			//   processing
480			// - So we see 13 OffenceReported events but only 3 offences in the processing queue
481			// - The queue processing happens one offence per block in staking-async pallet.
482
483			// Process all queued offences (one offence per block)
484			// We have 3 offences queued (one per validator), so we need to roll 3 times
485			for _ in 0..3 {
486				ah::roll_next();
487			}
488
489			// Check that offences were processed for multiple validators
490			let staking_events = ah::mock::staking_events_since_last_call();
491
492			// Verify that OffenceReported events were emitted for all validators
493			let offence_reported_events: Vec<_> = staking_events
494				.iter()
495				.filter_map(|event| {
496					if let pallet_staking_async::Event::OffenceReported {
497						offence_era,
498						validator,
499						fraction,
500					} = event
501					{
502						Some((offence_era, validator, fraction))
503					} else {
504						None
505					}
506				})
507				.collect();
508
509			// Verify all OffenceReported events
510			assert_eq!(
511				offence_reported_events,
512				vec![
513					// 13 offences total, no deduplication here.
514					(&1, &5, &Perbill::from_percent(40)),
515					(&1, &2, &Perbill::from_percent(70)),
516					(&1, &1, &Perbill::from_percent(65)),
517					(&1, &1, &Perbill::from_percent(85)),
518					(&1, &5, &Perbill::from_percent(45)),
519					(&1, &2, &Perbill::from_percent(90)),
520					(&1, &2, &Perbill::from_percent(80)),
521					(&1, &1, &Perbill::from_percent(60)),
522					(&1, &5, &Perbill::from_percent(55)),
523					(&1, &2, &Perbill::from_percent(25)),
524					(&1, &1, &Perbill::from_percent(75)),
525					(&1, &2, &Perbill::from_percent(50)),
526					(&1, &2, &Perbill::from_percent(100))
527				]
528			);
529
530			// Verify that SlashComputed events were emitted for all three validators
531			let slash_computed_events: Vec<_> = staking_events
532				.iter()
533				.filter_map(|event| {
534					if let pallet_staking_async::Event::SlashComputed {
535						offence_era,
536						slash_era,
537						offender,
538						page,
539					} = event
540					{
541						Some((offence_era, slash_era, offender, page))
542					} else {
543						None
544					}
545				})
546				.collect();
547
548			// Should have SlashComputed events for all three validators. Here we deduplicate for
549			// maximum per session. Note: OffenceQueue uses StorageDoubleMap with Twox64Concat
550			// hasher, so iteration order depends on hash(validator_id).
551			assert_eq!(
552				slash_computed_events,
553				vec![
554					(&1, &3, &5, &0), /* validator 5: offence_era=1, slash_era=3, offender=5,
555					                   * page=0 */
556					(&1, &3, &1, &0), /* validator 1: offence_era=1, slash_era=3, offender=1,
557					                   * page=0 */
558					(&1, &3, &2, &0), /* validator 2: offence_era=1, slash_era=3, offender=2,
559					                   * page=0 */
560				]
561			);
562
563			// Verify that all offences have been processed (no longer in queue)
564			assert!(
565				!pallet_staking_async::OffenceQueue::<ah::Runtime>::contains_key(1, 1),
566				"Expected no remaining offences for validator 1"
567			);
568			assert!(
569				!pallet_staking_async::OffenceQueue::<ah::Runtime>::contains_key(1, 2),
570				"Expected no remaining offences for validator 2"
571			);
572			assert!(
573				!pallet_staking_async::OffenceQueue::<ah::Runtime>::contains_key(1, 5),
574				"Expected no remaining offences for validator 5"
575			);
576			// offence is deferred by two eras, ie 1 + 2 = 3. Note that this is one era less than
577			// staking-classic since slashing happens in multi-block, and we want to apply all
578			// slashes before the era 4 starts.
579			// Check if at least one of the validators has an unapplied slash
580			// Check for unapplied slashes for all validators with any of the slash fractions
581
582			// Check validator 2 slashes
583			let slash_v2_100_present = pallet_staking_async::UnappliedSlashes::<ah::Runtime>::get(
584				3,
585				(2, Perbill::from_percent(100), 0),
586			)
587			.is_some();
588			let slash_v2_90_present = pallet_staking_async::UnappliedSlashes::<ah::Runtime>::get(
589				3,
590				(2, Perbill::from_percent(90), 0),
591			)
592			.is_some();
593			let slash_v2_70_present = pallet_staking_async::UnappliedSlashes::<ah::Runtime>::get(
594				3,
595				(2, Perbill::from_percent(70), 0),
596			)
597			.is_some();
598
599			let total_slashes_v2 =
600				slash_v2_100_present as u8 + slash_v2_90_present as u8 + slash_v2_70_present as u8;
601			assert_eq!(
602				total_slashes_v2, 1,
603				"Expected exactly 1 unapplied slash for validator 2, got {} (100%:{}, 90%:{}, 70%:{})",
604				total_slashes_v2, slash_v2_100_present, slash_v2_90_present, slash_v2_70_present
605			);
606
607			// Check validator 1 slashes
608			let slash_v1_75_present = pallet_staking_async::UnappliedSlashes::<ah::Runtime>::get(
609				3,
610				(1, Perbill::from_percent(75), 0),
611			)
612			.is_some();
613			let slash_v1_85_present = pallet_staking_async::UnappliedSlashes::<ah::Runtime>::get(
614				3,
615				(1, Perbill::from_percent(85), 0),
616			)
617			.is_some();
618			let slash_v1_65_present = pallet_staking_async::UnappliedSlashes::<ah::Runtime>::get(
619				3,
620				(1, Perbill::from_percent(65), 0),
621			)
622			.is_some();
623
624			let total_slashes_v1 =
625				slash_v1_75_present as u8 + slash_v1_85_present as u8 + slash_v1_65_present as u8;
626			assert_eq!(
627				total_slashes_v1, 1,
628				"Expected exactly 1 unapplied slash for validator 1, got {} (75%:{}, 85%:{}, 65%:{})",
629				total_slashes_v1, slash_v1_75_present, slash_v1_85_present, slash_v1_65_present
630			);
631
632			// Check validator 5 slashes
633			let slash_v5_55_present = pallet_staking_async::UnappliedSlashes::<ah::Runtime>::get(
634				3,
635				(5, Perbill::from_percent(55), 0),
636			)
637			.is_some();
638			let slash_v5_45_present = pallet_staking_async::UnappliedSlashes::<ah::Runtime>::get(
639				3,
640				(5, Perbill::from_percent(45), 0),
641			)
642			.is_some();
643			let slash_v5_40_present = pallet_staking_async::UnappliedSlashes::<ah::Runtime>::get(
644				3,
645				(5, Perbill::from_percent(40), 0),
646			)
647			.is_some();
648
649			let total_slashes_v5 =
650				slash_v5_55_present as u8 + slash_v5_45_present as u8 + slash_v5_40_present as u8;
651			assert_eq!(
652				total_slashes_v5, 1,
653				"Expected exactly 1 unapplied slash for validator 5, got {} (55%:{}, 45%:{}, 40%:{})",
654				total_slashes_v5, slash_v5_55_present, slash_v5_45_present, slash_v5_40_present
655			);
656		});
657
658		// NOW: lets verify we kick off the election at the appropriate time
659		shared::in_ah(|| {
660			// roll another block just to strongly prove election is not kicked off at the end of
661			// migration.
662			ah::roll_next();
663
664			// ensure no election is kicked off yet
665			// (when election is kicked off, current_era = active_era + 1)
666			assert_eq!(pallet_staking_async::CurrentEra::<ah::Runtime>::get(), Some(1));
667			assert_eq!(pallet_staking_async::ActiveEra::<ah::Runtime>::get().unwrap().index, 1);
668			// also no session report is sent to AH yet.
669			assert_eq!(shared::CounterRCAHSessionReport::get(), 0);
670		});
671
672		// It was more than 6 sessions since the last election, on RC, so an election is already
673		// overdue. The next session change should trigger an election.
674
675		let mut post_migration_session_block_number = 0;
676		shared::in_rc(|| {
677			rc::roll_to_next_session(true);
678			post_migration_session_block_number =
679				frame_system::Pallet::<rc::Runtime>::block_number();
680
681			// all the buffered validators points are flushed
682			assert_eq!(ah_client::ValidatorPoints::<rc::Runtime>::iter().count(), 0,);
683		});
684
685		// AH receives the session report.
686		assert_eq!(shared::CounterRCAHSessionReport::get(), 1);
687		shared::in_ah(|| {
688			assert_eq!(pallet_staking_async::ActiveEra::<ah::Runtime>::get().unwrap().index, 1);
689			assert_eq!(pallet_staking_async::CurrentEra::<ah::Runtime>::get(), Some(1 + 1));
690
691			// by now one session report should have been received in staking
692			assert_eq!(
693				ah::rc_client_events_since_last_call(),
694				vec![rc_client::Event::SessionReportReceived {
695					end_index: 14,
696					activation_timestamp: None,
697					validator_points_counts: 1,
698					leftover: false
699				}]
700			);
701
702			assert_eq!(
703				staking_events_since_last_call(),
704				vec![pallet_staking_async::Event::SessionRotated {
705					starting_session: 15,
706					active_era: 1,
707					planned_era: 2
708				}]
709			);
710
711			// all expected era reward points are here
712			assert_eq!(
713				pallet_staking_async::ErasRewardPoints::<ah::Runtime>::get(1).total,
714				((post_migration_session_block_number - pre_migration_block_number) * 20) as u32 +
715				// --- ^^ these were buffered in ah-client
716					post_migration_era_reward_points // --- ^^ these were migrated as part of AHM
717			);
718
719			// ensure new validator is sent once election is complete.
720			ah::roll_until_matches(|| shared::CounterAHRCValidatorSet::get() == 1, true);
721
722			assert_eq!(
723				ah::staking_events_since_last_call(),
724				vec![
725					pallet_staking_async::Event::PagedElectionProceeded { page: 2, result: Ok(4) },
726					pallet_staking_async::Event::PagedElectionProceeded { page: 1, result: Ok(0) },
727					pallet_staking_async::Event::PagedElectionProceeded { page: 0, result: Ok(0) }
728				]
729			);
730		});
731
732		shared::in_rc(|| {
733			assert_eq!(
734				rc::ah_client_events_since_last_call(),
735				vec![ah_client::Event::ValidatorSetReceived {
736					id: 2,
737					new_validator_set_count: 4,
738					prune_up_to: None,
739					leftover: false
740				}]
741			);
742
743			let (planned_era, next_validator_set) =
744				ah_client::ValidatorSet::<rc::Runtime>::get().unwrap();
745
746			assert_eq!(planned_era, 2);
747			assert!(next_validator_set.len() >= rc::MinimumValidatorSetSize::get() as usize);
748		});
749
750		shared::in_ah(|| {
751			assert_eq!(pallet_staking_async::ActiveEra::<ah::Runtime>::get().unwrap().index, 1);
752			// at next session, the validator set is queued but not applied yet.
753			ah::roll_until_matches(|| shared::CounterRCAHSessionReport::get() == 2, true);
754			// active era is still 1.
755			assert_eq!(pallet_staking_async::ActiveEra::<ah::Runtime>::get().unwrap().index, 1);
756			// the following session, the validator set is applied.
757			ah::roll_until_matches(|| shared::CounterRCAHSessionReport::get() == 3, true);
758			assert_eq!(pallet_staking_async::ActiveEra::<ah::Runtime>::get().unwrap().index, 2);
759		});
760	}
761
762	#[test]
763	fn election_result_on_ah_reported_to_rc() {
764		// when election result is complete
765		// staking stores all exposures
766		// validators reported to rc
767		// validators enacted for next session
768	}
769
770	#[test]
771	fn rc_continues_with_same_validators_if_ah_is_late() {
772		// A test where ah is late to give us election result.
773	}
774
775	#[test]
776	fn authoring_points_reported_to_ah_per_session() {}
777
778	#[test]
779	fn rc_is_late_to_report_session_change() {}
780
781	#[test]
782	fn pruning_is_at_least_bonding_duration() {}
783
784	#[test]
785	fn ah_eras_are_delayed() {
786		// rc will trigger new sessions,
787		// ah cannot start a new era (election fail)
788		// we don't prune anything, because era should not be increased.
789	}
790
791	#[test]
792	fn ah_knows_good_era_duration() {
793		// era duration and rewards work.
794	}
795
796	#[test]
797	fn election_provider_fails_to_start() {
798		// call to ElectionProvider::start fails because it is already ongoing. What do we do?
799	}
800
801	#[test]
802	fn overlapping_election_wont_happen() {
803		// while one election is ongoing, enough sessions pass that we think we should plan yet
804		// another era.
805	}
806
807	#[test]
808	fn session_report_burst() {
809		// AH is offline for a while, and it suddenly receives 3 eras worth of session reports. What
810		// do we do?
811	}
812
813	mod message_queue_sizes {
814		use super::*;
815		use sp_core::crypto::AccountId32;
816
817		#[test]
818		fn normal_session_report() {
819			assert_eq!(
820				rc_client::SessionReport::<AccountId32> {
821					end_index: 0,
822					activation_timestamp: Some((0, 0)),
823					leftover: false,
824					validator_points: (0..1000)
825						.map(|i| (AccountId32::from([i as u8; 32]), 1000))
826						.collect(),
827				}
828				.encoded_size(),
829				36_020
830			);
831		}
832
833		#[test]
834		fn normal_validator_set() {
835			assert_eq!(
836				rc_client::ValidatorSetReport::<AccountId32> {
837					id: 42,
838					leftover: false,
839					new_validator_set: (0..1000)
840						.map(|i| AccountId32::from([i as u8; 32]))
841						.collect(),
842					prune_up_to: Some(69),
843				}
844				.encoded_size(),
845				32_012
846			);
847		}
848
849		#[test]
850		fn offence() {
851			// when one validator had an offence
852			let offences = (0..1)
853				.map(|i| rc_client::Offence::<AccountId32> {
854					offender: AccountId32::from([i as u8; 32]),
855					reporters: vec![AccountId32::from([42; 32])],
856					slash_fraction: Perbill::from_percent(50),
857				})
858				.collect::<Vec<_>>();
859
860			// offence + session-index
861			let encoded_size = offences.encoded_size() + 42u32.encoded_size();
862			assert_eq!(encoded_size, 74);
863		}
864
865		// Kusama has the same configurations as of now.
866		const POLKADOT_MAX_DOWNWARD_MESSAGE_SIZE: u32 = 51200; // 50 Kib
867		const POLKADOT_MAX_UPWARD_MESSAGE_SIZE: u32 = 65531; // 64 Kib
868
869		#[test]
870		fn maximum_session_report() {
871			let mut num_validator_points = 1;
872			loop {
873				let session_report = rc_client::SessionReport::<AccountId32> {
874					end_index: 0,
875					activation_timestamp: Some((0, 0)),
876					leftover: false,
877					validator_points: (0..num_validator_points)
878						.map(|i| (AccountId32::from([i as u8; 32]), 1000))
879						.collect(),
880				};
881
882				// Note: the real encoded size of the message will be a few bytes more, due to call
883				// indices and XCM instructions, but not significant.
884				let encoded_size = session_report.encoded_size() as u32;
885
886				if encoded_size > POLKADOT_MAX_DOWNWARD_MESSAGE_SIZE {
887					println!(
888						"SessionReport: num_validator_points: {}, encoded len: {}, max: {:?}, largest session report: {}",
889						num_validator_points, encoded_size, POLKADOT_MAX_DOWNWARD_MESSAGE_SIZE, num_validator_points - 1
890					);
891					break;
892				}
893				num_validator_points += 1;
894			}
895
896			// We can send up to 1422 32-octet validators + u32 points in a single message. This
897			// should inform the configuration `MaximumValidatorsWithPoints`.
898			assert_eq!(num_validator_points, 1422);
899		}
900
901		#[test]
902		fn maximum_validator_set() {
903			let mut num_validators = 1;
904			loop {
905				let validator_set_report = rc_client::ValidatorSetReport::<AccountId32> {
906					id: 42,
907					leftover: false,
908					new_validator_set: (0..num_validators)
909						.map(|i| AccountId32::from([i as u8; 32]))
910						.collect(),
911					prune_up_to: Some(69),
912				};
913
914				// Note: the real encoded size of the message will be a few bytes more, due to call
915				// indices and XCM instructions, but not significant.
916				let encoded_size = validator_set_report.encoded_size() as u32;
917				if encoded_size > POLKADOT_MAX_DOWNWARD_MESSAGE_SIZE {
918					println!(
919						"ValidatorSetReport: num_validators: {}, encoded len: {}, max: {:?}, largest validator set: {}",
920						num_validators, encoded_size, POLKADOT_MAX_DOWNWARD_MESSAGE_SIZE, num_validators - 1
921					);
922					break;
923				}
924				num_validators += 1;
925			}
926
927			// We can send up to 1599 32-octet validator keys (+ other small metadata) in a single
928			// validator set report.
929			assert_eq!(num_validators, 1600);
930		}
931
932		#[test]
933		fn maximum_offence_batched() {
934			let mut num_offences = 1;
935			let session_index: u32 = 42;
936			loop {
937				let offences = (0..num_offences)
938					.map(|i| {
939						(
940							session_index,
941							rc_client::Offence::<AccountId32> {
942								offender: AccountId32::from([i as u8; 32]),
943								reporters: vec![AccountId32::from([42; 32])],
944								slash_fraction: Perbill::from_percent(50),
945							},
946						)
947					})
948					.collect::<Vec<_>>();
949				let encoded_size = offences.encoded_size();
950
951				if encoded_size as u32 > POLKADOT_MAX_UPWARD_MESSAGE_SIZE {
952					println!(
953						"Offence (batched): num_offences: {}, encoded len: {}, max: {:?}, largest offence batch: {}",
954						num_offences, encoded_size, POLKADOT_MAX_UPWARD_MESSAGE_SIZE, num_offences - 1
955					);
956					break;
957				}
958
959				num_offences += 1;
960			}
961
962			// expectedly, this is a bit less than `offence_legacy` since we encode the session
963			// index over and over again.
964			assert_eq!(num_offences, 898);
965		}
966	}
967}