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

Remove AcceptedErrorCodes from MirrorNodeClient - GET_ACCOUNTS_BY_ID_ENDPOINT #1366

Closed
Closed
Show file tree
Hide file tree
Changes from all 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
5 changes: 3 additions & 2 deletions packages/relay/src/lib/clients/mirrorNodeClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export class MirrorNodeClient {
private static CONTRACT_RESULT_LOGS_PROPERTY = 'logs';

static acceptedErrorStatusesResponsePerRequestPathMap: Map<string, Array<number>> = new Map([
[MirrorNodeClient.GET_ACCOUNTS_BY_ID_ENDPOINT, [400, 404]],
[MirrorNodeClient.GET_ACCOUNTS_BY_ID_ENDPOINT, []],
[MirrorNodeClient.GET_BALANCE_ENDPOINT, [400, 404]],
[MirrorNodeClient.GET_BLOCK_ENDPOINT, [400, 404]],
[MirrorNodeClient.GET_BLOCKS_ENDPOINT, [400, 404]],
Expand Down Expand Up @@ -325,7 +325,8 @@ export class MirrorNodeClient {
this.logger.error(new Error(error.message), `${requestIdPrefix} [${method}] ${path} ${effectiveStatusCode} status`);

// we only need contract revert errors here as it's not the same as not supported
if (mirrorError.isContractReverted() && !mirrorError.isNotSupported() && !mirrorError.isNotSupportedSystemContractOperaton()) {
// Contract Revert error is valid only for contract calls
if (pathLabel == MirrorNodeClient.CONTRACT_CALL_ENDPOINT && mirrorError.isContractReverted() && !mirrorError.isNotSupported() && !mirrorError.isNotSupportedSystemContractOperaton()) {
throw predefined.CONTRACT_REVERT(mirrorError.errorMessage);
}

Expand Down
60 changes: 47 additions & 13 deletions packages/relay/src/lib/eth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -496,7 +496,7 @@ export class EthImpl implements Eth {
const accountCacheKey = `${constants.CACHE_KEY.ACCOUNT}_${transaction.to}`;
let toAccount: object | null = this.cache.get(accountCacheKey);
if (!toAccount) {
toAccount = await this.mirrorNodeClient.getAccount(transaction.to, requestId);
toAccount = await this.getAccountOrNull(transaction.to, requestId);
}

// when account exists return default base gas, otherwise return the minimum amount of gas to create an account entity
Expand All @@ -521,6 +521,31 @@ export class EthImpl implements Eth {
return gas;
}

/**
* Gets the account data using the mirror node and handles the potential 404 error and returns null instead.
* */
async getAccountOrNull(address: string, requestId?: string) {
let account;
try {
account = await this.mirrorNodeClient.getAccount(address, requestId);
} catch (error: any) {
if(error instanceof MirrorNodeClientError) {
if(error.statusCode == 404){
return null;

} else if(error.statusCode == 400){
this.logger.debug(`${formatRequestIdMessage(requestId)} Got Invalid Parameter when trying to fetch account from mirror node: ${JSON.stringify(error)}`);
throw predefined.INVALID_PARAMETER(address, `Invalid 'address' field in transaction param. Address must be a valid 20 bytes hex string`);
}
} else {
this.logger.error(`${formatRequestIdMessage(requestId)} Unexpected error raised while fetching account from mirror-node: ${JSON.stringify(error)}`);
throw error;
}
}

return account;
}

/**
* Gets the current gas price of the network.
*/
Expand Down Expand Up @@ -761,7 +786,16 @@ export class EthImpl implements Eth {
let blockNumber = null;
let balanceFound = false;
let weibars = BigInt(0);
const mirrorAccount = await this.mirrorNodeClient.getAccountPageLimit(account, requestId);
let mirrorAccount;
try {
mirrorAccount = await this.mirrorNodeClient.getAccountPageLimit(account, requestId);
} catch (error) {
if(error instanceof MirrorNodeClientError && error.statusCode === 404) {
mirrorAccount = null;
} else {
throw error;
}
}

try {
if (!EthImpl.blockTagIsLatestOrPending(blockNumberOrTag)) {
Expand Down Expand Up @@ -1088,7 +1122,7 @@ export class EthImpl implements Eth {
// if latest or pending, get latest ethereumNonce from mirror node account API
nonceCount = await this.getAccountLatestEthereumNonce(address, requestId);
} else if (blockNumOrTag === EthImpl.blockEarliest) {
nonceCount = await this.getAccountNonceForEarliestBlock(requestId);
nonceCount = await this.getAccountNonceForEarliestBlock(requestId);
} else if (!isNaN(blockNum)) {
nonceCount = await this.getAccountNonceForHistoricBlock(address, blockNum, requestId);
} else {
Expand All @@ -1102,7 +1136,7 @@ export class EthImpl implements Eth {

const cacheTtl = blockNumOrTag === EthImpl.blockEarliest || !isNaN(blockNum) ? constants.CACHE_TTL.ONE_DAY : this.ethGetTransactionCountCacheTtl; // cache historical values longer as they don't change
this.logger.trace(`${requestIdPrefix} caching ${cacheKey}:${nonceCount} for ${cacheTtl} ms`);
this.cache.set(cacheKey, nonceCount, { ttl: cacheTtl });
this.cache.set(cacheKey, nonceCount, { ttl: cacheTtl });

return nonceCount;
}
Expand Down Expand Up @@ -1400,7 +1434,7 @@ export class EthImpl implements Eth {
const accountCacheKey = `${constants.CACHE_KEY.ACCOUNT}_${fromAddress}`;
let accountResult: any | null = this.cache.get(accountCacheKey);
if (!accountResult) {
accountResult = await this.mirrorNodeClient.getAccount(fromAddress, requestId);
accountResult = await this.getAccountOrNull(fromAddress, requestId);
if (accountResult) {
this.logger.trace(`${requestIdPrefix} caching ${accountCacheKey}:${JSON.stringify(accountResult)} for ${constants.CACHE_TTL.ONE_HOUR} ms`);
this.cache.set(accountCacheKey, accountResult);
Expand Down Expand Up @@ -1893,10 +1927,10 @@ export class EthImpl implements Eth {
const validContract = await this.mirrorNodeClient.isValidContract(address, requestId);
if (validContract) {
return EthImpl.oneHex;
}
}

// get latest ethereumNonce from mirror node account API
const mirrorAccount = await this.mirrorNodeClient.getAccount(address, requestId);
const mirrorAccount = await this.getAccountOrNull(address, requestId);
if (mirrorAccount?.ethereum_nonce) {
return EthImpl.numberTo0x(mirrorAccount.ethereum_nonce);
}
Expand All @@ -1907,7 +1941,7 @@ export class EthImpl implements Eth {
/**
* Returns the number of transactions sent from an address by searching for the ethereum transaction involving the address
* Remove when https://github.com/hashgraph/hedera-mirror-node/issues/5862 is implemented
*
*
* @param address string
* @param blockNum string
* @param requestId string
Expand All @@ -1918,7 +1952,7 @@ export class EthImpl implements Eth {
const block = await this.mirrorNodeClient.getBlock(blockNum, requestId); // consider caching error responses
if (block == null) {
throw predefined.UNKNOWN_BLOCK;
}
}

// get the latest 2 ethereum transactions for the account
const ethereumTransactions = await this.mirrorNodeClient.getAccountLatestEthereumTransactionsByTimestamp(address, block.timestamp.to, 2, requestId);
Expand Down Expand Up @@ -1956,10 +1990,10 @@ export class EthImpl implements Eth {
if (block.number <= 1) {
// if the earliest block is the genesis block or 1 , then the nonce is 0 as only system accounts are present
return EthImpl.zeroHex;
}
}

// note the mirror node may be a partial one, in which case there may be a valid block with number greater 1.
throw predefined.INTERNAL_ERROR(`Partial mirror node encountered, earliest block number is ${block.number}`);
throw predefined.INTERNAL_ERROR(`Partial mirror node encountered, earliest block number is ${block.number}`);
}

private async getAccountNonceForHistoricBlock(address: string, blockNum: number, requestId?: string): Promise<string> {
Expand Down
13 changes: 9 additions & 4 deletions packages/relay/src/lib/precheck.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { ethers, Transaction } from 'ethers';
import { formatRequestIdMessage } from '../formatters';
import HAPIService from './services/hapiService/hapiService';
import { SDKClientError } from './errors/SDKClientError';
import {MirrorNodeClientError} from "./errors/MirrorNodeClientError";

export class Precheck {
private mirrorNodeClient: MirrorNodeClient;
Expand Down Expand Up @@ -145,10 +146,14 @@ export class Precheck {
const txTotalValue = tx.value.add(txGas.mul(tx.gasLimit));
let tinybars;

const accountResponse: any = await this.mirrorNodeClient.getAccount(tx.from!, requestId);
if (accountResponse == null) {
this.logger.trace(`${requestIdPrefix} Failed to retrieve account details from mirror node on balance precheck for sendRawTransaction(transaction=${JSON.stringify(tx)}, totalValue=${txTotalValue})`);
throw predefined.RESOURCE_NOT_FOUND(`tx.from '${tx.from}'.`);
let accountResponse: any;
try {
accountResponse = await this.mirrorNodeClient.getAccount(tx.from!, requestId);
} catch (error) {
if(error instanceof MirrorNodeClientError && error.statusCode === 404) {
this.logger.trace(`${requestIdPrefix} Failed to retrieve account details from mirror node on balance precheck for sendRawTransaction(transaction=${JSON.stringify(tx)}, totalValue=${txTotalValue})`);
throw predefined.RESOURCE_NOT_FOUND(`tx.from '${tx.from}'.`);
}
}

try {
Expand Down
39 changes: 39 additions & 0 deletions packages/relay/tests/lib/eth.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3407,6 +3407,45 @@ describe('Eth calls using MirrorNode', async function () {
});
});

describe('getAccountOrNull helper', async function () {
it('getAccountOrNull with no account found', async function () {
restMock.onGet(`accounts/${contractAddress1}${limitOrderPostFix}`).reply(404);
const account = await ethImpl.getAccountOrNull(contractAddress1);
expect(account).to.be.null;
});

it('getAccountOrNull with account found', async function () {
restMock.onGet(`accounts/${contractAddress1}${limitOrderPostFix}`).replyOnce(200, defaultContract);
const account = await ethImpl.getAccountOrNull(contractAddress1);
expect(account).to.deep.equal(defaultContract);
});

it('getAccountOrNull with invalid parameter throws error', async function () {
const invalidAddress = "0x123";
restMock.onGet(`accounts/${invalidAddress}${limitOrderPostFix}`).reply(400);
let errorRaised = false;
try {
await ethImpl.getAccountOrNull(invalidAddress);
} catch (error: any) {
errorRaised = true;
expect(error.message).to.equal(`Invalid parameter ${invalidAddress}: Invalid 'address' field in transaction param. Address must be a valid 20 bytes hex string`);
}
expect(errorRaised).to.be.true;
});

it('getAccountOrNull with valid parameter throws unexpected error', async function () {
restMock.onGet(`accounts/${contractAddress1}${limitOrderPostFix}`).replyOnce(500, { error: 'unexpected error' });
let errorRaised = false;
try {
await ethImpl.getAccountOrNull(contractAddress1);
}
catch (error: any) {
errorRaised = true;
expect(error.message).to.equal(`Request failed with status code 500`);
}
});
});

describe('eth_call precheck failures', async function () {
let callConsensusNodeSpy: sinon.SinonSpy;
let callMirrorNodeSpy: sinon.SinonSpy;
Expand Down
7 changes: 5 additions & 2 deletions packages/relay/tests/lib/mirrorNodeClient.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -373,9 +373,12 @@ describe('MirrorNodeClient', async function () {
it('`getAccount` not found', async () => {
const evmAddress = '0x00000000000000000000000000000000000003f6';
mock.onGet(`accounts/${evmAddress}${limitOrderPostFix}`).reply(404, mockData.notFound);
try {
await mirrorNodeInstance.getAccount(evmAddress);
} catch (error) {
expect(error.statusCode).equal(404);
}

const result = await mirrorNodeInstance.getAccount(evmAddress);
expect(result).to.be.null;
});

it('`getTokenById`', async () => {
Expand Down