Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add Koa Server Request Timeout #2504

Merged
merged 6 commits into from
May 28, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
6 changes: 6 additions & 0 deletions .github/workflows/acceptance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,12 @@ jobs:
with:
testfilter: cache-service

server-config:
quiet-node marked this conversation as resolved.
Show resolved Hide resolved
name: Server Config
uses: ./.github/workflows/acceptance-workflow.yml
with:
testfilter: serverconfig

publish_results:
name: Publish Results
if: ${{ !cancelled() }}
Expand Down
1 change: 1 addition & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ Unless you need to set a non-default value, it is recommended to only populate o
| `RATE_LIMIT_DISABLED` | "false" | Flag to disable IP based rate limiting. |
| `REQUEST_ID_IS_OPTIONAL` | "" | Flag to set it the JSON RPC request id field in the body should be optional. Note, this breaks the API spec and is not advised and is provided for test purposes only where some wallets may be non compliant |
| `SERVER_PORT` | "7546" | The RPC server port number to listen for requests on. Currently a static value defaulting to 7546. See [#955](https://github.com/hashgraph/hedera-json-rpc-relay/issues/955) |
| `SERVER_REQUEST_TIMEOUT_MS`| "60000" | The time of inactivity allowed before a timeout is triggered and the socket is closed. See [NodeJs Server Timeout](https://nodejs.org/api/http.html#serversettimeoutmsecs-callback) |

## Relay

Expand Down
13 changes: 7 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"acceptancetest:precompile-calls": "ts-mocha packages/server/tests/acceptance/index.spec.ts -g '@precompile-calls' --exit",
"acceptancetest:cache-service": "ts-mocha packages/server/tests/acceptance/index.spec.ts -g '@cache-service' --exit",
"acceptancetest:rpc_api_schema_conformity": "ts-mocha packages/server/tests/acceptance/index.spec.ts -g '@api-conformity' --exit",
"acceptancetest:serverconfig": "ts-mocha packages/server/tests/acceptance/index.spec.ts -g '@server-config' --exit",
"build": "npx lerna run build",
"build-and-test": "npx lerna run build && npx lerna run test",
"build:docker": "docker build . -t ${npm_package_name}",
Expand Down
6 changes: 5 additions & 1 deletion packages/server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,13 @@
*/

import app from './server';
import { setServerTimeout } from './koaJsonRpc/lib/utils'; // Import the 'setServerTimeout' function from the correct location

async function main() {
await app.listen({ port: process.env.SERVER_PORT || 7546 });
const server = await app.listen({ port: process.env.SERVER_PORT || 7546 });

// set request timeout to ensure sockets are closed after specified time of inactivity
setServerTimeout(server);
}

main();
26 changes: 26 additions & 0 deletions packages/server/src/koaJsonRpc/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*-
*
* Hedera JSON RPC Relay
*
* Copyright (C) 2024 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

import type { Server } from 'http';

export function setServerTimeout(server: Server): void {
const requestTimeoutMs = parseInt(process.env.SERVER_REQUEST_TIMEOUT_MS ?? '60000');
server.setTimeout(requestTimeoutMs);
quiet-node marked this conversation as resolved.
Show resolved Hide resolved
}
2 changes: 2 additions & 0 deletions packages/server/tests/acceptance/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import constants from '@hashgraph/json-rpc-relay/dist/lib/constants';
// Utils and types
import { Utils } from '../helpers/utils';
import { AliasAccount } from '../types/AliasAccount';
import { setServerTimeout } from '../../src/koaJsonRpc/lib/utils';

chai.use(chaiAsPromised);
dotenv.config({ path: path.resolve(__dirname, '../../../.env') });
Expand Down Expand Up @@ -185,6 +186,7 @@ describe('RPC Server Acceptance Tests', function () {

logger.info(`Start relay on port ${constants.RELAY_PORT}`);
relayServer = app.listen({ port: constants.RELAY_PORT });
setServerTimeout(relayServer);

if (process.env.TEST_WS_SERVER === 'true') {
logger.info(`Start ws-server on port ${constants.WEB_SOCKET_PORT}`);
Expand Down
27 changes: 26 additions & 1 deletion packages/server/tests/acceptance/rateLimiter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
*
* Hedera JSON RPC Relay
*
* Copyright (C) 2023 Hedera Hashgraph, LLC
* Copyright (C) 2023-2024 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -158,4 +158,29 @@ describe('@ratelimiter Rate Limiters Acceptance Tests', function () {
});
});
});

describe('Koa Server Timeout', () => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Either we can cancel the server-config CI and have this test here, or we keep the server-config CI and remove this test in ratelimiter.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Agreed. That got missed in the back and forth. Removing.

before(async () => {
process.env.SERVER_REQUEST_TIMEOUT_MS = '3000';
});

it('should timeout a request after the specified time', async () => {
this.timeout(5000);
const requestTimeoutMs: number = parseInt(process.env.SERVER_REQUEST_TIMEOUT_MS || '3000');

const host = 'localhost';
const port = parseInt(process.env.SERVER_PORT || '7546');
const method = 'eth_blockNumber';
const params: any[] = [];

try {
await sendJsonRpcRequestWithDelay(host, port, method, params, requestTimeoutMs + 5000);
throw new Error('Request did not timeout as expected'); // Force the test to fail if the request does not time out
} catch (err) {
console.log(`Error details: ${err}`);
expect(err.code).to.equal('ECONNRESET');
expect(err.message).to.equal('socket hang up');
}
});
});
});
41 changes: 41 additions & 0 deletions packages/server/tests/acceptance/serverConfig.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*-
*
* Hedera JSON RPC Relay
*
* Copyright (C) 2024 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { expect } from 'chai';
import { Utils } from '../helpers/utils';

