1#![warn(missing_docs)]
22#![cfg_attr(not(feature = "std"), no_std)]
23
24extern crate alloc;
25
26pub use pallet::*;
27
28use alloc::vec::Vec;
29use core::cmp::Ordering;
30use frame::{
31 deps::{
32 sp_io::{self, MultiRemovalResults},
33 sp_runtime,
34 },
35 prelude::*,
36};
37use serde::{Deserialize, Serialize};
38use sp_application_crypto::RuntimeAppPublic;
39use sp_mixnet::types::{
40 AuthorityId, AuthoritySignature, KxPublic, Mixnode, MixnodesErr, PeerId, SessionIndex,
41 SessionPhase, SessionStatus, KX_PUBLIC_SIZE,
42};
43
44const LOG_TARGET: &str = "runtime::mixnet";
45
46pub type AuthorityIndex = u32;
48
49#[derive(
55 Clone,
56 Decode,
57 DecodeWithMemTracking,
58 Encode,
59 MaxEncodedLen,
60 PartialEq,
61 TypeInfo,
62 Debug,
63 Serialize,
64 Deserialize,
65)]
66pub struct BoundedMixnode<ExternalAddresses> {
67 pub kx_public: KxPublic,
69 pub peer_id: PeerId,
71 pub external_addresses: ExternalAddresses,
73}
74
75impl<MaxExternalAddressSize, MaxExternalAddresses> Into<Mixnode>
76 for BoundedMixnode<BoundedVec<BoundedVec<u8, MaxExternalAddressSize>, MaxExternalAddresses>>
77{
78 fn into(self) -> Mixnode {
79 Mixnode {
80 kx_public: self.kx_public,
81 peer_id: self.peer_id,
82 external_addresses: self
83 .external_addresses
84 .into_iter()
85 .map(BoundedVec::into_inner)
86 .collect(),
87 }
88 }
89}
90
91impl<MaxExternalAddressSize: Get<u32>, MaxExternalAddresses: Get<u32>> From<Mixnode>
92 for BoundedMixnode<BoundedVec<BoundedVec<u8, MaxExternalAddressSize>, MaxExternalAddresses>>
93{
94 fn from(mixnode: Mixnode) -> Self {
95 Self {
96 kx_public: mixnode.kx_public,
97 peer_id: mixnode.peer_id,
98 external_addresses: mixnode
99 .external_addresses
100 .into_iter()
101 .flat_map(|addr| match addr.try_into() {
102 Ok(addr) => Some(addr),
103 Err(addr) => {
104 log::debug!(
105 target: LOG_TARGET,
106 "Mixnode external address {addr:x?} too long; discarding",
107 );
108 None
109 },
110 })
111 .take(MaxExternalAddresses::get() as usize)
112 .collect::<Vec<_>>()
113 .try_into()
114 .expect("Excess external addresses discarded with take()"),
115 }
116 }
117}
118
119pub type BoundedMixnodeFor<T> = BoundedMixnode<
121 BoundedVec<
122 BoundedVec<u8, <T as Config>::MaxExternalAddressSize>,
123 <T as Config>::MaxExternalAddressesPerMixnode,
124 >,
125>;
126
127#[derive(Clone, Decode, DecodeWithMemTracking, Encode, PartialEq, TypeInfo, Debug)]
134pub struct Registration<BlockNumber, BoundedMixnode> {
135 pub block_number: BlockNumber,
140 pub session_index: SessionIndex,
143 pub authority_index: AuthorityIndex,
145 pub mixnode: BoundedMixnode,
147}
148
149pub type RegistrationFor<T> = Registration<BlockNumberFor<T>, BoundedMixnodeFor<T>>;
151
152fn check_removed_all(res: MultiRemovalResults) {
157 debug_assert!(res.maybe_cursor.is_none());
158}
159
160fn twox<BlockNumber: UniqueSaturatedInto<u64>>(
161 block_number: BlockNumber,
162 kx_public: &KxPublic,
163) -> u64 {
164 let block_number: u64 = block_number.unique_saturated_into();
165 let mut data = [0; 8 + KX_PUBLIC_SIZE];
166 data[..8].copy_from_slice(&block_number.to_le_bytes());
167 data[8..].copy_from_slice(kx_public);
168 u64::from_le_bytes(sp_io::hashing::twox_64(&data))
169}
170
171#[frame::pallet(dev_mode)]
176pub mod pallet {
177 use super::*;
178 #[pallet::pallet]
179 pub struct Pallet<T>(_);
180
181 #[pallet::config]
182 pub trait Config: frame_system::Config + CreateBare<Call<Self>> {
183 #[pallet::constant]
185 type MaxAuthorities: Get<AuthorityIndex>;
186
187 #[pallet::constant]
189 type MaxExternalAddressSize: Get<u32>;
190
191 #[pallet::constant]
193 type MaxExternalAddressesPerMixnode: Get<u32>;
194
195 type NextSessionRotation: EstimateNextSessionRotation<BlockNumberFor<Self>>;
198
199 #[pallet::constant]
201 type NumCoverToCurrentBlocks: Get<BlockNumberFor<Self>>;
202
203 #[pallet::constant]
205 type NumRequestsToCurrentBlocks: Get<BlockNumberFor<Self>>;
206
207 #[pallet::constant]
209 type NumCoverToPrevBlocks: Get<BlockNumberFor<Self>>;
210
211 #[pallet::constant]
215 type NumRegisterStartSlackBlocks: Get<BlockNumberFor<Self>>;
216
217 #[pallet::constant]
222 type NumRegisterEndSlackBlocks: Get<BlockNumberFor<Self>>;
223
224 #[pallet::constant]
226 type RegistrationPriority: Get<TransactionPriority>;
227
228 #[pallet::constant]
231 type MinMixnodes: Get<u32>;
232 }
233
234 #[pallet::storage]
237 pub(crate) type CurrentSessionIndex<T> = StorageValue<_, SessionIndex, ValueQuery>;
238
239 #[pallet::storage]
241 pub(crate) type CurrentSessionStartBlock<T> = StorageValue<_, BlockNumberFor<T>, ValueQuery>;
242
243 #[pallet::storage]
245 pub(crate) type NextAuthorityIds<T> = StorageMap<_, Identity, AuthorityIndex, AuthorityId>;
246
247 #[pallet::storage]
254 pub(crate) type Mixnodes<T> =
255 StorageDoubleMap<_, Identity, SessionIndex, Identity, AuthorityIndex, BoundedMixnodeFor<T>>;
256
257 #[pallet::genesis_config]
258 #[derive(DefaultNoBound)]
259 pub struct GenesisConfig<T: Config> {
260 pub mixnodes: BoundedVec<BoundedMixnodeFor<T>, T::MaxAuthorities>,
262 }
263
264 #[pallet::genesis_build]
265 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
266 fn build(&self) {
267 assert!(
268 Mixnodes::<T>::iter_prefix_values(0).next().is_none(),
269 "Initial mixnodes already set"
270 );
271 for (i, mixnode) in self.mixnodes.iter().enumerate() {
272 Mixnodes::<T>::insert(0, i as AuthorityIndex, mixnode);
275 }
276 }
277 }
278
279 #[pallet::call]
280 impl<T: Config> Pallet<T> {
281 #[pallet::call_index(0)]
283 #[pallet::weight(1)] pub fn register(
285 origin: OriginFor<T>,
286 registration: RegistrationFor<T>,
287 _signature: AuthoritySignature,
288 ) -> DispatchResult {
289 ensure_none(origin)?;
290
291 debug_assert_eq!(registration.session_index, CurrentSessionIndex::<T>::get());
293 debug_assert!(registration.authority_index < T::MaxAuthorities::get());
294
295 Mixnodes::<T>::insert(
296 registration.session_index + 1,
298 registration.authority_index,
299 registration.mixnode,
300 );
301
302 Ok(())
303 }
304 }
305
306 #[allow(deprecated)]
307 #[pallet::validate_unsigned]
308 impl<T: Config> ValidateUnsigned for Pallet<T> {
309 type Call = Call<T>;
310
311 fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity {
312 let Self::Call::register { registration, signature } = call else {
313 return InvalidTransaction::Call.into();
314 };
315
316 match registration.session_index.cmp(&CurrentSessionIndex::<T>::get()) {
318 Ordering::Greater => return InvalidTransaction::Future.into(),
319 Ordering::Less => return InvalidTransaction::Stale.into(),
320 Ordering::Equal => (),
321 }
322
323 if registration.authority_index >= T::MaxAuthorities::get() {
325 return InvalidTransaction::BadProof.into();
326 }
327 let Some(authority_id) = NextAuthorityIds::<T>::get(registration.authority_index)
328 else {
329 return InvalidTransaction::BadProof.into();
330 };
331
332 if Self::already_registered(registration.session_index, registration.authority_index) {
334 return InvalidTransaction::Stale.into();
335 }
336
337 let signature_ok = registration.using_encoded(|encoded_registration| {
341 authority_id.verify(&encoded_registration, signature)
342 });
343 if !signature_ok {
344 return InvalidTransaction::BadProof.into();
345 }
346
347 ValidTransaction::with_tag_prefix("MixnetRegistration")
348 .priority(T::RegistrationPriority::get())
349 .and_provides((
352 registration.session_index,
353 registration.authority_index,
354 authority_id,
355 ))
356 .longevity(
357 (T::NextSessionRotation::average_session_length() / 2_u32.into())
358 .try_into()
359 .unwrap_or(64_u64),
360 )
361 .build()
362 }
363 }
364}
365
366impl<T: Config> Pallet<T> {
367 fn session_phase() -> SessionPhase {
369 let block_in_phase = frame_system::Pallet::<T>::block_number()
370 .saturating_sub(CurrentSessionStartBlock::<T>::get());
371 let Some(block_in_phase) = block_in_phase.checked_sub(&T::NumCoverToCurrentBlocks::get())
372 else {
373 return SessionPhase::CoverToCurrent;
374 };
375 let Some(block_in_phase) =
376 block_in_phase.checked_sub(&T::NumRequestsToCurrentBlocks::get())
377 else {
378 return SessionPhase::RequestsToCurrent;
379 };
380 if block_in_phase < T::NumCoverToPrevBlocks::get() {
381 SessionPhase::CoverToPrev
382 } else {
383 SessionPhase::DisconnectFromPrev
384 }
385 }
386
387 pub fn session_status() -> SessionStatus {
389 SessionStatus {
390 current_index: CurrentSessionIndex::<T>::get(),
391 phase: Self::session_phase(),
392 }
393 }
394
395 fn mixnodes(session_index: SessionIndex) -> Result<Vec<Mixnode>, MixnodesErr> {
398 let mixnodes: Vec<_> =
399 Mixnodes::<T>::iter_prefix_values(session_index).map(Into::into).collect();
400 if mixnodes.len() < T::MinMixnodes::get() as usize {
401 Err(MixnodesErr::InsufficientRegistrations {
402 num: mixnodes.len() as u32,
403 min: T::MinMixnodes::get(),
404 })
405 } else {
406 Ok(mixnodes)
407 }
408 }
409
410 pub fn prev_mixnodes() -> Result<Vec<Mixnode>, MixnodesErr> {
412 let Some(prev_session_index) = CurrentSessionIndex::<T>::get().checked_sub(1) else {
413 return Err(MixnodesErr::InsufficientRegistrations {
414 num: 0,
415 min: T::MinMixnodes::get(),
416 });
417 };
418 Self::mixnodes(prev_session_index)
419 }
420
421 pub fn current_mixnodes() -> Result<Vec<Mixnode>, MixnodesErr> {
423 Self::mixnodes(CurrentSessionIndex::<T>::get())
424 }
425
426 fn should_register_by_session_progress(
428 block_number: BlockNumberFor<T>,
429 mixnode: &Mixnode,
430 ) -> bool {
431 let block_in_session = block_number.saturating_sub(CurrentSessionStartBlock::<T>::get());
434 if block_in_session < T::NumRegisterStartSlackBlocks::get() {
435 return false;
436 }
437
438 let (Some(end_block), _weight) =
439 T::NextSessionRotation::estimate_next_session_rotation(block_number)
440 else {
441 return true;
444 };
445
446 let remaining_blocks = end_block
447 .saturating_sub(block_number)
448 .saturating_sub(T::NumRegisterEndSlackBlocks::get());
449 if remaining_blocks.is_zero() {
450 return true;
453 }
454
455 let random = twox(block_number, &mixnode.kx_public);
461 random.is_multiple_of(remaining_blocks.try_into().unwrap_or(u64::MAX))
462 }
463
464 fn next_local_authority() -> Option<(AuthorityIndex, AuthorityId)> {
465 let mut local_ids = AuthorityId::all();
468 local_ids.sort();
469 NextAuthorityIds::<T>::iter().find(|(_index, id)| local_ids.binary_search(id).is_ok())
470 }
471
472 fn already_registered(session_index: SessionIndex, authority_index: AuthorityIndex) -> bool {
475 Mixnodes::<T>::contains_key(session_index + 1, authority_index)
476 }
477
478 pub fn maybe_register(session_index: SessionIndex, mixnode: Mixnode) -> bool {
494 let current_session_index = CurrentSessionIndex::<T>::get();
495 if session_index != current_session_index {
496 log::trace!(
497 target: LOG_TARGET,
498 "Session {session_index} registration attempted, \
499 but current session is {current_session_index}",
500 );
501 return false;
502 }
503
504 let block_number = frame_system::Pallet::<T>::block_number();
505 if !Self::should_register_by_session_progress(block_number, &mixnode) {
506 log::trace!(
507 target: LOG_TARGET,
508 "Waiting for the session to progress further before registering",
509 );
510 return false;
511 }
512
513 let Some((authority_index, authority_id)) = Self::next_local_authority() else {
514 log::trace!(
515 target: LOG_TARGET,
516 "Not an authority in the next session; cannot register a mixnode",
517 );
518 return false;
519 };
520
521 if Self::already_registered(session_index, authority_index) {
522 log::trace!(
523 target: LOG_TARGET,
524 "Already registered a mixnode for the next session",
525 );
526 return false;
527 }
528
529 let registration =
530 Registration { block_number, session_index, authority_index, mixnode: mixnode.into() };
531 let Some(signature) = authority_id.sign(®istration.encode()) else {
532 log::debug!(target: LOG_TARGET, "Failed to sign registration");
533 return false;
534 };
535 let call = Call::register { registration, signature };
536 let xt = T::create_bare(call.into());
537 match SubmitTransaction::<T, Call<T>>::submit_transaction(xt) {
538 Ok(()) => true,
539 Err(()) => {
540 log::debug!(
541 target: LOG_TARGET,
542 "Failed to submit registration transaction",
543 );
544 false
545 },
546 }
547 }
548}
549
550impl<T: Config> sp_runtime::BoundToRuntimeAppPublic for Pallet<T> {
551 type Public = AuthorityId;
552}
553
554impl<T: Config> OneSessionHandler<T::AccountId> for Pallet<T> {
555 type Key = AuthorityId;
556
557 fn on_genesis_session<'a, I: 'a>(validators: I)
558 where
559 I: Iterator<Item = (&'a T::AccountId, Self::Key)>,
560 {
561 assert!(
562 NextAuthorityIds::<T>::iter().next().is_none(),
563 "Initial authority IDs already set"
564 );
565 for (i, (_, authority_id)) in validators.enumerate() {
566 NextAuthorityIds::<T>::insert(i as AuthorityIndex, authority_id);
567 }
568 }
569
570 fn on_new_session<'a, I: 'a>(changed: bool, _validators: I, queued_validators: I)
571 where
572 I: Iterator<Item = (&'a T::AccountId, Self::Key)>,
573 {
574 let session_index = CurrentSessionIndex::<T>::mutate(|index| {
575 *index += 1;
576 *index
577 });
578 CurrentSessionStartBlock::<T>::put(frame_system::Pallet::<T>::block_number());
579
580 if let Some(prev_prev_session_index) = session_index.checked_sub(2) {
582 check_removed_all(Mixnodes::<T>::clear_prefix(
583 prev_prev_session_index,
584 T::MaxAuthorities::get(),
585 None,
586 ));
587 }
588
589 if changed {
590 check_removed_all(NextAuthorityIds::<T>::clear(T::MaxAuthorities::get(), None));
594 for (i, (_, authority_id)) in queued_validators.enumerate() {
595 NextAuthorityIds::<T>::insert(i as AuthorityIndex, authority_id);
596 }
597 }
598 }
599
600 fn on_disabled(_i: u32) {
601 }
604}