Skip to content

Commit

Permalink
feat: added support for getTransactionByHash endpoint in the WS server (
Browse files Browse the repository at this point in the history
#2273) (#2295)

* refactor: refactored validateParamsLength

Signed-off-by: Logan Nguyen <[email protected]>

* feat: added support for getTransactionByHas endpoint in the WS server

Signed-off-by: Logan Nguyen <[email protected]>

* refactor: refactored sending requests method

Signed-off-by: Logan Nguyen <[email protected]>

---------

Signed-off-by: Logan Nguyen <[email protected]>
  • Loading branch information
quiet-node authored Apr 5, 2024
1 parent 624072c commit 967056c
Show file tree
Hide file tree
Showing 8 changed files with 372 additions and 36 deletions.
120 changes: 120 additions & 0 deletions packages/server/tests/acceptance/ws/getTransactionByHash.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/*-
*
* 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_getTransactionByHash', async function () {
const WS_RELAY_URL = `${process.env.WS_RELAY_URL}`;
const METHOD_NAME = 'eth_getTransactionByHash';
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);
await new Promise((resolve) => setTimeout(resolve, 1000));
});

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

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.hash).to.be.eq(expectedTxReceipt.hash);
expect(txReceipt.r).to.be.eq(expectedTxReceipt.r);
expect(txReceipt.s).to.be.eq(expectedTxReceipt.s);
expect(Number(txReceipt.v)).to.be.eq(expectedTxReceipt.v);
});
});
85 changes: 85 additions & 0 deletions packages/ws-server/src/controllers/eth_getTransaction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/* -
*
* 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.
*
*/

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

/**
* Handles the "eth_getTransactionByHash" 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.
* @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} 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.
*/
export const handleEthGetTransactionByHash = async (
ctx: any,
params: any,
logger: any,
relay: Relay,
request: any,
method: string,
socketIdPrefix: string,
requestIdPrefix: string,
connectionIdPrefix: string,
) => {
const TX_HASH = params[0];
const TAG = JSON.stringify({ method, signedTx: TX_HASH });

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

logger.info(
`${connectionIdPrefix} ${requestIdPrefix} ${socketIdPrefix}: Retrieving transaction info with txHash=${TX_HASH} for tag=${TAG}`,
);

return handleSendingTransactionRequests(
ctx,
TAG,
TX_HASH,
relay,
logger,
request,
method,
'getTransactionByHash',
socketIdPrefix,
requestIdPrefix,
connectionIdPrefix,
);
};
61 changes: 27 additions & 34 deletions packages/ws-server/src/controllers/eth_sendRawTransaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@

import { sendToClient } from '../utils/utils';
import { Relay } from '@hashgraph/json-rpc-relay';
import { predefined } from '@hashgraph/json-rpc-relay';
import jsonResp from '@hashgraph/json-rpc-server/dist/koaJsonRpc/lib/RpcResponse';
import { validateParamsLength } from '../utils/validators';
import { handleSendingTransactionRequests } from './helpers';

/**
* Handles the "eth_sendRawTransaction" method request by submitting a raw transaction to the Websocket server.
Expand Down Expand Up @@ -52,40 +52,33 @@ export const handleEthSendRawTransaction = async (
const SIGNED_TX = params[0];
const TAG = JSON.stringify({ method, signedTx: SIGNED_TX });

if (params.length !== 1) {
const ERR_MSG = 'INVALID PARAMETERS';
logger.error(`${connectionIdPrefix} ${requestIdPrefix} ${socketIdPrefix}: Invalid parameters ${params}`);
sendToClient(ctx.websocket, method, ERR_MSG, TAG, logger, socketIdPrefix, requestIdPrefix, connectionIdPrefix);
throw predefined.INVALID_PARAMETERS;
}

validateParamsLength(
ctx,
params,
method,
TAG,
logger,
sendToClient,
1,
socketIdPrefix,
requestIdPrefix,
connectionIdPrefix,
);
logger.info(
`${connectionIdPrefix} ${requestIdPrefix} ${socketIdPrefix}: Submitting raw transaction with signedTx=${SIGNED_TX} for tag=${TAG}`,
);

try {
const txRes = await relay.eth().sendRawTransaction(SIGNED_TX, requestIdPrefix);
if (txRes) {
sendToClient(ctx.websocket, method, txRes, TAG, logger, socketIdPrefix, requestIdPrefix, connectionIdPrefix);
} else {
logger.error(
`${connectionIdPrefix} ${requestIdPrefix} ${socketIdPrefix}: Fail to retrieve result for tag=${TAG}`,
);
}

return jsonResp(request.id, null, txRes);
} catch (error: any) {
sendToClient(
ctx.websocket,
method,
JSON.stringify(error.message || error),
TAG,
logger,
socketIdPrefix,
requestIdPrefix,
connectionIdPrefix,
);

throw predefined.INTERNAL_ERROR(JSON.stringify(error.message || error));
}
return handleSendingTransactionRequests(
ctx,
TAG,
SIGNED_TX,
relay,
logger,
request,
method,
'sendRawTransaction',
socketIdPrefix,
requestIdPrefix,
connectionIdPrefix,
);
};
81 changes: 81 additions & 0 deletions packages/ws-server/src/controllers/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/* -
*
* 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.
*
*/
import { sendToClient } from '../utils/utils';
import { Relay } from '@hashgraph/json-rpc-relay';
import { predefined } from '@hashgraph/json-rpc-relay';
import jsonResp from '@hashgraph/json-rpc-server/dist/koaJsonRpc/lib/RpcResponse';

/**
* Handles sending transaction-related requests to the Hedera network, such as sending raw transactions or getting transaction information.
* Executes the specified Hedera RPC call endpoint with the provided argument, retrieves the response, and sends it back to the client.
* @param {any} ctx - The context object containing information about the WebSocket connection.
* @param {string} tag - A tag used for logging and identifying the message.
* @param {string} arg - The argument required for the Hedera RPC call.
* @param {Relay} relay - The relay object for interacting with the Hedera network.
* @param {any} logger - The logger object for logging messages and events.
* @param {any} request - The request object received from the client.
* @param {string} method - The JSON-RPC method associated with the request.
* @param {string} rpcCallEndpoint - The Hedera RPC call endpoint to execute.
* @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 Hedera RPC call or an internal error occurs.
*/
export const handleSendingTransactionRequests = async (
ctx: any,
tag: string,
arg: string,
relay: Relay,
logger: any,
request: any,
method: string,
rpcCallEndpoint: string,
socketIdPrefix: string,
requestIdPrefix: string,
connectionIdPrefix: string,
): Promise<any> => {
try {
const txRes = await relay.eth()[rpcCallEndpoint](arg, requestIdPrefix);

if (txRes) {
sendToClient(ctx.websocket, method, txRes, tag, logger, socketIdPrefix, requestIdPrefix, connectionIdPrefix);
} else {
logger.error(
`${connectionIdPrefix} ${requestIdPrefix} ${socketIdPrefix}: Fail to retrieve result for tag=${tag}`,
);
}

return jsonResp(request.id, null, txRes);
} catch (error: any) {
sendToClient(
ctx.websocket,
method,
JSON.stringify(error.message || error),
tag,
logger,
socketIdPrefix,
requestIdPrefix,
connectionIdPrefix,
);

throw predefined.INTERNAL_ERROR(JSON.stringify(error.message || error));
}
};
3 changes: 2 additions & 1 deletion packages/ws-server/src/controllers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import { handleEthSubsribe } from './eth_subscribe';
import { handleEthUnsubscribe } from './eth_unscribe';
import { handleEthGetTransactionByHash } from './eth_getTransaction';
import { handleEthSendRawTransaction } from './eth_sendRawTransaction';

export { handleEthUnsubscribe, handleEthSubsribe, handleEthSendRawTransaction };
export { handleEthUnsubscribe, handleEthSubsribe, handleEthSendRawTransaction, handleEthGetTransactionByHash };
1 change: 1 addition & 0 deletions packages/ws-server/src/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,5 +58,6 @@ export const WS_CONSTANTS = {
ETH_UNSUBSCRIBE: 'eth_unsubscribe',
ETH_CHAIN_ID: 'eth_chainId',
ETH_SEND_RAW_TRANSACTION: 'eth_sendRawTransaction',
ETH_GET_TRANSACTION_BY_HASH: 'eth_getTransactionByHash',
},
};
Loading

0 comments on commit 967056c

Please sign in to comment.