Skip to content

fix(ssz): correct JustificationValidators LIMIT formula in rule doc#728

Merged
tcoratger merged 1 commit into
leanEthereum:mainfrom
tcoratger:fix/justification-validators-limit-doc
May 18, 2026
Merged

fix(ssz): correct JustificationValidators LIMIT formula in rule doc#728
tcoratger merged 1 commit into
leanEthereum:mainfrom
tcoratger:fix/justification-validators-limit-doc

Conversation

@tcoratger
Copy link
Copy Markdown
Collaborator

Summary

The SSZ-patterns rule doc showed LIMIT = HISTORICAL_ROOTS_LIMIT * HISTORICAL_ROOTS_LIMIT for the JustificationValidators example, but
the type stores one bit per (tracked root, registered validator)
pair. The second factor must be the validator registry limit. The
code (state/types.py:167) already uses the correct product — only
the rule doc was wrong.

Why it matters

BaseBitlist.LIMIT directly controls merkleization depth:

Source Formula Value (bits) Chunk count Tree depth
Code 2^18 * 2^12 2^30 2^22 22
Rule doc (was) 2^18 * 2^18 2^36 2^28 28

A second-client implementer treating .claude/rules/ssz-patterns.md
as canonical would compute a different hash_tree_root for the same
state field. Different state roots → different block roots → silent
consensus divergence on the first network exchange. Exactly the kind
of doc/code drift this rule file is meant to prevent.

Changes

  • .claude/rules/ssz-patterns.md — fix the formula in the example
    and add an inline rationale block explaining why the product matters
    (consensus-critical merkle depth). Also define VALIDATOR_REGISTRY_LIMIT
    alongside HISTORICAL_ROOTS_LIMIT so the math is self-contained.
  • .claude/rules/ssz-patterns.md — update the frontmatter path
    glob and the architecture diagram to reflect that containers moved
    to forks/<fork>/containers/ in the recent refactor.
  • state/types.py — expand the one-line docstring on
    JustificationValidators to document the layout and the
    consensus-critical nature of the LIMIT product. One sentence per
    line per the documentation rules.

Test plan

  • ruff check, ruff format --check, ty check pass
  • 104/104 tests in tests/lean_spec/forks/lstar/state and
    tests/lean_spec/forks/lstar/forkchoice pass
  • No code-level value changed — the code was already correct, only
    the doc was wrong

🤖 Generated with Claude Code

The SSZ-patterns rule doc showed `LIMIT = HISTORICAL_ROOTS_LIMIT *
HISTORICAL_ROOTS_LIMIT` for the JustificationValidators example, but
the type stores one bit per (tracked root, registered validator)
pair. The second factor must be the validator registry limit.

The numerical impact is large: the doc value is 64x the correct
value. Because BaseBitlist.LIMIT controls the merkleization depth,
a second-client implementer following the rule doc would compute a
different hash_tree_root for the same state field and fork off
silently the first time they tried to talk to a node built from
this code.

Changes:

- ssz-patterns.md: fix the formula in the example, add an inline
  rationale, and define VALIDATOR_REGISTRY_LIMIT alongside
  HISTORICAL_ROOTS_LIMIT so the math is self-contained.
- ssz-patterns.md: update the frontmatter path glob and the
  architecture diagram to reflect that containers moved to
  forks/<fork>/containers/ in the recent refactor.
- state/types.py: expand the one-line docstring on
  JustificationValidators to document the layout and the
  consensus-critical nature of the LIMIT product.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@tcoratger tcoratger merged commit 5c45e17 into leanEthereum:main May 18, 2026
13 checks passed
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