From 364ec8c2f2b015db75e93cdb5e0b459989f9c79d Mon Sep 17 00:00:00 2001 From: Nana-EC Date: Tue, 5 Jul 2022 22:53:15 -0500 Subject: [PATCH 1/3] Improve logging, metrics and error handling Signed-off-by: Nana-EC --- packages/relay/src/lib/clients/sdkClient.ts | 130 +++++++------------ packages/relay/src/lib/eth.ts | 69 ++++++---- packages/relay/src/lib/precheck.ts | 6 +- packages/relay/src/lib/relay.ts | 58 ++++----- packages/relay/tests/lib/eth.spec.ts | 99 +++++++++++++- packages/server/tests/clients/relayClient.ts | 2 +- 6 files changed, 217 insertions(+), 147 deletions(-) diff --git a/packages/relay/src/lib/clients/sdkClient.ts b/packages/relay/src/lib/clients/sdkClient.ts index 05a311573e..316d371c4c 100644 --- a/packages/relay/src/lib/clients/sdkClient.ts +++ b/packages/relay/src/lib/clients/sdkClient.ts @@ -74,12 +74,14 @@ export class SDKClient { private consensusNodeClientHistorgram; private operatorAccountGauge; + private operatorAccountId; // populate with consensusnode requests via SDK constructor(clientMain: Client, logger: Logger, register: Registry) { this.clientMain = clientMain; this.logger = logger; this.register = register; + this.operatorAccountId = clientMain.operatorAccountId!.toString(); // clear and create metrics in registry const metricHistogramName = 'rpc_relay_consensusnode_response'; @@ -87,7 +89,7 @@ export class SDKClient { this.consensusNodeClientHistorgram = new Histogram({ name: metricHistogramName, help: 'Relay consensusnode mode type status cost histogram', - labelNames: ['mode', 'type', 'status'], + labelNames: ['mode', 'type', 'status', 'caller'], registers: [register] }); @@ -96,7 +98,7 @@ export class SDKClient { this.operatorAccountGauge = new Gauge({ name: metricGaugeName, help: 'Relay operator balance gauge', - labelNames: ['mode', 'type'], + labelNames: ['mode', 'type', 'accountId'], registers: [register], async collect() { // Invoked when the registry collects its metrics' values. @@ -105,59 +107,59 @@ export class SDKClient { const accountBalance = await (new AccountBalanceQuery() .setAccountId(clientMain.operatorAccountId!)) .execute(clientMain); - this.set(accountBalance.hbars.toTinybars().toNumber()); + this.labels({ 'accountId': clientMain.operatorAccountId!.toString() }) + .set(accountBalance.hbars.toTinybars().toNumber()); } catch (e: any) { - logger.error(e, `Error collecting operator balance. Setting 0 default`); - this.set(0); + logger.error(e, `Error collecting operator balance. Skipping balance set`); } }, }); } - async getAccountBalance(account: string): Promise { + async getAccountBalance(account: string, callerName: string): Promise { return this.executeQuery(new AccountBalanceQuery() - .setAccountId(AccountId.fromString(account)), this.clientMain); + .setAccountId(AccountId.fromString(account)), this.clientMain, callerName); } - async getAccountBalanceInWeiBar(account: string): Promise { - const balance = await this.getAccountBalance(account); + async getAccountBalanceInWeiBar(account: string, callerName: string): Promise { + const balance = await this.getAccountBalance(account, callerName); return SDKClient.HbarToWeiBar(balance); } - async getAccountInfo(address: string): Promise { + async getAccountInfo(address: string, callerName: string): Promise { return this.executeQuery(new AccountInfoQuery() - .setAccountId(AccountId.fromString(address)), this.clientMain); + .setAccountId(AccountId.fromString(address)), this.clientMain, callerName); } - async getContractByteCode(shard: number | Long, realm: number | Long, address: string): Promise { + async getContractByteCode(shard: number | Long, realm: number | Long, address: string, callerName: string): Promise { return this.executeQuery(new ContractByteCodeQuery() - .setContractId(ContractId.fromEvmAddress(shard, realm, address)), this.clientMain); + .setContractId(ContractId.fromEvmAddress(shard, realm, address)), this.clientMain, callerName); } - async getContractBalance(contract: string): Promise { + async getContractBalance(contract: string, callerName: string): Promise { return this.executeQuery(new AccountBalanceQuery() - .setContractId(ContractId.fromString(contract)), this.clientMain); + .setContractId(ContractId.fromString(contract)), this.clientMain, callerName); } - async getContractBalanceInWeiBar(account: string): Promise { - const balance = await this.getContractBalance(account); + async getContractBalanceInWeiBar(account: string, callerName: string): Promise { + const balance = await this.getContractBalance(account, callerName); return SDKClient.HbarToWeiBar(balance); } - async getExchangeRate(): Promise { - const exchangeFileBytes = await this.getFileIdBytes(constants.EXCHANGE_RATE_FILE_ID); + async getExchangeRate(callerName: string): Promise { + const exchangeFileBytes = await this.getFileIdBytes(constants.EXCHANGE_RATE_FILE_ID, callerName); return ExchangeRates.fromBytes(exchangeFileBytes); } - async getFeeSchedule(): Promise { - const feeSchedulesFileBytes = await this.getFileIdBytes(constants.FEE_SCHEDULE_FILE_ID); + async getFeeSchedule(callerName: string): Promise { + const feeSchedulesFileBytes = await this.getFileIdBytes(constants.FEE_SCHEDULE_FILE_ID, callerName); return FeeSchedules.fromBytes(feeSchedulesFileBytes); } - async getTinyBarGasFee(): Promise { - const feeSchedules = await this.getFeeSchedule(); + async getTinyBarGasFee(callerName: string): Promise { + const feeSchedules = await this.getFeeSchedule(callerName); if (_.isNil(feeSchedules.current) || feeSchedules.current?.transactionFeeSchedule === undefined) { throw new Error('Invalid FeeSchedules proto format'); } @@ -165,7 +167,7 @@ export class SDKClient { for (const schedule of feeSchedules.current?.transactionFeeSchedule) { if (schedule.hederaFunctionality?._code === constants.ETH_FUNCTIONALITY_CODE && schedule.fees !== undefined) { // get exchange rate & convert to tiny bar - const exchangeRates = await this.getExchangeRate(); + const exchangeRates = await this.getExchangeRate(callerName); return this.convertGasPriceToTinyBars(schedule.fees[0].servicedata, exchangeRates); } @@ -174,9 +176,9 @@ export class SDKClient { throw new Error(`${constants.ETH_FUNCTIONALITY_CODE} code not found in feeSchedule`); } - async getFileIdBytes(address: string): Promise { + async getFileIdBytes(address: string, callerName: string): Promise { return this.executeQuery(new FileContentsQuery() - .setFileId(address), this.clientMain); + .setFileId(address), this.clientMain, callerName); } async getRecord(transactionResponse: TransactionResponse) { @@ -188,7 +190,7 @@ export class SDKClient { .setEthereumData(transactionBuffer)); } - async submitContractCallQuery(to: string, data: string, gas: number): Promise { + async submitContractCallQuery(to: string, data: string, gas: number, callerName: string): Promise { const contract = SDKClient.prune0x(to); const callData = SDKClient.prune0x(data); const contractId = contract.startsWith("00000000000") @@ -208,7 +210,7 @@ export class SDKClient { const cost = await contractCallQuery .getCost(this.clientMain); return this.executeQuery(contractCallQuery - .setQueryPayment(cost), this.clientMain); + .setQueryPayment(cost), this.clientMain, callerName); } private convertGasPriceToTinyBars = (feeComponents: FeeComponents | undefined, exchangeRates: ExchangeRates) => { @@ -223,7 +225,7 @@ export class SDKClient { ); }; - private executeQuery = async (query: Query, client: Client) => { + private executeQuery = async (query: Query, client: Client, callerName: string) => { try { const resp = await query.execute(client); this.logger.info(`Consensus Node query response: ${query.constructor.name} ${Status.Success._code}`); @@ -234,7 +236,8 @@ export class SDKClient { SDKClient.queryMode, query.constructor.name, Status.Success, - query._queryPayment?.toTinybars().toNumber()); + query._queryPayment?.toTinybars().toNumber(), + callerName); return resp; } catch (e: any) { @@ -244,7 +247,8 @@ export class SDKClient { SDKClient.queryMode, query.constructor.name, e.status, - query._queryPayment?.toTinybars().toNumber()); + query._queryPayment?.toTinybars().toNumber(), + callerName); if (e.status && e.status._code) { throw new Error(e.message); @@ -266,7 +270,7 @@ export class SDKClient { const statusCode = e.status ? e.status._code : Status.Unknown._code; this.logger.info(`Consensus Node ${transactionType} transaction response: ${statusCode}`); - // capture sdk transaction response errorsand shorten familiar stack trace + // capture sdk transaction response errors and shorten familiar stack trace if (e.status && e.status._code) { throw new Error(e.message); } @@ -275,23 +279,7 @@ export class SDKClient { } }; - private executeAndGetTransactionReceipt = async (transaction: Transaction): Promise => { - let resp; - try { - resp = await this.executeTransaction(transaction); - return resp.getReceipt(this.clientMain); - } - catch (e: any) { - // capture sdk receipt retrieval errors and shorten familiar stack trace - if (e.status && e.status._code) { - throw new Error(e.message); - } - - throw e; - } - }; - - executeGetTransactionRecord = async (resp: TransactionResponse, transactionName: string): Promise => { + executeGetTransactionRecord = async (resp: TransactionResponse, transactionName: string, callerName: string): Promise => { try { if (!resp.getRecord) { throw new Error(`Invalid response format, expected record availability: ${JSON.stringify(resp)}`); @@ -303,7 +291,8 @@ export class SDKClient { SDKClient.transactionMode, transactionName, transactionRecord.receipt.status, - transactionRecord.transactionFee.toTinybars().toNumber()); + transactionRecord.transactionFee.toTinybars().toNumber(), + callerName); return transactionRecord; } catch (e: any) { @@ -313,7 +302,8 @@ export class SDKClient { SDKClient.transactionMode, transactionName, e.status, - 0); + 0, + callerName); throw new Error(e.message); } @@ -322,47 +312,17 @@ export class SDKClient { } }; - private captureMetrics = (mode, type, status, cost) => { + private captureMetrics = (mode, type, status, cost, caller) => { const resolvedCost = cost ? cost : 0; this.consensusNodeClientHistorgram.labels( mode, type, - status) + status, + caller) .observe(resolvedCost); - this.operatorAccountGauge.labels(mode, type).dec(cost); + this.operatorAccountGauge.labels(mode, type, this.operatorAccountId).dec(cost); }; - /** - * Internal helper method that converts an ethAddress (with, or without a leading 0x) - * into an alias friendly AccountId. - * @param ethAddress - * @private - */ - private static toAccountId(ethAddress: string) { - return AccountId.fromEvmAddress(0, 0, SDKClient.prune0x(ethAddress)); - } - - /** - * Internal helper method that converts an ethAddress (with, or without a leading 0x) - * into an alias friendly ContractId. - * @param ethAddress - * @private - */ - private static toContractId(ethAddress: string) { - return ContractId.fromSolidityAddress(SDKClient.prepend0x(ethAddress)); - } - - /** - * Internal helper method that prepends a leading 0x if there isn't one. - * @param input - * @private - */ - private static prepend0x(input: string): string { - return input.startsWith('0x') - ? input - : '0x' + input; - } - /** * Internal helper method that removes the leading 0x if there is one. * @param input diff --git a/packages/relay/src/lib/eth.ts b/packages/relay/src/lib/eth.ts index d0e58a31c4..ee6217c4a0 100644 --- a/packages/relay/src/lib/eth.ts +++ b/packages/relay/src/lib/eth.ts @@ -55,8 +55,19 @@ export class EthImpl implements Eth { static ethTxType = 'EthereumTransaction'; static ethEmptyTrie = '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421'; static defaultGasUsedRatio = EthImpl.numberTo0x(0.5); - static feeHistoryZeroBlockCountResponse = {gasUsedRatio:null, oldestBlock:EthImpl.zeroHex}; - static feeHistoryEmptyResponse = {baseFeePerGas:[], gasUsedRatio:[], reward:[], oldestBlock:EthImpl.zeroHex}; + static feeHistoryZeroBlockCountResponse = { gasUsedRatio: null, oldestBlock: EthImpl.zeroHex }; + static feeHistoryEmptyResponse = { baseFeePerGas: [], gasUsedRatio: [], reward: [], oldestBlock: EthImpl.zeroHex }; + + // endpoint metric callerNames + static ethCall = 'eth_call'; + static ethGasPrice = 'eth_gasPrice'; + static ethGetBalance = 'eth_getBalance'; + static ethGetCode = 'eth_getCode'; + static ethFeeHistory = 'eth_feeHistory'; + static ethGetTransactionCount = 'eth_getTransactionCount'; + static ethSendRawTransaction = 'eth_sendRawTransaction'; + + /** * The sdk client use for connecting to both the consensus nodes and mirror node. The account * associated with this client will pay for all operations on the main network. @@ -149,7 +160,7 @@ export class EthImpl implements Eth { let feeHistory: object | undefined = cache.get(constants.CACHE_KEY.FEE_HISTORY); if (!feeHistory) { - feeHistory = await this.getFeeHistory(blockCount, newestBlockNumber, latestBlockNumber, rewardPercentiles); + feeHistory = await this.getFeeHistory(blockCount, newestBlockNumber, latestBlockNumber, rewardPercentiles); this.logger.trace(`caching ${constants.CACHE_KEY.FEE_HISTORY} for ${constants.CACHE_TTL.ONE_HOUR} ms`); cache.set(constants.CACHE_KEY.FEE_HISTORY, feeHistory, constants.CACHE_TTL.ONE_HOUR); @@ -167,7 +178,7 @@ export class EthImpl implements Eth { try { const block = await this.mirrorNodeClient.getBlock(blockNumber); - fee = await this.getFeeWeibars(`lte:${block.timestamp.to}`); + fee = await this.getFeeWeibars(EthImpl.ethFeeHistory, `lte:${block.timestamp.to}`); } catch (error) { this.logger.warn(error, `Fee history cannot retrieve block or fee. Returning ${fee} fee for block ${blockNumber}`); } @@ -186,7 +197,7 @@ export class EthImpl implements Eth { }; // get fees from oldest to newest blocks - for(let blockNumber = oldestBlockNumber; blockNumber <= newestBlockNumber; blockNumber++) { + for (let blockNumber = oldestBlockNumber; blockNumber <= newestBlockNumber; blockNumber++) { const fee = await this.getFeeByBlockNumber(blockNumber); feeHistory.baseFeePerGas.push(fee); @@ -200,11 +211,11 @@ export class EthImpl implements Eth { // get next block fee if the newest block is not the latest nextBaseFeePerGas = await this.getFeeByBlockNumber(newestBlockNumber + 1); } - + if (nextBaseFeePerGas) { feeHistory.baseFeePerGas.push(nextBaseFeePerGas); } - + if (shouldIncludeRewards) { feeHistory['reward'] = Array(blockCount).fill(Array(rewardPercentiles.length).fill(EthImpl.zeroHex)); } @@ -212,7 +223,7 @@ export class EthImpl implements Eth { return feeHistory; } - private async getFeeWeibars(timestamp?: string) { + private async getFeeWeibars(callerName: string, timestamp?: string) { let networkFees; try { @@ -228,7 +239,7 @@ export class EthImpl implements Eth { networkFees = { fees: [ { - gas: await this.sdkClient.getTinyBarGasFee(), + gas: await this.sdkClient.getTinyBarGasFee(callerName), 'transaction_type': EthImpl.ethTxType } ] @@ -298,7 +309,7 @@ export class EthImpl implements Eth { let gasPrice: number | undefined = cache.get(constants.CACHE_KEY.GAS_PRICE); if (!gasPrice) { - gasPrice = await this.getFeeWeibars(); + gasPrice = await this.getFeeWeibars(EthImpl.ethGasPrice, undefined); this.logger.trace(`caching ${constants.CACHE_KEY.GAS_PRICE} for ${constants.CACHE_TTL.ONE_HOUR} ms`); cache.set(constants.CACHE_KEY.GAS_PRICE, gasPrice, constants.CACHE_TTL.ONE_HOUR); @@ -433,10 +444,10 @@ export class EthImpl implements Eth { let weibars: BigNumber | number = 0; const result = await this.mirrorNodeClient.resolveEntityType(account); if (result?.type === constants.TYPE_ACCOUNT) { - weibars = await this.sdkClient.getAccountBalanceInWeiBar(result.entity.account); + weibars = await this.sdkClient.getAccountBalanceInWeiBar(result.entity.account, EthImpl.ethGetBalance); } else if (result?.type === constants.TYPE_CONTRACT) { - weibars = await this.sdkClient.getContractBalanceInWeiBar(result.entity.contract_id); + weibars = await this.sdkClient.getContractBalanceInWeiBar(result.entity.contract_id, EthImpl.ethGetBalance); } return EthImpl.numberTo0x(weibars); @@ -462,7 +473,7 @@ export class EthImpl implements Eth { // FIXME: This has to be reimplemented to get the data from the mirror node. this.logger.trace('getCode(address=%s, blockNumber=%s)', address, blockNumber); try { - const bytecode = await this.sdkClient.getContractByteCode(0, 0, address); + 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 @@ -600,7 +611,7 @@ export class EthImpl implements Eth { } else { const result = await this.mirrorNodeClient.resolveEntityType(address); if (result?.type === constants.TYPE_ACCOUNT) { - const accountInfo = await this.sdkClient.getAccountInfo(result?.entity.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) { @@ -621,17 +632,17 @@ export class EthImpl implements Eth { await this.precheck.nonce(transaction); const chainIdPrecheckRes = this.precheck.chainId(transaction); - if ( !chainIdPrecheckRes.passes ) { + if (!chainIdPrecheckRes.passes) { return chainIdPrecheckRes.error; } - const gasPrice = await this.getFeeWeibars(); + const gasPrice = await this.getFeeWeibars(EthImpl.ethSendRawTransaction); const gasPrecheck = this.precheck.gasPrice(transaction, gasPrice); if (!gasPrecheck.passes) { return gasPrecheck.error; } - const balancePrecheck = await this.precheck.balance(transaction); + const balancePrecheck = await this.precheck.balance(transaction, EthImpl.ethSendRawTransaction); if (!balancePrecheck.passes) { return balancePrecheck.error; } @@ -643,7 +654,7 @@ export class EthImpl implements Eth { try { // Wait for the record from the execution. - const record = await this.sdkClient.executeGetTransactionRecord(contractExecuteResponse, EthereumTransaction.name); + const record = await this.sdkClient.executeGetTransactionRecord(contractExecuteResponse, EthereumTransaction.name, EthImpl.ethSendRawTransaction); if (record.ethereumHash == null) { throw new Error('The ethereumHash can never be null for an ethereum transaction, and yet it was!!'); } @@ -711,7 +722,7 @@ export class EthImpl implements Eth { // Execute the call and get the response this.logger.debug('Making eth_call on contract %o with gas %d and call data "%s"', call.to, gas, call.data); - const contractCallResponse = await this.sdkClient.submitContractCallQuery(call.to, call.data, gas); + const contractCallResponse = await this.sdkClient.submitContractCallQuery(call.to, call.data, gas, EthImpl.ethCall); // FIXME Is this right? Maybe so? return EthImpl.prepend0x(Buffer.from(contractCallResponse.asBytes()).toString('hex')); @@ -925,12 +936,15 @@ export class EthImpl implements Eth { timestamp = result.timestamp.substring(0, result.timestamp.indexOf('.')); // mirrorNode response assures format of ssssssssss.nnnnnnnnn } - const transaction = await this.getTransactionFromContractResult(result.to, result.timestamp); - if (transaction !== null) { - if (showDetails) { - transactionObjects.push(transaction); - } else { - transactionHashes.push(transaction.hash); + // depending on stage of contract execution revert the result.to value may be null + if (!_.isNil(result.to)) { + const transaction = await this.getTransactionFromContractResult(result.to, result.timestamp); + if (transaction !== null) { + if (showDetails) { + transactionObjects.push(transaction); + } else { + transactionHashes.push(transaction.hash); + } } } } @@ -978,6 +992,11 @@ export class EthImpl implements Eth { } const contractResult = contractResults.results[0]; + if (contractResult === undefined) { + // contract result not found + return null; + } + return this.getTransactionFromContractResult(contractResult.to, contractResult.timestamp); } diff --git a/packages/relay/src/lib/precheck.ts b/packages/relay/src/lib/precheck.ts index 4a18309cce..f3223da329 100644 --- a/packages/relay/src/lib/precheck.ts +++ b/packages/relay/src/lib/precheck.ts @@ -92,10 +92,10 @@ export class Precheck { return { passes, error: predefined.GAS_PRICE_TOO_LOW - } + }; } - async balance(transaction: string) { + async balance(transaction: string, callerName: string) { const result = { passes: false, error: predefined.INSUFFICIENT_ACCOUNT_BALANCE @@ -107,7 +107,7 @@ export class Precheck { try { const { account }: any = await this.mirrorNodeClient.getAccount(tx.from!); - const accountBalance = await this.sdkClient.getAccountBalanceInWeiBar(account); + const accountBalance = await this.sdkClient.getAccountBalanceInWeiBar(account, callerName); result.passes = ethers.ethers.BigNumber.from(accountBalance.toString()).gte(txTotalValue); diff --git a/packages/relay/src/lib/relay.ts b/packages/relay/src/lib/relay.ts index 76c674d6e9..771e9f0abc 100644 --- a/packages/relay/src/lib/relay.ts +++ b/packages/relay/src/lib/relay.ts @@ -44,7 +44,7 @@ export class RelayImpl implements Relay { constructor(logger: Logger, register: Registry) { dotenv.config({ path: findConfig('.env') || '' }); - logger.info('Configurations successully loaded'); + logger.info('Configurations successfully loaded'); const hederaNetwork: string = process.env.HEDERA_NETWORK || '{}'; @@ -73,7 +73,7 @@ export class RelayImpl implements Relay { mirrorNodeClient, logger.child({ name: 'relay-eth' }), chainId); - + logger.info('Relay running with chainId=%s', chainId); } @@ -96,35 +96,35 @@ export class RelayImpl implements Relay { } else { client = Client.forNetwork(JSON.parse(hederaNetwork)); } - - logger.info(`SDK client successfully configured to ${JSON.stringify(hederaNetwork)}`); - - switch (type) { - case 'eth_sendRawTransaction': { - if ( - process.env.OPERATOR_ID_ETH_SENDRAWTRANSACTION && - process.env.OPERATOR_KEY_ETH_SENDRAWTRANSACTION - ) { - client = client.setOperator( - AccountId.fromString( - process.env.OPERATOR_ID_ETH_SENDRAWTRANSACTION - ), - PrivateKey.fromString( - process.env.OPERATOR_KEY_ETH_SENDRAWTRANSACTION - ) - ); - } - return client; + + if (type === 'eth_sendRawTransaction') { + if ( + process.env.OPERATOR_ID_ETH_SENDRAWTRANSACTION && + process.env.OPERATOR_KEY_ETH_SENDRAWTRANSACTION + ) { + client = client.setOperator( + AccountId.fromString( + process.env.OPERATOR_ID_ETH_SENDRAWTRANSACTION + ), + PrivateKey.fromString( + process.env.OPERATOR_KEY_ETH_SENDRAWTRANSACTION + ) + ); + } else { + throw new Error(`Invalid 'ETH_SENDRAWTRANSACTION' env variable provided`); } - default: { - if (process.env.OPERATOR_ID_MAIN && process.env.OPERATOR_KEY_MAIN) { - client = client.setOperator( - AccountId.fromString(process.env.OPERATOR_ID_MAIN.trim()), - PrivateKey.fromString(process.env.OPERATOR_KEY_MAIN) - ); - } - return client; + } else { + if (process.env.OPERATOR_ID_MAIN && process.env.OPERATOR_KEY_MAIN) { + client = client.setOperator( + AccountId.fromString(process.env.OPERATOR_ID_MAIN.trim()), + PrivateKey.fromString(process.env.OPERATOR_KEY_MAIN) + ); + } else { + throw new Error(`Invalid 'OPERATOR' env variables provided`); } } + + logger.info(`SDK client successfully configured to ${JSON.stringify(hederaNetwork)} for account ${client.operatorAccountId}`); + return client; } } diff --git a/packages/relay/tests/lib/eth.spec.ts b/packages/relay/tests/lib/eth.spec.ts index a02979af08..82046dea27 100644 --- a/packages/relay/tests/lib/eth.spec.ts +++ b/packages/relay/tests/lib/eth.spec.ts @@ -110,6 +110,8 @@ describe('Eth calls using MirrorNode', async function () { const blockNumber3 = 5; const blockNumberHex = `0x${blockNumber.toString(16)}`; const blockTransactionCount = 77; + const gasUsed1 = 200000; + const gasUsed2 = 800000; const maxGasLimit = 250000; const maxGasLimitHex = EthImpl.numberTo0x(maxGasLimit); const firstTransactionTimestampSeconds = '1653077547'; @@ -153,7 +155,8 @@ describe('Eth calls using MirrorNode', async function () { 'from': '0x0000000000000000000000000000000000000557', 'function_parameters': '0x', 'gas_limit': maxGasLimit, - 'gas_used': 200000, + 'gas_used': gasUsed1, + 'hash': contractHash1, 'timestamp': `${contractTimestamp1}`, 'to': `${contractAddress1}` }, @@ -167,7 +170,8 @@ describe('Eth calls using MirrorNode', async function () { 'from': '0x0000000000000000000000000000000000000557', 'function_parameters': '0x2b6adf430000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000084865792c204d6121000000000000000000000000000000000000000000000000', 'gas_limit': maxGasLimit - 1000, - 'gas_used': 80000, + 'gas_used': gasUsed2, + 'hash': contractHash2, 'timestamp': `${contractTimestamp2}`, 'to': `${contractAddress2}` } @@ -177,6 +181,29 @@ describe('Eth calls using MirrorNode', async function () { } }; + const defaultContractResultsRevert = { + 'results': [ + { + 'amount': 0, + 'bloom': '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + 'call_result': '0x', + 'contract_id': null, + 'created_contract_ids': [], + 'error_message': '0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002645524332303a207472616e7366657220616d6f756e7420657863656564732062616c616e63650000000000000000000000000000000000000000000000000000', + 'from': '0x0000000000000000000000000000000000000557', + 'function_parameters': '0x', + 'gas_limit': maxGasLimit, + 'gas_used': gasUsed1, + 'hash': contractHash1, + 'timestamp': `${contractTimestamp1}`, + 'to': null + } + ], + 'links': { + 'next': null + } + }; + const defaultDetailedContractResults = { 'access_list': '0x', 'amount': 2000000000, @@ -442,6 +469,29 @@ describe('Eth calls using MirrorNode', async function () { verifyBlockConstants(result); }); + it('eth_getBlockByNumber with block match and contract revert', async function () { + // mirror node request mocks + mock.onGet(`blocks/${blockNumber}`).reply(200, defaultBlock); + mock.onGet(`contracts/results?timestamp=gte:${defaultBlock.timestamp.from}×tamp=lte:${defaultBlock.timestamp.to}`).reply(200, defaultContractResultsRevert); + mock.onGet('network/fees').reply(200, defaultNetworkFees); + + const result = await ethImpl.getBlockByNumber(EthImpl.numberTo0x(blockNumber), true); + expect(result).to.exist; + if (result == null) return; + + // verify aggregated info + expect(result.hash).equal(blockHashTrimmed); + expect(result.gasUsed).equal(EthImpl.numberTo0x(gasUsed1)); + expect(result.gasLimit).equal(maxGasLimitHex); + expect(result.number).equal(blockNumberHex); + expect(result.parentHash).equal(blockHashPreviousTrimmed); + expect(result.timestamp).equal(firstTransactionTimestampSecondsHex); + expect(result.transactions.length).equal(0); + + // verify expected constants + verifyBlockConstants(result); + }); + it('eth_getBlockByNumber with no match', async function () { mock.onGet(`blocks/${blockNumber}`).reply(400, { '_status': { @@ -560,6 +610,29 @@ describe('Eth calls using MirrorNode', async function () { verifyBlockConstants(result); }); + it('eth_getBlockByHash with block match and contract revert', async function () { + // mirror node request mocks + mock.onGet(`blocks/${blockHash}`).reply(200, defaultBlock); + mock.onGet(`contracts/results?timestamp=gte:${defaultBlock.timestamp.from}×tamp=lte:${defaultBlock.timestamp.to}`).reply(200, defaultContractResultsRevert); + mock.onGet('network/fees').reply(200, defaultNetworkFees); + + const result = await ethImpl.getBlockByHash(blockHash, true); + expect(result).to.exist; + if (result == null) return; + + // verify aggregated info + expect(result.hash).equal(blockHashTrimmed); + expect(result.gasUsed).equal(EthImpl.numberTo0x(gasUsed1)); + expect(result.gasLimit).equal(maxGasLimitHex); + expect(result.number).equal(blockNumberHex); + expect(result.parentHash).equal(blockHashPreviousTrimmed); + expect(result.timestamp).equal(firstTransactionTimestampSecondsHex); + expect(result.transactions.length).equal(0); + + // verify expected constants + verifyBlockConstants(result); + }); + it('eth_getBlockByHash with no match', async function () { // mirror node request mocks mock.onGet(`blocks/${blockHash}`).reply(400, { @@ -688,6 +761,15 @@ describe('Eth calls using MirrorNode', async function () { expect(result).to.equal(null); }); + it('eth_getTransactionByBlockNumberAndIndex with no contract results', async function () { + mock.onGet(`contracts/results?block.number=${defaultBlock.number}&transaction.index=${defaultBlock.count}`).reply(200, { + 'results': [] + }); + + const result = await ethImpl.getTransactionByBlockNumberAndIndex(defaultBlock.number.toString(), defaultBlock.count); + expect(result).to.equal(null); + }); + it('eth_getTransactionByBlockNumberAndIndex with latest tag', async function () { // mirror node request mocks mock.onGet('blocks?limit=1&order=desc').reply(200, { blocks: [defaultBlock] }); @@ -782,7 +864,16 @@ describe('Eth calls using MirrorNode', async function () { } }); - const result = await ethImpl.getTransactionByBlockNumberAndIndex(defaultBlock.number.toString(), defaultBlock.count); + const result = await ethImpl.getTransactionByBlockHashAndIndex(defaultBlock.hash.toString(), defaultBlock.count); + expect(result).to.equal(null); + }); + + it('eth_getTransactionByBlockHashAndIndex with no contract results', async function () { + mock.onGet(`contracts/results?block.number=${defaultBlock.hash}&transaction.index=${defaultBlock.count}`).reply(200, { + 'results': [] + }); + + const result = await ethImpl.getTransactionByBlockHashAndIndex(defaultBlock.hash.toString(), defaultBlock.count); expect(result).to.equal(null); }); @@ -798,7 +889,7 @@ describe('Eth calls using MirrorNode', async function () { } }); - const result = await ethImpl.getTransactionByBlockNumberAndIndex(defaultBlock.number.toString(), defaultBlock.count); + const result = await ethImpl.getTransactionByBlockHashAndIndex(defaultBlock.hash.toString(), defaultBlock.count); expect(result).to.equal(null); }); diff --git a/packages/server/tests/clients/relayClient.ts b/packages/server/tests/clients/relayClient.ts index be5e04b0ce..bebac44d2e 100644 --- a/packages/server/tests/clients/relayClient.ts +++ b/packages/server/tests/clients/relayClient.ts @@ -40,7 +40,7 @@ export default class RelayClient { */ async call(methodName: string, params: any[]) { const result = await this.provider.send(methodName, params); - this.logger.trace(`[POST] to relay '${methodName}' with params [${params}] returned ${JSON.stringify(result)}`); + this.logger.trace(`[POST] to relay '${methodName}' with params [${JSON.stringify(params)}] returned ${JSON.stringify(result)}`); return result; }; From 0ea9bd84b75a940a1216abc7e23bfbeb2bd4ace7 Mon Sep 17 00:00:00 2001 From: Nana-EC Date: Tue, 5 Jul 2022 23:10:00 -0500 Subject: [PATCH 2/3] Fix code smell Signed-off-by: Nana-EC --- packages/relay/src/lib/clients/sdkClient.ts | 1 - packages/relay/src/lib/eth.ts | 2 +- packages/relay/src/lib/relay.ts | 4 ++-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/relay/src/lib/clients/sdkClient.ts b/packages/relay/src/lib/clients/sdkClient.ts index 316d371c4c..9a440ad498 100644 --- a/packages/relay/src/lib/clients/sdkClient.ts +++ b/packages/relay/src/lib/clients/sdkClient.ts @@ -39,7 +39,6 @@ import { Query, Transaction, TransactionRecord, - TransactionReceipt, Status } from '@hashgraph/sdk'; import { BigNumber } from '@hashgraph/sdk/lib/Transfer'; diff --git a/packages/relay/src/lib/eth.ts b/packages/relay/src/lib/eth.ts index ee6217c4a0..56a25caa2f 100644 --- a/packages/relay/src/lib/eth.ts +++ b/packages/relay/src/lib/eth.ts @@ -309,7 +309,7 @@ export class EthImpl implements Eth { let gasPrice: number | undefined = cache.get(constants.CACHE_KEY.GAS_PRICE); if (!gasPrice) { - gasPrice = await this.getFeeWeibars(EthImpl.ethGasPrice, undefined); + gasPrice = await this.getFeeWeibars(EthImpl.ethGasPrice); this.logger.trace(`caching ${constants.CACHE_KEY.GAS_PRICE} for ${constants.CACHE_TTL.ONE_HOUR} ms`); cache.set(constants.CACHE_KEY.GAS_PRICE, gasPrice, constants.CACHE_TTL.ONE_HOUR); diff --git a/packages/relay/src/lib/relay.ts b/packages/relay/src/lib/relay.ts index 771e9f0abc..88fb59fc5b 100644 --- a/packages/relay/src/lib/relay.ts +++ b/packages/relay/src/lib/relay.ts @@ -111,7 +111,7 @@ export class RelayImpl implements Relay { ) ); } else { - throw new Error(`Invalid 'ETH_SENDRAWTRANSACTION' env variable provided`); + logger.warn(`Invalid 'ETH_SENDRAWTRANSACTION' env variables provided`); } } else { if (process.env.OPERATOR_ID_MAIN && process.env.OPERATOR_KEY_MAIN) { @@ -120,7 +120,7 @@ export class RelayImpl implements Relay { PrivateKey.fromString(process.env.OPERATOR_KEY_MAIN) ); } else { - throw new Error(`Invalid 'OPERATOR' env variables provided`); + logger.warn(`Invalid 'OPERATOR' env variables provided`); } } From 67813cf2306780da0096c567d6b428deedcd638e Mon Sep 17 00:00:00 2001 From: Nana-EC Date: Tue, 5 Jul 2022 23:19:19 -0500 Subject: [PATCH 3/3] Set operator default for tests Signed-off-by: Nana-EC --- packages/relay/src/lib/clients/sdkClient.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/relay/src/lib/clients/sdkClient.ts b/packages/relay/src/lib/clients/sdkClient.ts index 9a440ad498..131256feab 100644 --- a/packages/relay/src/lib/clients/sdkClient.ts +++ b/packages/relay/src/lib/clients/sdkClient.ts @@ -80,7 +80,7 @@ export class SDKClient { this.clientMain = clientMain; this.logger = logger; this.register = register; - this.operatorAccountId = clientMain.operatorAccountId!.toString(); + this.operatorAccountId = clientMain.operatorAccountId ? clientMain.operatorAccountId.toString() : 'UNKNOWN'; // clear and create metrics in registry const metricHistogramName = 'rpc_relay_consensusnode_response';