@lido-nestjs/execution 1.15.0 → 1.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/error/index.d.ts +1 -0
- package/dist/error/transaction-wait-timeout.error.d.ts +11 -0
- package/dist/error/transaction-wait-timeout.error.js +24 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/interfaces/simple-fallback-provider-config.d.ts +1 -0
- package/dist/interfaces/wait-for-transaction.d.ts +14 -0
- package/dist/provider/simple-fallback-json-rpc-batch-provider.d.ts +14 -0
- package/dist/provider/simple-fallback-json-rpc-batch-provider.js +102 -8
- package/package.json +1 -1
package/dist/error/index.d.ts
CHANGED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thrown when waitForTransactionWithFallback times out.
|
|
3
|
+
* lastError distinguishes network issues (present) from slow tx (null).
|
|
4
|
+
*/
|
|
5
|
+
export declare class TransactionWaitTimeoutError extends Error {
|
|
6
|
+
name: string;
|
|
7
|
+
txHash: string;
|
|
8
|
+
timeoutMs: number;
|
|
9
|
+
lastError: Error | null;
|
|
10
|
+
constructor(txHash: string, timeoutMs: number, lastError: Error | null);
|
|
11
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Thrown when waitForTransactionWithFallback times out.
|
|
7
|
+
* lastError distinguishes network issues (present) from slow tx (null).
|
|
8
|
+
*/
|
|
9
|
+
class TransactionWaitTimeoutError extends Error {
|
|
10
|
+
constructor(txHash, timeoutMs, lastError) {
|
|
11
|
+
const baseMessage = `Transaction ${txHash} not confirmed within ${timeoutMs}ms`;
|
|
12
|
+
const errorContext = lastError
|
|
13
|
+
? `. Last provider error: ${lastError.message}`
|
|
14
|
+
: ' (no provider errors, transaction may still be pending)';
|
|
15
|
+
super(baseMessage + errorContext);
|
|
16
|
+
this.name = 'TransactionWaitTimeoutError';
|
|
17
|
+
this.txHash = txHash;
|
|
18
|
+
this.timeoutMs = timeoutMs;
|
|
19
|
+
this.lastError = lastError;
|
|
20
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
exports.TransactionWaitTimeoutError = TransactionWaitTimeoutError;
|
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export * from './interfaces/fallback-provider';
|
|
|
7
7
|
export * from './interfaces/simple-fallback-provider-config';
|
|
8
8
|
export * from './interfaces/module.options';
|
|
9
9
|
export * from './interfaces/non-empty-array';
|
|
10
|
+
export * from './interfaces/wait-for-transaction';
|
|
10
11
|
export * from './ethers/fee-history';
|
|
11
12
|
export * from './ethers/block-tag';
|
|
12
13
|
export * from './error';
|
package/dist/index.js
CHANGED
|
@@ -12,6 +12,7 @@ var allProvidersFailed_error = require('./error/all-providers-failed.error.js');
|
|
|
12
12
|
var fetch_error = require('./error/fetch.error.js');
|
|
13
13
|
var noNewBlocksWhilePolling_error = require('./error/no-new-blocks-while-polling.error.js');
|
|
14
14
|
var requestTimeout_error = require('./error/request-timeout.error.js');
|
|
15
|
+
var transactionWaitTimeout_error = require('./error/transaction-wait-timeout.error.js');
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
|
|
@@ -39,3 +40,4 @@ exports.AllProvidersFailedError = allProvidersFailed_error.AllProvidersFailedErr
|
|
|
39
40
|
exports.FetchError = fetch_error.FetchError;
|
|
40
41
|
exports.NoNewBlocksWhilePollingError = noNewBlocksWhilePolling_error.NoNewBlocksWhilePollingError;
|
|
41
42
|
exports.RequestTimeoutError = requestTimeout_error.RequestTimeoutError;
|
|
43
|
+
exports.TransactionWaitTimeoutError = transactionWaitTimeout_error.TransactionWaitTimeoutError;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { TransactionReceipt } from '@ethersproject/abstract-provider';
|
|
2
|
+
export interface WaitForTransactionOptions {
|
|
3
|
+
/** Max wait time in ms. @default 60000 */
|
|
4
|
+
timeout?: number;
|
|
5
|
+
/** Polling interval in ms. @default 3000 */
|
|
6
|
+
pollInterval?: number;
|
|
7
|
+
/** Required confirmations. @default 1 */
|
|
8
|
+
confirmations?: number;
|
|
9
|
+
}
|
|
10
|
+
export interface WaitForTransactionResult {
|
|
11
|
+
receipt: TransactionReceipt;
|
|
12
|
+
pollCount: number;
|
|
13
|
+
elapsedMs: number;
|
|
14
|
+
}
|
|
@@ -10,6 +10,7 @@ import { BigNumber, BigNumberish } from '@ethersproject/bignumber';
|
|
|
10
10
|
import { Deferrable } from '@ethersproject/properties';
|
|
11
11
|
import { EventType, Listener } from '@ethersproject/abstract-provider';
|
|
12
12
|
import { FeeHistory } from '../ethers/fee-history';
|
|
13
|
+
import { WaitForTransactionOptions, WaitForTransactionResult } from '../interfaces/wait-for-transaction';
|
|
13
14
|
import { TraceConfig, TraceResult } from '../interfaces/debug-traces';
|
|
14
15
|
import { FallbackProviderEvents } from '../events';
|
|
15
16
|
/**
|
|
@@ -46,6 +47,7 @@ export declare class SimpleFallbackJsonRpcBatchProvider extends BaseProvider {
|
|
|
46
47
|
constructor(config: SimpleFallbackProviderConfig, logger: LoggerService);
|
|
47
48
|
static _formatter: Formatter | null;
|
|
48
49
|
static getFormatter(): Formatter;
|
|
50
|
+
protected formatLog(message: string, providerIndex?: number): string;
|
|
49
51
|
on(eventName: EventType, listener: Listener): this;
|
|
50
52
|
getFeeHistory(blockCount: number, newestBlock?: string | null | number, rewardPercentiles?: number[]): Promise<FeeHistory>;
|
|
51
53
|
getDebugTraceBlockByHash(blockHash: string, traceConfig: Partial<TraceConfig>): Promise<TraceResult[]>;
|
|
@@ -61,4 +63,16 @@ export declare class SimpleFallbackJsonRpcBatchProvider extends BaseProvider {
|
|
|
61
63
|
protected networksEqual(networkA: Network, networkB: Network): boolean;
|
|
62
64
|
get activeProviderIndex(): number;
|
|
63
65
|
get eventEmitter(): SimpleFallbackJsonRpcBatchProviderEventEmitter;
|
|
66
|
+
/**
|
|
67
|
+
* Waits for transaction confirmation with fallback provider support.
|
|
68
|
+
*
|
|
69
|
+
* Use instead of tx.wait() which hangs forever when providers fail:
|
|
70
|
+
* - node_modules/@ethersproject/providers/src.ts/base-provider.ts:1055 - polling calls getTransactionReceipt
|
|
71
|
+
* - node_modules/@ethersproject/providers/src.ts/base-provider.ts:1060 - .catch(e => emit("error", e)) swallows error
|
|
72
|
+
* - node_modules/@ethersproject/providers/src.ts/base-provider.ts:1313 - waits for event that never comes
|
|
73
|
+
* - node_modules/@ethersproject/providers/src.ts/base-provider.ts:1412 - timeout=0 by default, no reject
|
|
74
|
+
*
|
|
75
|
+
* This method polls via perform() with fallback and always returns/throws.
|
|
76
|
+
*/
|
|
77
|
+
waitForTransactionWithFallback(txHash: string, options?: WaitForTransactionOptions): Promise<WaitForTransactionResult>;
|
|
64
78
|
}
|
|
@@ -7,18 +7,21 @@ var providers = require('@ethersproject/providers');
|
|
|
7
7
|
var extendedJsonRpcBatchProvider = require('./extended-json-rpc-batch-provider.js');
|
|
8
8
|
var common = require('@nestjs/common');
|
|
9
9
|
var retrier = require('../common/retrier.js');
|
|
10
|
+
var sleep = require('../common/sleep.js');
|
|
10
11
|
var formatterWithEip1898 = require('../ethers/formatter-with-eip1898.js');
|
|
11
12
|
var networks = require('../common/networks.js');
|
|
12
13
|
var noNewBlocksWhilePolling_error = require('../error/no-new-blocks-while-polling.error.js');
|
|
13
14
|
var errors = require('../common/errors.js');
|
|
14
15
|
var allProvidersFailed_error = require('../error/all-providers-failed.error.js');
|
|
15
16
|
var requestTimeout_error = require('../error/request-timeout.error.js');
|
|
17
|
+
var transactionWaitTimeout_error = require('../error/transaction-wait-timeout.error.js');
|
|
16
18
|
var feeHistory = require('../ethers/fee-history.js');
|
|
17
19
|
var debugTraceBlockByHash = require('../ethers/debug-trace-block-by-hash.js');
|
|
18
20
|
var events = require('events');
|
|
19
21
|
|
|
20
22
|
exports.SimpleFallbackJsonRpcBatchProvider = class SimpleFallbackJsonRpcBatchProvider extends providers.BaseProvider {
|
|
21
23
|
constructor(config, logger) {
|
|
24
|
+
var _a;
|
|
22
25
|
super(config.network);
|
|
23
26
|
this.detectNetworkFirstRun = true;
|
|
24
27
|
this.resetTimer = null;
|
|
@@ -55,6 +58,22 @@ exports.SimpleFallbackJsonRpcBatchProvider = class SimpleFallbackJsonRpcBatchPro
|
|
|
55
58
|
};
|
|
56
59
|
});
|
|
57
60
|
this.activeFallbackProviderIndex = 0;
|
|
61
|
+
// Log initialization info
|
|
62
|
+
const configInfo = {
|
|
63
|
+
providers: conns.length,
|
|
64
|
+
network: this.config.network,
|
|
65
|
+
requestPolicy: this.config.requestPolicy,
|
|
66
|
+
maxRetries: this.config.maxRetries,
|
|
67
|
+
minBackoffMs: this.config.minBackoffMs,
|
|
68
|
+
maxBackoffMs: this.config.maxBackoffMs,
|
|
69
|
+
logRetries: this.config.logRetries,
|
|
70
|
+
resetIntervalMs: this.config.resetIntervalMs,
|
|
71
|
+
fetchMiddlewares: ((_a = this.config.fetchMiddlewares) === null || _a === void 0 ? void 0 : _a.length) || 0,
|
|
72
|
+
maxTimeWithoutNewBlocksMs: this.config.maxTimeWithoutNewBlocksMs,
|
|
73
|
+
requestTimeoutMs: this.config.requestTimeoutMs || 'disabled',
|
|
74
|
+
instanceLabel: this.config.instanceLabel || 'none',
|
|
75
|
+
};
|
|
76
|
+
this.logger.log(this.formatLog(`Initialized SimpleFallbackJsonRpcBatchProvider: ${JSON.stringify(configInfo)}`));
|
|
58
77
|
}
|
|
59
78
|
static getFormatter() {
|
|
60
79
|
if (this._formatter == null) {
|
|
@@ -62,6 +81,19 @@ exports.SimpleFallbackJsonRpcBatchProvider = class SimpleFallbackJsonRpcBatchPro
|
|
|
62
81
|
}
|
|
63
82
|
return this._formatter;
|
|
64
83
|
}
|
|
84
|
+
formatLog(message, providerIndex) {
|
|
85
|
+
const parts = [];
|
|
86
|
+
if (this.config.instanceLabel) {
|
|
87
|
+
parts.push(`[${this.config.instanceLabel}]`);
|
|
88
|
+
}
|
|
89
|
+
if (providerIndex !== undefined) {
|
|
90
|
+
parts.push(`[provider:${providerIndex}]`);
|
|
91
|
+
}
|
|
92
|
+
if (parts.length > 0) {
|
|
93
|
+
return `${parts.join('')} ${message}`;
|
|
94
|
+
}
|
|
95
|
+
return message;
|
|
96
|
+
}
|
|
65
97
|
on(eventName, listener) {
|
|
66
98
|
let dieTimer = null;
|
|
67
99
|
const startDieTimer = (latestObservedBlockNumber) => {
|
|
@@ -110,11 +142,13 @@ exports.SimpleFallbackJsonRpcBatchProvider = class SimpleFallbackJsonRpcBatchPro
|
|
|
110
142
|
}
|
|
111
143
|
switchToNextProvider() {
|
|
112
144
|
if (this.fallbackProviders.length === 1) {
|
|
113
|
-
this.logger.warn('Will not switch to next provider. No valid backup provider provided.');
|
|
145
|
+
this.logger.warn(this.formatLog('Will not switch to next provider. No valid backup provider provided.'));
|
|
114
146
|
return;
|
|
115
147
|
}
|
|
116
|
-
this.activeFallbackProviderIndex
|
|
117
|
-
this.
|
|
148
|
+
const oldIndex = this.activeFallbackProviderIndex;
|
|
149
|
+
this.activeFallbackProviderIndex =
|
|
150
|
+
(this.activeFallbackProviderIndex + 1) % this.fallbackProviders.length;
|
|
151
|
+
this.logger.log(this.formatLog(`Switched provider: [${oldIndex}] -> [${this.activeFallbackProviderIndex}] (total: ${this.fallbackProviders.length})`));
|
|
118
152
|
}
|
|
119
153
|
isNonRetryableError(error) {
|
|
120
154
|
return (!errors.isEthersServerError(error) &&
|
|
@@ -141,9 +175,11 @@ exports.SimpleFallbackJsonRpcBatchProvider = class SimpleFallbackJsonRpcBatchPro
|
|
|
141
175
|
try {
|
|
142
176
|
let performRetryAttempt = 0;
|
|
143
177
|
attempt++;
|
|
178
|
+
// Log which provider we're attempting to use
|
|
179
|
+
this.logger.log(this.formatLog(`Attempting request (attempt ${attempt}/${this.fallbackProviders.length})`, this.activeFallbackProviderIndex));
|
|
144
180
|
// awaiting is extremely important here
|
|
145
181
|
// without it, the error will not be caught in current try-catch scope
|
|
146
|
-
|
|
182
|
+
const result = await retry(() => {
|
|
147
183
|
const provider = this.provider;
|
|
148
184
|
const event = {
|
|
149
185
|
action: 'fallback-provider:request',
|
|
@@ -162,6 +198,9 @@ exports.SimpleFallbackJsonRpcBatchProvider = class SimpleFallbackJsonRpcBatchPro
|
|
|
162
198
|
}
|
|
163
199
|
return performPromise;
|
|
164
200
|
});
|
|
201
|
+
// Log successful request
|
|
202
|
+
this.logger.log(this.formatLog(`Request successful after ${performRetryAttempt} retry attempt(s)`, this.activeFallbackProviderIndex));
|
|
203
|
+
return result;
|
|
165
204
|
}
|
|
166
205
|
catch (e) {
|
|
167
206
|
this.lastError = e;
|
|
@@ -173,10 +212,22 @@ exports.SimpleFallbackJsonRpcBatchProvider = class SimpleFallbackJsonRpcBatchPro
|
|
|
173
212
|
error: e,
|
|
174
213
|
};
|
|
175
214
|
this._eventEmitter.emit('rpc', event);
|
|
215
|
+
// Log context (label + provider index) synchronously before error object
|
|
216
|
+
// to ensure proper ordering in async logging systems
|
|
217
|
+
this.logger.error(this.formatLog(`Non-retryable error occurred`, this.activeFallbackProviderIndex));
|
|
218
|
+
this.logger.error(e);
|
|
176
219
|
throw e;
|
|
177
220
|
}
|
|
178
|
-
|
|
179
|
-
|
|
221
|
+
// Log context (label + provider index) synchronously before error object
|
|
222
|
+
// to ensure proper ordering in async logging systems
|
|
223
|
+
if (e instanceof requestTimeout_error.RequestTimeoutError) {
|
|
224
|
+
this.logger.error(this.formatLog(`Request timeout after ${e.timeoutMs}ms. Will switch to next provider.`, this.activeFallbackProviderIndex));
|
|
225
|
+
this.logger.error(e);
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
this.logger.error(this.formatLog(`Error occurred. Will switch to next provider.`, this.activeFallbackProviderIndex));
|
|
229
|
+
this.logger.error(e);
|
|
230
|
+
}
|
|
180
231
|
// This check is needed to avoid multiple `switchToNextProvider` calls when doing one JSON-RPC batch.
|
|
181
232
|
// This can happen when multiple N calls to `perform` are batched in one JSON-RPC request and
|
|
182
233
|
// that request fails and throws `Error`. This `Error` is bubbled N times to corresponding `perform` calls.
|
|
@@ -234,7 +285,7 @@ exports.SimpleFallbackJsonRpcBatchProvider = class SimpleFallbackJsonRpcBatchPro
|
|
|
234
285
|
if (this.detectNetworkFirstRun) {
|
|
235
286
|
throw new Error(`Fallback provider [${index}] network is different to other provider's networks`);
|
|
236
287
|
}
|
|
237
|
-
this.logger.warn(`Fallback provider [${index}] network is different to other provider's networks`);
|
|
288
|
+
this.logger.warn(this.formatLog(`Fallback provider [${index}] network is different to other provider's networks`));
|
|
238
289
|
}
|
|
239
290
|
}
|
|
240
291
|
else {
|
|
@@ -261,7 +312,7 @@ exports.SimpleFallbackJsonRpcBatchProvider = class SimpleFallbackJsonRpcBatchPro
|
|
|
261
312
|
if (this.resetTimer) {
|
|
262
313
|
clearTimeout(this.resetTimer);
|
|
263
314
|
}
|
|
264
|
-
this.fallbackProviders.forEach((
|
|
315
|
+
this.fallbackProviders.forEach((_, index) => {
|
|
265
316
|
var _a;
|
|
266
317
|
if (!((_a = this.fallbackProviders[index].network) === null || _a === void 0 ? void 0 : _a.chainId)) {
|
|
267
318
|
this.fallbackProviders[index].unreachable = false;
|
|
@@ -278,6 +329,49 @@ exports.SimpleFallbackJsonRpcBatchProvider = class SimpleFallbackJsonRpcBatchPro
|
|
|
278
329
|
get eventEmitter() {
|
|
279
330
|
return this._eventEmitter;
|
|
280
331
|
}
|
|
332
|
+
/**
|
|
333
|
+
* Waits for transaction confirmation with fallback provider support.
|
|
334
|
+
*
|
|
335
|
+
* Use instead of tx.wait() which hangs forever when providers fail:
|
|
336
|
+
* - node_modules/@ethersproject/providers/src.ts/base-provider.ts:1055 - polling calls getTransactionReceipt
|
|
337
|
+
* - node_modules/@ethersproject/providers/src.ts/base-provider.ts:1060 - .catch(e => emit("error", e)) swallows error
|
|
338
|
+
* - node_modules/@ethersproject/providers/src.ts/base-provider.ts:1313 - waits for event that never comes
|
|
339
|
+
* - node_modules/@ethersproject/providers/src.ts/base-provider.ts:1412 - timeout=0 by default, no reject
|
|
340
|
+
*
|
|
341
|
+
* This method polls via perform() with fallback and always returns/throws.
|
|
342
|
+
*/
|
|
343
|
+
async waitForTransactionWithFallback(txHash, options = {}) {
|
|
344
|
+
const { timeout = 60000, pollInterval = 3000, confirmations = 1, } = options;
|
|
345
|
+
const startTime = Date.now();
|
|
346
|
+
let lastError = null;
|
|
347
|
+
let pollCount = 0;
|
|
348
|
+
this.logger.log(this.formatLog(`Starting waitForTransactionWithFallback for ${txHash} (timeout: ${timeout}ms, pollInterval: ${pollInterval}ms, confirmations: ${confirmations})`));
|
|
349
|
+
while (Date.now() - startTime < timeout) {
|
|
350
|
+
pollCount++;
|
|
351
|
+
try {
|
|
352
|
+
// Uses perform() which handles fallback switching automatically
|
|
353
|
+
const receipt = await this.getTransactionReceipt(txHash);
|
|
354
|
+
if (receipt && receipt.confirmations >= confirmations) {
|
|
355
|
+
const elapsedMs = Date.now() - startTime;
|
|
356
|
+
this.logger.log(this.formatLog(`Transaction ${txHash} confirmed after ${pollCount} polls (${elapsedMs}ms, ${receipt.confirmations} confirmations)`));
|
|
357
|
+
return { receipt, pollCount, elapsedMs };
|
|
358
|
+
}
|
|
359
|
+
lastError = null;
|
|
360
|
+
}
|
|
361
|
+
catch (error) {
|
|
362
|
+
// All providers failed - log and retry until timeout
|
|
363
|
+
lastError = error;
|
|
364
|
+
this.logger.warn(this.formatLog(`waitForTransactionWithFallback poll #${pollCount} failed for ${txHash}: ${error}`));
|
|
365
|
+
}
|
|
366
|
+
await sleep.sleep(pollInterval);
|
|
367
|
+
}
|
|
368
|
+
const elapsedMs = Date.now() - startTime;
|
|
369
|
+
const errorContext = lastError
|
|
370
|
+
? `All providers failed on last poll: ${lastError.message}`
|
|
371
|
+
: 'Transaction not found in any block';
|
|
372
|
+
this.logger.error(this.formatLog(`waitForTransactionWithFallback timeout for ${txHash} after ${pollCount} polls (${elapsedMs}ms). ${errorContext}`));
|
|
373
|
+
throw new transactionWaitTimeout_error.TransactionWaitTimeoutError(txHash, timeout, lastError);
|
|
374
|
+
}
|
|
281
375
|
};
|
|
282
376
|
exports.SimpleFallbackJsonRpcBatchProvider._formatter = null;
|
|
283
377
|
exports.SimpleFallbackJsonRpcBatchProvider = tslib.__decorate([
|