Skip to content

feat(rpc): add GET /lean/v0/genesis#455

Open
MegaRedHand wants to merge 4 commits into
mainfrom
feat/lean-api-genesis
Open

feat(rpc): add GET /lean/v0/genesis#455
MegaRedHand wants to merge 4 commits into
mainfrom
feat/lean-api-genesis

Conversation

@MegaRedHand

Copy link
Copy Markdown
Collaborator

Adds the GET /lean/v0/genesis endpoint, which returns the genesis time and genesis validator set from the node's configuration.

Useful for clients that need to bootstrap their slot clock or verify they are on the correct network. Has unit tests and passed clippy.

Stacked on #454.

@github-actions

Copy link
Copy Markdown

🤖 Kimi Code Review

Review of PR 455: Add genesis RPC endpoint

The implementation is clean and follows existing patterns, but there's a critical semantic issue regarding what constitutes "genesis" data.

Critical Issue

  1. Endpoint returns head state, not genesis state (genesis.rs:14-15)
    The endpoint /lean/v0/genesis returns store.head_state().validators.len(), which gives the current validator count. This mutates as the chain processes deposits/exits. The standard Beacon API /eth/v1/beacon/genesis returns immutable genesis data.

    Fix: Either rename the endpoint to /lean/v0/head/summary (if current state is intended), or query the actual genesis state (e.g., store.genesis_state() or store.state_at_slot(0)) to return the genesis validator count and genesis_validators_root.

Medium Concerns

  1. State access pattern (genesis.rs:14)
    Verify that store.head_state() returns an Arc<BeaconState> or reference rather than cloning the entire state. If it clones, this creates a DoS vector where frequent API calls trigger expensive state copies (Ethereum states are large).

  2. Missing genesis fields
    Standard genesis endpoints return genesis_validators_root and genesis_fork_version. Consider adding these for API compatibility, or document the intentional omission in the GenesisResponse docstring.

Minor Suggestions

  1. Explicit casting (genesis.rs:15)
    The as u64 cast from usize is safe on 64-bit targets (standard for Ethereum nodes), but prefer u64::try_from(state.validators.len()).expect("validator count overflow") for explicitness, or add a comment acknowledging the assumption.

  2. Error handling (genesis.rs:13)
    Ensure store.head_state() cannot panic or hang if the state is uninitialized. If it returns Option or Result, handle it gracefully rather than unwrapping implicitly via the accessor.

Code Quality

  • Tests are well-structured using tower::ServiceExt for integration testing.
  • Axum router composition in lib.rs:104 follows existing conventions correctly.
  • The json_response helper usage maintains consistency with adjacent modules.

Recommendation: Address Item 1 (semantic mismatch) before merging. The current implementation will confuse users expecting immutable genesis data while the node is synced past genesis.


Automated review by Kimi (Moonshot AI) · kimi-k2.5 · custom prompt

@github-actions

Copy link
Copy Markdown

🤖 Codex Code Review

  1. Low: get_genesis derives validator_count via store.head_state().validators.len() at crates/net/rpc/src/genesis.rs. Store::head_state() returns a full cloned State crates/storage/src/store.rs, and State carries large SSZ-backed collections like historical roots and justification bitfields crates/common/types/src/state.rs crates/common/types/src/state.rs. For a lightweight metadata endpoint, that makes each request O(size of state) in CPU/memory traffic. I’d prefer a cheap accessor such as cached validator-count metadata on Store, or another path that avoids cloning the full consensus state.

No correctness, fork-choice, attestation, finalization, XMSS, or SSZ issues are introduced by this PR beyond that performance concern; the change is otherwise straightforward route wiring plus a focused test.

I could not run the Rust test suite in this sandbox: cargo/rustup need writes under /home/runner/.rustup and /home/runner/.cargo, which are read-only here, and dependency fetches are therefore blocked.


Automated review by OpenAI Codex · gpt-5.4 · custom prompt

@greptile-apps

greptile-apps Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a new GET /lean/v0/genesis endpoint that returns the chain's genesis time and validator count, along with a unit test and documentation update.

  • genesis.rs: Implements the handler by reading genesis_time from store.config() and validator_count from store.head_state().validators.len(), with a comment explaining that Lean's no-churn assumption makes these equivalent.
  • lib.rs: Registers the new module and merges its routes into the API router.
  • docs/rpc.md: Adds the endpoint to the reference table with an example response and field description.

Confidence Score: 5/5

The change is additive and isolated — a new read-only endpoint with no side effects on existing routes or state.

The handler only reads config and state; it cannot mutate anything. The no-churn protocol assumption is documented in both the code comment and the docs, making the head_state() usage a deliberate, acknowledged choice. The unit test exercises the full request path against a real in-memory store. No existing code paths are altered.

No files require special attention.

Important Files Changed

