@lido-nestjs/execution 1.19.0 → 1.20.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;
@@ -14,6 +14,7 @@ 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');
17
18
  var lazyEventEmitter = require('../common/lazy-event-emitter.js');
18
19
 
19
20
  exports.ExtendedJsonRpcBatchProvider = class ExtendedJsonRpcBatchProvider extends providers.JsonRpcProvider {
@@ -77,7 +78,7 @@ exports.ExtendedJsonRpcBatchProvider = class ExtendedJsonRpcBatchProvider extend
77
78
  : '';
78
79
  const error = new fetch_error.FetchError(errMessage + detailedMessage);
79
80
  error.code = errorCodes.ErrorCode.UNEXPECTED_BATCH_RESULT;
80
- error.data = batchResult.error;
81
+ error.data = sanitizeError.sanitizeErrorData(batchResult.error);
81
82
  throw error;
82
83
  }
83
84
  const resultMap = batchResult.reduce((resultMap, payload) => {
@@ -101,7 +102,7 @@ exports.ExtendedJsonRpcBatchProvider = class ExtendedJsonRpcBatchProvider extend
101
102
  else if (payload.error) {
102
103
  const error = new fetch_error.FetchError(payload.error.message);
103
104
  error.code = payload.error.code;
104
- error.data = payload.error.data;
105
+ error.data = sanitizeError.sanitizeErrorData(payload.error.data);
105
106
  inflightRequest.reject(error);
106
107
  }
107
108
  else {
@@ -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');
@@ -211,18 +212,18 @@ exports.SimpleFallbackJsonRpcBatchProvider = class SimpleFallbackJsonRpcBatchPro
211
212
  // Log context (label + provider index) synchronously before error object
212
213
  // to ensure proper ordering in async logging systems
213
214
  this.logger.error(this.formatLog(`${method} non-retryable error occurred`, this.activeFallbackProviderIndex));
214
- this.logger.error(e);
215
+ this.logger.error(sanitizeError.sanitizeError(e));
215
216
  throw e;
216
217
  }
217
218
  // Log context (label + provider index) synchronously before error object
218
219
  // to ensure proper ordering in async logging systems
219
220
  if (e instanceof requestTimeout_error.RequestTimeoutError) {
220
221
  this.logger.error(this.formatLog(`${method} timeout after ${e.timeoutMs}ms. Will switch to next provider.`, this.activeFallbackProviderIndex));
221
- this.logger.error(e);
222
+ this.logger.error(sanitizeError.sanitizeError(e));
222
223
  }
223
224
  else {
224
225
  this.logger.error(this.formatLog(`${method} error occurred. Will switch to next provider.`, this.activeFallbackProviderIndex));
225
- this.logger.error(e);
226
+ this.logger.error(sanitizeError.sanitizeError(e));
226
227
  }
227
228
  // This check is needed to avoid multiple `switchToNextProvider` calls when doing one JSON-RPC batch.
228
229
  // This can happen when multiple N calls to `perform` are batched in one JSON-RPC request and
@@ -236,6 +237,7 @@ exports.SimpleFallbackJsonRpcBatchProvider = class SimpleFallbackJsonRpcBatchPro
236
237
  }
237
238
  }
238
239
  const allProvidersFailedError = new allProvidersFailed_error.AllProvidersFailedError(`All attempts to do ETH1 RPC request failed for ${method}`);
240
+ sanitizeError.sanitizeErrorInPlace(this.lastError);
239
241
  allProvidersFailedError.cause = this.lastError;
240
242
  this._eventEmitter.emitLazy('rpc', () => ({
241
243
  action: 'fallback-provider:request:failed:all',
@@ -389,7 +391,7 @@ exports.SimpleFallbackJsonRpcBatchProvider = class SimpleFallbackJsonRpcBatchPro
389
391
  catch (error) {
390
392
  // All providers failed - log and retry until timeout
391
393
  lastError = error;
392
- this.logger.warn(this.formatLog(`waitForTransactionWithFallback poll #${pollCount} failed for ${txHash}: ${error}`));
394
+ this.logger.warn(this.formatLog(`waitForTransactionWithFallback poll #${pollCount} failed for ${txHash}`), sanitizeError.sanitizeError(error));
393
395
  await sleep.sleep(pollInterval);
394
396
  }
395
397
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lido-nestjs/execution",
3
- "version": "1.19.0",
3
+ "version": "1.20.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "license": "MIT",