-
Notifications
You must be signed in to change notification settings - Fork 683
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adds multi-block election types and refactors current pallets to support new interfaces and types #6034
base: master
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only looked at traits. Will do another pass.
fn elect(remaining: PageIndex) -> Result<BoundedSupportsOf<Self>, Self::Error>; | ||
|
||
/// The index of the *most* significant page that this election provider supports. | ||
fn msp() -> PageIndex { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
may be call it highest page or top page?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, since the current page index needs to be stored somewhere, could we just store it on ElectionProvider
, and Staking then can just call next_elect()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I called it like this initially be be simiar to the concept if msp (most significant bit) in cpus.
…into gpestana/epm-mb
// defensive: Since npos solver returns a result always bounded by `desired_targets`, this | ||
// is never expected to happen as long as npos solver does what is expected for it to do. | ||
let supports: BoundedSupportsOf<Self> = to_supports(&staked) | ||
.try_into_bounded_supports() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
okay, so this conversion checks the bounds, and bails if any are not met. So the onchain election has no trimming capabiliites here.
Here this is fine. But long term the MB-Miner should ideally be able to trim to meet these bounds, please make a note of this if it is not the case already.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Keeping track of this here #6237, I'll ensure that's the case for all the (offchain) miners.
@@ -652,20 +669,22 @@ pub mod pallet { | |||
#[pallet::constant] | |||
type SignedDepositWeight: Get<BalanceOf<Self>>; | |||
|
|||
/// The maximum number of winners that can be elected by this `ElectionProvider` | |||
/// implementation. | |||
/// Maximum number of winners that an electio supports. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// Maximum number of winners that an electio supports. | |
/// Maximum number of winners that an election supports. |
} | ||
fn elect(page: PageIndex) -> Result<BoundedSupportsOf<Self>, Self::Error> { | ||
// Note: this pallet **MUST** only by used in the single-block mode. | ||
ensure!(page.is_zero(), ElectionError::<T>::MultiPageNotSupported); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BadPageIndex
or similar makes more sense I think
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ensure!(page.is_zero(), ElectionError::<T>::MultiPageNotSupported); | |
ensure!(page == SINGLE_PAGE, ElectionError::<T>::MultiPageNotSupported); |
@@ -1398,7 +1413,8 @@ impl<T: Config> Pallet<T> { | |||
/// Current best solution, signed or unsigned, queued to be returned upon `elect`. | |||
/// | |||
/// Always sorted by score. | |||
pub fn queued_solution() -> Option<ReadySolution<T::AccountId, T::MaxWinners>> { | |||
pub fn queued_solution( | |||
) -> Option<ReadySolution<T::AccountId, T::MaxWinners, T::MaxBackersPerWinner>> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be time to make this ReadySolution<T>
to avoid mixing the latter two.
@@ -1663,7 +1681,9 @@ impl<T: Config> Pallet<T> { | |||
} | |||
|
|||
/// record the weight of the given `supports`. | |||
fn weigh_supports(supports: &Supports<T::AccountId>) { | |||
fn weigh_supports( | |||
supports: &BoundedSupports<T::AccountId, T::MaxWinners, T::MaxBackersPerWinner>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here.
Not urgently needed. But the risk is if we mix the order..
assert!(QueuedSolution::<Runtime>::get().is_some()); | ||
|
||
// single page elect call works as expected. | ||
assert_ok!(MultiPhase::elect(SINGLE_PAGE)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you call MultiPhase::elect(SINGLE_PAGE)
twice in a row, it will fail anyways. So the second call is not being treated fairly, you should first assert that MultiPhase::elect(10)
is a noop and returns an error, then try MultiPhase::elect(SINGLE_PAGE)
. Or, do the MultiPhase::elect(SINGLE_PAGE)
first, but in a transactional layer and rollback.
(40, Support { total: 60, voters: vec![(2, 5), (3, 10), (4, 5), (40, 40)] }) | ||
] | ||
); | ||
let expected_supports = vec![ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
comparing bounded stuff is a PITA..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a long PR. Haven't gone through everything yet.
doc: | ||
- audience: Runtime Dev | ||
description: | | ||
This PR adds election types and structs required to run a multi-block election. In additoin, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR adds election types and structs required to run a multi-block election. In additoin, | |
This PR adds election types and structs required to run a multi-block election. In addition, |
//! [`frame_election_provider_support::ElectionProvider`] traits used by this pallet support a | ||
//! multi page election. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
//! [`frame_election_provider_support::ElectionProvider`] traits used by this pallet support a | |
//! multi page election. | |
//! [`frame_election_provider_support::ElectionProvider`] traits used by this pallet can support a | |
//! multi-page election. |
//! [`frame_election_provider_support::ElectionProvider`] traits used by this pallet support a | ||
//! multi page election. | ||
//! | ||
//! However, this pallet is meant to be used only in the context of a single-page election and data |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
//! However, this pallet is meant to be used only in the context of a single-page election and data | |
//! However, this pallet only supports single-page election and data |
@@ -494,13 +508,15 @@ pub enum ElectionError<T: Config> { | |||
DataProvider(&'static str), | |||
/// An error nested in the fallback. | |||
Fallback(FallbackErrorOf<T>), | |||
/// An error occurred when requesting an election result. The caller expects a mulit-paged | |||
/// election, which this pallet does not support. | |||
MultiPageNotSupported, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prefer UnsupportedPageIndex
which is more general. When this/another pallet supports pages, it could still run into conceptually similar error.
type MaxWinners: Get<u32>; | ||
|
||
/// Maximum number of voters that can support a winner target in an election solution. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// Maximum number of voters that can support a winner target in an election solution. | |
/// Maximum number of voters that can support a winner in an election solution. |
@@ -427,8 +429,10 @@ pub trait MinerConfig { | |||
/// | |||
/// The weight is computed using `solution_weight`. | |||
type MaxWeight: Get<Weight>; | |||
/// The maximum number of winners that can be elected. | |||
/// The maximum number of winners that can be elected per page (and overall). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
overall? Since this pallet only supports one page? Bit confusing.
type MaxWinners: Get<u32>; | ||
/// The maximum number of backers (edges) per winner in the last solution. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to try using less new terminologies
/// The maximum number of backers (edges) per winner in the last solution. | |
/// The maximum number of backers per winner in the last solution. |
//! Both [`ElectionDataProvider`] and [`ElectionProvider`] traits are parameterized by page, | ||
//! supporting an election to be performed over multiple pages. This enables the | ||
//! [`ElectionDataProvider`] implementor to provide all the election data over multiple pages. | ||
//! Similarly [`ElectionProvider::elect`] is parameterized by page index. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does election data and elect pages indexed same? Or can one have more pages than the other?
@@ -293,24 +326,32 @@ pub trait ElectionDataProvider { | |||
/// Maximum number of votes per voter that this data provider is providing. | |||
type MaxVotesPerVoter: Get<u32>; | |||
|
|||
/// All possible targets for the election, i.e. the targets that could become elected, thus | |||
/// "electable". | |||
/// Returns the possible targets for the election associated with page `page`, i.e. the targets |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// Returns the possible targets for the election associated with page `page`, i.e. the targets | |
/// Returns the possible targets for the election associated with the provided `page`, i.e. the targets |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we also add here the expectation that it should return targets sorted such that the highest page (msp) returns the top targets?
|
||
/// An extension trait to convert from [`sp_npos_elections::Supports<AccountId>`] into | ||
/// [`BoundedSupports`]. | ||
pub trait TryIntoBoundedSupports<AccountId, BOuter: Get<u32>, BInner: Get<u32>> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think probably because we need to somehow pass the bounds. But I still feel the trait is not very useful, since you should be able to build BoundedSupports
only from Supports
.
I would remove the trait, and have a fn implemented directly on the type Supports
.
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)] | ||
pub enum SnapshotStatus<AccountId> { | ||
/// Paged snapshot is in progress, the `AccountId` was the last staker iterated. | ||
Ongoing(AccountId), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can use the #[default]
attribite here.
@@ -382,6 +414,13 @@ pub struct ActiveEraInfo { | |||
pub start: Option<u64>, | |||
} | |||
|
|||
/// Pointer to the last iterated indices for targets and voters used when generating the snapshot. | |||
#[derive(Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)] | |||
pub(crate) struct LastIteratedStakers<AccountId> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not used
<<T as Config>::ElectionProvider as ElectionProvider>::Pages::get().into(); | ||
|
||
// election ongoing, fetch the next page. | ||
if let Some(started_at) = ElectingStartedAt::<T>::get() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This storage item is never set?
/// | ||
/// Deduplicates stashes in place and returns an error if the bounds are exceeded. | ||
pub(crate) fn add_electables( | ||
stashes: BoundedVec<T::AccountId, MaxWinnersPerPageOf<T::ElectionProvider>>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
check in integrity_test
: MaxWinnersPerPageOf * PageCount <= ValidatorSetSize
/// While the lock is set, there should be no mutations on the ledgers/staking data, ensuring | ||
/// that the data provided to [`Config::ElectionDataProvider`] is stable during all pages. | ||
#[pallet::storage] | ||
pub(crate) type ElectionDataLock<T: Config> = StorageValue<_, (), OptionQuery>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need this? if someone changes their stake mid snapshot, what could go wrong?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay the only thing is that we should be worried about is removing nodes form teh VoteList
during this, so that the iteration can resume at a reasonable spot.
To be specific, we can remove anyone, other than the single poor voter/target that is the pointer to the next page.
I suggest we add this logic directly to VoterList
via some hook, and any operation like chill
that attempts to remove a node might fail on T::VoterList::remove()?
, so no extra code is needed here to handle the error.
We certainly won't need a storage item here. We just need to communicate to the VoterList
like:
/// Voter list pallet needs a single storage item, that is one `node` that cannot be removed.
#[pallet::storage]
pub type IterationLock = StoarageValue<_>
/// set/uptess `who` as the lock prevents them from being removed from the list
/// This should be called per non-terminal page of snapshot creation
T::VoterList::set_lock(who)
/// Remove any existing locks
/// This is called on the terminal (last) page of the snapshot creation.
T::VoterList::remove_lock()
pub(crate) fn add_electables( | ||
stashes: BoundedVec<T::AccountId, MaxWinnersPerPageOf<T::ElectionProvider>>, | ||
) -> Result<(), ()> { | ||
let mut storage_stashes = ElectableStashes::<T>::get(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be written better with a ElectableStashes::mutate
.
@@ -461,6 +499,42 @@ pub struct PagedExposureMetadata<Balance: HasCompact + codec::MaxEncodedLen> { | |||
pub nominator_count: u32, | |||
/// Number of pages of nominators. | |||
pub page_count: Page, | |||
/// Number of empty slots in the last page. | |||
pub last_page_empty_slots: u32, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Ank4n please review the chnages related to this.
This PR refactors the types and structs required to run a mulit-block election and updates the EPM, staking-pallet and all dependent pallets to use the multi-block types. The Westend runtime is configured to run a 1 paged election, which is a noop refactor compared to the current single-block election.
Note: The multi-block election provider pallet is wip and it's added in a separate PR (#6213).
To finish:
on_intialize
in pallet staking