From 36fa692aa02439efa26350ad75d86d8614c97d50 Mon Sep 17 00:00:00 2001 From: unnawut Date: Wed, 6 Aug 2025 15:12:00 +0700 Subject: [PATCH 01/20] feat: 1st draft pqdevnet0 --- pyproject.toml | 1 + src/lean_spec/subspecs/pqdevnet-0/__init__.py | 1 + src/lean_spec/subspecs/pqdevnet-0/block.py | 28 ++++++++++++++++++ src/lean_spec/subspecs/pqdevnet-0/preset.py | 21 ++++++++++++++ src/lean_spec/subspecs/pqdevnet-0/state.py | 27 +++++++++++++++++ src/lean_spec/subspecs/pqdevnet-0/vote.py | 29 +++++++++++++++++++ 6 files changed, 107 insertions(+) create mode 100644 src/lean_spec/subspecs/pqdevnet-0/__init__.py create mode 100644 src/lean_spec/subspecs/pqdevnet-0/block.py create mode 100644 src/lean_spec/subspecs/pqdevnet-0/preset.py create mode 100644 src/lean_spec/subspecs/pqdevnet-0/state.py create mode 100644 src/lean_spec/subspecs/pqdevnet-0/vote.py diff --git a/pyproject.toml b/pyproject.toml index 405a772e..96f78b22 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,6 +19,7 @@ classifiers = [ ] requires-python = ">=3.12" dependencies = [ + "remerkleable>=0.1.28,<0.2", "pydantic>=2.9.2,<3", "typing-extensions>=4.4", ] diff --git a/src/lean_spec/subspecs/pqdevnet-0/__init__.py b/src/lean_spec/subspecs/pqdevnet-0/__init__.py new file mode 100644 index 00000000..ea63ee42 --- /dev/null +++ b/src/lean_spec/subspecs/pqdevnet-0/__init__.py @@ -0,0 +1 @@ +"""Specification for pqdevnet-0""" diff --git a/src/lean_spec/subspecs/pqdevnet-0/block.py b/src/lean_spec/subspecs/pqdevnet-0/block.py new file mode 100644 index 00000000..be7e0fc1 --- /dev/null +++ b/src/lean_spec/subspecs/pqdevnet-0/block.py @@ -0,0 +1,28 @@ +""" +A `Block` is a single link in the Lean Consensus chain. Each `Block` contains +a `Header` and zero or more transactions. Each `Header` contains associated +metadata like the block number, parent block hash, and how much gas was +consumed by its transactions. + +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 dataclasses import dataclass +from remerkleable.basic import uint64 +from remerkleable.byte_arrays import Bytes32 +from remerkleable.complex import List +from pydantic import BaseModel, ConfigDict + +from preset import VALIDATOR_REGISTRY_LIMIT +from vote import Vote + +@dataclass +class Block(BaseModel): + model_config = ConfigDict(arbitrary_types_allowed=True) + + slot: uint64 + parent: Bytes32 + votes: List[Vote, VALIDATOR_REGISTRY_LIMIT] + state_root: Bytes32 diff --git a/src/lean_spec/subspecs/pqdevnet-0/preset.py b/src/lean_spec/subspecs/pqdevnet-0/preset.py new file mode 100644 index 00000000..b224e970 --- /dev/null +++ b/src/lean_spec/subspecs/pqdevnet-0/preset.py @@ -0,0 +1,21 @@ +from remerkleable.basic import uint64 + +# Time parameters +# --------------------------------------------------------------- + +# 4 seconds +SLOT_DURATION_MS = 4000 + +# Basis points (out of 10000) +PROPOSER_REORG_CUTOFF_BPS: 2500 +VOTE_DUE_BPS: 5000 +FAST_CONFIRM_DUE_BPS: 7500 +VIEW_FREEZE_CUTOFF_BPS: 7500 + +# Misc +# --------------------------------------------------------------- + +# 2^18, enough for 2^18 / (60 / 4) / 60 / 24 = 12.1 days +MAX_HISTORICAL_BLOCK_HASHES: uint64 = 262144 + +VALIDATOR_REGISTRY_LIMIT: uint64 = 4096 diff --git a/src/lean_spec/subspecs/pqdevnet-0/state.py b/src/lean_spec/subspecs/pqdevnet-0/state.py new file mode 100644 index 00000000..ca2cb466 --- /dev/null +++ b/src/lean_spec/subspecs/pqdevnet-0/state.py @@ -0,0 +1,27 @@ +from dataclasses import dataclass +from remerkleable.basic import uint64 +from remerkleable.bitfields import Bitlist +from remerkleable.byte_arrays import Bytes32 +from remerkleable.complex import List +from pydantic import BaseModel, ConfigDict + +from preset import MAX_HISTORICAL_BLOCK_HASHES, VALIDATOR_REGISTRY_LIMIT + +@dataclass +class State(BaseModel): + model_config = ConfigDict(arbitrary_types_allowed=True) + + latest_justified_hash: Bytes32 + latest_justified_slot: uint64 + + latest_finalized_hash: Bytes32 + latest_finalized_slot: uint64 + + historical_block_hashes: List[Bytes32, MAX_HISTORICAL_BLOCK_HASHES] + justified_slots: List[bool, MAX_HISTORICAL_BLOCK_HASHES] + + # Originally in 3SF-mini: `justifications: Dict[str, List[bool]]` + justifications_roots: List[Bytes32, MAX_HISTORICAL_BLOCK_HASHES] + + # Capacity should be enough for a flattened `justifications[root][validator_id]` + justifications_roots_validators: Bitlist[MAX_HISTORICAL_BLOCK_HASHES * VALIDATOR_REGISTRY_LIMIT] diff --git a/src/lean_spec/subspecs/pqdevnet-0/vote.py b/src/lean_spec/subspecs/pqdevnet-0/vote.py new file mode 100644 index 00000000..9ca43e3c --- /dev/null +++ b/src/lean_spec/subspecs/pqdevnet-0/vote.py @@ -0,0 +1,29 @@ +""" +A `Block` is a single link in the Lean Consensus chain. Each `Block` contains +a `Header` and zero or more transactions. Each `Header` contains associated +metadata like the block number, parent block hash, and how much gas was +consumed by its transactions. + +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 dataclasses import dataclass +from remerkleable.basic import uint64 +from remerkleable.byte_arrays import Bytes32 +from remerkleable.complex import List +from pydantic import BaseModel, ConfigDict + +@dataclass +class Vote(BaseModel): + model_config = ConfigDict(arbitrary_types_allowed=True) + + validator_id: uint64 + slot: uint64 + head: Bytes32 + head_slot: uint64 + target: Bytes32 + target_slot: uint64 + source: Bytes32 + source_slot: uint64 From 3ce36d92c2596dcdaa16c0ca689d0edb8051c538 Mon Sep 17 00:00:00 2001 From: unnawut Date: Wed, 6 Aug 2025 15:16:11 +0700 Subject: [PATCH 02/20] docs: improve descriptions --- src/lean_spec/subspecs/pqdevnet-0/block.py | 4 +--- src/lean_spec/subspecs/pqdevnet-0/preset.py | 5 +++++ src/lean_spec/subspecs/pqdevnet-0/state.py | 9 +++++++++ src/lean_spec/subspecs/pqdevnet-0/vote.py | 12 +++--------- 4 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/lean_spec/subspecs/pqdevnet-0/block.py b/src/lean_spec/subspecs/pqdevnet-0/block.py index be7e0fc1..1e42035e 100644 --- a/src/lean_spec/subspecs/pqdevnet-0/block.py +++ b/src/lean_spec/subspecs/pqdevnet-0/block.py @@ -1,8 +1,6 @@ """ A `Block` is a single link in the Lean Consensus chain. Each `Block` contains -a `Header` and zero or more transactions. Each `Header` contains associated -metadata like the block number, parent block hash, and how much gas was -consumed by its transactions. +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 diff --git a/src/lean_spec/subspecs/pqdevnet-0/preset.py b/src/lean_spec/subspecs/pqdevnet-0/preset.py index b224e970..7b1eb780 100644 --- a/src/lean_spec/subspecs/pqdevnet-0/preset.py +++ b/src/lean_spec/subspecs/pqdevnet-0/preset.py @@ -1,3 +1,8 @@ +""" +The `preset` module contains the parameters that are used to configure the +Lean Consensus chain. +""" + from remerkleable.basic import uint64 # Time parameters diff --git a/src/lean_spec/subspecs/pqdevnet-0/state.py b/src/lean_spec/subspecs/pqdevnet-0/state.py index ca2cb466..34c288fa 100644 --- a/src/lean_spec/subspecs/pqdevnet-0/state.py +++ b/src/lean_spec/subspecs/pqdevnet-0/state.py @@ -1,3 +1,12 @@ +""" +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 dataclasses import dataclass from remerkleable.basic import uint64 from remerkleable.bitfields import Bitlist diff --git a/src/lean_spec/subspecs/pqdevnet-0/vote.py b/src/lean_spec/subspecs/pqdevnet-0/vote.py index 9ca43e3c..2196a8b2 100644 --- a/src/lean_spec/subspecs/pqdevnet-0/vote.py +++ b/src/lean_spec/subspecs/pqdevnet-0/vote.py @@ -1,18 +1,12 @@ """ -A `Block` is a single link in the Lean Consensus chain. Each `Block` contains -a `Header` and zero or more transactions. Each `Header` contains associated -metadata like the block number, parent block hash, and how much gas was -consumed by its transactions. - -Together, these blocks form a cryptographically secure journal recording the -history of all state transitions that have happened since the genesis of the -chain. +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 dataclasses import dataclass from remerkleable.basic import uint64 from remerkleable.byte_arrays import Bytes32 -from remerkleable.complex import List from pydantic import BaseModel, ConfigDict @dataclass From 0c0b13f9c8e30bed64b67a3c88cf4bc0fd686ff0 Mon Sep 17 00:00:00 2001 From: unnawut Date: Wed, 6 Aug 2025 15:44:22 +0700 Subject: [PATCH 03/20] docs: annotate divergences from 3SF-mini.py --- src/lean_spec/subspecs/pqdevnet-0/block.py | 1 + src/lean_spec/subspecs/pqdevnet-0/state.py | 12 ++++++++---- src/lean_spec/subspecs/pqdevnet-0/vote.py | 4 ++++ 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/lean_spec/subspecs/pqdevnet-0/block.py b/src/lean_spec/subspecs/pqdevnet-0/block.py index 1e42035e..eeb78999 100644 --- a/src/lean_spec/subspecs/pqdevnet-0/block.py +++ b/src/lean_spec/subspecs/pqdevnet-0/block.py @@ -23,4 +23,5 @@ class Block(BaseModel): slot: uint64 parent: Bytes32 votes: List[Vote, VALIDATOR_REGISTRY_LIMIT] + # Diverged from 3SF-mini.py: Removed Optional from `state_root` state_root: Bytes32 diff --git a/src/lean_spec/subspecs/pqdevnet-0/state.py b/src/lean_spec/subspecs/pqdevnet-0/state.py index 34c288fa..536cdff2 100644 --- a/src/lean_spec/subspecs/pqdevnet-0/state.py +++ b/src/lean_spec/subspecs/pqdevnet-0/state.py @@ -20,6 +20,11 @@ class State(BaseModel): model_config = ConfigDict(arbitrary_types_allowed=True) + # Diverged from 3SF-mini.py: + # - Removed `config: Config` from the state + # - Using uint64 instead of native int for all fields + # - Using Bytes32 instead of native str for all fields + latest_justified_hash: Bytes32 latest_justified_slot: uint64 @@ -29,8 +34,7 @@ class State(BaseModel): historical_block_hashes: List[Bytes32, MAX_HISTORICAL_BLOCK_HASHES] justified_slots: List[bool, MAX_HISTORICAL_BLOCK_HASHES] - # Originally in 3SF-mini: `justifications: Dict[str, List[bool]]` + # Diverged from 3SF-mini.py: + # - Flattened `justifications: Dict[str, List[bool]]` for SSZ compatibility justifications_roots: List[Bytes32, MAX_HISTORICAL_BLOCK_HASHES] - - # Capacity should be enough for a flattened `justifications[root][validator_id]` - justifications_roots_validators: Bitlist[MAX_HISTORICAL_BLOCK_HASHES * VALIDATOR_REGISTRY_LIMIT] + justifications_validators: Bitlist[MAX_HISTORICAL_BLOCK_HASHES * VALIDATOR_REGISTRY_LIMIT] diff --git a/src/lean_spec/subspecs/pqdevnet-0/vote.py b/src/lean_spec/subspecs/pqdevnet-0/vote.py index 2196a8b2..04a683ab 100644 --- a/src/lean_spec/subspecs/pqdevnet-0/vote.py +++ b/src/lean_spec/subspecs/pqdevnet-0/vote.py @@ -13,6 +13,10 @@ class Vote(BaseModel): model_config = ConfigDict(arbitrary_types_allowed=True) + # Diverged from 3SF-mini.py: + # - Using `uint64` instead of native `int` for all fields + # - Using `Bytes32` instead of native `str` for all fields + validator_id: uint64 slot: uint64 head: Bytes32 From b39afbd53d5ac5f798e0c3d9dcbba7219b7beb6b Mon Sep 17 00:00:00 2001 From: unnawut Date: Wed, 6 Aug 2025 15:49:13 +0700 Subject: [PATCH 04/20] fix: deps order --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 96f78b22..25a2ef62 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,8 +19,8 @@ classifiers = [ ] requires-python = ">=3.12" dependencies = [ - "remerkleable>=0.1.28,<0.2", "pydantic>=2.9.2,<3", + "remerkleable>=0.1.28,<0.2", "typing-extensions>=4.4", ] From ddfd5fb69eb68cac96814647c677111db583cffb Mon Sep 17 00:00:00 2001 From: unnawut Date: Thu, 7 Aug 2025 20:00:56 +0700 Subject: [PATCH 05/20] refactor: move from remerkleable to ethereum_types and py-ssz --- pyproject.toml | 2 ++ src/lean_spec/subspecs/pqdevnet-0/block.py | 9 +++++---- src/lean_spec/subspecs/pqdevnet-0/state.py | 19 ++++++++++--------- src/lean_spec/subspecs/pqdevnet-0/vote.py | 17 +++++++++-------- 4 files changed, 26 insertions(+), 21 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 25a2ef62..eb9fcd27 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,8 +19,10 @@ classifiers = [ ] requires-python = ">=3.12" dependencies = [ + "ethereum-types<=0.2.4,<0.3", "pydantic>=2.9.2,<3", "remerkleable>=0.1.28,<0.2", + "ssz>=0.5.2,<0.6", "typing-extensions>=4.4", ] diff --git a/src/lean_spec/subspecs/pqdevnet-0/block.py b/src/lean_spec/subspecs/pqdevnet-0/block.py index eeb78999..c56f80a1 100644 --- a/src/lean_spec/subspecs/pqdevnet-0/block.py +++ b/src/lean_spec/subspecs/pqdevnet-0/block.py @@ -8,11 +8,12 @@ """ from dataclasses import dataclass -from remerkleable.basic import uint64 -from remerkleable.byte_arrays import Bytes32 -from remerkleable.complex import List from pydantic import BaseModel, ConfigDict +from ethereum_types.bytes import Bytes32 +from ethereum_types.numeric import U64 +from ssz.sedes.list import List + from preset import VALIDATOR_REGISTRY_LIMIT from vote import Vote @@ -20,7 +21,7 @@ class Block(BaseModel): model_config = ConfigDict(arbitrary_types_allowed=True) - slot: uint64 + slot: U64 parent: Bytes32 votes: List[Vote, VALIDATOR_REGISTRY_LIMIT] # Diverged from 3SF-mini.py: Removed Optional from `state_root` diff --git a/src/lean_spec/subspecs/pqdevnet-0/state.py b/src/lean_spec/subspecs/pqdevnet-0/state.py index 536cdff2..40caadae 100644 --- a/src/lean_spec/subspecs/pqdevnet-0/state.py +++ b/src/lean_spec/subspecs/pqdevnet-0/state.py @@ -8,12 +8,13 @@ """ from dataclasses import dataclass -from remerkleable.basic import uint64 -from remerkleable.bitfields import Bitlist -from remerkleable.byte_arrays import Bytes32 -from remerkleable.complex import List from pydantic import BaseModel, ConfigDict +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 preset import MAX_HISTORICAL_BLOCK_HASHES, VALIDATOR_REGISTRY_LIMIT @dataclass @@ -22,14 +23,14 @@ class State(BaseModel): # Diverged from 3SF-mini.py: # - Removed `config: Config` from the state - # - Using uint64 instead of native int for all fields - # - Using Bytes32 instead of native str for all fields + # - Using `U64` instead of native int for all fields + # - Using `Bytes32` instead of native str for all fields latest_justified_hash: Bytes32 - latest_justified_slot: uint64 + latest_justified_slot: U64 latest_finalized_hash: Bytes32 - latest_finalized_slot: uint64 + latest_finalized_slot: U64 historical_block_hashes: List[Bytes32, MAX_HISTORICAL_BLOCK_HASHES] justified_slots: List[bool, MAX_HISTORICAL_BLOCK_HASHES] @@ -37,4 +38,4 @@ class State(BaseModel): # Diverged from 3SF-mini.py: # - Flattened `justifications: Dict[str, List[bool]]` for SSZ compatibility justifications_roots: List[Bytes32, MAX_HISTORICAL_BLOCK_HASHES] - justifications_validators: Bitlist[MAX_HISTORICAL_BLOCK_HASHES * VALIDATOR_REGISTRY_LIMIT] + justifications_validators: Bitlist(MAX_HISTORICAL_BLOCK_HASHES * VALIDATOR_REGISTRY_LIMIT) diff --git a/src/lean_spec/subspecs/pqdevnet-0/vote.py b/src/lean_spec/subspecs/pqdevnet-0/vote.py index 04a683ab..6f656c13 100644 --- a/src/lean_spec/subspecs/pqdevnet-0/vote.py +++ b/src/lean_spec/subspecs/pqdevnet-0/vote.py @@ -5,23 +5,24 @@ """ from dataclasses import dataclass -from remerkleable.basic import uint64 -from remerkleable.byte_arrays import Bytes32 from pydantic import BaseModel, ConfigDict +from ethereum_types.bytes import Bytes32 +from ethereum_types.numeric import U64 + @dataclass class Vote(BaseModel): model_config = ConfigDict(arbitrary_types_allowed=True) # Diverged from 3SF-mini.py: - # - Using `uint64` instead of native `int` for all fields + # - Using `U64` instead of native `int` for all fields # - Using `Bytes32` instead of native `str` for all fields - validator_id: uint64 - slot: uint64 + validator_id: U64 + slot: U64 head: Bytes32 - head_slot: uint64 + head_slot: U64 target: Bytes32 - target_slot: uint64 + target_slot: U64 source: Bytes32 - source_slot: uint64 + source_slot: U64 From 703f63a97246cf8a623a99ca0a05273cb84033e5 Mon Sep 17 00:00:00 2001 From: unnawut Date: Thu, 7 Aug 2025 20:36:00 +0700 Subject: [PATCH 06/20] fix: add docs and import order --- src/lean_spec/subspecs/pqdevnet-0/block.py | 8 +++++--- src/lean_spec/subspecs/pqdevnet-0/state.py | 12 ++++++++---- src/lean_spec/subspecs/pqdevnet-0/vote.py | 4 +++- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/lean_spec/subspecs/pqdevnet-0/block.py b/src/lean_spec/subspecs/pqdevnet-0/block.py index c56f80a1..4d547e59 100644 --- a/src/lean_spec/subspecs/pqdevnet-0/block.py +++ b/src/lean_spec/subspecs/pqdevnet-0/block.py @@ -8,17 +8,19 @@ """ from dataclasses import dataclass -from pydantic import BaseModel, ConfigDict 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 +from .preset import VALIDATOR_REGISTRY_LIMIT +from .vote import Vote + @dataclass class Block(BaseModel): + """A single block in the Lean Consensus chain.""" model_config = ConfigDict(arbitrary_types_allowed=True) slot: U64 diff --git a/src/lean_spec/subspecs/pqdevnet-0/state.py b/src/lean_spec/subspecs/pqdevnet-0/state.py index 40caadae..430912a6 100644 --- a/src/lean_spec/subspecs/pqdevnet-0/state.py +++ b/src/lean_spec/subspecs/pqdevnet-0/state.py @@ -8,17 +8,19 @@ """ from dataclasses import dataclass -from pydantic import BaseModel, ConfigDict from ethereum_types.bytes import Bytes32 from ethereum_types.numeric import U64 +from pydantic import BaseModel, ConfigDict from ssz.sedes.bitlist import Bitlist from ssz.sedes.list import List -from preset import MAX_HISTORICAL_BLOCK_HASHES, VALIDATOR_REGISTRY_LIMIT +from .preset import MAX_HISTORICAL_BLOCK_HASHES, VALIDATOR_REGISTRY_LIMIT + @dataclass class State(BaseModel): + """Represents the current state of the Lean Consensus chain.""" model_config = ConfigDict(arbitrary_types_allowed=True) # Diverged from 3SF-mini.py: @@ -36,6 +38,8 @@ class State(BaseModel): justified_slots: List[bool, MAX_HISTORICAL_BLOCK_HASHES] # Diverged from 3SF-mini.py: - # - Flattened `justifications: Dict[str, List[bool]]` for SSZ compatibility + # - 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) + justifications_validators: Bitlist[ + MAX_HISTORICAL_BLOCK_HASHES * VALIDATOR_REGISTRY_LIMIT + ] diff --git a/src/lean_spec/subspecs/pqdevnet-0/vote.py b/src/lean_spec/subspecs/pqdevnet-0/vote.py index 6f656c13..d5c5555d 100644 --- a/src/lean_spec/subspecs/pqdevnet-0/vote.py +++ b/src/lean_spec/subspecs/pqdevnet-0/vote.py @@ -5,13 +5,15 @@ """ from dataclasses import dataclass -from pydantic import BaseModel, ConfigDict from ethereum_types.bytes import Bytes32 from ethereum_types.numeric import U64 +from pydantic import BaseModel, ConfigDict + @dataclass class Vote(BaseModel): + """A single vote for a block in the Lean Consensus chain.""" model_config = ConfigDict(arbitrary_types_allowed=True) # Diverged from 3SF-mini.py: From f77a05ba55e961a336ab14ebeaec6ebaff8f18a4 Mon Sep 17 00:00:00 2001 From: unnawut Date: Thu, 7 Aug 2025 20:42:38 +0700 Subject: [PATCH 07/20] fix: move pqdevnet to client and out of subspecs folder --- src/lean_spec/{subspecs/pqdevnet-0 => client}/__init__.py | 0 src/lean_spec/{subspecs/pqdevnet-0 => client}/block.py | 1 + src/lean_spec/{subspecs/pqdevnet-0 => client}/preset.py | 0 src/lean_spec/{subspecs/pqdevnet-0 => client}/state.py | 1 + src/lean_spec/{subspecs/pqdevnet-0 => client}/vote.py | 1 + 5 files changed, 3 insertions(+) rename src/lean_spec/{subspecs/pqdevnet-0 => client}/__init__.py (100%) rename src/lean_spec/{subspecs/pqdevnet-0 => client}/block.py (99%) rename src/lean_spec/{subspecs/pqdevnet-0 => client}/preset.py (100%) rename src/lean_spec/{subspecs/pqdevnet-0 => client}/state.py (99%) rename src/lean_spec/{subspecs/pqdevnet-0 => client}/vote.py (99%) diff --git a/src/lean_spec/subspecs/pqdevnet-0/__init__.py b/src/lean_spec/client/__init__.py similarity index 100% rename from src/lean_spec/subspecs/pqdevnet-0/__init__.py rename to src/lean_spec/client/__init__.py diff --git a/src/lean_spec/subspecs/pqdevnet-0/block.py b/src/lean_spec/client/block.py similarity index 99% rename from src/lean_spec/subspecs/pqdevnet-0/block.py rename to src/lean_spec/client/block.py index 4d547e59..776c79c6 100644 --- a/src/lean_spec/subspecs/pqdevnet-0/block.py +++ b/src/lean_spec/client/block.py @@ -21,6 +21,7 @@ @dataclass class Block(BaseModel): """A single block in the Lean Consensus chain.""" + model_config = ConfigDict(arbitrary_types_allowed=True) slot: U64 diff --git a/src/lean_spec/subspecs/pqdevnet-0/preset.py b/src/lean_spec/client/preset.py similarity index 100% rename from src/lean_spec/subspecs/pqdevnet-0/preset.py rename to src/lean_spec/client/preset.py diff --git a/src/lean_spec/subspecs/pqdevnet-0/state.py b/src/lean_spec/client/state.py similarity index 99% rename from src/lean_spec/subspecs/pqdevnet-0/state.py rename to src/lean_spec/client/state.py index 430912a6..5c92bbc0 100644 --- a/src/lean_spec/subspecs/pqdevnet-0/state.py +++ b/src/lean_spec/client/state.py @@ -21,6 +21,7 @@ @dataclass class State(BaseModel): """Represents the current state of the Lean Consensus chain.""" + model_config = ConfigDict(arbitrary_types_allowed=True) # Diverged from 3SF-mini.py: diff --git a/src/lean_spec/subspecs/pqdevnet-0/vote.py b/src/lean_spec/client/vote.py similarity index 99% rename from src/lean_spec/subspecs/pqdevnet-0/vote.py rename to src/lean_spec/client/vote.py index d5c5555d..6e165eb5 100644 --- a/src/lean_spec/subspecs/pqdevnet-0/vote.py +++ b/src/lean_spec/client/vote.py @@ -14,6 +14,7 @@ @dataclass class Vote(BaseModel): """A single vote for a block in the Lean Consensus chain.""" + model_config = ConfigDict(arbitrary_types_allowed=True) # Diverged from 3SF-mini.py: From 24767f055bc3e0698b91949603c48bb5b45b813b Mon Sep 17 00:00:00 2001 From: unnawut Date: Thu, 7 Aug 2025 20:44:24 +0700 Subject: [PATCH 08/20] fix: blank lines --- src/lean_spec/client/block.py | 2 +- src/lean_spec/client/state.py | 2 +- src/lean_spec/client/vote.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lean_spec/client/block.py b/src/lean_spec/client/block.py index 776c79c6..bc94fb8a 100644 --- a/src/lean_spec/client/block.py +++ b/src/lean_spec/client/block.py @@ -21,7 +21,7 @@ @dataclass class Block(BaseModel): """A single block in the Lean Consensus chain.""" - + model_config = ConfigDict(arbitrary_types_allowed=True) slot: U64 diff --git a/src/lean_spec/client/state.py b/src/lean_spec/client/state.py index 5c92bbc0..1678ab13 100644 --- a/src/lean_spec/client/state.py +++ b/src/lean_spec/client/state.py @@ -21,7 +21,7 @@ @dataclass class State(BaseModel): """Represents the current state of the Lean Consensus chain.""" - + model_config = ConfigDict(arbitrary_types_allowed=True) # Diverged from 3SF-mini.py: diff --git a/src/lean_spec/client/vote.py b/src/lean_spec/client/vote.py index 6e165eb5..be0c3a97 100644 --- a/src/lean_spec/client/vote.py +++ b/src/lean_spec/client/vote.py @@ -14,7 +14,7 @@ @dataclass class Vote(BaseModel): """A single vote for a block in the Lean Consensus chain.""" - + model_config = ConfigDict(arbitrary_types_allowed=True) # Diverged from 3SF-mini.py: From ef95e4238267e656f3c71c5db185feb3d49cfe43 Mon Sep 17 00:00:00 2001 From: unnawut Date: Thu, 7 Aug 2025 21:04:29 +0700 Subject: [PATCH 09/20] refactor: root and slot pairs become checkpoints --- src/lean_spec/client/checkpoint.py | 24 ++++++++++++++++++++++++ src/lean_spec/client/preset.py | 16 ++++++++-------- src/lean_spec/client/state.py | 9 +++------ src/lean_spec/client/vote.py | 12 +++++------- 4 files changed, 40 insertions(+), 21 deletions(-) create mode 100644 src/lean_spec/client/checkpoint.py diff --git a/src/lean_spec/client/checkpoint.py b/src/lean_spec/client/checkpoint.py new file mode 100644 index 00000000..fe8cd2e5 --- /dev/null +++ b/src/lean_spec/client/checkpoint.py @@ -0,0 +1,24 @@ +""" +A `Checkpoint` is a single checkpoint for a block in the Lean Consensus chain. +Each `Checkpoint` contains its associated block root and slot. +""" + +from dataclasses import dataclass + +from ethereum_types.bytes import Bytes32 +from ethereum_types.numeric import U64 +from pydantic import BaseModel, ConfigDict + + +@dataclass +class Checkpoint(BaseModel): + """A single checkpoint in the Lean Consensus chain.""" + + model_config = ConfigDict(arbitrary_types_allowed=True) + + # Diverged from 3SF-mini.py: + # - Using `Bytes32` instead of native `str` for all fields + # - Using `U64` instead of native `int` for all fields + + root: Bytes32 + slot: U64 diff --git a/src/lean_spec/client/preset.py b/src/lean_spec/client/preset.py index 7b1eb780..ed30399f 100644 --- a/src/lean_spec/client/preset.py +++ b/src/lean_spec/client/preset.py @@ -3,24 +3,24 @@ Lean Consensus chain. """ -from remerkleable.basic import uint64 +from ethereum_types.numeric import U64 # Time parameters # --------------------------------------------------------------- # 4 seconds -SLOT_DURATION_MS = 4000 +SLOT_DURATION_MS: U64 = U64(4000) # Basis points (out of 10000) -PROPOSER_REORG_CUTOFF_BPS: 2500 -VOTE_DUE_BPS: 5000 -FAST_CONFIRM_DUE_BPS: 7500 -VIEW_FREEZE_CUTOFF_BPS: 7500 +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: uint64 = 262144 +MAX_HISTORICAL_BLOCK_HASHES: U64 = U64(262144) -VALIDATOR_REGISTRY_LIMIT: uint64 = 4096 +VALIDATOR_REGISTRY_LIMIT: U64 = U64(4096) diff --git a/src/lean_spec/client/state.py b/src/lean_spec/client/state.py index 1678ab13..51900421 100644 --- a/src/lean_spec/client/state.py +++ b/src/lean_spec/client/state.py @@ -10,11 +10,11 @@ from dataclasses import dataclass from ethereum_types.bytes import Bytes32 -from ethereum_types.numeric import U64 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 @@ -29,11 +29,8 @@ class State(BaseModel): # - Using `U64` instead of native int for all fields # - Using `Bytes32` instead of native str for all fields - latest_justified_hash: Bytes32 - latest_justified_slot: U64 - - latest_finalized_hash: Bytes32 - latest_finalized_slot: U64 + latest_justified: Checkpoint + latest_finalized: Checkpoint historical_block_hashes: List[Bytes32, MAX_HISTORICAL_BLOCK_HASHES] justified_slots: List[bool, MAX_HISTORICAL_BLOCK_HASHES] diff --git a/src/lean_spec/client/vote.py b/src/lean_spec/client/vote.py index be0c3a97..9a97d19a 100644 --- a/src/lean_spec/client/vote.py +++ b/src/lean_spec/client/vote.py @@ -6,10 +6,11 @@ from dataclasses import dataclass -from ethereum_types.bytes import Bytes32 from ethereum_types.numeric import U64 from pydantic import BaseModel, ConfigDict +from .checkpoint import Checkpoint + @dataclass class Vote(BaseModel): @@ -23,9 +24,6 @@ class Vote(BaseModel): validator_id: U64 slot: U64 - head: Bytes32 - head_slot: U64 - target: Bytes32 - target_slot: U64 - source: Bytes32 - source_slot: U64 + head: Checkpoint + target: Checkpoint + source: Checkpoint From 4c89d14103db90767dc00b009dcdb9b96fe59b43 Mon Sep 17 00:00:00 2001 From: unnawut Date: Sun, 10 Aug 2025 13:46:21 +0700 Subject: [PATCH 10/20] fix: use Union for optionals --- src/lean_spec/client/block.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/lean_spec/client/block.py b/src/lean_spec/client/block.py index bc94fb8a..1410fe6e 100644 --- a/src/lean_spec/client/block.py +++ b/src/lean_spec/client/block.py @@ -8,8 +8,10 @@ """ from dataclasses import dataclass +from typing import Union from ethereum_types.bytes import Bytes32 +from ethereum_types.bytes import Bytes48 from ethereum_types.numeric import U64 from pydantic import BaseModel, ConfigDict from ssz.sedes.list import List @@ -25,7 +27,6 @@ class Block(BaseModel): model_config = ConfigDict(arbitrary_types_allowed=True) slot: U64 - parent: Bytes32 + parent: Union[None, Bytes32] votes: List[Vote, VALIDATOR_REGISTRY_LIMIT] - # Diverged from 3SF-mini.py: Removed Optional from `state_root` - state_root: Bytes32 + state_root: Union[None, Bytes32] From bc1b30586b97d7853f771097d31f4b110e34470e Mon Sep 17 00:00:00 2001 From: unnawut Date: Sun, 10 Aug 2025 13:47:49 +0700 Subject: [PATCH 11/20] fix: unused import --- src/lean_spec/client/block.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lean_spec/client/block.py b/src/lean_spec/client/block.py index 1410fe6e..6713fb63 100644 --- a/src/lean_spec/client/block.py +++ b/src/lean_spec/client/block.py @@ -11,7 +11,6 @@ from typing import Union from ethereum_types.bytes import Bytes32 -from ethereum_types.bytes import Bytes48 from ethereum_types.numeric import U64 from pydantic import BaseModel, ConfigDict from ssz.sedes.list import List From b239dd63722ebe91d356b1fdaf4320037aa40900 Mon Sep 17 00:00:00 2001 From: unnawut Date: Sun, 10 Aug 2025 13:49:53 +0700 Subject: [PATCH 12/20] fix: remove no longer used remerkleable --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index eb9fcd27..62067402 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,6 @@ requires-python = ">=3.12" dependencies = [ "ethereum-types<=0.2.4,<0.3", "pydantic>=2.9.2,<3", - "remerkleable>=0.1.28,<0.2", "ssz>=0.5.2,<0.6", "typing-extensions>=4.4", ] From e7227676a4719b3049aa47a8f14e75ddcb7b807f Mon Sep 17 00:00:00 2001 From: unnawut Date: Sun, 10 Aug 2025 13:52:53 +0700 Subject: [PATCH 13/20] docs: consolidted comments --- src/lean_spec/client/__init__.py | 9 ++++++++- src/lean_spec/client/checkpoint.py | 4 ---- src/lean_spec/client/state.py | 8 ++------ src/lean_spec/client/vote.py | 4 ---- 4 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/lean_spec/client/__init__.py b/src/lean_spec/client/__init__.py index ea63ee42..cf2177de 100644 --- a/src/lean_spec/client/__init__.py +++ b/src/lean_spec/client/__init__.py @@ -1 +1,8 @@ -"""Specification for pqdevnet-0""" +""" +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 +""" diff --git a/src/lean_spec/client/checkpoint.py b/src/lean_spec/client/checkpoint.py index fe8cd2e5..e61b20e1 100644 --- a/src/lean_spec/client/checkpoint.py +++ b/src/lean_spec/client/checkpoint.py @@ -16,9 +16,5 @@ class Checkpoint(BaseModel): model_config = ConfigDict(arbitrary_types_allowed=True) - # Diverged from 3SF-mini.py: - # - Using `Bytes32` instead of native `str` for all fields - # - Using `U64` instead of native `int` for all fields - root: Bytes32 slot: U64 diff --git a/src/lean_spec/client/state.py b/src/lean_spec/client/state.py index 51900421..e5a1e99e 100644 --- a/src/lean_spec/client/state.py +++ b/src/lean_spec/client/state.py @@ -24,10 +24,7 @@ class State(BaseModel): model_config = ConfigDict(arbitrary_types_allowed=True) - # Diverged from 3SF-mini.py: - # - Removed `config: Config` from the state - # - Using `U64` instead of native int for all fields - # - Using `Bytes32` instead of native str for all fields + # Diverged from 3SF-mini.py: Removed `config: Config` from the state latest_justified: Checkpoint latest_finalized: Checkpoint @@ -35,8 +32,7 @@ class State(BaseModel): 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 + # Diverged from 3SF-mini.py: Flattened `justifications: Dict[str, List[bool]]` justifications_roots: List[Bytes32, MAX_HISTORICAL_BLOCK_HASHES] justifications_validators: Bitlist[ MAX_HISTORICAL_BLOCK_HASHES * VALIDATOR_REGISTRY_LIMIT diff --git a/src/lean_spec/client/vote.py b/src/lean_spec/client/vote.py index 9a97d19a..65533b81 100644 --- a/src/lean_spec/client/vote.py +++ b/src/lean_spec/client/vote.py @@ -18,10 +18,6 @@ class Vote(BaseModel): model_config = ConfigDict(arbitrary_types_allowed=True) - # Diverged from 3SF-mini.py: - # - Using `U64` instead of native `int` for all fields - # - Using `Bytes32` instead of native `str` for all fields - validator_id: U64 slot: U64 head: Checkpoint From 5dc17954f73a49cdb60bf9014a092b2c23651a29 Mon Sep 17 00:00:00 2001 From: unnawut Date: Sun, 10 Aug 2025 14:13:56 +0700 Subject: [PATCH 14/20] fix: remove Union optional --- src/lean_spec/client/block.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/lean_spec/client/block.py b/src/lean_spec/client/block.py index 6713fb63..252a1113 100644 --- a/src/lean_spec/client/block.py +++ b/src/lean_spec/client/block.py @@ -8,7 +8,6 @@ """ from dataclasses import dataclass -from typing import Union from ethereum_types.bytes import Bytes32 from ethereum_types.numeric import U64 @@ -26,6 +25,6 @@ class Block(BaseModel): model_config = ConfigDict(arbitrary_types_allowed=True) slot: U64 - parent: Union[None, Bytes32] + parent: Bytes32 votes: List[Vote, VALIDATOR_REGISTRY_LIMIT] - state_root: Union[None, Bytes32] + state_root: Bytes32 From ac41ad4de5a2a19f876dda340677d68e48cbc148 Mon Sep 17 00:00:00 2001 From: unnawut Date: Sun, 10 Aug 2025 14:25:39 +0700 Subject: [PATCH 15/20] docs: add change comment --- src/lean_spec/client/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lean_spec/client/__init__.py b/src/lean_spec/client/__init__.py index cf2177de..604cd663 100644 --- a/src/lean_spec/client/__init__.py +++ b/src/lean_spec/client/__init__.py @@ -5,4 +5,5 @@ - 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` """ From 1465223d7f2d9d71fcb1123fac12d22c43c20ee6 Mon Sep 17 00:00:00 2001 From: unnawut Date: Tue, 12 Aug 2025 15:00:26 +0700 Subject: [PATCH 16/20] fix: remove dataclass --- src/lean_spec/client/block.py | 3 --- src/lean_spec/client/checkpoint.py | 3 --- src/lean_spec/client/state.py | 3 --- src/lean_spec/client/vote.py | 3 --- 4 files changed, 12 deletions(-) diff --git a/src/lean_spec/client/block.py b/src/lean_spec/client/block.py index 252a1113..01200346 100644 --- a/src/lean_spec/client/block.py +++ b/src/lean_spec/client/block.py @@ -7,8 +7,6 @@ chain. """ -from dataclasses import dataclass - from ethereum_types.bytes import Bytes32 from ethereum_types.numeric import U64 from pydantic import BaseModel, ConfigDict @@ -18,7 +16,6 @@ from .vote import Vote -@dataclass class Block(BaseModel): """A single block in the Lean Consensus chain.""" diff --git a/src/lean_spec/client/checkpoint.py b/src/lean_spec/client/checkpoint.py index e61b20e1..6af0a8f5 100644 --- a/src/lean_spec/client/checkpoint.py +++ b/src/lean_spec/client/checkpoint.py @@ -3,14 +3,11 @@ Each `Checkpoint` contains its associated block root and slot. """ -from dataclasses import dataclass - from ethereum_types.bytes import Bytes32 from ethereum_types.numeric import U64 from pydantic import BaseModel, ConfigDict -@dataclass class Checkpoint(BaseModel): """A single checkpoint in the Lean Consensus chain.""" diff --git a/src/lean_spec/client/state.py b/src/lean_spec/client/state.py index e5a1e99e..b9130dc1 100644 --- a/src/lean_spec/client/state.py +++ b/src/lean_spec/client/state.py @@ -7,8 +7,6 @@ progressing correctly. """ -from dataclasses import dataclass - from ethereum_types.bytes import Bytes32 from pydantic import BaseModel, ConfigDict from ssz.sedes.bitlist import Bitlist @@ -18,7 +16,6 @@ from .preset import MAX_HISTORICAL_BLOCK_HASHES, VALIDATOR_REGISTRY_LIMIT -@dataclass class State(BaseModel): """Represents the current state of the Lean Consensus chain.""" diff --git a/src/lean_spec/client/vote.py b/src/lean_spec/client/vote.py index 65533b81..061d29ae 100644 --- a/src/lean_spec/client/vote.py +++ b/src/lean_spec/client/vote.py @@ -4,15 +4,12 @@ voted for, and the block hash they voted for. """ -from dataclasses import dataclass - from ethereum_types.numeric import U64 from pydantic import BaseModel, ConfigDict from .checkpoint import Checkpoint -@dataclass class Vote(BaseModel): """A single vote for a block in the Lean Consensus chain.""" From 1f8b78f7ed0e5ac4fcae777a7416588fe2c51cbc Mon Sep 17 00:00:00 2001 From: unnawut Date: Tue, 12 Aug 2025 15:00:37 +0700 Subject: [PATCH 17/20] fix: add tests for all containers --- src/lean_spec/client/state.py | 9 +++-- tests/lean_spec/client/test_block.py | 38 ++++++++++++++++++ tests/lean_spec/client/test_checkpoint.py | 15 +++++++ tests/lean_spec/client/test_state.py | 49 +++++++++++++++++++++++ tests/lean_spec/client/test_vote.py | 27 +++++++++++++ 5 files changed, 134 insertions(+), 4 deletions(-) create mode 100644 tests/lean_spec/client/test_block.py create mode 100644 tests/lean_spec/client/test_checkpoint.py create mode 100644 tests/lean_spec/client/test_state.py create mode 100644 tests/lean_spec/client/test_vote.py diff --git a/src/lean_spec/client/state.py b/src/lean_spec/client/state.py index b9130dc1..b8453ee5 100644 --- a/src/lean_spec/client/state.py +++ b/src/lean_spec/client/state.py @@ -29,8 +29,9 @@ class State(BaseModel): 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]]` + # 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 - ] + justifications_validators: Bitlist( + int(MAX_HISTORICAL_BLOCK_HASHES * VALIDATOR_REGISTRY_LIMIT) + ) diff --git a/tests/lean_spec/client/test_block.py b/tests/lean_spec/client/test_block.py new file mode 100644 index 00000000..7eafde08 --- /dev/null +++ b/tests/lean_spec/client/test_block.py @@ -0,0 +1,38 @@ +""" +Tests for the client's Block container. +""" + +from ethereum_types.bytes import Bytes32 +from ethereum_types.numeric import U64 +from lean_spec.client.block import Block +from lean_spec.client.checkpoint import Checkpoint +from lean_spec.client.vote import Vote +from lean_spec.client.preset import VALIDATOR_REGISTRY_LIMIT +from ssz.sedes.list import List + +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), + ) diff --git a/tests/lean_spec/client/test_checkpoint.py b/tests/lean_spec/client/test_checkpoint.py new file mode 100644 index 00000000..486794bc --- /dev/null +++ b/tests/lean_spec/client/test_checkpoint.py @@ -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), + ) + diff --git a/tests/lean_spec/client/test_state.py b/tests/lean_spec/client/test_state.py new file mode 100644 index 00000000..a23d3f39 --- /dev/null +++ b/tests/lean_spec/client/test_state.py @@ -0,0 +1,49 @@ +""" +Tests for the client's State container. +""" + +from unittest.loader import VALID_MODULE_NAME +from ethereum_types.bytes import Bytes32 +from ethereum_types.numeric import U64 +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 +from ssz.sedes.list import List +from ssz.sedes.bitlist import Bitlist + +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) + ), + ) diff --git a/tests/lean_spec/client/test_vote.py b/tests/lean_spec/client/test_vote.py new file mode 100644 index 00000000..9d1fd480 --- /dev/null +++ b/tests/lean_spec/client/test_vote.py @@ -0,0 +1,27 @@ +""" +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), + ), + ) From e6392a789d7c253f67407f8fb44872a5a07d4f93 Mon Sep 17 00:00:00 2001 From: unnawut Date: Tue, 12 Aug 2025 15:04:34 +0700 Subject: [PATCH 18/20] fix: import ordering --- tests/lean_spec/client/test_block.py | 6 ++++-- tests/lean_spec/client/test_checkpoint.py | 2 ++ tests/lean_spec/client/test_state.py | 12 ++++++++---- tests/lean_spec/client/test_vote.py | 2 ++ 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/tests/lean_spec/client/test_block.py b/tests/lean_spec/client/test_block.py index 7eafde08..f10d0d92 100644 --- a/tests/lean_spec/client/test_block.py +++ b/tests/lean_spec/client/test_block.py @@ -4,11 +4,13 @@ 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.vote import Vote from lean_spec.client.preset import VALIDATOR_REGISTRY_LIMIT -from ssz.sedes.list import List +from lean_spec.client.vote import Vote + def test_block(): diff --git a/tests/lean_spec/client/test_checkpoint.py b/tests/lean_spec/client/test_checkpoint.py index 486794bc..24288a53 100644 --- a/tests/lean_spec/client/test_checkpoint.py +++ b/tests/lean_spec/client/test_checkpoint.py @@ -4,8 +4,10 @@ from ethereum_types.bytes import Bytes32 from ethereum_types.numeric import U64 + from lean_spec.client.checkpoint import Checkpoint + def test_checkpoint(): Checkpoint( diff --git a/tests/lean_spec/client/test_state.py b/tests/lean_spec/client/test_state.py index a23d3f39..56c4d809 100644 --- a/tests/lean_spec/client/test_state.py +++ b/tests/lean_spec/client/test_state.py @@ -2,14 +2,18 @@ Tests for the client's State container. """ -from unittest.loader import VALID_MODULE_NAME 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.preset import ( + MAX_HISTORICAL_BLOCK_HASHES, + VALIDATOR_REGISTRY_LIMIT, +) from lean_spec.client.state import State -from ssz.sedes.list import List -from ssz.sedes.bitlist import Bitlist + def test_state(): diff --git a/tests/lean_spec/client/test_vote.py b/tests/lean_spec/client/test_vote.py index 9d1fd480..e2c6c34f 100644 --- a/tests/lean_spec/client/test_vote.py +++ b/tests/lean_spec/client/test_vote.py @@ -4,9 +4,11 @@ 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( From bfcdca8c9f6842a0f2e97233ade7aaec113d24ab Mon Sep 17 00:00:00 2001 From: unnawut Date: Tue, 12 Aug 2025 15:05:49 +0700 Subject: [PATCH 19/20] fix: ruff format --- tests/lean_spec/client/test_block.py | 1 - tests/lean_spec/client/test_checkpoint.py | 2 -- tests/lean_spec/client/test_state.py | 1 - tests/lean_spec/client/test_vote.py | 1 - 4 files changed, 5 deletions(-) diff --git a/tests/lean_spec/client/test_block.py b/tests/lean_spec/client/test_block.py index f10d0d92..913a8c98 100644 --- a/tests/lean_spec/client/test_block.py +++ b/tests/lean_spec/client/test_block.py @@ -13,7 +13,6 @@ def test_block(): - Block( slot=U64(1), parent=Bytes32(b"\x02" * 32), diff --git a/tests/lean_spec/client/test_checkpoint.py b/tests/lean_spec/client/test_checkpoint.py index 24288a53..e786bb3b 100644 --- a/tests/lean_spec/client/test_checkpoint.py +++ b/tests/lean_spec/client/test_checkpoint.py @@ -9,9 +9,7 @@ def test_checkpoint(): - Checkpoint( root=Bytes32(b"\x42" * 32), slot=U64(42), ) - diff --git a/tests/lean_spec/client/test_state.py b/tests/lean_spec/client/test_state.py index 56c4d809..fc2a4ba9 100644 --- a/tests/lean_spec/client/test_state.py +++ b/tests/lean_spec/client/test_state.py @@ -16,7 +16,6 @@ def test_state(): - State( latest_justified=Checkpoint( root=Bytes32(b"\x00" * 32), diff --git a/tests/lean_spec/client/test_vote.py b/tests/lean_spec/client/test_vote.py index e2c6c34f..b857073e 100644 --- a/tests/lean_spec/client/test_vote.py +++ b/tests/lean_spec/client/test_vote.py @@ -10,7 +10,6 @@ def test_vote(): - Vote( validator_id=U64(1), slot=U64(2), From 33603bc3140b01eedd66e374a49cccd2eb4c6576 Mon Sep 17 00:00:00 2001 From: unnawut Date: Tue, 12 Aug 2025 15:14:50 +0700 Subject: [PATCH 20/20] fix: mypy --- src/lean_spec/client/state.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lean_spec/client/state.py b/src/lean_spec/client/state.py index b8453ee5..6618cfda 100644 --- a/src/lean_spec/client/state.py +++ b/src/lean_spec/client/state.py @@ -32,6 +32,6 @@ class State(BaseModel): # Diverged from 3SF-mini.py: # Flattened `justifications: Dict[str, List[bool]]` for SSZ justifications_roots: List[Bytes32, MAX_HISTORICAL_BLOCK_HASHES] - justifications_validators: Bitlist( - int(MAX_HISTORICAL_BLOCK_HASHES * VALIDATOR_REGISTRY_LIMIT) - ) + justifications_validators: Bitlist[ + MAX_HISTORICAL_BLOCK_HASHES * VALIDATOR_REGISTRY_LIMIT + ]