Skip to content

feat(rpc): add /genesisBlock and /identities GET endpoints#921

Merged
tcsenpai merged 3 commits into
stabilisationfrom
feat/genesis-block-and-identities-endpoints
Jun 5, 2026
Merged

feat(rpc): add /genesisBlock and /identities GET endpoints#921
tcsenpai merged 3 commits into
stabilisationfrom
feat/genesis-block-and-identities-endpoints

Conversation

@tcsenpai

@tcsenpai tcsenpai commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

What your code does

The Demos node runs an HTTP RPC server (default port 53550) that exposes a
set of read-only GET routes — things like /health, /peerlist,
/version, and /genesis. Each route reads some piece of node or chain
state and returns it as JSON. They all sit behind one shared middleware
stack: CORS, a per-IP rate limiter, and JSON formatting.

Two things were missing from that set:

  1. A way to fetch the entire genesis block over HTTP. There was already
    a /genesis route, but it only returns the embedded genesis data
    (chain params, balances, validators) parsed out of the block — not the
    block record itself (hash, number, signatures, the raw content envelope).
  2. A way to list the identities of every account. Accounts live in the
    GCR (gcr_main table); each row has a pubkey and an identities jsonb
    blob holding the user's linked cross-chain / web2 / pqc identities. There
    was no endpoint that enumerated them.

What this enables

  1. /genesisBlock — dump the whole genesis block (block 0) as stored, for
    tooling/debugging that needs block-level fields, not just the chain
    params.
  2. /identities — paginate through every account's linked identities
    (pubkey + identities only), so an operator or indexer can walk the
    full identity set without a bespoke DB query.

How this lands

  • /genesisBlock returns Chain.getGenesisBlock() (block 0) verbatim.
    503 if the block isn't available yet (node still booting). Blocks has no
    bigint columns, so it serializes safely.
  • /identities is backed by a new GCR.listIdentities(limit, cursor):
    • Keyset pagination on the pubkey primary key
      (WHERE pubkey > :cursor ORDER BY pubkey ASC LIMIT n) — O(log n) at any
      depth, unlike OFFSET.
    • Column projection to pubkey + identities only — keeps the
      payload focused and never touches the bigint balance column (which
      JSON.stringify cannot serialize).
    • limit clamped to [1, 1000]; response carries nextCursor (null at
      end of table).
  • DDoS hardening on /identities (it's the most expensive route — a
    paginated full-table read over jsonb-heavy rows). Three independent brakes:
    1. Per-IP rate limit — 30 req/60s (vs the 2000/60s default), via the
      rate limiter's pathMethodMap + methodLimits.
    2. Global concurrency gate — at most 3 /identities handlers run
      at once across all callers, catching distributed (many-IP) bursts the
      per-IP limit can't. Overflow queues up to 12 deep, waits ≤2s for a slot,
      then returns 503 + Retry-After. Implemented by a new
      ConcurrencyGate util: a pure in-process counting semaphore with a
      bounded FIFO wait queue and per-acquire timeout (15 unit tests).
    3. Bounded work per request — hard limit cap + keyset pagination +
      column projection.
  • Bun workers were considered and deliberately not used. Workers (as in
    txValidatorPool) offload CPU-bound crypto off the event loop.
    /identities is I/O-bound (a Postgres query) — the event loop is already
    free while awaiting the DB, so a worker would add IPC copy overhead, would
    not reduce DB load, and would give an attacker more threads/memory to
    exhaust. The gate caps the actual bottleneck (DB load) directly.

Why this matters

The dangerous combination would have been shipping /identities without
the gate: a single full-table read endpoint over a large jsonb table, behind
only the default 2000 req/min per-IP limit, is a trivial amplification
target — a handful of distributed clients could pin the DB. Layering a tight
per-IP limit, a global in-flight cap with fast-503 backpressure, and a hard
page-size bound means no single caller and no distributed burst can drive the
node's DB load past a fixed ceiling. The endpoints stay useful for legitimate
paginated reads while being safe to expose on the same public surface as the
existing GET routes.


Technical notes

Files (additive only — 921 insertions, 0 deletions):

  • src/libs/blockchain/gcr/gcr.tsGCR.listIdentities(limit, cursor)
  • src/libs/network/server_rpc.ts/genesisBlock + /identities routes, gate wiring
  • src/libs/network/middleware/rateLimiter.ts/identitiesidentities method in pathMethodMap
  • src/utilities/sharedState.tsidentities entry in methodLimits
  • src/utilities/constants.ts — rate-limit + gate tunables
  • src/libs/network/utils/concurrencyGate.ts (+ .test.ts) — new semaphore util
  • docs/runbooks/genesis-and-identities-endpoints.md — runbook