describe('@server-config Rate Limiters Acceptance Tests', function () {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Either we can cancel the server-config CI and have this test in ratelimiter, or we keep the server-config CI and remove this test here.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Agreed, see comment above and updated tests title.

describe('Koa Server Timeout', () => {
it('should timeout a request after the specified time', async () => {
const requestTimeoutMs: number = parseInt(process.env.SERVER_REQUEST_TIMEOUT_MS || '3000');
const host = 'localhost';
const port = parseInt(process.env.SERVER_PORT || '7546');
const method = 'eth_blockNumber';
const params: any[] = [];

try {
await Utils.sendJsonRpcRequestWithDelay(host, port, method, params, requestTimeoutMs + 1000);
throw new Error('Request did not timeout as expected'); // Force the test to fail if the request does not time out
} catch (err) {
expect(err.code).to.equal('ECONNRESET');
expect(err.message).to.equal('socket hang up');
}
});
});
});
58 changes: 58 additions & 0 deletions packages/server/tests/helpers/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import RelayCall from '../../tests/helpers/constants';
import { AccountId, KeyList, PrivateKey } from '@hashgraph/sdk';
import { AliasAccount } from '../types/AliasAccount';
import ServicesClient from '../clients/servicesClient';
import http from 'http';

export class Utils {
/**
Expand Down Expand Up @@ -308,4 +309,61 @@ export class Utils {
}
return accounts;
}

static sendJsonRpcRequestWithDelay(
host: string,
port: number,
method: string,
params: any[],
delayMs: number,
): Promise<any> {
const requestData = JSON.stringify({
jsonrpc: '2.0',
method: method,
params: params,
id: 1,
});

const options = {
hostname: host,
port: port,
path: '/',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(requestData),
},
timeout: delayMs,
};

return new Promise((resolve, reject) => {
const req = http.request(options, (res) => {
quiet-node marked this conversation as resolved.
Show resolved Hide resolved
let data = '';

res.on('data', (chunk) => {
data += chunk;
});

res.on('end', () => {
resolve(JSON.parse(data));
});
});

req.on('timeout', () => {
req.destroy();
reject(new Error(`Request timed out after ${delayMs}ms`));
});

req.on('error', (err) => {
reject(err);
});

// Introduce a delay with inactivity, before sending the request
setTimeout(async () => {
req.write(requestData);
req.end();
await new Promise((r) => setTimeout(r, delayMs + 1000));
}, delayMs);
});
}
}
1 change: 1 addition & 0 deletions packages/server/tests/localAcceptance.env
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ TEST_GAS_PRICE_DEVIATION=0.2
WS_NEW_HEADS_ENABLED=false
INITIAL_BALANCE='5000000000'
LIMIT_DURATION=90000
SERVER_REQUEST_TIMEOUT_MS=60000
1 change: 1 addition & 0 deletions packages/server/tests/previewnetAcceptance.env
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ FILTER_API_ENABLED=true
DEBUG_API_ENABLED=true
TEST_GAS_PRICE_DEVIATION=0.2
WS_NEW_HEADS_ENABLED=true
SERVER_REQUEST_TIMEOUT_MS=60000

1 change: 1 addition & 0 deletions packages/server/tests/testnetAcceptance.env
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ SUBSCRIPTIONS_ENABLED=true
FILTER_API_ENABLED=true
DEBUG_API_ENABLED=true
TEST_GAS_PRICE_DEVIATION=0.2
SERVER_REQUEST_TIMEOUT_MS=60000
Loading