session_info.rs 12.4 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Copyright 2020 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.

// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.

17
//! The session info pallet provides information about validator sets
18
19
20
21
//! from prior sessions needed for approvals and disputes.
//!
//! See https://w3f.github.io/parachain-implementers-guide/runtime/session_info.html.

22
23
24
25
use crate::{
	configuration, paras, scheduler, shared,
	util::{take_active_subset, take_active_subset_and_inactive},
};
Shawn Tabrizi's avatar
Shawn Tabrizi committed
26
use frame_support::{pallet_prelude::*, traits::OneSessionHandler};
27
use primitives::v1::{AssignmentId, AuthorityDiscoveryId, SessionIndex, SessionInfo};
28
use sp_std::vec::Vec;
29

30
pub use pallet::*;
31

32
33
34
#[frame_support::pallet]
pub mod pallet {
	use super::*;
35

36
37
38
39
40
41
42
43
44
45
46
47
48
	#[pallet::pallet]
	#[pallet::generate_store(pub(super) trait Store)]
	pub struct Pallet<T>(_);

	#[pallet::config]
	pub trait Config:
		frame_system::Config
		+ configuration::Config
		+ shared::Config
		+ paras::Config
		+ scheduler::Config
		+ AuthorityDiscoveryConfig
	{
49
	}
50
51
52
53
54

	/// Assignment keys for the current session.
	/// Note that this API is private due to it being prone to 'off-by-one' at session boundaries.
	/// When in doubt, use `Sessions` API instead.
	#[pallet::storage]
Shawn Tabrizi's avatar
Shawn Tabrizi committed
55
56
	pub(super) type AssignmentKeysUnsafe<T: Config> =
		StorageValue<_, Vec<AssignmentId>, ValueQuery>;
57
58
59
60
61
62
63
64
65
66
67
68

	/// The earliest session for which previous session info is stored.
	#[pallet::storage]
	#[pallet::getter(fn earliest_stored_session)]
	pub(crate) type EarliestStoredSession<T: Config> = StorageValue<_, SessionIndex, ValueQuery>;

	/// Session information in a rolling window.
	/// Should have an entry in range `EarliestStoredSession..=CurrentSessionIndex`.
	/// Does not have any entries before the session index in the first session change notification.
	#[pallet::storage]
	#[pallet::getter(fn session_info)]
	pub(crate) type Sessions<T: Config> = StorageMap<_, Identity, SessionIndex, SessionInfo>;
69
70
71
72
}

/// An abstraction for the authority discovery pallet
/// to help with mock testing.
73
pub trait AuthorityDiscoveryConfig {
Andronik Ordian's avatar
Andronik Ordian committed
74
	/// Retrieve authority identifiers of the current authority set in canonical ordering.
75
76
77
	fn authorities() -> Vec<AuthorityDiscoveryId>;
}

78
impl<T: pallet_authority_discovery::Config> AuthorityDiscoveryConfig for T {
79
	fn authorities() -> Vec<AuthorityDiscoveryId> {
80
		<pallet_authority_discovery::Pallet<T>>::current_authorities().to_vec()
81
82
83
	}
}

