From 716bb750784848235333e93bcb254e39312165ff Mon Sep 17 00:00:00 2001 From: Fabio Rigamonti <73019897+fabiorigam@users.noreply.github.com> Date: Thu, 1 Feb 2024 17:32:29 +0100 Subject: [PATCH] Implement rpc method eth_syncing (#531) * feat: add eth_syncing RPC method * fix: add mock tests for eth_syncing * fix: update comment for RPC methods to implement * fix: add test for eth_syncing * fix: add test for eth_syncing * fix: proper type for eth_syncing.ts --------- Co-authored-by: rodolfopietro97 --- .../src/utils/const/rpc-mapper/rpc-methods.ts | 4 +- .../src/utils/formatter/blocks/types.d.ts | 11 ++- .../methods-map/methods/eth_syncing.ts | 71 ++++++++++---- .../src/utils/rpc-mapper/rpc-mapper.ts | 7 +- .../eth_syncing/eth_syncing.mock.test.ts | 95 +++++++++++++++++++ .../methods/eth_syncing/eth_syncing.test.ts | 41 ++------ 6 files changed, 170 insertions(+), 59 deletions(-) create mode 100644 packages/provider/tests/rpc-mapper/methods/eth_syncing/eth_syncing.mock.test.ts diff --git a/packages/provider/src/utils/const/rpc-mapper/rpc-methods.ts b/packages/provider/src/utils/const/rpc-mapper/rpc-methods.ts index 15e9233e7..db31955d3 100644 --- a/packages/provider/src/utils/const/rpc-mapper/rpc-methods.ts +++ b/packages/provider/src/utils/const/rpc-mapper/rpc-methods.ts @@ -44,8 +44,8 @@ enum RPC_METHODS { eth_getTransactionByHash = 'eth_getTransactionByHash', eth_getTransactionCount = 'eth_getTransactionCount', // TEMPORARY COMMENT - IMPLEMENTED (Understand only if count is better, instead of nonce) eth_getTransactionReceipt = 'eth_getTransactionReceipt', - eth_sendTransaction = 'eth_sendTransaction', // TEMPORARY COMMENT - TO IMPLEMENT - eth_syncing = 'eth_syncing', // TEMPORARY COMMENT - TO IMPLEMENT + eth_sendTransaction = 'eth_sendTransaction', // TEMPORARY COMMENT - TO IMPLEMENT (WALLET NEEDED) + eth_syncing = 'eth_syncing', net_version = 'net_version', web3_clientVersion = 'web3_clientVersion', // TEMPORARY COMMENT -IMPLEMENTED (Better understand if 'thor' is ok) eth_subscribe = 'eth_subscribe', // TEMPORARY COMMENT - TO IMPLEMENT diff --git a/packages/provider/src/utils/formatter/blocks/types.d.ts b/packages/provider/src/utils/formatter/blocks/types.d.ts index 41c533ccd..9eaa33910 100644 --- a/packages/provider/src/utils/formatter/blocks/types.d.ts +++ b/packages/provider/src/utils/formatter/blocks/types.d.ts @@ -90,4 +90,13 @@ interface BlocksRPC extends BlockHeaderRPC { mixHash: string; } -export { type BlockHeaderRPC, type BlocksRPC }; +/** + * Return type of eth_syncing for RPC method. + */ +interface SyncBlockRPC { + startingBlock: null; + currentBlock: BlocksRPC | null; + highestBlock: string | null; +} + +export { type BlockHeaderRPC, type BlocksRPC, type SyncBlockRPC }; diff --git a/packages/provider/src/utils/rpc-mapper/methods-map/methods/eth_syncing.ts b/packages/provider/src/utils/rpc-mapper/methods-map/methods/eth_syncing.ts index a28ed9de0..1b4c07eec 100644 --- a/packages/provider/src/utils/rpc-mapper/methods-map/methods/eth_syncing.ts +++ b/packages/provider/src/utils/rpc-mapper/methods-map/methods/eth_syncing.ts @@ -1,32 +1,65 @@ import { type ThorClient } from '@vechain/vechain-sdk-network'; -import { buildError, FUNCTION } from '@vechain/vechain-sdk-errors'; +import { + blocksFormatter, + RPC_METHODS, + RPCMethodsMap, + type SyncBlockRPC +} from '../../../../provider'; +import { JSONRPC, buildProviderError } from '@vechain/vechain-sdk-errors'; /** * RPC Method eth_syncing implementation * + * @link [eth_syncing](https://docs.infura.io/networks/ethereum/json-rpc-methods/eth_syncing) + * * @param thorClient - The thor client instance to use. - * @param params - The standard array of rpc call parameters. - * @note: - * * params[0]: ... - * * params[1]: ... - * * params[n]: ... + * + * @returns Returns an object with the sync status of the node if the node is out-of-sync and is syncing. Returns false when the node is already in sync. */ const ethSyncing = async ( - thorClient: ThorClient, - params: unknown[] -): Promise => { - // To avoid eslint error - await Promise.resolve(0); + thorClient: ThorClient +): Promise => { + try { + const bestBlock = await thorClient.blocks.getBestBlock(); + const genesisBlock = await thorClient.blocks.getGenesisBlock(); - // Not implemented yet - throw buildError( - FUNCTION.NOT_IMPLEMENTED, - 'Method "eth_syncing" not not implemented yet', - { - params, - thorClient + // Check if the node is already in sync + if ( + bestBlock != null && + Math.floor(Date.now() / 1000) - bestBlock.timestamp < 11000 + ) { + return false; } - ); + + // Calculate the chainId + const chainId = (await RPCMethodsMap(thorClient)[ + RPC_METHODS.eth_chainId + ]([])) as string; + + const highestBlock = + genesisBlock != null + ? Math.floor((Date.now() - genesisBlock.timestamp) / 10000) + : null; + + return { + startingBlock: null, + currentBlock: + bestBlock != null + ? blocksFormatter.formatToRPCStandard(bestBlock, chainId) + : null, + highestBlock: + highestBlock != null ? highestBlock.toString(16) : null + }; + } catch (e) { + throw buildProviderError( + JSONRPC.INTERNAL_ERROR, + `Method 'eth_syncing' failed: Error while getting last block\n + URL: ${thorClient.httpClient.baseURL}`, + { + innerError: JSON.stringify(e) + } + ); + } }; export { ethSyncing }; diff --git a/packages/provider/src/utils/rpc-mapper/rpc-mapper.ts b/packages/provider/src/utils/rpc-mapper/rpc-mapper.ts index 920e5de55..ef0773442 100644 --- a/packages/provider/src/utils/rpc-mapper/rpc-mapper.ts +++ b/packages/provider/src/utils/rpc-mapper/rpc-mapper.ts @@ -79,6 +79,7 @@ import { RPC_METHODS } from '../const'; import { type BlocksRPC, type SendRawTransactionResultRPC, + type SyncBlockRPC, type TransactionReceiptRPC, type TransactionRPC } from '../formatter'; @@ -184,8 +185,10 @@ const RPCMethodsMap = ( await ethSendTransaction(thorClient, params); }, - [RPC_METHODS.eth_syncing]: async (params) => { - await ethSyncing(thorClient, params); + [RPC_METHODS.eth_syncing]: async (): Promise< + boolean | SyncBlockRPC + > => { + return await ethSyncing(thorClient); }, [RPC_METHODS.net_version]: async (): Promise => { diff --git a/packages/provider/tests/rpc-mapper/methods/eth_syncing/eth_syncing.mock.test.ts b/packages/provider/tests/rpc-mapper/methods/eth_syncing/eth_syncing.mock.test.ts new file mode 100644 index 000000000..5efee5360 --- /dev/null +++ b/packages/provider/tests/rpc-mapper/methods/eth_syncing/eth_syncing.mock.test.ts @@ -0,0 +1,95 @@ +import { beforeEach, describe, expect, jest, test } from '@jest/globals'; +import { ProviderRpcError } from '@vechain/vechain-sdk-errors'; +import { RPC_METHODS, RPCMethodsMap } from '../../../../src'; +import { ThorClient } from '@vechain/vechain-sdk-network'; +import { soloNetwork } from '../../../fixture'; + +/** + * RPC Mapper integration tests for 'eth_syncing' method with Solo Network and mocked functionality + * + * @group integration/rpc-mapper/methods/eth_syncing + */ +describe('RPC Mapper - eth_syncing method tests', () => { + /** + * Thor client instance + */ + let thorClient: ThorClient; + + /** + * Init thor client before each test + */ + beforeEach(() => { + // Init thor client + thorClient = new ThorClient(soloNetwork); + }); + + /** + * eth_syncing RPC call tests - Negative cases + */ + describe('eth_syncing - Negative cases', () => { + /** + * Test case that mocks an error thrown by the getBestBlock method + */ + test('Should throw `ProviderRpcError` if an error occurs while retrieving the best block', async () => { + // Mock the getGenesisBlock method to return null + jest.spyOn(thorClient.blocks, 'getBestBlock').mockRejectedValue( + new Error() + ); + + await expect( + RPCMethodsMap(thorClient)[RPC_METHODS.eth_syncing]([]) + ).rejects.toThrowError(ProviderRpcError); + }); + + /** + * Test case that mocks an error thrown by the getGenesisBlock method + */ + test('Should throw `ProviderRpcError` if an error occurs while retrieving the genesis block', async () => { + // Mock the getGenesisBlock method to return null + jest.spyOn(thorClient.blocks, 'getGenesisBlock').mockRejectedValue( + new Error() + ); + + await expect( + RPCMethodsMap(thorClient)[RPC_METHODS.eth_syncing]([]) + ).rejects.toThrowError(ProviderRpcError); + }); + + /** + * Test case where the best block is not defined + */ + test('Should return an object with the sync status of the node if the node is out-of-sync', async () => { + // Mock the getBestBlock method to return null + jest.spyOn(thorClient.blocks, 'getBestBlock').mockResolvedValue( + null + ); + + const status = (await RPCMethodsMap(thorClient)[ + RPC_METHODS.eth_syncing + ]([])) as string; + + expect(status).not.toBe(false); + }); + + /** + * Test case where the best block and genesis block are not defined + */ + test('Should return an object with the sync status of the node if the node is out-of-sync', async () => { + // Mock the getBestBlock method to return null + jest.spyOn(thorClient.blocks, 'getBestBlock').mockResolvedValue( + null + ); + + // Mock the getGenesisBlock method to return null + jest.spyOn(thorClient.blocks, 'getGenesisBlock').mockResolvedValue( + null + ); + + const status = (await RPCMethodsMap(thorClient)[ + RPC_METHODS.eth_syncing + ]([])) as string; + + expect(status).not.toBe(false); + }); + }); +}); diff --git a/packages/provider/tests/rpc-mapper/methods/eth_syncing/eth_syncing.test.ts b/packages/provider/tests/rpc-mapper/methods/eth_syncing/eth_syncing.test.ts index 665f74889..31ddad5b6 100644 --- a/packages/provider/tests/rpc-mapper/methods/eth_syncing/eth_syncing.test.ts +++ b/packages/provider/tests/rpc-mapper/methods/eth_syncing/eth_syncing.test.ts @@ -1,5 +1,4 @@ -import { afterEach, beforeEach, describe, expect, test } from '@jest/globals'; -import { NotImplementedError } from '@vechain/vechain-sdk-errors'; +import { beforeEach, describe, expect, test } from '@jest/globals'; import { RPC_METHODS, RPCMethodsMap } from '../../../../src'; import { ThorClient } from '@vechain/vechain-sdk-network'; import { testNetwork } from '../../../fixture'; @@ -23,46 +22,18 @@ describe('RPC Mapper - eth_syncing method tests', () => { thorClient = new ThorClient(testNetwork); }); - /** - * Destroy thor client after each test - */ - afterEach(() => { - thorClient.destroy(); - }); - /** * eth_syncing RPC call tests - Positive cases */ describe('eth_syncing - Positive cases', () => { /** - * Positive case 1 - ... Description ... + * Positive case 1 - Check status */ test('eth_syncing - positive case 1', async () => { - // NOT IMPLEMENTED YET! - await expect( - async () => - await RPCMethodsMap(thorClient)[RPC_METHODS.eth_syncing]([ - -1 - ]) - ).rejects.toThrowError(NotImplementedError); - }); - }); - - /** - * eth_syncing RPC call tests - Negative cases - */ - describe('eth_syncing - Negative cases', () => { - /** - * Negative case 1 - ... Description ... - */ - test('eth_syncing - negative case 1', async () => { - // NOT IMPLEMENTED YET! - await expect( - async () => - await RPCMethodsMap(thorClient)[RPC_METHODS.eth_syncing]([ - 'SOME_RANDOM_PARAM' - ]) - ).rejects.toThrowError(NotImplementedError); + const status = await RPCMethodsMap(thorClient)[ + RPC_METHODS.eth_syncing + ]([]); + expect(status).toBe(false); }); }); });