pos: lock out-queue stake on dispute for fully-retired nodes#3524
Open
peilun-conflux wants to merge 2 commits into
Open
pos: lock out-queue stake on dispute for fully-retired nodes#3524peilun-conflux wants to merge 2 commits into
peilun-conflux wants to merge 2 commits into
Conversation
CIP-156 changed the dispute penalty from forfeiting stake to locking it for `dispute_locked_views`. `NodeLockStatus::forfeit` gated the entire relock on `available_votes > 0`, but `available_votes` counts only `in_queue + locked` — not `out_queue`. A node that retired all of its available votes (stake sitting only in `out_queue`, `available_votes == 0`) was therefore not relocked when disputed: a Byzantine validator that self-retires after equivocating recovers its stake on the normal out-queue schedule instead of being locked for `dispute_locked_views`. Relocking the out-queue mutates the per-block PosState, so it is a consensus-rule change and is gated behind a new `pos_fix_cip156_transition_view` (u64::MAX on master; a finite activation view is set on the mainnet/testnet branches when the hardfork is scheduled). Before the transition behavior is unchanged; after it, a dispute also relocks the out-queue stake. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contributor
Code Review SummaryStatus: No Issues Found | Recommendation: Merge Overview
Resolved Issues
Files Reviewed (3 files)
Reviewed by claude-4.6-sonnet-20260217 · 91,574 tokens |
Addresses a review suggestion: StatusList exposed len() but not is_empty(), so the out_queue check read `len() > 0`. Add is_empty() and use the idiomatic `!self.out_queue.is_empty()`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ChenxingLi
requested changes
Jun 6, 2026
ChenxingLi
left a comment
Contributor
There was a problem hiding this comment.
@ChenxingLi reviewed 3 files and all commit messages, and made 1 comment.
Reviewable status: all files reviewed, 1 unresolved discussion (waiting on peilun-conflux).
a discussion (no related file):
Does this PR require a hardfork?
peilun-conflux
commented
Jun 6, 2026
peilun-conflux
left a comment
Contributor
Author
There was a problem hiding this comment.
@peilun-conflux made 1 comment.
Reviewable status: all files reviewed, 1 unresolved discussion (waiting on ChenxingLi).
a discussion (no related file):
Previously, ChenxingLi (Chenxing Li) wrote…
Does this PR require a hardfork?
Right, it is gated behind a new pos_fix_cip156_transition_view, defaulting to u64::MAX, which will be set with the next hardfork.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
CIP-156 changed the PoS dispute penalty from permanently forfeiting stake to locking it for
dispute_locked_views.NodeLockStatus::forfeitgates that relock onavailable_votes > 0, butavailable_votescounts onlyin_queue + locked, notout_queue. A validator that has retired all of its available votes — stake sitting only inout_queue, soavailable_votes == 0— is therefore not relocked when disputed, and recovers its stake on the normal out-queue schedule instead of being locked fordispute_locked_views. A Byzantine validator can exploit this by self-retiring after equivocating, defeating the dispute time-lock deterrent. (Under CIP-156 the penalty is a lock rather than a burn, so this weakens the deterrent rather than enabling fund loss, and cannot split consensus since all nodes apply the same rule.)Relocking the out-queue mutates the per-block
PosState, so changing this behavior is a consensus-rule change. It is gated behind a newpos_fix_cip156_transition_view, defaulting tou64::MAX.Related: #3513 — another consensus-rule PoS fix slated for the same next PoS hardfork.
Follow-up
No unit test is included here. The
NodeLockStatusmethods (includingforfeit) read the chain config from a process-global singleton (POS_STATE_CONFIG), so a unit test can't supply its own config without mutating shared global state. A follow-up PR will refactor these methods to take the config as an explicit parameter, making them unit-testable in isolation; the regression test for this fix will be added there.This change is