Skip to content

Commit

Permalink
add specific error classes for SDKClient and MirrorNodeCLient (#382)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
lukelee-sl committed Jul 27, 2022
1 parent acc4c9e commit 61ae4d4
Show file tree
Hide file tree
Showing 12 changed files with 168 additions and 81 deletions.
6 changes: 3 additions & 3 deletions packages/relay/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
*
*/

import {Block, Log, Receipt, Transaction} from './lib/model';
import {JsonRpcError} from './lib/errors';
import { Block, Log, Receipt, Transaction } from './lib/model';
import { JsonRpcError } from './lib/errors/JsonRpcError';

export { JsonRpcError };

Expand Down Expand Up @@ -79,7 +79,7 @@ export interface Eth {

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

getTransactionCount(address: string, blocknum: string): Promise<string>;
getTransactionCount(address: string, blocknum: string): Promise<string | JsonRpcError>;

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

Expand Down
4 changes: 2 additions & 2 deletions packages/relay/src/lib/clients/mirrorNodeClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
*/

import Axios, { AxiosInstance } from 'axios';
import { predefined } from '../errors';
import { MirrorNodeClientError } from './../errors/MirrorNodeClientError';
import { Logger } from "pino";
import constants from './../constants';
import { Histogram, Registry } from 'prom-client';
Expand Down Expand Up @@ -167,7 +167,7 @@ export class MirrorNodeClient {
}

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

public async getAccountLatestTransactionByAddress(idOrAliasOrEvmAddress: string): Promise<object> {
Expand Down
65 changes: 30 additions & 35 deletions packages/relay/src/lib/clients/sdkClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import { BigNumber } from '@hashgraph/sdk/lib/Transfer';
import { Logger } from "pino";
import { Gauge, Histogram, Registry } from 'prom-client';
import constants from './../constants';
import { SDKClientError } from './../errors/SDKClientError';

const _ = require('lodash');

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

for (const schedule of feeSchedules.current?.transactionFeeSchedule) {
Expand All @@ -177,7 +178,7 @@ export class SDKClient {
}
}

throw new Error(`${constants.ETH_FUNCTIONALITY_CODE} code not found in feeSchedule`);
throw new SDKClientError({}, `${constants.ETH_FUNCTIONALITY_CODE} code not found in feeSchedule`);
}

async getFileIdBytes(address: string, callerName: string): Promise<Uint8Array> {
Expand Down Expand Up @@ -252,20 +253,18 @@ export class SDKClient {
return resp;
}
catch (e: any) {
const statusCode = e.status ? e.status._code : Status.Unknown._code;
this.logger.debug(`Consensus Node query response: ${query.constructor.name} ${statusCode}`);
this.captureMetrics(
SDKClient.queryMode,
query.constructor.name,
e.status,
query._queryPayment?.toTinybars().toNumber(),
callerName);

if (e.status && e.status._code) {
throw new Error(e.message);
const sdkClientError = new SDKClientError(e);
if(sdkClientError.isValidNetworkError()) {
this.logger.debug(`Consensus Node query response: ${query.constructor.name} ${sdkClientError.statusCode}`);
this.captureMetrics(
SDKClient.queryMode,
query.constructor.name,
sdkClientError.status,
query._queryPayment?.toTinybars().toNumber(),
callerName);
}

throw e;
throw sdkClientError;
}
};

Expand All @@ -278,28 +277,25 @@ export class SDKClient {
return resp;
}
catch (e: any) {
const statusCode = e.status ? e.status._code : Status.Unknown._code;
this.logger.info(`Consensus Node ${transactionType} transaction response: ${statusCode}`);
this.captureMetrics(
SDKClient.transactionMode,
transactionType,
statusCode,
0,
callerName);

// capture sdk transaction response errors and shorten familiar stack trace
if (e.status && e.status._code) {
throw new Error(e.message);
const sdkClientError = new SDKClientError(e);
if(sdkClientError.isValidNetworkError()) {
this.logger.info(`Consensus Node ${transactionType} transaction response: ${sdkClientError.statusCode}`);
this.captureMetrics(
SDKClient.transactionMode,
transactionType,
sdkClientError.statusCode,
0,
callerName);
}

throw e;
throw sdkClientError;
}
};

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

const transactionRecord: TransactionRecord = await resp.getRecord(this.clientMain);
Expand All @@ -314,18 +310,17 @@ export class SDKClient {
}
catch (e: any) {
// capture sdk record retrieval errors and shorten familiar stack trace
if (e.status && e.status._code) {
const sdkClientError = new SDKClientError(e);
if(sdkClientError.isValidNetworkError()) {
this.captureMetrics(
SDKClient.transactionMode,
transactionName,
e.status,
sdkClientError.status,
0,
callerName);

throw new Error(e.message);
}

throw e;
throw sdkClientError;
}
};

Expand Down Expand Up @@ -353,7 +348,7 @@ export class SDKClient {

private static HbarToWeiBar(balance: AccountBalance): BigNumber {
return balance.hbars
.to(HbarUnit.Tinybar)
.multipliedBy(constants.TINYBAR_TO_WEIBAR_COEF);
.to(HbarUnit.Tinybar)
.multipliedBy(constants.TINYBAR_TO_WEIBAR_COEF);
}
}
File renamed without changes.
32 changes: 32 additions & 0 deletions packages/relay/src/lib/errors/MirrorNodeClientError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@

/*-
*
* Hedera JSON RPC Relay
*
* Copyright (C) 2022 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.
*
*/

export class MirrorNodeClientError extends Error {
public statusCode: number;

constructor(message: string, statusCode: number) {
super(message);
this.statusCode = statusCode;

Object.setPrototypeOf(this, MirrorNodeClientError.prototype);
}
}

55 changes: 55 additions & 0 deletions packages/relay/src/lib/errors/SDKClientError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*-
*
* Hedera JSON RPC Relay
*
* Copyright (C) 2022 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 { Status } from "@hashgraph/sdk";

export class SDKClientError extends Error {
public status: Status = Status.Unknown;
private validNetworkError: boolean = false;

constructor(e: any, message?: string) {
super(e && e.status && e.status._code ? e.message : message);

if(e && e.status && e.status._code) {
this.validNetworkError = true;
this.status = e.status;
}

Object.setPrototypeOf(this, SDKClientError.prototype);
}

get statusCode(): number {
return this.status._code;
}

public isValidNetworkError(): boolean {
return this.validNetworkError;
}

public isInvalidAccountId(): boolean {
return this.isValidNetworkError() && this.statusCode === Status.InvalidAccountId._code;
}

public isInvalidContractId(): boolean {
return this.isValidNetworkError() &&
(this.statusCode === Status.InvalidContractId._code || this.message?.includes(Status.InvalidContractId.toString()));
}
}

62 changes: 34 additions & 28 deletions packages/relay/src/lib/eth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,13 @@
*/

import { Eth } from '../index';
import { ContractId, Status, Hbar, EthereumTransaction } from '@hashgraph/sdk';
import { ContractId, Hbar, EthereumTransaction } from '@hashgraph/sdk';
import { BigNumber } from '@hashgraph/sdk/lib/Transfer';
import { Logger } from 'pino';
import { Block, Transaction, Log } from './model';
import { MirrorNodeClient, SDKClient } from './clients';
import { JsonRpcError, predefined } from './errors';
import { JsonRpcError, predefined } from './errors/JsonRpcError';
import { SDKClientError } from './errors/SDKClientError';
import constants from './constants';
import { Precheck } from './precheck';

Expand Down Expand Up @@ -454,11 +455,13 @@ export class EthImpl implements Eth {

return EthImpl.numberTo0x(weibars);
} catch (e: any) {
// handle INVALID_ACCOUNT_ID
if (e?.status?._code === Status.InvalidAccountId._code) {
this.logger.debug(`Unable to find account ${account} in block ${JSON.stringify(blockNumber)}(${blockNumberOrTag}), returning 0x0 balance`);
cache.set(cachedLabel, EthImpl.zeroHex, constants.CACHE_TTL.ONE_HOUR);
return EthImpl.zeroHex;
if(e instanceof SDKClientError) {
// handle INVALID_ACCOUNT_ID
if (e.isInvalidAccountId()) {
this.logger.debug(`Unable to find account ${account} in block ${JSON.stringify(blockNumber)}(${blockNumberOrTag}), returning 0x0 balance`);
cache.set(cachedLabel, EthImpl.zeroHex, constants.CACHE_TTL.ONE_HOUR);
return EthImpl.zeroHex;
}
}

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

this.logger.error(e, 'Error raised during getCode for address %s, err code: %s', address, e?.status?._code);
throw e;
}
}
Expand Down Expand Up @@ -614,19 +621,24 @@ export class EthImpl implements Eth {
* @param address
* @param blockNumOrTag
*/
async getTransactionCount(address: string, blockNumOrTag: string): Promise<string> {
async getTransactionCount(address: string, blockNumOrTag: string): Promise<string | JsonRpcError> {
this.logger.trace('getTransactionCount(address=%s, blockNumOrTag=%s)', address, blockNumOrTag);
const blockNumber = await this.translateBlockTag(blockNumOrTag);
if (blockNumber === 0) {
return '0x0';
} else {
const result = await this.mirrorNodeClient.resolveEntityType(address);
if (result?.type === constants.TYPE_ACCOUNT) {
const accountInfo = await this.sdkClient.getAccountInfo(result?.entity.account, EthImpl.ethGetTransactionCount);
return EthImpl.numberTo0x(Number(accountInfo.ethereumNonce));
}
else if (result?.type === constants.TYPE_CONTRACT) {
return EthImpl.numberTo0x(1);
try {
const result = await this.mirrorNodeClient.resolveEntityType(address);
if (result?.type === constants.TYPE_ACCOUNT) {
const accountInfo = await this.sdkClient.getAccountInfo(result?.entity.account, EthImpl.ethGetTransactionCount);
return EthImpl.numberTo0x(Number(accountInfo.ethereumNonce));
}
else if (result?.type === constants.TYPE_CONTRACT) {
return EthImpl.numberTo0x(1);
}
} catch (e: any) {
this.logger.error(e, 'Error raised during getTransactionCount for address %s, block number or tag %s', address, blockNumOrTag);
return predefined.INTERNAL_ERROR;
}

return EthImpl.numberTo0x(0);
Expand Down Expand Up @@ -711,13 +723,7 @@ export class EthImpl implements Eth {
// FIXME Is this right? Maybe so?
return EthImpl.prepend0x(Buffer.from(contractCallResponse.asBytes()).toString('hex'));
} catch (e: any) {
// handle client error
let resolvedError = e;
if (e.status && e.status._code) {
resolvedError = new Error(e.message);
}

this.logger.error(resolvedError, 'Failed to successfully submit contractCallQuery');
this.logger.error(e, 'Failed to successfully submit contractCallQuery');
return predefined.INTERNAL_ERROR;
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/relay/src/lib/precheck.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
*/

import * as ethers from 'ethers';
import { predefined } from './errors';
import { predefined } from './errors/JsonRpcError';
import { MirrorNodeClient, SDKClient } from './clients';
import { EthImpl } from './eth';
import { Logger } from 'pino';
Expand Down
Loading

0 comments on commit 61ae4d4

Please sign in to comment.