From 9288e0ca9fc80dd969551408a5b29a3e78f658eb Mon Sep 17 00:00:00 2001 From: lukelee-sl <109538178+lukelee-sl@users.noreply.github.com> Date: Mon, 25 Jul 2022 07:50:46 -0700 Subject: [PATCH 1/3] remove mirrorNode.ts and dependencies (#374) * remove mirrorNode.ts and dependencies Signed-off-by: lukelee-sl * address sonarcloud issue Signed-off-by: lukelee-sl --- packages/relay/src/lib/eth.ts | 31 +----- packages/relay/src/lib/mirrorNode.ts | 143 --------------------------- packages/relay/src/lib/model.ts | 21 ---- packages/relay/src/lib/relay.ts | 4 - packages/relay/tests/lib/eth.spec.ts | 5 +- 5 files changed, 4 insertions(+), 200 deletions(-) delete mode 100755 packages/relay/src/lib/mirrorNode.ts diff --git a/packages/relay/src/lib/eth.ts b/packages/relay/src/lib/eth.ts index 7bedc9023..5a51a7062 100644 --- a/packages/relay/src/lib/eth.ts +++ b/packages/relay/src/lib/eth.ts @@ -22,8 +22,7 @@ import { Eth } from '../index'; import { ContractId, Status, Hbar, EthereumTransaction } from '@hashgraph/sdk'; import { BigNumber } from '@hashgraph/sdk/lib/Transfer'; import { Logger } from 'pino'; -import { Block, CachedBlock, Transaction, Log } from './model'; -import { MirrorNode } from './mirrorNode'; +import { Block, Transaction, Log } from './model'; import { MirrorNodeClient, SDKClient } from './clients'; import { JsonRpcError, predefined } from './errors'; import constants from './constants'; @@ -76,12 +75,6 @@ export class EthImpl implements Eth { */ private readonly sdkClient: SDKClient; - /** - * The mirror node mock - * @private - */ - private readonly mirrorNode: MirrorNode; - /** * The interface through which we interact with the mirror node * @private @@ -109,20 +102,17 @@ export class EthImpl implements Eth { /** * Create a new Eth implementation. * @param nodeClient - * @param mirrorNode * @param mirrorNodeClient * @param logger * @param chain */ constructor( nodeClient: SDKClient, - mirrorNode: MirrorNode, mirrorNodeClient: MirrorNodeClient, logger: Logger, chain: string ) { this.sdkClient = nodeClient; - this.mirrorNode = mirrorNode; this.mirrorNodeClient = mirrorNodeClient; this.logger = logger; this.chain = chain; @@ -669,24 +659,7 @@ export class EthImpl implements Eth { if (record.ethereumHash == null) { throw new Error('The ethereumHash can never be null for an ethereum transaction, and yet it was!!'); } - const txHash = EthImpl.prepend0x(Buffer.from(record.ethereumHash).toString('hex')); - - // If the transaction succeeded, create a new block for the transaction. - const mostRecentBlock = await this.mirrorNode.getMostRecentBlock(); - this.logger.debug('mostRecentBlock=%o', mostRecentBlock); - let block = mostRecentBlock; - if (record.receipt.status == Status.Success) { - block = new CachedBlock(mostRecentBlock, txHash); - this.mirrorNode.storeBlock(block); - } - - // Create a receipt. Register the receipt in the cache and return the tx hash - if (block == null) { - this.logger.error('Failed to get a block for transaction'); - return ''; - } - - return txHash; + return EthImpl.prepend0x(Buffer.from(record.ethereumHash).toString('hex')); } catch (e) { this.logger.error(e, 'Failed sendRawTransaction during record retrieval for transaction %s, returning computed hash', transaction); diff --git a/packages/relay/src/lib/mirrorNode.ts b/packages/relay/src/lib/mirrorNode.ts deleted file mode 100755 index 6d60ed6b9..000000000 --- a/packages/relay/src/lib/mirrorNode.ts +++ /dev/null @@ -1,143 +0,0 @@ -/*- - * - * 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 { Logger } from "pino"; -// Used for temporary purposes to store block info. As the mirror node supports the APIs, we will remove this. -import { Block, CachedBlock } from './model'; - -export class MirrorNode { - // A FAKE implementation until mirror node is integrated and ready. - // Keeps all blocks in memory. We're going to do our own bookkeeping - // to keep track of blocks and only create them once per transaction. - // So it may have been 20 minutes and if there were no transactions - // then we don't advance the block list. The first block is block 0, - // so we can quickly look them up in the array. Yes, we will eventually - // end up running out of memory. - private static MOST_RECENT_BLOCK_NUMBER_KEY = "mostRecentBlockNumber"; - private static MOST_RECENT_BLOCK_KEY = "mostRecentBlock"; - private readonly store: Map = new Map(); - - - /** - * The logger used for logging all output from this class. - * @private - */ - private readonly logger: Logger; - - constructor(logger: Logger) { - this.logger = logger; - - // FIXME: Create an empty genesis block (which has no transactions!) - // to preload the system. - if (this.store.has(MirrorNode.MOST_RECENT_BLOCK_KEY)) { - this.logger.trace("Cache recent block"); - } else { - this.logger.trace("Fresh start, creating genesis block with no transactions"); - const genesisBlock = new CachedBlock(null, null); - this.storeBlock(genesisBlock); - } - } - - public async getFeeHistory(fee: number, _blockCount: number, _newestBlock: string, rewardPercentiles: Array | null) { - // FIXME: This is a fake implementation. It works for now, but should - // actually delegate to the mirror node. - this.logger.trace('getFeeHistory()'); - - const mostRecentBlockNumber = await this.getMostRecentBlockNumber(); - this.logger.debug('computing fee history for mostRecentBlockNumber=%d', mostRecentBlockNumber); - const mostRecentBlocks: Block[] = []; - for (let blockNumber = Math.max(0, mostRecentBlockNumber - 9); blockNumber <= mostRecentBlockNumber; blockNumber++) { - const block = await this.getBlockByNumber(blockNumber); - this.logger.trace("block for %d is %o", blockNumber, block); - if (block != null) { - mostRecentBlocks.push(block); - } else { - this.logger.error('Error: unable to find block by number %d', blockNumber); - } - } - this.logger.debug('Computing fee history based on the last %d blocks', mostRecentBlocks.length); - - const feeHistoryResponse = { - baseFeePerGasArray: Array(mostRecentBlocks.length).fill('0x' + fee.toString(16)), - gasUsedRatioArray: Array(mostRecentBlocks.length).fill('0.5'), - oldestBlockNumber: mostRecentBlocks[0].number - }; - - if (rewardPercentiles) { - feeHistoryResponse['reward'] = Array(mostRecentBlocks.length).fill(Array(rewardPercentiles.length).fill("0x0")); - } - - return feeHistoryResponse; - } - - // FIXME this is for demo/temp purposes, remove it when the mirror node has real blocks - // that they get from the main net nodes - public storeBlock(block: CachedBlock) { - this.store.set(MirrorNode.MOST_RECENT_BLOCK_NUMBER_KEY, block.getNum()); - this.store.set(MirrorNode.MOST_RECENT_BLOCK_KEY, block); - this.store.set(block.getNum().toString(), block); - this.store.set(block.transactionHashes[0], block); - } - - public async getMostRecentBlockNumber(): Promise { - // FIXME: Fake implementation for now. Should go to the mirror node. - this.logger.trace('getMostRecentBlockNumber()'); - const num = this.store.get(MirrorNode.MOST_RECENT_BLOCK_NUMBER_KEY); - this.logger.debug('Latest block number: %s', num); - return num === undefined ? 0 : Number(num); - } - - public async getMostRecentBlock(): Promise { - // FIXME: Fake implementation for now. Should go to the mirror node. - this.logger.trace('getMostRecentBlock()'); - const block = this.store.get(MirrorNode.MOST_RECENT_BLOCK_KEY); - if (block === undefined) { - this.logger.debug("No blocks retrievable"); - return null; - } else { - this.logger.debug("Retrieved block number: %s", block.getNum()); - return block; - } - } - - // TODO: mirror node method is not yet implemented - public async getBlockByNumber(blockNumber: number): Promise { - // FIXME: This needs to be reimplemented to go to the mirror node. - // return this.request(`blocks/${blockNumber}`); - this.logger.trace('getBlockByNumber(blockNumber=%d)', blockNumber); - const block = this.store.get(blockNumber.toString()); - return block === undefined ? null : block; - } - - public async getBlockByHash(hash: string, showDetails: boolean): Promise { - // FIXME: This needs to be reimplemented to go to the mirror node. - this.logger.trace('getBlockByHash(hash=%s, showDetails=%o)', hash, showDetails); - - // We don't support this yet, so log a warning in case somebody tries to use it - // we can learn of that usage. - if (showDetails) { - this.logger.warn('getBlockByHash does not yet support "showDetails"'); - } - - // Look up the block number by hash - const block = this.store.get(hash); - return block === undefined ? null : block; - } -} diff --git a/packages/relay/src/lib/model.ts b/packages/relay/src/lib/model.ts index e8311c177..7a998e743 100644 --- a/packages/relay/src/lib/model.ts +++ b/packages/relay/src/lib/model.ts @@ -80,27 +80,6 @@ export class Block { } } -export class CachedBlock extends Block { - public readonly parentBlock:Block|null; - public readonly transactionHashes:string[] = []; - - constructor(parentBlock:(null | Block), transactionHash:(string|null), args?:any) { - super(args); - this.parentBlock = parentBlock; - - const num = parentBlock == null ? 0 : parentBlock.getNum() + 1; - this.number = '0x' + Number(num).toString(16); - this.parentHash = parentBlock == null ? '0x0' : parentBlock.hash; - if (transactionHash) { - this.transactionHashes.push(transactionHash); - } - - const numberAsString = num.toString(); - const baseHash = "0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b"; - this.hash = baseHash.slice(0, baseHash.length - numberAsString.length) + numberAsString; - } -} - export class Receipt { public readonly transactionHash:string; public readonly transactionIndex:string; diff --git a/packages/relay/src/lib/relay.ts b/packages/relay/src/lib/relay.ts index 88fb59fc5..aab5fd899 100644 --- a/packages/relay/src/lib/relay.ts +++ b/packages/relay/src/lib/relay.ts @@ -26,7 +26,6 @@ import { NetImpl } from './net'; import { EthImpl } from './eth'; import { AccountId, Client, PrivateKey } from '@hashgraph/sdk'; import { Logger } from 'pino'; -import { MirrorNode } from './mirrorNode'; import { MirrorNodeClient, SDKClient } from './clients'; import { Registry } from 'prom-client'; @@ -57,8 +56,6 @@ export class RelayImpl implements Relay { this.web3Impl = new Web3Impl(this.clientMain); this.netImpl = new NetImpl(this.clientMain, chainId); - const mirrorNode = new MirrorNode(logger.child({ name: `mirror-node` })); - const mirrorNodeClient = new MirrorNodeClient( process.env.MIRROR_NODE_URL || '', logger.child({ name: `mirror-node` }), @@ -69,7 +66,6 @@ export class RelayImpl implements Relay { this.ethImpl = new EthImpl( sdkClient, - mirrorNode, mirrorNodeClient, logger.child({ name: 'relay-eth' }), chainId); diff --git a/packages/relay/tests/lib/eth.spec.ts b/packages/relay/tests/lib/eth.spec.ts index fb7c2e449..b853a46db 100644 --- a/packages/relay/tests/lib/eth.spec.ts +++ b/packages/relay/tests/lib/eth.spec.ts @@ -30,7 +30,6 @@ dotenv.config({ path: path.resolve(__dirname, '../test.env') }); import { RelayImpl } from '@hashgraph/json-rpc-relay'; import { EthImpl } from '../../src/lib/eth'; import { MirrorNodeClient } from '../../src/lib/clients/mirrorNodeClient'; -import { MirrorNode } from '../../src/lib/mirrorNode'; import { expectUnsupportedMethod } from '../helpers'; import pino from 'pino'; @@ -92,7 +91,7 @@ describe('Eth calls using MirrorNode', async function () { mirrorNodeInstance = new MirrorNodeClient(process.env.MIRROR_NODE_URL, logger.child({ name: `mirror-node` }), registry, instance); sdkClientStub = sinon.createStubInstance(SDKClient); // @ts-ignore - ethImpl = new EthImpl(sdkClientStub, new MirrorNode(logger.child({ name: `mirror-node-faux` })), mirrorNodeInstance, logger, '0x12a'); + ethImpl = new EthImpl(sdkClientStub, mirrorNodeInstance, logger, '0x12a'); }); this.beforeEach(() => { @@ -1326,7 +1325,7 @@ describe('Eth', async function () { let ethImpl: EthImpl; this.beforeAll(() => { // @ts-ignore - ethImpl = new EthImpl(null, null, mirrorNodeInstance, logger); + ethImpl = new EthImpl(null, mirrorNodeInstance, logger); }); const defaultTxHash = '0x4a563af33c4871b51a8b108aa2fe1dd5280a30dfb7236170ae5e5e7957eb6392'; From 5fd35e370ce5bc269659fafe27891b189d1f76e2 Mon Sep 17 00:00:00 2001 From: Danno Ferrin Date: Mon, 25 Jul 2022 16:46:17 -0600 Subject: [PATCH 2/3] Support 'from' in 'eth_call' (#375) * Support from in eth_call When a from value is in the eth_call params, set it to the senderId. Signed-off-by: Danno Ferrin --- packages/relay/src/lib/clients/sdkClient.ts | 6 +- packages/relay/src/lib/eth.ts | 6 +- packages/relay/tests/lib/eth.spec.ts | 77 ++++++++++++++++++- .../server/tests/acceptance/erc20.spec.ts | 14 ++-- 4 files changed, 91 insertions(+), 12 deletions(-) diff --git a/packages/relay/src/lib/clients/sdkClient.ts b/packages/relay/src/lib/clients/sdkClient.ts index eab04a43a..a5a2b4526 100644 --- a/packages/relay/src/lib/clients/sdkClient.ts +++ b/packages/relay/src/lib/clients/sdkClient.ts @@ -194,7 +194,7 @@ export class SDKClient { .setEthereumData(transactionBuffer), callerName); } - async submitContractCallQuery(to: string, data: string, gas: number, callerName: string): Promise { + async submitContractCallQuery(to: string, data: string, gas: number, from: string, callerName: string): Promise { const contract = SDKClient.prune0x(to); const contractId = contract.startsWith("00000000000") ? ContractId.fromSolidityAddress(contract) @@ -209,6 +209,10 @@ export class SDKClient { contractCallQuery.setFunctionParameters(Buffer.from(SDKClient.prune0x(data), 'hex')); } + if (from) { + contractCallQuery.setSenderAccountId(AccountId.fromEvmAddress(0,0, from)) + } + if (this.clientMain.operatorAccountId !== null) { contractCallQuery .setPaymentTransactionId(TransactionId.generate(this.clientMain.operatorAccountId)); diff --git a/packages/relay/src/lib/eth.ts b/packages/relay/src/lib/eth.ts index 5a51a7062..eb704dfe9 100644 --- a/packages/relay/src/lib/eth.ts +++ b/packages/relay/src/lib/eth.ts @@ -703,10 +703,10 @@ export class EthImpl implements Eth { gas = call.gas; } } - + // 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, EthImpl.ethCall); + this.logger.debug('Making eth_call on contract %o with gas %d and call data "%s" from "%s"', call.to, gas, call.data, call.from); + const contractCallResponse = await this.sdkClient.submitContractCallQuery(call.to, call.data, gas, call.from, EthImpl.ethCall); // FIXME Is this right? Maybe so? return EthImpl.prepend0x(Buffer.from(contractCallResponse.asBytes()).toString('hex')); diff --git a/packages/relay/tests/lib/eth.spec.ts b/packages/relay/tests/lib/eth.spec.ts index b853a46db..618dc4ecf 100644 --- a/packages/relay/tests/lib/eth.spec.ts +++ b/packages/relay/tests/lib/eth.spec.ts @@ -36,7 +36,6 @@ import pino from 'pino'; import { Block, Transaction } from '../../src/lib/model'; import constants from '../../src/lib/constants'; import { SDKClient } from '../../src/lib/clients'; -import { TextEncoder } from 'util'; const logger = pino(); const registry = new Registry(); const Relay = new RelayImpl(logger, registry); @@ -114,6 +113,7 @@ describe('Eth calls using MirrorNode', async function () { const gasUsed2 = 800000; const maxGasLimit = 250000; const maxGasLimitHex = EthImpl.numberTo0x(maxGasLimit); + const contractCallData = "0xef641f44" const firstTransactionTimestampSeconds = '1653077547'; const firstTransactionTimestampSecondsHex = EthImpl.numberTo0x(Number(firstTransactionTimestampSeconds)); const contractAddress1 = '0x000000000000000000000000000000000000055f'; @@ -1317,6 +1317,81 @@ describe('Eth calls using MirrorNode', async function () { expect(error.message).to.equal('Error encountered estimating the gas price'); } }); + + describe('eth_call', async function () { + it('eth_call with no gas', async function () { + sdkClientStub.submitContractCallQuery.returns({ + asBytes: function () { + return Uint8Array.of(0) + } + } + ); + + const result = await ethImpl.call({ + "from": contractAddress1, + "to": contractAddress2, + "data": contractCallData, + }, 'latest') + + sinon.assert.calledWith(sdkClientStub.submitContractCallQuery, contractAddress2, contractCallData, 400_000, contractAddress1, 'eth_call'); + expect(result).to.equal("0x00") + }); + + it('eth_call with no data', async function () { + sdkClientStub.submitContractCallQuery.returns({ + asBytes: function () { + return Uint8Array.of(0) + } + } + ); + + var result = await ethImpl.call({ + "from": contractAddress1, + "to": contractAddress2, + "gas": maxGasLimitHex + }, 'latest') + + sinon.assert.calledWith(sdkClientStub.submitContractCallQuery, contractAddress2, undefined, maxGasLimit, contractAddress1, 'eth_call'); + expect(result).to.equal("0x00") + }); + + it('eth_call with no from address', async function () { + sdkClientStub.submitContractCallQuery.returns({ + asBytes: function () { + return Uint8Array.of(0) + } + } + ); + + const result = await ethImpl.call({ + "to": contractAddress2, + "data": contractCallData, + "gas": maxGasLimitHex + }, 'latest') + + sinon.assert.calledWith(sdkClientStub.submitContractCallQuery, contractAddress2, contractCallData, maxGasLimit, undefined, 'eth_call'); + expect(result).to.equal("0x00") + }); + + it('eth_call with all fields', async function () { + sdkClientStub.submitContractCallQuery.returns({ + asBytes: function () { + return Uint8Array.of(0) + } + } + ); + + const result = await ethImpl.call({ + "from": contractAddress1, + "to": contractAddress2, + "data": contractCallData, + "gas": maxGasLimitHex + }, 'latest') + + sinon.assert.calledWith(sdkClientStub.submitContractCallQuery, contractAddress2, contractCallData, maxGasLimit, contractAddress1, 'eth_call'); + expect(result).to.equal("0x00") + }); + }); }); describe('Eth', async function () { diff --git a/packages/server/tests/acceptance/erc20.spec.ts b/packages/server/tests/acceptance/erc20.spec.ts index 147d572e8..4fc7dc5f9 100644 --- a/packages/server/tests/acceptance/erc20.spec.ts +++ b/packages/server/tests/acceptance/erc20.spec.ts @@ -32,7 +32,7 @@ import {Utils} from '../helpers/utils'; describe('ERC20 Acceptance Tests', async function () { this.timeout(240 * 1000); // 240 seconds - const {servicesNode, relay, logger} = global; + const {servicesNode, relay} = global; // cached entities const accounts: AliasAccount[] = []; @@ -175,7 +175,8 @@ describe('ERC20 Acceptance Tests', async function () { if (testTitles[i].testName !== HTS) { describe('when the spender has enough allowance', function () { before(async function () { - await contract.connect(tokenOwnerWallet).approve(spender, initialSupply); + const tx = await contract.connect(tokenOwnerWallet).approve(spender, initialSupply); + await tx.wait(); }); describe('when the token owner has enough balance', function () { @@ -190,6 +191,7 @@ describe('ERC20 Acceptance Tests', async function () { it('transfers the requested amount', async function () { tx = await contract.connect(spenderWallet).transferFrom(tokenOwner, to, initialSupply); + await tx.wait(); const ownerBalance = await contract.balanceOf(tokenOwner); const toBalance = await contract.balanceOf(to); expect(ownerBalance.toString()).to.be.equal('0'); @@ -336,7 +338,7 @@ describe('ERC20 Acceptance Tests', async function () { const receipt = await relay.provider.getTransactionReceipt(contract.deployTransaction.hash); contract = new ethers.Contract(receipt.to, contractJson.abi, accounts[0].wallet); return contract; - }; + } const createHTS = async(tokenName, symbol, adminAccount, initialSupply, abi, associatedAccounts) => { const htsResult = await servicesNode.createHTS({ @@ -356,8 +358,6 @@ describe('ERC20 Acceptance Tests', async function () { // Setup initial balance of token owner account await servicesNode.transferHTSToken(accounts[0].accountId, htsResult.receipt.tokenId, initialSupply, htsResult.client); const evmAddress = Utils.idToEvmAddress(htsResult.receipt.tokenId.toString()); - const contract = new ethers.Contract(evmAddress, abi, accounts[0].wallet); - - return contract; + return new ethers.Contract(evmAddress, abi, accounts[0].wallet); }; -}); \ No newline at end of file +}); From 5af00f9d160ec28a216745bfde99c447b31f0805 Mon Sep 17 00:00:00 2001 From: ar-conmit <47981732+ar-conmit@users.noreply.github.com> Date: Tue, 26 Jul 2022 10:13:03 +0300 Subject: [PATCH 3/3] Ensure Schema compliance with execution-apis schema (#372) * Add openrpc integration tests Signed-off-by: Anton Rusev --- docs/openrpc.json | 25 +- package.json | 2 +- packages/relay/src/index.ts | 4 +- packages/relay/src/lib/eth.ts | 22 +- packages/relay/tests/helpers.ts | 302 +++++++++++- packages/relay/tests/lib/eth.spec.ts | 24 +- packages/relay/tests/lib/openrpc.spec.ts | 435 ++++++++++++++++++ packages/server/package.json | 3 +- packages/server/tests/acceptance/rpc.spec.ts | 4 +- .../server/tests/integration/openrpc.spec.ts | 13 - 10 files changed, 778 insertions(+), 56 deletions(-) create mode 100644 packages/relay/tests/lib/openrpc.spec.ts delete mode 100644 packages/server/tests/integration/openrpc.spec.ts diff --git a/docs/openrpc.json b/docs/openrpc.json index 96d774c67..99ba73ff1 100644 --- a/docs/openrpc.json +++ b/docs/openrpc.json @@ -131,7 +131,8 @@ "items": { "title": "rewardPercentile", "description": "Floating point value between 0 and 100.", - "type": "number" + "type": "number", + "pattern": "^[0-9][0-9]?$|^100$" } } } @@ -159,7 +160,9 @@ "description": "An array of gas used ratio.", "type": "array", "items": { - "$ref": "#/components/schemas/uint" + "title": "hex encoded unsigned integer", + "type": "string", + "pattern": "^0x([1-9a-f]+[0-9a-f]*|0|0.8)$" } }, "baseFeePerGas": { @@ -358,11 +361,7 @@ "result": { "name": "Log objects", "schema": { - "title": "Filter results", - "type": "array", - "items": { - "$ref": "#/components/schemas/FilterResults" - } + "$ref": "#/components/schemas/FilterResults" } } }, @@ -460,10 +459,7 @@ "name": "Transaction count", "schema": { "title": "Transaction count", - "type": "array", - "items": { - "$ref": "#/components/schemas/uint" - } + "$ref": "#/components/schemas/uint" } } }, @@ -683,7 +679,7 @@ "name": "The current client version.", "schema": { "type": "string", - "pattern": "relay/" + "pattern": "relay\/[0-9]\\.[0-9]\\.[0-9]" } } } @@ -1378,6 +1374,11 @@ "title": "Error message", "type": "string", "pattern": "Unsupported JSON-RPC method" + }, + "name": { + "title": "Error name", + "type": "string", + "pattern": "Method not found" } } } diff --git a/package.json b/package.json index 03b6f8964..afc3616ee 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "start": "npx lerna exec --scope @hashgraph/json-rpc-server -- npm run start", "start:docker": "docker run --name hedera-relay -d -p 7546:7546 ${npm_package_name}:latest", "test": "npx lerna run test", - "openrpctest": "ts-mocha packages/server/tests/integration/openrpc.spec.ts", + "openrpctest": "ts-mocha packages/relay/tests/lib/openrpc.spec.ts --exit", "integration:prerequisite": "ts-node packages/server/tests/helpers/prerequisite.ts" }, "dependencies": { diff --git a/packages/relay/src/index.ts b/packages/relay/src/index.ts index 708928e6b..8be869279 100644 --- a/packages/relay/src/index.ts +++ b/packages/relay/src/index.ts @@ -61,9 +61,9 @@ export interface Eth { getBlockByNumber(blockNum: string, showDetails: boolean): Promise; - getBlockTransactionCountByHash(hash: string): Promise; + getBlockTransactionCountByHash(hash: string): Promise; - getBlockTransactionCountByNumber(blockNum: string): Promise + getBlockTransactionCountByNumber(blockNum: string): Promise getCode(address: string, blockNumber: string | null): Promise; diff --git a/packages/relay/src/lib/eth.ts b/packages/relay/src/lib/eth.ts index eb704dfe9..16a2a1ce2 100644 --- a/packages/relay/src/lib/eth.ts +++ b/packages/relay/src/lib/eth.ts @@ -530,7 +530,7 @@ export class EthImpl implements Eth { * * @param hash */ - async getBlockTransactionCountByHash(hash: string): Promise { + async getBlockTransactionCountByHash(hash: string): Promise { this.logger.trace('getBlockTransactionCountByHash(hash=%s, showDetails=%o)', hash); return this.mirrorNodeClient .getBlock(hash) @@ -545,7 +545,7 @@ export class EthImpl implements Eth { * Gets the number of transaction in a block by its block number. * @param blockNumOrTag */ - async getBlockTransactionCountByNumber(blockNumOrTag: string): Promise { + async getBlockTransactionCountByNumber(blockNumOrTag: string): Promise { this.logger.trace('getBlockTransactionCountByNumber(blockNum=%s, showDetails=%o)', blockNumOrTag); const blockNum = await this.translateBlockTag(blockNumOrTag); return this.mirrorNodeClient @@ -751,13 +751,13 @@ export class EthImpl implements Eth { input: contractResult.function_parameters, maxPriorityFeePerGas: maxPriorityFee, maxFeePerGas: maxFee, - nonce: contractResult.nonce, + nonce: EthImpl.nullableNumberTo0x(contractResult.nonce), r: rSig, s: sSig, to: contractResult.to?.substring(0, 42), transactionIndex: EthImpl.numberTo0x(contractResult.transaction_index), - type: contractResult.type, - v: contractResult.v, + type: EthImpl.nullableNumberTo0x(contractResult.type), + v: EthImpl.nullableNumberTo0x(contractResult.v), value: EthImpl.numberTo0x(contractResult.amount), }); } @@ -836,6 +836,10 @@ export class EthImpl implements Eth { return EthImpl.emptyHex + input.toString(16); } + static nullableNumberTo0x(input: number | BigNumber): string | null { + return input === null ? null : EthImpl.numberTo0x(input); + } + static toHash32(value: string): string { return value.substring(0, 66); } @@ -970,7 +974,7 @@ export class EthImpl implements Eth { return null; } - return block.count; + return EthImpl.numberTo0x(block.count); } private getTransactionFromContractResults(contractResults: any) { @@ -1006,13 +1010,13 @@ export class EthImpl implements Eth { input: contractResultDetails.function_parameters, maxPriorityFeePerGas: EthImpl.toNullIfEmptyHex(contractResultDetails.max_priority_fee_per_gas), maxFeePerGas: EthImpl.toNullIfEmptyHex(contractResultDetails.max_fee_per_gas), - nonce: contractResultDetails.nonce, + nonce: EthImpl.nullableNumberTo0x(contractResultDetails.nonce), r: rSig, s: sSig, to: contractResultDetails.to.substring(0, 42), transactionIndex: EthImpl.numberTo0x(contractResultDetails.transaction_index), - type: contractResultDetails.type, - v: contractResultDetails.v, + type: EthImpl.nullableNumberTo0x(contractResultDetails.type), + v: EthImpl.nullableNumberTo0x(contractResultDetails.v), value: EthImpl.numberTo0x(contractResultDetails.amount), }); }) diff --git a/packages/relay/tests/helpers.ts b/packages/relay/tests/helpers.ts index 3a13d4d98..767ad8f10 100644 --- a/packages/relay/tests/helpers.ts +++ b/packages/relay/tests/helpers.ts @@ -18,8 +18,8 @@ * */ -import {expect} from "chai"; -import {ethers} from 'ethers'; +import { expect } from "chai"; +import { ethers } from 'ethers'; // Randomly generated key const defaultPrivateKey = '8841e004c6f47af679c91d9282adc62aeb9fabd19cdff6a9da5a358d0613c30a'; @@ -93,4 +93,300 @@ const mockData = { } }; -export {expectUnsupportedMethod, expectedError, signTransaction, mockData}; +export { expectUnsupportedMethod, expectedError, signTransaction, mockData }; + + +export const bytecode = '0x608060405234801561001057600080fd5b5060405161078938038061078983398181016040528101906100329190'; +export const blockHashTrimmed = '0x3c08bbbee74d287b1dcd3f0ca6d1d2cb92c90883c4acf9747de9f3f3162ad25b'; +export const blockHash = `${blockHashTrimmed}999fc7e86699f60f2a3fb3ed9a646c6b`; +export const blockHash2 = `${blockHashTrimmed}999fc7e86699f60f2a3fb3ed9a646c6c`; +export const blockHash3 = `${blockHashTrimmed}999fc7e86699f60f2a3fb3ed9a646c6d`; +export const blockNumber = 3; +export const blockNumber2 = 4; +export const blockNumber3 = 5; +export const blockTransactionCount = 77; +export const gasUsed1 = 200000; +export const gasUsed2 = 800000; +export const maxGasLimit = 250000; +export const firstTransactionTimestampSeconds = '1653077547'; +export const contractAddress1 = '0x000000000000000000000000000000000000055f'; +export const contractTimestamp1 = `${firstTransactionTimestampSeconds}.983983199`; +export const contractHash1 = '0x4a563af33c4871b51a8b108aa2fe1dd5280a30dfb7236170ae5e5e7957eb6392'; +export const contractHash2 = '0x4a563af33c4871b51a8b108aa2fe1dd5280a30dfb7236170ae5e5e7957eb6393'; +export const contractHash3 = '0x4a563af33c4871b51a8b108aa2fe1dd5280a30dfb7236170ae5e5e7957eb6394'; +export const contractAddress2 = '0x000000000000000000000000000000000000055e'; +export const contractAddress3 = '0x44FDFC794FFFF9Ef010672DfC483b6DDb1950989'; +export const contractTimestamp2 = '1653077542.701408897'; +export const contractTimestamp3 = '1653088542.123456789'; +export const contractId1 = '0.0.5001'; +export const contractId2 = '0.0.5002'; +export const signedTransactionHash = '0x02f87482012a0485a7a358200085a7a3582000832dc6c09400000000000000000000000000000000000003f78502540be40080c001a006f4cd8e6f84b76a05a5c1542a08682c928108ef7163d9c1bf1f3b636b1cd1fba032097cbf2dda17a2dcc40f62c97964d9d930cdce2e8a9df9a8ba023cda28e4ad' + +export const defaultBlock = { + 'count': blockTransactionCount, + 'hapi_version': '0.27.0', + 'hash': blockHash, + 'name': '2022-05-03T06_46_26.060890949Z.rcd', + 'number': blockNumber, + 'previous_hash': '0xf7d6481f659c866c35391ee230c374f163642ebf13a5e604e04a95a9ca48a298dc2dfa10f51bcbaab8ae23bc6d662a0b', + 'size': null, + 'timestamp': { + 'from': '1651560386.060890949', + 'to': '1651560389.060890949' + } +}; +export const defaultContractResults = { + 'results': [ + { + 'amount': 1, + 'bloom': '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + 'call_result': '0x6080604052600436106100385760003560e01c80632b6adf431461003c5780633d99e80d1461010f5780634bfdab701461015257610038565b5b5b005b61010d600480360360408110156100535760006000fd5b81019080803563ffffffff169060200190929190803590602001906401000000008111156100815760006000fd5b8201836020820111156100945760006000fd5b803590602001918460018302840111640100000000831117156100b75760006000fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505090909192909091929050505061018a565b005b34801561011c5760006000fd5b50610150600480360360208110156101345760006000fd5b81019080803563ffffffff169060200190929190505050610292565b005b34801561015f5760006000fd5b506101686102b7565b604051808263ffffffff1663ffffffff16815260200191505060405180910390f35b60008263ffffffff166effffffffffffffffffffffffffffff1690508073ffffffffffffffffffffffffffffffffffffffff166108fc60019081150290604051600060405180830381858888f193505050501580156101ee573d600060003e3d6000fd5b507f930f628a0950173c55b8f7d31636aa82e481f09d70191adc38b8c8cd186a0ad7826040518080602001828103825283818151815260200191508051906020019080838360005b838110156102525780820151818401525b602081019050610236565b50505050905090810190601f16801561027f5780820380516001836020036101000a031916815260200191505b509250505060405180910390a1505b5050565b80600060006101000a81548163ffffffff021916908363ffffffff1602179055505b50565b6000600060009054906101000a900463ffffffff1690506102d3565b9056fea265627a7a723158201b51cf608b8b7e2c5d36bd8733f2213b669e5d1cfa53b67f52a7e878d1d7bb0164736f6c634300050b0032', + 'contract_id': '0.0.1375', + 'created_contract_ids': ['0.0.1375'], + 'error_message': null, + 'from': '0x0000000000000000000000000000000000000557', + 'function_parameters': '0x', + 'gas_limit': maxGasLimit, + 'gas_used': gasUsed1, + 'hash': contractHash1, + 'timestamp': `${contractTimestamp1}`, + 'to': `${contractAddress1}` + }, + { + 'amount': 0, + 'bloom': '0x00000000000000000000000000000000000000000000000000000000000000040000000000000000000001000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000', + 'call_result': '0x', + 'contract_id': '0.0.1374', + 'created_contract_ids': [], + 'error_message': null, + 'from': '0x0000000000000000000000000000000000000557', + 'function_parameters': '0x2b6adf430000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000084865792c204d6121000000000000000000000000000000000000000000000000', + 'gas_limit': maxGasLimit - 1000, + 'gas_used': gasUsed2, + 'hash': contractHash2, + 'timestamp': `${contractTimestamp2}`, + 'to': `${contractAddress2}` + } + ], + 'links': { + 'next': '/api/v1/contracts/results?limit=2×tamp=lt:1653077542.701408897' + } +}; + +export const defaultLogTopics = [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000000000000000000000000000000000000208fa13", + "0x0000000000000000000000000000000000000000000000000000000000000005" +]; + + +export const logBloom1 = '0x1111'; +export const logBloom2 = '0x2222'; +export const logBloom3 = '0x3333'; +export const logBloom4 = '0x4444'; + +export const defaultLogs1 = [ + { + "address": "0x0000000000000000000000000000000002131951", + "bloom": logBloom1, + "contract_id": contractId1, + "data": "0x", + "index": "0x0", + "topics": defaultLogTopics, + "root_contract_id": "0.0.34806097", + "timestamp": contractTimestamp1 + }, + { + "address": "0x0000000000000000000000000000000002131951", + "bloom": logBloom2, + "contract_id": contractId1, + "data": "0x", + "index": "0x1", + "topics": defaultLogTopics, + "root_contract_id": "0.0.34806097", + "timestamp": contractTimestamp1 + } +]; + +export const defaultLogs2 = [ + { + "address": "0x0000000000000000000000000000000002131951", + "bloom": logBloom3, + "contract_id": contractId1, + "data": "0x", + "index": "0x0", + "topics": [], + "root_contract_id": "0.0.34806097", + "timestamp": contractTimestamp2 + } +]; + +export const defaultLogs3 = [ + { + "address": "0x0000000000000000000000000000000002131951", + "bloom": logBloom4, + "contract_id": contractId2, + "data": "0x", + "index": "0x0", + "topics": [], + "root_contract_id": "0.0.34806097", + "timestamp": contractTimestamp3 + } +]; + +export const defaultLogsList = defaultLogs1.concat(defaultLogs2).concat(defaultLogs3); +export const defaultLogs = { + "logs": defaultLogsList +}; + +export const defaultDetailedContractResults = { + 'access_list': '0x', + 'amount': 2000000000, + 'block_gas_used': 50000000, + 'block_hash': blockHash, + 'block_number': blockNumber, + 'bloom': '0x0505', + 'call_result': '0x0606', + 'chain_id': '0x12a', + 'contract_id': contractId1, + 'created_contract_ids': ['0.0.7001'], + 'error_message': null, + 'from': '0x0000000000000000000000000000000000001f41', + 'function_parameters': '0x0707', + 'gas_limit': 1000000, + 'gas_price': '0x4a817c80', + 'gas_used': 123, + 'hash': contractHash1, + 'logs': defaultLogs1, + 'max_fee_per_gas': '0x', + 'max_priority_fee_per_gas': '0x', + 'nonce': 1, + 'r': '0xd693b532a80fed6392b428604171fb32fdbf953728a3a7ecc7d4062b1652c042', + 'result': 'SUCCESS', + 's': '0x24e9c602ac800b983b035700a14b23f78a253ab762deab5dc27e3555a750b354', + 'state_changes': [ + { + 'address': contractAddress1, + 'contract_id': contractId1, + 'slot': '0x0000000000000000000000000000000000000000000000000000000000000101', + 'value_read': '0x97c1fc0a6ed5551bc831571325e9bdb365d06803100dc20648640ba24ce69750', + 'value_written': '0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925' + } + ], + 'status': '0x1', + 'timestamp': contractTimestamp1, + 'to': contractAddress1, + 'transaction_index': 1, + 'type': 2, + 'v': 1 +}; + +export const defaultDetailedContractResults2 = { + ...defaultDetailedContractResults, ...{ + 'timestamp': contractTimestamp2, + 'block_hash': blockHash2, + 'block_number': blockNumber2, + 'hash': contractHash2, + 'logs': defaultLogs2 + } +}; + +export const defaultDetailedContractResults3 = { + ...defaultDetailedContractResults, ...{ + 'timestamp': contractTimestamp3, + 'block_hash': blockHash3, + 'block_number': blockNumber3, + 'hash': contractHash3, + 'contract_id': contractId2, + 'logs': defaultLogs3 + } +}; +export const defaultNetworkFees = { + 'fees': [ + { + 'gas': 77, + 'transaction_type': 'ContractCall' + }, + { + 'gas': 771, + 'transaction_type': 'ContractCreate' + }, + { + 'gas': 57, + 'transaction_type': 'EthereumTransaction' + } + ], + 'timestamp': '1653644164.591111113' +}; + +export const defaultTxHash = '0x4a563af33c4871b51a8b108aa2fe1dd5280a30dfb7236170ae5e5e7957eb6392'; +export const defaultTransaction = { + "accessList": undefined, + "blockHash": "0xd693b532a80fed6392b428604171fb32fdbf953728a3a7ecc7d4062b1652c042", + "blockNumber": "0x11", + "chainId": "0x12a", + "from": "0x0000000000000000000000000000000000001f41", + "gas": "0x7b", + "gasPrice": "0x4a817c80", + "hash": defaultTxHash, + "input": "0x0707", + "maxFeePerGas": undefined, + "maxPriorityFeePerGas": undefined, + "nonce": 1, + "r": "0xd693b532a80fed6392b428604171fb32fdbf953728a3a7ecc7d4062b1652c042", + "s": "0x24e9c602ac800b983b035700a14b23f78a253ab762deab5dc27e3555a750b354", + "to": "0x0000000000000000000000000000000000001389", + "transactionIndex": "0x1", + "type": 2, + "v": 1, + "value": "0x77359400" +}; + +export const defaultDetailedContractResultByHash = { + "amount": 2000000000, + "bloom": + "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "call_result": "0x0606", + "contract_id": "0.0.5001", + "created_contract_ids": ["0.0.7001"], + "error_message": null, + "from": "0x0000000000000000000000000000000000001f41", + "function_parameters": "0x0707", + "gas_limit": 1000000, + "gas_used": 123, + "timestamp": "167654.000123456", + "to": "0x0000000000000000000000000000000000001389", + "block_hash": "0xd693b532a80fed6392b428604171fb32fdbf953728a3a7ecc7d4062b1652c042000102030405060708090a0b0c0d0e0f", + "block_number": 17, + "logs": [{ + "address": "0x0000000000000000000000000000000000001389", + "bloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "contract_id": "0.0.5001", + "data": "0x0123", + "index": 0, + "topics": ["0x97c1fc0a6ed5551bc831571325e9bdb365d06803100dc20648640ba24ce69750"] + }], + "result": "SUCCESS", + "transaction_index": 1, + "hash": "0x4a563af33c4871b51a8b108aa2fe1dd5280a30dfb7236170ae5e5e7957eb6392", + "state_changes": [{ + "address": "0x0000000000000000000000000000000000001389", + "contract_id": "0.0.5001", + "slot": "0x0000000000000000000000000000000000000000000000000000000000000101", + "value_read": "0x97c1fc0a6ed5551bc831571325e9bdb365d06803100dc20648640ba24ce69750", + "value_written": "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925" + }], + "status": "0x1", + "access_list": "0x", + "block_gas_used": 50000000, + "chain_id": "0x12a", + "gas_price": "0x4a817c80", + "max_fee_per_gas": "0x", + "max_priority_fee_per_gas": "0x", + "r": "0xd693b532a80fed6392b428604171fb32fdbf953728a3a7ecc7d4062b1652c042", + "s": "0x24e9c602ac800b983b035700a14b23f78a253ab762deab5dc27e3555a750b354", + "type": 2, + "v": 1, + "nonce": 1 +}; \ No newline at end of file diff --git a/packages/relay/tests/lib/eth.spec.ts b/packages/relay/tests/lib/eth.spec.ts index 618dc4ecf..671c0e3d6 100644 --- a/packages/relay/tests/lib/eth.spec.ts +++ b/packages/relay/tests/lib/eth.spec.ts @@ -652,7 +652,7 @@ describe('Eth calls using MirrorNode', async function () { mock.onGet(`blocks/${blockNumber}`).reply(200, defaultBlock); const result = await ethImpl.getBlockTransactionCountByNumber(blockNumber.toString()); - expect(result).equal(blockTransactionCount); + expect(result).equal(EthImpl.numberTo0x(blockTransactionCount)); }); it('eth_getBlockTransactionCountByNumber with no match', async function () { @@ -676,7 +676,7 @@ describe('Eth calls using MirrorNode', async function () { mock.onGet(`blocks/${blockNumber}`).reply(200, defaultBlock); const result = await ethImpl.getBlockTransactionCountByNumber('latest'); - expect(result).equal(blockTransactionCount); + expect(result).equal(EthImpl.numberTo0x(blockTransactionCount)); }); it('eth_getBlockTransactionCountByNumber with pending tag', async function () { @@ -685,7 +685,7 @@ describe('Eth calls using MirrorNode', async function () { mock.onGet(`blocks/${blockNumber}`).reply(200, defaultBlock); const result = await ethImpl.getBlockTransactionCountByNumber('pending'); - expect(result).equal(blockTransactionCount); + expect(result).equal(EthImpl.numberTo0x(blockTransactionCount)); }); it('eth_getBlockTransactionCountByNumber with earliest tag', async function () { @@ -693,7 +693,7 @@ describe('Eth calls using MirrorNode', async function () { mock.onGet(`blocks/0`).reply(200, defaultBlock); const result = await ethImpl.getBlockTransactionCountByNumber('earliest'); - expect(result).equal(blockTransactionCount); + expect(result).equal(EthImpl.numberTo0x(blockTransactionCount)); }); it('eth_getBlockTransactionCountByNumber with hex number', async function () { @@ -701,7 +701,7 @@ describe('Eth calls using MirrorNode', async function () { mock.onGet(`blocks/3735929054`).reply(200, defaultBlock); const result = await ethImpl.getBlockTransactionCountByNumber('0xdeadc0de'); - expect(result).equal(blockTransactionCount); + expect(result).equal(EthImpl.numberTo0x(blockTransactionCount)); }); it('eth_getBlockTransactionCountByHash with match', async function () { @@ -709,7 +709,7 @@ describe('Eth calls using MirrorNode', async function () { mock.onGet(`blocks/${blockHash}`).reply(200, defaultBlock); const result = await ethImpl.getBlockTransactionCountByHash(blockHash); - expect(result).equal(blockTransactionCount); + expect(result).equal(EthImpl.numberTo0x(blockTransactionCount)); }); it('eth_getBlockTransactionCountByHash with no match', async function () { @@ -1681,13 +1681,13 @@ describe('Eth', async function () { expect(result.input).to.eq(defaultTransaction.input); expect(result.maxFeePerGas).to.eq(defaultTransaction.maxFeePerGas); expect(result.maxPriorityFeePerGas).to.eq(defaultTransaction.maxPriorityFeePerGas); - expect(result.nonce).to.eq(defaultTransaction.nonce); + expect(result.nonce).to.eq(EthImpl.numberTo0x(defaultTransaction.nonce)); expect(result.r).to.eq(defaultTransaction.r); expect(result.s).to.eq(defaultTransaction.s); expect(result.to).to.eq(defaultTransaction.to); expect(result.transactionIndex).to.eq(defaultTransaction.transactionIndex); - expect(result.type).to.eq(defaultTransaction.type); - expect(result.v).to.eq(defaultTransaction.v); + expect(result.type).to.eq(EthImpl.numberTo0x(defaultTransaction.type)); + expect(result.v).to.eq(EthImpl.numberTo0x(defaultTransaction.v)); expect(result.value).to.eq(defaultTransaction.value); }); @@ -1715,13 +1715,13 @@ describe('Eth', async function () { expect(result.input).to.eq(defaultTransaction.input); expect(result.maxFeePerGas).to.eq(defaultTransaction.maxFeePerGas); expect(result.maxPriorityFeePerGas).to.eq(defaultTransaction.maxPriorityFeePerGas); - expect(result.nonce).to.eq(defaultTransaction.nonce); + expect(result.nonce).to.eq(EthImpl.numberTo0x(defaultTransaction.nonce)); expect(result.r).to.be.null; expect(result.s).to.be.null; expect(result.to).to.eq(defaultTransaction.to); expect(result.transactionIndex).to.eq(defaultTransaction.transactionIndex); - expect(result.type).to.eq(defaultTransaction.type); - expect(result.v).to.eq(defaultTransaction.v); + expect(result.type).to.eq(EthImpl.numberTo0x(defaultTransaction.type)); + expect(result.v).to.eq(EthImpl.numberTo0x(defaultTransaction.v)); expect(result.value).to.eq(defaultTransaction.value); }); }); diff --git a/packages/relay/tests/lib/openrpc.spec.ts b/packages/relay/tests/lib/openrpc.spec.ts new file mode 100644 index 000000000..f1918e396 --- /dev/null +++ b/packages/relay/tests/lib/openrpc.spec.ts @@ -0,0 +1,435 @@ +/*- + * + * 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 { expect } from 'chai'; +import { validateOpenRPCDocument, parseOpenRPCDocument } from "@open-rpc/schema-utils-js"; + +import Ajv from 'ajv' + +import path from 'path'; +import pino from 'pino'; +import axios from 'axios'; +import sinon from 'sinon'; +import dotenv from 'dotenv'; +import MockAdapter from 'axios-mock-adapter'; +import { RelayImpl } from '@hashgraph/json-rpc-relay'; +import { Registry } from 'prom-client'; + +import { EthImpl } from '../../src/lib/eth'; +import { SDKClient } from '../../src/lib/clients'; +import { MirrorNodeClient } from '../../src/lib/clients/mirrorNodeClient'; + +import openRpcSchema from "../../../../docs/openrpc.json"; +import { + blockHash, + blockNumber, + bytecode, + contractAddress1, + contractAddress2, + contractAddress3, + contractId1, + contractId2, + contractTimestamp1, + contractTimestamp2, + contractTimestamp3, + defaultBlock, + defaultContractResults, + defaultDetailedContractResultByHash, + defaultDetailedContractResults, + defaultDetailedContractResults2, + defaultDetailedContractResults3, + defaultLogs, + defaultLogTopics, + defaultNetworkFees, + defaultTransaction, + defaultTxHash, + signedTransactionHash +} from '../helpers'; + +dotenv.config({ path: path.resolve(__dirname, '../test.env') }); + +process.env.npm_package_version = "relay/0.0.1-SNAPSHOT"; + +const logger = pino(); +const registry = new Registry(); +const Relay = new RelayImpl(logger, registry); + +let mock: MockAdapter; +let mirrorNodeInstance: MirrorNodeClient; +let sdkClientStub: any; + + +describe("Open RPC Specification", function () { + + let rpcDocument: any; + let methodsResponseSchema: { [method: string]: any }; + let ethImpl: EthImpl; + + this.beforeAll(async () => { + rpcDocument = await parseOpenRPCDocument(JSON.stringify(openRpcSchema)); + methodsResponseSchema = rpcDocument.methods.reduce((res: { [method: string]: any }, method: any) => ({ + ...res, + [method.name]: method.result.schema + }), {}); + + // mock axios + const instance = axios.create({ + baseURL: 'https://localhost:5551/api/v1', + responseType: 'json' as const, + headers: { + 'Content-Type': 'application/json' + }, + timeout: 10 * 1000 + }); + + // @ts-ignore + mock = new MockAdapter(instance, { onNoMatch: "throwException" }); + // @ts-ignore + mirrorNodeInstance = new MirrorNodeClient(process.env.MIRROR_NODE_URL, logger.child({ name: `mirror-node` }), registry, instance); + sdkClientStub = sinon.createStubInstance(SDKClient); + // @ts-ignore + ethImpl = new EthImpl(sdkClientStub, mirrorNodeInstance, logger, '0x12a'); + + // mocked data + mock.onGet('blocks?limit=1&order=desc').reply(200, { blocks: [defaultBlock] }); + mock.onGet(`blocks/${defaultBlock.number}`).reply(200, defaultBlock); + mock.onGet(`blocks/${blockHash}`).reply(200, defaultBlock); + mock.onGet('network/fees').reply(200, defaultNetworkFees); + mock.onGet(`network/fees?timestamp=lte:${defaultBlock.timestamp.to}`).reply(200, defaultNetworkFees); + mock.onGet(`contracts/${contractAddress1}`).reply(200, null); + mock.onGet(`contracts/results?timestamp=gte:${defaultBlock.timestamp.from}×tamp=lte:${defaultBlock.timestamp.to}`).reply(200, defaultContractResults); + mock.onGet(`contracts/results/logs`).reply(200, defaultLogs); + mock.onGet(`contracts/results/${defaultTxHash}`).reply(200, defaultDetailedContractResultByHash); + mock.onGet(`contracts/results?block.hash=${defaultBlock.hash}&transaction.index=${defaultBlock.count}`).reply(200, defaultContractResults); + mock.onGet(`contracts/results?block.number=${defaultBlock.number}&transaction.index=${defaultBlock.count}`).reply(200, defaultContractResults); + mock.onGet(`contracts/${contractAddress1}/results/${contractTimestamp1}`).reply(200, defaultDetailedContractResults); + mock.onGet(`contracts/${contractAddress2}/results/${contractTimestamp2}`).reply(200, defaultDetailedContractResults); + mock.onGet(`contracts/${contractId1}/results/${contractTimestamp1}`).reply(200, defaultDetailedContractResults); + mock.onGet(`contracts/${contractId1}/results/${contractTimestamp2}`).reply(200, defaultDetailedContractResults2); + mock.onGet(`contracts/${contractId2}/results/${contractTimestamp3}`).reply(200, defaultDetailedContractResults3); + mock.onGet(`accounts/${contractAddress1}`).reply(200, { account: contractAddress1 }); + mock.onGet(`accounts/${contractAddress3}`).reply(200, { account: contractAddress3 }); + mock.onGet(`accounts/0xbC989b7b17d18702663F44A6004cB538b9DfcBAc`).reply(200, { account: '0xbC989b7b17d18702663F44A6004cB538b9DfcBAc' }); + + sdkClientStub.getAccountBalanceInWeiBar.returns(1000); + sdkClientStub.getAccountBalanceInTinyBar.returns(100000000000) + sdkClientStub.getContractByteCode.returns(Buffer.from(bytecode.replace('0x', ''), 'hex')); + sdkClientStub.getAccountInfo.returns({ ethereumNonce: '0x1' }); + sdkClientStub.submitEthereumTransaction.returns({}); + }); + + const validateResponseSchema = (schema: any, response: any) => { + const ajv = new Ajv(); + ajv.validate(schema, response); + + if (ajv.errors && ajv.errors.length > 0) { + console.log({ + errors: ajv.errors + }); + } + + expect(ajv.errors).to.be.null; + } + + it(`validates the openrpc document`, async () => { + const rpcDocument = await parseOpenRPCDocument(JSON.stringify(openRpcSchema)); + const isValid = validateOpenRPCDocument(rpcDocument); + + expect(isValid).to.be.true; + }); + + it('should execute "eth_accounts"', function () { + const response = ethImpl.accounts(); + + validateResponseSchema(methodsResponseSchema.eth_accounts, response); + }); + + it('should execute "eth_blockNumber"', async function () { + const response = await ethImpl.blockNumber(); + + validateResponseSchema(methodsResponseSchema.eth_blockNumber, response); + }); + + it('should execute "eth_call"', async function () { + sdkClientStub.submitContractCallQuery.returns({ asBytes: () => Buffer.from('12') }); + + const response = await ethImpl.call(defaultTransaction, 'latest'); + + validateResponseSchema(methodsResponseSchema.eth_call, response); + }); + + it('should execute "eth_chainId"', function () { + const response = ethImpl.chainId(); + + validateResponseSchema(methodsResponseSchema.eth_chainId, response); + }); + + it('should execute "eth_coinbase"', function () { + const response = ethImpl.coinbase(); + + validateResponseSchema(methodsResponseSchema.eth_coinbase, response); + }); + + it('should execute "eth_estimateGas"', async function () { + const response = await ethImpl.estimateGas({}, null); + + validateResponseSchema(methodsResponseSchema.eth_estimateGas, response); + }); + + it('should execute "eth_feeHistory"', async function () { + const response = await ethImpl.feeHistory(1, 'latest', [0]); + + validateResponseSchema(methodsResponseSchema.eth_feeHistory, response); + }); + + it('should execute "eth_gasPrice"', async function () { + const response = await ethImpl.gasPrice(); + + validateResponseSchema(methodsResponseSchema.eth_gasPrice, response); + }); + + it('should execute "eth_getBalance"', async function () { + const response = await ethImpl.getBalance(contractAddress1, 'latest'); + + validateResponseSchema(methodsResponseSchema.eth_getBalance, response); + }); + + it('should execute "eth_getBlockByHash" with hydrated = true', async function () { + const response = await ethImpl.getBlockByHash(blockHash, true); + + validateResponseSchema(methodsResponseSchema.eth_getBlockByHash, response); + }); + + it('should execute "eth_getBlockByHash" with hydrated = false', async function () { + const response = await ethImpl.getBlockByHash(blockHash, true); + + validateResponseSchema(methodsResponseSchema.eth_getBlockByHash, response); + }); + + it('should execute "eth_getBlockByNumber" with hydrated = true', async function () { + const response = await ethImpl.getBlockByNumber(EthImpl.numberTo0x(blockNumber), true); + + validateResponseSchema(methodsResponseSchema.eth_getBlockByNumber, response); + }); + + it('should execute "eth_getBlockByNumber" with hydrated = false', async function () { + const response = await ethImpl.getBlockByNumber(EthImpl.numberTo0x(blockNumber), false); + + validateResponseSchema(methodsResponseSchema.eth_getBlockByNumber, response); + }); + + it('should execute "eth_getBlockTransactionCountByHash"', async function () { + const response = await ethImpl.getBlockTransactionCountByHash(blockHash); + + validateResponseSchema(methodsResponseSchema.eth_getBlockTransactionCountByHash, response); + }); + + it('should execute "eth_getBlockTransactionCountByNumber" with block tag', async function () { + const response = await ethImpl.getBlockTransactionCountByNumber('latest'); + + validateResponseSchema(methodsResponseSchema.eth_getBlockTransactionCountByNumber, response); + }); + + it('should execute "eth_getBlockTransactionCountByNumber" with block number', async function () { + const response = await ethImpl.getBlockTransactionCountByNumber('0x3'); + + validateResponseSchema(methodsResponseSchema.eth_getBlockTransactionCountByNumber, response); + }); + + it('should execute "eth_getCode" with block tag', async function () { + const response = await ethImpl.getCode(contractAddress1, 'latest'); + + validateResponseSchema(methodsResponseSchema.eth_getCode, response); + }); + + it('should execute "eth_getCode" with block number', async function () { + const response = await ethImpl.getCode(contractAddress1, '0x3'); + + validateResponseSchema(methodsResponseSchema.eth_getCode, response); + }); + + it('should execute "eth_getLogs" with no filters', async function () { + const response = await ethImpl.getLogs(null, null, null, null, null); + + validateResponseSchema(methodsResponseSchema.eth_getLogs, response); + }); + + it('should execute "eth_getLogs" with topics filter', async function () { + const filteredLogs = { + logs: [defaultLogs.logs[0], defaultLogs.logs[1]] + }; + mock.onGet( + `contracts/results/logs` + + `?topic0=${defaultLogTopics[0]}&topic1=${defaultLogTopics[1]}` + + `&topic2=${defaultLogTopics[2]}&topic3=${defaultLogTopics[3]}` + ).reply(200, filteredLogs); + mock.onGet('blocks?block.number=gte:0x5&block.number=lte:0x10').reply(200, { + blocks: [defaultBlock] + }); + + const response = await ethImpl.getLogs(null, null, null, null, defaultLogTopics); + + validateResponseSchema(methodsResponseSchema.eth_getLogs, response); + }); + + it('should execute "eth_getTransactionByBlockHashAndIndex"', async function () { + const response = await ethImpl.getTransactionByBlockHashAndIndex(defaultBlock.hash, defaultBlock.count); + + validateResponseSchema(methodsResponseSchema.eth_getTransactionByBlockHashAndIndex, response); + }); + + it('should execute "eth_getTransactionByBlockNumberAndIndex"', async function () { + const response = await ethImpl.getTransactionByBlockNumberAndIndex(defaultBlock.number.toString(), defaultBlock.count); + + validateResponseSchema(methodsResponseSchema.eth_getTransactionByBlockNumberAndIndex, response); + }); + + it('should execute "eth_getTransactionByHash"', async function () { + const response = await ethImpl.getTransactionByHash(defaultTxHash); + + validateResponseSchema(methodsResponseSchema.eth_getTransactionByHash, response); + }); + + it('should execute "eth_getTransactionCount"', async function () { + const response = await ethImpl.getTransactionCount(contractAddress1, 'latest'); + + validateResponseSchema(methodsResponseSchema.eth_getTransactionCount, response); + }); + + it('should execute "eth_getTransactionReceipt"', async function () { + const response = await ethImpl.getTransactionReceipt(defaultTxHash); + + validateResponseSchema(methodsResponseSchema.eth_getTransactionReceipt, response); + }); + + it('should execute "eth_getUncleByBlockHashAndIndex"', async function () { + const response = await ethImpl.getUncleByBlockHashAndIndex(); + + validateResponseSchema(methodsResponseSchema.eth_getUncleByBlockHashAndIndex, response); + }); + + it('should execute "eth_getUncleByBlockNumberAndIndex"', async function () { + const response = await ethImpl.getUncleByBlockNumberAndIndex(); + + validateResponseSchema(methodsResponseSchema.eth_getUncleByBlockNumberAndIndex, response); + }); + + it('should execute "eth_getUncleByBlockNumberAndIndex"', async function () { + const response = await ethImpl.getUncleByBlockNumberAndIndex(); + + validateResponseSchema(methodsResponseSchema.eth_getUncleByBlockNumberAndIndex, response); + }); + + it('should execute "eth_getUncleCountByBlockHash"', async function () { + const response = await ethImpl.getUncleCountByBlockHash(); + + validateResponseSchema(methodsResponseSchema.eth_getUncleCountByBlockHash, response); + }); + + it('should execute "eth_getUncleCountByBlockNumber"', async function () { + const response = await ethImpl.getUncleCountByBlockNumber(); + + validateResponseSchema(methodsResponseSchema.eth_getUncleCountByBlockNumber, response); + }); + + it('should execute "eth_getWork"', async function () { + const response = ethImpl.getWork(); + + validateResponseSchema(methodsResponseSchema.eth_getWork, response); + }); + + it('should execute "eth_hashrate"', async function () { + const response = await ethImpl.hashrate(); + + validateResponseSchema(methodsResponseSchema.eth_hashrate, response); + }); + + it('should execute "eth_mining"', async function () { + const response = await ethImpl.mining(); + + validateResponseSchema(methodsResponseSchema.eth_mining, response); + }); + + it('should execute "eth_protocolVersion"', async function () { + const response = ethImpl.protocolVersion(); + + validateResponseSchema(methodsResponseSchema.eth_protocolVersion, response); + }); + + it('should execute "eth_sendRawTransaction"', async function () { + const response = await ethImpl.sendRawTransaction(signedTransactionHash); + + validateResponseSchema(methodsResponseSchema.eth_sendRawTransaction, response); + }); + + it('should execute "eth_sendTransaction"', async function () { + const response = ethImpl.sendTransaction(); + + validateResponseSchema(methodsResponseSchema.eth_sendTransaction, response); + }); + + it('should execute "eth_signTransaction"', async function () { + const response = ethImpl.signTransaction(); + + validateResponseSchema(methodsResponseSchema.eth_signTransaction, response); + }); + + it('should execute "eth_sign"', async function () { + const response = ethImpl.sign(); + + validateResponseSchema(methodsResponseSchema.eth_sign, response); + }); + + it('should execute "eth_submitHashrate"', async function () { + const response = ethImpl.submitHashrate(); + + validateResponseSchema(methodsResponseSchema.eth_submitHashrate, response); + }); + + it('should execute "eth_submitWork"', async function () { + const response = await ethImpl.submitWork(); + + validateResponseSchema(methodsResponseSchema.eth_submitWork, response); + }); + + it('should execute "eth_syncing"', async function () { + const response = await ethImpl.syncing(); + + validateResponseSchema(methodsResponseSchema.eth_syncing, response); + }); + + it('should execute "net_listening"', function () { + const response = Relay.net().listening(); + + validateResponseSchema(methodsResponseSchema.net_listening, response); + }); + + it('should execute "net_version"', function () { + const response = Relay.net().version() + + validateResponseSchema(methodsResponseSchema.net_version, response); + }); + + it('should execute "web3_clientVersion"', function () { + const response = Relay.web3().clientVersion() + + validateResponseSchema(methodsResponseSchema.web3_clientVersion, response); + }); +}); diff --git a/packages/server/package.json b/packages/server/package.json index 23cc9baa8..01b5bd8db 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -44,8 +44,7 @@ "compile": "tsc -b tsconfig.json", "acceptancetest": "ts-mocha tests/acceptance/index.spec.ts", "start": "node dist/index.js", - "test": "nyc ts-mocha --recursive ./tests/integration/*.spec.ts", - "openrpctest": "ts-mocha ./tests/integration/openrpc.spec.ts", + "test": "nyc ts-mocha --recursive ./tests/integration/server.spec.ts", "integration:prerequisite": "ts-node ./tests/helpers/prerequisite.ts" }, "nyc": { diff --git a/packages/server/tests/acceptance/rpc.spec.ts b/packages/server/tests/acceptance/rpc.spec.ts index b48318d31..31233eb84 100644 --- a/packages/server/tests/acceptance/rpc.spec.ts +++ b/packages/server/tests/acceptance/rpc.spec.ts @@ -301,7 +301,7 @@ describe('RPC Server Acceptance Tests', function () { it('@release should execute "eth_getBlockTransactionCountByNumber"', async function () { const res = await relay.call('eth_getBlockTransactionCountByNumber', [mirrorBlock.number]); - expect(res).to.be.equal(mirrorBlock.count); + expect(res).to.be.equal(ethers.utils.hexValue(mirrorBlock.count)); }); it('should execute "eth_getBlockTransactionCountByNumber" for non-existing block number', async function () { @@ -311,7 +311,7 @@ describe('RPC Server Acceptance Tests', function () { it('@release should execute "eth_getBlockTransactionCountByHash"', async function () { const res = await relay.call('eth_getBlockTransactionCountByHash', [mirrorBlock.hash]); - expect(res).to.be.equal(mirrorBlock.count); + expect(res).to.be.equal(ethers.utils.hexValue(mirrorBlock.count)); }); it('should execute "eth_getBlockTransactionCountByHash" for non-existing block hash', async function () { diff --git a/packages/server/tests/integration/openrpc.spec.ts b/packages/server/tests/integration/openrpc.spec.ts deleted file mode 100644 index dc2fb5d6e..000000000 --- a/packages/server/tests/integration/openrpc.spec.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { expect } from 'chai'; -import { validateOpenRPCDocument, parseOpenRPCDocument } from "@open-rpc/schema-utils-js"; - -import openRpcSchema from "../../../../docs/openrpc.json"; - -describe("Open RPC Specification", () => { - it(`validates the openrpc document`, async () => { - const rpcDocument = await parseOpenRPCDocument(JSON.stringify(openRpcSchema)); - const isValid = validateOpenRPCDocument(rpcDocument); - - expect(isValid).to.be.true; - }); -});