pezpallet_session/historical/
mod.rs1pub 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 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
67
68 #[pezpallet::pezpallet]
69 #[pezpallet::storage_version(STORAGE_VERSION)]
70 pub struct Pezpallet<T>(_);
71
72 #[pezpallet::config]
74 pub trait Config: pezpallet_session::Config + pezframe_system::Config {
75 #[allow(deprecated)]
77 type RuntimeEvent: From<Event<Self>>
78 + IsType<<Self as pezframe_system::Config>::RuntimeEvent>;
79
80 type FullIdentification: Parameter;
82
83 type FullIdentificationOf: Convert<Self::ValidatorId, Option<Self::FullIdentification>>;
91 }
92
93 #[pezpallet::storage]
95 #[pezpallet::getter(fn historical_root)]
96 pub type HistoricalSessions<T: Config> =
97 StorageMap<_, Twox64Concat, SessionIndex, (T::Hash, ValidatorCount), OptionQuery>;
98
99 #[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 RootStored { index: SessionIndex },
108 RootsPruned { up_to: SessionIndex },
110 }
111}
112
113impl<T: Config> Pezpallet<T> {
114 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, };
122
123 let up_to = core::cmp::min(up_to, end);
124
125 if up_to < start {
126 return; }
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 } 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
171pub trait SessionManager<ValidatorId, FullIdentification>:
174 pezpallet_session::SessionManager<ValidatorId>
175{
176 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
188pub 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
253pub type IdentificationTuple<T> =
255 (<T as pezpallet_session::Config>::ValidatorId, <T as Config>::FullIdentification);
256
257pub 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 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 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 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 pub fn root(&self) -> &T::Hash {
314 &self.root
315 }
316
317 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 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 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}