fix(reqresp): map malformed request payloads to invalid request#9645
fix(reqresp): map malformed request payloads to invalid request#9645Osraka wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces a deserializeRequestBody helper function to safely wrap request deserialization in a try-catch block, throwing a ResponseError with RespStatus.INVALID_REQUEST upon failure. All request handlers have been updated to use this helper, and a corresponding unit test has been added. Feedback suggests optimizing performance by passing the deserializable type and data buffer directly to avoid closure allocations in hot paths, as well as improving error safety by checking if the caught exception is an instance of Error before accessing its message.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| function deserializeRequestBody<T>(deserialize: () => T): T { | ||
| try { | ||
| return deserialize(); | ||
| } catch (e) { | ||
| throw new ResponseError(RespStatus.INVALID_REQUEST, (e as Error).message); | ||
| } | ||
| } |
There was a problem hiding this comment.
Performance & Safety Improvement
-
Avoid Closure Allocations in Hot Paths:
Currently,deserializeRequestBodytakes a closure() => T. Since this helper is executed on every incoming p2p request, creating a new closure() => ...for every request introduces unnecessary memory allocations and garbage collection overhead in high-throughput network paths.We can avoid this by passing the deserializable type and the data buffer directly to the helper.
-
Safe Error Handling:
Castinge as Errorand accessing.messageis unsafe if a non-Errorobject (ornull/undefined) is thrown. Usinge instanceof Error ? e.message : String(e)is much safer and guarantees a valid string message.
With this change, the usages can be simplified from:
const body = deserializeRequestBody(() => ssz.phase0.BeaconBlocksByRangeRequest.deserialize(req.data));to:
const body = deserializeRequestBody(ssz.phase0.BeaconBlocksByRangeRequest, req.data);interface Deserializable<T> {
deserialize(data: Uint8Array): T;
}
function deserializeRequestBody<T>(type: Deserializable<T>, data: Uint8Array): T {
try {
return type.deserialize(data);
} catch (e) {
throw new ResponseError(RespStatus.INVALID_REQUEST, e instanceof Error ? e.message : String(e));
}
}|
Addressed Gemini’s review in the follow-up commit. |
Summary
Maps malformed req/resp request-body deserialization failures to
INVALID_REQUESTinstead of letting them fall through as generic handler errors.This keeps the existing request decoding path unchanged, but wraps the app-level SSZ deserialization done by beacon-node req/resp handlers. That prevents malformed request bytes, such as malformed
DataColumnSidecarsByRootpayloads, from being reported as server errors.Refs #9398.
Notes
columnsrequests.Testing
pnpm exec vitest run --project unit packages/beacon-node/test/unit/network/reqresp/handlers/index.test.ts packages/beacon-node/test/unit/network/peers/discover.test.ts packages/reqresp/test/unit/encoders/requestDecode.test.ts packages/reqresp/test/unit/response/index.test.tspnpm --filter @lodestar/beacon-node lintpnpm --filter @lodestar/beacon-node check-typespnpm --filter @lodestar/beacon-node buildI also tried
pnpm exec vitest run --project e2e packages/beacon-node/test/e2e/network/reqresp.test.ts, but this local checkout was installed with--ignore-scriptsand runs Node v22 while the repo expects Node v24. The test suite failed during collection on a missingclassic-levelnative binding before reaching the req/resp tests.