pallet_session/historical/
offchain.rs1use alloc::vec::Vec;
27use sp_runtime::{
28 offchain::storage::{MutateStorageError, StorageRetrievalError, StorageValueRef},
29 KeyTypeId,
30};
31use sp_session::MembershipProof;
32
33use super::{shared, Config, IdentificationTuple, ProvingTrie};
34use crate::{Pallet as SessionModule, SessionIndex};
35
36struct ValidatorSet<T: Config> {
38 validator_set: Vec<IdentificationTuple<T>>,
39}
40
41impl<T: Config> ValidatorSet<T> {
42 pub fn load_from_offchain_db(session_index: SessionIndex) -> Option<Self> {
47 let derived_key = shared::derive_key(shared::PREFIX, session_index);
48 StorageValueRef::persistent(derived_key.as_ref())
49 .get::<Vec<(T::ValidatorId, T::FullIdentification)>>()
50 .ok()
51 .flatten()
52 .map(|validator_set| Self { validator_set })
53 }
54
55 #[inline]
56 fn len(&self) -> usize {
57 self.validator_set.len()
58 }
59}
60
61impl<T: Config> core::iter::IntoIterator for ValidatorSet<T> {
64 type Item = (T::ValidatorId, T::FullIdentification);
65 type IntoIter = alloc::vec::IntoIter<Self::Item>;
66 fn into_iter(self) -> Self::IntoIter {
67 self.validator_set.into_iter()
68 }
69}
70
71pub fn prove_session_membership<T: Config, D: AsRef<[u8]>>(
77 session_index: SessionIndex,
78 session_key: (KeyTypeId, D),
79) -> Option<MembershipProof> {
80 let validators = ValidatorSet::<T>::load_from_offchain_db(session_index)?;
81 let count = validators.len() as u32;
82 let trie = ProvingTrie::<T>::generate_for(validators.into_iter()).ok()?;
83
84 let (id, data) = session_key;
85 trie.prove(id, data.as_ref()).map(|trie_nodes| MembershipProof {
86 session: session_index,
87 trie_nodes,
88 validator_count: count,
89 })
90}
91
92pub fn prune_older_than<T: Config>(first_to_keep: SessionIndex) {
98 let derived_key = shared::LAST_PRUNE.to_vec();
99 let entry = StorageValueRef::persistent(derived_key.as_ref());
100 match entry.mutate(
101 |current: Result<Option<SessionIndex>, StorageRetrievalError>| -> Result<_, ()> {
102 match current {
103 Ok(Some(current)) if current < first_to_keep => Ok(first_to_keep),
104 Ok(Some(current)) => Ok(current),
106 Ok(None) => Ok(first_to_keep),
107 Err(_) => Ok(first_to_keep),
111 }
112 },
113 ) {
114 Ok(new_value) => {
115 if new_value < first_to_keep {
117 for session_index in new_value..first_to_keep {
118 let derived_key = shared::derive_key(shared::PREFIX, session_index);
119 let _ = StorageValueRef::persistent(derived_key.as_ref()).clear();
120 }
121 }
122 },
123 Err(MutateStorageError::ConcurrentModification(_)) => {},
124 Err(MutateStorageError::ValueFunctionFailed(_)) => {},
125 }
126}
127
128pub fn keep_newest<T: Config>(n_to_keep: usize) {
130 let session_index = <SessionModule<T>>::current_index();
131 let n_to_keep = n_to_keep as SessionIndex;
132 if n_to_keep < session_index {
133 prune_older_than::<T>(session_index - n_to_keep)
134 }
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140 use crate::{
141 historical::{onchain, Pallet},
142 mock::{force_new_session, set_next_validators, NextValidators, Session, System, Test},
143 };
144
145 use codec::Encode;
146 use sp_core::{
147 crypto::key_types::DUMMY,
148 offchain::{testing::TestOffchainExt, OffchainDbExt, OffchainWorkerExt, StorageKind},
149 };
150 use sp_runtime::{testing::UintAuthorityId, BuildStorage};
151 use sp_state_machine::BasicExternalities;
152
153 use frame_support::traits::{KeyOwnerProofSystem, OnInitialize};
154
155 type Historical = Pallet<Test>;
156
157 pub fn new_test_ext() -> sp_io::TestExternalities {
158 let mut t = frame_system::GenesisConfig::<Test>::default()
159 .build_storage()
160 .expect("Failed to create test externalities.");
161
162 let keys: Vec<_> = NextValidators::get()
163 .iter()
164 .cloned()
165 .map(|i| (i, i, UintAuthorityId(i).into()))
166 .collect();
167
168 BasicExternalities::execute_with_storage(&mut t, || {
169 for (ref k, ..) in &keys {
170 frame_system::Pallet::<Test>::inc_providers(k);
171 }
172 });
173
174 crate::GenesisConfig::<Test> { keys, ..Default::default() }
175 .assimilate_storage(&mut t)
176 .unwrap();
177
178 let mut ext = sp_io::TestExternalities::new(t);
179
180 let (offchain, offchain_state) = TestOffchainExt::with_offchain_db(ext.offchain_db());
181
182 const ITERATIONS: u32 = 5u32;
183 let mut seed = [0u8; 32];
184 seed[0..4].copy_from_slice(&ITERATIONS.to_le_bytes());
185 offchain_state.write().seed = seed;
186
187 ext.register_extension(OffchainDbExt::new(offchain.clone()));
188 ext.register_extension(OffchainWorkerExt::new(offchain));
189 ext
190 }
191
192 #[test]
193 fn encode_decode_roundtrip() {
194 use super::super::{super::Config as SessionConfig, Config as HistoricalConfig};
195 use codec::{Decode, Encode};
196
197 let sample = (
198 22u32 as <Test as SessionConfig>::ValidatorId,
199 7_777_777 as <Test as HistoricalConfig>::FullIdentification,
200 );
201
202 let encoded = sample.encode();
203 let decoded = Decode::decode(&mut encoded.as_slice()).expect("Must decode");
204 assert_eq!(sample, decoded);
205 }
206
207 #[test]
208 fn onchain_to_offchain() {
209 let mut ext = new_test_ext();
210
211 const DATA: &[u8] = &[7, 8, 9, 10, 11];
212 ext.execute_with(|| {
213 b"alphaomega"[..].using_encoded(|key| sp_io::offchain_index::set(key, DATA));
214 });
215
216 ext.persist_offchain_overlay();
217
218 ext.execute_with(|| {
219 let data = b"alphaomega"[..].using_encoded(|key| {
220 sp_io::offchain::local_storage_get(StorageKind::PERSISTENT, key)
221 });
222 assert_eq!(data, Some(DATA.to_vec()));
223 });
224 }
225
226 #[test]
227 fn historical_proof_offchain() {
228 let mut ext = new_test_ext();
229 let encoded_key_1 = UintAuthorityId(1).encode();
230
231 ext.execute_with(|| {
232 set_next_validators(vec![1, 2]);
233 force_new_session();
234
235 System::set_block_number(1);
236 Session::on_initialize(1);
237
238 onchain::store_current_session_validator_set_to_offchain::<Test>();
240 assert_eq!(<SessionModule<Test>>::current_index(), 1);
241
242 set_next_validators(vec![7, 8]);
243
244 force_new_session();
245 });
246
247 ext.persist_offchain_overlay();
248
249 ext.execute_with(|| {
250 System::set_block_number(2);
251 Session::on_initialize(2);
252 assert_eq!(<SessionModule<Test>>::current_index(), 2);
253
254 let proof = prove_session_membership::<Test, _>(1, (DUMMY, &encoded_key_1));
256 assert!(proof.is_some());
257 let proof = proof.expect("Must be Some(Proof)");
258
259 assert!(Historical::check_proof((DUMMY, &encoded_key_1[..]), proof.clone()).is_some());
260 });
261 }
262}