pallet_session_validator_management/
pallet_session_support.rs1use crate::{CommitteeRotationStage, CommitteeRotationStages};
7use frame_support::traits::UnfilteredDispatchable;
8use frame_system::RawOrigin;
9use frame_system::pallet_prelude::BlockNumberFor;
10use log::{debug, info, warn};
11use sp_staking::SessionIndex;
12use sp_std::collections::btree_set::BTreeSet;
13use sp_std::{vec, vec::Vec};
14
15impl<T: crate::Config + pallet_session::Config> pallet_session::SessionManager<T::AccountId>
16 for crate::Pallet<T>
17where
18 <T as pallet_session::Config>::Keys: From<T::AuthorityKeys>,
19{
20 fn new_session_genesis(_new_index: SessionIndex) -> Option<Vec<T::AccountId>> {
22 Some(
23 crate::Pallet::<T>::current_committee_storage()
24 .committee
25 .into_iter()
26 .map(|member| member.authority_id().into())
27 .collect::<Vec<_>>(),
28 )
29 }
30
31 fn new_session(new_index: SessionIndex) -> Option<Vec<T::AccountId>> {
35 if CommitteeRotationStage::<T>::get() == CommitteeRotationStages::AdditionalSession {
36 info!("💼 Session manager: new additional session {new_index}");
37 CommitteeRotationStage::<T>::put(CommitteeRotationStages::AwaitEpochChange);
38 let committee = crate::Pallet::<T>::current_committee_storage().committee;
39 return Some(committee.iter().map(|member| member.authority_id().into()).collect());
40 }
41
42 info!("💼 Session manager: new_session {new_index}, rotating the committee");
43 let new_committee = crate::Pallet::<T>::rotate_committee_to_next_epoch().expect(
44 "Session should never end without current epoch validators defined. \
45 Check ShouldEndSession implementation or if it is used before starting new session",
46 );
47
48 let old_committee_accounts = crate::ProvidedAccounts::<T>::take();
49 let mut new_committee_accounts: BTreeSet<T::AccountId> = BTreeSet::new();
50
51 for member in new_committee.iter() {
52 let account: T::AccountId = member.authority_id().into();
53
54 if !new_committee_accounts.contains(&account) {
55 new_committee_accounts.insert(account.clone());
56
57 if !old_committee_accounts.contains(&account) {
59 provide_account::<T>(&account);
60 }
61
62 let new_keys = member.authority_keys();
63 let current_keys = load_keys::<T>(&account);
64
65 if current_keys != Some(new_keys.clone().into()) {
66 purge_keys::<T>(&account);
67 set_keys::<T>(&account, new_keys.clone());
68 }
69 }
70 }
71
72 for account in old_committee_accounts.difference(&new_committee_accounts) {
73 purge_keys::<T>(account);
74 unprovide_account::<T>(account);
75 }
76
77 crate::ProvidedAccounts::<T>::set(new_committee_accounts.clone().try_into().unwrap());
78
79 Some(new_committee.into_iter().map(|member| member.authority_id().into()).collect())
80 }
81
82 fn end_session(end_index: SessionIndex) {
83 debug!("Session manager: End session {end_index}");
84 }
85
86 fn start_session(start_index: SessionIndex) {
88 let epoch_number = T::current_epoch_number();
89 debug!("Session manager: Start session {start_index}, epoch {epoch_number}");
90 }
91}
92
93fn load_keys<T: pallet_session::Config>(account: &T::AccountId) -> Option<T::Keys> {
94 <T as pallet_session::Config>::ValidatorId::try_from(account.clone())
95 .ok()
96 .as_ref()
97 .and_then(pallet_session::Pallet::<T>::load_keys)
98}
99
100fn set_keys<T: crate::Config + pallet_session::Config>(
101 account: &T::AccountId,
102 keys: T::AuthorityKeys,
103) where
104 <T as pallet_session::Config>::Keys: From<T::AuthorityKeys>,
105{
106 let set_keys_result = pallet_session::Call::<T>::set_keys { keys: keys.into(), proof: vec![] }
107 .dispatch_bypass_filter(RawOrigin::Signed(account.clone()).into());
108
109 match set_keys_result {
110 Ok(_) => debug!("set_keys for {account:?}"),
111 Err(e) => {
112 info!("Could not set_keys for {account:?}, error: {:?}", e.error)
113 },
114 }
115}
116
117fn purge_keys<T: crate::Config + pallet_session::Config>(account: &T::AccountId)
118where
119 <T as pallet_session::Config>::Keys: From<T::AuthorityKeys>,
120{
121 let purge_keys_result = pallet_session::Call::<T>::purge_keys {}
122 .dispatch_bypass_filter(RawOrigin::Signed(account.clone()).into());
123 match purge_keys_result {
124 Ok(_) => debug!("purge_keys for {account:?}"),
125 Err(e) => info!("Could not purge_keys for {account:?}, error: {:?}", e.error),
126 }
127}
128
129fn provide_account<T: crate::Config + pallet_session::Config>(account: &T::AccountId) {
130 log::debug!(
131 "➕💼 Incrementing provider count and registering keys for block producer {account:?}"
132 );
133
134 frame_system::Pallet::<T>::inc_providers(&account);
135}
136
137fn unprovide_account<T: crate::Config + pallet_session::Config>(account: &T::AccountId) {
138 log::info!(
139 "➖💼 Decrementing provider count and deregistering keys for block producer {account:?}"
140 );
141 frame_system::Pallet::<T>::dec_providers(&account).expect(
142 "We always match dec_providers with corresponding inc_providers, thus it cannot fail",
143 );
144}
145
146impl<T> pallet_session::ShouldEndSession<BlockNumberFor<T>> for crate::Pallet<T>
148where
149 T: crate::Config,
150{
151 fn should_end_session(n: BlockNumberFor<T>) -> bool {
152 let current_epoch_number = T::current_epoch_number();
153 let current_committee_epoch = crate::Pallet::<T>::current_committee_storage().epoch;
154 let next_committee_is_defined = crate::Pallet::<T>::next_committee().is_some();
155 if current_epoch_number > current_committee_epoch {
156 if next_committee_is_defined {
157 info!("Session manager: should_end_session({n:?}) = true");
158 CommitteeRotationStage::<T>::put(CommitteeRotationStages::NewSessionDueEpochChange);
159 true
160 } else {
161 warn!(
162 "Session manager: should_end_session({n:?}) 'current epoch' > 'committee epoch' but the next committee is not defined"
163 );
164 false
165 }
166 } else {
167 let stage = CommitteeRotationStage::<T>::get();
168 if stage == CommitteeRotationStages::NewSessionDueEpochChange {
169 CommitteeRotationStage::<T>::put(CommitteeRotationStages::AdditionalSession);
170 info!("Session manager: should_end_session({n:?}) to force the new committee");
171 true
172 } else {
173 false
174 }
175 }
176 }
177}
178
179#[cfg(test)]
180mod tests {
181 use crate::{
182 CommitteeInfo, CurrentCommittee, NextCommittee,
183 mock::{mock_pallet::CurrentEpoch, *},
184 tests::increment_epoch,
185 };
186 use pallet_session::ShouldEndSession;
187 pub const IRRELEVANT: u64 = 2;
188 use sidechain_domain::ScEpochNumber;
189 use sp_runtime::testing::UintAuthorityId;
190
191 type Manager = crate::Pallet<Test>;
192
193 #[test]
194 fn should_end_session_if_last_one_ended_late_and_new_committee_is_defined() {
195 let current_committee_epoch = ScEpochNumber(100);
196 let current_committee = as_permissioned_members(&[alice()]);
197 let next_committee_epoch = 102;
198 let next_committee = as_permissioned_members(&[bob()]);
199
200 new_test_ext().execute_with(|| {
201 CurrentCommittee::<Test>::put(CommitteeInfo {
202 epoch: current_committee_epoch.into(),
203 committee: current_committee,
204 });
205 CurrentEpoch::<Test>::set(current_committee_epoch.next().next());
206 assert!(!Manager::should_end_session(IRRELEVANT));
207 NextCommittee::<Test>::put(CommitteeInfo {
208 epoch: next_committee_epoch.into(),
209 committee: next_committee,
210 });
211 assert!(Manager::should_end_session(IRRELEVANT));
212 });
213 }
214
215 #[test]
216 fn register_session_keys_for_provided_authorities() {
217 new_test_ext().execute_with(|| {
218 set_validators_directly(&[dave(), eve()], 1).unwrap();
219 assert_eq!(Session::load_keys(&dave().account_id()), None);
221 assert_eq!(Session::load_keys(&eve().account_id()), None);
222 increment_epoch();
223
224 start_session(1);
225
226 assert_eq!(Session::load_keys(&dave().account_id()), Some(dave().authority_keys));
228 assert_eq!(Session::load_keys(&eve().account_id()), Some(eve().authority_keys));
229 });
230 }
231
232 #[test]
233 fn reregister_changed_session_keys_for_sitting_authority() {
234 new_test_ext().execute_with(|| {
235 set_validators_directly(&[dave(), eve()], 1).unwrap();
236 assert_eq!(Session::load_keys(&dave().account_id()), None);
238 assert_eq!(Session::load_keys(&eve().account_id()), None);
239 increment_epoch();
240
241 start_session(1);
242
243 assert_eq!(Session::load_keys(&dave().account_id()), Some(dave().authority_keys));
245 assert_eq!(Session::load_keys(&eve().account_id()), Some(eve().authority_keys));
246
247 let eve_with_different_keys = MockValidator {
248 name: "Eve with different keys",
249 authority_keys: SessionKeys { foo: UintAuthorityId(44) },
250 ..eve()
251 };
252
253 set_validators_directly(&[dave(), eve_with_different_keys.clone()], 2).unwrap();
255 increment_epoch();
256 start_session(2);
257
258 assert_eq!(
260 Session::load_keys(&eve().account_id()),
261 Some(eve_with_different_keys.authority_keys)
262 );
263 });
264 }
265
266 #[test]
267 fn ends_two_sessions_and_rotates_once_when_committee_changes() {
268 new_test_ext().execute_with(|| {
269 assert_eq!(Session::current_index(), 0);
270 assert_eq!(SessionCommitteeManagement::current_committee_storage().epoch.0, 0);
271 increment_epoch();
272 set_validators_directly(&[charlie(), dave()], 1).unwrap();
273
274 advance_one_block();
275 assert_eq!(Session::current_index(), 1);
276 assert_eq!(Session::validators(), vec![]);
278 assert_eq!(SessionCommitteeManagement::current_committee_storage().epoch.0, 1);
279
280 advance_one_block();
281 assert_eq!(Session::current_index(), 2);
282 assert_eq!(Session::validators(), vec![charlie().account_id(), dave().account_id()]);
283 assert_eq!(SessionCommitteeManagement::current_committee_storage().epoch.0, 1);
284
285 for _i in 0..10 {
286 advance_one_block();
287 assert_eq!(Session::current_index(), 2);
288 assert_eq!(
289 Session::validators(),
290 vec![charlie().account_id(), dave().account_id()]
291 );
292 assert_eq!(SessionCommitteeManagement::current_committee_storage().epoch.0, 1);
293 }
294 });
295 }
296}