/identities response shape:

{
  "success": true,
  "identities": [ { "pubkey": "0x…", "identities": { "xm": {}, "web2": {}, "pqc": {}, "ud": [] } } ],
  "count": 100, "limit": 100, "nextCursor": "0x…" // null = end of table
}

Tunables (src/utilities/constants.ts): RATE_LIMIT_IDENTITIES_MAX_REQUESTS=30,
RATE_LIMIT_IDENTITIES_WINDOW_MS=60000, IDENTITIES_MAX_CONCURRENT=3,
IDENTITIES_MAX_QUEUE=12, IDENTITIES_ACQUIRE_TIMEOUT_MS=2000.

Verification:

  • bun test src/libs/network/utils/concurrencyGate.test.ts → 15 pass, 0 fail.
  • npm run type-check-ts → no new errors from this change (the two remaining
    errors near the touched files — /genesis's pre-existing .content.extra
    typing and a Server<WebSocketData> generic in rateLimiter.ts — are
    pre-existing, confirmed via git stash baseline).
  • Pre-existing rateLimiter.test.ts "trusted internal requests" failures are
    unrelated (fail identically on baseline).

Not done: no live end-to-end run against a booted node + Postgres. Query
mirrors the proven GCR.getTopAccountsByPoints; gate is unit-tested in
isolation.

Note: /identities and /genesisBlock are public/unauthenticated, like
the other GET routes (/genesis, /peerlist). Left that way intentionally.

Summary by CodeRabbit

  • New Features

    • Added /genesisBlock endpoint returning the full genesis block (503 when state not ready)
    • Added /identities endpoint offering paginated, keyset-based identity listings with limit/cursor paging and 503+Retry-After when capacity is exhausted
  • Documentation

    • Added runbook documenting endpoints, pagination semantics, error responses, rate limits, and concurrency protections

@qodo-code-review

Copy link
Copy Markdown
Contributor

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8c13dad3-9683-4b1d-8f9e-92ad2a9194ae

📥 Commits

Reviewing files that changed from the base of the PR and between d14a14b and 8fef326.

📒 Files selected for processing (4)
  • docs/runbooks/genesis-and-identities-endpoints.md
  • src/libs/blockchain/gcr/gcr.ts
  • src/libs/network/middleware/rateLimiter.ts
  • src/utilities/constants.ts
✅ Files skipped from review due to trivial changes (1)
  • docs/runbooks/genesis-and-identities-endpoints.md
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/libs/network/middleware/rateLimiter.ts
  • src/utilities/constants.ts
  • src/libs/blockchain/gcr/gcr.ts

Walkthrough

Adds GET /genesisBlock (full persisted block 0) and GET /identities (keyset-paginated pubkey + identities) endpoints, introduces an in-process ConcurrencyGate with tests, new identity-specific constants and rate-limit wiring, implements GCR.listIdentities, and adds a runbook documenting behavior and DDoS protections.

Changes

Genesis and Identities Endpoints

Layer / File(s) Summary
ConcurrencyGate implementation and errors
src/libs/network/utils/concurrencyGate.ts
Adds ConcurrencyGate class with FIFO queue, per-acquire timeout, idempotent release and handoff; exports GateTimeoutError and GateRejectedError.
ConcurrencyGate tests
src/libs/network/utils/concurrencyGate.test.ts
Bun test suite validating constructor validation, immediate acquires, queueing/FIFO behavior, queue-full rejection, acquire timeouts, release idempotency, run() semantics, and counters.
Constants and rate-limit wiring
src/utilities/constants.ts, src/utilities/sharedState.ts, src/libs/network/middleware/rateLimiter.ts
Adds /identities throttling and admission-control constants; wires identities method limits into SharedState; maps /identities and /genesisBlock paths to rate-limit method keys.
Identities keyset pagination query
src/libs/blockchain/gcr/gcr.ts
Implements GCR.listIdentities(limit, cursor?) with clamped limit, keyset pagination on pubkey, minimal projection (pubkey, identities), nextCursor derivation, and standardized RPCResponse/error handling.
RPC endpoints and concurrency wiring
src/libs/network/server_rpc.ts
Adds GET /genesisBlock returning block 0 (503 STATE_NOT_READY when unavailable) and GET /identities performing paginated listing via GCR.listIdentities() wrapped by a global ConcurrencyGate; gate timeouts/rejections return 503 with Retry-After, other errors return 500 and are logged.
Genesis and identities runbook
docs/runbooks/genesis-and-identities-endpoints.md
Documents routes, response shapes, keyset pagination semantics, limit clamping, curl examples, error modes (500, 503+Retry-After), layered DDoS protections, and rationale for not using Bun workers for I/O-bound Postgres work.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant RateLimiter
  participant ConcurrencyGate
  participant GCR
  participant Postgres
  Client->>RateLimiter: GET /identities?limit=100&cursor=abc
  alt rate limit exceeded
    RateLimiter-->>Client: 429 Too Many Requests
  else
    RateLimiter->>ConcurrencyGate: acquire()
    alt queue full or timeout
      ConcurrencyGate-->>Client: 503 Service busy + Retry-After
    else slot acquired
      ConcurrencyGate->>GCR: listIdentities(limit, cursor)
      GCR->>Postgres: SELECT pubkey, identities WHERE pubkey > :cursor ORDER BY pubkey ASC LIMIT N
      alt query error
        Postgres-->>GCR: error
        GCR-->>Client: 500 error
      else success
        Postgres-->>GCR: rows
        GCR-->>ConcurrencyGate: RPCResponse { identities, nextCursor }
        ConcurrencyGate-->>Client: 200 OK with payload
        ConcurrencyGate->>ConcurrencyGate: release()
      end
    end
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • kynesyslabs/node#661: Related changes to the identities structure and routines that extend stored identity data consumed by the new /identities endpoint.

Suggested labels

Review effort 3/5

"A rabbit hops through the code with glee,
Gates and queries dance in nice harmony,
Genesis and identities, paged with care,
Queues keep the DB calm, Retry-After to spare.
Tests and docs in tune — a soft little cheer from me!" 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(rpc): add /genesisBlock and /identities GET endpoints' clearly and concisely summarizes the main changes: adding two new RPC endpoints.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/genesis-block-and-identities-endpoints

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@tcsenpai

tcsenpai commented Jun 5, 2026

Copy link
Copy Markdown
Contributor Author

@greptile review

@tcsenpai tcsenpai changed the base branch from testnet to stabilisation June 5, 2026 08:53
Add two read-only GET routes to the node RPC HTTP server, plus DDoS
hardening for the expensive one.

- /genesisBlock: returns the full genesis Blocks record (block 0) via
  Chain.getGenesisBlock(). Distinct from the existing /genesis, which
  returns only the embedded genesisData. Blocks has no bigint columns so
  it serializes cleanly.
- /identities: paginated listing of every account's linked identities,
  returning only pubkey + the identities jsonb blob per row (never the
  bigint balance, which JSON.stringify cannot serialize). New
  GCR.listIdentities(limit, cursor) uses keyset pagination on the pubkey
  primary key and column projection so a single request cannot pull the
  whole gcr_main table into memory. limit is clamped to [1, 1000].

DDoS hardening on /identities (three independent brakes):
- Per-IP rate limit: 30 req/60s (vs 2000 default GET allowance), via the
  rate limiter pathMethodMap + methodLimits config.
- Global concurrency gate: at most 3 /identities handlers run at once
  across ALL callers (catches distributed many-IP bursts the per-IP limit
  cannot). Overflow queues up to 12 deep and waits at most 2s for a slot,
  then returns 503 + Retry-After. New ConcurrencyGate util: a pure
  in-process counting semaphore with a bounded FIFO wait queue + per-acquire
  timeout, covered by 15 unit tests.
- Bounded work per request: hard limit cap + keyset pagination + column
  projection.

Bun workers were considered and deliberately not used: /identities is
I/O-bound (a Postgres query), not CPU-bound, so workers would add IPC
overhead without reducing DB load. The gate caps the actual bottleneck.

Additive only. New rate-limit/gate tunables live in
src/utilities/constants.ts. Runbook added at
docs/runbooks/genesis-and-identities-endpoints.md.
@tcsenpai tcsenpai force-pushed the feat/genesis-block-and-identities-endpoints branch from 0f4c306 to d14a14b Compare June 5, 2026 08:54
@tcsenpai

tcsenpai commented Jun 5, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

@greptile-apps

greptile-apps Bot commented Jun 5, 2026

Copy link
Copy Markdown

Greptile Summary