84
impl<T: Config> Pallet<T> {
85
86
	/// Handle an incoming session change.
	pub(crate) fn initializer_on_new_session(
Shawn Tabrizi's avatar
Shawn Tabrizi committed
87
		notification: &crate::initializer::SessionChangeNotification<T::BlockNumber>,
88
	) {
89
		let config = <configuration::Pallet<T>>::config();
90
91
92
93

		let dispute_period = config.dispute_period;

		let validators = notification.validators.clone();
94
		let discovery_keys = <T as AuthorityDiscoveryConfig>::authorities();
95
		let assignment_keys = AssignmentKeysUnsafe::<T>::get();
96
		let active_set = <shared::Pallet<T>>::active_validator_indices();
97

98
99
		let validator_groups = <scheduler::Pallet<T>>::validator_groups();
		let n_cores = <scheduler::Pallet<T>>::availability_cores().len() as u32;
100
101
102
103
104
105
106
		let zeroth_delay_tranche_width = config.zeroth_delay_tranche_width;
		let relay_vrf_modulo_samples = config.relay_vrf_modulo_samples;
		let n_delay_tranches = config.n_delay_tranches;
		let no_show_slots = config.no_show_slots;
		let needed_approvals = config.needed_approvals;

		let new_session_index = notification.session_index;
107
		let old_earliest_stored_session = EarliestStoredSession::<T>::get();
108
		let new_earliest_stored_session = new_session_index.saturating_sub(dispute_period);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
109
110
		let new_earliest_stored_session =
			core::cmp::max(new_earliest_stored_session, old_earliest_stored_session);
111
		// remove all entries from `Sessions` from the previous value up to the new value
112
		// avoid a potentially heavy loop when introduced on a live chain
113
		if old_earliest_stored_session != 0 || Sessions::<T>::get(0).is_some() {
114
			for idx in old_earliest_stored_session..new_earliest_stored_session {
115
				Sessions::<T>::remove(&idx);
116
			}
117
			// update `EarliestStoredSession` based on `config.dispute_period`
118
			EarliestStoredSession::<T>::set(new_earliest_stored_session);
119
120
		} else {
			// just introduced on a live chain
121
			EarliestStoredSession::<T>::set(new_session_index);
122
123
124
		}
		// create a new entry in `Sessions` with information about the current session
		let new_session_info = SessionInfo {
125
			validators, // these are from the notification and are thus already correct.
126
			discovery_keys: take_active_subset_and_inactive(&active_set, &discovery_keys),
127
			assignment_keys: take_active_subset(&active_set, &assignment_keys),
128
129
130
131
132
133
134
135
			validator_groups,
			n_cores,
			zeroth_delay_tranche_width,
			relay_vrf_modulo_samples,
			n_delay_tranches,
			no_show_slots,
			needed_approvals,
		};
136
		Sessions::<T>::insert(&new_session_index, &new_session_info);
137
138
	}

139
	/// Called by the initializer to initialize the session info pallet.
140
141
142
143
	pub(crate) fn initializer_initialize(_now: T::BlockNumber) -> Weight {
		0
	}

144
	/// Called by the initializer to finalize the session info pallet.
145
146
147
	pub(crate) fn initializer_finalize() {}
}

148
impl<T: Config> sp_runtime::BoundToRuntimeAppPublic for Pallet<T> {
149
150
151
	type Public = AssignmentId;
}

152
impl<T: pallet_session::Config + Config> OneSessionHandler<T::AccountId> for Pallet<T> {
153
154
155
	type Key = AssignmentId;

