referrerpolicy=no-referrer-when-downgrade

pallet_session/historical/
mod.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//! An opt-in utility for tracking historical sessions in FRAME-session.
19//!
20//! This is generally useful when implementing blockchains that require accountable
21//! safety where validators from some amount f prior sessions must remain slashable.
22//!
23//! Rather than store the full session data for any given session, we instead commit
24//! to the roots of merkle tries containing the session data.
25//!
26//! These roots and proofs of inclusion can be generated at any time during the current session.
27//! Afterwards, the proofs can be fed to a consensus module when reporting misbehavior.
28
29pub mod offchain;
30pub mod onchain;
31mod shared;
32
33use alloc::vec::Vec;
34use codec::{Decode, Encode};
35use core::fmt::Debug;
36use sp_runtime::{
37	traits::{Convert, OpaqueKeys},
38	KeyTypeId,
39};
40use sp_session::{MembershipProof, ValidatorCount};
41use sp_staking::SessionIndex;
42use sp_trie::{
43	trie_types::{TrieDBBuilder, TrieDBMutBuilderV0},
44	LayoutV0, MemoryDB, RandomState, Recorder, StorageProof, Trie, TrieMut, TrieRecorder,
45};
46
47use frame_support::{
48	traits::{KeyOwnerProofSystem, ValidatorSet, ValidatorSetWithIdentification},
49	Parameter,
50};
51
52const LOG_TARGET: &'static str = "runtime::historical";
53
54use crate::{self as pallet_session, Pallet as Session};
55
56pub use pallet::*;
57use sp_trie::{accessed_nodes_tracker::AccessedNodesTracker, recorder_ext::RecorderExt};
58
59#[frame_support::pallet]
60pub mod pallet {
61	use super::*;
62	use frame_support::pallet_prelude::*;
63
64	/// The in-code storage version.
65	const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
66
67	#[pallet::pallet]
68	#[pallet::storage_version(STORAGE_VERSION)]
69	pub struct Pallet<T>(_);
70
71	/// Config necessary for the historical pallet.
72	#[pallet::config]
73	pub trait Config: pallet_session::Config + frame_system::Config {
74		/// The overarching event type.
75		#[allow(deprecated)]
76		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
77
78		/// Full identification of the validator.
79		type FullIdentification: Parameter;
80
81		/// A conversion from validator ID to full identification.
82		///
83		/// This should contain any references to economic actors associated with the
84		/// validator, since they may be outdated by the time this is queried from a
85		/// historical trie.
86		///
87		/// It must return the identification for the current session index.
88		type FullIdentificationOf: Convert<Self::ValidatorId, Option<Self::FullIdentification>>;
89	}
90
91	/// Mapping from historical session indices to session-data root hash and validator count.
92	#[pallet::storage]
93	#[pallet::getter(fn historical_root)]
94	pub type HistoricalSessions<T: Config> =
95		StorageMap<_, Twox64Concat, SessionIndex, (T::Hash, ValidatorCount), OptionQuery>;
96
97	/// The range of historical sessions we store. [first, last)
98	#[pallet::storage]
99	pub type StoredRange<T> = StorageValue<_, (SessionIndex, SessionIndex), OptionQuery>;
100
101	#[pallet::event]
102	#[pallet::generate_deposit(pub(super) fn deposit_event)]
103	pub enum Event<T> {
104		/// The merkle root of the validators of the said session were stored
105		RootStored { index: SessionIndex },
106		/// The merkle roots of up to this session index were pruned
107		RootsPruned { up_to: SessionIndex },
108	}
109}
110
111impl<T: Config> Pallet<T> {
112	/// Prune historical stored session roots up to (but not including)
113	/// `up_to`.
114	pub fn prune_up_to(up_to: SessionIndex) {
115		StoredRange::<T>::mutate(|range| {
116			let (start, end) = match *range {
117				Some(range) => range,
118				None => return, // nothing to prune.
119			};
120
121			let up_to = core::cmp::min(up_to, end);
122
123			if up_to < start {
124				return; // out of bounds. harmless.
125			}
126
127			(start..up_to).for_each(HistoricalSessions::<T>::remove);
128
129			let new_start = up_to;
130			*range = if new_start == end {
131				None // nothing is stored.
132			} else {
133				Some((new_start, end))
134			}
135		});
136
137		Self::deposit_event(Event::<T>::RootsPruned { up_to });
138	}
139
140	fn full_id_validators() -> Vec<(T::ValidatorId, T::FullIdentification)> {
141		<Session<T>>::validators()
142			.into_iter()
143			.filter_map(|validator| {
144				T::FullIdentificationOf::convert(validator.clone())
145					.map(|full_id| (validator, full_id))
146			})
147			.collect::<Vec<_>>()
148	}
149}
150
151impl<T: Config> ValidatorSet<T::AccountId> for Pallet<T> {
152	type ValidatorId = T::ValidatorId;
153	type ValidatorIdOf = T::ValidatorIdOf;
154
155	fn session_index() -> sp_staking::SessionIndex {
156		super::Pallet::<T>::current_index()
157	}
158
159	fn validators() -> Vec<Self::ValidatorId> {
160		super::Pallet::<T>::validators()
161	}
162}
163
164impl<T: Config> ValidatorSetWithIdentification<T::AccountId> for Pallet<T> {
165	type Identification = T::FullIdentification;
166	type IdentificationOf = T::FullIdentificationOf;
167}
168
169/// Specialization of the crate-level `SessionManager` which returns the set of full identification
170/// when creating a new session.
171pub trait SessionManager<ValidatorId, FullIdentification>:
172	pallet_session::SessionManager<ValidatorId>
173{
174	/// If there was a validator set change, its returns the set of new validators along with their
175	/// full identifications.
176	fn new_session(new_index: SessionIndex) -> Option<Vec<(ValidatorId, FullIdentification)>>;
177	fn new_session_genesis(
178		new_index: SessionIndex,
179	) -> Option<Vec<(ValidatorId, FullIdentification)>> {
180		<Self as SessionManager<_, _>>::new_session(new_index)
181	}
182	fn start_session(start_index: SessionIndex);
183	fn end_session(end_index: SessionIndex);
184}
185
186/// An `SessionManager` implementation that wraps an inner `I` and also
187/// sets the historical trie root of the ending session.
188pub struct NoteHistoricalRoot<T, I>(core::marker::PhantomData<(T, I)>);
189
190impl<T: Config, I: SessionManager<T::ValidatorId, T::FullIdentification>> NoteHistoricalRoot<T, I> {
191	fn do_new_session(new_index: SessionIndex, is_genesis: bool) -> Option<Vec<T::ValidatorId>> {
192		<StoredRange<T>>::mutate(|range| {
193			range.get_or_insert_with(|| (new_index, new_index)).1 = new_index + 1;
194		});
195
196		let new_validators_and_id = if is_genesis {
197			<I as SessionManager<_, _>>::new_session_genesis(new_index)
198		} else {
199			<I as SessionManager<_, _>>::new_session(new_index)
200		};
201		let new_validators_opt = new_validators_and_id
202			.as_ref()
203			.map(|new_validators| new_validators.iter().map(|(v, _id)| v.clone()).collect());
204
205		if let Some(new_validators) = new_validators_and_id {
206			let count = new_validators.len() as ValidatorCount;
207			match ProvingTrie::<T>::generate_for(new_validators) {
208				Ok(trie) => {
209					<HistoricalSessions<T>>::insert(new_index, &(trie.root, count));
210					Pallet::<T>::deposit_event(Event::RootStored { index: new_index });
211				},
212				Err(reason) => {
213					log::error!(
214						target: LOG_TARGET,
215						"Failed to generate historical ancestry-inclusion proof: {:?}",
216						reason,
217					);
218				},
219			};
220		} else {
221			let previous_index = new_index.saturating_sub(1);
222			if let Some(previous_session) = <HistoricalSessions<T>>::get(previous_index) {
223				<HistoricalSessions<T>>::insert(new_index, previous_session);
224				Pallet::<T>::deposit_event(Event::RootStored { index: new_index });
225			}
226		}
227
228		new_validators_opt
229	}
230}
231
232impl<T: Config, I> pallet_session::SessionManager<T::ValidatorId> for NoteHistoricalRoot<T, I>
233where
234	I: SessionManager<T::ValidatorId, T::FullIdentification>,
235{
236	fn new_session(new_index: SessionIndex) -> Option<Vec<T::ValidatorId>> {
237		Self::do_new_session(new_index, false)
238	}
239
240	fn new_session_genesis(new_index: SessionIndex) -> Option<Vec<T::ValidatorId>> {
241		Self::do_new_session(new_index, true)
242	}
243
244	fn start_session(start_index: SessionIndex) {
245		<I as SessionManager<_, _>>::start_session(start_index)
246	}
247
248	fn end_session(end_index: SessionIndex) {
249		onchain::store_session_validator_set_to_offchain::<T>(end_index);
250		<I as SessionManager<_, _>>::end_session(end_index)
251	}
252}
253
254/// A tuple of the validator's ID and their full identification.
255pub type IdentificationTuple<T> =
256	(<T as pallet_session::Config>::ValidatorId, <T as Config>::FullIdentification);
257
258/// A trie instance for checking and generating proofs.
259pub struct ProvingTrie<T: Config> {
260	db: MemoryDB<T::Hashing>,
261	root: T::Hash,
262}
263
264impl<T: Config> ProvingTrie<T> {
265	fn generate_for<I>(validators: I) -> Result<Self, &'static str>
266	where
267		I: IntoIterator<Item = (T::ValidatorId, T::FullIdentification)>,
268	{
269		let mut db = MemoryDB::with_hasher(RandomState::default());
270		let mut root = Default::default();
271
272		{
273			let mut trie = TrieDBMutBuilderV0::new(&mut db, &mut root).build();
274			for (i, (validator, full_id)) in validators.into_iter().enumerate() {
275				let i = i as u32;
276				let keys = match <Session<T>>::load_keys(&validator) {
277					None => continue,
278					Some(k) => k,
279				};
280
281				let id_tuple = (validator, full_id);
282
283				// map each key to the owner index.
284				for key_id in T::Keys::key_ids() {
285					let key = keys.get_raw(*key_id);
286					let res =
287						(key_id, key).using_encoded(|k| i.using_encoded(|v| trie.insert(k, v)));
288
289					res.map_err(|_| "failed to insert into trie")?;
290				}
291
292				// map each owner index to the full identification.
293				i.using_encoded(|k| id_tuple.using_encoded(|v| trie.insert(k, v)))
294					.map_err(|_| "failed to insert into trie")?;
295			}
296		}
297
298		Ok(ProvingTrie { db, root })
299	}
300
301	fn from_proof(root: T::Hash, proof: StorageProof) -> Self {
302		ProvingTrie { db: proof.into_memory_db(), root }
303	}
304
305	/// Prove the full verification data for a given key and key ID.
306	pub fn prove(&self, key_id: KeyTypeId, key_data: &[u8]) -> Option<Vec<Vec<u8>>> {
307		let mut recorder = Recorder::<LayoutV0<T::Hashing>>::new();
308		self.query(key_id, key_data, Some(&mut recorder));
309
310		Some(recorder.into_raw_storage_proof())
311	}
312
313	/// Access the underlying trie root.
314	pub fn root(&self) -> &T::Hash {
315		&self.root
316	}
317
318	/// Search for a key inside the proof.
319	fn query(
320		&self,
321		key_id: KeyTypeId,
322		key_data: &[u8],
323		recorder: Option<&mut dyn TrieRecorder<T::Hash>>,
324	) -> Option<IdentificationTuple<T>> {
325		let trie = TrieDBBuilder::new(&self.db, &self.root)
326			.with_optional_recorder(recorder)
327			.build();
328
329		let val_idx = (key_id, key_data)
330			.using_encoded(|s| trie.get(s))
331			.ok()?
332			.and_then(|raw| u32::decode(&mut &*raw).ok())?;
333
334		val_idx
335			.using_encoded(|s| trie.get(s))
336			.ok()?
337			.and_then(|raw| <IdentificationTuple<T>>::decode(&mut &*raw).ok())
338	}
339}
340
341impl<T: Config, D: AsRef<[u8]>> KeyOwnerProofSystem<(KeyTypeId, D)> for Pallet<T> {
342	type Proof = MembershipProof;
343	type IdentificationTuple = IdentificationTuple<T>;
344
345	fn prove(key: (KeyTypeId, D)) -> Option<Self::Proof> {
346		let session = <Session<T>>::current_index();
347		let validators = Self::full_id_validators();
348
349		let count = validators.len() as ValidatorCount;
350
351		let trie = ProvingTrie::<T>::generate_for(validators).ok()?;
352
353		let (id, data) = key;
354		trie.prove(id, data.as_ref()).map(|trie_nodes| MembershipProof {
355			session,
356			trie_nodes,
357			validator_count: count,
358		})
359	}
360
361	fn check_proof(key: (KeyTypeId, D), proof: Self::Proof) -> Option<IdentificationTuple<T>> {
362		fn print_error<E: Debug>(e: E) {
363			log::error!(
364				target: LOG_TARGET,
365				"Rejecting equivocation report because of key ownership proof error: {:?}", e
366			);
367		}
368
369		let (id, data) = key;
370		let (root, count) = if proof.session == <Session<T>>::current_index() {
371			let validators = Self::full_id_validators();
372			let count = validators.len() as ValidatorCount;
373			let trie = ProvingTrie::<T>::generate_for(validators).map_err(print_error).ok()?;
374			(trie.root, count)
375		} else {
376			<HistoricalSessions<T>>::get(&proof.session)?
377		};
378
379		if count != proof.validator_count {
380			print_error("InvalidCount");
381			return None;
382		}
383
384		let proof = StorageProof::new_with_duplicate_nodes_check(proof.trie_nodes)
385			.map_err(print_error)
386			.ok()?;
387		let mut accessed_nodes_tracker = AccessedNodesTracker::<T::Hash>::new(proof.len());
388		let trie = ProvingTrie::<T>::from_proof(root, proof);
389		let res = trie.query(id, data.as_ref(), Some(&mut accessed_nodes_tracker))?;
390		accessed_nodes_tracker.ensure_no_unused_nodes().map_err(print_error).ok()?;
391		Some(res)
392	}
393}
394
395#[cfg(test)]
396pub(crate) mod tests {
397	use super::*;
398	use crate::mock::{
399		force_new_session, set_next_validators, NextValidators, Session, System, Test,
400	};
401	use alloc::vec;
402
403	use sp_runtime::{key_types::DUMMY, testing::UintAuthorityId, BuildStorage};
404	use sp_state_machine::BasicExternalities;
405
406	use frame_support::traits::{KeyOwnerProofSystem, OnInitialize};
407
408	type Historical = Pallet<Test>;
409
410	pub(crate) fn new_test_ext() -> sp_io::TestExternalities {
411		let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap();
412		let keys: Vec<_> = NextValidators::get()
413			.iter()
414			.cloned()
415			.map(|i| (i, i, UintAuthorityId(i).into()))
416			.collect();
417		BasicExternalities::execute_with_storage(&mut t, || {
418			for (ref k, ..) in &keys {
419				frame_system::Pallet::<Test>::inc_providers(k);
420			}
421		});
422		pallet_session::GenesisConfig::<Test> { keys, ..Default::default() }
423			.assimilate_storage(&mut t)
424			.unwrap();
425		sp_io::TestExternalities::new(t)
426	}
427
428	#[test]
429	fn generated_proof_is_good() {
430		new_test_ext().execute_with(|| {
431			set_next_validators(vec![1, 2]);
432			force_new_session();
433
434			System::set_block_number(1);
435			Session::on_initialize(1);
436
437			let encoded_key_1 = UintAuthorityId(1).encode();
438			let proof = Historical::prove((DUMMY, &encoded_key_1[..])).unwrap();
439
440			// proof-checking in the same session is OK.
441			assert!(Historical::check_proof((DUMMY, &encoded_key_1[..]), proof.clone()).is_some());
442
443			set_next_validators(vec![1, 2, 4]);
444			force_new_session();
445
446			System::set_block_number(2);
447			Session::on_initialize(2);
448
449			assert!(Historical::historical_root(proof.session).is_some());
450			assert!(Session::current_index() > proof.session);
451
452			// proof-checking in the next session is also OK.
453			assert!(Historical::check_proof((DUMMY, &encoded_key_1[..]), proof.clone()).is_some());
454
455			set_next_validators(vec![1, 2, 5]);
456
457			force_new_session();
458			System::set_block_number(3);
459			Session::on_initialize(3);
460		});
461	}
462
463	#[test]
464	fn prune_up_to_works() {
465		new_test_ext().execute_with(|| {
466			for i in 1..99u64 {
467				set_next_validators(vec![i]);
468				force_new_session();
469
470				System::set_block_number(i);
471				Session::on_initialize(i);
472			}
473
474			assert_eq!(<StoredRange<Test>>::get(), Some((0, 100)));
475
476			for i in 0..100 {
477				assert!(Historical::historical_root(i).is_some())
478			}
479
480			Historical::prune_up_to(10);
481			assert_eq!(<StoredRange<Test>>::get(), Some((10, 100)));
482
483			Historical::prune_up_to(9);
484			assert_eq!(<StoredRange<Test>>::get(), Some((10, 100)));
485
486			for i in 10..100 {
487				assert!(Historical::historical_root(i).is_some())
488			}
489
490			Historical::prune_up_to(99);
491			assert_eq!(<StoredRange<Test>>::get(), Some((99, 100)));
492
493			Historical::prune_up_to(100);
494			assert_eq!(<StoredRange<Test>>::get(), None);
495
496			for i in 99..199u64 {
497				set_next_validators(vec![i]);
498				force_new_session();
499
500				System::set_block_number(i);
501				Session::on_initialize(i);
502			}
503
504			assert_eq!(<StoredRange<Test>>::get(), Some((100, 200)));
505
506			for i in 100..200 {
507				assert!(Historical::historical_root(i).is_some())
508			}
509
510			Historical::prune_up_to(9999);
511			assert_eq!(<StoredRange<Test>>::get(), None);
512
513			for i in 100..200 {
514				assert!(Historical::historical_root(i).is_none())
515			}
516		});
517	}
518}