Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added support for eth_getTransactionReceipt() to the WS server (#2274) #2297

12 changes: 9 additions & 3 deletions packages/server/tests/acceptance/ws/estimateGas.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@
*/

// external resources
import { expect } from 'chai';
import { Contract, ethers, JsonRpcProvider, WebSocketProvider } from 'ethers';
import { AliasAccount } from '../../clients/servicesClient';
import WebSocket from 'ws';
import { expect } from 'chai';
import basicContractJson from '../../contracts/Basic.json';
import { AliasAccount } from '../../clients/servicesClient';
import { Contract, ethers, JsonRpcProvider, WebSocketProvider } from 'ethers';

describe('@release @web-socket eth_estimateGas', async function () {
// @ts-ignore
Expand Down Expand Up @@ -58,6 +58,12 @@ describe('@release @web-socket eth_estimateGas', async function () {
gasPriceDeviation = parseFloat(expectedGas.toString() ?? '0.2');
});

afterEach(async () => {
if (wsProvider) {
await wsProvider.destroy();
}
});

it('@release should execute "eth_estimateGas" for contract call, using a websocket provider', async function () {
const estimatedGas = await wsProvider.estimateGas({
to: `0x${basicContract.contractId.toSolidityAddress()}`,
Expand Down
6 changes: 6 additions & 0 deletions packages/server/tests/acceptance/ws/getCode.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ describe('@release @web-socket eth_getCode', async function () {
webSocket = new WebSocket(WS_RELAY_URL);
});

afterEach(async () => {
if (wsProvider) {
await wsProvider.destroy();
}
});

it('should return the code ethers WebSocketProvider', async function () {
await new Promise((resolve) => setTimeout(resolve, 1000));
const codeFromWs = await wsProvider.getCode(`0x${basicContract.contractId.toSolidityAddress()}`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,11 @@ describe('@release @web-socket eth_getTransactionByHash', async function () {

beforeEach(async () => {
wsProvider = new ethers.WebSocketProvider(WS_RELAY_URL);
await new Promise((resolve) => setTimeout(resolve, 1000));
});

afterEach(async () => {
if (wsProvider) {
await wsProvider.destroy();
await new Promise((resolve) => setTimeout(resolve, 1000));
}
});

Expand Down
117 changes: 117 additions & 0 deletions packages/server/tests/acceptance/ws/getTransactionReceipt.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*-
*
* Hedera JSON RPC Relay
*
* Copyright (C) 2024 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.
*
*/

// external resources
import { expect } from 'chai';
import { ethers, WebSocketProvider } from 'ethers';
import RelayClient from '../../clients/relayClient';
import MirrorClient from '../../clients/mirrorClient';
import { AliasAccount } from '../../clients/servicesClient';
import { numberTo0x } from '@hashgraph/json-rpc-relay/src/formatters';
import { ONE_TINYBAR_IN_WEI_HEX } from '@hashgraph/json-rpc-relay/tests/lib/eth/eth-config';

describe('@release @web-socket eth_getTransactionReceipt', async function () {
const WS_RELAY_URL = `${process.env.WS_RELAY_URL}`;
const METHOD_NAME = 'eth_getTransactionReceipt';
const CHAIN_ID = process.env.CHAIN_ID || '0x12a';
const INVALID_PARAMS = [['hedera', 'hbar'], [], ['websocket', 'rpc', 'invalid']];
const INVALID_TX_HASH = ['0xhbar', '0xHedera', '', 66, 'abc', true, false, 39];

let accounts: AliasAccount[] = [];
let mirrorNodeServer: MirrorClient, requestId: string, relayClient: RelayClient, wsProvider: WebSocketProvider;

before(async () => {
// @ts-ignore
const { servicesNode, mirrorNode, relay } = global;

mirrorNodeServer = mirrorNode;
relayClient = relay;

accounts[0] = await servicesNode.createAliasAccount(100, relay.provider, requestId);
accounts[1] = await servicesNode.createAliasAccount(5, relay.provider, requestId);
});

beforeEach(async () => {
wsProvider = new ethers.WebSocketProvider(WS_RELAY_URL);
});

afterEach(async () => {
if (wsProvider) {
await wsProvider.destroy();
}
});

for (const params of INVALID_PARAMS) {
it(`Should throw predefined.INVALID_PARAMETERS if the request's params variable is invalid (params.length !== 1). params=[${params}]`, async () => {
try {
await wsProvider.send(METHOD_NAME, params);
expect(true).to.eq(false);
} catch (error) {
expect(error.error).to.exist;
expect(error.error.code).to.eq(-32602);
expect(error.error.name).to.eq('Invalid parameters');
expect(error.error.message).to.eq('Invalid params');
}
});
}

for (const txHash of INVALID_TX_HASH) {
it(`Should handle invalid data correctly. txHash = ${txHash}`, async () => {
try {
const res = await wsProvider.send(METHOD_NAME, [txHash]);
if (txHash === '') {
expect(res).to.be.null;
} else {
expect(true).to.eq(false);
}
} catch (error) {
expect(error.error.code).to.eq(-32603);
expect(error.error.name).to.eq(`Internal error`);
expect(error.error.message).to.eq(
'Error invoking RPC: "Invalid Transaction id. Please use \\"shard.realm.num-sss-nnn\\" format where sss are seconds and nnn are nanoseconds"',
);
}
});
}

it('Should handle valid data correctly', async () => {
const tx = {
value: ONE_TINYBAR_IN_WEI_HEX,
gasLimit: numberTo0x(30000),
chainId: Number(CHAIN_ID),
to: accounts[1].address,
nonce: await relayClient.getAccountNonce(accounts[0].address, requestId),
maxFeePerGas: await relayClient.gasPrice(requestId),
};

const signedTx = await accounts[0].wallet.signTransaction(tx);
const txHash = await relayClient.sendRawTransaction(signedTx, requestId);
const expectedTxReceipt = await mirrorNodeServer.get(`/contracts/results/${txHash}`);

const txReceipt = await wsProvider.send(METHOD_NAME, [txHash]);

expect(txReceipt.from).to.be.eq(accounts[0].address);
expect(txReceipt.to).to.be.eq(accounts[1].address);
expect(txReceipt.blockHash).to.be.eq(expectedTxReceipt.block_hash.slice(0, 66));
expect(txReceipt.contractAddress).to.be.eq(expectedTxReceipt.address);
expect(txReceipt.transactionHash).to.be.eq(expectedTxReceipt.hash);
expect(Number(txReceipt.transactionIndex)).to.be.eq(expectedTxReceipt.transaction_index);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,11 @@ describe('@release @web-socket eth_sendRawTransaction', async function () {

beforeEach(async () => {
wsProvider = new ethers.WebSocketProvider(WS_RELAY_URL);
await new Promise((resolve) => setTimeout(resolve, 1000));
});

afterEach(async () => {
if (wsProvider) {
await wsProvider.destroy();
await new Promise((resolve) => setTimeout(resolve, 1000));
}
});

Expand Down
43 changes: 13 additions & 30 deletions packages/ws-server/src/controllers/eth_estimateGas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,21 @@
*
*/

import { sendToClient } from '../utils/utils';
import { Relay } from '@hashgraph/json-rpc-relay';
import { validateParamsLength } from '../utils/validators';
import { handleSendingTransactionRequests } from './helpers';
import { handleSendingRequestsToRelay } from './helpers';
import { predefined, Relay } from '@hashgraph/json-rpc-relay';

/**
* Handles the "eth_estimateGas" method request by retrieving transaction details from the Hedera network.
* Validates the parameters, retrieves the transaction details, and sends the response back to the client.
* Handles the "eth_estimateGas" method request by estimating the gas needed to execute a transaction.
* Validates the parameters, retrieves the gas estimation using the relay object, and sends the response back to the client.
* @param {any} ctx - The context object containing information about the WebSocket connection.
* @param {any[]} params - The parameters of the method request, expecting a single parameter: the transaction hash.
* @param {any[]} params - The parameters of the method request.
* @param {any} logger - The logger object for logging messages and events.
* @param {Relay} relay - The relay object for interacting with the Hedera network.
* @param {any} request - The request object received from the client.
* @param {string} method - The JSON-RPC method associated with the request.
* @param {string} socketIdPrefix - The prefix for the socket ID.
* @param {string} requestIdPrefix - The prefix for the request ID.
* @param {string} connectionIdPrefix - The prefix for the connection ID.
* @returns {Promise<any>} Returns a promise that resolves with the JSON-RPC response to the client.
* @throws {JsonRpcError} Throws a JsonRpcError if there is an issue with the parameters or an internal error occurs.
* @throws {JsonRpcError} Throws a JsonRpcError if the method parameters are invalid or an internal error occurs.
*/
export const handleEthEstimateGas = async (
ctx: any,
Expand All @@ -45,40 +41,27 @@ export const handleEthEstimateGas = async (
relay: Relay,
request: any,
method: string,
socketIdPrefix: string,
requestIdPrefix: string,
connectionIdPrefix: string,
) => {
if (params.length !== 1) {
throw predefined.INVALID_PARAMETERS;
}

const TXN = params[0];
const TAG = JSON.stringify({ method, txn: TXN });

validateParamsLength(
ctx,
params,
method,
TAG,
logger,
sendToClient,
1,
socketIdPrefix,
requestIdPrefix,
connectionIdPrefix,
);

logger.info(
`${connectionIdPrefix} ${requestIdPrefix} ${socketIdPrefix}: Retrieving estimated gas with txn=${TXN} for tag=${TAG}`,
);
logger.info(`${connectionIdPrefix} ${requestIdPrefix}: Retrieving gas estimation for tag=${TAG}`);

return handleSendingTransactionRequests(
await handleSendingRequestsToRelay(
ctx,
TAG,
params,
[TXN, requestIdPrefix],
relay,
logger,
request,
method,
'estimateGas',
socketIdPrefix,
requestIdPrefix,
connectionIdPrefix,
);
Expand Down
46 changes: 15 additions & 31 deletions packages/ws-server/src/controllers/eth_getCode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,21 @@
*
*/

import { sendToClient } from '../utils/utils';
import { Relay } from '@hashgraph/json-rpc-relay';
import { validateParamsLength } from '../utils/validators';
import { handleSendingTransactionRequests } from './helpers';
import { handleSendingRequestsToRelay } from './helpers';
import { predefined, Relay } from '@hashgraph/json-rpc-relay';

/**
* Handles the "eth_getCode" method request by retrieving transaction details from the Hedera network.
* Validates the parameters, retrieves the transaction details, and sends the response back to the client.
* Handles the "eth_getCode" method request by retrieving the code at a specific address on the Hedera network.
* Validates the parameters, retrieves the contract code using the relay object, and sends the response back to the client.
* @param {any} ctx - The context object containing information about the WebSocket connection.
* @param {any[]} params - The parameters of the method request, expecting a single parameter: the transaction hash.
* @param {any[]} params - The parameters of the method request, expecting an address and a block parameter.
* @param {any} logger - The logger object for logging messages and events.
* @param {Relay} relay - The relay object for interacting with the Hedera network.
* @param {any} request - The request object received from the client.
* @param {string} method - The JSON-RPC method associated with the request.
* @param {string} socketIdPrefix - The prefix for the socket ID.
* @param {string} requestIdPrefix - The prefix for the request ID.
* @param {string} connectionIdPrefix - The prefix for the connection ID.
* @returns {Promise<any>} Returns a promise that resolves with the JSON-RPC response to the client.
* @throws {JsonRpcError} Throws a JsonRpcError if there is an issue with the parameters or an internal error occurs.
* @throws {JsonRpcError} Throws a JsonRpcError if the method parameters are invalid or an internal error occurs.
*/
export const handleEthGetCode = async (
ctx: any,
Expand All @@ -45,40 +41,28 @@ export const handleEthGetCode = async (
relay: Relay,
request: any,
method: string,
socketIdPrefix: string,
requestIdPrefix: string,
connectionIdPrefix: string,
) => {
const ADDRESS = params[0];
const TAG = JSON.stringify({ method, address: ADDRESS });
if (params.length !== 2) {
throw predefined.INVALID_PARAMETERS;
}

validateParamsLength(
ctx,
params,
method,
TAG,
logger,
sendToClient,
2,
socketIdPrefix,
requestIdPrefix,
connectionIdPrefix,
);
const ADDRESS = params[0];
const BLOCK_TAG = params[1];
const TAG = JSON.stringify({ method, address: ADDRESS, block: BLOCK_TAG });

logger.info(
`${connectionIdPrefix} ${requestIdPrefix} ${socketIdPrefix}: Retrieving code with address=${ADDRESS} for tag=${TAG}`,
);
logger.info(`${connectionIdPrefix} ${requestIdPrefix}: Retrieving contract code for tag=${TAG}`);

return handleSendingTransactionRequests(
await handleSendingRequestsToRelay(
ctx,
TAG,
params,
[ADDRESS, BLOCK_TAG, requestIdPrefix],
relay,
logger,
request,
method,
'getCode',
socketIdPrefix,
requestIdPrefix,
connectionIdPrefix,
);
Expand Down
Loading
Loading