//! The Joystream Substrate Node runtime. #![cfg_attr(not(feature = "std"), no_std)] // `construct_runtime!` does a lot of recursion and requires us to increase the limit to 256. #![recursion_limit = "256"] // srml_staking_reward_curve::build! - substrate macro produces a warning. // TODO: remove after post-Rome substrate upgrade #![allow(array_into_iter)] // Runtime integration tests mod tests; // Make the WASM binary available. // This is required only by the node build. // A dummy wasm_binary.rs will be built for the IDE. #[cfg(feature = "std")] include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); mod integration; use authority_discovery_primitives::{ AuthorityId as EncodedAuthorityId, Signature as EncodedSignature, }; use babe_primitives::{AuthorityId as BabeId, AuthoritySignature as BabeSignature}; use codec::{Decode, Encode}; use grandpa::fg_primitives; use grandpa::{AuthorityId as GrandpaId, AuthorityWeight as GrandpaWeight}; use im_online::sr25519::AuthorityId as ImOnlineId; use primitives::OpaqueMetadata; use rstd::prelude::*; use sr_primitives::curve::PiecewiseLinear; use sr_primitives::traits::{ BlakeTwo256, Block as BlockT, IdentifyAccount, NumberFor, StaticLookup, Verify, }; use sr_primitives::weights::Weight; use sr_primitives::{ create_runtime_str, generic, impl_opaque_keys, transaction_validity::TransactionValidity, ApplyResult, MultiSignature, }; use substrate_client::{ block_builder::api::{self as block_builder_api, CheckInherentsResult, InherentData}, impl_runtime_apis, runtime_api as client_api, }; use system::offchain::TransactionSubmitter; #[cfg(feature = "std")] use version::NativeVersion; use version::RuntimeVersion; // A few exports that help ease life for downstream crates. pub use balances::Call as BalancesCall; #[cfg(any(feature = "std", test))] pub use sr_primitives::BuildStorage; pub use sr_primitives::{Perbill, Permill}; pub use srml_support::{ construct_runtime, parameter_types, traits::Currency, traits::Imbalance, traits::Randomness, StorageLinkedMap, StorageMap, StorageValue, }; pub use staking::StakerStatus; pub use timestamp::Call as TimestampCall; use integration::proposals::{CouncilManager, ExtrinsicProposalEncoder, MembershipOriginValidator}; pub use proposals_codex::ProposalsConfigParameters; /// An index to a block. pub type BlockNumber = u32; /// Alias to 512-bit hash when used in the context of a transaction signature on the chain. pub type Signature = MultiSignature; /// Some way of identifying an account on the chain. We intentionally make it equivalent /// to the public key of our transaction signing scheme. pub type AccountId = <::Signer as IdentifyAccount>::AccountId; /// The type for looking up accounts. We don't expect more than 4 billion of them, but you /// never know... pub type AccountIndex = u32; /// Balance of an account. pub type Balance = u128; /// Index of a transaction in the chain. pub type Index = u32; /// A hash of some data used by the chain. pub type Hash = primitives::H256; /// Digest item type. pub type DigestItem = generic::DigestItem; /// Moment type pub type Moment = u64; /// Credential type pub type Credential = u64; /// Represents a thread identifier for both Forum and Proposals Discussion /// /// Note: Both modules expose type names ThreadId and PostId (which are defined on their Trait) and /// used in state storage and dispatchable method's argument types, /// and are therefore part of the public API/metadata of the runtime. /// In the currenlty version the polkadot-js/api that is used and is compatible with the runtime, /// the type registry has flat namespace and its not possible /// to register identically named types from different modules, separately. And so we MUST configure /// the underlying types to be identicaly to avoid issues with encoding/decoding these types on the client side. pub type ThreadId = u64; /// Represents a post identifier for both Forum and Proposals Discussion /// /// See the Note about ThreadId pub type PostId = u64; /// Represent an actor in membership group, which is the same in the working groups. pub type ActorId = u64; /// Opaque types. These are used by the CLI to instantiate machinery that don't need to know /// the specifics of the runtime. They can then be made to be agnostic over specific formats /// of data like extrinsics, allowing for them to continue syncing the network through upgrades /// to even the core datastructures. pub mod opaque { use super::*; pub use sr_primitives::OpaqueExtrinsic as UncheckedExtrinsic; /// Opaque block header type. pub type Header = generic::Header; /// Opaque block type. pub type Block = generic::Block; /// Opaque block identifier type. pub type BlockId = generic::BlockId; pub type SessionHandlers = (Grandpa, Babe, ImOnline); impl_opaque_keys! { pub struct SessionKeys { pub grandpa: Grandpa, pub babe: Babe, pub im_online: ImOnline, } } } /// This runtime version. pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("joystream-node"), impl_name: create_runtime_str!("joystream-node"), authoring_version: 6, spec_version: 15, impl_version: 0, apis: RUNTIME_API_VERSIONS, }; /// Constants for Babe. /// Since BABE is probabilistic this is the average expected block time that /// we are targetting. Blocks will be produced at a minimum duration defined /// by `SLOT_DURATION`, but some slots will not be allocated to any /// authority and hence no block will be produced. We expect to have this /// block time on average following the defined slot duration and the value /// of `c` configured for BABE (where `1 - c` represents the probability of /// a slot being empty). /// This value is only used indirectly to define the unit constants below /// that are expressed in blocks. The rest of the code should use /// `SLOT_DURATION` instead (like the timestamp module for calculating the /// minimum period). /// pub const MILLISECS_PER_BLOCK: Moment = 6000; pub const SECS_PER_BLOCK: Moment = MILLISECS_PER_BLOCK / 1000; pub const SLOT_DURATION: Moment = 6000; pub const EPOCH_DURATION_IN_BLOCKS: BlockNumber = 10 * MINUTES; pub const EPOCH_DURATION_IN_SLOTS: u64 = { const SLOT_FILL_RATE: f64 = MILLISECS_PER_BLOCK as f64 / SLOT_DURATION as f64; (EPOCH_DURATION_IN_BLOCKS as f64 * SLOT_FILL_RATE) as u64 }; // These time units are defined in number of blocks. pub const MINUTES: BlockNumber = 60 / (SECS_PER_BLOCK as BlockNumber); pub const HOURS: BlockNumber = MINUTES * 60; pub const DAYS: BlockNumber = HOURS * 24; // 1 in 4 blocks (on average, not counting collisions) will be primary babe blocks. pub const PRIMARY_PROBABILITY: (u64, u64) = (1, 4); /// The version infromation used to identify this runtime when compiled natively. #[cfg(feature = "std")] pub fn native_version() -> NativeVersion { NativeVersion { runtime_version: VERSION, can_author_with: Default::default(), } } parameter_types! { pub const BlockHashCount: BlockNumber = 250; pub const MaximumBlockWeight: Weight = 1_000_000_000; pub const AvailableBlockRatio: Perbill = Perbill::from_percent(75); pub const MaximumBlockLength: u32 = 5 * 1024 * 1024; pub const Version: RuntimeVersion = VERSION; } impl system::Trait for Runtime { /// The identifier used to distinguish between accounts. type AccountId = AccountId; /// The aggregated dispatch type that is available for extrinsics. type Call = Call; /// The lookup mechanism to get account ID from whatever is passed in dispatchers. type Lookup = Indices; /// The index type for storing how many extrinsics an account has signed. type Index = Index; /// The index type for blocks. type BlockNumber = BlockNumber; /// The type for hashing blocks and tries. type Hash = Hash; /// The hashing algorithm used. type Hashing = BlakeTwo256; /// The header type. type Header = generic::Header; /// The ubiquitous event type. type Event = Event; /// The ubiquitous origin type. type Origin = Origin; /// Maximum number of block number to block hash mappings to keep (oldest pruned first). type BlockHashCount = BlockHashCount; /// Maximum weight of each block. With a default weight system of 1byte == 1weight, 4mb is ok. type MaximumBlockWeight = MaximumBlockWeight; /// Maximum size of all encoded transactions (in bytes) that are allowed in one block. type MaximumBlockLength = MaximumBlockLength; /// Portion of the block weight that is available to all normal transactions. type AvailableBlockRatio = AvailableBlockRatio; type Version = Version; } parameter_types! { pub const EpochDuration: u64 = EPOCH_DURATION_IN_SLOTS as u64; pub const ExpectedBlockTime: Moment = MILLISECS_PER_BLOCK; } impl babe::Trait for Runtime { type EpochDuration = EpochDuration; type ExpectedBlockTime = ExpectedBlockTime; type EpochChangeTrigger = babe::ExternalTrigger; } impl grandpa::Trait for Runtime { type Event = Event; } impl indices::Trait for Runtime { /// The type for recording indexing into the account enumeration. If this ever overflows, there /// will be problems! type AccountIndex = u32; /// Use the standard means of resolving an index hint from an id. type ResolveHint = indices::SimpleResolveHint; /// Determine whether an account is dead. type IsDeadAccount = Balances; /// The ubiquitous event type. type Event = Event; } parameter_types! { pub const MinimumPeriod: Moment = SLOT_DURATION / 2; } impl timestamp::Trait for Runtime { /// A timestamp: milliseconds since the unix epoch. type Moment = Moment; type OnTimestampSet = Babe; type MinimumPeriod = MinimumPeriod; } parameter_types! { pub const ExistentialDeposit: u128 = 0; pub const TransferFee: u128 = 0; pub const CreationFee: u128 = 0; pub const TransactionBaseFee: u128 = 1; pub const TransactionByteFee: u128 = 0; pub const InitialMembersBalance: u32 = 2000; } impl balances::Trait for Runtime { /// The type for recording an account's balance. type Balance = Balance; /// What to do if an account's free balance gets zeroed. type OnFreeBalanceZero = (Staking, Session); /// What to do if a new account is created. type OnNewAccount = (); // Indices; // disable use of Indices feature /// The ubiquitous event type. type Event = Event; type DustRemoval = (); type TransferPayment = (); type ExistentialDeposit = ExistentialDeposit; type TransferFee = TransferFee; type CreationFee = CreationFee; } impl transaction_payment::Trait for Runtime { type Currency = Balances; type OnTransactionPayment = (); type TransactionBaseFee = TransactionBaseFee; type TransactionByteFee = TransactionByteFee; type WeightToFee = (); type FeeMultiplierUpdate = (); // FeeMultiplierUpdateHandler; } impl sudo::Trait for Runtime { type Event = Event; type Proposal = Call; } parameter_types! { pub const UncleGenerations: BlockNumber = 5; } impl authorship::Trait for Runtime { type FindAuthor = session::FindAccountFromAuthorIndex; type UncleGenerations = UncleGenerations; type FilterUncle = (); type EventHandler = Staking; } type SessionHandlers = (Grandpa, Babe, ImOnline); impl_opaque_keys! { pub struct SessionKeys { pub grandpa: Grandpa, pub babe: Babe, pub im_online: ImOnline, } } // NOTE: `SessionHandler` and `SessionKeys` are co-dependent: One key will be used for each handler. // The number and order of items in `SessionHandler` *MUST* be the same number and order of keys in // `SessionKeys`. // TODO: Introduce some structure to tie these together to make it a bit less of a footgun. This // should be easy, since OneSessionHandler trait provides the `Key` as an associated type. #2858 parameter_types! { pub const DisabledValidatorsThreshold: Perbill = Perbill::from_percent(17); } impl session::Trait for Runtime { type OnSessionEnding = Staking; type SessionHandler = SessionHandlers; type ShouldEndSession = Babe; type Event = Event; type Keys = SessionKeys; type ValidatorId = AccountId; type ValidatorIdOf = staking::StashOf; type SelectInitialValidators = Staking; type DisabledValidatorsThreshold = DisabledValidatorsThreshold; } impl session::historical::Trait for Runtime { type FullIdentification = staking::Exposure; type FullIdentificationOf = staking::ExposureOf; } srml_staking_reward_curve::build! { const REWARD_CURVE: PiecewiseLinear<'static> = curve!( min_inflation: 0_025_000, max_inflation: 0_300_000, ideal_stake: 0_300_000, falloff: 0_050_000, max_piece_count: 100, test_precision: 0_005_000, ); } parameter_types! { pub const SessionsPerEra: sr_staking_primitives::SessionIndex = 6; pub const BondingDuration: staking::EraIndex = 24; pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE; } impl staking::Trait for Runtime { type Currency = Balances; type Time = Timestamp; type CurrencyToVote = common::currency::CurrencyToVoteHandler; type RewardRemainder = (); type Event = Event; type Slash = (); // where to send the slashed funds. type Reward = (); // rewards are minted from the void type SessionsPerEra = SessionsPerEra; type BondingDuration = BondingDuration; type SessionInterface = Self; type RewardCurve = RewardCurve; } type SubmitTransaction = TransactionSubmitter; parameter_types! { pub const SessionDuration: BlockNumber = EPOCH_DURATION_IN_SLOTS as _; } impl im_online::Trait for Runtime { type AuthorityId = ImOnlineId; type Call = Call; type Event = Event; type SubmitTransaction = SubmitTransaction; type ReportUnresponsiveness = Offences; type SessionDuration = SessionDuration; } impl offences::Trait for Runtime { type Event = Event; type IdentificationTuple = session::historical::IdentificationTuple; type OnOffenceHandler = Staking; } impl authority_discovery::Trait for Runtime { type AuthorityId = BabeId; } parameter_types! { pub const WindowSize: BlockNumber = 101; pub const ReportLatency: BlockNumber = 1000; } impl finality_tracker::Trait for Runtime { type OnFinalizationStalled = Grandpa; type WindowSize = WindowSize; type ReportLatency = ReportLatency; } pub use forum; pub use governance::election_params::ElectionParameters; use governance::{council, election}; use membership::members; use storage::{data_directory, data_object_storage_registry, data_object_type_registry}; pub use versioned_store; pub use content_working_group as content_wg; mod migration; use roles::actors; /// Alias for ContentId, used in various places. pub type ContentId = primitives::H256; impl versioned_store::Trait for Runtime { type Event = Event; } impl versioned_store_permissions::Trait for Runtime { type Credential = Credential; type CredentialChecker = (ContentWorkingGroupCredentials, SudoKeyHasAllCredentials); type CreateClassPermissionsChecker = ContentLeadOrSudoKeyCanCreateClasses; } // Credential Checker that gives the sudo key holder all credentials pub struct SudoKeyHasAllCredentials {} impl versioned_store_permissions::CredentialChecker for SudoKeyHasAllCredentials { fn account_has_credential( account: &AccountId, _credential: ::Credential, ) -> bool { >::key() == *account } } parameter_types! { pub const CurrentLeadCredential: Credential = 0; pub const AnyActiveCuratorCredential: Credential = 1; pub const AnyActiveChannelOwnerCredential: Credential = 2; pub const PrincipalIdMappingStartsAtCredential: Credential = 1000; } pub struct ContentWorkingGroupCredentials {} impl versioned_store_permissions::CredentialChecker for ContentWorkingGroupCredentials { fn account_has_credential( account: &AccountId, credential: ::Credential, ) -> bool { match credential { // Credentials from 0..999 represents groups or more complex requirements // Current Lead if set credential if credential == CurrentLeadCredential::get() => { match >::ensure_lead_is_set() { Ok((_, lead)) => lead.role_account == *account, _ => false, } } // Any Active Curator credential if credential == AnyActiveCuratorCredential::get() => { // Look for a Curator with a matching role account for (_principal_id, principal) in >::enumerate() { if let content_wg::Principal::Curator(curator_id) = principal { let curator = >::get(curator_id); if curator.role_account == *account && curator.stage == content_wg::CuratorRoleStage::Active { return true; } } } false } // Any Active Channel Owner credential if credential == AnyActiveChannelOwnerCredential::get() => { // Look for a ChannelOwner with a matching role account for (_principal_id, principal) in >::enumerate() { if let content_wg::Principal::ChannelOwner(channel_id) = principal { let channel = >::get(channel_id); if channel.role_account == *account { return true; // should we also take publishing_status/curation_status into account ? } } } false } // mapping to workging group principal id n if n >= PrincipalIdMappingStartsAtCredential::get() => { >::account_has_credential( account, n - PrincipalIdMappingStartsAtCredential::get(), ) } _ => false, } } } // Allow sudo key holder permission to create classes pub struct SudoKeyCanCreateClasses {} impl versioned_store_permissions::CreateClassPermissionsChecker for SudoKeyCanCreateClasses { fn account_can_create_class_permissions(account: &AccountId) -> bool { >::key() == *account } } pub struct ContentLeadOrSudoKeyCanCreateClasses {} impl versioned_store_permissions::CreateClassPermissionsChecker for ContentLeadOrSudoKeyCanCreateClasses { fn account_can_create_class_permissions(account: &AccountId) -> bool { ContentLeadCanCreateClasses::account_can_create_class_permissions(account) || SudoKeyCanCreateClasses::account_can_create_class_permissions(account) } } // Allow content working group lead to create classes in content directory pub struct ContentLeadCanCreateClasses {} impl versioned_store_permissions::CreateClassPermissionsChecker for ContentLeadCanCreateClasses { fn account_can_create_class_permissions(account: &AccountId) -> bool { // get current lead id let maybe_current_lead_id = content_wg::CurrentLeadId::::get(); if let Some(lead_id) = maybe_current_lead_id { let lead = content_wg::LeadById::::get(lead_id); lead.role_account == *account } else { false } } } impl hiring::Trait for Runtime { type OpeningId = u64; type ApplicationId = u64; type ApplicationDeactivatedHandler = (); // TODO - what needs to happen? type StakeHandlerProvider = hiring::Module; } impl minting::Trait for Runtime { type Currency = ::Currency; type MintId = u64; } impl recurringrewards::Trait for Runtime { type PayoutStatusHandler = (); // TODO - deal with successful and failed payouts type RecipientId = u64; type RewardRelationshipId = u64; } parameter_types! { pub const StakePoolId: [u8; 8] = *b"joystake"; } impl stake::Trait for Runtime { type Currency = ::Currency; type StakePoolId = StakePoolId; type StakingEventsHandler = ( ContentWorkingGroupStakingEventHandler, crate::integration::proposals::StakingEventsHandler, ); type StakeId = u64; type SlashId = u64; } pub struct ContentWorkingGroupStakingEventHandler {} impl stake::StakingEventsHandler for ContentWorkingGroupStakingEventHandler { fn unstaked( stake_id: &::StakeId, _unstaked_amount: stake::BalanceOf, remaining_imbalance: stake::NegativeImbalance, ) -> stake::NegativeImbalance { if !hiring::ApplicationIdByStakingId::::exists(stake_id) { // Stake not related to a staked role managed by the hiring module return remaining_imbalance; } let application_id = hiring::ApplicationIdByStakingId::::get(stake_id); if !content_wg::CuratorApplicationById::::exists(application_id) { // Stake not for a Curator return remaining_imbalance; } // Notify the Hiring module - is there a potential re-entrancy bug if // instant unstaking is occuring? hiring::Module::::unstaked(*stake_id); // Only notify working group module if non instantaneous unstaking occured if content_wg::UnstakerByStakeId::::exists(stake_id) { content_wg::Module::::unstaked(*stake_id); } // Determine member id of the curator let curator_application = content_wg::CuratorApplicationById::::get(application_id); let member_id = curator_application.member_id; // get member's profile let member_profile = membership::members::MemberProfile::::get(member_id).unwrap(); // deposit funds to member's root_account // The application doesn't recorded the original source_account from which staked funds were // provided, so we don't really have another option at the moment. ::Currency::resolve_creating( &member_profile.root_account, remaining_imbalance, ); stake::NegativeImbalance::::zero() } // Handler for slashing event fn slashed( _id: &::StakeId, _slash_id: Option<::SlashId>, _slashed_amount: stake::BalanceOf, _remaining_stake: stake::BalanceOf, remaining_imbalance: stake::NegativeImbalance, ) -> stake::NegativeImbalance { // Check if the stake is associated with a hired curator or applicant // if their stake goes below minimum required for the role, // they should get deactivated. // Since we don't currently implement any slash initiation in working group, // there is nothing to do for now. // Not interested in transfering the slashed amount anywhere for now, // so return it to next handler. remaining_imbalance } } impl content_wg::Trait for Runtime { type Event = Event; } impl common::currency::GovernanceCurrency for Runtime { type Currency = balances::Module; } impl governance::election::Trait for Runtime { type Event = Event; type CouncilElected = (Council, integration::proposals::CouncilElectedHandler); } impl governance::council::Trait for Runtime { type Event = Event; type CouncilTermEnded = (CouncilElection,); } impl memo::Trait for Runtime { type Event = Event; } impl storage::data_object_type_registry::Trait for Runtime { type Event = Event; type DataObjectTypeId = u64; } impl storage::data_directory::Trait for Runtime { type Event = Event; type ContentId = ContentId; type StorageProviderHelper = integration::storage::StorageProviderHelper; type IsActiveDataObjectType = DataObjectTypeRegistry; type MemberOriginValidator = MembershipOriginValidator; } impl storage::data_object_storage_registry::Trait for Runtime { type Event = Event; type DataObjectStorageRelationshipId = u64; type ContentIdExists = DataDirectory; } impl members::Trait for Runtime { type Event = Event; type MemberId = u64; type PaidTermId = u64; type SubscriptionId = u64; type ActorId = ActorId; type InitialMembersBalance = InitialMembersBalance; } /* * Forum module integration * * ForumUserRegistry could have been implemented directly on * the membership module, and likewise ForumUser on Profile, * however this approach is more loosley coupled. * * Further exploration required to decide what the long * run convention should be. */ /// Shim registry which will proxy ForumUserRegistry behaviour to the members module pub struct ShimMembershipRegistry {} impl forum::ForumUserRegistry for ShimMembershipRegistry { fn get_forum_user(id: &AccountId) -> Option> { if members::Module::::is_member_account(id) { // For now we don't retreive the members profile since it is not used for anything, // but in the future we may need it to read out more // information possibly required to construct a // ForumUser. // Now convert member profile to a forum user Some(forum::ForumUser { id: id.clone() }) } else { None } } } impl forum::Trait for Runtime { type Event = Event; type MembershipRegistry = ShimMembershipRegistry; type EnsureForumLeader = working_group::Module; type ThreadId = ThreadId; type PostId = PostId; } impl migration::Trait for Runtime { type Event = Event; } // Forum working group impl working_group::Trait for Runtime { type Event = Event; } // Storage working group impl working_group::Trait for Runtime { type Event = Event; } impl actors::Trait for Runtime { type Event = Event; type OnActorRemoved = (); } //TODO: SWG - remove with roles module deletion impl actors::ActorRemoved for () { fn actor_removed(_: &AccountId) {} } impl service_discovery::Trait for Runtime { type Event = Event; } parameter_types! { pub const ProposalCancellationFee: u64 = 10000; pub const ProposalRejectionFee: u64 = 5000; pub const ProposalTitleMaxLength: u32 = 40; pub const ProposalDescriptionMaxLength: u32 = 3000; pub const ProposalMaxActiveProposalLimit: u32 = 5; } impl proposals_engine::Trait for Runtime { type Event = Event; type ProposerOriginValidator = MembershipOriginValidator; type VoterOriginValidator = CouncilManager; type TotalVotersCounter = CouncilManager; type ProposalId = u32; type StakeHandlerProvider = proposals_engine::DefaultStakeHandlerProvider; type CancellationFee = ProposalCancellationFee; type RejectionFee = ProposalRejectionFee; type TitleMaxLength = ProposalTitleMaxLength; type DescriptionMaxLength = ProposalDescriptionMaxLength; type MaxActiveProposalLimit = ProposalMaxActiveProposalLimit; type DispatchableCallCode = Call; } impl Default for Call { fn default() -> Self { panic!("shouldn't call default for Call"); } } parameter_types! { pub const ProposalMaxPostEditionNumber: u32 = 0; // post update is disabled pub const ProposalMaxThreadInARowNumber: u32 = 100_000; // will not be used pub const ProposalThreadTitleLengthLimit: u32 = 40; pub const ProposalPostLengthLimit: u32 = 1000; } impl proposals_discussion::Trait for Runtime { type Event = Event; type PostAuthorOriginValidator = MembershipOriginValidator; type ThreadId = ThreadId; type PostId = PostId; type MaxPostEditionNumber = ProposalMaxPostEditionNumber; type ThreadTitleLengthLimit = ProposalThreadTitleLengthLimit; type PostLengthLimit = ProposalPostLengthLimit; type MaxThreadInARowNumber = ProposalMaxThreadInARowNumber; } parameter_types! { pub const TextProposalMaxLength: u32 = 5_000; pub const RuntimeUpgradeWasmProposalMaxLength: u32 = 2_000_000; } impl proposals_codex::Trait for Runtime { type MembershipOriginValidator = MembershipOriginValidator; type TextProposalMaxLength = TextProposalMaxLength; type RuntimeUpgradeWasmProposalMaxLength = RuntimeUpgradeWasmProposalMaxLength; type ProposalEncoder = ExtrinsicProposalEncoder; } construct_runtime!( pub enum Runtime where Block = Block, NodeBlock = opaque::Block, UncheckedExtrinsic = UncheckedExtrinsic { // Substrate System: system::{Module, Call, Storage, Config, Event}, Babe: babe::{Module, Call, Storage, Config, Inherent(Timestamp)}, Timestamp: timestamp::{Module, Call, Storage, Inherent}, Authorship: authorship::{Module, Call, Storage, Inherent}, Indices: indices, Balances: balances, TransactionPayment: transaction_payment::{Module, Storage}, Staking: staking::{default, OfflineWorker}, Session: session::{Module, Call, Storage, Event, Config}, FinalityTracker: finality_tracker::{Module, Call, Inherent}, Grandpa: grandpa::{Module, Call, Storage, Config, Event}, ImOnline: im_online::{Module, Call, Storage, Event, ValidateUnsigned, Config}, AuthorityDiscovery: authority_discovery::{Module, Call, Config}, Offences: offences::{Module, Call, Storage, Event}, RandomnessCollectiveFlip: randomness_collective_flip::{Module, Call, Storage}, Sudo: sudo, // Joystream Migration: migration::{Module, Call, Storage, Event, Config}, CouncilElection: election::{Module, Call, Storage, Event, Config}, Council: council::{Module, Call, Storage, Event, Config}, Memo: memo::{Module, Call, Storage, Event}, Members: members::{Module, Call, Storage, Event, Config}, Forum: forum::{Module, Call, Storage, Event, Config}, Actors: actors::{Module, Call, Storage, Event, Config}, VersionedStore: versioned_store::{Module, Call, Storage, Event, Config}, VersionedStorePermissions: versioned_store_permissions::{Module, Call, Storage}, Stake: stake::{Module, Call, Storage}, Minting: minting::{Module, Call, Storage}, RecurringRewards: recurringrewards::{Module, Call, Storage}, Hiring: hiring::{Module, Call, Storage}, ContentWorkingGroup: content_wg::{Module, Call, Storage, Event, Config}, // --- Storage DataObjectTypeRegistry: data_object_type_registry::{Module, Call, Storage, Event, Config}, DataDirectory: data_directory::{Module, Call, Storage, Event}, DataObjectStorageRegistry: data_object_storage_registry::{Module, Call, Storage, Event, Config}, Discovery: service_discovery::{Module, Call, Storage, Event}, // --- Proposals ProposalsEngine: proposals_engine::{Module, Call, Storage, Event}, ProposalsDiscussion: proposals_discussion::{Module, Call, Storage, Event}, ProposalsCodex: proposals_codex::{Module, Call, Storage, Error, Config}, // --- Working groups ForumWorkingGroup: working_group::::{Module, Call, Storage, Event}, StorageWorkingGroup: working_group::::{Module, Call, Storage, Event}, } ); /// The address format for describing accounts. pub type Address = ::Source; /// Block header type as expected by this runtime. pub type Header = generic::Header; /// Block type as expected by this runtime. pub type Block = generic::Block; /// A Block signed with a Justification pub type SignedBlock = generic::SignedBlock; /// BlockId type as expected by this runtime. pub type BlockId = generic::BlockId; /// The SignedExtension to the basic transaction logic. pub type SignedExtra = ( system::CheckVersion, system::CheckGenesis, system::CheckEra, system::CheckNonce, system::CheckWeight, transaction_payment::ChargeTransactionPayment, ); /// Unchecked extrinsic type as expected by this runtime. pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; /// Extrinsic type that has already been checked. pub type CheckedExtrinsic = generic::CheckedExtrinsic; /// Executive: handles dispatch to the various modules. pub type Executive = executive::Executive, Runtime, AllModules>; impl_runtime_apis! { impl client_api::Core for Runtime { fn version() -> RuntimeVersion { VERSION } fn execute_block(block: Block) { Executive::execute_block(block) } fn initialize_block(header: &::Header) { Executive::initialize_block(header) } } impl client_api::Metadata for Runtime { fn metadata() -> OpaqueMetadata { Runtime::metadata().into() } } impl block_builder_api::BlockBuilder for Runtime { fn apply_extrinsic(extrinsic: ::Extrinsic) -> ApplyResult { Executive::apply_extrinsic(extrinsic) } fn finalize_block() -> ::Header { Executive::finalize_block() } fn inherent_extrinsics(data: InherentData) -> Vec<::Extrinsic> { data.create_extrinsics() } fn check_inherents(block: Block, data: InherentData) -> CheckInherentsResult { data.check_extrinsics(&block) } fn random_seed() -> ::Hash { RandomnessCollectiveFlip::random_seed() } } impl client_api::TaggedTransactionQueue for Runtime { fn validate_transaction(tx: ::Extrinsic) -> TransactionValidity { Executive::validate_transaction(tx) } } impl offchain_primitives::OffchainWorkerApi for Runtime { fn offchain_worker(number: NumberFor) { Executive::offchain_worker(number) } } impl fg_primitives::GrandpaApi for Runtime { fn grandpa_authorities() -> Vec<(GrandpaId, GrandpaWeight)> { Grandpa::grandpa_authorities() } } impl babe_primitives::BabeApi for Runtime { fn configuration() -> babe_primitives::BabeConfiguration { // The choice of `c` parameter (where `1 - c` represents the // probability of a slot being empty), is done in accordance to the // slot duration and expected target block time, for safely // resisting network delays of maximum two seconds. // babe_primitives::BabeConfiguration { slot_duration: Babe::slot_duration(), epoch_length: EpochDuration::get(), c: PRIMARY_PROBABILITY, genesis_authorities: Babe::authorities(), randomness: Babe::randomness(), secondary_slots: true, } } } impl authority_discovery_primitives::AuthorityDiscoveryApi for Runtime { fn authorities() -> Vec { AuthorityDiscovery::authorities().into_iter() .map(|id| id.encode()) .map(EncodedAuthorityId) .collect() } fn sign(payload: &Vec) -> Option<(EncodedSignature, EncodedAuthorityId)> { AuthorityDiscovery::sign(payload).map(|(sig, id)| { (EncodedSignature(sig.encode()), EncodedAuthorityId(id.encode())) }) } fn verify(payload: &Vec, signature: &EncodedSignature, authority_id: &EncodedAuthorityId) -> bool { let signature = match BabeSignature::decode(&mut &signature.0[..]) { Ok(s) => s, _ => return false, }; let authority_id = match BabeId::decode(&mut &authority_id.0[..]) { Ok(id) => id, _ => return false, }; AuthorityDiscovery::verify(payload, signature, authority_id) } } impl system_rpc_runtime_api::AccountNonceApi for Runtime { fn account_nonce(account: AccountId) -> Index { System::account_nonce(account) } } impl substrate_session::SessionKeys for Runtime { fn generate_session_keys(seed: Option>) -> Vec { let seed = seed.as_ref().map(|s| rstd::str::from_utf8(&s).expect("Seed is an utf8 string")); opaque::SessionKeys::generate(seed) } } }