Skip to content

Commit 61ae4d4

Browse files
authored
add specific error classes for SDKClient and MirrorNodeCLient (#382)
* add specific error classes for SDKClient and MirrorNodeCLient Signed-off-by: lukelee-sl <[email protected]> * address sonarCloud issue Signed-off-by: lukelee-sl <[email protected]> * fix spacing Signed-off-by: lukelee-sl <[email protected]> * revert spacing change Signed-off-by: lukelee-sl <[email protected]> * refactor to address code review comments Signed-off-by: lukelee-sl <[email protected]> * address sonarCloud issue Signed-off-by: lukelee-sl <[email protected]> * address code review comments Signed-off-by: lukelee-sl <[email protected]> * fix build error Signed-off-by: lukelee-sl <[email protected]>
1 parent acc4c9e commit 61ae4d4

File tree

12 files changed

+168
-81
lines changed

12 files changed

+168
-81
lines changed

packages/relay/src/index.ts

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

21-
import {Block, Log, Receipt, Transaction} from './lib/model';
22-
import {JsonRpcError} from './lib/errors';
21+
import { Block, Log, Receipt, Transaction } from './lib/model';
22+
import { JsonRpcError } from './lib/errors/JsonRpcError';
2323

2424
export { JsonRpcError };
2525

@@ -79,7 +79,7 @@ export interface Eth {
7979

8080
getTransactionByHash(hash: string): Promise<Transaction | null>;
8181

82-
getTransactionCount(address: string, blocknum: string): Promise<string>;
82+
getTransactionCount(address: string, blocknum: string): Promise<string | JsonRpcError>;
8383

8484
getTransactionReceipt(hash: string): Promise<Receipt | null>;
8585

packages/relay/src/lib/clients/mirrorNodeClient.ts

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

2121
import Axios, { AxiosInstance } from 'axios';
22-
import { predefined } from '../errors';
22+
import { MirrorNodeClientError } from './../errors/MirrorNodeClientError';
2323
import { Logger } from "pino";
2424
import constants from './../constants';
2525
import { Histogram, Registry } from 'prom-client';
@@ -167,7 +167,7 @@ export class MirrorNodeClient {
167167
}
168168

169169
this.logger.error(new Error(error.message), `[GET] ${path} ${effectiveStatusCode} status`);
170-
throw predefined.INTERNAL_ERROR;
170+
throw new MirrorNodeClientError(error.message, effectiveStatusCode);
171171
}
172172

173173
public async getAccountLatestTransactionByAddress(idOrAliasOrEvmAddress: string): Promise<object> {

packages/relay/src/lib/clients/sdkClient.ts

Lines changed: 30 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import { BigNumber } from '@hashgraph/sdk/lib/Transfer';
4545
import { Logger } from "pino";
4646
import { Gauge, Histogram, Registry } from 'prom-client';
4747
import constants from './../constants';
48+
import { SDKClientError } from './../errors/SDKClientError';
4849

4950
const _ = require('lodash');
5051

@@ -165,7 +166,7 @@ export class SDKClient {
165166
async getTinyBarGasFee(callerName: string): Promise<number> {
166167
const feeSchedules = await this.getFeeSchedule(callerName);
167168
if (_.isNil(feeSchedules.current) || feeSchedules.current?.transactionFeeSchedule === undefined) {
168-
throw new Error('Invalid FeeSchedules proto format');
169+
throw new SDKClientError({}, 'Invalid FeeSchedules proto format');
169170
}
170171

171172
for (const schedule of feeSchedules.current?.transactionFeeSchedule) {
@@ -177,7 +178,7 @@ export class SDKClient {
177178
}
178179
}
179180

180-
throw new Error(`${constants.ETH_FUNCTIONALITY_CODE} code not found in feeSchedule`);
181+
throw new SDKClientError({}, `${constants.ETH_FUNCTIONALITY_CODE} code not found in feeSchedule`);
181182
}
182183

183184
async getFileIdBytes(address: string, callerName: string): Promise<Uint8Array> {
@@ -252,20 +253,18 @@ export class SDKClient {
252253
return resp;
253254
}
254255
catch (e: any) {
255-
const statusCode = e.status ? e.status._code : Status.Unknown._code;
256-
this.logger.debug(`Consensus Node query response: ${query.constructor.name} ${statusCode}`);
257-
this.captureMetrics(
258-
SDKClient.queryMode,
259-
query.constructor.name,
260-
e.status,
261-
query._queryPayment?.toTinybars().toNumber(),
262-
callerName);
263-
264-
if (e.status && e.status._code) {
265-
throw new Error(e.message);
256+
const sdkClientError = new SDKClientError(e);
257+
if(sdkClientError.isValidNetworkError()) {
258+
this.logger.debug(`Consensus Node query response: ${query.constructor.name} ${sdkClientError.statusCode}`);
259+
this.captureMetrics(
260+
SDKClient.queryMode,
261+
query.constructor.name,
262+
sdkClientError.status,
263+
query._queryPayment?.toTinybars().toNumber(),
264+
callerName);
266265
}
267266

268-
throw e;
267+
throw sdkClientError;
269268
}
270269
};
271270

@@ -278,28 +277,25 @@ export class SDKClient {
278277
return resp;
279278
}
280279
catch (e: any) {
281-
const statusCode = e.status ? e.status._code : Status.Unknown._code;
282-
this.logger.info(`Consensus Node ${transactionType} transaction response: ${statusCode}`);
283-
this.captureMetrics(
284-
SDKClient.transactionMode,
285-
transactionType,
286-
statusCode,
287-
0,
288-
callerName);
289-
290-
// capture sdk transaction response errors and shorten familiar stack trace
291-
if (e.status && e.status._code) {
292-
throw new Error(e.message);
280+
const sdkClientError = new SDKClientError(e);
281+
if(sdkClientError.isValidNetworkError()) {
282+
this.logger.info(`Consensus Node ${transactionType} transaction response: ${sdkClientError.statusCode}`);
283+
this.captureMetrics(
284+
SDKClient.transactionMode,
285+
transactionType,
286+
sdkClientError.statusCode,
287+
0,
288+
callerName);
293289
}
294290

295-
throw e;
291+
throw sdkClientError;
296292
}
297293
};
298294

299295
executeGetTransactionRecord = async (resp: TransactionResponse, transactionName: string, callerName: string): Promise<TransactionRecord> => {
300296
try {
301297
if (!resp.getRecord) {
302-
throw new Error(`Invalid response format, expected record availability: ${JSON.stringify(resp)}`);
298+
throw new SDKClientError({}, `Invalid response format, expected record availability: ${JSON.stringify(resp)}`);
303299
}
304300

305301
const transactionRecord: TransactionRecord = await resp.getRecord(this.clientMain);
@@ -314,18 +310,17 @@ export class SDKClient {
314310
}
315311
catch (e: any) {
316312
// capture sdk record retrieval errors and shorten familiar stack trace
317-
if (e.status && e.status._code) {
313+
const sdkClientError = new SDKClientError(e);
314+
if(sdkClientError.isValidNetworkError()) {
318315
this.captureMetrics(
319316
SDKClient.transactionMode,
320317
transactionName,
321-
e.status,
318+
sdkClientError.status,
322319
0,
323320
callerName);
324-
325-
throw new Error(e.message);
326321
}
327322

328-
throw e;
323+
throw sdkClientError;
329324
}
330325
};
331326

@@ -353,7 +348,7 @@ export class SDKClient {
353348

354349
private static HbarToWeiBar(balance: AccountBalance): BigNumber {
355350
return balance.hbars
356-
.to(HbarUnit.Tinybar)
357-
.multipliedBy(constants.TINYBAR_TO_WEIBAR_COEF);
351+
.to(HbarUnit.Tinybar)
352+
.multipliedBy(constants.TINYBAR_TO_WEIBAR_COEF);
358353
}
359354
}
File renamed without changes.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
2+
/*-
3+
*
4+
* Hedera JSON RPC Relay
5+
*
6+
* Copyright (C) 2022 Hedera Hashgraph, LLC
7+
*
8+
* Licensed under the Apache License, Version 2.0 (the "License");
9+
* you may not use this file except in compliance with the License.
10+
* You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing, software
15+
* distributed under the License is distributed on an "AS IS" BASIS,
16+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
* See the License for the specific language governing permissions and
18+
* limitations under the License.
19+
*
20+
*/
21+
22+
export class MirrorNodeClientError extends Error {
23+
public statusCode: number;
24+
25+
constructor(message: string, statusCode: number) {
26+
super(message);
27+
this.statusCode = statusCode;
28+
29+
Object.setPrototypeOf(this, MirrorNodeClientError.prototype);
30+
}
31+
}
32+
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*-
2+
*
3+
* Hedera JSON RPC Relay
4+
*
5+
* Copyright (C) 2022 Hedera Hashgraph, LLC
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*
19+
*/
20+
21+
import { Status } from "@hashgraph/sdk";
22+
23+
export class SDKClientError extends Error {
24+
public status: Status = Status.Unknown;
25+
private validNetworkError: boolean = false;
26+
27+
constructor(e: any, message?: string) {
28+
super(e && e.status && e.status._code ? e.message : message);
29+
30+
if(e && e.status && e.status._code) {
31+
this.validNetworkError = true;
32+
this.status = e.status;
33+
}
34+
35+
Object.setPrototypeOf(this, SDKClientError.prototype);
36+
}
37+
38+
get statusCode(): number {
39+
return this.status._code;
40+
}
41+
42+
public isValidNetworkError(): boolean {
43+
return this.validNetworkError;
44+
}
45+
46+
public isInvalidAccountId(): boolean {
47+
return this.isValidNetworkError() && this.statusCode === Status.InvalidAccountId._code;
48+
}
49+
50+
public isInvalidContractId(): boolean {
51+
return this.isValidNetworkError() &&
52+
(this.statusCode === Status.InvalidContractId._code || this.message?.includes(Status.InvalidContractId.toString()));
53+
}
54+
}
55+

packages/relay/src/lib/eth.ts

Lines changed: 34 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,13 @@
1919
*/
2020

2121
import { Eth } from '../index';
22-
import { ContractId, Status, Hbar, EthereumTransaction } from '@hashgraph/sdk';
22+
import { ContractId, Hbar, EthereumTransaction } from '@hashgraph/sdk';
2323
import { BigNumber } from '@hashgraph/sdk/lib/Transfer';
2424
import { Logger } from 'pino';
2525
import { Block, Transaction, Log } from './model';
2626
import { MirrorNodeClient, SDKClient } from './clients';
27-
import { JsonRpcError, predefined } from './errors';
27+
import { JsonRpcError, predefined } from './errors/JsonRpcError';
28+
import { SDKClientError } from './errors/SDKClientError';
2829
import constants from './constants';
2930
import { Precheck } from './precheck';
3031

@@ -454,11 +455,13 @@ export class EthImpl implements Eth {
454455

455456
return EthImpl.numberTo0x(weibars);
456457
} catch (e: any) {
457-
// handle INVALID_ACCOUNT_ID
458-
if (e?.status?._code === Status.InvalidAccountId._code) {
459-
this.logger.debug(`Unable to find account ${account} in block ${JSON.stringify(blockNumber)}(${blockNumberOrTag}), returning 0x0 balance`);
460-
cache.set(cachedLabel, EthImpl.zeroHex, constants.CACHE_TTL.ONE_HOUR);
461-
return EthImpl.zeroHex;
458+
if(e instanceof SDKClientError) {
459+
// handle INVALID_ACCOUNT_ID
460+
if (e.isInvalidAccountId()) {
461+
this.logger.debug(`Unable to find account ${account} in block ${JSON.stringify(blockNumber)}(${blockNumberOrTag}), returning 0x0 balance`);
462+
cache.set(cachedLabel, EthImpl.zeroHex, constants.CACHE_TTL.ONE_HOUR);
463+
return EthImpl.zeroHex;
464+
}
462465
}
463466

464467
this.logger.error(e, 'Error raised during getBalance for account %s', account);
@@ -486,14 +489,18 @@ export class EthImpl implements Eth {
486489
const bytecode = await this.sdkClient.getContractByteCode(0, 0, address, EthImpl.ethGetCode);
487490
return EthImpl.prepend0x(Buffer.from(bytecode).toString('hex'));
488491
} catch (e: any) {
489-
// handle INVALID_CONTRACT_ID
490-
if (e?.status?._code === Status.InvalidContractId._code || e?.message?.includes(Status.InvalidContractId.toString())) {
491-
this.logger.debug('Unable to find code for contract %s in block "%s", returning 0x0, err code: %s', address, blockNumber, e?.status?._code);
492-
cache.set(cachedLabel, '0x0', constants.CACHE_TTL.ONE_HOUR);
493-
return '0x0';
492+
if(e instanceof SDKClientError) {
493+
// handle INVALID_CONTRACT_ID
494+
if (e.isInvalidContractId()) {
495+
this.logger.debug('Unable to find code for contract %s in block "%s", returning 0x0, err code: %s', address, blockNumber, e.statusCode);
496+
cache.set(cachedLabel, '0x0', constants.CACHE_TTL.ONE_HOUR);
497+
return '0x0';
498+
}
499+
this.logger.error(e, 'Error raised during getCode for address %s, err code: %s', address, e.statusCode);
500+
} else {
501+
this.logger.error(e, 'Error raised during getCode for address %s', address);
494502
}
495503

496-
this.logger.error(e, 'Error raised during getCode for address %s, err code: %s', address, e?.status?._code);
497504
throw e;
498505
}
499506
}
@@ -614,19 +621,24 @@ export class EthImpl implements Eth {
614621
* @param address
615622
* @param blockNumOrTag
616623
*/
617-
async getTransactionCount(address: string, blockNumOrTag: string): Promise<string> {
624+
async getTransactionCount(address: string, blockNumOrTag: string): Promise<string | JsonRpcError> {
618625
this.logger.trace('getTransactionCount(address=%s, blockNumOrTag=%s)', address, blockNumOrTag);
619626
const blockNumber = await this.translateBlockTag(blockNumOrTag);
620627
if (blockNumber === 0) {
621628
return '0x0';
622629
} else {
623-
const result = await this.mirrorNodeClient.resolveEntityType(address);
624-
if (result?.type === constants.TYPE_ACCOUNT) {
625-
const accountInfo = await this.sdkClient.getAccountInfo(result?.entity.account, EthImpl.ethGetTransactionCount);
626-
return EthImpl.numberTo0x(Number(accountInfo.ethereumNonce));
627-
}
628-
else if (result?.type === constants.TYPE_CONTRACT) {
629-
return EthImpl.numberTo0x(1);
630+
try {
631+
const result = await this.mirrorNodeClient.resolveEntityType(address);
632+
if (result?.type === constants.TYPE_ACCOUNT) {
633+
const accountInfo = await this.sdkClient.getAccountInfo(result?.entity.account, EthImpl.ethGetTransactionCount);
634+
return EthImpl.numberTo0x(Number(accountInfo.ethereumNonce));
635+
}
636+
else if (result?.type === constants.TYPE_CONTRACT) {
637+
return EthImpl.numberTo0x(1);
638+
}
639+
} catch (e: any) {
640+
this.logger.error(e, 'Error raised during getTransactionCount for address %s, block number or tag %s', address, blockNumOrTag);
641+
return predefined.INTERNAL_ERROR;
630642
}
631643

632644
return EthImpl.numberTo0x(0);
@@ -711,13 +723,7 @@ export class EthImpl implements Eth {
711723
// FIXME Is this right? Maybe so?
712724
return EthImpl.prepend0x(Buffer.from(contractCallResponse.asBytes()).toString('hex'));
713725
} catch (e: any) {
714-
// handle client error
715-
let resolvedError = e;
716-
if (e.status && e.status._code) {
717-
resolvedError = new Error(e.message);
718-
}
719-
720-
this.logger.error(resolvedError, 'Failed to successfully submit contractCallQuery');
726+
this.logger.error(e, 'Failed to successfully submit contractCallQuery');
721727
return predefined.INTERNAL_ERROR;
722728
}
723729
}

packages/relay/src/lib/precheck.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
*/
2020

2121
import * as ethers from 'ethers';
22-
import { predefined } from './errors';
22+
import { predefined } from './errors/JsonRpcError';
2323
import { MirrorNodeClient, SDKClient } from './clients';
2424
import { EthImpl } from './eth';
2525
import { Logger } from 'pino';

0 commit comments

Comments
 (0)