Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ classifiers = [
]
requires-python = ">=3.12"
dependencies = [
"ethereum-types<=0.2.4,<0.3",
"pydantic>=2.9.2,<3",
"ssz>=0.5.2,<0.6",
"typing-extensions>=4.4",
]

Expand Down
9 changes: 9 additions & 0 deletions src/lean_spec/client/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"""
Specification for pqdevnet-0

Key differences from 3SF-mini.py:
- Using `U64` instead of native `int` for all fields
- Using `Bytes32` instead of native `str` for all fields
- Combined `*_root` and `*_slot` pairs into a single `Checkpoint` field
- Removed optionals from `Block`
"""
27 changes: 27 additions & 0 deletions src/lean_spec/client/block.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""
A `Block` is a single link in the Lean Consensus chain. Each `Block` contains
associated metadata like the slot number, parent block hash and votes.

Together, these blocks form a cryptographically secure journal recording the
history of all state transitions that have happened since the genesis of the
chain.
"""

from ethereum_types.bytes import Bytes32
from ethereum_types.numeric import U64
from pydantic import BaseModel, ConfigDict
from ssz.sedes.list import List

from .preset import VALIDATOR_REGISTRY_LIMIT
from .vote import Vote


class Block(BaseModel):
"""A single block in the Lean Consensus chain."""

model_config = ConfigDict(arbitrary_types_allowed=True)

slot: U64
parent: Bytes32
votes: List[Vote, VALIDATOR_REGISTRY_LIMIT]
state_root: Bytes32
17 changes: 17 additions & 0 deletions src/lean_spec/client/checkpoint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"""
A `Checkpoint` is a single checkpoint for a block in the Lean Consensus chain.
Each `Checkpoint` contains its associated block root and slot.
"""

from ethereum_types.bytes import Bytes32
from ethereum_types.numeric import U64
from pydantic import BaseModel, ConfigDict


class Checkpoint(BaseModel):
"""A single checkpoint in the Lean Consensus chain."""

model_config = ConfigDict(arbitrary_types_allowed=True)

root: Bytes32
slot: U64
26 changes: 26 additions & 0 deletions src/lean_spec/client/preset.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""
The `preset` module contains the parameters that are used to configure the
Lean Consensus chain.
"""

from ethereum_types.numeric import U64

# Time parameters
# ---------------------------------------------------------------

# 4 seconds
SLOT_DURATION_MS: U64 = U64(4000)

# Basis points (out of 10000)
PROPOSER_REORG_CUTOFF_BPS: U64 = U64(2500)
VOTE_DUE_BPS: U64 = U64(5000)
FAST_CONFIRM_DUE_BPS: U64 = U64(7500)
VIEW_FREEZE_CUTOFF_BPS: U64 = U64(7500)

# Misc
# ---------------------------------------------------------------

# 2^18, enough for 2^18 / (60 / 4) / 60 / 24 = 12.1 days
MAX_HISTORICAL_BLOCK_HASHES: U64 = U64(262144)

VALIDATOR_REGISTRY_LIMIT: U64 = U64(4096)
37 changes: 37 additions & 0 deletions src/lean_spec/client/state.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""
A `State` is a collection of metadata that describes the current state of the
Lean Consensus chain. It contains information about the latest justified and
finalized blocks, as well as the historical block hashes and justified slots.

It is used to verify the integrity of the chain and to ensure that the chain is
progressing correctly.
"""

from ethereum_types.bytes import Bytes32
from pydantic import BaseModel, ConfigDict
from ssz.sedes.bitlist import Bitlist
from ssz.sedes.list import List

from .checkpoint import Checkpoint
from .preset import MAX_HISTORICAL_BLOCK_HASHES, VALIDATOR_REGISTRY_LIMIT


class State(BaseModel):
"""Represents the current state of the Lean Consensus chain."""

model_config = ConfigDict(arbitrary_types_allowed=True)

# Diverged from 3SF-mini.py: Removed `config: Config` from the state

latest_justified: Checkpoint
latest_finalized: Checkpoint

historical_block_hashes: List[Bytes32, MAX_HISTORICAL_BLOCK_HASHES]
justified_slots: List[bool, MAX_HISTORICAL_BLOCK_HASHES]

# Diverged from 3SF-mini.py:
# Flattened `justifications: Dict[str, List[bool]]` for SSZ
justifications_roots: List[Bytes32, MAX_HISTORICAL_BLOCK_HASHES]
justifications_validators: Bitlist[
MAX_HISTORICAL_BLOCK_HASHES * VALIDATOR_REGISTRY_LIMIT
]
22 changes: 22 additions & 0 deletions src/lean_spec/client/vote.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"""
A `Vote` is a single vote for a block in the Lean Consensus chain. Each `Vote`
contains information about the validator that voted, the slot of the block they
voted for, and the block hash they voted for.
"""

from ethereum_types.numeric import U64
from pydantic import BaseModel, ConfigDict

from .checkpoint import Checkpoint


class Vote(BaseModel):
"""A single vote for a block in the Lean Consensus chain."""

model_config = ConfigDict(arbitrary_types_allowed=True)

validator_id: U64
slot: U64
head: Checkpoint
target: Checkpoint
source: Checkpoint
39 changes: 39 additions & 0 deletions tests/lean_spec/client/test_block.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"""
Tests for the client's Block container.
"""

from ethereum_types.bytes import Bytes32
from ethereum_types.numeric import U64
from ssz.sedes.list import List

from lean_spec.client.block import Block
from lean_spec.client.checkpoint import Checkpoint
from lean_spec.client.preset import VALIDATOR_REGISTRY_LIMIT
from lean_spec.client.vote import Vote


def test_block():
Block(
slot=U64(1),
parent=Bytes32(b"\x02" * 32),
votes=List(
Vote(
validator_id=U64(1),
slot=U64(2),
head=Checkpoint(
root=Bytes32(b"\x04" * 32),
slot=U64(3),
),
source=Checkpoint(
root=Bytes32(b"\x05" * 32),
slot=U64(5),
),
target=Checkpoint(
root=Bytes32(b"\x06" * 32),
slot=U64(4),
),
),
int(VALIDATOR_REGISTRY_LIMIT),
),
state_root=Bytes32(b"\x03" * 32),
)
15 changes: 15 additions & 0 deletions tests/lean_spec/client/test_checkpoint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""
Tests for the client's Checkpoint container.
"""

from ethereum_types.bytes import Bytes32
from ethereum_types.numeric import U64

from lean_spec.client.checkpoint import Checkpoint


def test_checkpoint():
Checkpoint(
root=Bytes32(b"\x42" * 32),
slot=U64(42),
)
52 changes: 52 additions & 0 deletions tests/lean_spec/client/test_state.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""
Tests for the client's State container.
"""

from ethereum_types.bytes import Bytes32
from ethereum_types.numeric import U64
from ssz.sedes.bitlist import Bitlist
from ssz.sedes.list import List

from lean_spec.client.checkpoint import Checkpoint
from lean_spec.client.preset import (
MAX_HISTORICAL_BLOCK_HASHES,
VALIDATOR_REGISTRY_LIMIT,
)
from lean_spec.client.state import State


def test_state():
State(
latest_justified=Checkpoint(
root=Bytes32(b"\x00" * 32),
slot=U64(0),
),
latest_finalized=Checkpoint(
root=Bytes32(b"\x00" * 32),
slot=U64(0),
),
historical_block_hashes=List(
[
Bytes32(b"\x00" * 32),
Bytes32(b"\x01" * 32),
],
int(MAX_HISTORICAL_BLOCK_HASHES),
),
justified_slots=List(
[
True,
False,
],
int(MAX_HISTORICAL_BLOCK_HASHES),
),
justifications_roots=List(
[
Bytes32(b"\x00" * 32),
Bytes32(b"\x01" * 32),
],
int(MAX_HISTORICAL_BLOCK_HASHES),
),
justifications_validators=Bitlist(
int(MAX_HISTORICAL_BLOCK_HASHES * VALIDATOR_REGISTRY_LIMIT)
),
)
28 changes: 28 additions & 0 deletions tests/lean_spec/client/test_vote.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""
Tests for the client's Vote container.
"""

from ethereum_types.bytes import Bytes32
from ethereum_types.numeric import U64

from lean_spec.client.checkpoint import Checkpoint
from lean_spec.client.vote import Vote


def test_vote():
Vote(
validator_id=U64(1),
slot=U64(2),
head=Checkpoint(
root=Bytes32(b"\x03" * 32),
slot=U64(3),
),
source=Checkpoint(
root=Bytes32(b"\x05" * 32),
slot=U64(5),
),
target=Checkpoint(
root=Bytes32(b"\x04" * 32),
slot=U64(4),
),
)
Loading