fix: support new annotated_validators.yaml lean-quickstart format#277
Conversation
🤖 Kimi Code ReviewThe PR refactors validator key loading to support Security & Correctness
Code Quality
Logic Validation
Consensus Safety
Recommendation: Address the absolute path consideration if the YAML source is ever untrusted; otherwise the PR is sound and ready for merge. Automated review by Kimi (Moonshot AI) · kimi-k2.5 · custom prompt |
🤖 Claude Code ReviewNow I have full context. Let me write the review. PR #277 Review:
|
🤖 Codex Code Review
No fork-choice, justification/finalization, STF, attestation-processing, XMSS verification, or SSZ logic is touched here; the risk is isolated to validator key/config loading. I could not run Automated review by OpenAI Codex · gpt-5.4 · custom prompt |
Greptile SummarySwitches The implementation is clean: Confidence Score: 5/5Safe to merge; the only finding is a trivial visibility annotation on a module-private helper. No P0 or P1 issues found. The single comment is a P2 style suggestion (removing No files require special attention.
|
| Filename | Overview |
|---|---|
| bin/ethlambda/src/main.rs | Rewrites validator key loading to consume lean-quickstart's native two-entry-per-validator YAML format; new logic groups entries by index, routes via filename substring to attestation/proposal slots, and validates pubkey_hex for hex/length only (documented limitation). |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["annotated_validators.yaml\n(2 entries per validator)"] --> B["read_validator_keys()"]
B --> C["Lookup node_id\nBTreeMap<String, Vec<AnnotatedValidator>>"]
C -->|not found| ERR1["Error: node_id not found"]
C -->|found| D["Iterate Vec<AnnotatedValidator>"]
D --> E["classify_role(privkey_file)"]
E -->|contains 'attester'| F["ValidatorKeyRole::Attestation"]
E -->|contains 'proposer'| G["ValidatorKeyRole::Proposal"]
E -->|neither / both| ERR2["Error: ambiguous filename"]
F --> H["grouped BTreeMap<u64, RoleSlots>"]
G --> H
H -->|duplicate role| ERR3["Error: duplicate role entry"]
H --> I["For each (idx, RoleSlots)"]
I -->|missing attestation| ERR4["Error: missing attester entry"]
I -->|missing proposal| ERR5["Error: missing proposer entry"]
I --> J["load_key(att_path)\nload_key(prop_path)"]
J --> K["HashMap<u64, ValidatorKeyPair>"]
Prompt To Fix All With AI
This is a comment left during a code review.
Path: bin/ethlambda/src/main.rs
Line: 255
Comment:
**Unnecessary `pub` visibility on module-private function**
`deser_pubkey_hex` is only referenced by the `#[serde(deserialize_with = "...")]` attribute on `AnnotatedValidator`, which is in the same module. In a binary crate there is no external caller, and `pub` adds no value here — the function would work identically without it.
```suggestion
fn deser_pubkey_hex<'de, D>(d: D) -> Result<ValidatorPubkeyBytes, D::Error>
```
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "compat: load annotated_validators.yaml i..." | Re-trigger Greptile
| privkey_file: PathBuf, | ||
| } | ||
|
|
||
| pub fn deser_pubkey_hex<'de, D>(d: D) -> Result<ValidatorPubkeyBytes, D::Error> |
There was a problem hiding this comment.
Unnecessary
pub visibility on module-private function
deser_pubkey_hex is only referenced by the #[serde(deserialize_with = "...")] attribute on AnnotatedValidator, which is in the same module. In a binary crate there is no external caller, and pub adds no value here — the function would work identically without it.
| pub fn deser_pubkey_hex<'de, D>(d: D) -> Result<ValidatorPubkeyBytes, D::Error> | |
| fn deser_pubkey_hex<'de, D>(d: D) -> Result<ValidatorPubkeyBytes, D::Error> |
Prompt To Fix With AI
This is a comment left during a code review.
Path: bin/ethlambda/src/main.rs
Line: 255
Comment:
**Unnecessary `pub` visibility on module-private function**
`deser_pubkey_hex` is only referenced by the `#[serde(deserialize_with = "...")]` attribute on `AnnotatedValidator`, which is in the same module. In a binary crate there is no external caller, and `pub` adds no value here — the function would work identically without it.
```suggestion
fn deser_pubkey_hex<'de, D>(d: D) -> Result<ValidatorPubkeyBytes, D::Error>
```
How can I resolve this? If you propose a fix, please make it concise.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
The lean-quickstart devnet4 generator (generate-genesis.sh:655-672, DUAL_KEY_MODE=true branch) emits two entries per validator with `pubkey_hex` + `privkey_file` fields, distinguished by `attester` / `proposer` substring in the filename. zeam and lantern already parse this layout. ethlambda was the only client requiring an explicit `attestation_privkey_file` + `proposal_privkey_file` schema, forcing operators to hand-rewrite the file after every regeneration. Switch the loader to consume the generator's native output: - AnnotatedValidator now models one role-specific entry (single privkey_file + pubkey_hex). - read_validator_keys groups entries per validator index and routes by filename substring, matching zeam (pkgs/cli/src/node.zig:540) and lantern (client_keys.c:606). - pubkey_hex is parsed for hex-format validation (52-byte hex) but not cross-checked against the loaded secret key. Deriving the pubkey would require either an upstream leanSig accessor (no public_key() exists today on GeneralizedXMSSSecretKey) or burning an XMSS one-time slot per startup. ValidatorKeyPair, KeyManager, and BlockChain::spawn signatures unchanged.
7358311 to
61dc50b
Compare
… (incl. #275 #277) ## Motivation Implements devnet4 ([leanSpec#449](leanEthereum/leanSpec#449)): separate attestation and proposal keys for validators, replacing the single-key model and removing the proposer attestation wrapper. ## Description ### Phase 1: Types (#230) - `Validator` gains `attestation_pubkey` + `proposal_pubkey` (replaces single `pubkey`) - `SignedBlockWithAttestation` → `SignedBlock`, `BlockWithAttestation` deleted - Genesis config updated to dual-key YAML format ### Phase 2: Key manager + block proposal (#231) - `ValidatorKeyPair` with separate attestation/proposal secret keys - Block proposal signs `hash_tree_root(block)` with proposal key (no proposer attestation) - All validators now attest at interval 1 (proposer no longer skipped) ### Phase 3: Store + verification (#232) - Signature verification uses `get_attestation_pubkey()` / `get_proposal_pubkey()` as appropriate - Removed ~40 lines of proposer attestation handling from `on_block_core` - Storage reads/writes `SignedBlock` directly ### Phase 4: Network + tests (#233) - Type rename cascade across all network layer files - Test harness updated: removed `ProposerAttestation`, simplified `build_signed_block()` - Added proposal signing metrics - leanSpec bumped to 9c30436 (dual-key test fixtures) - Skipped `test_reorg_with_slot_gaps` (fixture relies on gossip proposer attestation state) ## Supersedes Closes #230, closes #231, closes #232 ## Blockers - **lean-quickstart**: needs devnet4 genesis config support for manual devnet testing --------- Co-authored-by: Tomás Grüner <47506558+MegaRedHand@users.noreply.github.com>
The lean-quickstart devnet4 generator (generate-genesis.sh:655-672, DUAL_KEY_MODE=true branch) emits two entries per validator with
pubkey_hex+privkey_filefields, distinguished byattester/proposersubstring in the filename. zeam and lantern already parse this layout. ethlambda was the only client requiring an explicitattestation_privkey_file+proposal_privkey_fileschema, forcing operators to hand-rewrite the file after every regeneration.Switch the loader to consume the generator's native output:
ValidatorKeyPair, KeyManager, and BlockChain::spawn signatures unchanged.
This PR also updates the lean-multisig version to the latest
devnet4commit and thelog_inv_rateaggregation parameter to 2 (which is what other clients use).It also adds a limit to the number of attestation data objects per block (leanEthereum/leanSpec#536)