diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1417e327ea2..f2e1c219adc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -958,3 +958,21 @@ should use 4.0.1-alpha.0 for testing.
- Parameters decoding error for nested components (#5714)
## [Unreleased]
+
+### Changed
+
+#### web3-providers-ipc
+
+- Refactor to use common SocketProvider class (#5683)
+
+#### web3-providers-ws
+
+- Refactor to use common SocketProvider class (#5683)
+
+#### web3-utils
+
+- Add SocketProvider class and Eip1193Provider abstract class (#5683)
+
+#### web3-types
+
+- These types were added: ProviderRpcError, EthSubscription, ProviderMessage, ProviderConnectInfo (#5683)
diff --git a/packages/web3-eth-ens/test/integration/ens.test.ts b/packages/web3-eth-ens/test/integration/ens.test.ts
index 85c1d7dce2b..82433eeb602 100644
--- a/packages/web3-eth-ens/test/integration/ens.test.ts
+++ b/packages/web3-eth-ens/test/integration/ens.test.ts
@@ -30,6 +30,7 @@ import {
isWs,
isIpc,
closeOpenConnection,
+ isSocket,
} from '../fixtures/system_tests_utils';
import { ENSRegistryAbi } from '../../src/abi/ens/ENSRegistry';
@@ -132,9 +133,10 @@ describe('ens', () => {
});
afterAll(async () => {
- if (isWs || isIpc) {
+ if (isSocket) {
await closeOpenConnection(ens);
- await closeOpenConnection(ens['_registry']['contract']);
+ // @ts-expect-error @typescript-eslint/ban-ts-comment
+ await closeOpenConnection(ens?._registry?.contract);
await closeOpenConnection(getEnsResolver);
await closeOpenConnection(setEnsResolver);
await closeOpenConnection(registry);
diff --git a/packages/web3-eth-ens/test/integration/resolver.test.ts b/packages/web3-eth-ens/test/integration/resolver.test.ts
index 63b494458db..096df74ac6c 100644
--- a/packages/web3-eth-ens/test/integration/resolver.test.ts
+++ b/packages/web3-eth-ens/test/integration/resolver.test.ts
@@ -30,6 +30,7 @@ import {
isWs,
isIpc,
closeOpenConnection,
+ isSocket,
} from '../fixtures/system_tests_utils';
import { ENSRegistryAbi } from '../../src/abi/ens/ENSRegistry';
@@ -130,9 +131,10 @@ describe('ens', () => {
});
afterAll(async () => {
- if (isWs || isIpc) {
+ if (isSocket) {
await closeOpenConnection(ens);
- await closeOpenConnection(ens['_registry']['contract']);
+ // @ts-expect-error @typescript-eslint/ban-ts-comment
+ await closeOpenConnection(ens?._registry?.contract);
await closeOpenConnection(registry);
await closeOpenConnection(resolver);
await closeOpenConnection(nameWrapper);
diff --git a/packages/web3-eth/test/integration/batch.test.ts b/packages/web3-eth/test/integration/batch.test.ts
index 5e00bdd3688..1e167740b6f 100644
--- a/packages/web3-eth/test/integration/batch.test.ts
+++ b/packages/web3-eth/test/integration/batch.test.ts
@@ -14,7 +14,6 @@ GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with web3.js. If not, see .
*/
-import WebSocketProvider from 'web3-providers-ws';
// eslint-disable-next-line import/no-extraneous-dependencies
import { hexToNumber } from 'web3-utils';
import { Web3Eth } from '../../src';
@@ -23,7 +22,7 @@ import {
closeOpenConnection,
createTempAccount,
getSystemTestProvider,
- isWs,
+ waitForOpenConnection,
} from '../fixtures/system_test_utils';
describe('eth', () => {
@@ -33,18 +32,8 @@ describe('eth', () => {
beforeAll(async () => {
clientUrl = getSystemTestProvider();
-
- if (isWs) {
- web3Eth = new Web3Eth(
- new WebSocketProvider(
- clientUrl,
- {},
- { delay: 1, autoReconnect: false, maxAttempts: 1 },
- ),
- );
- } else {
- web3Eth = new Web3Eth(clientUrl);
- }
+ web3Eth = new Web3Eth(clientUrl);
+ await waitForOpenConnection(web3Eth);
});
afterAll(async () => {
await closeOpenConnection(web3Eth);
@@ -54,6 +43,7 @@ describe('eth', () => {
it('BatchRequest', async () => {
const acc1 = await createTempAccount();
const acc2 = await createTempAccount();
+
const batch = new web3Eth.BatchRequest();
const request1 = {
id: 10,
diff --git a/packages/web3-eth/test/integration/block/rpc.getBlock.test.ts b/packages/web3-eth/test/integration/block/rpc.getBlock.test.ts
new file mode 100644
index 00000000000..9fd0693b3fc
--- /dev/null
+++ b/packages/web3-eth/test/integration/block/rpc.getBlock.test.ts
@@ -0,0 +1,130 @@
+/*
+This file is part of web3.js.
+
+web3.js is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+web3.js is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with web3.js. If not, see .
+*/
+import { FMT_BYTES, FMT_NUMBER } from 'web3-utils';
+import { TransactionReceipt, Transaction } from 'web3-types';
+// eslint-disable-next-line import/no-extraneous-dependencies
+import { Contract } from 'web3-eth-contract';
+import { validator } from 'web3-validator';
+import { Web3Eth } from '../../../src';
+import {
+ getSystemTestProvider,
+ createTempAccount,
+ closeOpenConnection,
+} from '../../fixtures/system_test_utils';
+import { BasicAbi, BasicBytecode } from '../../shared_fixtures/build/Basic';
+import { toAllVariants } from '../../shared_fixtures/utils';
+import { sendFewTxes } from '../helper';
+import { blockSchema } from '../../../src/schemas';
+
+describe('rpc with block', () => {
+ let web3Eth: Web3Eth;
+ let clientUrl: string;
+
+ let contract: Contract;
+ let deployOptions: Record;
+ let sendOptions: Record;
+
+ let blockData: {
+ earliest: 'earliest';
+ latest: 'latest';
+ pending: 'pending';
+ blockNumber: number | bigint;
+ blockHash: string;
+ transactionHash: string;
+ transactionIndex: number | bigint;
+ };
+ let tempAcc: { address: string; privateKey: string };
+ let tempAcc2: { address: string; privateKey: string };
+
+ beforeAll(() => {
+ clientUrl = getSystemTestProvider();
+ web3Eth = new Web3Eth({
+ provider: clientUrl,
+ config: {
+ transactionPollingTimeout: 2000,
+ },
+ });
+
+ contract = new Contract(BasicAbi, undefined, {
+ provider: clientUrl,
+ });
+
+ deployOptions = {
+ data: BasicBytecode,
+ arguments: [10, 'string init value'],
+ };
+ });
+ beforeAll(async () => {
+ tempAcc = await createTempAccount();
+ tempAcc2 = await createTempAccount();
+ sendOptions = { from: tempAcc.address, gas: '1000000' };
+
+ await contract.deploy(deployOptions).send(sendOptions);
+ const [receipt]: TransactionReceipt[] = await sendFewTxes({
+ web3Eth,
+ from: tempAcc.address,
+ to: tempAcc2.address,
+ value: '0x1',
+ times: 1,
+ });
+ blockData = {
+ pending: 'pending',
+ latest: 'latest',
+ earliest: 'earliest',
+ blockNumber: Number(receipt.blockNumber),
+ blockHash: String(receipt.blockHash),
+ transactionHash: String(receipt.transactionHash),
+ transactionIndex: Number(receipt.transactionIndex),
+ };
+ });
+ afterAll(async () => {
+ await closeOpenConnection(web3Eth);
+ await closeOpenConnection(contract);
+ });
+
+ describe('methods', () => {
+ it.each(
+ toAllVariants<{
+ block: 'earliest' | 'latest' | 'pending' | 'blockHash' | 'blockNumber';
+ hydrated: boolean;
+ format: string;
+ }>({
+ block: ['earliest', 'latest', 'blockHash', 'blockNumber'],
+ hydrated: [true, false],
+ format: Object.values(FMT_NUMBER),
+ }),
+ )('getBlock', async ({ hydrated, block, format }) => {
+ const b = {
+ ...(await web3Eth.getBlock(blockData[block], hydrated, {
+ number: format as FMT_NUMBER,
+ bytes: FMT_BYTES.HEX,
+ })),
+ };
+ if (blockData[block] === 'pending') {
+ b.nonce = '0x0';
+ b.miner = '0x0000000000000000000000000000000000000000';
+ b.totalDifficulty = '0x0';
+ }
+ expect(validator.validateJSONSchema(blockSchema, b)).toBeUndefined();
+
+ if (hydrated && b.transactions?.length > 0) {
+ // eslint-disable-next-line jest/no-conditional-expect
+ expect(b.transactions).toBeInstanceOf(Array);
+ }
+ });
+ });
+});
diff --git a/packages/web3-eth/test/integration/block/rpc.getBlockTransactionCount.test.ts b/packages/web3-eth/test/integration/block/rpc.getBlockTransactionCount.test.ts
new file mode 100644
index 00000000000..d64d2a43794
--- /dev/null
+++ b/packages/web3-eth/test/integration/block/rpc.getBlockTransactionCount.test.ts
@@ -0,0 +1,115 @@
+/*
+This file is part of web3.js.
+
+web3.js is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+web3.js is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with web3.js. If not, see .
+*/
+import { TransactionReceipt } from 'web3-types';
+// eslint-disable-next-line import/no-extraneous-dependencies
+import { Contract } from 'web3-eth-contract';
+import { Web3Eth } from '../../../src';
+import {
+ getSystemTestBackend,
+ getSystemTestProvider,
+ createTempAccount,
+ closeOpenConnection,
+} from '../../fixtures/system_test_utils';
+import { BasicAbi, BasicBytecode } from '../../shared_fixtures/build/Basic';
+import { toAllVariants } from '../../shared_fixtures/utils';
+import { sendFewTxes } from '../helper';
+
+describe('rpc with block', () => {
+ let web3Eth: Web3Eth;
+ let clientUrl: string;
+
+ let contract: Contract;
+ let deployOptions: Record;
+ let sendOptions: Record;
+
+ let blockData: {
+ earliest: 'earliest';
+ latest: 'latest';
+ pending: 'pending';
+ blockNumber: number | bigint;
+ blockHash: string;
+ transactionHash: string;
+ transactionIndex: number | bigint;
+ };
+ let tempAcc: { address: string; privateKey: string };
+ let tempAcc2: { address: string; privateKey: string };
+
+ beforeAll(() => {
+ clientUrl = getSystemTestProvider();
+ web3Eth = new Web3Eth({
+ provider: clientUrl,
+ config: {
+ transactionPollingTimeout: 2000,
+ },
+ });
+
+ contract = new Contract(BasicAbi, undefined, {
+ provider: clientUrl,
+ });
+
+ deployOptions = {
+ data: BasicBytecode,
+ arguments: [10, 'string init value'],
+ };
+ });
+ beforeAll(async () => {
+ tempAcc = await createTempAccount();
+ tempAcc2 = await createTempAccount();
+ sendOptions = { from: tempAcc.address, gas: '1000000' };
+
+ await contract.deploy(deployOptions).send(sendOptions);
+ const [receipt]: TransactionReceipt[] = await sendFewTxes({
+ web3Eth,
+ from: tempAcc.address,
+ to: tempAcc2.address,
+ value: '0x1',
+ times: 1,
+ });
+ blockData = {
+ pending: 'pending',
+ latest: 'latest',
+ earliest: 'earliest',
+ blockNumber: Number(receipt.blockNumber),
+ blockHash: String(receipt.blockHash),
+ transactionHash: String(receipt.transactionHash),
+ transactionIndex: Number(receipt.transactionIndex),
+ };
+ });
+ afterAll(async () => {
+ await closeOpenConnection(web3Eth);
+ await closeOpenConnection(contract);
+ });
+
+ describe('methods', () => {
+ it.each(
+ toAllVariants<{
+ block: 'earliest' | 'latest' | 'pending' | 'blockHash' | 'blockNumber';
+ }>({
+ block: ['earliest', 'latest', 'pending', 'blockHash', 'blockNumber'],
+ }),
+ )('getBlockTransactionCount', async ({ block }) => {
+ const res = await web3Eth.getBlockTransactionCount(blockData[block]);
+ let shouldBe: number;
+ if (getSystemTestBackend() === 'ganache') {
+ shouldBe = blockData[block] === 'earliest' ? 0 : 1;
+ } else {
+ shouldBe = ['earliest', 'pending'].includes(String(blockData[block])) ? 0 : 1;
+ }
+ expect(Number(res)).toBe(shouldBe);
+ });
+ });
+});
diff --git a/packages/web3-eth/test/integration/block/rpc.getBlockUncleCount.test.ts b/packages/web3-eth/test/integration/block/rpc.getBlockUncleCount.test.ts
new file mode 100644
index 00000000000..3c4d6ef911b
--- /dev/null
+++ b/packages/web3-eth/test/integration/block/rpc.getBlockUncleCount.test.ts
@@ -0,0 +1,108 @@
+/*
+This file is part of web3.js.
+
+web3.js is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+web3.js is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with web3.js. If not, see .
+*/
+import { TransactionReceipt } from 'web3-types';
+// eslint-disable-next-line import/no-extraneous-dependencies
+import { Contract } from 'web3-eth-contract';
+import { Web3Eth } from '../../../src';
+import {
+ getSystemTestProvider,
+ createTempAccount,
+ closeOpenConnection,
+} from '../../fixtures/system_test_utils';
+import { BasicAbi, BasicBytecode } from '../../shared_fixtures/build/Basic';
+import { toAllVariants } from '../../shared_fixtures/utils';
+import { sendFewTxes } from '../helper';
+
+describe('rpc with block', () => {
+ let web3Eth: Web3Eth;
+ let clientUrl: string;
+
+ let contract: Contract;
+ let deployOptions: Record;
+ let sendOptions: Record;
+
+ let blockData: {
+ earliest: 'earliest';
+ latest: 'latest';
+ pending: 'pending';
+ blockNumber: number | bigint;
+ blockHash: string;
+ transactionHash: string;
+ transactionIndex: number | bigint;
+ };
+ let tempAcc: { address: string; privateKey: string };
+ let tempAcc2: { address: string; privateKey: string };
+
+ beforeAll(() => {
+ clientUrl = getSystemTestProvider();
+ web3Eth = new Web3Eth({
+ provider: clientUrl,
+ config: {
+ transactionPollingTimeout: 2000,
+ },
+ });
+
+ contract = new Contract(BasicAbi, undefined, {
+ provider: clientUrl,
+ });
+
+ deployOptions = {
+ data: BasicBytecode,
+ arguments: [10, 'string init value'],
+ };
+ });
+ beforeAll(async () => {
+ tempAcc = await createTempAccount();
+ tempAcc2 = await createTempAccount();
+ sendOptions = { from: tempAcc.address, gas: '1000000' };
+
+ await contract.deploy(deployOptions).send(sendOptions);
+ const [receipt]: TransactionReceipt[] = await sendFewTxes({
+ web3Eth,
+ from: tempAcc.address,
+ to: tempAcc2.address,
+ value: '0x1',
+ times: 1,
+ });
+ blockData = {
+ pending: 'pending',
+ latest: 'latest',
+ earliest: 'earliest',
+ blockNumber: Number(receipt.blockNumber),
+ blockHash: String(receipt.blockHash),
+ transactionHash: String(receipt.transactionHash),
+ transactionIndex: Number(receipt.transactionIndex),
+ };
+ });
+ afterAll(async () => {
+ await closeOpenConnection(web3Eth);
+ await closeOpenConnection(contract);
+ });
+
+ describe('methods', () => {
+ it.each(
+ toAllVariants<{
+ block: 'earliest' | 'latest' | 'pending' | 'blockHash' | 'blockNumber';
+ }>({
+ block: ['earliest', 'latest', 'pending', 'blockHash', 'blockNumber'],
+ }),
+ )('getBlockUncleCount', async ({ block }) => {
+ const res = await web3Eth.getBlockUncleCount(blockData[block]);
+ expect(Number(res)).toBe(0);
+ });
+ });
+});
diff --git a/packages/web3-eth/test/integration/rpc.block.test.ts b/packages/web3-eth/test/integration/block/rpc.getTransactionCount.test.ts
similarity index 52%
rename from packages/web3-eth/test/integration/rpc.block.test.ts
rename to packages/web3-eth/test/integration/block/rpc.getTransactionCount.test.ts
index b8ebf18f99b..6466d0c78a2 100644
--- a/packages/web3-eth/test/integration/rpc.block.test.ts
+++ b/packages/web3-eth/test/integration/block/rpc.getTransactionCount.test.ts
@@ -15,27 +15,19 @@ You should have received a copy of the GNU Lesser General Public License
along with web3.js. If not, see .
*/
import { FMT_BYTES, FMT_NUMBER } from 'web3-utils';
-import { TransactionInfo, TransactionReceipt, Transaction } from 'web3-types';
+import { TransactionReceipt } from 'web3-types';
// eslint-disable-next-line import/no-extraneous-dependencies
import { Contract } from 'web3-eth-contract';
-// eslint-disable-next-line import/no-extraneous-dependencies
-import IpcProvider from 'web3-providers-ipc';
-import { validator } from 'web3-validator';
-
-import { Web3Eth } from '../../src';
-
+import { Web3Eth } from '../../../src';
import {
- getSystemTestBackend,
getSystemTestProvider,
createNewAccount,
- isIpc,
createTempAccount,
closeOpenConnection,
-} from '../fixtures/system_test_utils';
-import { BasicAbi, BasicBytecode } from '../shared_fixtures/build/Basic';
-import { toAllVariants } from '../shared_fixtures/utils';
-import { sendFewTxes, validateTransaction } from './helper';
-import { blockSchema } from '../../src/schemas';
+} from '../../fixtures/system_test_utils';
+import { BasicAbi, BasicBytecode } from '../../shared_fixtures/build/Basic';
+import { toAllVariants } from '../../shared_fixtures/utils';
+import { sendFewTxes } from '../helper';
describe('rpc with block', () => {
let web3Eth: Web3Eth;
@@ -57,7 +49,7 @@ describe('rpc with block', () => {
let tempAcc: { address: string; privateKey: string };
let tempAcc2: { address: string; privateKey: string };
- beforeAll(async () => {
+ beforeAll(() => {
clientUrl = getSystemTestProvider();
web3Eth = new Web3Eth({
provider: clientUrl,
@@ -74,10 +66,6 @@ describe('rpc with block', () => {
data: BasicBytecode,
arguments: [10, 'string init value'],
};
- if (isIpc) {
- await (contract.provider as IpcProvider).waitForConnection();
- await (web3Eth.provider as IpcProvider).waitForConnection();
- }
});
beforeAll(async () => {
tempAcc = await createTempAccount();
@@ -108,60 +96,6 @@ describe('rpc with block', () => {
});
describe('methods', () => {
- beforeAll(async () => {
- tempAcc = await createTempAccount();
- tempAcc2 = await createTempAccount();
- sendOptions = { from: tempAcc.address, gas: '1000000' };
-
- await contract.deploy(deployOptions).send(sendOptions);
- const [receipt]: TransactionReceipt[] = await sendFewTxes({
- web3Eth,
- from: tempAcc.address,
- to: tempAcc2.address,
- value: '0x1',
- times: 1,
- });
- blockData = {
- pending: 'pending',
- latest: 'latest',
- earliest: 'earliest',
- blockNumber: Number(receipt.blockNumber),
- blockHash: String(receipt.blockHash),
- transactionHash: String(receipt.transactionHash),
- transactionIndex: Number(receipt.transactionIndex),
- };
- });
-
- it.each(
- toAllVariants<{
- block: 'earliest' | 'latest' | 'pending' | 'blockHash' | 'blockNumber';
- hydrated: boolean;
- format: string;
- }>({
- block: ['earliest', 'latest', 'blockHash', 'blockNumber'],
- hydrated: [true, false],
- format: Object.values(FMT_NUMBER),
- }),
- )('getBlock', async ({ hydrated, block, format }) => {
- const b = {
- ...(await web3Eth.getBlock(blockData[block], hydrated, {
- number: format as FMT_NUMBER,
- bytes: FMT_BYTES.HEX,
- })),
- };
- if (blockData[block] === 'pending') {
- b.nonce = '0x0';
- b.miner = '0x0000000000000000000000000000000000000000';
- b.totalDifficulty = '0x0';
- }
- expect(validator.validateJSONSchema(blockSchema, b)).toBeUndefined();
-
- if (hydrated && b.transactions?.length > 0) {
- // eslint-disable-next-line jest/no-conditional-expect
- expect(b.transactions).toBeInstanceOf(Array);
- }
- });
-
it.each(
toAllVariants<{
block: 'earliest' | 'latest' | 'pending' | 'blockHash' | 'blockNumber';
@@ -219,54 +153,5 @@ describe('rpc with block', () => {
blockData[block] === 'earliest' ? 0 : count,
);
});
-
- it.each(
- toAllVariants<{
- block: 'earliest' | 'latest' | 'pending' | 'blockHash' | 'blockNumber';
- }>({
- block: ['earliest', 'latest', 'pending', 'blockHash', 'blockNumber'],
- }),
- )('getBlockTransactionCount', async ({ block }) => {
- const res = await web3Eth.getBlockTransactionCount(blockData[block]);
- let shouldBe: number;
- if (getSystemTestBackend() === 'ganache') {
- shouldBe = blockData[block] === 'earliest' ? 0 : 1;
- } else {
- shouldBe = ['earliest', 'pending'].includes(String(blockData[block])) ? 0 : 1;
- }
- expect(Number(res)).toBe(shouldBe);
- });
-
- it.each(
- toAllVariants<{
- block: 'earliest' | 'latest' | 'pending' | 'blockHash' | 'blockNumber';
- }>({
- block: ['earliest', 'latest', 'pending', 'blockHash', 'blockNumber'],
- }),
- )('getBlockUncleCount', async ({ block }) => {
- const res = await web3Eth.getBlockUncleCount(blockData[block]);
- expect(Number(res)).toBe(0);
- });
-
- it.each(
- toAllVariants<{
- block: 'earliest' | 'latest' | 'pending' | 'blockNumber';
- }>({
- block: ['earliest', 'latest', 'pending', 'blockNumber'],
- }),
- )('getUncle', async ({ block }) => {
- const res = await web3Eth.getUncle(blockData[block], 0);
- // eslint-disable-next-line jest/no-standalone-expect
- expect(res).toBeNull();
- });
- it('getTransactionFromBlock', async () => {
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- const tx = (await web3Eth.getTransactionFromBlock(
- blockData.blockNumber,
- blockData.transactionIndex,
- ))!;
- validateTransaction(tx as TransactionInfo);
- expect(tx?.hash).toBe(blockData.transactionHash);
- });
});
});
diff --git a/packages/web3-eth/test/integration/block/rpc.getTransactionFromBlock.test.ts b/packages/web3-eth/test/integration/block/rpc.getTransactionFromBlock.test.ts
new file mode 100644
index 00000000000..16317373c1a
--- /dev/null
+++ b/packages/web3-eth/test/integration/block/rpc.getTransactionFromBlock.test.ts
@@ -0,0 +1,106 @@
+/*
+This file is part of web3.js.
+
+web3.js is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+web3.js is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with web3.js. If not, see .
+*/
+import { TransactionInfo, TransactionReceipt } from 'web3-types';
+// eslint-disable-next-line import/no-extraneous-dependencies
+import { Contract } from 'web3-eth-contract';
+import { Web3Eth } from '../../../src';
+import {
+ getSystemTestProvider,
+ createTempAccount,
+ closeOpenConnection,
+} from '../../fixtures/system_test_utils';
+import { BasicAbi, BasicBytecode } from '../../shared_fixtures/build/Basic';
+import { sendFewTxes, validateTransaction } from '../helper';
+
+describe('rpc with block', () => {
+ let web3Eth: Web3Eth;
+ let clientUrl: string;
+
+ let contract: Contract;
+ let deployOptions: Record;
+ let sendOptions: Record;
+
+ let blockData: {
+ earliest: 'earliest';
+ latest: 'latest';
+ pending: 'pending';
+ blockNumber: number | bigint;
+ blockHash: string;
+ transactionHash: string;
+ transactionIndex: number | bigint;
+ };
+ let tempAcc: { address: string; privateKey: string };
+ let tempAcc2: { address: string; privateKey: string };
+
+ beforeAll(() => {
+ clientUrl = getSystemTestProvider();
+ web3Eth = new Web3Eth({
+ provider: clientUrl,
+ config: {
+ transactionPollingTimeout: 2000,
+ },
+ });
+
+ contract = new Contract(BasicAbi, undefined, {
+ provider: clientUrl,
+ });
+
+ deployOptions = {
+ data: BasicBytecode,
+ arguments: [10, 'string init value'],
+ };
+ });
+ beforeAll(async () => {
+ tempAcc = await createTempAccount();
+ tempAcc2 = await createTempAccount();
+ sendOptions = { from: tempAcc.address, gas: '1000000' };
+
+ await contract.deploy(deployOptions).send(sendOptions);
+ const [receipt]: TransactionReceipt[] = await sendFewTxes({
+ web3Eth,
+ from: tempAcc.address,
+ to: tempAcc2.address,
+ value: '0x1',
+ times: 1,
+ });
+ blockData = {
+ pending: 'pending',
+ latest: 'latest',
+ earliest: 'earliest',
+ blockNumber: Number(receipt.blockNumber),
+ blockHash: String(receipt.blockHash),
+ transactionHash: String(receipt.transactionHash),
+ transactionIndex: Number(receipt.transactionIndex),
+ };
+ });
+ afterAll(async () => {
+ await closeOpenConnection(web3Eth);
+ await closeOpenConnection(contract);
+ });
+
+ describe('methods', () => {
+ it('getTransactionFromBlock', async () => {
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ const tx = (await web3Eth.getTransactionFromBlock(
+ blockData.blockNumber,
+ blockData.transactionIndex,
+ ))!;
+ validateTransaction(tx as TransactionInfo);
+ expect(tx?.hash).toBe(blockData.transactionHash);
+ });
+ });
+});
diff --git a/packages/web3-eth/test/integration/block/rpc.getUncle.test.ts b/packages/web3-eth/test/integration/block/rpc.getUncle.test.ts
new file mode 100644
index 00000000000..89dd0e0b3ca
--- /dev/null
+++ b/packages/web3-eth/test/integration/block/rpc.getUncle.test.ts
@@ -0,0 +1,111 @@
+/*
+This file is part of web3.js.
+
+web3.js is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+web3.js is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with web3.js. If not, see .
+*/
+import { TransactionReceipt } from 'web3-types';
+// eslint-disable-next-line import/no-extraneous-dependencies
+import { Contract } from 'web3-eth-contract';
+
+import { Web3Eth } from '../../../src';
+
+import {
+ getSystemTestProvider,
+ createTempAccount,
+ closeOpenConnection,
+} from '../../fixtures/system_test_utils';
+import { BasicAbi, BasicBytecode } from '../../shared_fixtures/build/Basic';
+import { toAllVariants } from '../../shared_fixtures/utils';
+import { sendFewTxes } from '../helper';
+
+describe('rpc with block', () => {
+ let web3Eth: Web3Eth;
+ let clientUrl: string;
+
+ let contract: Contract;
+ let deployOptions: Record;
+ let sendOptions: Record;
+
+ let blockData: {
+ earliest: 'earliest';
+ latest: 'latest';
+ pending: 'pending';
+ blockNumber: number | bigint;
+ blockHash: string;
+ transactionHash: string;
+ transactionIndex: number | bigint;
+ };
+ let tempAcc: { address: string; privateKey: string };
+ let tempAcc2: { address: string; privateKey: string };
+
+ beforeAll(() => {
+ clientUrl = getSystemTestProvider();
+ web3Eth = new Web3Eth({
+ provider: clientUrl,
+ config: {
+ transactionPollingTimeout: 2000,
+ },
+ });
+
+ contract = new Contract(BasicAbi, undefined, {
+ provider: clientUrl,
+ });
+
+ deployOptions = {
+ data: BasicBytecode,
+ arguments: [10, 'string init value'],
+ };
+ });
+ beforeAll(async () => {
+ tempAcc = await createTempAccount();
+ tempAcc2 = await createTempAccount();
+ sendOptions = { from: tempAcc.address, gas: '1000000' };
+
+ await contract.deploy(deployOptions).send(sendOptions);
+ const [receipt]: TransactionReceipt[] = await sendFewTxes({
+ web3Eth,
+ from: tempAcc.address,
+ to: tempAcc2.address,
+ value: '0x1',
+ times: 1,
+ });
+ blockData = {
+ pending: 'pending',
+ latest: 'latest',
+ earliest: 'earliest',
+ blockNumber: Number(receipt.blockNumber),
+ blockHash: String(receipt.blockHash),
+ transactionHash: String(receipt.transactionHash),
+ transactionIndex: Number(receipt.transactionIndex),
+ };
+ });
+ afterAll(async () => {
+ await closeOpenConnection(web3Eth);
+ await closeOpenConnection(contract);
+ });
+
+ describe('methods', () => {
+ it.each(
+ toAllVariants<{
+ block: 'earliest' | 'latest' | 'pending' | 'blockNumber';
+ }>({
+ block: ['earliest', 'latest', 'pending', 'blockNumber'],
+ }),
+ )('getUncle', async ({ block }) => {
+ const res = await web3Eth.getUncle(blockData[block], 0);
+ // eslint-disable-next-line jest/no-standalone-expect
+ expect(res).toBeNull();
+ });
+ });
+});
diff --git a/packages/web3-eth/test/integration/defaults.test.ts b/packages/web3-eth/test/integration/defaults.test.ts
index 4320c977298..b750d2d9e1a 100644
--- a/packages/web3-eth/test/integration/defaults.test.ts
+++ b/packages/web3-eth/test/integration/defaults.test.ts
@@ -20,11 +20,7 @@ import { hexToNumber, numberToHex, DEFAULT_RETURN_FORMAT } from 'web3-utils';
import { TransactionBuilder, TransactionTypeParser, Web3Context, Web3PromiEvent } from 'web3-core';
import { Hardfork, TransactionReceipt, ValidChains, Web3BaseProvider } from 'web3-types';
import {
- TransactionBlockTimeoutError,
- TransactionPollingTimeoutError,
- TransactionSendTimeoutError,
-} from 'web3-errors';
-import {
+ detectTransactionType,
prepareTransactionForSigning,
SendTransactionEvents,
transactionBuilder,
@@ -36,9 +32,6 @@ import {
createNewAccount,
createTempAccount,
getSystemTestProvider,
- isIpc,
- isWs,
- itIf,
} from '../fixtures/system_test_utils';
import {
@@ -48,11 +41,8 @@ import {
} from '../../src/utils';
import { BasicAbi, BasicBytecode } from '../shared_fixtures/build/Basic';
import { MsgSenderAbi, MsgSenderBytecode } from '../shared_fixtures/build/MsgSender';
-import { detectTransactionType } from '../../dist';
import { getTransactionGasPricing } from '../../src/utils/get_transaction_gas_pricing';
-import { Resolve, sendFewTxes } from './helper';
-
-const MAX_32_SIGNED_INTEGER = 2147483647;
+import { Resolve, sendFewTxes, sendFewTxesWithoutReceipt } from './helper';
describe('defaults', () => {
let web3Eth: Web3Eth;
@@ -63,16 +53,9 @@ describe('defaults', () => {
let sendOptions: Record;
let tempAcc: { address: string; privateKey: string };
- beforeAll(() => {
+ beforeEach(async () => {
clientUrl = getSystemTestProvider();
web3Eth = new Web3Eth(clientUrl);
- });
-
- afterAll(async () => {
- await closeOpenConnection(web3Eth);
- await closeOpenConnection(eth2);
- });
- beforeEach(async () => {
tempAcc = await createTempAccount();
contract = new Contract(BasicAbi, web3Eth.getContextObject() as any);
deployOptions = {
@@ -82,6 +65,11 @@ describe('defaults', () => {
sendOptions = { from: tempAcc.address, gas: '1000000' };
});
+ afterEach(async () => {
+ await closeOpenConnection(web3Eth);
+ await closeOpenConnection(eth2);
+ });
+
describe('defaults', () => {
it('defaultAccount', async () => {
const tempAcc2 = await createTempAccount();
@@ -156,7 +144,6 @@ describe('defaults', () => {
tempAcc2.address.toLowerCase(),
);
});
-
it('handleRevert', () => {
/*
//TO DO: after handleRevert implementation https://github.com/ChainSafe/web3.js/issues/5069 add following tests in future release
@@ -283,7 +270,6 @@ describe('defaults', () => {
});
expect(eth2.transactionSendTimeout).toBe(120);
});
-
it('transactionBlockTimeout', () => {
// default
expect(web3Eth.transactionBlockTimeout).toBe(50);
@@ -323,9 +309,7 @@ describe('defaults', () => {
// eslint-disable-next-line jest/no-standalone-expect
expect(eth2.transactionConfirmationBlocks).toBe(4);
});
-
- // TODO: remove itIf when finish #5144
- itIf(!isIpc)('transactionConfirmationBlocks implementation', async () => {
+ it('transactionConfirmationBlocks implementation', async () => {
const tempAcc2 = await createTempAccount();
const waitConfirmations = 1;
const eth = new Web3Eth(web3Eth.provider);
@@ -365,10 +349,16 @@ describe('defaults', () => {
});
await sentTx;
await receiptPromise;
- await sendFewTxes({ web3Eth: eth, from, to, value, times: waitConfirmations });
+ await sendFewTxesWithoutReceipt({
+ web3Eth: eth,
+ from,
+ to,
+ value,
+ times: waitConfirmations,
+ });
await confirmationPromise;
+ await closeOpenConnection(eth);
});
-
it('transactionPollingInterval and transactionPollingTimeout', () => {
// default
expect(web3Eth.transactionPollingInterval).toBe(1000);
@@ -478,7 +468,6 @@ describe('defaults', () => {
});
expect(eth2.blockHeaderTimeout).toBe(4);
});
-
it('enableExperimentalFeatures', () => {
// default
expect(web3Eth.enableExperimentalFeatures.useSubscriptionWhenCheckingBlockTimeout).toBe(
@@ -503,7 +492,6 @@ describe('defaults', () => {
true,
);
});
-
it('should fallback to polling if provider support `on` but `newBlockHeaders` does not arrive in `blockHeaderTimeout` seconds', async () => {
const tempAcc2 = await createTempAccount();
@@ -574,172 +562,8 @@ describe('defaults', () => {
// Ensure the promise the get the confirmations resolves with no error
const status = await confirmationPromise;
expect(status).toBe(BigInt(1));
+ await closeOpenConnection(tempEth);
});
-
- it('should fail if Ethereum Node did not respond because of a high nonce', async () => {
- const eth = new Web3Eth(clientUrl);
-
- // Make the test run faster by causing the timeout to happen after 0.2 second
- eth.transactionSendTimeout = 200;
- eth.transactionPollingTimeout = 200;
-
- const from = tempAcc.address;
- const to = (await createTempAccount()).address;
- const value = `0x1`;
-
- try {
- // Setting a high `nonce` when sending a transaction, to cause the RPC call to stuck at the Node
- await eth.sendTransaction({
- to,
- value,
- from,
- // Give a high nonce so the transaction stuck forever.
- // However, make this random to be able to run the test many times without receiving an error that indicate submitting the same transaction twice.
- nonce: Number.MAX_SAFE_INTEGER - Math.floor(Math.random() * 100000000),
- });
- expect(true).toBe(false); // the test should fail if there is no exception
- } catch (error) {
- // Some providers would not respond to the RPC request when sending a transaction (like Ganache v7.4.0)
- if (error instanceof TransactionSendTimeoutError) {
- // eslint-disable-next-line jest/no-conditional-expect
- expect(error.message).toContain(
- `connected Ethereum Node did not respond within ${
- eth.transactionSendTimeout / 1000
- } seconds`,
- );
- }
- // Some other providers would not respond when trying to get the transaction receipt (like Geth v1.10.22-unstable)
- else if (error instanceof TransactionPollingTimeoutError) {
- // eslint-disable-next-line jest/no-conditional-expect
- expect(error.message).toContain(
- `Transaction was not mined within ${
- eth.transactionPollingTimeout / 1000
- } seconds`,
- );
- } else {
- throw error;
- }
- }
- });
-
- it('should fail if transaction was not mined within `transactionBlockTimeout` blocks', async () => {
- const eth = new Web3Eth(clientUrl);
- const tempAcc1 = await createTempAccount();
- const tempAcc2 = await createTempAccount();
-
- // Make the test run faster by casing the polling to start after 2 blocks
- eth.transactionBlockTimeout = 2;
-
- // Increase other timeouts so only `transactionBlockTimeout` would be reached
- eth.transactionSendTimeout = MAX_32_SIGNED_INTEGER;
- eth.transactionPollingTimeout = MAX_32_SIGNED_INTEGER;
- eth.blockHeaderTimeout = MAX_32_SIGNED_INTEGER / 1000;
-
- const from = tempAcc1.address;
- const to = tempAcc2.address;
- const value = `0x0`;
-
- // Setting a high `nonce` when sending a transaction, to cause the RPC call to stuck at the Node
- const sentTx: Web3PromiEvent<
- TransactionReceipt,
- SendTransactionEvents
- > = eth.sendTransaction({
- to,
- value,
- from,
- // Give a high nonce so the transaction stuck forever.
- // However, make this random to be able to run the test many times without receiving an error that indicate submitting the same transaction twice.
- nonce: Number.MAX_SAFE_INTEGER - Math.floor(Math.random() * 100000000),
- });
-
- // Some providers (mostly used for development) will make blocks only when there are new transactions
- // So, send 2 transactions, one after another, because in this test `transactionBlockTimeout = 2`.
- // eslint-disable-next-line no-void
- void sendFewTxes({
- web3Eth: eth,
- from: tempAcc2.address,
- to: tempAcc1.address,
- times: 2,
- value: '0x1',
- });
-
- try {
- await sentTx;
- throw new Error(
- 'The test should fail if there is no exception when sending a transaction that could not be mined within transactionBlockTimeout',
- );
- } catch (error) {
- // eslint-disable-next-line jest/no-conditional-expect
- expect(error).toBeInstanceOf(TransactionBlockTimeoutError);
- // eslint-disable-next-line jest/no-conditional-expect
- expect((error as Error).message).toMatch(/was not mined within [0-9]+ blocks/);
- }
- });
-
- // The code of this test case is identical to the pervious one except for `eth.enableExperimentalFeatures = true`
- // And this test case will be removed once https://github.com/web3/web3.js/issues/5521 is implemented.
- itIf(isWs)(
- 'should fail if transaction was not mined within `transactionBlockTimeout` blocks - when subscription is used',
- async () => {
- const eth = new Web3Eth(clientUrl);
-
- // using subscription to get the new blocks and fire `TransactionBlockTimeoutError` is currently supported only
- // with `enableExperimentalFeatures.useSubscriptionWhenCheckingBlockTimeout` equal true.
- eth.enableExperimentalFeatures.useSubscriptionWhenCheckingBlockTimeout = true;
-
- const tempAcc1 = await createTempAccount();
- const tempAcc2 = await createTempAccount();
-
- // Make the test run faster by casing the polling to start after 2 blocks
- eth.transactionBlockTimeout = 2;
-
- // Increase other timeouts so only `transactionBlockTimeout` would be reached
- eth.transactionSendTimeout = MAX_32_SIGNED_INTEGER;
- eth.transactionPollingTimeout = MAX_32_SIGNED_INTEGER;
- eth.blockHeaderTimeout = MAX_32_SIGNED_INTEGER / 1000;
-
- const from = tempAcc1.address;
- const to = tempAcc2.address;
- const value = `0x0`;
-
- // Setting a high `nonce` when sending a transaction, to cause the RPC call to stuck at the Node
- const sentTx: Web3PromiEvent<
- TransactionReceipt,
- SendTransactionEvents
- > = eth.sendTransaction({
- to,
- value,
- from,
- // Give a high nonce so the transaction stuck forever.
- // However, make this random to be able to run the test many times without receiving an error that indicate submitting the same transaction twice.
- nonce: Number.MAX_SAFE_INTEGER - Math.floor(Math.random() * 100000000),
- });
-
- // Some providers (mostly used for development) will make blocks only when there are new transactions
- // So, send 2 transactions, one after another, because in this test `transactionBlockTimeout = 2`.
- // eslint-disable-next-line no-void
- void sendFewTxes({
- web3Eth: eth,
- from: tempAcc2.address,
- to: tempAcc1.address,
- times: 2,
- value: '0x1',
- });
-
- try {
- await sentTx;
- throw new Error(
- 'The test should fail if there is no exception when sending a transaction that could not be mined within transactionBlockTimeout',
- );
- } catch (error) {
- // eslint-disable-next-line jest/no-conditional-expect, jest/no-standalone-expect
- expect(error).toBeInstanceOf(TransactionBlockTimeoutError);
- // eslint-disable-next-line jest/no-conditional-expect, jest/no-standalone-expect
- expect((error as Error).message).toMatch(/was not mined within [0-9]+ blocks/);
- }
- },
- );
-
it('maxListenersWarningThreshold test default config', () => {
// default
expect(web3Eth.maxListenersWarningThreshold).toBe(100);
diff --git a/packages/web3-eth/test/integration/defaults.transactionBlockTimeout.test.ts b/packages/web3-eth/test/integration/defaults.transactionBlockTimeout.test.ts
new file mode 100644
index 00000000000..6649c4cb85b
--- /dev/null
+++ b/packages/web3-eth/test/integration/defaults.transactionBlockTimeout.test.ts
@@ -0,0 +1,173 @@
+/*
+This file is part of web3.js.
+
+web3.js is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+web3.js is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with web3.js. If not, see .
+*/
+// eslint-disable-next-line import/no-extraneous-dependencies
+import { DEFAULT_RETURN_FORMAT } from 'web3-utils';
+import { Web3PromiEvent } from 'web3-core';
+import { TransactionReceipt } from 'web3-types';
+import { TransactionBlockTimeoutError } from 'web3-errors';
+import { SendTransactionEvents, Web3Eth } from '../../src';
+
+import {
+ closeOpenConnection,
+ createTempAccount,
+ getSystemTestProvider,
+ isSocket,
+ itIf,
+ waitForOpenConnection,
+} from '../fixtures/system_test_utils';
+
+import { sendFewTxesWithoutReceipt } from './helper';
+
+const MAX_32_SIGNED_INTEGER = 2147483647;
+/* eslint-disable jest/no-standalone-expect */
+describe('defaults', () => {
+ let web3Eth: Web3Eth;
+ let eth2: Web3Eth;
+ let clientUrl: string;
+
+ beforeEach(() => {
+ clientUrl = getSystemTestProvider();
+ web3Eth = new Web3Eth(clientUrl);
+ });
+
+ afterEach(async () => {
+ await closeOpenConnection(web3Eth);
+ await closeOpenConnection(eth2);
+ });
+
+ describe('defaults', () => {
+ it('should fail if transaction was not mined within `transactionBlockTimeout` blocks', async () => {
+ const eth = new Web3Eth(clientUrl);
+ const tempAcc1 = await createTempAccount();
+ const tempAcc2 = await createTempAccount();
+
+ // Make the test run faster by casing the polling to start after 2 blocks
+ eth.transactionBlockTimeout = 2;
+
+ // Increase other timeouts so only `transactionBlockTimeout` would be reached
+ eth.transactionSendTimeout = MAX_32_SIGNED_INTEGER;
+ eth.transactionPollingTimeout = MAX_32_SIGNED_INTEGER;
+ eth.blockHeaderTimeout = MAX_32_SIGNED_INTEGER / 1000;
+
+ const from = tempAcc1.address;
+ const to = tempAcc2.address;
+ const value = `0x0`;
+
+ // Setting a high `nonce` when sending a transaction, to cause the RPC call to stuck at the Node
+ const sentTx: Web3PromiEvent<
+ TransactionReceipt,
+ SendTransactionEvents
+ > = eth.sendTransaction({
+ to,
+ value,
+ from,
+ // Give a high nonce so the transaction stuck forever.
+ // However, make this random to be able to run the test many times without receiving an error that indicate submitting the same transaction twice.
+ nonce: Number.MAX_SAFE_INTEGER - Math.floor(Math.random() * 100000000),
+ });
+
+ // Some providers (mostly used for development) will make blocks only when there are new transactions
+ // So, send 2 transactions, one after another, because in this test `transactionBlockTimeout = 2`.
+ // eslint-disable-next-line no-void
+ void sendFewTxesWithoutReceipt({
+ web3Eth: eth,
+ from: tempAcc2.address,
+ to: tempAcc1.address,
+ times: 2,
+ value: '0x1',
+ });
+
+ try {
+ await sentTx;
+ throw new Error(
+ 'The test should fail if there is no exception when sending a transaction that could not be mined within transactionBlockTimeout',
+ );
+ } catch (error) {
+ // eslint-disable-next-line jest/no-conditional-expect
+ expect(error).toBeInstanceOf(TransactionBlockTimeoutError);
+ // eslint-disable-next-line jest/no-conditional-expect
+ expect((error as Error).message).toMatch(/was not mined within [0-9]+ blocks/);
+ }
+ await closeOpenConnection(eth);
+ });
+
+ // The code of this test case is identical to the pervious one except for `eth.enableExperimentalFeatures = true`
+ // And this test case will be removed once https://github.com/web3/web3.js/issues/5521 is implemented.
+ itIf(isSocket)(
+ 'should fail if transaction was not mined within `transactionBlockTimeout` blocks - when subscription is used',
+ async () => {
+ const eth = new Web3Eth(clientUrl);
+ await waitForOpenConnection(eth);
+ // using subscription to get the new blocks and fire `TransactionBlockTimeoutError` is currently supported only
+ // with `enableExperimentalFeatures.useSubscriptionWhenCheckingBlockTimeout` equal true.
+ eth.enableExperimentalFeatures.useSubscriptionWhenCheckingBlockTimeout = true;
+
+ const tempAcc1 = await createTempAccount();
+ const tempAcc2 = await createTempAccount();
+
+ // Make the test run faster by casing the polling to start after 2 blocks
+ eth.transactionBlockTimeout = 2;
+
+ // Increase other timeouts so only `transactionBlockTimeout` would be reached
+ eth.transactionSendTimeout = MAX_32_SIGNED_INTEGER;
+ eth.transactionPollingTimeout = MAX_32_SIGNED_INTEGER;
+ eth.blockHeaderTimeout = MAX_32_SIGNED_INTEGER / 1000;
+
+ const from = tempAcc1.address;
+ const to = tempAcc2.address;
+ const value = `0x0`;
+
+ // Setting a high `nonce` when sending a transaction, to cause the RPC call to stuck at the Node
+ const sentTx: Web3PromiEvent<
+ TransactionReceipt,
+ SendTransactionEvents
+ > = eth.sendTransaction({
+ to,
+ value,
+ from,
+ // Give a high nonce so the transaction stuck forever.
+ // However, make this random to be able to run the test many times without receiving an error that indicate submitting the same transaction twice.
+ nonce: Number.MAX_SAFE_INTEGER - Math.floor(Math.random() * 100000000),
+ });
+
+ // Some providers (mostly used for development) will make blocks only when there are new transactions
+ // So, send 2 transactions, one after another, because in this test `transactionBlockTimeout = 2`.
+ // eslint-disable-next-line no-void
+ void sendFewTxesWithoutReceipt({
+ web3Eth: eth,
+ from: tempAcc2.address,
+ to: tempAcc1.address,
+ times: 2,
+ value: '0x1',
+ });
+
+ try {
+ await sentTx;
+ throw new Error(
+ 'The test should fail if there is no exception when sending a transaction that could not be mined within transactionBlockTimeout',
+ );
+ } catch (error) {
+ // eslint-disable-next-line jest/no-conditional-expect, jest/no-standalone-expect
+ expect(error).toBeInstanceOf(TransactionBlockTimeoutError);
+ // eslint-disable-next-line jest/no-conditional-expect, jest/no-standalone-expect
+ expect((error as Error).message).toMatch(/was not mined within [0-9]+ blocks/);
+ }
+ await closeOpenConnection(eth);
+ },
+ );
+ });
+});
diff --git a/packages/web3-eth/test/integration/eth.test.ts b/packages/web3-eth/test/integration/eth.test.ts
index 7e9fc400672..f047ed5829c 100644
--- a/packages/web3-eth/test/integration/eth.test.ts
+++ b/packages/web3-eth/test/integration/eth.test.ts
@@ -42,7 +42,7 @@ describe('eth', () => {
let sendOptions: Record;
let tempAcc: { address: string; privateKey: string };
- beforeAll(async () => {
+ beforeAll(() => {
clientUrl = getSystemTestProvider();
web3Eth = new Web3Eth(clientUrl);
contract = new Contract(BasicAbi, {
diff --git a/packages/web3-eth/test/integration/helper.ts b/packages/web3-eth/test/integration/helper.ts
index b0c9877504f..72754f7ca6c 100644
--- a/packages/web3-eth/test/integration/helper.ts
+++ b/packages/web3-eth/test/integration/helper.ts
@@ -27,6 +27,7 @@ type SendFewTxParams = {
from: string;
value: string;
times?: number;
+ waitReceipt?: boolean;
};
export type Resolve = (value?: TransactionReceipt) => void;
export const sendFewTxes = async ({
@@ -38,10 +39,12 @@ export const sendFewTxes = async ({
}: SendFewTxParams): Promise => {
const res: TransactionReceipt[] = [];
for (let i = 0; i < times; i += 1) {
+ // @TODO: Investigate why we need timeout here #5730
// eslint-disable-next-line no-await-in-loop
await new Promise(resolve => {
setTimeout(resolve, 500);
});
+
const tx: Web3PromiEvent<
TransactionReceipt,
SendTransactionEvents
@@ -70,6 +73,38 @@ export const sendFewTxes = async ({
return res;
};
+export const sendFewTxesWithoutReceipt = async ({
+ web3Eth,
+ to,
+ value,
+ from,
+ times = 3,
+}: SendFewTxParams): Promise<
+ Web3PromiEvent>[]
+> => {
+ const res: Web3PromiEvent<
+ TransactionReceipt,
+ SendTransactionEvents
+ >[] = [];
+ for (let i = 0; i < times; i += 1) {
+ // @TODO: Investigate why we need timeout here #5730
+ // eslint-disable-next-line no-await-in-loop
+ await new Promise(resolve => {
+ setTimeout(resolve, 500);
+ });
+
+ res.push(
+ web3Eth.sendTransaction({
+ to,
+ value,
+ from,
+ }),
+ );
+ }
+
+ return res;
+};
+
const regexHex20 = /0[xX][0-9a-fA-F]{40}/i;
const regexHex32 = /0[xX][0-9a-fA-F]{64}/i;
diff --git a/packages/web3-eth/test/integration/nonce.test.ts b/packages/web3-eth/test/integration/nonce.test.ts
new file mode 100644
index 00000000000..6d94717bda3
--- /dev/null
+++ b/packages/web3-eth/test/integration/nonce.test.ts
@@ -0,0 +1,94 @@
+/*
+This file is part of web3.js.
+
+web3.js is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+web3.js is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with web3.js. If not, see .
+*/
+// eslint-disable-next-line import/no-extraneous-dependencies
+import { TransactionPollingTimeoutError, TransactionSendTimeoutError } from 'web3-errors';
+import { Web3Eth } from '../../src';
+
+import {
+ closeOpenConnection,
+ createTempAccount,
+ getSystemTestProvider,
+} from '../fixtures/system_test_utils';
+
+describe('defaults', () => {
+ let web3Eth: Web3Eth;
+ let eth2: Web3Eth;
+ let clientUrl: string;
+ let tempAcc: { address: string; privateKey: string };
+
+ beforeEach(() => {
+ clientUrl = getSystemTestProvider();
+ web3Eth = new Web3Eth(clientUrl);
+ });
+
+ afterEach(async () => {
+ await closeOpenConnection(web3Eth);
+ await closeOpenConnection(eth2);
+ });
+ beforeEach(async () => {
+ tempAcc = await createTempAccount();
+ });
+
+ describe('defaults', () => {
+ it('should fail if Ethereum Node did not respond because of a high nonce', async () => {
+ const eth = new Web3Eth(clientUrl);
+
+ // Make the test run faster by causing the timeout to happen after 0.2 second
+ eth.transactionSendTimeout = 200;
+ eth.transactionPollingTimeout = 200;
+
+ const from = tempAcc.address;
+ const to = (await createTempAccount()).address;
+ const value = `0x1`;
+
+ try {
+ // Setting a high `nonce` when sending a transaction, to cause the RPC call to stuck at the Node
+ await eth.sendTransaction({
+ to,
+ value,
+ from,
+ // Give a high nonce so the transaction stuck forever.
+ // However, make this random to be able to run the test many times without receiving an error that indicate submitting the same transaction twice.
+ nonce: Number.MAX_SAFE_INTEGER - Math.floor(Math.random() * 100000000),
+ });
+ expect(true).toBe(false); // the test should fail if there is no exception
+ } catch (error) {
+ // Some providers would not respond to the RPC request when sending a transaction (like Ganache v7.4.0)
+ if (error instanceof TransactionSendTimeoutError) {
+ // eslint-disable-next-line jest/no-conditional-expect
+ expect(error.message).toContain(
+ `connected Ethereum Node did not respond within ${
+ eth.transactionSendTimeout / 1000
+ } seconds`,
+ );
+ }
+ // Some other providers would not respond when trying to get the transaction receipt (like Geth v1.10.22-unstable)
+ else if (error instanceof TransactionPollingTimeoutError) {
+ // eslint-disable-next-line jest/no-conditional-expect
+ expect(error.message).toContain(
+ `Transaction was not mined within ${
+ eth.transactionPollingTimeout / 1000
+ } seconds`,
+ );
+ } else {
+ throw error;
+ }
+ }
+ await closeOpenConnection(eth);
+ });
+ });
+});
diff --git a/packages/web3-eth/test/integration/rpc.test.ts b/packages/web3-eth/test/integration/rpc.test.ts
index 572b07dc14a..4f3cf431354 100644
--- a/packages/web3-eth/test/integration/rpc.test.ts
+++ b/packages/web3-eth/test/integration/rpc.test.ts
@@ -23,7 +23,6 @@ import { hexToNumber, hexToString, numberToHex, FMT_BYTES, FMT_NUMBER } from 'we
import { AbiEventFragment } from 'web3-eth-abi';
import { getStorageSlotNumForLongString } from 'web3-utils/src';
// eslint-disable-next-line import/no-extraneous-dependencies
-import IpcProvider from 'web3-providers-ipc';
import { Web3Eth } from '../../src';
import {
@@ -32,7 +31,6 @@ import {
getSystemTestProvider,
createNewAccount,
itIf,
- isIpc,
createTempAccount,
} from '../fixtures/system_test_utils';
import { BasicAbi, BasicBytecode } from '../shared_fixtures/build/Basic';
@@ -61,7 +59,6 @@ describe('rpc', () => {
transactionPollingTimeout: 2000,
},
});
-
contract = new Contract(BasicAbi, undefined, {
provider: clientUrl,
});
@@ -75,10 +72,6 @@ describe('rpc', () => {
sendOptions = { from: tempAcc.address, gas: '1000000' };
contractDeployed = await contract.deploy(deployOptions).send(sendOptions);
- if (isIpc) {
- await (contract.provider as IpcProvider).waitForConnection();
- await (web3Eth.provider as IpcProvider).waitForConnection();
- }
});
afterAll(async () => {
diff --git a/packages/web3-eth/test/integration/setup.js b/packages/web3-eth/test/integration/setup.js
index 5be1bccf7cc..976511e0e5f 100644
--- a/packages/web3-eth/test/integration/setup.js
+++ b/packages/web3-eth/test/integration/setup.js
@@ -19,6 +19,6 @@ along with web3.js. If not, see .
// eslint-disable-next-line @typescript-eslint/no-require-imports
require('../config/setup');
-const jestTimeout = 15000;
+const jestTimeout = process.env.WEB3_SYSTEM_TEST_PROVIDER.includes('ipc') ? 30000 : 15000;
jest.setTimeout(jestTimeout);
diff --git a/packages/web3-eth/test/integration/subscribe.test.ts b/packages/web3-eth/test/integration/subscribe.test.ts
index 533a4670657..a41ba49b147 100644
--- a/packages/web3-eth/test/integration/subscribe.test.ts
+++ b/packages/web3-eth/test/integration/subscribe.test.ts
@@ -17,30 +17,35 @@ along with web3.js. If not, see .
import WebSocketProvider from 'web3-providers-ws';
import { Web3BaseProvider } from 'web3-types';
/* eslint-disable import/no-named-as-default */
-import Web3Eth from '../../src/index';
-import {
+// eslint-disable-next-line import/no-extraneous-dependencies
+import IpcProvider from 'web3-providers-ipc';
+import Web3Eth, {
NewHeadsSubscription,
SyncingSubscription,
NewPendingTransactionsSubscription,
LogsSubscription,
-} from '../../src';
+} from '../../src/index';
import {
getSystemTestProvider,
describeIf,
+ isSocket,
isWs,
createTempAccount,
+ closeOpenConnection,
} from '../fixtures/system_test_utils';
-describeIf(isWs)('subscribe', () => {
+describeIf(isSocket)('subscribe', () => {
let web3Eth: Web3Eth;
- let provider: WebSocketProvider;
+ let provider: WebSocketProvider | IpcProvider;
- beforeAll(async () => {
- provider = new WebSocketProvider(getSystemTestProvider());
+ beforeAll(() => {
+ provider = isWs
+ ? new WebSocketProvider(getSystemTestProvider())
+ : new IpcProvider(getSystemTestProvider());
});
- afterAll(() => {
- provider.disconnect();
+ afterAll(async () => {
+ await closeOpenConnection(web3Eth);
});
afterEach(async () => {
diff --git a/packages/web3-eth/test/integration/subscription_heads.test.ts b/packages/web3-eth/test/integration/subscription_heads.test.ts
index aa499962fc7..f3d03269ca8 100644
--- a/packages/web3-eth/test/integration/subscription_heads.test.ts
+++ b/packages/web3-eth/test/integration/subscription_heads.test.ts
@@ -14,48 +14,39 @@ GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with web3.js. If not, see .
*/
-import WebSocketProvider from 'web3-providers-ws';
-import { BlockHeaderOutput, Web3BaseProvider } from 'web3-types';
-import { Web3Eth } from '../../src';
-import { sendFewTxes, Resolve } from './helper';
-import { NewHeadsSubscription } from '../../src/web3_subscriptions';
+import { BlockHeaderOutput } from 'web3-types';
+import { Web3Eth, NewHeadsSubscription } from '../../src';
+import { Resolve, sendFewTxesWithoutReceipt } from './helper';
import {
+ closeOpenConnection,
createTempAccount,
describeIf,
getSystemTestProvider,
- isWs,
+ isSocket,
+ waitForOpenConnection,
} from '../fixtures/system_test_utils';
-const checkTxCount = 5;
-type SubName = 'newHeads' | 'newBlockHeaders';
-const subNames: Array = ['newHeads', 'newBlockHeaders'];
+const checkTxCount = 3;
-describeIf(isWs)('subscription', () => {
- let web3Eth: Web3Eth;
+describeIf(isSocket)('subscription', () => {
let clientUrl: string;
- let providerWs: WebSocketProvider;
- let tempAcc: { address: string; privateKey: string };
let tempAcc2: { address: string; privateKey: string };
beforeEach(async () => {
- tempAcc = await createTempAccount();
tempAcc2 = await createTempAccount();
});
beforeAll(() => {
clientUrl = getSystemTestProvider();
- providerWs = new WebSocketProvider(clientUrl);
- web3Eth = new Web3Eth(providerWs as Web3BaseProvider);
- });
- afterAll(() => {
- providerWs.disconnect();
});
describe('heads', () => {
- it.each(subNames)(`wait for ${checkTxCount} newHeads`, async (subName: SubName) => {
- const sub: NewHeadsSubscription = await web3Eth.subscribe(subName);
- const from = tempAcc.address;
+ it(`wait for ${checkTxCount} newHeads`, async () => {
+ const web3Eth = new Web3Eth(clientUrl);
+ const sub: NewHeadsSubscription = await web3Eth.subscribe('newHeads');
+ const tempAccForEachTest = await createTempAccount();
+ const from = tempAccForEachTest.address;
const to = tempAcc2.address;
const value = `0x1`;
-
+ await waitForOpenConnection(web3Eth);
let times = 0;
const pr = new Promise((resolve: Resolve, reject) => {
sub.on('data', (data: BlockHeaderOutput) => {
@@ -64,9 +55,9 @@ describeIf(isWs)('subscription', () => {
}
expect(times).toBeGreaterThanOrEqual(times);
if (times >= checkTxCount) {
- sub.off('data', () => {
- // no need to do anything
- });
+ // sub.off('data', () => {
+ // no need to do anything
+ // });
resolve();
}
});
@@ -74,16 +65,26 @@ describeIf(isWs)('subscription', () => {
reject(error);
});
});
+ await sendFewTxesWithoutReceipt({
+ web3Eth,
+ from,
+ to,
+ value,
+ times: checkTxCount,
+ });
- await sendFewTxes({ web3Eth, from, to, value, times: checkTxCount });
await pr;
await web3Eth.subscriptionManager?.removeSubscription(sub);
+ await closeOpenConnection(web3Eth);
});
- it.each(subNames)(`clear`, async (subName: SubName) => {
- const sub: NewHeadsSubscription = await web3Eth.subscribe(subName);
+ it(`clear`, async () => {
+ const web3Eth = new Web3Eth(clientUrl);
+ await waitForOpenConnection(web3Eth);
+ const sub: NewHeadsSubscription = await web3Eth.subscribe('newHeads');
expect(sub.id).toBeDefined();
await web3Eth.subscriptionManager?.removeSubscription(sub);
expect(sub.id).toBeUndefined();
+ await closeOpenConnection(web3Eth);
});
});
});
diff --git a/packages/web3-eth/test/integration/subscription_logs.test.ts b/packages/web3-eth/test/integration/subscription_logs.test.ts
index 4f2fd5d8367..78a75f4d838 100644
--- a/packages/web3-eth/test/integration/subscription_logs.test.ts
+++ b/packages/web3-eth/test/integration/subscription_logs.test.ts
@@ -20,6 +20,8 @@ import { Contract, decodeEventABI } from 'web3-eth-contract';
// eslint-disable-next-line import/no-extraneous-dependencies
import { AbiEventFragment } from 'web3-eth-abi';
import { Web3BaseProvider } from 'web3-types';
+// eslint-disable-next-line import/no-extraneous-dependencies
+import IpcProvider from 'web3-providers-ipc';
import { Web3Eth } from '../../src';
import { BasicAbi, BasicBytecode } from '../shared_fixtures/build/Basic';
import { eventAbi, Resolve } from './helper';
@@ -28,7 +30,9 @@ import {
describeIf,
getSystemTestProvider,
isWs,
+ isSocket,
createTempAccount,
+ closeOpenConnection,
} from '../fixtures/system_test_utils';
const checkEventCount = 2;
@@ -49,10 +53,10 @@ const makeFewTxToContract = async ({
prs.push(await contract.methods?.firesStringEvent(testDataString).send(sendOptions));
}
};
-describeIf(isWs)('subscription', () => {
+describeIf(isSocket)('subscription', () => {
let clientUrl: string;
let web3Eth: Web3Eth;
- let providerWs: WebSocketProvider;
+ let provider: WebSocketProvider | IpcProvider;
let contract: Contract;
let contractDeployed: Contract;
let deployOptions: Record;
@@ -65,19 +69,19 @@ describeIf(isWs)('subscription', () => {
});
beforeAll(() => {
clientUrl = getSystemTestProvider();
- providerWs = new WebSocketProvider(clientUrl);
+ provider = isWs ? new WebSocketProvider(clientUrl) : new IpcProvider(clientUrl);
contract = new Contract(BasicAbi, undefined, {
provider: clientUrl,
});
});
- afterAll(() => {
- providerWs.disconnect();
- (contract.provider as WebSocketProvider).disconnect();
+ afterAll(async () => {
+ provider.disconnect();
+ await closeOpenConnection(web3Eth);
});
describe('logs', () => {
it(`wait for ${checkEventCount} logs`, async () => {
- web3Eth = new Web3Eth(providerWs as Web3BaseProvider);
+ web3Eth = new Web3Eth(provider as Web3BaseProvider);
const from = tempAcc.address;
deployOptions = {
data: BasicBytecode,
diff --git a/packages/web3-eth/test/integration/subscription_logs_block.test.ts b/packages/web3-eth/test/integration/subscription_logs_block.test.ts
index 585ec625030..82c8a40e079 100644
--- a/packages/web3-eth/test/integration/subscription_logs_block.test.ts
+++ b/packages/web3-eth/test/integration/subscription_logs_block.test.ts
@@ -21,6 +21,8 @@ import { Contract, decodeEventABI } from 'web3-eth-contract';
import { AbiEventFragment } from 'web3-eth-abi';
import { Web3BaseProvider } from 'web3-types';
import { numberToHex } from 'web3-utils';
+// eslint-disable-next-line import/no-extraneous-dependencies
+import IpcProvider from 'web3-providers-ipc';
import { Web3Eth } from '../../src';
import { BasicAbi, BasicBytecode } from '../shared_fixtures/build/Basic';
import { eventAbi, Resolve } from './helper';
@@ -30,6 +32,8 @@ import {
describeIf,
getSystemTestProvider,
isWs,
+ isSocket,
+ closeOpenConnection,
} from '../fixtures/system_test_utils';
const checkEventCount = 2;
@@ -50,22 +54,22 @@ const makeFewTxToContract = async ({
prs.push(await contract.methods?.firesStringEvent(testDataString).send(sendOptions));
}
};
-describeIf(isWs)('subscription', () => {
+describeIf(isSocket)('subscription', () => {
let clientUrl: string;
- let providerWs: WebSocketProvider;
+ let providerWs: WebSocketProvider | IpcProvider;
let contract: Contract;
const testDataString = 'someTestString';
beforeAll(() => {
clientUrl = getSystemTestProvider();
- providerWs = new WebSocketProvider(clientUrl);
+ providerWs = isWs ? new WebSocketProvider(clientUrl) : new IpcProvider(clientUrl);
contract = new Contract(BasicAbi, undefined, {
provider: clientUrl,
});
});
- afterAll(() => {
+ afterAll(async () => {
providerWs.disconnect();
- (contract.provider as WebSocketProvider).disconnect();
+ await closeOpenConnection(contract);
});
describe('logs', () => {
diff --git a/packages/web3-eth/test/integration/subscription_logs_clear.test.ts b/packages/web3-eth/test/integration/subscription_logs_clear.test.ts
index 991f28def61..262889a339f 100644
--- a/packages/web3-eth/test/integration/subscription_logs_clear.test.ts
+++ b/packages/web3-eth/test/integration/subscription_logs_clear.test.ts
@@ -14,29 +14,28 @@ GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with web3.js. If not, see .
*/
-import WebSocketProvider from 'web3-providers-ws';
-// eslint-disable-next-line import/no-extraneous-dependencies
-// eslint-disable-next-line import/no-extraneous-dependencies
-import { Web3BaseProvider } from 'web3-types';
import { Web3Eth } from '../../src';
+import {
+ closeOpenConnection,
+ describeIf,
+ getSystemTestProvider,
+ isSocket,
+} from '../fixtures/system_test_utils';
import { LogsSubscription } from '../../src/web3_subscriptions';
-import { describeIf, getSystemTestProvider, isWs } from '../fixtures/system_test_utils';
+// eslint-disable-next-line import/no-extraneous-dependencies
+// eslint-disable-next-line import/no-extraneous-dependencies
-describeIf(isWs)('subscription', () => {
- let clientUrl: string;
+describeIf(isSocket)('subscription', () => {
let web3Eth: Web3Eth;
- let providerWs: WebSocketProvider;
- beforeAll(async () => {
- clientUrl = getSystemTestProvider();
- providerWs = new WebSocketProvider(clientUrl);
+ beforeAll(() => {
+ web3Eth = new Web3Eth(getSystemTestProvider());
});
- afterAll(() => {
- providerWs.disconnect();
+ afterAll(async () => {
+ await closeOpenConnection(web3Eth);
});
describe('logs', () => {
it(`clear`, async () => {
- web3Eth = new Web3Eth(providerWs as Web3BaseProvider);
const sub: LogsSubscription = await web3Eth.subscribe('logs');
expect(sub.id).toBeDefined();
await web3Eth.clearSubscriptions();
diff --git a/packages/web3-eth/test/integration/subscription_new_pending_tx.test.ts b/packages/web3-eth/test/integration/subscription_new_pending_tx.test.ts
index 55bc55d4c45..fcf7d9ea138 100644
--- a/packages/web3-eth/test/integration/subscription_new_pending_tx.test.ts
+++ b/packages/web3-eth/test/integration/subscription_new_pending_tx.test.ts
@@ -14,43 +14,33 @@ GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with web3.js. If not, see .
*/
-import WebSocketProvider from 'web3-providers-ws';
-import { Web3BaseProvider } from 'web3-types';
import { Web3Eth, NewPendingTransactionsSubscription } from '../../src';
import { sendFewTxes } from './helper';
import {
+ closeOpenConnection,
createTempAccount,
describeIf,
getSystemTestProvider,
- isWs,
+ isIpc,
+ isSocket,
+ waitForOpenConnection,
} from '../fixtures/system_test_utils';
const checkTxCount = 2;
-type SubName = 'pendingTransactions' | 'newPendingTransactions';
-const subNames: SubName[] = ['pendingTransactions', 'newPendingTransactions'];
-
-describeIf(isWs)('subscription', () => {
- let web3Eth: Web3Eth;
- let providerWs: WebSocketProvider;
- let clientUrl: string;
- beforeAll(() => {
- clientUrl = getSystemTestProvider();
- providerWs = new WebSocketProvider(clientUrl);
- });
- afterAll(() => {
- web3Eth.subscriptionManager?.clear();
- providerWs.disconnect();
- });
-
+describeIf(isSocket && !isIpc)('subscription', () => {
describe('new pending transaction', () => {
- it.each(subNames)(`wait ${checkTxCount} transaction - %s`, async subName => {
+ it(`wait ${checkTxCount} transaction - %s`, async () => {
+ const web3Eth = new Web3Eth(getSystemTestProvider());
const [tempAcc, tempAcc2] = await Promise.all([
createTempAccount(),
createTempAccount(),
]);
- web3Eth = new Web3Eth(providerWs as Web3BaseProvider);
- const sub: NewPendingTransactionsSubscription = await web3Eth.subscribe(subName);
+ await waitForOpenConnection(web3Eth);
+
+ const sub: NewPendingTransactionsSubscription = await web3Eth.subscribe(
+ 'pendingTransactions',
+ );
const from = tempAcc.address;
const to = tempAcc2.address;
const value = `0x1`;
@@ -89,7 +79,7 @@ describeIf(isWs)('subscription', () => {
from,
to,
value,
- times: checkTxCount,
+ times: isIpc ? checkTxCount * 3 : checkTxCount,
})
).map(r => String(r?.transactionHash));
if (receipts.length > 0 && waitList.length > 0) {
@@ -113,13 +103,18 @@ describeIf(isWs)('subscription', () => {
for (const hash of txHashes) {
expect(receipts).toContain(hash);
}
+ await closeOpenConnection(web3Eth);
});
- it.each(subNames)(`clear`, async (subName: SubName) => {
- web3Eth = new Web3Eth(providerWs as Web3BaseProvider);
- const sub: NewPendingTransactionsSubscription = await web3Eth.subscribe(subName);
+ it(`clear`, async () => {
+ const web3Eth = new Web3Eth(getSystemTestProvider());
+ await waitForOpenConnection(web3Eth);
+ const sub: NewPendingTransactionsSubscription = await web3Eth.subscribe(
+ 'pendingTransactions',
+ );
expect(sub.id).toBeDefined();
await web3Eth.subscriptionManager?.removeSubscription(sub);
expect(sub.id).toBeUndefined();
+ await closeOpenConnection(web3Eth);
});
});
});
diff --git a/packages/web3-eth/test/integration/unsubscribe.test.ts b/packages/web3-eth/test/integration/unsubscribe.test.ts
index 552d0f0be47..6f60bf16c56 100644
--- a/packages/web3-eth/test/integration/unsubscribe.test.ts
+++ b/packages/web3-eth/test/integration/unsubscribe.test.ts
@@ -16,19 +16,28 @@ along with web3.js. If not, see .
*/
import WebSocketProvider from 'web3-providers-ws/dist';
import { Web3BaseProvider } from 'web3-types';
-/* eslint-disable import/no-named-as-default */
+/* eslint-disable import/no-extraneous-dependencies */
+import IpcProvider from 'web3-providers-ipc';
import Web3Eth from '../../src/index';
import { NewHeadsSubscription, SyncingSubscription } from '../../src/web3_subscriptions';
-import { getSystemTestProvider, describeIf, isWs } from '../fixtures/system_test_utils';
+import {
+ getSystemTestProvider,
+ describeIf,
+ isWs,
+ isSocket,
+ closeOpenConnection,
+} from '../fixtures/system_test_utils';
-describeIf(isWs)('unsubscribe', () => {
+describeIf(isSocket)('unsubscribe', () => {
let web3Eth: Web3Eth;
- let provider: WebSocketProvider;
+ let provider: WebSocketProvider | IpcProvider;
beforeAll(() => {
- provider = new WebSocketProvider(getSystemTestProvider());
+ provider = isWs
+ ? new WebSocketProvider(getSystemTestProvider())
+ : new IpcProvider(getSystemTestProvider());
});
- afterAll(() => {
- provider.disconnect();
+ afterAll(async () => {
+ await closeOpenConnection(web3Eth);
});
describe('unsubscribe from', () => {
diff --git a/packages/web3-eth/test/integration/watch_transaction.test.ts b/packages/web3-eth/test/integration/watch_transaction.test.ts
index 3707a236bfb..e50a15f2dce 100644
--- a/packages/web3-eth/test/integration/watch_transaction.test.ts
+++ b/packages/web3-eth/test/integration/watch_transaction.test.ts
@@ -14,44 +14,33 @@ GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with web3.js. If not, see .
*/
-import WebSocketProvider from 'web3-providers-ws';
import { DEFAULT_RETURN_FORMAT } from 'web3-utils';
-import { Web3BaseProvider, TransactionReceipt } from 'web3-types';
+import { TransactionReceipt } from 'web3-types';
import { Web3PromiEvent } from 'web3-core';
import { Web3Eth, SendTransactionEvents } from '../../src';
-import { sendFewTxes } from './helper';
+import { sendFewTxesWithoutReceipt } from './helper';
import {
getSystemTestProvider,
describeIf,
- isWs,
createTempAccount,
+ closeOpenConnection,
+ isSocket,
+ waitForOpenConnection,
// eslint-disable-next-line import/no-relative-packages
} from '../fixtures/system_test_utils';
-const waitConfirmations = 5;
+const waitConfirmations = 2;
type Resolve = (value?: unknown) => void;
-describeIf(isWs)('watch subscription transaction', () => {
- let web3Eth: Web3Eth;
- let providerWs: WebSocketProvider;
- let clientUrl: string;
-
- beforeAll(() => {
- clientUrl = getSystemTestProvider();
-
- providerWs = new WebSocketProvider(clientUrl);
- });
- afterAll(() => {
- providerWs.disconnect();
- });
-
+describeIf(isSocket)('watch subscription transaction', () => {
describe('wait for confirmation subscription', () => {
it('subscription to heads', async () => {
+ const web3Eth = new Web3Eth(getSystemTestProvider());
+ await waitForOpenConnection(web3Eth);
const tempAccount = await createTempAccount();
const tempAccount2 = await createTempAccount();
- web3Eth = new Web3Eth(providerWs as Web3BaseProvider);
web3Eth.setConfig({ transactionConfirmationBlocks: waitConfirmations });
@@ -88,8 +77,15 @@ describeIf(isWs)('watch subscription transaction', () => {
});
});
await receiptPromise;
- await sendFewTxes({ web3Eth, from, to, value, times: waitConfirmations });
+ await sendFewTxesWithoutReceipt({
+ web3Eth,
+ from,
+ to,
+ value,
+ times: waitConfirmations,
+ });
await confirmationPromise;
+ await closeOpenConnection(web3Eth);
});
});
});
diff --git a/packages/web3-eth/test/integration/watch_transaction_polling.test.ts b/packages/web3-eth/test/integration/watch_transaction_polling.test.ts
index f61c41fe4e7..ef67ce5bc70 100644
--- a/packages/web3-eth/test/integration/watch_transaction_polling.test.ts
+++ b/packages/web3-eth/test/integration/watch_transaction_polling.test.ts
@@ -25,15 +25,13 @@ import {
describeIf,
getSystemTestProvider,
isHttp,
- isIpc,
} from '../fixtures/system_test_utils';
-const waitConfirmations = 5;
+const waitConfirmations = 3;
type Resolve = (value?: unknown) => void;
-describeIf(isHttp || isIpc)('watch polling transaction', () => {
- let web3Eth: Web3Eth;
+describeIf(isHttp)('watch polling transaction', () => {
let clientUrl: string;
let tempAcc: { address: string; privateKey: string };
let tempAcc2: { address: string; privateKey: string };
@@ -42,16 +40,13 @@ describeIf(isHttp || isIpc)('watch polling transaction', () => {
tempAcc = await createTempAccount();
tempAcc2 = await createTempAccount();
});
- beforeAll(async () => {
+ beforeAll(() => {
clientUrl = getSystemTestProvider();
});
- afterAll(async () => {
- await closeOpenConnection(web3Eth);
- });
describe('wait for confirmation polling', () => {
it('polling', async () => {
- web3Eth = new Web3Eth(clientUrl);
+ const web3Eth = new Web3Eth(clientUrl);
web3Eth.setConfig({ transactionConfirmationBlocks: waitConfirmations });
const from = tempAcc.address;
@@ -97,6 +92,7 @@ describeIf(isHttp || isIpc)('watch polling transaction', () => {
await sentTx;
await confirmationPromise;
sentTx.removeAllListeners();
+ await closeOpenConnection(web3Eth);
});
});
});
diff --git a/packages/web3-eth/test/integration/web3_eth/getFeeHistory.test.ts b/packages/web3-eth/test/integration/web3_eth/getFeeHistory.test.ts
index 56571fb7659..1d664d8555f 100644
--- a/packages/web3-eth/test/integration/web3_eth/getFeeHistory.test.ts
+++ b/packages/web3-eth/test/integration/web3_eth/getFeeHistory.test.ts
@@ -29,7 +29,7 @@ describeIf(getSystemTestBackend().includes('geth'))('Web3Eth.getFeeHistory', ()
let web3Eth: Web3Eth;
let systemProvider: string;
- beforeAll(async () => {
+ beforeAll(() => {
systemProvider = getSystemTestProvider();
web3Eth = new Web3Eth(systemProvider);
});
diff --git a/packages/web3-providers-ipc/CHANGELOG.md b/packages/web3-providers-ipc/CHANGELOG.md
index bd093a66550..0cf612b53d3 100644
--- a/packages/web3-providers-ipc/CHANGELOG.md
+++ b/packages/web3-providers-ipc/CHANGELOG.md
@@ -54,3 +54,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Updated dependencies (#5725)
## [Unreleased]
+
+### Changed
+
+- Refactor to use common SocketProvider class (#5683)
diff --git a/packages/web3-providers-ipc/src/index.ts b/packages/web3-providers-ipc/src/index.ts
index c278d670232..1d80046dc58 100644
--- a/packages/web3-providers-ipc/src/index.ts
+++ b/packages/web3-providers-ipc/src/index.ts
@@ -15,265 +15,156 @@ You should have received a copy of the GNU Lesser General Public License
along with web3.js. If not, see .
*/
-import { EventEmitter } from 'events';
-import { existsSync } from 'fs';
import { Socket } from 'net';
+import { InvalidConnectionError, ConnectionNotOpenError, InvalidClientError } from 'web3-errors';
+import { SocketProvider } from 'web3-utils';
import {
EthExecutionAPI,
JsonRpcId,
- JsonRpcNotification,
- JsonRpcResponseWithResult,
- JsonRpcResult,
+ SocketRequestItem,
Web3APIMethod,
Web3APIPayload,
- Web3APIReturnType,
Web3APISpec,
- Web3BaseProvider,
- Web3ProviderEventCallback,
Web3ProviderStatus,
} from 'web3-types';
-import {
- ConnectionNotOpenError,
- InvalidClientError,
- InvalidConnectionError,
- ResponseError,
-} from 'web3-errors';
-import { isNullish, Web3DeferredPromise, jsonRpc, ChunkResponseParser } from 'web3-utils';
-
-type WaitOptions = {
- timeOutTime: number;
- maxNumberOfAttempts: number;
-};
-export default class IpcProvider<
- API extends Web3APISpec = EthExecutionAPI,
-> extends Web3BaseProvider {
- private readonly _emitter: EventEmitter = new EventEmitter();
- private readonly _socketPath: string;
- private readonly _socket: Socket;
- private readonly chunkResponseParser: ChunkResponseParser;
- private waitOptions: WaitOptions;
- private _connectionStatus: Web3ProviderStatus;
+import { existsSync } from 'fs';
- private readonly _requestQueue: Map>;
+// todo had to ignore, introduce error in doc generation,see why/better solution
+/** @ignore */
+export default class IpcProvider extends SocketProvider<
+ Buffer | string,
+ CloseEvent,
+ Error,
+ API
+> {
+ private _connectionStatus: Web3ProviderStatus;
+ // Message handlers. Due to bounding of `this` and removing the listeners we have to keep it's reference.
+ protected _socketConnection?: Socket;
public constructor(socketPath: string) {
- super();
-
- this._connectionStatus = 'disconnected';
- this._socketPath = socketPath;
- this._socket = new Socket();
-
- this._requestQueue = new Map>();
-
- this.connect();
- this.waitOptions = {
- timeOutTime: 5000,
- maxNumberOfAttempts: 10,
- };
- this.chunkResponseParser = new ChunkResponseParser();
- this.chunkResponseParser.onError(() => {
- this._clearQueues();
- });
+ super(socketPath);
+ this._connectionStatus = 'connecting';
}
public getStatus(): Web3ProviderStatus {
+ if (this._socketConnection?.connecting) {
+ return 'connecting';
+ }
return this._connectionStatus;
}
-
- /* eslint-disable class-methods-use-this */
- public supportsSubscriptions(): boolean {
- return true;
- }
-
- public on(
- type: 'message' | 'connect' | 'disconnect' | string,
- callback: Web3ProviderEventCallback,
- ): void {
- this._emitter.on(type, callback);
- }
-
- public once(type: string, callback: Web3ProviderEventCallback): void {
- this._emitter.once(type, callback);
- }
-
- public removeListener(type: string, callback: Web3ProviderEventCallback): void {
- this._emitter.removeListener(type, callback);
- }
-
public connect(): void {
if (!existsSync(this._socketPath)) {
throw new InvalidClientError(this._socketPath);
}
-
+ if (!this._socketConnection || this.getStatus() === 'disconnected') {
+ this._socketConnection = new Socket();
+ }
try {
+ this._connectionStatus = 'connecting';
this._addSocketListeners();
- this._socket.connect({ path: this._socketPath });
+ this._socketConnection.connect({ path: this._socketPath });
} catch (e) {
throw new InvalidConnectionError(this._socketPath);
}
}
- public disconnect(): void {
- this._requestQueue.clear();
- this._removeSocketListeners();
- this._socket.end();
- }
-
- public reset(): void {
- this._requestQueue.clear();
-
- this._removeSocketListeners();
- this._addSocketListeners();
+ protected _closeSocketConnection(code?: number, data?: string) {
+ this._socketConnection?.end(() => {
+ this._onDisconnect(code, data);
+ });
}
- public get waitTimeOut(): number {
- return this.waitOptions.timeOutTime;
+ protected _sendToSocket>(
+ payload: Web3APIPayload,
+ ): void {
+ if (this.getStatus() === 'disconnected') {
+ throw new ConnectionNotOpenError();
+ }
+ this._socketConnection?.write(JSON.stringify(payload));
}
- public set waitTimeOut(timeOut: number) {
- this.waitOptions.timeOutTime = timeOut;
- }
+ protected _onCloseEvent(event: CloseEvent): void {
+ if (
+ this._reconnectOptions.autoReconnect &&
+ (![1000, 1001].includes(event.code) || !event.wasClean)
+ ) {
+ this._reconnect();
+ return;
+ }
- public get waitMaxNumberOfAttempts(): number {
- return this.waitOptions.maxNumberOfAttempts;
+ this._clearQueues(event);
+ this._removeSocketListeners();
+ this._onDisconnect(event.code, event.reason);
}
- public set waitMaxNumberOfAttempts(maxNumberOfAttempts: number) {
- this.waitOptions.maxNumberOfAttempts = maxNumberOfAttempts;
+ protected _parseResponses(e: Buffer | string) {
+ return this.chunkResponseParser.parseResponse(
+ typeof e === 'string' ? e : e.toString('utf8'),
+ );
}
- public async waitForConnection(): Promise {
- return new Promise((resolve, reject) => {
- let currentAttempt = 0;
- const interval = setInterval(() => {
- if (currentAttempt > this.waitMaxNumberOfAttempts - 1) {
- clearInterval(interval);
- reject(new ConnectionNotOpenError());
- } else if (this.getStatus() === 'connected') {
- clearInterval(interval);
- resolve();
- }
- currentAttempt += 1;
- }, this.waitTimeOut / this.waitMaxNumberOfAttempts);
- });
+ protected _onClose(event: CloseEvent): void {
+ this._clearQueues(event);
+ this._removeSocketListeners();
}
- // eslint-disable-next-line @typescript-eslint/require-await
- public async request<
- Method extends Web3APIMethod,
- ResultType = Web3APIReturnType,
- >(request: Web3APIPayload): Promise> {
- if (isNullish(this._socket)) throw new Error('IPC connection is undefined');
-
- const requestId = jsonRpc.isBatchRequest(request) ? request[0].id : request.id;
-
- if (isNullish(requestId)) throw new Error('Request Id not defined');
-
- if (this.getStatus() !== 'connected') {
- await this.waitForConnection();
- }
-
- // TODO: once https://github.com/web3/web3.js/issues/5460 is implemented, remove this block.
- // And catch the error by listening to the error event.
- // Additionally, after both https://github.com/web3/web3.js/issues/5466 and https://github.com/web3/web3.js/issues/5467
- // are implemented. There should be no case in the tests that cause a request to the provider after closing the connection.
- if (!this._socket.writable) {
- console.error(
- 'Can not send a request. The internal socket is not `writable`. Request data: ',
- request,
- );
- const dummyPromise = new Web3DeferredPromise>();
- return dummyPromise;
- }
-
+ protected _addSocketListeners(): void {
+ this._socketConnection?.on('data', this._onMessageHandler);
+ this._socketConnection?.on('connect', this._onOpenHandler);
+ this._socketConnection?.on('close', this._onClose.bind(this));
+ this._socketConnection?.on('end', this._onCloseHandler);
+ let errorListeners: unknown[] | undefined;
try {
- const defPromise = new Web3DeferredPromise>();
- this._requestQueue.set(requestId, defPromise);
- this._socket.write(JSON.stringify(request));
-
- return defPromise;
+ errorListeners = (this._socketConnection as Socket)?.listeners('error');
} catch (error) {
- this._requestQueue.delete(requestId);
- throw error;
+ // At some cases (at GitHub pipeline) there is an error raised when trying to access the listeners
+ // However, no need to do take any specific action in this case beside try adding the event listener for `error`
+ this._socketConnection?.on('error', this._onErrorHandler);
+ return;
+ }
+ // The error event listener may be already there because we do not remove it like the others
+ // So we add it only if it was not already added
+ if (!errorListeners || errorListeners.length === 0) {
+ this._socketConnection?.on('error', this._onErrorHandler);
}
}
- public removeAllListeners(type: string): void {
- this._emitter.removeAllListeners(type);
+ protected _removeSocketListeners(): void {
+ this._socketConnection?.removeAllListeners('connect');
+ this._socketConnection?.removeAllListeners('end');
+ this._socketConnection?.removeAllListeners('close');
+ this._socketConnection?.removeAllListeners('data');
}
- private _onMessage(e: Buffer | string): void {
- const responses = this.chunkResponseParser.parseResponse(
- typeof e === 'string' ? e : e.toString('utf8'),
- );
- if (!responses) {
- return;
- }
- for (const response of responses) {
- if (
- jsonRpc.isResponseWithNotification(response as JsonRpcNotification) &&
- (response as JsonRpcNotification).method.endsWith('_subscription')
- ) {
- this._emitter.emit('message', undefined, response);
- return;
- }
-
- const requestId = jsonRpc.isBatchResponse(response) ? response[0].id : response.id;
- const requestItem = this._requestQueue.get(requestId);
-
- if (!requestItem) {
- return;
- }
-
- if (jsonRpc.isBatchResponse(response) || jsonRpc.isResponseWithResult(response)) {
- this._emitter.emit('message', undefined, response);
- requestItem.resolve(response);
- } else {
- this._emitter.emit('message', response, undefined);
- requestItem?.reject(new ResponseError(response));
- }
-
- this._requestQueue.delete(requestId);
+ protected _clearQueues(event?: CloseEvent) {
+ if (this._pendingRequestsQueue.size > 0) {
+ this._pendingRequestsQueue.forEach(
+ (request: SocketRequestItem, key: JsonRpcId) => {
+ request.deferredPromise.reject(new ConnectionNotOpenError(event));
+ this._pendingRequestsQueue.delete(key);
+ },
+ );
}
- }
- private _clearQueues(event?: CloseEvent) {
- if (this._requestQueue.size > 0) {
- this._requestQueue.forEach((request: Web3DeferredPromise, key: JsonRpcId) => {
- request.reject(new ConnectionNotOpenError(event));
- this._requestQueue.delete(key);
- });
+ if (this._sentRequestsQueue.size > 0) {
+ this._sentRequestsQueue.forEach(
+ (request: SocketRequestItem, key: JsonRpcId) => {
+ request.deferredPromise.reject(new ConnectionNotOpenError(event));
+ this._sentRequestsQueue.delete(key);
+ },
+ );
}
this._removeSocketListeners();
}
- private _onConnect() {
+ protected _onConnect() {
this._connectionStatus = 'connected';
- this._emitter.emit('connect');
+ super._onConnect();
}
- private _onDisconnect(): void {
+ protected _onDisconnect(code?: number, data?: string): void {
this._connectionStatus = 'disconnected';
- this._emitter.emit('disconnect');
- }
-
- private _onClose(event: CloseEvent): void {
- this._clearQueues(event);
- this._removeSocketListeners();
- }
-
- private _addSocketListeners(): void {
- this._socket.on('connect', this._onConnect.bind(this));
- this._socket.on('end', this._onDisconnect.bind(this));
- this._socket.on('close', this._onClose.bind(this));
- this._socket.on('data', this._onMessage.bind(this));
- }
-
- private _removeSocketListeners(): void {
- this._socket?.removeAllListeners('connect');
- this._socket?.removeAllListeners('end');
- this._socket?.removeAllListeners('close');
- this._socket?.removeAllListeners('data');
+ super._onDisconnect(code, data);
}
}
diff --git a/packages/web3-providers-ipc/test/fixtures/accounts.json b/packages/web3-providers-ipc/test/fixtures/accounts.json
new file mode 120000
index 00000000000..7dbcddb60a2
--- /dev/null
+++ b/packages/web3-providers-ipc/test/fixtures/accounts.json
@@ -0,0 +1 @@
+../../../../scripts/accounts.json
\ No newline at end of file
diff --git a/packages/web3-providers-ipc/test/fixtures/system_test_utils.ts b/packages/web3-providers-ipc/test/fixtures/system_test_utils.ts
new file mode 120000
index 00000000000..2ab08a83752
--- /dev/null
+++ b/packages/web3-providers-ipc/test/fixtures/system_test_utils.ts
@@ -0,0 +1 @@
+../../../../scripts/system_tests_utils.ts
\ No newline at end of file
diff --git a/packages/web3-providers-ipc/test/fixtures/test_data.ts b/packages/web3-providers-ipc/test/fixtures/test_data.ts
new file mode 100644
index 00000000000..c1bddff0444
--- /dev/null
+++ b/packages/web3-providers-ipc/test/fixtures/test_data.ts
@@ -0,0 +1,45 @@
+/*
+This file is part of web3.js.
+
+web3.js is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+web3.js is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with web3.js. If not, see .
+*/
+
+export const validConnectionStrings = [
+ 'ws://localhost:8545',
+ 'ws://www.localhost',
+ 'ws://localhost',
+ 'wss://foo.com',
+ 'ws://foo.ninja',
+ 'wss://foo.com',
+ 'ws://foo.com:8545',
+];
+
+export const invalidConnectionStrings = [
+ 'htt://localhost:8545',
+ 'http//localhost:8545',
+ 'ipc://localhost:8545',
+ '',
+ // Using "null" value intentionally for validation
+ // eslint-disable-next-line no-null/no-null
+ null,
+ undefined,
+ 42,
+];
+
+export const wsProviderOptions = {
+ followRedirects: true,
+ handshakeTimeout: 1500,
+ maxRedirects: 3,
+ perMessageDeflate: true,
+};
diff --git a/packages/web3-providers-ipc/test/integration/eip1193.test.ts b/packages/web3-providers-ipc/test/integration/eip1193.test.ts
new file mode 100644
index 00000000000..96c2480ee04
--- /dev/null
+++ b/packages/web3-providers-ipc/test/integration/eip1193.test.ts
@@ -0,0 +1,109 @@
+/*
+This file is part of web3.js.
+
+web3.js is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+web3.js is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with web3.js. If not, see .
+*/
+
+import { hexToNumber } from 'web3-utils';
+import {
+ HexString,
+ ProviderConnectInfo,
+ ProviderRpcError,
+ Web3ProviderEventCallback,
+} from 'web3-types';
+import IpcProvider from '../../src/index';
+
+import {
+ getSystemTestProvider,
+ describeIf,
+ isIpc,
+ waitForSocketConnect,
+ waitForSocketDisconnect,
+} from '../fixtures/system_test_utils';
+
+describeIf(isIpc)('IpcProvider - eip1193', () => {
+ let socketPath: string;
+ let socketProvider: IpcProvider;
+
+ beforeAll(() => {
+ socketPath = getSystemTestProvider();
+ });
+ beforeEach(() => {
+ socketProvider = new IpcProvider(socketPath);
+ });
+ afterEach(async () => {
+ socketProvider.disconnect(1000);
+ await waitForSocketDisconnect(socketProvider);
+ });
+
+ describe('check events', () => {
+ it('should send connect event', async () => {
+ const { chainId } = await new Promise(resolve => {
+ socketProvider.on('connect', ((_error: unknown, data) => {
+ resolve(data as unknown as ProviderConnectInfo);
+ }) as Web3ProviderEventCallback);
+ });
+ expect(hexToNumber(chainId)).toBeGreaterThan(0);
+ });
+
+ it('should send disconnect event', async () => {
+ await waitForSocketConnect(socketProvider);
+ const disconnectPromise = new Promise(resolve => {
+ socketProvider.on('disconnect', ((error: ProviderRpcError) => {
+ resolve(error);
+ }) as Web3ProviderEventCallback);
+ });
+ socketProvider.disconnect(1000, 'Some extra data');
+
+ const err = await disconnectPromise;
+ expect(err.code).toBe(1000);
+ expect(err.data).toBe('Some extra data');
+ });
+ it('should send chainChanged event', async () => {
+ await waitForSocketConnect(socketProvider);
+ // @ts-expect-error set private variable
+ socketProvider._chainId = '0x1';
+ socketProvider.disconnect(1000);
+ await waitForSocketDisconnect(socketProvider);
+ const chainChangedPromise = new Promise(resolve => {
+ socketProvider.on('chainChanged', ((_error, data) => {
+ resolve(data as unknown as ProviderConnectInfo);
+ }) as Web3ProviderEventCallback);
+ });
+ socketProvider.connect();
+ await waitForSocketConnect(socketProvider);
+ const changedData = await chainChangedPromise;
+ expect(changedData.chainId).not.toBe('0x1');
+ expect(hexToNumber(changedData.chainId)).toBeGreaterThan(0);
+ });
+ it('should send accountsChanged event', async () => {
+ await waitForSocketConnect(socketProvider);
+
+ // @ts-expect-error set private variable
+ socketProvider._accounts = ['1', '2'];
+ socketProvider.disconnect(1000);
+ await waitForSocketDisconnect(socketProvider);
+ const chainChangedPromise = new Promise<{ accounts: HexString[] }>(resolve => {
+ socketProvider.on('accountsChanged', ((_error, data) => {
+ resolve(data as unknown as { accounts: HexString[] });
+ }) as Web3ProviderEventCallback<{ accounts: HexString[] }>);
+ });
+ socketProvider.connect();
+ await waitForSocketConnect(socketProvider);
+ const changedData = await chainChangedPromise;
+
+ expect(JSON.stringify(changedData.accounts)).not.toBe(JSON.stringify(['1', '2']));
+ });
+ });
+});
diff --git a/packages/web3-providers-ipc/test/unit/ipc_provider.test.ts b/packages/web3-providers-ipc/test/unit/ipc_provider.test.ts
index 9f66de3e8f6..f1a2d492ca6 100644
--- a/packages/web3-providers-ipc/test/unit/ipc_provider.test.ts
+++ b/packages/web3-providers-ipc/test/unit/ipc_provider.test.ts
@@ -62,29 +62,39 @@ describe('IpcProvider', () => {
it('should add listeners to socket', () => {
const provider = new IpcProvider(socketPath);
- jest.spyOn(provider['_socket'], 'on');
-
- expect(provider['_socket'].on).toHaveBeenCalledWith('connect', expect.any(Function));
- expect(provider['_socket'].on).toHaveBeenCalledWith('end', expect.any(Function));
- expect(provider['_socket'].on).toHaveBeenCalledWith('close', expect.any(Function));
- expect(provider['_socket'].on).toHaveBeenCalledWith('data', expect.any(Function));
+ // @ts-expect-error-next-line
+ jest.spyOn(provider._socketConnection, 'on');
+
+ // @ts-expect-error-next-line
+ expect(provider._socketConnection.on).toHaveBeenCalledWith(
+ 'connect',
+ expect.any(Function),
+ );
+ // @ts-expect-error-next-line
+ expect(provider._socketConnection.on).toHaveBeenCalledWith('end', expect.any(Function));
+ // @ts-expect-error-next-line
+ expect(provider._socketConnection.on).toHaveBeenCalledWith(
+ 'close',
+ expect.any(Function),
+ );
+ // @ts-expect-error-next-line
+ expect(provider._socketConnection.on).toHaveBeenCalledWith(
+ 'data',
+ expect.any(Function),
+ );
});
it('should connect to socket path', () => {
const provider = new IpcProvider(socketPath);
- jest.spyOn(provider['_socket'], 'connect');
-
- expect(provider['_socket'].connect).toHaveBeenCalledTimes(1);
- expect(provider['_socket'].connect).toHaveBeenCalledWith({ path: socketPath });
- });
- it('check wait params', () => {
- const provider = new IpcProvider(socketPath);
- expect(provider.waitTimeOut).toBe(5000);
- expect(provider.waitMaxNumberOfAttempts).toBe(10);
- provider.waitTimeOut = 300;
- provider.waitMaxNumberOfAttempts = 20;
- expect(provider.waitTimeOut).toBe(300);
- expect(provider.waitMaxNumberOfAttempts).toBe(20);
+ // @ts-expect-error-next-line
+ jest.spyOn(provider._socketConnection, 'connect');
+
+ // @ts-expect-error-next-line
+ expect(provider._socketConnection.connect).toHaveBeenCalledTimes(1);
+ // @ts-expect-error-next-line
+ expect(provider._socketConnection.connect).toHaveBeenCalledWith({
+ path: socketPath,
+ });
});
});
});
diff --git a/packages/web3-providers-ws/CHANGELOG.md b/packages/web3-providers-ws/CHANGELOG.md
index 5e211c3684f..6f1e0884a71 100644
--- a/packages/web3-providers-ws/CHANGELOG.md
+++ b/packages/web3-providers-ws/CHANGELOG.md
@@ -48,3 +48,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Updated dependencies (#5725)
## [Unreleased]
+
+### Changed
+
+- Refactor to use common SocketProvider class (#5683)
diff --git a/packages/web3-providers-ws/src/index.ts b/packages/web3-providers-ws/src/index.ts
index 7d7f5832efc..8d2425728f8 100644
--- a/packages/web3-providers-ws/src/index.ts
+++ b/packages/web3-providers-ws/src/index.ts
@@ -15,334 +15,108 @@ You should have received a copy of the GNU Lesser General Public License
along with web3.js. If not, see .
*/
-import { EventEmitter } from 'events';
import { ClientRequestArgs } from 'http';
-import WebSocket, { ClientOptions, CloseEvent, ErrorEvent, MessageEvent } from 'isomorphic-ws';
+import WebSocket, { ClientOptions, CloseEvent } from 'isomorphic-ws';
import {
+ ConnectionEvent,
EthExecutionAPI,
JsonRpcId,
- JsonRpcNotification,
- JsonRpcResult,
+ SocketRequestItem,
Web3APIMethod,
Web3APIPayload,
- Web3APIReturnType,
Web3APISpec,
- Web3BaseProvider,
- Web3ProviderEventCallback,
Web3ProviderStatus,
- JsonRpcResponseWithResult,
} from 'web3-types';
-import { jsonRpc, isNullish, Web3DeferredPromise, ChunkResponseParser } from 'web3-utils';
-import {
- InvalidClientError,
- InvalidConnectionError,
- ConnectionNotOpenError,
- PendingRequestsOnReconnectingError,
- Web3WSProviderError,
- RequestAlreadySentError,
- ResponseError,
-} from 'web3-errors';
-import { EventEmittedCallback, OnCloseEvent, ReconnectOptions, WSRequestItem } from './types';
+import { isNullish } from 'web3-utils';
+import { InvalidConnectionError, ConnectionNotOpenError } from 'web3-errors';
+import { SocketProvider } from 'web3-utils';
export { ClientRequestArgs } from 'http';
// todo had to ignore, introduce error in doc generation,see why/better solution
/** @ignore */
export { ClientOptions } from 'isomorphic-ws';
-export { ReconnectOptions } from './types';
+
export default class WebSocketProvider<
API extends Web3APISpec = EthExecutionAPI,
-> extends Web3BaseProvider {
- private readonly _wsEventEmitter: EventEmitter = new EventEmitter();
-
- private readonly _clientUrl: string;
- private readonly _wsProviderOptions?: ClientOptions | ClientRequestArgs;
-
- private _webSocketConnection?: WebSocket;
- private readonly chunkResponseParser: ChunkResponseParser;
-
- /* eslint-disable @typescript-eslint/no-explicit-any */
- protected readonly _pendingRequestsQueue: Map>;
- /* eslint-disable @typescript-eslint/no-explicit-any */
- protected readonly _sentRequestsQueue: Map>;
-
- private _reconnectAttempts!: number;
- private readonly _reconnectOptions: ReconnectOptions;
-
- // Message handlers. Due to bounding of `this` and removing the listeners we have to keep it's reference.
- private readonly _onMessageHandler: (event: MessageEvent) => void;
- private readonly _onOpenHandler: () => void;
- private readonly _onCloseHandler: (event: CloseEvent) => void;
- private readonly _onErrorHandler: (event: ErrorEvent) => void;
-
- public constructor(
- clientUrl: string,
- wsProviderOptions?: ClientOptions | ClientRequestArgs,
- reconnectOptions?: ReconnectOptions,
- ) {
- super();
- if (!WebSocketProvider._validateProviderUrl(clientUrl))
- throw new InvalidClientError(clientUrl);
-
- this._clientUrl = clientUrl;
- this._wsProviderOptions = wsProviderOptions;
-
- const DEFAULT_PROVIDER_RECONNECTION_OPTIONS = {
- autoReconnect: true,
- delay: 5000,
- maxAttempts: 5,
- };
-
- this._reconnectOptions = {
- ...DEFAULT_PROVIDER_RECONNECTION_OPTIONS,
- ...reconnectOptions,
- };
-
- this._pendingRequestsQueue = new Map>();
- this._sentRequestsQueue = new Map>();
+> extends SocketProvider {
+ protected readonly _providerOptions?: ClientOptions | ClientRequestArgs;
+ protected _socketConnection?: WebSocket;
- this._onMessageHandler = this._onMessage.bind(this);
- this._onOpenHandler = this._onConnect.bind(this);
- this._onCloseHandler = this._onClose.bind(this);
- this._onErrorHandler = this._onError.bind(this);
-
- this._init();
- this.connect();
- this.chunkResponseParser = new ChunkResponseParser();
- this.chunkResponseParser.onError(() => {
- this._clearQueues();
- });
- }
-
- private static _validateProviderUrl(providerUrl: string): boolean {
+ // eslint-disable-next-line class-methods-use-this
+ protected _validateProviderPath(providerUrl: string): boolean {
return typeof providerUrl === 'string' ? /^ws(s)?:\/\//i.test(providerUrl) : false;
}
public getStatus(): Web3ProviderStatus {
- if (isNullish(this._webSocketConnection)) return 'disconnected';
-
- switch (this._webSocketConnection.readyState) {
- case this._webSocketConnection.CONNECTING: {
- return 'connecting';
- }
- case this._webSocketConnection.OPEN: {
- return 'connected';
- }
- default: {
- return 'disconnected';
+ if (this._socketConnection && !isNullish(this._socketConnection)) {
+ switch (this._socketConnection.readyState) {
+ case this._socketConnection.CONNECTING: {
+ return 'connecting';
+ }
+ case this._socketConnection.OPEN: {
+ return 'connected';
+ }
+ default: {
+ return 'disconnected';
+ }
}
}
- }
-
- /* eslint-disable class-methods-use-this */
- public supportsSubscriptions(): boolean {
- return true;
- }
-
- public on(
- type: 'message' | string,
- callback: Web3ProviderEventCallback | EventEmittedCallback,
- ): void {
- this._wsEventEmitter.on(type, callback);
- }
-
- public once(type: string, callback: Web3ProviderEventCallback): void {
- this._wsEventEmitter.once(type, callback);
- }
-
- public removeListener(type: string, callback: Web3ProviderEventCallback): void {
- this._wsEventEmitter.removeListener(type, callback);
+ return 'disconnected';
}
public connect(): void {
try {
- this._webSocketConnection = new WebSocket(
- this._clientUrl,
+ this._socketConnection = new WebSocket(
+ this._socketPath,
undefined,
- this._wsProviderOptions && Object.keys(this._wsProviderOptions).length === 0
+ this._providerOptions && Object.keys(this._providerOptions).length === 0
? undefined
- : this._wsProviderOptions,
+ : this._providerOptions,
);
this._addSocketListeners();
-
- // TODO: Debug why this is needed
- // if (this.getStatus() === 'connecting') {
- // // Rejecting promises if provider is not connected even after reattempts
- // setTimeout(() => {
- // if (this.getStatus() === 'disconnected') {
- // this._clearQueues(undefined);
- // }
- // }, this._reconnectOptions.delay * (this._reconnectOptions.maxAttempts + 1));
- // }
} catch (e) {
- throw new InvalidConnectionError(this._clientUrl);
+ throw new InvalidConnectionError(this._socketPath);
}
}
- public disconnect(code?: number, reason?: string): void {
- this._emitCloseEvent(code, reason);
- this._removeSocketListeners();
- this._webSocketConnection?.close(code, reason);
+ protected _closeSocketConnection(code?: number, data?: string) {
+ this._socketConnection?.close(code, data);
}
-
- public reset(): void {
- this._sentRequestsQueue.clear();
- this._pendingRequestsQueue.clear();
-
- this._init();
- this._removeSocketListeners();
- this._addSocketListeners();
- }
-
- public async request<
- Method extends Web3APIMethod,
- ResultType = Web3APIReturnType,
- >(request: Web3APIPayload): Promise> {
- const requestId = jsonRpc.isBatchRequest(request) ? request[0].id : request.id;
-
- if (!requestId) {
- throw new Web3WSProviderError('Request Id not defined');
- }
-
- if (this._sentRequestsQueue.has(requestId)) {
- throw new RequestAlreadySentError(requestId);
- }
-
- const deferredPromise = new Web3DeferredPromise>();
-
- const reqItem: WSRequestItem> = {
- payload: request,
- deferredPromise,
- };
-
- if (this.getStatus() === 'connecting') {
- this._pendingRequestsQueue.set(requestId, reqItem);
-
- return reqItem.deferredPromise;
- }
-
- this._sentRequestsQueue.set(requestId, reqItem);
-
- try {
- this._sendToSocket(reqItem.payload);
- } catch (error) {
- this._sentRequestsQueue.delete(requestId);
- throw error;
- }
-
- return deferredPromise;
+ protected _parseResponses(event: WebSocket.MessageEvent) {
+ return this.chunkResponseParser.parseResponse(event.data as string);
}
- private _sendToSocket>(
+ protected _sendToSocket>(
payload: Web3APIPayload,
): void {
- if (!this._webSocketConnection) {
- throw new Web3WSProviderError('WebSocket connection is not created');
- }
-
if (this.getStatus() === 'disconnected') {
throw new ConnectionNotOpenError();
}
-
- this._webSocketConnection.send(JSON.stringify(payload));
- }
-
- public removeAllListeners(type: string): void {
- this._wsEventEmitter.removeAllListeners(type);
+ this._socketConnection?.send(JSON.stringify(payload));
}
- private _init() {
- this._reconnectAttempts = 0;
- }
-
- private _addSocketListeners(): void {
- this._webSocketConnection?.addEventListener('message', this._onMessageHandler);
- this._webSocketConnection?.addEventListener('open', this._onOpenHandler);
- this._webSocketConnection?.addEventListener('close', this._onCloseHandler);
-
+ protected _addSocketListeners(): void {
+ this._socketConnection?.addEventListener('message', this._onMessageHandler);
+ this._socketConnection?.addEventListener('open', this._onOpenHandler);
+ this._socketConnection?.addEventListener('close', e => this._onCloseHandler(e));
let errorListeners: unknown[] | undefined;
try {
- errorListeners = this._webSocketConnection?.listeners('error');
+ errorListeners = this._socketConnection?.listeners('error');
} catch (error) {
// At some cases (at GitHub pipeline) there is an error raised when trying to access the listeners
// However, no need to do take any specific action in this case beside try adding the event listener for `error`
- this._webSocketConnection?.addEventListener('error', this._onErrorHandler);
+ this._socketConnection?.addEventListener('error', this._onErrorHandler);
return;
}
// The error event listener may be already there because we do not remove it like the others
// So we add it only if it was not already added
if (!errorListeners || errorListeners.length === 0) {
- this._webSocketConnection?.addEventListener('error', this._onErrorHandler);
+ this._socketConnection?.addEventListener('error', this._onErrorHandler);
}
}
- private _reconnect(): void {
- if (this._sentRequestsQueue.size > 0) {
- this._sentRequestsQueue.forEach(
- (request: WSRequestItem, key: JsonRpcId) => {
- request.deferredPromise.reject(new PendingRequestsOnReconnectingError());
- this._sentRequestsQueue.delete(key);
- },
- );
- }
-
- if (this._reconnectAttempts < this._reconnectOptions.maxAttempts) {
- setTimeout(() => {
- this._reconnectAttempts += 1;
- this._removeSocketListeners();
- this.connect();
- }, this._reconnectOptions.delay);
- }
- }
-
- private _onMessage(event: MessageEvent): void {
- const responses = this.chunkResponseParser.parseResponse(event.data as string);
- if (!responses) {
- return;
- }
- for (const response of responses) {
- if (
- jsonRpc.isResponseWithNotification(response as JsonRpcNotification) &&
- (response as JsonRpcNotification).method.endsWith('_subscription')
- ) {
- this._wsEventEmitter.emit('message', undefined, response);
- return;
- }
-
- const requestId = jsonRpc.isBatchResponse(response) ? response[0].id : response.id;
- const requestItem = this._sentRequestsQueue.get(requestId);
-
- if (!requestItem) {
- return;
- }
-
- if (jsonRpc.isBatchResponse(response) || jsonRpc.isResponseWithResult(response)) {
- this._wsEventEmitter.emit('message', undefined, response);
- requestItem.deferredPromise.resolve(response);
- } else {
- this._wsEventEmitter.emit('message', response, undefined);
- requestItem?.deferredPromise.reject(new ResponseError(response));
- }
-
- this._sentRequestsQueue.delete(requestId);
- }
- }
-
- private _onConnect() {
- this._reconnectAttempts = 0;
- this._wsEventEmitter.emit('open');
- this._sendPendingRequests();
- }
-
- private _sendPendingRequests() {
- for (const [id, value] of this._pendingRequestsQueue.entries()) {
- this._sendToSocket(value.payload as Web3APIPayload);
- this._pendingRequestsQueue.delete(id);
- this._sentRequestsQueue.set(id, value);
- }
- }
-
- private _onClose(event: CloseEvent): void {
+ protected _onCloseEvent(event: CloseEvent): void {
if (
this._reconnectOptions.autoReconnect &&
(![1000, 1001].includes(event.code) || !event.wasClean)
@@ -351,19 +125,22 @@ export default class WebSocketProvider<
return;
}
- this._emitCloseEvent(event.code, event.reason);
this._clearQueues(event);
this._removeSocketListeners();
+ this._onDisconnect(event.code, event.reason);
}
- private _onError(event: ErrorEvent): void {
- this._wsEventEmitter.emit('error', event);
+ protected _removeSocketListeners(): void {
+ this._socketConnection?.removeEventListener('message', this._onMessageHandler);
+ this._socketConnection?.removeEventListener('open', this._onOpenHandler);
+ this._socketConnection?.removeEventListener('close', this._onCloseHandler);
+ // note: we intentionally keep the error event listener to be able to emit it in case an error happens when closing the connection
}
- private _clearQueues(event?: CloseEvent) {
+ protected _clearQueues(event?: ConnectionEvent) {
if (this._pendingRequestsQueue.size > 0) {
this._pendingRequestsQueue.forEach(
- (request: WSRequestItem, key: JsonRpcId) => {
+ (request: SocketRequestItem, key: JsonRpcId) => {
request.deferredPromise.reject(new ConnectionNotOpenError(event));
this._pendingRequestsQueue.delete(key);
},
@@ -372,7 +149,7 @@ export default class WebSocketProvider<
if (this._sentRequestsQueue.size > 0) {
this._sentRequestsQueue.forEach(
- (request: WSRequestItem, key: JsonRpcId) => {
+ (request: SocketRequestItem, key: JsonRpcId) => {
request.deferredPromise.reject(new ConnectionNotOpenError(event));
this._sentRequestsQueue.delete(key);
},
@@ -381,18 +158,4 @@ export default class WebSocketProvider<
this._removeSocketListeners();
}
-
- private _removeSocketListeners(): void {
- this._webSocketConnection?.removeEventListener('message', this._onMessageHandler);
- this._webSocketConnection?.removeEventListener('open', this._onOpenHandler);
- this._webSocketConnection?.removeEventListener('close', this._onCloseHandler);
- // note: we intentionally keep the error event listener to be able to emit it in case an error happens when closing the connection
- }
-
- private _emitCloseEvent(code?: number, reason?: string): void {
- this._wsEventEmitter.emit('close', undefined, {
- code,
- reason,
- } as OnCloseEvent);
- }
}
diff --git a/packages/web3-providers-ws/src/types.ts b/packages/web3-providers-ws/src/types.ts
index ad6cd2b0745..f416303d3ba 100644
--- a/packages/web3-providers-ws/src/types.ts
+++ b/packages/web3-providers-ws/src/types.ts
@@ -1,4 +1,4 @@
-/*
+/*
This file is part of web3.js.
web3.js is free software: you can redistribute it and/or modify
@@ -15,29 +15,8 @@ You should have received a copy of the GNU Lesser General Public License
along with web3.js. If not, see .
*/
-import { Web3APIMethod, Web3APIPayload, Web3APISpec, Web3DeferredPromise } from 'web3-types';
-
export type ReconnectOptions = {
autoReconnect: boolean;
delay: number;
maxAttempts: number;
};
-
-export interface WSRequestItem<
- API extends Web3APISpec,
- Method extends Web3APIMethod,
- ResponseType,
-> {
- payload: Web3APIPayload;
- deferredPromise: Web3DeferredPromise;
-}
-
-export type OnCloseEvent = {
- code: number;
- reason: string;
-};
-
-export type EventEmittedCallback = (
- error: Error | undefined,
- event?: OnCloseEvent | undefined,
-) => void;
diff --git a/packages/web3-providers-ws/test/fixtures/helpers.ts b/packages/web3-providers-ws/test/fixtures/helpers.ts
index 6ce37e9b4f7..fd90c7aa722 100644
--- a/packages/web3-providers-ws/test/fixtures/helpers.ts
+++ b/packages/web3-providers-ws/test/fixtures/helpers.ts
@@ -15,27 +15,21 @@ You should have received a copy of the GNU Lesser General Public License
along with web3.js. If not, see .
*/
+import { ProviderConnectInfo, ProviderRpcError, Web3ProviderEventCallback } from 'web3-types';
import WebSocketProvider from '../../src/index';
-export const waitForOpenConnection = async (
- provider: WebSocketProvider,
- currentAttempt: number,
- status = 'connected',
-) => {
- return new Promise((resolve, reject) => {
- const maxNumberOfAttempts = 10;
- const intervalTime = 2000; // ms
+export const waitForOpenConnection = async (provider: WebSocketProvider) => {
+ return new Promise(resolve => {
+ provider.on('connect', ((_error, data) => {
+ resolve(data as unknown as ProviderConnectInfo);
+ }) as Web3ProviderEventCallback);
+ });
+};
- const interval = setInterval(() => {
- if (currentAttempt > maxNumberOfAttempts - 1) {
- clearInterval(interval);
- reject(new Error('Maximum number of attempts exceeded'));
- } else if (provider.getStatus() === status) {
- clearInterval(interval);
- resolve();
- }
- // eslint-disable-next-line no-plusplus, no-param-reassign
- currentAttempt++;
- }, intervalTime);
+export const waitForCloseConnection = async (provider: WebSocketProvider) => {
+ return new Promise(resolve => {
+ provider.on('disconnect', ((_error, data) => {
+ resolve(data as unknown as ProviderRpcError);
+ }) as Web3ProviderEventCallback);
});
};
diff --git a/packages/web3-providers-ws/test/integration/basic_auth.test.ts b/packages/web3-providers-ws/test/integration/basic_auth.test.ts
index a569ac778ca..a3e69a4bf09 100644
--- a/packages/web3-providers-ws/test/integration/basic_auth.test.ts
+++ b/packages/web3-providers-ws/test/integration/basic_auth.test.ts
@@ -18,7 +18,7 @@ import express from 'express';
import { createProxyMiddleware } from 'http-proxy-middleware';
import { Server } from 'http';
-import { waitForOpenConnection } from '../fixtures/helpers';
+import { waitForCloseConnection, waitForOpenConnection } from '../fixtures/helpers';
import WebSocketProvider from '../../src/index';
import { getSystemTestProvider, describeIf, isWs } from '../fixtures/system_test_utils';
@@ -26,7 +26,6 @@ describeIf(isWs)('Support of Basic Auth', () => {
let server: Server;
let clientWsUrl: string;
let webSocketProvider: WebSocketProvider;
- let currentAttempt = 0;
beforeAll(() => {
clientWsUrl = getSystemTestProvider();
@@ -74,18 +73,18 @@ describeIf(isWs)('Support of Basic Auth', () => {
{},
{ delay: 1, autoReconnect: false, maxAttempts: 1 },
);
- currentAttempt = 0;
});
afterEach(async () => {
// make sure we try to close the connection after it is established
if (webSocketProvider.getStatus() === 'connecting') {
- await waitForOpenConnection(webSocketProvider, currentAttempt);
+ await waitForOpenConnection(webSocketProvider);
}
webSocketProvider.disconnect();
+ await waitForCloseConnection(webSocketProvider);
});
// eslint-disable-next-line jest/expect-expect
test('should connect with basic auth', async () => {
- await waitForOpenConnection(webSocketProvider, currentAttempt);
- webSocketProvider.disconnect();
+ await waitForOpenConnection(webSocketProvider);
+ expect(webSocketProvider.getStatus()).toBe('connected');
});
});
diff --git a/packages/web3-providers-ws/test/integration/eip1193.test.ts b/packages/web3-providers-ws/test/integration/eip1193.test.ts
new file mode 100644
index 00000000000..b92e8182578
--- /dev/null
+++ b/packages/web3-providers-ws/test/integration/eip1193.test.ts
@@ -0,0 +1,108 @@
+/*
+This file is part of web3.js.
+
+web3.js is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+web3.js is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with web3.js. If not, see .
+*/
+
+import { hexToNumber } from 'web3-utils';
+import {
+ HexString,
+ ProviderConnectInfo,
+ ProviderRpcError,
+ Web3ProviderEventCallback,
+} from 'web3-types';
+import WebSocketProvider from '../../src/index';
+
+import {
+ getSystemTestProvider,
+ describeIf,
+ isWs,
+ waitForSocketConnect,
+ waitForSocketDisconnect,
+} from '../fixtures/system_test_utils';
+
+describeIf(isWs)('WebSocketProvider - eip1193', () => {
+ let socketPath: string;
+ let socketProvider: WebSocketProvider;
+
+ beforeAll(() => {
+ socketPath = getSystemTestProvider();
+ });
+ beforeEach(() => {
+ socketProvider = new WebSocketProvider(socketPath);
+ });
+ afterEach(async () => {
+ socketProvider.disconnect(1000);
+ await waitForSocketDisconnect(socketProvider);
+ });
+
+ describe('check events', () => {
+ it('should send connect event', async () => {
+ const { chainId } = await new Promise(resolve => {
+ socketProvider.on('connect', ((_error: unknown, data) => {
+ resolve(data as unknown as ProviderConnectInfo);
+ }) as Web3ProviderEventCallback);
+ });
+ expect(hexToNumber(chainId)).toBeGreaterThan(0);
+ });
+ it('should send disconnect event', async () => {
+ await waitForSocketConnect(socketProvider);
+ const disconnectPromise = new Promise(resolve => {
+ socketProvider.on('disconnect', ((error: ProviderRpcError) => {
+ resolve(error);
+ }) as Web3ProviderEventCallback);
+ });
+ socketProvider.disconnect(1000, 'Some extra data');
+
+ const err = await disconnectPromise;
+ expect(err.code).toBe(1000);
+ expect(err.data).toBe('Some extra data');
+ });
+ it('should send chainChanged event', async () => {
+ await waitForSocketConnect(socketProvider);
+ // @ts-expect-error set private variable
+ socketProvider._chainId = '0x1';
+ socketProvider.disconnect(1000);
+ await waitForSocketDisconnect(socketProvider);
+ const chainChangedPromise = new Promise(resolve => {
+ socketProvider.on('chainChanged', ((_error, data) => {
+ resolve(data as unknown as ProviderConnectInfo);
+ }) as Web3ProviderEventCallback);
+ });
+ socketProvider.connect();
+ await waitForSocketConnect(socketProvider);
+ const changedData = await chainChangedPromise;
+ expect(changedData.chainId).not.toBe('0x1');
+ expect(hexToNumber(changedData.chainId)).toBeGreaterThan(0);
+ });
+ it('should send accountsChanged event', async () => {
+ await waitForSocketConnect(socketProvider);
+
+ // @ts-expect-error set private variable
+ socketProvider._accounts = ['1', '2'];
+ socketProvider.disconnect(1000);
+ await waitForSocketDisconnect(socketProvider);
+ const chainChangedPromise = new Promise<{ accounts: HexString[] }>(resolve => {
+ socketProvider.on('accountsChanged', ((_error, data) => {
+ resolve(data as unknown as { accounts: HexString[] });
+ }) as Web3ProviderEventCallback<{ accounts: HexString[] }>);
+ });
+ socketProvider.connect();
+ await waitForSocketConnect(socketProvider);
+ const changedData = await chainChangedPromise;
+
+ expect(JSON.stringify(changedData.accounts)).not.toBe(JSON.stringify(['1', '2']));
+ });
+ });
+});
diff --git a/packages/web3-providers-ws/test/integration/web_socket_provider_integration.test.ts b/packages/web3-providers-ws/test/integration/web_socket_provider_integration.test.ts
index d28bb590274..9828a768af6 100644
--- a/packages/web3-providers-ws/test/integration/web_socket_provider_integration.test.ts
+++ b/packages/web3-providers-ws/test/integration/web_socket_provider_integration.test.ts
@@ -22,13 +22,15 @@ import {
JsonRpcSubscriptionResult,
JsonRpcId,
JsonRpcResponse,
+ ProviderRpcError,
+ Web3ProviderEventCallback,
+ SocketRequestItem,
} from 'web3-types';
import { Web3DeferredPromise } from 'web3-utils';
import { Web3WSProviderError } from 'web3-errors';
import WebSocketProvider from '../../src/index';
-import { OnCloseEvent, WSRequestItem } from '../../src/types';
-import { waitForOpenConnection } from '../fixtures/helpers';
+import { waitForCloseConnection, waitForOpenConnection } from '../fixtures/helpers';
import {
getSystemTestProvider,
@@ -45,7 +47,6 @@ describeIf(isWs)('WebSocketProvider - implemented methods', () => {
let webSocketProvider: WebSocketProvider;
let jsonRpcPayload: Web3APIPayload;
// helper function
- let currentAttempt = 0;
beforeAll(async () => {
clientWsUrl = getSystemTestProvider();
@@ -63,20 +64,18 @@ describeIf(isWs)('WebSocketProvider - implemented methods', () => {
{},
{ delay: 1, autoReconnect: false, maxAttempts: 1 },
);
- currentAttempt = 0;
});
afterEach(async () => {
// make sure we try to close the connection after it is established
if (webSocketProvider.getStatus() === 'connecting') {
- await waitForOpenConnection(webSocketProvider, currentAttempt);
+ await waitForOpenConnection(webSocketProvider);
}
-
webSocketProvider.disconnect(1000);
});
describe('websocker provider tests', () => {
it('should connect', async () => {
- await waitForOpenConnection(webSocketProvider, currentAttempt);
+ await waitForOpenConnection(webSocketProvider);
expect(webSocketProvider).toBeInstanceOf(WebSocketProvider);
expect(webSocketProvider.getStatus()).toBe('connected');
});
@@ -94,6 +93,9 @@ describeIf(isWs)('WebSocketProvider - implemented methods', () => {
if (error) {
throw new Error(error.message);
}
+ if (result?.id !== jsonRpcPayload.id) {
+ return;
+ }
expect(result?.id).toBe(jsonRpcPayload.id);
resolve();
},
@@ -104,6 +106,7 @@ describeIf(isWs)('WebSocketProvider - implemented methods', () => {
});
it('should subscribe to `error` event that could happen at the underlying WebSocket connection', async () => {
+ await waitForOpenConnection(webSocketProvider);
const errorMsg = 'Custom WebSocket error occurred';
const errorPromise = new Promise((resolve: Resolve) => {
@@ -113,7 +116,7 @@ describeIf(isWs)('WebSocketProvider - implemented methods', () => {
});
});
- webSocketProvider['_webSocketConnection']?.emit(
+ webSocketProvider['_socketConnection']?.emit(
'error',
new Web3WSProviderError(errorMsg),
);
@@ -122,27 +125,23 @@ describeIf(isWs)('WebSocketProvider - implemented methods', () => {
it('should subscribe to `connect` event', async () => {
const openPromise = new Promise((resolve: Resolve) => {
- webSocketProvider.on('open', () => {
+ webSocketProvider.on('connect', () => {
resolve('resolved');
});
});
await expect(openPromise).resolves.toBe('resolved');
});
- it('should subscribe to `close` event', async () => {
+ it('should subscribe to `disconnect` event', async () => {
const code = 1000;
- const closePromise = new Promise((resolve: Resolve) => {
- webSocketProvider.on('close', (err?: Error, event?: OnCloseEvent) => {
- if (err) {
- throw new Error(err.message);
- }
- expect(event?.code).toEqual(code);
- resolve();
- });
+ const closePromise = new Promise(resolve => {
+ webSocketProvider.on('disconnect', ((error: ProviderRpcError) => {
+ expect(error?.code).toEqual(code);
+ resolve(error);
+ }) as Web3ProviderEventCallback);
});
- currentAttempt = 0;
- await waitForOpenConnection(webSocketProvider, currentAttempt);
+ await waitForOpenConnection(webSocketProvider);
webSocketProvider.disconnect(code);
await closePromise;
});
@@ -150,20 +149,22 @@ describeIf(isWs)('WebSocketProvider - implemented methods', () => {
describe('disconnect and reset test', () => {
it('should disconnect', async () => {
+ await waitForOpenConnection(webSocketProvider);
const provider = new WebSocketProvider(
clientWsUrl,
{},
{ delay: 1, autoReconnect: false, maxAttempts: 1 },
);
- await waitForOpenConnection(provider, currentAttempt);
+ await waitForOpenConnection(provider);
provider.disconnect(1000);
- await waitForOpenConnection(provider, currentAttempt, 'disconnected');
+ await waitForCloseConnection(provider);
expect(provider.getStatus()).toBe('disconnected');
});
it('should reset', async () => {
+ await waitForOpenConnection(webSocketProvider);
class TestReset extends WebSocketProvider {
- public pendigRequestsSize() {
+ public pendingRequestsSize() {
return this._pendingRequestsQueue.size;
}
@@ -171,11 +172,11 @@ describeIf(isWs)('WebSocketProvider - implemented methods', () => {
return this._pendingRequestsQueue.size;
}
- public setPendingRequest(id: JsonRpcId, reqItem: WSRequestItem) {
+ public setPendingRequest(id: JsonRpcId, reqItem: SocketRequestItem) {
this._pendingRequestsQueue.set(id, reqItem);
}
- public setSentRequest(id: JsonRpcId, reqItem: WSRequestItem) {
+ public setSentRequest(id: JsonRpcId, reqItem: SocketRequestItem) {
this._sentRequestsQueue.set(id, reqItem);
}
}
@@ -185,23 +186,23 @@ describeIf(isWs)('WebSocketProvider - implemented methods', () => {
{ delay: 1, autoReconnect: false, maxAttempts: 1 },
);
- await waitForOpenConnection(testResetProvider, currentAttempt);
+ await waitForOpenConnection(testResetProvider);
const defPromise = new Web3DeferredPromise>();
- const reqItem: WSRequestItem = {
+ const reqItem: SocketRequestItem = {
payload: jsonRpcPayload,
deferredPromise: defPromise,
};
testResetProvider.setPendingRequest(jsonRpcPayload.id, reqItem);
- expect(testResetProvider.pendigRequestsSize()).toBe(1);
+ expect(testResetProvider.pendingRequestsSize()).toBe(1);
testResetProvider.setSentRequest(jsonRpcPayload.id, reqItem);
expect(testResetProvider.sentRequestsSize()).toBe(1);
testResetProvider.reset();
- expect(testResetProvider.pendigRequestsSize()).toBe(0);
+ expect(testResetProvider.pendingRequestsSize()).toBe(0);
expect(testResetProvider.sentRequestsSize()).toBe(0);
testResetProvider.disconnect(1000);
@@ -209,17 +210,19 @@ describeIf(isWs)('WebSocketProvider - implemented methods', () => {
});
describe('getStatus get and validate all status tests', () => {
- it('should getStatus `connecting`', () => {
+ it('should getStatus `connecting`', async () => {
expect(webSocketProvider.getStatus()).toBe('connecting');
+ await waitForOpenConnection(webSocketProvider);
});
it('should getStatus `connected`', async () => {
- await waitForOpenConnection(webSocketProvider, currentAttempt);
+ await waitForOpenConnection(webSocketProvider);
expect(webSocketProvider.getStatus()).toBe('connected');
});
it('should getStatus `disconnected`', async () => {
- await waitForOpenConnection(webSocketProvider, currentAttempt);
+ await waitForOpenConnection(webSocketProvider);
webSocketProvider.disconnect();
+ await waitForCloseConnection(webSocketProvider);
expect(webSocketProvider.getStatus()).toBe('disconnected');
});
});
diff --git a/packages/web3-types/CHANGELOG.md b/packages/web3-types/CHANGELOG.md
index a9d00e08735..0ce4a80b3dc 100644
--- a/packages/web3-types/CHANGELOG.md
+++ b/packages/web3-types/CHANGELOG.md
@@ -58,3 +58,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Make the `request` method of `EIP1193Provider` class, compatible with EIP 1193 (#5591)
## [Unreleased]
+
+### Changed
+
+- These types were added: ProviderRpcError, EthSubscription, ProviderMessage, ProviderConnectInfo (#5683)
diff --git a/packages/web3-types/src/web3_api_types.ts b/packages/web3-types/src/web3_api_types.ts
index 472b60ed91d..1cb7bcf5a4f 100644
--- a/packages/web3-types/src/web3_api_types.ts
+++ b/packages/web3-types/src/web3_api_types.ts
@@ -17,6 +17,24 @@ along with web3.js. If not, see .
import { JsonRpcId, JsonRpcIdentifier } from './json_rpc_types';
+export interface ProviderMessage {
+ readonly type: string;
+ readonly data: unknown;
+}
+export interface EthSubscription extends ProviderMessage {
+ readonly type: 'eth_subscription';
+ readonly data: {
+ readonly subscription: string;
+ readonly result: unknown;
+ };
+}
+export interface ProviderRpcError extends Error {
+ code: number;
+ data?: unknown;
+}
+export interface ProviderConnectInfo {
+ readonly chainId: string;
+}
export type Web3APISpec = Record any> | unknown;
export type Web3APIMethod = string & keyof Exclude;
export type Web3APIParams<
diff --git a/packages/web3-types/src/web3_base_provider.ts b/packages/web3-types/src/web3_base_provider.ts
index 067cae9cfea..c602cff5f32 100644
--- a/packages/web3-types/src/web3_base_provider.ts
+++ b/packages/web3-types/src/web3_base_provider.ts
@@ -27,16 +27,33 @@ import {
JsonRpcResult,
JsonRpcSubscriptionResult,
} from './json_rpc_types';
-import { Web3APISpec, Web3APIMethod, Web3APIReturnType, Web3APIPayload } from './web3_api_types';
+import {
+ Web3APISpec,
+ Web3APIMethod,
+ Web3APIReturnType,
+ Web3APIPayload,
+ ProviderConnectInfo,
+ ProviderRpcError,
+} from './web3_api_types';
import { Web3EthExecutionAPI } from './apis/web3_eth_execution_api';
+import { Web3DeferredPromise } from './web3_deferred_promise_type';
const symbol = Symbol.for('web3/base-provider');
+export interface SocketRequestItem<
+ API extends Web3APISpec,
+ Method extends Web3APIMethod,
+ ResponseType,
+> {
+ payload: Web3APIPayload;
+ deferredPromise: Web3DeferredPromise;
+}
+
// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1193.md#connectivity
export type Web3ProviderStatus = 'connecting' | 'connected' | 'disconnected';
export type Web3ProviderEventCallback = (
- error: Error | undefined,
+ error: Error | ProviderRpcError | undefined,
result?: JsonRpcSubscriptionResult | JsonRpcNotification,
) => void;
@@ -137,22 +154,23 @@ export abstract class Web3BaseProvider(args: Web3APIPayload): Promise>;
// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1193.md#events
+
+ public abstract on(
+ type: 'disconnect',
+ callback: Web3ProviderEventCallback,
+ ): void;
public abstract on(
- type: 'message' | 'disconnect' | string,
+ type: 'message' | string,
callback: Web3ProviderEventCallback,
): void;
public abstract on(
type: 'connect' | 'chainChanged',
- callback: Web3ProviderEventCallback<{
- readonly [key: string]: unknown;
- readonly chainId: string;
- }>,
+ callback: Web3ProviderEventCallback,
): void;
public abstract on(
type: 'accountsChanged',
callback: Web3ProviderEventCallback<{
- readonly [key: string]: unknown;
- readonly accountsChanged: string[];
+ readonly accounts: string[];
}>,
): void;
public abstract removeListener(type: string, callback: Web3ProviderEventCallback): void;
@@ -163,7 +181,7 @@ export abstract class Web3BaseProvider.
+*/
+import {
+ EthExecutionAPI,
+ JsonRpcBatchRequest,
+ JsonRpcBatchResponse,
+ JsonRpcId,
+ JsonRpcNotification,
+ JsonRpcRequest,
+ JsonRpcResponse,
+ JsonRpcResponseWithResult,
+ JsonRpcResult,
+ SocketRequestItem,
+ Web3APIMethod,
+ Web3APIPayload,
+ Web3APIReturnType,
+ Web3APISpec,
+ Web3ProviderEventCallback,
+} from 'web3-types';
+import {
+ InvalidClientError,
+ PendingRequestsOnReconnectingError,
+ RequestAlreadySentError,
+ ResponseError,
+ Web3WSProviderError,
+} from 'web3-errors';
+import { Eip1193Provider } from './web3_eip1193_provider';
+import { ChunkResponseParser } from './chunk_response_parser';
+import { isNullish } from './validation';
+import { Web3DeferredPromise } from './web3_deferred_promise';
+import * as jsonRpc from './json_rpc';
+
+type ReconnectOptions = {
+ autoReconnect: boolean;
+ delay: number;
+ maxAttempts: number;
+};
+
+type EventType = 'message' | 'connect' | 'disconnect' | 'chainChanged' | 'accountsChanged' | string;
+
+export abstract class SocketProvider<
+ MessageEvent,
+ CloseEvent,
+ ErrorEvent,
+ API extends Web3APISpec = EthExecutionAPI,
+> extends Eip1193Provider {
+ protected readonly _socketPath: string;
+ protected readonly chunkResponseParser: ChunkResponseParser;
+ /* eslint-disable @typescript-eslint/no-explicit-any */
+ protected readonly _pendingRequestsQueue: Map>;
+ /* eslint-disable @typescript-eslint/no-explicit-any */
+ protected readonly _sentRequestsQueue: Map>;
+ protected _reconnectAttempts!: number;
+ protected readonly _providerOptions?: object;
+ protected readonly _reconnectOptions: ReconnectOptions;
+ protected _socketConnection?: unknown;
+ protected readonly _onMessageHandler: (event: MessageEvent) => void;
+ protected readonly _onOpenHandler: () => void;
+ protected readonly _onCloseHandler: (event: CloseEvent) => void;
+ protected readonly _onErrorHandler: (event: ErrorEvent) => void;
+ public constructor(socketPath: string, options?: object, reconnectOptions?: object) {
+ super();
+ this._onMessageHandler = this._onMessage.bind(this);
+ this._onOpenHandler = this._onConnect.bind(this);
+ this._onCloseHandler = this._onCloseEvent.bind(this);
+ this._onErrorHandler = this._onError.bind(this);
+ if (!this._validateProviderPath(socketPath)) throw new InvalidClientError(socketPath);
+
+ this._socketPath = socketPath;
+ this._providerOptions = options;
+
+ const DEFAULT_PROVIDER_RECONNECTION_OPTIONS = {
+ autoReconnect: true,
+ delay: 5000,
+ maxAttempts: 5,
+ };
+
+ this._reconnectOptions = {
+ ...DEFAULT_PROVIDER_RECONNECTION_OPTIONS,
+ ...(reconnectOptions ?? {}),
+ };
+
+ this._pendingRequestsQueue = new Map>();
+ this._sentRequestsQueue = new Map>();
+
+ this._init();
+ this.connect();
+ this.chunkResponseParser = new ChunkResponseParser();
+ this.chunkResponseParser.onError(() => {
+ this._clearQueues();
+ });
+ }
+ protected _init() {
+ this._reconnectAttempts = 0;
+ }
+ public abstract connect(): void;
+ protected abstract _addSocketListeners(): void;
+ protected abstract _removeSocketListeners(): void;
+ protected abstract _onCloseEvent(_event: unknown): void;
+ protected abstract _sendToSocket(_payload: Web3APIPayload): void;
+ protected abstract _parseResponses(_event: MessageEvent): JsonRpcResponse[];
+ protected abstract _clearQueues(_event?: unknown): void;
+ protected abstract _closeSocketConnection(_code?: number, _data?: string): void;
+ // eslint-disable-next-line class-methods-use-this
+ protected _validateProviderPath(path: string): boolean {
+ return !!path;
+ }
+ // eslint-disable-next-line class-methods-use-this
+ public supportsSubscriptions(): boolean {
+ return true;
+ }
+ public on(type: EventType, callback: Web3ProviderEventCallback): void {
+ this._eventEmitter.on(type, callback);
+ }
+ public once(type: EventType, callback: Web3ProviderEventCallback): void {
+ this._eventEmitter.once(type, callback);
+ }
+ public removeListener(type: EventType, callback: Web3ProviderEventCallback): void {
+ this._eventEmitter.removeListener(type, callback);
+ }
+
+ protected _onDisconnect(code?: number, data?: string) {
+ super._onDisconnect(code, data);
+ }
+ public disconnect(code?: number, data?: string): void {
+ this._removeSocketListeners();
+ if (this.getStatus() !== 'disconnected') {
+ this._closeSocketConnection(code, data);
+ }
+ this._onDisconnect(code, data);
+ }
+ public removeAllListeners(type: string): void {
+ this._eventEmitter.removeAllListeners(type);
+ }
+
+ protected _onError(event: ErrorEvent): void {
+ this._eventEmitter.emit('error', event);
+ }
+
+ public reset(): void {
+ this._sentRequestsQueue.clear();
+ this._pendingRequestsQueue.clear();
+
+ this._init();
+ this._removeSocketListeners();
+ this._addSocketListeners();
+ }
+ protected _reconnect(): void {
+ if (this._sentRequestsQueue.size > 0) {
+ this._sentRequestsQueue.forEach(
+ (request: SocketRequestItem, key: JsonRpcId) => {
+ request.deferredPromise.reject(new PendingRequestsOnReconnectingError());
+ this._sentRequestsQueue.delete(key);
+ },
+ );
+ }
+
+ if (this._reconnectAttempts < this._reconnectOptions.maxAttempts) {
+ setTimeout(() => {
+ this._reconnectAttempts += 1;
+ this._removeSocketListeners();
+ this.connect();
+ }, this._reconnectOptions.delay);
+ }
+ }
+
+ public async request<
+ Method extends Web3APIMethod,
+ ResultType = Web3APIReturnType,
+ >(request: Web3APIPayload): Promise> {
+ if (isNullish(this._socketConnection)) {
+ throw new Error('Connection is undefined');
+ }
+ // if socket disconnected - open connection
+ if (this.getStatus() === 'disconnected') {
+ this.connect();
+ }
+
+ const requestId = jsonRpc.isBatchRequest(request)
+ ? (request as unknown as JsonRpcBatchRequest)[0].id
+ : (request as unknown as JsonRpcRequest).id;
+
+ if (!requestId) {
+ throw new Web3WSProviderError('Request Id not defined');
+ }
+
+ if (this._sentRequestsQueue.has(requestId)) {
+ throw new RequestAlreadySentError(requestId);
+ }
+
+ const deferredPromise = new Web3DeferredPromise>();
+
+ const reqItem: SocketRequestItem> = {
+ payload: request,
+ deferredPromise,
+ };
+
+ if (this.getStatus() === 'connecting') {
+ this._pendingRequestsQueue.set(requestId, reqItem);
+
+ return reqItem.deferredPromise;
+ }
+
+ this._sentRequestsQueue.set(requestId, reqItem);
+
+ try {
+ this._sendToSocket(reqItem.payload);
+ } catch (error) {
+ this._sentRequestsQueue.delete(requestId);
+ throw error;
+ }
+
+ return deferredPromise;
+ }
+
+ protected _onConnect() {
+ this._reconnectAttempts = 0;
+ super._onConnect();
+ this._sendPendingRequests();
+ }
+
+ private _sendPendingRequests() {
+ for (const [id, value] of this._pendingRequestsQueue.entries()) {
+ this._sendToSocket(value.payload as Web3APIPayload);
+ this._pendingRequestsQueue.delete(id);
+ this._sentRequestsQueue.set(id, value);
+ }
+ }
+
+ protected _onMessage(event: MessageEvent): void {
+ const responses = this._parseResponses(event);
+ if (!responses) {
+ return;
+ }
+ for (const response of responses) {
+ if (
+ jsonRpc.isResponseWithNotification(response as JsonRpcNotification) &&
+ (response as JsonRpcNotification).method.endsWith('_subscription')
+ ) {
+ this._eventEmitter.emit('message', undefined, response);
+ return;
+ }
+
+ const requestId = jsonRpc.isBatchResponse(response)
+ ? (response as unknown as JsonRpcBatchResponse)[0].id
+ : (response as unknown as JsonRpcResponseWithResult).id;
+
+ const requestItem = this._sentRequestsQueue.get(requestId);
+
+ if (!requestItem) {
+ return;
+ }
+
+ if (jsonRpc.isBatchResponse(response) || jsonRpc.isResponseWithResult(response)) {
+ this._eventEmitter.emit('message', undefined, response);
+ requestItem.deferredPromise.resolve(response);
+ } else {
+ this._eventEmitter.emit('message', response, undefined);
+ requestItem?.deferredPromise.reject(new ResponseError(response));
+ }
+
+ this._sentRequestsQueue.delete(requestId);
+ }
+ }
+}
diff --git a/packages/web3-utils/src/web3_eip1193_provider.ts b/packages/web3-utils/src/web3_eip1193_provider.ts
new file mode 100644
index 00000000000..74585e7e03a
--- /dev/null
+++ b/packages/web3-utils/src/web3_eip1193_provider.ts
@@ -0,0 +1,118 @@
+/*
+This file is part of web3.js.
+
+web3.js is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+web3.js is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with web3.js. If not, see .
+*/
+import {
+ EthExecutionAPI,
+ HexString,
+ Web3APIMethod,
+ Web3APIPayload,
+ Web3APISpec,
+ Web3BaseProvider,
+} from 'web3-types';
+import { EventEmitter } from 'events';
+import { toPayload } from './json_rpc';
+
+export abstract class Eip1193Provider<
+ API extends Web3APISpec = EthExecutionAPI,
+> extends Web3BaseProvider {
+ protected readonly _eventEmitter: EventEmitter = new EventEmitter();
+ private _chainId: HexString = '';
+ private _accounts: HexString[] = [];
+
+ private async _getChainId(): Promise {
+ const data = await (this as Web3BaseProvider).request<
+ Web3APIMethod,
+ ResponseType
+ >(
+ toPayload({
+ method: 'eth_chainId',
+ params: [],
+ }) as Web3APIPayload>,
+ );
+ return data?.result ?? '';
+ }
+
+ private async _getAccounts(): Promise {
+ const data = await (this as Web3BaseProvider).request, HexString[]>(
+ toPayload({
+ method: 'eth_accounts',
+ params: [],
+ }) as Web3APIPayload>,
+ );
+ return data?.result ?? [];
+ }
+
+ protected _onConnect() {
+ Promise.all([
+ this._getChainId()
+ .then(chainId => {
+ if (chainId !== this._chainId) {
+ this._chainId = chainId;
+ this._eventEmitter.emit('chainChanged', undefined, {
+ chainId: this._chainId,
+ });
+ }
+ })
+ .catch(err => {
+ // todo: add error handler
+ // eslint-disable-next-line no-console
+ console.error(err);
+ }),
+
+ this._getAccounts()
+ .then(accounts => {
+ if (
+ !(
+ this._accounts.length === accounts.length &&
+ accounts.every(v => accounts.includes(v))
+ )
+ ) {
+ this._accounts = accounts;
+ this._onAccountsChanged();
+ }
+ })
+ .catch(err => {
+ // todo: add error handler
+ // eslint-disable-next-line no-console
+ console.error(err);
+ }),
+ ])
+ .then(() =>
+ this._eventEmitter.emit('connect', undefined, {
+ chainId: this._chainId,
+ }),
+ )
+ .catch(err => {
+ // todo: add error handler
+ // eslint-disable-next-line no-console
+ console.error(err);
+ });
+ }
+
+ protected _onDisconnect(code?: number, data?: unknown) {
+ this._eventEmitter.emit('disconnect', {
+ code,
+ data,
+ });
+ }
+
+ private _onAccountsChanged() {
+ // get chainId and safe to local
+ this._eventEmitter.emit('accountsChanged', undefined, {
+ accounts: this._accounts,
+ });
+ }
+}
diff --git a/packages/web3/test/integration/ipc.test.ts b/packages/web3/test/integration/ipc.test.ts
index 8de98d503b4..93da73f5681 100644
--- a/packages/web3/test/integration/ipc.test.ts
+++ b/packages/web3/test/integration/ipc.test.ts
@@ -21,6 +21,7 @@ import {
describeIf,
isIpc,
closeOpenConnection,
+ waitForSocketConnect,
} from '../shared_fixtures/system_tests_utils';
import Web3 from '../../src/index';
@@ -43,7 +44,7 @@ describe('Web3 instance', () => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
expect(web3).toBeInstanceOf(Web3);
- await (web3.provider as IpcProvider).waitForConnection();
+ await waitForSocketConnect(web3.provider as IpcProvider);
expect((web3.provider as IpcProvider).getStatus()).toBe('connected');
});
});
diff --git a/scripts/geth_binary.sh b/scripts/geth_binary.sh
index 74cda36b273..33dbdefc373 100755
--- a/scripts/geth_binary.sh
+++ b/scripts/geth_binary.sh
@@ -21,11 +21,11 @@ getOS(){
getDownloadLink(){
case "$OS" in
SOLARIS*) LINK="-" ;;
- OSX*) LINK="https://gethstore.blob.core.windows.net/builds/geth-darwin-amd64-1.10.19-23bee162.tar.gz" ;;
- LINUX*) LINK="https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.10.19-23bee162.tar.gz" ;;
- BSD*) LINK="https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.10.19-23bee162.tar.gz" ;;
- WINDOWS*) LINK="-" ;;
- "ALSO WINDOWS"*) LINK="-" ;;
+ OSX*) LINK="https://gethstore.blob.core.windows.net/builds/geth-darwin-amd64-1.10.26-e5eb32ac.tar.gz" ;;
+ LINUX*) LINK="https://gethstore.blob.core.windows.net/builds/geth-linux-386-1.10.26-e5eb32ac.tar.gz" ;;
+ BSD*) LINK="https://gethstore.blob.core.windows.net/builds/geth-darwin-amd64-1.10.26-e5eb32ac.tar.gz" ;;
+ WINDOWS*) LINK="https://gethstore.blob.core.windows.net/builds/geth-windows-386-1.10.26-e5eb32ac.exe" ;;
+ "ALSO WINDOWS"*) LINK="https://gethstore.blob.core.windows.net/builds/geth-windows-386-1.10.26-e5eb32ac.exe" ;;
*) LINK="-" ;;
esac
}
diff --git a/scripts/system_tests_utils.ts b/scripts/system_tests_utils.ts
index 7eee73e0228..2004a2561ec 100644
--- a/scripts/system_tests_utils.ts
+++ b/scripts/system_tests_utils.ts
@@ -16,7 +16,7 @@ along with web3.js. If not, see .
*/
// eslint-disable-next-line import/no-extraneous-dependencies
-import { ETH_DATA_FORMAT, format } from 'web3-utils';
+import { ETH_DATA_FORMAT, format, SocketProvider } from 'web3-utils';
// eslint-disable-next-line import/no-extraneous-dependencies
import {
create,
@@ -39,6 +39,11 @@ import {
Transaction,
Receipt,
KeyStore,
+ ProviderConnectInfo,
+ Web3ProviderEventCallback,
+ ProviderRpcError,
+ JsonRpcSubscriptionResult,
+ JsonRpcNotification,
} from 'web3-types';
// eslint-disable-next-line import/no-extraneous-dependencies
import { Personal } from 'web3-eth-personal';
@@ -47,6 +52,7 @@ import Web3 from 'web3';
// eslint-disable-next-line import/no-extraneous-dependencies
import { NonPayableMethodObject } from 'web3-eth-contract';
+// eslint-disable-next-line import/no-extraneous-dependencies
import accountsString from './accounts.json';
/**
@@ -74,6 +80,7 @@ export const isChrome: boolean = getSystemTestEngine() === 'chrome';
export const isFirefox: boolean = getSystemTestEngine() === 'firefox';
export const isElectron: boolean = getSystemTestEngine() === 'electron';
export const isNode: boolean = getSystemTestEngine() === 'isNode';
+export const isSocket: boolean = isWs || isIpc;
export const isBrowser: boolean = ['chrome', 'firefox'].includes(getSystemTestEngine());
export const getSystemTestMnemonic = (): string => getEnvVar('WEB3_SYSTEM_TEST_MNEMONIC') ?? '';
@@ -88,8 +95,8 @@ export const itIf = (condition: (() => boolean) | boolean) =>
export const describeIf = (condition: (() => boolean) | boolean) =>
(typeof condition === 'function' ? condition() : condition) ? describe : describe.skip;
-const maxNumberOfAttempts = 10;
-const intervalTime = 5000; // ms
+const maxNumberOfAttempts = 100;
+const intervalTime = 500; // ms
export const waitForOpenConnection = async (
web3Context: Web3Context,
@@ -97,7 +104,7 @@ export const waitForOpenConnection = async (
status = 'connected',
) =>
new Promise((resolve, reject) => {
- if (!getSystemTestProvider().startsWith('ws')) {
+ if (!isSocket) {
resolve();
return;
}
@@ -118,7 +125,7 @@ export const waitForOpenConnection = async (
});
export const closeOpenConnection = async (web3Context: Web3Context) => {
- if (!isWs && !isIpc) {
+ if (!isSocket) {
return;
}
@@ -390,3 +397,28 @@ export const createLocalAccount = async (web3: Web3) => {
web3.eth.accounts.wallet.add(account);
return account;
};
+/* eslint-disable @typescript-eslint/no-unsafe-call */
+/* eslint-disable @typescript-eslint/no-unsafe-member-access */
+// eslint-disable-next-line arrow-body-style
+export const waitForSocketConnect = async (provider: SocketProvider) => {
+ return new Promise(resolve => {
+ provider.on('connect', ((
+ _error: Error | ProviderRpcError | undefined,
+ data: JsonRpcSubscriptionResult | JsonRpcNotification | undefined,
+ ) => {
+ resolve(data as unknown as ProviderConnectInfo);
+ }) as Web3ProviderEventCallback);
+ });
+};
+
+// eslint-disable-next-line arrow-body-style
+export const waitForSocketDisconnect = async (provider: SocketProvider) => {
+ return new Promise(resolve => {
+ provider.on('disconnect', ((
+ _error: ProviderRpcError | Error | undefined,
+ data: JsonRpcSubscriptionResult | JsonRpcNotification | undefined,
+ ) => {
+ resolve(data as unknown as ProviderRpcError);
+ }) as Web3ProviderEventCallback);
+ });
+};