@lido-nestjs/execution 1.19.0 → 1.21.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/common/retrier.js +2 -1
- package/dist/common/sanitize-error.d.ts +20 -0
- package/dist/common/sanitize-error.js +113 -0
- package/dist/provider/extended-json-rpc-batch-provider.d.ts +2 -1
- package/dist/provider/extended-json-rpc-batch-provider.js +18 -5
- package/dist/provider/simple-fallback-json-rpc-batch-provider.d.ts +0 -1
- package/dist/provider/simple-fallback-json-rpc-batch-provider.js +8 -21
- package/package.json +1 -1
package/dist/common/retrier.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var sleep = require('./sleep.js');
|
|
4
|
+
var sanitizeError = require('./sanitize-error.js');
|
|
4
5
|
|
|
5
6
|
const retrier = (logger, defaultMaxRetryCount = 3, defaultMinBackoffMs = 1000, defaultMaxBackoffMs = 60000, defaultLogWarning = false, defaultErrorFilter) => {
|
|
6
7
|
return async (callback, maxRetryCount, minBackoffMs, maxBackoffMs, logWarning, errorFilter) => {
|
|
@@ -17,7 +18,7 @@ const retrier = (logger, defaultMaxRetryCount = 3, defaultMinBackoffMs = 1000, d
|
|
|
17
18
|
throw err;
|
|
18
19
|
}
|
|
19
20
|
if (logger && logWarning) {
|
|
20
|
-
logger.warn(err, `Retrying after (${minBackoffMs}ms). Remaining retries [${maxRetryCount}]`);
|
|
21
|
+
logger.warn(sanitizeError.sanitizeError(err), `Retrying after (${minBackoffMs}ms). Remaining retries [${maxRetryCount}]`);
|
|
21
22
|
}
|
|
22
23
|
if (maxRetryCount <= 1 || minBackoffMs >= maxBackoffMs) {
|
|
23
24
|
throw err;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns a safe, bounded string representation of an error for logging.
|
|
3
|
+
* inspect() handles everything internally: circular references, throwing
|
|
4
|
+
* getters, frozen objects, huge nested data. No manual field extraction needed.
|
|
5
|
+
*/
|
|
6
|
+
export declare function sanitizeError(error: unknown, maxLength?: number): string;
|
|
7
|
+
/**
|
|
8
|
+
* Truncates unknown data to a safe size.
|
|
9
|
+
* Use for FetchError.data assignment to prevent huge RPC payloads
|
|
10
|
+
* from being attached to error objects.
|
|
11
|
+
*/
|
|
12
|
+
export declare function sanitizeErrorData(data: unknown, maxLength?: number): unknown;
|
|
13
|
+
/**
|
|
14
|
+
* Mutates the error object in place, truncating heavy fields.
|
|
15
|
+
* Preserves the original error type (instanceof checks still work).
|
|
16
|
+
* Use for error objects that are thrown to consumers (e.g. AllProvidersFailedError.cause).
|
|
17
|
+
*/
|
|
18
|
+
export declare function sanitizeErrorInPlace(error: unknown, maxLength?: number,
|
|
19
|
+
/** @internal tracks visited objects to prevent circular-reference stack overflow */
|
|
20
|
+
_seen?: WeakSet<object>): void;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var util = require('util');
|
|
4
|
+
|
|
5
|
+
const DEFAULT_MAX_LENGTH = 1000;
|
|
6
|
+
const INSPECT_OPTIONS = (maxLength) => ({
|
|
7
|
+
depth: 4,
|
|
8
|
+
maxStringLength: maxLength,
|
|
9
|
+
maxArrayLength: 20,
|
|
10
|
+
compact: true,
|
|
11
|
+
breakLength: Infinity,
|
|
12
|
+
});
|
|
13
|
+
/**
|
|
14
|
+
* Uses util.inspect to produce a bounded string representation.
|
|
15
|
+
* Never calls JSON.stringify (avoids V8 "RangeError: Invalid string length"
|
|
16
|
+
* on objects >512 MB). inspect() handles circular references, throwing
|
|
17
|
+
* getters, frozen objects, and huge data internally.
|
|
18
|
+
*/
|
|
19
|
+
function truncate(value, maxLength) {
|
|
20
|
+
if (value === null || value === undefined)
|
|
21
|
+
return value;
|
|
22
|
+
if (typeof value === 'string') {
|
|
23
|
+
return value.length > maxLength
|
|
24
|
+
? value.slice(0, maxLength) +
|
|
25
|
+
`...[truncated, total length: ${value.length}]`
|
|
26
|
+
: value;
|
|
27
|
+
}
|
|
28
|
+
if (typeof value !== 'object')
|
|
29
|
+
return value;
|
|
30
|
+
try {
|
|
31
|
+
const inspected = util.inspect(value, INSPECT_OPTIONS(maxLength));
|
|
32
|
+
if (inspected.length <= maxLength)
|
|
33
|
+
return value;
|
|
34
|
+
return (inspected.slice(0, maxLength) +
|
|
35
|
+
`...[truncated, total length: ${inspected.length}]`);
|
|
36
|
+
}
|
|
37
|
+
catch (_a) {
|
|
38
|
+
return '[unserializable]';
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Returns a safe, bounded string representation of an error for logging.
|
|
43
|
+
* inspect() handles everything internally: circular references, throwing
|
|
44
|
+
* getters, frozen objects, huge nested data. No manual field extraction needed.
|
|
45
|
+
*/
|
|
46
|
+
function sanitizeError(error, maxLength = DEFAULT_MAX_LENGTH) {
|
|
47
|
+
try {
|
|
48
|
+
const result = util.inspect(error, INSPECT_OPTIONS(maxLength));
|
|
49
|
+
if (result.length <= maxLength)
|
|
50
|
+
return result;
|
|
51
|
+
return (result.slice(0, maxLength) +
|
|
52
|
+
`...[truncated, total length: ${result.length}]`);
|
|
53
|
+
}
|
|
54
|
+
catch (_a) {
|
|
55
|
+
return '[unserializable error]';
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Truncates unknown data to a safe size.
|
|
60
|
+
* Use for FetchError.data assignment to prevent huge RPC payloads
|
|
61
|
+
* from being attached to error objects.
|
|
62
|
+
*/
|
|
63
|
+
function sanitizeErrorData(data, maxLength = DEFAULT_MAX_LENGTH) {
|
|
64
|
+
return truncate(data, maxLength);
|
|
65
|
+
}
|
|
66
|
+
const HEAVY_FIELDS = ['data', 'body', 'requestBody', 'serverError'];
|
|
67
|
+
/**
|
|
68
|
+
* Mutates the error object in place, truncating heavy fields.
|
|
69
|
+
* Preserves the original error type (instanceof checks still work).
|
|
70
|
+
* Use for error objects that are thrown to consumers (e.g. AllProvidersFailedError.cause).
|
|
71
|
+
*/
|
|
72
|
+
function sanitizeErrorInPlace(error, maxLength = DEFAULT_MAX_LENGTH,
|
|
73
|
+
/** @internal tracks visited objects to prevent circular-reference stack overflow */
|
|
74
|
+
_seen) {
|
|
75
|
+
if (error === null || error === undefined || typeof error !== 'object') {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const seen = _seen !== null && _seen !== void 0 ? _seen : new WeakSet();
|
|
79
|
+
if (seen.has(error))
|
|
80
|
+
return;
|
|
81
|
+
seen.add(error);
|
|
82
|
+
const err = error;
|
|
83
|
+
for (const field of HEAVY_FIELDS) {
|
|
84
|
+
try {
|
|
85
|
+
if (err[field] !== undefined) {
|
|
86
|
+
err[field] = truncate(err[field], maxLength);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
catch (_a) {
|
|
90
|
+
// read-only property or throwing getter
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
if (err.error && typeof err.error === 'object') {
|
|
95
|
+
sanitizeErrorInPlace(err.error, maxLength, seen);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch (_b) {
|
|
99
|
+
// throwing getter on .error
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
if (err.cause && typeof err.cause === 'object') {
|
|
103
|
+
sanitizeErrorInPlace(err.cause, maxLength, seen);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
catch (_c) {
|
|
107
|
+
// throwing getter on .cause
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
exports.sanitizeError = sanitizeError;
|
|
112
|
+
exports.sanitizeErrorData = sanitizeErrorData;
|
|
113
|
+
exports.sanitizeErrorInPlace = sanitizeErrorInPlace;
|
|
@@ -75,7 +75,8 @@ export declare class ExtendedJsonRpcBatchProvider extends JsonRpcProvider {
|
|
|
75
75
|
protected _fetchMiddlewareService: MiddlewareService<Promise<any>>;
|
|
76
76
|
protected _domain: string;
|
|
77
77
|
protected _eventEmitter: ExtendedJsonRpcBatchProviderEventEmitter;
|
|
78
|
-
|
|
78
|
+
protected _requestTimeoutMs?: number;
|
|
79
|
+
constructor(url: ConnectionInfo | string, network?: Networkish, requestPolicy?: RequestPolicy, fetchMiddlewares?: MiddlewareCallback<Promise<any>>[], requestTimeoutMs?: number);
|
|
79
80
|
static _formatter: Formatter | null;
|
|
80
81
|
static getFormatter(): Formatter;
|
|
81
82
|
protected _batchAggregatorTick(): void;
|
|
@@ -14,14 +14,17 @@ var feeHistory = require('../ethers/fee-history.js');
|
|
|
14
14
|
var errorCodes = require('../error/codes/error-codes.js');
|
|
15
15
|
var debugTraceBlockByHash = require('../ethers/debug-trace-block-by-hash.js');
|
|
16
16
|
var networks = require('../common/networks.js');
|
|
17
|
+
var sanitizeError = require('../common/sanitize-error.js');
|
|
18
|
+
var requestTimeout_error = require('../error/request-timeout.error.js');
|
|
17
19
|
var lazyEventEmitter = require('../common/lazy-event-emitter.js');
|
|
18
20
|
|
|
19
21
|
exports.ExtendedJsonRpcBatchProvider = class ExtendedJsonRpcBatchProvider extends providers.JsonRpcProvider {
|
|
20
|
-
constructor(url, network, requestPolicy, fetchMiddlewares = []) {
|
|
22
|
+
constructor(url, network, requestPolicy, fetchMiddlewares = [], requestTimeoutMs) {
|
|
21
23
|
super(url, network);
|
|
22
24
|
this._batchAggregator = null;
|
|
23
25
|
this._queue = new queue.Queue();
|
|
24
26
|
this._tickCounter = 0;
|
|
27
|
+
this._requestTimeoutMs = requestTimeoutMs;
|
|
25
28
|
this._eventEmitter = new lazyEventEmitter.LazyEventEmitter();
|
|
26
29
|
this._domain = networks.getConnectionFQDN(url);
|
|
27
30
|
this._requestPolicy = requestPolicy !== null && requestPolicy !== void 0 ? requestPolicy : {
|
|
@@ -77,7 +80,7 @@ exports.ExtendedJsonRpcBatchProvider = class ExtendedJsonRpcBatchProvider extend
|
|
|
77
80
|
: '';
|
|
78
81
|
const error = new fetch_error.FetchError(errMessage + detailedMessage);
|
|
79
82
|
error.code = errorCodes.ErrorCode.UNEXPECTED_BATCH_RESULT;
|
|
80
|
-
error.data = batchResult.error;
|
|
83
|
+
error.data = sanitizeError.sanitizeErrorData(batchResult.error);
|
|
81
84
|
throw error;
|
|
82
85
|
}
|
|
83
86
|
const resultMap = batchResult.reduce((resultMap, payload) => {
|
|
@@ -101,7 +104,7 @@ exports.ExtendedJsonRpcBatchProvider = class ExtendedJsonRpcBatchProvider extend
|
|
|
101
104
|
else if (payload.error) {
|
|
102
105
|
const error = new fetch_error.FetchError(payload.error.message);
|
|
103
106
|
error.code = payload.error.code;
|
|
104
|
-
error.data = payload.error.data;
|
|
107
|
+
error.data = sanitizeError.sanitizeErrorData(payload.error.data);
|
|
105
108
|
inflightRequest.reject(error);
|
|
106
109
|
}
|
|
107
110
|
else {
|
|
@@ -188,12 +191,22 @@ exports.ExtendedJsonRpcBatchProvider = class ExtendedJsonRpcBatchProvider extend
|
|
|
188
191
|
};
|
|
189
192
|
const currentRequest = {
|
|
190
193
|
request,
|
|
191
|
-
reject: null,
|
|
192
194
|
resolve: null,
|
|
195
|
+
reject: null,
|
|
193
196
|
};
|
|
197
|
+
let timerId;
|
|
194
198
|
const promise = new Promise((resolve, reject) => {
|
|
195
199
|
currentRequest.resolve = resolve;
|
|
196
200
|
currentRequest.reject = reject;
|
|
201
|
+
if (this._requestTimeoutMs) {
|
|
202
|
+
const timeoutMs = this._requestTimeoutMs;
|
|
203
|
+
timerId = setTimeout(() => {
|
|
204
|
+
reject(new requestTimeout_error.RequestTimeoutError(`Request timeout after ${timeoutMs}ms`, timeoutMs));
|
|
205
|
+
}, timeoutMs);
|
|
206
|
+
}
|
|
207
|
+
}).finally(() => {
|
|
208
|
+
if (timerId)
|
|
209
|
+
clearTimeout(timerId);
|
|
197
210
|
});
|
|
198
211
|
this._queue.enqueue(currentRequest);
|
|
199
212
|
this._startBatchAggregator();
|
|
@@ -219,5 +232,5 @@ exports.ExtendedJsonRpcBatchProvider = class ExtendedJsonRpcBatchProvider extend
|
|
|
219
232
|
exports.ExtendedJsonRpcBatchProvider._formatter = null;
|
|
220
233
|
exports.ExtendedJsonRpcBatchProvider = tslib.__decorate([
|
|
221
234
|
common.Injectable(),
|
|
222
|
-
tslib.__metadata("design:paramtypes", [Object, Object, Object, Array])
|
|
235
|
+
tslib.__metadata("design:paramtypes", [Object, Object, Object, Array, Number])
|
|
223
236
|
], exports.ExtendedJsonRpcBatchProvider);
|
|
@@ -55,7 +55,6 @@ export declare class SimpleFallbackJsonRpcBatchProvider extends BaseProvider {
|
|
|
55
55
|
protected get provider(): FallbackProvider;
|
|
56
56
|
protected switchToNextProvider(): void;
|
|
57
57
|
protected isNonRetryableError(error: Error | unknown): boolean;
|
|
58
|
-
protected withTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T>;
|
|
59
58
|
perform(method: string, params: {
|
|
60
59
|
[name: string]: unknown;
|
|
61
60
|
}): Promise<unknown>;
|
|
@@ -12,6 +12,7 @@ var noNewBlocksWhilePolling_error = require('../error/no-new-blocks-while-pollin
|
|
|
12
12
|
var errors = require('../common/errors.js');
|
|
13
13
|
var allProvidersFailed_error = require('../error/all-providers-failed.error.js');
|
|
14
14
|
var requestTimeout_error = require('../error/request-timeout.error.js');
|
|
15
|
+
var sanitizeError = require('../common/sanitize-error.js');
|
|
15
16
|
var transactionWaitTimeout_error = require('../error/transaction-wait-timeout.error.js');
|
|
16
17
|
var feeHistory = require('../ethers/fee-history.js');
|
|
17
18
|
var debugTraceBlockByHash = require('../ethers/debug-trace-block-by-hash.js');
|
|
@@ -45,7 +46,7 @@ exports.SimpleFallbackJsonRpcBatchProvider = class SimpleFallbackJsonRpcBatchPro
|
|
|
45
46
|
}
|
|
46
47
|
this.fallbackProviders = conns.map((conn, index) => {
|
|
47
48
|
var _a;
|
|
48
|
-
const provider = new extendedJsonRpcBatchProvider.ExtendedJsonRpcBatchProvider(conn, undefined, config.requestPolicy, (_a = config.fetchMiddlewares) !== null && _a !== void 0 ? _a : []);
|
|
49
|
+
const provider = new extendedJsonRpcBatchProvider.ExtendedJsonRpcBatchProvider(conn, undefined, config.requestPolicy, (_a = config.fetchMiddlewares) !== null && _a !== void 0 ? _a : [], config.requestTimeoutMs);
|
|
49
50
|
return {
|
|
50
51
|
network: null,
|
|
51
52
|
provider,
|
|
@@ -151,16 +152,6 @@ exports.SimpleFallbackJsonRpcBatchProvider = class SimpleFallbackJsonRpcBatchPro
|
|
|
151
152
|
errors.isErrorHasCode(error) &&
|
|
152
153
|
errors.nonRetryableErrors.includes(error.code));
|
|
153
154
|
}
|
|
154
|
-
withTimeout(promise, timeoutMs) {
|
|
155
|
-
return Promise.race([
|
|
156
|
-
promise,
|
|
157
|
-
new Promise((_, reject) => {
|
|
158
|
-
setTimeout(() => {
|
|
159
|
-
reject(new requestTimeout_error.RequestTimeoutError(`Request timeout after ${timeoutMs}ms`, timeoutMs));
|
|
160
|
-
}, timeoutMs);
|
|
161
|
-
}),
|
|
162
|
-
]);
|
|
163
|
-
}
|
|
164
155
|
async perform(method, params) {
|
|
165
156
|
var _a, _b;
|
|
166
157
|
const retry = retrier.retrier(this.logger, this.config.maxRetries, this.config.minBackoffMs, this.config.maxBackoffMs, this.config.logRetries, (e) => this.isNonRetryableError(e));
|
|
@@ -188,12 +179,7 @@ exports.SimpleFallbackJsonRpcBatchProvider = class SimpleFallbackJsonRpcBatchPro
|
|
|
188
179
|
retryAttempt: performRetryAttempt,
|
|
189
180
|
}));
|
|
190
181
|
performRetryAttempt++;
|
|
191
|
-
|
|
192
|
-
// Apply timeout if configured
|
|
193
|
-
if (this.config.requestTimeoutMs) {
|
|
194
|
-
return this.withTimeout(performPromise, this.config.requestTimeoutMs);
|
|
195
|
-
}
|
|
196
|
-
return performPromise;
|
|
182
|
+
return provider.provider.perform(method, params);
|
|
197
183
|
});
|
|
198
184
|
// Log successful request
|
|
199
185
|
this.logger.log(this.formatLog(`${method} successful after ${performRetryAttempt} retry attempt(s)`, this.activeFallbackProviderIndex));
|
|
@@ -211,18 +197,18 @@ exports.SimpleFallbackJsonRpcBatchProvider = class SimpleFallbackJsonRpcBatchPro
|
|
|
211
197
|
// Log context (label + provider index) synchronously before error object
|
|
212
198
|
// to ensure proper ordering in async logging systems
|
|
213
199
|
this.logger.error(this.formatLog(`${method} non-retryable error occurred`, this.activeFallbackProviderIndex));
|
|
214
|
-
this.logger.error(e);
|
|
200
|
+
this.logger.error(sanitizeError.sanitizeError(e));
|
|
215
201
|
throw e;
|
|
216
202
|
}
|
|
217
203
|
// Log context (label + provider index) synchronously before error object
|
|
218
204
|
// to ensure proper ordering in async logging systems
|
|
219
205
|
if (e instanceof requestTimeout_error.RequestTimeoutError) {
|
|
220
206
|
this.logger.error(this.formatLog(`${method} timeout after ${e.timeoutMs}ms. Will switch to next provider.`, this.activeFallbackProviderIndex));
|
|
221
|
-
this.logger.error(e);
|
|
207
|
+
this.logger.error(sanitizeError.sanitizeError(e));
|
|
222
208
|
}
|
|
223
209
|
else {
|
|
224
210
|
this.logger.error(this.formatLog(`${method} error occurred. Will switch to next provider.`, this.activeFallbackProviderIndex));
|
|
225
|
-
this.logger.error(e);
|
|
211
|
+
this.logger.error(sanitizeError.sanitizeError(e));
|
|
226
212
|
}
|
|
227
213
|
// This check is needed to avoid multiple `switchToNextProvider` calls when doing one JSON-RPC batch.
|
|
228
214
|
// This can happen when multiple N calls to `perform` are batched in one JSON-RPC request and
|
|
@@ -236,6 +222,7 @@ exports.SimpleFallbackJsonRpcBatchProvider = class SimpleFallbackJsonRpcBatchPro
|
|
|
236
222
|
}
|
|
237
223
|
}
|
|
238
224
|
const allProvidersFailedError = new allProvidersFailed_error.AllProvidersFailedError(`All attempts to do ETH1 RPC request failed for ${method}`);
|
|
225
|
+
sanitizeError.sanitizeErrorInPlace(this.lastError);
|
|
239
226
|
allProvidersFailedError.cause = this.lastError;
|
|
240
227
|
this._eventEmitter.emitLazy('rpc', () => ({
|
|
241
228
|
action: 'fallback-provider:request:failed:all',
|
|
@@ -389,7 +376,7 @@ exports.SimpleFallbackJsonRpcBatchProvider = class SimpleFallbackJsonRpcBatchPro
|
|
|
389
376
|
catch (error) {
|
|
390
377
|
// All providers failed - log and retry until timeout
|
|
391
378
|
lastError = error;
|
|
392
|
-
this.logger.warn(this.formatLog(`waitForTransactionWithFallback poll #${pollCount} failed for ${txHash}
|
|
379
|
+
this.logger.warn(this.formatLog(`waitForTransactionWithFallback poll #${pollCount} failed for ${txHash}`), sanitizeError.sanitizeError(error));
|
|
393
380
|
await sleep.sleep(pollInterval);
|
|
394
381
|
}
|
|
395
382
|
}
|