Skip to content

Implement RFC #104: Stale Nomination Reward Curve#11961

Open
shawntabrizi wants to merge 15 commits intomasterfrom
shawntabrizi-stale-nominations
Open

Implement RFC #104: Stale Nomination Reward Curve#11961
shawntabrizi wants to merge 15 commits intomasterfrom
shawntabrizi-stale-nominations

Conversation

@shawntabrizi
Copy link
Copy Markdown
Member

@shawntabrizi shawntabrizi commented May 1, 2026

Description

Implements RFC #104: Stale Nomination Reward Curve.

A nominator's voter weight is multiplied by a configurable decay function based on how long it has been since they last called nominate. The multiplier is applied at the election snapshot input only; reward distribution downstream is unchanged, so a stale nominator's "lost" share of validator rewards naturally flows to that validator's non-stale co-nominators through existing exposure-based payout math.

The mechanism is applied to both pallet-staking and pallet-staking-async. The shared trait and default impls live in sp-staking so the two pallets consume the same definitions.

What's implemented

sp-staking

  • New NominationStalenessCurve trait.
  • Default no-op impl NoNominationStaleness that preserves pre-staleness behaviour.
  • Default piecewise-linear impl LinearStalenessCurve<GracePeriod, DecayPeriod, Floor>.

pallet-staking and pallet-staking-async

Each pallet adds:

  • New Config::NominationStalenessCurve associated type. TestDefaultConfig defaults to NoNominationStaleness.
  • The existing submitted_in field on Nominations is now consumed at the election snapshot site (get_npos_voters) to compute the per-voter multiplier.
  • migrations::nomination_staleness::reset_all_nomination_submitted_in plus try-runtime pre/post helpers, ready to be wrapped in a versioned migration when a runtime opts in. Storage version is intentionally not bumped in this PR.
  • Mock runtime wires LinearStalenessCurve over mutable parameter_types!. Defaults disable the curve (grace = u32::MAX) so existing tests are unaffected.
  • 14 new tests under tests::nomination_staleness:
    • Curve boundary behaviour (grace, linear decay, floor, disabled-curve cases).
    • Refresh-trigger semantics: nominate resets even with same targets, bond_extra does not, chill removes the entry entirely.
    • Snapshot behaviour: weight reduction, exclusion at floor 0, restoration on re-nominate.
    • Migration helper.

Test status

  • pallet-staking: 225/225 tests pass.
  • pallet-staking-async: 274/274 tests pass (+14 from this PR).
  • sp-staking: compiles clean (including under --all-features for try-runtime).

Not in this PR

  • Wiring LinearStalenessCurve<28, 140, 0> into Polkadot / Kusama / Westend runtime configs. Production runtimes should opt in once the RFC is approved.
  • Versioned OnRuntimeUpgrade wrapper around the migration helpers.
  • prdoc entry.
  • Benchmark for the per-voter multiplier evaluation cost. The cost is dominated by existing per-voter snapshot work, but a dedicated micro-benchmark would be appropriate before final merge.
  • Integration tests in pallet-nomination-pools exercising pool-level freshness.

Review focus

  • API shape of NominationStalenessCurve and LinearStalenessCurve in sp-staking.
  • Snapshot integration sites in each pallet's get_npos_voters. These are the single points of behavioural change.
  • Default values (28 / 140 / 0); see the RFC's Unresolved Questions for context.

shawntabrizi and others added 7 commits June 27, 2024 15:46
Replace the hardcoded 10/10 era constants with a `NominationStalenessCurve`
trait wired through `Config`.

- `LinearStalenessCurve<GracePeriod, DecayPeriod, Floor>` — piecewise-linear
  decay, fully configurable.
- `NoNominationStaleness` — no-op default that preserves pre-staleness
  behaviour.
- `migrations::nomination_staleness::reset_all_nomination_submitted_in` and
  try-runtime helpers, ready to be wrapped in a versioned migration when
  production runtimes opt in. Storage version is intentionally not bumped.

The mock runtime defaults the curve to disabled (`grace = u32::MAX`) so
existing tests are unaffected. 14 new tests cover the curve at boundaries,
refresh-trigger semantics (`nominate` resets, `bond_extra`/`chill` do not),
snapshot weight reduction, exclusion at floor 0, and the migration helper.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@cla-bot-2021
Copy link
Copy Markdown

cla-bot-2021 Bot commented May 1, 2026

User @claude, please sign the CLA here.

@shawntabrizi shawntabrizi added I5-enhancement An additional feature request. T1-FRAME This PR/Issue is related to core FRAME, the framework. T2-pallets This PR/Issue is related to a particular pallet. T8-polkadot This PR/Issue is related to/affects the Polkadot network. labels May 1, 2026
/// Intended to be called from inside a versioned `on_runtime_upgrade` when the
/// nomination-staleness mechanism is first enabled on a runtime. See the module
/// docs for context.
pub fn reset_all_nomination_submitted_in<T: Config>() -> Weight {
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This migration is only needed if we decide to start all nominators as fresh. Maybe an alternative approach would be to hard code a block number which we treat any block before this as starting at that block number.

Some other tricks like this.

Copy link
Copy Markdown
Contributor

@kianenigma kianenigma left a comment

Choose a reason for hiding this comment

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

Should be implemented on staking-async pallet, otherwise the structure looks good.

shawntabrizi and others added 2 commits May 4, 2026 17:37
Move `NominationStalenessCurve`, `NoNominationStaleness`, and
`LinearStalenessCurve` from `pallet-staking` into `sp-staking` so both
`pallet-staking` and `pallet-staking-async` consume the same definitions.

Apply the same staleness mechanism to `pallet-staking-async`:

- New `Config::NominationStalenessCurve` associated type, defaulting to
  `NoNominationStaleness` in `TestDefaultConfig`.
- Multiplier applied at the snapshot site in `get_npos_voters`. Mirrors
  the change in `pallet-staking`.
- `migrations::nomination_staleness` helpers (`reset_all_nomination_submitted_in`
  plus try-runtime pre/post helpers), ready to be wrapped in a versioned
  migration when a runtime opts in. Storage version intentionally not bumped.
- Mock wires `LinearStalenessCurve` over mutable `parameter_types!` so
  individual tests can configure the curve. Defaults disable it
  (`grace = u32::MAX`).
- 14 new tests under `tests::nomination_staleness` mirroring the
  `pallet-staking` suite.

`pallet-staking` and its tests continue to compile and pass (225/225).
`pallet-staking-async` tests pass (274/274, +14 from this PR).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@shawntabrizi shawntabrizi requested a review from a team as a code owner May 4, 2026 08:52
@shawntabrizi
Copy link
Copy Markdown
Member Author

Added the same changes to staking-async (so now both pallets have these changes), and moved the common traits and impls into sp-staking

shawntabrizi and others added 5 commits May 4, 2026 18:23
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Qualify links to `NoNominationStaleness` and
`reset_all_nomination_submitted_in` so rustdoc can resolve them from
their respective scopes (the Config trait doc and the migrations module
doc are both outside the scope where the bare names resolve).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

I5-enhancement An additional feature request. T1-FRAME This PR/Issue is related to core FRAME, the framework. T2-pallets This PR/Issue is related to a particular pallet. T8-polkadot This PR/Issue is related to/affects the Polkadot network.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants