diff --git a/node/src/chain_spec.rs b/node/src/chain_spec.rs index be081c5df8d..c5aa94ad2cf 100644 --- a/node/src/chain_spec.rs +++ b/node/src/chain_spec.rs @@ -7,7 +7,7 @@ use sp_core::{Pair, Public, sr25519}; use sp_consensus_babe::{AuthorityId as BabeId}; use grandpa_primitives::{AuthorityId as GrandpaId}; use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId; -use sp_runtime::{Perbill, traits::{Verify, IdentifyAccount}}; +use sp_runtime::{traits::{Verify, IdentifyAccount}}; use sc_telemetry::TelemetryEndpoints; use pallet_im_online::sr25519::{AuthorityId as ImOnlineId}; @@ -16,8 +16,8 @@ pub use node_primitives::{AccountId, Balance, Signature, Block}; use moonbeam_runtime::{ GenesisConfig, AuthorityDiscoveryConfig, BabeConfig, BalancesConfig, ContractsConfig, - GrandpaConfig, ImOnlineConfig, SessionConfig, SessionKeys, StakerStatus, StakingConfig, - IndicesConfig, SudoConfig, SystemConfig, WASM_BINARY, MoonbeamCoreConfig + GrandpaConfig, ImOnlineConfig, SessionConfig, SessionKeys, + IndicesConfig, SudoConfig, SystemConfig, WASM_BINARY, MoonbeamCoreConfig, MoonbeamSessionConfig }; use moonbeam_runtime::constants::mb_genesis::*; @@ -111,18 +111,6 @@ fn testnet_genesis( pallet_session: Some(SessionConfig { keys: keys, }), - // https://crates.parity.io/pallet_staking/struct.GenesisConfig.html - pallet_staking: Some(StakingConfig { - current_era: 0, - validator_count: initial_authorities.len() as u32 * 2, - minimum_validator_count: initial_authorities.len() as u32, - stakers: initial_authorities.iter().map(|x| { - (x.0.clone(), x.1.clone(), STASH, StakerStatus::Validator) - }).collect(), - invulnerables: initial_authorities.iter().map(|x| x.0.clone()).collect(), - slash_reward_fraction: Perbill::from_percent(10), - .. Default::default() - }), pallet_indices: Some(IndicesConfig { indices: vec![], }), @@ -150,8 +138,14 @@ fn testnet_genesis( }), pallet_vesting: Some(Default::default()), mb_core: Some(MoonbeamCoreConfig { - treasury: TREASURY_ENDOWMENT, - genesis_accounts: endowed_accounts, + treasury: TREASURY_FUND, + genesis_accounts: endowed_accounts.clone(), + }), + mb_session: Some(MoonbeamSessionConfig { + treasury: TREASURY_FUND, + session_validators: initial_authorities.iter().map(|x| { + x.0.clone() + }).collect(), }), } } @@ -161,8 +155,10 @@ fn development_config_genesis() -> GenesisConfig { let seeds = vec![ "Armstrong", "Aldrin", + "Collins", "Armstrong//stash", - "Aldrin//stash" + "Aldrin//stash", + "Collins//stash" ]; let mut accounts: Vec = vec![]; let mut initial_authorities: Vec<( @@ -180,6 +176,11 @@ fn development_config_genesis() -> GenesisConfig { initial_authorities.push( get_authority_keys_from_seed(&s) ); } } + + // default accounts with some endorsement + accounts.push( get_account_id_from_seed::("Alice") ); + accounts.push( get_account_id_from_seed::("Bob") ); + accounts.push( get_account_id_from_seed::("Ferdie") ); testnet_genesis( initial_authorities, diff --git a/pallets/mb-session/Cargo.toml b/pallets/mb-session/Cargo.toml new file mode 100644 index 00000000000..744ac3fa4b8 --- /dev/null +++ b/pallets/mb-session/Cargo.toml @@ -0,0 +1,40 @@ +[package] +authors = ['PureStake'] +edition = '2018' +name = 'mb-session' +version = '0.1.0' + +[dependencies] +codec = { package = "parity-scale-codec", version = "1.0.0", default-features = false, features = ["derive"] } +safe-mix = { default-features = false, version = '1.0.0' } +serde = { version = "1.0.102", features = ["derive"] } + +# primitives +sp-core = { git = 'https://github.com/paritytech/substrate.git', rev = '992aea815a753da256a9c0bff053df408532df02', default-features = false } +sp-io = { git = 'https://github.com/paritytech/substrate.git', rev = '992aea815a753da256a9c0bff053df408532df02', default-features = false } +sp-runtime = { git = 'https://github.com/paritytech/substrate.git', rev = '992aea815a753da256a9c0bff053df408532df02', default-features = false } +sp-std = { git = 'https://github.com/paritytech/substrate.git', rev = '992aea815a753da256a9c0bff053df408532df02', default-features = false } +sp-session = { git = 'https://github.com/paritytech/substrate.git', rev = '992aea815a753da256a9c0bff053df408532df02', default-features = false } +sp-staking = { git = 'https://github.com/paritytech/substrate.git', rev = '992aea815a753da256a9c0bff053df408532df02', default-features = false } +node-primitives = { git = 'https://github.com/paritytech/substrate.git', rev = '992aea815a753da256a9c0bff053df408532df02', default-features = false } + +# frame dependencies +frame-support = { git = 'https://github.com/paritytech/substrate.git', rev = '992aea815a753da256a9c0bff053df408532df02', default-features = false } +system = { package = 'frame-system', git = 'https://github.com/paritytech/substrate.git', rev = '992aea815a753da256a9c0bff053df408532df02', default-features = false } +pallet-balances = { git = 'https://github.com/paritytech/substrate.git', rev = '992aea815a753da256a9c0bff053df408532df02', default-features = false } +pallet-session = { features = ["historical"], git = 'https://github.com/paritytech/substrate.git', rev = '992aea815a753da256a9c0bff053df408532df02', default-features = false } +pallet-authorship = { git = 'https://github.com/paritytech/substrate.git', rev = '992aea815a753da256a9c0bff053df408532df02', default-features = false } + +[features] +default = ['std'] +std = [ + "sp-std/std", + "sp-staking/std", + 'codec/std', + 'frame-support/std', + 'safe-mix/std', + "sp-session/std", + 'system/std', + "pallet-session/std", + "pallet-authorship/std", +] diff --git a/pallets/mb-session/src/lib.rs b/pallets/mb-session/src/lib.rs new file mode 100644 index 00000000000..765a1c6917e --- /dev/null +++ b/pallets/mb-session/src/lib.rs @@ -0,0 +1,488 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +use sp_std::prelude::*; +use codec::{HasCompact, Encode, Decode}; +use sp_runtime::{RuntimeDebug,Perbill}; +// use sp_runtime::traits::{OpaqueKeys,Convert}; +use sp_runtime::traits::{Convert,SaturatedConversion}; +use sp_staking::offence::{OffenceDetails}; +use frame_support::{decl_module, decl_storage, decl_event, decl_error, debug}; +use frame_support::dispatch::{DispatchResult}; +use frame_support::traits::{Currency,Get}; +use system::{ensure_signed}; + +use sp_core::crypto::KeyTypeId; +use system::offchain::{SubmitSignedTransaction}; + +#[path = "../../../runtime/src/constants.rs"] +#[allow(dead_code)] +mod constants; +use constants::time::{EPOCH_DURATION_IN_BLOCKS}; +use constants::mb_genesis::{VALIDATORS_PER_SESSION}; + +pub const KEY_TYPE: KeyTypeId = KeyTypeId(*b"mbst"); + +type BalanceOf = + <::Currency as Currency<::AccountId>>::Balance; + +pub mod crypto { + pub use super::KEY_TYPE; + use sp_runtime::app_crypto::{app_crypto, sr25519}; + app_crypto!(sr25519, KEY_TYPE); +} + +pub trait Trait: system::Trait + pallet_balances::Trait + pallet_session::Trait { + type Event: From> + Into<::Event>; + type Call: From>; + type SubmitTransaction: SubmitSignedTransaction::Call>; + type Currency: Currency; + type SessionsPerEra: Get; +} + +decl_storage! { + trait Store for Module as MoonbeamStakingModule { + /// The number of Era. + EraIndex: u32; + /// The total validator pool. + Validators: Vec; + /// The current session of an Era. + SessionOfEraIndex: u32; + /// The current Block Index of an Era. + BlockOfEraIndex: u32; + /// The validator set selected for the Era. + SessionValidators get(session_validators): Vec; + /// Number of blocks authored by a given validator in this Era. + SessionValidatorAuthoring: + map hasher(blake2_256) T::AccountId => u32; + /// One to Many Validator -> Endorsers. + ValidatorEndorsers: map hasher(blake2_256) T::AccountId => Vec; + /// One to One Endorser -> Validator. + Endorser: map hasher(blake2_256) T::AccountId => T::AccountId; + /// A timeline of free_balances for an endorser that allows us to calculate + /// the average of free_balance of an era. + /// + /// TODO: Used to select era validators at the start (or end TODO) of an era. + /// We are by now supposing that an endorsement represents all the free_balance + /// of the token holder. + /// When the free_balance of an endorser changes, a new snapshot is created + /// together with the current block_index of the current era. + /// + /// Endorser, Validator => (session_block_index,endorser_balance) + EndorserSnapshots: + double_map hasher(blake2_256) T::AccountId, hasher(blake2_256) T::AccountId => Vec<(u32,BalanceOf)>; + + /// TODO the Treasury balance. It is still unclear if this will be a pallet account or + /// will remain as a Storage balance. + Treasury get(treasury): T::Balance; + } + add_extra_genesis { + config(session_validators): Vec; + config(treasury): T::Balance; + build(|config: &GenesisConfig| { + // set all validators + let _ = >::append(config.session_validators.clone()); + // set initial selected validators + let _ = >::append(config.session_validators.clone()); + // set treasury + >::put(config.treasury); + // set genesis era data + EraIndex::put(1); + BlockOfEraIndex::put(1); + }); + } +} + +decl_error! { + pub enum Error for Module { + AlreadyEndorsing, + NotEndorsing, + } +} + +decl_event!( + pub enum Event + where + AccountId = ::AccountId, + { + BlockAuthored(AccountId), + NewEra(u32), + NewSession(u32), + EndSession(u32), + } +); + +decl_module! { + pub struct Module for enum Call where origin: T::Origin { + + type Error = Error; + fn deposit_event() = default; + + /// Endorsing dispatchable function + pub fn endorse( + origin, to:T::AccountId + ) -> DispatchResult { + let from = ensure_signed(origin)?; + // Check if the Account is already endorsing. + if >::contains_key(&from) { + return Err(Error::::AlreadyEndorsing).map_err(Into::into); + } + // Set One to One endorser->validator association. + >::insert(&from,&to); + // Set One to Many validator->endorsers association. + >::append(&to,vec![&from])?; + // Create a snapshot with the current free balance of the endorser. + Self::set_snapshot(&from,&to,T::Currency::free_balance(&from))?; + Ok(()) + } + + /// Unndorsing dispatchable function + pub fn unendorse( + origin + ) -> DispatchResult { + let from = ensure_signed(origin)?; + // Check if the Account is actively endorsing. + if !>::contains_key(&from) { + return Err(Error::::NotEndorsing).map_err(Into::into); + } + + let validator = >::get(&from); + let mut endorsers = >::get(&validator); + + // Remove One to Many validator->endorsers association + endorsers.retain(|x| x != &from); + >::insert(&validator, endorsers); + // Remove One to One endorser->validator association + >::remove(&from); + // Remove all snapshots associated to the endorser, using the double map prefix + >::remove_prefix(&from); + + Ok(()) + } + + fn offchain_worker(block_number: T::BlockNumber) { + // Set snapshots + Self::offchain_set_snapshots(); + // Select validators off-chain + Self::offchain_validator_selection(block_number); + } + + fn persist_selected_validators( + origin,selected_validators: Vec + ) -> DispatchResult { + ensure_signed(origin)?; + >::put(selected_validators.clone()); + Ok(()) + } + + fn persist_snapshots( + origin,snapshots: Vec<(T::AccountId,T::AccountId,BalanceOf)> + ) -> DispatchResult { + ensure_signed(origin)?; + for s in &snapshots { + Self::set_snapshot(&s.0,&s.1,s.2)?; + } + Ok(()) + } + } +} + +impl Module { + + /// First approach to keep moving forward until we find out how to track BalanceOf + /// changes in real-time. + /// + /// This approach, although functional, is invalid as it has multiple issues like + /// sending signed transactions potentially every block. + /// + /// Other messy ways could be, again a per-block offchain task, pattern matching the + /// >::events() to find pallet_balances events that are registered + /// in the Storage. + fn offchain_set_snapshots() { + let mut output: Vec<(T::AccountId,T::AccountId,BalanceOf)> = vec![]; + let validators = >::get(); + for v in &validators { + let endorsers = >::get(v); + for ed in &endorsers { + let snapshots = >::get(ed,v.clone()); + let len = snapshots.len(); + // Make sure we have a previous block reference in this Era + if len > 0 { + let snapshot_balance = snapshots[len-1].1; + let current_balance = T::Currency::free_balance(ed); + if snapshot_balance != current_balance { + output.push((ed.clone(),v.clone(),current_balance)); + } + } + } + } + // If there are snapshots, send signed transaction + if output.len() > 0 { + let call = Call::persist_snapshots(output); + let res = T::SubmitTransaction::submit_signed(call); + if res.is_empty() { + debug::native::info!("No local accounts found."); + } else { + debug::native::info!("Sending snapshots transaction."); + } + } + } + + /// Offchain task to select validators + fn offchain_validator_selection(block_number: T::BlockNumber) { + // Find out where we are in Era + let current_era: u128 = EraIndex::get() as u128; + let last_block_of_era: u128 = + (current_era * (T::SessionsPerEra::get() as u128) * (EPOCH_DURATION_IN_BLOCKS as u128)).saturated_into(); + let validator_selection_delta: u128 = 5; + let current_block_number: u128 = block_number.saturated_into(); + // When we are 5 blocks away of a new Era, run the validator selection. + if (last_block_of_era - current_block_number) == validator_selection_delta { + // Perform the validator selection + let selected_validators = >::select_validators(); + // Send signed transaction to persist the new validators to the on-chain storage + let call = Call::persist_selected_validators(selected_validators); + let res = T::SubmitTransaction::submit_signed(call); + if res.is_empty() { + debug::native::info!("No local accounts found."); + } else { + debug::native::info!("Sending selected validator transaction."); + } + } + } + + /// Sets a snapshot using the current era's block index and the Account free_balance. + fn set_snapshot( + endorser: &T::AccountId, validator: &T::AccountId, amount: BalanceOf + ) -> DispatchResult { + >::append(&endorser,&validator, + vec![(BlockOfEraIndex::get(),amount)])?; + Ok(()) + } + /// Calculates a single endorser weighted balance for the era by measuring the + /// block index distances. + fn calculate_endorsement( + endorser: &T::AccountId, validator: &T::AccountId + ) -> f64 { + + let duration: u32 = EPOCH_DURATION_IN_BLOCKS; + let points = >::get(endorser,validator); + let points_dim: usize = points.len(); + + if points_dim == 0 { + return 0 as f64; + } + + let n: usize = points_dim-1; + let (points,n) = Self::set_snapshot_boundaries(duration,n,points); + let mut previous: (u32,BalanceOf) = (0,BalanceOf::::from(0)); + // Find the distances between snapshots, weight the free_balance against them. + // Finally sum all values. + let mut endorsement: f64 = points.iter().map(|p| { + let out: f64; + if previous != (0,BalanceOf::::from(0)) { + let delta = p.0-previous.0; + let w = delta as f64 / duration as f64; + out = w * previous.1.saturated_into() as f64; + } else { + out = 0 as f64; + } + previous = *p; + out + }) + .sum::(); + // The above iterative approach excludes the last block, sum it to the result. + endorsement += (1 as f64 / duration as f64) * points[n].1.saturated_into() as f64; + endorsement + } + /// Selects a new validator set based on their amount of weighted endorsement. + fn select_validators() -> Vec { + let validators = >::get(); + // Get the calculated endorsement per validator. + let mut selected_validators = validators.iter().map(|v| { + let endorsers = >::get(v); + let total_validator_endorsement = endorsers.iter().map(|ed| { + Self::calculate_endorsement(ed,v) + }) + .sum::(); + (total_validator_endorsement,v) + }) + .collect::>(); + // Sort descendant validators by amount. + selected_validators.sort_by(|(x0, _y0), (x1, _y1)| x0.partial_cmp(&x1).unwrap()); + selected_validators.reverse(); + // Take the by-configuration amount of validators. + selected_validators.into_iter().take(VALIDATORS_PER_SESSION as usize) + .map(|(_x,y)| y.clone()) + .collect::>() + } + /// Conditionally set the boundary balances to complete a snapshot series. + /// (if no snapshot is defined on block 1 or {era_len} indexes). + fn set_snapshot_boundaries( + duration: u32, + mut last_index: usize, + mut collection: Vec<(u32,BalanceOf)> + ) -> (Vec<(u32,BalanceOf)>,usize) { + + if collection[0].0 != 1 { + collection.insert(0,(1,BalanceOf::::from(0))); + last_index += 1; + } + if collection[last_index].0 != duration { + collection.push((duration,collection[last_index].1)); + } + (collection,last_index) + } + /// All snapshots are reset on era change with a single checkpoint of the + /// current endorser's Account free_balance. + fn reset_snapshots() { + let validators = >::get(); + for validator in validators.iter() { + let endorsers = >::get(validator); + for endorser in endorsers.iter() { + >::insert(endorser,validator,vec![( + 1 as u32, + T::Currency::free_balance(endorser) + )]); + } + } + } +} + +pub struct SessionManager(T); +impl pallet_session::SessionManager for SessionManager { + fn new_session(new_index: u32) -> Option> { + + >::deposit_event( + RawEvent::NewSession(new_index) + ); + + if new_index > 1 { + let current_era = EraIndex::get(); + if new_index > (current_era * (T::SessionsPerEra::get() as u32)) { + // Era change + // Reset SessionOfEraIndex to 1 + SessionOfEraIndex::put(1); + // Reset BlockOfEraIndex to 1 + BlockOfEraIndex::put(1); + // Increase the EraIndex by 1 + let new_era_idx = EraIndex::get().checked_add(1) + .ok_or("SessionOfEraIndex Overflow").unwrap(); + EraIndex::put(new_era_idx); + // Reset all snapshots + >::reset_snapshots(); + } else { + // Same Era, next session. Increase SessionOfEraIndex by 1. + let new_era_session_idx = SessionOfEraIndex::get().checked_add(1) + .ok_or("SessionOfEraIndex Overflow").unwrap(); + SessionOfEraIndex::put(new_era_session_idx); + } + Some(>::get()) + } else { + None + } + } + + fn end_session(end_index: u32) { + >::deposit_event( + RawEvent::EndSession(end_index) + ); + } +} + +pub struct AuthorshipEventHandler(T); +impl pallet_authorship::EventHandler for AuthorshipEventHandler { + fn note_author(author: T::AccountId) { + let authored_blocks = + >::get(&author).checked_add(1).ok_or("Overflow").unwrap(); + >::insert(&author,authored_blocks); + BlockOfEraIndex::mutate(|x| *x += 1); + // >::deposit_event( + // RawEvent::BlockAuthored(author) + // ); + } + fn note_uncle(_author: T::AccountId, _age: u32) { + + } +} + +// !!!!!! +// All below are trait implemenations that we need to satisfy for the historical feature of the pallet-session +// and by offences. Find a way to remove without having to implement or move outside of the pallet. + +/// The amount of exposure (to slashing) than an individual nominator has. +#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, RuntimeDebug)] +pub struct IndividualExposure { + /// The stash account of the nominator in question. + who: AccountId, + /// Amount of funds exposed. + #[codec(compact)] + value: Balance, +} + +/// A snapshot of the stake backing a single validator in the system. +#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, Default, RuntimeDebug)] +pub struct Exposure { + /// The total balance backing this validator. + #[codec(compact)] + pub total: Balance, + /// The validator's own stash that is exposed. + #[codec(compact)] + pub own: Balance, + /// The portions of nominators stashes that are exposed. + pub others: Vec>, +} + +/// A typed conversion from stash account ID to the current exposure of nominators +/// on that account. +pub struct ExposureOf(sp_std::marker::PhantomData); +impl Convert>>> + for ExposureOf +{ + fn convert(_validator: T::AccountId) -> Option>> { + None + } +} + +pub struct Offences(sp_std::marker::PhantomData); +impl sp_staking::offence::OnOffenceHandler> for Offences where + T: pallet_session::Trait::AccountId>, + T: pallet_session::historical::Trait< + FullIdentification = Exposure<::AccountId, BalanceOf>, + FullIdentificationOf = ExposureOf, + >, + T::SessionHandler: pallet_session::SessionHandler<::AccountId>, + T::SessionManager: pallet_session::SessionManager<::AccountId>, + T::ValidatorIdOf: Convert<::AccountId, Option<::AccountId>> +{ + fn on_offence( + _offenders: &[OffenceDetails>], + _slash_fraction: &[Perbill], + _slash_session: u32, + ) { + + } +} + +// struct SessionHandler(T); +// impl pallet_session::SessionHandler for SessionHandler { + +// const KEY_TYPE_IDS: &'static [KeyTypeId] = &[]; + +// fn on_genesis_session(_validators: &[(T::AccountId, Ks)]) {} + +// fn on_new_session( +// _changed: bool, +// validators: &[(T::AccountId, Ks)], +// _queued_validators: &[(T::AccountId, Ks)], +// ) { +// SessionIndex::mutate(|x| *x + 1); +// let current_session = SessionIndex::get(); +// >::deposit_event( +// RawEvent::NewSessionIndex(current_session) +// ); +// } + +// fn on_disabled(validator_index: usize) { + +// } +// } \ No newline at end of file diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 72b8de8bb28..9b78a35d363 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -58,6 +58,7 @@ pallet-transaction-payment = { git = 'https://github.com/paritytech/substrate.gi pallet-transaction-payment-rpc-runtime-api = { git = 'https://github.com/paritytech/substrate.git', rev = '992aea815a753da256a9c0bff053df408532df02', default-features = false } pallet-vesting = { git = 'https://github.com/paritytech/substrate.git', rev = '992aea815a753da256a9c0bff053df408532df02', default-features = false } mb-core = { version = "0.1.0", default-features = false, path = "../pallets/mb-core", package = "mb-core" } +mb-session = { version = "0.1.0", default-features = false, path = "../pallets/mb-session", package = "mb-session" } [build-dependencies] wasm-builder-runner = { package = "substrate-wasm-builder-runner", version = "1.0.5" } @@ -113,4 +114,5 @@ std = [ "pallet-recovery/std", "pallet-vesting/std", "mb-core/std", + "mb-session/std", ] diff --git a/runtime/src/constants.rs b/runtime/src/constants.rs index 3a2ff5ea6ec..b672ac768a5 100644 --- a/runtime/src/constants.rs +++ b/runtime/src/constants.rs @@ -3,9 +3,12 @@ pub mod mb_genesis { use node_primitives::{Balance}; use super::currency::{GLMR}; - /// Six decimals for 500_000 glmr units - pub const TOTAL_GLMR_SUPPLY: Balance = 500_000 * GLMR; - pub const TREASURY_ENDOWMENT: Balance = 100_000 * GLMR; + /// 8 decimals for 500_000 glmr units + pub const TOTAL_GLMR_SUPPLY: Balance = 10_000_000 * GLMR; + pub const TREASURY_FUND: Balance = TOTAL_GLMR_SUPPLY / 5; + pub const REWARD_PER_YEAR: Balance = 250_000 * GLMR; + + pub const VALIDATORS_PER_SESSION: u8 = 2; } /// Money matters. @@ -50,7 +53,9 @@ pub mod time { // 1 in 4 blocks (on average, not counting collisions) will be primary BABE blocks. pub const PRIMARY_PROBABILITY: (u64, u64) = (1, 4); - pub const EPOCH_DURATION_IN_BLOCKS: BlockNumber = 10 * MINUTES; + // Sessions are set low (1 per minute) for debugging purposes. + // As a block is produced every 3 seconds by configuration, a new session will occur every X blocks. + pub const EPOCH_DURATION_IN_BLOCKS: BlockNumber = 5 * MINUTES; pub const EPOCH_DURATION_IN_SLOTS: u64 = { const SLOT_FILL_RATE: f64 = MILLISECS_PER_BLOCK as f64 / SLOT_DURATION as f64; @@ -61,4 +66,10 @@ pub mod time { pub const MINUTES: BlockNumber = 60 / (SECS_PER_BLOCK as BlockNumber); pub const HOURS: BlockNumber = MINUTES * 60; pub const DAYS: BlockNumber = HOURS * 24; + + // epochs/session per era + pub const EPOCH_PER_ERA: u8 = 1; + + // convenience year in milliseconds. TODO. + pub const MILLISECS_PER_YEAR: u64 = 3660 * 24 * 365 * 1000; } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 74893c3e457..89d2d2e281f 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -16,7 +16,6 @@ use sp_runtime::traits::{ self, BlakeTwo256, Block as BlockT, SaturatedConversion, StaticLookup, ConvertInto, OpaqueKeys }; -use sp_runtime::curve::PiecewiseLinear; use sp_api::impl_runtime_apis; use sp_version::RuntimeVersion; use sp_inherents::{InherentData, CheckInherentsResult}; @@ -58,10 +57,11 @@ pub use constants::{time::*, currency::*, mb_genesis::*}; /// Importing the moonbeam core pallet pub use mb_core; +pub use mb_session; /// Implementations of some helper traits passed into runtime modules as associated types. pub mod impls; -use impls::{CurrencyToVoteHandler, Author, LinearWeightToFee, TargetedFeeAdjustment}; +use impls::{Author, LinearWeightToFee, TargetedFeeAdjustment}; impl_opaque_keys! { pub struct SessionKeys { @@ -127,7 +127,7 @@ impl frame_system::Trait for Runtime { type ModuleToIndex = ModuleToIndex; type AccountData = pallet_balances::AccountData; type OnNewAccount = (); - type OnReapAccount = (Balances, Staking, Contracts, Session, Recovery); + type OnReapAccount = (); } parameter_types! { @@ -228,7 +228,7 @@ impl pallet_authorship::Trait for Runtime { type FindAuthor = pallet_session::FindAccountFromAuthorIndex; type UncleGenerations = UncleGenerations; type FilterUncle = (); - type EventHandler = (Staking, ImOnline); + type EventHandler = (mb_session::AuthorshipEventHandler, ImOnline); } parameter_types! { @@ -238,64 +238,17 @@ parameter_types! { impl pallet_session::Trait for Runtime { type Event = Event; type ValidatorId = ::AccountId; - type ValidatorIdOf = pallet_staking::StashOf; + type ValidatorIdOf = ConvertInto; type ShouldEndSession = Babe; - type SessionManager = Staking; + type SessionManager = mb_session::SessionManager; type SessionHandler = ::KeyTypeIdProviders; type Keys = SessionKeys; type DisabledValidatorsThreshold = DisabledValidatorsThreshold; } impl pallet_session::historical::Trait for Runtime { - type FullIdentification = pallet_staking::Exposure; - type FullIdentificationOf = pallet_staking::ExposureOf; -} - -pallet_staking_reward_curve::build! { - const REWARD_CURVE: PiecewiseLinear<'static> = curve!( - min_inflation: 0_025_000, - max_inflation: 0_100_000, - ideal_stake: 0_500_000, - falloff: 0_050_000, - max_piece_count: 40, - test_precision: 0_005_000, - ); -} - -// TODO read theory, figure out how to disable inflation. -// https://research.web3.foundation/en/latest/polkadot/Token%20Economics.html#inflation-model -// https://github.com/paritytech/substrate/blob/db1ab7d18fbe7876cdea43bbf30f147ddd263f94/frame/staking/reward-curve/src/lib.rs#L267 -// const REWARD_CURVE: PiecewiseLinear<'static> = PiecewiseLinear { -// points: &[ -// (Perbill::from_parts(0),Perbill::from_parts(1000000)), -// (Perbill::from_parts(1000000),Perbill::from_parts(1000000)), -// ], -// maximum: Perbill::from_parts(1000000) -// }; - -parameter_types! { - pub const SessionsPerEra: sp_staking::SessionIndex = 6; - pub const BondingDuration: pallet_staking::EraIndex = 24 * 28; - pub const SlashDeferDuration: pallet_staking::EraIndex = 24 * 7; // 1/4 the bonding duration. - pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE; -} - -impl pallet_staking::Trait for Runtime { - type Currency = Balances; - type Time = Timestamp; - type CurrencyToVote = CurrencyToVoteHandler; - type RewardRemainder = mb_core::RewardRemainder; - type Event = Event; - type Slash = mb_core::Absorb; // send the slashed funds to the treasury. - type Reward = mb_core::Reward; // rewards are minted from the void - type SessionsPerEra = SessionsPerEra; - type BondingDuration = BondingDuration; - type SlashDeferDuration = SlashDeferDuration; - /// A super-majority of the council can cancel the slash. - //type SlashCancelOrigin = pallet_collective::EnsureProportionAtLeast<_3, _4, AccountId, CouncilCollective>; - type SlashCancelOrigin = mb_core::Collective; - type SessionInterface = Self; - type RewardCurve = RewardCurve; + type FullIdentification = mb_session::Exposure; + type FullIdentificationOf = mb_session::ExposureOf; } parameter_types! { @@ -353,7 +306,7 @@ impl pallet_im_online::Trait for Runtime { impl pallet_offences::Trait for Runtime { type Event = Event; type IdentificationTuple = pallet_session::historical::IdentificationTuple; - type OnOffenceHandler = Staking; + type OnOffenceHandler = mb_session::Offences; } impl pallet_authority_discovery::Trait for Runtime {} @@ -442,6 +395,25 @@ impl mb_core::Trait for Runtime { type Event = Event; } +parameter_types! { + pub const SessionsPerEra: u8 = EPOCH_PER_ERA; +} + +type SubmitMBTransaction = TransactionSubmitter< + mb_session::crypto::Public, + Runtime, + UncheckedExtrinsic +>; + +impl mb_session::Trait for Runtime { + type Currency = Balances; + type SubmitTransaction = SubmitMBTransaction; + type Call = Call; + type Event = Event; + type SessionsPerEra = SessionsPerEra; +} + + construct_runtime!( pub enum Runtime where Block = Block, @@ -456,7 +428,6 @@ construct_runtime!( Indices: pallet_indices::{Module, Call, Storage, Config, Event}, Balances: pallet_balances::{Module, Call, Storage, Config, Event}, TransactionPayment: pallet_transaction_payment::{Module, Storage}, - Staking: pallet_staking::{Module, Call, Config, Storage, Event}, Session: pallet_session::{Module, Call, Storage, Event, Config}, FinalityTracker: pallet_finality_tracker::{Module, Call, Inherent}, Grandpa: pallet_grandpa::{Module, Call, Storage, Config, Event}, @@ -469,6 +440,7 @@ construct_runtime!( Recovery: pallet_recovery::{Module, Call, Storage, Event}, Vesting: pallet_vesting::{Module, Call, Storage, Event, Config}, MoonbeamCore: mb_core::{Module, Call, Storage, Event, Config}, + MoonbeamSession: mb_session::{Module, Call, Storage, Event, Config}, } ); diff --git a/scripts/staging/run-node-collins.sh b/scripts/staging/run-node-collins.sh new file mode 100755 index 00000000000..9643543d0c7 --- /dev/null +++ b/scripts/staging/run-node-collins.sh @@ -0,0 +1,9 @@ +../../node/target/release/moonbeam \ + --base-path ./tmp/Moonbeam-Collins-03 \ + --chain ./rawspec.json \ + --port 30335 \ + --ws-port 9946 \ + --rpc-port 9935 \ + --validator \ + --name Moonbeam-Collins-03 + diff --git a/scripts/staging/set-keys.sh b/scripts/staging/set-keys.sh index b9a46fbd112..999d928f5ad 100755 --- a/scripts/staging/set-keys.sh +++ b/scripts/staging/set-keys.sh @@ -7,6 +7,7 @@ echo $ARMSTRONG_BABE echo "Armstrong granpa:" ARMSTRONG_GRANPA=$(echo $(subkey --ed25519 inspect "//Armstrong" | grep "^ Public key (hex)" | cut -f2- -d:) | xargs) echo $ARMSTRONG_GRANPA + echo "Aldrin babe:" ALDRIN_BABE=$(echo $(subkey inspect "//Aldrin" | grep "^ Public key (hex)" | cut -f2- -d:) | xargs) echo $ALDRIN_BABE @@ -14,6 +15,25 @@ echo "Aldrin granpa:" ALDRIN_GRANPA=$(echo $(subkey --ed25519 inspect "//Aldrin" | grep "^ Public key (hex)" | cut -f2- -d:) | xargs) echo $ALDRIN_GRANPA +echo "Collins babe:" +COLLINS_BABE=$(echo $(subkey inspect "//Collins" | grep "^ Public key (hex)" | cut -f2- -d:) | xargs) +echo $COLLINS_BABE +echo "Collins granpa:" +COLLINS_GRANPA=$(echo $(subkey --ed25519 inspect "//Collins" | grep "^ Public key (hex)" | cut -f2- -d:) | xargs) +echo $COLLINS_GRANPA + +curl http://localhost:9933 -H "Content-Type:application/json;charset=utf-8" -d \ + '{ + "jsonrpc":"2.0", + "id":1, + "method":"author_insertKey", + "params": [ + "mbst", + "//Armstrong", + "'"$ARMSTRONG_BABE"'" + ] + }' + curl http://localhost:9933 -H "Content-Type:application/json;charset=utf-8" -d \ '{ "jsonrpc":"2.0", @@ -61,3 +81,27 @@ curl http://localhost:9934 -H "Content-Type:application/json;charset=utf-8" -d \ "'"$ALDRIN_GRANPA"'" ] }' + +curl http://localhost:9935 -H "Content-Type:application/json;charset=utf-8" -d \ + '{ + "jsonrpc":"2.0", + "id":1, + "method":"author_insertKey", + "params": [ + "babe", + "//Collins", + "'"$COLLINS_BABE"'" + ] + }' + +curl http://localhost:9935 -H "Content-Type:application/json;charset=utf-8" -d \ + '{ + "jsonrpc":"2.0", + "id":1, + "method":"author_insertKey", + "params": [ + "gran", + "//Collins", + "'"$COLLINS_GRANPA"'" + ] + }'