Skip to content
Snippets Groups Projects
Commit 060cb448 authored by Éloïs's avatar Éloïs
Browse files

chore: create pallet authority-members

parent 22b297ce
Branches
Tags
1 merge request!28Smiths sub-wot
......@@ -1923,6 +1923,7 @@ dependencies = [
"frame-system-rpc-runtime-api",
"hex-literal",
"pallet-authority-discovery",
"pallet-authority-members",
"pallet-authorship",
"pallet-babe",
"pallet-balances",
......@@ -4165,6 +4166,26 @@ dependencies = [
"sp-std",
]
[[package]]
name = "pallet-authority-members"
version = "3.0.0"
dependencies = [
"frame-benchmarking",
"frame-support",
"frame-system",
"maplit",
"pallet-session",
"parity-scale-codec",
"scale-info",
"serde",
"sp-core",
"sp-io",
"sp-membership",
"sp-runtime",
"sp-staking",
"sp-std",
]
[[package]]
name = "pallet-authorship"
version = "4.0.0-dev"
......
......@@ -104,6 +104,7 @@ members = [
'pallets/duniter-wot',
'pallets/identity',
'pallets/membership',
'pallets/authority-members',
'pallets/ud-accounts-storage',
'pallets/universal-dividend',
'primitives/membership',
......
[package]
authors = ['librelois <c@elo.tf>']
description = 'FRAME pallet authority members.'
edition = '2018'
homepage = 'https://substrate.dev'
license = 'AGPL-3.0'
name = 'pallet-authority-members'
readme = 'README.md'
repository = 'https://git.duniter.org/nodes/rust/duniter-v2s'
version = '3.0.0'
[features]
default = ['std']
runtime-benchmarks = ['frame-benchmarking']
std = [
'codec/std',
'frame-support/std',
'frame-system/std',
'frame-benchmarking/std',
'pallet-session/std',
'serde',
'sp-core/std',
'sp-membership/std',
'sp-runtime/std',
'sp-staking/std',
'sp-std/std',
]
try-runtime = ['frame-support/try-runtime']
[dependencies]
sp-membership = { path = "../../primitives/membership", default-features = false }
# substrate
scale-info = { version = "1.0", default-features = false, features = ["derive"] }
[dependencies.codec]
default-features = false
features = ['derive']
package = 'parity-scale-codec'
version = '2.3.1'
[dependencies.frame-benchmarking]
default-features = false
git = 'https://github.com/librelois/substrate.git'
optional = true
branch = 'duniter-monthly-2022-01'
[dependencies.frame-support]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-01'
[dependencies.frame-system]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-01'
[dependencies.pallet-session]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-01'
[dependencies.serde]
version = "1.0.101"
optional = true
features = ["derive"]
[dependencies.sp-core]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-01'
[dependencies.sp-runtime]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-01'
[dependencies.sp-staking]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-01'
[dependencies.sp-std]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-01'
### DOC ###
[package.metadata.docs.rs]
targets = ['x86_64-unknown-linux-gnu']
### DEV ###
[dev-dependencies.maplit]
version = '1.0.2'
[dev-dependencies.serde]
version = '1.0.119'
[dev-dependencies.sp-io]
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-01'
// Copyright 2021 Axiom-Team
//
// This file is part of Substrate-Libre-Currency.
//
// Substrate-Libre-Currency is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License.
//
// Substrate-Libre-Currency is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>.
#![cfg_attr(not(feature = "std"), no_std)]
#![allow(clippy::type_complexity)]
pub mod traits;
mod types;
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
/*#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;*/
pub use pallet::*;
pub use types::*;
use frame_support::traits::Get;
use sp_staking::SessionIndex;
use sp_std::prelude::*;
#[frame_support::pallet]
pub mod pallet {
use super::*;
use crate::traits::OnRemovedMember;
use frame_support::pallet_prelude::*;
use frame_support::traits::{StorageVersion, UnfilteredDispatchable};
use frame_system::pallet_prelude::*;
use sp_runtime::traits::{Convert, IsMember};
use sp_std::collections::btree_map::BTreeMap;
/// The current storage version.
const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
#[pallet::storage_version(STORAGE_VERSION)]
pub struct Pallet<T>(_);
// CONFIG //
#[pallet::config]
pub trait Config: frame_system::Config + pallet_session::Config {
type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;
type IsMember: IsMember<Self::MemberId>;
type OnRemovedMember: OnRemovedMember<Self::MemberId>;
type OwnerKeyOf: Convert<Self::MemberId, Option<Self::AccountId>>;
type MemberId: Copy + MaybeSerializeDeserialize + Parameter + Ord;
#[pallet::constant]
type MaxOfflineSessions: Get<SessionIndex>;
type RefreshValidatorIdOrigin: EnsureOrigin<Self::Origin>;
type RemoveMemberOrigin: EnsureOrigin<Self::Origin>;
}
// GENESIS STUFF //
#[pallet::genesis_config]
pub struct GenesisConfig<T: Config> {
pub initial_authorities: BTreeMap<T::MemberId, T::ValidatorId>,
}
#[cfg(feature = "std")]
impl<T: Config> Default for GenesisConfig<T> {
fn default() -> Self {
Self {
initial_authorities: BTreeMap::new(),
}
}
}
#[pallet::genesis_build]
impl<T: Config> GenesisBuild<T> for GenesisConfig<T> {
fn build(&self) {
for (member_id, validator_id) in &self.initial_authorities {
Members::<T>::insert(member_id, MemberData::new_genesis(validator_id.clone()));
}
let mut members_ids = self
.initial_authorities
.keys()
.copied()
.collect::<Vec<T::MemberId>>();
members_ids.sort();
OnlineAuthorities::<T>::put(members_ids);
}
}
// STORAGE //
#[pallet::storage]
#[pallet::getter(fn incoming)]
pub type IncomingAuthorities<T: Config> = StorageValue<_, Vec<T::MemberId>, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn online)]
pub type OnlineAuthorities<T: Config> = StorageValue<_, Vec<T::MemberId>, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn outgoing)]
pub type OutgoingAuthorities<T: Config> = StorageValue<_, Vec<T::MemberId>, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn member)]
pub type Members<T: Config> =
StorageMap<_, Blake2_128Concat, T::MemberId, MemberData<T::ValidatorId>, OptionQuery>;
#[pallet::storage]
#[pallet::getter(fn members_expire_on)]
pub type MembersExpireOn<T: Config> =
StorageMap<_, Blake2_128Concat, SessionIndex, Vec<T::MemberId>, ValueQuery>;
// HOOKS //
// EVENTS //
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
/// List of members who will enter the set of authorities at the next session.
/// [Vec<member_id>]
IncomingAuthorities(Vec<T::MemberId>),
/// List of members who will leave the set of authorities at the next session.
/// [Vec<member_id>]
OutgoingAuthorities(Vec<T::MemberId>),
/// A member will leave the set of authorities in 2 sessions.
/// [member_id]
MemberGoOffline(T::MemberId),
/// A member will enter the set of authorities in 2 sessions.
/// [member_id]
MemberGoOnline(T::MemberId),
/// A member has lost the right to be part of the authorities, he will be removed from
//// the authority set in 2 sessions.
/// [member_id]
MemberRemoved(T::MemberId),
}
// ERRORS //
#[pallet::error]
pub enum Error<T> {
/// Already incoming
AlreadyIncoming,
/// Already online
AlreadyOnline,
/// Already outgoing
AlreadyOutgoing,
/// Not found owner key
OwnerKeyNotFound,
/// Neither online nor scheduled
NotOnlineNorIncoming,
/// Not owner
NotOwner,
/// Not member
NotMember,
/// Session keys not provided
SessionKeysNotProvided,
}
// CALLS //
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::weight(0)]
pub fn go_offline(
origin: OriginFor<T>,
member_id: T::MemberId,
) -> DispatchResultWithPostInfo {
// Verification phase //
let who = ensure_signed(origin)?;
Self::verify_ownership_and_membership(&who, member_id)?;
let member_data =
Members::<T>::try_get(member_id).map_err(|_| Error::<T>::SessionKeysNotProvided)?;
if !member_data.session_keys_provided {
return Err(Error::<T>::SessionKeysNotProvided.into());
}
if !Self::is_online(member_id) && !Self::is_incoming(member_id) {
return Err(Error::<T>::NotOnlineNorIncoming.into());
}
// Apply phase //
if !Self::insert_out(member_id) {
Err(Error::<T>::AlreadyOutgoing.into())
} else {
Self::remove_in(member_id);
Ok(().into())
}
}
#[pallet::weight(0)]
pub fn go_online(
origin: OriginFor<T>,
member_id: T::MemberId,
) -> DispatchResultWithPostInfo {
// Verification phase //
let who = ensure_signed(origin)?;
Self::verify_ownership_and_membership(&who, member_id)?;
let member_data =
Members::<T>::try_get(member_id).map_err(|_| Error::<T>::SessionKeysNotProvided)?;
if !member_data.session_keys_provided {
return Err(Error::<T>::SessionKeysNotProvided.into());
}
// Apply phase //
if Self::is_online(member_id) {
if Self::is_outgoing(member_id) {
Self::remove_out(member_id);
Ok(().into())
} else {
Err(Error::<T>::AlreadyOnline.into())
}
} else if Self::is_outgoing(member_id) {
Self::remove_out(member_id);
Ok(().into())
} else if !Self::insert_in(member_id) {
Err(Error::<T>::AlreadyIncoming.into())
} else {
Ok(().into())
}
}
#[pallet::weight(0)]
pub fn set_session_keys(
origin: OriginFor<T>,
member_id: T::MemberId,
keys: T::Keys,
) -> DispatchResultWithPostInfo {
let who = ensure_signed(origin.clone())?;
Self::verify_ownership_and_membership(&who, member_id)?;
let validator_id = T::ValidatorIdOf::convert(who)
.ok_or(pallet_session::Error::<T>::NoAssociatedValidatorId)?;
let _post_info = pallet_session::Call::<T>::set_keys {
keys,
proof: vec![],
}
.dispatch_bypass_filter(origin)?;
let expire_on_session = pallet_session::Pallet::<T>::current_index()
.saturating_add(T::MaxOfflineSessions::get());
Members::<T>::mutate_exists(member_id, |member_data_opt| {
let mut member_data = member_data_opt.get_or_insert(MemberData {
expire_on_session,
session_keys_provided: true,
validator_id: validator_id.clone(),
});
member_data.session_keys_provided = true;
member_data.validator_id = validator_id;
});
Ok(().into())
}
#[pallet::weight(0)]
pub fn refresh_validator_id(
origin: OriginFor<T>,
member_id: T::MemberId,
) -> DispatchResultWithPostInfo {
T::RefreshValidatorIdOrigin::ensure_origin(origin)?;
let owner = T::OwnerKeyOf::convert(member_id).ok_or(Error::<T>::OwnerKeyNotFound)?;
let validator_id = T::ValidatorIdOf::convert(owner)
.ok_or(pallet_session::Error::<T>::NoAssociatedValidatorId)?;
if !T::IsMember::is_member(&member_id) {
return Err(Error::<T>::NotMember.into());
}
let expire_on_session = pallet_session::Pallet::<T>::current_index()
.saturating_add(T::MaxOfflineSessions::get());
Members::<T>::mutate(member_id, |member_data_opt| {
let validator_id_clone = validator_id.clone();
member_data_opt
.get_or_insert(MemberData::new(validator_id, expire_on_session))
.validator_id = validator_id_clone;
});
Ok(().into())
}
#[pallet::weight(0)]
pub fn remove_member(
origin: OriginFor<T>,
member_id: T::MemberId,
) -> DispatchResultWithPostInfo {
T::RemoveMemberOrigin::ensure_origin(origin)?;
if !T::IsMember::is_member(&member_id) {
return Err(Error::<T>::NotMember.into());
}
if let Some(owner) = T::OwnerKeyOf::convert(member_id) {
let _post_info = pallet_session::Call::<T>::purge_keys {}
.dispatch_bypass_filter(frame_system::Origin::<T>::Signed(owner).into())?;
}
Self::do_remove_member(member_id);
Ok(().into())
}
}
// INTERNAL FUNCTIONS //
impl<T: Config> Pallet<T> {
fn do_remove_member(member_id: T::MemberId) -> Weight {
if Self::is_online(member_id) {
// Trigger the member deletion for next session
Self::insert_out(member_id);
}
// remove all member data
Self::remove_in(member_id);
Self::remove_online(member_id);
Members::<T>::remove(member_id);
Self::deposit_event(Event::MemberRemoved(member_id));
let _ = T::OnRemovedMember::on_removed_member(member_id);
0
}
pub(super) fn expire_memberships(current_session_index: SessionIndex) {
for member_id in MembersExpireOn::<T>::take(current_session_index) {
if let Some(member_data) = Members::<T>::get(member_id) {
if member_data.expire_on_session == current_session_index {
Self::do_remove_member(member_id);
}
}
}
}
fn insert_in(member_id: T::MemberId) -> bool {
let not_already_inserted = IncomingAuthorities::<T>::mutate(|members_ids| {
if let Err(index) = members_ids.binary_search(&member_id) {
members_ids.insert(index, member_id);
true
} else {
false
}
});
if not_already_inserted {
Self::deposit_event(Event::MemberGoOnline(member_id));
}
not_already_inserted
}
fn insert_out(member_id: T::MemberId) -> bool {
let not_already_inserted = OutgoingAuthorities::<T>::mutate(|members_ids| {
if let Err(index) = members_ids.binary_search(&member_id) {
members_ids.insert(index, member_id);
true
} else {
false
}
});
if not_already_inserted {
Self::deposit_event(Event::MemberGoOffline(member_id));
}
not_already_inserted
}
fn is_incoming(member_id: T::MemberId) -> bool {
IncomingAuthorities::<T>::get()
.binary_search(&member_id)
.is_ok()
}
fn is_online(member_id: T::MemberId) -> bool {
OnlineAuthorities::<T>::get()
.binary_search(&member_id)
.is_ok()
}
fn is_outgoing(member_id: T::MemberId) -> bool {
OutgoingAuthorities::<T>::get()
.binary_search(&member_id)
.is_ok()
}
fn remove_in(member_id: T::MemberId) {
IncomingAuthorities::<T>::mutate(|members_ids| {
if let Ok(index) = members_ids.binary_search(&member_id) {
members_ids.remove(index);
}
});
}
fn remove_online(member_id: T::MemberId) {
OnlineAuthorities::<T>::mutate(|members_ids| {
if let Ok(index) = members_ids.binary_search(&member_id) {
members_ids.remove(index);
}
});
}
fn remove_out(member_id: T::MemberId) {
OutgoingAuthorities::<T>::mutate(|members_ids| {
if let Ok(index) = members_ids.binary_search(&member_id) {
members_ids.remove(index);
}
});
}
fn verify_ownership_and_membership(
who: &T::AccountId,
member_id: T::MemberId,
) -> Result<(), DispatchError> {
if let Some(owner) = T::OwnerKeyOf::convert(member_id) {
if who != &owner {
return Err(Error::<T>::NotOwner.into());
}
} else {
return Err(Error::<T>::OwnerKeyNotFound.into());
}
if !T::IsMember::is_member(&member_id) {
return Err(Error::<T>::NotMember.into());
}
Ok(())
}
}
}
impl<T: Config> pallet_session::SessionManager<T::ValidatorId> for Pallet<T> {
/// Plan a new session, and optionally provide the new validator set.
///
/// Even if the validator-set is the same as before, if any underlying economic conditions have
/// changed (i.e. stake-weights), the new validator set must be returned. This is necessary for
/// consensus engines making use of the session pallet to issue a validator-set change so
/// misbehavior can be provably associated with the new economic conditions as opposed to the
/// old. The returned validator set, if any, will not be applied until `new_index`. `new_index`
/// is strictly greater than from previous call.
///
/// The first session start at index 0.
///
/// `new_session(session)` is guaranteed to be called before `end_session(session-1)`. In other
/// words, a new session must always be planned before an ongoing one can be finished.
fn new_session(session_index: SessionIndex) -> Option<Vec<T::ValidatorId>> {
let members_ids_to_add = IncomingAuthorities::<T>::take();
let members_ids_to_del = OutgoingAuthorities::<T>::take();
if members_ids_to_add.is_empty() {
if members_ids_to_del.is_empty() {
return None;
} else {
// Apply MaxOfflineSessions rule
for member_id in &members_ids_to_del {
let expire_on_session =
session_index.saturating_add(T::MaxOfflineSessions::get());
Members::<T>::mutate_exists(member_id, |member_data_opt| {
if let Some(ref mut member_data) = member_data_opt {
member_data.expire_on_session = expire_on_session;
}
});
MembersExpireOn::<T>::append(expire_on_session, member_id);
}
Self::deposit_event(Event::OutgoingAuthorities(members_ids_to_del.clone()));
}
} else {
Self::deposit_event(Event::IncomingAuthorities(members_ids_to_add.clone()));
}
Some(
OnlineAuthorities::<T>::mutate(|members_ids| {
for member_id in members_ids_to_del {
if let Ok(index) = members_ids.binary_search(&member_id) {
members_ids.remove(index);
}
}
for member_id in members_ids_to_add {
if let Err(index) = members_ids.binary_search(&member_id) {
members_ids.insert(index, member_id);
}
}
members_ids.clone()
})
.iter()
.filter_map(Members::<T>::get)
.map(|member_data| member_data.validator_id)
.collect(),
)
}
/// Same as `new_session`, but it this should only be called at genesis.
///
/// The session manager might decide to treat this in a different way. Default impl is simply
/// using [`new_session`](Self::new_session).
fn new_session_genesis(_new_index: SessionIndex) -> Option<Vec<T::ValidatorId>> {
None
}
/// End the session.
///
/// Because the session pallet can queue validator set the ending session can be lower than the
/// last new session index.
fn end_session(_end_index: SessionIndex) {}
/// Start an already planned session.
///
/// The session start to be used for validation.
fn start_session(start_index: SessionIndex) {
Self::expire_memberships(start_index);
}
}
// Copyright 2021 Axiom-Team
//
// This file is part of Substrate-Libre-Currency.
//
// Substrate-Libre-Currency is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License.
//
// Substrate-Libre-Currency is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>.
use super::*;
use crate::{self as pallet_authority_members};
use frame_support::{
pallet_prelude::*,
parameter_types,
traits::{Everything, GenesisBuild},
BasicExternalities,
};
use frame_system as system;
use pallet_session::ShouldEndSession;
use sp_core::{crypto::key_types::DUMMY, H256};
use sp_runtime::{
impl_opaque_keys,
testing::{Header, UintAuthorityId},
traits::{BlakeTwo256, ConvertInto, IdentityLookup, IsMember, OpaqueKeys},
KeyTypeId,
};
type AccountId = u64;
type Block = frame_system::mocking::MockBlock<Test>;
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
impl_opaque_keys! {
pub struct MockSessionKeys {
pub dummy: UintAuthorityId,
}
}
impl From<UintAuthorityId> for MockSessionKeys {
fn from(dummy: UintAuthorityId) -> Self {
Self { dummy }
}
}
// Configure a mock runtime to test the pallet.
frame_support::construct_runtime!(
pub enum Test where
Block = Block,
NodeBlock = Block,
UncheckedExtrinsic = UncheckedExtrinsic,
{
System: frame_system::{Pallet, Call, Config, Storage, Event<T>},
Session: pallet_session::{Pallet, Call, Storage, Config<T>, Event},
AuthorityMembers: pallet_authority_members::{Pallet, Call, Storage, Config<T>, Event<T>},
}
);
parameter_types! {
pub const BlockHashCount: u64 = 250;
pub const SS58Prefix: u8 = 42;
}
impl system::Config for Test {
type BaseCallFilter = Everything;
type BlockWeights = ();
type BlockLength = ();
type DbWeight = ();
type Origin = Origin;
type Call = Call;
type Index = u64;
type BlockNumber = u64;
type Hash = H256;
type Hashing = BlakeTwo256;
type AccountId = AccountId;
type Lookup = IdentityLookup<Self::AccountId>;
type Header = Header;
type Event = Event;
type BlockHashCount = BlockHashCount;
type Version = ();
type PalletInfo = PalletInfo;
type AccountData = ();
type OnNewAccount = ();
type OnKilledAccount = ();
type SystemWeightInfo = ();
type SS58Prefix = SS58Prefix;
type OnSetCode = ();
type MaxConsumers = frame_support::traits::ConstU32<16>;
}
pub struct TestSessionHandler;
impl pallet_session::SessionHandler<u64> for TestSessionHandler {
const KEY_TYPE_IDS: &'static [KeyTypeId] = &[DUMMY];
fn on_new_session<Ks: OpaqueKeys>(
_changed: bool,
_validators: &[(u64, Ks)],
_queued_validators: &[(u64, Ks)],
) {
}
fn on_disabled(_validator_index: u32) {}
fn on_genesis_session<Ks: OpaqueKeys>(_validators: &[(u64, Ks)]) {}
}
const SESSION_LENGTH: u64 = 5;
pub struct TestShouldEndSession;
impl ShouldEndSession<u64> for TestShouldEndSession {
fn should_end_session(now: u64) -> bool {
now % SESSION_LENGTH == 0
}
}
impl pallet_session::Config for Test {
type Event = Event;
type ValidatorId = u64;
type ValidatorIdOf = sp_runtime::traits::ConvertInto;
type ShouldEndSession = TestShouldEndSession;
type NextSessionRotation = ();
type SessionManager = AuthorityMembers;
type SessionHandler = TestSessionHandler;
type Keys = MockSessionKeys;
type WeightInfo = ();
}
pub struct TestIsSmithMember;
impl IsMember<u64> for TestIsSmithMember {
fn is_member(member_id: &u64) -> bool {
member_id % 3 == 0
}
}
impl pallet_authority_members::Config for Test {
type Event = Event;
type IsMember = TestIsSmithMember;
type MaxOfflineSessions = ConstU32<2>;
type MemberId = u64;
type OnRemovedMember = ();
type OwnerKeyOf = ConvertInto;
type RefreshValidatorIdOrigin = system::EnsureRoot<u64>;
type RemoveMemberOrigin = system::EnsureRoot<u64>;
}
// Build genesis storage according to the mock runtime.
pub fn new_test_ext(initial_authorities_len: u64) -> sp_io::TestExternalities {
let initial_authorities = (1..=initial_authorities_len)
.map(|i| (i * 3, i * 3))
.collect();
let keys: Vec<_> = (1..=initial_authorities_len)
.map(|i| (i * 3, i * 3, UintAuthorityId(i * 3).into()))
.collect();
let mut t = frame_system::GenesisConfig::default()
.build_storage::<Test>()
.unwrap();
BasicExternalities::execute_with_storage(&mut t, || {
for (ref k, ..) in &keys {
frame_system::Pallet::<Test>::inc_providers(k);
}
// A dedicated test account
frame_system::Pallet::<Test>::inc_providers(&12);
});
pallet_authority_members::GenesisConfig::<Test> {
initial_authorities,
}
.assimilate_storage(&mut t)
.unwrap();
pallet_session::GenesisConfig::<Test> { keys }
.assimilate_storage(&mut t)
.unwrap();
sp_io::TestExternalities::new(t)
}
pub fn run_to_block(n: u64) {
while System::block_number() < n {
Session::on_finalize(System::block_number());
AuthorityMembers::on_initialize(System::block_number());
System::on_finalize(System::block_number());
System::reset_events();
System::set_block_number(System::block_number() + 1);
System::on_initialize(System::block_number());
AuthorityMembers::on_initialize(System::block_number());
Session::on_initialize(System::block_number());
}
}
// Copyright 2021 Axiom-Team
//
// This file is part of Substrate-Libre-Currency.
//
// Substrate-Libre-Currency is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License.
//
// Substrate-Libre-Currency is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>.
use super::*;
use crate::mock::*;
use crate::MemberData;
use frame_support::assert_ok;
use sp_runtime::testing::UintAuthorityId;
const EMPTY: Vec<u64> = Vec::new();
#[test]
fn test_genesis_build() {
new_test_ext(3).execute_with(|| {
run_to_block(1);
// Verify AuthorityMembers state
assert_eq!(AuthorityMembers::incoming(), EMPTY);
assert_eq!(AuthorityMembers::online(), vec![3, 6, 9]);
assert_eq!(AuthorityMembers::outgoing(), EMPTY);
assert_eq!(
AuthorityMembers::member(3),
Some(MemberData {
expire_on_session: 0,
session_keys_provided: true,
validator_id: 3,
})
);
assert_eq!(
AuthorityMembers::member(6),
Some(MemberData {
expire_on_session: 0,
session_keys_provided: true,
validator_id: 6,
})
);
assert_eq!(
AuthorityMembers::member(9),
Some(MemberData {
expire_on_session: 0,
session_keys_provided: true,
validator_id: 9,
})
);
// Verify Session state
assert_eq!(Session::current_index(), 0);
assert_eq!(Session::validators(), vec![3, 6, 9]);
});
}
#[test]
fn test_new_session_shoud_not_change_authorities_set() {
new_test_ext(3).execute_with(|| {
run_to_block(6);
assert_eq!(Session::current_index(), 1);
assert_eq!(Session::validators(), vec![3, 6, 9]);
});
}
#[test]
fn test_go_offline() {
new_test_ext(3).execute_with(|| {
run_to_block(1);
// Member 9 should be able to go offline
assert_ok!(AuthorityMembers::go_offline(Origin::signed(9), 9),);
// Verify state
assert_eq!(AuthorityMembers::incoming(), EMPTY);
assert_eq!(AuthorityMembers::online(), vec![3, 6, 9]);
assert_eq!(AuthorityMembers::outgoing(), vec![9]);
assert_eq!(
AuthorityMembers::member(9),
Some(MemberData {
expire_on_session: 0,
session_keys_provided: true,
validator_id: 9,
})
);
// Member 9 should be "deprogrammed" at the next session
run_to_block(5);
assert_eq!(
AuthorityMembers::member(9),
Some(MemberData {
expire_on_session: 4,
session_keys_provided: true,
validator_id: 9,
})
);
assert_eq!(AuthorityMembers::members_expire_on(4), vec![9],);
assert_eq!(Session::current_index(), 1);
assert_eq!(Session::validators(), vec![3, 6, 9]);
assert_eq!(Session::queued_keys().len(), 2);
assert_eq!(Session::queued_keys()[0].0, 3);
assert_eq!(Session::queued_keys()[1].0, 6);
// Member 9 should be **effectively** out at session 2
run_to_block(10);
assert_eq!(Session::current_index(), 2);
assert_eq!(Session::validators(), vec![3, 6]);
// Member 9 should be removed at session 4
run_to_block(20);
assert_eq!(Session::current_index(), 4);
assert_eq!(Session::validators(), vec![3, 6]);
assert_eq!(AuthorityMembers::members_expire_on(4), EMPTY);
assert_eq!(AuthorityMembers::member(9), None);
});
}
#[test]
fn test_go_online() {
new_test_ext(3).execute_with(|| {
run_to_block(1);
// Member 12 should be able to set his session keys
assert_ok!(AuthorityMembers::set_session_keys(
Origin::signed(12),
12,
UintAuthorityId(12).into(),
));
assert_eq!(
AuthorityMembers::member(12),
Some(MemberData {
expire_on_session: 2,
session_keys_provided: true,
validator_id: 12,
})
);
// Member 12 should be able to go online
assert_ok!(AuthorityMembers::go_online(Origin::signed(12), 12),);
// Verify state
assert_eq!(AuthorityMembers::incoming(), vec![12]);
assert_eq!(AuthorityMembers::online(), vec![3, 6, 9]);
assert_eq!(AuthorityMembers::outgoing(), EMPTY);
assert_eq!(
AuthorityMembers::member(12),
Some(MemberData {
expire_on_session: 2,
session_keys_provided: true,
validator_id: 12,
})
);
// Member 12 should be "programmed" at the next session
run_to_block(5);
assert_eq!(Session::current_index(), 1);
assert_eq!(Session::validators(), vec![3, 6, 9]);
assert_eq!(Session::queued_keys().len(), 4);
assert_eq!(Session::queued_keys()[0].0, 3);
assert_eq!(Session::queued_keys()[1].0, 6);
assert_eq!(Session::queued_keys()[2].0, 9);
assert_eq!(Session::queued_keys()[3].0, 12);
// Member 12 should be **effectively** in the authorities set in 2 sessions
run_to_block(10);
assert_eq!(Session::current_index(), 2);
assert_eq!(Session::validators(), vec![3, 6, 9, 12]);
});
}
#[test]
fn test_go_online_then_go_offline_in_same_session() {
new_test_ext(3).execute_with(|| {
run_to_block(1);
// Member 12 set his session keys & go online
assert_ok!(AuthorityMembers::set_session_keys(
Origin::signed(12),
12,
UintAuthorityId(12).into(),
));
assert_ok!(AuthorityMembers::go_online(Origin::signed(12), 12),);
run_to_block(2);
// Member 12 should be able to go offline at the same session to "cancel" his previous
// action
assert_ok!(AuthorityMembers::go_offline(Origin::signed(12), 12),);
// Verify state
assert_eq!(AuthorityMembers::incoming(), EMPTY);
assert_eq!(AuthorityMembers::online(), vec![3, 6, 9]);
assert_eq!(AuthorityMembers::outgoing(), vec![12]);
assert_eq!(
AuthorityMembers::member(12),
Some(MemberData {
expire_on_session: 2,
session_keys_provided: true,
validator_id: 12,
})
);
});
}
#[test]
fn test_go_offline_then_go_online_in_same_session() {
new_test_ext(3).execute_with(|| {
run_to_block(6);
// Member 9 go offline
assert_ok!(AuthorityMembers::go_offline(Origin::signed(9), 9),);
run_to_block(7);
// Member 9 should be able to go online at the same session to "cancel" his previous action
assert_ok!(AuthorityMembers::go_online(Origin::signed(9), 9),);
// Verify state
assert_eq!(AuthorityMembers::incoming(), EMPTY);
assert_eq!(AuthorityMembers::online(), vec![3, 6, 9]);
assert_eq!(AuthorityMembers::outgoing(), EMPTY);
assert_eq!(
AuthorityMembers::member(9),
Some(MemberData {
expire_on_session: 0,
session_keys_provided: true,
validator_id: 9,
})
);
});
}
// Copyright 2021 Axiom-Team
//
// This file is part of Substrate-Libre-Currency.
//
// Substrate-Libre-Currency is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License.
//
// Substrate-Libre-Currency is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>.
use frame_support::pallet_prelude::Weight;
pub trait OnRemovedMember<MemberId> {
fn on_removed_member(member_id: MemberId) -> Weight;
}
impl<MemberId> OnRemovedMember<MemberId> for () {
fn on_removed_member(_: MemberId) -> Weight {
0
}
}
// Copyright 2021 Axiom-Team
//
// This file is part of Substrate-Libre-Currency.
//
// Substrate-Libre-Currency is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License.
//
// Substrate-Libre-Currency is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>.
//! Various basic types for use in the certification pallet.
use codec::{Decode, Encode};
use scale_info::TypeInfo;
#[cfg(feature = "std")]
use serde::{Deserialize, Serialize};
use sp_staking::SessionIndex;
#[cfg_attr(feature = "std", derive(Debug, Deserialize, Serialize))]
#[derive(Encode, Decode, Clone, PartialEq, Eq, TypeInfo)]
pub struct MemberData<ValidatorId: Decode + Encode + TypeInfo> {
pub expire_on_session: SessionIndex,
pub session_keys_provided: bool,
pub validator_id: ValidatorId,
}
impl<ValidatorId: Decode + Encode + TypeInfo> MemberData<ValidatorId> {
pub fn new(validator_id: ValidatorId, expire_on_session: SessionIndex) -> Self {
MemberData {
expire_on_session,
session_keys_provided: false,
validator_id,
}
}
pub fn new_genesis(validator_id: ValidatorId) -> Self {
MemberData {
expire_on_session: 0,
session_keys_provided: true,
validator_id,
}
}
}
......@@ -96,6 +96,7 @@ pub mod pallet {
}
// GENESIS STUFF //
#[pallet::genesis_config]
pub struct GenesisConfig<T: Config> {
pub identities: Vec<IdtyValue<T::AccountId, T::BlockNumber, T::IdtyData>>,
......
......@@ -34,6 +34,7 @@ std = [
'frame-system-rpc-runtime-api/std',
'frame-system/std',
'pallet-authority-discovery/std',
'pallet-authority-members/std',
'pallet-babe/std',
'pallet-balances/std',
'pallet-certification/std',
......@@ -70,6 +71,7 @@ std = [
[dependencies]
common-runtime = { path = "../common", default-features = false }
pallet-authority-members = { path = '../../pallets/authority-members', default-features = false }
pallet-certification = { path = '../../pallets/certification', default-features = false }
pallet-duniter-test-parameters = { path = '../../pallets/duniter-test-parameters', default-features = false }
pallet-duniter-wot = { path = '../../pallets/duniter-wot', default-features = false }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment