Skip to content

Commit 5c45e17

Browse files
tcoratgerclaude
andauthored
fix(ssz): correct JustificationValidators LIMIT formula in rule doc (leanEthereum#728)
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>
1 parent 1056f2b commit 5c45e17

2 files changed

Lines changed: 17 additions & 6 deletions

File tree

.claude/rules/ssz-patterns.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
paths:
3-
- "src/lean_spec/subspecs/containers/**/*.py"
3+
- "src/lean_spec/forks/*/containers/**/*.py"
44
- "src/lean_spec/types/**/*.py"
55
---
66

@@ -36,7 +36,7 @@ When creating SSZ types, follow these established patterns:
3636
Containers should be organized into modules with clear separation:
3737

3838
```
39-
src/lean_spec/subspecs/containers/
39+
src/lean_spec/forks/<fork>/containers/
4040
├── state/
4141
│ ├── __init__.py # Exports State and related types
4242
│ ├── state.py # Main State container class
@@ -61,11 +61,15 @@ src/lean_spec/subspecs/containers/
6161

6262
```python
6363
# In state/types.py
64-
HISTORICAL_ROOTS_LIMIT = 262144
64+
HISTORICAL_ROOTS_LIMIT = 262144 # 2**18 tracked roots
65+
VALIDATOR_REGISTRY_LIMIT = 4096 # 2**12 registered validators
6566

6667
class JustificationValidators(BaseBitlist):
67-
"""Bitlist for tracking validator justifications."""
68-
LIMIT = HISTORICAL_ROOTS_LIMIT * HISTORICAL_ROOTS_LIMIT
68+
"""Per-root validator vote bitfields, concatenated into one flat bitlist."""
69+
# Worst-case size: one bit per (tracked root, registered validator) pair.
70+
# Any larger LIMIT inflates the bitlist's merkle depth and changes the
71+
# state root for identical data, so this product is consensus-critical.
72+
LIMIT = HISTORICAL_ROOTS_LIMIT * VALIDATOR_REGISTRY_LIMIT
6973

7074
# In block/types.py
7175
class Attestations(SSZList):

src/lean_spec/forks/lstar/containers/state/types.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,13 @@ def shift_window(self, delta: int) -> JustifiedSlots:
162162

163163

164164
class JustificationValidators(BaseBitlist):
165-
"""Bitlist for tracking validator justifications per historical root."""
165+
"""Per-root validator vote bitfields, concatenated into one flat bitlist.
166+
167+
Each tracked root contributes one bit per registered validator.
168+
The cap is the maximum tracked roots times the validator registry limit.
169+
170+
Why this product: a larger cap inflates the bitlist's merkle tree depth.
171+
That changes the state root for identical data, so this limit is consensus-critical.
172+
"""
166173

167174
LIMIT = int(HISTORICAL_ROOTS_LIMIT) * int(VALIDATOR_REGISTRY_LIMIT)

0 commit comments

Comments
 (0)