Skip to content

Commit f23e77b

Browse files
authored
Improve error handling for eth_getLogs (#388)
* fix: improve eth_getLogs error handling Signed-off-by: Ivo Yankov <[email protected]> * wip: fix failing tests Signed-off-by: Ivo Yankov <[email protected]> * chore: resolve code structure conflicts Signed-off-by: Ivo Yankov <[email protected]> * test: assert the errors Signed-off-by: Ivo Yankov <[email protected]> * nit: fix code smells Signed-off-by: Ivo Yankov <[email protected]>
1 parent 61ae4d4 commit f23e77b

File tree

7 files changed

+116
-20
lines changed

7 files changed

+116
-20
lines changed

packages/relay/src/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@
1919
*/
2020

2121
import { Block, Log, Receipt, Transaction } from './lib/model';
22-
import { JsonRpcError } from './lib/errors/JsonRpcError';
22+
import { JsonRpcError, predefined } from './lib/errors/JsonRpcError';
23+
import { MirrorNodeClientError } from './lib/errors/MirrorNodeClientError';
2324

24-
export { JsonRpcError };
25+
export { JsonRpcError, predefined, MirrorNodeClientError };
2526

2627
export { RelayImpl } from './lib/relay';
2728

packages/relay/src/lib/errors/JsonRpcError.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,4 +106,9 @@ export const predefined = {
106106
code: -32602,
107107
message: 'Value below 10_000_000_000 wei which is 1 tinybar'
108108
}),
109+
'REQUEST_TIMEOUT': new JsonRpcError({
110+
name: 'Request timeout',
111+
code: -32010,
112+
message: 'Request timeout. Please try again.'
113+
}),
109114
};

packages/relay/src/lib/errors/MirrorNodeClientError.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,25 @@
2121

2222
export class MirrorNodeClientError extends Error {
2323
public statusCode: number;
24+
25+
static statusCodes = {
26+
TIMEOUT: 567,
27+
NOT_FOUND: 404
28+
};
2429

2530
constructor(message: string, statusCode: number) {
2631
super(message);
2732
this.statusCode = statusCode;
2833

2934
Object.setPrototypeOf(this, MirrorNodeClientError.prototype);
3035
}
36+
37+
public isTimeout(): boolean {
38+
return this.statusCode === MirrorNodeClientError.statusCodes.TIMEOUT;
39+
}
40+
41+
public isNotFound(): boolean {
42+
return this.statusCode === MirrorNodeClientError.statusCodes.NOT_FOUND;
43+
}
3144
}
3245

packages/relay/src/lib/eth.ts

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { Block, Transaction, Log } from './model';
2626
import { MirrorNodeClient, SDKClient } from './clients';
2727
import { JsonRpcError, predefined } from './errors/JsonRpcError';
2828
import { SDKClientError } from './errors/SDKClientError';
29+
import { MirrorNodeClientError } from './errors/MirrorNodeClientError';
2930
import constants from './constants';
3031
import { Precheck } from './precheck';
3132