Adds two new GET endpoints to the RPC HTTP server: /genesisBlock (returns the full genesis Blocks record) and /identities (paginated keyset listing of every account's linked identities). The /identities route ships with three independent DDoS brakes: a tight per-IP rate limit (30 req/60s), a new ConcurrencyGate semaphore capping global in-flight requests at 3 (queue depth 12, 2s timeout → fast 503), and hard page-size bounding with keyset pagination.

Confidence Score: 5/5

Safe to merge — purely additive, all DDoS brakes correctly wired, previous rate-limit bypass for /genesisBlock addressed.

The ConcurrencyGate logic is sound and well-tested. Rate-limiter mapping, concurrency gate wiring, error handling, and keyset pagination are all implemented correctly. The single finding is a maintenance nit in the parameter default.

No files require special attention.

Important Files Changed

Filename Overview
src/libs/blockchain/gcr/gcr.ts Adds static listIdentities(limit, cursor) — keyset-paginated, column-projected query over gcr_main. Parameter default is hardcoded 100 rather than IDENTITIES_DEFAULT_LIMIT; constants are imported but then re-aliased.
src/libs/network/server_rpc.ts Adds /genesisBlock (single-block fetch, 503 on null) and /identities (gate-wrapped, errors properly distinguished, Retry-After header present).
src/libs/network/utils/concurrencyGate.ts Clean counting-semaphore: fast path, stable activeCount on handoff, idempotent release, timeout self-removes from queue. 15 unit tests.
src/libs/network/middleware/rateLimiter.ts Adds /genesisBlockgenesisblock and /identitiesidentities to pathMethodMap; fallback to defaultLimit for unmapped methods is correct.
src/utilities/constants.ts All /identities tunables added with clear names and comments.
src/utilities/sharedState.ts Wires identities into methodLimits. No issues.
src/libs/network/utils/concurrencyGate.test.ts 15 unit tests covering all paths including FIFO ordering, timeout, queue-full rejection, and double-release idempotency.

Reviews (4): Last reviewed commit: "fix(rpc): rate-limit /genesisBlock + hoi..." | Re-trigger Greptile

Comment thread src/libs/blockchain/gcr/gcr.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/libs/blockchain/gcr/gcr.ts`:
- Around line 1146-1148: The computed pageSize can become 0 for fractional
positive parsedLimit (e.g., 0.5) causing an empty-page dereference later; clamp
the computed value to a minimum of 1 when setting pageSize (use Math.max(1,
Math.min(Math.floor(parsedLimit), MAX_LIMIT)) or fall back to DEFAULT_LIMIT) and
also add a defensive check before dereferencing page[0] (in the code paths
around the existing page and cursor handling at lines handling page length) to
avoid accessing undefined when page is empty; update references to parsedLimit,
pageSize, MAX_LIMIT, DEFAULT_LIMIT and the page/cursor handling accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 21dd19ee-b852-4a75-a9e5-eee70280dd2d

📥 Commits

Reviewing files that changed from the base of the PR and between c3067fa and d14a14b.

📒 Files selected for processing (8)
  • docs/runbooks/genesis-and-identities-endpoints.md
  • src/libs/blockchain/gcr/gcr.ts
  • src/libs/network/middleware/rateLimiter.ts
  • src/libs/network/server_rpc.ts
  • src/libs/network/utils/concurrencyGate.test.ts
  • src/libs/network/utils/concurrencyGate.ts
  • src/utilities/constants.ts
  • src/utilities/sharedState.ts

Comment thread src/libs/blockchain/gcr/gcr.ts
tcsenpai added 2 commits June 5, 2026 11:03
A fractional positive limit (e.g. ?limit=0.5) floored to 0, making
pageSize 0; the full-page check (identities.length === pageSize) then
matched an empty result and dereferenced identities[-1], returning 500.
Clamp pageSize to a minimum of 1 and guard nextCursor on length > 0.

Addresses CodeRabbit review feedback on PR #921.
…onstants

Address Greptile review on PR #921:
- /genesisBlock had no pathMethodMap entry, so GET requests fell through to
  the lax POST bucket (200k/24h) instead of the default GET limit. Map it to
  the 'genesisblock' method -> default per-IP GET allowance (2000/60s).
- Move /identities DEFAULT_LIMIT (100) and MAX_LIMIT (1000) out of
  listIdentities() into constants.ts (IDENTITIES_DEFAULT_LIMIT /
  IDENTITIES_MAX_LIMIT), alongside the other /identities tunables, so the
  page-size bounds are configurable without touching GCR code.
@tcsenpai

tcsenpai commented Jun 5, 2026

Copy link
Copy Markdown
Contributor Author

@greptile review

@tcsenpai tcsenpai merged commit 621871a into stabilisation Jun 5, 2026
6 checks passed
tcsenpai added a commit that referenced this pull request Jun 5, 2026
A fractional positive limit (e.g. ?limit=0.5) floored to 0, making
pageSize 0; the full-page check (identities.length === pageSize) then
matched an empty result and dereferenced identities[-1], returning 500.
Clamp pageSize to a minimum of 1 and guard nextCursor on length > 0.

Addresses CodeRabbit review feedback on PR #921.
@tcsenpai tcsenpai deleted the feat/genesis-block-and-identities-endpoints branch June 5, 2026 11:37
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