11"""
2- Validator registry for managing validator keys .
2+ Validator key loading .
33
4- Loads validator keys from YAML configuration files compatible with ream and zeam .
4+ Two YAML files describe the keys .
55
6- The registry supports two YAML files :
6+ - validators.yaml maps each node to the validator indices it controls :
77
8- 1. **validators.yaml** - Maps node IDs to validator indices:
8+ lean_spec_0:
9+ - 0
10+ - 1
11+ lean_spec_1:
12+ - 2
913
10- lean_spec_0:
11- - 0
12- - 1
13- lean_spec_1:
14- - 2
14+ - validator-keys-manifest.yaml lists each validator's key metadata and file paths:
1515
16- 2. **validator-keys-manifest.yaml** - Contains key metadata and file paths:
17-
18- key_scheme: SIGTopLevelTargetSumLifetime32Dim64Base8
19- hash_function: Poseidon
20- num_validators: 3
21- validators:
22- - index: 0
23- public_key_hex: 0xe2a03c...
24- private_key_file: validator_0_secret_key.ssz
16+ key_scheme: SIGTopLevelTargetSumLifetime32Dim64Base8
17+ hash_function: Poseidon
18+ num_validators: 3
19+ validators:
20+ - index: 0
21+ public_key_hex: 0xe2a03c...
22+ private_key_file: validator_0_secret_key.ssz
2523"""
2624
2725from __future__ import annotations
3937
4038logger = logging .getLogger (__name__ )
4139
42- type NodeValidatorMapping = dict [str , list [int ]]
43- """Mapping from node identifier to list of validator indices."""
44-
4540
4641class ValidatorManifestEntry (BaseModel ):
4742 """Single validator entry from the manifest file."""
@@ -63,12 +58,7 @@ class ValidatorManifestEntry(BaseModel):
6358
6459
6560class ValidatorManifest (BaseModel ):
66- """
67- Key metadata from validator-keys-manifest.yaml.
68-
69- Contains cryptographic scheme info and validator key paths.
70- This format matches ream's manifest structure.
71- """
61+ """Key metadata for every validator, matching the ream manifest format."""
7262
7363 key_scheme : str
7464 """Signature scheme identifier (e.g., SIGTopLevelTargetSumLifetime32Dim64Base8)."""
@@ -96,46 +86,18 @@ class ValidatorManifest(BaseModel):
9686
9787 @classmethod
9888 def from_yaml_file (cls , path : Path ) -> ValidatorManifest :
99- """
100- Load manifest from YAML file.
101-
102- Args:
103- path: Path to validator-keys-manifest.yaml.
104-
105- Returns:
106- Validated ValidatorManifest instance.
107- """
89+ """Load and validate a manifest from a YAML file."""
10890 with path .open () as f :
10991 return cls .model_validate (yaml .safe_load (f ))
11092
11193
112- def load_node_validator_mapping (path : Path ) -> NodeValidatorMapping :
113- """
114- Load node-to-validator index mapping from validators.yaml.
115-
116- Maps node identifiers to lists of validator indices they control.
117-
118- Args:
119- path: Path to validators.yaml.
120-
121- Returns:
122- Mapping from node ID to list of validator indices.
123- Empty dict if file is empty.
124- """
125- with path .open () as yaml_file :
126- parsed_yaml = yaml .safe_load (yaml_file )
127- # YAML returns None for empty file
128- return parsed_yaml or {}
129-
130-
13194@dataclass (frozen = True , slots = True )
13295class ValidatorEntry :
13396 """
13497 A single validator's key material.
13598
136- Holds the index and both secret keys needed for signing.
137- Attestation and proposal keys are separate to allow independent
138- OTS signing within the same slot.
99+ Attestation and proposal keys are separate.
100+ This lets one validator sign both within the same slot without OTS conflict.
139101 """
140102
141103 index : ValidatorIndex
@@ -150,63 +112,33 @@ class ValidatorEntry:
150112
151113@dataclass (slots = True )
152114class ValidatorRegistry :
153- """
154- Registry of validator keys controlled by this node.
155-
156- The registry holds secret keys for validators assigned to this node.
157- It provides lookup by validator index for signing operations.
158- """
115+ """Signing keys for the validators this node controls."""
159116
160117 _validators : dict [ValidatorIndex , ValidatorEntry ] = field (default_factory = dict )
161118 """Map from validator index to entry."""
162119
163120 def add (self , entry : ValidatorEntry ) -> None :
164- """
165- Add or replace a validator entry in the registry.
166-
167- Replaces any existing entry with the same index.
168- Used to persist updated key state after signing.
169-
170- Args:
171- entry: Validator entry to add.
172- """
121+ """Add a validator entry, replacing any existing entry with the same index."""
173122 self ._validators [entry .index ] = entry
174123
175124 def get (self , index : ValidatorIndex ) -> ValidatorEntry | None :
176- """
177- Get validator entry by index.
178-
179- Args:
180- index: Validator index to look up.
181-
182- Returns:
183- Validator entry if found, None otherwise.
184- """
125+ """Return the validator entry for an index, or None if not controlled."""
185126 return self ._validators .get (index )
186127
187128 def __contains__ (self , index : ValidatorIndex ) -> bool :
188129 """Check if we control this validator."""
189130 return index in self ._validators
190131
191132 def indices (self ) -> ValidatorIndices :
192- """
193- Get all validator indices we control.
194-
195- Returns:
196- ValidatorIndices collection.
197- """
133+ """Return every validator index this node controls."""
198134 return ValidatorIndices (data = list (self ._validators .keys ()))
199135
200136 def primary_index (self ) -> ValidatorIndex | None :
201137 """
202- Get the primary validator index for store-level identity .
138+ The store-level identity for this node, or None if it controls no validators .
203139
204- Returns the first validator index in the registry.
205- With ATTESTATION_COMMITTEE_COUNT = 1, all validators share subnet 0,
206- so a single ID suffices for store-level operations.
207-
208- Returns:
209- First validator index, or None if registry is empty.
140+ Every validator shares the single attestation subnet.
141+ So the first controlled index suffices for store-level operations.
210142 """
211143 if not self ._validators :
212144 return None
@@ -219,24 +151,13 @@ def __len__(self) -> int:
219151 @classmethod
220152 def from_keys_directory (cls , node_id : str , base_directory : Path | str ) -> ValidatorRegistry :
221153 """
222- Load a validator registry from the ream/zeam keystore layout.
223-
224- Two files relative to the base directory:
225-
226- - validators.yaml: maps each node to its validator indices.
227- - hash-sig-keys/validator-keys-manifest.yaml: lists each validator's
228- key metadata and SSZ file path.
154+ Load a registry from the ream/zeam keystore layout.
229155
230- Args:
231- node_id: Identifier looked up in the node-to-validator mapping.
232- base_directory: Directory containing the two layout files.
156+ Reads validators.yaml and hash-sig-keys/validator-keys-manifest.yaml,
157+ both relative to the base directory.
233158
234- Returns:
235- Registry populated with the keys assigned to the node.
236-
237- Raises:
238- FileNotFoundError: If the manifest file is missing.
239- A missing validators mapping is allowed and yields an empty registry.
159+ A missing manifest raises FileNotFoundError.
160+ A missing validators mapping is allowed and yields an empty registry.
240161 """
241162 base = Path (base_directory )
242163 manifest_path = base / "hash-sig-keys" / "validator-keys-manifest.yaml"
@@ -255,28 +176,13 @@ def from_yaml(
255176 validators_path : Path | str ,
256177 manifest_path : Path | str ,
257178 ) -> ValidatorRegistry :
258- """
259- Load validator registry from YAML configuration files.
260-
261- Loading process:
262-
263- 1. Read validators.yaml to find indices assigned to this node
264- 2. Read manifest to get key file paths
265- 3. Load secret keys from SSZ files
266-
267- Args:
268- node_id: Identifier for this node in validators.yaml.
269- validators_path: Path to validators.yaml.
270- manifest_path: Path to validator-keys-manifest.yaml.
271-
272- Returns:
273- Registry populated with validator keys for this node.
274- """
179+ """Load a registry for one node from its validators.yaml and manifest files."""
275180 validators_path = Path (validators_path )
276181 manifest_path = Path (manifest_path )
277182
278- # Load node-to-validator mapping.
279- node_mapping = load_node_validator_mapping (validators_path )
183+ # Read the node-to-validator mapping; an empty file parses to None.
184+ with validators_path .open () as validators_file :
185+ node_mapping = yaml .safe_load (validators_file ) or {}
280186
281187 # Get indices assigned to this node.
282188 assigned_indices = node_mapping .get (node_id , [])
@@ -308,7 +214,7 @@ def from_yaml(
308214 )
309215 continue
310216
311- # Load attestation secret key from SSZ file.
217+ # Decode the attestation key from its SSZ file.
312218 attestation_key_path = manifest_directory / manifest_entry .attestation_private_key_file
313219 try :
314220 attestation_secret_key = SecretKey .decode_bytes (attestation_key_path .read_bytes ())
@@ -321,7 +227,7 @@ def from_yaml(
321227 f"Failed to load attestation key for validator { validator_index } : { exception } "
322228 ) from exception
323229
324- # Load proposal secret key from SSZ file.
230+ # Decode the proposal key from its SSZ file.
325231 proposal_key_path = manifest_directory / manifest_entry .proposal_private_key_file
326232 try :
327233 proposal_secret_key = SecretKey .decode_bytes (proposal_key_path .read_bytes ())
0 commit comments