Unverified Commit dd9ad157 authored by asynchronous rob's avatar asynchronous rob Committed by GitHub
Browse files

Reward validators for participating in parachains (#2089)

* plumbing for rewarding backers

* give validators reward points for participating

* fix tests

* add bitfield rewarding

* add mocks for backing rewards

* add testing for backing & availability rewards

* implement RewardValidators on top of staking

* add to test-runtime and rococo

* add to test-runtime & rococo

* point to source on rewards values

* fix common tests

* do not reward availability anymore
parent 782cf447
Pipeline #117375 failed with stages
in 26 minutes and 20 seconds
......@@ -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`.
......
......@@ -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<Item = ValidatorIndex>) { }
fn reward_bitfields(_: impl IntoIterator<Item = ValidatorIndex>) { }
}
impl inclusion::Config for Test {
type Event = ();
type RewardValidators = TestRewardValidators;
}
impl session_info::AuthorityDiscoveryConfig for Test {
......
......@@ -62,6 +62,8 @@ pub struct CandidatePendingAvailability<H, N> {
descriptor: CandidateDescriptor<H>,
/// The received availability votes. One bit per validator.
availability_votes: BitVec<BitOrderLsb0, u8>,
/// The backers of the candidate pending availability.
backers: BitVec<BitOrderLsb0, u8>,
/// 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<H, N> CandidatePendingAvailability<H, N> {
}
}
/// 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<Item=ValidatorIndex>);
// 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<Item=ValidatorIndex>);
}
pub trait Config:
frame_system::Config
+ paras::Config
......@@ -94,6 +106,7 @@ pub trait Config:
+ configuration::Config
{
type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;
type RewardValidators: RewardValidators;
}
decl_storage! {
......@@ -341,6 +354,8 @@ impl<T: Config> Module<T> {
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<T: Config> Module<T> {
let check_cx = CandidateCheckContext::<T>::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<T: Config> Module<T> {
'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<T: Config> Module<T> {
),
Err(()) => { Err(Error::<T>::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<T: Config> Module<T> {
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<T: Config> Module<T> {
descriptor,
availability_votes,
relay_parent_number: check_cx.relay_parent_number,
backers,
backed_in_number: check_cx.now,
});
<PendingAvailabilityCommitments>::insert(&para_id, commitments);
......@@ -589,11 +617,23 @@ impl<T: Config> Module<T> {
fn enact_candidate(
relay_parent_number: T::BlockNumber,
receipt: CommittedCandidateReceipt<T::Hash>,
backers: BitVec<BitOrderLsb0, u8>,
availability_votes: BitVec<BitOrderLsb0, u8>,
) -> Weight {
let plain = receipt.to_plain();
let commitments = receipt.commitments;
let config = <configuration::Module<T>>::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<T: Config> Module<T> {
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<BitOrderLsb0, u8> {
bitvec::bitvec![BitOrderLsb0, u8; 0; Validators::get().len()]
}
fn backing_bitfield(v: &[usize]) -> BitVec<BitOrderLsb0, u8> {
let mut b = default_backing_bitfield();
for i in v {
b.set(*i, true);
}
b
}
fn validator_pubkeys(val_ids: &[Sr25519Keyring]) -> Vec<ValidatorId> {
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(),
});
<PendingAvailabilityCommitments>::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(),
});
<PendingAvailabilityCommitments>::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(),
});
<PendingAvailabilityCommitments>::insert(&chain_b, candidate.commitments);
......
......@@ -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;
......
......@@ -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<HashMap<ValidatorIndex, usize>>
= RefCell::new(HashMap::new());
pub static AVAILABILITY_REWARDS: RefCell<HashMap<ValidatorIndex, usize>>
= RefCell::new(HashMap::new());
}
pub fn backing_rewards() -> HashMap<ValidatorIndex, usize> {
BACKING_REWARDS.with(|r| r.borrow().clone())
}
pub fn availability_rewards() -> HashMap<ValidatorIndex, usize> {
AVAILABILITY_REWARDS.with(|r| r.borrow().clone())
}
pub struct TestRewardValidators;
impl inclusion::RewardValidators for TestRewardValidators {
fn reward_backing(v: impl IntoIterator<Item = ValidatorIndex>) {
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<Item = ValidatorIndex>) {
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<Test>;
/// Mocked initializer.
......@@ -155,6 +195,9 @@ pub type SessionInfo = crate::session_info::Module<Test>;
/// 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::<Test>().unwrap();
state.configuration.assimilate_storage(&mut t).unwrap();
state.paras.assimilate_storage(&mut t).unwrap();
......
// 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 <http://www.gnu.org/licenses/>.
//! 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<C>(sp_std::marker::PhantomData<C>);
fn reward_by_indices<C, I>(points: u32, indices: I) where
C: pallet_staking::Config,
I: IntoIterator<Item = ValidatorIndex>
{
// 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));
<pallet_staking::Module<C>>::reward_by_ids(rewards);
}
impl<C> crate::inclusion::RewardValidators for RewardValidatorsWithEraPoints<C>
where C: pallet_staking::Config
{
fn reward_backing(validators: impl IntoIterator<Item=ValidatorIndex>) {
reward_by_indices::<C, _>(BACKING_POINTS, validators);
}
fn reward_bitfields(_validators: impl IntoIterator<Item=ValidatorIndex>) { }
}
......@@ -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<Runtime>;
}
impl parachains_paras::Config for Runtime {
......
......@@ -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<Runtime>;
}
impl parachains_inclusion_inherent::Config for Runtime {}
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment