Skip to content

feat: queue stake updates#309

Open
0xCardiE wants to merge 21 commits intomasterfrom
feat/new_staking
Open

feat: queue stake updates#309
0xCardiE wants to merge 21 commits intomasterfrom
feat/new_staking

Conversation

@0xCardiE
Copy link
Copy Markdown
Contributor

@0xCardiE 0xCardiE commented Apr 13, 2026

Summary

  • replace immediate staking mutations with queued stake updates for deposits, top-ups, height changes, overlay changes, withdrawals, and exits
  • simplify effective stake so an unfrozen node's effective stake is just its current balance, and a frozen node's effective stake is 0
  • add forward-looking staking preview getters with explicit lookahead semantics instead of AtRound naming
  • reconcile queued withdrawals after slashing so queued payouts cannot exceed the remaining stake
  • make redistribution claim payout atomic with the postage withdrawal
  • update deployment config and tests to cover the new staking lifecycle, withdrawal/exit behavior, payout retry behavior, winner selection stability, and Bee-facing preview semantics
  • harden error paths: applyUpdates reverts on frozen withdrawals/exits, post-exit operations revert QueueClosed instead of NotStaked, constructor validates wait-parameter ordering
  • expose UPDATE_QUEUE_MAX_LENGTH as a public constant
  • remove dead code: unused Frozen error, duplicate StakeState struct (merged into Stake), redundant _toStakeView conversion
  • fix _applyReadyUpdates so privileged callers (freezeDeposit, slashDeposit, migrateStake) can never be blocked by a frozen withdrawal — they skip frozen entries instead of reverting

Bee Node Changes

  • Bee must no longer assume staking changes become effective immediately. createDeposit, addTokens, increaseHeight, changeOverlay, withdraw, and exit now enqueue updates that only become effective after the relevant wait period and after applyUpdates(owner) materializes them on-chain.
  • Bee should read the staking contract wait parameters instead of hardcoding timings:
    • WAIT_BASE for deposit, top-up, and height increase
    • WAIT_OVERLAY_CHANGE for overlay changes
    • WAIT_WITHDRAWAL for withdraw and exit
  • On deployed networks, WAIT_WITHDRAWAL is intended to represent roughly a 28-day delay in rounds. Local development configs may still use short values.
  • After the relevant wait period has elapsed, Bee should call applyUpdates(owner) to materialize ready updates on-chain. This is especially important for withdrawals and exits, because the BZZ transfer is only executed during applyUpdates(owner).
  • If a queued withdrawal or exit has reached its effective round but the node is still frozen, applyUpdates(owner) reverts with FrozenWithdrawal(). Bee should catch this revert and retry after the freeze expires. The frozen update stays in the queue and will execute on a subsequent applyUpdates call once the node is unfrozen.
  • Bee should continue to use Redistribution.isParticipatingInUpcomingRound(owner, depth) as the source of truth for eligibility. Bee should not try to reconstruct that decision from plain StakeRegistry getters alone, because redistribution combines staking state with round/anchor logic.
  • Bee should treat nodeEffectiveStake(owner), overlayOfAddress(owner), heightOfAddress(owner), and stakes(owner) as the currently effective staking state only. Queued future state is not implicitly exposed through those getters.
  • If Bee needs forward-looking raw staking previews, the explicit preview getters are now:
    • nodeEffectiveStakeLookahead(owner, lookahead)
    • overlayOfAddressLookahead(owner, lookahead)
    • heightOfAddressLookahead(owner, lookahead)
  • lookahead = 0 means "effective state for the current round context", and lookahead = 1 means "effective state one round ahead". This replaces the previous absolute-round preview naming.
  • Bee should continue participating under the old effective balance and metadata while updates are still waiting in the queue. A newly requested overlay or height must not be used for commit or reveal until the delay has passed and applyUpdates(owner) has succeeded.
  • Bee should treat nodeEffectiveStake(owner) as the live effective stake value. The previous committed/potential split is gone.
  • Bee may queue staking updates while frozen, but frozen nodes still cannot participate, and queued withdrawals or exits will not execute until the freeze no longer blocks them.
  • After exit() is queued, the owner's staking queue is closed. Any subsequent call to createDeposit, addTokens, changeOverlay, increaseHeight, withdraw, or exit reverts with QueueClosed(). No further staking updates can be enqueued for that owner until the exit is applied and the queue is cleared. Bee should treat a queued exit as a terminal pending action for that stake position.
  • Bee can read UPDATE_QUEUE_MAX_LENGTH on-chain to check queue capacity before enqueuing. The queue reverts with UpdateQueueFull() when the limit is reached.
  • Bee operators that are migrating existing integration logic should update any workflow that previously relied on immediate post-transaction staking state. The safe sequence is:
    1. submit staking change
    2. wait for the contract-defined round delay
    3. call applyUpdates(owner)
    4. continue with actions that depend on the new state

Deployment Notes

  • The constructor now enforces WAIT_OVERLAY_CHANGE >= WAIT_BASE and WAIT_WITHDRAWAL >= WAIT_BASE. Deployment scripts that pass wait parameters violating this invariant will fail with InvalidWaitConfiguration().

Contract Cleanup

  • Removed unused Frozen error declaration.
  • Merged identical StakeState struct into Stake — the duplicate struct and _toStakeView conversion function were dead weight since the fields were identical.
  • Moved the _queueClosed guard from _enqueueUpdate into all six public caller functions. This ensures QueueClosed is the first revert a user sees when the queue is terminating, before any gas is spent on _previewStake. The createDeposit function previously lacked this guard entirely.
  • _applyReadyUpdates now takes a _revertOnFrozen parameter. Public applyUpdates passes true (reverts with FrozenWithdrawal). Privileged callers (freezeDeposit, slashDeposit, migrateStake) pass false (breaks out of the loop) so the redistributor can always freeze and slash, and migration always succeeds.

Redistribution Notes

  • claim() now keeps payout and round finalization atomic. If the postage payout fails, the whole claim reverts and can be retried later once the underlying issue is resolved.
  • This avoids a state where a round is marked as claimed but the winner was not paid.
  • Upcoming-round eligibility now uses the staking lookahead preview API rather than target-round naming, so the staking/redistribution interface more clearly expresses forward preview semantics.

Reference

Implement the SWIP-40 and SWIP-41 staking flow with delayed queue-based stake updates, withdrawals, and exits while keeping redistribution aligned with effective active stake.
Prevent queued stake withdrawals and exits from executing while a node is frozen or actively participating in the current redistribution round, and wire staking to the redistribution contract for the runtime check.
Prevent claims from finalizing when postage payout fails, and initialize staking with the expected redistribution contract so deployment catches linkage mismatches early.
Add direct effective stake coverage and make the two-reveal winner assertion resilient to deterministic state changes, while cleaning related test typing and lint issues.
@0xCardiE 0xCardiE changed the title feat: queue stake updates and harden redistribution payouts feat: queue stake updates Apr 13, 2026
@0xCardiE 0xCardiE self-assigned this Apr 13, 2026
0xCardiE added 11 commits April 14, 2026 00:56
Reconcile queued withdrawals after slashing and preview stake state at a specific round so upcoming-round eligibility uses the same round context as the anchor.
Prevent new stake updates from being enqueued after an exit is scheduled, and align withdrawal waits on real networks with the intended 28-day round window while keeping local settings fast.
Use overlay presence as the stake initialization check and remove the dead lastUpdatedBlockNumber field and related test assertions.
…king

Allow effective withdrawals and exits to execute without current-round participation blocking them, and remove the admin-controlled redistribution hook from staking and deployment wiring.
Clarify that queued stake preview getters are forward-looking rather than historical by switching the staking and redistribution APIs from target-round naming to explicit round lookahead semantics.
- Revert FrozenWithdrawal on frozen withdrawal/exit in applyUpdates
  instead of silently skipping
- Check _queueClosed before _previewStake so terminating queues
  revert QueueClosed instead of NotStaked
- Enforce WAIT_OVERLAY_CHANGE >= WAIT_BASE and
  WAIT_WITHDRAWAL >= WAIT_BASE in constructor
- Make UPDATE_QUEUE_MAX_LENGTH public
- Remove dead `Frozen` error (unused after FrozenWithdrawal was added)
- Merge identical `StakeState` into `Stake`, remove `_toStakeView`
- Add `_revertOnFrozen` param to `_applyReadyUpdates` so privileged
  callers (freezeDeposit, slashDeposit, migrateStake) break instead
  of reverting when a frozen withdrawal is encountered
- Move `_queueClosed` check from `_enqueueUpdate` to all six public
  callers including `createDeposit` which previously lacked it
- Update tests to expect FrozenWithdrawal revert on applyUpdates
  while frozen
Move FrozenWithdrawal revert into applyUpdates as a post-call
check instead of threading a bool through _applyReadyUpdates.
The internal function now always breaks on frozen entries.
Swap freeze and apply order in freezeDeposit so mature withdrawals
settle while the node is still unfrozen, then the freeze takes
effect for future rounds.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant