Files
pezkuwi-sdk/bizinikiwi/pezframe/staking-async/ahm-test/src/rc/test.rs
T
pezkuwichain cf463fe8ee FAZ 1 Complete: Workspace compile fixes, warning cleanup, version bumps
- Fixed is_using_frame_crate() macro to check for pezframe/pezkuwi_sdk
- Removed disable_pezframe_system_supertrait_check temporary bypasses
- Feature-gated storage-benchmark and teyrchain-benchmarks code
- Fixed dead_code warnings with underscore prefix (_Header)
- Removed unused imports and shadowing use statements
- Version bumps: procedural-tools 10.0.1, benchmarking-cli 32.0.1,
  docs 0.0.2, minimal-runtime 0.0.1, yet-another-teyrchain 0.6.1, umbrella 0.1.2
- Updated MAINNET_ROADMAP.md with FAZ 1 completion status
2026-01-02 11:41:09 +03:00

1480 lines
40 KiB
Rust

// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd. and Dijital Kurdistan Tech Institute
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::rc::mock::*;
use pezframe::testing_prelude::*;
use pezpallet_staking_async_ah_client::{
self as ah_client, Mode, OffenceSendQueue, OperatingMode, OutgoingSessionReport, UnexpectedKind,
};
use pezpallet_staking_async_rc_client::{
self as rc_client, Offence, SessionReport, ValidatorSetReport,
};
// Tests that are specific to Relay Chain.
#[test]
fn send_session_report_no_election_comes_in() {
ExtBuilder::default().local_queue().build().execute_with(|| {
roll_until_matches(|| pezpallet_session::CurrentIndex::<Runtime>::get() == 10, false);
assert_eq!(
LocalQueue::get().unwrap(),
vec![
(
30,
OutgoingMessages::SessionReport(SessionReport {
end_index: 0,
validator_points: vec![(11, 580)],
activation_timestamp: None,
leftover: false
})
),
(
60,
OutgoingMessages::SessionReport(SessionReport {
end_index: 1,
validator_points: vec![(11, 600)],
activation_timestamp: None,
leftover: false
})
),
(
90,
OutgoingMessages::SessionReport(SessionReport {
end_index: 2,
validator_points: vec![(11, 600)],
activation_timestamp: None,
leftover: false
})
),
(
120,
OutgoingMessages::SessionReport(SessionReport {
end_index: 3,
validator_points: vec![(11, 600)],
activation_timestamp: None,
leftover: false
})
),
(
150,
OutgoingMessages::SessionReport(SessionReport {
end_index: 4,
validator_points: vec![(11, 600)],
activation_timestamp: None,
leftover: false
})
),
(
180,
OutgoingMessages::SessionReport(SessionReport {
end_index: 5,
validator_points: vec![(11, 600)],
activation_timestamp: None,
leftover: false
})
),
(
210,
OutgoingMessages::SessionReport(SessionReport {
end_index: 6,
validator_points: vec![(11, 600)],
activation_timestamp: None,
leftover: false
})
),
(
240,
OutgoingMessages::SessionReport(SessionReport {
end_index: 7,
validator_points: vec![(11, 600)],
activation_timestamp: None,
leftover: false
})
),
(
270,
OutgoingMessages::SessionReport(SessionReport {
end_index: 8,
validator_points: vec![(11, 600)],
activation_timestamp: None,
leftover: false
})
),
(
300,
OutgoingMessages::SessionReport(SessionReport {
end_index: 9,
validator_points: vec![(11, 600)],
activation_timestamp: None,
leftover: false
})
)
]
);
})
}
#[test]
fn upon_receiving_election_queue_and_activate_next_session() {
ExtBuilder::default()
.session_keys(vec![1, 2, 3, 4, 5])
.local_queue()
.no_default_author()
.build()
.execute_with(|| {
// roll 3 sessions, and then validator set comes
roll_until_matches(|| pezpallet_session::CurrentIndex::<Runtime>::get() == 3, false);
assert_eq!(
session_events_since_last_call(),
vec![
pezpallet_session::Event::<Runtime>::NewSession { session_index: 1 },
pezpallet_session::Event::<Runtime>::NewSession { session_index: 2 },
pezpallet_session::Event::<Runtime>::NewSession { session_index: 3 }
]
);
assert_eq!(
LocalQueue::get_since_last_call(),
vec![
(
30,
OutgoingMessages::SessionReport(SessionReport {
end_index: 0,
validator_points: vec![],
activation_timestamp: None,
leftover: false
})
),
(
60,
OutgoingMessages::SessionReport(SessionReport {
end_index: 1,
validator_points: vec![],
activation_timestamp: None,
leftover: false
})
),
(
90,
OutgoingMessages::SessionReport(SessionReport {
end_index: 2,
validator_points: vec![],
activation_timestamp: None,
leftover: false
})
),
]
);
// current session validators are:
assert!(pezpallet_session::Validators::<Runtime>::get().is_empty());
assert_eq!(pezpallet_session::QueuedChanged::<Runtime>::get(), false);
assert!(pezpallet_session::QueuedKeys::<Runtime>::get().is_empty());
// new validator set comes in.
let report = ValidatorSetReport {
id: 1,
prune_up_to: None,
leftover: false,
new_validator_set: vec![1, 2, 3, 4],
};
assert_ok!(ah_client::Pezpallet::<Runtime>::validator_set(
RuntimeOrigin::root(),
report
));
// session validators are not set yet.
assert!(pezpallet_session::Validators::<Runtime>::get().is_empty());
assert_eq!(pezpallet_session::QueuedChanged::<Runtime>::get(), false);
assert!(pezpallet_session::QueuedKeys::<Runtime>::get().is_empty());
// rotate one more session
roll_until_matches(|| pezpallet_session::CurrentIndex::<Runtime>::get() == 4, false);
// current validators are still the same
assert!(pezpallet_session::Validators::<Runtime>::get().is_empty());
// queued has changed
assert_eq!(pezpallet_session::QueuedChanged::<Runtime>::get(), true);
assert_eq!(
pezpallet_session::QueuedKeys::<Runtime>::get()
.into_iter()
.map(|(x, _)| x)
.collect::<Vec<_>>(),
vec![1, 2, 3, 4]
);
assert_eq!(
session_events_since_last_call(),
vec![
pezpallet_session::Event::<Runtime>::NewQueued,
pezpallet_session::Event::<Runtime>::NewSession { session_index: 4 },
]
);
assert_eq!(
LocalQueue::get_since_last_call(),
vec![(
120,
OutgoingMessages::SessionReport(SessionReport {
end_index: 3,
validator_points: vec![],
activation_timestamp: None,
leftover: false
})
)]
);
// rotate one more session
roll_until_matches(|| pezpallet_session::CurrentIndex::<Runtime>::get() == 5, false);
// current validators have changed
assert_eq!(pezpallet_session::Validators::<Runtime>::get(), vec![1, 2, 3, 4]);
// queued is back to normal
assert_eq!(pezpallet_session::QueuedChanged::<Runtime>::get(), false);
assert_eq!(
pezpallet_session::QueuedKeys::<Runtime>::get()
.into_iter()
.map(|(x, _)| x)
.collect::<Vec<_>>(),
vec![1, 2, 3, 4]
);
assert_eq!(
session_events_since_last_call(),
vec![pezpallet_session::Event::<Runtime>::NewSession { session_index: 5 },]
);
assert_eq!(
LocalQueue::get_since_last_call(),
vec![(
150,
OutgoingMessages::SessionReport(SessionReport {
end_index: 4,
validator_points: vec![],
activation_timestamp: Some((150000, 1)),
leftover: false
})
),]
);
// send another report, this time remove 4 and replace with 5
// new validator set comes in.
let report = ValidatorSetReport {
id: 2,
prune_up_to: None,
leftover: false,
new_validator_set: vec![1, 2, 3, 5],
};
assert_ok!(ah_client::Pezpallet::<Runtime>::validator_set(
RuntimeOrigin::root(),
report
));
// rotate one more session
roll_until_matches(|| pezpallet_session::CurrentIndex::<Runtime>::get() == 6, false);
// current validators not changed
assert_eq!(pezpallet_session::Validators::<Runtime>::get(), vec![1, 2, 3, 4]);
// queued is set -- notice 5 is queued but not in the current set
assert_eq!(pezpallet_session::QueuedChanged::<Runtime>::get(), true);
assert_eq!(
pezpallet_session::QueuedKeys::<Runtime>::get()
.into_iter()
.map(|(x, _)| x)
.collect::<Vec<_>>(),
vec![1, 2, 3, 5]
);
assert_eq!(
session_events_since_last_call(),
vec![
pezpallet_session::Event::<Runtime>::NewQueued,
pezpallet_session::Event::<Runtime>::NewSession { session_index: 6 },
]
);
assert_eq!(
LocalQueue::get_since_last_call(),
vec![(
180,
OutgoingMessages::SessionReport(SessionReport {
end_index: 5,
validator_points: vec![],
activation_timestamp: None,
leftover: false
})
)],
);
// rotate one more session
roll_until_matches(|| pezpallet_session::CurrentIndex::<Runtime>::get() == 7, false);
// current validators changed
assert_eq!(pezpallet_session::Validators::<Runtime>::get(), vec![1, 2, 3, 5]);
assert_eq!(pezpallet_session::QueuedChanged::<Runtime>::get(), false);
assert_eq!(
pezpallet_session::QueuedKeys::<Runtime>::get()
.into_iter()
.map(|(x, _)| x)
.collect::<Vec<_>>(),
vec![1, 2, 3, 5]
);
assert_eq!(
session_events_since_last_call(),
vec![pezpallet_session::Event::<Runtime>::NewSession { session_index: 7 }]
);
assert_eq!(
LocalQueue::get_since_last_call(),
vec![(
210,
OutgoingMessages::SessionReport(SessionReport {
end_index: 6,
validator_points: vec![],
activation_timestamp: Some((210000, 2)),
leftover: false
})
)]
);
})
}
#[test]
fn cleans_validator_points_upon_session_report() {
ExtBuilder::default().local_queue().build().execute_with(|| {
// given
ah_client::ValidatorPoints::<Runtime>::insert(1, 100);
ah_client::ValidatorPoints::<Runtime>::insert(2, 200);
// when
roll_until_matches(|| pezpallet_session::CurrentIndex::<Runtime>::get() == 1, false);
// then
assert_eq!(
LocalQueue::get().unwrap(),
vec![(
30,
OutgoingMessages::SessionReport(SessionReport {
end_index: 0,
// first two are inserted by us, the other one by the test mock
validator_points: vec![(1, 100), (2, 200), (11, 580)],
activation_timestamp: None,
leftover: false
})
),]
);
// then it is drained.
assert!(!ah_client::ValidatorPoints::<Runtime>::contains_key(1));
assert!(!ah_client::ValidatorPoints::<Runtime>::contains_key(2));
})
}
#[test]
fn session_report_send_fails_after_retries() {
// if a session report cannot be sent, first we retry. If we still fail and retries are out, we
// restore the points.
ExtBuilder::default().local_queue().build().execute_with(|| {
// insert a custom validator point for easier tracking
ah_client::ValidatorPoints::<Runtime>::insert(1, 100);
assert_eq!(pezpallet_session::CurrentIndex::<Runtime>::get(), 0);
assert!(ah_client::OutgoingSessionReport::<Runtime>::get().is_none());
// when roll forward, but next message will fail to be sent
NextAhDeliveryFails::set(true);
roll_until_matches(|| pezpallet_session::CurrentIndex::<Runtime>::get() == 1, false);
// these are the points that are saved in the outgoing report
assert_eq!(
OutgoingSessionReport::<Runtime>::get().unwrap().0.validator_points,
vec![(1, 100), (11, 580)]
);
// now we have 2 retries left
assert!(matches!(ah_client::OutgoingSessionReport::<Runtime>::get(), Some((_, 2))));
// validator points are drained, since we have the session report.
assert_eq!(validator_points(), vec![]);
// event emitted
assert_eq!(
ah_client_events_since_last_call(),
vec![ah_client::Event::Unexpected(UnexpectedKind::SessionReportSendFailed)]
);
// again
NextAhDeliveryFails::set(true);
roll_next();
assert!(matches!(ah_client::OutgoingSessionReport::<Runtime>::get(), Some((_, 1))));
// this is registered by our mock setup
assert_eq!(validator_points(), vec![(11, 20)]);
assert_eq!(
ah_client_events_since_last_call(),
vec![ah_client::Event::Unexpected(UnexpectedKind::SessionReportSendFailed)]
);
// in the meantime, we receive some new validator points.
ah_client::ValidatorPoints::<Runtime>::insert(1, 50);
// again
NextAhDeliveryFails::set(true);
roll_next();
assert!(matches!(ah_client::OutgoingSessionReport::<Runtime>::get(), Some((_, 0))));
assert_eq!(validator_points(), vec![(1, 50), (11, 40)]);
assert_eq!(
ah_client_events_since_last_call(),
vec![ah_client::Event::Unexpected(UnexpectedKind::SessionReportSendFailed)]
);
// last time, we will drop it now.
NextAhDeliveryFails::set(true);
roll_next();
assert!(matches!(ah_client::OutgoingSessionReport::<Runtime>::get(), None));
assert_eq!(
ah_client_events_since_last_call(),
vec![
ah_client::Event::Unexpected(UnexpectedKind::SessionReportSendFailed),
ah_client::Event::Unexpected(UnexpectedKind::SessionReportDropped)
]
);
// validator points are restored and merged with what we have noted in the meantime.
assert_eq!(validator_points(), vec![(1, 150), (11, 640)]);
})
}
#[test]
fn reports_unexpected_event_if_too_many_validator_points() {
ExtBuilder::default().local_queue().build().execute_with(|| {
// create 1 too many validator points
for v in 0..=MaximumValidatorsWithPoints::get() {
ah_client::ValidatorPoints::<Runtime>::insert(v as AccountId, 100);
}
// roll until next session
roll_until_matches(|| pezpallet_session::CurrentIndex::<Runtime>::get() == 1, false);
// message is placed in the outbox
assert!(matches!(
&LocalQueue::get_since_last_call()[..],
[(
30,
OutgoingMessages::SessionReport(SessionReport {
validator_points, ..
})
)] if validator_points.len() as u32 == MaximumValidatorsWithPoints::get()
));
// but there is an unexpected event for us
assert_eq!(
ah_client_events_since_last_call(),
vec![ah_client::Event::Unexpected(UnexpectedKind::ValidatorPointDropped)]
);
// and one validator point is left;
assert_eq!(ah_client::ValidatorPoints::<Runtime>::iter().count(), 1);
// it will be sent in the next session report
roll_until_matches(|| pezpallet_session::CurrentIndex::<Runtime>::get() == 2, false);
assert!(matches!(
&LocalQueue::get_since_last_call()[..],
[(
60,
OutgoingMessages::SessionReport(SessionReport {
validator_points, ..
})
)] if validator_points.len() as u32 == 1 + 1
// 1 more validator point added by the authorship pezpallet in our test setup
));
})
}
#[test]
fn drops_too_small_validator_set() {
ExtBuilder::default().local_queue().build().execute_with(|| {
assert_eq!(MinimumValidatorSetSize::get(), 4);
let report = ValidatorSetReport {
id: 1,
prune_up_to: None,
leftover: false,
new_validator_set: vec![1],
};
// This will raise okay, but nothing is queued, and event is emitted
assert_ok!(ah_client::Pezpallet::<Runtime>::validator_set(RuntimeOrigin::root(), report),);
assert_eq!(
ah_client_events_since_last_call(),
vec![ah_client::Event::SetTooSmallAndDropped]
);
assert!(ah_client::ValidatorSet::<Runtime>::get().is_none());
assert!(ah_client::IncompleteValidatorSetReport::<Runtime>::get().is_none());
})
}
#[test]
fn splitted_drops_too_small_validator_set() {
ExtBuilder::default().local_queue().build().execute_with(|| {
let parts = ValidatorSetReport {
id: 1,
prune_up_to: None,
leftover: true,
new_validator_set: vec![1, 2],
}
.split(1);
assert_eq!(parts.len(), 2);
assert_ok!(ah_client::Pezpallet::<Runtime>::validator_set(
RuntimeOrigin::root(),
parts[0].clone()
));
assert_eq!(
ah_client_events_since_last_call(),
vec![ah_client::Event::ValidatorSetReceived {
id: 1,
new_validator_set_count: 1,
prune_up_to: None,
leftover: true
}]
);
assert_eq!(ah_client::ValidatorSet::<Runtime>::get(), None);
assert!(ah_client::IncompleteValidatorSetReport::<Runtime>::get().is_some());
assert_ok!(ah_client::Pezpallet::<Runtime>::validator_set(
RuntimeOrigin::root(),
parts[1].clone()
));
assert_eq!(
ah_client_events_since_last_call(),
vec![ah_client::Event::SetTooSmallAndDropped]
);
assert_eq!(ah_client::ValidatorSet::<Runtime>::get(), None);
assert!(ah_client::IncompleteValidatorSetReport::<Runtime>::get().is_none());
})
}
#[test]
fn on_offence_non_validator() {
ExtBuilder::default()
.local_queue()
.session_keys(vec![1, 2, 3, 4])
.build()
.execute_with(|| {
receive_validator_set_at(3, 1, vec![1, 2, 3, 4], true);
assert_eq!(pezpallet_session::CurrentIndex::<Runtime>::get(), 5);
// flush some relevant data
LocalQueue::flush();
let _ = session_events_since_last_call();
// submit an offence for validator 5 in current session, which is not a validator
// really. Note that we have to provide a manual identification, as the default one
// won't work here.
assert_ok!(pezpallet_root_offences::Pezpallet::<Runtime>::create_offence(
RuntimeOrigin::root(),
vec![(5, Perbill::from_percent(50))],
Some(vec![Default::default()]),
None
));
// roll 1 block to process
roll_next();
// we nonetheless have sent the offence report to AH
assert_eq!(
LocalQueue::get_since_last_call(),
vec![(
151,
OutgoingMessages::OffenceReportPaged(vec![(
5,
Offence {
offender: 5,
reporters: vec![],
slash_fraction: Perbill::from_percent(50)
}
)])
)]
);
// no disabling has happened in session
assert_eq!(session_events_since_last_call(), vec![]);
})
}
#[test]
fn offences_first_queued_and_then_sent() {
ExtBuilder::default().local_queue().build().execute_with(|| {
// flush some relevant data
LocalQueue::flush();
let _ = session_events_since_last_call();
// submit an offence for two accounts
assert_ok!(pezpallet_root_offences::Pezpallet::<Runtime>::create_offence(
RuntimeOrigin::root(),
vec![(4, Perbill::from_percent(50)), (5, Perbill::from_percent(50))],
Some(vec![Default::default(), Default::default()]),
None
));
// Nothing is in our local outgoing queue yet
assert_eq!(LocalQueue::get_since_last_call(), vec![]);
// But we have it in our internal buffer
assert_eq!(OffenceSendQueue::<Runtime>::count(), 2);
// roll one block forward
roll_next();
// now it is in outbox.
assert_eq!(
LocalQueue::get_since_last_call(),
vec![(
2,
OutgoingMessages::OffenceReportPaged(vec![
(
0,
Offence {
offender: 4,
reporters: vec![],
slash_fraction: Perbill::from_percent(50)
}
),
(
0,
Offence {
offender: 5,
reporters: vec![],
slash_fraction: Perbill::from_percent(50)
}
)
])
)]
);
})
}
#[test]
fn offences_spam_sent_page_by_page() {
ExtBuilder::default().local_queue().build().execute_with(|| {
// flush some relevant data
LocalQueue::flush();
let _ = session_events_since_last_call();
let onchain_batch_size = MaxOffenceBatchSize::get();
// fill 2.5 pages worth of offecnces all at once
let offence_count = 5 * onchain_batch_size / 2;
let offences = (0..offence_count)
.map(|i| {
(
// identification tuple,
(i as AccountId, Default::default()),
// session index
0,
// time-slot, opaque number, just to make sure we create lots of unique
// offences.
i as u128,
// slash fraction
Perbill::from_percent(50).deconstruct(),
)
})
.collect::<Vec<_>>();
assert_ok!(pezpallet_root_offences::Pezpallet::<Runtime>::report_offence(RuntimeOrigin::root(), offences));
// all offences reported to the offence pezpallet.
assert_eq!(offence_events_since_last_call().len() as u32, offence_count);
// offence pezpallet will try and deduplicate them, but they all have the same time-slot,
// therefore are all reported to ah-client.
assert_eq!(ah_client::OffenceSendQueue::<Runtime>::count(), offence_count);
// 2.5 pages worth of offences
assert_eq!(ah_client::OffenceSendQueue::<Runtime>::pages(), 3);
// Nothing is in our local (outgoing queue yet)
assert_eq!(LocalQueue::get_since_last_call(), vec![]);
// roll one block forward, a page is sent.
roll_next();
// we have set 1 message in our outbox, which is consisted of a batch of offences. First page is `onchain_batch_size / 2`
assert!(matches!(
&LocalQueue::get_since_last_call()[..],
[(_, OutgoingMessages::OffenceReportPaged(ref offences))] if offences.len() as u32 == onchain_batch_size / 2
));
assert_eq!(ah_client::OffenceSendQueue::<Runtime>::count(), 2 * onchain_batch_size);
assert_eq!(ah_client::OffenceSendQueue::<Runtime>::pages(), 2);
// To spice it up, we simulate 1 failed attempt in the next page as well. This is equivalent to the DMP queue being too busy to receive this message from us.
NextAhDeliveryFails::set(true);
roll_next();
// offence queue has not changed, we didn't send anyhting.
assert!(LocalQueue::get_since_last_call().is_empty());
assert_eq!(ah_client::OffenceSendQueue::<Runtime>::count(), 2 * onchain_batch_size);
assert_eq!(ah_client::OffenceSendQueue::<Runtime>::pages(), 2);
// even to warn us is emitted
assert_eq!(ah_client_events_since_last_call(), vec![ah_client::Event::Unexpected(UnexpectedKind::OffenceSendFailed)]);
// Now let's make real progress again
roll_next();
assert!(matches!(
&LocalQueue::get_since_last_call()[..],
[(_, OutgoingMessages::OffenceReportPaged(ref offences))] if offences.len() as u32 == onchain_batch_size
));
assert_eq!(ah_client::OffenceSendQueue::<Runtime>::count(), onchain_batch_size);
assert_eq!(ah_client::OffenceSendQueue::<Runtime>::pages(), 1);
roll_next();
assert!(matches!(
&LocalQueue::get_since_last_call()[..],
[(_, OutgoingMessages::OffenceReportPaged(ref offences))] if offences.len() as u32 == onchain_batch_size
));
assert_eq!(ah_client::OffenceSendQueue::<Runtime>::count(), 0);
assert_eq!(ah_client::OffenceSendQueue::<Runtime>::pages(), 0);
// nothing more is set to outbox.
roll_next();
assert!(LocalQueue::get_since_last_call().is_empty());
})
}
#[test]
fn on_offence_non_validator_and_active() {
ExtBuilder::default()
.local_queue()
.session_keys(vec![1, 2, 3, 4])
.build()
.execute_with(|| {
receive_validator_set_at(3, 1, vec![1, 2, 3, 4], true);
assert_eq!(pezpallet_session::CurrentIndex::<Runtime>::get(), 5);
// flush some relevant data
LocalQueue::flush();
let _ = session_events_since_last_call();
// submit an offence for 5 and 4, first a non-validator and second an active one.
assert_ok!(pezpallet_root_offences::Pezpallet::<Runtime>::create_offence(
RuntimeOrigin::root(),
vec![(4, Perbill::from_percent(50)), (5, Perbill::from_percent(50))],
Some(vec![Default::default(), Default::default()]),
None
));
// roll 1 block to process it
roll_next();
// we nonetheless have sent the offence report to AH
assert_eq!(
LocalQueue::get_since_last_call(),
vec![(
151,
OutgoingMessages::OffenceReportPaged(vec![
(
5,
Offence {
offender: 4,
reporters: vec![],
slash_fraction: Perbill::from_percent(50)
}
),
(
5,
Offence {
offender: 5,
reporters: vec![],
slash_fraction: Perbill::from_percent(50)
}
)
])
)]
);
// one validator has been disabled in session
assert_eq!(
session_events_since_last_call(),
vec![pezpallet_session::Event::ValidatorDisabled { validator: 4 }]
);
})
}
#[test]
fn wont_disable_past_session_offence() {
ExtBuilder::default()
.local_queue()
.session_keys(vec![1, 2, 3, 4])
.minimum_validator_set_size(1)
.build()
.execute_with(|| {
// receive 1, 2 at 3, activate them
receive_validator_set_at(3, 1, vec![1, 2], true);
assert_eq!(pezpallet_session::CurrentIndex::<Runtime>::get(), 5);
// receive 3, 4 at 6, activate them
receive_validator_set_at(6, 2, vec![3, 4], true);
assert_eq!(pezpallet_session::CurrentIndex::<Runtime>::get(), 8);
// flush some relevant data
LocalQueue::flush();
let _ = session_events_since_last_call();
// submit an offence for 1, who is a past validator, in a past session.
assert_ok!(pezpallet_root_offences::Pezpallet::<Runtime>::create_offence(
RuntimeOrigin::root(),
vec![(1, Perbill::from_percent(50))],
Some(vec![Default::default()]),
Some(5)
));
// roll 1 block to process it
roll_next();
// we nonetheless have sent the offence report to AH
assert_eq!(
LocalQueue::get_since_last_call(),
vec![(
241,
OutgoingMessages::OffenceReportPaged(vec![(
5,
Offence {
offender: 1,
reporters: vec![],
slash_fraction: Perbill::from_percent(50)
}
)])
)]
);
// no one disabled in session
assert_eq!(session_events_since_last_call(), vec![]);
})
}
#[test]
fn on_offence_disable_and_re_enabled_next_set() {
ExtBuilder::default()
.local_queue()
.session_keys(vec![1, 2, 3, 4])
.build()
.execute_with(|| {
receive_validator_set_at(3, 1, vec![1, 2, 3, 4], true);
assert_eq!(pezpallet_session::CurrentIndex::<Runtime>::get(), 5);
// flush some relevant data
LocalQueue::flush();
let _ = session_events_since_last_call();
// submit an offence for 4 in the current session
assert_ok!(pezpallet_root_offences::Pezpallet::<Runtime>::create_offence(
RuntimeOrigin::root(),
vec![(4, Perbill::from_percent(50))],
Some(vec![Default::default()]),
None
));
// session disables 4 immediately
assert_eq!(
session_events_since_last_call(),
vec![pezpallet_session::Event::ValidatorDisabled { validator: 4 }]
);
assert_eq!(
pezpallet_session::DisabledValidators::<Runtime>::get()
.into_iter()
.map(|(x, _)| x)
.collect::<Vec<_>>(),
vec![3]
);
// roll 1 block to process it.
roll_next();
// offence dispatched to AH
assert_eq!(
LocalQueue::get_since_last_call(),
vec![(
151,
OutgoingMessages::OffenceReportPaged(vec![(
5,
Offence {
offender: 4,
reporters: vec![],
slash_fraction: Perbill::from_percent(50)
}
)])
)]
);
// now receive the same validator set, again
receive_validator_set_at(6, 2, vec![1, 2, 3, 4], true);
assert_eq!(pezpallet_session::CurrentIndex::<Runtime>::get(), 8);
// events related to session rotation
assert_eq!(
session_events_since_last_call(),
vec![
pezpallet_session::Event::NewSession { session_index: 6 },
pezpallet_session::Event::NewQueued,
pezpallet_session::Event::NewSession { session_index: 7 },
pezpallet_session::Event::NewSession { session_index: 8 }
]
);
// disabled validators is now gone
assert!(pezpallet_session::DisabledValidators::<Runtime>::get().is_empty());
});
}
mod session_pruning {
use super::*;
#[test]
fn stores_and_prunes_old_validator_set_trie() {
ExtBuilder::default()
.session_keys((1..100).collect::<Vec<_>>())
.local_queue()
.build()
.execute_with(|| {
// initially, no historical data
assert_eq!(pezpallet_session::historical::StoredRange::<T>::get(), None);
// forward 10 sessions, and each one set 10 different validators
for i in 1..=10 {
let session_validators =
(i * 10..(i + 1) * 10).map(|x| x as AccountId).collect::<Vec<_>>();
assert_ok!(ah_client::Pezpallet::<T>::validator_set(
RuntimeOrigin::root(),
ValidatorSetReport {
id: i,
prune_up_to: None,
leftover: false,
new_validator_set: session_validators.clone(),
},
));
roll_until_matches(|| pezpallet_session::CurrentIndex::<T>::get() == i, false);
assert_eq!(
session_events_since_last_call(),
vec![
pezpallet_session::Event::<T>::NewQueued,
pezpallet_session::Event::<T>::NewSession { session_index: i },
]
);
assert_eq!(
historical_events_since_last_call(),
vec![pezpallet_session::historical::Event::<T>::RootStored {
index: i + 1
}]
)
}
// ensure that we have the root for these recorded in the historical session
// pezpallet
assert_eq!(pezpallet_session::historical::StoredRange::<T>::get(), Some((2, 12)));
// send back a new validator set, but with some pruning info.
assert_ok!(ah_client::Pezpallet::<T>::validator_set(
RuntimeOrigin::root(),
ValidatorSetReport {
id: 999,
prune_up_to: Some(5),
leftover: false,
new_validator_set: vec![1, 2, 3, 4],
},
));
assert_eq!(pezpallet_session::historical::StoredRange::<T>::get(), Some((5, 12)));
assert_eq!(
historical_events_since_last_call(),
vec![pezpallet_session::historical::Event::<T>::RootsPruned { up_to: 5 }]
);
})
}
}
mod blocking {
use super::*;
#[test]
fn drops_incoming_if_passive_mode() {
ExtBuilder::default().local_queue().pre_migration().build().execute_with(|| {
// given
let report = ValidatorSetReport {
id: 1,
prune_up_to: None,
leftover: false,
new_validator_set: vec![1, 2, 3, 4],
};
// when
assert_noop!(
ah_client::Pezpallet::<Runtime>::validator_set(RuntimeOrigin::root(), report),
ah_client::Error::<Runtime>::Blocked,
);
// then
assert_eq!(ah_client::ValidatorSet::<T>::get(), None);
})
}
#[test]
fn drops_outgoing_if_passive_mode() {
ExtBuilder::default().local_queue().pre_migration().build().execute_with(|| {
// roll 5 sessions
roll_until_matches(|| pezpallet_session::CurrentIndex::<Runtime>::get() == 5, false);
// nothing is queued; No outgoing messages expected in passive mode.
assert_eq!(LocalQueue::get().unwrap(), vec![]);
// make pezpallet active
Mode::<T>::put(OperatingMode::Active);
// roll another session
roll_until_matches(|| pezpallet_session::CurrentIndex::<Runtime>::get() == 6, false);
// now session report is queued.
assert_eq!(
LocalQueue::get().unwrap(),
vec![(
180,
OutgoingMessages::SessionReport(SessionReport {
end_index: 5,
validator_points: vec![(11, 600)],
activation_timestamp: None,
leftover: false,
})
)]
);
})
}
}
mod splitting {
use super::*;
#[test]
fn can_split_and_merge_session_report() {
ExtBuilder::default().local_queue().build().execute_with(|| {
let full_report = SessionReport {
activation_timestamp: None,
end_index: 0,
leftover: false,
validator_points: vec![(1, 1), (2, 2), (3, 3), (4, 4), (5, 5)],
};
// Split by 0
assert_eq!(
full_report
.clone()
.split(0)
.into_iter()
.map(|r| r.validator_points)
.collect::<Vec<_>>(),
vec![vec![(1, 1)], vec![(2, 2)], vec![(3, 3)], vec![(4, 4)], vec![(5, 5)]]
);
// Split by 1 is noop
assert_eq!(
full_report
.clone()
.split(1)
.into_iter()
.map(|r| r.validator_points)
.collect::<Vec<_>>(),
vec![vec![(1, 1)], vec![(2, 2)], vec![(3, 3)], vec![(4, 4)], vec![(5, 5)]]
);
// split by 2
assert_eq!(
full_report
.clone()
.split(2)
.into_iter()
.map(|r| r.validator_points)
.collect::<Vec<_>>(),
vec![vec![(1, 1), (2, 2)], vec![(3, 3), (4, 4)], vec![(5, 5)]]
);
// split by 3
assert_eq!(
full_report
.clone()
.split(3)
.into_iter()
.map(|r| r.validator_points)
.collect::<Vec<_>>(),
vec![vec![(1, 1), (2, 2), (3, 3)], vec![(4, 4), (5, 5)]]
);
// split by 4
assert_eq!(
full_report
.clone()
.split(4)
.into_iter()
.map(|r| r.validator_points)
.collect::<Vec<_>>(),
vec![vec![(1, 1), (2, 2), (3, 3), (4, 4)], vec![(5, 5)]]
);
// split by 5
assert_eq!(
full_report
.clone()
.split(5)
.into_iter()
.map(|r| r.validator_points)
.collect::<Vec<_>>(),
vec![vec![(1, 1), (2, 2), (3, 3), (4, 4), (5, 5)]]
);
// split by 6
assert_eq!(
full_report
.clone()
.split(6)
.into_iter()
.map(|r| r.validator_points)
.collect::<Vec<_>>(),
vec![vec![(1, 1), (2, 2), (3, 3), (4, 4), (5, 5)]]
);
})
}
#[test]
fn splitting_and_merging_equal() {
let full_report = ValidatorSetReport {
new_validator_set: vec![1, 2, 3, 4, 5],
id: 0,
prune_up_to: None,
leftover: false,
};
for c in 1..=6 {
assert_eq!(
full_report.clone().split(c).into_iter().reduce(|acc, x| acc.merge(x).unwrap()),
Some(full_report.clone())
);
}
}
#[test]
fn can_split_and_merge_validator_set_report() {
ExtBuilder::default().local_queue().build().execute_with(|| {
let full_report = ValidatorSetReport {
new_validator_set: vec![1, 2, 3, 4, 5],
id: 0,
prune_up_to: None,
leftover: false,
};
// Split by 0
assert_eq!(
full_report
.clone()
.split(0)
.into_iter()
.map(|r| r.new_validator_set)
.collect::<Vec<_>>(),
vec![vec![1], vec![2], vec![3], vec![4], vec![5]]
);
// Split by 1 is noop
assert_eq!(
full_report
.clone()
.split(1)
.into_iter()
.map(|r| r.new_validator_set)
.collect::<Vec<_>>(),
vec![vec![1], vec![2], vec![3], vec![4], vec![5]]
);
// split by 2
assert_eq!(
full_report
.clone()
.split(2)
.into_iter()
.map(|r| r.new_validator_set)
.collect::<Vec<_>>(),
vec![vec![1, 2], vec![3, 4], vec![5]]
);
// split by 3
assert_eq!(
full_report
.clone()
.split(3)
.into_iter()
.map(|r| r.new_validator_set)
.collect::<Vec<_>>(),
vec![vec![1, 2, 3], vec![4, 5]]
);
// split by 4
assert_eq!(
full_report
.clone()
.split(4)
.into_iter()
.map(|r| r.new_validator_set)
.collect::<Vec<_>>(),
vec![vec![1, 2, 3, 4], vec![5]]
);
// split by 5
assert_eq!(
full_report
.clone()
.split(5)
.into_iter()
.map(|r| r.new_validator_set)
.collect::<Vec<_>>(),
vec![vec![1, 2, 3, 4, 5]]
);
// split by 6
assert_eq!(
full_report
.clone()
.split(6)
.into_iter()
.map(|r| r.new_validator_set)
.collect::<Vec<_>>(),
vec![vec![1, 2, 3, 4, 5]]
);
})
}
#[test]
fn can_handle_splitted_validator_set() {
ExtBuilder::default().local_queue().build().execute_with(|| {
let full_report = ValidatorSetReport {
new_validator_set: vec![1, 2, 3, 4, 5, 6],
id: 0,
prune_up_to: None,
leftover: false,
};
let splitted = full_report.split(2);
let incomplete0 = splitted[0].clone();
let incomplete1 = splitted[1].clone();
let complete = splitted[2].clone();
assert!(incomplete0.leftover);
assert!(incomplete1.leftover);
assert!(!complete.leftover);
// nothing is queued.
assert!(ah_client::IncompleteValidatorSetReport::<Runtime>::get().is_none());
assert!(ah_client::ValidatorSet::<Runtime>::get().is_none());
// when
assert_ok!(StakingAhClient::validator_set(RuntimeOrigin::root(), incomplete0.clone()));
assert_eq!(
ah_client_events_since_last_call(),
vec![ah_client::Event::<T>::ValidatorSetReceived {
id: 0,
new_validator_set_count: 2,
prune_up_to: None,
leftover: true
}]
);
// then
assert_eq!(
ah_client::IncompleteValidatorSetReport::<Runtime>::get()
.map(|r| r.new_validator_set),
Some(vec![1, 2])
);
assert!(ah_client::ValidatorSet::<Runtime>::get().is_none());
// when
assert_ok!(StakingAhClient::validator_set(RuntimeOrigin::root(), incomplete1.clone()));
assert_eq!(
ah_client_events_since_last_call(),
vec![ah_client::Event::<T>::ValidatorSetReceived {
id: 0,
new_validator_set_count: 4,
prune_up_to: None,
leftover: true
}]
);
// then
assert_eq!(
ah_client::IncompleteValidatorSetReport::<Runtime>::get()
.map(|r| r.new_validator_set),
Some(vec![1, 2, 3, 4])
);
assert!(ah_client::ValidatorSet::<Runtime>::get().is_none());
// when
assert_ok!(StakingAhClient::validator_set(RuntimeOrigin::root(), complete.clone()));
assert_eq!(
ah_client_events_since_last_call(),
vec![ah_client::Event::<T>::ValidatorSetReceived {
id: 0,
new_validator_set_count: 6,
prune_up_to: None,
leftover: false
}]
);
// then
assert_eq!(ah_client::IncompleteValidatorSetReport::<Runtime>::get(), None);
assert_eq!(
ah_client::ValidatorSet::<Runtime>::get(),
Some((0, vec![1, 2, 3, 4, 5, 6]))
);
})
}
#[test]
fn incomplete_wrong_id_dropped() {
ExtBuilder::default().local_queue().build().execute_with(|| {
let incomplete0 = rc_client::ValidatorSetReport {
id: 0,
new_validator_set: vec![1, 2],
leftover: true,
prune_up_to: None,
};
let broken = rc_client::ValidatorSetReport {
id: 1,
new_validator_set: vec![3, 4],
leftover: true,
prune_up_to: None,
};
// nothing is queued.
assert!(ah_client::IncompleteValidatorSetReport::<Runtime>::get().is_none());
assert!(ah_client::ValidatorSet::<Runtime>::get().is_none());
// when
assert_ok!(StakingAhClient::validator_set(RuntimeOrigin::root(), incomplete0.clone()));
// then
assert_eq!(
ah_client::IncompleteValidatorSetReport::<Runtime>::get()
.map(|r| r.new_validator_set),
Some(vec![1, 2])
);
assert!(ah_client::ValidatorSet::<Runtime>::get().is_none());
// when
assert_ok!(StakingAhClient::validator_set(RuntimeOrigin::root(), broken.clone()));
// then
assert_eq!(ah_client::IncompleteValidatorSetReport::<Runtime>::get(), None);
assert!(ah_client::ValidatorSet::<Runtime>::get().is_none());
assert_eq!(
pezframe_system::Pezpallet::<Runtime>::read_events_for_pallet::<
ah_client::Event<Runtime>,
>(),
vec![
ah_client::Event::<T>::ValidatorSetReceived {
id: 0,
new_validator_set_count: 2,
prune_up_to: None,
leftover: true
},
ah_client::Event::<T>::CouldNotMergeAndDropped
]
);
})
}
}
#[cfg(test)]
mod key_proofs {
use pezframe::traits::KeyOwnerProofSystem;
use pezframe_support::pezsp_runtime;
use super::*;
#[test]
#[ignore = "not complete yet"]
fn can_generate_valid_latest_key_ownership_proof() {
ExtBuilder::default()
.local_queue()
.session_keys(vec![1, 2, 3, 4])
.build()
.execute_with(|| {
// no sessions exists, cannot generate any proofs
assert_eq!(pezpallet_session::historical::StoredRange::<T>::get(), None);
assert_eq!(pezpallet_session::CurrentIndex::<T>::get(), 0);
// receive a validator set, and trigger a 3 new sessions, such that we store some
// roots.
assert_ok!(ah_client::Pezpallet::<T>::validator_set(
RuntimeOrigin::root(),
ValidatorSetReport {
id: 0,
prune_up_to: None,
leftover: false,
new_validator_set: vec![1, 2, 3, 4],
},
));
roll_until_matches(|| pezpallet_session::CurrentIndex::<T>::get() == 3, false);
assert_eq!(
historical_events_since_last_call(),
vec![
pezpallet_session::historical::Event::RootStored { index: 2 },
pezpallet_session::historical::Event::RootStored { index: 3 },
pezpallet_session::historical::Event::RootStored { index: 4 }
]
);
assert_eq!(pezpallet_session::CurrentIndex::<T>::get(), 3);
assert_eq!(pezpallet_session::historical::StoredRange::<T>::get(), Some((2, 5)));
// generate the proof for one of the validators
use pezsp_runtime::{
key_types::DUMMY, testing::UintAuthorityId, traits::OpaqueKeys,
};
let key_ids = <SessionKeys as OpaqueKeys>::key_ids();
assert_eq!(key_ids.len(), 1, "we have inserted only one key type in mock");
let keys = pezpallet_session::Pezpallet::<T>::load_keys(&1).unwrap();
let our_key = keys.get::<UintAuthorityId>(key_ids[0]);
assert_eq!(key_ids[0], DUMMY);
let proof = pezpallet_session::historical::Pezpallet::<T>::prove((
DUMMY,
&our_key.encode()[..],
))
.unwrap();
assert_eq!(proof.session, 3);
assert_eq!(proof.validator_count, 4);
// proof is valid, and it results into a default exposure.
assert_eq!(
pezpallet_session::historical::Pezpallet::<T>::check_proof(
(DUMMY, &our_key.encode()[..]),
proof
)
.unwrap(),
(1, pezsp_staking::Exposure::default())
)
})
}
}