From 6f8178b3d0914a20560e2478686bd5913deed47f Mon Sep 17 00:00:00 2001 From: tgmichel Date: Wed, 4 Mar 2020 16:10:36 +0100 Subject: [PATCH 01/10] alternative custom staking module. # impl pallet_session::SessionManager # impl pallet_authorship::EventHandler # setup initial storage: - SessionIndexStore - SessionValidators - SessionValidatorAuthoring --- node/src/chain_spec.rs | 25 ++--- pallets/mb-staking/Cargo.toml | 39 ++++++++ pallets/mb-staking/src/lib.rs | 179 ++++++++++++++++++++++++++++++++++ runtime/Cargo.toml | 2 + runtime/src/lib.rs | 73 +++----------- 5 files changed, 245 insertions(+), 73 deletions(-) create mode 100644 pallets/mb-staking/Cargo.toml create mode 100644 pallets/mb-staking/src/lib.rs diff --git a/node/src/chain_spec.rs b/node/src/chain_spec.rs index be081c5df8d..8af99cc361d 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, MoonbeamStakingConfig }; 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![], }), @@ -151,7 +139,12 @@ fn testnet_genesis( pallet_vesting: Some(Default::default()), mb_core: Some(MoonbeamCoreConfig { treasury: TREASURY_ENDOWMENT, - genesis_accounts: endowed_accounts, + genesis_accounts: endowed_accounts.clone(), + }), + mb_staking: Some(MoonbeamStakingConfig { + session_validators: initial_authorities.iter().map(|x| { + x.0.clone() + }).collect(), }), } } diff --git a/pallets/mb-staking/Cargo.toml b/pallets/mb-staking/Cargo.toml new file mode 100644 index 00000000000..ccf4e1c6ec9 --- /dev/null +++ b/pallets/mb-staking/Cargo.toml @@ -0,0 +1,39 @@ +[package] +authors = ['PureStake'] +edition = '2018' +name = 'mb-staking' +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 } + +# 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-staking/src/lib.rs b/pallets/mb-staking/src/lib.rs new file mode 100644 index 00000000000..c18feafde44 --- /dev/null +++ b/pallets/mb-staking/src/lib.rs @@ -0,0 +1,179 @@ +#![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}; +use sp_staking::offence::{OffenceDetails}; +use frame_support::{decl_module, decl_storage, decl_event}; +// use frame_support::dispatch::{DispatchResult}; +use frame_support::traits::{Currency,LockableCurrency}; +// use system::{ensure_root,ensure_signed}; + +type BalanceOf = + <::Currency as Currency<::AccountId>>::Balance; + +pub trait Trait: system::Trait + pallet_balances::Trait + pallet_session::Trait { + type Event: From> + Into<::Event>; + type Currency: LockableCurrency; +} + +decl_storage! { + trait Store for Module as MoonbeamStakingModule { + SessionIndexStore: u32; + SessionValidators get(session_validators): Vec; + SessionValidatorAuthoring: map hasher(blake2_256) T::AccountId => u32; + } + add_extra_genesis { + config(session_validators): Vec; + build(|config: &GenesisConfig| { + let _ = >::append(config.session_validators.clone()); + }); + } +} + +decl_event!( + pub enum Event + where + AccountId = ::AccountId, + { + BlockAuthored(AccountId), + NewSessionIndex(u32), + EndSessionIndex(u32), + } +); + +decl_module! { + pub struct Module for enum Call where origin: T::Origin { + fn deposit_event() = default; + } +} + +impl Module { + +} + +pub struct SessionManager(T); +impl pallet_session::SessionManager for SessionManager { + fn new_session(new_index: u32) -> Option> { + SessionIndexStore::put(new_index); + >::deposit_event( + RawEvent::NewSessionIndex(new_index) + ); + if new_index > 1 { + // TODO rotate, validator queue, etc use SessionHandler trait? + // TODO call custom economic logic + let validators = >::get(); + for v in validators.iter() { + >::insert(v.clone(),0); + } + Some(validators) + } else { + None + } + } + + fn end_session(end_index: u32) { + >::deposit_event( + RawEvent::EndSessionIndex(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); + // >::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 +// Is required by offences and session::historical. Find a way to remove without having to implement. + +/// 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..617e8c323a8 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-staking = { version = "0.1.0", default-features = false, path = "../pallets/mb-staking", package = "mb-staking" } [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-staking/std", ] diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 74893c3e457..91ef2830925 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_staking; /// 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_staking::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_staking::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_staking::Exposure; + type FullIdentificationOf = mb_staking::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_staking::Offences; } impl pallet_authority_discovery::Trait for Runtime {} @@ -442,6 +395,12 @@ impl mb_core::Trait for Runtime { type Event = Event; } +impl mb_staking::Trait for Runtime { + type Currency = Balances; + type Event = Event; +} + + construct_runtime!( pub enum Runtime where Block = Block, @@ -456,7 +415,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 +427,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}, + MoonbeamStaking: mb_staking::{Module, Call, Storage, Event, Config}, } ); From 7686951789e749c07ab8b6293a3a893583827aae Mon Sep 17 00:00:00 2001 From: tgmichel Date: Fri, 6 Mar 2020 14:39:19 +0100 Subject: [PATCH 02/10] 1st iteration on weighted rewards --- node/src/chain_spec.rs | 6 +- pallets/mb-staking/Cargo.toml | 1 + pallets/mb-staking/src/lib.rs | 180 ++++++++++++++++++++++++++++++---- runtime/src/constants.rs | 17 +++- runtime/src/lib.rs | 5 + 5 files changed, 186 insertions(+), 23 deletions(-) diff --git a/node/src/chain_spec.rs b/node/src/chain_spec.rs index 8af99cc361d..8dfaf4e975d 100644 --- a/node/src/chain_spec.rs +++ b/node/src/chain_spec.rs @@ -138,10 +138,11 @@ fn testnet_genesis( }), pallet_vesting: Some(Default::default()), mb_core: Some(MoonbeamCoreConfig { - treasury: TREASURY_ENDOWMENT, + treasury: TREASURY_FUND, genesis_accounts: endowed_accounts.clone(), }), mb_staking: Some(MoonbeamStakingConfig { + treasury: TREASURY_FUND, session_validators: initial_authorities.iter().map(|x| { x.0.clone() }).collect(), @@ -173,6 +174,9 @@ 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") ); testnet_genesis( initial_authorities, diff --git a/pallets/mb-staking/Cargo.toml b/pallets/mb-staking/Cargo.toml index ccf4e1c6ec9..dfe1aaf723b 100644 --- a/pallets/mb-staking/Cargo.toml +++ b/pallets/mb-staking/Cargo.toml @@ -16,6 +16,7 @@ sp-runtime = { git = 'https://github.com/paritytech/substrate.git', rev = '992ae 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 } diff --git a/pallets/mb-staking/src/lib.rs b/pallets/mb-staking/src/lib.rs index c18feafde44..711856603f8 100644 --- a/pallets/mb-staking/src/lib.rs +++ b/pallets/mb-staking/src/lib.rs @@ -1,34 +1,69 @@ + + #![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}; +use sp_runtime::traits::{Convert,SaturatedConversion,CheckedSub}; use sp_staking::offence::{OffenceDetails}; use frame_support::{decl_module, decl_storage, decl_event}; -// use frame_support::dispatch::{DispatchResult}; -use frame_support::traits::{Currency,LockableCurrency}; -// use system::{ensure_root,ensure_signed}; +use frame_support::dispatch::{DispatchResult}; +use frame_support::traits::{Currency,Get}; +use system::{ensure_signed}; + +#[path = "../../../runtime/src/constants.rs"] +#[allow(dead_code)] +mod constants; +use constants::time::{MILLISECS_PER_YEAR,EPOCH_DURATION_IN_BLOCKS}; +use constants::mb_genesis::{REWARD_PER_YEAR}; type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; pub trait Trait: system::Trait + pallet_balances::Trait + pallet_session::Trait { type Event: From> + Into<::Event>; - type Currency: LockableCurrency; + type Currency: Currency; + type SessionsPerEra: Get; +} + +#[derive(Encode, Decode)] +enum EndorserStatus { Raised, Active, Chill } +impl Default for EndorserStatus { + fn default() -> Self { + EndorserStatus::Raised + } } decl_storage! { trait Store for Module as MoonbeamStakingModule { - SessionIndexStore: u32; + EraIndex: u32; + // Session + SessionOfEraIndex: u32; SessionValidators get(session_validators): Vec; SessionValidatorAuthoring: map hasher(blake2_256) T::AccountId => u32; + // Endorsement + EndorsementStatus: + double_map hasher(blake2_256) T::AccountId, hasher(blake2_256) T::AccountId => EndorserStatus; + EndorsementBalance: + double_map hasher(blake2_256) T::AccountId, hasher(blake2_256) T::AccountId => T::Balance; // Endorser, Validator => amount + Endorsers: map hasher(blake2_256) T::AccountId => Vec; // Validator => Endorsers + // Validator Balance + ValidatorBalance: map hasher(blake2_256) T::AccountId => T::Balance; + // Treasury + Treasury get(treasury): T::Balance; } add_extra_genesis { config(session_validators): Vec; + config(treasury): T::Balance; build(|config: &GenesisConfig| { + // set validators let _ = >::append(config.session_validators.clone()); + // set treasury + >::put(config.treasury); + // set genesis era + EraIndex::put(1); }); } } @@ -39,36 +74,145 @@ decl_event!( AccountId = ::AccountId, { BlockAuthored(AccountId), - NewSessionIndex(u32), - EndSessionIndex(u32), + NewEra(u32), + NewSession(u32), + EndSession(u32), } ); decl_module! { pub struct Module for enum Call where origin: T::Origin { fn deposit_event() = default; + + pub fn endorse( + origin, to:T::AccountId, value: T::Balance + ) -> DispatchResult { + let from = ensure_signed(origin)?; + >::insert(&from,&to,value); + >::append(&to,vec![from])?; + Ok(()) + } } } impl Module { + /// Calculates the total reward for the current era using the expected yearly rewards. + /// (TODO This could probably be cached). + fn total_payout() -> u64 { + let era_duration_in_ms: u64 = + (EPOCH_DURATION_IN_BLOCKS as u64) * 1000 * (T::SessionsPerEra::get() as u64); + let year_amount: u128 = REWARD_PER_YEAR; + let era_coef: f64 = + era_duration_in_ms as f64 / MILLISECS_PER_YEAR as f64; + let total_payout: u64 = (era_coef as f64 * year_amount as f64) as u64; + total_payout + } + + /// The payout for each validator is calculated as a weighted average of its produced blocks and endorsement, + /// and the expected total era reward. + /// + /// For every validator we use: + /// - The number of blocks `b` produced in the era. + /// - The endorsement `d` behind it. + /// For a total number of blocks B and a total endorsement D we calculate the validator weight W: + /// W = ((b/B) + (d/D)) / 2 + /// For an era payout P, each validator payout `p`: + /// p = W * P + /// + /// TODO: + /// How about not using iterators? The number of validators and its endorsers are not expected to be big. Also + /// this code is executed on end of eras, so it should not impact to the chain performance. + /// + /// However, it is desirable to find a storage layout using index helpers to help us remove the iterative approach. + fn validator_payout() { + let total_payout = Self::total_payout(); + + let validators = >::get(); + + // get the total blocks and endorsement + let mut total_block_count: u32 = 0; + let mut total_endoresement: u128 = 0; + for v in &validators { + total_block_count = total_block_count.checked_add( + >::get(&v) + ).ok_or("total_block_count Overflow").unwrap(); + let endorsers = >::get(&v); + for ed in endorsers { + total_endoresement = total_endoresement.checked_add( + >::get(&ed,&v).saturated_into() + ).ok_or("total_endoresement Overflow").unwrap(); + } + } + + if total_block_count > 0 && total_endoresement > 0 { + for v in &validators { + // block fraction + let block_count: u64 = >::get(&v) as u64; + let block_count_coef: f64 = block_count as f64 / total_block_count as f64; + // endorsement fraction + let mut endorsement: u128 = 0; + let endorsers = >::get(&v); + for ed in &endorsers { + endorsement = endorsement + .checked_add( + >::get(&ed,&v).saturated_into() + ) + .ok_or("endorsement Overflow").unwrap(); + } + let endorsement_coef: f64 = endorsement as f64 / total_endoresement as f64; + // weighted avg + let coef: f64 = (block_count_coef + endorsement_coef) / (2 as f64); + let payout: u32 = (total_payout as f64 * coef) as u32; + // desposit from treasury + let new_balance = >::get(&v) + T::Balance::from(payout); + let new_treasury = >::get().checked_sub(&T::Balance::from(payout)) + .ok_or("new_treasury Overflow").unwrap(); + + >::insert(&v,new_balance); + >::put(new_treasury); + } + } + } + + fn reset_validator_stats() { + let validators = >::get(); + for v in &validators { + >::insert(v.clone(),0); + } + } } pub struct SessionManager(T); impl pallet_session::SessionManager for SessionManager { fn new_session(new_index: u32) -> Option> { - SessionIndexStore::put(new_index); + >::deposit_event( - RawEvent::NewSessionIndex(new_index) + RawEvent::NewSession(new_index) ); + if new_index > 1 { - // TODO rotate, validator queue, etc use SessionHandler trait? - // TODO call custom economic logic - let validators = >::get(); - for v in validators.iter() { - >::insert(v.clone(),0); + let current_era = EraIndex::get(); + if new_index > (current_era * (T::SessionsPerEra::get() as u32)) { + // Era change. Reset SessionOfEraIndex to 1, increase the EraIndex by 1 + SessionOfEraIndex::put(1); + + let new_era_idx = EraIndex::get().checked_add(1) + .ok_or("SessionOfEraIndex Overflow").unwrap(); + + EraIndex::put(new_era_idx); + + >::validator_payout(); + >::reset_validator_stats(); + + // TODO new validator set? + } 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(validators) + Some(>::get()) } else { None } @@ -76,7 +220,7 @@ impl pallet_session::SessionManager for SessionManager>::deposit_event( - RawEvent::EndSessionIndex(end_index) + RawEvent::EndSession(end_index) ); } } @@ -97,7 +241,7 @@ impl pallet_authorship::EventHandler for AuthorshipE } // All below are trait implemenations that we need to satisfy for the historical feature of the pallet-session -// Is required by offences and session::historical. Find a way to remove without having to implement. +// and by offences. Find a way to remove without having to implement. /// The amount of exposure (to slashing) than an individual nominator has. #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, RuntimeDebug)] diff --git a/runtime/src/constants.rs b/runtime/src/constants.rs index 3a2ff5ea6ec..187e9cf7602 100644 --- a/runtime/src/constants.rs +++ b/runtime/src/constants.rs @@ -3,9 +3,10 @@ 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; } /// Money matters. @@ -50,7 +51,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 20 blocks. + pub const EPOCH_DURATION_IN_BLOCKS: BlockNumber = 1 * MINUTES; pub const EPOCH_DURATION_IN_SLOTS: u64 = { const SLOT_FILL_RATE: f64 = MILLISECS_PER_BLOCK as f64 / SLOT_DURATION as f64; @@ -61,4 +64,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 91ef2830925..6e9d35e4512 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -395,9 +395,14 @@ impl mb_core::Trait for Runtime { type Event = Event; } +parameter_types! { + pub const SessionsPerEra: u8 = EPOCH_PER_ERA; +} + impl mb_staking::Trait for Runtime { type Currency = Balances; type Event = Event; + type SessionsPerEra = SessionsPerEra; } From 147f55b6a0932fd91520e64bd2f1d96ad82d9790 Mon Sep 17 00:00:00 2001 From: tgmichel Date: Tue, 10 Mar 2020 18:19:58 +0100 Subject: [PATCH 03/10] not optimized endorsement queue approach --- pallets/mb-staking/src/lib.rs | 175 +++++++++++++++------------------- 1 file changed, 78 insertions(+), 97 deletions(-) diff --git a/pallets/mb-staking/src/lib.rs b/pallets/mb-staking/src/lib.rs index 711856603f8..95b3f58a048 100644 --- a/pallets/mb-staking/src/lib.rs +++ b/pallets/mb-staking/src/lib.rs @@ -1,5 +1,3 @@ - - #![cfg_attr(not(feature = "std"), no_std)] use sp_std::prelude::*; @@ -8,7 +6,7 @@ use sp_runtime::{RuntimeDebug,Perbill}; // use sp_runtime::traits::{OpaqueKeys,Convert}; use sp_runtime::traits::{Convert,SaturatedConversion,CheckedSub}; use sp_staking::offence::{OffenceDetails}; -use frame_support::{decl_module, decl_storage, decl_event}; +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}; @@ -28,7 +26,7 @@ pub trait Trait: system::Trait + pallet_balances::Trait + pallet_session::Trait type SessionsPerEra: Get; } -#[derive(Encode, Decode)] +#[derive(Debug, Encode, Decode)] enum EndorserStatus { Raised, Active, Chill } impl Default for EndorserStatus { fn default() -> Self { @@ -42,15 +40,23 @@ decl_storage! { // Session SessionOfEraIndex: u32; SessionValidators get(session_validators): Vec; - SessionValidatorAuthoring: map hasher(blake2_256) T::AccountId => u32; - // Endorsement - EndorsementStatus: - double_map hasher(blake2_256) T::AccountId, hasher(blake2_256) T::AccountId => EndorserStatus; - EndorsementBalance: - double_map hasher(blake2_256) T::AccountId, hasher(blake2_256) T::AccountId => T::Balance; // Endorser, Validator => amount - Endorsers: map hasher(blake2_256) T::AccountId => Vec; // Validator => Endorsers - // Validator Balance - ValidatorBalance: map hasher(blake2_256) T::AccountId => T::Balance; + SessionValidatorAuthoring: + map hasher(blake2_256) T::AccountId => u32; + + EndorsementQueue: + map hasher(blake2_256) T::AccountId => (EndorserStatus, u32); // Endorser => + + EndorsementQueueIndex: + map hasher(blake2_256) EndorserStatus => Vec; + + EndorsementQueueStatus: + map hasher(blake2_256) (EndorserStatus, u32) => (T::AccountId, T::AccountId); // Vec<(Endorser,Validator)> + + + // Endorser, Validator => (session_block_index,endorser_balance) + EndorsementSnapshot: + double_map hasher(blake2_256) T::AccountId, hasher(blake2_256) T::AccountId => (u32,BalanceOf); + // Treasury Treasury get(treasury): T::Balance; } @@ -68,6 +74,13 @@ decl_storage! { } } +decl_error! { + pub enum Error for Module { + AlreadyEndorsing, + NotEndorsing, + } +} + decl_event!( pub enum Event where @@ -82,104 +95,72 @@ decl_event!( decl_module! { pub struct Module for enum Call where origin: T::Origin { + + type Error = Error; fn deposit_event() = default; pub fn endorse( - origin, to:T::AccountId, value: T::Balance + origin, to:T::AccountId ) -> DispatchResult { let from = ensure_signed(origin)?; - >::insert(&from,&to,value); - >::append(&to,vec![from])?; - Ok(()) + Self::add_to_queue(&EndorserStatus::Raised,&from,&to) } } } impl Module { - /// Calculates the total reward for the current era using the expected yearly rewards. - /// (TODO This could probably be cached). - fn total_payout() -> u64 { - let era_duration_in_ms: u64 = - (EPOCH_DURATION_IN_BLOCKS as u64) * 1000 * (T::SessionsPerEra::get() as u64); - let year_amount: u128 = REWARD_PER_YEAR; - let era_coef: f64 = - era_duration_in_ms as f64 / MILLISECS_PER_YEAR as f64; - let total_payout: u64 = (era_coef as f64 * year_amount as f64) as u64; - total_payout - } - - /// The payout for each validator is calculated as a weighted average of its produced blocks and endorsement, - /// and the expected total era reward. - /// - /// For every validator we use: - /// - The number of blocks `b` produced in the era. - /// - The endorsement `d` behind it. - /// For a total number of blocks B and a total endorsement D we calculate the validator weight W: - /// W = ((b/B) + (d/D)) / 2 - /// For an era payout P, each validator payout `p`: - /// p = W * P - /// - /// TODO: - /// How about not using iterators? The number of validators and its endorsers are not expected to be big. Also - /// this code is executed on end of eras, so it should not impact to the chain performance. - /// - /// However, it is desirable to find a storage layout using index helpers to help us remove the iterative approach. - fn validator_payout() { - let total_payout = Self::total_payout(); + fn add_to_queue( + status: &EndorserStatus, from: &T::AccountId, to: &T::AccountId + ) -> DispatchResult { - let validators = >::get(); - - // get the total blocks and endorsement - let mut total_block_count: u32 = 0; - let mut total_endoresement: u128 = 0; - for v in &validators { - total_block_count = total_block_count.checked_add( - >::get(&v) - ).ok_or("total_block_count Overflow").unwrap(); - let endorsers = >::get(&v); - for ed in endorsers { - total_endoresement = total_endoresement.checked_add( - >::get(&ed,&v).saturated_into() - ).ok_or("total_endoresement Overflow").unwrap(); - } - } + let max_idx: u32 = match EndorsementQueueIndex::get(status).iter().max() { + Some(v) => *v, + None => 0 + }; + let idx: u32 = max_idx.checked_add(1) + .ok_or("SessionOfEraIndex Overflow").unwrap(); + + >::insert(from,(status, idx)); + EndorsementQueueIndex::append(status, vec![idx])?; + >::insert((status, idx),(from,to)); + Ok(()) + } - if total_block_count > 0 && total_endoresement > 0 { - for v in &validators { - // block fraction - let block_count: u64 = >::get(&v) as u64; - let block_count_coef: f64 = block_count as f64 / total_block_count as f64; - // endorsement fraction - let mut endorsement: u128 = 0; - let endorsers = >::get(&v); - for ed in &endorsers { - endorsement = endorsement - .checked_add( - >::get(&ed,&v).saturated_into() - ) - .ok_or("endorsement Overflow").unwrap(); - } - let endorsement_coef: f64 = endorsement as f64 / total_endoresement as f64; - // weighted avg - let coef: f64 = (block_count_coef + endorsement_coef) / (2 as f64); - let payout: u32 = (total_payout as f64 * coef) as u32; - // desposit from treasury - let new_balance = >::get(&v) + T::Balance::from(payout); - let new_treasury = >::get().checked_sub(&T::Balance::from(payout)) - .ok_or("new_treasury Overflow").unwrap(); - - >::insert(&v,new_balance); - >::put(new_treasury); - } - } + fn remove_from_queue(status: &EndorserStatus, index: u32, endorser: &T::AccountId) { + >::remove(endorser); + // Cant we use mutate() for this? + // https://crates.parity.io/frame_support/storage/trait.StorageMap.html + EndorsementQueueIndex::insert(status, EndorsementQueueIndex::get(status) + .iter().cloned() + .filter(|x| { + *x != index + }) + .collect::>() + ); + >::remove((status,index)); } - fn reset_validator_stats() { - let validators = >::get(); - for v in &validators { - >::insert(v.clone(),0); + fn queue_update() -> DispatchResult { + + let raised: Vec = + EndorsementQueueIndex::get(EndorserStatus::Raised); + + for idx in raised { + let d = >::get((EndorserStatus::Raised, idx)); + let endorser: &T::AccountId = &d.0; + let validator: &T::AccountId = &d.1; + + >::insert( + endorser, + validator, + (1, T::Currency::free_balance(&endorser)) + ); + + Self::remove_from_queue(&EndorserStatus::Raised,idx,validator); + Self::add_to_queue(&EndorserStatus::Active,endorser,validator)?; } + Ok(()) } } @@ -202,8 +183,8 @@ impl pallet_session::SessionManager for SessionManager>::validator_payout(); - >::reset_validator_stats(); + >::queue_update(); + // TODO reset snapshots // TODO new validator set? } else { From 3ba30b16766a94e7ce25d685ade579a781cf9d63 Mon Sep 17 00:00:00 2001 From: tgmichel Date: Wed, 11 Mar 2020 15:14:47 +0100 Subject: [PATCH 04/10] use hashes instead u32 --- pallets/mb-staking/src/lib.rs | 110 ++++++++++++++++++++-------------- 1 file changed, 65 insertions(+), 45 deletions(-) diff --git a/pallets/mb-staking/src/lib.rs b/pallets/mb-staking/src/lib.rs index 95b3f58a048..6d23c0675f2 100644 --- a/pallets/mb-staking/src/lib.rs +++ b/pallets/mb-staking/src/lib.rs @@ -4,7 +4,7 @@ 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,CheckedSub}; +use sp_runtime::traits::{Hash,Convert,SaturatedConversion,CheckedSub}; use sp_staking::offence::{OffenceDetails}; use frame_support::{decl_module, decl_storage, decl_event, decl_error, debug}; use frame_support::dispatch::{DispatchResult}; @@ -43,19 +43,39 @@ decl_storage! { SessionValidatorAuthoring: map hasher(blake2_256) T::AccountId => u32; - EndorsementQueue: - map hasher(blake2_256) T::AccountId => (EndorserStatus, u32); // Endorser => - - EndorsementQueueIndex: - map hasher(blake2_256) EndorserStatus => Vec; - - EndorsementQueueStatus: - map hasher(blake2_256) (EndorserStatus, u32) => (T::AccountId, T::AccountId); // Vec<(Endorser,Validator)> - - - // Endorser, Validator => (session_block_index,endorser_balance) - EndorsementSnapshot: - double_map hasher(blake2_256) T::AccountId, hasher(blake2_256) T::AccountId => (u32,BalanceOf); + /// Hashed key and status by endorser AccountId. + /// + /// - Used to access `Endorser` - endorser-validator association - using + /// an Endorser AccountId. + EndorserKeys: + map hasher(blake2_256) T::AccountId => (EndorserStatus, T::Hash); + + /// List of hashed keys for a status. + /// + /// A hashed key is the result of hashing endorser and validator AccountIds. + /// + /// - Used on era start to change status of endorsements. + /// - Used for validator selection. + EndorserQueue: + map hasher(blake2_256) EndorserStatus => Vec; + + /// Tuples of (endorser,validator)'s AccountIds mapped by status and hashed key . + /// + /// The actual association between endorser and validator AccountIds. + Endorsers: + map hasher(blake2_256) (EndorserStatus, T::Hash) => (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)>; // Treasury Treasury get(treasury): T::Balance; @@ -105,6 +125,15 @@ decl_module! { let from = ensure_signed(origin)?; Self::add_to_queue(&EndorserStatus::Raised,&from,&to) } + + pub fn unendorse( + origin + ) -> DispatchResult { + let from = ensure_signed(origin)?; + let (status, key) = >::get(&from); + Self::remove_from_queue(&status,key,&from); + Ok(()) + } } } @@ -113,53 +142,44 @@ impl Module { fn add_to_queue( status: &EndorserStatus, from: &T::AccountId, to: &T::AccountId ) -> DispatchResult { - - let max_idx: u32 = match EndorsementQueueIndex::get(status).iter().max() { - Some(v) => *v, - None => 0 - }; - let idx: u32 = max_idx.checked_add(1) - .ok_or("SessionOfEraIndex Overflow").unwrap(); - - >::insert(from,(status, idx)); - EndorsementQueueIndex::append(status, vec![idx])?; - >::insert((status, idx),(from,to)); + + let key = (from,to).using_encoded(::Hashing::hash); + + >::insert(from,(status, key)); + >::append(status, vec![key])?; + >::insert((status, key),(from,to)); + Ok(()) } - fn remove_from_queue(status: &EndorserStatus, index: u32, endorser: &T::AccountId) { - >::remove(endorser); - // Cant we use mutate() for this? - // https://crates.parity.io/frame_support/storage/trait.StorageMap.html - EndorsementQueueIndex::insert(status, EndorsementQueueIndex::get(status) - .iter().cloned() - .filter(|x| { - *x != index - }) - .collect::>() - ); - >::remove((status,index)); + fn remove_from_queue(status: &EndorserStatus, key: T::Hash, endorser: &T::AccountId) { + >::remove(endorser); + let mut queue_items = >::get(status); + queue_items.retain(|x| *x != key); + >::insert(status, queue_items); + >::remove((status,key)); } fn queue_update() -> DispatchResult { - let raised: Vec = - EndorsementQueueIndex::get(EndorserStatus::Raised); + let raised_queue: Vec = + >::get(EndorserStatus::Raised); - for idx in raised { - let d = >::get((EndorserStatus::Raised, idx)); + for key in raised_queue { + let d = >::get((EndorserStatus::Raised, key)); let endorser: &T::AccountId = &d.0; let validator: &T::AccountId = &d.1; - >::insert( + >::append( endorser, validator, - (1, T::Currency::free_balance(&endorser)) - ); + vec![(1, T::Currency::free_balance(&endorser))] + )?; - Self::remove_from_queue(&EndorserStatus::Raised,idx,validator); + >::remove((EndorserStatus::Raised,key)); Self::add_to_queue(&EndorserStatus::Active,endorser,validator)?; } + >::insert(EndorserStatus::Raised,>::new()); Ok(()) } } From 154cac54508da0b3bf61411bc2486953f2e090b3 Mon Sep 17 00:00:00 2001 From: tgmichel Date: Wed, 11 Mar 2020 17:22:14 +0100 Subject: [PATCH 05/10] removed status complexity --- pallets/mb-staking/src/lib.rs | 130 +++++++++++++++------------------- 1 file changed, 58 insertions(+), 72 deletions(-) diff --git a/pallets/mb-staking/src/lib.rs b/pallets/mb-staking/src/lib.rs index 6d23c0675f2..330837edcde 100644 --- a/pallets/mb-staking/src/lib.rs +++ b/pallets/mb-staking/src/lib.rs @@ -26,45 +26,24 @@ pub trait Trait: system::Trait + pallet_balances::Trait + pallet_session::Trait type SessionsPerEra: Get; } -#[derive(Debug, Encode, Decode)] -enum EndorserStatus { Raised, Active, Chill } -impl Default for EndorserStatus { - fn default() -> Self { - EndorserStatus::Raised - } -} - decl_storage! { trait Store for Module as MoonbeamStakingModule { EraIndex: u32; + + Validators: Vec; + // Session SessionOfEraIndex: u32; + BlockOfEraIndex: u32; SessionValidators get(session_validators): Vec; SessionValidatorAuthoring: map hasher(blake2_256) T::AccountId => u32; - /// Hashed key and status by endorser AccountId. - /// - /// - Used to access `Endorser` - endorser-validator association - using - /// an Endorser AccountId. - EndorserKeys: - map hasher(blake2_256) T::AccountId => (EndorserStatus, T::Hash); - - /// List of hashed keys for a status. - /// - /// A hashed key is the result of hashing endorser and validator AccountIds. - /// - /// - Used on era start to change status of endorsements. - /// - Used for validator selection. - EndorserQueue: - map hasher(blake2_256) EndorserStatus => Vec; - - /// Tuples of (endorser,validator)'s AccountIds mapped by status and hashed key . - /// - /// The actual association between endorser and validator AccountIds. - Endorsers: - map hasher(blake2_256) (EndorserStatus, T::Hash) => (T::AccountId, T::AccountId); + // validator -> endorsers + ValidatorEndorsers: map hasher(blake2_256) T::AccountId => Vec; + // 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. @@ -84,7 +63,9 @@ decl_storage! { config(session_validators): Vec; config(treasury): T::Balance; build(|config: &GenesisConfig| { - // set validators + // set all validators + let _ = >::append(config.session_validators.clone()); + // set initial selected validators let _ = >::append(config.session_validators.clone()); // set treasury >::put(config.treasury); @@ -123,64 +104,65 @@ decl_module! { origin, to:T::AccountId ) -> DispatchResult { let from = ensure_signed(origin)?; - Self::add_to_queue(&EndorserStatus::Raised,&from,&to) + if >::contains_key(&from) { + // TODO already endorsing + } + >::insert(&from,&to); + >::append(&to,vec![&from])?; + + Self::snapshot(&from,&to,T::Currency::free_balance(&from))?; + + Ok(()) } pub fn unendorse( origin ) -> DispatchResult { let from = ensure_signed(origin)?; - let (status, key) = >::get(&from); - Self::remove_from_queue(&status,key,&from); + if !>::contains_key(&from) { + // TODO is not endorsing + } + let validator = >::get(&from); + let mut endorsers = >::get(&validator); + + // remove from validator's endorser list + endorsers.retain(|x| x != &from); + >::insert(&validator, endorsers); + // remove as an endorser + >::remove(&from); + // remove all snapshots associated to the endorser + >::remove_prefix(&from); + + // Self::snapshot(&from,&validator,BalanceOf::::from(0))?; + Ok(()) } } } impl Module { - - fn add_to_queue( - status: &EndorserStatus, from: &T::AccountId, to: &T::AccountId + fn snapshot( + endorser: &T::AccountId, validator: &T::AccountId, amount: BalanceOf ) -> DispatchResult { - - let key = (from,to).using_encoded(::Hashing::hash); - - >::insert(from,(status, key)); - >::append(status, vec![key])?; - >::insert((status, key),(from,to)); - + >::append(&endorser,&validator, + vec![( + BlockOfEraIndex::get(), + amount + )])?; Ok(()) } - fn remove_from_queue(status: &EndorserStatus, key: T::Hash, endorser: &T::AccountId) { - >::remove(endorser); - let mut queue_items = >::get(status); - queue_items.retain(|x| *x != key); - >::insert(status, queue_items); - >::remove((status,key)); - } - - fn queue_update() -> DispatchResult { - - let raised_queue: Vec = - >::get(EndorserStatus::Raised); - - for key in raised_queue { - let d = >::get((EndorserStatus::Raised, key)); - let endorser: &T::AccountId = &d.0; - let validator: &T::AccountId = &d.1; - - >::append( - endorser, - validator, - vec![(1, T::Currency::free_balance(&endorser))] - )?; - - >::remove((EndorserStatus::Raised,key)); - Self::add_to_queue(&EndorserStatus::Active,endorser,validator)?; + 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) + )]); + } } - >::insert(EndorserStatus::Raised,>::new()); - Ok(()) } } @@ -198,12 +180,15 @@ impl pallet_session::SessionManager for SessionManager>::queue_update(); + >::reset_snapshots(); // TODO reset snapshots // TODO new validator set? @@ -232,6 +217,7 @@ impl pallet_authorship::EventHandler for AuthorshipE 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) // ); From f1b312ce5aba28c34b20a37d54a68c256c063202 Mon Sep 17 00:00:00 2001 From: tgmichel Date: Thu, 12 Mar 2020 14:57:33 +0100 Subject: [PATCH 06/10] endorsement calculation and validator selection --- node/src/chain_spec.rs | 6 +- pallets/mb-staking/src/lib.rs | 158 +++++++++++++++++++++------- runtime/src/constants.rs | 2 + scripts/staging/run-node-collins.sh | 9 ++ scripts/staging/set-keys.sh | 32 ++++++ 5 files changed, 168 insertions(+), 39 deletions(-) create mode 100755 scripts/staging/run-node-collins.sh diff --git a/node/src/chain_spec.rs b/node/src/chain_spec.rs index 8dfaf4e975d..2c626f82dcb 100644 --- a/node/src/chain_spec.rs +++ b/node/src/chain_spec.rs @@ -155,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<( @@ -177,6 +179,8 @@ fn development_config_genesis() -> GenesisConfig { // 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-staking/src/lib.rs b/pallets/mb-staking/src/lib.rs index 330837edcde..c3beaadd886 100644 --- a/pallets/mb-staking/src/lib.rs +++ b/pallets/mb-staking/src/lib.rs @@ -15,7 +15,7 @@ use system::{ensure_signed}; #[allow(dead_code)] mod constants; use constants::time::{MILLISECS_PER_YEAR,EPOCH_DURATION_IN_BLOCKS}; -use constants::mb_genesis::{REWARD_PER_YEAR}; +use constants::mb_genesis::{REWARD_PER_YEAR,VALIDATORS_PER_SESSION}; type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; @@ -28,35 +28,38 @@ pub trait Trait: system::Trait + pallet_balances::Trait + pallet_session::Trait decl_storage! { trait Store for Module as MoonbeamStakingModule { + /// The number of Era. EraIndex: u32; - + /// The total validator pool. Validators: Vec; - - // Session + /// 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; - - // validator -> endorsers + /// One to Many Validator -> Endorsers. ValidatorEndorsers: map hasher(blake2_256) T::AccountId => Vec; - - // endorser => validator + /// 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. + /// 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)>; - // Treasury + /// 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 { @@ -69,8 +72,9 @@ decl_storage! { let _ = >::append(config.session_validators.clone()); // set treasury >::put(config.treasury); - // set genesis era + // set genesis era data EraIndex::put(1); + BlockOfEraIndex::put(1); }); } } @@ -100,58 +104,136 @@ decl_module! { 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) { - // TODO already endorsing + 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])?; - - Self::snapshot(&from,&to,T::Currency::free_balance(&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) { - // TODO is not endorsing + Err(Error::::NotEndorsing).map_err(Into::into) } + let validator = >::get(&from); let mut endorsers = >::get(&validator); - - // remove from validator's endorser list + + /// Remove One to Many validator->endorsers association endorsers.retain(|x| x != &from); >::insert(&validator, endorsers); - // remove as an endorser + /// Remove One to One endorser->validator association >::remove(&from); - // remove all snapshots associated to the endorser + /// Remove all snapshots associated to the endorser, using the double map prefix >::remove_prefix(&from); - // Self::snapshot(&from,&validator,BalanceOf::::from(0))?; - Ok(()) } } } impl Module { - fn snapshot( + + /// 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 - )])?; + 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() { @@ -177,21 +259,20 @@ impl pallet_session::SessionManager for SessionManager 1 { let current_era = EraIndex::get(); if new_index > (current_era * (T::SessionsPerEra::get() as u32)) { - // Era change. Reset SessionOfEraIndex to 1, increase the EraIndex by 1 + // 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); - + // Select a new validator set + let selected_validatiors = >::select_validators(); + >::put(selected_validatiors.clone()); + // Reset all snapshots >::reset_snapshots(); - // TODO reset snapshots - - // TODO new validator set? } else { // Same Era, next session. Increase SessionOfEraIndex by 1. let new_era_session_idx = SessionOfEraIndex::get().checked_add(1) @@ -227,8 +308,9 @@ impl pallet_authorship::EventHandler for AuthorshipE } } +// !!!!!! // 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. +// 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)] diff --git a/runtime/src/constants.rs b/runtime/src/constants.rs index 187e9cf7602..69361483470 100644 --- a/runtime/src/constants.rs +++ b/runtime/src/constants.rs @@ -7,6 +7,8 @@ pub mod mb_genesis { 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. 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..307b338a670 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,13 @@ 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", @@ -61,3 +69,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"'" + ] + }' From ff7bef72d636ca5c0eb0e046b6acb15fca4214e3 Mon Sep 17 00:00:00 2001 From: tgmichel Date: Fri, 13 Mar 2020 13:25:29 +0100 Subject: [PATCH 07/10] fix custom DispatchErrors --- pallets/mb-staking/src/lib.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pallets/mb-staking/src/lib.rs b/pallets/mb-staking/src/lib.rs index c3beaadd886..623c7b0de33 100644 --- a/pallets/mb-staking/src/lib.rs +++ b/pallets/mb-staking/src/lib.rs @@ -109,15 +109,15 @@ decl_module! { origin, to:T::AccountId ) -> DispatchResult { let from = ensure_signed(origin)?; - /// Check if the Account is already endorsing. + // Check if the Account is already endorsing. if >::contains_key(&from) { - Err(Error::::AlreadyEndorsing).map_err(Into::into) + return Err(Error::::AlreadyEndorsing).map_err(Into::into); } - /// Set One to One endorser->validator association. + // Set One to One endorser->validator association. >::insert(&from,&to); - /// Set One to Many validator->endorsers association. + // Set One to Many validator->endorsers association. >::append(&to,vec![&from])?; - /// Create a snapshot with the current free balance of the endorser. + // Create a snapshot with the current free balance of the endorser. Self::set_snapshot(&from,&to,T::Currency::free_balance(&from))?; Ok(()) } @@ -127,20 +127,20 @@ decl_module! { origin ) -> DispatchResult { let from = ensure_signed(origin)?; - /// Check if the Account is actively endorsing. + // Check if the Account is actively endorsing. if !>::contains_key(&from) { - Err(Error::::NotEndorsing).map_err(Into::into) + return Err(Error::::NotEndorsing).map_err(Into::into); } let validator = >::get(&from); let mut endorsers = >::get(&validator); - /// Remove One to Many validator->endorsers association + // Remove One to Many validator->endorsers association endorsers.retain(|x| x != &from); >::insert(&validator, endorsers); - /// Remove One to One endorser->validator association + // Remove One to One endorser->validator association >::remove(&from); - /// Remove all snapshots associated to the endorser, using the double map prefix + // Remove all snapshots associated to the endorser, using the double map prefix >::remove_prefix(&from); Ok(()) From c451307d382fa4c9e1b6ee0c9d74ea559f62aa24 Mon Sep 17 00:00:00 2001 From: tgmichel Date: Tue, 17 Mar 2020 12:42:02 +0100 Subject: [PATCH 08/10] offchain validator selection --- pallets/mb-staking/src/lib.rs | 58 +++++++++++++++++++++++++++++++---- runtime/src/lib.rs | 8 +++++ scripts/staging/set-keys.sh | 12 ++++++++ 3 files changed, 72 insertions(+), 6 deletions(-) diff --git a/pallets/mb-staking/src/lib.rs b/pallets/mb-staking/src/lib.rs index 623c7b0de33..5bdcc95a129 100644 --- a/pallets/mb-staking/src/lib.rs +++ b/pallets/mb-staking/src/lib.rs @@ -4,24 +4,37 @@ use sp_std::prelude::*; use codec::{HasCompact, Encode, Decode}; use sp_runtime::{RuntimeDebug,Perbill}; // use sp_runtime::traits::{OpaqueKeys,Convert}; -use sp_runtime::traits::{Hash,Convert,SaturatedConversion,CheckedSub}; +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::{MILLISECS_PER_YEAR,EPOCH_DURATION_IN_BLOCKS}; -use constants::mb_genesis::{REWARD_PER_YEAR,VALIDATORS_PER_SESSION}; +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; } @@ -145,10 +158,46 @@ decl_module! { Ok(()) } + + fn offchain_worker(block_number: T::BlockNumber) { + // 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(()) + } } } impl Module { + + /// 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( @@ -268,9 +317,6 @@ impl pallet_session::SessionManager for SessionManager>::select_validators(); - >::put(selected_validatiors.clone()); // Reset all snapshots >::reset_snapshots(); } else { diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 6e9d35e4512..a212ed5348c 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -399,8 +399,16 @@ parameter_types! { pub const SessionsPerEra: u8 = EPOCH_PER_ERA; } +type SubmitMBTransaction = TransactionSubmitter< + mb_staking::crypto::Public, + Runtime, + UncheckedExtrinsic +>; + impl mb_staking::Trait for Runtime { type Currency = Balances; + type SubmitTransaction = SubmitMBTransaction; + type Call = Call; type Event = Event; type SessionsPerEra = SessionsPerEra; } diff --git a/scripts/staging/set-keys.sh b/scripts/staging/set-keys.sh index 307b338a670..999d928f5ad 100755 --- a/scripts/staging/set-keys.sh +++ b/scripts/staging/set-keys.sh @@ -22,6 +22,18 @@ 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", From e83a0b4b7c05899b96e0d5925d04ac954bfc7bed Mon Sep 17 00:00:00 2001 From: tgmichel Date: Thu, 19 Mar 2020 11:53:29 +0100 Subject: [PATCH 09/10] offchain snapshots --- pallets/mb-staking/src/lib.rs | 52 ++++++++++++++++++++++++++++++++++- runtime/src/constants.rs | 4 +-- 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/pallets/mb-staking/src/lib.rs b/pallets/mb-staking/src/lib.rs index 5bdcc95a129..765a1c6917e 100644 --- a/pallets/mb-staking/src/lib.rs +++ b/pallets/mb-staking/src/lib.rs @@ -160,6 +160,8 @@ decl_module! { } fn offchain_worker(block_number: T::BlockNumber) { + // Set snapshots + Self::offchain_set_snapshots(); // Select validators off-chain Self::offchain_validator_selection(block_number); } @@ -171,11 +173,60 @@ decl_module! { >::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 @@ -384,7 +435,6 @@ pub struct Exposure { /// 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 { diff --git a/runtime/src/constants.rs b/runtime/src/constants.rs index 69361483470..b672ac768a5 100644 --- a/runtime/src/constants.rs +++ b/runtime/src/constants.rs @@ -54,8 +54,8 @@ pub mod time { pub const PRIMARY_PROBABILITY: (u64, u64) = (1, 4); // 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 20 blocks. - pub const EPOCH_DURATION_IN_BLOCKS: BlockNumber = 1 * MINUTES; + // 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; From 6c3962d72f2ec5cf0723b0c1efe1ade4c63a18ca Mon Sep 17 00:00:00 2001 From: tgmichel Date: Thu, 19 Mar 2020 12:13:01 +0100 Subject: [PATCH 10/10] rename mb-staking to mb-session --- node/src/chain_spec.rs | 4 ++-- pallets/{mb-staking => mb-session}/Cargo.toml | 2 +- pallets/{mb-staking => mb-session}/src/lib.rs | 0 runtime/Cargo.toml | 4 ++-- runtime/src/lib.rs | 18 +++++++++--------- 5 files changed, 14 insertions(+), 14 deletions(-) rename pallets/{mb-staking => mb-session}/Cargo.toml (99%) rename pallets/{mb-staking => mb-session}/src/lib.rs (100%) diff --git a/node/src/chain_spec.rs b/node/src/chain_spec.rs index 2c626f82dcb..c5aa94ad2cf 100644 --- a/node/src/chain_spec.rs +++ b/node/src/chain_spec.rs @@ -17,7 +17,7 @@ pub use node_primitives::{AccountId, Balance, Signature, Block}; use moonbeam_runtime::{ GenesisConfig, AuthorityDiscoveryConfig, BabeConfig, BalancesConfig, ContractsConfig, GrandpaConfig, ImOnlineConfig, SessionConfig, SessionKeys, - IndicesConfig, SudoConfig, SystemConfig, WASM_BINARY, MoonbeamCoreConfig, MoonbeamStakingConfig + IndicesConfig, SudoConfig, SystemConfig, WASM_BINARY, MoonbeamCoreConfig, MoonbeamSessionConfig }; use moonbeam_runtime::constants::mb_genesis::*; @@ -141,7 +141,7 @@ fn testnet_genesis( treasury: TREASURY_FUND, genesis_accounts: endowed_accounts.clone(), }), - mb_staking: Some(MoonbeamStakingConfig { + mb_session: Some(MoonbeamSessionConfig { treasury: TREASURY_FUND, session_validators: initial_authorities.iter().map(|x| { x.0.clone() diff --git a/pallets/mb-staking/Cargo.toml b/pallets/mb-session/Cargo.toml similarity index 99% rename from pallets/mb-staking/Cargo.toml rename to pallets/mb-session/Cargo.toml index dfe1aaf723b..744ac3fa4b8 100644 --- a/pallets/mb-staking/Cargo.toml +++ b/pallets/mb-session/Cargo.toml @@ -1,7 +1,7 @@ [package] authors = ['PureStake'] edition = '2018' -name = 'mb-staking' +name = 'mb-session' version = '0.1.0' [dependencies] diff --git a/pallets/mb-staking/src/lib.rs b/pallets/mb-session/src/lib.rs similarity index 100% rename from pallets/mb-staking/src/lib.rs rename to pallets/mb-session/src/lib.rs diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 617e8c323a8..9b78a35d363 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -58,7 +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-staking = { version = "0.1.0", default-features = false, path = "../pallets/mb-staking", package = "mb-staking" } +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" } @@ -114,5 +114,5 @@ std = [ "pallet-recovery/std", "pallet-vesting/std", "mb-core/std", - "mb-staking/std", + "mb-session/std", ] diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index a212ed5348c..89d2d2e281f 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -57,7 +57,7 @@ pub use constants::{time::*, currency::*, mb_genesis::*}; /// Importing the moonbeam core pallet pub use mb_core; -pub use mb_staking; +pub use mb_session; /// Implementations of some helper traits passed into runtime modules as associated types. pub mod impls; @@ -228,7 +228,7 @@ impl pallet_authorship::Trait for Runtime { type FindAuthor = pallet_session::FindAccountFromAuthorIndex; type UncleGenerations = UncleGenerations; type FilterUncle = (); - type EventHandler = (mb_staking::AuthorshipEventHandler, ImOnline); + type EventHandler = (mb_session::AuthorshipEventHandler, ImOnline); } parameter_types! { @@ -240,15 +240,15 @@ impl pallet_session::Trait for Runtime { type ValidatorId = ::AccountId; type ValidatorIdOf = ConvertInto; type ShouldEndSession = Babe; - type SessionManager = mb_staking::SessionManager; + type SessionManager = mb_session::SessionManager; type SessionHandler = ::KeyTypeIdProviders; type Keys = SessionKeys; type DisabledValidatorsThreshold = DisabledValidatorsThreshold; } impl pallet_session::historical::Trait for Runtime { - type FullIdentification = mb_staking::Exposure; - type FullIdentificationOf = mb_staking::ExposureOf; + type FullIdentification = mb_session::Exposure; + type FullIdentificationOf = mb_session::ExposureOf; } parameter_types! { @@ -306,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 = mb_staking::Offences; + type OnOffenceHandler = mb_session::Offences; } impl pallet_authority_discovery::Trait for Runtime {} @@ -400,12 +400,12 @@ parameter_types! { } type SubmitMBTransaction = TransactionSubmitter< - mb_staking::crypto::Public, + mb_session::crypto::Public, Runtime, UncheckedExtrinsic >; -impl mb_staking::Trait for Runtime { +impl mb_session::Trait for Runtime { type Currency = Balances; type SubmitTransaction = SubmitMBTransaction; type Call = Call; @@ -440,7 +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}, - MoonbeamStaking: mb_staking::{Module, Call, Storage, Event, Config}, + MoonbeamSession: mb_session::{Module, Call, Storage, Event, Config}, } );