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