Skip to content
This repository was archived by the owner on Mar 5, 2025. It is now read-only.

Commit 750411c

Browse files
authored
watchTransactionBySubscription fallback fix (#7118)
* watchTransactionByPolling fix * watchTransactionBySubscription fallback test
1 parent bfe2769 commit 750411c

File tree

2 files changed

+90
-81
lines changed

2 files changed

+90
-81
lines changed

packages/web3-eth/src/utils/watch_transaction_by_polling.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,10 @@ export const watchTransactionByPolling = <
5656
let confirmations = 1;
5757
const intervalId = setInterval(() => {
5858
(async () => {
59-
if (confirmations >= web3Context.transactionConfirmationBlocks)
59+
if (confirmations >= web3Context.transactionConfirmationBlocks){
6060
clearInterval(intervalId);
61+
return;
62+
}
6163

6264
const nextBlock = await ethRpcMethods.getBlockByNumber(
6365
web3Context.requestManager,

packages/web3-eth/test/unit/utils/watch_transaction_by_subscription.test.ts

Lines changed: 87 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -14,117 +14,124 @@ GNU Lesser General Public License for more details.
1414
You should have received a copy of the GNU Lesser General Public License
1515
along with web3.js. If not, see <http://www.gnu.org/licenses/>.
1616
*/
17-
import { Web3Context, Web3RequestManager } from 'web3-core';
18-
import { format } from 'web3-utils';
19-
import { DEFAULT_RETURN_FORMAT, JsonRpcResponseWithResult, Web3EthExecutionAPI } from 'web3-types';
20-
import { ethRpcMethods } from 'web3-rpc-methods';
21-
import { WebSocketProvider } from 'web3-providers-ws';
17+
18+
import { Web3Context } from 'web3-core';
19+
import { DEFAULT_RETURN_FORMAT, Web3EthExecutionAPI } from 'web3-types';
20+
import WebSocketProvider from 'web3-providers-ws';
21+
2222
import * as rpcMethodWrappers from '../../../src/rpc_method_wrappers';
23-
import * as WatchTransactionBySubscription from '../../../src/utils/watch_transaction_by_subscription';
2423
import {
2524
expectedTransactionReceipt,
2625
expectedTransactionHash,
2726
testData,
2827
} from '../rpc_method_wrappers/fixtures/send_signed_transaction';
29-
import { transactionReceiptSchema } from '../../../src/schemas';
30-
import { registeredSubscriptions } from '../../../src';
28+
import { blockMockResult } from '../../fixtures/transactions_data';
29+
3130

32-
jest.mock('web3-rpc-methods');
3331
jest.mock('web3-providers-ws');
34-
jest.mock('../../../src/utils/watch_transaction_by_polling');
3532

3633
const testMessage =
3734
'Title: %s\ninputSignedTransaction: %s\nexpectedTransactionHash: %s\nexpectedTransactionReceipt: %s\n';
3835

39-
async function waitUntilCalled(mock: jest.Mock, timeout = 1000): Promise<jest.Mock> {
40-
return new Promise((resolve, reject) => {
41-
let timeoutId: NodeJS.Timeout | undefined;
42-
const intervalId = setInterval(() => {
43-
if (mock.mock.calls.length > 0) {
44-
clearInterval(intervalId);
45-
if (timeoutId) {
46-
clearTimeout(timeoutId);
47-
}
48-
resolve(mock);
49-
}
50-
}, 100);
51-
timeoutId = setTimeout(() => {
52-
clearInterval(intervalId);
53-
if (timeoutId) {
54-
clearTimeout(timeoutId);
55-
}
56-
reject(new Error('timeout'));
57-
}, timeout);
58-
});
59-
}
6036

6137
describe('watchTransactionBySubscription', () => {
38+
const CONFIRMATION_BLOCKS = 5;
6239
describe('should revert to polling in cases where getting by subscription did not workout', () => {
6340
let web3Context: Web3Context<Web3EthExecutionAPI>;
6441

6542
beforeEach(() => {
66-
jest.spyOn(Web3RequestManager.prototype, 'send').mockImplementation(async () => {
67-
return {} as Promise<unknown>;
68-
});
69-
jest.spyOn(WebSocketProvider.prototype, 'request').mockImplementation(async () => {
70-
return {} as Promise<JsonRpcResponseWithResult<unknown>>;
71-
});
72-
73-
(ethRpcMethods.sendRawTransaction as jest.Mock).mockResolvedValue(
74-
expectedTransactionHash,
75-
);
76-
(ethRpcMethods.getTransactionReceipt as jest.Mock).mockResolvedValue(
77-
expectedTransactionHash,
78-
);
7943
web3Context = new Web3Context({
80-
// dummy provider that does supports subscription
81-
provider: new WebSocketProvider('ws://localhost:8546'),
82-
registeredSubscriptions,
83-
});
44+
provider: new WebSocketProvider('wss://localhost:8546'),}
45+
);
46+
8447
(web3Context.provider as any).supportsSubscriptions = () => true;
48+
web3Context.transactionConfirmationBlocks = CONFIRMATION_BLOCKS;
49+
web3Context.enableExperimentalFeatures.useSubscriptionWhenCheckingBlockTimeout =
50+
true;
51+
8552
});
86-
afterEach(() => {
87-
// to clear the interval inside the subscription function:
88-
web3Context.transactionConfirmationBlocks = 0;
89-
});
90-
let counter = 0;
91-
it.each(testData)(
92-
`should call getBlockNumber if blockHeaderTimeout reached\n ${testMessage}`,
93-
async (_, inputTransaction) => {
94-
if (counter > 0) {
95-
return;
96-
}
97-
counter += 1;
98-
const formattedTransactionReceipt = format(
99-
transactionReceiptSchema,
100-
expectedTransactionReceipt,
101-
DEFAULT_RETURN_FORMAT,
102-
);
10353

104-
web3Context.enableExperimentalFeatures.useSubscriptionWhenCheckingBlockTimeout =
105-
true;
106-
// this will case the function to revert to polling:
107-
web3Context.blockHeaderTimeout = 0;
54+
it.each(testData)(
55+
`should call getBlockByNumber if blockHeaderTimeout reached\n ${testMessage}`,
56+
async (_, inputTransaction,) => {
10857

109-
web3Context.transactionSendTimeout = 2;
58+
let blockNum = 100;
59+
let ethGetBlockByNumberCount = 0;
60+
web3Context.requestManager.send = jest.fn(async (request) => {
61+
62+
if (request.method === 'eth_getBlockByNumber') {
63+
ethGetBlockByNumberCount += 1;
64+
return Promise.resolve(
65+
{ ...blockMockResult.result,
66+
number: (request as any).params[0]
67+
});
68+
}
69+
if (request.method === 'eth_call') {
70+
71+
return Promise.resolve("0x");
72+
}
73+
if (request.method === 'eth_blockNumber') {
74+
blockNum += 1;
75+
return Promise.resolve(blockNum.toString(16));
76+
}
77+
if (request.method === 'eth_sendRawTransaction') {
78+
return Promise.resolve(expectedTransactionHash);
79+
}
80+
if (request.method === 'eth_getTransactionReceipt') {
81+
return Promise.resolve(expectedTransactionReceipt);
82+
}
83+
84+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
85+
return Promise.reject(new Error("Unknown Request")) as any;
86+
});
11087

11188
const promiEvent = rpcMethodWrappers.sendSignedTransaction(
11289
web3Context,
11390
inputTransaction,
11491
DEFAULT_RETURN_FORMAT,
11592
);
116-
// await promiEvent;
117-
WatchTransactionBySubscription.watchTransactionBySubscription({
118-
web3Context,
119-
transactionReceipt: formattedTransactionReceipt,
120-
transactionPromiEvent: promiEvent,
121-
returnFormat: DEFAULT_RETURN_FORMAT,
93+
94+
let confirmationsCount = 0;
95+
const confirmationPromise = new Promise<void>((resolve, reject) => {
96+
97+
const handleConfirmation = (confirmation: { confirmations: bigint }) => {
98+
confirmationsCount += 1;
99+
100+
if (confirmation.confirmations >= CONFIRMATION_BLOCKS) {
101+
resolve();
102+
}
103+
};
104+
105+
const handleError = (_error: any) => {
106+
reject();
107+
};
108+
109+
promiEvent
110+
.on('confirmation', handleConfirmation)
111+
.on('error', handleError)
112+
.then((res) => {
113+
// eslint-disable-next-line jest/no-conditional-expect
114+
expect(res).toBeDefined();
115+
})
116+
.catch(reject);
117+
});
118+
119+
// Wait for the confirmationPromise to resolve or timeout after 5 seconds
120+
let timeoutId;
121+
const timeout = new Promise((_res, reject) => {
122+
timeoutId = setTimeout(() => reject(new Error('Timeout waiting for confirmations')), 500000);
122123
});
123-
await waitUntilCalled(ethRpcMethods.getBlockNumber as jest.Mock, 5000);
124124

125-
await promiEvent;
126-
},
127-
60000,
125+
await Promise.race([confirmationPromise, timeout]);
126+
127+
clearTimeout(timeoutId);
128+
129+
expect(confirmationsCount).toBe(CONFIRMATION_BLOCKS);
130+
expect(ethGetBlockByNumberCount).toBe(CONFIRMATION_BLOCKS - 1); // means polling called getblock 4 times as first confirmation is receipt it self
131+
132+
}
128133
);
134+
135+
129136
});
130137
});

0 commit comments

Comments
 (0)