diff --git a/KIPs/kip-286.md b/KIPs/kip-286.md index d3e0996..3567c22 100644 --- a/KIPs/kip-286.md +++ b/KIPs/kip-286.md @@ -161,8 +161,8 @@ The `User tx` is a transaction initiated by the user, while `System tx` is a sys | From | To | Timing | Condition | Trigger | | ----------- | ----------- | ------- | --------------------- | --------- | -| ValActive | ValInactive | Epoch | Below `MinStake` | System tx | -| ValActive | ValInactive | Epoch | Below Top 50 by stake | System tx | +| ValActive | ValInactive | Epoch | Below `MinStake` AND at least one `ValActive` remains after the epoch transition | System tx | +| ValActive | ValInactive | Epoch | Below Top 50 by stake AND at least one `ValActive` remains after the epoch transition | System tx | | ValPaused | ValInactive | Epoch | Below `MinStake` | System tx | | ValInactive | ValReady | Anytime | Over `MinStake` | User tx | | ValReady | ValInactive | Anytime | - | User tx | @@ -215,6 +215,8 @@ def process_epoch_transition(): # over all entities. T1 and T4 do not contribute competitors. # T3b (MinStake) and pool building are handled inline via compete_or_demote. competitors = [] + # Captured before the pass mutates states, for the liveness floor below. + previously_active = [e for e in get_all_entities() if e.state == ValActive] def compete_or_demote(entity): # T3b (below MinStake): demote directly to ValInactive, no slot check. @@ -261,6 +263,13 @@ def process_epoch_transition(): else: # T3b: Demote to ValInactive (entities below top 50 by slot competition) transition(entity, ValInactive) + + # Liveness floor: retain the entities that entered as ValActive when the + # demotions above leave none. A ValPaused entity in the top 50 stays paused, + # so it does not satisfy this floor. + if count_by_state(ValActive) == 0: + for entity in previously_active: + transition(entity, ValActive) ``` **Ordering Rationale:** @@ -279,6 +288,8 @@ T1, T4, T2, and the eligible-pool collection (T3b MinStake check) are processed 6. **T3b (competition demotion)**: Entities in the eligible pool but outside top 50 are demoted to `ValInactive`. +7. **Liveness floor**: an empty `ValActive` set has no in-band recovery, since no block can be produced to restore stake or ready a replacement. The entities that entered the epoch as `ValActive` are therefore retained when the demotions above would leave none. Only that set is retained, and it competes normally at the next boundary. + ### Violation Transition Violation transitions run at every block (after epoch transition if on an epoch block). They handle MinStake and PFS violations with slot-limited demotions. Validators are processed in deterministic address order to ensure consistent results across nodes.