Filename Overview
crates/net/rpc/src/genesis.rs New handler for GET /lean/v0/genesis; reads genesis_time from config and validator_count from head_state() with a no-churn protocol assumption comment. Unit test covers the happy path.
crates/net/rpc/src/lib.rs Adds the genesis module declaration and merges genesis::routes() into the API router — mechanical, correct wiring.
docs/rpc.md Documentation update adds the new endpoint to the reference table and provides an example response and field description.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Client
    participant RPC as Axum RPC Server
    participant Handler as get_genesis handler
    participant Store

    Client->>RPC: GET /lean/v0/genesis
    RPC->>Handler: route match
    Handler->>Store: config()
    Store-->>Handler: "ChainConfig { genesis_time }"
    Handler->>Store: head_state()
    Store-->>Handler: "State { validators: [...] }"
    Handler-->>RPC: "GenesisResponse { genesis_time, validator_count }"
    RPC-->>Client: 200 OK (JSON)
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Client
    participant RPC as Axum RPC Server
    participant Handler as get_genesis handler
    participant Store

    Client->>RPC: GET /lean/v0/genesis
    RPC->>Handler: route match
    Handler->>Store: config()
    Store-->>Handler: "ChainConfig { genesis_time }"
    Handler->>Store: head_state()
    Store-->>Handler: "State { validators: [...] }"
    Handler-->>RPC: "GenesisResponse { genesis_time, validator_count }"
    RPC-->>Client: 200 OK (JSON)
Loading

Reviews (2): Last reviewed commit: "docs(rpc): document GET /lean/v0/genesis" | Re-trigger Greptile

Comment thread crates/net/rpc/src/genesis.rs
Comment thread crates/net/rpc/src/genesis.rs
@github-actions

Copy link
Copy Markdown

🤖 Claude Code Review

Now I have all the information I need. Here is the review:


PR Review: feat(rpc): add GET /lean/v0/genesis

This PR adds a /lean/v0/genesis endpoint that returns genesis_time and validator_count from the store, stacked on #454. The implementation is small and clean, but there are a few issues worth addressing.


Findings

crates/net/rpc/src/genesis.rs:8-10 — Response shape diverges from the PR description

The PR description says the endpoint returns "the genesis time and genesis validator set." GenesisResponse only contains genesis_time and validator_count — a derived integer, not the actual validator set. If any lean client calls this endpoint to retrieve genesis pubkeys for bootstrapping (e.g. to verify committee assignments or reconstruct the genesis state), it receives only a count and silently gets nothing useful. If the spec or other client implementations define this endpoint to return actual pubkeys, ethlambda will be incompatible. Either the response struct should include the validator pubkeys, or the PR description (and probably the endpoint name/semantics) should be clarified to "genesis metadata" rather than "genesis validator set."


crates/net/rpc/src/genesis.rs:15validator_count is read from head_state, not genesis state

head_state() returns the state at the current chain head (slot N), not at genesis (slot 0). For an endpoint explicitly named /genesis, the semantically correct source is the genesis state. While lean consensus has no validator churn today so the counts happen to match, this couples the genesis endpoint to live chain state. Two nodes at different heads (e.g. one slightly behind during sync) would return the same value coincidentally but for the wrong reason. If validator registration is added to the state transition function, this endpoint silently returns a post-genesis count. A correct implementation would read the anchor/genesis state, or store the genesis validator count in ChainConfig alongside genesis_time.


crates/net/rpc/src/genesis.rs:15 — Full state deserialization for a single .len() call

store.head_state() deserializes the entire State from storage, including the full validator list with ~104 bytes of XMSS pubkeys per validator, just to call .len() and discard everything else. This is the same pattern as fork_choice.rs:51-52, but a genesis endpoint is supposed to return immutable data — the answer never changes after genesis. The validator count could be stored in ChainConfig (next to genesis_time) or cached in a metadata key, eliminating both the correctness issue above and the deserialization cost on every request.


crates/net/rpc/src/genesis.rs:55-57 — Test assertion is vacuous for validator_count

create_test_state() initializes validators: Default::default() (empty list), so assert_eq!(json["validator_count"], 0) passes trivially — it would pass even if the implementation always returned 0 regardless of actual state. A meaningful test would populate the genesis state with one or more validators and assert the count matches. As written, the test gives false confidence that the validator count logic is exercised.


crates/net/rpc/src/genesis.rs:47 — Test uses crate::build_api_router instead of routes().with_state(store)

fork_choice.rs (line 105) tests the module in isolation via routes().with_state(store), keeping tests self-contained and independent of the full router. The genesis test reaches into the parent crate with crate::build_api_router(store), coupling it to all other routes. This is a minor inconsistency but could cause unexpected interactions if other routes change.


Automated review by Claude (Anthropic) · sonnet · custom prompt

Base automatically changed from feat/lean-api-router-scaffold to main June 24, 2026 19:10
…ount test)

Build a 3-validator anchor state in the genesis test so asserting
validator_count == 3 actually exercises the handler. Also adds a
clarifying comment explaining why head_state().validators.len() equals
the genesis count (no churn in lean), and switches the test router to
routes().with_state(store) for isolation.
@MegaRedHand MegaRedHand force-pushed the feat/lean-api-genesis branch from c36ca30 to b9422c3 Compare June 25, 2026 14:56
@MegaRedHand MegaRedHand marked this pull request as draft June 25, 2026 19:58
@MegaRedHand MegaRedHand marked this pull request as ready for review July 1, 2026 21:12
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