@@ -1040,28 +1041,36 @@ export class EthImpl implements Eth {
10401041
async getLogs(blockHash: string | null, fromBlock: string | null, toBlock: string | null, address: string | null, topics: any[] | null): Promise<Log[]> {
10411042
const params: any = {};
10421043
if (blockHash) {
1043-
const block = await this.mirrorNodeClient.getBlock(blockHash);
1044-
if (block) {
1045-
params.timestamp = [
1046-
`gte:${block.timestamp.from}`,
1047-
`lte:${block.timestamp.to}`
1048-
];
1044+
try {
1045+
const block = await this.mirrorNodeClient.getBlock(blockHash);
1046+
if (block) {
1047+
params.timestamp = [
1048+
`gte:${block.timestamp.from}`,
1049+
`lte:${block.timestamp.to}`
1050+
];
1051+
}
10491052
}
1050-
}
1051-
else if (fromBlock || toBlock) {
1053+
catch(e: any) {
1054+
if (e instanceof MirrorNodeClientError && e.isNotFound()) {
1055+
return [];
1056+
}
1057+
1058+
throw e;
1059+
}
1060+
} else if (fromBlock || toBlock) {
10521061
const filters = [];
10531062
let order;
1054-
if(toBlock) {
1063+
if (toBlock) {
10551064
// @ts-ignore
10561065
filters.push(`lte:${parseInt(toBlock)}`);
10571066
order = constants.ORDER.DESC;
10581067
}
1059-
if(fromBlock) {
1068+
if (fromBlock) {
10601069
// @ts-ignore
10611070
filters.push(`gte:${parseInt(fromBlock)}`);
10621071
order = constants.ORDER.ASC;
10631072
}
1064-
const blocksResult = await this.mirrorNodeClient.getBlocks(filters, undefined , {order});
1073+
const blocksResult = await this.mirrorNodeClient.getBlocks(filters, undefined, {order});
10651074

10661075
const blocks = blocksResult?.blocks;
10671076
if (blocks?.length) {
@@ -1112,8 +1121,8 @@ export class EthImpl implements Eth {
11121121
}
11131122
}
11141123

1115-
// Populate the Log objects with block and transaction data from ContractResultsDetails
11161124
try {
1125+
// Populate the Log objects with block and transaction data from ContractResultsDetails
11171126
const contractsResultsDetails = await Promise.all(promises);
11181127
for (let i = 0; i < contractsResultsDetails.length; i++) {
11191128
const detail = contractsResultsDetails[i];
@@ -1137,8 +1146,12 @@ export class EthImpl implements Eth {
11371146
}
11381147
}
11391148
}
1140-
catch (e) {
1141-
return [];
1149+
catch(e: any) {
1150+
if (e instanceof MirrorNodeClientError && e.isNotFound()) {
1151+
return [];
1152+
}
1153+
1154+
throw e;
11421155
}
11431156

11441157
return logs;

packages/relay/tests/lib/eth.spec.ts

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import { Registry } from 'prom-client';
2727
import sinon from 'sinon';
2828
import cache from 'js-cache';
2929
dotenv.config({ path: path.resolve(__dirname, '../test.env') });
30-
import { RelayImpl } from '@hashgraph/json-rpc-relay';
30+
import {RelayImpl, predefined, MirrorNodeClientError} from '@hashgraph/json-rpc-relay';
3131
import { EthImpl } from '../../src/lib/eth';
3232
import { MirrorNodeClient } from '../../src/lib/clients/mirrorNodeClient';
3333
import { expectUnsupportedMethod } from '../helpers';
@@ -337,6 +337,7 @@ describe('Eth calls using MirrorNode', async function () {
337337
};
338338

339339
const detailedContractResultNotFound = { "_status": { "messages": [{ "message": "No correlating transaction" }] } };
340+
const timeoutError = { "type": "Error", "message": "timeout of 10000ms exceeded" };
340341

341342
const defaultDetailedContractResultsWithNullNullableValues = {
342343
...defaultDetailedContractResults,
@@ -1016,6 +1017,51 @@ describe('Eth calls using MirrorNode', async function () {
10161017
expect(result.length).to.eq(0);
10171018
});
10181019

1020+
it('one of contract results details timeouts and throws the expected error', async function () {
1021+
mock.onGet(`contracts/results/logs`).reply(200, defaultLogs);
1022+
mock.onGet(`contracts/${contractId1}/results/${contractTimestamp1}`).reply(200, defaultDetailedContractResults);
1023+
mock.onGet(`contracts/${contractId1}/results/${contractTimestamp2}`).reply(567, timeoutError);
1024+
mock.onGet(`contracts/${contractId2}/results/${contractTimestamp2}`).reply(200, defaultDetailedContractResults3);
1025+
1026+
try {
1027+
const result = await ethImpl.getLogs(null, null, null, null, null);
1028+
expect(true).to.eq(false);
1029+
} catch (error: any) {
1030+
expect(error.statusCode).to.equal(MirrorNodeClientError.statusCodes.TIMEOUT);
1031+
}
1032+
});
1033+
1034+
it('blockHash filter timeouts and throws the expected error', async function () {
1035+
const filteredLogs = {
1036+
logs: [defaultLogs.logs[0], defaultLogs.logs[1]]
1037+
};
1038+
1039+
mock.onGet(`contracts/results/logs`).reply(200, defaultLogs);
1040+
mock.onGet(`contracts/${contractId1}/results/${contractTimestamp1}`).reply(200, defaultDetailedContractResults);
1041+
mock.onGet(`contracts/results/logs?timestamp=gte:${defaultBlock.timestamp.from}&timestamp=lte:${defaultBlock.timestamp.to}`).reply(200, filteredLogs);
1042+
mock.onGet(`blocks/${blockHash}`).reply(567, timeoutError);
1043+
1044+
try {
1045+
const result = await ethImpl.getLogs(blockHash, null, null, null, null);
1046+
expect(true).to.eq(false);
1047+
} catch (error: any) {
1048+
expect(error.statusCode).to.equal(MirrorNodeClientError.statusCodes.TIMEOUT);
1049+
}
1050+
});
1051+
1052+
it('address filter timeouts and throws the expected error', async function () {
1053+
mock.onGet(`contracts/${contractId1}/results/${contractTimestamp1}`).reply(200, defaultDetailedContractResults);
1054+
mock.onGet(`contracts/${contractId1}/results/${contractTimestamp2}`).reply(200, defaultDetailedContractResults2);
1055+
mock.onGet(`contracts/${contractAddress1}/results/logs`).reply(567, timeoutError);
1056+
1057+
try {
1058+
const result = await ethImpl.getLogs(null, null, null, contractAddress1, null);
1059+
expect(true).to.eq(false);
1060+
} catch (error: any) {
1061+
expect(error.statusCode).to.equal(MirrorNodeClientError.statusCodes.TIMEOUT);
1062+
}
1063+
});
1064+
10191065
it('error when retrieving logs', async function () {
10201066
mock.onGet(`contracts/results/logs`).reply(400, { "_status": { "messages": [{ "message": "Mocked error" }] } });
10211067
const result = await ethImpl.getLogs(null, null, null, null, null);

packages/server/src/server.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
*
1919
*/
2020

21-
import { Relay, RelayImpl, JsonRpcError } from '@hashgraph/json-rpc-relay';
21+
import { Relay, RelayImpl, JsonRpcError, predefined, MirrorNodeClientError } from '@hashgraph/json-rpc-relay';
2222
import Koa from 'koa';
2323
import koaJsonRpc from 'koa-jsonrpc';
2424
import { collectDefaultMetrics, Histogram, Registry } from 'prom-client';
@@ -143,11 +143,21 @@ const logAndHandleResponse = async (methodName, methodFunction) => {
143143
methodResponseHistogram.labels(methodName, status).observe(ms);
144144
logger.info(`${messagePrefix} ${status} ${ms} ms `);
145145
return response;
146-
} catch (e) {
146+
} catch (e: any) {
147147
ms = Date.now() - start;
148148
methodResponseHistogram.labels(methodName, responseInternalErrorCode).observe(ms);
149149
logger.error(e, `${messagePrefix} ${responseInternalErrorCode} ${ms} ms`);
150-
throw e;
150+
151+
if (e instanceof MirrorNodeClientError) {
152+
if (e.isTimeout()) {
153+
return predefined.REQUEST_TIMEOUT;
154+
}
155+
}
156+
else if (e instanceof JsonRpcError) {
157+
return e;
158+
}
159+
160+
return predefined.INTERNAL_ERROR;
151161
}
152162
};
153163

packages/server/tests/acceptance/rpc.spec.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,14 @@ describe('RPC Server Acceptance Tests', function () {
209209
}
210210
});
211211

212+
it('should return empty result for non-existing `blockHash`', async () => {
213+
const logs = await relay.call('eth_getLogs', [{
214+
'blockHash': NON_EXISTING_BLOCK_HASH
215+
}]);
216+
expect(logs).to.exist;
217+
expect(logs.length).to.be.eq(0);
218+
});
219+
212220
it('should be able to use `topics` param', async () => {
213221
const logs = await relay.call('eth_getLogs', [{
214222
'fromBlock': log0Block.blockNumber,

0 commit comments

Comments
 (0)