Skip to content

Commit e571d0d

Browse files
authored
refactor: refactor type factories (leanEthereum#64)
* WIP: Review pydantic approach for List and Vector * refactor: List -> SSZList as a pydantic model * refactor: Bitlist refactor for SSZModel * refactor: More sweeping refactors; rename specific types appropriately * refactor: reassign types to specific properties for containers * cleanup: Fix failing tests after refactor * fix: implement abstract method for Union type * cleanup: fix failing tests for refactored types * refactor: draw explicit line between SSZType (is data) and SSZModel (has data) * refactor: properly type MockState and abstract into fixture for general use * fix: cleanup some AI slop and tighten up casts * remove claude iteration / planning doc * refactor: abstract HISTORICAL_ROOTS_LIMIT * Use SSZModel for Container * refactor: address comments from PR leanEthereum#64 * refactor: {ClassName}Base -> Base{ClassName} for consistency with BaseBytes / BaseUint
1 parent 4565b74 commit e571d0d

35 files changed

Lines changed: 2217 additions & 2117 deletions

CLAUDE.md

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,3 +100,81 @@ uv run tox
100100
2. All models should use Pydantic for automatic validation.
101101
3. Keep things simple, readable, and clear. These are meant to be clear specifications.
102102
4. The repository is `leanSpec` not `lean-spec`.
103+
104+
## SSZ Type Design Patterns
105+
106+
When creating SSZ types, follow these established patterns:
107+
108+
### Domain-Specific Types (Preferred)
109+
- Use meaningful names that describe the purpose: `JustificationValidators`, `HistoricalBlockHashes`, `Attestations`
110+
- Define domain-specific types in modular structure (see Architecture section below)
111+
- Avoid generic names with numbers like `Bitlist68719476736` or `SignedVoteList4096`
112+
113+
### SSZType vs SSZModel Design Decision
114+
115+
**SSZType (IS-A pattern)**: Use for types that *are* data
116+
- Primitive scalars: `Uint64`, `Boolean`, `Bytes32`
117+
- These inherit directly from their underlying Python types
118+
- Example: `Uint64(42)` *is* the integer 42 with SSZ serialization
119+
120+
**SSZModel (HAS-A pattern)**: Use for types that *have* data
121+
- Collections: `SSZList`, `SSZVector`, bitfields
122+
- Containers: `State`, `Block`, etc.
123+
- These use Pydantic models with a `data` field for contents
124+
- Example: `MyList(data=[1, 2, 3])` *has* a list of data with SSZ serialization
125+
126+
**Key principle**: If the type conceptually *holds* or *contains* other data, use SSZModel for consistent validation and immutability.
127+
128+
### Modular Architecture
129+
130+
Containers should be organized into modules with clear separation:
131+
132+
```
133+
src/lean_spec/subspecs/containers/
134+
├── state/
135+
│ ├── __init__.py # Exports State and related types
136+
│ ├── state.py # Main State container class
137+
│ └── types.py # State-specific types: JustifiedSlots, HistoricalBlockHashes, etc.
138+
├── block/
139+
│ ├── __init__.py # Exports Block classes
140+
│ ├── block.py # Main Block container classes
141+
│ └── types.py # Block-specific types: Attestations, etc.
142+
└── ...
143+
```
144+
145+
**Key principles:**
146+
- **Base types** (BaseBitlist, SSZList, etc.) stay in general scope (`src/lean_spec/types/`)
147+
- **Spec-specific types** go in their respective modules (`state/types.py`, `block/types.py`)
148+
- **Public API** exposed through `__init__.py` files for backward compatibility
149+
- **Domain-specific types** defined close to where they're used
150+
151+
### Examples
152+
153+
**Good domain-specific types:**
154+
```python
155+
# In state/types.py
156+
HISTORICAL_ROOTS_LIMIT = 262144
157+
158+
class JustificationValidators(BaseBitlist):
159+
"""Bitlist for tracking validator justifications."""
160+
LIMIT = HISTORICAL_ROOTS_LIMIT * HISTORICAL_ROOTS_LIMIT
161+
162+
# In block/types.py
163+
class Attestations(SSZList):
164+
"""List of signed votes (attestations) included in a block."""
165+
ELEMENT_TYPE = SignedVote
166+
LIMIT = 4096 # VALIDATOR_REGISTRY_LIMIT
167+
```
168+
169+
**Avoid generic types:**
170+
```python
171+
# Don't do this:
172+
class Bitlist68719476736(BaseBitlist): ...
173+
class SignedVoteList4096(SSZList): ...
174+
```
175+
176+
### API Compatibility
177+
178+
When refactoring, maintain backward compatibility:
179+
- Keep existing import paths working through `__init__.py` exports
180+
- Preserve method signatures and behavior

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ plugins = ["pydantic.mypy"]
9191
strict = true
9292
warn_return_any = true
9393
warn_unused_configs = true
94+
warn_unused_ignores = true
9495
no_implicit_reexport = true
9596
namespace_packages = false
9697
explicit_package_bases = false
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
"""Block containers and related types for the Lean Ethereum consensus specification."""
2+
3+
from .block import Block, BlockBody, BlockHeader, SignedBlock
4+
from .types import Attestations
5+
6+
__all__ = [
7+
"Block",
8+
"BlockBody",
9+
"BlockHeader",
10+
"SignedBlock",
11+
"Attestations",
12+
]

src/lean_spec/subspecs/containers/block.py renamed to src/lean_spec/subspecs/containers/block/block.py

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
1-
"""Block Containers."""
1+
"""Block Containers for the Lean Ethereum consensus specification."""
22

33
from lean_spec.subspecs.containers.slot import Slot
4-
from lean_spec.types import Bytes32, List, Uint64
4+
from lean_spec.types import Bytes32, Uint64
55
from lean_spec.types.container import Container
66

7-
from ..chain import config
8-
from .vote import SignedVote
7+
from .types import Attestations
98

109

1110
class BlockBody(Container):
1211
"""The body of a block, containing payload data."""
1312

14-
attestations: List[SignedVote, config.VALIDATOR_REGISTRY_LIMIT.as_int()] # type: ignore
13+
attestations: Attestations
1514
"""
1615
A list of votes included in the block.
1716
@@ -32,14 +31,14 @@ class BlockHeader(Container):
3231
"""The root of the parent block."""
3332

3433
state_root: Bytes32
35-
"""The root of the state after processing the block."""
34+
"""The root of the state after applying transactions in this block."""
3635

3736
body_root: Bytes32
38-
"""The root of the block's body."""
37+
"""The root of the block body."""
3938

4039

4140
class Block(Container):
42-
"""Represents a single block in the chain."""
41+
"""A complete block including header and body."""
4342

4443
slot: Slot
4544
"""The slot in which the block was proposed."""
@@ -61,11 +60,10 @@ class SignedBlock(Container):
6160
"""A container for a block and the proposer's signature."""
6261

6362
message: Block
64-
"""The block data that was signed."""
63+
"""The block being signed."""
6564

6665
signature: Bytes32
6766
"""
6867
The proposer's signature of the block message.
69-
7068
Note: Bytes32 is a placeholder; the actual signature is much larger.
7169
"""
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
"""Block-specific SSZ types for the Lean Ethereum consensus specification."""
2+
3+
from lean_spec.types import SSZList
4+
5+
from ...chain.config import VALIDATOR_REGISTRY_LIMIT
6+
from ..vote import SignedVote
7+
8+
9+
class Attestations(SSZList):
10+
"""List of signed votes (attestations) included in a block."""
11+
12+
ELEMENT_TYPE = SignedVote
13+
LIMIT = int(VALIDATOR_REGISTRY_LIMIT)

0 commit comments

Comments
 (0)