refactor(rpc): compose api router from per-feature route modules#454
Conversation
Split the monolithic lib.rs API router into focused modules: core.rs holds the finalized-state/finalized-block/justified-checkpoint handlers and shared response helpers; blocks.rs, fork_choice.rs, and admin.rs each expose pub(crate) routes() -> Router<Store>. build_api_router merges them with .with_state(store). No behavior change.
🤖 Kimi Code ReviewOverall Assessment: Good refactoring that improves code organization by decentralizing route definitions into submodule Critical Issues
Moderate Issues
Minor Observations
Security Note Consensus Correctness Automated review by Kimi (Moonshot AI) · kimi-k2.5 · custom prompt |
🤖 Codex Code ReviewFindings
I didn’t find other correctness, security, performance, or consensus-path regressions in this PR. The change is effectively a route/module refactor; fork choice, attestation validation, justification/finalization, STF, XMSS, and SSZ logic are untouched. I couldn’t run Automated review by OpenAI Codex · gpt-5.4 · custom prompt |
Greptile SummaryThis PR splits the RPC crate's monolithic router in
Confidence Score: 4/5Safe to merge — all endpoints, paths, and response shapes are unchanged; the refactor is purely structural. The change is a clean mechanical extraction: routes move into dedicated modules, handlers become crate-private, and the merge composition preserves axum's static-before-dynamic matching priority. Two minor rough edges remain — the new module is named No files require special attention beyond the two style notes in
|
| Filename | Overview |
|---|---|
| crates/net/rpc/src/lib.rs | Monolithic router replaced with .merge() of per-feature route modules; naming a local module core shadows Rust's built-in core crate. |
| crates/net/rpc/src/core.rs | New module housing finalized-state, finalized-block, and justified-checkpoint endpoints plus shared helpers; stale comment claims genesis returns 404 but tests assert 200. |
| crates/net/rpc/src/admin.rs | Added routes() factory function; handlers tightened to pub(crate); no behavioural change. |
| crates/net/rpc/src/blocks.rs | Added routes() factory function; handlers tightened to pub(crate); no behavioural change. |
| crates/net/rpc/src/fork_choice.rs | Added routes() factory function; test helper updated to reuse it; handlers tightened to pub(crate); no behavioural change. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["start_rpc_server()"] --> B["build_api_router(store)"]
B --> C["core::routes()"]
B --> D["blocks::routes()"]
B --> E["fork_choice::routes()"]
B --> F["admin::routes()"]
C --> C1["/lean/v0/health"]
C --> C2["/lean/v0/states/finalized"]
C --> C3["/lean/v0/blocks/finalized"]
C --> C4["/lean/v0/checkpoints/justified"]
D --> D1["/lean/v0/blocks/{block_id}"]
D --> D2["/lean/v0/blocks/{block_id}/header"]
E --> E1["/lean/v0/fork_choice"]
E --> E2["/lean/v0/fork_choice/ui"]
F --> F1["/lean/v0/admin/aggregator (GET+POST)"]
A --> G["build_debug_router()"]
G --> G1["/debug/pprof/allocs"]
G --> G2["/debug/pprof/allocs/flamegraph"]
%%{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"}}}%%
flowchart TD
A["start_rpc_server()"] --> B["build_api_router(store)"]
B --> C["core::routes()"]
B --> D["blocks::routes()"]
B --> E["fork_choice::routes()"]
B --> F["admin::routes()"]
C --> C1["/lean/v0/health"]
C --> C2["/lean/v0/states/finalized"]
C --> C3["/lean/v0/blocks/finalized"]
C --> C4["/lean/v0/checkpoints/justified"]
D --> D1["/lean/v0/blocks/{block_id}"]
D --> D2["/lean/v0/blocks/{block_id}/header"]
E --> E1["/lean/v0/fork_choice"]
E --> E2["/lean/v0/fork_choice/ui"]
F --> F1["/lean/v0/admin/aggregator (GET+POST)"]
A --> G["build_debug_router()"]
G --> G1["/debug/pprof/allocs"]
G --> G2["/debug/pprof/allocs/flamegraph"]
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 2
crates/net/rpc/src/core.rs:43
**Stale inline comment contradicts test behaviour**
The comment says "Returns 404 for genesis since it doesn't have a valid signature", but the integration test `test_get_latest_finalized_block_serves_genesis_with_placeholder_proof` (in `lib.rs`) explicitly documents and asserts that genesis returns 200 with a synthesised placeholder proof. The comment was copied from the original `lib.rs` without being updated, so it now misleads anyone reading this handler.
### Issue 2 of 2
crates/net/rpc/src/lib.rs:13
**Module name `core` shadows the standard library crate**
Declaring `mod core;` in a Rust crate root shadows the built-in `core` crate for all code in `lib.rs`. Any future use of `core::` within this file will resolve to the local module rather than the standard library, which can cause confusing compilation errors or subtle misbehaviour. A name like `endpoints_core`, `base`, or `common` would sidestep this shadowing entirely.
Reviews (1): Last reviewed commit: "refactor(rpc): compose api router from p..." | Re-trigger Greptile
| axum::extract::State(store): axum::extract::State<Store>, | ||
| ) -> impl IntoResponse { | ||
| let finalized = store.latest_finalized(); | ||
| // Returns 404 for genesis since it doesn't have a valid signature |
There was a problem hiding this comment.
Stale inline comment contradicts test behaviour
The comment says "Returns 404 for genesis since it doesn't have a valid signature", but the integration test test_get_latest_finalized_block_serves_genesis_with_placeholder_proof (in lib.rs) explicitly documents and asserts that genesis returns 200 with a synthesised placeholder proof. The comment was copied from the original lib.rs without being updated, so it now misleads anyone reading this handler.
Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/net/rpc/src/core.rs
Line: 43
Comment:
**Stale inline comment contradicts test behaviour**
The comment says "Returns 404 for genesis since it doesn't have a valid signature", but the integration test `test_get_latest_finalized_block_serves_genesis_with_placeholder_proof` (in `lib.rs`) explicitly documents and asserts that genesis returns 200 with a synthesised placeholder proof. The comment was copied from the original `lib.rs` without being updated, so it now misleads anyone reading this handler.
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!
|
|
||
| mod admin; | ||
| mod blocks; | ||
| mod core; |
There was a problem hiding this comment.
Module name
core shadows the standard library crate
Declaring mod core; in a Rust crate root shadows the built-in core crate for all code in lib.rs. Any future use of core:: within this file will resolve to the local module rather than the standard library, which can cause confusing compilation errors or subtle misbehaviour. A name like endpoints_core, base, or common would sidestep this shadowing entirely.
Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/net/rpc/src/lib.rs
Line: 13
Comment:
**Module name `core` shadows the standard library crate**
Declaring `mod core;` in a Rust crate root shadows the built-in `core` crate for all code in `lib.rs`. Any future use of `core::` within this file will resolve to the local module rather than the standard library, which can cause confusing compilation errors or subtle misbehaviour. A name like `endpoints_core`, `base`, or `common` would sidestep this shadowing entirely.
How can I resolve this? If you propose a fix, please make it concise.
🤖 Claude Code ReviewI have enough to write the review. Here are my findings: PR Review:
|
…eckpoint handler name - Rename `mod core` → `mod base` to avoid shadowing Rust's built-in `core` crate - Fix stale comment on `get_latest_finalized_block`: genesis returns 200 with a synthesized blank proof, not 404 - Rename handler `get_latest_justified_state` → `get_latest_justified_checkpoint` to reflect that it returns a Checkpoint, not a State
Adds a plain `GET /health` liveness probe on the metrics port (5054),
alongside `/metrics`.
The handler reuses the existing `get_health` function, so the response
body and content-type are identical to the API server's
`/lean/v0/health`. This lets k8s/ops probe the metrics port
independently of the API server.
- `GET /health` → 200 `{"status":"healthy","service":"lean-rpc-api"}`
with `application/json`
- `GET /metrics` unchanged
Two unit tests cover the new route and regression-check `/metrics`.
Stacked on #454.
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.
Adds the `GET /lean/v0/config/spec` endpoint, which exposes chain configuration parameters (slot duration, committee sizes, finality constants, etc.) as JSON. Allows external tools and cross-client integrations to discover runtime config without out-of-band coordination. Has unit tests and passed clippy. Stacked on #454.
Adds two node-info endpoints: - `GET /lean/v0/node/identity` — returns peer ID and ENR. - `GET /lean/v0/node/syncing` — returns current sync status (head slot, finalized slot, sync lag). Both are useful for health dashboards and orchestration tooling. Has unit tests and passed clippy. Stacked on #454.
Behavior-preserving refactor of the RPC crate's router setup. Instead of a single monolithic router, routes are now composed from per-feature modules, each responsible for registering its own handlers.
This scaffolding is a prerequisite for the stacked endpoint PRs that follow: each new endpoint is added as its own module without touching unrelated code.
Passes clippy with
-D warningsand all existing tests.