From b1558157cbaa6580d7bf21e02179425f1ab5cc67 Mon Sep 17 00:00:00 2001 From: Gavin Wood <gavin@parity.io> Date: Fri, 11 Oct 2019 08:27:30 +0200 Subject: [PATCH] Introduce Parathreads (runtime) (#341) * Rest of parathread draft implementation, parachain permissioning. * Update Substrate * Update Substrate again * Integrate weight/fee stuff. * Council * Build fixes * More fixes * Minor additions * fix some small errors * Revert "fix some small errors" This reverts commit 4fb52c82adfdaf3af98edfe36b280133bcd4f9d3. * Merge fix. * do_swap -> on_swap * Update depdendency to polkadot-master * Fix more merge problems * Some patching of errors * Fix storage closure * Actually fix storage. It builds! * Tests run... but not successfully. * Add `run_to_block` to get parachains active to start * More `run_to_block` * Fix build * Queue up changes to threads * Move registration test * Fix regsiter/deregister test * Retry queue. * Minor refactor * Refactor to avoid heavy storage items * Make tests pass * remove para on deregister, add events * Remove println * Fix register/deregister parathread test * fix merge * Parathread can be activated test * Test auction * Add `Debtors` storage item I considered putting the debtor information in `ParaInfo`, but it did not make sense to me since this information only applies to parathreads, not `paras` in general. * remove comment code * Some new tests * Fixes for removing threads when scheduled. Tests. * Test progression of threads. * Test that reschedule queuing works properly. * Make test slightly more interesting * whitespace * Swap works properly. * Update locks * Build * Rename can_swap * Add test for funds to be correctly returned after a swap Swap does not seem to have logic which correctly swaps the debtor account to the new parathread. * Make tests consistant * Add check that `PendingSwap` is cleaned up * Update runtime/src/parachains.rs Co-Authored-By: Robert Habermeier <rphmeier@gmail.com> * Update runtime/src/registrar.rs Co-Authored-By: Robert Habermeier <rphmeier@gmail.com> * Some fixes/suggestions from review * Docs * Apply suggestions from code review Co-Authored-By: Robert Habermeier <rphmeier@gmail.com> Co-Authored-By: Shawn Tabrizi <shawntabrizi@gmail.com> * Update network/src/gossip.rs Co-Authored-By: Robert Habermeier <rphmeier@gmail.com> * Rename OnSwap * Add missing `]` * Rejig ordering semantics, making everything a bit slower but correct. * Some Fixes to Parathread Compile (#470) * Some Fixes * Fix queue_upward_messages * Change back to const * Build fixes * Fix tests --- polkadot/Cargo.lock | 2 + polkadot/network/src/gossip.rs | 3 +- polkadot/network/src/tests/validation.rs | 6 +- polkadot/parachain/Cargo.toml | 2 + polkadot/parachain/src/lib.rs | 22 + polkadot/primitives/src/parachain.rs | 65 +- polkadot/runtime/Cargo.toml | 2 + polkadot/runtime/src/crowdfund.rs | 32 +- polkadot/runtime/src/lib.rs | 42 +- polkadot/runtime/src/parachains.rs | 509 +++--- polkadot/runtime/src/registrar.rs | 1368 +++++++++++++++++ polkadot/runtime/src/slots.rs | 113 +- polkadot/runtime/src/testing.rs | 0 polkadot/service/src/chain_spec.rs | 6 +- .../test-parachains/adder/wasm/Cargo.toml | 0 polkadot/validation/src/evaluation.rs | 4 +- polkadot/validation/src/lib.rs | 2 +- 17 files changed, 1886 insertions(+), 292 deletions(-) create mode 100644 polkadot/runtime/src/registrar.rs create mode 100644 polkadot/runtime/src/testing.rs create mode 100644 polkadot/test-parachains/adder/wasm/Cargo.toml diff --git a/polkadot/Cargo.lock b/polkadot/Cargo.lock index 38617b5277f..d70335d03af 100644 --- a/polkadot/Cargo.lock +++ b/polkadot/Cargo.lock @@ -2773,6 +2773,7 @@ dependencies = [ "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", "shared_memory 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", "sr-std 2.0.0 (git+https://github.com/paritytech/substrate?branch=polkadot-master)", + "substrate-primitives 2.0.0 (git+https://github.com/paritytech/substrate?branch=polkadot-master)", "tiny-keccak 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "wasmi 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2805,6 +2806,7 @@ dependencies = [ "libsecp256k1 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", "parity-scale-codec 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "polkadot-parachain 0.6.0", "polkadot-primitives 0.6.0", "rustc-hex 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "safe-mix 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/polkadot/network/src/gossip.rs b/polkadot/network/src/gossip.rs index 1f6c5382c55..873596a7fce 100644 --- a/polkadot/network/src/gossip.rs +++ b/polkadot/network/src/gossip.rs @@ -265,7 +265,8 @@ impl<F, P> ChainContext for (F, P) where let leaf_id = BlockId::Hash(leaf); let active_parachains = api.active_parachains(&leaf_id)?; - for para_id in active_parachains { + // TODO: https://github.com/paritytech/polkadot/issues/467 + for (para_id, _) in active_parachains { if let Some(ingress) = api.ingress(&leaf_id, para_id, None)? { for (_height, _from, queue_root) in ingress.iter() { with_queue_root(queue_root); diff --git a/polkadot/network/src/tests/validation.rs b/polkadot/network/src/tests/validation.rs index cc08d5eda8d..36e98b9bb90 100644 --- a/polkadot/network/src/tests/validation.rs +++ b/polkadot/network/src/tests/validation.rs @@ -30,7 +30,7 @@ use polkadot_primitives::{Block, BlockNumber, Hash, Header, BlockId}; use polkadot_primitives::parachain::{ Id as ParaId, Chain, DutyRoster, ParachainHost, TargetedMessage, ValidatorId, StructuredUnroutedIngress, BlockIngressRoots, Status, - FeeSchedule, HeadData, + FeeSchedule, HeadData, Retriable, CollatorId }; use parking_lot::Mutex; use substrate_client::error::Result as ClientResult; @@ -177,7 +177,7 @@ impl NetworkService for TestNetwork { struct ApiData { validators: Vec<ValidatorId>, duties: Vec<Chain>, - active_parachains: Vec<ParaId>, + active_parachains: Vec<(ParaId, Option<(CollatorId, Retriable)>)>, ingress: HashMap<ParaId, StructuredUnroutedIngress>, } @@ -279,7 +279,7 @@ impl ParachainHost<Block> for RuntimeApi { _: ExecutionContext, _: Option<()>, _: Vec<u8>, - ) -> ClientResult<NativeOrEncoded<Vec<ParaId>>> { + ) -> ClientResult<NativeOrEncoded<Vec<(ParaId, Option<(CollatorId, Retriable)>)>>> { Ok(NativeOrEncoded::Native(self.data.lock().active_parachains.clone())) } diff --git a/polkadot/parachain/Cargo.toml b/polkadot/parachain/Cargo.toml index c916486a9e6..0087d97c9c3 100644 --- a/polkadot/parachain/Cargo.toml +++ b/polkadot/parachain/Cargo.toml @@ -11,6 +11,7 @@ wasmi = { version = "0.4.3", optional = true } derive_more = { version = "0.14", optional = true } serde = { version = "1.0", default-features = false, features = [ "derive" ] } rstd = { package = "sr-std", git = "https://github.com/paritytech/substrate", branch = "polkadot-master", default-features = false } +substrate-primitives = { git = "https://github.com/paritytech/substrate", branch = "polkadot-master", default-features = false } lazy_static = { version = "1.3.0", optional = true } parking_lot = { version = "0.7.1", optional = true } log = { version = "0.4.6", optional = true } @@ -33,6 +34,7 @@ std = [ "serde/std", "rstd/std", "shared_memory", + "substrate-primitives/std", "lazy_static", "parking_lot", "log" diff --git a/polkadot/parachain/src/lib.rs b/polkadot/parachain/src/lib.rs index 8964743beeb..07c5b4166c3 100644 --- a/polkadot/parachain/src/lib.rs +++ b/polkadot/parachain/src/lib.rs @@ -54,6 +54,7 @@ pub mod wasm_api; use rstd::vec::Vec; use codec::{Encode, Decode}; +use substrate_primitives::TypeId; /// Validation parameters for evaluating the parachain validity function. // TODO: balance downloads (https://github.com/paritytech/polkadot/issues/220) @@ -82,6 +83,16 @@ pub struct ValidationResult { #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize, Debug))] pub struct Id(u32); +impl TypeId for Id { + const TYPE_ID: [u8; 4] = *b"para"; +} + +/// Type for determining the active set of parachains. +pub trait ActiveThreads { + /// Return the current ordered set of `Id`s of active parathreads. + fn active_threads() -> Vec<Id>; +} + impl codec::CompactAs for Id { type As = u32; fn encode_as(&self) -> &u32 { @@ -105,11 +116,19 @@ impl From<u32> for Id { fn from(x: u32) -> Self { Id(x) } } +const USER_INDEX_START: u32 = 1000; + +/// The ID of the first user (non-system) parachain. +pub const LOWEST_USER_ID: Id = Id(USER_INDEX_START); + impl Id { /// Convert this Id into its inner representation. pub fn into_inner(self) -> u32 { self.0 } + + /// Returns `true` if this parachain runs with system-level privileges. + pub fn is_system(&self) -> bool { self.0 < USER_INDEX_START } } // TODO: Remove all of this, move sr-primitives::AccountIdConversion to own crate and and use that. @@ -191,6 +210,9 @@ pub enum ParachainDispatchOrigin { /// As the special `Origin::Parachain(ParaId)`. This is good when interacting with parachain- /// aware modules which need to succinctly verify that the origin is a parachain. Parachain, + /// As the simple, superuser `Origin::Root`. This can only be done on specially permissioned + /// parachains. + Root, } impl rstd::convert::TryFrom<u8> for ParachainDispatchOrigin { diff --git a/polkadot/primitives/src/parachain.rs b/polkadot/primitives/src/parachain.rs index 24bcf990816..bfe4f8ca33c 100644 --- a/polkadot/primitives/src/parachain.rs +++ b/polkadot/primitives/src/parachain.rs @@ -30,7 +30,7 @@ use primitives::bytes; use application_crypto::KeyTypeId; pub use polkadot_parachain::{ - Id, AccountIdConversion, ParachainDispatchOrigin, UpwardMessage, + Id, ParachainDispatchOrigin, LOWEST_USER_ID, UpwardMessage, }; /// The key type ID for a collator key. @@ -78,6 +78,67 @@ pub type ValidatorPair = validator_app::Pair; /// so we define it to be the same type as `SessionKey`. In the future it may have different crypto. pub type ValidatorSignature = validator_app::Signature; +/// Retriability for a given active para. +#[derive(Clone, Eq, PartialEq, Encode, Decode)] +#[cfg_attr(feature = "std", derive(Debug))] +pub enum Retriable { + /// Ineligible for retry. This means it's either a parachain which is always scheduled anyway or + /// has been removed/swapped. + Never, + /// Eligible for retry; the associated value is the number of retries that the para already had. + WithRetries(u32), +} + +/// Type determining the active set of parachains in current block. +pub trait ActiveParas { + /// Return the active set of parachains in current block. This attempts to keep any IDs in the + /// same place between sequential blocks. It is therefore unordered. The second item in the + /// tuple is the required collator ID, if any. If `Some`, then it is invalid to include any + /// other collator's block. + /// + /// NOTE: The initial implementation simply concatenates the (ordered) set of (permanent) + /// parachain IDs with the (unordered) set of parathread IDs selected for this block. + fn active_paras() -> Vec<(Id, Option<(CollatorId, Retriable)>)>; +} + +/// Description of how often/when this parachain is scheduled for progression. +#[derive(Encode, Decode, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "std", derive(Debug))] +pub enum Scheduling { + /// Scheduled every block. + Always, + /// Scheduled dynamically (i.e. a parathread). + Dynamic, +} + +/// Information regarding a deployed parachain/thread. +#[derive(Encode, Decode, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "std", derive(Debug))] +pub struct Info { + /// Scheduling info. + pub scheduling: Scheduling, +} + +/// An `Info` value for a standard leased parachain. +pub const PARACHAIN_INFO: Info = Info { + scheduling: Scheduling::Always, +}; + +/// Auxilliary for when there's an attempt to swapped two parachains/parathreads. +pub trait SwapAux { + /// Result describing whether it is possible to swap two parachains. Doesn't mutate state. + fn ensure_can_swap(one: Id, other: Id) -> Result<(), &'static str>; + + /// Updates any needed state/references to enact a logical swap of two parachains. Identity, + /// code and head_data remain equivalent for all parachains/threads, however other properties + /// such as leases, deposits held and thread/chain nature are swapped. + /// + /// May only be called on a state that `ensure_can_swap` has previously returned `Ok` for: if this is + /// not the case, the result is undefined. May only return an error if `ensure_can_swap` also returns + /// an error. + fn on_swap(one: Id, other: Id) -> Result<(), &'static str>; +} + /// Identifier for a chain, either one of a number of parachains or the relay chain. #[derive(Copy, Clone, PartialEq, Encode, Decode)] #[cfg_attr(feature = "std", derive(Debug))] @@ -432,7 +493,7 @@ substrate_client::decl_runtime_apis! { /// Get the current duty roster. fn duty_roster() -> DutyRoster; /// Get the currently active parachains. - fn active_parachains() -> Vec<Id>; + fn active_parachains() -> Vec<(Id, Option<(CollatorId, Retriable)>)>; /// Get the given parachain's status. fn parachain_status(id: Id) -> Option<Status>; /// Get the given parachain's head code blob. diff --git a/polkadot/runtime/Cargo.toml b/polkadot/runtime/Cargo.toml index 24a773cea77..b290bd42731 100644 --- a/polkadot/runtime/Cargo.toml +++ b/polkadot/runtime/Cargo.toml @@ -53,6 +53,7 @@ timestamp = { package = "srml-timestamp", git = "https://github.com/paritytech/s treasury = { package = "srml-treasury", git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-master" } primitives = { package = "polkadot-primitives", path = "../primitives", default-features = false } +polkadot-parachain = { path = "../parachain", default-features = false } [dev-dependencies] hex-literal = "0.2.0" @@ -79,6 +80,7 @@ std = [ "codec/std", "inherents/std", "substrate-primitives/std", + "polkadot-parachain/std", "client/std", "offchain-primitives/std", "rstd/std", diff --git a/polkadot/runtime/src/crowdfund.rs b/polkadot/runtime/src/crowdfund.rs index 881dee0222c..f9132f73de1 100644 --- a/polkadot/runtime/src/crowdfund.rs +++ b/polkadot/runtime/src/crowdfund.rs @@ -77,14 +77,16 @@ use sr_primitives::{ModuleId, weights::SimpleDispatchInfo, use crate::slots; use codec::{Encode, Decode}; use rstd::vec::Vec; -use crate::parachains::ParachainRegistrar; use substrate_primitives::storage::well_known_keys::CHILD_STORAGE_KEY_PREFIX; +use primitives::parachain::Id as ParaId; const MODULE_ID: ModuleId = ModuleId(*b"py/cfund"); -pub type BalanceOf<T> = <<T as slots::Trait>::Currency as Currency<<T as system::Trait>::AccountId>>::Balance; -pub type NegativeImbalanceOf<T> = <<T as slots::Trait>::Currency as Currency<<T as system::Trait>::AccountId>>::NegativeImbalance; -pub type ParaIdOf<T> = <<T as slots::Trait>::Parachains as ParachainRegistrar<<T as system::Trait>::AccountId>>::ParaId; +pub type BalanceOf<T> = + <<T as slots::Trait>::Currency as Currency<<T as system::Trait>::AccountId>>::Balance; +#[allow(dead_code)] +pub type NegativeImbalanceOf<T> = + <<T as slots::Trait>::Currency as Currency<<T as system::Trait>::AccountId>>::NegativeImbalance; pub trait Trait: slots::Trait { type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>; @@ -117,7 +119,7 @@ pub enum LastContribution<BlockNumber> { #[derive(Encode, Decode, Clone, PartialEq, Eq)] #[cfg_attr(feature = "std", derive(Debug))] -pub struct FundInfo<AccountId, Balance, Hash, BlockNumber, ParaId> { +pub struct FundInfo<AccountId, Balance, Hash, BlockNumber> { /// The parachain that this fund has funded, if there is one. As long as this is `Some`, then /// the funds may not be withdrawn and the fund cannot be dissolved. parachain: Option<ParaId>, @@ -154,7 +156,7 @@ decl_storage! { trait Store for Module<T: Trait> as Example { /// Info on all of the funds. Funds get(funds): - map FundIndex => Option<FundInfo<T::AccountId, BalanceOf<T>, T::Hash, T::BlockNumber, ParaIdOf<T>>>; + map FundIndex => Option<FundInfo<T::AccountId, BalanceOf<T>, T::Hash, T::BlockNumber>>; /// The total number of funds that have so far been allocated. FundCount get(fund_count): FundIndex; @@ -172,7 +174,6 @@ decl_event! { pub enum Event<T> where <T as system::Trait>::AccountId, Balance = BalanceOf<T>, - ParaId = ParaIdOf<T>, { Created(FundIndex), Contributed(AccountId, FundIndex, Balance), @@ -321,7 +322,7 @@ decl_module! { /// - `para_id` is the parachain index that this fund won. fn onboard(origin, #[compact] index: FundIndex, - #[compact] para_id: ParaIdOf<T> + #[compact] para_id: ParaId ) { let _ = ensure_signed(origin)?; @@ -503,13 +504,14 @@ mod tests { use srml_support::{impl_outer_origin, assert_ok, assert_noop, parameter_types}; use sr_io::with_externalities; use substrate_primitives::{H256, Blake2Hasher}; - use primitives::parachain::Id as ParaId; + use primitives::parachain::Info as ParaInfo; // The testing primitives are very useful for avoiding having to work with signatures // or public keys. `u64` is used as the `AccountId` and no `Signature`s are requried. use sr_primitives::{ Perbill, Permill, testing::Header, traits::{BlakeTwo256, OnInitialize, OnFinalize, IdentityLookup, ConvertInto}, }; + use crate::registrar::Registrar; impl_outer_origin! { pub enum Origin for Test {} @@ -595,16 +597,16 @@ mod tests { } pub struct TestParachains; - impl ParachainRegistrar<u64> for TestParachains { - type ParaId = ParaId; - fn new_id() -> Self::ParaId { + impl Registrar<u64> for TestParachains { + fn new_id() -> ParaId { PARACHAIN_COUNT.with(|p| { *p.borrow_mut() += 1; (*p.borrow() - 1).into() }) } - fn register_parachain( - id: Self::ParaId, + fn register_para( + id: ParaId, + _info: ParaInfo, code: Vec<u8>, initial_head_data: Vec<u8> ) -> Result<(), &'static str> { @@ -616,7 +618,7 @@ mod tests { Ok(()) }) } - fn deregister_parachain(id: Self::ParaId) -> Result<(), &'static str> { + fn deregister_para(id: ParaId) -> Result<(), &'static str> { PARACHAINS.with(|p| { if !p.borrow().contains_key(&id.into_inner()) { panic!("ID doesn't exist") diff --git a/polkadot/runtime/src/lib.rs b/polkadot/runtime/src/lib.rs index 69224ae5311..5b9f11fd6f1 100644 --- a/polkadot/runtime/src/lib.rs +++ b/polkadot/runtime/src/lib.rs @@ -24,15 +24,16 @@ mod attestations; mod claims; mod parachains; mod slot_range; +mod registrar; mod slots; mod crowdfund; use rstd::prelude::*; -use codec::{Encode, Decode}; use substrate_primitives::u32_trait::{_1, _2, _3, _4}; +use codec::{Encode, Decode}; use primitives::{ AccountId, AccountIndex, Balance, BlockNumber, Hash, Nonce, Signature, Moment, - parachain, ValidityError, + parachain::{self, ActiveParas}, ValidityError, }; use client::{ block_builder::api::{self as block_builder_api, InherentData, CheckInherentsResult}, @@ -187,7 +188,7 @@ impl indices::Trait for Runtime { } parameter_types! { - pub const ExistentialDeposit: Balance = 10 * CENTS; + pub const ExistentialDeposit: Balance = 100 * CENTS; pub const TransferFee: Balance = 1 * CENTS; pub const CreationFee: Balance = 1 * CENTS; pub const TransactionBaseFee: Balance = 1 * CENTS; @@ -221,7 +222,6 @@ impl balances::Trait for Runtime { parameter_types! { pub const MinimumPeriod: u64 = SLOT_DURATION / 2; } - impl timestamp::Trait for Runtime { type Moment = u64; type OnTimestampSet = Babe; @@ -275,15 +275,15 @@ impl session::Trait for Runtime { type ShouldEndSession = Babe; type Event = Event; type Keys = SessionKeys; - type SelectInitialValidators = Staking; type ValidatorId = AccountId; type ValidatorIdOf = staking::StashOf<Self>; + type SelectInitialValidators = Staking; type DisabledValidatorsThreshold = DisabledValidatorsThreshold; } impl session::historical::Trait for Runtime { type FullIdentification = staking::Exposure<AccountId, Balance>; - type FullIdentificationOf = staking::ExposureOf<Self>; + type FullIdentificationOf = staking::ExposureOf<Runtime>; } srml_staking_reward_curve::build! { @@ -480,6 +480,24 @@ impl parachains::Trait for Runtime { type Origin = Origin; type Call = Call; type ParachainCurrency = Balances; + type ActiveParachains = Registrar; + type Registrar = Registrar; +} + +parameter_types! { + pub const ParathreadDeposit: Balance = 500 * DOLLARS; + pub const QueueSize: usize = 2; + pub const MaxRetries: u32 = 3; +} + +impl registrar::Trait for Runtime { + type Event = Event; + type Origin = Origin; + type Currency = Balances; + type ParathreadDeposit = ParathreadDeposit; + type SwapAux = Slots; + type QueueSize = QueueSize; + type MaxRetries = MaxRetries; } parameter_types!{ @@ -489,8 +507,8 @@ parameter_types!{ impl slots::Trait for Runtime { type Event = Event; - type Currency = balances::Module<Self>; - type Parachains = parachains::Module<Self>; + type Currency = Balances; + type Parachains = Registrar; type LeasePeriod = LeasePeriod; type EndingPeriod = EndingPeriod; } @@ -553,9 +571,10 @@ construct_runtime!( // Parachains stuff; slots are disabled (no auctions initially). The rest are safe as they // have no public dispatchables. - Parachains: parachains::{Module, Call, Storage, Config<T>, Inherent, Origin}, + Parachains: parachains::{Module, Call, Storage, Config, Inherent, Origin}, Attestations: attestations::{Module, Call, Storage}, Slots: slots::{Module, Call, Storage, Event<T>}, + Registrar: registrar::{Module, Call, Storage, Event, Config<T>}, // Sudo. Usable initially. // RELEASE: remove this for release build. @@ -583,6 +602,7 @@ pub type SignedExtra = ( system::CheckNonce<Runtime>, system::CheckWeight<Runtime>, balances::TakeFees<Runtime>, + registrar::LimitParathreadCommits<Runtime> ); /// Unchecked extrinsic type as expected by this runtime. pub type UncheckedExtrinsic = generic::UncheckedExtrinsic<Address, Call, Signature, SignedExtra>; @@ -653,8 +673,8 @@ impl_runtime_apis! { fn duty_roster() -> parachain::DutyRoster { Parachains::calculate_duty_roster().0 } - fn active_parachains() -> Vec<parachain::Id> { - Parachains::active_parachains() + fn active_parachains() -> Vec<(parachain::Id, Option<(parachain::CollatorId, parachain::Retriable)>)> { + Registrar::active_paras() } fn parachain_status(id: parachain::Id) -> Option<parachain::Status> { Parachains::parachain_status(&id) diff --git a/polkadot/runtime/src/parachains.rs b/polkadot/runtime/src/parachains.rs index a31b9e6ea93..b45021e24af 100644 --- a/polkadot/runtime/src/parachains.rs +++ b/polkadot/runtime/src/parachains.rs @@ -17,17 +17,20 @@ //! Main parachains logic. For now this is just the determination of which validators do what. use rstd::prelude::*; +use rstd::result; use rstd::collections::btree_map::BTreeMap; -use codec::{Encode, Decode, HasCompact}; -use srml_support::{decl_storage, decl_module, fail, ensure}; +use codec::{Encode, Decode}; +use srml_support::{decl_storage, decl_module, ensure}; use sr_primitives::traits::{ - Hash as HashT, BlakeTwo256, Member, CheckedConversion, Saturating, One, Zero, Dispatchable, + Hash as HashT, BlakeTwo256, Saturating, One, Zero, Dispatchable, + AccountIdConversion, }; use sr_primitives::weights::SimpleDispatchInfo; use primitives::{Hash, Balance, parachain::{ - self, Id as ParaId, Chain, DutyRoster, AttestedCandidate, Statement, AccountIdConversion, - ParachainDispatchOrigin, UpwardMessage, BlockIngressRoots, ValidatorId + self, Id as ParaId, Chain, DutyRoster, AttestedCandidate, Statement, + ParachainDispatchOrigin, UpwardMessage, BlockIngressRoots, ValidatorId, ActiveParas, CollatorId, + Retriable }}; use {system, session}; use srml_support::{ @@ -36,11 +39,9 @@ use srml_support::{ use inherents::{ProvideInherent, InherentData, RuntimeString, MakeFatalError, InherentIdentifier}; -#[cfg(any(feature = "std", test))] -use rstd::marker::PhantomData; - -use system::{ensure_none, ensure_root}; +use system::ensure_none; use crate::attestations::{self, IncludedBlocks}; +use crate::registrar::Registrar; // ranges for iteration of general block number don't work, so this // is a utility to get around that. @@ -68,75 +69,6 @@ fn number_range<N>(low: N, high: N) -> BlockNumberRange<N> { BlockNumberRange { low, high } } -/// Parachain registration API. -pub trait ParachainRegistrar<AccountId> { - /// An identifier for a parachain. - type ParaId: Member + Parameter + Default + AccountIdConversion<AccountId> + Copy + HasCompact; - - /// Create a new unique parachain identity for later registration. - fn new_id() -> Self::ParaId; - - /// Register a parachain with given `code` and `initial_head_data`. `id` must not yet be registered or it will - /// result in a error. - fn register_parachain(id: Self::ParaId, code: Vec<u8>, initial_head_data: Vec<u8>) -> Result; - - /// Deregister a parachain with given `id`. If `id` is not currently registered, an error is returned. - fn deregister_parachain(id: Self::ParaId) -> Result; -} - -impl<T: Trait> ParachainRegistrar<T::AccountId> for Module<T> { - type ParaId = ParaId; - fn new_id() -> ParaId { - <NextFreeId>::mutate(|n| { let r = *n; *n = ParaId::from(u32::from(*n) + 1); r }) - } - fn register_parachain(id: ParaId, code: Vec<u8>, initial_head_data: Vec<u8>) -> Result { - let mut parachains = Self::active_parachains(); - match parachains.binary_search(&id) { - Ok(_) => fail!("Parachain already exists"), - Err(idx) => parachains.insert(idx, id), - } - - <Code>::insert(id, code); - <Parachains>::put(parachains); - <Heads>::insert(id, initial_head_data); - - // Because there are no ordering guarantees that inherents - // are applied before regular transactions, a parachain candidate could - // be registered before the `UpdateHeads` inherent is processed. If so, messages - // could be sent to a parachain in the block it is registered. - <Watermarks<T>>::insert(id, <system::Module<T>>::block_number().saturating_sub(One::one())); - - Ok(()) - } - fn deregister_parachain(id: ParaId) -> Result { - let mut parachains = Self::active_parachains(); - match parachains.binary_search(&id) { - Ok(idx) => { parachains.remove(idx); } - Err(_) => return Ok(()), - } - - <Code>::remove(id); - <Heads>::remove(id); - - let watermark = <Watermarks<T>>::take(id); - - // clear all routing entries _to_. But not those _from_. - if let Some(watermark) = watermark { - let now = <system::Module<T>>::block_number(); - - // iterate over all blocks between watermark and now + 1 (since messages might - // have already been sent to `id` in this block. - for unrouted_block in number_range(watermark, now).map(|n| n.saturating_add(One::one())) { - <UnroutedIngress<T>>::remove(&(unrouted_block, id)); - } - } - - <Parachains>::put(parachains); - - Ok(()) - } -} - // wrapper trait because an associated type of `Currency<Self::AccountId,Balance=Balance>` // doesn't work.` pub trait ParachainCurrency<AccountId> { @@ -186,6 +118,12 @@ pub trait Trait: attestations::Trait { /// Some way of interacting with balances for fees. type ParachainCurrency: ParachainCurrency<Self::AccountId>; + + /// Means to determine what the current set of active parachains are. + type ActiveParachains: ActiveParas; + + /// The way that we are able to register parachains. + type Registrar: Registrar<Self::AccountId>; } /// Origin for the parachains module. @@ -213,8 +151,6 @@ decl_storage! { trait Store for Module<T: Trait> as Parachains { /// All authorities' keys at the moment. pub Authorities get(authorities) config(authorities): Vec<ValidatorId>; - /// Vector of all parachain IDs. - pub Parachains get(active_parachains): Vec<ParaId>; /// The parachains registered at present. pub Code get(parachain_code): map ParaId => Option<Vec<u8>>; /// The heads of the parachains registered at present. @@ -237,35 +173,14 @@ decl_storage! { /// decoding when checking receipt validity. First item in tuple is the count of messages /// second if the total length (in bytes) of the message payloads. pub RelayDispatchQueueSize: map ParaId => (u32, u32); + /// The ordered list of ParaIds that have a `RelayDispatchQueue` entry. + NeedsDispatch: Vec<ParaId>; - /// Did the parachain heads get updated in this block? - DidUpdate: bool; - - /// The next unused ParaId value. - NextFreeId: ParaId; - } - add_extra_genesis { - config(parachains): Vec<(ParaId, Vec<u8>, Vec<u8>)>; - config(_phdata): PhantomData<T>; - build(build::<T>); - } -} - -#[cfg(feature = "std")] -fn build<T: Trait>(config: &GenesisConfig<T>) { - let mut p = config.parachains.clone(); - p.sort_unstable_by_key(|&(ref id, _, _)| *id); - p.dedup_by_key(|&mut (ref id, _, _)| *id); - - let only_ids: Vec<ParaId> = p.iter().map(|&(ref id, _, _)| id).cloned().collect(); - - Parachains::put(&only_ids); - - for (id, code, genesis) in p { - // no ingress -- a chain cannot be routed to until it is live. - Code::insert(&id, &code); - Heads::insert(&id, &genesis); - <Watermarks<T>>::insert(&id, &T::BlockNumber::zero()); + /// Some if the parachain heads get updated in this block, along with the parachain IDs that + /// did update. Ordered in the same way as `registrar::Active` (i.e. by ParaId). + /// + /// None if not yet updated. + pub DidUpdate: Option<Vec<ParaId>>; } } @@ -274,32 +189,38 @@ decl_module! { pub struct Module<T: Trait> for enum Call where origin: <T as system::Trait>::Origin { /// Provide candidate receipts for parachains, in ascending order by id. #[weight = SimpleDispatchInfo::FixedNormal(1_000_000)] - fn set_heads(origin, heads: Vec<AttestedCandidate>) -> Result { + pub fn set_heads(origin, heads: Vec<AttestedCandidate>) -> Result { ensure_none(origin)?; ensure!(!<DidUpdate>::exists(), "Parachain heads must be updated only once in the block"); let active_parachains = Self::active_parachains(); + let parachain_count = active_parachains.len(); ensure!(heads.len() <= parachain_count, "Too many parachain candidates"); + let mut proceeded = Vec::with_capacity(heads.len()); + if !active_parachains.is_empty() { // perform integrity checks before writing to storage. { let mut last_id = None; + let mut iter = active_parachains.iter(); for head in &heads { let id = head.parachain_index(); // proposed heads must be ascending order by parachain ID without duplicate. ensure!( last_id.as_ref().map_or(true, |x| x < &id), - "Parachain candidates out of order by ID" + "candidate out of order" ); // must be unknown since active parachains are always sorted. - ensure!( - iter.find(|x| x == &&id).is_some(), - "Submitted candidate for unregistered or out-of-order parachain {}" - ); + let (_, maybe_required_collator) = iter.find(|para| para.0 == id) + .ok_or("candidate for unregistered parachain {}")?; + + if let Some((required_collator, _)) = maybe_required_collator { + ensure!(required_collator == &head.candidate.collator, "invalid collator"); + } Self::check_upward_messages( id, @@ -309,7 +230,9 @@ decl_module! { )?; Self::check_egress_queue_roots(&head, &active_parachains)?; - last_id = Some(head.parachain_index()); + let id = head.parachain_index(); + proceeded.push(id); + last_id = Some(id); } } @@ -324,36 +247,23 @@ decl_module! { ); Self::dispatch_upward_messages( - current_number, - &active_parachains, MAX_QUEUE_COUNT, WATERMARK_QUEUE_SIZE, Self::dispatch_message, ); } - <DidUpdate>::put(true); + DidUpdate::put(proceeded); Ok(()) } - /// Register a parachain with given code. - /// Fails if given ID is already used. - #[weight = SimpleDispatchInfo::FixedOperational(5_000_000)] - pub fn register_parachain(origin, id: ParaId, code: Vec<u8>, initial_head_data: Vec<u8>) -> Result { - ensure_root(origin)?; - <Self as ParachainRegistrar<T::AccountId>>::register_parachain(id, code, initial_head_data) + fn on_initialize() { + <Self as Store>::DidUpdate::kill(); } - /// Deregister a parachain with given id - #[weight = SimpleDispatchInfo::FixedOperational(10_000)] - pub fn deregister_parachain(origin, id: ParaId) -> Result { - ensure_root(origin)?; - <Self as ParachainRegistrar<T::AccountId>>::deregister_parachain(id) - } - - fn on_finalize(_n: T::BlockNumber) { - assert!(<Self as Store>::DidUpdate::take(), "Parachain heads must be updated once in the block"); + fn on_finalize() { + assert!(<Self as Store>::DidUpdate::exists(), "Parachain heads must be updated once in the block"); } } } @@ -369,6 +279,42 @@ fn localized_payload(statement: Statement, parent_hash: ::primitives::Hash) -> V } impl<T: Trait> Module<T> { + /// Initialize the state of a new parachain/parathread. + pub fn initialize_para( + id: ParaId, + code: Vec<u8>, + initial_head_data: Vec<u8>, + ) { + <Code>::insert(id, code); + <Heads>::insert(id, initial_head_data); + + // Because there are no ordering guarantees that inherents + // are applied before regular transactions, a parachain candidate could + // be registered before the `UpdateHeads` inherent is processed. If so, messages + // could be sent to a parachain in the block it is registered. + <Watermarks<T>>::insert(id, <system::Module<T>>::block_number().saturating_sub(One::one())); + } + + pub fn cleanup_para( + id: ParaId, + ) { + <Code>::remove(id); + <Heads>::remove(id); + + let watermark = <Watermarks<T>>::take(id); + + // clear all routing entries _to_. But not those _from_. + if let Some(watermark) = watermark { + let now = <system::Module<T>>::block_number(); + + // iterate over all blocks between watermark and now + 1 (since messages might + // have already been sent to `id` in this block. + for unrouted_block in number_range(watermark, now).map(|n| n.saturating_add(One::one())) { + <UnroutedIngress<T>>::remove(&(unrouted_block, id)); + } + } + } + /// Dispatch some messages from a parachain. fn dispatch_message( id: ParaId, @@ -381,6 +327,8 @@ impl<T: Trait> Module<T> { system::RawOrigin::Signed(id.into_account()).into(), ParachainDispatchOrigin::Parachain => Origin::Parachain(id).into(), + ParachainDispatchOrigin::Root => + system::RawOrigin::Root.into(), }; let _ok = message_call.dispatch(origin).is_ok(); // Not much to do with the result as it is. It's up to the parachain to ensure that the @@ -415,6 +363,11 @@ impl<T: Trait> Module<T> { ), "Messages added when queue full" ); + if !id.is_system() { + for m in upward_messages.iter() { + ensure!(m.origin != ParachainDispatchOrigin::Root, "bad message origin"); + } + } } Ok(()) } @@ -431,6 +384,10 @@ impl<T: Trait> Module<T> { let mut ingress_update = BTreeMap::new(); + // we sort them in order to provide a fast lookup to ensure we can avoid duplicates in the + // needs_dispatch queue. + let mut ordered_needs_dispatch = NeedsDispatch::get(); + for head in heads.iter() { let id = head.parachain_index(); <Heads>::insert(id, &head.candidate.head_data.0); @@ -452,9 +409,15 @@ impl<T: Trait> Module<T> { } // Queue up upwards messages (from parachains to relay chain). - Self::queue_upward_messages(id, &head.candidate.upward_messages); + Self::queue_upward_messages( + id, + &head.candidate.upward_messages, + &mut ordered_needs_dispatch, + ); } + NeedsDispatch::put(ordered_needs_dispatch); + // apply the ingress update. for (to, ingress_roots) in ingress_update { <UnroutedIngress<T>>::insert((now, to), ingress_roots); @@ -462,36 +425,46 @@ impl<T: Trait> Module<T> { } /// Place any new upward messages into our queue for later dispatch. - fn queue_upward_messages(id: ParaId, upward_messages: &[UpwardMessage]) { + /// + /// `ordered_needs_dispatch` is mutated to ensure it reflects the new value of + /// `RelayDispatchQueueSize`. It is up to the caller to guarantee that it gets written into + /// storage after this call. + fn queue_upward_messages( + id: ParaId, + upward_messages: &[UpwardMessage], + ordered_needs_dispatch: &mut Vec<ParaId>, + ) { if !upward_messages.is_empty() { - <RelayDispatchQueueSize>::mutate(id, |&mut(ref mut count, ref mut len)| { + RelayDispatchQueueSize::mutate(id, |&mut(ref mut count, ref mut len)| { *count += upward_messages.len() as u32; *len += upward_messages.iter() .fold(0, |a, x| a + x.data.len()) as u32; }); // Should never be able to fail assuming our state is uncorrupted, but best not // to panic, even if it does. - let _ = <RelayDispatchQueue>::append(id, upward_messages); + let _ = RelayDispatchQueue::append(id, upward_messages); + if let Err(i) = ordered_needs_dispatch.binary_search(&id) { + // same. + ordered_needs_dispatch.insert(i, id); + } else { + sr_primitives::print("ordered_needs_dispatch contains id?!"); + } } } - /// Simple round-robin dispatcher, using block number modulo parachain count - /// to decide which takes precedence and proceeding from there. + /// Simple FIFO dispatcher. fn dispatch_upward_messages( - now: T::BlockNumber, - active_parachains: &[ParaId], max_queue_count: usize, watermark_queue_size: usize, mut dispatch_message: impl FnMut(ParaId, ParachainDispatchOrigin, &[u8]), ) { - let para_count = active_parachains.len(); - let offset = (now % T::BlockNumber::from(para_count as u32)) - .checked_into::<usize>() - .expect("value is modulo a usize value; qed"); - + let queueds = NeedsDispatch::get(); + let mut drained_count = 0usize; let mut dispatched_count = 0usize; let mut dispatched_size = 0usize; - for id in active_parachains.iter().cycle().skip(offset).take(para_count) { + for id in queueds.iter() { + drained_count += 1; + let (count, size) = <RelayDispatchQueueSize>::get(id); let count = count as usize; let size = size as usize; @@ -501,8 +474,8 @@ impl<T: Trait> Module<T> { ) { if count > 0 { // still dispatching messages... - <RelayDispatchQueueSize>::remove(id); - let messages = <RelayDispatchQueue>::take(id); + RelayDispatchQueueSize::remove(id); + let messages = RelayDispatchQueue::take(id); for UpwardMessage { origin, data } in messages.into_iter() { dispatch_message(*id, origin, &data); } @@ -516,6 +489,7 @@ impl<T: Trait> Module<T> { } } } + NeedsDispatch::put(&queueds[drained_count..]); } /// Calculate the current block's duty roster using system's random seed. @@ -523,19 +497,24 @@ impl<T: Trait> Module<T> { pub fn calculate_duty_roster() -> (DutyRoster, [u8; 32]) { let parachains = Self::active_parachains(); let parachain_count = parachains.len(); + // TODO: use decode length. substrate #2794 let validator_count = Self::authorities().len(); - let validators_per_parachain = if parachain_count != 0 { (validator_count - 1) / parachain_count } else { 0 }; + let validators_per_parachain = + if parachain_count == 0 { + 0 + } else { + (validator_count - 1) / parachain_count + }; let mut roles_val = (0..validator_count).map(|i| match i { i if i < parachain_count * validators_per_parachain => { let idx = i / validators_per_parachain; - Chain::Parachain(parachains[idx].clone()) + Chain::Parachain(parachains[idx].0.clone()) } _ => Chain::Relay, }).collect::<Vec<_>>(); - let mut seed = { let phrase = b"validator_role_pairs"; let seed = randomness_collective_flip::Module::<T>::random(&phrase[..]); @@ -621,9 +600,17 @@ impl<T: Trait> Module<T> { }) } - fn check_egress_queue_roots(head: &AttestedCandidate, active_parachains: &[ParaId]) -> Result { + /// Get the currently active set of parachains. + pub fn active_parachains() -> Vec<(ParaId, Option<(CollatorId, Retriable)>)> { + T::ActiveParachains::active_paras() + } + + fn check_egress_queue_roots( + head: &AttestedCandidate, + active_parachains: &[(ParaId, Option<(CollatorId, Retriable)>)] + ) -> Result { let mut last_egress_id = None; - let mut iter = active_parachains.iter(); + let mut iter = active_parachains.iter().map(|x| x.0); for (egress_para_id, root) in &head.candidate.egress_queue_roots { // egress routes should be ascending order by parachain ID without duplicate. ensure!( @@ -645,7 +632,7 @@ impl<T: Trait> Module<T> { // can't route to a parachain which doesn't exist ensure!( - iter.find(|x| x == &egress_para_id).is_some(), + iter.find(|x| x == egress_para_id).is_some(), "Routing to non-existent parachain" ); @@ -656,8 +643,10 @@ impl<T: Trait> Module<T> { // check the attestations on these candidates. The candidates should have been checked // that each candidates' chain ID is valid. - fn check_candidates(attested_candidates: &[AttestedCandidate], active_parachains: &[ParaId]) - -> rstd::result::Result<IncludedBlocks<T>, &'static str> + fn check_candidates( + attested_candidates: &[AttestedCandidate], + active_parachains: &[(ParaId, Option<(CollatorId, Retriable)>)] + ) -> rstd::result::Result<IncludedBlocks<T>, &'static str> { use primitives::parachain::ValidityAttestation; use sr_primitives::traits::AppVerify; @@ -815,7 +804,7 @@ impl<T: Trait> Module<T> { actual_number: <system::Module<T>>::block_number(), session: <session::Module<T>>::current_index(), random_seed, - active_parachains: active_parachains.to_vec(), + active_parachains: active_parachains.iter().map(|x| x.0).collect(), para_blocks: para_block_hashes, }) } @@ -877,6 +866,17 @@ impl<T: Trait> ProvideInherent for Module<T> { } } +/// Ensure that the origin `o` represents a parachain. +/// Returns `Ok` with the parachain ID that effected the extrinsic or an `Err` otherwise. +pub fn ensure_parachain<OuterOrigin>(o: OuterOrigin) -> result::Result<ParaId, &'static str> + where OuterOrigin: Into<result::Result<Origin, OuterOrigin>> +{ + match o.into() { + Ok(Origin::Parachain(id)) => Ok(id), + _ => Err("bad origin: expected to be a parachain origin"), + } +} + #[cfg(test)] mod tests { use super::*; @@ -887,12 +887,12 @@ mod tests { use substrate_trie::NodeCodec; use sr_primitives::{ Perbill, - traits::{BlakeTwo256, IdentityLookup, ConvertInto, OnInitialize}, + traits::{BlakeTwo256, IdentityLookup, ConvertInto, OnInitialize, OnFinalize}, testing::{UintAuthorityId, Header}, curve::PiecewiseLinear, }; use primitives::{ - parachain::{CandidateReceipt, HeadData, ValidityAttestation, ValidatorId}, + parachain::{CandidateReceipt, HeadData, ValidityAttestation, ValidatorId, Info as ParaInfo, Scheduling}, BlockNumber, }; use crate::constants::time::*; @@ -901,6 +901,8 @@ mod tests { impl_outer_origin, impl_outer_dispatch, assert_ok, assert_err, parameter_types, }; use crate::parachains; + use crate::registrar; + use crate::slots; impl_outer_origin! { pub enum Origin for Test { @@ -1048,15 +1050,47 @@ mod tests { type RewardAttestation = (); } + parameter_types!{ + pub const LeasePeriod: u64 = 10; + pub const EndingPeriod: u64 = 3; + } + + impl slots::Trait for Test { + type Event = (); + type Currency = balances::Module<Test>; + type Parachains = registrar::Module<Test>; + type EndingPeriod = EndingPeriod; + type LeasePeriod = LeasePeriod; + } + + parameter_types! { + pub const ParathreadDeposit: Balance = 10; + pub const QueueSize: usize = 2; + pub const MaxRetries: u32 = 3; + } + + impl registrar::Trait for Test { + type Event = (); + type Origin = Origin; + type Currency = balances::Module<Test>; + type ParathreadDeposit = ParathreadDeposit; + type SwapAux = slots::Module<Test>; + type QueueSize = QueueSize; + type MaxRetries = MaxRetries; + } + impl Trait for Test { type Origin = Origin; type Call = Call; type ParachainCurrency = balances::Module<Test>; + type ActiveParachains = registrar::Module<Test>; + type Registrar = registrar::Module<Test>; } type Parachains = Module<Test>; type System = system::Module<Test>; type RandomnessCollectiveFlip = randomness_collective_flip::Module<Test>; + type Registrar = registrar::Module<Test>; fn new_test_ext(parachains: Vec<(ParaId, Vec<u8>, Vec<u8>)>) -> TestExternalities<Blake2Hasher> { use staking::StakerStatus; @@ -1096,9 +1130,12 @@ mod tests { let balances: Vec<_> = (0..authority_keys.len()).map(|i| (i as u64, 10_000_000)).collect(); - GenesisConfig::<Test> { - parachains, + GenesisConfig { authorities: authorities.clone(), + }.assimilate_storage(&mut t).unwrap(); + + registrar::GenesisConfig::<Test> { + parachains, _phdata: Default::default(), }.assimilate_storage(&mut t).unwrap(); @@ -1217,6 +1254,32 @@ mod tests { } } + fn init_block() { + println!("Initializing {}", System::block_number()); + System::on_initialize(System::block_number()); + Registrar::on_initialize(System::block_number()); + Parachains::on_initialize(System::block_number()); + } + fn run_to_block(n: u64) { + println!("Running until block {}", n); + while System::block_number() < n { + if System::block_number() > 1 { + println!("Finalizing {}", System::block_number()); + Parachains::on_finalize(System::block_number()); + Registrar::on_finalize(System::block_number()); + System::on_finalize(System::block_number()); + } + System::set_block_number(System::block_number() + 1); + init_block(); + } + } + + fn queue_upward_messages(id: ParaId, upward_messages: &[UpwardMessage]) { + NeedsDispatch::mutate(|nd| + Parachains::queue_upward_messages(id, upward_messages, nd) + ); + } + #[test] fn check_dispatch_upward_works() { let parachains = vec![ @@ -1225,16 +1288,16 @@ mod tests { (2u32.into(), vec![], vec![]), ]; with_externalities(&mut new_test_ext(parachains.clone()), || { - let parachains = vec![0.into(), 1.into(), 2.into()]; - Parachains::queue_upward_messages(0.into(), &vec![ + init_block(); + queue_upward_messages(0.into(), &vec![ UpwardMessage { origin: ParachainDispatchOrigin::Parachain, data: vec![0; 4] } ]); - Parachains::queue_upward_messages(1.into(), &vec![ + queue_upward_messages(1.into(), &vec![ UpwardMessage { origin: ParachainDispatchOrigin::Parachain, data: vec![1; 4] } ]); let mut dispatched: Vec<(ParaId, ParachainDispatchOrigin, Vec<u8>)> = vec![]; let dummy = |id, origin, data: &[u8]| dispatched.push((id, origin, data.to_vec())); - Parachains::dispatch_upward_messages(0, ¶chains, 2, 3, dummy); + Parachains::dispatch_upward_messages(2, 3, dummy); assert_eq!(dispatched, vec![ (0.into(), ParachainDispatchOrigin::Parachain, vec![0; 4]) ]); @@ -1242,19 +1305,19 @@ mod tests { assert_eq!(<RelayDispatchQueue>::get(ParaId::from(1)).len(), 1); }); with_externalities(&mut new_test_ext(parachains.clone()), || { - let parachains = vec![0.into(), 1.into(), 2.into()]; - Parachains::queue_upward_messages(0.into(), &vec![ + init_block(); + queue_upward_messages(0.into(), &vec![ UpwardMessage { origin: ParachainDispatchOrigin::Parachain, data: vec![0; 2] } ]); - Parachains::queue_upward_messages(1.into(), &vec![ + queue_upward_messages(1.into(), &vec![ UpwardMessage { origin: ParachainDispatchOrigin::Parachain, data: vec![1; 2] } ]); - Parachains::queue_upward_messages(2.into(), &vec![ + queue_upward_messages(2.into(), &vec![ UpwardMessage { origin: ParachainDispatchOrigin::Parachain, data: vec![2] } ]); let mut dispatched: Vec<(ParaId, ParachainDispatchOrigin, Vec<u8>)> = vec![]; let dummy = |id, origin, data: &[u8]| dispatched.push((id, origin, data.to_vec())); - Parachains::dispatch_upward_messages(0, ¶chains, 2, 3, dummy); + Parachains::dispatch_upward_messages(2, 3, dummy); assert_eq!(dispatched, vec![ (0.into(), ParachainDispatchOrigin::Parachain, vec![0; 2]), (2.into(), ParachainDispatchOrigin::Parachain, vec![2]) @@ -1264,44 +1327,44 @@ mod tests { assert!(<RelayDispatchQueue>::get(ParaId::from(2)).is_empty()); }); with_externalities(&mut new_test_ext(parachains.clone()), || { - let parachains = vec![0.into(), 1.into(), 2.into()]; - Parachains::queue_upward_messages(0.into(), &vec![ + init_block(); + queue_upward_messages(0.into(), &vec![ UpwardMessage { origin: ParachainDispatchOrigin::Parachain, data: vec![0; 2] } ]); - Parachains::queue_upward_messages(1.into(), &vec![ + queue_upward_messages(1.into(), &vec![ UpwardMessage { origin: ParachainDispatchOrigin::Parachain, data: vec![1; 2] } ]); - Parachains::queue_upward_messages(2.into(), &vec![ + queue_upward_messages(2.into(), &vec![ UpwardMessage { origin: ParachainDispatchOrigin::Parachain, data: vec![2] } ]); let mut dispatched: Vec<(ParaId, ParachainDispatchOrigin, Vec<u8>)> = vec![]; let dummy = |id, origin, data: &[u8]| dispatched.push((id, origin, data.to_vec())); - Parachains::dispatch_upward_messages(1, ¶chains, 2, 3, dummy); + Parachains::dispatch_upward_messages(2, 3, dummy); assert_eq!(dispatched, vec![ - (1.into(), ParachainDispatchOrigin::Parachain, vec![1; 2]), + (0.into(), ParachainDispatchOrigin::Parachain, vec![0; 2]), (2.into(), ParachainDispatchOrigin::Parachain, vec![2]) ]); - assert_eq!(<RelayDispatchQueue>::get(ParaId::from(0)).len(), 1); - assert!(<RelayDispatchQueue>::get(ParaId::from(1)).is_empty()); + assert!(<RelayDispatchQueue>::get(ParaId::from(0)).is_empty()); + assert_eq!(<RelayDispatchQueue>::get(ParaId::from(1)).len(), 1); assert!(<RelayDispatchQueue>::get(ParaId::from(2)).is_empty()); }); with_externalities(&mut new_test_ext(parachains.clone()), || { - let parachains = vec![0.into(), 1.into(), 2.into()]; - Parachains::queue_upward_messages(0.into(), &vec![ + init_block(); + queue_upward_messages(0.into(), &vec![ UpwardMessage { origin: ParachainDispatchOrigin::Parachain, data: vec![0; 2] } ]); - Parachains::queue_upward_messages(1.into(), &vec![ + queue_upward_messages(1.into(), &vec![ UpwardMessage { origin: ParachainDispatchOrigin::Parachain, data: vec![1; 2] } ]); - Parachains::queue_upward_messages(2.into(), &vec![ + queue_upward_messages(2.into(), &vec![ UpwardMessage { origin: ParachainDispatchOrigin::Parachain, data: vec![2] } ]); let mut dispatched: Vec<(ParaId, ParachainDispatchOrigin, Vec<u8>)> = vec![]; let dummy = |id, origin, data: &[u8]| dispatched.push((id, origin, data.to_vec())); - Parachains::dispatch_upward_messages(2, ¶chains, 2, 3, dummy); + Parachains::dispatch_upward_messages(2, 3, dummy); assert_eq!(dispatched, vec![ + (0.into(), ParachainDispatchOrigin::Parachain, vec![0; 2]), (2.into(), ParachainDispatchOrigin::Parachain, vec![2]), - (0.into(), ParachainDispatchOrigin::Parachain, vec![0; 2]) ]); assert!(<RelayDispatchQueue>::get(ParaId::from(0)).is_empty()); assert_eq!(<RelayDispatchQueue>::get(ParaId::from(1)).len(), 1); @@ -1315,20 +1378,21 @@ mod tests { (0u32.into(), vec![], vec![]), ]; with_externalities(&mut new_test_ext(parachains), || { + run_to_block(2); let messages = vec![ UpwardMessage { origin: ParachainDispatchOrigin::Signed, data: vec![0] } ]; assert_ok!(Parachains::check_upward_messages(0.into(), &messages, 2, 3)); // all good. - Parachains::queue_upward_messages(0.into(), &vec![ + queue_upward_messages(0.into(), &vec![ UpwardMessage { origin: ParachainDispatchOrigin::Signed, data: vec![0] }, ]); let messages = vec![ UpwardMessage { origin: ParachainDispatchOrigin::Parachain, data: vec![1, 2] } ]; assert_ok!(Parachains::check_upward_messages(0.into(), &messages, 2, 3)); - Parachains::queue_upward_messages(0.into(), &messages); + queue_upward_messages(0.into(), &messages); assert_eq!(<RelayDispatchQueue>::get(ParaId::from(0)), vec![ UpwardMessage { origin: ParachainDispatchOrigin::Signed, data: vec![0] }, UpwardMessage { origin: ParachainDispatchOrigin::Parachain, data: vec![1, 2] }, @@ -1342,6 +1406,7 @@ mod tests { (0u32.into(), vec![], vec![]), ]; with_externalities(&mut new_test_ext(parachains), || { + run_to_block(2); // oversize, but ok since it's just one and the queue is empty. let messages = vec![ UpwardMessage { origin: ParachainDispatchOrigin::Signed, data: vec![0; 4] }, @@ -1377,8 +1442,9 @@ mod tests { (0u32.into(), vec![], vec![]), ]; with_externalities(&mut new_test_ext(parachains), || { + run_to_block(2); // too many messages. - Parachains::queue_upward_messages(0.into(), &vec![ + queue_upward_messages(0.into(), &vec![ UpwardMessage { origin: ParachainDispatchOrigin::Signed, data: vec![0] }, ]); let messages = vec![ @@ -1398,8 +1464,9 @@ mod tests { (0u32.into(), vec![], vec![]), ]; with_externalities(&mut new_test_ext(parachains), || { + run_to_block(2); // too much data. - Parachains::queue_upward_messages(0.into(), &vec![ + queue_upward_messages(0.into(), &vec![ UpwardMessage { origin: ParachainDispatchOrigin::Signed, data: vec![0, 1] }, ]); let messages = vec![ @@ -1418,8 +1485,9 @@ mod tests { (0u32.into(), vec![], vec![]), ]; with_externalities(&mut new_test_ext(parachains), || { + run_to_block(2); // bad - already an oversize messages queued. - Parachains::queue_upward_messages(0.into(), &vec![ + queue_upward_messages(0.into(), &vec![ UpwardMessage { origin: ParachainDispatchOrigin::Signed, data: vec![0; 4] }, ]); let messages = vec![ @@ -1438,8 +1506,9 @@ mod tests { (0u32.into(), vec![], vec![]), ]; with_externalities(&mut new_test_ext(parachains), || { + run_to_block(2); // bad - oversized and already a message queued. - Parachains::queue_upward_messages(0.into(), &vec![ + queue_upward_messages(0.into(), &vec![ UpwardMessage { origin: ParachainDispatchOrigin::Signed, data: vec![0] }, ]); let messages = vec![ @@ -1461,6 +1530,7 @@ mod tests { ]; with_externalities(&mut new_test_ext(parachains), || { + run_to_block(2); // parachain 0 is self let mut candidates = vec![ new_candidate_with_upward_messages(0, vec![ @@ -1490,9 +1560,10 @@ mod tests { ]; with_externalities(&mut new_test_ext(parachains), || { - assert_eq!(Parachains::active_parachains(), vec![5u32.into(), 100u32.into()]); - assert_eq!(Parachains::parachain_code(ParaId::from(5u32)), Some(vec![1,2,3])); - assert_eq!(Parachains::parachain_code(ParaId::from(100u32)), Some(vec![4,5,6])); + run_to_block(2); + assert_eq!(Parachains::active_parachains(), vec![(5u32.into(), None), (100u32.into(), None)]); + assert_eq!(Parachains::parachain_code(ParaId::from(5u32)), Some(vec![1, 2, 3])); + assert_eq!(Parachains::parachain_code(ParaId::from(100u32)), Some(vec![4, 5, 6])); }); } @@ -1504,20 +1575,28 @@ mod tests { ]; with_externalities(&mut new_test_ext(parachains), || { - assert_eq!(Parachains::active_parachains(), vec![5u32.into(), 100u32.into()]); + run_to_block(2); + assert_eq!(Parachains::active_parachains(), vec![(5u32.into(), None), (100u32.into(), None)]); assert_eq!(Parachains::parachain_code(ParaId::from(5u32)), Some(vec![1,2,3])); assert_eq!(Parachains::parachain_code(ParaId::from(100u32)), Some(vec![4,5,6])); - assert_ok!(Parachains::register_parachain(Origin::ROOT, 99u32.into(), vec![7,8,9], vec![1, 1, 1])); + assert_ok!(Registrar::register_para(Origin::ROOT, 99u32.into(), ParaInfo{scheduling: Scheduling::Always}, vec![7,8,9], vec![1, 1, 1])); + assert_ok!(Parachains::set_heads(Origin::NONE, vec![])); - assert_eq!(Parachains::active_parachains(), vec![5u32.into(), 99u32.into(), 100u32.into()]); - assert_eq!(Parachains::parachain_code(ParaId::from(99u32)), Some(vec![7,8,9])); + run_to_block(3); - assert_ok!(Parachains::deregister_parachain(Origin::ROOT, 5u32.into())); + assert_eq!(Parachains::active_parachains(), vec![(5u32.into(), None), (99u32.into(), None), (100u32.into(), None)]); + assert_eq!(Parachains::parachain_code(&ParaId::from(99u32)), Some(vec![7,8,9])); - assert_eq!(Parachains::active_parachains(), vec![99u32.into(), 100u32.into()]); - assert_eq!(Parachains::parachain_code(ParaId::from(5u32)), None); + assert_ok!(Registrar::deregister_para(Origin::ROOT, 5u32.into())); + assert_ok!(Parachains::set_heads(Origin::NONE, vec![])); + + // parachain still active this block. another block must pass before it's inactive. + run_to_block(4); + + assert_eq!(Parachains::active_parachains(), vec![(99u32.into(), None), (100u32.into(), None)]); + assert_eq!(Parachains::parachain_code(&ParaId::from(5u32)), None); }); } @@ -1529,6 +1608,7 @@ mod tests { ]; with_externalities(&mut new_test_ext(parachains), || { + run_to_block(2); let check_roster = |duty_roster: &DutyRoster| { assert_eq!(duty_roster.validator_duty.len(), 8); for i in (0..2).map(ParaId::from) { @@ -1564,6 +1644,7 @@ mod tests { ]; with_externalities(&mut new_test_ext(parachains), || { + run_to_block(2); let candidate = AttestedCandidate { validity_votes: vec![], validator_indices: BitVec::new(), @@ -1592,6 +1673,9 @@ mod tests { ]; with_externalities(&mut new_test_ext(parachains), || { + run_to_block(2); + assert_eq!(Parachains::active_parachains().len(), 2); + let mut candidate_a = AttestedCandidate { validity_votes: vec![], validator_indices: BitVec::new(), @@ -1645,6 +1729,7 @@ mod tests { ]; with_externalities(&mut new_test_ext(parachains), || { + run_to_block(2); let mut candidate = AttestedCandidate { validity_votes: vec![], validator_indices: BitVec::new(), @@ -1681,6 +1766,7 @@ mod tests { ]; with_externalities(&mut new_test_ext(parachains), || { + run_to_block(2); let mut candidate = AttestedCandidate { validity_votes: vec![], validator_indices: BitVec::new(), @@ -1711,8 +1797,6 @@ mod tests { #[test] fn ingress_works() { - use sr_primitives::traits::OnFinalize; - let parachains = vec![ (0u32.into(), vec![], vec![]), (1u32.into(), vec![], vec![]), @@ -1723,8 +1807,9 @@ mod tests { assert_eq!(Parachains::ingress(ParaId::from(1), None), Some(Vec::new())); assert_eq!(Parachains::ingress(ParaId::from(99), None), Some(Vec::new())); + init_block(); for i in 1..10 { - System::set_block_number(i); + run_to_block(i); let from_a = vec![(1.into(), [i as u8; 32].into())]; let mut candidate_a = AttestedCandidate { @@ -1765,11 +1850,9 @@ mod tests { set_heads(vec![candidate_a, candidate_b]), Origin::NONE, )); - - Parachains::on_finalize(i); } - System::set_block_number(10); + run_to_block(10); assert_ok!(Parachains::dispatch( set_heads(vec![]), Origin::NONE, @@ -1795,7 +1878,7 @@ mod tests { ))).collect::<Vec<_>>()), ); - assert_ok!(Parachains::deregister_parachain(Origin::ROOT, 1u32.into())); + assert_ok!(Registrar::deregister_para(Origin::ROOT, 1u32.into())); // after deregistering, there is no ingress to 1, but unrouted messages // from 1 stick around. @@ -1804,8 +1887,7 @@ mod tests { vec![(1.into(), [i as u8; 32].into())] ))).collect::<Vec<_>>())); - Parachains::on_finalize(10); - System::set_block_number(11); + run_to_block(11); let mut candidate_c = AttestedCandidate { validity_votes: vec![], @@ -1828,8 +1910,7 @@ mod tests { Origin::NONE, )); - Parachains::on_finalize(11); - System::set_block_number(12); + run_to_block(12); // at the next block, ingress to 99 should be empty. assert_eq!(Parachains::ingress(ParaId::from(99), None), Some(Vec::new())); @@ -1845,6 +1926,7 @@ mod tests { ]; with_externalities(&mut new_test_ext(parachains), || { + run_to_block(2); // parachain 99 does not exist let non_existent = vec![(99.into(), [1; 32].into())]; let mut candidate = new_candidate_with_egress_roots(non_existent); @@ -1869,6 +1951,7 @@ mod tests { ]; with_externalities(&mut new_test_ext(parachains), || { + run_to_block(2); // parachain 0 is self let to_self = vec![(0.into(), [1; 32].into())]; let mut candidate = new_candidate_with_egress_roots(to_self); @@ -1893,6 +1976,7 @@ mod tests { ]; with_externalities(&mut new_test_ext(parachains), || { + run_to_block(2); // parachain 0 is self let out_of_order = vec![(1.into(), [1; 32].into()), ((0.into(), [1; 32].into()))]; let mut candidate = new_candidate_with_egress_roots(out_of_order); @@ -1917,6 +2001,7 @@ mod tests { ]; with_externalities(&mut new_test_ext(parachains), || { + run_to_block(2); // parachain 0 is self let contains_empty_trie_root = vec![(1.into(), [1; 32].into()), ((2.into(), EMPTY_TRIE_ROOT.into()))]; let mut candidate = new_candidate_with_egress_roots(contains_empty_trie_root); diff --git a/polkadot/runtime/src/registrar.rs b/polkadot/runtime/src/registrar.rs new file mode 100644 index 00000000000..77b2663e443 --- /dev/null +++ b/polkadot/runtime/src/registrar.rs @@ -0,0 +1,1368 @@ +// Copyright 2017 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 <http://www.gnu.org/licenses/>. + +//! Module to handle which parachains/parathreads (collectively referred to as "paras") are +//! registered and which are scheduled. Doesn't manage any of the actual execution/validation logic +//! which is left to `parachains.rs`. + +use rstd::{prelude::*, result}; +#[cfg(any(feature = "std", test))] +use rstd::marker::PhantomData; +use codec::{Encode, Decode}; + +use sr_primitives::{ + weights::{SimpleDispatchInfo, DispatchInfo}, + transaction_validity::{TransactionValidityError, ValidTransaction, TransactionValidity}, + traits::{Hash as HashT, SignedExtension} +}; + +use srml_support::{ + decl_storage, decl_module, decl_event, ensure, + dispatch::{Result, IsSubType}, traits::{Get, Currency, ReservableCurrency} +}; +use system::{self, ensure_root, ensure_signed}; +use primitives::parachain::{ + Id as ParaId, CollatorId, Scheduling, LOWEST_USER_ID, SwapAux, Info as ParaInfo, ActiveParas, + Retriable +}; +use crate::parachains; +use sr_primitives::transaction_validity::InvalidTransaction; + +/// Parachain registration API. +pub trait Registrar<AccountId> { + /// Create a new unique parachain identity for later registration. + fn new_id() -> ParaId; + + /// Register a parachain with given `code` and `initial_head_data`. `id` must not yet be registered or it will + /// result in a error. + fn register_para( + id: ParaId, + info: ParaInfo, + code: Vec<u8>, + initial_head_data: Vec<u8>, + ) -> Result; + + /// Deregister a parachain with given `id`. If `id` is not currently registered, an error is returned. + fn deregister_para(id: ParaId) -> Result; +} + +impl<T: Trait> Registrar<T::AccountId> for Module<T> { + fn new_id() -> ParaId { + <NextFreeId>::mutate(|n| { let r = *n; *n = ParaId::from(u32::from(*n) + 1); r }) + } + + fn register_para( + id: ParaId, + info: ParaInfo, + code: Vec<u8>, + initial_head_data: Vec<u8>, + ) -> Result { + ensure!(!Paras::exists(id), "Parachain already exists"); + if let Scheduling::Always = info.scheduling { + Parachains::mutate(|parachains| + match parachains.binary_search(&id) { + Ok(_) => Err("Parachain already exists"), + Err(idx) => { + parachains.insert(idx, id); + Ok(()) + } + } + )?; + } + <parachains::Module<T>>::initialize_para(id, code, initial_head_data); + Paras::insert(id, info); + Ok(()) + } + + fn deregister_para(id: ParaId) -> Result { + let info = Paras::take(id).ok_or("Invalid id")?; + if let Scheduling::Always = info.scheduling { + Parachains::mutate(|parachains| + parachains.binary_search(&id) + .map(|index| parachains.remove(index)) + .map_err(|_| "Invalid id") + )?; + } + <parachains::Module<T>>::cleanup_para(id); + Paras::remove(id); + Ok(()) + } +} + +type BalanceOf<T> = + <<T as Trait>::Currency as Currency<<T as system::Trait>::AccountId>>::Balance; + +pub trait Trait: parachains::Trait { + /// The overarching event type. + type Event: From<Event> + Into<<Self as system::Trait>::Event>; + + /// 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<<Self as system::Trait>::Origin> + + Into<result::Result<parachains::Origin, <Self as Trait>::Origin>>; + + /// The system's currency for parathread payment. + type Currency: ReservableCurrency<Self::AccountId>; + + /// The deposit to be paid to run a parathread. + type ParathreadDeposit: Get<BalanceOf<Self>>; + + /// Handler for when two ParaIds are swapped. + type SwapAux: SwapAux; + + /// The number of items in the parathread queue, aka the number of blocks in advance to schedule + /// parachain execution. + type QueueSize: Get<usize>; + + /// The number of rotations that you will have as grace if you miss a block. + type MaxRetries: Get<u32>; +} + +decl_storage! { + trait Store for Module<T: Trait> as Registrar { + // Vector of all parachain IDs, in ascending order. + Parachains: Vec<ParaId>; + + /// The number of threads to schedule per block. + ThreadCount: u32; + + /// An array of the queue of set of threads scheduled for the coming blocks; ordered by + /// ascending para ID. There can be no duplicates of para ID in each list item. + SelectedThreads: Vec<Vec<(ParaId, CollatorId)>>; + + /// Parathreads/chains scheduled for execution this block. If the collator ID is set, then + /// a particular collator has already been chosen for the next block, and no other collator + /// may provide the block. In this case we allow the possibility of the combination being + /// retried in a later block, expressed by `Retriable`. + /// + /// Ordered by ParaId. + Active: Vec<(ParaId, Option<(CollatorId, Retriable)>)>; + + /// The next unused ParaId value. Start this high in order to keep low numbers for + /// system-level chains. + NextFreeId: ParaId = LOWEST_USER_ID; + + /// Pending swap operations. + PendingSwap: map ParaId => Option<ParaId>; + + /// Map of all registered parathreads/chains. + Paras get(paras): map ParaId => Option<ParaInfo>; + + /// The current queue for parathreads that should be retried. + RetryQueue get(retry_queue): Vec<Vec<(ParaId, CollatorId)>>; + + /// Users who have paid a parathread's deposit + Debtors: map ParaId => T::AccountId; + } + add_extra_genesis { + config(parachains): Vec<(ParaId, Vec<u8>, Vec<u8>)>; + config(_phdata): PhantomData<T>; + build(build::<T>); + } +} + +#[cfg(feature = "std")] +fn build<T: Trait>(config: &GenesisConfig<T>) { + use sr_primitives::traits::Zero; + + let mut p = config.parachains.clone(); + p.sort_unstable_by_key(|&(ref id, _, _)| *id); + p.dedup_by_key(|&mut (ref id, _, _)| *id); + + let only_ids: Vec<ParaId> = p.iter().map(|&(ref id, _, _)| id).cloned().collect(); + + Parachains::put(&only_ids); + + for (id, code, genesis) in p { + Paras::insert(id, &primitives::parachain::PARACHAIN_INFO); + // no ingress -- a chain cannot be routed to until it is live. + <parachains::Code>::insert(&id, &code); + <parachains::Heads>::insert(&id, &genesis); + <parachains::Watermarks<T>>::insert(&id, T::BlockNumber::zero()); + // Save initial parachains in registrar + Paras::insert(id, ParaInfo { scheduling: Scheduling::Always }) + } +} + +/// Swap the existence of two items, provided by value, within an ordered list. +/// +/// If neither item exists, or if both items exist this will do nothing. If exactly one of the +/// items exists, then it will be removed and the other inserted. +pub fn swap_ordered_existence<T: PartialOrd + Ord + Copy>(ids: &mut [T], one: T, other: T) { + let maybe_one_pos = ids.binary_search(&one); + let maybe_other_pos = ids.binary_search(&other); + match (maybe_one_pos, maybe_other_pos) { + (Ok(one_pos), Err(_)) => ids[one_pos] = other, + (Err(_), Ok(other_pos)) => ids[other_pos] = one, + _ => return, + }; + ids.sort(); +} + +decl_module! { + /// Parachains module. + pub struct Module<T: Trait> for enum Call where origin: <T as system::Trait>::Origin { + fn deposit_event() = default; + + /// Register a parachain with given code. + /// Fails if given ID is already used. + #[weight = SimpleDispatchInfo::FixedOperational(5_000_000)] + pub fn register_para(origin, + #[compact] id: ParaId, + info: ParaInfo, + code: Vec<u8>, + initial_head_data: Vec<u8>, + ) -> Result { + ensure_root(origin)?; + <Self as Registrar<T::AccountId>>:: + register_para(id, info, code, initial_head_data) + } + + /// Deregister a parachain with given id + #[weight = SimpleDispatchInfo::FixedOperational(10_000)] + pub fn deregister_para(origin, #[compact] id: ParaId) -> Result { + ensure_root(origin)?; + <Self as Registrar<T::AccountId>>::deregister_para(id) + } + + /// Reset the number of parathreads that can pay to be scheduled in a single block. + /// + /// - `count`: The number of parathreads. + /// + /// Must be called from Root origin. + fn set_thread_count(origin, count: u32) { + ensure_root(origin)?; + ThreadCount::put(count); + } + + /// Register a parathread for immediate use. + /// + /// Must be sent from a Signed origin that is able to have ParathreadDeposit reserved. + /// `code` and `initial_head_data` are used to initialize the parathread's state. + fn register_parathread(origin, + code: Vec<u8>, + initial_head_data: Vec<u8>, + ) { + let who = ensure_signed(origin)?; + + T::Currency::reserve(&who, T::ParathreadDeposit::get())?; + + let info = ParaInfo { + scheduling: Scheduling::Dynamic, + }; + let id = <Self as Registrar<T::AccountId>>::new_id(); + + let _ = <Self as Registrar<T::AccountId>>:: + register_para(id, info, code, initial_head_data); + + <Debtors<T>>::insert(id, who); + + Self::deposit_event(Event::ParathreadRegistered(id)); + } + + /// Place a bid for a parathread to be progressed in the next block. + /// + /// This is a kind of special transaction that should by heavily prioritized in the + /// transaction pool according to the `value`; only `ThreadCount` of them may be presented + /// in any single block. + fn select_parathread(origin, + #[compact] _id: ParaId, + _collator: CollatorId, + _head_hash: T::Hash, + ) { + ensure_signed(origin)?; + // Everything else is checked for in the transaction `SignedExtension`. + } + + /// Deregister a parathread and retrieve 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 retrieve them (without + /// governance intervention). + fn deregister_parathread(origin) { + let id = parachains::ensure_parachain(<T as Trait>::Origin::from(origin))?; + + let info = Paras::get(id).ok_or("invalid id")?; + if let Scheduling::Dynamic = info.scheduling {} else { Err("invalid parathread id")? } + + <Self as Registrar<T::AccountId>>::deregister_para(id)?; + Self::force_unschedule(|i| i == id); + + let debtor = <Debtors<T>>::take(id); + let _ = T::Currency::unreserve(&debtor, T::ParathreadDeposit::get()); + + Self::deposit_event(Event::ParathreadRegistered(id)); + } + + /// 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. + fn swap(origin, #[compact] other: ParaId) { + let id = parachains::ensure_parachain(<T as Trait>::Origin::from(origin))?; + + if PendingSwap::get(other) == Some(id) { + // actually do the swap. + T::SwapAux::ensure_can_swap(id, other)?; + + // Remove intention to swap. + PendingSwap::remove(other); + Self::force_unschedule(|i| i == id || i == other); + Parachains::mutate(|ids| swap_ordered_existence(ids, id, other)); + Paras::mutate(id, |i| + Paras::mutate(other, |j| + rstd::mem::swap(i, j) + ) + ); + + <Debtors<T>>::mutate(id, |i| + <Debtors<T>>::mutate(other, |j| + rstd::mem::swap(i, j) + ) + ); + let _ = T::SwapAux::on_swap(id, other); + } else { + PendingSwap::insert(id, other); + } + } + + /// Block initializer. Clears SelectedThreads and constructs/replaces Active. + fn on_initialize() { + let next_up = SelectedThreads::mutate(|t| { + let r = if t.len() >= T::QueueSize::get() { + // Take the first set of parathreads in queue + t.remove(0) + } else { + vec![] + }; + while t.len() < T::QueueSize::get() { + t.push(vec![]); + } + r + }); + // mutable so that we can replace with `None` if parathread appears in new schedule. + let mut retrying = Self::take_next_retry(); + if let Some(((para, _), _)) = retrying { + // this isn't really ideal: better would be if there were an earlier pass that set + // retrying to the first item in the Missed queue that isn't already scheduled, but + // this is potentially O(m*n) in terms of missed queue size and parathread pool size. + if next_up.iter().any(|x| x.0 == para) { + retrying = None + } + } + + let mut paras = Parachains::get().into_iter() + .map(|id| (id, None)) + .chain(next_up.into_iter() + .map(|(para, collator)| + (para, Some((collator, Retriable::WithRetries(0)))) + ) + ).chain(retrying.into_iter() + .map(|((para, collator), retries)| + (para, Some((collator, Retriable::WithRetries(retries + 1)))) + ) + ).collect::<Vec<_>>(); + // for Rust's timsort algorithm, sorting a concatenation of two sorted ranges is near + // O(N). + paras.sort_by_key(|&(ref id, _)| *id); + + Active::put(paras); + } + + fn on_finalize() { + // a block without this will panic, but let's not panic here. + if let Some(proceeded_vec) = parachains::DidUpdate::get() { + // Active is sorted and DidUpdate is a sorted subset of its elements. + // + // We just go through the contents of active and find any items that don't appear in + // DidUpdate *and* which are enabled for retry. + let mut proceeded = proceeded_vec.into_iter(); + let mut i = proceeded.next(); + for sched in Active::get().into_iter() { + match i { + // Scheduled parachain proceeded properly. Move onto next item. + Some(para) if para == sched.0 => i = proceeded.next(), + // Scheduled `sched` missed their block. + // Queue for retry if it's allowed. + _ => if let (i, Some((c, Retriable::WithRetries(n)))) = sched { + Self::retry_later((i, c), n) + }, + } + } + } + } + } +} + +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), + } +} + +impl<T: Trait> Module<T> { + /// Ensures that the given `ParaId` corresponds to a registered parathread, and returns a descriptor if so. + pub fn ensure_thread_id(id: ParaId) -> Option<ParaInfo> { + Paras::get(id).and_then(|info| if let Scheduling::Dynamic = info.scheduling { + Some(info) + } else { + None + }) + } + + fn retry_later(sched: (ParaId, CollatorId), retries: u32) { + if retries < T::MaxRetries::get() { + RetryQueue::mutate(|q| { + q.resize(T::MaxRetries::get() as usize, vec![]); + q[retries as usize].push(sched); + }); + } + } + + fn take_next_retry() -> Option<((ParaId, CollatorId), u32)> { + RetryQueue::mutate(|q| { + for (i, q) in q.iter_mut().enumerate() { + if !q.is_empty() { + return Some((q.remove(0), i as u32)); + } + } + None + }) + } + + /// Forcibly remove the threads matching `m` from all current and future scheduling. + fn force_unschedule(m: impl Fn(ParaId) -> bool) { + RetryQueue::mutate(|qs| for q in qs.iter_mut() { + q.retain(|i| !m(i.0)) + }); + SelectedThreads::mutate(|qs| for q in qs.iter_mut() { + q.retain(|i| !m(i.0)) + }); + Active::mutate(|a| for i in a.iter_mut() { + if m(i.0) { + if let Some((_, ref mut r)) = i.1 { + *r = Retriable::Never; + } + } + }); + } +} + +impl<T: Trait> ActiveParas for Module<T> { + fn active_paras() -> Vec<(ParaId, Option<(CollatorId, Retriable)>)> { + Active::get() + } +} + +/// Ensure that parathread selections happen prioritized by fees. +#[derive(Encode, Decode, Clone, Eq, PartialEq)] +pub struct LimitParathreadCommits<T: Trait + Send + Sync>(rstd::marker::PhantomData<T>) where + <T as system::Trait>::Call: IsSubType<Module<T>, T>; + +#[cfg(feature = "std")] +impl<T: Trait + Send + Sync> rstd::fmt::Debug for LimitParathreadCommits<T> where + <T as system::Trait>::Call: IsSubType<Module<T>, T> +{ + fn fmt(&self, f: &mut rstd::fmt::Formatter) -> rstd::fmt::Result { + write!(f, "LimitParathreadCommits<T>") + } +} + +/// Custom validity errors used in Polkadot while validating transactions. +#[repr(u8)] +pub enum Error { + /// Parathread ID has already been submitted for this block. + Duplicate = 0, + /// Parathread ID does not identify a parathread. + InvalidId = 1, +} + +impl<T: Trait + Send + Sync> SignedExtension for LimitParathreadCommits<T> where + <T as system::Trait>::Call: IsSubType<Module<T>, T> +{ + type AccountId = T::AccountId; + type Call = <T as system::Trait>::Call; + type AdditionalSigned = (); + type Pre = (); + + fn additional_signed(&self) + -> rstd::result::Result<Self::AdditionalSigned, TransactionValidityError> + { + Ok(()) + } + + fn validate( + &self, + _who: &Self::AccountId, + call: &Self::Call, + _info: DispatchInfo, + _len: usize, + ) -> TransactionValidity { + let mut r = ValidTransaction::default(); + if let Some(local_call) = call.is_sub_type() { + if let Call::select_parathread(id, collator, hash) = local_call { + // ensure that the para ID is actually a parathread. + let e = TransactionValidityError::from(InvalidTransaction::Custom(Error::InvalidId as u8)); + <Module<T>>::ensure_thread_id(*id).ok_or(e)?; + + // ensure that we haven't already had a full complement of selected parathreads. + let mut upcoming_selected_threads = SelectedThreads::get(); + if upcoming_selected_threads.is_empty() { + upcoming_selected_threads.push(vec![]); + } + let i = upcoming_selected_threads.len() - 1; + let selected_threads = &mut upcoming_selected_threads[i]; + let thread_count = ThreadCount::get() as usize; + ensure!( + selected_threads.len() < thread_count, + InvalidTransaction::ExhaustsResources.into() + ); + + // ensure that this is not selecting a duplicate parathread ID + let e = TransactionValidityError::from(InvalidTransaction::Custom(Error::Duplicate as u8)); + let pos = selected_threads + .binary_search_by(|&(ref other_id, _)| other_id.cmp(id)) + .err() + .ok_or(e)?; + + // ensure that this is a live bid (i.e. that the thread's chain head matches) + let e = TransactionValidityError::from(InvalidTransaction::Custom(Error::InvalidId as u8)); + let head = <parachains::Module<T>>::parachain_head(id).ok_or(e)?; + let actual = T::Hashing::hash(&head); + ensure!(&actual == hash, InvalidTransaction::Stale.into()); + + // updated the selected threads. + selected_threads.insert(pos, (*id, collator.clone())); + rstd::mem::drop(selected_threads); + SelectedThreads::put(upcoming_selected_threads); + + // provides the state-transition for this head-data-hash; this should cue the pool + // to throw out competing transactions with lesser fees. + r.provides = vec![hash.encode()]; + } + } + Ok(r) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use bitvec::vec::BitVec; + use sr_io::{TestExternalities, with_externalities}; + use substrate_primitives::{H256, Blake2Hasher, Pair}; + use sr_primitives::{ + traits::{ + BlakeTwo256, IdentityLookup, ConvertInto, OnInitialize, OnFinalize, Dispatchable, + AccountIdConversion, + }, testing::{UintAuthorityId, Header}, Perbill + }; + use primitives::{ + parachain::{ + ValidatorId, Info as ParaInfo, Scheduling, LOWEST_USER_ID, AttestedCandidate, + CandidateReceipt, HeadData, ValidityAttestation, Statement, Chain, CollatorPair, + }, + Balance, BlockNumber, + }; + use srml_support::{ + impl_outer_origin, impl_outer_dispatch, assert_ok, parameter_types, assert_noop, + }; + use keyring::Sr25519Keyring; + + use crate::parachains; + use crate::slots; + use crate::attestations; + + impl_outer_origin! { + pub enum Origin for Test { + parachains, + } + } + + impl_outer_dispatch! { + pub enum Call for Test where origin: Origin { + parachains::Parachains, + registrar::Registrar, + } + } + + #[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 system::Trait for Test { + type Origin = Origin; + type Call = Call; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup<u64>; + type Header = Header; + type WeightMultiplierUpdate = (); + type Event = (); + type BlockHashCount = BlockHashCount; + type MaximumBlockWeight = MaximumBlockWeight; + type MaximumBlockLength = MaximumBlockLength; + type AvailableBlockRatio = AvailableBlockRatio; + type Version = (); + } + + parameter_types! { + pub const ExistentialDeposit: Balance = 0; + pub const TransferFee: Balance = 0; + pub const CreationFee: Balance = 0; + pub const TransactionBaseFee: Balance = 0; + pub const TransactionByteFee: Balance = 0; + } + + impl balances::Trait for Test { + type Balance = Balance; + type OnFreeBalanceZero = (); + type OnNewAccount = (); + type Event = (); + type TransactionPayment = (); + type DustRemoval = (); + type TransferPayment = (); + type ExistentialDeposit = ExistentialDeposit; + type TransferFee = TransferFee; + type CreationFee = CreationFee; + type TransactionBaseFee = TransactionBaseFee; + type TransactionByteFee = TransactionByteFee; + type WeightToFee = ConvertInto; + } + + parameter_types!{ + pub const LeasePeriod: u64 = 10; + pub const EndingPeriod: u64 = 3; + } + + impl slots::Trait for Test { + type Event = (); + type Currency = balances::Module<Test>; + type Parachains = Registrar; + type EndingPeriod = EndingPeriod; + type LeasePeriod = LeasePeriod; + } + + parameter_types!{ + pub const AttestationPeriod: BlockNumber = 100; + } + + impl attestations::Trait for Test { + type AttestationPeriod = AttestationPeriod; + type ValidatorIdentities = parachains::ValidatorIdentities<Test>; + type RewardAttestation = (); + } + + parameter_types! { + pub const Period: BlockNumber = 1; + pub const Offset: BlockNumber = 0; + pub const DisabledValidatorsThreshold: Perbill = Perbill::from_percent(17); + } + + impl session::Trait for Test { + type OnSessionEnding = (); + type Keys = UintAuthorityId; + type ShouldEndSession = session::PeriodicSessions<Period, Offset>; + type SessionHandler = (); + type Event = (); + type SelectInitialValidators = (); + type ValidatorId = u64; + type ValidatorIdOf = (); + type DisabledValidatorsThreshold = DisabledValidatorsThreshold; + } + + impl parachains::Trait for Test { + type Origin = Origin; + type Call = Call; + type ParachainCurrency = balances::Module<Test>; + type ActiveParachains = Registrar; + type Registrar = Registrar; + } + + 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 = balances::Module<Test>; + type ParathreadDeposit = ParathreadDeposit; + type SwapAux = slots::Module<Test>; + type QueueSize = QueueSize; + type MaxRetries = MaxRetries; + } + + type Balances = balances::Module<Test>; + type Parachains = parachains::Module<Test>; + type System = system::Module<Test>; + type Slots = slots::Module<Test>; + type Registrar = Module<Test>; + + const AUTHORITY_KEYS: [Sr25519Keyring; 8] = [ + Sr25519Keyring::Alice, + Sr25519Keyring::Bob, + Sr25519Keyring::Charlie, + Sr25519Keyring::Dave, + Sr25519Keyring::Eve, + Sr25519Keyring::Ferdie, + Sr25519Keyring::One, + Sr25519Keyring::Two, + ]; + + fn new_test_ext(parachains: Vec<(ParaId, Vec<u8>, Vec<u8>)>) -> TestExternalities<Blake2Hasher> { + let mut t = system::GenesisConfig::default().build_storage::<Test>().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, UintAuthorityId(i as u64))) + .collect(); + + let authorities: Vec<_> = authority_keys.iter().map(|k| ValidatorId::from(k.public())).collect(); + + let balances: Vec<_> = (0..authority_keys.len()).map(|i| (i as u64, 10_000_000)).collect(); + + parachains::GenesisConfig { + authorities: authorities.clone(), + }.assimilate_storage(&mut t).unwrap(); + + GenesisConfig::<Test> { + parachains, + _phdata: Default::default(), + }.assimilate_storage(&mut t).unwrap(); + + session::GenesisConfig::<Test> { + keys: session_keys, + }.assimilate_storage(&mut t).unwrap(); + + balances::GenesisConfig::<Test> { + balances, + vesting: vec![], + }.assimilate_storage(&mut t).unwrap(); + + t.into() + } + + fn init_block() { + println!("Initializing {}", System::block_number()); + System::on_initialize(System::block_number()); + Registrar::on_initialize(System::block_number()); + Parachains::on_initialize(System::block_number()); + Slots::on_initialize(System::block_number()); + } + + fn run_to_block(n: u64) { + println!("Running until block {}", n); + while System::block_number() < n { + if System::block_number() > 1 { + println!("Finalizing {}", System::block_number()); + if !parachains::DidUpdate::exists() { + println!("Null heads update"); + assert_ok!(Parachains::set_heads(system::RawOrigin::None.into(), vec![])); + } + Slots::on_finalize(System::block_number()); + Parachains::on_finalize(System::block_number()); + Registrar::on_finalize(System::block_number()); + System::on_finalize(System::block_number()); + } + System::set_block_number(System::block_number() + 1); + init_block(); + } + } + + fn schedule_thread(id: ParaId, head_data: &[u8], col: &CollatorId) { + let tx: LimitParathreadCommits<Test> = LimitParathreadCommits(Default::default()); + let hdh = BlakeTwo256::hash(head_data); + let inner_call = super::Call::select_parathread(id, col.clone(), hdh); + let call = Call::Registrar(inner_call); + let origin = 4u64; + assert!(tx.validate(&origin, &call, Default::default(), 0).is_ok()); + assert_ok!(call.dispatch(Origin::signed(origin))); + } + + fn user_id(i: u32) -> ParaId { + (LOWEST_USER_ID.into_inner() + i).into() + } + + fn attest(id: ParaId, collator: &CollatorPair, head_data: &[u8], block_data: &[u8]) -> AttestedCandidate { + let block_data_hash = BlakeTwo256::hash(block_data); + let candidate = CandidateReceipt { + parachain_index: id, + collator: collator.public(), + signature: block_data_hash.using_encoded(|d| collator.sign(d)), + head_data: HeadData(head_data.to_vec()), + egress_queue_roots: vec![], + fees: 0, + block_data_hash, + upward_messages: vec![], + }; + let payload = (Statement::Valid(candidate.hash()), System::parent_hash()).encode(); + let roster = Parachains::calculate_duty_roster().0.validator_duty; + AttestedCandidate { + candidate, + validity_votes: AUTHORITY_KEYS.iter() + .enumerate() + .filter(|(i, _)| roster[*i] == Chain::Parachain(id)) + .map(|(_, k)| k.sign(&payload).into()) + .map(ValidityAttestation::Explicit) + .collect(), + validator_indices: roster.iter() + .map(|i| i == &Chain::Parachain(id)) + .collect::<BitVec>(), + } + } + + #[test] + fn basic_setup_works() { + with_externalities(&mut new_test_ext(vec![]), || { + assert_eq!(super::Parachains::get(), vec![]); + assert_eq!(ThreadCount::get(), 0); + assert_eq!(Active::get(), vec![]); + assert_eq!(NextFreeId::get(), LOWEST_USER_ID); + assert_eq!(PendingSwap::get(&ParaId::from(0u32)), None); + assert_eq!(Paras::get(&ParaId::from(0u32)), None); + }); + } + + #[test] + fn genesis_registration_works() { + let parachains = vec![ + (5u32.into(), vec![1,2,3], vec![1]), + (100u32.into(), vec![4,5,6], vec![2,]), + ]; + + with_externalities(&mut new_test_ext(parachains), || { + // Need to trigger on_initialize + run_to_block(2); + // Genesis registration works + assert_eq!(Registrar::active_paras(), vec![(5u32.into(), None), (100u32.into(), None)]); + assert_eq!(Registrar::paras(&ParaId::from(5u32)), Some(ParaInfo { scheduling: Scheduling::Always })); + assert_eq!(Registrar::paras(&ParaId::from(100u32)), Some(ParaInfo { scheduling: Scheduling::Always })); + assert_eq!(Parachains::parachain_code(&ParaId::from(5u32)), Some(vec![1, 2, 3])); + assert_eq!(Parachains::parachain_code(&ParaId::from(100u32)), Some(vec![4, 5, 6])); + }); + } + + #[test] + fn swap_chain_and_thread_works() { + with_externalities(&mut new_test_ext(vec![]), || { + assert_ok!(Registrar::set_thread_count(Origin::ROOT, 1)); + + // Need to trigger on_initialize + run_to_block(2); + + // Register a new parathread + assert_ok!(Registrar::register_parathread( + Origin::signed(1u64), + vec![1; 3], + vec![1; 3], + )); + + // Lease out a new parachain + assert_ok!(Slots::new_auction(Origin::ROOT, 5, 1)); + assert_ok!(Slots::bid(Origin::signed(1), 0, 1, 1, 4, 1)); + + run_to_block(9); + // Ensure that the thread is scheduled around the swap time. + let col = Sr25519Keyring::One.public().into(); + schedule_thread(user_id(0), &[1; 3], &col); + + run_to_block(10); + let h = BlakeTwo256::hash(&[2u8; 3]); + assert_ok!(Slots::fix_deploy_data(Origin::signed(1), 0, user_id(1), h, vec![2; 3])); + assert_ok!(Slots::elaborate_deploy_data(Origin::signed(0), user_id(1), vec![2; 3])); + assert_ok!(Slots::set_offboarding(Origin::signed(user_id(1).into_account()), 1)); + + run_to_block(11); + // should be one active parachain and one active parathread. + assert_eq!(Registrar::active_paras(), vec![ + (user_id(0), Some((col.clone(), Retriable::WithRetries(0)))), + (user_id(1), None), + ]); + + // One half of the swap call does not actually trigger the swap. + assert_ok!(Registrar::swap(parachains::Origin::Parachain(user_id(0)).into(), user_id(1))); + + // Nothing changes from what was originally registered + assert_eq!(Registrar::paras(&user_id(0)), Some(ParaInfo { scheduling: Scheduling::Dynamic })); + assert_eq!(Registrar::paras(&user_id(1)), Some(ParaInfo { scheduling: Scheduling::Always })); + assert_eq!(super::Parachains::get(), vec![user_id(1)]); + assert_eq!(Slots::managed_ids(), vec![user_id(1)]); + assert_eq!(Slots::deposits(user_id(1)), vec![1; 3]); + assert_eq!(Slots::offboarding(user_id(1)), 1); + assert_eq!(Parachains::parachain_code(&user_id(0)), Some(vec![1u8; 3])); + assert_eq!(Parachains::parachain_head(&user_id(0)), Some(vec![1u8; 3])); + assert_eq!(Parachains::parachain_code(&user_id(1)), Some(vec![2u8; 3])); + assert_eq!(Parachains::parachain_head(&user_id(1)), Some(vec![2u8; 3])); + // Intention to swap is added + assert_eq!(PendingSwap::get(user_id(0)), Some(user_id(1))); + + // Intention to swap is reciprocated, swap actually happens + assert_ok!(Registrar::swap(parachains::Origin::Parachain(user_id(1)).into(), user_id(0))); + + assert_eq!(Registrar::paras(&user_id(0)), Some(ParaInfo { scheduling: Scheduling::Always })); + assert_eq!(Registrar::paras(&user_id(1)), Some(ParaInfo { scheduling: Scheduling::Dynamic })); + assert_eq!(super::Parachains::get(), vec![user_id(0)]); + assert_eq!(Slots::managed_ids(), vec![user_id(0)]); + assert_eq!(Slots::deposits(user_id(0)), vec![1; 3]); + assert_eq!(Slots::offboarding(user_id(0)), 1); + assert_eq!(Parachains::parachain_code(&user_id(0)), Some(vec![1u8; 3])); + assert_eq!(Parachains::parachain_head(&user_id(0)), Some(vec![1u8; 3])); + assert_eq!(Parachains::parachain_code(&user_id(1)), Some(vec![2u8; 3])); + assert_eq!(Parachains::parachain_head(&user_id(1)), Some(vec![2u8; 3])); + + // Intention to swap is no longer present + assert_eq!(PendingSwap::get(user_id(0)), None); + assert_eq!(PendingSwap::get(user_id(1)), None); + + run_to_block(12); + // thread should not be queued or scheduled any more, even though it would otherwise be + // being retried.. + assert_eq!(Registrar::active_paras(), vec![(user_id(0), None)]); + }); + } + + #[test] + fn swap_handles_funds_correctly() { + with_externalities(&mut new_test_ext(vec![]), || { + assert_ok!(Registrar::set_thread_count(Origin::ROOT, 1)); + + // Need to trigger on_initialize + 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), + vec![1; 3], + vec![1; 3], + )); + + // User 2 leases out a new parachain + assert_ok!(Slots::new_auction(Origin::ROOT, 5, 1)); + assert_ok!(Slots::bid(Origin::signed(2), 0, 1, 1, 4, 1)); + + run_to_block(9); + + // Swap the parachain and parathread + assert_ok!(Registrar::swap(parachains::Origin::Parachain(user_id(0)).into(), user_id(1))); + assert_ok!(Registrar::swap(parachains::Origin::Parachain(user_id(1)).into(), user_id(0))); + + // Deregister the parathread that was originally a parachain + assert_ok!(Registrar::deregister_parathread(parachains::Origin::Parachain(user_id(1)).into())); + + // Go past when a parachain loses its slot + run_to_block(50); + + // Funds are correctly returned + assert_eq!(Balances::free_balance(1), initial_1_balance); + assert_eq!(Balances::free_balance(2), initial_2_balance); + }); + } + + #[test] + fn register_deregister_chains_works() { + let parachains = vec![ + (1u32.into(), vec![1; 3], vec![1; 3]), + ]; + + with_externalities(&mut new_test_ext(parachains), || { + // Need to trigger on_initialize + run_to_block(2); + + // Genesis registration works + assert_eq!(Registrar::active_paras(), vec![(1u32.into(), None)]); + assert_eq!( + Registrar::paras(&ParaId::from(1u32)), + Some(ParaInfo { scheduling: Scheduling::Always }) + ); + assert_eq!(Parachains::parachain_code(&ParaId::from(1u32)), Some(vec![1; 3])); + + // Register a new parachain + assert_ok!(Registrar::register_para( + Origin::ROOT, + 2u32.into(), + ParaInfo { scheduling: Scheduling::Always }, + vec![2; 3], + vec![2; 3], + )); + + let orig_bal = Balances::free_balance(&3u64); + // Register a new parathread + assert_ok!(Registrar::register_parathread( + Origin::signed(3u64), + vec![3; 3], + vec![3; 3], + )); + // 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); + + // New paras are registered + assert_eq!(Registrar::active_paras(), vec![(1u32.into(), None), (2u32.into(), None)]); + assert_eq!( + Registrar::paras(&ParaId::from(2u32)), + Some(ParaInfo { scheduling: Scheduling::Always }) + ); + assert_eq!( + Registrar::paras(&user_id(0)), + Some(ParaInfo { scheduling: Scheduling::Dynamic }) + ); + assert_eq!(Parachains::parachain_code(&ParaId::from(2u32)), Some(vec![2; 3])); + assert_eq!(Parachains::parachain_code(&user_id(0)), Some(vec![3; 3])); + + assert_ok!(Registrar::deregister_para(Origin::ROOT, 2u32.into())); + assert_ok!(Registrar::deregister_parathread( + parachains::Origin::Parachain(user_id(0)).into() + )); + // reserved balance should be returned. + assert_eq!(Balances::free_balance(&3u64), orig_bal); + assert_eq!(Balances::reserved_balance(&3u64), 0); + + run_to_block(4); + + assert_eq!(Registrar::active_paras(), vec![(1u32.into(), None)]); + assert_eq!(Registrar::paras(&ParaId::from(2u32)), None); + assert_eq!(Parachains::parachain_code(&ParaId::from(2u32)), None); + assert_eq!(Registrar::paras(&user_id(0)), None); + assert_eq!(Parachains::parachain_code(&user_id(0)), None); + }); + } + + #[test] + fn parathread_scheduling_works() { + with_externalities(&mut new_test_ext(vec![]), || { + assert_ok!(Registrar::set_thread_count(Origin::ROOT, 1)); + + run_to_block(2); + + // Register a new parathread + assert_ok!(Registrar::register_parathread( + Origin::signed(3u64), + vec![3; 3], + vec![3; 3], + )); + + run_to_block(3); + + // transaction submitted to get parathread progressed. + let col = Sr25519Keyring::One.public().into(); + schedule_thread(user_id(0), &[3; 3], &col); + + run_to_block(5); + assert_eq!(Registrar::active_paras(), vec![ + (user_id(0), Some((col.clone(), Retriable::WithRetries(0)))) + ]); + assert_ok!(Parachains::set_heads(Origin::NONE, vec![ + attest(user_id(0), &Sr25519Keyring::One.pair().into(), &[3; 3], &[0; 0]) + ])); + + run_to_block(6); + // at next block, it shouldn't be retried. + assert_eq!(Registrar::active_paras(), vec![]); + }); + } + + #[test] + fn removing_scheduled_parathread_works() { + with_externalities(&mut new_test_ext(vec![]), || { + assert_ok!(Registrar::set_thread_count(Origin::ROOT, 1)); + + run_to_block(2); + + // Register some parathreads. + assert_ok!(Registrar::register_parathread(Origin::signed(3), vec![3; 3], vec![3; 3])); + + run_to_block(3); + // transaction submitted to get parathread progressed. + let col = Sr25519Keyring::One.public().into(); + schedule_thread(user_id(0), &[3; 3], &col); + + // now we remove the parathread + assert_ok!(Registrar::deregister_parathread( + parachains::Origin::Parachain(user_id(0)).into() + )); + + run_to_block(5); + assert_eq!(Registrar::active_paras(), vec![]); // should not be scheduled. + + assert_ok!(Registrar::register_parathread(Origin::signed(3), vec![4; 3], vec![4; 3])); + + run_to_block(6); + // transaction submitted to get parathread progressed. + schedule_thread(user_id(1), &[4; 3], &col); + + run_to_block(9); + // thread's slot was missed and is now being re-scheduled. + + assert_ok!(Registrar::deregister_parathread( + parachains::Origin::Parachain(user_id(1)).into() + )); + + run_to_block(10); + // thread's rescheduled slot was missed, but should not be reschedule since it was + // removed. + assert_eq!(Registrar::active_paras(), vec![]); // should not be scheduled. + }); + } + + #[test] + fn parathread_rescheduling_works() { + with_externalities(&mut new_test_ext(vec![]), || { + assert_ok!(Registrar::set_thread_count(Origin::ROOT, 1)); + + run_to_block(2); + + // Register some parathreads. + assert_ok!(Registrar::register_parathread(Origin::signed(3), vec![3; 3], vec![3; 3])); + assert_ok!(Registrar::register_parathread(Origin::signed(4), vec![4; 3], vec![4; 3])); + assert_ok!(Registrar::register_parathread(Origin::signed(5), vec![5; 3], vec![5; 3])); + + run_to_block(3); + + // transaction submitted to get parathread progressed. + let col = Sr25519Keyring::One.public().into(); + schedule_thread(user_id(0), &[3; 3], &col); + + // 4x: the initial time it was scheduled, plus 3 retries. + for n in 5..9 { + run_to_block(n); + assert_eq!(Registrar::active_paras(), vec![ + (user_id(0), Some((col.clone(), Retriable::WithRetries((n - 5) as u32)))) + ]); + } + + // missed too many times. dropped. + run_to_block(9); + assert_eq!(Registrar::active_paras(), vec![]); + + // schedule and miss all 3 and check that they go through the queueing system ok. + assert_ok!(Registrar::set_thread_count(Origin::ROOT, 2)); + schedule_thread(user_id(0), &[3; 3], &col); + schedule_thread(user_id(1), &[4; 3], &col); + + run_to_block(10); + schedule_thread(user_id(2), &[5; 3], &col); + + // 0 and 1 scheduled as normal. + run_to_block(11); + assert_eq!(Registrar::active_paras(), vec![ + (user_id(0), Some((col.clone(), Retriable::WithRetries(0)))), + (user_id(1), Some((col.clone(), Retriable::WithRetries(0)))) + ]); + + // 2 scheduled, 0 retried + run_to_block(12); + assert_eq!(Registrar::active_paras(), vec![ + (user_id(0), Some((col.clone(), Retriable::WithRetries(1)))), + (user_id(2), Some((col.clone(), Retriable::WithRetries(0)))), + ]); + + // 1 retried + run_to_block(13); + assert_eq!(Registrar::active_paras(), vec![ + (user_id(1), Some((col.clone(), Retriable::WithRetries(1)))) + ]); + + // 2 retried + run_to_block(14); + assert_eq!(Registrar::active_paras(), vec![ + (user_id(2), Some((col.clone(), Retriable::WithRetries(1)))) + ]); + + run_to_block(15); + assert_eq!(Registrar::active_paras(), vec![ + (user_id(0), Some((col.clone(), Retriable::WithRetries(2)))) + ]); + + run_to_block(16); + assert_eq!(Registrar::active_paras(), vec![ + (user_id(1), Some((col.clone(), Retriable::WithRetries(2)))) + ]); + + run_to_block(17); + assert_eq!(Registrar::active_paras(), vec![ + (user_id(2), Some((col.clone(), Retriable::WithRetries(2)))) + ]); + }); + } + + #[test] + fn parathread_auction_handles_basic_errors() { + with_externalities(&mut new_test_ext(vec![]), || { + run_to_block(2); + let o = Origin::signed(0); + assert_ok!(Registrar::register_parathread(o, vec![7, 8, 9], vec![1, 1, 1])); + + run_to_block(3); + assert_eq!( + Registrar::paras(&user_id(0)), + Some(ParaInfo { scheduling: Scheduling::Dynamic }) + ); + + let good_para_id = user_id(0); + let bad_para_id = user_id(1); + let bad_head_hash = <Test as system::Trait>::Hashing::hash(&vec![1, 2, 1]); + let good_head_hash = <Test as system::Trait>::Hashing::hash(&vec![1, 1, 1]); + let info = DispatchInfo::default(); + + // Allow for threads + assert_ok!(Registrar::set_thread_count(Origin::ROOT, 10)); + + // Bad parathread id + let col = CollatorId::default(); + let inner = super::Call::select_parathread(bad_para_id, col.clone(), good_head_hash); + let call = Call::Registrar(inner); + assert!( + LimitParathreadCommits::<Test>(std::marker::PhantomData) + .validate(&0, &call, info, 0).is_err() + ); + + // Bad head data + let inner = super::Call::select_parathread(good_para_id, col.clone(), bad_head_hash); + let call = Call::Registrar(inner); + assert!( + LimitParathreadCommits::<Test>(std::marker::PhantomData) + .validate(&0, &call, info, 0).is_err() + ); + + // No duplicates + let inner = super::Call::select_parathread(good_para_id, col.clone(), good_head_hash); + let call = Call::Registrar(inner); + assert!( + LimitParathreadCommits::<Test>(std::marker::PhantomData) + .validate(&0, &call, info, 0).is_ok() + ); + assert!( + LimitParathreadCommits::<Test>(std::marker::PhantomData) + .validate(&0, &call, info, 0).is_err() + ); + }); + } + + #[test] + fn parathread_auction_works() { + with_externalities(&mut new_test_ext(vec![]), || { + run_to_block(2); + // Register 5 parathreads + for x in 0..5 { + let o = Origin::signed(x as u64); + assert_ok!(Registrar::register_parathread(o, vec![x; 3], vec![x; 3])); + } + + run_to_block(3); + + for x in 0..5 { + assert_eq!( + Registrar::paras(&user_id(x)), + Some(ParaInfo { scheduling: Scheduling::Dynamic }) + ); + } + + // Only 3 slots available... who will win?? + assert_ok!(Registrar::set_thread_count(Origin::ROOT, 3)); + + // Everyone wants a thread + for x in 0..5 { + let para_id = user_id(x as u32); + let collator_id = CollatorId::default(); + let head_hash = <Test as system::Trait>::Hashing::hash(&vec![x; 3]); + let inner = super::Call::select_parathread(para_id, collator_id, head_hash); + let call = Call::Registrar(inner); + let info = DispatchInfo::default(); + + // First 3 transactions win a slot + if x < 3 { + assert!( + LimitParathreadCommits::<Test>(std::marker::PhantomData) + .validate(&0, &call, info, 0) + .is_ok() + ); + } else { + // All others lose + assert_noop!( + LimitParathreadCommits::<Test>(std::marker::PhantomData) + .validate(&0, &call, info, 0), + InvalidTransaction::ExhaustsResources.into() + ); + } + } + + // 3 Threads are selected + assert_eq!( + SelectedThreads::get()[1], + vec![ + (user_id(0), CollatorId::default()), + (user_id(1), CollatorId::default()), + (user_id(2), CollatorId::default()), + ] + ); + + // Assuming Queue Size is 2 + assert_eq!(<Test as self::Trait>::QueueSize::get(), 2); + + // 2 blocks later + run_to_block(5); + // Threads left queue + assert_eq!(SelectedThreads::get()[0], vec![]); + // Threads are active + assert_eq!( + Registrar::active_paras(), + vec![ + (user_id(0), Some((CollatorId::default(), Retriable::WithRetries(0)))), + (user_id(1), Some((CollatorId::default(), Retriable::WithRetries(0)))), + (user_id(2), Some((CollatorId::default(), Retriable::WithRetries(0)))), + ] + ); + }); + } +} diff --git a/polkadot/runtime/src/slots.rs b/polkadot/runtime/src/slots.rs index 944c6b6057e..5c4f04f56b5 100644 --- a/polkadot/runtime/src/slots.rs +++ b/polkadot/runtime/src/slots.rs @@ -19,20 +19,21 @@ //! information for commissioning and decommissioning them. use rstd::{prelude::*, mem::swap, convert::TryInto}; -use sr_primitives::traits::{CheckedSub, StaticLookup, Zero, One, CheckedConversion, Hash}; +use sr_primitives::traits::{CheckedSub, StaticLookup, Zero, One, CheckedConversion, Hash, AccountIdConversion}; use sr_primitives::weights::SimpleDispatchInfo; -use codec::{Encode, Decode}; +use codec::{Encode, Decode, Codec}; use srml_support::{ decl_module, decl_storage, decl_event, ensure, traits::{Currency, ReservableCurrency, WithdrawReason, ExistenceRequirement, Get}, }; -use primitives::parachain::AccountIdConversion; -use crate::parachains::ParachainRegistrar; +use primitives::parachain::{ + SwapAux, PARACHAIN_INFO, Id as ParaId +}; use system::{ensure_signed, ensure_root}; +use crate::registrar::{Registrar, swap_ordered_existence}; use crate::slot_range::{SlotRange, SLOT_RANGE_COUNT}; type BalanceOf<T> = <<T as Trait>::Currency as Currency<<T as system::Trait>::AccountId>>::Balance; -type ParaIdOf<T> = <<T as Trait>::Parachains as ParachainRegistrar<<T as system::Trait>::AccountId>>::ParaId; /// The module's configuration trait. pub trait Trait: system::Trait { @@ -43,7 +44,7 @@ pub trait Trait: system::Trait { type Currency: ReservableCurrency<Self::AccountId>; /// The parachain registrar type. - type Parachains: ParachainRegistrar<Self::AccountId>; + type Parachains: Registrar<Self::AccountId>; /// The number of blocks over which an auction may be retroactively ended. type EndingPeriod: Get<Self::BlockNumber>; @@ -74,7 +75,7 @@ pub struct NewBidder<AccountId> { /// The desired target of a bidder in an auction. #[derive(Clone, Eq, PartialEq, Encode, Decode)] #[cfg_attr(feature = "std", derive(Debug))] -pub enum Bidder<AccountId, ParaId> { +pub enum Bidder<AccountId> { /// An account ID, funds coming from that account. New(NewBidder<AccountId>), @@ -83,7 +84,7 @@ pub enum Bidder<AccountId, ParaId> { Existing(ParaId), } -impl<AccountId: Clone, ParaId: AccountIdConversion<AccountId>> Bidder<AccountId, ParaId> { +impl<AccountId: Clone + Default + Codec> Bidder<AccountId> { /// Get the account that will fund this bid. fn funding_account(&self) -> AccountId { match self { @@ -110,10 +111,12 @@ pub enum IncomingParachain<AccountId, Hash> { type LeasePeriodOf<T> = <T as system::Trait>::BlockNumber; // Winning data type. This encodes the top bidders of each range together with their bid. -type WinningData<T> = [Option<(Bidder<<T as system::Trait>::AccountId, ParaIdOf<T>>, BalanceOf<T>)>; SLOT_RANGE_COUNT]; +type WinningData<T> = + [Option<(Bidder<<T as system::Trait>::AccountId>, BalanceOf<T>)>; SLOT_RANGE_COUNT]; // Winners data type. This encodes each of the final winners of a parachain auction, the parachain // index assigned to them, their winning bid and the range that they won. -type WinnersData<T> = Vec<(Option<NewBidder<<T as system::Trait>::AccountId>>, ParaIdOf<T>, BalanceOf<T>, SlotRange)>; +type WinnersData<T> = + Vec<(Option<NewBidder<<T as system::Trait>::AccountId>>, ParaId, BalanceOf<T>, SlotRange)>; // This module's storage items. decl_storage! { @@ -122,9 +125,9 @@ decl_storage! { /// The number of auctions that been started so far. pub AuctionCounter get(auction_counter): AuctionIndex; - /// All `ParaId` values that are managed by this module. This includes chains that are not - /// yet deployed (but have won an auction in the future). - pub ManagedIds get(managed_ids): Vec<ParaIdOf<T>>; + /// Ordered list of all `ParaId` values that are managed by this module. This includes + /// chains that are not yet deployed (but have won an auction in the future). + pub ManagedIds get(managed_ids): Vec<ParaId>; /// Various amounts on deposit for each parachain. An entry in `ManagedIds` implies a non- /// default entry here. @@ -139,7 +142,7 @@ decl_storage! { /// If a parachain doesn't exist *yet* but is scheduled to exist in the future, then it /// will be left-padded with one or more zeroes to denote the fact that nothing is held on /// deposit for the non-existent chain currently, but is held at some point in the future. - pub Deposits get(deposits): map ParaIdOf<T> => Vec<BalanceOf<T>>; + pub Deposits get(deposits): map ParaId => Vec<BalanceOf<T>>; /// Information relating to the current auction, if there is one. /// @@ -155,22 +158,37 @@ decl_storage! { /// Amounts currently reserved in the accounts of the bidders currently winning /// (sub-)ranges. - pub ReservedAmounts get(reserved_amounts): map Bidder<T::AccountId, ParaIdOf<T>> => Option<BalanceOf<T>>; + pub ReservedAmounts get(reserved_amounts): map Bidder<T::AccountId> => Option<BalanceOf<T>>; /// The set of Para IDs that have won and need to be on-boarded at an upcoming lease-period. /// This is cleared out on the first block of the lease period. - pub OnboardQueue get(onboard_queue): map LeasePeriodOf<T> => Vec<ParaIdOf<T>>; + pub OnboardQueue get(onboard_queue): map LeasePeriodOf<T> => Vec<ParaId>; /// The actual on-boarding information. Only exists when one of the following is true: /// - It is before the lease period that the parachain should be on-boarded. /// - The full on-boarding information has not yet been provided and the parachain is not /// yet due to be off-boarded. - pub Onboarding get(onboarding): map ParaIdOf<T> => + pub Onboarding get(onboarding): map ParaId => Option<(LeasePeriodOf<T>, IncomingParachain<T::AccountId, T::Hash>)>; /// Off-boarding account; currency held on deposit for the parachain gets placed here if the /// parachain gets off-boarded; i.e. its lease period is up and it isn't renewed. - pub Offboarding get(offboarding): map ParaIdOf<T> => T::AccountId; + pub Offboarding get(offboarding): map ParaId => T::AccountId; + } +} + +impl<T: Trait> SwapAux for Module<T> { + fn ensure_can_swap(one: ParaId, other: ParaId) -> Result<(), &'static str> { + if <Onboarding<T>>::exists(one) || <Onboarding<T>>::exists(other) { + Err("can't swap an undeployed parachain")? + } + Ok(()) + } + fn on_swap(one: ParaId, other: ParaId) -> Result<(), &'static str> { + <Offboarding<T>>::swap(one, other); + <Deposits<T>>::swap(one, other); + ManagedIds::mutate(|ids| swap_ordered_existence(ids, one, other)); + Ok(()) } } @@ -179,7 +197,7 @@ decl_event!( AccountId = <T as system::Trait>::AccountId, BlockNumber = <T as system::Trait>::BlockNumber, LeasePeriod = LeasePeriodOf<T>, - ParaId = ParaIdOf<T>, + ParaId = ParaId, Balance = BalanceOf<T>, { /// A new lease period is beginning. @@ -281,7 +299,7 @@ decl_module! { /// - `amount` is the amount to bid to be held as deposit for the parachain should the /// bid win. This amount is held throughout the range. #[weight = SimpleDispatchInfo::FixedNormal(500_000)] - fn bid(origin, + pub fn bid(origin, #[compact] sub: SubId, #[compact] auction_index: AuctionIndex, #[compact] first_slot: LeasePeriodOf<T>, @@ -308,7 +326,7 @@ decl_module! { /// absolute lease period index value, not an auction-specific offset. /// - `amount` is the amount to bid to be held as deposit for the parachain should the /// bid win. This amount is held throughout the range. - #[weight = SimpleDispatchInfo::FixedNormal(500_000)] + #[weight = SimpleDispatchInfo::FixedNormal(500_000)] fn bid_renew(origin, #[compact] auction_index: AuctionIndex, #[compact] first_slot: LeasePeriodOf<T>, @@ -316,7 +334,7 @@ decl_module! { #[compact] amount: BalanceOf<T> ) { let who = ensure_signed(origin)?; - let para_id = <ParaIdOf<T>>::try_from_account(&who) + let para_id = <ParaId>::try_from_account(&who) .ok_or("account is not a parachain")?; let bidder = Bidder::Existing(para_id); Self::handle_bid(bidder, auction_index, first_slot, last_slot, amount)?; @@ -328,10 +346,10 @@ decl_module! { /// /// - `dest` is the destination account to receive the parachain's deposit. #[weight = SimpleDispatchInfo::FixedNormal(1_000_000)] - fn set_offboarding(origin, dest: <T::Lookup as StaticLookup>::Source) { + pub fn set_offboarding(origin, dest: <T::Lookup as StaticLookup>::Source) { let who = ensure_signed(origin)?; let dest = T::Lookup::lookup(dest)?; - let para_id = <ParaIdOf<T>>::try_from_account(&who) + let para_id = <ParaId>::try_from_account(&who) .ok_or("not a parachain origin")?; <Offboarding<T>>::insert(para_id, dest); } @@ -343,10 +361,10 @@ decl_module! { /// - `para_id` is the parachain ID allotted to the winning bidder. /// - `code_hash` is the hash of the parachain's Wasm validation function. /// - `initial_head_data` is the parachain's initial head data. - #[weight = SimpleDispatchInfo::FixedNormal(500_000)] + #[weight = SimpleDispatchInfo::FixedNormal(500_000)] pub fn fix_deploy_data(origin, #[compact] sub: SubId, - #[compact] para_id: ParaIdOf<T>, + #[compact] para_id: ParaId, code_hash: T::Hash, initial_head_data: Vec<u8> ) { @@ -375,7 +393,7 @@ decl_module! { /// - `para_id` is the parachain ID whose code will be elaborated. /// - `code` is the preimage of the registered `code_hash` of `para_id`. #[weight = SimpleDispatchInfo::FixedNormal(5_000_000)] - fn elaborate_deploy_data(_origin, #[compact] para_id: ParaIdOf<T>, code: Vec<u8>) { + pub fn elaborate_deploy_data(_origin, #[compact] para_id: ParaId, code: Vec<u8>) { let (starts, details) = <Onboarding<T>>::get(¶_id) .ok_or("parachain id not in onboarding")?; if let IncomingParachain::Fixed{code_hash, initial_head_data} = details { @@ -388,7 +406,8 @@ decl_module! { // Should have already begun. Remove the on-boarding entry and register the // parachain for its immediate start. <Onboarding<T>>::remove(¶_id); - let _ = T::Parachains::register_parachain(para_id, code, initial_head_data); + let _ = T::Parachains:: + register_para(para_id, PARACHAIN_INFO, code, initial_head_data); } } else { return Err("deploy data not yet fixed") @@ -399,7 +418,7 @@ decl_module! { impl<T: Trait> Module<T> { /// Deposit currently held for a particular parachain that we administer. - fn deposit_held(para_id: &ParaIdOf<T>) -> BalanceOf<T> { + fn deposit_held(para_id: &ParaId) -> BalanceOf<T> { <Deposits<T>>::get(para_id).into_iter().max().unwrap_or_else(Zero::zero) } @@ -493,8 +512,14 @@ impl<T: Trait> Module<T> { // Add para IDs of any chains that will be newly deployed to our set of managed // IDs. - <ManagedIds<T>>::mutate(|m| m.push(para_id)); - + ManagedIds::mutate(|ids| + if let Err(pos) = ids.binary_search(¶_id) { + ids.insert(pos, para_id) + } else { + // This can't happen as it's a winner being newly + // deployed and thus the para_id shouldn't already be being managed. + } + ); Self::deposit_event(RawEvent::WonDeploy(bidder.clone(), range, para_id, amount)); // Add a deployment record so we know to on-board them at the appropriate @@ -573,7 +598,7 @@ impl<T: Trait> Module<T> { Self::deposit_event(RawEvent::NewLeasePeriod(lease_period_index)); // First, bump off old deposits and decommission any managed chains that are coming // to a close. - <ManagedIds<T>>::mutate(|ids| { + ManagedIds::mutate(|ids| { let new = ids.drain(..).filter(|id| { let mut d = <Deposits<T>>::get(id); if !d.is_empty() { @@ -586,7 +611,7 @@ impl<T: Trait> Module<T> { // Only unregister it if it was actually registered in the first place. // If the on-boarding entry still existed, then it was never actually // commissioned. - let _ = T::Parachains::deregister_parachain(id.clone()); + let _ = T::Parachains::deregister_para(id.clone()); } // Return the full deposit to the off-boarding account. T::Currency::deposit_creating(&<Offboarding<T>>::take(id), d[0]); @@ -628,7 +653,8 @@ impl<T: Trait> Module<T> { { // The chain's deployment data is set; go ahead and register it, and remove the // now-redundant on-boarding entry. - let _ = T::Parachains::register_parachain(para_id.clone(), code, initial_head_data); + let _ = T::Parachains:: + register_para(para_id.clone(), PARACHAIN_INFO, code, initial_head_data); // ^^ not much we can do if it fails for some reason. <Onboarding<T>>::remove(para_id) } @@ -644,7 +670,7 @@ impl<T: Trait> Module<T> { /// - `last_slot`: The last lease period index of the range to be bid on (inclusive). /// - `amount`: The total amount to be the bid for deposit over the range. pub fn handle_bid( - bidder: Bidder<T::AccountId, ParaIdOf<T>>, + bidder: Bidder<T::AccountId>, auction_index: u32, first_slot: LeasePeriodOf<T>, last_slot: LeasePeriodOf<T>, @@ -735,7 +761,7 @@ impl<T: Trait> Module<T> { /// https://github.com/w3f/consensus/blob/master/NPoS/auctiondynamicthing.py fn calculate_winners( mut winning: WinningData<T>, - new_id: impl Fn() -> ParaIdOf<T> + new_id: impl Fn() -> ParaId ) -> WinnersData<T> { let winning_ranges = { let mut best_winners_ending_at: @@ -783,7 +809,6 @@ impl<T: Trait> Module<T> { } } - /// tests for this module #[cfg(test)] mod tests { @@ -799,7 +824,7 @@ mod tests { }; use srml_support::{impl_outer_origin, parameter_types, assert_ok, assert_noop}; use balances; - use primitives::parachain::Id as ParaId; + use primitives::parachain::{Id as ParaId, Info as ParaInfo}; impl_outer_origin! { pub enum Origin for Test {} @@ -866,16 +891,16 @@ mod tests { } pub struct TestParachains; - impl ParachainRegistrar<u64> for TestParachains { - type ParaId = ParaId; - fn new_id() -> Self::ParaId { + impl Registrar<u64> for TestParachains { + fn new_id() -> ParaId { PARACHAIN_COUNT.with(|p| { *p.borrow_mut() += 1; (*p.borrow() - 1).into() }) } - fn register_parachain( - id: Self::ParaId, + fn register_para( + id: ParaId, + _info: ParaInfo, code: Vec<u8>, initial_head_data: Vec<u8> ) -> Result<(), &'static str> { @@ -887,7 +912,7 @@ mod tests { Ok(()) }) } - fn deregister_parachain(id: Self::ParaId) -> Result<(), &'static str> { + fn deregister_para(id: ParaId) -> Result<(), &'static str> { PARACHAINS.with(|p| { if !p.borrow().contains_key(&id.into_inner()) { panic!("ID doesn't exist") diff --git a/polkadot/runtime/src/testing.rs b/polkadot/runtime/src/testing.rs new file mode 100644 index 00000000000..e69de29bb2d diff --git a/polkadot/service/src/chain_spec.rs b/polkadot/service/src/chain_spec.rs index 25943a2b1ad..2dde54059ca 100644 --- a/polkadot/service/src/chain_spec.rs +++ b/polkadot/service/src/chain_spec.rs @@ -22,7 +22,7 @@ use polkadot_runtime::{ GenesisConfig, CouncilConfig, ElectionsConfig, DemocracyConfig, SystemConfig, SessionConfig, StakingConfig, BalancesConfig, SessionKeys, TechnicalCommitteeConfig, SudoConfig, IndicesConfig, StakerStatus, WASM_BINARY, - ClaimsConfig, ParachainsConfig + ClaimsConfig, ParachainsConfig, RegistrarConfig }; use polkadot_runtime::constants::{currency::DOTS, time::*}; use sr_primitives::Perbill; @@ -151,6 +151,8 @@ fn staging_testnet_config_genesis() -> GenesisConfig { authority_discovery: Some(Default::default()), parachains: Some(ParachainsConfig { authorities: vec![], + }), + registrar: Some(RegistrarConfig { parachains: vec![], _phdata: Default::default(), }), @@ -289,6 +291,8 @@ pub fn testnet_genesis( authority_discovery: Some(Default::default()), parachains: Some(ParachainsConfig { authorities: vec![], + }), + registrar: Some(RegistrarConfig{ parachains: vec![], _phdata: Default::default(), }), diff --git a/polkadot/test-parachains/adder/wasm/Cargo.toml b/polkadot/test-parachains/adder/wasm/Cargo.toml new file mode 100644 index 00000000000..e69de29bb2d diff --git a/polkadot/validation/src/evaluation.rs b/polkadot/validation/src/evaluation.rs index 0099f489a09..778a735925f 100644 --- a/polkadot/validation/src/evaluation.rs +++ b/polkadot/validation/src/evaluation.rs @@ -20,7 +20,7 @@ use super::MAX_TRANSACTIONS_SIZE; use codec::Encode; use polkadot_primitives::{Block, Hash, BlockNumber}; -use polkadot_primitives::parachain::Id as ParaId; +use polkadot_primitives::parachain::{Id as ParaId, CollatorId, Retriable}; /// Result type alias for block evaluation pub type Result<T> = std::result::Result<T, Error>; @@ -66,7 +66,7 @@ pub fn evaluate_initial( _now: u64, parent_hash: &Hash, parent_number: BlockNumber, - _active_parachains: &[ParaId], + _active_parachains: &[(ParaId, Option<(CollatorId, Retriable)>)], ) -> Result<()> { let transactions_size = proposal.extrinsics.iter().fold(0, |a, tx| { a + Encode::encode(tx).len() diff --git a/polkadot/validation/src/lib.rs b/polkadot/validation/src/lib.rs index ddf6d538dc6..8382ec0a034 100644 --- a/polkadot/validation/src/lib.rs +++ b/polkadot/validation/src/lib.rs @@ -763,7 +763,7 @@ impl<C, TxApi> CreateProposal<C, TxApi> where self.believed_minimum_timestamp, &self.parent_hash, self.parent_number, - &active_parachains, + &active_parachains[..], ).is_ok()); Ok(new_block) -- GitLab