	fn on_genesis_session<'a, I: 'a>(_validators: I)
Shawn Tabrizi's avatar
Shawn Tabrizi committed
156
157
	where
		I: Iterator<Item = (&'a T::AccountId, Self::Key)>,
158
159
160
161
	{
	}

	fn on_new_session<'a, I: 'a>(_changed: bool, validators: I, _queued: I)
Shawn Tabrizi's avatar
Shawn Tabrizi committed
162
163
	where
		I: Iterator<Item = (&'a T::AccountId, Self::Key)>,
164
165
	{
		let assignment_keys: Vec<_> = validators.map(|(_, v)| v).collect();
166
		AssignmentKeysUnsafe::<T>::set(assignment_keys);
167
168
	}

169
	fn on_disabled(_i: u32) {}
170
171
}

172
173
174
#[cfg(test)]
mod tests {
	use super::*;
Shawn Tabrizi's avatar
Shawn Tabrizi committed
175
176
177
178
179
180
181
	use crate::{
		configuration::HostConfiguration,
		initializer::SessionChangeNotification,
		mock::{
			new_test_ext, Configuration, MockGenesisConfig, Origin, ParasShared, SessionInfo,
			System, Test,
		},
182
		util::take_active_subset,
183
	};
184
	use keyring::Sr25519Keyring;
Shawn Tabrizi's avatar
Shawn Tabrizi committed
185
	use primitives::v1::{BlockNumber, ValidatorId, ValidatorIndex};
186
187
188
189
190
191
192
193
194

	fn run_to_block(
		to: BlockNumber,
		new_session: impl Fn(BlockNumber) -> Option<SessionChangeNotification<BlockNumber>>,
	) {
		while System::block_number() < to {
			let b = System::block_number();

			SessionInfo::initializer_finalize();
195
			ParasShared::initializer_finalize();
196
197
198
			Configuration::initializer_finalize();

			if let Some(notification) = new_session(b + 1) {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
199
				Configuration::initializer_on_new_session(&notification.session_index);
200
				ParasShared::initializer_on_new_session(
201
202
203
204
205
					notification.session_index,
					notification.random_seed,
					&notification.new_config,
					notification.validators.clone(),
				);
206
207
208
				SessionInfo::initializer_on_new_session(&notification);
			}

209
210
211
212
213
			System::on_finalize(b);

			System::on_initialize(b + 1);
			System::set_block_number(b + 1);

214
			Configuration::initializer_initialize(b + 1);
215
			ParasShared::initializer_initialize(b + 1);
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
			SessionInfo::initializer_initialize(b + 1);
		}
	}

	fn default_config() -> HostConfiguration<BlockNumber> {
		HostConfiguration {
			parathread_cores: 1,
			dispute_period: 2,
			needed_approvals: 3,
			..Default::default()
		}
	}

	fn genesis_config() -> MockGenesisConfig {
		MockGenesisConfig {
			configuration: configuration::GenesisConfig {
				config: default_config(),
				..Default::default()
			},
			..Default::default()
		}
	}

	fn session_changes(n: BlockNumber) -> Option<SessionChangeNotification<BlockNumber>> {
240
		if n % 10 == 0 {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
241
			Some(SessionChangeNotification { session_index: n / 10, ..Default::default() })
242
243
		} else {
			None
244
245
246
247
		}
	}

	fn new_session_every_block(n: BlockNumber) -> Option<SessionChangeNotification<BlockNumber>> {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
248
		Some(SessionChangeNotification { session_index: n, ..Default::default() })
249
250
251
	}

	#[test]
252
	fn session_pruning_is_based_on_dispute_period() {
253
		new_test_ext(genesis_config()).execute_with(|| {
254
255
256
257
258
			// Dispute period starts at 2
			let config = Configuration::config();
			assert_eq!(config.dispute_period, 2);

			// Move to session 10
259
			run_to_block(100, session_changes);
260
			// Earliest stored session is 10 - 2 = 8
261
			assert_eq!(EarliestStoredSession::<Test>::get(), 8);
262
			// Pruning works as expected
263
264
265
			assert!(Sessions::<Test>::get(7).is_none());
			assert!(Sessions::<Test>::get(8).is_some());
			assert!(Sessions::<Test>::get(9).is_some());
266
267
268
269

			// changing dispute_period works
			let dispute_period = 5;
			Configuration::set_dispute_period(Origin::root(), dispute_period).unwrap();
270
271
272
273
274
275
276
277
278

			// Dispute period does not automatically change
			let config = Configuration::config();
			assert_eq!(config.dispute_period, 2);
			// Two sessions later it will though
			run_to_block(120, session_changes);
			let config = Configuration::config();
			assert_eq!(config.dispute_period, 5);

279
			run_to_block(200, session_changes);
280
			assert_eq!(EarliestStoredSession::<Test>::get(), 20 - dispute_period);
281

282
			// Increase dispute period even more
283
284
			let new_dispute_period = 16;
			Configuration::set_dispute_period(Origin::root(), new_dispute_period).unwrap();
285
286

			run_to_block(210, session_changes);
287
			assert_eq!(EarliestStoredSession::<Test>::get(), 21 - dispute_period);
288
289
290
291
292
293

			// Two sessions later it kicks in
			run_to_block(220, session_changes);
			let config = Configuration::config();
			assert_eq!(config.dispute_period, 16);
			// Earliest session stays the same
294
			assert_eq!(EarliestStoredSession::<Test>::get(), 21 - dispute_period);
295
296

			// We still don't have enough stored sessions to start pruning
297
			run_to_block(300, session_changes);
298
			assert_eq!(EarliestStoredSession::<Test>::get(), 21 - dispute_period);
299
300

			// now we do
301
			run_to_block(420, session_changes);
302
			assert_eq!(EarliestStoredSession::<Test>::get(), 42 - new_dispute_period);
303
304
305
306
307
308
309
		})
	}

	#[test]
	fn session_info_is_based_on_config() {
		new_test_ext(genesis_config()).execute_with(|| {
			run_to_block(1, new_session_every_block);
310
			let session = Sessions::<Test>::get(&1).unwrap();
311
312
313
314
			assert_eq!(session.needed_approvals, 3);

			// change some param
			Configuration::set_needed_approvals(Origin::root(), 42).unwrap();
315
316
			// 2 sessions later
			run_to_block(3, new_session_every_block);
317
			let session = Sessions::<Test>::get(&3).unwrap();
318
319
320
			assert_eq!(session.needed_approvals, 42);
		})
	}
321
322
323
324
325
326
327
328
329
330
331
332
333

	#[test]
	fn session_info_active_subsets() {
		let unscrambled = vec![
			Sr25519Keyring::Alice,
			Sr25519Keyring::Bob,
			Sr25519Keyring::Charlie,
			Sr25519Keyring::Dave,
			Sr25519Keyring::Eve,
		];

		let active_set = vec![ValidatorIndex(4), ValidatorIndex(0), ValidatorIndex(2)];

Shawn Tabrizi's avatar
Shawn Tabrizi committed
334
335
336
337
338
339
		let unscrambled_validators: Vec<ValidatorId> =
			unscrambled.iter().map(|v| v.public().into()).collect();
		let unscrambled_discovery: Vec<AuthorityDiscoveryId> =
			unscrambled.iter().map(|v| v.public().into()).collect();
		let unscrambled_assignment: Vec<AssignmentId> =
			unscrambled.iter().map(|v| v.public().into()).collect();
340
341
342
343

		let validators = take_active_subset(&active_set, &unscrambled_validators);

		new_test_ext(genesis_config()).execute_with(|| {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
344
			ParasShared::set_active_validators_with_indices(active_set.clone(), validators.clone());
345

346
			assert_eq!(ParasShared::active_validator_indices(), active_set);
347

348
			AssignmentKeysUnsafe::<Test>::set(unscrambled_assignment.clone());
349
			crate::mock::set_discovery_authorities(unscrambled_discovery.clone());
350
			assert_eq!(<Test>::authorities(), unscrambled_discovery);
351
352
353
354
355
356
357
358

			// invoke directly, because `run_to_block` will invoke `Shared`	and clobber our
			// values.
			SessionInfo::initializer_on_new_session(&SessionChangeNotification {
				session_index: 1,
				validators: validators.clone(),
				..Default::default()
			});
359
			let session = Sessions::<Test>::get(&1).unwrap();
360
361
362
363

			assert_eq!(session.validators, validators);
			assert_eq!(
				session.discovery_keys,
364
				take_active_subset_and_inactive(&active_set, &unscrambled_discovery),
365
366
367
368
369
370
371
			);
			assert_eq!(
				session.assignment_keys,
				take_active_subset(&active_set, &unscrambled_assignment),
			);
		})
	}
372
}