#![cfg_attr(not(feature = "std"), no_std)]
#![recursion_limit = "128"]
#![allow(clippy::borrowed_box)]
extern crate alloc;
use alloc::{boxed::Box, vec::Vec};
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
#[cfg(feature = "runtime-benchmarks")]
pub mod benchmarking;
pub mod extension;
pub mod types;
pub mod weights;
pub use pallet::*;
pub use types::*;
pub use weights::WeightInfo;
use codec::{Decode, Encode, MaxEncodedLen};
use core::{
cmp::{self},
ops::Range,
};
use frame_support::{
dispatch::{
extract_actual_weight, DispatchInfo, DispatchResultWithPostInfo, GetDispatchInfo,
PostDispatchInfo,
},
storage::with_storage_layer,
traits::{
reality::{
AddOnlyPeopleTrait, Context, ContextualAlias, CountedMembers, PeopleTrait, PersonalId,
RingIndex,
},
Defensive, EnsureOriginWithArg, IsSubType, OriginTrait,
},
transactional,
weights::WeightMeter,
};
use scale_info::TypeInfo;
use sp_runtime::{
traits::{BadOrigin, Dispatchable},
ArithmeticError, Debug, SaturatedConversion, Saturating,
};
use verifiable::{Alias, GenerateVerifiable};
#[cfg(feature = "runtime-benchmarks")]
pub use benchmarking::BenchmarkHelper;
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::{pallet_prelude::*, traits::Contains};
use frame_system::pallet_prelude::{BlockNumberFor, *};
const LOG_TARGET: &str = "runtime::people";
#[pallet::pallet]
pub struct Pallet<T>(_);
#[pallet::config]
pub trait Config:
frame_system::Config<
RuntimeOrigin: From<Origin>
+ From<<Self::RuntimeOrigin as OriginTrait>::PalletsOrigin>
+ OriginTrait<
PalletsOrigin: From<Origin>
+ TryInto<
Origin,
Error = <Self::RuntimeOrigin as OriginTrait>::PalletsOrigin,
>,
>,
RuntimeCall: Parameter
+ GetDispatchInfo
+ IsSubType<Call<Self>>
+ Dispatchable<
RuntimeOrigin = Self::RuntimeOrigin,
Info = DispatchInfo,
PostInfo = PostDispatchInfo,
>,
>
{
type WeightInfo: WeightInfo;
#[allow(deprecated)]
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
type Crypto: GenerateVerifiable<
Proof: Send + Sync + DecodeWithMemTracking,
Signature: Send + Sync + DecodeWithMemTracking,
Member: DecodeWithMemTracking,
>;
type AccountContexts: Contains<Context>;
#[pallet::constant]
type ChunkPageSize: Get<u32>;
#[pallet::constant]
type MaxRingSize: Get<u32>;
#[pallet::constant]
type OnboardingQueuePageSize: Get<u32>;
#[cfg(feature = "runtime-benchmarks")]
type BenchmarkHelper: BenchmarkHelper<<Self::Crypto as GenerateVerifiable>::StaticChunk>;
}
#[pallet::storage]
pub type Root<T> = StorageMap<_, Blake2_128Concat, RingIndex, RingRoot<T>>;
#[pallet::storage]
pub type CurrentRingIndex<T: Config> = StorageValue<_, u32, ValueQuery>;
#[pallet::storage]
pub type OnboardingSize<T: Config> = StorageValue<_, u32, ValueQuery>;
#[pallet::storage]
pub type RingBuildingPeopleLimit<T: Config> = StorageValue<_, u32, OptionQuery>;
#[pallet::storage]
pub type RingKeys<T: Config> = StorageMap<
_,
Blake2_128Concat,
RingIndex,
BoundedVec<MemberOf<T>, T::MaxRingSize>,
ValueQuery,
>;
#[pallet::storage]
pub type RingKeysStatus<T: Config> =
StorageMap<_, Blake2_128Concat, RingIndex, RingStatus, ValueQuery>;
#[pallet::storage]
pub type PendingSuspensions<T: Config> =
StorageMap<_, Twox64Concat, RingIndex, BoundedVec<u32, T::MaxRingSize>, ValueQuery>;
#[pallet::storage]
pub type ActiveMembers<T: Config> = StorageValue<_, u32, ValueQuery>;
#[pallet::storage]
pub type Keys<T> = CountedStorageMap<_, Blake2_128Concat, MemberOf<T>, PersonalId>;
#[pallet::storage]
pub type KeyMigrationQueue<T: Config> =
StorageMap<_, Blake2_128Concat, PersonalId, MemberOf<T>>;
#[pallet::storage]
pub type People<T: Config> =
StorageMap<_, Blake2_128Concat, PersonalId, PersonRecord<MemberOf<T>, T::AccountId>>;
#[pallet::storage]
pub type AliasToAccount<T> = StorageMap<
_,
Blake2_128Concat,
ContextualAlias,
<T as frame_system::Config>::AccountId,
OptionQuery,
>;
#[pallet::storage]
pub type AccountToAlias<T> = StorageMap<
_,
Blake2_128Concat,
<T as frame_system::Config>::AccountId,
RevisedContextualAlias,
OptionQuery,
>;
#[pallet::storage]
pub type AccountToPersonalId<T> = StorageMap<
_,
Blake2_128Concat,
<T as frame_system::Config>::AccountId,
PersonalId,
OptionQuery,
>;
#[pallet::storage]
pub type Chunks<T> = StorageMap<_, Twox64Concat, PageIndex, ChunksOf<T>, OptionQuery>;
#[pallet::storage]
pub type NextPersonalId<T> = StorageValue<_, PersonalId, ValueQuery>;
#[pallet::storage]
pub type RingsState<T> = StorageValue<_, RingMembersState, ValueQuery>;
#[pallet::storage]
pub type ReservedPersonalId<T: Config> =
StorageMap<_, Twox64Concat, PersonalId, (), OptionQuery>;
#[pallet::storage]
pub type QueuePageIndices<T: Config> = StorageValue<_, (PageIndex, PageIndex), ValueQuery>;
#[pallet::storage]
pub type OnboardingQueue<T> = StorageMap<
_,
Twox64Concat,
PageIndex,
BoundedVec<MemberOf<T>, <T as Config>::OnboardingQueuePageSize>,
ValueQuery,
>;
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
PersonhoodRecognized { who: PersonalId, key: MemberOf<T> },
PersonOnboarding { who: PersonalId, key: MemberOf<T> },
}
#[pallet::extra_constants]
impl<T: Config> Pallet<T> {
pub fn account_setup_time_tolerance() -> BlockNumberFor<T> {
600u32.into()
}
}
#[pallet::error]
pub enum Error<T> {
NotPerson,
NoKey,
InvalidContext,
InvalidAccount,
AccountInUse,
InvalidProof,
InvalidSignature,
NoMembers,
Incomplete,
StillFresh,
TooManyMembers,
KeyAlreadyInUse,
KeyNotFound,
CouldNotPush,
SameKey,
PersonalIdNotReserved,
PersonalIdReservationCannotRenew,
PersonalIdNotReservedOrNotRecognized,
InvalidRing,
SuspensionsPending,
RingAboveMergeThreshold,
InvalidSuspensions,
NoMutationSession,
CouldNotStartMutationSession,
SuspensionSessionInProgress,
TimeOutOfRange,
AliasAccountAlreadySet,
NotSuspended,
Suspended,
InvalidKeyMigration,
KeyAlreadySuspended,
InvalidOnboardingSize,
}
#[pallet::origin]
#[derive(
Clone, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen, TypeInfo, DecodeWithMemTracking,
)]
pub enum Origin {
PersonalIdentity(PersonalId),
PersonalAlias(RevisedContextualAlias),
}
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn integrity_test() {
assert!(
<T as Config>::ChunkPageSize::get() > 0,
"chunk page size must hold at least one element"
);
assert!(<T as Config>::MaxRingSize::get() > 0, "rings must hold at least one person");
assert!(
<T as Config>::MaxRingSize::get() <= <T as Config>::OnboardingQueuePageSize::get(),
"onboarding queue page size must greater than or equal to max ring size"
);
}
fn on_poll(_: BlockNumberFor<T>, weight_meter: &mut WeightMeter) {
if weight_meter.try_consume(T::WeightInfo::on_poll_base()).is_err() {
return;
}
if RingsState::<T>::get().key_migration() {
Self::migrate_keys(weight_meter);
}
if let Some(ring_index) = PendingSuspensions::<T>::iter_keys().next() {
if Self::should_remove_suspended_keys(ring_index, true) &&
weight_meter.can_consume(T::WeightInfo::remove_suspended_people(
T::MaxRingSize::get(),
)) {
let actual = Self::remove_suspended_keys(ring_index);
weight_meter.consume(actual)
}
}
let merge_weight = T::WeightInfo::merge_queue_pages();
if !weight_meter.can_consume(merge_weight) {
return;
}
let merge_action = Self::should_merge_queue_pages();
if let QueueMergeAction::Merge {
initial_head,
new_head,
first_key_page,
second_key_page,
} = merge_action
{
Self::merge_queue_pages(initial_head, new_head, first_key_page, second_key_page);
weight_meter.consume(merge_weight);
}
}
fn on_idle(_block: BlockNumberFor<T>, limit: Weight) -> Weight {
let mut weight_meter = WeightMeter::with_limit(limit.saturating_div(2));
let on_idle_weight = T::WeightInfo::on_idle_base();
if !weight_meter.can_consume(on_idle_weight) {
return weight_meter.consumed();
}
weight_meter.consume(on_idle_weight);
let max_ring_size = T::MaxRingSize::get();
let remove_people_weight = T::WeightInfo::remove_suspended_people(max_ring_size);
let rings_state = RingsState::<T>::get();
if !rings_state.append_only() {
return weight_meter.consumed();
}
let suspension_step_weight = T::WeightInfo::pending_suspensions_iteration();
if !weight_meter.can_consume(suspension_step_weight) {
return weight_meter.consumed();
}
while let Some(ring_index) = PendingSuspensions::<T>::iter_keys().next() {
weight_meter.consume(suspension_step_weight);
if !weight_meter.can_consume(remove_people_weight) {
return weight_meter.consumed();
}
if Self::should_remove_suspended_keys(ring_index, false) {
let actual = Self::remove_suspended_keys(ring_index);
weight_meter.consume(actual)
}
if !weight_meter.can_consume(suspension_step_weight) {
return weight_meter.consumed();
}
}
let onboard_people_weight = T::WeightInfo::onboard_people();
if !weight_meter.can_consume(onboard_people_weight) {
return weight_meter.consumed();
}
let op_res = with_storage_layer::<(), DispatchError, _>(|| Self::onboard_people());
weight_meter.consume(onboard_people_weight);
if let Err(e) = op_res {
log::debug!(target: LOG_TARGET, "failed to onboard people: {:?}", e);
}
let current_ring = CurrentRingIndex::<T>::get();
let should_build_ring_weight = T::WeightInfo::should_build_ring(max_ring_size);
let build_ring_weight = T::WeightInfo::build_ring(max_ring_size);
for ring_index in (0..=current_ring).rev() {
if !weight_meter.can_consume(should_build_ring_weight) {
return weight_meter.consumed();
}
let maybe_to_include = Self::should_build_ring(ring_index, max_ring_size);
weight_meter.consume(should_build_ring_weight);
let Some(to_include) = maybe_to_include else { continue };
if !weight_meter.can_consume(build_ring_weight) {
return weight_meter.consumed();
}
let op_res = with_storage_layer::<(), DispatchError, _>(|| {
Self::build_ring(ring_index, to_include)
});
weight_meter.consume(build_ring_weight);
if let Err(e) = op_res {
log::error!(target: LOG_TARGET, "failed to build ring: {:?}", e);
}
}
weight_meter.consumed()
}
}
#[pallet::genesis_config]
pub struct GenesisConfig<T: Config> {
pub encoded_chunks: Vec<u8>,
#[serde(skip)]
pub _phantom_data: core::marker::PhantomData<T>,
pub onboarding_size: u32,
}
impl<T: Config> Default for GenesisConfig<T> {
fn default() -> Self {
use verifiable::ring_vrf_impl::StaticChunk;
let params = verifiable::ring_vrf_impl::ring_verifier_builder_params();
let chunks: Vec<StaticChunk> = params.0.iter().map(|c| StaticChunk(*c)).collect();
Self {
encoded_chunks: chunks.encode(),
_phantom_data: PhantomData,
onboarding_size: T::MaxRingSize::get(),
}
}
}
#[pallet::genesis_build]
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
fn build(&self) {
let chunks: Vec<<<T as Config>::Crypto as GenerateVerifiable>::StaticChunk> =
Decode::decode(&mut &(self.encoded_chunks.clone())[..])
.expect("couldn't decode chunks");
assert_eq!(chunks.len(), 1 << 9);
let page_size = <T as Config>::ChunkPageSize::get();
let mut page_idx = 0;
let mut chunk_idx = 0;
while chunk_idx < chunks.len() {
let chunk_idx_end = cmp::min(chunk_idx + page_size as usize, chunks.len());
let chunk_page: ChunksOf<T> = chunks[chunk_idx..chunk_idx_end]
.to_vec()
.try_into()
.expect("page size was checked against the array length; qed");
Chunks::<T>::insert(page_idx, chunk_page);
page_idx += 1;
chunk_idx = chunk_idx_end;
}
OnboardingSize::<T>::set(self.onboarding_size);
}
}
#[pallet::call(weight = <T as Config>::WeightInfo)]
impl<T: Config> Pallet<T> {
#[pallet::weight(
T::WeightInfo::should_build_ring(
limit.unwrap_or_else(T::MaxRingSize::get)
).saturating_add(T::WeightInfo::build_ring(limit.unwrap_or_else(T::MaxRingSize::get))))]
#[pallet::call_index(100)]
pub fn build_ring_manual(
origin: OriginFor<T>,
ring_index: RingIndex,
limit: Option<u32>,
) -> DispatchResultWithPostInfo {
ensure_signed(origin)?;
let (keys, mut ring_status) = Self::ring_keys_and_info(ring_index);
let to_include =
Self::should_build_ring(ring_index, limit.unwrap_or_else(T::MaxRingSize::get))
.ok_or(Error::<T>::StillFresh)?;
let (next_revision, mut intermediate) =
if let Some(existing_root) = Root::<T>::get(ring_index) {
(
existing_root.revision.checked_add(1).ok_or(ArithmeticError::Overflow)?,
existing_root.intermediate,
)
} else {
(0, T::Crypto::start_members())
};
T::Crypto::push_members(
&mut intermediate,
keys.iter()
.skip(ring_status.included as usize)
.take(to_include as usize)
.cloned(),
Self::fetch_chunks,
)
.map_err(|_| Error::<T>::CouldNotPush)?;
ring_status.included = ring_status.included.saturating_add(to_include);
RingKeysStatus::<T>::insert(ring_index, ring_status);
let root = T::Crypto::finish_members(intermediate.clone());
let ring_root = RingRoot { root, revision: next_revision, intermediate };
Root::<T>::insert(ring_index, ring_root);
Ok(Pays::No.into())
}
#[pallet::weight(T::WeightInfo::onboard_people())]
#[pallet::call_index(101)]
pub fn onboard_people_manual(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
ensure_signed(origin)?;
let (top_ring_index, mut keys) = Self::available_ring();
let mut ring_status = RingKeysStatus::<T>::get(top_ring_index);
defensive_assert!(
keys.len() == ring_status.total as usize,
"Stored key count doesn't match the actual length"
);
let keys_len = keys.len() as u32;
let open_slots = T::MaxRingSize::get().saturating_sub(keys_len);
let (mut head, tail) = QueuePageIndices::<T>::get();
let old_head = head;
let mut keys_to_include: Vec<MemberOf<T>> =
OnboardingQueue::<T>::take(head).into_inner();
if keys_to_include.len() < open_slots as usize && head != tail {
head = head.checked_add(1).unwrap_or(0);
let second_key_page = OnboardingQueue::<T>::take(head);
defensive_assert!(!second_key_page.is_empty());
keys_to_include.extend(second_key_page.into_iter());
}
let onboarding_size = OnboardingSize::<T>::get();
let (to_include, ring_filled) = Self::should_onboard_people(
top_ring_index,
&ring_status,
open_slots,
keys_to_include.len().saturated_into(),
onboarding_size,
)
.ok_or(Error::<T>::Incomplete)?;
let mut remaining_keys = keys_to_include.split_off(to_include as usize);
for key in keys_to_include.into_iter() {
let personal_id = Keys::<T>::get(&key).defensive().ok_or(Error::<T>::NotPerson)?;
let mut record =
People::<T>::get(personal_id).defensive().ok_or(Error::<T>::KeyNotFound)?;
record.position = RingPosition::Included {
ring_index: top_ring_index,
ring_position: keys.len().saturated_into(),
scheduled_for_removal: false,
};
People::<T>::insert(personal_id, record);
keys.try_push(key).map_err(|_| Error::<T>::TooManyMembers)?;
}
RingKeys::<T>::insert(top_ring_index, keys);
ActiveMembers::<T>::mutate(|active| *active = active.saturating_add(to_include));
ring_status.total = ring_status.total.saturating_add(to_include);
RingKeysStatus::<T>::insert(top_ring_index, ring_status);
if ring_filled {
CurrentRingIndex::<T>::mutate(|i| i.saturating_inc());
}
if remaining_keys.len() > T::OnboardingQueuePageSize::get() as usize {
let split_idx =
remaining_keys.len().saturating_sub(T::OnboardingQueuePageSize::get() as usize);
let second_page_keys: BoundedVec<MemberOf<T>, T::OnboardingQueuePageSize> =
remaining_keys
.split_off(split_idx)
.try_into()
.expect("the list shrunk so it must fit; qed");
let remaining_keys: BoundedVec<MemberOf<T>, T::OnboardingQueuePageSize> =
remaining_keys.try_into().expect("the list shrunk so it must fit; qed");
OnboardingQueue::<T>::insert(old_head, remaining_keys);
OnboardingQueue::<T>::insert(head, second_page_keys);
QueuePageIndices::<T>::put((old_head, tail));
} else if !remaining_keys.is_empty() {
let remaining_keys: BoundedVec<MemberOf<T>, T::OnboardingQueuePageSize> =
remaining_keys.try_into().expect("the list shrunk so it must fit; qed");
OnboardingQueue::<T>::insert(head, remaining_keys);
QueuePageIndices::<T>::put((head, tail));
} else {
if head != tail {
head = head.checked_add(1).unwrap_or(0);
}
QueuePageIndices::<T>::put((head, tail));
}
Ok(Pays::No.into())
}
#[pallet::call_index(102)]
pub fn merge_rings(
origin: OriginFor<T>,
base_ring_index: RingIndex,
target_ring_index: RingIndex,
) -> DispatchResultWithPostInfo {
let _ = ensure_signed(origin)?;
ensure!(RingsState::<T>::get().append_only(), Error::<T>::SuspensionSessionInProgress);
let current_ring_index = CurrentRingIndex::<T>::get();
ensure!(
base_ring_index != target_ring_index &&
base_ring_index != current_ring_index &&
target_ring_index != current_ring_index,
Error::<T>::InvalidRing
);
let (mut base_keys, mut base_ring_status) = Self::ring_keys_and_info(base_ring_index);
ensure!(
base_keys.len() < T::MaxRingSize::get() as usize / 2,
Error::<T>::RingAboveMergeThreshold
);
ensure!(
PendingSuspensions::<T>::decode_len(base_ring_index).unwrap_or(0) == 0,
Error::<T>::SuspensionsPending
);
let target_keys = RingKeys::<T>::get(target_ring_index);
RingKeysStatus::<T>::remove(target_ring_index);
ensure!(
target_keys.len() < T::MaxRingSize::get() as usize / 2,
Error::<T>::RingAboveMergeThreshold
);
ensure!(
PendingSuspensions::<T>::decode_len(target_ring_index).unwrap_or(0) == 0,
Error::<T>::SuspensionsPending
);
base_ring_status.total =
base_ring_status.total.saturating_add(target_keys.len().saturated_into());
for key in target_keys {
let personal_id =
Keys::<T>::get(&key).defensive().ok_or(Error::<T>::KeyNotFound)?;
let mut record =
People::<T>::get(personal_id).defensive().ok_or(Error::<T>::NotPerson)?;
record.position = RingPosition::Included {
ring_index: base_ring_index,
ring_position: base_keys.len().saturated_into(),
scheduled_for_removal: false,
};
base_keys.try_push(key).map_err(|_| Error::<T>::TooManyMembers)?;
People::<T>::insert(personal_id, record)
}
RingKeys::<T>::insert(base_ring_index, base_keys);
RingKeysStatus::<T>::insert(base_ring_index, base_ring_status);
Root::<T>::remove(target_ring_index);
RingKeys::<T>::remove(target_ring_index);
RingKeysStatus::<T>::remove(target_ring_index);
Ok(Pays::No.into())
}
#[pallet::call_index(0)]
#[pallet::weight(T::WeightInfo::under_alias().saturating_add(call.get_dispatch_info().call_weight))]
pub fn under_alias(
origin: OriginFor<T>,
call: Box<<T as frame_system::Config>::RuntimeCall>,
) -> DispatchResultWithPostInfo {
let account = ensure_signed(origin.clone())?;
let rev_ca = AccountToAlias::<T>::get(&account).ok_or(Error::<T>::InvalidAccount)?;
ensure!(
Root::<T>::get(rev_ca.ring).is_some_and(|ring| ring.revision == rev_ca.revision),
DispatchError::BadOrigin,
);
let derivation_weight = T::WeightInfo::under_alias();
let local_origin = Origin::PersonalAlias(rev_ca);
Self::derivative_call(origin, local_origin, *call, derivation_weight)
}
#[pallet::call_index(1)]
pub fn set_alias_account(
origin: OriginFor<T>,
account: T::AccountId,
call_valid_at: BlockNumberFor<T>,
) -> DispatchResultWithPostInfo {
let rev_ca = Self::ensure_revised_personal_alias(origin)?;
let now = frame_system::Pallet::<T>::block_number();
let time_tolerance = Self::account_setup_time_tolerance();
ensure!(
call_valid_at <= now && now <= call_valid_at.saturating_add(time_tolerance),
Error::<T>::TimeOutOfRange
);
ensure!(T::AccountContexts::contains(&rev_ca.ca.context), Error::<T>::InvalidContext);
ensure!(!AccountToPersonalId::<T>::contains_key(&account), Error::<T>::AccountInUse);
let old_account = AliasToAccount::<T>::get(&rev_ca.ca);
let old_rev_ca = old_account.as_ref().and_then(AccountToAlias::<T>::get);
let needs_revision = old_rev_ca.is_some_and(|old_rev_ca| {
old_rev_ca.revision != rev_ca.revision || old_rev_ca.ring != rev_ca.ring
});
ensure!(
old_account.as_ref() != Some(&account) || needs_revision,
Error::<T>::AliasAccountAlreadySet
);
if old_account.as_ref() != Some(&account) {
ensure!(!AccountToAlias::<T>::contains_key(&account), Error::<T>::AccountInUse);
if let Some(old_account) = &old_account {
frame_system::Pallet::<T>::dec_sufficients(old_account);
AccountToAlias::<T>::remove(old_account);
}
frame_system::Pallet::<T>::inc_sufficients(&account);
}
AccountToAlias::<T>::insert(&account, &rev_ca);
AliasToAccount::<T>::insert(&rev_ca.ca, &account);
if old_account.is_none() || needs_revision {
Ok(Pays::No.into())
} else {
Ok(Pays::Yes.into())
}
}
#[pallet::call_index(2)]
pub fn unset_alias_account(origin: OriginFor<T>) -> DispatchResult {
let alias = Self::ensure_personal_alias(origin)?;
let account = AliasToAccount::<T>::take(&alias).ok_or(Error::<T>::InvalidAccount)?;
AccountToAlias::<T>::remove(&account);
frame_system::Pallet::<T>::dec_sufficients(&account);
Ok(())
}
#[pallet::call_index(3)]
pub fn force_recognize_personhood(
origin: OriginFor<T>,
people: Vec<MemberOf<T>>,
) -> DispatchResultWithPostInfo {
ensure_root(origin)?;
for key in people {
let personal_id = Self::reserve_new_id();
Self::recognize_personhood(personal_id, Some(key))?;
}
Ok(().into())
}
#[pallet::call_index(4)]
pub fn set_personal_id_account(
origin: OriginFor<T>,
account: T::AccountId,
call_valid_at: BlockNumberFor<T>,
) -> DispatchResultWithPostInfo {
let id = Self::ensure_personal_identity(origin)?;
let now = frame_system::Pallet::<T>::block_number();
let time_tolerance = Self::account_setup_time_tolerance();
ensure!(
call_valid_at <= now && now <= call_valid_at.saturating_add(time_tolerance),
Error::<T>::TimeOutOfRange
);
ensure!(!AccountToPersonalId::<T>::contains_key(&account), Error::<T>::AccountInUse);
ensure!(!AccountToAlias::<T>::contains_key(&account), Error::<T>::AccountInUse);
let mut record = People::<T>::get(id).ok_or(Error::<T>::NotPerson)?;
let pays = if let Some(old_account) = record.account {
frame_system::Pallet::<T>::dec_sufficients(&old_account);
AccountToPersonalId::<T>::remove(&old_account);
Pays::Yes
} else {
Pays::No
};
record.account = Some(account.clone());
frame_system::Pallet::<T>::inc_sufficients(&account);
AccountToPersonalId::<T>::insert(&account, id);
People::<T>::insert(id, &record);
Ok(pays.into())
}
#[pallet::call_index(5)]
pub fn unset_personal_id_account(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
let id = Self::ensure_personal_identity(origin)?;
let mut record = People::<T>::get(id).ok_or(Error::<T>::NotPerson)?;
let account = record.account.take().ok_or(Error::<T>::InvalidAccount)?;
AccountToPersonalId::<T>::take(&account).ok_or(Error::<T>::InvalidAccount)?;
frame_system::Pallet::<T>::dec_sufficients(&account);
People::<T>::insert(id, &record);
Ok(Pays::Yes.into())
}
#[pallet::call_index(6)]
pub fn migrate_included_key(
origin: OriginFor<T>,
new_key: MemberOf<T>,
) -> DispatchResultWithPostInfo {
let id = Self::ensure_personal_identity(origin)?;
ensure!(!Keys::<T>::contains_key(&new_key), Error::<T>::KeyAlreadyInUse);
let mut record = People::<T>::get(id).ok_or(Error::<T>::NotPerson)?;
ensure!(record.key != new_key, Error::<T>::SameKey);
match &record.position {
RingPosition::Included { ring_index, ring_position, .. } => {
if let Some(old_migrated_key) = KeyMigrationQueue::<T>::get(id) {
Keys::<T>::remove(old_migrated_key);
}
KeyMigrationQueue::<T>::insert(id, &new_key);
record.position = RingPosition::Included {
ring_index: *ring_index,
ring_position: *ring_position,
scheduled_for_removal: true,
};
People::<T>::insert(id, record);
},
RingPosition::Onboarding { .. } => {
return Err(Error::<T>::InvalidKeyMigration.into())
},
RingPosition::Suspended => return Err(Error::<T>::Suspended.into()),
}
Keys::<T>::insert(new_key, id);
Ok(().into())
}
#[pallet::call_index(7)]
pub fn migrate_onboarding_key(
origin: OriginFor<T>,
new_key: MemberOf<T>,
) -> DispatchResultWithPostInfo {
let id = Self::ensure_personal_identity(origin)?;
ensure!(!Keys::<T>::contains_key(&new_key), Error::<T>::KeyAlreadyInUse);
let mut record = People::<T>::get(id).ok_or(Error::<T>::NotPerson)?;
ensure!(record.key != new_key, Error::<T>::SameKey);
match &record.position {
RingPosition::Onboarding { queue_page } => {
let mut keys = OnboardingQueue::<T>::get(queue_page);
if let Some(idx) = keys.iter().position(|k| *k == record.key) {
Keys::<T>::remove(&keys[idx]);
keys[idx] = new_key.clone();
OnboardingQueue::<T>::insert(queue_page, keys);
record.key = new_key.clone();
People::<T>::insert(id, record);
} else {
defensive!("No key found at the position in the person record of {}", id);
}
},
RingPosition::Included { .. } => return Err(Error::<T>::InvalidKeyMigration.into()),
RingPosition::Suspended => return Err(Error::<T>::Suspended.into()),
}
Keys::<T>::insert(new_key, id);
Ok(().into())
}
#[pallet::call_index(8)]
pub fn set_onboarding_size(
origin: OriginFor<T>,
onboarding_size: u32,
) -> DispatchResultWithPostInfo {
ensure_root(origin)?;
ensure!(
onboarding_size <= <T as Config>::MaxRingSize::get(),
Error::<T>::InvalidOnboardingSize
);
OnboardingSize::<T>::put(onboarding_size);
Ok(Pays::No.into())
}
}
impl<T: Config> Pallet<T> {
pub(crate) fn should_build_ring(ring_index: RingIndex, limit: u32) -> Option<u32> {
if !RingsState::<T>::get().append_only() {
return None;
}
if PendingSuspensions::<T>::contains_key(ring_index) {
return None;
}
let ring_status = RingKeysStatus::<T>::get(ring_index);
let not_included_count = ring_status.total.saturating_sub(ring_status.included);
let to_include = not_included_count.min(limit);
if to_include == 0 {
return None;
}
Some(to_include)
}
fn should_onboard_people(
ring_index: RingIndex,
ring_status: &RingStatus,
open_slots: u32,
available_for_inclusion: u32,
onboarding_size: u32,
) -> Option<(u32, bool)> {
if !RingsState::<T>::get().append_only() {
return None;
}
if PendingSuspensions::<T>::contains_key(ring_index) {
return None;
}
let to_include = available_for_inclusion.min(open_slots);
if to_include == 0 {
return None;
}
let can_onboard_with_cohort = to_include >= onboarding_size &&
ring_status.total.saturating_add(to_include.saturated_into()) <=
T::MaxRingSize::get().saturating_sub(onboarding_size);
let ring_filled = open_slots == to_include;
let should_onboard = ring_filled || can_onboard_with_cohort;
if !should_onboard {
return None;
}
Some((to_include, ring_filled))
}
pub(crate) fn should_remove_suspended_keys(
ring_index: RingIndex,
check_rings_state: bool,
) -> bool {
if check_rings_state && !RingsState::<T>::get().append_only() {
return false;
}
let suspended_count = PendingSuspensions::<T>::decode_len(ring_index).unwrap_or(0);
if suspended_count == 0 {
return false;
}
true
}
pub(crate) fn should_merge_queue_pages() -> QueueMergeAction<T> {
let (initial_head, tail) = QueuePageIndices::<T>::get();
let first_key_page = OnboardingQueue::<T>::get(initial_head);
if initial_head == tail {
return QueueMergeAction::NoAction;
}
let new_head = initial_head.checked_add(1).unwrap_or(0);
let second_key_page = OnboardingQueue::<T>::get(new_head);
let page_size = T::OnboardingQueuePageSize::get();
if first_key_page.len().saturating_add(second_key_page.len()) > page_size as usize {
return QueueMergeAction::NoAction;
}
QueueMergeAction::Merge { initial_head, new_head, first_key_page, second_key_page }
}
pub(crate) fn build_ring(ring_index: RingIndex, to_include: u32) -> DispatchResult {
let (keys, mut ring_status) = Self::ring_keys_and_info(ring_index);
let (next_revision, mut intermediate) =
if let Some(existing_root) = Root::<T>::get(ring_index) {
(
existing_root.revision.checked_add(1).ok_or(ArithmeticError::Overflow)?,
existing_root.intermediate,
)
} else {
(0, T::Crypto::start_members())
};
T::Crypto::push_members(
&mut intermediate,
keys.iter()
.skip(ring_status.included as usize)
.take(to_include as usize)
.cloned(),
Self::fetch_chunks,
)
.defensive()
.map_err(|_| Error::<T>::CouldNotPush)?;
ring_status.included = ring_status.included.saturating_add(to_include);
RingKeysStatus::<T>::insert(ring_index, ring_status);
let root = T::Crypto::finish_members(intermediate.clone());
let ring_root = RingRoot { root, revision: next_revision, intermediate };
Root::<T>::insert(ring_index, ring_root);
Ok(())
}
#[transactional]
pub(crate) fn onboard_people() -> DispatchResult {
let (top_ring_index, mut keys) = Self::available_ring();
let mut ring_status = RingKeysStatus::<T>::get(top_ring_index);
defensive_assert!(
keys.len() == ring_status.total as usize,
"Stored key count doesn't match the actual length"
);
let keys_len = keys.len() as u32;
let open_slots = T::MaxRingSize::get().saturating_sub(keys_len);
let (mut head, tail) = QueuePageIndices::<T>::get();
let old_head = head;
let mut keys_to_include: Vec<MemberOf<T>> =
OnboardingQueue::<T>::take(head).into_inner();
if keys_to_include.len() < open_slots as usize && head != tail {
head = head.checked_add(1).unwrap_or(0);
let second_key_page = OnboardingQueue::<T>::take(head);
defensive_assert!(!second_key_page.is_empty());
keys_to_include.extend(second_key_page.into_iter());
}
let onboarding_size = OnboardingSize::<T>::get();
let (to_include, ring_filled) = Self::should_onboard_people(
top_ring_index,
&ring_status,
open_slots,
keys_to_include.len().saturated_into(),
onboarding_size,
)
.ok_or(Error::<T>::Incomplete)?;
let mut remaining_keys = keys_to_include.split_off(to_include as usize);
for key in keys_to_include.into_iter() {
let personal_id = Keys::<T>::get(&key).defensive().ok_or(Error::<T>::NotPerson)?;
let mut record =
People::<T>::get(personal_id).defensive().ok_or(Error::<T>::KeyNotFound)?;
record.position = RingPosition::Included {
ring_index: top_ring_index,
ring_position: keys.len().saturated_into(),
scheduled_for_removal: false,
};
People::<T>::insert(personal_id, record);
keys.try_push(key).defensive().map_err(|_| Error::<T>::TooManyMembers)?;
}
RingKeys::<T>::insert(top_ring_index, keys);
ActiveMembers::<T>::mutate(|active| *active = active.saturating_add(to_include));
ring_status.total = ring_status.total.saturating_add(to_include);
RingKeysStatus::<T>::insert(top_ring_index, ring_status);
if ring_filled {
CurrentRingIndex::<T>::mutate(|i| i.saturating_inc());
}
if remaining_keys.len() > T::OnboardingQueuePageSize::get() as usize {
let split_idx =
remaining_keys.len().saturating_sub(T::OnboardingQueuePageSize::get() as usize);
let second_page_keys: BoundedVec<MemberOf<T>, T::OnboardingQueuePageSize> =
remaining_keys
.split_off(split_idx)
.try_into()
.expect("the list shrunk so it must fit; qed");
let remaining_keys: BoundedVec<MemberOf<T>, T::OnboardingQueuePageSize> =
remaining_keys.try_into().expect("the list shrunk so it must fit; qed");
OnboardingQueue::<T>::insert(old_head, remaining_keys);
OnboardingQueue::<T>::insert(head, second_page_keys);
QueuePageIndices::<T>::put((old_head, tail));
} else if !remaining_keys.is_empty() {
let remaining_keys: BoundedVec<MemberOf<T>, T::OnboardingQueuePageSize> =
remaining_keys.try_into().expect("the list shrunk so it must fit; qed");
OnboardingQueue::<T>::insert(head, remaining_keys);
QueuePageIndices::<T>::put((head, tail));
} else {
if head != tail {
head = head.checked_add(1).unwrap_or(0);
}
QueuePageIndices::<T>::put((head, tail));
}
Ok(())
}
fn derivative_call(
mut origin: OriginFor<T>,
local_origin: Origin,
call: <T as frame_system::Config>::RuntimeCall,
derivation_weight: Weight,
) -> DispatchResultWithPostInfo {
origin.set_caller_from(<T::RuntimeOrigin as OriginTrait>::PalletsOrigin::from(
local_origin,
));
let info = call.get_dispatch_info();
let result = call.dispatch(origin);
let weight = derivation_weight.saturating_add(extract_actual_weight(&result, &info));
result
.map(|p| PostDispatchInfo { actual_weight: Some(weight), pays_fee: p.pays_fee })
.map_err(|mut err| {
err.post_info = Some(weight).into();
err
})
}
pub fn ensure_personal_identity(
origin: T::RuntimeOrigin,
) -> Result<PersonalId, DispatchError> {
Ok(ensure_personal_identity(origin.into_caller())?)
}
pub fn ensure_personal_alias(
origin: T::RuntimeOrigin,
) -> Result<ContextualAlias, DispatchError> {
Ok(ensure_personal_alias(origin.into_caller())?)
}
pub fn ensure_revised_personal_alias(
origin: T::RuntimeOrigin,
) -> Result<RevisedContextualAlias, DispatchError> {
Ok(ensure_revised_personal_alias(origin.into_caller())?)
}
pub fn available_ring() -> (RingIndex, BoundedVec<MemberOf<T>, T::MaxRingSize>) {
let mut current_ring_index = CurrentRingIndex::<T>::get();
let mut current_keys = RingKeys::<T>::get(current_ring_index);
defensive_assert!(
!current_keys.is_full(),
"Something bad happened inside the STF, where the current keys are full, but we should have incremented in that case."
);
if current_keys.is_full() {
current_ring_index.saturating_inc();
CurrentRingIndex::<T>::put(current_ring_index);
current_keys = RingKeys::<T>::get(current_ring_index);
}
defensive_assert!(
!current_keys.is_full(),
"Something bad happened inside the STF, where the current key and next key are both full. Nothing we can do here."
);
(current_ring_index, current_keys)
}
pub fn do_insert_key(who: PersonalId, key: MemberOf<T>) -> DispatchResult {
ensure!(!Keys::<T>::contains_key(&key), Error::<T>::KeyAlreadyInUse);
ensure!(
ReservedPersonalId::<T>::take(who).is_some(),
Error::<T>::PersonalIdNotReservedOrNotRecognized
);
Self::push_to_onboarding_queue(who, key, None)
}
pub fn queue_personhood_suspensions(suspensions: &[PersonalId]) -> DispatchResult {
ensure!(RingsState::<T>::get().mutating(), Error::<T>::NoMutationSession);
for who in suspensions {
let mut record = People::<T>::get(who).ok_or(Error::<T>::InvalidSuspensions)?;
match record.position {
RingPosition::Included { ring_index, ring_position, .. } => {
let mut suspended_indices = PendingSuspensions::<T>::get(ring_index);
let Err(insert_idx) = suspended_indices.binary_search(&ring_position)
else {
return Err(Error::<T>::KeyAlreadySuspended.into());
};
suspended_indices
.try_insert(insert_idx, ring_position)
.defensive()
.map_err(|_| Error::<T>::TooManyMembers)?;
PendingSuspensions::<T>::insert(ring_index, suspended_indices);
},
RingPosition::Onboarding { queue_page } => {
let mut keys = OnboardingQueue::<T>::get(queue_page);
let queue_idx = keys.iter().position(|k| *k == record.key);
if let Some(idx) = queue_idx {
keys.remove(idx);
OnboardingQueue::<T>::insert(queue_page, keys);
} else {
defensive!(
"No key found at the position in the person record of {}",
who
);
}
},
RingPosition::Suspended => {
defensive!("Suspension queued for person {} while already suspended", who);
},
}
record.position = RingPosition::Suspended;
if let Some(account) = record.account {
AccountToPersonalId::<T>::remove(account);
record.account = None;
}
People::<T>::insert(who, record);
}
Ok(())
}
pub fn resume_personhood(who: PersonalId) -> DispatchResult {
let record = People::<T>::get(who).ok_or(Error::<T>::NotPerson)?;
ensure!(record.position.suspended(), Error::<T>::NotSuspended);
ensure!(Keys::<T>::get(&record.key) == Some(who), Error::<T>::NoKey);
Self::push_to_onboarding_queue(who, record.key, record.account)
}
fn push_to_onboarding_queue(
who: PersonalId,
key: MemberOf<T>,
account: Option<T::AccountId>,
) -> DispatchResult {
let (head, mut tail) = QueuePageIndices::<T>::get();
let mut keys = OnboardingQueue::<T>::get(tail);
if let Err(k) = keys.try_push(key.clone()) {
tail = tail.checked_add(1).unwrap_or(0);
ensure!(tail != head, Error::<T>::TooManyMembers);
keys = alloc::vec![k].try_into().expect("must be able to hold one key; qed");
};
let record = PersonRecord {
key,
position: RingPosition::Onboarding { queue_page: tail },
account,
};
Keys::<T>::insert(&record.key, who);
People::<T>::insert(who, &record);
Self::deposit_event(Event::<T>::PersonOnboarding { who, key: record.key });
QueuePageIndices::<T>::put((head, tail));
OnboardingQueue::<T>::insert(tail, keys);
Ok(())
}
pub fn ring_keys_and_info(
ring_index: RingIndex,
) -> (BoundedVec<MemberOf<T>, T::MaxRingSize>, RingStatus) {
let keys = RingKeys::<T>::get(ring_index);
let ring_status = RingKeysStatus::<T>::get(ring_index);
defensive_assert!(
keys.len() == ring_status.total as usize,
"Stored key count doesn't match the actual length"
);
(keys, ring_status)
}
pub(crate) fn fetch_chunks(
range: Range<usize>,
) -> Result<Vec<<T::Crypto as GenerateVerifiable>::StaticChunk>, ()> {
let chunk_page_size = T::ChunkPageSize::get();
let expected_len = range.end.saturating_sub(range.start);
let mut page_idx = range.start.checked_div(chunk_page_size as usize).ok_or(())?;
let mut chunks: Vec<_> = Chunks::<T>::get(page_idx.saturated_into::<u32>())
.defensive()
.ok_or(())?
.into_iter()
.skip(range.start % chunk_page_size as usize)
.take(expected_len)
.collect();
while chunks.len() < expected_len {
page_idx = page_idx.checked_add(1).ok_or(())?;
let page =
Chunks::<T>::get(page_idx.saturated_into::<u32>()).defensive().ok_or(())?;
chunks.extend(
page.into_inner().into_iter().take(expected_len.saturating_sub(chunks.len())),
);
}
Ok(chunks)
}
pub(crate) fn migrate_keys(meter: &mut WeightMeter) {
let mut drain = KeyMigrationQueue::<T>::drain();
loop {
let weight = T::WeightInfo::migrate_keys_single_included_key()
.saturating_add(T::DbWeight::get().reads_writes(1, 1));
if !meter.can_consume(weight) {
return;
}
let op_res = with_storage_layer::<bool, DispatchError, _>(|| match drain.next() {
Some((id, new_key)) => {
Self::migrate_keys_single_included_key(id, new_key).map(|_| false)
},
None => {
let rings_state = RingsState::<T>::get()
.end_key_migration()
.map_err(|_| Error::<T>::NoMutationSession)?;
RingsState::<T>::put(rings_state);
meter.consume(T::DbWeight::get().reads_writes(1, 1));
Ok(true)
},
});
match op_res {
Ok(false) => meter.consume(weight),
Ok(true) => {
meter.consume(T::DbWeight::get().reads(1));
break;
},
Err(e) => {
meter.consume(weight);
log::error!(target: LOG_TARGET, "failed to migrate keys: {:?}", e);
break;
},
}
}
}
pub(crate) fn migrate_keys_single_included_key(
id: PersonalId,
new_key: MemberOf<T>,
) -> DispatchResult {
if let Some(record) = People::<T>::get(id) {
let RingPosition::Included {
ring_index,
ring_position,
scheduled_for_removal: true,
} = record.position
else {
Keys::<T>::remove(new_key);
return Ok(());
};
let mut suspended_indices = PendingSuspensions::<T>::get(ring_index);
let Err(insert_idx) = suspended_indices.binary_search(&ring_position) else {
log::info!(target: LOG_TARGET, "key migration for person {} skipped as the person's key was already suspended", id);
return Ok(());
};
suspended_indices
.try_insert(insert_idx, ring_position)
.map_err(|_| Error::<T>::TooManyMembers)?;
PendingSuspensions::<T>::insert(ring_index, suspended_indices);
Keys::<T>::remove(&record.key);
Self::push_to_onboarding_queue(id, new_key, record.account)?;
} else {
log::info!(target: LOG_TARGET, "key migration for person {} skipped as no record was found", id);
}
Ok(())
}
pub(crate) fn remove_suspended_keys(ring_index: RingIndex) -> Weight {
let keys = RingKeys::<T>::get(ring_index);
let keys_len = keys.len();
let suspended_indices = PendingSuspensions::<T>::get(ring_index);
let mut new_keys: BoundedVec<MemberOf<T>, T::MaxRingSize> = Default::default();
let mut j = 0;
for (i, key) in keys.into_iter().enumerate() {
if j < suspended_indices.len() && i == suspended_indices[j] as usize {
j += 1;
} else if new_keys
.try_push(key)
.defensive_proof("cannot move more ring members than the max ring size; qed")
.is_err()
{
return T::WeightInfo::remove_suspended_people(
keys_len.try_into().unwrap_or(u32::MAX),
);
}
}
let suspended_count = RingKeysStatus::<T>::mutate(ring_index, |ring_status| {
let new_total = new_keys.len().saturated_into();
let suspended_count = ring_status.total.saturating_sub(new_total);
ring_status.total = new_total;
ring_status.included = 0;
suspended_count
});
ActiveMembers::<T>::mutate(|active| *active = active.saturating_sub(suspended_count));
RingKeys::<T>::insert(ring_index, new_keys);
Root::<T>::mutate(ring_index, |maybe_root| {
if let Some(root) = maybe_root {
root.intermediate = T::Crypto::start_members();
}
});
PendingSuspensions::<T>::remove(ring_index);
T::WeightInfo::remove_suspended_people(keys_len.try_into().unwrap_or(u32::MAX))
}
pub(crate) fn merge_queue_pages(
initial_head: u32,
new_head: u32,
mut first_key_page: BoundedVec<MemberOf<T>, T::OnboardingQueuePageSize>,
second_key_page: BoundedVec<MemberOf<T>, T::OnboardingQueuePageSize>,
) {
let op_res = with_storage_layer::<(), DispatchError, _>(|| {
for key in first_key_page.iter() {
let personal_id =
Keys::<T>::get(key).defensive().ok_or(Error::<T>::NotPerson)?;
let mut record =
People::<T>::get(personal_id).defensive().ok_or(Error::<T>::KeyNotFound)?;
record.position = RingPosition::Onboarding { queue_page: new_head };
People::<T>::insert(personal_id, record);
}
first_key_page
.try_extend(second_key_page.into_iter())
.defensive()
.map_err(|_| Error::<T>::TooManyMembers)?;
OnboardingQueue::<T>::remove(initial_head);
OnboardingQueue::<T>::insert(new_head, first_key_page);
QueuePageIndices::<T>::mutate(|(h, _)| *h = new_head);
Ok(())
});
if let Err(e) = op_res {
log::error!(target: LOG_TARGET, "failed to merge queue pages: {:?}", e);
}
}
}
impl<T: Config> AddOnlyPeopleTrait for Pallet<T> {
type Member = MemberOf<T>;
fn reserve_new_id() -> PersonalId {
let new_id = NextPersonalId::<T>::mutate(|id| {
let new_id = *id;
id.saturating_inc();
new_id
});
ReservedPersonalId::<T>::insert(new_id, ());
new_id
}
fn cancel_id_reservation(personal_id: PersonalId) -> Result<(), DispatchError> {
ReservedPersonalId::<T>::take(personal_id).ok_or(Error::<T>::PersonalIdNotReserved)?;
Ok(())
}
fn renew_id_reservation(personal_id: PersonalId) -> Result<(), DispatchError> {
if NextPersonalId::<T>::get() <= personal_id ||
People::<T>::contains_key(personal_id) ||
ReservedPersonalId::<T>::contains_key(personal_id)
{
return Err(Error::<T>::PersonalIdReservationCannotRenew.into());
}
ReservedPersonalId::<T>::insert(personal_id, ());
Ok(())
}
fn recognize_personhood(
who: PersonalId,
maybe_key: Option<MemberOf<T>>,
) -> Result<(), DispatchError> {
match maybe_key {
Some(key) => Self::do_insert_key(who, key),
None => Self::resume_personhood(who),
}
}
#[cfg(feature = "runtime-benchmarks")]
type Secret = <<T as Config>::Crypto as GenerateVerifiable>::Secret;
#[cfg(feature = "runtime-benchmarks")]
fn mock_key(who: PersonalId) -> (Self::Member, Self::Secret) {
let mut buf = [0u8; 32];
buf[..core::mem::size_of::<PersonalId>()].copy_from_slice(&who.to_le_bytes()[..]);
let secret = T::Crypto::new_secret(buf);
(T::Crypto::member_from_secret(&secret), secret)
}
}
impl<T: Config> PeopleTrait for Pallet<T> {
fn suspend_personhood(suspensions: &[PersonalId]) -> DispatchResult {
Self::queue_personhood_suspensions(suspensions)
}
fn start_people_set_mutation_session() -> DispatchResult {
let current_state = RingsState::<T>::get();
RingsState::<T>::put(
current_state
.start_mutation_session()
.map_err(|_| Error::<T>::CouldNotStartMutationSession)?,
);
Ok(())
}
fn end_people_set_mutation_session() -> DispatchResult {
let current_state = RingsState::<T>::get();
RingsState::<T>::put(
current_state
.end_mutation_session()
.map_err(|_| Error::<T>::NoMutationSession)?,
);
Ok(())
}
}
pub fn ensure_personal_identity<OuterOrigin>(o: OuterOrigin) -> Result<PersonalId, BadOrigin>
where
OuterOrigin: TryInto<Origin, Error = OuterOrigin>,
{
match o.try_into() {
Ok(Origin::PersonalIdentity(m)) => Ok(m),
_ => Err(BadOrigin),
}
}
pub fn ensure_personal_alias<OuterOrigin>(o: OuterOrigin) -> Result<ContextualAlias, BadOrigin>
where
OuterOrigin: TryInto<Origin, Error = OuterOrigin>,
{
match o.try_into() {
Ok(Origin::PersonalAlias(rev_ca)) => Ok(rev_ca.ca),
_ => Err(BadOrigin),
}
}
pub struct EnsurePersonalIdentity<T>(PhantomData<T>);
impl<T: Config> EnsureOrigin<OriginFor<T>> for EnsurePersonalIdentity<T> {
type Success = PersonalId;
fn try_origin(o: OriginFor<T>) -> Result<Self::Success, OriginFor<T>> {
ensure_personal_identity(o.clone().into_caller()).map_err(|_| o)
}
#[cfg(feature = "runtime-benchmarks")]
fn try_successful_origin() -> Result<OriginFor<T>, ()> {
Ok(Origin::PersonalIdentity(0).into())
}
}
frame_support::impl_ensure_origin_with_arg_ignoring_arg! {
impl<{ T: Config, A }>
EnsureOriginWithArg< OriginFor<T>, A> for EnsurePersonalIdentity<T>
{}
}
impl<T: Config> CountedMembers for EnsurePersonalIdentity<T> {
fn active_count(&self) -> u32 {
Keys::<T>::count()
}
}
pub struct EnsurePersonalAlias<T>(PhantomData<T>);
impl<T: Config> EnsureOrigin<OriginFor<T>> for EnsurePersonalAlias<T> {
type Success = ContextualAlias;
fn try_origin(o: OriginFor<T>) -> Result<Self::Success, OriginFor<T>> {
ensure_personal_alias(o.clone().into_caller()).map_err(|_| o)
}
#[cfg(feature = "runtime-benchmarks")]
fn try_successful_origin() -> Result<OriginFor<T>, ()> {
Ok(Origin::PersonalAlias(RevisedContextualAlias {
revision: 0,
ring: 0,
ca: ContextualAlias { alias: [1; 32], context: [0; 32] },
})
.into())
}
}
frame_support::impl_ensure_origin_with_arg_ignoring_arg! {
impl<{ T: Config, A }>
EnsureOriginWithArg< OriginFor<T>, A> for EnsurePersonalAlias<T>
{}
}
impl<T: Config> CountedMembers for EnsurePersonalAlias<T> {
fn active_count(&self) -> u32 {
ActiveMembers::<T>::get()
}
}
pub struct EnsurePersonalAliasInContext<T>(PhantomData<T>);
impl<T: Config> EnsureOriginWithArg<OriginFor<T>, Context> for EnsurePersonalAliasInContext<T> {
type Success = Alias;
fn try_origin(o: OriginFor<T>, arg: &Context) -> Result<Self::Success, OriginFor<T>> {
match ensure_personal_alias(o.clone().into_caller()) {
Ok(ca) if &ca.context == arg => Ok(ca.alias),
_ => Err(o),
}
}
#[cfg(feature = "runtime-benchmarks")]
fn try_successful_origin(context: &Context) -> Result<OriginFor<T>, ()> {
Ok(Origin::PersonalAlias(RevisedContextualAlias {
revision: 0,
ring: 0,
ca: ContextualAlias { alias: [1; 32], context: *context },
})
.into())
}
}
impl<T: Config> CountedMembers for EnsurePersonalAliasInContext<T> {
fn active_count(&self) -> u32 {
ActiveMembers::<T>::get()
}
}
pub fn ensure_revised_personal_alias<OuterOrigin>(
o: OuterOrigin,
) -> Result<RevisedContextualAlias, BadOrigin>
where
OuterOrigin: TryInto<Origin, Error = OuterOrigin>,
{
match o.try_into() {
Ok(Origin::PersonalAlias(rev_ca)) => Ok(rev_ca),
_ => Err(BadOrigin),
}
}
pub struct EnsureRevisedPersonalAlias<T>(PhantomData<T>);
impl<T: Config> EnsureOrigin<OriginFor<T>> for EnsureRevisedPersonalAlias<T> {
type Success = RevisedContextualAlias;
fn try_origin(o: OriginFor<T>) -> Result<Self::Success, OriginFor<T>> {
ensure_revised_personal_alias(o.clone().into_caller()).map_err(|_| o)
}
#[cfg(feature = "runtime-benchmarks")]
fn try_successful_origin() -> Result<OriginFor<T>, ()> {
Ok(Origin::PersonalAlias(RevisedContextualAlias {
revision: 0,
ring: 0,
ca: ContextualAlias { alias: [1; 32], context: [0; 32] },
})
.into())
}
}
frame_support::impl_ensure_origin_with_arg_ignoring_arg! {
impl<{ T: Config, A }>
EnsureOriginWithArg< OriginFor<T>, A> for EnsureRevisedPersonalAlias<T>
{}
}
impl<T: Config> CountedMembers for EnsureRevisedPersonalAlias<T> {
fn active_count(&self) -> u32 {
ActiveMembers::<T>::get()
}
}
pub struct EnsureRevisedPersonalAliasInContext<T>(PhantomData<T>);
impl<T: Config> EnsureOriginWithArg<OriginFor<T>, Context>
for EnsureRevisedPersonalAliasInContext<T>
{
type Success = RevisedAlias;
fn try_origin(o: OriginFor<T>, arg: &Context) -> Result<Self::Success, OriginFor<T>> {
match ensure_revised_personal_alias(o.clone().into_caller()) {
Ok(ca) if &ca.ca.context == arg => {
Ok(RevisedAlias { revision: ca.revision, ring: ca.ring, alias: ca.ca.alias })
},
_ => Err(o),
}
}
#[cfg(feature = "runtime-benchmarks")]
fn try_successful_origin(context: &Context) -> Result<OriginFor<T>, ()> {
Ok(Origin::PersonalAlias(RevisedContextualAlias {
revision: 0,
ring: 0,
ca: ContextualAlias { alias: [1; 32], context: *context },
})
.into())
}
}
impl<T: Config> CountedMembers for EnsureRevisedPersonalAliasInContext<T> {
fn active_count(&self) -> u32 {
ActiveMembers::<T>::get()
}
}
}