Skip to content

Commit f55fe5c

Browse files
authored
feat(rpc): add GET /lean/v0/config/spec (#456)
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.
1 parent 1285391 commit f55fe5c

6 files changed

Lines changed: 110 additions & 8 deletions

File tree

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
//! Protocol constants shared across crates.
2+
3+
/// Fork digest embedded in every gossipsub topic string, as lowercase hex
4+
/// without a `0x` prefix.
5+
///
6+
/// The [leanSpec](https://github.com/leanEthereum/leanSpec/pull/622)
7+
/// currently mandates a dummy value shared across all clients; this will
8+
/// eventually be derived from the fork version and genesis validators root.
9+
// TODO: derive dynamically once the spec defines fork identification.
10+
pub const FORK_DIGEST: &str = "12345678";

crates/common/types/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ pub mod aggregator;
22
pub mod attestation;
33
pub mod block;
44
pub mod checkpoint;
5+
pub mod constants;
56
pub mod genesis;
67
pub mod primitives;
78
pub mod signature;

crates/net/p2p/src/gossipsub/messages.rs

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,4 @@
1-
/// Fork digest embedded in every gossipsub topic string, as lowercase hex
2-
/// without a `0x` prefix.
3-
///
4-
/// The [leanSpec](https://github.com/leanEthereum/leanSpec/pull/622)
5-
/// currently mandates a dummy value shared across all clients; this will
6-
/// eventually be derived from the fork version and genesis validators root.
7-
// TODO: derive dynamically once the spec defines fork identification.
8-
pub const FORK_DIGEST: &str = "12345678";
1+
pub use ethlambda_types::constants::FORK_DIGEST;
92

103
/// Topic kind for block gossip
114
pub const BLOCK_TOPIC_KIND: &str = "block";

crates/net/rpc/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ mod fork_choice;
1515
mod genesis;
1616
mod heap_profiling;
1717
pub mod metrics;
18+
mod spec;
1819
pub mod test_driver;
1920

2021
pub(crate) use base::json_response;
@@ -102,6 +103,7 @@ fn build_api_router(store: Store) -> Router {
102103
.merge(fork_choice::routes())
103104
.merge(admin::routes())
104105
.merge(genesis::routes())
106+
.merge(spec::routes())
105107
.with_state(store)
106108
}
107109

crates/net/rpc/src/spec.rs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
use axum::{Router, response::IntoResponse, routing::get};
2+
use ethlambda_blockchain::{INTERVALS_PER_SLOT, MILLISECONDS_PER_INTERVAL, MILLISECONDS_PER_SLOT};
3+
use ethlambda_storage::Store;
4+
use ethlambda_types::{constants::FORK_DIGEST, state::HISTORICAL_ROOTS_LIMIT};
5+
use serde::Serialize;
6+
7+
use crate::json_response;
8+
9+
#[derive(Serialize)]
10+
struct SpecResponse {
11+
#[serde(rename = "MILLISECONDS_PER_SLOT")]
12+
ms_per_slot: u64,
13+
#[serde(rename = "INTERVALS_PER_SLOT")]
14+
intervals_per_slot: u64,
15+
#[serde(rename = "MILLISECONDS_PER_INTERVAL")]
16+
ms_per_interval: u64,
17+
#[serde(rename = "HISTORICAL_ROOTS_LIMIT")]
18+
historical_roots_limit: u64,
19+
#[serde(rename = "FORK_DIGEST")]
20+
fork_digest: &'static str,
21+
}
22+
23+
async fn get_spec() -> impl IntoResponse {
24+
json_response(SpecResponse {
25+
ms_per_slot: MILLISECONDS_PER_SLOT,
26+
intervals_per_slot: INTERVALS_PER_SLOT,
27+
ms_per_interval: MILLISECONDS_PER_INTERVAL,
28+
historical_roots_limit: HISTORICAL_ROOTS_LIMIT as u64,
29+
fork_digest: FORK_DIGEST,
30+
})
31+
}
32+
33+
pub(crate) fn routes() -> Router<Store> {
34+
Router::new().route("/lean/v0/config/spec", get(get_spec))
35+
}
36+
37+
#[cfg(test)]
38+
mod tests {
39+
use super::FORK_DIGEST;
40+
use crate::test_utils::create_test_state;
41+
use axum::{
42+
body::Body,
43+
http::{Request, StatusCode},
44+
};
45+
use ethlambda_blockchain::{
46+
INTERVALS_PER_SLOT, MILLISECONDS_PER_INTERVAL, MILLISECONDS_PER_SLOT,
47+
};
48+
use ethlambda_storage::{Store, backend::InMemoryBackend};
49+
use ethlambda_types::state::HISTORICAL_ROOTS_LIMIT;
50+
use http_body_util::BodyExt;
51+
use std::sync::Arc;
52+
use tower::ServiceExt;
53+
54+
#[tokio::test]
55+
async fn spec_returns_lean_constants() {
56+
let store = Store::from_anchor_state(Arc::new(InMemoryBackend::new()), create_test_state());
57+
let app = crate::build_api_router(store);
58+
let resp = app
59+
.oneshot(
60+
Request::builder()
61+
.uri("/lean/v0/config/spec")
62+
.body(Body::empty())
63+
.unwrap(),
64+
)
65+
.await
66+
.unwrap();
67+
assert_eq!(resp.status(), StatusCode::OK);
68+
let body = resp.into_body().collect().await.unwrap().to_bytes();
69+
let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
70+
assert_eq!(json["MILLISECONDS_PER_SLOT"], MILLISECONDS_PER_SLOT);
71+
assert_eq!(json["INTERVALS_PER_SLOT"], INTERVALS_PER_SLOT);
72+
assert_eq!(json["MILLISECONDS_PER_INTERVAL"], MILLISECONDS_PER_INTERVAL);
73+
assert_eq!(
74+
json["HISTORICAL_ROOTS_LIMIT"],
75+
HISTORICAL_ROOTS_LIMIT as u64
76+
);
77+
assert_eq!(json["FORK_DIGEST"], FORK_DIGEST);
78+
}
79+
}

docs/rpc.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ If `--api-port` and `--metrics-port` are equal, all routers are merged onto a si
2323
| Method | Path | Response | Description |
2424
|--------|------|----------|-------------|
2525
| `GET` | `/lean/v0/health` | JSON | Liveness check |
26+
| `GET` | `/lean/v0/config/spec` | JSON | Protocol constants the node runs with |
2627
| `GET` | `/lean/v0/genesis` | JSON | Genesis time and validator count |
2728
| `GET` | `/lean/v0/states/finalized` | SSZ | Latest finalized `State` |
2829
| `GET` | `/lean/v0/blocks/finalized` | SSZ | Latest finalized `SignedBlock` |
@@ -42,6 +43,22 @@ The handler emits a fixed, compact body (no whitespace):
4243
{"status":"healthy","service":"lean-rpc-api"}
4344
```
4445

46+
### `GET /lean/v0/config/spec`
47+
48+
Protocol constants the node was built with. Keys mirror the leanSpec constant names:
49+
50+
```json
51+
{
52+
"MILLISECONDS_PER_SLOT": 4000,
53+
"INTERVALS_PER_SLOT": 5,
54+
"MILLISECONDS_PER_INTERVAL": 800,
55+
"HISTORICAL_ROOTS_LIMIT": 262144,
56+
"FORK_DIGEST": "12345678"
57+
}
58+
```
59+
60+
`FORK_DIGEST` is the 4-byte hex string (no `0x` prefix) embedded in gossipsub topic names.
61+
4562
### `GET /lean/v0/genesis`
4663

4764
```json

0 commit comments

Comments
 (0)