From 6df0341064db40c31d449cc42e1cecb3579a529a Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 9 Sep 2020 12:06:26 +0300 Subject: [PATCH] extract ShiftSessionManager into separate pallet (#342) --- bridges/bin/node/runtime/Cargo.toml | 6 + bridges/bin/node/runtime/src/lib.rs | 95 +------- .../modules/shift-session-manager/Cargo.toml | 67 ++++++ .../modules/shift-session-manager/src/lib.rs | 213 ++++++++++++++++++ 4 files changed, 289 insertions(+), 92 deletions(-) create mode 100644 bridges/modules/shift-session-manager/Cargo.toml create mode 100644 bridges/modules/shift-session-manager/src/lib.rs diff --git a/bridges/bin/node/runtime/Cargo.toml b/bridges/bin/node/runtime/Cargo.toml index 7713c621dc..143c41a7a6 100644 --- a/bridges/bin/node/runtime/Cargo.toml +++ b/bridges/bin/node/runtime/Cargo.toml @@ -78,6 +78,11 @@ tag = 'v2.0.0-rc6' default-features = false git = "https://github.com/paritytech/substrate/" +[dependencies.pallet-shift-session-manager] +version = "0.1.0" +default-features = false +path = "../../../modules/shift-session-manager" + [dependencies.pallet-sudo] version = "2.0.0-rc6" tag = 'v2.0.0-rc6' @@ -264,6 +269,7 @@ std = [ "pallet-bridge-eth-poa/std", "pallet-grandpa/std", "pallet-randomness-collective-flip/std", + "pallet-shift-session-manager/std", "pallet-sudo/std", "pallet-timestamp/std", "pallet-transaction-payment/std", diff --git a/bridges/bin/node/runtime/src/lib.rs b/bridges/bin/node/runtime/src/lib.rs index 3bd039e3a5..92ee820f7c 100644 --- a/bridges/bin/node/runtime/src/lib.rs +++ b/bridges/bin/node/runtime/src/lib.rs @@ -35,7 +35,6 @@ pub mod benches; pub mod kovan; pub mod rialto; -use codec::{Decode, Encode}; use pallet_grandpa::{fg_primitives, AuthorityId as GrandpaId, AuthorityList as GrandpaAuthorityList}; use sp_api::impl_runtime_apis; use sp_consensus_aura::sr25519::AuthorityId as AuraId; @@ -401,63 +400,14 @@ impl pallet_session::Trait for Runtime { type ValidatorIdOf = (); type ShouldEndSession = pallet_session::PeriodicSessions; type NextSessionRotation = pallet_session::PeriodicSessions; - type SessionManager = ShiftSessionManager; + type SessionManager = pallet_shift_session_manager::Module; type SessionHandler = ::KeyTypeIdProviders; type Keys = SessionKeys; type DisabledValidatorsThreshold = (); type WeightInfo = (); } -pub struct ShiftSessionManager; - -impl ShiftSessionManager { - /// Select validators for session. - fn select_validators( - session_index: sp_staking::SessionIndex, - available_validators: &[AccountId], - ) -> Vec { - let available_validators_count = available_validators.len(); - let count = sp_std::cmp::max(1, 2 * available_validators_count / 3); - let offset = session_index as usize % available_validators_count; - let end = offset + count; - let session_validators = match end.overflowing_sub(available_validators_count) { - (wrapped_end, false) if wrapped_end != 0 => available_validators[offset..] - .iter() - .chain(available_validators[..wrapped_end].iter()) - .cloned() - .collect(), - _ => available_validators[offset..end].to_vec(), - }; - - session_validators - } -} - -impl pallet_session::SessionManager for ShiftSessionManager { - fn end_session(_: sp_staking::SessionIndex) {} - fn start_session(_: sp_staking::SessionIndex) {} - fn new_session(session_index: sp_staking::SessionIndex) -> Option> { - // can't access genesis config here :/ - if session_index == 0 || session_index == 1 { - return None; - } - - // the idea that on first call (i.e. when session 1 ends) we're reading current - // set of validators from session module (they are initial validators) and save - // in our 'local storage'. - // then for every session we select (deterministically) 2/3 of these initial - // validators to serve validators of new session - let available_validators = sp_io::storage::get(b":available_validators") - .and_then(|validators| Decode::decode(&mut &validators[..]).ok()) - .unwrap_or_else(|| { - let validators = >::validators(); - sp_io::storage::set(b":available_validators", &validators.encode()); - validators - }); - - Some(Self::select_validators(session_index, &available_validators)) - } -} +impl pallet_shift_session_manager::Trait for Runtime {} construct_runtime!( pub enum Runtime where @@ -479,6 +429,7 @@ construct_runtime!( TransactionPayment: pallet_transaction_payment::{Module, Storage}, Sudo: pallet_sudo::{Module, Call, Config, Storage, Event}, Session: pallet_session::{Module, Call, Storage, Event, Config}, + ShiftSessionManager: pallet_shift_session_manager::{Module}, } ); @@ -771,46 +722,6 @@ mod tests { use super::*; use bp_currency_exchange::DepositInto; - #[test] - fn shift_session_manager_works() { - let acc1 = AccountId::from([1u8; 32]); - let acc2 = AccountId::from([2u8; 32]); - let acc3 = AccountId::from([3u8; 32]); - let acc4 = AccountId::from([4u8; 32]); - let acc5 = AccountId::from([5u8; 32]); - let all_accs = vec![acc1.clone(), acc2.clone(), acc3.clone(), acc4.clone(), acc5.clone()]; - - // at least 1 validator is selected - assert_eq!( - ShiftSessionManager::select_validators(0, &[acc1.clone()]), - vec![acc1.clone()], - ); - - // at session#0, shift is also 0 - assert_eq!( - ShiftSessionManager::select_validators(0, &all_accs), - vec![acc1.clone(), acc2.clone(), acc3.clone()], - ); - - // at session#1, shift is also 1 - assert_eq!( - ShiftSessionManager::select_validators(1, &all_accs), - vec![acc2.clone(), acc3.clone(), acc4.clone()], - ); - - // at session#3, we're wrapping - assert_eq!( - ShiftSessionManager::select_validators(3, &all_accs), - vec![acc4, acc5, acc1.clone()], - ); - - // at session#5, we're starting from the beginning again - assert_eq!( - ShiftSessionManager::select_validators(5, &all_accs), - vec![acc1, acc2, acc3], - ); - } - fn run_deposit_into_test(test: impl Fn(AccountId) -> Balance) { let mut ext: sp_io::TestExternalities = SystemConfig::default().build_storage::().unwrap().into(); ext.execute_with(|| { diff --git a/bridges/modules/shift-session-manager/Cargo.toml b/bridges/modules/shift-session-manager/Cargo.toml new file mode 100644 index 0000000000..706733e73f --- /dev/null +++ b/bridges/modules/shift-session-manager/Cargo.toml @@ -0,0 +1,67 @@ +[package] +name = "pallet-shift-session-manager" +description = "A Substrate Runtime module that selects 2/3 of initial validators for every session" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2018" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] +codec = { package = "parity-scale-codec", version = "1.3.1", default-features = false } + +# Substrate Based Dependencies + +[dependencies.frame-support] +version = "2.0.0-rc6" +tag = 'v2.0.0-rc6' +default-features = false +git = "https://github.com/paritytech/substrate/" + +[dependencies.frame-system] +version = "2.0.0-rc6" +tag = 'v2.0.0-rc6' +default-features = false +git = "https://github.com/paritytech/substrate/" + +[dependencies.pallet-session] +version = "2.0.0-rc6" +tag = 'v2.0.0-rc6' +default-features = false +git = "https://github.com/paritytech/substrate/" + +[dependencies.sp-core] +version = "2.0.0-rc6" +tag = 'v2.0.0-rc6' +default-features = false +git = "https://github.com/paritytech/substrate/" + +[dependencies.sp-runtime] +version = "2.0.0-rc6" +tag = 'v2.0.0-rc6' +default-features = false +git = "https://github.com/paritytech/substrate/" + +[dependencies.sp-staking] +version = "2.0.0-rc6" +tag = 'v2.0.0-rc6' +default-features = false +git = "https://github.com/paritytech/substrate/" + +[dependencies.sp-std] +version = "2.0.0-rc6" +tag = 'v2.0.0-rc6' +default-features = false +git = "https://github.com/paritytech/substrate/" + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-support/std", + "frame-system/std", + "pallet-session/std", + "sp-core/std", + "sp-runtime/std", + "sp-staking/std", + "sp-std/std", +] diff --git a/bridges/modules/shift-session-manager/src/lib.rs b/bridges/modules/shift-session-manager/src/lib.rs new file mode 100644 index 0000000000..d2ca15d215 --- /dev/null +++ b/bridges/modules/shift-session-manager/src/lib.rs @@ -0,0 +1,213 @@ +// Copyright 2019-2020 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common 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 General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Substrate session manager that selects 2/3 validators from initial set, +//! starting from session 2. + +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::{decl_module, decl_storage}; +use sp_std::prelude::*; + +/// The module configuration trait. +pub trait Trait: pallet_session::Trait {} + +decl_module! { + /// Shift session manager pallet. + pub struct Module for enum Call where origin: T::Origin {} +} + +decl_storage! { + trait Store for Module as ShiftSessionManager { + /// Validators of first two sessions. + InitialValidators: Option>; + } +} + +impl pallet_session::SessionManager for Module { + fn end_session(_: sp_staking::SessionIndex) {} + fn start_session(_: sp_staking::SessionIndex) {} + fn new_session(session_index: sp_staking::SessionIndex) -> Option> { + // we don't want to add even more fields to genesis config => just return None + if session_index == 0 || session_index == 1 { + return None; + } + + // the idea that on first call (i.e. when session 1 ends) we're reading current + // set of validators from session module (they are initial validators) and save + // in our 'local storage'. + // then for every session we select (deterministically) 2/3 of these initial + // validators to serve validators of new session + let available_validators = InitialValidators::::get().unwrap_or_else(|| { + let validators = >::validators(); + InitialValidators::::put(validators.clone()); + validators + }); + + Some(Self::select_validators(session_index, &available_validators)) + } +} + +impl Module { + /// Select validators for session. + fn select_validators( + session_index: sp_staking::SessionIndex, + available_validators: &[T::ValidatorId], + ) -> Vec { + let available_validators_count = available_validators.len(); + let count = sp_std::cmp::max(1, 2 * available_validators_count / 3); + let offset = session_index as usize % available_validators_count; + let end = offset + count; + let session_validators = match end.overflowing_sub(available_validators_count) { + (wrapped_end, false) if wrapped_end != 0 => available_validators[offset..] + .iter() + .chain(available_validators[..wrapped_end].iter()) + .cloned() + .collect(), + _ => available_validators[offset..end].to_vec(), + }; + + session_validators + } +} + +#[cfg(test)] +mod tests { + use super::*; + use frame_support::sp_io::TestExternalities; + use frame_support::sp_runtime::{ + testing::{Header, UintAuthorityId}, + traits::{BlakeTwo256, ConvertInto, IdentityLookup}, + Perbill, RuntimeAppPublic, + }; + use frame_support::{impl_outer_origin, parameter_types, weights::Weight}; + use sp_core::H256; + + type AccountId = u64; + + #[derive(Clone, Eq, PartialEq)] + pub struct TestRuntime; + + impl_outer_origin! { + pub enum Origin for TestRuntime {} + } + + parameter_types! { + pub const BlockHashCount: u64 = 250; + pub const MaximumBlockWeight: Weight = 1024; + pub const MaximumBlockLength: u32 = 2 * 1024; + pub const AvailableBlockRatio: Perbill = Perbill::one(); + } + + impl frame_system::Trait for TestRuntime { + type Origin = Origin; + type Index = u64; + type Call = (); + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = Header; + type Event = (); + type BlockHashCount = BlockHashCount; + type MaximumBlockWeight = MaximumBlockWeight; + type DbWeight = (); + type BlockExecutionWeight = (); + type ExtrinsicBaseWeight = (); + type MaximumExtrinsicWeight = (); + type AvailableBlockRatio = AvailableBlockRatio; + type MaximumBlockLength = MaximumBlockLength; + type Version = (); + type ModuleToIndex = (); + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type BaseCallFilter = (); + type SystemWeightInfo = (); + } + + parameter_types! { + pub const Period: u64 = 1; + pub const Offset: u64 = 0; + } + + impl pallet_session::Trait for TestRuntime { + type Event = (); + type ValidatorId = ::AccountId; + type ValidatorIdOf = ConvertInto; + type ShouldEndSession = pallet_session::PeriodicSessions; + type NextSessionRotation = pallet_session::PeriodicSessions; + type SessionManager = (); + type SessionHandler = TestSessionHandler; + type Keys = UintAuthorityId; + type DisabledValidatorsThreshold = (); + type WeightInfo = (); + } + + impl Trait for TestRuntime {} + + pub struct TestSessionHandler; + impl pallet_session::SessionHandler for TestSessionHandler { + const KEY_TYPE_IDS: &'static [sp_runtime::KeyTypeId] = &[UintAuthorityId::ID]; + + fn on_genesis_session(_validators: &[(AccountId, Ks)]) {} + + fn on_new_session(_: bool, _: &[(AccountId, Ks)], _: &[(AccountId, Ks)]) {} + + fn on_disabled(_: usize) {} + } + + fn new_test_ext() -> TestExternalities { + let mut t = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap(); + pallet_session::GenesisConfig:: { + keys: vec![ + (1, 1, UintAuthorityId(1)), + (2, 2, UintAuthorityId(2)), + (3, 3, UintAuthorityId(3)), + (4, 4, UintAuthorityId(4)), + (5, 5, UintAuthorityId(5)), + ], + } + .assimilate_storage(&mut t) + .unwrap(); + TestExternalities::new(t) + } + + #[test] + fn shift_session_manager_works() { + new_test_ext().execute_with(|| { + let all_accs = vec![1, 2, 3, 4, 5]; + + // at least 1 validator is selected + assert_eq!(Module::::select_validators(0, &[1]), vec![1],); + + // at session#0, shift is also 0 + assert_eq!(Module::::select_validators(0, &all_accs), vec![1, 2, 3],); + + // at session#1, shift is also 1 + assert_eq!(Module::::select_validators(1, &all_accs), vec![2, 3, 4],); + + // at session#3, we're wrapping + assert_eq!(Module::::select_validators(3, &all_accs), vec![4, 5, 1],); + + // at session#5, we're starting from the beginning again + assert_eq!(Module::::select_validators(5, &all_accs), vec![1, 2, 3],); + }); + } +}