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

Fix notify inactive collators failures at the end of a round #3128

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

Conversation

manuelmauro
Copy link
Contributor

What does it do?

Addresses issue #3100 for which "notify_inactive_collator" only works at the beginning of a round.

What important points reviewers should know?

Is there something left for follow-up PRs?

What alternative implementations were considered?

Are there relevant PRs or issues in other repositories (Substrate, Polkadot, Frontier, Cumulus)?

What value does it bring to the blockchain users?

Copy link
Contributor

github-actions bot commented Jan 8, 2025

WASM runtime size check:

Compared to target branch

Moonbase runtime: 2280 KB (no changes) ✅

Moonbeam runtime: 2260 KB (no changes) ✅

Moonriver runtime: 2260 KB (no changes) ✅

Compared to latest release (runtime-3400)

Moonbase runtime: 2280 KB (+252 KB compared to latest release) ⚠️

Moonbeam runtime: 2260 KB (+248 KB compared to latest release) ⚠️

Moonriver runtime: 2260 KB (+248 KB compared to latest release) ⚠️

@manuelmauro manuelmauro marked this pull request as ready for review January 9, 2025 14:12
@RomarQ RomarQ added the B7-runtimenoteworthy Changes should be noted in any runtime-upgrade release notes label Jan 9, 2025
Comment on lines 281 to 283
if finalize_parachain_staking {
ParachainStaking::on_finalize(System::block_number());
}
Copy link
Contributor

Choose a reason for hiding this comment

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

We should always call the on_finalize, otherwise we may get false positives/negatives in the tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 325137d

Please double check this changes in particular:
325137d#diff-a2fcd9e2ec1e588e9fcfed674d8d0058713d685aaba3ed374ba248d4c279e02fL6778-R6781

Note on the changes introduced in this commit: the function previously used to mimic the distribution of points and to obviate to the missing call to ParachainStaking::on_finalize directly modifies the storage in a similar way to what on_finalize does (although matching logic is not enforced).

// Same storage changes as ParachainStaking::on_finalize
pub(crate) fn set_author(round: BlockNumber, acc: u64, pts: u32) {
	<Points<Test>>::mutate(round, |p| *p += pts);
	<AwardedPts<Test>>::mutate(round, acc, |p| *p += pts);
}

With the call to on_finalize within block rolling, and the concurrent use of this function, rounds may assign a variable amount of points making check on rewards amounts unreliable.

IMO a better approach would be to introduce utility functions as:

/// Sets the block author and advances n blocks.
pub(crate) fn author_blocks(n: BlockNumber, acc: u64) -> BlockNumber {
	set_block_author(acc);
	roll_blocks(n)
}

/// Sets the block author and advances blocks until the beginning of the specified round.
pub(crate) fn author_to_round_begin(round: BlockNumber, acc: u64) -> BlockNumber {
	set_block_author(acc);
	roll_to_round_begin(round)
}

That would guarantee a higher fidelity of the mocking framework.

@crystalin
Copy link
Collaborator

For optimization purposes (knowing that Storage/PoV is usually the limiting factor), shouldn't we store those who didn't have stake. Instead of HadStake we could have WasInactive ? (What do you think @RomarQ ?)

@@ -503,6 +503,7 @@ pub mod pallet {
}
fn on_finalize(_n: BlockNumberFor<T>) {
Self::award_points_to_block_author();
Self::cleanup_stake_info();
Copy link
Contributor

@RomarQ RomarQ Jan 9, 2025

Choose a reason for hiding this comment

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

We need to account for this extra cost in the weight estimated inside on_initialize.

Copy link
Contributor

github-actions bot commented Jan 9, 2025

Coverage Report

@@                                             Coverage Diff                                             @@
##           master   manuel/notify-inactive-collators-works-only-at-the-beginning-of-a-round      +/-   ##
===========================================================================================================
+ Coverage   74.40%                                                                    74.42%   +0.02%     
  Files         376                                                                       376              
+ Lines       95432                                                                     95559     +127     
===========================================================================================================
+ Hits        70998                                                                     71119     +121     
+ Misses      24434                                                                     24440       +6     
Files Changed Coverage
/pallets/parachain-staking/src/lib.rs 91.86% (+0.07%) 🔼
/pallets/parachain-staking/src/mock.rs 97.76% (+0.06%) 🔼
/pallets/parachain-staking/src/tests.rs 90.88% (+0.03%) 🔼

Coverage generated Fri Jan 10 17:01:54 UTC 2025

@RomarQ
Copy link
Contributor

RomarQ commented Jan 9, 2025

For optimization purposes (knowing that Storage/PoV is usually the limiting factor), shouldn't we store those who didn't have stake. Instead of HadStake we could have WasInactive ? (What do you think @RomarQ ?)

Yes, having a WasInactive storage map is preferable:

pub type WasInactive<T: Config> =
		StorageDoubleMap<_, Twox64Concat, RoundIndex, Twox64Concat, T::AccountId, (), OptionQuery>;

When starting a new round, we would check the inactivity like the example below, where stake.is_some() && pts.is_zero() would mean that the collator is inactive. We would check it against every candidate.

let stake = <AtStake<T>>::get(r, &collator);
let pts = <AwardedPts<T>>::get(r, &collator);
if stake.is_some() && pts.is_zero() {

We would still clear the storage after MaxOfflineRounds

@RomarQ RomarQ added the not-breaking Does not need to be mentioned in breaking changes label Jan 9, 2025
Comment on lines 2368 to 2371
let _ = <HadStake<T>>::iter_prefix(now - minimum_rounds_required)
.drain()
.next();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

If we properly estimate the weights in on_initialize, we could remove all entries from WasInactive for the round here.

@manuelmauro
Copy link
Contributor Author

For optimization purposes (knowing that Storage/PoV is usually the limiting factor), shouldn't we store those who didn't have stake. Instead of HadStake we could have WasInactive ? (What do you think @RomarQ ?)

Addressed comment, as described by @RomarQ, in b4f4c00.
Now fixing weights.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
B7-runtimenoteworthy Changes should be noted in any runtime-upgrade release notes not-breaking Does not need to be mentioned in breaking changes
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants