Skip to content

test(beacon-node): cover REST query string array parsing at boundary sizes - #9712

Open
lodekeeper wants to merge 2 commits into
ChainSafe:unstablefrom
lodekeeper:test/rest-server-qs-arraylimit
Open

test(beacon-node): cover REST query string array parsing at boundary sizes#9712
lodekeeper wants to merge 2 commits into
ChainSafe:unstablefrom
lodekeeper:test/rest-server-qs-arraylimit

Conversation

@lodekeeper

Copy link
Copy Markdown
Contributor

Motivation

Regression coverage for #9672. After the v1.44.0 qs bump, GET /eth/v1/beacon/states/{state_id}/validators?id=a,b,c,... with more than 20 comma-separated ids started failing with id must be array (400): qs's default arrayLimit (20) is below the beacon-API maxItems (64) for the validator id query, so anything over 20 items was parsed as an object instead of an array and rejected by the type: "array" schema. #9673 fixed it by raising the querystring arrayLimit to NUMBER_OF_COLUMNS, but no test guards the behavior.

This is PR 1 of 2 for the ask in #9672 (unit coverage that would have caught the regression); a separate PR adds e2e coverage against a real running node.

Description

Adds a RestApiServer-level unit test that exercises query-string array parsing at representative boundary sizes.

Key point: the test drives the real RestApiServer, so it is bound to the production querystring parser configured in base.ts (rather than a stand-alone Fastify instance with a copied parser config). If the arrayLimit override regresses, the 21/64/65-item cases fail exactly as #9672 did.

  • Covers the validator id query (Schema.UintOrStringArray, maxItems=64) and the data-column indices query (Schema.UintArray, maxItems=NUMBER_OF_COLUMNS).
  • Representative boundary sizes — 1, 20, 21, 64, 65, NUMBER_OF_COLUMNS — for both comma-separated (?id=a,b,c) and repeated (?id=a&id=b) wire forms, rather than exhaustively enumerating 1..64.
  • Asserts that more than 64 items are still accepted (65 passes): Lodestar intentionally does not hard-cap validator id arrays server-side at the spec maxItems. The only cap is the qs arrayLimit (NUMBER_OF_COLUMNS), beyond which qs returns an object and schema validation rejects it (129 → 400 with the exact id must be array message from getStateValidators returns 400 "id must be array" for >20 comma-separated id values (regression in v1.44.0) #9672).

Test-only change; no source behavior is modified.

🤖 Generated with AI assistance

…sizes

Adds regression coverage for ChainSafe#9672, where more than 20 comma-separated
validator `id` values started failing with `id must be array` (400) after
the qs bump lowered the effective array limit below the beacon-API
`maxItems` (64). ChainSafe#9673 fixed it by raising the querystring `arrayLimit` to
`NUMBER_OF_COLUMNS`.

The test drives the real `RestApiServer` so it is bound to the production
querystring parser in `base.ts`, rather than a stand-alone Fastify instance
with a copied config. If the `arrayLimit` override regresses, the 21/64/65
item cases fail exactly as ChainSafe#9672 did.

Uses representative boundary sizes (1, 20, 21, 64, 65, NUMBER_OF_COLUMNS)
instead of exhaustive enumeration, and asserts that more than 64 items are
still accepted — Lodestar intentionally does not hard-cap validator `id`
arrays server-side at the spec `maxItems`.

🤖 Generated with AI assistance

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@lodekeeper
lodekeeper requested a review from a team as a code owner July 25, 2026 14:26

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 25a81582bd

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

for (const n of ACCEPTED_COUNTS) {
it(`parses ${n} comma-separated ids as an array`, async () => {
const res = await server.fastify.inject({method: "GET", url: `/validators?${commaQuery("id", n)}`});
expect(res.statusCode).toBe(200);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Add messages to assertions inside the boundary loop

The assertions in both test cases generated by this loop omit assertion messages. Add messages identifying the wire form and boundary count so failures retain the required context; repository guidance explicitly requires assertion messages for looped or repeated assertions.

AGENTS.md reference: AGENTS.md:L294-L299

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 9d90312 — added diagnostic messages naming the wire form (comma-separated / repeated) and item count to each assertion inside the ACCEPTED_COUNTS boundary loop, per the AGENTS.md looped-assertion convention:

expect(res.statusCode, `comma-separated id query with ${n} items should be accepted (200)`).toBe(200);
expect(Array.isArray(id), `comma-separated id query with ${n} items should parse as an array`).toBe(true);
expect(id, `comma-separated id query should preserve all ${n} parsed items`).toHaveLength(n);

(and the same for the repeated-form it).

Address Codex review r3650324925 on ChainSafe#9712: the assertions inside the
ACCEPTED_COUNTS boundary loop omitted diagnostic messages. Add messages
identifying the wire form (comma-separated/repeated) and item count to
each, per the repo convention requiring messages on looped assertions.

🤖 Generated with AI assistance
wemeetagain pushed a commit that referenced this pull request Jul 30, 2026
…ests (#9713)

## Motivation

Follow-up to #9672 and the sibling unit-test PR #9712. Per the request
in #9672, this adds an **e2e** suite that spins up a real beacon node
and verifies our REST server is beacon-API spec compliant for
query-string array lengths, so a regression that makes us non-compliant
(like the `qs` `arrayLimit` regression, where >20 comma-separated `id`
values returned `id must be array`/400) is caught end-to-end, not just
at the parser level.

This is PR 2 of 2 (PR 1 = #9712, `RestApiServer` unit coverage).

## Description

Boots a dev beacon node with the REST API enabled and exercises the
validator-id query endpoints via the typed `@lodestar/api` client and a
raw request:

- `getStateValidators` and `getStateValidatorBalances` with **64** ids
(the beacon-API `maxItems`) and **`NUMBER_OF_COLUMNS`** ids (the
configured server cap).
- The exact comma-separated wire form from the #9672 report
(`?id=a,b,c,...`) via a raw request — the `@lodestar/api` client
serializes arrays as repeat-format (`id=a&id=b`), so the comma form is
tested directly.

## Scope — beacon-API query-array audit

Nico asked to check "other cases too". I audited every query-string
array param across the beacon-API route definitions. Only the
validator-`id` queries carry more than ~20 items in normal client usage
(a VC resolving its managed validators), and they are the exact #9672
surface:

| Endpoint | Param | Schema | maxItems |
|---|---|---|---|
| `getStateValidators` | `id` | `UintOrStringArray` | 64 |
| `getStateValidatorBalances` | `id` | `UintOrStringArray` | 64 |
| `getDebugDataColumnSidecars` | `indices` | `UintArray` |
`NUMBER_OF_COLUMNS` (128) |

All other query arrays — event `topics`, validator `status`, peer
`state`/`direction`, blob `indices`, `versioned_hashes` — are enum- or
per-block-bounded well under the old default limit (20) and never
tripped the regression.

The data-column `indices` path needs a Fulu block carrying columns to
exercise end-to-end; it's covered at the unit level in #9712 and noted
in-file as a follow-up here.

Test-only change; no source behavior is modified.

🤖 Generated with AI assistance

---------

Co-authored-by: lodekeeper <lodekeeper@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
@nflaig

nflaig commented Jul 30, 2026

Copy link
Copy Markdown
Member

can we close this since we merged #9713? those should cover the same cases just with a different testing strategy, merging both prs may be redundant

@nflaig

nflaig commented Jul 30, 2026

Copy link
Copy Markdown
Member

@lodekeeper

@lodekeeper

Copy link
Copy Markdown
Contributor Author

Compared the two — they overlap heavily, but #9712 isn't a strict duplicate. Breakdown:

Both cover: validator id at 64 and NUMBER_OF_COLUMNS, comma + repeat form. So the #9672 regression itself (querystring arrayLimit < spec maxItems) is safely guarded by #9713 alone — agree there.

Only in #9712 (unit):

So closing #9712 would drop the indices-param + overflow + exact-boundary cases and leave #9713's docstring referencing a closed sibling.

My lean: keep #9712 as the unit half of the pair — it's cheap (no node spin-up) and covers what the e2e suite deliberately doesn't. If you'd rather not maintain two files, I can instead trim the overlapping 64/NUMBER_OF_COLUMNS id cases so #9712 holds only the unit-unique cases. Whichever you prefer — happy to close if you still want to consolidate.

markolazic01 pushed a commit to markolazic01/lodestar that referenced this pull request Jul 30, 2026
…ests (ChainSafe#9713)

## Motivation

Follow-up to ChainSafe#9672 and the sibling unit-test PR ChainSafe#9712. Per the request
in ChainSafe#9672, this adds an **e2e** suite that spins up a real beacon node
and verifies our REST server is beacon-API spec compliant for
query-string array lengths, so a regression that makes us non-compliant
(like the `qs` `arrayLimit` regression, where >20 comma-separated `id`
values returned `id must be array`/400) is caught end-to-end, not just
at the parser level.

This is PR 2 of 2 (PR 1 = ChainSafe#9712, `RestApiServer` unit coverage).

## Description

Boots a dev beacon node with the REST API enabled and exercises the
validator-id query endpoints via the typed `@lodestar/api` client and a
raw request:

- `getStateValidators` and `getStateValidatorBalances` with **64** ids
(the beacon-API `maxItems`) and **`NUMBER_OF_COLUMNS`** ids (the
configured server cap).
- The exact comma-separated wire form from the ChainSafe#9672 report
(`?id=a,b,c,...`) via a raw request — the `@lodestar/api` client
serializes arrays as repeat-format (`id=a&id=b`), so the comma form is
tested directly.

## Scope — beacon-API query-array audit

Nico asked to check "other cases too". I audited every query-string
array param across the beacon-API route definitions. Only the
validator-`id` queries carry more than ~20 items in normal client usage
(a VC resolving its managed validators), and they are the exact ChainSafe#9672
surface:

| Endpoint | Param | Schema | maxItems |
|---|---|---|---|
| `getStateValidators` | `id` | `UintOrStringArray` | 64 |
| `getStateValidatorBalances` | `id` | `UintOrStringArray` | 64 |
| `getDebugDataColumnSidecars` | `indices` | `UintArray` |
`NUMBER_OF_COLUMNS` (128) |

All other query arrays — event `topics`, validator `status`, peer
`state`/`direction`, blob `indices`, `versioned_hashes` — are enum- or
per-block-bounded well under the old default limit (20) and never
tripped the regression.

The data-column `indices` path needs a Fulu block carrying columns to
exercise end-to-end; it's covered at the unit level in ChainSafe#9712 and noted
in-file as a follow-up here.

Test-only change; no source behavior is modified.

🤖 Generated with AI assistance

---------

Co-authored-by: lodekeeper <lodekeeper@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

3 participants