pezpallet_session/historical/
mod.rs

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