referrerpolicy=no-referrer-when-downgrade

pallet_mixnet/
lib.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
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//! This pallet is responsible for determining the current mixnet session and phase, and the
19//! mixnode set for each session.
20
21#![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
46/// Index of an authority in the authority list for a session.
47pub type AuthorityIndex = u32;
48
49////////////////////////////////////////////////////////////////////////////////
50// Bounded mixnode type
51////////////////////////////////////////////////////////////////////////////////
52
53/// Like [`Mixnode`], but encoded size is bounded.
54#[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	/// Key-exchange public key for the mixnode.
68	pub kx_public: KxPublic,
69	/// libp2p peer ID of the mixnode.
70	pub peer_id: PeerId,
71	/// External addresses for the mixnode, in multiaddr format, UTF-8 encoded.
72	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
119/// [`BoundedMixnode`] type for the given configuration.
120pub type BoundedMixnodeFor<T> = BoundedMixnode<
121	BoundedVec<
122		BoundedVec<u8, <T as Config>::MaxExternalAddressSize>,
123		<T as Config>::MaxExternalAddressesPerMixnode,
124	>,
125>;
126
127////////////////////////////////////////////////////////////////////////////////
128// Registration type
129////////////////////////////////////////////////////////////////////////////////
130
131/// A mixnode registration. A registration transaction is formed from one of these plus an
132/// [`AuthoritySignature`].
133#[derive(Clone, Decode, DecodeWithMemTracking, Encode, PartialEq, TypeInfo, Debug)]
134pub struct Registration<BlockNumber, BoundedMixnode> {
135	/// Block number at the time of creation. When a registration transaction fails to make it on
136	/// to the chain for whatever reason, we send out another one. We want this one to have a
137	/// different hash in case the earlier transaction got banned somewhere; including the block
138	/// number is a simple way of achieving this.
139	pub block_number: BlockNumber,
140	/// The session during which this registration should be processed. Note that on success the
141	/// mixnode is registered for the _following_ session.
142	pub session_index: SessionIndex,
143	/// The index in the next session's authority list of the authority registering the mixnode.
144	pub authority_index: AuthorityIndex,
145	/// Mixnode information to register for the following session.
146	pub mixnode: BoundedMixnode,
147}
148
149/// [`Registration`] type for the given configuration.
150pub type RegistrationFor<T> = Registration<BlockNumberFor<T>, BoundedMixnodeFor<T>>;
151
152////////////////////////////////////////////////////////////////////////////////
153// Misc helper funcs
154////////////////////////////////////////////////////////////////////////////////
155
156fn 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////////////////////////////////////////////////////////////////////////////////
172// The pallet
173////////////////////////////////////////////////////////////////////////////////
174
175#[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		/// The maximum number of authorities per session.
184		#[pallet::constant]
185		type MaxAuthorities: Get<AuthorityIndex>;
186
187		/// The maximum size of one of a mixnode's external addresses.
188		#[pallet::constant]
189		type MaxExternalAddressSize: Get<u32>;
190
191		/// The maximum number of external addresses for a mixnode.
192		#[pallet::constant]
193		type MaxExternalAddressesPerMixnode: Get<u32>;
194
195		/// Session progress/length estimation. Used to determine when to send registration
196		/// transactions and the longevity of these transactions.
197		type NextSessionRotation: EstimateNextSessionRotation<BlockNumberFor<Self>>;
198
199		/// Length of the first phase of each session (`CoverToCurrent`), in blocks.
200		#[pallet::constant]
201		type NumCoverToCurrentBlocks: Get<BlockNumberFor<Self>>;
202
203		/// Length of the second phase of each session (`RequestsToCurrent`), in blocks.
204		#[pallet::constant]
205		type NumRequestsToCurrentBlocks: Get<BlockNumberFor<Self>>;
206
207		/// Length of the third phase of each session (`CoverToPrev`), in blocks.
208		#[pallet::constant]
209		type NumCoverToPrevBlocks: Get<BlockNumberFor<Self>>;
210
211		/// The number of "slack" blocks at the start of each session, during which
212		/// [`maybe_register`](Pallet::maybe_register) will not attempt to post registration
213		/// transactions.
214		#[pallet::constant]
215		type NumRegisterStartSlackBlocks: Get<BlockNumberFor<Self>>;
216
217		/// The number of "slack" blocks at the end of each session.
218		/// [`maybe_register`](Pallet::maybe_register) will try to register before this slack
219		/// period, but may post registration transactions during the slack period as a last
220		/// resort.
221		#[pallet::constant]
222		type NumRegisterEndSlackBlocks: Get<BlockNumberFor<Self>>;
223
224		/// Priority of unsigned transactions used to register mixnodes.
225		#[pallet::constant]
226		type RegistrationPriority: Get<TransactionPriority>;
227
228		/// Minimum number of mixnodes. If there are fewer than this many mixnodes registered for a
229		/// session, the mixnet will not be active during the session.
230		#[pallet::constant]
231		type MinMixnodes: Get<u32>;
232	}
233
234	/// Index of the current session. This may be offset relative to the session index tracked by
235	/// eg `pallet_session`; mixnet session indices are independent.
236	#[pallet::storage]
237	pub(crate) type CurrentSessionIndex<T> = StorageValue<_, SessionIndex, ValueQuery>;
238
239	/// Block in which the current session started.
240	#[pallet::storage]
241	pub(crate) type CurrentSessionStartBlock<T> = StorageValue<_, BlockNumberFor<T>, ValueQuery>;
242
243	/// Authority list for the next session.
244	#[pallet::storage]
245	pub(crate) type NextAuthorityIds<T> = StorageMap<_, Identity, AuthorityIndex, AuthorityId>;
246
247	/// Mixnode sets by session index. Only the mixnode sets for the previous, current, and next
248	/// sessions are kept; older sets are discarded.
249	///
250	/// The mixnodes in each set are keyed by authority index so we can easily check if an
251	/// authority has registered a mixnode. The authority indices should only be used during
252	/// registration; the authority indices for the very first session are made up.
253	#[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		/// The mixnode set for the very first session.
261		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				// We just make up authority indices here. This doesn't matter as authority indices
273				// are only used during registration to check an authority doesn't register twice.
274				Mixnodes::<T>::insert(0, i as AuthorityIndex, mixnode);
275			}
276		}
277	}
278
279	#[pallet::call]
280	impl<T: Config> Pallet<T> {
281		/// Register a mixnode for the following session.
282		#[pallet::call_index(0)]
283		#[pallet::weight(1)] // TODO
284		pub fn register(
285			origin: OriginFor<T>,
286			registration: RegistrationFor<T>,
287			_signature: AuthoritySignature,
288		) -> DispatchResult {
289			ensure_none(origin)?;
290
291			// Checked by ValidateUnsigned
292			debug_assert_eq!(registration.session_index, CurrentSessionIndex::<T>::get());
293			debug_assert!(registration.authority_index < T::MaxAuthorities::get());
294
295			Mixnodes::<T>::insert(
296				// Registering for the _following_ session
297				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			// Check session index matches
317			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			// Check authority index is valid
324			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			// Check the authority hasn't registered a mixnode yet
333			if Self::already_registered(registration.session_index, registration.authority_index) {
334				return InvalidTransaction::Stale.into();
335			}
336
337			// Check signature. Note that we don't use regular signed transactions for registration
338			// as we don't want validators to have to pay to register. Spam is prevented by only
339			// allowing one registration per session per validator (see above).
340			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				// Include both authority index _and_ ID in tag in case of forks with different
350				// authority lists
351				.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	/// Returns the phase of the current session.
368	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	/// Returns the index and phase of the current session.
388	pub fn session_status() -> SessionStatus {
389		SessionStatus {
390			current_index: CurrentSessionIndex::<T>::get(),
391			phase: Self::session_phase(),
392		}
393	}
394
395	/// Returns the mixnode set for the given session (which should be either the previous or the
396	/// current session).
397	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	/// Returns the mixnode set for the previous session.
411	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	/// Returns the mixnode set for the current session.
422	pub fn current_mixnodes() -> Result<Vec<Mixnode>, MixnodesErr> {
423		Self::mixnodes(CurrentSessionIndex::<T>::get())
424	}
425
426	/// Is now a good time to register, considering only session progress?
427	fn should_register_by_session_progress(
428		block_number: BlockNumberFor<T>,
429		mixnode: &Mixnode,
430	) -> bool {
431		// At the start of each session there are some "slack" blocks during which we avoid
432		// registering
433		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			// Things aren't going to work terribly well in this case as all the authorities will
442			// just pile in after the slack period...
443			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			// Into the slack time at the end of the session. Not necessarily too late;
451			// registrations are accepted right up until the session ends.
452			return true;
453		}
454
455		// Want uniform distribution over the remaining blocks, so pick this block with probability
456		// 1/remaining_blocks. maybe_register may be called multiple times per block; ensure the
457		// same decision gets made each time by using a hash of the block number and the mixnode's
458		// public key as the "random" source. This is slightly biased as remaining_blocks most
459		// likely won't divide into 2^64, but it doesn't really matter...
460		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		// In the case where multiple local IDs are in the next authority set, we just return the
466		// first one. There's (currently at least) no point in registering multiple times.
467		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	/// `session_index` should be the index of the current session. `authority_index` is the
473	/// authority index in the _next_ session.
474	fn already_registered(session_index: SessionIndex, authority_index: AuthorityIndex) -> bool {
475		Mixnodes::<T>::contains_key(session_index + 1, authority_index)
476	}
477
478	/// Try to register a mixnode for the next session.
479	///
480	/// If a registration extrinsic is submitted, `true` is returned. The caller should avoid
481	/// calling `maybe_register` again for a few blocks, to give the submitted extrinsic a chance
482	/// to get included.
483	///
484	/// With the above exception, `maybe_register` is designed to be called every block. Most of
485	/// the time it will not do anything, for example:
486	///
487	/// - If it is not an appropriate time to submit a registration extrinsic.
488	/// - If the local node has already registered a mixnode for the next session.
489	/// - If the local node is not permitted to register a mixnode for the next session.
490	///
491	/// `session_index` should match `session_status().current_index`; if it does not, `false` is
492	/// returned immediately.
493	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(&registration.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		// Discard the previous previous mixnode set, which we don't need any more
581		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			// Save authority set for the next session. Note that we don't care about the authority
591			// set for the current session; we just care about the key-exchange public keys that
592			// were registered and are stored in Mixnodes.
593			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		// For now, to keep things simple, just ignore
602		// TODO
603	}
604}