Skip to content

Commit c5589c5

Browse files
authored
RPC ChainId to equal genesis block Id (#1846)
* fix: 1840 * fix: mainnet id * chore: update test data * chore: fix e2e tests * chore: fix e2e tests * chore: fix e2e * fix: tests * fix: lint * fix: rpc test
1 parent cc66c64 commit c5589c5

File tree

16 files changed

+127
-38
lines changed

16 files changed

+127
-38
lines changed

docs/examples/provider/vechain-hardhat-provider.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
CHAIN_ID,
23
HardhatVeChainProvider,
34
ProviderInternalBaseWallet,
45
TESTNET_URL
@@ -21,4 +22,4 @@ const rpcCallChainId = await provider.request({
2122

2223
// END_SNIPPET: VechainHardhatProviderSnippet
2324

24-
expect(rpcCallChainId).toBe('0x186aa');
25+
expect(rpcCallChainId).toBe(CHAIN_ID.TESTNET);

docs/examples/provider/vechain-provider.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import { TESTNET_URL, ThorClient, VeChainProvider } from '@vechain/sdk-network';
1+
import {
2+
CHAIN_ID,
3+
TESTNET_URL,
4+
ThorClient,
5+
VeChainProvider
6+
} from '@vechain/sdk-network';
27
import { expect } from 'expect';
38

49
// START_SNIPPET: VeChainProviderSnippet
@@ -16,4 +21,4 @@ const rpcCallChainId = await provider.request({
1621

1722
// END_SNIPPET: VeChainProviderSnippet
1823

19-
expect(rpcCallChainId).toBe('0x186aa');
24+
expect(rpcCallChainId).toBe(CHAIN_ID.TESTNET);
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
/**
2-
* Chain ID's
3-
*
4-
* @link [Chain IDs](https://chainlist.org/?search=vechain&testnets=true)
2+
* Chain ID's this is the blockId of the genesis block
53
*/
64
const CHAIN_ID = {
7-
MAINNET: '0x186a9',
8-
TESTNET: '0x186aa'
5+
MAINNET:
6+
'0x00000000851caf3cfdb6e899cf5958bfb1ac3413d346d43539627e6be7ec1b4a',
7+
TESTNET:
8+
'0x000000000b2bce3c70bc649a02749e8687721b09ed2e15997f466536b20bb127'
99
};
1010

1111
export { CHAIN_ID };
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,28 @@
1+
import { Hex } from '@vechain/sdk-core';
12
import { type ThorClient } from '../../../../../thor-client';
23
import {
34
JSONRPCInternalError,
45
JSONRPCInvalidParams,
56
stringifyData
67
} from '@vechain/sdk-errors';
7-
import { CHAIN_ID } from '../../../const';
8-
import { networkInfo } from '@vechain/sdk-core';
8+
9+
// In-memory cache
10+
let cachedChainId: Hex | null = null;
11+
let cachedChainTag: Hex | null = null;
912

1013
/**
1114
* RPC Method eth_chainId implementation
1215
*
1316
* @link [eth_chainId](https://ethereum.github.io/execution-apis/api-documentation/)
14-
* @link [Chain IDs](https://chainlist.org/?search=vechain&testnets=true)
1517
*
1618
* @param thorClient - ThorClient instance.
17-
* @returns The chain id
19+
* @returns Returns the block id of the genesis block.
1820
* @throws {JSONRPCInvalidParams, JSONRPCInternalError}
1921
*/
2022
const ethChainId = async (thorClient: ThorClient): Promise<string> => {
2123
try {
24+
if (cachedChainId !== null) return cachedChainId.toString();
2225
const genesisBlock = await thorClient.blocks.getGenesisBlock();
23-
2426
if (genesisBlock?.id === null || genesisBlock?.id === undefined) {
2527
throw new JSONRPCInvalidParams(
2628
'eth_chainId()',
@@ -30,13 +32,19 @@ const ethChainId = async (thorClient: ThorClient): Promise<string> => {
3032
}
3133
);
3234
}
33-
34-
// We are on Mainnet
35-
if (genesisBlock.id === networkInfo.mainnet.genesisBlock.id)
36-
return CHAIN_ID.MAINNET;
37-
38-
// Testnet OR Solo OR some other network
39-
return CHAIN_ID.TESTNET;
35+
if (!Hex.isValid(genesisBlock.id)) {
36+
throw new JSONRPCInvalidParams(
37+
'eth_chainId()',
38+
'The genesis block id is invalid. Unable to get the chain id.',
39+
{
40+
url: thorClient.httpClient.baseURL
41+
}
42+
);
43+
}
44+
const genesisBlockId = Hex.of(genesisBlock.id);
45+
cachedChainId = genesisBlockId;
46+
cachedChainTag = Hex.of(genesisBlockId.bytes.slice(-2));
47+
return cachedChainId.toString();
4048
} catch (e) {
4149
throw new JSONRPCInternalError(
4250
'eth_chainId()',
@@ -49,4 +57,28 @@ const ethChainId = async (thorClient: ThorClient): Promise<string> => {
4957
}
5058
};
5159

52-
export { ethChainId };
60+
/*
61+
* Get the chain id from the cached value or fetch it from the network.
62+
*
63+
* @param thorClient - ThorClient instance.
64+
* @returns The chain id.
65+
*/
66+
const getCachedChainId = async (thorClient: ThorClient): Promise<string> => {
67+
return cachedChainId !== null
68+
? cachedChainId.toString()
69+
: await ethChainId(thorClient);
70+
};
71+
72+
/*
73+
* Get the chain tag from the cached value or fetch it from the network.
74+
*
75+
* @param thorClient - ThorClient instance.
76+
* @returns The chain tag.
77+
*/
78+
const getCachedChainTag = async (thorClient: ThorClient): Promise<string> => {
79+
return cachedChainTag !== null
80+
? cachedChainTag.toString()
81+
: await ethChainId(thorClient);
82+
};
83+
84+
export { ethChainId, getCachedChainId, getCachedChainTag };

packages/network/src/provider/utils/rpc-mapper/methods/eth_sendTransaction/eth_sendTransaction.ts

+11
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { type VeChainProvider } from '../../../../providers/vechain-provider';
88
import { type TransactionObjectInput } from './types';
99
import { type VeChainSigner } from '../../../../../signer';
1010
import { RPC_DOCUMENTATION_URL } from '../../../../../utils';
11+
import { getCachedChainId } from '../eth_chainId';
1112

1213
/**
1314
* RPC Method eth_sendTransaction implementation
@@ -69,6 +70,16 @@ const ethSendTransaction = async (
6970
// Input params
7071
const [transaction] = params as [TransactionObjectInput];
7172

73+
// Check if the chainId in the transaction object if specified matches the chainId of the network
74+
const chainId = await getCachedChainId(thorClient);
75+
if (transaction.chainId != null && transaction.chainId !== chainId) {
76+
throw new JSONRPCInvalidParams(
77+
'eth_sendTransaction',
78+
`ChainId in the transaction object does not match the chainId of the network. Expected: ${chainId}, Received: ${transaction.chainId}`,
79+
{ chainId: transaction.chainId }
80+
);
81+
}
82+
7283
try {
7384
// Get the signer of the provider
7485
const signer = (await provider.getSigner(

packages/network/src/provider/utils/rpc-mapper/methods/eth_sendTransaction/types.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { type BaseTransactionObjectInput } from '../types';
66
interface TransactionObjectInput extends BaseTransactionObjectInput {
77
from: string;
88
to?: string;
9+
chainId?: string;
910
}
1011

1112
export type { TransactionObjectInput };

packages/network/tests/provider/fixture.ts

+8-4
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ const blockWithTransactionsExpanded = {
2323
blockNumber: '0x10b7a6d',
2424
from: '0x7487d912d03ab9de786278f679592b3730bdd540',
2525
gas: '0x7436',
26-
chainId: '0x186aa',
26+
chainId:
27+
'0x000000000b2bce3c70bc649a02749e8687721b09ed2e15997f466536b20bb127',
2728
hash: '0xd331443a31ef1f32e2c4510710e62561012de11ef404c35086629436e4d5dded',
2829
nonce: '0xb8314776ce0bf5df',
2930
transactionIndex: '0x0',
@@ -46,7 +47,8 @@ const blockWithTransactionsExpanded = {
4647
blockNumber: '0x10b7a6d',
4748
from: '0x7487d912d03ab9de786278f679592b3730bdd540',
4849
gas: '0xbd30',
49-
chainId: '0x186aa',
50+
chainId:
51+
'0x000000000b2bce3c70bc649a02749e8687721b09ed2e15997f466536b20bb127',
5052
hash: '0x6994801b6f92f9a0a151ab4ac1c27d2dcf2ab61245b10ddf05504ae5384e759d',
5153
nonce: '0x176bbcbf79a3a672',
5254
transactionIndex: '0x1',
@@ -69,7 +71,8 @@ const blockWithTransactionsExpanded = {
6971
blockNumber: '0x10b7a6d',
7072
from: '0x7487d912d03ab9de786278f679592b3730bdd540',
7173
gas: '0xd14a',
72-
chainId: '0x186aa',
74+
chainId:
75+
'0x000000000b2bce3c70bc649a02749e8687721b09ed2e15997f466536b20bb127',
7376
hash: '0xb476d1a43b8632c25a581465c944a1cb5dd99e48d41d326a250847a0a279afa5',
7477
nonce: '0x7022eb9454a648b9',
7578
transactionIndex: '0x2',
@@ -155,7 +158,8 @@ const validTransactionDetailTestnet = {
155158
blockNumber: '0x10b7b5f',
156159
from: '0x8c59c63d6458c71b6ff88d57698437524a703084',
157160
gas: '0x618af',
158-
chainId: '0x186aa',
161+
chainId:
162+
'0x000000000b2bce3c70bc649a02749e8687721b09ed2e15997f466536b20bb127',
159163
hash: '0xb2e3f6e9782f462d797b72f9cbf5a4c38ca20cabcc1a091f9de6d3e6736c1f7c',
160164
nonce: '0x19b4782',
161165
transactionIndex: '0x0',

packages/network/tests/provider/providers/fixture.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ const providerMethodsTestCasesTestnet = [
2525
description: 'Should be able to call eth_chainId',
2626
method: 'eth_chainId',
2727
params: [],
28-
expected: '0x186aa'
28+
expected:
29+
'0x000000000b2bce3c70bc649a02749e8687721b09ed2e15997f466536b20bb127'
2930
},
3031
{
3132
description: `Should be able to call eth_getTransactionByHash with ${validTransactionHashTestnet} as the transaction hash`,

packages/network/tests/provider/rpc-mapper/methods/eth_chainId/eth_chainId.solo.test.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import { beforeEach, describe, expect, test } from '@jest/globals';
22
import {
3-
CHAIN_ID,
43
RPC_METHODS,
54
RPCMethodsMap,
65
THOR_SOLO_URL,
76
ThorClient
87
} from '../../../../../src';
98

9+
const soloChainId =
10+
'0x00000000c05a20fbca2bf6ae3affba6af4a74b800b585bf7a4988aba7aea69f6';
11+
1012
/**
1113
* RPC Mapper integration tests for 'eth_chainId' method
1214
*
@@ -38,7 +40,7 @@ describe('RPC Mapper - eth_chainId method tests solo', () => {
3840
RPC_METHODS.eth_chainId
3941
]([])) as string;
4042

41-
expect(rpcCallChainId).toBe(CHAIN_ID.TESTNET);
43+
expect(rpcCallChainId).toBe(soloChainId);
4244
});
4345
});
4446
});

packages/network/tests/provider/rpc-mapper/methods/eth_getTransactionByBlockHashAndIndex/fixture.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ const ethGetTransactionByBlockHashAndIndexTestCases = [
1919
blockNumber: '0x10b7a6d',
2020
from: '0x7487d912d03ab9de786278f679592b3730bdd540',
2121
gas: '0xbd30',
22-
chainId: '0x186aa',
22+
chainId:
23+
'0x000000000b2bce3c70bc649a02749e8687721b09ed2e15997f466536b20bb127',
2324
hash: '0x6994801b6f92f9a0a151ab4ac1c27d2dcf2ab61245b10ddf05504ae5384e759d',
2425
nonce: '0x176bbcbf79a3a672',
2526
transactionIndex: '0x1',
@@ -49,7 +50,8 @@ const ethGetTransactionByBlockHashAndIndexTestCases = [
4950
blockHash:
5051
'0x010b7a6d6f04407ac2f72e505ff83d49db8d01607f8af41f508b2ca7eca0d450',
5152
blockNumber: '0x10b7a6d',
52-
chainId: '0x186aa',
53+
chainId:
54+
'0x000000000b2bce3c70bc649a02749e8687721b09ed2e15997f466536b20bb127',
5355
from: '0x7487d912d03ab9de786278f679592b3730bdd540',
5456
gas: '0x7436',
5557
gasPrice: '0x0',

packages/network/tests/provider/rpc-mapper/methods/eth_getTransactionByBlockNumberAndIndex/fixture.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ const ethGetTransactionByBlockNumberAndIndexTestCases = [
1717
blockNumber: '0x10b7a6d',
1818
from: '0x7487d912d03ab9de786278f679592b3730bdd540',
1919
gas: '0xbd30',
20-
chainId: '0x186aa',
20+
chainId:
21+
'0x000000000b2bce3c70bc649a02749e8687721b09ed2e15997f466536b20bb127',
2122
hash: '0x6994801b6f92f9a0a151ab4ac1c27d2dcf2ab61245b10ddf05504ae5384e759d',
2223
nonce: '0x176bbcbf79a3a672',
2324
transactionIndex: '0x1',
@@ -44,7 +45,8 @@ const ethGetTransactionByBlockNumberAndIndexTestCases = [
4445
blockHash:
4546
'0x010b7a6d6f04407ac2f72e505ff83d49db8d01607f8af41f508b2ca7eca0d450',
4647
blockNumber: '0x10b7a6d',
47-
chainId: '0x186aa',
48+
chainId:
49+
'0x000000000b2bce3c70bc649a02749e8687721b09ed2e15997f466536b20bb127',
4850
from: '0x7487d912d03ab9de786278f679592b3730bdd540',
4951
gas: '0x7436',
5052
gasPrice: '0x0',

packages/network/tests/provider/rpc-mapper/methods/eth_sendTransaction/eth_sendTransaction.solo.test.ts

+21
Original file line numberDiff line numberDiff line change
@@ -302,5 +302,26 @@ describe('RPC Mapper - eth_sendTransaction method tests', () => {
302302
})
303303
).rejects.toThrowError(JSONRPCInvalidParams);
304304
});
305+
306+
/**
307+
* Negative case 5 - Chain Id mismatch
308+
*/
309+
test('eth_sendTransaction - Should throw error if chainId does not match', async () => {
310+
// Send a transaction with invalid chainId
311+
await expect(
312+
async () =>
313+
await provider.request({
314+
method: RPC_METHODS.eth_sendTransaction,
315+
params: [
316+
{
317+
from: THOR_SOLO_ACCOUNTS[1].address,
318+
to: THOR_SOLO_ACCOUNTS[2].address,
319+
value: '0x1',
320+
chainId: '0x123'
321+
}
322+
]
323+
})
324+
).rejects.toThrowError(JSONRPCInvalidParams);
325+
});
305326
});
306327
});

packages/rpc-proxy/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,7 @@ The following mappings are performed by the RPC proxy
274274
| finalized block | finalized block |
275275
| pending block | best block |
276276
| earliest block | block number 0 |
277+
| chainId | genesis block id |
277278

278279

279280
## Transaction Coversions

packages/rpc-proxy/tests/e2e_rpc_proxy.solo.test.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import {
99

1010
let environment: StartedDockerComposeEnvironment;
1111
const RPC_PROXY_URL = `http://localhost:8545`;
12+
const genesisBlockId =
13+
'0x0000000008602e7a995c747a3215b426c0c65709480b9e9ac57ad37c3f7d73de'; // custom genesis block id as solo is using a custom genesis file
1214

1315
beforeAll(async () => {
1416
environment = await new DockerComposeEnvironment(
@@ -202,7 +204,7 @@ describe('RPC Proxy endpoints', () => {
202204
console.log(response.data);
203205
expect(response.data).toHaveProperty('result');
204206
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
205-
expect(response.data.result).toBe('0x186aa');
207+
expect(response.data.result).toBe(genesisBlockId);
206208
});
207209

208210
it('eth_estimateGas method call', async () => {
@@ -764,8 +766,9 @@ describe('RPC Proxy endpoints', () => {
764766

765767
console.log(response.data);
766768
expect(response.data).toHaveProperty('result');
767-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
768-
expect(response.data.result).toBe('0x186aa');
769+
expect((response.data as { result: string }).result).toBe(
770+
genesisBlockId
771+
);
769772
});
770773

771774
it('txpool_content method call', async () => {

scripts/test-rpc-proxy.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ const endpointsTestCases = [
44
{
55
method: 'net_version',
66
params: [],
7-
expected: '0x186aa'
7+
expected:
8+
'0x000000000b2bce3c70bc649a02749e8687721b09ed2e15997f466536b20bb127'
89
},
910
{
1011
method: 'eth_chainId',
1112
params: [],
12-
expected: '0x186aa'
13+
expected:
14+
'0x000000000b2bce3c70bc649a02749e8687721b09ed2e15997f466536b20bb127'
1315
},
1416
{
1517
method: 'web3_clientVersion',

turbo.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
"$schema": "https://turbo.build/schema.json",
33
"tasks": {
44
"build": {
5-
"dependsOn": ["^build"]
5+
"dependsOn": ["^build"],
6+
"cache": false
67
},
78
"check:circular-dependencies": {},
89
"test": {

0 commit comments

Comments
 (0)