// 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 . //! 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_error, ensure, dispatch::DispatchResult, traits::{Get, Currency, ReservableCurrency}, }; 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 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>>; /// The system's currency for parathread payment. type Currency: ReservableCurrency; /// The deposit to be paid to run a parathread. type ParathreadDeposit: Get>; } 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; /// 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, /// Parathreads registration is disabled. ParathreadsRegistrationDisabled, } } 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 = 0] fn register_parathread( origin, id: ParaId, genesis_head: HeadData, validation_code: ValidationCode, ) -> DispatchResult { let who = ensure_signed(origin)?; ensure!(ParathreadsRegistrationEnabled::get(), Error::::ParathreadsRegistrationDisabled); 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(()) } /// 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] 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); let debtor = >::take(id); let _ = ::Currency::unreserve(&debtor, T::ParathreadDeposit::get()); >::schedule_para_cleanup(id); 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. /// /// 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 = 0] 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); } } } } 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::*; 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, router, 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 PalletInfo = (); 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 MaxLocks = (); 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 router::Trait for Test { } 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 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. 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(1); assert_ok!(Registrar::enable_parathread_registration( Origin::root(), )); run_to_block(2); assert_ok!(Registrar::register_parachain( 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(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(1); assert_ok!(Registrar::enable_parathread_registration( Origin::root(), )); 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( 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( 1u32.into(), vec![1; 3].into(), vec![1; 3].into(), )); run_to_block(4); assert_ok!(Registrar::deregister_parachain(1u32.into())); run_to_block(5); assert!(Registrar::register_parachain( 1u32.into(), vec![1; 3].into(), vec![1; 3].into(), ).is_err()); run_to_block(6); assert_ok!(Registrar::register_parachain( 1u32.into(), vec![1; 3].into(), vec![1; 3].into(), )); }); } }