diff --git a/polkadot/roadmap/implementers-guide/src/runtime/inclusion.md b/polkadot/roadmap/implementers-guide/src/runtime/inclusion.md index f2d9f21422..7433d51b96 100644 --- a/polkadot/roadmap/implementers-guide/src/runtime/inclusion.md +++ b/polkadot/roadmap/implementers-guide/src/runtime/inclusion.md @@ -17,6 +17,7 @@ struct CandidatePendingAvailability { descriptor: CandidateDescriptor, availability_votes: Bitfield, // one bit per validator. relay_parent_number: BlockNumber, // number of the relay-parent. + backers: Bitfield, // one bit per validator, set for those who backed the candidate. backed_in_number: BlockNumber, } ``` @@ -77,6 +78,7 @@ All failed checks should lead to an unrecoverable error making the block invalid * `enact_candidate(relay_parent_number: BlockNumber, CommittedCandidateReceipt)`: 1. If the receipt contains a code upgrade, Call `Paras::schedule_code_upgrade(para_id, code, relay_parent_number + config.validationl_upgrade_delay)`. > TODO: Note that this is safe as long as we never enact candidates where the relay parent is across a session boundary. In that case, which we should be careful to avoid with contextual execution, the configuration might have changed and the para may de-sync from the host's understanding of it. + 1. Reward all backing validators of each candidate, contained within the `backers` field. 1. call `Ump::enact_upward_messages` for each backed candidate, using the [`UpwardMessage`s](../types/messages.md#upward-message) from the [`CandidateCommitments`](../types/candidate.md#candidate-commitments). 1. call `Dmp::prune_dmq` with the para id of the candidate and the candidate's `processed_downward_messages`. 1. call `Hrmp::prune_hrmp` with the para id of the candiate and the candidate's `hrmp_watermark`. diff --git a/polkadot/runtime/common/src/paras_registrar.rs b/polkadot/runtime/common/src/paras_registrar.rs index a813f07060..55facc5d3c 100644 --- a/polkadot/runtime/common/src/paras_registrar.rs +++ b/polkadot/runtime/common/src/paras_registrar.rs @@ -262,7 +262,7 @@ mod tests { }, testing::{UintAuthorityId, TestXt}, Perbill, curve::PiecewiseLinear, }; use primitives::v1::{ - Balance, BlockNumber, Header, Signature, AuthorityDiscoveryId, + Balance, BlockNumber, Header, Signature, AuthorityDiscoveryId, ValidatorIndex, }; use frame_system::limits; use frame_support::{ @@ -472,8 +472,16 @@ mod tests { impl configuration::Config for Test { } + pub struct TestRewardValidators; + + impl inclusion::RewardValidators for TestRewardValidators { + fn reward_backing(_: impl IntoIterator) { } + fn reward_bitfields(_: impl IntoIterator) { } + } + impl inclusion::Config for Test { type Event = (); + type RewardValidators = TestRewardValidators; } impl session_info::AuthorityDiscoveryConfig for Test { diff --git a/polkadot/runtime/parachains/src/inclusion.rs b/polkadot/runtime/parachains/src/inclusion.rs index 0b05f78064..47bf111f4c 100644 --- a/polkadot/runtime/parachains/src/inclusion.rs +++ b/polkadot/runtime/parachains/src/inclusion.rs @@ -62,6 +62,8 @@ pub struct CandidatePendingAvailability { descriptor: CandidateDescriptor, /// The received availability votes. One bit per validator. availability_votes: BitVec, + /// The backers of the candidate pending availability. + backers: BitVec, /// The block number of the relay-parent of the receipt. relay_parent_number: N, /// The block number of the relay-chain block this was backed in. @@ -85,6 +87,16 @@ impl CandidatePendingAvailability { } } +/// A hook for applying validator rewards +pub trait RewardValidators { + // Reward the validators with the given indices for issuing backing statements. + fn reward_backing(validators: impl IntoIterator); + // Reward the validators with the given indices for issuing availability bitfields. + // Validators are sent to this hook when they have contributed to the availability + // of a candidate by setting a bit in their bitfield. + fn reward_bitfields(validators: impl IntoIterator); +} + pub trait Config: frame_system::Config + paras::Config @@ -94,6 +106,7 @@ pub trait Config: + configuration::Config { type Event: From> + Into<::Event>; + type RewardValidators: RewardValidators; } decl_storage! { @@ -341,6 +354,8 @@ impl Module { Self::enact_candidate( pending_availability.relay_parent_number, receipt, + pending_availability.backers, + pending_availability.availability_votes, ); freed_cores.push(pending_availability.core); @@ -375,9 +390,9 @@ impl Module { let check_cx = CandidateCheckContext::::new(); // do all checks before writing storage. - let core_indices = { + let core_indices_and_backers = { let mut skip = 0; - let mut core_indices = Vec::with_capacity(candidates.len()); + let mut core_indices_and_backers = Vec::with_capacity(candidates.len()); let mut last_core = None; let mut check_assignment_in_order = |assignment: &CoreAssignment| -> DispatchResult { @@ -408,6 +423,7 @@ impl Module { 'a: for (candidate_idx, candidate) in candidates.iter().enumerate() { let para_id = candidate.descriptor().para_id; + let mut backers = bitvec::bitvec![BitOrderLsb0, u8; 0; validators.len()]; // we require that the candidate is in the context of the parent block. ensure!( @@ -504,9 +520,19 @@ impl Module { ), Err(()) => { Err(Error::::InvalidBacking)?; } } + + for (bit_idx, _) in candidate + .validator_indices.iter() + .enumerate().filter(|(_, signed)| **signed) + { + let val_idx = group_vals.get(bit_idx) + .expect("this query done above; qed"); + + backers.set(*val_idx as _, true); + } } - core_indices.push(assignment.core); + core_indices_and_backers.push((assignment.core, backers)); continue 'a; } } @@ -525,11 +551,12 @@ impl Module { check_assignment_in_order(assignment)?; } - core_indices + core_indices_and_backers }; // one more sweep for actually writing to storage. - for (candidate, core) in candidates.into_iter().zip(core_indices.iter().cloned()) { + let core_indices = core_indices_and_backers.iter().map(|&(ref c, _)| c.clone()).collect(); + for (candidate, (core, backers)) in candidates.into_iter().zip(core_indices_and_backers) { let para_id = candidate.descriptor().para_id; // initialize all availability votes to 0. @@ -551,6 +578,7 @@ impl Module { descriptor, availability_votes, relay_parent_number: check_cx.relay_parent_number, + backers, backed_in_number: check_cx.now, }); ::insert(¶_id, commitments); @@ -589,11 +617,23 @@ impl Module { fn enact_candidate( relay_parent_number: T::BlockNumber, receipt: CommittedCandidateReceipt, + backers: BitVec, + availability_votes: BitVec, ) -> Weight { let plain = receipt.to_plain(); let commitments = receipt.commitments; let config = >::config(); + T::RewardValidators::reward_backing(backers.iter().enumerate() + .filter(|(_, backed)| **backed) + .map(|(i, _)| i as _) + ); + + T::RewardValidators::reward_bitfields(availability_votes.iter().enumerate() + .filter(|(_, voted)| **voted) + .map(|(i, _)| i as _) + ); + // initial weight is config read. let mut weight = T::DbWeight::get().reads_writes(1, 0); if let Some(new_code) = commitments.new_validation_code { @@ -690,6 +730,8 @@ impl Module { Self::enact_candidate( pending.relay_parent_number, candidate, + pending.backers, + pending.availability_votes, ); } } @@ -988,6 +1030,18 @@ mod tests { bitvec::bitvec![BitOrderLsb0, u8; 0; Validators::get().len()] } + fn default_backing_bitfield() -> BitVec { + bitvec::bitvec![BitOrderLsb0, u8; 0; Validators::get().len()] + } + + fn backing_bitfield(v: &[usize]) -> BitVec { + let mut b = default_backing_bitfield(); + for i in v { + b.set(*i, true); + } + b + } + fn validator_pubkeys(val_ids: &[Sr25519Keyring]) -> Vec { val_ids.iter().map(|v| v.public().into()).collect() } @@ -1062,6 +1116,7 @@ mod tests { availability_votes: default_availability_votes(), relay_parent_number: 0, backed_in_number: 0, + backers: default_backing_bitfield(), }); PendingAvailabilityCommitments::insert(chain_a, default_candidate.commitments.clone()); @@ -1071,6 +1126,7 @@ mod tests { availability_votes: default_availability_votes(), relay_parent_number: 0, backed_in_number: 0, + backers: default_backing_bitfield(), }); PendingAvailabilityCommitments::insert(chain_b, default_candidate.commitments); @@ -1234,6 +1290,7 @@ mod tests { availability_votes: default_availability_votes(), relay_parent_number: 0, backed_in_number: 0, + backers: default_backing_bitfield(), }); PendingAvailabilityCommitments::insert(chain_a, default_candidate.commitments); @@ -1268,6 +1325,7 @@ mod tests { availability_votes: default_availability_votes(), relay_parent_number: 0, backed_in_number: 0, + backers: default_backing_bitfield(), }); *bare_bitfield.0.get_mut(0).unwrap() = true; @@ -1339,6 +1397,7 @@ mod tests { availability_votes: default_availability_votes(), relay_parent_number: 0, backed_in_number: 0, + backers: backing_bitfield(&[3, 4]), }); PendingAvailabilityCommitments::insert(chain_a, candidate_a.commitments); @@ -1354,6 +1413,7 @@ mod tests { availability_votes: default_availability_votes(), relay_parent_number: 0, backed_in_number: 0, + backers: backing_bitfield(&[0, 2]), }); PendingAvailabilityCommitments::insert(chain_b, candidate_b.commitments); @@ -1424,6 +1484,25 @@ mod tests { // and check that chain head was enacted. assert_eq!(Paras::para_head(&chain_a), Some(vec![1, 2, 3, 4].into())); + + // Check that rewards are applied. + { + let rewards = crate::mock::availability_rewards(); + + assert_eq!(rewards.len(), 4); + assert_eq!(rewards.get(&0).unwrap(), &1); + assert_eq!(rewards.get(&1).unwrap(), &1); + assert_eq!(rewards.get(&2).unwrap(), &1); + assert_eq!(rewards.get(&3).unwrap(), &1); + } + + { + let rewards = crate::mock::backing_rewards(); + + assert_eq!(rewards.len(), 2); + assert_eq!(rewards.get(&3).unwrap(), &1); + assert_eq!(rewards.get(&4).unwrap(), &1); + } }); } @@ -1764,6 +1843,7 @@ mod tests { availability_votes: default_availability_votes(), relay_parent_number: 3, backed_in_number: 4, + backers: default_backing_bitfield(), }); ::insert(&chain_a, candidate.commitments); @@ -2051,6 +2131,7 @@ mod tests { availability_votes: default_availability_votes(), relay_parent_number: System::block_number() - 1, backed_in_number: System::block_number(), + backers: backing_bitfield(&[0, 1]), }) ); assert_eq!( @@ -2066,6 +2147,7 @@ mod tests { availability_votes: default_availability_votes(), relay_parent_number: System::block_number() - 1, backed_in_number: System::block_number(), + backers: backing_bitfield(&[2, 3]), }) ); assert_eq!( @@ -2081,6 +2163,7 @@ mod tests { availability_votes: default_availability_votes(), relay_parent_number: System::block_number() - 1, backed_in_number: System::block_number(), + backers: backing_bitfield(&[4]), }) ); assert_eq!( @@ -2175,6 +2258,7 @@ mod tests { availability_votes: default_availability_votes(), relay_parent_number: System::block_number() - 1, backed_in_number: System::block_number(), + backers: backing_bitfield(&[0, 1, 2]), }) ); assert_eq!( @@ -2249,6 +2333,7 @@ mod tests { availability_votes: default_availability_votes(), relay_parent_number: 5, backed_in_number: 6, + backers: default_backing_bitfield(), }); ::insert(&chain_a, candidate.commitments.clone()); @@ -2258,6 +2343,7 @@ mod tests { availability_votes: default_availability_votes(), relay_parent_number: 6, backed_in_number: 7, + backers: default_backing_bitfield(), }); ::insert(&chain_b, candidate.commitments); diff --git a/polkadot/runtime/parachains/src/lib.rs b/polkadot/runtime/parachains/src/lib.rs index dfdf0be285..f7cf7cf335 100644 --- a/polkadot/runtime/parachains/src/lib.rs +++ b/polkadot/runtime/parachains/src/lib.rs @@ -33,6 +33,7 @@ pub mod origin; pub mod dmp; pub mod ump; pub mod hrmp; +pub mod reward_points; pub mod runtime_api_impl; diff --git a/polkadot/runtime/parachains/src/mock.rs b/polkadot/runtime/parachains/src/mock.rs index 0481c1941f..ee9cc2a063 100644 --- a/polkadot/runtime/parachains/src/mock.rs +++ b/polkadot/runtime/parachains/src/mock.rs @@ -21,11 +21,13 @@ use sp_core::H256; use sp_runtime::traits::{ BlakeTwo256, IdentityLookup, }; -use primitives::v1::{AuthorityDiscoveryId, BlockNumber, Header}; +use primitives::v1::{AuthorityDiscoveryId, BlockNumber, Header, ValidatorIndex}; use frame_support::{ impl_outer_origin, impl_outer_dispatch, impl_outer_event, parameter_types, traits::Randomness as RandomnessT, }; +use std::cell::RefCell; +use std::collections::HashMap; use crate::inclusion; use crate as parachains; @@ -114,6 +116,7 @@ impl crate::scheduler::Config for Test { } impl crate::inclusion::Config for Test { type Event = TestEvent; + type RewardValidators = TestRewardValidators; } impl crate::session_info::Config for Test { } @@ -124,6 +127,43 @@ impl crate::session_info::AuthorityDiscoveryConfig for Test { } } +thread_local! { + pub static BACKING_REWARDS: RefCell> + = RefCell::new(HashMap::new()); + + pub static AVAILABILITY_REWARDS: RefCell> + = RefCell::new(HashMap::new()); +} + +pub fn backing_rewards() -> HashMap { + BACKING_REWARDS.with(|r| r.borrow().clone()) +} + +pub fn availability_rewards() -> HashMap { + AVAILABILITY_REWARDS.with(|r| r.borrow().clone()) +} + +pub struct TestRewardValidators; + +impl inclusion::RewardValidators for TestRewardValidators { + fn reward_backing(v: impl IntoIterator) { + BACKING_REWARDS.with(|r| { + let mut r = r.borrow_mut(); + for i in v { + *r.entry(i).or_insert(0) += 1; + } + }) + } + fn reward_bitfields(v: impl IntoIterator) { + AVAILABILITY_REWARDS.with(|r| { + let mut r = r.borrow_mut(); + for i in v { + *r.entry(i).or_insert(0) += 1; + } + }) + } +} + pub type System = frame_system::Module; /// Mocked initializer. @@ -155,6 +195,9 @@ pub type SessionInfo = crate::session_info::Module; /// Create a new set of test externalities. pub fn new_test_ext(state: GenesisConfig) -> TestExternalities { + BACKING_REWARDS.with(|r| r.borrow_mut().clear()); + AVAILABILITY_REWARDS.with(|r| r.borrow_mut().clear()); + let mut t = state.system.build_storage::().unwrap(); state.configuration.assimilate_storage(&mut t).unwrap(); state.paras.assimilate_storage(&mut t).unwrap(); diff --git a/polkadot/runtime/parachains/src/reward_points.rs b/polkadot/runtime/parachains/src/reward_points.rs new file mode 100644 index 0000000000..7ff208d6d1 --- /dev/null +++ b/polkadot/runtime/parachains/src/reward_points.rs @@ -0,0 +1,55 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +//! An implementation of the `RewardValidators` trait used by `inclusion` that employs +//! `pallet-staking` to compute the rewards. +//! +//! Based on https://w3f-research.readthedocs.io/en/latest/polkadot/Token%20Economics.html +//! which doesn't currently mention availability bitfields. As such, we don't reward them +//! for the time being, although we will build schemes to do so in the future. + +use primitives::v1::ValidatorIndex; +use pallet_staking::SessionInterface; + +/// The amount of era points given by backing a candidate that is included. +pub const BACKING_POINTS: u32 = 20; + +/// Rewards validators for participating in parachains with era points in pallet-staking. +pub struct RewardValidatorsWithEraPoints(sp_std::marker::PhantomData); + +fn reward_by_indices(points: u32, indices: I) where + C: pallet_staking::Config, + I: IntoIterator +{ + // Fetch the validators from the _session_ because sessions are offset from eras + // and we are rewarding for behavior in current session. + let validators = C::SessionInterface::validators(); + let rewards = indices.into_iter() + .filter_map(|i| validators.get(i as usize).map(|v| v.clone())) + .map(|v| (v, points)); + + >::reward_by_ids(rewards); +} + +impl crate::inclusion::RewardValidators for RewardValidatorsWithEraPoints + where C: pallet_staking::Config +{ + fn reward_backing(validators: impl IntoIterator) { + reward_by_indices::(BACKING_POINTS, validators); + } + + fn reward_bitfields(_validators: impl IntoIterator) { } +} diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs index da437e71c0..f2a6732006 100644 --- a/polkadot/runtime/rococo/src/lib.rs +++ b/polkadot/runtime/rococo/src/lib.rs @@ -78,6 +78,7 @@ use runtime_parachains::dmp as parachains_dmp; use runtime_parachains::ump as parachains_ump; use runtime_parachains::hrmp as parachains_hrmp; use runtime_parachains::scheduler as parachains_scheduler; +use runtime_parachains::reward_points::RewardValidatorsWithEraPoints; pub use pallet_balances::Call as BalancesCall; pub use pallet_staking::StakerStatus; @@ -538,6 +539,7 @@ impl parachains_configuration::Config for Runtime {} impl parachains_inclusion::Config for Runtime { type Event = Event; + type RewardValidators = RewardValidatorsWithEraPoints; } impl parachains_paras::Config for Runtime { diff --git a/polkadot/runtime/test-runtime/src/lib.rs b/polkadot/runtime/test-runtime/src/lib.rs index 82eb754a3f..7d68a6f69c 100644 --- a/polkadot/runtime/test-runtime/src/lib.rs +++ b/polkadot/runtime/test-runtime/src/lib.rs @@ -73,6 +73,7 @@ use frame_support::{ use authority_discovery_primitives::AuthorityId as AuthorityDiscoveryId; use pallet_transaction_payment_rpc_runtime_api::RuntimeDispatchInfo; use pallet_session::historical as session_historical; +use polkadot_runtime_parachains::reward_points::RewardValidatorsWithEraPoints; #[cfg(feature = "std")] pub use pallet_staking::StakerStatus; @@ -446,6 +447,7 @@ impl parachains_configuration::Config for Runtime {} impl parachains_inclusion::Config for Runtime { type Event = Event; + type RewardValidators = RewardValidatorsWithEraPoints; } impl parachains_inclusion_inherent::Config for Runtime {}