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

add specific error classes for SDKClient and MirrorNodeCLient #382

Merged
merged 10 commits into from
Jul 27, 2022
4 changes: 2 additions & 2 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
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) {
lukelee-sl marked this conversation as resolved.
Show resolved Hide resolved
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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,4 @@ export const predefined = {
code: -32602,
message: 'Value below 10_000_000_000 wei which is 1 tinybar'
}),
};
};
lukelee-sl marked this conversation as resolved.
Show resolved Hide resolved
31 changes: 31 additions & 0 deletions packages/relay/src/lib/errors/MirrorNodeClientError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@

/*-
*
* 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);
}
}
lukelee-sl marked this conversation as resolved.
Show resolved Hide resolved
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()));
}
}

60 changes: 33 additions & 27 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 @@ -620,13 +627,18 @@ export class EthImpl implements Eth {
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);
throw e;
lukelee-sl marked this conversation as resolved.
Show resolved Hide resolved
}

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