Skip to content

Commit b4e7107

Browse files
authored
api: small touchups (leanEthereum#280)
1 parent 937974c commit b4e7107

File tree

4 files changed

+34
-48
lines changed

4 files changed

+34
-48
lines changed

src/lean_spec/subspecs/api/client.py

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import httpx
1414

1515
from lean_spec.subspecs.chain.config import DEVNET_CONFIG
16+
from lean_spec.subspecs.containers import Slot
1617
from lean_spec.subspecs.ssz.hash import hash_tree_root
1718

1819
if TYPE_CHECKING:
@@ -28,8 +29,6 @@
2829
class CheckpointSyncError(Exception):
2930
"""Error during checkpoint sync."""
3031

31-
pass
32-
3332

3433
async def fetch_finalized_state(url: str, state_class: type[Any]) -> "State":
3534
"""
@@ -52,9 +51,7 @@ async def fetch_finalized_state(url: str, state_class: type[Any]) -> "State":
5251

5352
logger.info(f"Fetching finalized state from {full_url}")
5453

55-
headers = {
56-
"Accept": "application/octet-stream",
57-
}
54+
headers = {"Accept": "application/octet-stream"}
5855

5956
try:
6057
async with httpx.AsyncClient(timeout=DEFAULT_TIMEOUT) as client:
@@ -77,8 +74,6 @@ async def fetch_finalized_state(url: str, state_class: type[Any]) -> "State":
7774
raise CheckpointSyncError(
7875
f"HTTP error {exc.response.status_code}: {exc.response.text[:200]}"
7976
) from exc
80-
except CheckpointSyncError:
81-
raise
8277
except Exception as e:
8378
raise CheckpointSyncError(f"Failed to fetch state: {e}") from e
8479

@@ -96,9 +91,7 @@ async def verify_checkpoint_state(state: "State") -> bool:
9691
True if valid, False otherwise
9792
"""
9893
try:
99-
computed_root = hash_tree_root(state)
100-
101-
if int(state.slot) < 0:
94+
if state.slot < Slot(0):
10295
logger.error("Invalid state: negative slot")
10396
return False
10497

@@ -114,7 +107,8 @@ async def verify_checkpoint_state(state: "State") -> bool:
114107
)
115108
return False
116109

117-
root_preview = computed_root.hex()[:16]
110+
state_root = hash_tree_root(state)
111+
root_preview = state_root.hex()[:16]
118112
logger.info(f"Checkpoint state verified: slot={state.slot}, root={root_preview}...")
119113
return True
120114

src/lean_spec/subspecs/api/server.py

Lines changed: 23 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import asyncio
1414
import logging
1515
from collections.abc import Callable
16-
from dataclasses import dataclass
16+
from dataclasses import dataclass, field
1717
from typing import TYPE_CHECKING
1818

1919
from aiohttp import web
@@ -24,6 +24,16 @@
2424
logger = logging.getLogger(__name__)
2525

2626

27+
def _no_store() -> Store | None:
28+
"""Default store getter that returns None."""
29+
return None
30+
31+
32+
async def _handle_health(_request: web.Request) -> web.Response:
33+
"""Handle health check endpoint."""
34+
return web.json_response({"status": "healthy", "service": "lean-spec-api"})
35+
36+
2737
@dataclass(frozen=True, slots=True)
2838
class ApiServerConfig:
2939
"""Configuration for the API server."""
@@ -38,6 +48,7 @@ class ApiServerConfig:
3848
"""Whether the API server is enabled."""
3949

4050

51+
@dataclass(slots=True)
4152
class ApiServer:
4253
"""
4354
HTTP API server for checkpoint sync and node status.
@@ -49,36 +60,22 @@ class ApiServer:
4960
Uses aiohttp to handle HTTP protocol details efficiently.
5061
"""
5162

52-
def __init__(
53-
self,
54-
config: ApiServerConfig,
55-
store_getter: Callable[[], Store | None] = lambda: None,
56-
):
57-
"""
58-
Initialize the API server.
63+
config: ApiServerConfig
64+
"""Server configuration."""
5965

60-
Args:
61-
config: Server configuration.
62-
store_getter: Callable that returns the current Store instance.
63-
"""
64-
self.config = config
65-
self._store_getter = store_getter
66-
self._runner: web.AppRunner | None = None
67-
self._site: web.TCPSite | None = None
66+
store_getter: Callable[[], Store | None] = _no_store
67+
"""Callable that returns the current Store instance."""
6868

69-
def set_store_getter(self, getter: Callable[[], Store | None]) -> None:
70-
"""
71-
Set the store getter function.
69+
_runner: web.AppRunner | None = field(default=None, init=False)
70+
"""The aiohttp application runner."""
7271

73-
Args:
74-
getter: Callable that returns the current Store instance.
75-
"""
76-
self._store_getter = getter
72+
_site: web.TCPSite | None = field(default=None, init=False)
73+
"""The TCP site for the server."""
7774

7875
@property
7976
def store(self) -> Store | None:
8077
"""Get the current Store instance."""
81-
return self._store_getter()
78+
return self.store_getter()
8279

8380
async def start(self) -> None:
8481
"""Start the API server in the background."""
@@ -89,7 +86,7 @@ async def start(self) -> None:
8986
app = web.Application()
9087
app.add_routes(
9188
[
92-
web.get("/health", self._handle_health),
89+
web.get("/health", _handle_health),
9390
web.get("/lean/states/finalized", self._handle_finalized_state),
9491
]
9592
)
@@ -127,11 +124,7 @@ async def _async_stop(self) -> None:
127124
self._site = None
128125
logger.info("API server stopped")
129126

130-
async def _handle_health(self, request: web.Request) -> web.Response:
131-
"""Handle health check endpoint."""
132-
return web.json_response({"status": "healthy", "service": "lean-spec-api"})
133-
134-
async def _handle_finalized_state(self, request: web.Request) -> web.Response:
127+
async def _handle_finalized_state(self, _request: web.Request) -> web.Response:
135128
"""
136129
Handle finalized checkpoint state endpoint.
137130

src/lean_spec/subspecs/node/node.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -145,10 +145,11 @@ def from_genesis(cls, config: NodeConfig) -> Node:
145145
# Create API server if configured
146146
api_server: ApiServer | None = None
147147
if config.api_config is not None:
148-
api_server = ApiServer(config=config.api_config)
149-
# Set up store getter so API server can access current state
150-
# We use a lambda that captures sync_service to get the live store
151-
api_server.set_store_getter(lambda: sync_service.store)
148+
# Store getter captures sync_service to get the live store
149+
api_server = ApiServer(
150+
config=config.api_config,
151+
store_getter=lambda: sync_service.store,
152+
)
152153

153154
return cls(
154155
store=store,

tests/lean_spec/subspecs/api/test_server.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,7 @@ def test_server_created_without_store(self) -> None:
5656
def test_store_getter_provides_access_to_store(self, store: Store) -> None:
5757
"""Store getter callable provides access to the forkchoice store."""
5858
config = ApiServerConfig()
59-
server = ApiServer(config=config)
60-
61-
server.set_store_getter(lambda: store)
59+
server = ApiServer(config=config, store_getter=lambda: store)
6260

6361
assert server.store is store
6462

0 commit comments

Comments
 (0)