Skip to content

[Staking] Vesting implementation for validator self stake incentive #11876

@Ank4n

Description

@Ank4n

Finishing up the Budget splitting work from the original PR #10844, which we broke down into multiple PRs, with this being left.

Full spec: https://hackmd.io/@jonasW3F/rkN6BXE2ex

What's left

Today, transfer_validator_incentive in pallet-staking-async does a direct Currency::transfer. We need to replace it with a vested transfer so the incentive unlocks gradually.

Recommended approach

We can't create an unbounded number of vesting schedules, so rewards within an epoch need to batch together. In the original PR, I did this by putting a (custom staking) hold on the reward for eras where era % 28 != 0, and then when era % 28 == 0 releasing the hold and creating a vesting schedule. It works but adds a lot of unneeded complexity into staking which we can offload to vesting pallet itself.

Each epoch (28 eras) gets a single vesting schedule per validator. The schedule's starting_block is the block at which that epoch began, i.e. the last era boundary where era % BondingDuration == 0. Every incentive payout within the epoch merges back into that schedule.

  • Eras 28..55 → all merge into the schedule whose starting_block is era 28's block.
  • Eras 56..83 → all merge into the schedule whose starting_block is era 56's block.
  • …and so on.

Concretely:

  • At each era boundary, if era % BondingDuration == 0, snapshot the current block into a Staking::VestingEpochStart storage item.
  • On every incentive payout, call pallet-vesting to create or merge to existing schedule.

Today the VestedPayout trait only has vested_transfer (always creates a new schedule). We'll need to extend it with something like add_to_vesting(source, dest, amount, duration, start_at) and implement the merge inside pallet-vesting.

Hard requirements

  • VestingDuration = 0 must still work (liquid payout). Config-switchable, no vesting when set to zero.
  • Stay within MaxVestingSchedules = 28. With BondingDuration = 28 eras ≈ 28 days and VestingDuration = 365 days, a new schedule is added per epoch and vests for a year. So a validator holds ~13 active incentive schedules.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions