Skip to main content

plant_session/historical/
mod.rs

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