chain-halt: ValidatorByConsAddr propagates ErrNoValidatorFound, so the evidence/slashing BeginBlock nil-guards never run #1708
Summary
ValidatorByConsAddr (x/staking/keeper/alias_functions.go in the
gonka-ai/cosmos-sdk fork) returns
ErrNoValidatorFound when the consensus address does not resolve. Its only non-test consumers are
x/evidence handleEquivocationEvidence and x/slashing HandleValidatorSignature, both running
inside BeginBlock. Both check the error before checking for a nil validator, so the graceful
branches written for exactly this situation are unreachable. The error propagates out of
BeginBlocker into FinalizeBlock and consensus stops on every node at once:
Same failure class as #1205, different mechanism: #1205 is about a deletion path that removes a record still in use, this one is about the lookup contract that turns any such absence into a halt, whatever removed the validator.
How the state arises
Stock cosmos removes a validator only after the full unbonding period, long after it has left every
commit. The compute-validator model deletes zero-power and stale validators immediately, with no
unbonding, on epoch transitions. CometBFT keeps a validator in the active set for
ValidatorUpdateDelay blocks after its power update, so it signs one more block and appears in that
block's LastCommitInfo. The next block's BeginBlock looks it up and finds nothing.
Two independent routes, both on every node:
- Evidence —
handleEquivocationEvidenceresolves the reported validator. CometBFT submits equivocation evidence within the evidence age window, which on this chain can outlive the validator record by many blocks. - Slashing —
HandleValidatorSignatureresolves a validator that crossed the downtime threshold. Removal at epoch rotation is routine, and missed blocks accumulate on their own.
Root cause
// x/staking/keeper/alias_functions.go
func (k Keeper) ValidatorByConsAddr(ctx context.Context, addr sdk.ConsAddress) (types.ValidatorI, error) {
return k.GetValidatorByConsAddr(ctx, addr) // returns ErrNoValidatorFound
}
Consumer side, evidence:
validator, err := k.stakingKeeper.ValidatorByConsAddr(ctx, consAddr)
if err != nil {
return err // <- exits here, error reaches FinalizeBlock
}
if validator == nil || validator.IsUnbonded() {
// Defensive: Simulation doesn't take unbonding periods into account, and
// CometBFT might break this assumption at some point.
return nil // <- never reached
}
Slashing has the same shape with if validator != nil.
Fix
Report a missing validator as (nil, nil) so both consumers take the branches they already have.
Submitted as gonka-ai/cosmos-sdk#19, with two regression tests; the staking and slashing suites show
the same pre-existing failures before and after.
Verification
Multi-seed sweep of the simulation suite from #982, 500 blocks x 200 ops, release/v0.53.x + gonka-ai/cosmos-sdk#14 and
gonka-ai/cosmos-sdk#16: 27 of 37 seeds halt at heights 54-367 without the fix, 0 of 37 with it.
Re-verified 2026-09-02 against upgrade-v0.2.16 (SDK v0.53.3-ps19-observability +
gonka-ai/cosmos-sdk#14 and #16): seed 99 at 500x200 halts at height 74 without the fix and completes in 12 seconds with it.
Severity
Full chain halt: the error surfaces from BeginBlocker on every validator simultaneously, so the
network stops and needs manual intervention to recover.
Context
First reported 2026-06-06 as a comment on gonka-ai/cosmos-sdk#14, with the root cause, the stack trace and the same fix: https://github.com/gonka-ai/cosmos-sdk/pull/14#issuecomment-4637525807 — filing it here so it has a tracking issue of its own.
💬 Comments (1)
🔄 Auto-synced from Issue #1708 every hour.
Submitted to the HackerOne program.
plitochnik_ru— my GitHub handlevitaly-andris listed on that profileThe submission covers this issue together with the fix in gonka-ai/cosmos-sdk#19. The lookup contract is unchanged on
release/v0.53.xand onv0.53.3-ps19-observabilityas of today.