plant_session/historical/
mod.rs1pub 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 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
56
57 #[pallet::pallet]
58 #[pallet::storage_version(STORAGE_VERSION)]
59 pub struct Pallet<T>(_);
60
61 #[pallet::config]
63 pub trait Config: plant_session::Config + topsoil_core::system::Config {
64 #[allow(deprecated)]
66 type RuntimeEvent: From<Event<Self>>
67 + IsType<<Self as topsoil_core::system::Config>::RuntimeEvent>;
68
69 type FullIdentification: Parameter;
71
72 type FullIdentificationOf: Convert<Self::ValidatorId, Option<Self::FullIdentification>>;
80 }
81
82 #[pallet::storage]
84 #[pallet::getter(fn historical_root)]
85 pub type HistoricalSessions<T: Config> =
86 StorageMap<_, Twox64Concat, SessionIndex, (T::Hash, ValidatorCount), OptionQuery>;
87
88 #[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 RootStored { index: SessionIndex },
97 RootsPruned { up_to: SessionIndex },
99 }
100}
101
102impl<T: Config> Pallet<T> {
103 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, };
111
112 let up_to = core::cmp::min(up_to, end);
113
114 if up_to < start {
115 return; }
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 } 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
160pub trait SessionManager<ValidatorId, FullIdentification>:
163 plant_session::SessionManager<ValidatorId>
164{
165 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
177pub 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
242pub type IdentificationTuple<T> =
244 (<T as plant_session::Config>::ValidatorId, <T as Config>::FullIdentification);
245
246pub 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 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 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 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 pub fn root(&self) -> &T::Hash {
303 &self.root
304 }
305
306 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 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 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}