@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.
@@ -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
- constructor(url: ConnectionInfo | string, network?: Networkish, requestPolicy?: RequestPolicy, fetchMiddlewares?: MiddlewareCallback<Promise<any>>[]);
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
- const performPromise = provider.provider.perform(method, params);
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}: ${error}`));
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lido-nestjs/execution",
3
- "version": "1.19.0",
3
+ "version": "1.21.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "license": "MIT",