From 2741ab3140ca5d758baa299506a733edc81681d8 Mon Sep 17 00:00:00 2001 From: Fedor Sakharov Date: Fri, 7 Aug 2020 17:45:03 +0300 Subject: [PATCH 1/7] Initial commit. --- runtime/common/src/lib.rs | 1 + runtime/common/src/paras_registrar.rs | 675 ++++++++++++++++++++++++++ runtime/parachains/src/lib.rs | 4 +- runtime/parachains/src/mock.rs | 9 +- runtime/parachains/src/paras.rs | 13 +- runtime/rococo-v1/src/lib.rs | 16 +- 6 files changed, 709 insertions(+), 9 deletions(-) create mode 100644 runtime/common/src/paras_registrar.rs diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index c512e822cf46..1b026c65c970 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -28,6 +28,7 @@ pub mod crowdfund; pub mod purchase; pub mod impls; pub mod paras_sudo_wrapper; +pub mod paras_registrar; use primitives::v0::BlockNumber; use sp_runtime::{Perquintill, Perbill, FixedPointNumber, traits::Saturating}; diff --git a/runtime/common/src/paras_registrar.rs b/runtime/common/src/paras_registrar.rs new file mode 100644 index 000000000000..35abf49cfc32 --- /dev/null +++ b/runtime/common/src/paras_registrar.rs @@ -0,0 +1,675 @@ +// Copyright 2017-2020 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see . + +use sp_std::{prelude::*, result}; + +use frame_support::{ + decl_storage, decl_module, decl_event, decl_error, ensure, + dispatch::DispatchResult, + traits::{Get, Currency, ReservableCurrency}, + weights::DispatchClass, +}; +use frame_system::{self, ensure_root, ensure_signed}; +use primitives::v1::{ + Id as ParaId, ValidationCode, HeadData, +}; +use runtime_parachains::{ + paras::{ + self, + ParaGenesisArgs, + }, + ensure_parachain, + Origin, +}; + +type BalanceOf = + <::Currency as Currency<::AccountId>>::Balance; + +pub trait Trait: paras::Trait { + /// The overarching event type. + type Event: From + Into<::Event>; + + /// The aggregated origin type. + type Origin: From<::Origin> + + Into::Origin>>; + + /// The system's currency for parathread payment. + type Currency: ReservableCurrency; + + /// The deposit to be paid to run a parathread. + type ParathreadDeposit: Get>; +} + +decl_event! { + pub enum Event { + /// A parathread was registered; its new ID is supplied. + ParathreadRegistered(ParaId), + + /// The parathread of the supplied ID was de-registered. + ParathreadDeregistered(ParaId), + } +} + +decl_storage! { + trait Store for Module as Registrar { + /// Pending swap operations. + PendingSwap: map hasher(twox_64_concat) ParaId => Option; + + /// Map of all registered parathreads/chains. + Paras get(fn paras): map hasher(twox_64_concat) ParaId => Option; + + /// Users who have paid a parathread's deposit. + Debtors: map hasher(twox_64_concat) ParaId => T::AccountId; + } +} + +decl_error! { + pub enum Error for Module { + /// Parachain already exists. + ParaAlreadyExists, + /// Invalid parachain ID. + InvalidChainId, + /// Invalid parathread ID. + InvalidThreadId, + /// Invalid para code size. + CodeTooLarge, + /// Invalid para head data size. + HeadDataTooLarge, + } +} + +decl_module! { + pub struct Module for enum Call where origin: ::Origin { + type Error = Error; + + #[weight = (5_000_000_000, DispatchClass::Operational)] + pub fn register_parathread( + origin, + id: ParaId, + genesis_head: HeadData, + validation_code: ValidationCode, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + + ensure!(!Paras::contains_key(id), Error::::ParaAlreadyExists); + + let outgoing = >::outgoing_paras(); + + ensure!(outgoing.binary_search(&id).is_err(), Error::::ParaAlreadyExists); + + ::Currency::reserve(&who, T::ParathreadDeposit::get())?; + >::insert(id, who); + + Paras::insert(id, false); + + let genesis = ParaGenesisArgs { + genesis_head, + validation_code, + parachain: false, + }; + + >::schedule_para_initialize(id, genesis); + + Ok(()) + } + + #[weight = (10_000, DispatchClass::Operational)] + pub fn register_parachain( + origin, + id: ParaId, + genesis_head: HeadData, + validation_code: ValidationCode, + ) -> DispatchResult { + ensure_root(origin)?; + + ensure!(!Paras::contains_key(id), Error::::ParaAlreadyExists); + + let outgoing = >::outgoing_paras(); + + ensure!(outgoing.binary_search(&id).is_err(), Error::::ParaAlreadyExists); + + Paras::insert(id, true); + + let genesis = ParaGenesisArgs { + genesis_head, + validation_code, + parachain: true, + }; + + >::schedule_para_initialize(id, genesis); + + + Ok(()) + } + + #[weight = (10_000, DispatchClass::Operational)] + pub fn deregister_parachain(origin, id: ParaId) -> DispatchResult { + ensure_root(origin)?; + + let is_parachain = Paras::take(id).ok_or(Error::::InvalidChainId)?; + + ensure!(is_parachain, Error::::InvalidChainId); + + >::schedule_para_cleanup(id); + + Ok(()) + } + + #[weight = (10_000_000, DispatchClass::Operational)] + pub fn deregister_parathread(origin) -> DispatchResult { + let id = ensure_parachain(::Origin::from(origin))?; + + let is_parachain = Paras::take(id).ok_or(Error::::InvalidChainId)?; + + ensure!(!is_parachain, Error::::InvalidThreadId); + + let debtor = >::take(id); + let _ = ::Currency::unreserve(&debtor, T::ParathreadDeposit::get()); + + >::schedule_para_cleanup(id); + + Ok(()) + } + + /// Swap a parachain with another parachain or parathread. The origin must be a `Parachain`. + /// The swap will happen only if there is already an opposite swap pending. If there is not, + /// the swap will be stored in the pending swaps map, ready for a later confirmatory swap. + /// + /// The `ParaId`s remain mapped to the same head data and code so external code can rely on + /// `ParaId` to be a long-term identifier of a notional "parachain". However, their + /// scheduling info (i.e. whether they're a parathread or parachain), auction information + /// and the auction deposit are switched. + #[weight = (10_000_000, DispatchClass::Operational)] + pub fn swap(origin, other: ParaId) { + let id = ensure_parachain(::Origin::from(origin))?; + + if PendingSwap::get(other) == Some(id) { + // Remove intention to swap. + PendingSwap::remove(other); + + Paras::mutate(id, |i| + Paras::mutate(other, |j| + sp_std::mem::swap(i, j) + ) + ); + + >::mutate(id, |i| + >::mutate(other, |j| + sp_std::mem::swap(i, j) + ) + ); + } else { + PendingSwap::insert(id, other); + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use sp_io::TestExternalities; + use sp_core::H256; + use sp_runtime::{ + traits::{ + BlakeTwo256, IdentityLookup, Extrinsic as ExtrinsicT, + }, testing::{UintAuthorityId, TestXt}, Perbill, curve::PiecewiseLinear, + }; + use primitives::v1::{ + Balance, BlockNumber, Header, Signature, + }; + use frame_support::{ + traits::{Randomness, OnInitialize, OnFinalize}, + impl_outer_origin, impl_outer_dispatch, assert_ok, parameter_types, + }; + use keyring::Sr25519Keyring; + use runtime_parachains::{initializer, configuration, inclusion, scheduler}; + use pallet_session::OneSessionHandler; + + impl_outer_origin! { + pub enum Origin for Test { + paras, + } + } + + impl_outer_dispatch! { + pub enum Call for Test where origin: Origin { + paras::Parachains, + registrar::Registrar, + staking::Staking, + } + } + + pallet_staking_reward_curve::build! { + const REWARD_CURVE: PiecewiseLinear<'static> = curve!( + min_inflation: 0_025_000, + max_inflation: 0_100_000, + ideal_stake: 0_500_000, + falloff: 0_050_000, + max_piece_count: 40, + test_precision: 0_005_000, + ); + } + + #[derive(Clone, Eq, PartialEq)] + pub struct Test; + parameter_types! { + pub const BlockHashCount: u32 = 250; + pub const MaximumBlockWeight: u32 = 4 * 1024 * 1024; + pub const MaximumBlockLength: u32 = 4 * 1024 * 1024; + pub const AvailableBlockRatio: Perbill = Perbill::from_percent(75); + } + + impl frame_system::Trait for Test { + type BaseCallFilter = (); + type Origin = Origin; + type Call = Call; + type Index = u64; + type BlockNumber = BlockNumber; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type Event = (); + type BlockHashCount = BlockHashCount; + type MaximumBlockWeight = MaximumBlockWeight; + type DbWeight = (); + type BlockExecutionWeight = (); + type ExtrinsicBaseWeight = (); + type MaximumExtrinsicWeight = MaximumBlockWeight; + type MaximumBlockLength = MaximumBlockLength; + type AvailableBlockRatio = AvailableBlockRatio; + type Version = (); + type ModuleToIndex = (); + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = Balances; + type SystemWeightInfo = (); + } + + impl frame_system::offchain::SendTransactionTypes for Test where + Call: From, + { + type OverarchingCall = Call; + type Extrinsic = TestXt; + } + + parameter_types! { + pub const ExistentialDeposit: Balance = 1; + } + + impl pallet_balances::Trait for Test { + type Balance = u128; + type DustRemoval = (); + type Event = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + } + + parameter_types!{ + pub const SlashDeferDuration: pallet_staking::EraIndex = 7; + pub const AttestationPeriod: BlockNumber = 100; + pub const MinimumPeriod: u64 = 3; + pub const SessionsPerEra: sp_staking::SessionIndex = 6; + pub const BondingDuration: pallet_staking::EraIndex = 28; + pub const MaxNominatorRewardedPerValidator: u32 = 64; + } + + parameter_types! { + pub const Period: BlockNumber = 1; + pub const Offset: BlockNumber = 0; + pub const DisabledValidatorsThreshold: Perbill = Perbill::from_percent(17); + pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE; + } + + impl pallet_session::Trait for Test { + type SessionManager = (); + type Keys = UintAuthorityId; + type ShouldEndSession = pallet_session::PeriodicSessions; + type NextSessionRotation = pallet_session::PeriodicSessions; + type SessionHandler = pallet_session::TestSessionHandler; + type Event = (); + type ValidatorId = u64; + type ValidatorIdOf = (); + type DisabledValidatorsThreshold = DisabledValidatorsThreshold; + type WeightInfo = (); + } + + parameter_types! { + pub const MaxHeadDataSize: u32 = 100; + pub const MaxCodeSize: u32 = 100; + + pub const ValidationUpgradeFrequency: BlockNumber = 10; + pub const ValidationUpgradeDelay: BlockNumber = 2; + pub const SlashPeriod: BlockNumber = 50; + pub const ElectionLookahead: BlockNumber = 0; + pub const StakingUnsignedPriority: u64 = u64::max_value() / 2; + } + + impl pallet_staking::Trait for Test { + type RewardRemainder = (); + type CurrencyToVote = (); + type Event = (); + type Currency = pallet_balances::Module; + type Slash = (); + type Reward = (); + type SessionsPerEra = SessionsPerEra; + type BondingDuration = BondingDuration; + type SlashDeferDuration = SlashDeferDuration; + type SlashCancelOrigin = frame_system::EnsureRoot; + type SessionInterface = Self; + type UnixTime = pallet_timestamp::Module; + type RewardCurve = RewardCurve; + type MaxNominatorRewardedPerValidator = MaxNominatorRewardedPerValidator; + type NextNewSession = Session; + type ElectionLookahead = ElectionLookahead; + type Call = Call; + type UnsignedPriority = StakingUnsignedPriority; + type MaxIterations = (); + type MinSolutionScoreBump = (); + type WeightInfo = (); + } + + impl pallet_timestamp::Trait for Test { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = MinimumPeriod; + type WeightInfo = (); + } + + impl pallet_session::historical::Trait for Test { + type FullIdentification = pallet_staking::Exposure; + type FullIdentificationOf = pallet_staking::ExposureOf; + } + + // This is needed for a custom `AccountId` type which is `u64` in testing here. + pub mod test_keys { + use sp_core::crypto::KeyTypeId; + + pub const KEY_TYPE: KeyTypeId = KeyTypeId(*b"test"); + + mod app { + use super::super::Inclusion; + use sp_application_crypto::{app_crypto, sr25519}; + + app_crypto!(sr25519, super::KEY_TYPE); + + impl sp_runtime::traits::IdentifyAccount for Public { + type AccountId = u64; + + fn into_account(self) -> Self::AccountId { + let id = self.0.clone().into(); + Inclusion::validators().iter().position(|b| *b == id).unwrap() as u64 + } + } + } + + pub type ReporterId = app::Public; + } + + impl paras::Trait for Test { + type Origin = Origin; + } + + impl configuration::Trait for Test { } + + impl inclusion::Trait for Test { + type Event = (); + } + + pub struct TestRandomness; + + impl Randomness for TestRandomness { + fn random(_subject: &[u8]) -> H256 { + Default::default() + } + } + + impl initializer::Trait for Test { + type Randomness = TestRandomness; + } + + impl scheduler::Trait for Test { } + + type Extrinsic = TestXt; + + impl frame_system::offchain::CreateSignedTransaction for Test where + Call: From, + { + fn create_transaction>( + call: Call, + _public: test_keys::ReporterId, + _account: ::AccountId, + nonce: ::Index, + ) -> Option<(Call, ::SignaturePayload)> { + Some((call, (nonce, ()))) + } + } + + impl frame_system::offchain::SigningTypes for Test { + type Public = test_keys::ReporterId; + type Signature = Signature; + } + + parameter_types! { + pub const ParathreadDeposit: Balance = 10; + pub const QueueSize: usize = 2; + pub const MaxRetries: u32 = 3; + } + + impl Trait for Test { + type Event = (); + type Origin = Origin; + type Currency = pallet_balances::Module; + type ParathreadDeposit = ParathreadDeposit; + } + + type Balances = pallet_balances::Module; + type Parachains = paras::Module; + type Inclusion = inclusion::Module; + type System = frame_system::Module; + type Registrar = Module; + type Session = pallet_session::Module; + type Staking = pallet_staking::Module; + type Initializer = initializer::Module; + + fn new_test_ext() -> TestExternalities { + let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + + let authority_keys = [ + Sr25519Keyring::Alice, + Sr25519Keyring::Bob, + Sr25519Keyring::Charlie, + Sr25519Keyring::Dave, + Sr25519Keyring::Eve, + Sr25519Keyring::Ferdie, + Sr25519Keyring::One, + Sr25519Keyring::Two, + ]; + + // stashes are the index. + let session_keys: Vec<_> = authority_keys.iter().enumerate() + .map(|(i, _k)| (i as u64, i as u64, UintAuthorityId(i as u64))) + .collect(); + + let balances: Vec<_> = (0..authority_keys.len()).map(|i| (i as u64, 10_000_000)).collect(); + + pallet_session::GenesisConfig:: { + keys: session_keys, + }.assimilate_storage(&mut t).unwrap(); + + pallet_balances::GenesisConfig:: { + balances, + }.assimilate_storage(&mut t).unwrap(); + + t.into() + } + + fn init_block() { + println!("Initializing {}", System::block_number()); + System::on_initialize(System::block_number()); + Initializer::on_initialize(System::block_number()); + } + + fn run_to_block(n: BlockNumber) { + println!("Running until block {}", n); + while System::block_number() < n { + let b = System::block_number(); + + if System::block_number() > 1 { + println!("Finalizing {}", System::block_number()); + System::on_finalize(System::block_number()); + } + // Session change every 3 blocks. + // TODO: Better way to do this? + if (b + 1) % 3 == 0 { + Initializer::on_new_session( + false, + Vec::new().into_iter(), + Vec::new().into_iter(), + ); + } + System::set_block_number(b + 1); + init_block(); + } + } + + #[test] + fn basic_setup_works() { + new_test_ext().execute_with(|| { + assert_eq!(PendingSwap::get(&ParaId::from(0u32)), None); + assert_eq!(Paras::get(&ParaId::from(0u32)), None); + }); + } + + #[test] + fn register_deregister_chain_works() { + new_test_ext().execute_with(|| { + + run_to_block(2); + + assert_ok!(Registrar::register_parachain( + Origin::root(), + 2u32.into(), + vec![3; 3].into(), + vec![3; 3].into(), + )); + + let orig_bal = Balances::free_balance(&3u64); + + // Register a new parathread + assert_ok!(Registrar::register_parathread( + Origin::signed(3u64), + 8u32.into(), + vec![3; 3].into(), + vec![3; 3].into(), + )); + + // deposit should be taken (reserved) + assert_eq!(Balances::free_balance(3u64) + ParathreadDeposit::get(), orig_bal); + assert_eq!(Balances::reserved_balance(3u64), ParathreadDeposit::get()); + + run_to_block(3); + + assert_ok!(Registrar::deregister_parachain(Origin::root(), 2u32.into())); + + assert_ok!(Registrar::deregister_parathread( + runtime_parachains::Origin::Parachain(8u32.into()).into() + )); + + // reserved balance should be returned. + assert_eq!(Balances::free_balance(3u64), orig_bal); + assert_eq!(Balances::reserved_balance(3u64), 0); + }); + } + + #[test] + fn swap_handles_funds_correctly() { + new_test_ext().execute_with(|| { + run_to_block(2); + + let initial_1_balance = Balances::free_balance(1); + let initial_2_balance = Balances::free_balance(2); + + // User 1 register a new parathread + assert_ok!(Registrar::register_parathread( + Origin::signed(1), + 8u32.into(), + vec![1; 3].into(), + vec![1; 3].into(), + )); + + assert_ok!(Registrar::register_parachain( + Origin::root(), + 2u32.into(), + vec![1; 3].into(), + vec![1; 3].into(), + )); + + run_to_block(9); + + // Swap the parachain and parathread + assert_ok!(Registrar::swap(runtime_parachains::Origin::Parachain(2u32.into()).into(), 8u32.into())); + assert_ok!(Registrar::swap(runtime_parachains::Origin::Parachain(8u32.into()).into(), 2u32.into())); + + // Deregister a parathread that was originally a parachain + assert_ok!(Registrar::deregister_parathread(runtime_parachains::Origin::Parachain(2u32.into()).into())); + + run_to_block(12); + + // Funds are correctly returned + assert_eq!(Balances::free_balance(1), initial_1_balance); + assert_eq!(Balances::free_balance(2), initial_2_balance); + }); + } + + #[test] + fn cannot_register_until_para_is_cleaned_up() { + new_test_ext().execute_with(|| { + run_to_block(2); + + assert_ok!(Registrar::register_parachain( + Origin::root(), + 1u32.into(), + vec![1; 3].into(), + vec![1; 3].into(), + )); + + run_to_block(4); + + assert_ok!(Registrar::deregister_parachain(Origin::root(), 1u32.into())); + run_to_block(5); + + assert!(Registrar::register_parachain( + Origin::root(), + 1u32.into(), + vec![1; 3].into(), + vec![1; 3].into(), + ).is_err()); + + run_to_block(6); + + assert_ok!(Registrar::register_parachain( + Origin::root(), + 1u32.into(), + vec![1; 3].into(), + vec![1; 3].into(), + )); + }); + } +} diff --git a/runtime/parachains/src/lib.rs b/runtime/parachains/src/lib.rs index b41968b480f4..00a3a4fb6799 100644 --- a/runtime/parachains/src/lib.rs +++ b/runtime/parachains/src/lib.rs @@ -42,8 +42,8 @@ pub mod runtime_api_impl; mod mock; /// Origin for the parachains. -#[derive(PartialEq, Eq, Clone, Encode, Decode)] -#[cfg_attr(feature = "std", derive(Debug))] +#[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)] +// TODO: #[cfg_attr(feature = "std", derive(Debug))] pub enum Origin { /// It comes from a parachain. Parachain(ParaId), diff --git a/runtime/parachains/src/mock.rs b/runtime/parachains/src/mock.rs index 7001b1c1df9c..509272edb3c7 100644 --- a/runtime/parachains/src/mock.rs +++ b/runtime/parachains/src/mock.rs @@ -30,13 +30,16 @@ use frame_support::{ weights::Weight, traits::Randomness as RandomnessT, }; use crate::inclusion; +use crate::paras; /// A test runtime struct. #[derive(Clone, Eq, PartialEq)] pub struct Test; impl_outer_origin! { - pub enum Origin for Test { } + pub enum Origin for Test { + paras + } } impl_outer_dispatch! { @@ -101,7 +104,9 @@ impl crate::initializer::Trait for Test { impl crate::configuration::Trait for Test { } -impl crate::paras::Trait for Test { } +impl crate::paras::Trait for Test { + type Origin = Origin; +} impl crate::scheduler::Trait for Test { } diff --git a/runtime/parachains/src/paras.rs b/runtime/parachains/src/paras.rs index b1e48ca3ce62..9f63dbb93ce3 100644 --- a/runtime/parachains/src/paras.rs +++ b/runtime/parachains/src/paras.rs @@ -24,7 +24,7 @@ //! only occur at session boundaries. use sp_std::prelude::*; -use sp_std::marker::PhantomData; +use sp_std::result; use sp_runtime::traits::{One, BlakeTwo256, Hash as HashT, Saturating}; use primitives::v1::{ Id as ParaId, ValidationCode, HeadData, LocalValidationData, @@ -41,7 +41,14 @@ use sp_core::RuntimeDebug; #[cfg(feature = "std")] use serde::{Serialize, Deserialize}; -pub trait Trait: frame_system::Trait + configuration::Trait { } +pub use crate::Origin; + +pub trait Trait: frame_system::Trait + configuration::Trait { + /// The outer origin type. + type Origin: From + + From<::Origin> + + Into::Origin>>; +} // the two key times necessary to track for every code replacement. #[derive(Default, Encode, Decode)] @@ -205,7 +212,7 @@ decl_storage! { /// Upcoming paras instantiation arguments. UpcomingParasGenesis: map hasher(twox_64_concat) ParaId => Option; /// Paras that are to be cleaned up at the end of the session. - OutgoingParas: Vec; + OutgoingParas get(fn outgoing_paras): Vec; } add_extra_genesis { diff --git a/runtime/rococo-v1/src/lib.rs b/runtime/rococo-v1/src/lib.rs index f1af47613ed1..635eac3a0eba 100644 --- a/runtime/rococo-v1/src/lib.rs +++ b/runtime/rococo-v1/src/lib.rs @@ -62,6 +62,7 @@ use sp_staking::SessionIndex; use pallet_session::historical as session_historical; use frame_system::EnsureRoot; use runtime_common::paras_sudo_wrapper as paras_sudo_wrapper; +use runtime_common::paras_registrar; use runtime_parachains::configuration as parachains_configuration; use runtime_parachains::inclusion as parachains_inclusion; @@ -368,9 +369,10 @@ construct_runtime! { Inclusion: parachains_inclusion::{Module, Call, Storage, Event}, InclusionInherent: parachains_inclusion_inherent::{Module, Call, Storage}, Scheduler: parachains_scheduler::{Module, Call, Storage}, - Paras: parachains_paras::{Module, Call, Storage}, + Paras: parachains_paras::{Module, Call, Storage, Origin}, Initializer: parachains_initializer::{Module, Call, Storage}, + Registrar: paras_registrar::{Module, Call, Storage, Event}, ParasSudoWrapper: paras_sudo_wrapper::{Module, Call}, } } @@ -716,7 +718,9 @@ impl parachains_inclusion::Trait for Runtime { type Event = Event; } -impl parachains_paras::Trait for Runtime { } +impl parachains_paras::Trait for Runtime { + type Origin = Origin; +} impl parachains_inclusion_inherent::Trait for Runtime { } @@ -727,3 +731,11 @@ impl parachains_initializer::Trait for Runtime { } impl paras_sudo_wrapper::Trait for Runtime { } + +impl paras_registrar::Trait for Runtime { + type Event = Event; + type Currency = Balances; + type ParathreadDeposit = ParathreadDeposit; + type Origin = Origin; + type SwapAux = (); +} From bddfa0478e919051506d71a95106a2bad6eefdb5 Mon Sep 17 00:00:00 2001 From: Fedor Sakharov Date: Mon, 10 Aug 2020 18:29:50 +0300 Subject: [PATCH 2/7] Fix build --- runtime/parachains/src/paras.rs | 1 + runtime/rococo-v1/src/lib.rs | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/parachains/src/paras.rs b/runtime/parachains/src/paras.rs index 9f63dbb93ce3..513aee5e3488 100644 --- a/runtime/parachains/src/paras.rs +++ b/runtime/parachains/src/paras.rs @@ -25,6 +25,7 @@ use sp_std::prelude::*; use sp_std::result; +use sp_std::marker::PhantomData; use sp_runtime::traits::{One, BlakeTwo256, Hash as HashT, Saturating}; use primitives::v1::{ Id as ParaId, ValidationCode, HeadData, LocalValidationData, diff --git a/runtime/rococo-v1/src/lib.rs b/runtime/rococo-v1/src/lib.rs index 635eac3a0eba..3cbdd392da7a 100644 --- a/runtime/rococo-v1/src/lib.rs +++ b/runtime/rococo-v1/src/lib.rs @@ -737,5 +737,4 @@ impl paras_registrar::Trait for Runtime { type Currency = Balances; type ParathreadDeposit = ParathreadDeposit; type Origin = Origin; - type SwapAux = (); } From cdcd8f0d48a0044eb4db605c17cd751c32ea22a8 Mon Sep 17 00:00:00 2001 From: Fedor Sakharov Date: Tue, 11 Aug 2020 16:08:42 +0300 Subject: [PATCH 3/7] Add comments, remove Event --- runtime/common/src/paras_registrar.rs | 44 +++++++++++++++------------ runtime/rococo-v1/src/lib.rs | 3 +- 2 files changed, 26 insertions(+), 21 deletions(-) diff --git a/runtime/common/src/paras_registrar.rs b/runtime/common/src/paras_registrar.rs index 35abf49cfc32..653fad4efcc0 100644 --- a/runtime/common/src/paras_registrar.rs +++ b/runtime/common/src/paras_registrar.rs @@ -14,10 +14,13 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . +//! Module to handle parathread/parachain registration and related fund management. +//! In essence this is a simple wrapper around `paras`. + use sp_std::{prelude::*, result}; use frame_support::{ - decl_storage, decl_module, decl_event, decl_error, ensure, + decl_storage, decl_module, decl_error, ensure, dispatch::DispatchResult, traits::{Get, Currency, ReservableCurrency}, weights::DispatchClass, @@ -39,10 +42,10 @@ type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; pub trait Trait: paras::Trait { - /// The overarching event type. - type Event: From + Into<::Event>; - - /// The aggregated origin type. + /// The aggregated origin type must support the `parachains` origin. We require that we can + /// infallibly convert between this origin and the system origin, but in reality, they're the + /// same type, we just can't express that to the Rust type system without writing a `where` + /// clause everywhere. type Origin: From<::Origin> + Into::Origin>>; @@ -53,16 +56,6 @@ pub trait Trait: paras::Trait { type ParathreadDeposit: Get>; } -decl_event! { - pub enum Event { - /// A parathread was registered; its new ID is supplied. - ParathreadRegistered(ParaId), - - /// The parathread of the supplied ID was de-registered. - ParathreadDeregistered(ParaId), - } -} - decl_storage! { trait Store for Module as Registrar { /// Pending swap operations. @@ -95,6 +88,10 @@ decl_module! { pub struct Module for enum Call where origin: ::Origin { type Error = Error; + /// Register a parathread with given code for immediate use. + /// + /// Must be sent from a Signed origin that is able to have `ParathreadDeposit` reserved. + /// `gensis_head` and `validation_code` are used to initalize the parathread's state. #[weight = (5_000_000_000, DispatchClass::Operational)] pub fn register_parathread( origin, @@ -126,7 +123,9 @@ decl_module! { Ok(()) } - #[weight = (10_000, DispatchClass::Operational)] + /// Register a parachain with given code. Must be called by root. + /// Fails if given ID is already used. + #[weight = (5_000_000_000, DispatchClass::Operational)] pub fn register_parachain( origin, id: ParaId, @@ -155,7 +154,8 @@ decl_module! { Ok(()) } - #[weight = (10_000, DispatchClass::Operational)] + /// Deregister a parachain with the given ID. Must be called by root. + #[weight = (0, DispatchClass::Operational)] pub fn deregister_parachain(origin, id: ParaId) -> DispatchResult { ensure_root(origin)?; @@ -168,7 +168,14 @@ decl_module! { Ok(()) } - #[weight = (10_000_000, DispatchClass::Operational)] + /// Deregister a parathread and retreive the deposit. + /// + /// Must be sent from a `Parachain` origin which is currently a parathread. + /// + /// Ensure that before calling this that any funds you want emptied from the parathread's + /// account is moved out; after this it will be impossible to retreive them (without + /// governance intervention). + #[weight = (0, DispatchClass::Operational)] pub fn deregister_parathread(origin) -> DispatchResult { let id = ensure_parachain(::Origin::from(origin))?; @@ -473,7 +480,6 @@ mod tests { } impl Trait for Test { - type Event = (); type Origin = Origin; type Currency = pallet_balances::Module; type ParathreadDeposit = ParathreadDeposit; diff --git a/runtime/rococo-v1/src/lib.rs b/runtime/rococo-v1/src/lib.rs index 3cbdd392da7a..b9cf4e9ab803 100644 --- a/runtime/rococo-v1/src/lib.rs +++ b/runtime/rococo-v1/src/lib.rs @@ -372,7 +372,7 @@ construct_runtime! { Paras: parachains_paras::{Module, Call, Storage, Origin}, Initializer: parachains_initializer::{Module, Call, Storage}, - Registrar: paras_registrar::{Module, Call, Storage, Event}, + Registrar: paras_registrar::{Module, Call, Storage}, ParasSudoWrapper: paras_sudo_wrapper::{Module, Call}, } } @@ -733,7 +733,6 @@ impl parachains_initializer::Trait for Runtime { impl paras_sudo_wrapper::Trait for Runtime { } impl paras_registrar::Trait for Runtime { - type Event = Event; type Currency = Balances; type ParathreadDeposit = ParathreadDeposit; type Origin = Origin; From 62ad3c8170beeff62683bbacae56912f19f1ee59 Mon Sep 17 00:00:00 2001 From: Fedor Sakharov Date: Wed, 12 Aug 2020 19:03:26 +0300 Subject: [PATCH 4/7] Dont expose calls --- runtime/common/src/paras_registrar.rs | 99 +++++++++++++-------------- 1 file changed, 46 insertions(+), 53 deletions(-) diff --git a/runtime/common/src/paras_registrar.rs b/runtime/common/src/paras_registrar.rs index 653fad4efcc0..a4ca96e6ce2c 100644 --- a/runtime/common/src/paras_registrar.rs +++ b/runtime/common/src/paras_registrar.rs @@ -23,9 +23,8 @@ use frame_support::{ decl_storage, decl_module, decl_error, ensure, dispatch::DispatchResult, traits::{Get, Currency, ReservableCurrency}, - weights::DispatchClass, }; -use frame_system::{self, ensure_root, ensure_signed}; +use frame_system::{self, ensure_signed}; use primitives::v1::{ Id as ParaId, ValidationCode, HeadData, }; @@ -92,8 +91,8 @@ decl_module! { /// /// Must be sent from a Signed origin that is able to have `ParathreadDeposit` reserved. /// `gensis_head` and `validation_code` are used to initalize the parathread's state. - #[weight = (5_000_000_000, DispatchClass::Operational)] - pub fn register_parathread( + #[weight = 0] + fn register_parathread( origin, id: ParaId, genesis_head: HeadData, @@ -123,51 +122,6 @@ decl_module! { Ok(()) } - /// Register a parachain with given code. Must be called by root. - /// Fails if given ID is already used. - #[weight = (5_000_000_000, DispatchClass::Operational)] - pub fn register_parachain( - origin, - id: ParaId, - genesis_head: HeadData, - validation_code: ValidationCode, - ) -> DispatchResult { - ensure_root(origin)?; - - ensure!(!Paras::contains_key(id), Error::::ParaAlreadyExists); - - let outgoing = >::outgoing_paras(); - - ensure!(outgoing.binary_search(&id).is_err(), Error::::ParaAlreadyExists); - - Paras::insert(id, true); - - let genesis = ParaGenesisArgs { - genesis_head, - validation_code, - parachain: true, - }; - - >::schedule_para_initialize(id, genesis); - - - Ok(()) - } - - /// Deregister a parachain with the given ID. Must be called by root. - #[weight = (0, DispatchClass::Operational)] - pub fn deregister_parachain(origin, id: ParaId) -> DispatchResult { - ensure_root(origin)?; - - let is_parachain = Paras::take(id).ok_or(Error::::InvalidChainId)?; - - ensure!(is_parachain, Error::::InvalidChainId); - - >::schedule_para_cleanup(id); - - Ok(()) - } - /// Deregister a parathread and retreive the deposit. /// /// Must be sent from a `Parachain` origin which is currently a parathread. @@ -175,8 +129,8 @@ decl_module! { /// Ensure that before calling this that any funds you want emptied from the parathread's /// account is moved out; after this it will be impossible to retreive them (without /// governance intervention). - #[weight = (0, DispatchClass::Operational)] - pub fn deregister_parathread(origin) -> DispatchResult { + #[weight = 0] + fn deregister_parathread(origin) -> DispatchResult { let id = ensure_parachain(::Origin::from(origin))?; let is_parachain = Paras::take(id).ok_or(Error::::InvalidChainId)?; @@ -199,8 +153,8 @@ decl_module! { /// `ParaId` to be a long-term identifier of a notional "parachain". However, their /// scheduling info (i.e. whether they're a parathread or parachain), auction information /// and the auction deposit are switched. - #[weight = (10_000_000, DispatchClass::Operational)] - pub fn swap(origin, other: ParaId) { + #[weight = 0] + fn swap(origin, other: ParaId) { let id = ensure_parachain(::Origin::from(origin))?; if PendingSwap::get(other) == Some(id) { @@ -225,6 +179,45 @@ decl_module! { } } +impl Module { + /// Register a parachain with given code. Must be called by root. + /// Fails if given ID is already used. + pub fn register_parachain( + id: ParaId, + genesis_head: HeadData, + validation_code: ValidationCode, + ) -> DispatchResult { + ensure!(!Paras::contains_key(id), Error::::ParaAlreadyExists); + + let outgoing = >::outgoing_paras(); + + ensure!(outgoing.binary_search(&id).is_err(), Error::::ParaAlreadyExists); + + Paras::insert(id, true); + + let genesis = ParaGenesisArgs { + genesis_head, + validation_code, + parachain: true, + }; + + >::schedule_para_initialize(id, genesis); + + Ok(()) + } + + /// Deregister a parachain with the given ID. Must be called by root. + pub fn deregister_parachain(id: ParaId) -> DispatchResult { + let is_parachain = Paras::take(id).ok_or(Error::::InvalidChainId)?; + + ensure!(is_parachain, Error::::InvalidChainId); + + >::schedule_para_cleanup(id); + + Ok(()) + } +} + #[cfg(test)] mod tests { use super::*; From dd2678272ed8ddf6535b7aef5b98ae897fd69f9c Mon Sep 17 00:00:00 2001 From: Fedor Sakharov Date: Fri, 11 Sep 2020 18:51:50 +0300 Subject: [PATCH 5/7] Remove TODO and origins --- runtime/common/src/paras_registrar.rs | 10 ++-------- runtime/parachains/src/lib.rs | 5 ++--- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/runtime/common/src/paras_registrar.rs b/runtime/common/src/paras_registrar.rs index a4ca96e6ce2c..b0bc25368a17 100644 --- a/runtime/common/src/paras_registrar.rs +++ b/runtime/common/src/paras_registrar.rs @@ -535,7 +535,6 @@ mod tests { System::on_finalize(System::block_number()); } // Session change every 3 blocks. - // TODO: Better way to do this? if (b + 1) % 3 == 0 { Initializer::on_new_session( false, @@ -563,7 +562,6 @@ mod tests { run_to_block(2); assert_ok!(Registrar::register_parachain( - Origin::root(), 2u32.into(), vec![3; 3].into(), vec![3; 3].into(), @@ -585,7 +583,7 @@ mod tests { run_to_block(3); - assert_ok!(Registrar::deregister_parachain(Origin::root(), 2u32.into())); + assert_ok!(Registrar::deregister_parachain(2u32.into())); assert_ok!(Registrar::deregister_parathread( runtime_parachains::Origin::Parachain(8u32.into()).into() @@ -614,7 +612,6 @@ mod tests { )); assert_ok!(Registrar::register_parachain( - Origin::root(), 2u32.into(), vec![1; 3].into(), vec![1; 3].into(), @@ -643,7 +640,6 @@ mod tests { run_to_block(2); assert_ok!(Registrar::register_parachain( - Origin::root(), 1u32.into(), vec![1; 3].into(), vec![1; 3].into(), @@ -651,11 +647,10 @@ mod tests { run_to_block(4); - assert_ok!(Registrar::deregister_parachain(Origin::root(), 1u32.into())); + assert_ok!(Registrar::deregister_parachain(1u32.into())); run_to_block(5); assert!(Registrar::register_parachain( - Origin::root(), 1u32.into(), vec![1; 3].into(), vec![1; 3].into(), @@ -664,7 +659,6 @@ mod tests { run_to_block(6); assert_ok!(Registrar::register_parachain( - Origin::root(), 1u32.into(), vec![1; 3].into(), vec![1; 3].into(), diff --git a/runtime/parachains/src/lib.rs b/runtime/parachains/src/lib.rs index fe0f24834036..2f7f5eb1b87b 100644 --- a/runtime/parachains/src/lib.rs +++ b/runtime/parachains/src/lib.rs @@ -24,7 +24,7 @@ #![cfg_attr(not(feature = "std"), no_std)] use sp_std::result; -use sp_runtime::traits::BadOrigin; +use sp_runtime::{RuntimeDebug, traits::BadOrigin}; use primitives::v1::Id as ParaId; use codec::{Decode, Encode}; @@ -44,8 +44,7 @@ mod util; mod mock; /// Origin for the parachains. -#[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)] -// TODO: #[cfg_attr(feature = "std", derive(Debug))] +#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug)] pub enum Origin { /// It comes from a parachain. Parachain(ParaId), From f0cd6abcc94ecbf87271a0b6befae39df40458a7 Mon Sep 17 00:00:00 2001 From: Fedor Sakharov Date: Tue, 29 Sep 2020 23:21:22 +0300 Subject: [PATCH 6/7] Fix merge --- runtime/common/src/paras_registrar.rs | 7 +++++-- runtime/rococo-v1/src/lib.rs | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/runtime/common/src/paras_registrar.rs b/runtime/common/src/paras_registrar.rs index b0bc25368a17..4c983b6241c5 100644 --- a/runtime/common/src/paras_registrar.rs +++ b/runtime/common/src/paras_registrar.rs @@ -236,7 +236,7 @@ mod tests { impl_outer_origin, impl_outer_dispatch, assert_ok, parameter_types, }; use keyring::Sr25519Keyring; - use runtime_parachains::{initializer, configuration, inclusion, scheduler}; + use runtime_parachains::{initializer, configuration, inclusion, router, scheduler}; use pallet_session::OneSessionHandler; impl_outer_origin! { @@ -294,7 +294,7 @@ mod tests { type MaximumBlockLength = MaximumBlockLength; type AvailableBlockRatio = AvailableBlockRatio; type Version = (); - type ModuleToIndex = (); + type PalletInfo = (); type AccountData = pallet_balances::AccountData; type OnNewAccount = (); type OnKilledAccount = Balances; @@ -318,6 +318,7 @@ mod tests { type Event = (); type ExistentialDeposit = ExistentialDeposit; type AccountStore = System; + type MaxLocks = (); type WeightInfo = (); } @@ -392,6 +393,8 @@ mod tests { type WeightInfo = (); } + impl router::Trait for Test { } + impl pallet_session::historical::Trait for Test { type FullIdentification = pallet_staking::Exposure; type FullIdentificationOf = pallet_staking::ExposureOf; diff --git a/runtime/rococo-v1/src/lib.rs b/runtime/rococo-v1/src/lib.rs index 3852158076fc..56d33916035b 100644 --- a/runtime/rococo-v1/src/lib.rs +++ b/runtime/rococo-v1/src/lib.rs @@ -373,7 +373,7 @@ construct_runtime! { Inclusion: parachains_inclusion::{Module, Call, Storage, Event}, InclusionInherent: parachains_inclusion_inherent::{Module, Call, Storage}, Scheduler: parachains_scheduler::{Module, Call, Storage}, - Paras: parachains_paras::{Module, Call, Storage, Origin}, + Paras: parachains_paras::{Module, Call, Storage}, Initializer: parachains_initializer::{Module, Call, Storage}, Router: parachains_router::{Module, Call, Storage}, From d3cb51cc7ed60b9af09cbb9708141dad8fd5ab8e Mon Sep 17 00:00:00 2001 From: Fedor Sakharov Date: Tue, 29 Sep 2020 23:23:06 +0300 Subject: [PATCH 7/7] Enable or disable parathread registration --- runtime/common/src/paras_registrar.rs | 39 ++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/runtime/common/src/paras_registrar.rs b/runtime/common/src/paras_registrar.rs index 4c983b6241c5..e555a04a5d37 100644 --- a/runtime/common/src/paras_registrar.rs +++ b/runtime/common/src/paras_registrar.rs @@ -24,7 +24,7 @@ use frame_support::{ dispatch::DispatchResult, traits::{Get, Currency, ReservableCurrency}, }; -use frame_system::{self, ensure_signed}; +use frame_system::{self, ensure_root, ensure_signed}; use primitives::v1::{ Id as ParaId, ValidationCode, HeadData, }; @@ -57,6 +57,9 @@ pub trait Trait: paras::Trait { decl_storage! { trait Store for Module as Registrar { + /// Whether parathreads are enabled or not. + ParathreadsRegistrationEnabled: bool; + /// Pending swap operations. PendingSwap: map hasher(twox_64_concat) ParaId => Option; @@ -80,6 +83,8 @@ decl_error! { CodeTooLarge, /// Invalid para head data size. HeadDataTooLarge, + /// Parathreads registration is disabled. + ParathreadsRegistrationDisabled, } } @@ -100,6 +105,8 @@ decl_module! { ) -> DispatchResult { let who = ensure_signed(origin)?; + ensure!(ParathreadsRegistrationEnabled::get(), Error::::ParathreadsRegistrationDisabled); + ensure!(!Paras::contains_key(id), Error::::ParaAlreadyExists); let outgoing = >::outgoing_paras(); @@ -133,6 +140,8 @@ decl_module! { fn deregister_parathread(origin) -> DispatchResult { let id = ensure_parachain(::Origin::from(origin))?; + ensure!(ParathreadsRegistrationEnabled::get(), Error::::ParathreadsRegistrationDisabled); + let is_parachain = Paras::take(id).ok_or(Error::::InvalidChainId)?; ensure!(!is_parachain, Error::::InvalidThreadId); @@ -145,6 +154,25 @@ decl_module! { Ok(()) } + #[weight = 0] + fn enable_parathread_registration(origin) -> DispatchResult { + ensure_root(origin)?; + + ParathreadsRegistrationEnabled::put(true); + + Ok(()) + } + + #[weight = 0] + fn disable_parathread_registration(origin) -> DispatchResult { + ensure_root(origin)?; + + ParathreadsRegistrationEnabled::put(false); + + Ok(()) + } + + /// Swap a parachain with another parachain or parathread. The origin must be a `Parachain`. /// The swap will happen only if there is already an opposite swap pending. If there is not, /// the swap will be stored in the pending swaps map, ready for a later confirmatory swap. @@ -561,7 +589,11 @@ mod tests { #[test] fn register_deregister_chain_works() { new_test_ext().execute_with(|| { + run_to_block(1); + assert_ok!(Registrar::enable_parathread_registration( + Origin::root(), + )); run_to_block(2); assert_ok!(Registrar::register_parachain( @@ -601,6 +633,11 @@ mod tests { #[test] fn swap_handles_funds_correctly() { new_test_ext().execute_with(|| { + run_to_block(1); + + assert_ok!(Registrar::enable_parathread_registration( + Origin::root(), + )); run_to_block(2); let initial_1_balance = Balances::free_balance(1);