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

Commit 58beddf

Browse files
Stop waiting after transactionBlockTimeout (#5294)
* stop waiting after `transactionBlockTimeout` * fix an issue inside `transactionPollingTimeout` at `waitForTransactionReceipt` * refactor the code for `waitForTransactionReceipt` * added 3 reusable utility functions at web3-utils: `pollTillDefined`, `rejectIfTimeout` and `rejectIfConditionAtInterval` * add `removeAllListeners` after finish testing `watch polling transaction` * add a `warn` if the socket connection raises an error while closing * tiny fix and improvements to tests at web-eth * update CHANGELOG.md files
1 parent 88679d3 commit 58beddf

22 files changed

+494
-180
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -748,6 +748,12 @@ should use 4.0.1-alpha.0 for testing.
748748
- Added the error code `ERR_TX_GAS_MISMATCH` and used it inside `TransactionGasMismatchError` (#5462)
749749
- Added `SignatureError` to `web3-errors/src/errors/signature_errors.ts` (moved from `web3-eth/src/errors.ts`) (#5462)
750750
- Added the errors' classes to `web3-errors/src/errors/transaction_errors.ts` from `web3-eth/src/errors.ts` (#5462)
751+
- Added `TransactionBlockTimeoutError` class and its error code `ERR_TX_BLOCK_TIMEOUT` (#5294)
752+
753+
#### web3-eth
754+
755+
- [setimmediate](https://github.com/yuzujs/setImmediate) package to polyfill [setImmediate](https://nodejs.org/api/timers.html#setimmediatecallback-args) for browsers (#5450)
756+
- Implemented the logic for `transactionBlockTimeout` (#5294)
751757

752758
#### web3-eth-abi
753759

@@ -759,6 +765,10 @@ should use 4.0.1-alpha.0 for testing.
759765
- Decoding error data, using Error ABI if available, according to EIP-838. (#5434)
760766
- The class `Web3ContractError` is moved from this package to `web3-error`. (#5434)
761767

768+
#### web3-utils
769+
770+
- Added and exported three reusable utility functions: `pollTillDefined`, `rejectIfTimeout` and `rejectIfConditionAtInterval` which are useful when dealing with promises that involves polling, rejecting after timeout or rejecting if a condition was met when calling repeatably at every time intervals.
771+
762772
### Changed
763773

764774
#### web3-error

packages/web3-errors/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
4545
- Added the error code `ERR_TX_GAS_MISMATCH` and used it inside `TransactionGasMismatchError` (#5462)
4646
- Added `SignatureError` to `web3-errors/src/errors/signature_errors.ts` (moved from `web3-eth/src/errors.ts`) (#5462)
4747
- Added the errors' classes to `web3-errors/src/errors/transaction_errors.ts` from `web3-eth/src/errors.ts` (#5462)
48+
- Added `TransactionBlockTimeoutError` class and its error code `ERR_TX_BLOCK_TIMEOUT` (#5294)
4849

4950
### Changed
5051

packages/web3-errors/src/error_codes.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ export const ERR_TX_LOCAL_WALLET_NOT_AVAILABLE = 429;
7676

7777
export const ERR_TX_NOT_FOUND = 430;
7878
export const ERR_TX_SEND_TIMEOUT = 431;
79+
export const ERR_TX_BLOCK_TIMEOUT = 432;
7980

8081
export const ERR_TX_SIGNING = 433;
8182
export const ERR_TX_GAS_MISMATCH = 434;

packages/web3-errors/src/errors/transaction_errors.ts

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { Bytes, HexString, Numbers, TransactionReceipt } from 'web3-types';
2121
import {
2222
ERR_RAW_TX_UNDEFINED,
2323
ERR_TX,
24+
ERR_TX_BLOCK_TIMEOUT,
2425
ERR_TX_CONTRACT_NOT_STORED,
2526
ERR_TX_CHAIN_ID_MISMATCH,
2627
ERR_TX_DATA_AND_INPUT,
@@ -40,19 +41,19 @@ import {
4041
ERR_TX_MISSING_CUSTOM_CHAIN_ID,
4142
ERR_TX_MISSING_GAS,
4243
ERR_TX_NO_CONTRACT_ADDRESS,
44+
ERR_TX_NOT_FOUND,
4345
ERR_TX_OUT_OF_GAS,
46+
ERR_TX_POLLING_TIMEOUT,
47+
ERR_TX_RECEIPT_MISSING_BLOCK_NUMBER,
48+
ERR_TX_RECEIPT_MISSING_OR_BLOCKHASH_NULL,
4449
ERR_TX_REVERT_INSTRUCTION,
4550
ERR_TX_REVERT_TRANSACTION,
4651
ERR_TX_REVERT_WITHOUT_REASON,
47-
ERR_TX_NOT_FOUND,
4852
ERR_TX_SEND_TIMEOUT,
4953
ERR_TX_SIGNING,
5054
ERR_TX_UNABLE_TO_POPULATE_NONCE,
5155
ERR_TX_UNSUPPORTED_EIP_1559,
5256
ERR_TX_UNSUPPORTED_TYPE,
53-
ERR_TX_POLLING_TIMEOUT,
54-
ERR_TX_RECEIPT_MISSING_OR_BLOCKHASH_NULL,
55-
ERR_TX_RECEIPT_MISSING_BLOCK_NUMBER,
5657
} from '../error_codes';
5758
import { InvalidValueError, Web3Error } from '../web3_error_base';
5859

@@ -157,6 +158,7 @@ export class TransactionNotFound extends TransactionError {
157158
this.code = ERR_TX_NOT_FOUND;
158159
}
159160
}
161+
160162
export class InvalidTransactionWithSender extends InvalidValueError {
161163
public code = ERR_TX_INVALID_SENDER;
162164

@@ -410,6 +412,22 @@ export class TransactionPollingTimeoutError extends Web3Error {
410412
}
411413
}
412414

415+
export class TransactionBlockTimeoutError extends Web3Error {
416+
public code = ERR_TX_BLOCK_TIMEOUT;
417+
418+
public constructor(value: {
419+
starterBlockNumber: number;
420+
numberOfBlocks: number;
421+
transactionHash?: Bytes;
422+
}) {
423+
super(
424+
`Transaction started at ${value.starterBlockNumber} but was not mined within ${
425+
value.numberOfBlocks
426+
} blocks. ${transactionTimeoutHint(value.transactionHash)}`,
427+
);
428+
}
429+
}
430+
413431
export class TransactionMissingReceiptOrBlockHashError extends InvalidValueError {
414432
public code = ERR_TX_RECEIPT_MISSING_OR_BLOCKHASH_NULL;
415433

packages/web3-eth-personal/test/integration/personal.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import {
2424
createAccount,
2525
createNewAccount,
2626
createTempAccount,
27-
closeOpenConnection
27+
closeOpenConnection,
2828
} from '../fixtures/system_test_utils';
2929

3030
describe('personal integration tests', () => {

packages/web3-eth/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
4040
### Added
4141

4242
- [setimmediate](https://github.com/yuzujs/setImmediate) package to polyfill [setImmediate](https://nodejs.org/api/timers.html#setimmediatecallback-args) for browsers (#5450)
43+
- Implemented the logic for `transactionBlockTimeout` (#5294)
4344

4445
### Removed
4546

packages/web3-eth/src/rpc_method_wrappers.ts

Lines changed: 30 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -41,21 +41,11 @@ import {
4141
TransactionWithLocalWalletIndex,
4242
} from 'web3-types';
4343
import { Web3Context, Web3PromiEvent } from 'web3-core';
44-
import {
45-
ETH_DATA_FORMAT,
46-
FormatType,
47-
DataFormat,
48-
DEFAULT_RETURN_FORMAT,
49-
format,
50-
waitWithTimeout,
51-
} from 'web3-utils';
44+
import { ETH_DATA_FORMAT, FormatType, DataFormat, DEFAULT_RETURN_FORMAT, format } from 'web3-utils';
5245
import { isBlockTag, isBytes, isNullish, isString } from 'web3-validator';
53-
import {
54-
TransactionError,
55-
TransactionRevertError,
56-
SignatureError,
57-
TransactionSendTimeoutError,
58-
} from 'web3-errors';
46+
import { SignatureError, TransactionError, TransactionRevertError } from 'web3-errors';
47+
48+
import { decodeSignedTransaction } from './utils/decode_signed_transaction';
5949
import * as rpcMethods from './rpc_methods';
6050
import {
6151
accountSchema,
@@ -77,11 +67,12 @@ import { formatTransaction } from './utils/format_transaction';
7767
// eslint-disable-next-line import/no-cycle
7868
import { getTransactionGasPricing } from './utils/get_transaction_gas_pricing';
7969
// eslint-disable-next-line import/no-cycle
70+
import { trySendTransaction } from './utils/try_send_transaction';
71+
// eslint-disable-next-line import/no-cycle
8072
import { waitForTransactionReceipt } from './utils/wait_for_transaction_receipt';
8173
import { watchTransactionForConfirmations } from './utils/watch_transaction_for_confirmations';
8274
import { Web3EthExecutionAPI } from './web3_eth_execution_api';
8375
import { NUMBER_DATA_FORMAT } from './constants';
84-
import { decodeSignedTransaction } from './utils/decode_signed_transaction';
8576

8677
/**
8778
*
@@ -1119,27 +1110,23 @@ export function sendTransaction<
11191110
transactionFormatted as Record<string, unknown>,
11201111
);
11211112

1122-
transactionHash = await waitWithTimeout(
1123-
rpcMethods.sendRawTransaction(
1124-
web3Context.requestManager,
1125-
signedTransaction.rawTransaction,
1126-
),
1127-
web3Context.transactionSendTimeout,
1128-
new TransactionSendTimeoutError({
1129-
numberOfSeconds: web3Context.transactionSendTimeout / 1000,
1130-
transactionHash: signedTransaction.transactionHash,
1131-
}),
1113+
transactionHash = await trySendTransaction(
1114+
web3Context,
1115+
async (): Promise<string> =>
1116+
rpcMethods.sendRawTransaction(
1117+
web3Context.requestManager,
1118+
signedTransaction.rawTransaction,
1119+
),
1120+
signedTransaction.transactionHash,
11321121
);
11331122
} else {
1134-
transactionHash = await waitWithTimeout(
1135-
rpcMethods.sendTransaction(
1136-
web3Context.requestManager,
1137-
transactionFormatted as Partial<TransactionWithSenderAPI>,
1138-
),
1139-
web3Context.transactionSendTimeout,
1140-
new TransactionSendTimeoutError({
1141-
numberOfSeconds: web3Context.transactionSendTimeout / 1000,
1142-
}),
1123+
transactionHash = await trySendTransaction(
1124+
web3Context,
1125+
async (): Promise<string> =>
1126+
rpcMethods.sendTransaction(
1127+
web3Context.requestManager,
1128+
transactionFormatted as Partial<TransactionWithSenderAPI>,
1129+
),
11431130
);
11441131
}
11451132

@@ -1190,6 +1177,7 @@ export function sendTransaction<
11901177
);
11911178
}
11921179
reject(transactionReceiptFormatted as unknown as ResolveType);
1180+
return;
11931181
} else {
11941182
resolve(transactionReceiptFormatted as unknown as ResolveType);
11951183
}
@@ -1340,9 +1328,13 @@ export function sendSignedTransaction<
13401328
// await getRevertReason(web3Context, transaction, returnFormat);
13411329
// }
13421330

1343-
const transactionHash = await rpcMethods.sendRawTransaction(
1344-
web3Context.requestManager,
1345-
signedTransactionFormattedHex,
1331+
const transactionHash = await trySendTransaction(
1332+
web3Context,
1333+
async (): Promise<string> =>
1334+
rpcMethods.sendRawTransaction(
1335+
web3Context.requestManager,
1336+
signedTransactionFormattedHex,
1337+
),
13461338
);
13471339

13481340
if (promiEvent.listenerCount('sent') > 0) {
@@ -1392,6 +1384,7 @@ export function sendSignedTransaction<
13921384
);
13931385
}
13941386
reject(transactionReceiptFormatted as unknown as ResolveType);
1387+
return;
13951388
} else {
13961389
resolve(transactionReceiptFormatted as unknown as ResolveType);
13971390
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
This file is part of web3.js.
3+
4+
web3.js is free software: you can redistribute it and/or modify
5+
it under the terms of the GNU Lesser General Public License as published by
6+
the Free Software Foundation, either version 3 of the License, or
7+
(at your option) any later version.
8+
9+
web3.js is distributed in the hope that it will be useful,
10+
but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
GNU Lesser General Public License for more details.
13+
14+
You should have received a copy of the GNU Lesser General Public License
15+
along with web3.js. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
import { EthExecutionAPI, Bytes } from 'web3-types';
18+
import { Web3Context } from 'web3-core';
19+
import { rejectIfConditionAtInterval } from 'web3-utils';
20+
21+
import { TransactionBlockTimeoutError } from 'web3-errors';
22+
import { NUMBER_DATA_FORMAT } from '../constants';
23+
// eslint-disable-next-line import/no-cycle
24+
import { getBlockNumber } from '../rpc_method_wrappers';
25+
26+
/* TODO: After merge, there will be constant block mining time (exactly 12 second each block, except slot missed that currently happens in <1% of slots. ) so we can optimize following function
27+
for POS NWs, we can skip checking getBlockNumber(); after interval and calculate only based on time that certain num of blocked are mined after that for internal double check, can do one getBlockNumber() call and timeout.
28+
*/
29+
export function rejectIfBlockTimeout(
30+
web3Context: Web3Context<EthExecutionAPI>,
31+
starterBlockNumber: number,
32+
interval: number,
33+
transactionHash?: Bytes,
34+
): [NodeJS.Timer, Promise<never>] {
35+
return rejectIfConditionAtInterval(async () => {
36+
let lastBlockNumber;
37+
try {
38+
lastBlockNumber = await getBlockNumber(web3Context, NUMBER_DATA_FORMAT);
39+
} catch (error) {
40+
console.warn('An error happen while trying to get the block number', error);
41+
return undefined;
42+
}
43+
const numberOfBlocks = lastBlockNumber - starterBlockNumber;
44+
if (numberOfBlocks >= web3Context.transactionBlockTimeout) {
45+
return new TransactionBlockTimeoutError({
46+
starterBlockNumber,
47+
numberOfBlocks,
48+
transactionHash,
49+
});
50+
}
51+
return undefined;
52+
}, interval);
53+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
This file is part of web3.js.
3+
4+
web3.js is free software: you can redistribute it and/or modify
5+
it under the terms of the GNU Lesser General Public License as published by
6+
the Free Software Foundation, either version 3 of the License, or
7+
(at your option) any later version.
8+
9+
web3.js is distributed in the hope that it will be useful,
10+
but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
GNU Lesser General Public License for more details.
13+
14+
You should have received a copy of the GNU Lesser General Public License
15+
along with web3.js. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
import { Web3Context } from 'web3-core';
18+
import { EthExecutionAPI, Bytes } from 'web3-types';
19+
import { AsyncFunction, rejectIfTimeout } from 'web3-utils';
20+
21+
import { TransactionSendTimeoutError } from 'web3-errors';
22+
import { NUMBER_DATA_FORMAT } from '../constants';
23+
// eslint-disable-next-line import/no-cycle
24+
import { rejectIfBlockTimeout } from './reject_if_block_timeout';
25+
// eslint-disable-next-line import/no-cycle
26+
import { getBlockNumber } from '../rpc_method_wrappers';
27+
28+
/**
29+
* An internal function to send a transaction or throws if sending did not finish during the timeout during the blocks-timeout.
30+
* @param web3Context the context to read the configurations from
31+
* @param sendTransactionFunc the function that will send the transaction (could be sendTransaction or sendRawTransaction)
32+
* @param transactionHash to be used inside the exception message if there will be any exceptions.
33+
* @returns the Promise<string> returned by the `sendTransactionFunc`.
34+
*/
35+
export async function trySendTransaction(
36+
web3Context: Web3Context<EthExecutionAPI>,
37+
sendTransactionFunc: AsyncFunction<string>,
38+
transactionHash?: Bytes,
39+
): Promise<string> {
40+
const pollingInterval = web3Context.transactionPollingInterval;
41+
42+
const [timeoutId, rejectOnTimeout] = rejectIfTimeout(
43+
web3Context.transactionSendTimeout,
44+
new TransactionSendTimeoutError({
45+
numberOfSeconds: web3Context.transactionSendTimeout / 1000,
46+
transactionHash,
47+
}),
48+
);
49+
50+
const starterBlockNumber = await getBlockNumber(web3Context, NUMBER_DATA_FORMAT);
51+
const [intervalId, rejectOnBlockTimeout] = rejectIfBlockTimeout(
52+
web3Context,
53+
starterBlockNumber,
54+
pollingInterval,
55+
transactionHash,
56+
);
57+
58+
try {
59+
return await Promise.race([sendTransactionFunc(), rejectOnTimeout, rejectOnBlockTimeout]);
60+
} finally {
61+
clearTimeout(timeoutId);
62+
clearInterval(intervalId);
63+
}
64+
}

0 commit comments

Comments
 (0)