Skip to content
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

Open
wants to merge 33 commits into
base: master
Choose a base branch
from

Conversation

gpestana
Copy link
Contributor

@gpestana gpestana commented Oct 13, 2024

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:

  • add benchmarks for on_intialize in pallet staking

@gpestana gpestana self-assigned this Oct 13, 2024
@gpestana gpestana requested a review from a team as a code owner October 13, 2024 23:49
@gpestana gpestana requested a review from Ank4n October 13, 2024 23:49
@gpestana gpestana marked this pull request as draft October 13, 2024 23:49
@gpestana gpestana added the T2-pallets This PR/Issue is related to a particular pallet. label Oct 13, 2024
Copy link
Contributor

@Ank4n Ank4n left a 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 {
Copy link
Contributor

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?

Copy link
Contributor

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()?

Copy link
Contributor

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.

@gpestana gpestana marked this pull request as ready for review October 18, 2024 16:42
// 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()
Copy link
Contributor

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.

Copy link
Contributor Author

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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// 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);
Copy link
Contributor

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

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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>> {
Copy link
Contributor

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>,
Copy link
Contributor

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));
Copy link
Contributor

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![
Copy link
Contributor

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..

Copy link
Contributor

@Ank4n Ank4n left a 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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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,

Comment on lines +195 to +196
//! [`frame_election_provider_support::ElectionProvider`] traits used by this pallet support a
//! multi page election.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
//! [`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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
//! 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,
Copy link
Contributor

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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// 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).
Copy link
Contributor

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.
Copy link
Contributor

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

Suggested change
/// The maximum number of backers (edges) per winner in the last solution.
/// The maximum number of backers per winner in the last solution.

Comment on lines +63 to +66
//! 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.
Copy link
Contributor

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// 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

Copy link
Contributor

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>> {
Copy link
Contributor

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.

@paritytech-review-bot paritytech-review-bot bot requested a review from a team November 5, 2024 13:20
#[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),
Copy link
Contributor

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> {
Copy link
Contributor

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() {
Copy link
Contributor

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>>,
Copy link
Contributor

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>;
Copy link
Contributor

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?

Copy link
Contributor

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();
Copy link
Contributor

@kianenigma kianenigma Nov 5, 2024

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,
Copy link
Contributor

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T2-pallets This PR/Issue is related to a particular pallet.
Projects
Status: In review
Development

Successfully merging this pull request may close these issues.

3 participants