🤖 Kimi Code Review

The PR introduces a new RPC endpoint to expose genesis configuration. The implementation is clean and follows Rust idioms. A few minor points:

crates/net/rpc/src/genesis.rs

  • Line 15: The as u64 cast from usize is acceptable given Ethereum's validator limit (~2^19), but prefer u64::try_from(...).expect("validator count fits in u64") to make the conversion explicit and panic-safe on the off-chance of 32-bit architectures or future protocol changes.
  • Line 13: store.head_state() assumes the store is initialized. If the store can return None or error (e.g., during startup before genesis is loaded), this will panic. Consider if the store API guarantees initialization; if not, map the error to an HTTP 503.
  • Lines 39-40 (test): The 52-byte pubkey arrays are unusual (BLS12-381 uses 48 bytes). Given the PR context mentions XMSS, confirm this matches your XMSS instantiation's public key size; otherwise, the test data may be invalid.

Documentation

  • The rpc.md update correctly documents the JSON schema and the lean-specific invariant that validators are fixed at genesis.

General

  • No blocking issues. The endpoint is read-only, returns simple JSON, and does not introduce consensus-critical logic. The test coverage is adequate for the happy path.

Automated review by Kimi (Moonshot AI) · kimi-k2.5 · custom prompt

@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

🤖 Codex Code Review

  1. Performance / DoS risk: crates/net/rpc/src/genesis.rs serves two scalar fields, but it gets validator_count via store.head_state(), which returns a full cloned State (crates/storage/src/store.rs). get_state() clones again even on cache hits, and may reconstruct from diffs on misses (crates/storage/src/store.rs). Since State carries the validator registry plus historical/justification structures (crates/common/types/src/state.rs), this turns a public metadata endpoint into a relatively expensive allocation/copy path. I’d avoid head_state() here and persist the validator count in store metadata at init time, or add a lightweight accessor that does not materialize the full state.

I did not find consensus-layer correctness regressions in this PR; it is isolated to RPC/docs wiring. I could not run the targeted test in this environment because the sandbox blocks writes to the default rustup/cargo directories, so there is still an unverified compilation/test gap.


Automated review by OpenAI Codex · gpt-5.4 · custom prompt

@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

🤖 Claude Code Review

Review: feat(rpc): add GET /lean/v0/genesis

Small, well-tested addition. Overall the code is clean and follows existing patterns in the crate (json_response helper, Router<Store> composition, test style). A few points worth considering:

1. validator_count alone is a weak "correct network" signal

The PR description states this endpoint helps clients "verify they are on the correct network," but crates/net/rpc/src/genesis.rs:17 only exposes a validator count, not any digest of the actual validator set (pubkeys). Two different devnets/testnets can trivially have the same validator count, so a client comparing only validator_count (and genesis_time, which is also easy to collide on for local devnets) gets very little assurance it's talking to the right network. Standard beacon-API /eth/v1/beacon/genesis solves this with genesis_validators_root, a hash committing to the full set. Worth considering whether this endpoint should also return a root/digest of the genesis validator set (or the full set) rather than just a count, if "correct network" verification is really a goal — otherwise the doc/PR description overstates what's delivered.

2. Deriving validator_count via head_state() is more expensive than necessary

crates/net/rpc/src/genesis.rs:15 calls store.head_state(), which (per crates/storage/src/store.rs:1508get_state) can require reconstructing the entire State from a chain of diffs back to the nearest 1024-slot snapshot anchor when the LRU cache is cold — just to read validators.len(). In practice this is likely warm most of the time since other duties (block building) also call head_state(), but a dedicated lightweight endpoint doing a full state deep-clone (including justified_slots, justifications_validators, etc.) purely for a usize is wasteful. Since validators are fixed at genesis (per the comment on line 12-13), consider sourcing the count from the anchor/genesis state or persisting it alongside ChainConfig in Metadata instead of reconstructing head state.

3. Minor: comment relies on an invariant that isn't enforced at the call site

The comment at genesis.rs:12-13 ("Lean validators are fixed at genesis, no churn") is a correctness-critical assumption for this handler to be accurate — if validator set mutation is ever introduced (unlikely per current spec, but worth flagging since this is consensus-adjacent code), this handler would silently return a stale/wrong count with no compiler or runtime signal. Not a blocker, just noting the coupling for future maintainers touching validator registry logic.

Nits

  • validators.len() as u64 (genesis.rs:16) is fine on supported platforms but is a silent truncating cast pattern; consistent with codebase conventions elsewhere so not a blocker.
  • Test and doc coverage (docs/rpc.md) are good — the 3-validator non-vacuous test and the .with_state(store) isolation fix from the earlier review round are solid.

Nothing here blocks merging; Point 1 is the one I'd actually want addressed or explicitly acknowledged as out-of-scope before relying on this endpoint for network identity checks.


Automated review by Claude (Anthropic) · sonnet · custom prompt

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant