Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -1339,12 +1339,16 @@ Submit a blob directly to the batcher queue. Structure and cryptographic proofs

**Rate-limited `429`**

Per client IP, `API_RATE_LIMIT_MAX` requests per minute across every `/v1/*`
route (default 600; `/v1/health` and `/v1/health/sync` are exempt). The
`x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset` headers
are set on every counted response; back off on `429` instead of retry-looping.

```json
{
"success": false,
"error": "Rate limit exceeded",
"message": "Too many requests. Please retry after 60 seconds.",
"retryAfter": 60
"statusCode": 429,
"error": "RATE_LIMITED",
"reason": "Too many requests — please wait before retrying."
}
```

Expand Down
2 changes: 1 addition & 1 deletion FRONTEND-API-HANDOFF.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ from memory. The backend will be **redeployed from zero** with these changes —
is no compatibility window and no legacy fallback: the old API simply stops existing.

Base URL: `http://<host>:9999`. All offer-related endpoints moved from `/api/*` to
`/v1/*`. Rate limit: **60 requests/min per IP** across all routes → HTTP 429
`/v1/*`. Rate limit: **600 requests/min per IP** (`API_RATE_LIMIT_MAX`) across all routes → HTTP 429
`{ "error": "RATE_LIMITED", "reason": "..." }`. On 429, back off; do not retry-loop.

---
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,7 @@ Fund the `celestia1...` address shown by `celestia state account-address` with T
| `CELESTIA_POLLING_INTERVAL_MS` | optional | Sync cadence. Defaults: devnet 6 000 ms, mainnet 30 000 ms. |
| `MIDNIGHT_START_BLOCK` | yes | Numeric block height to start Midnight sync from. |
| `NTP_START_TIME` | optional | NTP reference timestamp; resumed from DB when unset. |
| `API_RATE_LIMIT_MAX`, `API_RATE_LIMIT_ALLOWLIST` | optional | Per-client-IP budget for `/v1/*` per minute (default 600; `/v1/health*` exempt) and comma-separated IPs exempt from it. Size it for a solver plus its console on one address (~450/min worst case); `deploy/` sets 6000. |
| `BATCHER_SUBMIT_TIMEOUT_MS` | optional | Absolute batcher fetch + receipt-body deadline; default 310 000 ms, bounded to 1 000–600 000 ms. |
| `API_SSE_MAX_CONNECTIONS` | optional | Per-node concurrent `/v1/offers/stream` cap; default 100. Excess clients receive `503 SSE_CAPACITY`. |
| `API_UPDATES_MAX_CONNECTIONS` | optional | Per-node concurrent `/v1/offers/updates` websocket cap; default 100. Excess clients are refused the connection (this endpoint's refusals are disconnects, not HTTP statuses — see API.md). |
Expand Down
2 changes: 1 addition & 1 deletion deploy/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ SOLVER_FRONTEND_POLL_MS=
SOLVER_FRONTEND_HISTORY_LIMIT=

# ── kernel ───────────────────────────────────────────────────────────────────
# The dev per-IP budget (60/min) throttles a co-located solver during a
# The code default per-IP budget (600/min) still throttles a co-located solver during a
# page-through plus settlement polls, and would throttle the E2E driver itself.
API_RATE_LIMIT_MAX=6000
API_RATE_LIMIT_ALLOWLIST=
Expand Down
4 changes: 2 additions & 2 deletions packages/node/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ export const apiRouter: StartConfigApiRouter = async function (
server: any,
dbConn: any,
): Promise<void> {
// Per-IP request budget (default 60/min) — applied to every route in this
// Per-IP request budget (default 600/min) — applied to every route in this
// router.
//
// `statusCode` is load-bearing, not decoration: @fastify/rate-limit THROWS
Expand Down Expand Up @@ -745,7 +745,7 @@ export const apiRouter: StartConfigApiRouter = async function (
// Uses effectstream.effectstream_blocks for NTP and
// effectstream.sync_protocol_pagination for parallel chains.
// Chain tips are fetched from the Midnight indexer / Celestia RPC and cached 60 s.
// Exempt from the 60/min API budget — UIs poll this as a liveness probe.
// Exempt from the per-IP API budget — UIs poll this as a liveness probe.
server.get("/v1/health/sync", { config: { rateLimit: false } }, async () => {
return getSyncStatus(dbConn);
});
Expand Down
2 changes: 1 addition & 1 deletion packages/node/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ export const OFFER_MAX_BYTES = parseInt(
// building a router gets them — the same reason isTokenRegistryEnabled below
// is a function.
export const apiRateLimitMax = (): number =>
parseInt(getEnv("API_RATE_LIMIT_MAX") ?? "60");
parseInt(getEnv("API_RATE_LIMIT_MAX") ?? "600");

export const apiRateLimitAllowList = (): string[] =>
(getEnv("API_RATE_LIMIT_ALLOWLIST") ?? "")
Expand Down
Loading