Skip to content

Commit a117259

Browse files
committed
2 parents b62d9b3 + 201bb3f commit a117259

6 files changed

Lines changed: 115 additions & 3 deletions

File tree

README.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,22 @@ The Lean Ethereum protocol specifications and cryptographic subspecifications.
66

77
### Prerequisites
88

9-
- Python 3.12 or later
10-
- [uv](https://github.com/astral-sh/uv) package manager
9+
#### Installing uv
10+
11+
[uv](https://github.com/astral-sh/uv) is a fast Python package manager that handles dependencies and Python versions.
12+
13+
```bash
14+
curl -LsSf https://astral.sh/uv/install.sh | sh
15+
````
16+
17+
#### Installing Python 3.12+
18+
19+
This project requires Python 3.12 or later and should be installed via `uv`:
20+
21+
```bash
22+
# Install Python 3.12, or latest stable version
23+
uv python install 3.12
24+
```
1125

1226
### Setup
1327

src/lean_spec/subspecs/containers/state.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from typing_extensions import Annotated
55

66
from lean_spec.subspecs.chain import DEVNET_CONFIG
7-
from lean_spec.types import Bytes32, StrictBaseModel, Uint64
7+
from lean_spec.types import Bytes32, StrictBaseModel, Uint64, ValidatorIndex
88

99
from .block import BlockHeader
1010
from .checkpoint import Checkpoint
@@ -62,3 +62,7 @@ class State(StrictBaseModel):
6262
),
6363
]
6464
"""A bitlist of validators who participated in justifications."""
65+
66+
def is_proposer(self, validator_index: ValidatorIndex) -> bool:
67+
"""Check if a validator is the proposer for the current slot."""
68+
return self.slot % self.config.num_validators == validator_index

src/lean_spec/types/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44
from .basispt import BasisPoint
55
from .hash import Bytes32
66
from .uint64 import Uint64
7+
from .validator import ValidatorIndex
78

89
__all__ = [
910
"Uint64",
1011
"BasisPoint",
1112
"Bytes32",
1213
"StrictBaseModel",
14+
"ValidatorIndex",
1315
]

src/lean_spec/types/validator.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
"""Validator-related type definitions for the specification."""
2+
3+
from .uint64 import Uint64
4+
5+
ValidatorIndex = Uint64
6+
"""A type alias for a validator's index in the registry."""
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
"""
2+
Tests for the State container.
3+
"""
4+
5+
import pytest
6+
7+
from lean_spec.subspecs.containers import (
8+
BlockHeader,
9+
Checkpoint,
10+
Config,
11+
State,
12+
)
13+
from lean_spec.types import Bytes32, Uint64, ValidatorIndex
14+
15+
16+
@pytest.fixture
17+
def sample_config() -> Config:
18+
"""A sample configuration with 10 validators."""
19+
return Config(num_validators=10, genesis_time=0)
20+
21+
22+
@pytest.fixture
23+
def sample_block_header() -> BlockHeader:
24+
"""A sample, empty block header."""
25+
return BlockHeader(
26+
slot=0,
27+
proposer_index=0,
28+
parent_root=Bytes32(b"\x00" * 32),
29+
state_root=Bytes32(b"\x00" * 32),
30+
body_root=Bytes32(b"\x00" * 32),
31+
)
32+
33+
34+
@pytest.fixture
35+
def sample_checkpoint() -> Checkpoint:
36+
"""A sample, empty checkpoint."""
37+
return Checkpoint(root=Bytes32(b"\x00" * 32), slot=0)
38+
39+
40+
def test_is_proposer(
41+
sample_config: Config,
42+
sample_block_header: BlockHeader,
43+
sample_checkpoint: Checkpoint,
44+
) -> None:
45+
"""
46+
Test the `is_proposer` method with various slots and validator indices.
47+
"""
48+
49+
def create_state_at_slot(slot: int) -> State:
50+
"""Create a state object at a given slot."""
51+
return State(
52+
config=sample_config,
53+
slot=Uint64(slot),
54+
latest_block_header=sample_block_header,
55+
latest_justified=sample_checkpoint,
56+
latest_finalized=sample_checkpoint,
57+
historical_block_hashes=[],
58+
justified_slots=[],
59+
justifications_roots=[],
60+
justifications_validators=[],
61+
)
62+
63+
# Slot 0
64+
state_slot_0 = create_state_at_slot(0)
65+
assert state_slot_0.is_proposer(ValidatorIndex(0)) is True
66+
assert state_slot_0.is_proposer(ValidatorIndex(1)) is False
67+
68+
# Slot 7
69+
state_slot_7 = create_state_at_slot(7)
70+
assert state_slot_7.is_proposer(ValidatorIndex(7)) is True
71+
assert state_slot_7.is_proposer(ValidatorIndex(8)) is False
72+
73+
# Slot 10 (wraps around)
74+
state_slot_10 = create_state_at_slot(10)
75+
assert state_slot_10.is_proposer(ValidatorIndex(0)) is True
76+
assert state_slot_10.is_proposer(ValidatorIndex(1)) is False
77+
78+
# Slot 23 (wraps around)
79+
state_slot_23 = create_state_at_slot(23)
80+
assert state_slot_23.is_proposer(ValidatorIndex(3)) is True
81+
assert state_slot_23.is_proposer(ValidatorIndex(2)) is False

tox.ini

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@ env_list =
55
docs
66
skip_missing_interpreters = true
77

8+
[testenv]
9+
basepython = python3
10+
uv_python = >=3.12
11+
812
[testenv:all-checks]
13+
base_python = python3.12
914
description = Run all quality checks (lint, typecheck, spellcheck)
1015
extras =
1116
lint

0 commit comments

Comments
 (0)