Skip to content

Commit b102fae

Browse files
committed
fix: json compliance layer simplified a little bit (#4630)
Signed-off-by: Mariusz Jasuwienas <[email protected]>
1 parent ac37d36 commit b102fae

File tree

2 files changed

+41
-79
lines changed

2 files changed

+41
-79
lines changed

packages/server/src/compliance.ts

Lines changed: 34 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -3,96 +3,57 @@
33
import { ConfigService } from '@hashgraph/json-rpc-config-service/dist/services';
44
import { ParameterizedContext } from 'koa';
55

6-
interface JsonRpcSuccess {
7-
jsonrpc: unknown;
8-
id: string | number | unknown;
9-
result: unknown;
10-
}
11-
12-
interface JsonRpcError {
13-
jsonrpc: unknown;
14-
id: string | number | unknown;
15-
error?: {
16-
code?: number;
17-
message: unknown;
18-
data?: unknown;
19-
};
20-
}
21-
22-
type JsonRpcResponse = JsonRpcSuccess | JsonRpcError;
23-
type JsonRpcBatch = JsonRpcResponse[];
24-
type JsonRpcBody = JsonRpcResponse | JsonRpcBatch;
25-
266
const VALID_JSON_RPC_HTTP_REQUESTS_STATUS_CODE = ConfigService.get('VALID_JSON_RPC_HTTP_REQUESTS_STATUS_CODE');
277

28-
const isCorrectSuccess = (res: JsonRpcResponse, httpStatus: unknown) =>
29-
httpStatus === 200 &&
30-
res.jsonrpc === '2.0' &&
31-
hasValidId(res.id) &&
32-
Object.prototype.hasOwnProperty.call(res, 'result');
33-
34-
const hasValidId = (id: unknown) => Boolean(id !== undefined && id !== null);
35-
36-
const isCorrectError = (res: JsonRpcResponse, httpStatus: unknown) =>
37-
httpStatus !== 200 &&
38-
res.jsonrpc === '2.0' &&
39-
hasValidId(res.id) &&
40-
Object.prototype.hasOwnProperty.call(res, 'error') &&
41-
typeof (res as JsonRpcError).error?.message === 'string';
42-
43-
const hasCorrectResponseBody = (res: JsonRpcResponse, httpStatus: unknown) =>
44-
isCorrectSuccess(res, httpStatus) || isCorrectError(res, httpStatus);
45-
46-
const fixResponseBody = (res: Partial<JsonRpcResponse> | undefined, httpStatus: unknown) => {
47-
const id = hasValidId(res?.id) ? (res!.id as string | number) : null;
48-
if (httpStatus === 200) {
49-
return {
50-
jsonrpc: '2.0',
51-
id,
52-
result: Object.prototype.hasOwnProperty.call(res ?? {}, 'result') ? (res as JsonRpcSuccess).result : '0x',
53-
};
54-
}
55-
const errorObj =
56-
'error' in (res ?? {}) && (res as JsonRpcError).error
57-
? {
58-
code: (res as JsonRpcError).error!.code ?? -32603,
59-
message: (res as JsonRpcError).error!.message,
60-
}
61-
: {
62-
code: -32603,
63-
message: 'Internal error',
64-
};
8+
const FALLBACK_RESPONSE_BODY = {
9+
jsonrpc: '2.0',
10+
id: null,
11+
error: { code: -32600, message: 'Request body is empty; expected a JSON-RPC 2.0 request' },
12+
};
6513

66-
return {
67-
jsonrpc: '2.0',
68-
id,
69-
error: errorObj,
70-
};
14+
export const INVALID_METHOD_RESPONSE_BODY = {
15+
...FALLBACK_RESPONSE_BODY,
16+
error: { code: -32600, message: 'Invalid HTTP method: only POST is allowed' },
7117
};
7218

7319
/**
7420
* Ensures a JSON-RPC response uses a valid JSON-RPC 2.0 structure.
7521
* Normalizes missing or invalid fields for both single and batch responses.
7622
* May update HTTP status depending on VALID_JSON_RPC_HTTP_REQUESTS_STATUS_CODE.
7723
*
78-
* This function must be invoked in any place where Koa's `next()` might not run,
79-
* or where the response may bypass normal middleware cleanup - so it's not implemented as a KOA's middleware.
80-
*
8124
* @param {ParameterizedContext} ctx - Koa context containing status and body.
8225
*/
83-
export const jsonRpcComplianceLayer = async (ctx: ParameterizedContext) => {
84-
const body = ctx.body as JsonRpcBody | undefined;
85-
if (!body) {
86-
ctx.body = fixResponseBody(undefined, ctx.status);
87-
ctx.status = VALID_JSON_RPC_HTTP_REQUESTS_STATUS_CODE ? 200 : 400;
26+
export const jsonRpcComplianceLayer = async (
27+
ctx: {
28+
body: {
29+
jsonrpc: unknown;
30+
id: unknown;
31+
result?: unknown;
32+
error?: { code: unknown; message: unknown };
33+
};
34+
status: number | undefined;
35+
} & ParameterizedContext,
36+
) => {
37+
if (!ctx.body) {
38+
ctx.status = 400;
39+
ctx.body = FALLBACK_RESPONSE_BODY;
8840
return;
8941
}
90-
if (Array.isArray(body)) {
91-
// Regardless of the mode the batch requests will always return 200;
42+
43+
// Always return 200 for array requests.
44+
if (Array.isArray(ctx.body)) {
9245
ctx.status = 200;
9346
return;
9447
}
48+
if (typeof ctx.body !== 'object') {
49+
ctx.status = 400;
50+
ctx.body = FALLBACK_RESPONSE_BODY;
51+
return;
52+
}
9553

96-
if (!hasCorrectResponseBody(body, ctx.status)) ctx.body = fixResponseBody(body, ctx.status);
54+
if (!ctx.body.jsonrpc) ctx.body.jsonrpc = FALLBACK_RESPONSE_BODY.jsonrpc;
55+
if (!ctx.body.id) ctx.body.id = FALLBACK_RESPONSE_BODY.id;
56+
if (ctx.status === 200 && !ctx.body.result) ctx.body.result = '0x';
57+
if (ctx.status === 400 && (!ctx.body.error || !ctx.body.error.code)) ctx.body.error = FALLBACK_RESPONSE_BODY.error;
9758
if (ctx.status === 400 && VALID_JSON_RPC_HTTP_REQUESTS_STATUS_CODE) ctx.status = 200;
9859
};

packages/server/src/server.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,13 @@ import { ConfigService } from '@hashgraph/json-rpc-config-service/dist/services'
66
import { Relay } from '@hashgraph/json-rpc-relay/dist';
77
import { RedisClientManager } from '@hashgraph/json-rpc-relay/dist/lib/clients/redisClientManager';
88
import fs from 'fs';
9-
import { ParameterizedContext } from 'koa';
109
import cors from 'koa-cors';
1110
import path from 'path';
1211
import pino from 'pino';
1312
import { collectDefaultMetrics, Histogram, Registry } from 'prom-client';
1413
import { v4 as uuid } from 'uuid';
1514

16-
import { jsonRpcComplianceLayer } from './compliance';
15+
import { INVALID_METHOD_RESPONSE_BODY, jsonRpcComplianceLayer } from './compliance';
1716
import { formatRequestIdMessage } from './formatters';
1817
import KoaJsonRpc from './koaJsonRpc';
1918
import { spec } from './koaJsonRpc/lib/RpcError';
@@ -276,8 +275,8 @@ export async function initializeServer() {
276275
await next();
277276
return;
278277
}
279-
ctx.status = 400;
280-
await jsonRpcComplianceLayer(ctx);
278+
ctx.status = 403;
279+
ctx.body = INVALID_METHOD_RESPONSE_BODY;
281280
});
282281

283282
app.use((ctx, next) => {
@@ -306,11 +305,13 @@ export async function initializeServer() {
306305

307306
const rpcApp = koaJsonRpc.rpcApp();
308307

309-
app.use(async (ctx) => {
308+
app.use(async (ctx, next) => {
310309
await rpcApp(ctx);
311-
await jsonRpcComplianceLayer(ctx);
310+
await next();
312311
});
313312

313+
app.use(jsonRpcComplianceLayer);
314+
314315
process.on('unhandledRejection', (reason, p) => {
315316
logger.error(`Unhandled Rejection at: Promise: ${JSON.stringify(p)}, reason: ${reason}`);
316317
});

0 commit comments

Comments
 (0)