diff --git a/Cargo.lock b/Cargo.lock
index 22dc2428f5..2056831568 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4117,6 +4117,7 @@ dependencies = [
"log",
"moonbeam-rpc-primitives-txpool",
"pallet-aura",
+ "pallet-author-filter",
"pallet-balances",
"pallet-democracy",
"pallet-ethereum",
@@ -4520,6 +4521,20 @@ dependencies = [
"sp-timestamp",
]
+[[package]]
+name = "pallet-author-filter"
+version = "0.1.0"
+dependencies = [
+ "author-inherent",
+ "cumulus-parachain-upgrade",
+ "frame-support",
+ "frame-system",
+ "parity-scale-codec",
+ "sp-core",
+ "sp-runtime",
+ "stake",
+]
+
[[package]]
name = "pallet-authority-discovery"
version = "2.0.1"
diff --git a/pallets/author-filter/Cargo.toml b/pallets/author-filter/Cargo.toml
new file mode 100644
index 0000000000..3afe065657
--- /dev/null
+++ b/pallets/author-filter/Cargo.toml
@@ -0,0 +1,29 @@
+[package]
+authors = ["PureStake"]
+edition = "2018"
+name = "pallet-author-filter"
+version = "0.1.0"
+
+[dependencies]
+parity-scale-codec = { version = "1.3.0", default-features = false, features = ["derive"] }
+
+frame-support = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
+frame-system = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
+sp-core = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
+sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
+author-inherent = { path = "../author-inherent", default-features = false }
+stake = { path = "../stake", default-features = false }
+cumulus-parachain-upgrade = { git = "https://github.com/paritytech/cumulus", default-features = false, branch = "master" }
+
+[features]
+default = ["std"]
+std = [
+ "parity-scale-codec/std",
+ "frame-support/std",
+ "frame-system/std",
+ "author-inherent/std",
+ "stake/std",
+ "sp-core/std",
+ "sp-runtime/std",
+ "cumulus-parachain-upgrade/std",
+]
diff --git a/pallets/author-filter/src/lib.rs b/pallets/author-filter/src/lib.rs
new file mode 100644
index 0000000000..8ca0830dc5
--- /dev/null
+++ b/pallets/author-filter/src/lib.rs
@@ -0,0 +1,151 @@
+// Copyright 2019-2020 PureStake Inc.
+// This file is part of Moonbeam.
+
+// Moonbeam is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Moonbeam is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Moonbeam. If not, see .
+
+//! Small pallet responsible determining which accounts are eligible to author at the current
+//! block height.
+//!
+//! Currently this pallet is tightly coupled to our stake pallet, but this design
+//! should be generalized in the future.
+//!
+//! Using a randomness beacon supplied by the `Randomness` trait, this pallet takes the set of
+//! currently staked accounts from pallet stake, and filters them down to a pseudorandom subset.
+//! The current technique gives no preference to any particular author. In the future, we could
+//! disfavor authors who are authoring a disproportionate amount of the time in an attempt to
+//! "even the playing field".
+
+#![cfg_attr(not(feature = "std"), no_std)]
+
+use frame_support::pallet;
+
+pub use pallet::*;
+
+#[pallet]
+pub mod pallet {
+
+ use frame_support::pallet_prelude::*;
+ use frame_support::traits::Randomness;
+ use frame_support::traits::Vec;
+ use frame_system::pallet_prelude::*;
+ use sp_core::H256;
+ use sp_runtime::Percent;
+
+ /// The Author Filter pallet
+ #[pallet::pallet]
+ pub struct Pallet(PhantomData);
+
+ /// Configuration trait of this pallet.
+ #[pallet::config]
+ pub trait Config:
+ frame_system::Config + stake::Config + cumulus_parachain_upgrade::Config
+ {
+ /// The overarching event type
+ type Event: From> + IsType<::Event>;
+ /// Deterministic on-chain pseudo-randomness used to do the filtering
+ type RandomnessSource: Randomness;
+ }
+
+ // This code will be called by the author-inherent pallet to check whether the reported author
+ // of this block is eligible at this height. We calculate that result on demand and do not
+ // record it instorage (although we do emit a debugging event for now).
+ // This implementation relies on the relay parent's block number from the validation data
+ // inherent. Therefore the validation data inherent **must** be included before this check is
+ // performed. Concretely the validation data inherent must be included before the author
+ // inherent.
+ impl author_inherent::CanAuthor for Pallet {
+ fn can_author(account: &T::AccountId) -> bool {
+ let mut staked: Vec = stake::Module::::validators();
+
+ let num_eligible = EligibleRatio::::get().mul_ceil(staked.len());
+ let mut eligible = Vec::with_capacity(num_eligible);
+
+ // Grab the relay parent height as a temporary source of relay-based entropy
+ let validation_data = cumulus_parachain_upgrade::Module::::validation_data()
+ .expect("validation data was set in parachain system inherent");
+ let relay_height = validation_data.persisted.block_number;
+
+ for i in 0..num_eligible {
+ // A context identifier for grabbing the randomness. Consists of three parts
+ // - The constant string *b"filter" - to identify this pallet
+ // - The index `i` when we're selecting the ith eligible author
+ // - The relay parent block number so that the eligible authors at the next height
+ // change. Avoids liveness attacks from colluding minorities of active authors.
+ // Third one will not be necessary once we dleverage the relay chain's randomness.
+ let subject: [u8; 8] = [
+ b'f',
+ b'i',
+ b'l',
+ b't',
+ b'e',
+ b'r',
+ i as u8,
+ relay_height as u8,
+ ];
+ let index = T::RandomnessSource::random(&subject).to_low_u64_be() as usize;
+
+ // Move the selected author from the original vector into the eligible vector
+ // TODO we could short-circuit this check by returning early when the claimed
+ // author is selected. For now I'll leave it like this because:
+ // 1. it is easier to understand what our core filtering logic is
+ // 2. we currently show the entire filtered set in the debug event
+ eligible.push(staked.remove(index % staked.len()));
+ }
+
+ // Emit an event for debugging purposes
+ // let our_height = frame_system::Module::::block_number();
+ // >::deposit_event(Event::Filtered(our_height, relay_height, eligible.clone()));
+
+ eligible.contains(account)
+ }
+ }
+
+ // No hooks
+ #[pallet::hooks]
+ impl Hooks> for Pallet {}
+
+ #[pallet::call]
+ impl Pallet {
+ /// Update the eligible ratio. Intended to be called by governance.
+ #[pallet::weight(0)]
+ pub fn set_eligible(origin: OriginFor, new: Percent) -> DispatchResultWithPostInfo {
+ ensure_root(origin)?;
+ EligibleRatio::::put(&new);
+ >::deposit_event(Event::EligibleUpdated(new));
+
+ Ok(Default::default())
+ }
+ }
+
+ /// The percentage of active staked authors that will be eligible at each height.
+ #[pallet::storage]
+ pub type EligibleRatio = StorageValue<_, Percent, ValueQuery, Half>;
+
+ // Default value for the `EligibleRatio` is one half.
+ #[pallet::type_value]
+ pub fn Half() -> Percent {
+ Percent::from_percent(50)
+ }
+
+ #[pallet::event]
+ #[pallet::generate_deposit(fn deposit_event)]
+ pub enum Event {
+ /// The amount of eligible authors for the filter to select has been changed.
+ EligibleUpdated(Percent),
+ /// The staked authors have been filtered to these eligible authors in this block.
+ /// This is a debugging and development event and should be removed eventually.
+ /// Fields are: para block height, relay block height, eligible authors
+ Filtered(T::BlockNumber, u32, Vec),
+ }
+}
diff --git a/pallets/author-inherent/src/lib.rs b/pallets/author-inherent/src/lib.rs
index 17d3afc078..9c77584bf8 100644
--- a/pallets/author-inherent/src/lib.rs
+++ b/pallets/author-inherent/src/lib.rs
@@ -41,7 +41,8 @@ pub trait EventHandler {
pub trait CanAuthor {
fn can_author(account: &AccountId) -> bool;
}
-/// Default permissions is none, see `stake` pallet for different impl used in runtime
+/// Default implementation where anyone can author, see `stake` and `author-filter` pallets for
+/// additional implementations.
impl CanAuthor for () {
fn can_author(_: &T) -> bool {
true
@@ -52,7 +53,9 @@ pub trait Config: System {
/// Other pallets that want to be informed about block authorship
type EventHandler: EventHandler;
- /// Checks if account can be set as block author
+ /// Checks if account can be set as block author.
+ /// If the pallet that implements this trait depends on an inherent, that inherent **must**
+ /// be included before this one.
type CanAuthor: CanAuthor;
}
diff --git a/pallets/stake/src/lib.rs b/pallets/stake/src/lib.rs
index f4220b9322..c632efb46c 100644
--- a/pallets/stake/src/lib.rs
+++ b/pallets/stake/src/lib.rs
@@ -522,7 +522,7 @@ decl_storage! {
/// Current candidates with associated state
Candidates: map hasher(blake2_128_concat) T::AccountId => Option>;
/// Current validator set
- Validators: Vec;
+ Validators get(fn validators): Vec;
/// Total Locked
Total: BalanceOf;
/// Pool of candidates, ordered by account id
diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml
index a80106a075..050d3e8efe 100644
--- a/runtime/Cargo.toml
+++ b/runtime/Cargo.toml
@@ -17,6 +17,7 @@ precompiles = { path = "precompiles/", default-features = false }
account = { path = "account/", default-features = false }
pallet-ethereum-chain-id = { path = "../pallets/ethereum-chain-id", default-features = false }
stake = { path = "../pallets/stake", default-features = false }
+pallet-author-filter = { path = "../pallets/author-filter", default-features = false }
# Substrate dependencies
pallet-aura = { git = "https://github.com/paritytech/substrate.git", default-features = false, branch = "master", optional = true }
@@ -113,6 +114,7 @@ std = [
"cumulus-primitives/std",
"account/std",
"stake/std",
+ "pallet-author-filter/std",
]
# Will be enabled by the `wasm-builder` when building the runtime for WASM.
diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs
index 3c5f573bfc..f6826b75ca 100644
--- a/runtime/src/lib.rs
+++ b/runtime/src/lib.rs
@@ -110,7 +110,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
spec_name: create_runtime_str!("moonbase-alphanet"),
impl_name: create_runtime_str!("moonbase-alphanet"),
authoring_version: 3,
- spec_version: 18,
+ spec_version: 19,
impl_version: 1,
apis: RUNTIME_API_VERSIONS,
transaction_version: 2,
@@ -399,7 +399,12 @@ impl stake::Config for Runtime {
}
impl author_inherent::Config for Runtime {
type EventHandler = Stake;
- type CanAuthor = Stake;
+ type CanAuthor = AuthorFilter;
+}
+
+impl pallet_author_filter::Config for Runtime {
+ type Event = Event;
+ type RandomnessSource = RandomnessCollectiveFlip;
}
construct_runtime! {
@@ -420,9 +425,12 @@ construct_runtime! {
EVM: pallet_evm::{Module, Config, Call, Storage, Event},
Ethereum: pallet_ethereum::{Module, Call, Storage, Event, Config, ValidateUnsigned},
Stake: stake::{Module, Call, Storage, Event, Config},
- AuthorInherent: author_inherent::{Module, Call, Storage, Inherent},
Scheduler: pallet_scheduler::{Module, Storage, Config, Event, Call},
Democracy: pallet_democracy::{Module, Storage, Config, Event, Call},
+ // The order matters here. Inherents will be included in the order specified here.
+ // Concretely wee need the author inherent to come after the parachain_upgrade inherent.
+ AuthorInherent: author_inherent::{Module, Call, Storage, Inherent},
+ AuthorFilter: pallet_author_filter::{Module, Storage, Event,}
}
}